Skip to content

Conversation

@mindmonk
Copy link
Contributor

@mindmonk mindmonk commented Dec 5, 2025

This PR improves how the application detects and handles browser languages. The locale normalization logic in i18n/index.ts has been expanded so that short or inconsistent browser locale values (like de from Firefox) are now mapped reliably to full locale codes such as de-DE. A new detectBrowserLocale() helper ensures that the initial i18n locale is always a valid enum value.

In UserProfile.vue, the language selection has been cleaned up by introducing a dedicated languagePreference model and by using the centralized locale detection instead of raw browser values. The browser-language option is now handled explicitly via "browser", and the correct preference is restored when user data is loaded.

Overall, this fixes incorrect language behavior across different browsers and makes the language selection more consistent and predictable.

@mindmonk mindmonk requested a review from SailReal December 5, 2025 06:52
@coderabbitai
Copy link

coderabbitai bot commented Dec 5, 2025

Walkthrough

This PR changes language preference handling: UserProfile.vue introduces a LanguagePreference type (Locale | 'browser'), replaces the Listbox v-model locale with languagePreference, adds detectBrowserLocale import and uses it to initialize browserLocale, sets languagePreference after fetching user data, and updates saveLanguage to assign and persist the chosen locale (explicit locale or browser). i18n/index.ts expands mapToLocale (normalization, prefix mapping, fallbacks), adds getBrowserLocale and exported detectBrowserLocale, and initializes i18n with detectBrowserLocale() plus additional i18n options.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

  • Review mapToLocale normalization branches and mapping table for correct locale resolution and edge-case handling.
  • Verify getBrowserLocale / detectBrowserLocale fallbacks (navigator.languages[0] → navigator.language → EN_US).
  • Inspect UserProfile.vue lifecycle: languagePreference initialization after fetch, Listbox binding changes, and saveLanguage persistence logic.
  • Check new type LanguagePreference, imports, and reactive state consistency.

Files to pay extra attention to:

  • frontend/src/i18n/index.ts
  • frontend/src/components/UserProfile.vue

Possibly related PRs

Suggested Reviewers

  • SailReal
  • overheadhunter

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (2 passed)
Check name Status Explanation
Description check ✅ Passed The description directly relates to the changeset, explaining the locale normalization improvements in i18n/index.ts and the language preference refactoring in UserProfile.vue.
Title check ✅ Passed The title accurately captures the main changes: improving browser locale detection and unifying language selection behavior across the codebase.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feature/fix-language-settings-behaivour

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

🧹 Nitpick comments (4)
frontend/src/i18n/index.ts (2)

105-133: Locale normalization logic looks solid for supported languages

mapToLocale cleanly handles empty input, normalizes underscore vs. dash, accepts exact enum values, and falls back to a base‑language mapping, so short browser values like "de" or "fr" will reliably resolve to one of the defined Locale entries. I don’t see correctness issues here for the current set of supported locales.

If you ever need to extend this, a small optional improvement would be to precompute a Record<string, Locale> map (including lower‑cased variants) to avoid repeated Object.values scans and make additions less error‑prone than growing the switch, but that’s purely a maintainability tweak.


135-143: detectBrowserLocale() is SSR‑safe and prefers user language order

The guard around navigator plus the navigator.languages[0] || navigator.language || EN_US fallback chain is a good, SSR‑safe way to derive the browser locale and respects the user’s preferred languages array when available.

If you want to slightly simplify readability, you could factor the typeof navigator !== 'undefined' check once and then use optional chaining (navigator?.languages?.[0] ?? navigator?.language ?? Locale.EN_US), but that’s optional and doesn’t change behavior.

frontend/src/components/UserProfile.vue (2)

79-91: Minor typing/structure improvement for browserLocale

detectBrowserLocale() already guarantees a valid Locale, so browserLocale doesn’t need to be a ref<string>—it can be a simple Locale value. That tightens typing (no accidental non‑Locale strings) and removes an unnecessary ref:

-import { Locale, detectBrowserLocale } from '../i18n';
+import { Locale, detectBrowserLocale } from '../i18n';

-type LanguagePreference = Locale | 'browser';
-
-const languagePreference = ref<LanguagePreference>('browser');
+type LanguagePreference = Locale | 'browser';
+
+const languagePreference = ref<LanguagePreference>('browser');

And for browserLocale and its usage:

-const browserLocale = ref<string>(detectBrowserLocale());
+const browserLocale: Locale = detectBrowserLocale();
...
-  locale.value = selectedLocale ?? browserLocale.value;
+  locale.value = selectedLocale ?? browserLocale;

Functionally this is equivalent but gives you stronger type safety and simpler code.


96-107: Consider also applying stored user language to the active i18n locale

In onMounted, when me.value?.language is present you set languagePreference but leave locale.value unchanged. If nothing else in the app overwrites locale.value from the stored user language, the effective UI language may remain at the browser‑detected locale despite a saved preference, until the user manually changes it in this listbox.

If this component is intended to enforce the saved preference, you could (optionally) also sync the global locale here, and optionally normalize legacy values via mapToLocale:

-import { Locale, detectBrowserLocale } from '../i18n';
+import { Locale, detectBrowserLocale, mapToLocale } from '../i18n';
...
-const browserLocale = ref<string>(detectBrowserLocale());
+const browserLocale: Locale = detectBrowserLocale();
...
 onMounted(async () => {
   const cfg = config.get();
   keycloakUserAccountURL.value = `${cfg.keycloakUrl}/realms/${cfg.keycloakRealm}/account`;
   await fetchData();
-  if (me.value?.language) {
-    languagePreference.value = me.value.language as Locale;
-  } else {
-    languagePreference.value = 'browser';
-  }
+  if (me.value?.language) {
+    const persistedLocale = mapToLocale(me.value.language);
+    languagePreference.value = persistedLocale;
+    locale.value = persistedLocale;
+  } else {
+    languagePreference.value = 'browser';
+    locale.value = browserLocale;
+  }
 });

This would ensure the stored preference is both reflected in the dropdown and actually applied to the app’s language. If some other global bootstrap code already handles this, then the current code is fine, but it’s worth double‑checking to avoid subtle preference inconsistencies.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between aae529c and ba4a2f8.

📒 Files selected for processing (2)
  • frontend/src/components/UserProfile.vue (4 hunks)
  • frontend/src/i18n/index.ts (1 hunks)
🧰 Additional context used
🧠 Learnings (1)
📓 Common learnings
Learnt from: overheadhunter
Repo: cryptomator/hub PR: 282
File: frontend/src/common/userdata.ts:82-88
Timestamp: 2024-07-27T13:14:40.963Z
Learning: The error handling suggestion for `createBrowserKeys` in `frontend/src/common/userdata.ts` was discussed and resolved in PR #282, and should not be repeated in future reviews.
🔇 Additional comments (3)
frontend/src/i18n/index.ts (1)

145-154: Using detectBrowserLocale() for initial i18n locale is consistent with new mapping

Initializing createI18n with locale: detectBrowserLocale() nicely centralizes locale detection and ensures the same normalization logic is reused everywhere. The additional options (globalInjection, missingWarn: false, fallbackWarn: false, legacy: false) are compatible with composition‑API usage via useI18n({ useScope: 'global' }) elsewhere.

Just make sure any code that restores a user‑saved language (e.g., from userdata.me) still overrides this initial browser‑derived locale as intended, so existing preferences aren’t silently ignored.

frontend/src/components/UserProfile.vue (2)

25-47: LanguagePreference binding and explicit 'browser' sentinel are coherent

Switching the listbox v-model to languagePreference and using a stable value="browser" for the browser‑language option makes the model much clearer than encoding the browser locale string itself. The click handlers (saveLanguage(undefined) for browser, saveLanguage(availableLocale) for concrete locales) line up with this model and correctly distinguish between “follow browser” and “force specific locale”.

This all looks consistent with the new LanguagePreference type and should yield predictable behavior in the UI.


120-128: saveLanguage flow correctly distinguishes stored vs. browser‑derived language

The saveLanguage implementation cleanly applies the chosen locale to locale.value and persists only explicit selections to the backend (clearing updatedUser.language when the “browser” option is chosen via selectedLocale being undefined). Given the top‑level v-if="me == null" guard in the template, users can’t trigger this before me is loaded, so the updatedUser !== undefined check is an adequate safety net.

No issues from a correctness perspective; this aligns well with the new LanguagePreference model.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

🧹 Nitpick comments (1)
frontend/src/i18n/index.ts (1)

105-105: Minor typo: locallocale

The parameter name appears to be a typo.

-export const mapToLocale = (local: string): Locale => {
+export const mapToLocale = (locale: string): Locale => {

Then update references inside the function accordingly.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 5175d19 and ebff23c.

📒 Files selected for processing (1)
  • frontend/src/i18n/index.ts (1 hunks)
🧰 Additional context used
🧠 Learnings (1)
📓 Common learnings
Learnt from: overheadhunter
Repo: cryptomator/hub PR: 282
File: frontend/src/common/userdata.ts:82-88
Timestamp: 2024-07-27T13:14:40.963Z
Learning: The error handling suggestion for `createBrowserKeys` in `frontend/src/common/userdata.ts` was discussed and resolved in PR #282, and should not be repeated in future reviews.
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: Run Tests
🔇 Additional comments (3)
frontend/src/i18n/index.ts (3)

135-139: LGTM!

Clean helper that centralizes browser locale detection. The separation between fetching the raw browser value and mapping it to a supported Locale is well-structured.


141-151: LGTM!

Robust fallback chain for browser locale detection. Correctly handles SSR environments where navigator is undefined, prefers the more reliable navigator.languages[0], and provides sensible defaults.


153-163: LGTM!

Good integration of detectBrowserLocale() for initial locale. The added options are appropriate for vue-i18n v9+ with Vue 3 Composition API.

Note that missingWarn: false and fallbackWarn: false will suppress console warnings for missing translations. This reduces noise but could hide issues during development. If debugging translation coverage becomes necessary, consider enabling these in development builds only.

@mindmonk mindmonk changed the title Improve browser locale detection and unify language selection behavior Feature: Improve browser locale detection and unify language selection behavior Dec 5, 2025
@overheadhunter overheadhunter added this to the 1.5.0 milestone Dec 8, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants