# Lingui
Use lingui for i18n in react native
# Install
# package.json
{
"name": "your-app",
"main": "expo-router/entry",
"version": "1.0.0",
"scripts": {
"lang:compile": "lingui compile --typescript",
"lang:extract": "lingui extract",
"lang:build": "npm run lang:extract && npm run lang:compile",
},
"dependencies": {
"@lingui/core": "^5.3.1",
"@lingui/react": "^5.3.1",
}
}
# lingui.config.js
import { defineConfig } from '@lingui/cli'
export default defineConfig({
sourceLocale: 'vi',
locales: ['vi', 'en'],
catalogs: [
{
path: '<rootDir>/locales/{locale}/messages',
include: ['src', 'modules'],
},
],
format: 'po',
})
# Setup
# i18n.ts
import { i18n as i18nCore } from '@lingui/core'
import { messages as enMessages } from '@/locales/en/messages'
import { messages as viMessages } from '@/locales/vi/messages'
export const localeMessages = {
[SupportedLocale.en]: enMessages,
[SupportedLocale.vi]: viMessages,
}
// biome-ignore lint/complexity/noForEach: <explanation>
Object.values(SupportedLocale).forEach((locale) => {
i18nCore.load(locale, localeMessages[locale])
})
export const i18n = i18nCore
# app/_layout.tsx
import { i18n } from '@/i18n'
import { I18nProvider } from '@lingui/react'
export default function RootLayout() {
const colorScheme = useColorScheme() ?? 'light'
return (
<QueryClientProvider client={queryClient}>
<ThemeColorProvider<ThemeToken, ColorSchemaName>
colorScheme={colorScheme}
themeModes={{
light: lightTheme,
dark: darkTheme,
}}
>
{/* Wrap i18n provider */}
<I18nProvider i18n={i18n} defaultComponent={Text}>
<SafeAreaProvider>
<GestureHandlerRootView>
<BottomSheetProvider>
<RootSiblingParent>
<InternetConnectionBanner />
<InitLayout />
</RootSiblingParent>
</BottomSheetProvider>
</GestureHandlerRootView>
</SafeAreaProvider>
</I18nProvider>
</ThemeColorProvider>
</QueryClientProvider>
)
}
# Usage
# Single text case
import { useLingui } from '@lingui/react/macro'
function DiscountCode({ tierDiscount }: { tierDiscount: VffDiscountCode }) {
const { t } = useLingui()
return (
<Text style={[a.text_sm, a.text_white, a.font_weight_700]}>
{t`Mã giảm giá ${tierDiscount}`}
</Text>
)
}
# React nodes as children
import { Trans } from '@lingui/react/macro'
function FooterNotes() {
return (
<Trans>
<Text style={[a.text_xs]}>
Lưu ý: <Text style={[a.text_xs, a.font_weight_700]}>Mã giảm giá</Text>{' '}
sẽ được áp dụng tại <Text style={[a.text_xs, a.font_weight_700]}>trang thanh toán của Shop</Text>{' '}
(khi bạn được chuyển hướng từ ứng dụng). Đừng quên sao chép mã này để không bỏ lỡ ưu đãi đặc biệt dành cho bạn.
</Text>
</Trans>
)
}
# Dynamic loading
The idea is to fetch remote translations from API and load them into i18n core
import { i18n as i18nCore } from '@lingui/core'
import { get, map } from 'lodash'
import { useEffect, useMemo } from 'react'
import { useGetAppSetting } from '@/features/cms/hooks/useGetAppSettings'
import { safeCall } from '@/helpers'
import { cache } from '@/utils/cache'
type SupportedLocale = 'en' | 'vi'
type LangItemId = string // id after build
export type RemoteTranslations = Record<SupportedLocale, Record<LangItemId, string>>
function loadTranslations(translations: RemoteTranslations) {
map(translations, (messsages, locale) => {
i18nCore.load(locale, messsages)
})
}
export function useRemoteTranslations() {
const { data: setting } = useFetchRemoteLangItems()
const apiTranslations = useMemo(() => {
return get(setting, 'display_config.i18n') as unknown as RemoteTranslations | undefined
}, [setting])
// init if cached
useEffect(() => {
safeCall(() => {
const cacheTranslations = cache('remote_translations')
if (cacheTranslations) {
loadTranslations(cacheTranslations)
}
})
}, [])
useEffect(() => {
if (apiTranslations) {
cache('remote_translations', apiTranslations)
loadTranslations(apiTranslations)
}
}, [apiTranslations])
}