Skip to content

Commit 6899faa

Browse files
authored
Merge pull request #371 from cryptomator/feature/fix-language-settings-behaivour
Feature: Improve browser locale detection and unify language selection behavior
2 parents 4dcb8c3 + 0e74b52 commit 6899faa

File tree

2 files changed

+61
-9
lines changed

2 files changed

+61
-9
lines changed

frontend/src/components/UserProfile.vue

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@
2222
<ArrowTopRightOnSquareIcon class="-ml-1 mr-2 h-5 w-5" aria-hidden="true" />
2323
{{ t('userProfile.actions.manageAccount') }}
2424
</button>
25-
<Listbox v-model="locale" as="div">
25+
<Listbox v-model="languagePreference" as="div">
2626
<div class="relative">
2727
<ListboxButton class="relative w-full inline-flex items-center justify-center px-4 py-2 border border-gray-300 shadow-xs text-sm font-medium rounded-md text-gray-700 bg-white hover:bg-gray-50 focus:outline-hidden focus:ring-2 focus:ring-offset-2 focus:ring-primary">
2828
<LanguageIcon class="-ml-1 mr-2 h-5 w-5" aria-hidden="true" />
@@ -33,7 +33,7 @@
3333
</ListboxButton>
3434
<transition leave-active-class="transition ease-in duration-100" leave-from-class="opacity-100" leave-to-class="opacity-0">
3535
<ListboxOptions class="absolute z-10 mt-1 max-h-60 w-full overflow-auto rounded-md bg-white py-1 shadow-lg ring-1 ring-black/5 focus:outline-hidden text-sm">
36-
<ListboxOption v-slot="{ active, selected }" :value="browserLocale" class="relative cursor-default select-none py-2 pl-3 pr-9 ui-not-active:text-gray-900 ui-active:text-white ui-active:bg-primary" @click="saveLanguage(undefined)">
36+
<ListboxOption v-slot="{ active, selected }" value="browser" class="relative cursor-default select-none py-2 pl-3 pr-9 ui-not-active:text-gray-900 ui-active:text-white ui-active:bg-primary" @click="saveLanguage(undefined)">
3737
<span :class="[selected || active ? 'font-semibold' : 'font-normal', 'block truncate']">{{ t('userProfile.actions.changeLanguage.entry.browser') }}</span>
3838
<span v-if="selected" :class="[active ? 'text-white' : 'text-primary', 'absolute inset-y-0 right-0 flex items-center pr-4']">
3939
<CheckIcon class="h-5 w-5" aria-hidden="true" />
@@ -76,7 +76,7 @@ import backend, { UserDto, VersionDto } from '../common/backend';
7676
7777
import config from '../common/config';
7878
import userdata from '../common/userdata';
79-
import { Locale } from '../i18n';
79+
import { Locale, detectBrowserLocale } from '../i18n';
8080
import DeviceList from './DeviceList.vue';
8181
import FetchError from './FetchError.vue';
8282
import LegacyDeviceList from './LegacyDeviceList.vue';
@@ -85,16 +85,25 @@ import UserkeyFingerprint from './UserkeyFingerprint.vue';
8585
8686
const { locale, t } = useI18n({ useScope: 'global' });
8787
88+
type LanguagePreference = Locale | 'browser';
89+
90+
const languagePreference = ref<LanguagePreference>('browser');
91+
8892
const me = ref<UserDto>();
8993
const keycloakUserAccountURL = ref<string>();
9094
const version = ref<VersionDto>();
9195
const onFetchError = ref<Error>();
92-
const browserLocale = ref<string>(navigator.language);
96+
const browserLocale = ref<string>(detectBrowserLocale());
9397
9498
onMounted(async () => {
9599
const cfg = config.get();
96100
keycloakUserAccountURL.value = `${cfg.keycloakUrl}/realms/${cfg.keycloakRealm}/account`;
97101
await fetchData();
102+
if (me.value?.language) {
103+
languagePreference.value = me.value.language as Locale;
104+
} else {
105+
languagePreference.value = 'browser';
106+
}
98107
});
99108
100109
async function fetchData() {

frontend/src/i18n/index.ts

Lines changed: 48 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -100,13 +100,56 @@ export const numberFormats: I18nOptions['numberFormats'] = {
100100
[Locale.ZH_TW]: defaultNumberFormat
101101
};
102102

103-
export const mapToLocale = (local: string): Locale =>
104-
(Object.values(Locale) as string[]).includes(local)
105-
? (local as Locale)
106-
: Locale.EN_US;
103+
export const mapToLocale = (locale: string): Locale => {
104+
if (!locale) {
105+
return Locale.EN_US;
106+
}
107+
108+
const normalized = locale.replace('_', '-');
109+
110+
if ((Object.values(Locale) as string[]).includes(normalized)) {
111+
return normalized as Locale;
112+
}
113+
114+
const base = normalized.split('-')[0];
115+
116+
switch (base) {
117+
case 'de': return Locale.DE_DE;
118+
case 'en': return Locale.EN_US;
119+
case 'fr': return Locale.FR_FR;
120+
case 'it': return Locale.IT_IT;
121+
case 'ko': return Locale.KO_KR;
122+
case 'lv': return Locale.LV_LV;
123+
case 'nl': return Locale.NL_NL;
124+
case 'pt': return Locale.PT_PT;
125+
case 'ru': return Locale.RU_RU;
126+
case 'tr': return Locale.TR_TR;
127+
case 'uk': return Locale.UK_UA;
128+
case 'zh': return Locale.ZH_TW;
129+
default: return Locale.EN_US;
130+
}
131+
};
132+
133+
export const detectBrowserLocale = (): Locale => {
134+
const raw = getBrowserLocale();
135+
136+
return mapToLocale(raw);
137+
};
138+
139+
function getBrowserLocale(): string {
140+
if (typeof navigator === 'undefined') {
141+
return Locale.EN_US;
142+
} else if (navigator.languages && navigator.languages.length > 0) {
143+
return navigator.languages[0];
144+
} else if (navigator.language) {
145+
return navigator.language;
146+
} else {
147+
return Locale.EN_US;
148+
}
149+
}
107150

108151
const i18n = createI18n({
109-
locale: navigator.language,
152+
locale: detectBrowserLocale(),
110153
fallbackLocale: Locale.EN_US,
111154
messages,
112155
datetimeFormats,

0 commit comments

Comments
 (0)