Skip to content

feat(i18n): auto-detect OS/browser locale on first launch#223

Open
tyrael-z wants to merge 6 commits into
flick9000:mainfrom
tyrael-z:feature/locale-auto-detect
Open

feat(i18n): auto-detect OS/browser locale on first launch#223
tyrael-z wants to merge 6 commits into
flick9000:mainfrom
tyrael-z:feature/locale-auto-detect

Conversation

@tyrael-z

Copy link
Copy Markdown
Contributor

PR Type

What kind of change does this PR introduce?

  • Feature
  • Bugfix
  • Refactoring
  • Style
  • Localization
  • Other

Description

Automatically detect the user's OS/browser locale on first launch and redirect to the corresponding localized page.

  • Desktop (Tauri): reads the OS locale via the os:allow-locale plugin
  • Website: detects browser language preference via navigator.languages
  • Matching strategy: exact match → prefix match (e.g. de-ATde)
  • Priority: saved user preference > URL path > OS/browser locale > English default
  • Both app/ and website/ versions updated in sync

- Desktop (Tauri): detect OS locale via os:allow-locale plugin
- Website: detect browser locale via navigator.languages
- Fallback chain: saved preference > URL path > OS/browser locale > English
- Both app and website versions updated in sync
gemini-code-assist[bot]

This comment was marked as outdated.

tyrael.z added 2 commits June 25, 2026 01:51
…rted

- osLocale() returns a Promise in Tauri v2 — must await it
- Validate localStorage locale against current supported set;
  clear it if the locale has been removed in an update
@tyrael-z tyrael-z force-pushed the feature/locale-auto-detect branch from a1a2e3a to 741fc0b Compare June 24, 2026 17:57
@tyrael-z

Copy link
Copy Markdown
Contributor Author

/gemini review

gemini-code-assist[bot]

This comment was marked as outdated.

… empty set

When the DOM is not yet parsed or the language switcher is not present on
the current page, getSupportedLocales() returns an empty Set. Running
validation against an empty set could:
- delete a valid saved locale preference
- redirect away from valid non-locale paths (e.g. /about)
- prevent staying on a valid locale path (e.g. /de/)

Fix: only validate and take destructive actions (clear, redirect) when
supported.size > 0. When empty, trust the saved preference or URL path.
@tyrael-z

Copy link
Copy Markdown
Contributor Author

/gemini review

gemini-code-assist[bot]

This comment was marked as outdated.

@flick9000

flick9000 commented Jun 24, 2026

Copy link
Copy Markdown
Owner

Hi @tyrael-z.

This completely breaks the application (loadLocale()).
Since window.location.href is basically a refresh or redirect of the page, this function runs in a costant loop breaking the app.
Just keep my original code, state that if the window.location.href doesn't ALREADY contain the locale, go to that locale.

  let locale = localStorage.getItem("locale");
  if (locale) {
    const supported = getSupportedLocales();
    if (supported.has(locale)) {
      if (locale !== "en") {
        window.location.href = `/${locale}`;
      }
    }

Why the cache in the getSupportedLocales function?

const getSupportedLocales = (() => {
  let cache;
  return () => {
    if (cache) return cache;
    const buttons = document.querySelectorAll(".use-lang-btn");
    const locales = new Set(Array.from(buttons).map((btn) => btn.getAttribute("data-locale")));
    // Only cache if DOM was parsed and buttons actually exist
    if (locales.size > 0) {
      cache = locales;
    }
    return locales;
  };
})();

… cache

- Guard saved-preference redirect with !window.location.href.includes(locale) to stop constant reloads

- Replace cached getSupportedLocales IIFE with a plain DOM query

- Keep OS/browser auto-detection when no preference is saved

- Mirror changes between app/ and website/
@tyrael-z

Copy link
Copy Markdown
Contributor Author

Hi @flick9000

Thanks for the review.

You're right about the redirect loop in loadLocale(). The intended behavior was to redirect only when the current URL does not already contain the selected locale, but app/src/assets/js/main.js was missing that guard. I've updated it so existing locale paths are respected and we no longer assign window.location.href to the same locale route again.

About the cache in getSupportedLocales(): I originally added it thinking the helper might be called repeatedly while resolving the locale. After re-checking the call sites, loadLocale() only runs once on page load, and getSupportedLocales() is only reached through that flow. The DOM query is also tiny, so the cache does not buy us anything meaningful and makes the initialization behavior harder to reason about. I've removed it and now read the current DOM directly each time. I applied the same simplification to website/src/assets/js/main.js to keep both copies consistent.

Since this PR now has several iterative fix commits, I can squash them into a single commit before merging if you prefer.

@flick9000

Copy link
Copy Markdown
Owner

It looks fine now, but i think variables names could be more explicit, like here locale should be called osLocale:

locale = await detectOsLocale();
if (locale && locale !== "en") {
    localStorage.setItem("locale", locale);
    window.location.href = `/${locale}`;
  }

Maybe here change raw to rawLocale:

for (const raw of browserLocales) {
    // 1) Exact match: "zh-CN", "de", "fr", …
    if (supported.has(raw)) {
      return raw;
    }

@tyrael-z

Copy link
Copy Markdown
Contributor Author

Hi @flick9000

I’ve applied both naming suggestions:

  • In app/src/assets/js/main.js, renamed the detected OS locale variable from locale to osLocale.
  • In website/src/assets/js/main.js, renamed the loop variable from raw to rawLocale.

While making the first change, I also renamed the imported @tauri-apps/plugin-os function from osLocale to getOsLocale to avoid shadowing the new local variable.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants