本地化

套件里的每一条内置文案(77 个标签集共 468 条标签:aria-label、屏幕阅读器播报、不是你写的按钮文字、占位符)都由一个 provider 翻译。挂载一次并传入语言包,每个 goodui 组件就会说那门语言;数字、日期和货币格式通过 Intl 跟随,阿拉伯语和希伯来语会得到正确的方向。从不设置它的应用保持英文和 en-US 格式,不会多加载任何内容。

安装

npx shadcn@latest add https://goodui.dev/r/locale.json https://goodui.dev/r/locale-tr.json

locale 落在 lib/goodui-locale.ts 里(每个带标签的组件都已依赖它,所以你通常已经有了)。语言包落在 lib/goodui-locales/<lang>.ts 里,不依赖其他任何东西;只安装你要发布的语言。

设置语言

app/layout.tsx
import { GooduiLocaleProvider } from "@/lib/goodui-locale"
import tr from "@/lib/goodui-locales/tr"

export default function RootLayout({ children }) {
  return (
    <html lang="tr">
      <body>
        <GooduiLocaleProvider lang="tr" labels={tr}>{children}</GooduiLocaleProvider>
      </body>
    </html>
  )
}

lang 是 BCP 47 标签,驱动套件里的每一个 Intl 格式("de-AT", "pt-BR", "en-GB" 都可以)。labels 是语言包。要做语言切换器,把标签放在 state 里,从一个懒加载 import 的映射里取语言包;provider 可以嵌套,所以某一个界面可以用另一种语言。

语言包

18 种语言,每种一个文件,覆盖每个组件的每一条标签。原生名称,就像切换器里显示的那样:

  • locale-tr: Türkçe
  • locale-de: Deutsch
  • locale-fr: Français
  • locale-es: Español
  • locale-it: Italiano
  • locale-pt-BR: Português (Brasil)
  • locale-nl: Nederlands
  • locale-pl: Polski
  • locale-ru: Русский
  • locale-hi: हिन्दी
  • locale-id: Bahasa Indonesia
  • locale-vi: Tiếng Việt
  • locale-th: ไทย
  • locale-ja: 日本語
  • locale-ko: 한국어
  • locale-zh-CN: 中文(简体)
  • locale-ar: العربية (RTL)
  • locale-he: עברית (RTL)

locale-en 也存在:它由组件生成,是其他所有语言包的校验基准。想在整个应用里修改英文标签时,导入它即可。

RTL

组件从 DOM 读取书写方向(逻辑属性、:dir(rtl)、翻转的图标),所以要你自己在文档上设置:<html lang="ar" dir="rtl">。provider 从不碰 <html>;它只从 lang 推导 dir(ar、he、fa、ur 为 RTL)给需要的组件,你也可以传 dir 覆盖。dirOf("ar") 已导出,供你自己的布局代码使用。

Intl 格式

会格式化数值的组件(number-flow、counter-up、stat-card、meter、sparkline-draw、data-grid、date-picker、number-input)接受 locale prop。不设置时,现在取 provider 的语言而不是 "en-US";显式传入的 prop 仍然优先。同样的格式化函数可以通过 hook 拿到:

import { useGooduiLocale } from "@/lib/goodui-locale"

const { lang, dir, formatNumber, formatDate, formatRelative, formatCurrency } = useGooduiLocale()

formatNumber(12345.6)                       // "12.345,6" in de
formatDate(booking.start)                   // "3 Kas 2026" in tr; pass Intl.DateTimeFormat options for other styles
formatRelative(-2, "hour")                  // "vor 2 Stunden"
formatCurrency(49, "EUR")                   // "49,00 €"

实例按语言和选项缓存,所以在 render 里调用也没问题。在 React 之外,createFormatters(lang) 返回同样的四个函数。

覆盖单条标签

任何组件上的 labels prop 仍然优先于语言包,所以单个实例可以显示不同的文案。要在整个应用里修改,展开语言包即可:

<Dialog labels={{ confirm: "Rezervasyonu iptal et" }} … />        {/* this dialog only */}

const labels = { ...tr, alert: { ...tr.alert, dismiss: "Kapat" } }   // every alert
<GooduiLocaleProvider lang="tr" labels={labels}>

带值的标签是函数,所以复数是真正的复数:results: (n) => plural("pl", n, { one: "1 wynik", few: `${n} wyniki`, many: `${n} wyników`, other: `${n} wyniku` })。plural() 随 lib/goodui-locale 一起提供,封装了 Intl.PluralRules。

添加语言

把 lib/goodui-locales/en.ts 复制为新文件并翻译其中的值;保留键和函数签名。仓库里的清单 lib/locales/labels-manifest.json 列出了每个键的类型、参数和使用位置(aria-label、屏幕阅读器、可见文本)。我们自己的构建会运行 node scripts/extract-labels.ts,它从组件重新生成 lib/goodui-locales/en.ts,一旦任何语言包缺少组件、键或参数就会失败,所以 18 个语言包永远不会落后于组件。欢迎提交新语言的 pull request。

试一试

每个 Core 组件的 Playground 都有语言选择:预览会用那种语言重新渲染(阿拉伯语和希伯来语为 RTL),打印出的代码片段也会包含 provider。任何预览页都接受 ?lang=tr(以及用来强制方向的 &dir=rtl),例如 /preview/c/pagination?lang=ar.

goodui Native

React Native 套件计划采用同样的语言包和清单(foundation 里的一个 LocaleProvider,相同的组件 id);在那之前,Native 组件通过 props 和每个界面的 labels 对象接收文案。