fix(i18n): standardize locale formatting for Intl APIs
This commit is contained in:
@@ -39,7 +39,6 @@ export function LanguageSwitcher() {
|
||||
const { i18n, t } = useTranslation()
|
||||
const user = useAuthStore((s) => s.auth.user)
|
||||
const currentLanguage = normalizeInterfaceLanguage(i18n.language)
|
||||
console.log('Current interface language: %s', currentLanguage)
|
||||
const handleChangeLanguage = useCallback(
|
||||
async (code: string) => {
|
||||
await i18n.changeLanguage(code)
|
||||
|
||||
@@ -51,6 +51,7 @@ import {
|
||||
formatQuotaWithCurrency,
|
||||
getCurrencyLabel,
|
||||
} from '@/lib/currency'
|
||||
import { toIntlLocale } from '@/i18n/languages'
|
||||
import { formatTimestampToDate } from '@/lib/format'
|
||||
import { truncateText } from '@/lib/utils'
|
||||
|
||||
@@ -310,7 +311,7 @@ function BalanceCell({ channel }: { channel: Channel }) {
|
||||
const withSuffix = (value: string) =>
|
||||
tokenSuffix && value !== '-' ? `${value}${tokenSuffix}` : value
|
||||
|
||||
const locale = i18n.resolvedLanguage || i18n.language
|
||||
const locale = toIntlLocale(i18n.resolvedLanguage || i18n.language)
|
||||
const balanceFormatOptions = {
|
||||
digitsLarge: 2,
|
||||
digitsSmall: 4,
|
||||
@@ -519,7 +520,7 @@ export function useChannelsColumns(
|
||||
const { t, i18n } = useTranslation()
|
||||
const { sensitiveVisible } = useChannels()
|
||||
const enableSelection = options.enableSelection ?? true
|
||||
const locale = i18n.resolvedLanguage || i18n.language
|
||||
const locale = toIntlLocale(i18n.resolvedLanguage || i18n.language)
|
||||
// The column definitions only depend on the translation function, the active
|
||||
// locale, and sensitive-data visibility. Memoizing keeps the array (and every
|
||||
// cell renderer reference) stable across unrelated re-renders, so react-table
|
||||
|
||||
@@ -31,6 +31,7 @@ import type {
|
||||
QuotaDataItem,
|
||||
DashboardFilters,
|
||||
} from '@/features/dashboard/types'
|
||||
import { toIntlLocale } from '@/i18n/languages'
|
||||
import { formatCompactNumber, formatNumber, formatQuota } from '@/lib/format'
|
||||
import { computeTimeRange } from '@/lib/time'
|
||||
import { cn } from '@/lib/utils'
|
||||
@@ -121,7 +122,7 @@ export function LogStatCards(props: LogStatCardsProps) {
|
||||
|
||||
const items = statCardsConfig.map((config) => {
|
||||
const rawValue = config.getValue(adaptedStats, timeRangeMinutes)
|
||||
const locale = i18n.resolvedLanguage || i18n.language
|
||||
const locale = toIntlLocale(i18n.resolvedLanguage || i18n.language)
|
||||
const formatted =
|
||||
config.key === 'quota'
|
||||
? {
|
||||
|
||||
@@ -47,6 +47,7 @@ import {
|
||||
TooltipProvider,
|
||||
TooltipTrigger,
|
||||
} from '@/components/ui/tooltip'
|
||||
import { toIntlLocale } from '@/i18n/languages'
|
||||
import { formatTimestampRelative, formatTimestampToDate } from '@/lib/format'
|
||||
import { cn } from '@/lib/utils'
|
||||
|
||||
@@ -411,7 +412,7 @@ function SystemInstancesList(props: SystemInstancesTableProps) {
|
||||
{formatTimestampRelative(
|
||||
instance.last_seen_at,
|
||||
'seconds',
|
||||
i18n.language
|
||||
toIntlLocale(i18n.language)
|
||||
)}
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
|
||||
@@ -38,6 +38,7 @@ import type {
|
||||
SystemTask,
|
||||
SystemTaskStatus,
|
||||
} from '@/features/system-settings/types'
|
||||
import { toIntlLocale } from '@/i18n/languages'
|
||||
import { formatTimestampRelative, formatTimestampToDate } from '@/lib/format'
|
||||
import { cn } from '@/lib/utils'
|
||||
|
||||
@@ -185,7 +186,7 @@ function SystemTasksTable(props: SystemTasksTableProps) {
|
||||
{formatTimestampRelative(
|
||||
task.updated_at,
|
||||
'seconds',
|
||||
i18n.language
|
||||
toIntlLocale(i18n.language)
|
||||
)}
|
||||
</TableCell>
|
||||
<TableCell
|
||||
|
||||
Vendored
+26
-1
@@ -41,8 +41,33 @@ export function normalizeInterfaceLanguage(value?: string | null): string {
|
||||
normalized = 'zhCN'
|
||||
}
|
||||
|
||||
console.log('Normalized interface language: %s -> %s', value, normalized)
|
||||
return INTERFACE_LANGUAGE_OPTIONS.some((lang) => lang.code === normalized)
|
||||
? normalized
|
||||
: 'en'
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert an interface language code (the values i18next uses, such as `zhCN` /
|
||||
* `zhTW`) into a valid BCP-47 locale tag that the `Intl.*` APIs accept.
|
||||
*
|
||||
* `new Intl.NumberFormat('zhCN')` throws `RangeError: Invalid language tag`, so
|
||||
* any locale derived from `i18n.language` / `i18n.resolvedLanguage` MUST be run
|
||||
* through this before it reaches an `Intl` constructor. Unknown values fall back
|
||||
* to `undefined`, which makes `Intl` use the runtime default locale.
|
||||
*/
|
||||
export function toIntlLocale(value?: string | null): string | undefined {
|
||||
if (!value) return undefined
|
||||
switch (value) {
|
||||
case 'zhCN':
|
||||
return 'zh-CN'
|
||||
case 'zhTW':
|
||||
return 'zh-TW'
|
||||
default:
|
||||
break
|
||||
}
|
||||
try {
|
||||
return Intl.getCanonicalLocales(value)[0]
|
||||
} catch {
|
||||
return undefined
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user