Files
opencode-mobile/src/lib/i18n/locale-resolve.ts
Den baf73058fd feat(i18n): add i18n infrastructure + Simplified Chinese, localize Settings screen (refs #68) (#97)
- Add expo-localization, i18next, react-i18next (versions aligned with
  Expo 54 / RN 0.81)
- src/lib/i18n/locale-resolve.ts: pure locale-resolution helpers
  (system tag -> supported catalog, with en fallback), unit-tested via
  node --test with no RN imports
- src/lib/i18n/config.ts: i18next init wired to expo-localization
  device detection, en.json + zh-Hans.json catalogs
- Persist a locale preference (system | en | zh-Hans) in the settings
  zustand store, applied immediately via i18next.changeLanguage
- Wire I18nextProvider in app/_layout.tsx
- Localize the Settings screen (~28 strings) as the reference pattern
  for extracting user-facing strings, with a language picker row and
  reviewed Simplified Chinese translations

Other screens (session/[id], connection/*, index, chat components)
are deferred follow-up — issue #68 stays open for that work.

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
2026-07-17 02:47:18 -07:00

44 lines
1.7 KiB
TypeScript

// Pure locale-resolution logic, kept free of expo-localization/i18next imports
// so it's unit-testable with plain `node --test` (same split as
// settings-merge.ts / store-review-policy.ts).
export const SUPPORTED_LOCALES = ["en", "zh-Hans"] as const
export type SupportedLocale = (typeof SUPPORTED_LOCALES)[number]
export const FALLBACK_LOCALE: SupportedLocale = "en"
// User-facing preference: "system" defers to the device's locale list.
export type LocalePreference = "system" | SupportedLocale
/**
* Map one device locale tag (e.g. "zh-Hans-CN", "zh-CN", "en-US", "fr-FR") to
* one of our supported catalogs, matching by language subtag since we don't
* ship per-region variants. Returns null if we have no catalog for it.
*/
export function matchSupportedLocale(tag: string): SupportedLocale | null {
const lower = tag.toLowerCase()
if (lower.startsWith("zh")) return "zh-Hans"
if (lower.startsWith("en")) return "en"
return null
}
/**
* Resolve the effective app locale from a user preference plus the device's
* ranked list of locale tags (as returned by expo-localization's
* `getLocales().map(l => l.languageTag)`).
* - An explicit preference (anything but "system") always wins.
* - "system" walks the device list in priority order and uses the first tag
* we have a catalog for.
* - If nothing matches (or the list is empty), falls back to FALLBACK_LOCALE.
*/
export function resolveLocale(preference: LocalePreference, deviceTags: readonly string[]): SupportedLocale {
if (preference !== "system") return preference
for (const tag of deviceTags) {
const match = matchSupportedLocale(tag)
if (match) return match
}
return FALLBACK_LOCALE
}