Files
simple-react-app-kit/packages/ui/src/components/i18n.tsx
T
Maofeng ae1798f6f4 feat(ui): add framework-neutral localization support
- add a lightweight UI i18n provider with translation and locale contexts\n- localize accessibility labels across breadcrumb, carousel, dialog, pagination, sheet, sidebar, spinner, and toast components\n- integrate calendar locale data without coupling UI components to Lingui\n- publish tree-shakeable catalogs for eight common locales
2026-07-30 14:47:51 +08:00

157 lines
3.6 KiB
TypeScript

"use client"
import * as React from "react"
import type { Locale as DayPickerLocale } from "react-day-picker"
export type MessageValues = Record<string, unknown>
export interface MessageDescriptor {
id: string
message: string
comment?: string
values?: MessageValues
}
export type TranslateFunction = (
descriptor: MessageDescriptor,
values?: MessageValues
) => string
export type CalendarLocale = Partial<DayPickerLocale>
type UiMessages = Record<string, MessageDescriptor>
export const uiMessages = {
breadcrumb: /* i18n */ {
id: "ui.breadcrumb.label",
message: "Breadcrumb",
},
close: /* i18n */ {
id: "ui.common.close",
message: "Close",
},
closeToast: /* i18n */ {
id: "ui.toast.close",
message: "Close notification",
},
loading: /* i18n */ {
id: "ui.common.loading",
message: "Loading",
},
more: /* i18n */ {
id: "ui.common.more",
message: "More",
},
morePages: /* i18n */ {
id: "ui.pagination.more",
message: "More pages",
},
next: /* i18n */ {
id: "ui.pagination.next",
message: "Next",
},
nextPage: /* i18n */ {
id: "ui.pagination.nextPage",
message: "Go to next page",
},
nextSlide: /* i18n */ {
id: "ui.carousel.nextSlide",
message: "Next slide",
},
pagination: /* i18n */ {
id: "ui.pagination.label",
message: "Pagination",
},
previous: /* i18n */ {
id: "ui.pagination.previous",
message: "Previous",
},
previousPage: /* i18n */ {
id: "ui.pagination.previousPage",
message: "Go to previous page",
},
previousSlide: /* i18n */ {
id: "ui.carousel.previousSlide",
message: "Previous slide",
},
sidebar: /* i18n */ {
id: "ui.sidebar.title",
message: "Sidebar",
},
sidebarDescription: /* i18n */ {
id: "ui.sidebar.description",
message: "Displays the mobile sidebar.",
},
toggleSidebar: /* i18n */ {
id: "ui.sidebar.toggle",
message: "Toggle sidebar",
},
} satisfies UiMessages
const DEFAULT_LOCALE = "en-US"
const defaultTranslate: TranslateFunction = (descriptor) => descriptor.message
const TranslateContext =
React.createContext<TranslateFunction>(defaultTranslate)
const LocaleContext = React.createContext(DEFAULT_LOCALE)
const CalendarLocaleContext = React.createContext<CalendarLocale | undefined>(
undefined
)
export interface I18nProviderProps {
calendarLocale?: CalendarLocale
children: React.ReactNode
locale?: string
t?: TranslateFunction
}
export function I18nProvider({
calendarLocale,
children,
locale = DEFAULT_LOCALE,
t = defaultTranslate,
}: I18nProviderProps) {
return (
<TranslateContext.Provider value={t}>
<LocaleContext.Provider value={locale}>
<CalendarLocaleContext.Provider value={calendarLocale}>
{children}
</CalendarLocaleContext.Provider>
</LocaleContext.Provider>
</TranslateContext.Provider>
)
}
export function useTranslate(): TranslateFunction {
return React.useContext(TranslateContext)
}
export function useMessage(
descriptor: MessageDescriptor,
values?: MessageValues
) {
const translate = useTranslate()
return React.useMemo(
() => translate(descriptor, values),
[descriptor, translate, values]
)
}
export interface LocalizedTextProps {
message: MessageDescriptor
values?: MessageValues
}
export function LocalizedText({ message, values }: LocalizedTextProps) {
return useMessage(message, values)
}
export function useLocale(): string {
return React.useContext(LocaleContext)
}
export function useCalendarLocale(): CalendarLocale | undefined {
return React.useContext(CalendarLocaleContext)
}