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:
committed by
GitHub
parent
09297d92cb
commit
9f075b1340
+39
-3
@@ -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,4 +1,4 @@
|
|||||||
interface Language {
|
export interface Language {
|
||||||
code3: string
|
code3: string
|
||||||
code2: string
|
code2: string
|
||||||
name: string
|
name: string
|
||||||
|
|||||||
@@ -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={[
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
Reference in New Issue
Block a user