feat(i18n): use settings to set and get language

This commit is contained in:
2026-08-07 00:33:17 +02:00
parent d5f6108f47
commit b571a68e3b
4 changed files with 38 additions and 55 deletions
+4
View File
@@ -18,8 +18,10 @@ limitations under the License.
import Navbar from './features/nav/components/Navbar.vue';
import Footer from './features/footer/components/Footer.vue';
import { LANGUAGE_PATH } from './config/featureSettings.js';
import { updatePageTitle } from './router';
import { useSettings } from './features/settings/composables/useSettings.js';
import { setLocale } from './i18n';
import { computed, watch, watchEffect } from 'vue';
import { useRoute } from 'vue-router';
@@ -51,6 +53,8 @@ watch(colorScheme, (newColorScheme) => {
updateColorScheme(colorScheme.value);
watchEffect(() => updatePageTitle(route));
watch(() => getSetting(LANGUAGE_PATH), setLocale);
</script>
<template>
@@ -17,20 +17,20 @@ limitations under the License.
<script setup>
import Icon from '@/features/icons/components/Icon.vue';
import { LANGUAGE_PATH } from '@/config/featureSettings';
import { useSettings } from '@/features/settings/composables/useSettings';
import { ref } from 'vue';
import { useI18n } from 'vue-i18n';
import { loadLanguage, LANGUAGES_RTL, SUPPORTED_LANGUAGES } from '@/i18n';
import { SUPPORTED_LANGUAGES } from '@/i18n';
const { t, locale } = useI18n();
const { setSetting } = useSettings();
const isOpen = ref(false);
const languageDropdown = ref(null);
async function selectLanguage(code) {
await loadLanguage(code);
localStorage.setItem('locale', code);
document.documentElement.lang = code;
document.documentElement.dir = LANGUAGES_RTL.includes(code) ? 'rtl' : 'ltr';
const selectLanguage = function selectLanguage (code) {
setSetting(LANGUAGE_PATH, code);
close();
};
@@ -14,41 +14,19 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
import { expect, test, describe, vi, beforeEach } from 'vitest';
import { mount } from '@vue/test-utils';
import { LANGUAGE_PATH } from '@/config/featureSettings.js';
import { useSettings } from '@/features/settings/composables/useSettings.js';
import { mountComponent } from '@/test-utils/mountComponent.js';
import { expect, test, describe } from 'vitest';
import LanguageSwitchButton from '../LanguageSwitchButton.vue';
import { loadLanguage } from '@/i18n';
vi.mock('@/i18n', () => ({
loadLanguage: vi.fn(() => Promise.resolve()),
LANGUAGES_RTL: ['ar', 'he'],
SUPPORTED_LANGUAGES: ['en', 'de', 'ar']
}));
vi.mock('vue-i18n', () => ({
useI18n: () => ({
t: (key) => key,
locale: { value: 'de' }
})
}));
vi.mock('@/features/icons/components/Icon.vue', () => ({
default: {
name: 'Icon',
template: '<span>Icon</span>'
}
}));
const getWrapper = function getWrapper () {
return mountComponent(LanguageSwitchButton);
};
describe('LanguageSwitchButton', () => {
beforeEach(() => {
vi.clearAllMocks();
localStorage.clear();
document.documentElement.lang = '';
document.documentElement.dir = '';
});
test('renders correctly with initial state closed', () => {
const wrapper = mount(LanguageSwitchButton);
const wrapper = getWrapper();
expect(wrapper.find('.language-button').exists()).toBe(true);
expect(wrapper.find('.language-dropdown').exists()).toBe(false);
@@ -56,7 +34,7 @@ describe('LanguageSwitchButton', () => {
});
test('opens the dropdown when language button is clicked', async () => {
const wrapper = mount(LanguageSwitchButton);
const wrapper = getWrapper();
const button = wrapper.find('.language-button');
await button.trigger('click');
@@ -65,33 +43,28 @@ describe('LanguageSwitchButton', () => {
expect(button.attributes('aria-expanded')).toBe('true');
});
const languageTestCases = [
{ code: 'en', expectedDir: 'ltr' },
{ code: 'de', expectedDir: 'ltr' },
{ code: 'ar', expectedDir: 'rtl' }
];
test('sets the language when clicking it', async () => {
const { getSetting } = useSettings();
test.for(languageTestCases)('selectLanguage($code) sets localStorage, html attributes and changes layout direction to $expectedDir', async ({ code, expectedDir }) => {
const wrapper = mount(LanguageSwitchButton);
const wrapper = getWrapper();
await wrapper.find('.language-button').trigger('click');
const options = wrapper.findAll('.language-dropdown li');
const optionToClick = options.find(opt => opt.text().includes(code));
await optionToClick.trigger('click');
expect(loadLanguage).toHaveBeenCalledWith(code);
expect(localStorage.getItem('locale')).toBe(code);
expect(document.documentElement.lang).toBe(code);
expect(document.documentElement.dir).toBe(expectedDir);
const enOption = options.find(opt => opt.text().includes('en'));
await enOption.trigger('click');
expect(getSetting(LANGUAGE_PATH)).toBe('en');
expect(wrapper.find('.language-dropdown').exists()).toBe(false);
const deOption = options.find(opt => opt.text().includes('de'));
await deOption.trigger('click');
expect(getSetting(LANGUAGE_PATH)).toBe('de');
expect(wrapper.find('.language-dropdown').exists()).toBe(false);
});
test('closes the dropdown when clicking outside the component', async () => {
const wrapper = mount(LanguageSwitchButton, {
attachTo: document.body
});
const wrapper = getWrapper();
await wrapper.find('.language-button').trigger('click');
expect(wrapper.find('.language-dropdown').exists()).toBe(true);
+7 -1
View File
@@ -57,4 +57,10 @@ export async function loadLanguage (locale) {
i18n.global.locale.value = locale;
loadedLanguages.add(locale);
};
};
export const setLocale = async function setLocale (locale) {
await loadLanguage(locale);
document.documentElement.lang = locale;
document.documentElement.dir = LANGUAGES_RTL.includes(locale) ? 'rtl' : 'ltr';
};