mcpField
L
localization-engineer

localization-engineer

github.com/daffy0208开发38159 次安装44 周前更新
代码仓库

关于 localization-engineer

localization-engineer 是帮助开发者为应用加入国际化与本地化支持的技能,示例主要围绕 Next.js 和 next-intl。内容涵盖翻译管理、区域格式、语言切换及 RTL 布局。

主要功能

  1. 01配置 Next.js 国际化
  2. 02管理翻译文件与缺失翻译
  3. 03切换语言并检测区域
  4. 04格式化日期、数字和货币
  5. 05处理复数、变量和 RTL 布局
  6. 06针对多语言页面设置 SEO

适用场景

  1. 01为 Next.js 应用添加多语言内容
  2. 02按区域显示日期与货币
  3. 03支持阿拉伯语等 RTL 语言
  4. 04管理多语言产品的翻译和页面元数据

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/daffy0208/ai-dev-standards --skill localization-engineer

安装后在 Claude Code 中输入/localization-engineer确认已加载。

附带文件manifest.yamlREADME.md

详细介绍

本地化工程师技能

我可以帮助你构建支持完善国际化(i18n)和本地化(l10n)的多语言应用。

我能做什么

国际化:

  • 多语言文本内容
  • 日期/时间格式
  • 数字和货币格式
  • 从右到左(RTL)支持

本地化:

  • 翻译管理
  • 语言检测
  • 语言切换
  • 特定区域设置的内容

Next.js 国际化

使用 next-intl 进行设置
npm install next-intl
// i18n/request.ts
import { getRequestConfig } from 'next-intl/server'

export default getRequestConfig(async ({ locale }) => ({
  messages: (await import(`../messages/${locale}.json`)).default
}))
// middleware.ts
import createMiddleware from 'next-intl/middleware'

export default createMiddleware({
  locales: ['en', 'es', 'fr', 'de', 'ja'],
  defaultLocale: 'en'
})

export const config = {
  matcher: ['/((?!api|_next|.*\\..*).*)']
}
翻译文件
// messages/en.json
{
  "common": {
    "welcome": "Welcome",
    "loading": "Loading...",
    "error": "Something went wrong"
  },
  "navigation": {
    "home": "Home",
    "about": "About",
    "contact": "Contact"
  },
  "auth": {
    "login": "Log in",
    "logout": "Log out",
    "signUp": "Sign up",
    "emailPlaceholder": "Enter your email",
    "passwordPlaceholder": "Enter your password"
  }
}
// messages/es.json
{
  "common": {
    "welcome": "Bienvenido",
    "loading": "Cargando...",
    "error": "Algo salió mal"
  },
  "navigation": {
    "home": "Inicio",
    "about": "Acerca de",
    "contact": "Contacto"
  },
  "auth": {
    "login": "Iniciar sesión",
    "logout": "Cerrar sesión",
    "signUp": "Registrarse",
    "emailPlaceholder": "Ingrese su correo electrónico",
    "passwordPlaceholder": "Ingrese su contraseña"
  }
}

使用翻译

客户端组件
'use client'
import { useTranslations } from 'next-intl'

export function LoginForm() {
  const t = useTranslations('auth')

  return (
    <form>
      <input
        type="email"
        placeholder={t('emailPlaceholder')}
      />
      <input
        type="password"
        placeholder={t('passwordPlaceholder')}
      />
      <button>{t('login')}</button>
    </form>
  )
}
服务端组件
import { useTranslations } from 'next-intl'

export default function HomePage() {
  const t = useTranslations('common')

  return (
    <div>
      <h1>{t('welcome')}</h1>
    </div>
  )
}

语言切换器

'use client'
import { useLocale } from 'next-intl'
import { useRouter, usePathname } from 'next/navigation'

const languages = [
  { code: 'en', name: 'English', flag: '🇺🇸' },
  { code: 'es', name: 'Español', flag: '🇪🇸' },
  { code: 'fr', name: 'Français', flag: '🇫🇷' },
  { code: 'de', name: 'Deutsch', flag: '🇩🇪' },
  { code: 'ja', name: '日本語', flag: '🇯🇵' }
]

export function LanguageSwitcher() {
  const locale = useLocale()
  const router = useRouter()
  const pathname = usePathname()

  const switchLanguage = (newLocale: string) => {
    // Remove current locale from pathname
    const pathWithoutLocale = pathname.replace(`/${locale}`, '')
    // Navigate to new locale
    router.push(`/${newLocale}${pathWithoutLocale}`)
  }

  return (
    <select
      value={locale}
      onChange={(e) => switchLanguage(e.target.value)}
      className="px-4 py-2 border rounded"
    >
      {languages.map((lang) => (
        <option key={lang.code} value={lang.code}>
          {lang.flag} {lang.name}
        </option>
      ))}
    </select>
  )
}

日期和时间格式

'use client'
import { useFormatter } from 'next-intl'

export function FormattedDate({ date }: { date: Date }) {
  const format = useFormatter()

  return (
    <div>
      {/* Full date */}
      <p>{format.dateTime(date, { dateStyle: 'full' })}</p>

      {/* Short date */}
      <p>{format.dateTime(date, { dateStyle: 'short' })}</p>

      {/* Custom format */}
      <p>{format.dateTime(date, {
        year: 'numeric',
        month: 'long',
        day: 'numeric',
        hour: 'numeric',
        minute: 'numeric'
      })}</p>

      {/* Relative time */}
      <p>{format.relativeTime(date)}</p>
    </div>
  )
}

// Examples:
// en: "Monday, October 22, 2025"
// es: "lunes, 22 de octubre de 2025"
// ja: "2025年10月22日月曜日"

数字和货币格式

'use client'
import { useFormatter } from 'next-intl'

export function FormattedNumber({ value }: { value: number }) {
  const format = useFormatter()

  return (
    <div>
      {/* Number */}
      <p>{format.number(value)}</p>

      {/* Currency */}
      <p>{format.number(value, { style: 'currency', currency: 'USD' })}</p>

      {/* Percentage */}
      <p>{format.number(value / 100, { style: 'percent' })}</p>

      {/* Compact notation */}
      <p>{format.number(value, { notation: 'compact' })}</p>
    </div>
  )
}

// Examples:
// en: "1,234.56" "$1,234.56" "12%" "1.2K"
// de: "1.234,56" "1.234,56 $" "12 %" "1200"
// ja: "1,234.56" "$1,234.56" "12%" "1.2千"

复数形式

// messages/en.json
{
  "items": {
    "count": "{count, plural, =0 {No items} one {# item} other {# items}}"
  }
}
'use client'
import { useTranslations } from 'next-intl'

export function ItemCounter({ count }: { count: number }) {
  const t = useTranslations('items')

  return <p>{t('count', { count })}</p>
}

// count = 0: "No items"
// count = 1: "1 item"
// count = 5: "5 items"

RTL(从右到左)支持

// app/[locale]/layout.tsx
import { useLocale } from 'next-intl'

const rtlLanguages = ['ar', 'he', 'fa']

export default function LocaleLayout({ children }) {
  const locale = useLocale()
  const isRTL = rtlLanguages.includes(locale)

  return (
    <html lang={locale} dir={isRTL ? 'rtl' : 'ltr'}>
      <body>{children}</body>
    </html>
  )
}

RTL CSS:

/* Automatically flips for RTL */
.container {
  margin-inline-start: 1rem; /* Use logical properties */
  padding-inline-end: 1rem;
}

/* Manual RTL handling */
[dir='rtl'] .menu {
  left: auto;
  right: 0;
}

语言检测

// lib/detect-locale.ts

export function detectUserLocale(): string {
  // 1. Check URL parameter
  const urlParams = new URLSearchParams(window.location.search)
  const urlLocale = urlParams.get('lang')
  if (urlLocale) return urlLocale

  // 2. Check localStorage
  const savedLocale = localStorage.getItem('preferredLocale')
  if (savedLocale) return savedLocale

  // 3. Check browser language
  const browserLocale = navigator.language.split('-')[0]
  return browserLocale

  // 4. Default
  return 'en'
}

使用变量进行翻译

// messages/en.json
{
  "welcome": "Welcome, {name}!",
  "itemsInCart": "You have {count} {count, plural, one {item} other {items}} in your cart",
  "priceDisplay": "Price: {price, number, ::currency/USD}"
}
'use client'
import { useTranslations } from 'next-intl'

export function Greeting({ userName }: { userName: string }) {
  const t = useTranslations()

  return (
    <div>
      <h1>{t('welcome', { name: userName })}</h1>
      <p>{t('itemsInCart', { count: 3 })}</p>
      <p>{t('priceDisplay', { price: 49.99 })}</p>
    </div>
  )
}

// Output:
// "Welcome, John!"
// "You have 3 items in your cart"
// "Price: $49.99"

特定区域设置的内容

// app/[locale]/page.tsx
import { useLocale } from 'next-intl'

export default function HomePage() {
  const locale = useLocale()

  const content = {
    en: {
      hero: 'Build amazing apps',
      description: 'The best platform for developers'
    },
    es: {
      hero: 'Crea aplicaciones increíbles',
      description: 'La mejor plataforma para desarrolladores'
    },
    ja: {
      hero: '素晴らしいアプリを作成',
      description: '開発者のための最高のプラットフォーム'
    }
  }

  return (
    <div>
      <h1>{content[locale].hero}</h1>
      <p>{content[locale].description}</p>
    </div>
  )
}

翻译管理

使用翻译服务(Lokalise、Crowdin)
// scripts/sync-translations.ts

async function syncTranslations() {
  // Download translations from service
  const response = await fetch('https://api.lokalise.com/api2/projects/PROJECT_ID/files/download', {
    headers: {
      'X-Api-Token': process.env.LOKALISE_API_KEY!
    }
  })

  const data = await response.json()

  // Save to messages folder
  await fs.writeFile('./messages/en.json', JSON.stringify(data.en, null, 2))

  console.log('Translations synced!')
}

缺失翻译的处理

// i18n/request.ts
import { getRequestConfig } from 'next-intl/server'

export default getRequestConfig(async ({ locale }) => ({
  messages: (await import(`../messages/${locale}.json`)).default,
  onError: error => {
    console.error('Translation error:', error)
  },
  getMessageFallback: ({ namespace, key, error }) => {
    return `${namespace}.${key}` // Show key if translation missing
  }
}))

多语言网站的 SEO

// app/[locale]/layout.tsx
import { useLocale } from 'next-intl'

export async function generateMetadata({ params: { locale } }) {
  const t = await useTranslations('metadata')

  return {
    title: t('title'),
    description: t('description'),
    alternates: {
      canonical: `/${locale}`,
      languages: {
        en: '/en',
        es: '/es',
        fr: '/fr',
        de: '/de',
        ja: '/ja'
      }
    }
  }
}

HTML 输出:

<link rel="canonical" href="https://example.com/en" />
<link rel="alternate" hreflang="en" href="https://example.com/en" />
<link rel="alternate" hreflang="es" href="https://example.com/es" />
<link rel="alternate" hreflang="fr" href="https://example.com/fr" />

何时使用我

适用场景:

  • 构建多语言应用
  • 国际产品发布
  • 全球 SaaS 平台
  • 面向多个国家的电子商务
  • 内容管理系统

我可以帮助你:

  • 搭建 i18n 基础架构
  • 管理翻译
  • 格式化日期、数字和货币
  • 处理 RTL 语言
  • 优化 SEO

我会创建什么

🌍 Multi-Language Support
📅 Date/Time Formatting
💰 Currency Formatting
🔄 Language Switching
📝 Translation Management
🌐 RTL Support

让你的应用走向全球!

评论

支持 Markdown