Localize lang selectors according to the app language (#6207)

* Localize lang selectors according to the app language

* Explicitly ignore RangeError when translating locale names
This commit is contained in:
Stanislas Signoud
2024-12-31 22:27:14 +01:00
committed by GitHub
parent 09297d92cb
commit 9f075b1340
8 changed files with 97 additions and 45 deletions
+39 -3
View File
@@ -5,6 +5,7 @@ import lande from 'lande'
import {hasProp} from '#/lib/type-guards' import {hasProp} from '#/lib/type-guards'
import { import {
AppLanguage, AppLanguage,
type Language,
LANGUAGES_MAP_CODE2, LANGUAGES_MAP_CODE2,
LANGUAGES_MAP_CODE3, LANGUAGES_MAP_CODE3,
} from './languages' } from './languages'
@@ -31,9 +32,44 @@ export function code3ToCode2Strict(lang: string): string | undefined {
return undefined return undefined
} }
export function codeToLanguageName(lang: string): string { function getLocalizedLanguage(
const lang2 = code3ToCode2(lang) langCode: string,
return LANGUAGES_MAP_CODE2[lang2]?.name || lang appLang: string,
): string | undefined {
try {
const allNames = new Intl.DisplayNames([appLang], {
type: 'language',
fallback: 'none',
languageDisplay: 'standard',
})
const translatedName = allNames.of(langCode)
if (translatedName) {
// force simple title case (as languages do not always start with an uppercase in Unicode data)
return translatedName[0].toLocaleUpperCase() + translatedName.slice(1)
}
} catch (e) {
// ignore RangeError from Intl.DisplayNames APIs
if (!(e instanceof RangeError)) {
throw e
}
}
}
export function languageName(language: Language, appLang: string): string {
// if Intl.DisplayNames is unavailable on the target, display the English name
if (!(Intl as any).DisplayNames) {
return language.name
}
return getLocalizedLanguage(language.code2, appLang) || language.name
}
export function codeToLanguageName(lang2or3: string, appLang: string): string {
const code2 = code3ToCode2(lang2or3)
const knownLanguage = LANGUAGES_MAP_CODE2[code2]
return knownLanguage ? languageName(knownLanguage, appLang) : code2
} }
export function getPostLanguage( export function getPostLanguage(
+1 -1
View File
@@ -1,4 +1,4 @@
interface Language { export interface Language {
code3: string code3: string
code2: string code2: string
name: string name: string
+4 -4
View File
@@ -6,7 +6,7 @@ import {useLingui} from '@lingui/react'
import {APP_LANGUAGES, LANGUAGES} from '#/lib/../locale/languages' import {APP_LANGUAGES, LANGUAGES} from '#/lib/../locale/languages'
import {CommonNavigatorParams, NativeStackScreenProps} from '#/lib/routes/types' import {CommonNavigatorParams, NativeStackScreenProps} from '#/lib/routes/types'
import {sanitizeAppLanguageSetting} from '#/locale/helpers' import {languageName, sanitizeAppLanguageSetting} from '#/locale/helpers'
import {useModalControls} from '#/state/modals' import {useModalControls} from '#/state/modals'
import {useLanguagePrefs, useLanguagePrefsApi} from '#/state/preferences' import {useLanguagePrefs, useLanguagePrefsApi} from '#/state/preferences'
import {atoms as a, useTheme, web} from '#/alf' import {atoms as a, useTheme, web} from '#/alf'
@@ -57,10 +57,10 @@ export function LanguageSettingsScreen({}: Props) {
.map(lang => LANGUAGES.find(l => l.code2 === lang)) .map(lang => LANGUAGES.find(l => l.code2 === lang))
.filter(Boolean) .filter(Boolean)
// @ts-ignore // @ts-ignore
.map(l => l.name) .map(l => languageName(l, langPrefs.appLanguage))
.join(', ') .join(', ')
) )
}, [langPrefs.contentLanguages]) }, [langPrefs.appLanguage, langPrefs.contentLanguages])
return ( return (
<Layout.Screen testID="PreferencesLanguagesScreen"> <Layout.Screen testID="PreferencesLanguagesScreen">
@@ -179,7 +179,7 @@ export function LanguageSettingsScreen({}: Props) {
value={langPrefs.primaryLanguage} value={langPrefs.primaryLanguage}
onValueChange={onChangePrimaryLanguage} onValueChange={onChangePrimaryLanguage}
items={LANGUAGES.filter(l => Boolean(l.code2)).map(l => ({ items={LANGUAGES.filter(l => Boolean(l.code2)).map(l => ({
label: l.name, label: languageName(l, langPrefs.appLanguage),
value: l.code2, value: l.code2,
key: l.code2 + l.code3, key: l.code2 + l.code3,
}))} }))}
@@ -48,7 +48,7 @@ export function SelectLangBtn() {
function add(commaSeparatedLangCodes: string) { function add(commaSeparatedLangCodes: string) {
const langCodes = commaSeparatedLangCodes.split(',') const langCodes = commaSeparatedLangCodes.split(',')
const langName = langCodes const langName = langCodes
.map(code => codeToLanguageName(code)) .map(code => codeToLanguageName(code, langPrefs.appLanguage))
.join(' + ') .join(' + ')
/* /*
@@ -108,7 +108,9 @@ export function SelectLangBtn() {
accessibilityHint=""> accessibilityHint="">
{postLanguagesPref.length > 0 ? ( {postLanguagesPref.length > 0 ? (
<Text type="lg-bold" style={[pal.link, styles.label]} numberOfLines={1}> <Text type="lg-bold" style={[pal.link, styles.label]} numberOfLines={1}>
{postLanguagesPref.map(lang => codeToLanguageName(lang)).join(', ')} {postLanguagesPref
.map(lang => codeToLanguageName(lang, langPrefs.appLanguage))
.join(', ')}
</Text> </Text>
) : ( ) : (
<FontAwesomeIcon <FontAwesomeIcon
@@ -49,37 +49,48 @@ export function SuggestedLanguage({text}: {text: string}) {
return () => cancelIdle(idle) return () => cancelIdle(idle)
}, [text]) }, [text])
return suggestedLanguage && if (
!toPostLanguages(langPrefs.postLanguage).includes(suggestedLanguage) ? ( suggestedLanguage &&
<View style={[pal.border, styles.infoBar]}> !toPostLanguages(langPrefs.postLanguage).includes(suggestedLanguage)
<FontAwesomeIcon ) {
icon="language" const suggestedLanguageName = codeToLanguageName(
style={pal.text as FontAwesomeIconStyle} suggestedLanguage,
size={24} langPrefs.appLanguage,
/> )
<Text style={[pal.text, s.flex1]}>
<Trans>
Are you writing in{' '}
<Text type="sm-bold" style={pal.text}>
{codeToLanguageName(suggestedLanguage)}
</Text>
?
</Trans>
</Text>
<Button return (
type="default" <View style={[pal.border, styles.infoBar]}>
onPress={() => setLangPrefs.setPostLanguage(suggestedLanguage)} <FontAwesomeIcon
accessibilityLabel={_( icon="language"
msg`Change post language to ${codeToLanguageName(suggestedLanguage)}`, style={pal.text as FontAwesomeIconStyle}
)} size={24}
accessibilityHint=""> />
<Text type="button" style={[pal.link, s.fw600]}> <Text style={[pal.text, s.flex1]}>
<Trans>Yes</Trans> <Trans>
Are you writing in{' '}
<Text type="sm-bold" style={pal.text}>
{suggestedLanguageName}
</Text>
?
</Trans>
</Text> </Text>
</Button>
</View> <Button
) : null type="default"
onPress={() => setLangPrefs.setPostLanguage(suggestedLanguage)}
accessibilityLabel={_(
msg`Change post language to ${suggestedLanguageName}`,
)}
accessibilityHint="">
<Text type="button" style={[pal.link, s.fw600]}>
<Trans>Yes</Trans>
</Text>
</Button>
</View>
)
} else {
return null
}
} }
const styles = StyleSheet.create({ const styles = StyleSheet.create({
@@ -5,6 +5,7 @@ import {Trans} from '@lingui/macro'
import {usePalette} from '#/lib/hooks/usePalette' import {usePalette} from '#/lib/hooks/usePalette'
import {useWebMediaQueries} from '#/lib/hooks/useWebMediaQueries' import {useWebMediaQueries} from '#/lib/hooks/useWebMediaQueries'
import {deviceLanguageCodes} from '#/locale/deviceLocales' import {deviceLanguageCodes} from '#/locale/deviceLocales'
import {languageName} from '#/locale/helpers'
import {useModalControls} from '#/state/modals' import {useModalControls} from '#/state/modals'
import { import {
useLanguagePrefs, useLanguagePrefs,
@@ -88,7 +89,7 @@ export function Component({}: {}) {
key={lang.code2} key={lang.code2}
code2={lang.code2} code2={lang.code2}
langType="contentLanguages" langType="contentLanguages"
name={lang.name} name={languageName(lang, langPrefs.appLanguage)}
onPress={() => { onPress={() => {
onPress(lang.code2) onPress(lang.code2)
}} }}
@@ -5,6 +5,7 @@ import {Trans} from '@lingui/macro'
import {usePalette} from '#/lib/hooks/usePalette' import {usePalette} from '#/lib/hooks/usePalette'
import {useWebMediaQueries} from '#/lib/hooks/useWebMediaQueries' import {useWebMediaQueries} from '#/lib/hooks/useWebMediaQueries'
import {deviceLanguageCodes} from '#/locale/deviceLocales' import {deviceLanguageCodes} from '#/locale/deviceLocales'
import {languageName} from '#/locale/helpers'
import {useModalControls} from '#/state/modals' import {useModalControls} from '#/state/modals'
import { import {
hasPostLanguage, hasPostLanguage,
@@ -91,7 +92,7 @@ export function Component() {
return ( return (
<ToggleButton <ToggleButton
key={lang.code2} key={lang.code2}
label={lang.name} label={languageName(lang, langPrefs.appLanguage)}
isSelected={isSelected} isSelected={isSelected}
onPress={() => (isDisabled ? undefined : onPress(lang.code2))} onPress={() => (isDisabled ? undefined : onPress(lang.code2))}
style={[ style={[
+5 -4
View File
@@ -37,6 +37,7 @@ import {
} from '#/lib/routes/types' } from '#/lib/routes/types'
import {sanitizeDisplayName} from '#/lib/strings/display-names' import {sanitizeDisplayName} from '#/lib/strings/display-names'
import {augmentSearchQuery} from '#/lib/strings/helpers' import {augmentSearchQuery} from '#/lib/strings/helpers'
import {languageName} from '#/locale/helpers'
import {logger} from '#/logger' import {logger} from '#/logger'
import {isNative, isWeb} from '#/platform/detection' import {isNative, isWeb} from '#/platform/detection'
import {listenSoftReset} from '#/state/events' import {listenSoftReset} from '#/state/events'
@@ -328,7 +329,7 @@ function SearchLanguageDropdown({
}) { }) {
const t = useThemeNew() const t = useThemeNew()
const {_} = useLingui() const {_} = useLingui()
const {contentLanguages} = useLanguagePrefs() const {appLanguage, contentLanguages} = useLanguagePrefs()
const items = React.useMemo(() => { const items = React.useMemo(() => {
return [ return [
@@ -345,8 +346,8 @@ function SearchLanguageDropdown({
index === self.findIndex(t => t.code2 === lang.code2), // remove dupes (which will happen) index === self.findIndex(t => t.code2 === lang.code2), // remove dupes (which will happen)
) )
.map(l => ({ .map(l => ({
label: l.name, label: languageName(l, appLanguage),
inputLabel: l.name, inputLabel: languageName(l, appLanguage),
value: l.code2, value: l.code2,
key: l.code2 + l.code3, key: l.code2 + l.code3,
})) }))
@@ -365,7 +366,7 @@ function SearchLanguageDropdown({
return a.label.localeCompare(b.label) return a.label.localeCompare(b.label)
}), }),
) )
}, [_, contentLanguages]) }, [_, appLanguage, contentLanguages])
const style = { const style = {
backgroundColor: t.atoms.bg_contrast_25.backgroundColor, backgroundColor: t.atoms.bg_contrast_25.backgroundColor,