Skip to content

i18n

یک موتور بین‌المللی‌سازی مستقل از فریم‌ورک: هسته‌ای کوچک و واکنشی (I18nCore) با یک singleton سراسری اختیاری (createI18n / useI18n). هیچ‌چیز اینجا به DOM دست نمی‌زند: هرطور می‌خواهید به رابط کاربری گره‌اش بزنید.

ts
import { createI18n, useI18n } from 'ranuts/i18n';

از ranuts/utils هم دوباره صادر می‌شود. اگر تنها به i18n نیاز دارید از ranuts/i18n وارد کنید: این نقطه ورودی فقط موتور و دو کمک‌کننده‌اش را دارد، نه هرچه که بسته فراخِ utils ممکن است با خود بیاورد.

کاربرد

ts
import { createI18n, useI18n } from 'ranuts/i18n';

createI18n({
  messages: {
    en: { 'hero.title': 'Hello, {name}', 'nav.docs': 'Docs' },
    zh: { 'hero.title': '你好,{name}', 'nav.docs': '文档' },
  },
  fallbackLocale: 'en',
  persist: true,
  detectNavigator: true,
});

const i18n = useI18n()!;
i18n.t('hero.title', { name: 'Ada' }); // "Hello, Ada"
i18n.setLocale('zh');
i18n.t('hero.title', { name: 'Ada' }); // "你好,Ada"

واژه‌نامه‌ها تخت‌اند: t() مستقیم messages[locale][key] را می‌جوید، پس کلیدها رشته‌های عینی‌اند مثل 'hero.title'، نه شیءهای تودرتو.

زبان آغازین

یک بار در سازنده و به این ترتیب حل می‌شود:

  1. انتخاب ذخیره‌شده در localStorage (تنها وقتی persist روشن باشد و فقط اگر آن زبان واژه‌نامه داشته باشد)
  2. config.locale
  3. زبان‌های مرورگر (تنها وقتی detectNavigator روشن باشد)
  4. fallbackLocale

گام ۳ از resolveLocale می‌گذرد که به‌جای تنها navigator.language، کل فهرست مرتب navigator.languages را می‌خواند: خواننده‌ای که انتخاب اولش میان واژه‌نامه‌های شما نیست، به‌جای سقوط مستقیم به زبان جایگزین، انتخاب دومش را می‌گیرد.

درج مقدار

t(key, params) جای‌نگهدارهای {param} را در یک گذر از چپ به راست جایگزین می‌کند و از قرارداد رشته‌های قالب در format! زبان Rust، str.format پایتون و String.Format دات‌نت پیروی می‌کند:

ورودیخروجی
{{یک { عینی
}}یک } عینی
{name}params.name که به متن تبدیل شده
{name} بدون چنین پارامتریدست‌نخورده می‌ماند، پس جای‌نگهدارِ سرگردان دیده می‌شود نه اینکه خالی بماند

یک { / } تنها، یا گروهی با فاصله مثل { x }، جای‌نگهدار نیست و عیناً بیرون داده می‌شود؛ پس CSS و JSON و تکه‌های کد درون یک پیام سالم رد می‌شوند. برای پیچیدن یک مقدار در آکولاد عینی، جفت بیرونی را دو برابر کنید: {{{name}}}.

واژه‌نامه‌های تایپ‌دار

شکل واژه‌نامه‌تان را به‌عنوان آرگومان نوع بدهید تا هر فراخوانی t() در زمان کامپایل بررسی شود. بدون آن، کلیدی که نامش عوض شده یا غلط تایپ شده بی‌صدا به «خودِ کلید را رسم کن» تنزل می‌کند: کاربر جایی که باید جمله‌ای باشد agentModelFirstDownlaod می‌بیند، و تا آن لحظه هیچ‌چیز شکست نمی‌خورد.

ts
interface Messages {
  save: string;
  cancel: string;
}

const i18n = createI18n<Messages>({
  messages: {
    en: { save: 'Save', cancel: 'Cancel' },
    'zh-CN': { save: '保存' }, // هنوز در حال ترجمه — اشکالی ندارد
  },
  fallbackLocale: 'en',
});

i18n.t('save'); // ok
i18n.t('saev'); // خطای کامپایل

useI18n<Messages>()?.t('cancel'); // همان نوع را دوباره بدهید تا بررسی برقرار بماند

سه نکته این را از «صرفاً موجود» به «واقعاً به‌دردبخور» تبدیل می‌کند:

  1. هر زبان Partial است. ترجمه‌ای که در جریان است حالت عادی ماجراست؛ زبان جایگزین آنچه را یک زبان هنوز پر نکرده پوشش می‌دهد.
  2. نوع از آرگومان نوع می‌آید، هرگز از داده. messages در NoInfer پیچیده شده، پس زبان‌هایی با مجموعه‌کلیدهای متفاوت نمی‌توانند TypeScript را وادار کنند اشتراک آن‌ها را استنتاج کند. وگرنه کلیدی که تنها زبان جایگزین تعریفش کرده در هر محل فراخوانی رد می‌شد و ترجمه‌ای ناتمام به‌جای بازگشت در زمان اجرا، بیلد را می‌شکست.
  3. interface هم کار می‌کند، نه فقط type. قید StringValues<T> است ({ [K in keyof T]: string }) نه Record<string, string>، چون TypeScript امضای اندیس ضمنی را تنها به نام‌های مستعار نوع می‌دهد: قید گذاشتن به شیوه بدیهی، همه مصرف‌کننده‌ها را وادار می‌کرد واژه‌نامه‌شان را به type بازنویسی کنند.

نگذاشتن آرگومان نوع، رفتار بی‌تایپ را دقیقاً همان‌طور نگه می‌دارد: MessageDict پیش‌فرض همان Record<string, string> است که keyof آن string می‌شود.

پیکربندی

میدانتوضیحنوعپیش‌فرض
localeزبان آغازین. وقتی persist روشن باشد، انتخاب ذخیره‌شده بر آن می‌چربدstring-
fallbackLocaleزبانی که وقتی کلیدی در زبان فعال نباشد به کار می‌رودstring'en'
messagesزبان ← کلید ← رشتهLocaleMessages{}
persistزبان فعال را در localStorage نگه می‌داردbooleanfalse
storageKeyکلید localStorage وقتی persist روشن استstring'ran-locale'
detectNavigatorزبان آغازین را از ترجیح‌های زبانی مرورگر برمی‌داردbooleanfalse

API

createI18n

singleton سراسری را می‌سازد و ثبت می‌کند.

پارامترها

پارامترتوضیحنوعپیش‌فرض
configپیکربندی را ببینیدI18nConfig{}

بازگشت

آرگومانتوضیحنوع
i18nنمونه تازهI18nCore

useI18n

نمونه سراسری فعال را برمی‌گرداند، یا null وقتی هیچ نمونه‌ای ساخته نشده باشد.

بازگشت

آرگومانتوضیحنوع
i18nنمونه فعال یا nullI18nCore | null

I18nCore

عضوتوضیح
t(key, params?)ترجمه می‌کند؛ به زبان جایگزین و سپس به خود کلید برمی‌گردد
locale / getLocale()زبان فعال
setLocale(locale)زبان را عوض می‌کند، (در صورت روشن بودن) ذخیره و خبر می‌دهد. اگر تغییری نباشد کاری نمی‌کند
addMessages(locale, dict)واژه‌نامه‌ای را در یک زبان ادغام می‌کند و در صورت نبود می‌سازدش
getMessages(locale?)واژه‌نامه یک زبان، یا {}
availableLocalesزبان‌هایی که واژه‌نامه ثبت‌شده دارند
onChange(fn)تغییر زبان را مشترک می‌شود؛ تابعی برای لغو اشتراک برمی‌گرداند
destroy()همه مشترکان را برمی‌دارد

SSR

امن است. همه دسترسی‌ها به localStorage و navigator محافظت شده‌اند، پس ساختن یک نمونه هنگام رندر سمت سرور به config.locale یا fallbackLocale می‌رسد.

منتشرشده تحت مجوز MIT.