互客鱼 返回主站

运行您的工作区

语言和国际化

互客鱼附带 130+ 种语言的翻译,开箱即用,英语、西班牙语、法语和土耳其语 端到端覆盖,长尾(德语、印地语、孟加拉语、阿拉伯语、希伯来语、中文、日语、 韩语、越南语、每种流行的欧洲/亚洲/非洲语言,以及 RTL 脚本)覆盖 UI chrome — 按钮、导航、表单、状态药丸。给定区域设置尚未翻译的每个键自动回退到英语源, 因此当个别翻译者贡献长尾时,UI 永远不会中断。

添加新语言只是放下一个文件

LocaleResolver::supported() 服务通过在请求时扫描 lang/*.json 自动发现区域设置。要添加新语言,放下一个 lang/<code>.json 文件(例如瑞典语的 lang/sv.json) — 无需代码更改、无需迁移、无需服务重启。下次页面加载会拾取它,选择器模态框 列出它,SetLocale 中间件接受 ?locale=sv,它出现在枚举支持区域 设置的每个 API 响应中。

选择器从 App\Services\I18n\LocaleCatalog::ENTRIES 提取元数据 (本机名称、英文名称、旗帜 emoji、RTL 标志)— 130+ 种流行语言的精选列表。 如果您为目录中没有的代码提供 JSON 文件,它仍然有效 — 选择器回退到代码本身、 🌐 地球 emoji 和 LTR 方向。贡献目录元数据只是升级视觉效果。

从右到左语言

目录将阿拉伯语、希伯来语、波斯语、乌尔都语、普什图语、信德语、迪维希语、 意第绪语和维吾尔语标记为 RTL。渲染管道相应镜像:

  • Blade 根模板(管理 SPA 的 resources/views/app.blade.php、 营销网站的 resources/views/marketing/_layout.blade.php、 潜在客户捕获电子邮件的 resources/views/emails/leads/captured.blade.php) 在活动区域设置的目录条目具有 rtl => true 时在 <html> 上发出 dir="rtl"
  • Tailwind v4 逻辑属性携带布局:每个 ms-/me-/ps-/pe-/start-/end-/text-start/text-end 实用程序类根据文档方向自动翻转。代码库专门使用逻辑属性;物理 ml-/mr-/pl-/pr-/left-/right- 类在早期的 codemod 中被清除。
  • Radix <DirectionProvider> 包装 React 应用在 resources/js/app.tsx,因此每个 Radix 原语(DropdownMenu、Popover、 Tooltip、Select、Sheet、ContextMenu)获得正确的对齐 + 动画方向,无需每个组件代码。
  • useIsRtl() / useDirection() hooks 在 resources/js/lib/direction.ts 中从共享 i18n 目录读取当前区域 设置的 RTL 标志。当组件需要显式 JS 方向逻辑时使用它们。
  • 方向图标(后退、前进、chevrons)包装 <DirArrow direction="forward|back" /> 来自 resources/js/components/dir-icon.tsx,因此图标本身翻转。 方向装饰性的图标(paper-plane 发送、undo)使用 .flip-rtl CSS 实用程序而不是交换字形。
  • 访客小部件在启动时读取 init.agent.locale,如果区域设置是 RTL, 则在其 shadow root 上设置 dir="rtl" — 小部件内的每个 Tailwind 逻辑属性类然后像管理 SPA 一样翻转。
  • <Sidebar> 组件根据其方向将其 side prop 默认为视觉起始边缘(LTR 中的 side="left",RTL 中的 side="right"),因此 vanilla <Sidebar /> 始终固定到视觉起始。

tests/Feature/I18n/RtlDirTest.php 回归测试断言每个 RTL 区域设置在管理 Inertia 根、营销布局和潜在客户捕获电子邮件上产生 dir="rtl";LTR 区域设置相反产生 dir="ltr"

区域设置选择器模态框

管理 shell 和营销网站在您点击语言药丸时打开可搜索的 Dialog。列表显示 每个区域设置的本机名称 + 英文名称 + 旗帜,可按代码或名称过滤。两个表面上 相同的组件。有 130+ 个条目,旧的下拉菜单不可滚动 — 模态框扩展到数千种语言。

解析顺序

SetLocale 中间件在每个 web 请求的会话中间件之后运行,并遍历此优先级列表:

  1. 显式 ?locale=<slug> 查询(允许列表)。
  2. 经过身份验证的用户的 users.locale 列。
  3. pb_locale cookie(由 geo-banner 切换路径设置,为未授权访客跨会话持久化)。
  4. 浏览器的 Accept-Language 头(最高 q-value 获胜)。
  5. 来自 config/app.php 的应用程序默认值。

Geo-suggested 区域设置横幅

当 Cloudflare 的 CF-IPCountry 头映射到我们提供的区域设置且 访客的当前区域设置是其他内容时,会出现一个细长的横幅询问 “Switch to Español?” 横幅从不自动切换 — 惊喜 = 糟糕的 UX。 两个操作:

  • Switch to <language> — PATCHes /locale/switch。 登录时设置 users.locale 并始终设置 pb_locale cookie (1 年生命周期),然后用新语言重新加载。
  • — POSTs /locale/dismiss-suggestion, 设置 pb_locale_dismiss=1 cookie(180 天生命周期)。 横幅在该设备上永远不会再次出现。

抑制规则(服务器端,在 LocaleResolver::suggestionFor 中):

  1. 访客已经关闭 → null。
  2. 当前区域设置已经与建议匹配 → null。
  3. 没有 CF-IPCountry 头(本地开发、非 CF 部署)或值为 XX/T1 → null。
  4. 国家不在 COUNTRY_TO_LOCALE 中 → null。

国家 → 区域设置映射覆盖西班牙语拉丁美洲 + 西班牙(es)、法语欧洲 + 魁北克(fr)、土耳其(tr)。 其他国家没有建议。

横幅安装在管理 SPA(app-sidebar-layout)、每个 Inertia 营销页面(marketing-shell)上,并通过 Blade {{ __('Switch to :language?') }}resources/views/marketing/_layout.blade.php 中用于任何未来的 Blade-rendered 营销页面。

相同的 LocaleResolver 服务支持小部件。小部件传递在顶部添加一个 额外的步骤:智能体的 language_default — 管理员可以固定垂直特定的 语言,即使访客的浏览器不同意。

字符串在哪里

  • lang/{locale}.json — JSON 字典,由管理 React SPA、小部件、 营销 Blade 页面和邮件模板使用。英语源字符串充当键。
  • lang/{locale}/auth.phpvalidation.phppasswords.phppagination.php — Laravel 的命名空间 PHP 文件用于内置框架消息。
  • lang/_glossary.md — 术语锁,以便未来的字符串与之前的运行一致翻译。

在哪里添加翻译

每当您在代码中添加面向用户的字符串时:

  • 后端 Blade:包装为 English source
  • 管理 React:导入 hook 并调用 const { t } = useT();,然后 t('English source')
  • 小部件:core/i18n.ts 导入 t 并调用 t('English source')。 将键添加到 WidgetCopy::KEYS,以便服务器将其具体化到 /init 负载中。

翻译后的键添加到 lang/<locale>.json。 缺失的键静默回退到英文源 — UI 永远不会中断。tests/Feature/I18nTest::every supported locale ships a parseable JSON dictionary 回归测试断言每个发布的语言环境文件都是有效的 JSON, 具有非空字符串值;一个姊妹测试防止引入拼写错误的键(语言环境文件中任何在 en.json 中不存在的键都会使 CI 失败)。

添加新语言环境

  1. 放置一个 lang/<slug>.json 文件。仅此一项就使语言环境出现在选择器中, 并通过 SetLocale 中间件接受 ?locale=<slug>LocaleResolver::supported() 在下一个请求时自动发现该文件。
  2. (可选,但推荐)在 app/Services/I18n/LocaleCatalog::ENTRIES 中添加一个条目, 包含语言环境的 native 名称、english 名称、flag emoji 和 rtl 标志。没有条目,选择器仍然工作 — 它只是将语言环境代码显示为标签和 🌐 地球仪。
  3. (可选)将 lang/en/{auth,validation,passwords,pagination}.php 复制到 lang/<slug>/ 如果您想要 Fortify / 验证消息被翻译。没有这些文件, Laravel 会回退到英语。
  4. 运行 php artisan test --filter=I18nTest--filter=LocaleResolverTest 以确认新语言环境不会引入幻象键。
  5. (可选)更新 lang/_glossary.md 添加一列,以便未来的翻译保持一致性。

启用语言环境不需要代码更改。之前的 LocaleResolver::SUPPORTED 常量已被删除; 选择器、验证规则和中间件都从 LocaleResolver::supported() 读取,它返回自动发现的集合。

每用户与每访客语言环境

管理员和操作员/settings/locale 设置他们的首选语言。 该选择持久化到 users.locale 并应用于每个后续请求,包括代表他们发送的电子邮件。

访客 默认看到智能体的 language_default。如果智能体没有固定语言, 小部件回退到访客的浏览器语言环境,然后回退到英语。小部件中没有语言环境切换器 — 这是一个深思熟虑的决定,因此访客体验与管理员配置的内容相匹配。

覆盖率

每个面向客户的表面 — 访客小部件、营销网站(主页、定价、工作原理、集成、变更日志、隐私、条款)、 管理 SPA(每个客户管理员和平台管理员页面,包括智能体自定义、来源、精选答案、知识、游乐场、行为、 CTA、潜在客户、对话、实验、计费、集成、分析、工作流、每个设置选项卡)、身份验证 + 入职流程、 交易电子邮件和验证消息 — 都通过 useT()__() 连接。 英文源(lang/en.json)是事实来源,目前拥有约 2,300 个键。

每种语言环境的覆盖率各不相同。enesfrtr 完全端到端翻译(en.json 中的每个键都有一个本地化值)。其他约 130 个自动发现的语言环境文件 以最常用的 UI chrome 翻译开始(约 130 个键:按钮、导航、表单、状态药丸),并随着翻译人员的贡献而扩展。 给定语言环境尚未翻译的键通过 Laravel 的标准 JSON 键行为回退到英文源,因此 UI 永远不会中断; 用户在长尾填充时看到部分翻译。

随时检查语言环境的覆盖率:

node -e 'const fs=require("fs"); const en=JSON.parse(fs.readFileSync("lang/en.json")); const m=JSON.parse(fs.readFileSync("lang/<code>.json")); const total=Object.keys(en).length; const translated=Object.keys(en).filter(k=>k in m && m[k]!==en[k]).length; console.log(`${translated}/${total} = ${Math.round(translated/total*100)}% covered`);'

resources/views/documentation/pages/ 下的文档页面根据政策保持英语 — 翻译密集的技术文章是一个文案项目,而不是工程。如果需要特定交易的翻译文档,请在 Kanban 板上跟踪后续。

浏览器 SEO + 可访问性

  • 营销布局、管理 Inertia 根目录和交易电子邮件上的 <html lang> 属性在每次渲染时反映已解析的语言环境。
  • 验证消息、密码重置电子邮件和 Fortify 身份验证消息都流经 Laravel 的翻译器 — 它们会自动获取用户的语言环境。
  • 小部件的首次绘制已经使用正确的语言 — 服务器在 /init 时具体化 agent.copy, 因此在翻译副本水合之前永远不会有英语闪现的时刻。