i18n-workflow
npx skills add https://github.com/ponpon55837/mariokartworldparams --skill i18n-workflow
Agent 安装分布
Skill 文档
æçåè½
- æä¾å¤èªè¨éç¼çæ¨æºåæµç¨
- 確ä¿äºç¨®èªè¨ (ç¹ä¸ãç°¡ä¸ãè±ãæ¥ãé) çä¸è´æ§
- 管çç¿»è¯æªæ¡çæ°å¢ãæ´æ°åç¶è·
- åå©å¯¦ç¾èªè¨æä¹ åå峿åæ
ä½æä½¿ç¨æ
å¨ä»¥ä¸æ æ³ä¸ä½¿ç¨æ¤æè½ï¼
- æ°å¢ä½¿ç¨è å¯è¦çæåæè¨æ¯æ
- ä¿®æ¹ç¾ææåå §å®¹æ
- æ°å¢æ°åè½éè¦å¤èªè¨æ¯æ´
- 檢æ¥ç¿»è¯å®æ´æ§åä¸è´æ§
- è¨å®èªè¨åæåè½
æ¯æ´èªè¨
æ¬å°æ¡æ¯æ´äºç¨®èªè¨ï¼
- ç¹é«ä¸æ (zh-TW) – é è¨èªè¨
- ç°¡é«ä¸æ (zh-CN)
- è±æ (en)
- æ¥æ (ja)
- éæ (ko)
æªæ¡çµæ§
src/i18n/
âââ config.ts # i18next é
ç½®
âââ locales/
âââ zh-TW.json # ç¹é«ä¸æï¼é è¨ï¼
âââ zh-CN.json # ç°¡é«ä¸æ
âââ en.json # è±æ
âââ ja.json # æ¥æ
âââ ko.json # éæ
ä½¿ç¨ i18next
å¨å ä»¶ä¸ä½¿ç¨
'use client';
import { useTranslation } from 'react-i18next';
export default function Component() {
const { t } = useTranslation();
return (
<div>
<h1>{t('common.appName')}</h1>
<p>{t('stats.speed')}</p>
<button>{t('actions.save')}</button>
</div>
);
}
èªè¨åæ
'use client';
import { useTranslation } from 'react-i18next';
import { useAtom } from 'jotai';
import { languageAtom } from '@/store/dataAtoms';
export default function LanguageSelector() {
const { i18n } = useTranslation();
const [language, setLanguage] = useAtom(languageAtom);
const handleLanguageChange = (lang: string) => {
i18n.changeLanguage(lang);
setLanguage(lang as SupportedLanguage);
};
return (
<select
value={language}
onChange={(e) => handleLanguageChange(e.target.value)}
>
<option value="zh-TW">ç¹é«ä¸æ</option>
<option value="zh-CN">ç®ä½ä¸æ</option>
<option value="en">English</option>
<option value="ja">æ¥æ¬èª</option>
<option value="ko">íêµì´</option>
</select>
);
}
æ°å¢ç¿»è¯æµç¨
éè¦åå
â ï¸ å¿ é åææ´æ°ææäºåèªè¨æªæ¡
æ¯æ¬¡æ°å¢æä¿®æ¹ç¿»è¯æï¼å¿ é ç¢ºä¿ææèªè¨æªæ¡é½æå°æçç¿»è¯å §å®¹ã
æ¥é©
1. 確å®ç¿»è¯ Key çµæ§
使ç¨é»èåéçå½å空éçµæ§ï¼
category.subcategory.key
ç¯ä¾ï¼
common.appName– éç¨é¡å¥çæç¨ç¨å¼å稱stats.speed– çµ±è¨é¡å¥çé度actions.save– åä½é¡å¥çå²å
2. 卿æèªè¨æªæ¡ä¸æ°å¢ç¿»è¯
// zh-TW.json
{
"newFeature": {
"title": "æ°åè½æ¨é¡",
"description": "æ°åè½èªªæ",
"button": "確èª"
}
}
// zh-CN.json
{
"newFeature": {
"title": "æ°åè½æ é¢",
"description": "æ°åè½è¯´æ",
"button": "确认"
}
}
// en.json
{
"newFeature": {
"title": "New Feature Title",
"description": "New feature description",
"button": "Confirm"
}
}
// ja.json
{
"newFeature": {
"title": "æ°æ©è½ã¿ã¤ãã«",
"description": "æ°æ©è½ã®èª¬æ",
"button": "確èª"
}
}
// ko.json
{
"newFeature": {
"title": "ì ê¸°ë¥ ì 목",
"description": "ì ê¸°ë¥ ì¤ëª
",
"button": "íì¸"
}
}
3. å¨å ä»¶ä¸ä½¿ç¨
'use client';
import { useTranslation } from 'react-i18next';
export default function NewFeature() {
const { t } = useTranslation();
return (
<div>
<h2>{t('newFeature.title')}</h2>
<p>{t('newFeature.description')}</p>
<button>{t('newFeature.button')}</button>
</div>
);
}
ç¿»è¯æªæ¡çµæ§è¦ç¯
å½å空éåé¡
{
"common": {
"appName": "æç¨ç¨å¼å稱",
"loading": "è¼å
¥ä¸...",
"error": "é¯èª¤è¨æ¯"
},
"navigation": {
"home": "é¦é ",
"about": "éæ¼",
"settings": "è¨å®"
},
"stats": {
"speed": "é度",
"acceleration": "å é度",
"weight": "éé",
"handling": "ææ§æ§"
},
"actions": {
"save": "å²å",
"cancel": "åæ¶",
"confirm": "確èª",
"delete": "åªé¤"
},
"messages": {
"success": "æä½æå",
"error": "æä½å¤±æ",
"warning": "è¦åè¨æ¯"
}
}
ä¿æçµæ§ä¸è´
ææèªè¨æªæ¡å¿ é ä¿æç¸åç JSON çµæ§ï¼
// â
æ£ç¢º - ææèªè¨æç¸åç key
// zh-TW.json
{
"user": {
"profile": "åäººè³æ",
"settings": "è¨å®"
}
}
// en.json
{
"user": {
"profile": "Profile",
"settings": "Settings"
}
}
// â é¯èª¤ - çµæ§ä¸ä¸è´
// zh-TW.json
{
"user": {
"profile": "åäººè³æ",
"settings": "è¨å®"
}
}
// en.json
{
"user": {
"profile": "Profile"
// ç¼ºå° settings
}
}
èªè¨æä¹ å
ä½¿ç¨ Jotai atomWithStorage
// store/dataAtoms.ts
import { atomWithStorage } from "jotai/utils";
export const languageAtom = atomWithStorage<SupportedLanguage>(
"mario-kart-language",
"zh-TW",
);
èªè¨æä¹ å Hook
// hooks/useLanguagePersistence.ts
"use client";
import { useEffect } from "react";
import { useAtom } from "jotai";
import { useTranslation } from "react-i18next";
import { languageAtom } from "@/store/dataAtoms";
export function useLanguagePersistence() {
const [language] = useAtom(languageAtom);
const { i18n } = useTranslation();
useEffect(() => {
if (language && i18n.language !== language) {
i18n.changeLanguage(language);
}
}, [language, i18n]);
}
ç¿»è¯å質åå
1. æºç¢ºæ§
- ç¿»è¯å¿ é æºç¢ºå³éåæ
- é¿å ä½¿ç¨æ©å¨ç¿»è¯çç硬表é
- 符åç®æ¨èªè¨çèªè¨ç¿æ £
2. ä¸è´æ§
- ç¸åæ¦å¿µä½¿ç¨ç¸åç¿»è¯
- 建ç«å°æ¡è¡èªè¡¨
- çµ±ä¸å°æåè©çç¿»è¯
3. ç°¡æ½æ§
- é¿å åé·çç¿»è¯
- 使ç¨ç°¡æ½æ¸ æ°ç表é
- èæ ® UI 空ééå¶
4. æå驿
- èæ ®ä¸åæåèæ¯
- é¿å æåææå §å®¹
- 驿ç¶å°ä½¿ç¨ç¿æ £
åæ ç¿»è¯
叶忏çç¿»è¯
// zh-TW.json
{
"greeting": "ä½ å¥½ï¼{{name}}ï¼",
"itemCount": "å
± {{count}} åé
ç®"
}
// 使ç¨
const { t } = useTranslation();
<p>{t('greeting', { name: '使ç¨è
' })}</p>
<p>{t('itemCount', { count: 10 })}</p>
è¤æ¸å½¢å¼èç
// en.json
{
"items": "{{count}} item",
"items_other": "{{count}} items"
}
const { t } = useTranslation();
<p>{t('items', { count: 1 })}</p> // "1 item"
<p>{t('items', { count: 5 })}</p> // "5 items"
æª¢æ¥æ¸ å®
å¨æäº¤ç¿»è¯åï¼è«ç¢ºèªï¼
- ææäºåèªè¨æªæ¡é½å·²æ´æ°
- JSON æ ¼å¼æ£ç¢ºï¼æ²æèªæ³é¯èª¤
- ææèªè¨æªæ¡çµæ§ä¸è´
- ç¿»è¯æºç¢ºä¸ç¬¦åèªè¨ç¿æ £
- 使ç¨
t()å½å¼èé硬編碼æå - èªè¨åæåè½æ£å¸¸éä½
- localStorage æ£ç¢ºå²åèªè¨è¨å®
- 測試ææèªè¨ç顯示ææ
常è¦åé¡
Q: å¦ä½èçé·æåç¿»è¯ï¼
A: å°é·æåæåçºå¤å keyï¼æä½¿ç¨æè¡ç¬¦èï¼
{
"longText": "éæ¯ç¬¬ä¸æ®µæåã\néæ¯ç¬¬äºæ®µæåã"
}
Q: å¦ä½èç HTML æ¨ç±¤ï¼
A: ä½¿ç¨ Trans çµä»¶ï¼
import { Trans } from 'react-i18next';
<Trans i18nKey="richText">
This is <strong>bold</strong> text.
</Trans>
Q: å¦ä½ç¢ºä¿ç¿»è¯å®æ´æ§ï¼
A: 建ç«èªååè ³æ¬æª¢æ¥ææèªè¨æªæ¡æ¯å¦æç¸åç key çµæ§ã
Q: å¦ä½èçåçä¸çæåï¼
A: çºä¸åèªè¨æºåä¸åçåçï¼æ ¹æç¶åèªè¨åæ è¼å ¥ã
測試æµç¨
1. æå測試
# ååéç¼ä¼ºæå¨
pnpm dev
# 測試æ¥é©ï¼
1. åæå°æ¯ç¨®èªè¨
2. æª¢æ¥ææé é¢çæå顯示
3. 確èªèªè¨è¨å®è¢«æ£ç¢ºå²å
4. éæ°è¼å
¥é é¢ç¢ºèªèªè¨ä¿æ
2. JSON æ ¼å¼é©è
# ä½¿ç¨ JSON linter 檢æ¥èªæ³
pnpm lint
3. ç¿»è¯è¦èçæª¢æ¥
建ç«è ³æ¬æª¢æ¥ææèªè¨æªæ¡ç key æ¯å¦ä¸è´ï¼
// scripts/check-i18n.ts
const fs = require("fs");
const languages = ["zh-TW", "zh-CN", "en", "ja", "ko"];
const locales = {};
languages.forEach((lang) => {
locales[lang] = JSON.parse(
fs.readFileSync(`src/i18n/locales/${lang}.json`, "utf-8"),
);
});
// æª¢æ¥ key ä¸è´æ§
// ... 實ä½é輯
æä½³å¯¦è¸
1. æ©æåéå
å¾å°æ¡éå§å°±ä½¿ç¨ t() å½å¼ï¼é¿å
å¾æå¤§è¦æ¨¡éæ§ã
2. ç¿»è¯æä»¶å
建ç«ç¿»è¯æåæä»¶ï¼èªªæå°æ¡ç¹å®çè¡èªç¿»è¯ã
3. çæ¬æ§å¶
ä½¿ç¨ Git è¿½è¹¤ç¿»è¯æªæ¡è®æ´ï¼ä¾¿æ¼å¯©æ¥ååæº¯ã
4. åéåä½
- æå®èªè¨è² è²¬äººå¯©æ ¸ç¿»è¯
- ä½¿ç¨ Pull Request 審æ¥ç¿»è¯è®æ´
- 建ç«ç¿»è¯è¨è«ç¾¤çµ
5. æçºåªå
- æ¶é使ç¨è åé¥
- 宿坩æ¥ååªåç¿»è¯
- ä¿æç¿»è¯èåè½åæ¥æ´æ°