التدويل (i18n)
يريد MegaShop دخول السوق الياباني والعالمي، لكن جميع صفحاته بالصينية فقط. لا تستطيع Alice رؤية الأسعار بالين في اليابان، والمستخدمون اليابانيون لا يستطيعون العثور على المنتجات بأسمائها اليابانية. يحتاج Charlie إلى i18n لتمكين MegaShop من دعم التبديل بين الصينية والإنجليزية واليابانية، مع تنسيق الأسعار تلقائياً حسب العملة.
1. ما ستتعلمه
- تكوين وحدة @nuxtjs/i18n: قائمة اللغات، اللغة الافتراضية، استراتيجية URL لـ SEO
- إدارة ملفات الترجمة: ترجمات محملة بتكاسل + تنسيق JSON
- واجهة برمجة التشغيل: useI18n() / t() / localePath() / switchLocalePath()
- SEO وi18n: وسوم hreflang + عناوين URL للغات البديلة + خرائط مواقع متعددة اللغات
- MegaShop: دعم الصينية/الإنجليزية/اليابانية + عملات متعددة USD/JPY/CNY
2. قصة حقيقية لمهندس معماري
(1) نقطة الألم: قيود اللغة الواحدة على العولمة
موقع MegaShop بالصينية فقط، لذلك لا يستطيع المستخدمون اليابانيون فهمه، والمستخدمون الأمريكيون لا يجدون المنتجات بأسمائها الإنجليزية. جميع الأسعار معروضة باليوان، وعندما ترى Alice في أمريكا "¥2,999" لا تعرف إن كان بالين أم باليوان.
(2) حل @nuxtjs/i18n
وحدة Nuxt i18n تفعّل تلقائياً دعم تعدد اللغات لكل صفحة، واستراتيجية URL الخاصة بها صديقة لـ SEO:
/en/products/123 → صفحة إنجليزية، سعر بالدولار
/ja/products/123 → صفحة يابانية، سعر بالين
/zh/products/123 → صفحة صينية، سعر باليوان
(3) الفوائد: تغطية السوق العالمي
بعد ثلاثة أشهر، زادت الزيارات من اليابان بنسبة 200٪ وزيارات أمريكا بنسبة 150٪؛ وسوم hreflang مكّنت Google من فهرسة كل نسخة لغية بشكل صحيح.
3. سير عمل معالجة اللغات في i18n
(1) سير عمل اكتشاف اللغة → التحميل → العرض
flowchart TB
A[طلب المستخدم] --> B{اكتشاف اللغة}
B -->|Cookie| C[استخدام اللغة المحفوظة]
B -->|عنوان المتصفح| D[مطابقة Accept-Language]
B -->|الافتراضي| E[استخدام defaultLocale: en]
C --> F[تحميل ملف الترجمة]
D --> F
E --> F
F --> G[تعيين بادئة URL]
G --> H[عرض الصفحة باللغة المحددة]
H --> I[إنشاء وسوم hreflang]
```
---
## 4. تكوين @nuxtjs/i18n
### (1) التثبيت والتكوين الأساسي
```bash
npm install @nuxtjs/i18n
(1) ▶ مثال: تكوين وحدة i18n
// nuxt.config.ts
export default defineNuxtConfig({
modules: ['@nuxtjs/i18n'],
i18n: {
locales: [
{ code: 'en', name: 'English', file: 'en.json', currency: 'USD' },
{ code: 'zh', name: 'Chinese', file: 'zh.json', currency: 'CNY' },
{ code: 'ja', name: 'Japanese', file: 'ja.json', currency: 'JPY' }
],
defaultLocale: 'en',
lazy: true,
langDir: 'locales/',
strategy: 'prefix_except_default', // / للإنجليزية، /zh/...، /ja/...
detectBrowserLanguage: {
useCookie: true,
cookieKey: 'i18n-locale',
redirectOn: 'root'
}
}
})
الناتج:
// التنفيذ ناجح
(2) مقارنة استراتيجيات URL
| الاستراتيجية | URL الإنجليزية | URL الصينية | فعالية SEO |
|---|---|---|---|
| prefix_except_default | /products | /zh/products | ✅ مُوصى بها |
| prefix | /en/products | /zh/products | ✅ متطابقة |
| prefix_and_default | /en/products + /products | /zh/products | ⚠️ مكررة |
| no_prefix | /products | /products | ❌ لا يمكن التمييز |
4. إدارة ملفات الترجمة
(1) ▶ مثال: هيكل ملفات الترجمة
locales/
├── en.json # الإنجليزية
├── zh.json # الصينية
└── ja.json # اليابانية
الناتج:
التنفيذ ناجح
(2) ▶ مثال: ملف الترجمة الإنجليزية
{
"common": {
"home": "Home",
"products": "Products",
"cart": "Cart",
"search": "Search",
"signIn": "Sign In",
"signOut": "Sign Out"
},
"product": {
"addToCart": "Add to Cart",
"outOfStock": "Out of Stock",
"reviews": "{count} reviews",
"price": "Price: {amount} {currency}",
"freeShipping": "Free shipping on orders over {amount} USD"
},
"cart": {
"empty": "Your cart is empty",
"total": "Total: {amount}",
"checkout": "Proceed to Checkout",
"items": "{count} items"
}
}
الناتج:
{
"common": {
"home": "Home",
"products": "Products",
"cart": "Cart",
"search": "Search",
"signIn": "Sign In",
"signOut": "Sign Out"
},
"product": {
"addToCart": "Add to Cart",
"outOfStock": "Out of Stock",
"reviews": "{count} reviews",
"price": "Price: {amount} {currency}",
"freeShipping": "Free shipping on orders over {amount} USD"
},
"cart": {
"empty": "Your cart is empty",
"total": "Total: {amount}",
"checkout": "Proceed to Ch
(3) ▶ مثال: ملف الترجمة الصينية
{
"common": {
"home": "Home Page",
"products": "Products",
"cart": "Shopping Cart",
"search": "Search",
"signIn": "Sign In",
"signOut": "Sign Out"
},
"product": {
"addToCart": "Add to Cart",
"outOfStock": "Out of stock",
"reviews": "{count} reviews",
"price": "Price: {amount} {currency}",
"freeShipping": "Free shipping on orders of {amount} yuan or more"
},
"cart": {
"empty": "Your cart is empty",
"total": "Total: {amount}",
"checkout": "Proceed to Checkout",
"items": "{count} items"
}
}
الناتج:
{
"common": {
"home": "Home Page",
"products": "Products",
"cart": "Shopping Cart",
"search": "Search",
"signIn": "Sign In",
"signOut": "Sign Out"
},
"product": {
"addToCart": "Add to Cart",
"outOfStock": "Out of stock",
"reviews": "{count} reviews",
"price": "Price: {amount} {currency}",
"freeShipping": "Free shipping on orders of {amount} yuan or more"
},
"cart": {
"empty": "Your cart is empty",
"total": "Total: {amount}",
"checkout": "Proceed to Checkout",
"items": "{count} items"
}
}
5. واجهة برمجة التشغيل
(1) ▶ مثال: استخدام useI18n في مكوّن
<template>
<div>
<h1>{{ t('common.products') }}</h1>
<button>{{ t('product.addToCart') }}</button>
<p>{{ t('product.reviews', { count: 1200 }) }}</p>
<p>{{ t('product.price', { amount: formattedPrice, currency: currentCurrency }) }}</p>
</div>
</template>
<script setup lang="ts">
const { t, locale, locales } = useI18n()
// الحصول على العملة الحالية بناءً على اللغة
const currentCurrency = computed(() => {
const current = locales.value.find(l => l.code === locale.value)
return current?.currency || 'USD'
})
const formattedPrice = computed(() => {
return new Intl.NumberFormat(locale.value, {
style: 'currency',
currency: currentCurrency.value
}).format(299.99)
})
</script>
الناتج:
// التنفيذ ناجح
(2) ▶ مثال: تنقل تبديل اللغة
<!-- components/LanguageSwitcher.vue -->
<template>
<div class="language-switcher">
<NuxtLink
v-for="loc in availableLocales"
:key="loc.code"
:to="switchLocalePath(loc.code)"
:class="{ active: loc.code === locale }"
>
{{ loc.name }}
</NuxtLink>
</div>
</template>
<script setup lang="ts">
const { locale, locales, t } = useI18n()
const switchLocalePath = useSwitchLocalePath()
const availableLocales = computed(() =>
(locales.value as any[]).filter(l => l.code !== locale.value)
)
</script>
الناتج:
// التنفيذ ناجح
(3) ▶ مثال: ترجمة المسار localePath
<template>
<nav>
<NuxtLink :to="localePath('/')">{{ t('common.home') }}</NuxtLink>
<NuxtLink :to="localePath('/products')">{{ t('common.products') }}</NuxtLink>
<NuxtLink :to="localePath('/cart') }}">{{ t('common.cart') }}</NuxtLink>
</nav>
</template>
<script setup lang="ts">
const localePath = useLocalePath()
</script>
الناتج:
// التنفيذ ناجح
6. SEO وi18n
(1) الإنشاء التلقائي لوسوم hreflang
يُنشئ @nuxtjs/i18n تلقائياً وسم hreflang لكل صفحة:
<!-- مُنشأ تلقائياً بواسطة وحدة i18n -->
<link rel="alternate" hreflang="en" href="https://megashop.com/products/123" />
<link rel="alternate" hreflang="zh" href="https://megashop.com/zh/products/123" />
<link rel="alternate" hreflang="ja" href="https://megashop.com/ja/products/123" />
<link rel="alternate" hreflang="x-default" href="https://megashop.com/products/123" />
(1) ▶ مثال: خريطة موقع متعددة اللغات
// nuxt.config.ts - تكامل i18n + sitemap
export default defineNuxtConfig({
modules: ['@nuxtjs/i18n', '@nuxtjs/sitemap'],
i18n: {
locales: [
{ code: 'en', name: 'English', file: 'en.json' },
{ code: 'zh', name: 'Chinese', file: 'zh.json' },
{ code: 'ja', name: 'Japanese', file: 'ja.json' }
],
defaultLocale: 'en'
},
sitemap: {
hostname: 'https://megashop.com',
i18n: {
locales: ['en', 'zh', 'ja'],
defaultLocale: 'en'
}
}
})
الناتج:
// التنفيذ ناجح
(2) قائمة فحص SEO لـ i18n
| عنصر الفحص | المتطلب | طريقة التحقق |
|---|---|---|
| وسم hreflang | ربط متبادل بين النسخ اللغوية | عرض الكود المصدري |
| URL الأساسي | ربط بنسخة اللغة الحالية | عرض الكود المصدري |
| x-default | ربط باللغة الافتراضية | عرض الكود المصدري |
| عناوين URL تحتوي بادئات لغوية | /zh/ /ja/ | شريط عنوان المتصفح |
| معدل اكتمال الترجمة | > 95٪ | فحص آلي |
7. مثال شامل: MegaShop—تعدد اللغات والعملات
// composables/useLocalizedPrice.ts
export function useLocalizedPrice() {
const { locale, locales } = useI18n()
const currentCurrency = computed(() => {
const current = (locales.value as any[]).find(l => l.code === locale.value)
return current?.currency || 'USD'
})
function formatPrice(amount: number): string {
return new Intl.NumberFormat(locale.value, {
style: 'currency',
currency: currentCurrency.value
}).format(amount)
}
// تحويل السعر الأساسي بالدولار إلى العملة المحلية (مبسط)
const rates: Record<string, number> = { USD: 1, CNY: 7.25, JPY: 149.5 }
function convertFromUSD(usdAmount: number): number {
const rate = rates[currentCurrency.value] || 1
return usdAmount * rate
}
function formatFromUSD(usdAmount: number): string {
return formatPrice(convertFromUSD(usdAmount))
}
return { currentCurrency, formatPrice, convertFromUSD, formatFromUSD }
}
<!-- components/ProductCard.vue -->
<template>
<div class="product-card">
<img :src="product.image" :alt="product.name" />
<h3>{{ product.name }}</h3>
<p>{{ formatFromUSD(product.price) }}</p>
<button>{{ t('product.addToCart') }}</button>
<p>{{ t('product.reviews', { count: product.reviewCount }) }}</p>
</div>
</template>
<script setup lang="ts">
const { t } = useI18n()
const { formatFromUSD } = useLocalizedPrice()
defineProps<{ product: any }>()
</script>
❓ أسئلة شائعة
lazy: true وlazy: false؟lazy: true يحمّل ملفات الترجمة عند الطلب (فقط عند تبديل اللغة)، مما يقلل حجم المحتوى فوق الطي. lazy: false يحمّل جميع اللغات دفعة واحدة؛ التبديل فوري لكن المحتوى فوق الطي يتحمل ببطء أكبر. نوصي بـ lazy: true.Intl.NumberFormat للتنسيق، ونفّذ تحويل العملات في Composable. useLocalizedPrice في MegaShop يغلف كليهما.ProductTranslation)؛ ملفات الترجمة تتولى نصوص واجهة المستخدم فقط. API يرجع الترجمة المناسبة بناءً على اللغة الحالية.📖 ملخص
- وحدة @nuxtjs/i18n تدعم التحميل الكسول، استراتيجيات URL لـ SEO، واكتشاف لغة المتصفح
- ملفات الترجمة تستخدم تنسيق JSON وتدعم متغيرات الاستيفاء {count}/{amount}
- useI18n().t() يترجم النص، localePath() يُنشئ مسارات محلية، وswitchLocalePath() يبدل اللغات
- إنشاء تلقائي لوسوم hreflang، دعم خريطة موقع متعددة اللغات، وفهرسة صحيحة لكل نسخة لغوية عبر Google
- دعم العملات المتعددة باستخدام Intl.NumberFormat + Composable؛ بيانات المنتجات تُخزن بعدة لغات في قاعدة البيانات
📝 تمارين
- تمرين أساسي (الصعوبة: ⭐): ثبّت @nuxtjs/i18n، كوّن دعم الصينية والإنجليزية، ونفّذ ترجمة أساسية لواجهة المستخدم
- تمرين متقدم (الصعوبة: ⭐⭐): أضف دعم اللغة اليابانية + مبدل اللغة + ترجمة مسار localePath، وتحقق من وسم hreflang
- تحدٍ (الصعوبة: ⭐⭐⭐): نفّذ Composable باسم
useLocalizedPriceلدعم التحويل والتنسيق التلقائي لثلاث عملات—USD وCNY وJPY—واعرض الأسعار بالعملة المناسبة على صفحة المنتج بناءً على اللغة.
---|



