关于 hebrew-i18n
面向 Web 开发者的希伯来语国际化技能,提供 RTL 布局、本地化格式和复数规则的实施指导。内容涵盖 React、Vue、Next.js、Angular 等框架及双向文本处理。
主要功能
- 01根元素语言与方向设置
- 02希伯来语复数规则处理
- 03日期、数字和货币格式
- 04RTL CSS 与双向文本指导
- 05多框架集成示例
适用场景
- 01为 Web 应用添加希伯来语支持
- 02排查 RTL 布局和混合文字显示问题
- 03实现希伯来语复数与地区格式
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ skills-il/ localization --skill hebrew-i18n安装后在 Claude Code 中输入/hebrew-i18n确认已加载。
包含的文件
除 SKILL.md 外,安装时一起下载的参考资料和脚本。
evidence.jsonmetadata.jsonoptimization-log.jsonSKILL_HE.mdreferences/bidi.mdpluralization.mdscripts/generate_i18n.py详细介绍
希伯来语国际化
说明
第 1 步:设置国际化框架
Direction 应放在根 <html> 元素上,而不是包装用的 div 上。在运行时切换语言的单页应用中,每次区域设置变化时都要更新它:
function applyLocale(locale) {
document.documentElement.lang = locale;
document.documentElement.dir = locale === 'he' ? 'rtl' : 'ltr';
}
React(react-intl):
import { useEffect } from 'react';
import { IntlProvider } from 'react-intl';
import heMessages from './locales/he.json';
function App() {
useEffect(() => applyLocale('he'), []);
return (
<IntlProvider locale="he" messages={heMessages}>
{/* App content */}
</IntlProvider>
);
}
React(react-i18next): i18next 默认不使用 ICU 复数语法。它会根据 Intl.PluralRules 选择键后缀,因此希伯来语需要 _one、_two 和 _other 键:
{
"days_one": "יום אחד",
"days_two": "יומיים",
"days_other": "{{count}} ימים"
}
按 t('days', { count }) 调用它。自 i18next v24 起,当缺少 Intl.PluralRules 时不再有回退,因此只有英语风格的 _one/_other 能解析,而 _two 形式会静默地永不出现。i18next 文档指出,React Native 的 Hermes 引擎缺少 Intl.PluralRules;请在你的目标平台上检查 typeof Intl.PluralRules,如果它是 undefined,就添加一个 polyfill(例如 intl-pluralrules)。
Vue(vue-i18n): vue-i18n 的复数形式用竖线分隔,而不是 ICU。vue-i18n 11.x(当前发布线)按位置而不是按 Intl.PluralRules 选择情况:三种情况被读作 zero | one | other,因此没有规则时 t('days', 1) 会输出第二种情况。对于希伯来语,请注册一条规则,将 1、2 以及其他所有计数映射到这三种情况,并按 t('days', count) 调用:
import { createI18n } from 'vue-i18n';
// cases: one | two | other
const hePlural = (choice) => (choice === 1 ? 0 : choice === 2 ? 1 : 2);
const i18n = createI18n({
legacy: false, // 11.x defaults to legacy mode, which ignores pluralRules
locale: 'he',
fallbackLocale: 'en',
pluralRules: { he: hePlural }, // in legacy mode the option is pluralizationRules
messages: { he: heMessages, en: enMessages }, // he: { days: 'יום אחד | יומיים | {count} ימים' }
});
// t('days', 2) -> "יומיים"
由 Intl.PluralRules 自动选择是 vue-i18n 主分支上的一项破坏性变更,并未包含在 11.x 版本中。
Next.js App Router(next-intl):
// src/i18n/routing.ts
import { defineRouting } from 'next-intl/routing';
export const routing = defineRouting({
locales: ['he', 'en'],
defaultLocale: 'he',
});
// src/proxy.ts (named middleware.ts before Next.js 16)
import createMiddleware from 'next-intl/middleware';
import { routing } from './i18n/routing';
export default createMiddleware(routing);
export const config = {
matcher: '/((?!api|trpc|_next|_vercel|.*\\..*).*)'
};
// src/i18n/request.ts (supplies locale and messages to getTranslations / useTranslations)
import { hasLocale } from 'next-intl';
import { getRequestConfig } from 'next-intl/server';
import { routing } from './routing';
import { notFound } from 'next/navigation';
import * as rootParams from 'next/root-params';
export default getRequestConfig(async ({ locale }) => {
if (!locale) {
const paramValue = await rootParams.locale();
if (hasLocale(routing.locales, paramValue)) {
locale = paramValue;
} else {
notFound();
}
}
return {
locale,
messages: (await import(`../../messages/${locale}.json`)).default
};
});
next/root-params 从 Next.js 16.3 起默认可用。在更早的 Next.js 版本中,必须通过 next.config.ts 中的 experimental.rootParams 启用它。请在 next.config.ts 中使用 next-intl/plugin 的 createNextIntlPlugin 包装器接入 request.ts。
// src/app/[locale]/layout.tsx
import { NextIntlClientProvider } from 'next-intl';
import { getLocale } from 'next-intl/server';
export default async function LocaleLayout({ children }) {
const locale = await getLocale();
return (
<html lang={locale} dir={locale === 'he' ? 'rtl' : 'ltr'}>
<body>
<NextIntlClientProvider>{children}</NextIntlClientProvider>
</body>
</html>
);
}
由 Server Component 渲染时,NextIntlClientProvider 会从 request.ts 继承区域设置和消息,因此无需调用 getMessages() 并传入。
Angular:
// angular.json -- add Hebrew locale
"i18n": {
"sourceLocale": "en",
"locales": {
"he": "src/locale/messages.he.xlf"
}
}
还要在构建选项中设置 "localize": true(或区域设置 ID 数组),否则 CLI 不会生成希伯来语构建。
第 2 步:希伯来语复数形式
希伯来语有三种国际化框架必须处理的复数类别:
| 类别 | 希伯来语术语 | 计数 | 示例 |
|---|---|---|---|
| one(单数) | יחיד | 1,以及小于 1 的小数 | פריט אחד(一个项目) |
| two(双数) | זוגי | 2 | שני פריטים(两个项目)——使用特殊的双数形式 |
| other(复数) | רבים | 0、3+、1.0 及以上的小数 | 5 פריטים(5 个项目) |
注意:较旧的 many 类别(用于 20 或 100 这样的整数)已在 Unicode CLDR 42 版(2022 年)中移除。现代希伯来语复数规则只使用 one、two 和 other,整数会解析为 other。不要添加 many 分支;在任何当前的 ICU/CLDR 运行时上它都会是死代码。另请注意,Intl.PluralRules('he').select(0.5) 返回 one,因此请为分数使用单独的措辞("חצי שעה"),而不是把它们传给计数消息。
完整规则和边缘情况见 references/pluralization.md。
ICU MessageFormat 模式(react-intl、next-intl):
{count, plural,
one {פריט אחד}
two {שני פריטים}
other {{count} פריטים}
}
常见的希伯来语复数模式:
| 单数 (יחיד) | 双数 (זוגי) | 复数 (רבים) | 模式 |
|---|---|---|---|
| יום(天) | יומיים(2 天) | ימים(天) | 不规则双数 |
| שעה(小时) | שעתיים(2 小时) | שעות(小时) | 阴性双数 -תיים |
| חודש(月) | חודשיים(2 个月) | חודשים(月) | 阳性双数 -יים |
| שבוע(周) | שבועיים(2 周) | שבועות(周) | 阳性双数 -יים |
| שנה(年) | שנתיים(2 年) | שנים(年) | 不规则双数 |
第 3 步:日期与时间格式化
以色列日期格式: 日在前、月在后,永远不是 MM/DD/YYYY。注意 Intl.DateTimeFormat('he-IL') 渲染的短日期使用点号分隔(DD.MM.YYYY),例如 04.03.2026;如果你确实需要斜杠,请手动格式化各部分。
// Using Intl.DateTimeFormat
const formatter = new Intl.DateTimeFormat('he-IL', {
year: 'numeric',
month: 'long',
day: 'numeric',
});
// Output: "4 במרץ 2026"
// Short format
const shortFormatter = new Intl.DateTimeFormat('he-IL', {
year: 'numeric',
month: '2-digit',
day: '2-digit',
});
// Output: "04.03.2026" (he-IL uses dot separators, DD.MM.YYYY, not slashes)
// Hebrew calendar, no library needed
new Intl.DateTimeFormat('he-IL-u-ca-hebrew', { dateStyle: 'long' }).format(new Date(2026, 2, 4));
// Output: "ט״ו באדר תשפ״ו"
希伯来语星期与月份名称:
| 星期 | 希伯来语 | 缩写 |
|---|---|---|
| 星期日 | יום ראשון | א׳ |
| 星期一 | יום שני | ב׳ |
| 星期二 | יום שלישי | ג׳ |
| 星期三 | יום רביעי | ד׳ |
| 星期四 | יום חמישי | ה׳ |
| 星期五 | יום שישי | ו׳ |
| 星期六 | שבת | ש׳ |
以色列工作周:星期日至星期四(而非星期一至星期五)。
希伯来历日期: 仅用于展示时,上文中的 -u-ca-hebrew locale 扩展就足够了。当你需要节日表、parashat hashavua 或希伯来历中的日期运算时,请使用 hebcal 之类的库。
第 4 步:数字与货币格式化
// Israeli number format: 1,000.50 (comma for thousands, dot for decimal)
const numFormatter = new Intl.NumberFormat('he-IL');
numFormatter.format(1234567.89); // "1,234,567.89"
// Israeli Shekel currency
const currFormatter = new Intl.NumberFormat('he-IL', {
style: 'currency',
currency: 'ILS',
});
currFormatter.format(1234.50); // looks like "1,234.50 ₪"
货币字符串不是纯 ASCII:它在数字之前和 ₪ 符号之前包含不可见的 RIGHT-TO-LEFT MARK 字符(U+200F),并使用 NO-BREAK SPACE(U+00A0)而非普通空格。快照测试、=== 比较、CSV 导出以及对该字符串调用 parseFloat 都会失败。仅在文本比较时用 s.replace(/[\u200E\u200F]/g, '') 去除这些标记。切勿将格式化后的输出解析回数字:即使去除了标记,parseFloat('1,234.50 ₪') 也会返回 1,因为它在分组逗号处停止。请保留原始数字,或用 formatToParts() 读取各部分。
以色列特有的数字格式:
| 类型 | 格式 | 示例 |
|---|---|---|
| 电话(手机) | 05X-XXXXXXX | 054-1234567 |
| 电话(座机) | 0X-XXXXXXX | 02-6234567 |
| Teudat Zehut(身份证) | XXXXXXXXX | 123456782(9 位数字,含校验位) |
| 邮政编码 | XXXXXXX | 6100000(7 位数字) |
| 货币 | X,XXX.XX ₪ | 1,234.50 ₪(Intl 输出会添加不可见的方向标记) |
第 5 步:使用逻辑属性的 RTL CSS
对于面向 i18n 的布局,请始终使用 CSS 逻辑属性:
/* Base RTL setup */
html[lang="he"] {
direction: rtl;
}
/* Logical properties -- work in both LTR and RTL */
.card {
margin-inline-start: 1rem; /* right margin in RTL */
padding-inline-end: 0.5rem; /* left padding in RTL */
border-inline-start: 3px solid blue; /* right border in RTL */
text-align: start; /* right in RTL, left in LTR */
}
/* Flexbox automatically reverses in RTL */
.nav {
display: flex;
gap: 1rem;
/* No direction override needed -- flex respects dir attribute */
}
Tailwind CSS RTL(v3.3+,包括 v4):
Tailwind 提供逻辑属性工具类和 RTL 变体:
<div class="ms-4 me-2 ps-3 pe-1 text-start">
</div>
<div class="ltr:ml-4 rtl:mr-4 ltr:text-left rtl:text-right">
</div>
| 物理属性(避免) | 逻辑属性(推荐) | RTL 行为 |
|---|---|---|
ml-4 | ms-4 | RTL 中为右外边距 |
mr-4 | me-4 | RTL 中为左外边距 |
pl-4 | ps-4 | RTL 中为右内边距 |
pr-4 | pe-4 | RTL 中为左内边距 |
text-left | text-start | RTL 中右对齐 |
text-right | text-end | RTL 中左对齐 |
rounded-l-lg | rounded-s-lg | RTL 中为右侧圆角 |
border-r-2 | border-e-2 | RTL 中为左边框 |
第 6 步:双向文本与表单输入
详细模式与边界情况请参见 references/bidi.md。
<p dir="rtl">
הזמנה מספר <span dir="ltr">ORD-12345</span> אושרה
</p>
<p dir="rtl">
המשתמש <bdi>JohnDoe123</bdi> נרשם
</p>
<textarea dir="auto" name="comment"></textarea>
<label for="email">אימייל</label>
<input id="email" type="email" dir="ltr">
dir="auto" 使用第一个强方向性字符,因此以英文名开头的希伯来语注释会得到从左到右的基准方向。将其用于未知的用户输入,而在方向已知时使用显式的 dir(电子邮件、电话、URL、银行卡和身份证字段为 dir="ltr")。
以色列应用中常见的双向文本场景:
| 内容类型 | 方向 | 处理方式 |
|---|---|---|
| 希伯来语文本 | RTL | 默认,无需特殊处理 |
| 希伯来语中的英文文本 | LTR | 包裹在 dir="ltr" span 中 |
| 电话号码 | LTR | 包裹在 dir="ltr" 或 <bdo> 中 |
| URL 和电子邮件 | LTR | 包裹在 dir="ltr" span 中 |
| 希伯来语与代码混排 | 两者 | 使用 unicode-bidi: isolate |
| 货币金额 | LTR 数字 + RTL 符号 | 使用 Intl.NumberFormat |
| 用户输入的文本 | 未知 | 在字段和显示该文本的元素上使用 dir="auto" |
第 7 步:框架特定的 RTL 集成
使用 Tailwind 的 Next.js App Router: 此代码片段仅演示 Tailwind 实用工具类。请保留第 1 步布局中的 NextIntlClientProvider 和语言区域处理,否则调用 useTranslations 的客户端组件将会失败。
// app/[locale]/layout.tsx
export default async function LocaleLayout({ children, params }) {
const { locale } = await params;
return (
<html lang={locale} dir={locale === 'he' ? 'rtl' : 'ltr'}>
<body className="font-sans">
{/* Tailwind logical utilities auto-flip based on dir attribute */}
<main className="ms-4 me-4 text-start">{children}</main>
</body>
</html>
);
}
// components/NavBar.tsx -- uses rtl: variant for icon flipping
export function NavBar() {
return (
<nav className="flex items-center gap-4">
<button className="ltr:rotate-0 rtl:rotate-180">
<ChevronRight /> {/* Flips to point left in RTL */}
</button>
</nav>
);
}
使用 Vuetify 的 Vue:
import { createVuetify } from 'vuetify';
const vuetify = createVuetify({
locale: {
locale: 'he',
fallback: 'en',
rtl: { he: true },
},
});
Angular Material:
import { BidiModule } from '@angular/cdk/bidi';
@NgModule({
imports: [BidiModule],
})
export class AppModule {}
// In template:
// <div dir="rtl">...</div>
React Native: 对于希伯来语,调用 I18nManager.allowRTL(true) 和 I18nManager.forceRTL(true)。该设置会被持久化,且仅在应用重新加载后生效,因此应在启动时切换方向或提示重新加载,而不是期望立即翻转。
示例
示例 1:为现有 React 应用添加希伯来语
用户说:“我需要为我的 React 应用添加希伯来语支持”
结果:设置 react-intl(ICU 复数)或 react-i18next(_one/_two/_other 键),创建 he.json 消息文件,在语言区域变化时于 document.documentElement 上设置 lang 和 dir="rtl",用翻译键替换硬编码字符串,为自由文本输入添加 dir="auto",并处理混合内容的双向文本。
示例 2:格式化以色列日期和货币
用户说:“如何为以色列用户格式化日期和价格?”
结果:使用带 he-IL 语言区域的 Intl.DateTimeFormat 来格式化以色列格式的日期(日在月之前;短格式以点分隔,即 DD.MM.YYYY),使用 he-IL-u-ca-hebrew 格式化希伯来历日期,使用带 ILS 货币的 Intl.NumberFormat 格式化谢克尔,并在比较或导出格式化字符串之前去除不可见的方向标记。
示例 3:修复双向文本问题
用户说:“我的希伯来语界面中电话号码和英文文本显示不正确”
结果:将电话号码包裹在 dir="ltr" 的 span 中,使用 unicode-bidi: isolate 隔离英文内容,对用户生成的内容使用 bdi 元素,并用希伯来语/英语混合字符串进行测试。
示例 4:希伯来语复数形式
用户说:“我的希伯来语翻译显示了错误的复数形式”
结果:按照你的框架所期望的语法实现三个类别(单数/双数/其他)(react-intl/next-intl 使用 ICU,vue-i18n 使用管道符加复数规则,i18next 使用键后缀),处理时间单位的双数形式,并在 React Native 上添加 Intl.PluralRules polyfill。
示例 5:为 Next.js App Router 添加希伯来语
用户说:“我想为我的 Next.js App Router 项目添加希伯来语和英语支持”
结果:安装 next-intl,创建 [locale] 路由段,添加 i18n/routing.ts、i18n/request.ts 和 proxy.ts(Next.js 16 之前为 middleware.ts),用 createNextIntlPlugin 包装 next.config.ts,为希伯来语区域在 <html> 上设置 dir="rtl",创建使用 ICU 复数语法的 he.json 和 en.json,并使用 Tailwind 逻辑实用工具类(ms-*、me-*、text-start)实现支持 RTL 的样式。
捆绑资源
脚本
scripts/generate_i18n.py:生成希伯来语 i18n 消息文件。按照每个框架自己的语法(react-intl 和 next-intl 使用 ICU,vue-i18n 使用管道符分隔,并结合第 1 步中的希伯来语复数规则)搭建包含希伯来语复数形式的翻译 JSON 结构。运行:python scripts/generate_i18n.py --help
参考资料
references/pluralization.md:完整的希伯来语复数规则,涵盖常见词类(时间、数量、物体)的单数、双数和复数形式,当前 CLDR 规则文本,ICU MessageFormat 模式,以及小数和希伯来语数词一致的边缘情况。references/bidi.md:希伯来语应用的双向文本处理模式。Unicode 双向算法概述、HTML dir 属性用法、CSS unicode-bidi 属性、框架特定的双向解决方案,以及希伯来语/英语/数字混合内容的常见陷阱。
注意事项
- Agent 可能只在 body 元素或包装 div 上设置
dir="rtl",但必须在<html>层级设置 RTL 方向,才能正确影响滚动条、默认文本对齐和 CSS 逻辑属性。在 SPA 中,当 locale 变化时更新document.documentElement.dir。 - 希伯来语复数形式很复杂:有单数、双数(部分名词)和复数形式。Agent 可能实现简单的英语式单数/复数(1 与多),从而遗漏双数形式(例如 yomayim = 2 天)。
- Agent 会为每个框架编写 ICU 复数形式字符串。vue-i18n 期望以管道符分隔的 case(并且在 11.x 上还需要希伯来语复数规则),i18next 期望
_one/_two/_other键后缀;在这两者中,ICU 字符串要么按字面渲染,要么永远不做复数化。 - 希伯来语的 i18n 键不应使用英文文本作为键(例如
t('Submit')),因为希伯来语翻译可能短得多或长得多,从而破坏布局。请使用语义化键(例如t('form.submit'))。 - Agent 常常忘记在 RTL 中反转图标位置:箭头、chevron 和进度指示器应水平镜像。在希伯来语 UI 中,"next" 箭头应指向左,而不是右。
- Tailwind
space-x-*的行为因版本而异。在 v3 中它设置物理左/右边距且不会翻转,因此在 RTL 下需添加space-x-reverse,或改用gap-*。在 v4 中它设置margin-inline-start/end,已遵循dir,因此为 RTL 添加space-x-reverse会双重反转间距。两者中都优先使用gap-*搭配 flex/grid。
故障排查
错误:"Plural forms not matching Hebrew grammar"
原因:i18n 框架未配置希伯来语的三类复数规则,或消息使用了框架无法解析的语法
解决方案:希伯来语使用 one/two/other(不像英语只有 one/other)。在 react-intl 和 next-intl 中使用带 two 类别的 ICU MessageFormat;在 vue-i18n 中使用带希伯来语 pluralRules 函数的 one | two | other 管道符;在 i18next 中使用 _one/_two/_other 键。在 React Native 上添加 Intl.PluralRules polyfill。
错误:"Date showing MM/DD/YYYY instead of DD/MM/YYYY"
原因:日期格式化使用了 en-US locale 而不是 he-IL
解决方案:使用 new Intl.DateTimeFormat('he-IL'),或用 he-IL locale 配置你的日期库。绝不要为以色列用户假定美式日期格式。
错误:"Formatted price fails an equality test or parses to the wrong number"
原因:Intl.NumberFormat('he-IL', { style: 'currency', currency: 'ILS' }) 会插入 U+200F 方向标记、不换行空格和分组逗号
解决方案:比较和存储原始数字。对于文本比较,用 /[\u200E\u200F]/g 去除标记,并将 U+00A0 规范化为空格。不要对格式化字符串使用 parseFloat;它会在第一个逗号处停止。
错误:"Numbers appear reversed in RTL context"
原因:RTL 方向影响数字显示顺序
解决方案:希伯来语中的数字始终为 LTR。对数字内容使用 dir="ltr",或依赖 Unicode 双向算法,它默认能正确处理数字。问题通常出在周围的标点,而不是数字本身。