i18n settings improvements (#2184)
* Handle language selector * Improve type safety * Add a little more safety * Update commentzio/stable
parent
d82b1a1047
commit
c6ab6e8b8e
|
@ -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)
|
||||
})
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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[] = [
|
||||
|
|
|
@ -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}))
|
||||
},
|
||||
}),
|
||||
|
|
|
@ -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,
|
||||
|
|
Loading…
Reference in New Issue