Skip to content

Commit 9f075b1

Browse files
authored
Localize lang selectors according to the app language (bluesky-social#6207)
* Localize lang selectors according to the app language * Explicitly ignore RangeError when translating locale names
1 parent 09297d9 commit 9f075b1

File tree

8 files changed

+97
-45
lines changed

8 files changed

+97
-45
lines changed

src/locale/helpers.ts

+39-3
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import lande from 'lande'
55
import {hasProp} from '#/lib/type-guards'
66
import {
77
AppLanguage,
8+
type Language,
89
LANGUAGES_MAP_CODE2,
910
LANGUAGES_MAP_CODE3,
1011
} from './languages'
@@ -31,9 +32,44 @@ export function code3ToCode2Strict(lang: string): string | undefined {
3132
return undefined
3233
}
3334

34-
export function codeToLanguageName(lang: string): string {
35-
const lang2 = code3ToCode2(lang)
36-
return LANGUAGES_MAP_CODE2[lang2]?.name || lang
35+
function getLocalizedLanguage(
36+
langCode: string,
37+
appLang: string,
38+
): string | undefined {
39+
try {
40+
const allNames = new Intl.DisplayNames([appLang], {
41+
type: 'language',
42+
fallback: 'none',
43+
languageDisplay: 'standard',
44+
})
45+
const translatedName = allNames.of(langCode)
46+
47+
if (translatedName) {
48+
// force simple title case (as languages do not always start with an uppercase in Unicode data)
49+
return translatedName[0].toLocaleUpperCase() + translatedName.slice(1)
50+
}
51+
} catch (e) {
52+
// ignore RangeError from Intl.DisplayNames APIs
53+
if (!(e instanceof RangeError)) {
54+
throw e
55+
}
56+
}
57+
}
58+
59+
export function languageName(language: Language, appLang: string): string {
60+
// if Intl.DisplayNames is unavailable on the target, display the English name
61+
if (!(Intl as any).DisplayNames) {
62+
return language.name
63+
}
64+
65+
return getLocalizedLanguage(language.code2, appLang) || language.name
66+
}
67+
68+
export function codeToLanguageName(lang2or3: string, appLang: string): string {
69+
const code2 = code3ToCode2(lang2or3)
70+
const knownLanguage = LANGUAGES_MAP_CODE2[code2]
71+
72+
return knownLanguage ? languageName(knownLanguage, appLang) : code2
3773
}
3874

3975
export function getPostLanguage(

src/locale/languages.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
interface Language {
1+
export interface Language {
22
code3: string
33
code2: string
44
name: string

src/screens/Settings/LanguageSettings.tsx

+4-4
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import {useLingui} from '@lingui/react'
66

77
import {APP_LANGUAGES, LANGUAGES} from '#/lib/../locale/languages'
88
import {CommonNavigatorParams, NativeStackScreenProps} from '#/lib/routes/types'
9-
import {sanitizeAppLanguageSetting} from '#/locale/helpers'
9+
import {languageName, sanitizeAppLanguageSetting} from '#/locale/helpers'
1010
import {useModalControls} from '#/state/modals'
1111
import {useLanguagePrefs, useLanguagePrefsApi} from '#/state/preferences'
1212
import {atoms as a, useTheme, web} from '#/alf'
@@ -57,10 +57,10 @@ export function LanguageSettingsScreen({}: Props) {
5757
.map(lang => LANGUAGES.find(l => l.code2 === lang))
5858
.filter(Boolean)
5959
// @ts-ignore
60-
.map(l => l.name)
60+
.map(l => languageName(l, langPrefs.appLanguage))
6161
.join(', ')
6262
)
63-
}, [langPrefs.contentLanguages])
63+
}, [langPrefs.appLanguage, langPrefs.contentLanguages])
6464

6565
return (
6666
<Layout.Screen testID="PreferencesLanguagesScreen">
@@ -179,7 +179,7 @@ export function LanguageSettingsScreen({}: Props) {
179179
value={langPrefs.primaryLanguage}
180180
onValueChange={onChangePrimaryLanguage}
181181
items={LANGUAGES.filter(l => Boolean(l.code2)).map(l => ({
182-
label: l.name,
182+
label: languageName(l, langPrefs.appLanguage),
183183
value: l.code2,
184184
key: l.code2 + l.code3,
185185
}))}

src/view/com/composer/select-language/SelectLangBtn.tsx

+4-2
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@ export function SelectLangBtn() {
4848
function add(commaSeparatedLangCodes: string) {
4949
const langCodes = commaSeparatedLangCodes.split(',')
5050
const langName = langCodes
51-
.map(code => codeToLanguageName(code))
51+
.map(code => codeToLanguageName(code, langPrefs.appLanguage))
5252
.join(' + ')
5353

5454
/*
@@ -108,7 +108,9 @@ export function SelectLangBtn() {
108108
accessibilityHint="">
109109
{postLanguagesPref.length > 0 ? (
110110
<Text type="lg-bold" style={[pal.link, styles.label]} numberOfLines={1}>
111-
{postLanguagesPref.map(lang => codeToLanguageName(lang)).join(', ')}
111+
{postLanguagesPref
112+
.map(lang => codeToLanguageName(lang, langPrefs.appLanguage))
113+
.join(', ')}
112114
</Text>
113115
) : (
114116
<FontAwesomeIcon

src/view/com/composer/select-language/SuggestedLanguage.tsx

+40-29
Original file line numberDiff line numberDiff line change
@@ -49,37 +49,48 @@ export function SuggestedLanguage({text}: {text: string}) {
4949
return () => cancelIdle(idle)
5050
}, [text])
5151

52-
return suggestedLanguage &&
53-
!toPostLanguages(langPrefs.postLanguage).includes(suggestedLanguage) ? (
54-
<View style={[pal.border, styles.infoBar]}>
55-
<FontAwesomeIcon
56-
icon="language"
57-
style={pal.text as FontAwesomeIconStyle}
58-
size={24}
59-
/>
60-
<Text style={[pal.text, s.flex1]}>
61-
<Trans>
62-
Are you writing in{' '}
63-
<Text type="sm-bold" style={pal.text}>
64-
{codeToLanguageName(suggestedLanguage)}
65-
</Text>
66-
?
67-
</Trans>
68-
</Text>
52+
if (
53+
suggestedLanguage &&
54+
!toPostLanguages(langPrefs.postLanguage).includes(suggestedLanguage)
55+
) {
56+
const suggestedLanguageName = codeToLanguageName(
57+
suggestedLanguage,
58+
langPrefs.appLanguage,
59+
)
6960

70-
<Button
71-
type="default"
72-
onPress={() => setLangPrefs.setPostLanguage(suggestedLanguage)}
73-
accessibilityLabel={_(
74-
msg`Change post language to ${codeToLanguageName(suggestedLanguage)}`,
75-
)}
76-
accessibilityHint="">
77-
<Text type="button" style={[pal.link, s.fw600]}>
78-
<Trans>Yes</Trans>
61+
return (
62+
<View style={[pal.border, styles.infoBar]}>
63+
<FontAwesomeIcon
64+
icon="language"
65+
style={pal.text as FontAwesomeIconStyle}
66+
size={24}
67+
/>
68+
<Text style={[pal.text, s.flex1]}>
69+
<Trans>
70+
Are you writing in{' '}
71+
<Text type="sm-bold" style={pal.text}>
72+
{suggestedLanguageName}
73+
</Text>
74+
?
75+
</Trans>
7976
</Text>
80-
</Button>
81-
</View>
82-
) : null
77+
78+
<Button
79+
type="default"
80+
onPress={() => setLangPrefs.setPostLanguage(suggestedLanguage)}
81+
accessibilityLabel={_(
82+
msg`Change post language to ${suggestedLanguageName}`,
83+
)}
84+
accessibilityHint="">
85+
<Text type="button" style={[pal.link, s.fw600]}>
86+
<Trans>Yes</Trans>
87+
</Text>
88+
</Button>
89+
</View>
90+
)
91+
} else {
92+
return null
93+
}
8394
}
8495

8596
const styles = StyleSheet.create({

src/view/com/modals/lang-settings/ContentLanguagesSettings.tsx

+2-1
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import {Trans} from '@lingui/macro'
55
import {usePalette} from '#/lib/hooks/usePalette'
66
import {useWebMediaQueries} from '#/lib/hooks/useWebMediaQueries'
77
import {deviceLanguageCodes} from '#/locale/deviceLocales'
8+
import {languageName} from '#/locale/helpers'
89
import {useModalControls} from '#/state/modals'
910
import {
1011
useLanguagePrefs,
@@ -88,7 +89,7 @@ export function Component({}: {}) {
8889
key={lang.code2}
8990
code2={lang.code2}
9091
langType="contentLanguages"
91-
name={lang.name}
92+
name={languageName(lang, langPrefs.appLanguage)}
9293
onPress={() => {
9394
onPress(lang.code2)
9495
}}

src/view/com/modals/lang-settings/PostLanguagesSettings.tsx

+2-1
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import {Trans} from '@lingui/macro'
55
import {usePalette} from '#/lib/hooks/usePalette'
66
import {useWebMediaQueries} from '#/lib/hooks/useWebMediaQueries'
77
import {deviceLanguageCodes} from '#/locale/deviceLocales'
8+
import {languageName} from '#/locale/helpers'
89
import {useModalControls} from '#/state/modals'
910
import {
1011
hasPostLanguage,
@@ -91,7 +92,7 @@ export function Component() {
9192
return (
9293
<ToggleButton
9394
key={lang.code2}
94-
label={lang.name}
95+
label={languageName(lang, langPrefs.appLanguage)}
9596
isSelected={isSelected}
9697
onPress={() => (isDisabled ? undefined : onPress(lang.code2))}
9798
style={[

src/view/screens/Search/Search.tsx

+5-4
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ import {
3737
} from '#/lib/routes/types'
3838
import {sanitizeDisplayName} from '#/lib/strings/display-names'
3939
import {augmentSearchQuery} from '#/lib/strings/helpers'
40+
import {languageName} from '#/locale/helpers'
4041
import {logger} from '#/logger'
4142
import {isNative, isWeb} from '#/platform/detection'
4243
import {listenSoftReset} from '#/state/events'
@@ -328,7 +329,7 @@ function SearchLanguageDropdown({
328329
}) {
329330
const t = useThemeNew()
330331
const {_} = useLingui()
331-
const {contentLanguages} = useLanguagePrefs()
332+
const {appLanguage, contentLanguages} = useLanguagePrefs()
332333

333334
const items = React.useMemo(() => {
334335
return [
@@ -345,8 +346,8 @@ function SearchLanguageDropdown({
345346
index === self.findIndex(t => t.code2 === lang.code2), // remove dupes (which will happen)
346347
)
347348
.map(l => ({
348-
label: l.name,
349-
inputLabel: l.name,
349+
label: languageName(l, appLanguage),
350+
inputLabel: languageName(l, appLanguage),
350351
value: l.code2,
351352
key: l.code2 + l.code3,
352353
}))
@@ -365,7 +366,7 @@ function SearchLanguageDropdown({
365366
return a.label.localeCompare(b.label)
366367
}),
367368
)
368-
}, [_, contentLanguages])
369+
}, [_, appLanguage, contentLanguages])
369370

370371
const style = {
371372
backgroundColor: t.atoms.bg_contrast_25.backgroundColor,

0 commit comments

Comments
 (0)