Skip to content

feat: use system language when no locale has been chosen - #8020

Open
creeperkatze wants to merge 1 commit into
modrinth:mainfrom
creeperkatze:use-system-language
Open

creeperkatze wants to merge 1 commit into
modrinth:mainfrom
creeperkatze:use-system-language

Conversation

@creeperkatze

Copy link
Copy Markdown
Contributor

The app always started in English, and the website only used the browser language for logged-out users, since account preferences returned en-US and overrode it. Both now use the system/browser language until one is chosen.

The account localization.locale preference is now nullable and only applied when set. The app detects the language from navigator.languages on startup without saving it, so it follows the OS until one is picked, and a migration resets the stored en-US default. Matching goes through a new shared matchLocale helper in @modrinth/ui, which also prefers a language's home region (e.g. de becomes de-DE instead of de-CH).

Users who chose English on purpose can't be told apart from users who never chose the language, so on a non-English system they'll need to pick English again once. This is unavoidable.

@github-actions

Copy link
Copy Markdown
Contributor

Pull request changelog

App

Added

Changed

Deprecated

Removed

Fixed

Security

Website

Added

Changed

Deprecated

Removed

Fixed

Security

Hosting

Added

Changed

Deprecated

Removed

Fixed

Security

@creeperkatze

Copy link
Copy Markdown
Contributor Author

A minimum translation coverage for detection could be added, so poorly translated languages (e.g. Thai at roughly 11%) fall back to English instead.

Didnt add it since the website already uses the browser language with no threshold, and adding one only to the app would make the two behave differently.

@rahul05ranjan rahul05ranjan left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

The direction is right — Option<String> with null meaning "follow the system" is the correct model, and folding parseAcceptLanguage's hand-rolled matcher into the shared matchLocale removes a near-duplicate of the same matching logic. The home-region preference in matchLocale (de → de-DE) is a nice touch.

The main thing I'd push back on is the migration asymmetry:

  • packages/app-lib/migrations/20261019120000_detect-system-locale.sql resets every desktop user whose stored locale is en-US to system-detection — including users who deliberately picked English while running a non-English OS. They'll open the app after update and it's suddenly in their system language. If that tradeoff is accepted, it needs a release note, but it's worth checking whether the old app ever wrote en-US on first run without a user choice — if it did, then "explicitly chose English" and "never chose anything" are genuinely indistinguishable and the reset is the only way to give anyone the new behavior. But that should be stated in the migration comment, because as written it looks like a blanket reset.
  • There's no equivalent reset for the server-side web preferences, so web users whose stored localization.locale is en-US stay pinned and never get system detection. Desktop gets flipped even if they chose deliberately; web never gets flipped even if they didn't. One of those two seems wrong.

Two smaller points:

  • language-settings/index.vue: persistedLocale.value = value.localization.locale ?? locale.value means a user in system-detection mode who opens language settings already shows one unsaved "change" (the detected locale), and clicking Save pins it permanently. There's also no way back: once a concrete locale is saved, the selector has no "use system language" option, so the feature is one-way. An explicit auto/system entry in the selector would close both.
  • matchLocale maps bare zh / zh-Hant to whichever zh-* appears first in LOCALES, so a Traditional-Chinese system locale can land on Simplified. The old code had the same flaw, so no regression — just noting it's now the one place this logic lives.

The App.vue guards (locale &&) are consistent with the empty-string sentinel — I checked both watch paths and the settings write is correctly skipped in system mode.

@creeperkatze

Copy link
Copy Markdown
Contributor Author

The direction is right — Option<String> with null meaning "follow the system" is the correct model, and folding parseAcceptLanguage's hand-rolled matcher into the shared matchLocale removes a near-duplicate of the same matching logic. The home-region preference in matchLocale (de → de-DE) is a nice touch.

The main thing I'd push back on is the migration asymmetry:

  • packages/app-lib/migrations/20261019120000_detect-system-locale.sql resets every desktop user whose stored locale is en-US to system-detection — including users who deliberately picked English while running a non-English OS. They'll open the app after update and it's suddenly in their system language. If that tradeoff is accepted, it needs a release note, but it's worth checking whether the old app ever wrote en-US on first run without a user choice — if it did, then "explicitly chose English" and "never chose anything" are genuinely indistinguishable and the reset is the only way to give anyone the new behavior. But that should be stated in the migration comment, because as written it looks like a blanket reset.
  • There's no equivalent reset for the server-side web preferences, so web users whose stored localization.locale is en-US stay pinned and never get system detection. Desktop gets flipped even if they chose deliberately; web never gets flipped even if they didn't. One of those two seems wrong.

Two smaller points:

  • language-settings/index.vue: persistedLocale.value = value.localization.locale ?? locale.value means a user in system-detection mode who opens language settings already shows one unsaved "change" (the detected locale), and clicking Save pins it permanently. There's also no way back: once a concrete locale is saved, the selector has no "use system language" option, so the feature is one-way. An explicit auto/system entry in the selector would close both.
  • matchLocale maps bare zh / zh-Hant to whichever zh-* appears first in LOCALES, so a Traditional-Chinese system locale can land on Simplified. The old code had the same flaw, so no regression — just noting it's now the one place this logic lives.

The App.vue guards (locale &&) are consistent with the empty-string sentinel — I checked both watch paths and the settings write is correctly skipped in system mode.

Slop

This branch has not been deployed

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

Labels

None yet

Development

Successfully merging this pull request may close these issues.

2 participants