about summary refs log tree commit diff
diff options
context:
space:
mode:
authorEric Bailey <git@esb.lol>2023-12-12 12:42:11 -0600
committerGitHub <noreply@github.com>2023-12-12 12:42:11 -0600
commitc6ab6e8b8e81c1e2d72973a420cfea7aecc6e425 (patch)
treeab21ddf073883117b57a3d2e4c74e6b6e4b21311
parentd82b1a104794eb237b898336fae0918dc1ec6753 (diff)
downloadvoidsky-c6ab6e8b8e81c1e2d72973a420cfea7aecc6e425.tar.zst
i18n settings improvements (#2184)
* Handle language selector

* Improve type safety

* Add a little more safety

* Update comment
-rw-r--r--src/locale/__tests__/helpers.test.ts12
-rw-r--r--src/locale/helpers.ts33
-rw-r--r--src/locale/i18n.ts23
-rw-r--r--src/locale/i18n.web.ts23
-rw-r--r--src/locale/languages.ts15
-rw-r--r--src/state/preferences/languages.tsx7
-rw-r--r--src/view/screens/LanguageSettings.tsx5
7 files changed, 79 insertions, 39 deletions
diff --git a/src/locale/__tests__/helpers.test.ts b/src/locale/__tests__/helpers.test.ts
new file mode 100644
index 000000000..d4bc6c338
--- /dev/null
+++ b/src/locale/__tests__/helpers.test.ts
@@ -0,0 +1,12 @@
+import {test, expect} from '@jest/globals'
+
+import {sanitizeAppLanguageSetting} from '#/locale/helpers'
+import {AppLanguage} from '#/locale/languages'
+
+test('sanitizeAppLanguageSetting', () => {
+  expect(sanitizeAppLanguageSetting('en')).toBe(AppLanguage.en)
+  expect(sanitizeAppLanguageSetting('hi')).toBe(AppLanguage.hi)
+  expect(sanitizeAppLanguageSetting('foo')).toBe(AppLanguage.en)
+  expect(sanitizeAppLanguageSetting('en,fr')).toBe(AppLanguage.en)
+  expect(sanitizeAppLanguageSetting('fr,en')).toBe(AppLanguage.en)
+})
diff --git a/src/locale/helpers.ts b/src/locale/helpers.ts
index 4d01459ab..d80cb5032 100644
--- a/src/locale/helpers.ts
+++ b/src/locale/helpers.ts
@@ -2,7 +2,11 @@ import {AppBskyFeedDefs, AppBskyFeedPost} from '@atproto/api'
 import lande from 'lande'
 import {hasProp} from 'lib/type-guards'
 import * as bcp47Match from 'bcp-47-match'
-import {LANGUAGES_MAP_CODE2, LANGUAGES_MAP_CODE3} from './languages'
+import {
+  AppLanguage,
+  LANGUAGES_MAP_CODE2,
+  LANGUAGES_MAP_CODE3,
+} from './languages'
 
 export function code2ToCode3(lang: string): string {
   if (lang.length === 2) {
@@ -85,14 +89,33 @@ export function getTranslatorLink(text: string, lang: string): string {
   )}`
 }
 
-export function sanitizeAppLanguageSetting(appLanguage: string) {
+/**
+ * Returns a valid `appLanguage` value from an arbitrary string.
+ *
+ * Contenxt: post-refactor, we populated some user's `appLanguage` setting with
+ * `postLanguage`, which can be a comma-separated list of values. This breaks
+ * `appLanguage` handling in the app, so we introduced this util to parse out a
+ * valid `appLanguage` from the pre-populated `postLanguage` values.
+ *
+ * The `appLanguage` will continue to be incorrect until the user returns to
+ * language settings and selects a new option, at which point we'll re-save
+ * their choice, which should then be a valid option. Since we don't know when
+ * this will happen, we should leave this here until we feel it's safe to
+ * remove, or we re-migrate their storage.
+ */
+export function sanitizeAppLanguageSetting(appLanguage: string): AppLanguage {
   const langs = appLanguage.split(',').filter(Boolean)
 
   for (const lang of langs) {
-    if (['en', 'hi'].includes(lang)) {
-      return lang
+    switch (lang) {
+      case 'en':
+        return AppLanguage.en
+      case 'hi':
+        return AppLanguage.hi
+      default:
+        continue
     }
   }
 
-  return 'en'
+  return AppLanguage.en
 }
diff --git a/src/locale/i18n.ts b/src/locale/i18n.ts
index 93d21f381..2e54b15ea 100644
--- a/src/locale/i18n.ts
+++ b/src/locale/i18n.ts
@@ -5,22 +5,21 @@ import {useLanguagePrefs} from '#/state/preferences'
 import {messages as messagesEn} from '#/locale/locales/en/messages'
 import {messages as messagesHi} from '#/locale/locales/hi/messages'
 import {sanitizeAppLanguageSetting} from '#/locale/helpers'
-
-export const locales = {
-  en: 'English',
-  hi: 'हिंदी',
-}
-export const defaultLocale = 'en'
+import {AppLanguage} from '#/locale/languages'
 
 /**
  * We do a dynamic import of just the catalog that we need
- * @param locale any locale string
  */
-export async function dynamicActivate(locale: string) {
-  if (locale === 'hi') {
-    i18n.loadAndActivate({locale, messages: messagesHi})
-  } else {
-    i18n.loadAndActivate({locale, messages: messagesEn})
+export async function dynamicActivate(locale: AppLanguage) {
+  switch (locale) {
+    case AppLanguage.hi: {
+      i18n.loadAndActivate({locale, messages: messagesHi})
+      break
+    }
+    default: {
+      i18n.loadAndActivate({locale, messages: messagesEn})
+      break
+    }
   }
 }
 
diff --git a/src/locale/i18n.web.ts b/src/locale/i18n.web.ts
index bc484f303..54963836b 100644
--- a/src/locale/i18n.web.ts
+++ b/src/locale/i18n.web.ts
@@ -3,24 +3,23 @@ import {i18n} from '@lingui/core'
 
 import {useLanguagePrefs} from '#/state/preferences'
 import {sanitizeAppLanguageSetting} from '#/locale/helpers'
-
-export const locales = {
-  en: 'English',
-  hi: 'हिंदी',
-}
-export const defaultLocale = 'en'
+import {AppLanguage} from '#/locale/languages'
 
 /**
  * We do a dynamic import of just the catalog that we need
- * @param locale any locale string
  */
-export async function dynamicActivate(locale: string) {
+export async function dynamicActivate(locale: AppLanguage) {
   let mod: any
 
-  if (locale === 'hi') {
-    mod = await import(`./locales/hi/messages`)
-  } else {
-    mod = await import(`./locales/en/messages`)
+  switch (locale) {
+    case AppLanguage.hi: {
+      mod = await import(`./locales/hi/messages`)
+      break
+    }
+    default: {
+      mod = await import(`./locales/en/messages`)
+      break
+    }
   }
 
   i18n.load(locale, mod.messages)
diff --git a/src/locale/languages.ts b/src/locale/languages.ts
index cfcc60c5a..e45fdf42c 100644
--- a/src/locale/languages.ts
+++ b/src/locale/languages.ts
@@ -4,14 +4,19 @@ interface Language {
   name: string
 }
 
-interface AppLanguage {
-  code2: string
+export enum AppLanguage {
+  en = 'en',
+  hi = 'hi',
+}
+
+interface AppLanguageConfig {
+  code2: AppLanguage
   name: string
 }
 
-export const APP_LANGUAGES: AppLanguage[] = [
-  {code2: 'en', name: 'English'},
-  {code2: 'hi', name: 'हिंदी'},
+export const APP_LANGUAGES: AppLanguageConfig[] = [
+  {code2: AppLanguage.en, name: 'English'},
+  {code2: AppLanguage.hi, name: 'हिंदी'},
 ]
 
 export const LANGUAGES: Language[] = [
diff --git a/src/state/preferences/languages.tsx b/src/state/preferences/languages.tsx
index 8e779cfe5..df774c05e 100644
--- a/src/state/preferences/languages.tsx
+++ b/src/state/preferences/languages.tsx
@@ -1,5 +1,6 @@
 import React from 'react'
 import * as persisted from '#/state/persisted'
+import {AppLanguage} from '#/locale/languages'
 
 type SetStateCb = (
   s: persisted.Schema['languagePrefs'],
@@ -11,7 +12,7 @@ type ApiContext = {
   toggleContentLanguage: (code2: string) => void
   togglePostLanguage: (code2: string) => void
   savePostLanguageToHistory: () => void
-  setAppLanguage: (code2: string) => void
+  setAppLanguage: (code2: AppLanguage) => void
 }
 
 const stateContext = React.createContext<StateContext>(
@@ -23,7 +24,7 @@ const apiContext = React.createContext<ApiContext>({
   toggleContentLanguage: (_: string) => {},
   togglePostLanguage: (_: string) => {},
   savePostLanguageToHistory: () => {},
-  setAppLanguage: (_: string) => {},
+  setAppLanguage: (_: AppLanguage) => {},
 })
 
 export function Provider({children}: React.PropsWithChildren<{}>) {
@@ -106,7 +107,7 @@ export function Provider({children}: React.PropsWithChildren<{}>) {
             .slice(0, 6),
         }))
       },
-      setAppLanguage(code2: string) {
+      setAppLanguage(code2: AppLanguage) {
         setStateWrapped(s => ({...s, appLanguage: code2}))
       },
     }),
diff --git a/src/view/screens/LanguageSettings.tsx b/src/view/screens/LanguageSettings.tsx
index eefbfb2e2..819840a46 100644
--- a/src/view/screens/LanguageSettings.tsx
+++ b/src/view/screens/LanguageSettings.tsx
@@ -21,6 +21,7 @@ import {useModalControls} from '#/state/modals'
 import {useLanguagePrefs, useLanguagePrefsApi} from '#/state/preferences'
 import {Trans, msg} from '@lingui/macro'
 import {useLingui} from '@lingui/react'
+import {sanitizeAppLanguageSetting} from '#/locale/helpers'
 
 type Props = NativeStackScreenProps<CommonNavigatorParams, 'LanguageSettings'>
 
@@ -60,7 +61,7 @@ export function LanguageSettingsScreen(_props: Props) {
     (value: Parameters<PickerSelectProps['onValueChange']>[0]) => {
       if (!value) return
       if (langPrefs.appLanguage !== value) {
-        setLangPrefs.setAppLanguage(value)
+        setLangPrefs.setAppLanguage(sanitizeAppLanguageSetting(value))
       }
     },
     [langPrefs, setLangPrefs],
@@ -103,7 +104,7 @@ export function LanguageSettingsScreen(_props: Props) {
           <View style={{position: 'relative'}}>
             <RNPickerSelect
               placeholder={{}}
-              value={langPrefs.appLanguage}
+              value={sanitizeAppLanguageSetting(langPrefs.appLanguage)}
               onValueChange={onChangeAppLanguage}
               items={APP_LANGUAGES.filter(l => Boolean(l.code2)).map(l => ({
                 label: l.name,