Skip to content

Commit

Permalink
Merge pull request #2788 from infinitered/lindboe/fix-language-switching
Browse files Browse the repository at this point in the history
Fix language switching and update date-fns to v4
  • Loading branch information
lindboe authored Sep 27, 2024
2 parents 3956652 + 0beff0d commit 31428b7
Show file tree
Hide file tree
Showing 6 changed files with 75 additions and 54 deletions.
5 changes: 4 additions & 1 deletion boilerplate/app/app.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ import * as storage from "./utils/storage"
import { customFontsToLoad } from "./theme"
import Config from "./config"
import { KeyboardProvider } from "react-native-keyboard-controller"
import { loadDateFnsLocale } from "./utils/formatDate"

export const NAVIGATION_PERSISTENCE_KEY = "NAVIGATION_STATE"

Expand Down Expand Up @@ -75,7 +76,9 @@ function App(props: AppProps) {
const [isI18nInitialized, setIsI18nInitialized] = useState(false)

useEffect(() => {
initI18n().then(() => setIsI18nInitialized(true))
initI18n()
.then(() => setIsI18nInitialized(true))
.then(() => loadDateFnsLocale())
}, [])

// @mst replace-next-line React.useEffect(() => {
Expand Down
65 changes: 30 additions & 35 deletions boilerplate/app/i18n/i18n.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import * as Localization from "expo-localization"
import { I18nManager } from "react-native"
import * as i18next from "i18next"
import i18n from "i18next"
import { initReactI18next } from "react-i18next"
import "intl-pluralrules"

Expand All @@ -13,56 +13,51 @@ import fr from "./fr"
import ja from "./ja"
import hi from "./hi"

// to use regional locales use { "en-US": enUS } etc
const fallbackLocale = "en-US"

export let i18n: i18next.i18n
const systemLocales = Localization.getLocales()

const systemLocale = Localization.getLocales()[0]
const systemLocaleTag = systemLocale?.languageTag ?? fallbackLocale
const resources = { ar, en, ko, es, fr, ja, hi }
const supportedTags = Object.keys(resources)

// Checks to see if the device locale matches any of the supported locales
// Device locale may be more specific and still match (e.g., en-US matches en)
const systemTagMatchesSupportedTags = (deviceTag: string) => {
const primaryTag = deviceTag.split("-")[0]
return supportedTags.includes(primaryTag)
}

const pickSupportedLocale: () => Localization.Locale | undefined = () => {
return systemLocales.find((locale) => systemTagMatchesSupportedTags(locale.languageTag))
}

const locale = pickSupportedLocale()

export let isRTL = false

// Need to set RTL ASAP to ensure the app is rendered correctly. Waiting for i18n to init is too late.
if (locale?.languageTag && locale?.textDirection === "rtl") {
I18nManager.allowRTL(true)
isRTL = true
} else {
I18nManager.allowRTL(false)
}

export const initI18n = async () => {
i18n = i18next.use(initReactI18next)
i18n.use(initReactI18next)

await i18n.init({
resources: {
ar,
en,
"en-US": en,
ko,
es,
fr,
ja,
hi,
},
lng: fallbackLocale,
resources,
lng: locale?.languageTag ?? fallbackLocale,
fallbackLng: fallbackLocale,
interpolation: {
escapeValue: false,
},
})

if (Object.prototype.hasOwnProperty.call(i18n.languages, systemLocaleTag)) {
// if specific locales like en-FI or en-US is available, set it
await i18n.changeLanguage(systemLocaleTag)
} else {
// otherwise try to fallback to the general locale (dropping the -XX suffix)
const generalLocale = systemLocaleTag.split("-")[0]
if (Object.prototype.hasOwnProperty.call(i18n.languages, generalLocale)) {
await i18n.changeLanguage(generalLocale)
} else {
await i18n.changeLanguage(fallbackLocale)
}
}

return i18n
}

// handle RTL languages
export const isRTL = systemLocale?.textDirection === "rtl"
I18nManager.allowRTL(isRTL)
I18nManager.forceRTL(isRTL)

/**
* Builds up valid keypaths for translations.
*/
Expand Down
7 changes: 4 additions & 3 deletions boilerplate/app/i18n/translate.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
import { TOptions } from "i18next"
import { i18n, TxKeyPath } from "./i18n"
import i18n from "i18next"
import type { TOptions } from "i18next"
import { TxKeyPath } from "./i18n"

/**
* Translates text.
* @param {TxKeyPath} key - The i18n key.
* @param {i18n.TOptions} options - The i18n options.
* @param {TOptions} options - The i18n options.
* @returns {string} - The translated text.
* @example
* Translations:
Expand Down
4 changes: 2 additions & 2 deletions boilerplate/app/models/Episode.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,14 +27,14 @@ const episode = EpisodeModel.create(data)
test("publish date format", () => {
expect(episode.datePublished.textLabel).toBe("Jan 20, 2022")
expect(episode.datePublished.accessibilityLabel).toBe(
'demoPodcastListScreen:accessibility.publishLabel {"date":"Jan 20, 2022"}',
"demoPodcastListScreen:accessibility.publishLabel",
)
})

test("duration format", () => {
expect(episode.duration.textLabel).toBe("42:58")
expect(episode.duration.accessibilityLabel).toBe(
'demoPodcastListScreen:accessibility.durationLabel {"hours":0,"minutes":42,"seconds":58}',
"demoPodcastListScreen:accessibility.durationLabel",
)
})

Expand Down
46 changes: 34 additions & 12 deletions boilerplate/app/utils/formatDate.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,26 +2,48 @@
// If you import with the syntax: import { format } from "date-fns" the ENTIRE library
// will be included in your production bundle (even if you only use one function).
// This is because react-native does not support tree-shaking.
import type { Locale } from "date-fns"
import format from "date-fns/format"
import parseISO from "date-fns/parseISO"
import ar from "date-fns/locale/ar-SA"
import ko from "date-fns/locale/ko"
import en from "date-fns/locale/en-US"
import { i18n } from "@/i18n"
import { type Locale } from "date-fns/locale"
import { format } from "date-fns/format"
import { parseISO } from "date-fns/parseISO"
import i18n from "i18next"

type Options = Parameters<typeof format>[2]

const getLocale = (): Locale => {
const locale = i18n.language.split("-")[0]
return locale === "ar" ? ar : locale === "ko" ? ko : en
let dateFnsLocale: Locale
export const loadDateFnsLocale = () => {
const primaryTag = i18n.language.split("-")[0]
switch (primaryTag) {
case "en":
dateFnsLocale = require("date-fns/locale/en-US").default
break
case "ar":
dateFnsLocale = require("date-fns/locale/ar").default
break
case "ko":
dateFnsLocale = require("date-fns/locale/ko").default
break
case "es":
dateFnsLocale = require("date-fns/locale/es").default
break
case "fr":
dateFnsLocale = require("date-fns/locale/fr").default
break
case "hi":
dateFnsLocale = require("date-fns/locale/hi").default
break
case "ja":
dateFnsLocale = require("date-fns/locale/ja").default
break
default:
dateFnsLocale = require("date-fns/locale/en-US").default
break
}
}

export const formatDate = (date: string, dateFormat?: string, options?: Options) => {
const locale = getLocale()
const dateOptions = {
...options,
locale,
locale: dateFnsLocale,
}
return format(parseISO(date), dateFormat ?? "MMM dd, yyyy", dateOptions)
}
2 changes: 1 addition & 1 deletion boilerplate/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@
"@react-navigation/native-stack": "^6.0.2",
"@shopify/flash-list": "^1.6.4",
"apisauce": "3.0.1",
"date-fns": "^2.30.0",
"date-fns": "^4.1.0",
"expo": "~51.0.8",
"expo-application": "~5.9.1",
"expo-build-properties": "~0.12.1",
Expand Down

0 comments on commit 31428b7

Please sign in to comment.