Files
simple-react-app-kit/packages/i18n/src/runtime/i18n-provider.tsx
T
Maofeng 9e3f1d6b59 feat(i18n): add Lingui runtime and development tooling
- add SSR-safe I18nProvider, Translate, locale hooks, catalog activation, and typed runtime APIs

- add consumer-owned Lingui configuration helpers and a CLI for locale creation, extraction, compilation, and project discovery

- add catalog read/write services plus a Vite development API plugin scoped to each consuming application

- add the portal-based I18nDevtool, message editor, dark-theme tracking, and standalone Message Studio

- document the consumer workflow and cover runtime, configuration, catalog, CLI, and Devtool behavior with tests
2026-07-29 21:23:00 +08:00

123 lines
2.8 KiB
TypeScript

import * as React from "react"
import { setupI18n, type AllMessages, type I18n } from "@lingui/core"
import {
I18nProvider as LinguiI18nProvider,
type I18nProviderProps as LinguiI18nProviderProps,
} from "@lingui/react"
import { I18nRuntimeContext } from "./context"
import type { LocaleDefinition, LocaleDirection, LocaleInput } from "./types"
const RTL_LANGUAGES = new Set([
"ar",
"ckb",
"dv",
"fa",
"he",
"ku",
"ps",
"sd",
"ug",
"ur",
"yi",
])
function inferLocaleDirection(locale: string): LocaleDirection {
const language = locale.split("-")[0]?.toLowerCase()
return language && RTL_LANGUAGES.has(language) ? "rtl" : "ltr"
}
function normalizeLocales(
locale: string,
locales: readonly LocaleInput[] | undefined,
catalogs: AllMessages
): readonly LocaleDefinition[] {
const inputs =
locales && locales.length > 0
? locales
: Array.from(new Set([locale, ...Object.keys(catalogs)]))
const definitions = inputs.map<LocaleDefinition>((input) => {
const definition = typeof input === "string" ? { locale: input } : input
return {
...definition,
direction:
definition.direction ?? inferLocaleDirection(definition.locale),
label: definition.label ?? definition.locale,
}
})
if (!definitions.some((definition) => definition.locale === locale)) {
return [
...definitions,
{
direction: inferLocaleDirection(locale),
label: locale,
locale,
},
]
}
return definitions
}
export interface I18nProviderProps extends Pick<
LinguiI18nProviderProps,
"children" | "defaultComponent"
> {
catalogs?: AllMessages
i18n?: I18n
locale: string
locales?: readonly LocaleInput[]
missing?: ConstructorParameters<typeof I18n>[0]["missing"]
}
export function I18nProvider({
catalogs = {},
children,
defaultComponent,
i18n: providedI18n,
locale,
locales,
missing,
}: I18nProviderProps) {
const i18n = React.useMemo(() => {
const instance =
providedI18n ??
setupI18n({
locale,
messages: catalogs,
missing,
})
if (providedI18n) {
providedI18n.load(catalogs)
providedI18n.activate(locale)
}
return instance
}, [catalogs, locale, missing, providedI18n])
const normalizedLocales = React.useMemo(
() => normalizeLocales(locale, locales, catalogs),
[catalogs, locale, locales]
)
const contextValue = React.useMemo(
() => ({
catalogs,
locale,
locales: normalizedLocales,
}),
[catalogs, locale, normalizedLocales]
)
return (
<I18nRuntimeContext.Provider value={contextValue}>
<LinguiI18nProvider i18n={i18n} defaultComponent={defaultComponent}>
{children}
</LinguiI18nProvider>
</I18nRuntimeContext.Provider>
)
}