{"id":190,"date":"2026-06-30T10:27:52","date_gmt":"2026-06-30T02:27:52","guid":{"rendered":"https:\/\/yykcj.com\/?p=190"},"modified":"2026-06-30T10:27:52","modified_gmt":"2026-06-30T02:27:52","slug":"%e5%89%8d%e7%ab%af%e5%9b%bd%e9%99%85%e5%8c%96-i18n-%e6%9c%80%e4%bd%b3%e5%ae%9e%e8%b7%b5%ef%bc%9a%e4%bb%8e%e9%9b%b6%e6%90%ad%e5%bb%ba%e5%a4%9a%e8%af%ad%e8%a8%80%e5%ba%94%e7%94%a8","status":"publish","type":"post","link":"https:\/\/yykcj.com\/?p=190","title":{"rendered":"\u524d\u7aef\u56fd\u9645\u5316 (i18n) \u6700\u4f73\u5b9e\u8df5\uff1a\u4ece\u96f6\u642d\u5efa\u591a\u8bed\u8a00\u5e94\u7528"},"content":{"rendered":"<h1>\u524d\u7aef\u56fd\u9645\u5316 (i18n) \u6700\u4f73\u5b9e\u8df5\uff1a\u4ece\u96f6\u642d\u5efa\u591a\u8bed\u8a00\u5e94\u7528<\/h1>\n<h2>\u4e3a\u4ec0\u4e48\u9700\u8981\u56fd\u9645\u5316\uff1f<\/h2>\n<p>\u968f\u7740\u4ea7\u54c1\u8d70\u5411\u5168\u7403\uff0c\u652f\u6301\u591a\u8bed\u8a00\u5df2\u6210\u4e3a\u524d\u7aef\u5f00\u53d1\u7684\u6807\u914d\u9700\u6c42\u3002\u4f46\u5f88\u591a\u56e2\u961f\u5728\u521d\u671f\u53ea\u8003\u8651\u5355\u4e00\u8bed\u8a00\uff0c\u540e\u671f\u518d\u6dfb\u52a0\u56fd\u9645\u5316\u65f6\u5f80\u5f80\u9762\u4e34\u4ee3\u7801\u91cd\u6784\u3001\u7ffb\u8bd1\u7ba1\u7406\u6df7\u4e71\u3001\u6027\u80fd\u4e0b\u964d\u7b49\u95ee\u9898\u3002\u672c\u6587\u5c06\u4ece\u9009\u578b\u3001\u67b6\u6784\u3001\u5b9e\u73b0\u5230\u81ea\u52a8\u5316\uff0c\u5206\u4eab\u4e00\u5957\u7ecf\u8fc7\u5b9e\u8df5\u68c0\u9a8c\u7684\u56fd\u9645\u5316\u65b9\u6848\u3002<\/p>\n<h2>\u65b9\u6848\u9009\u578b<\/h2>\n<p>\u76ee\u524d\u4e3b\u6d41\u7684\u524d\u7aef\u56fd\u9645\u5316\u5e93\u6709\uff1a<\/p>\n<ul>\n<li><strong>i18next<\/strong>\uff1a\u529f\u80fd\u5f3a\u5927\uff0c\u751f\u6001\u4e30\u5bcc\uff0c\u652f\u6301 React\/Vue\/Angular \u7b49\u6846\u67b6\uff0c\u63d2\u4ef6\u5316\u7a0b\u5ea6\u9ad8\u3002<\/li>\n<li><strong>react-intl (FormatJS)<\/strong>\uff1aReact \u5b98\u65b9\u63a8\u8350\uff0c\u57fa\u4e8e\u7ec4\u4ef6\u5316 API\uff0c\u4f46\u4ec5\u9650 React\u3002<\/li>\n<li><strong>vue-i18n<\/strong>\uff1aVue \u751f\u6001\u9996\u9009\uff0c\u4e0e Vue \u6df1\u5ea6\u96c6\u6210\u3002<\/li>\n<\/ul>\n<p><strong>\u6211\u7684\u9009\u62e9\uff1ai18next<\/strong>\u3002\u539f\u56e0\uff1a<\/p>\n<ol>\n<li>\u6846\u67b6\u65e0\u5173\uff0c\u53ef\u590d\u7528\u540c\u4e00\u5957\u914d\u7f6e\u5230\u4e0d\u540c\u9879\u76ee\u3002<\/li>\n<li>\u652f\u6301\u5d4c\u5957\u7ffb\u8bd1\u3001\u590d\u6570\u3001\u4e0a\u4e0b\u6587\u3001ICU MessageFormat \u7b49\u9ad8\u7ea7\u529f\u80fd\u3002<\/li>\n<li>\u5f3a\u5927\u7684\u63d2\u4ef6\u7cfb\u7edf\uff0c\u5982\u81ea\u52a8\u68c0\u6d4b\u8bed\u8a00\u3001\u7f13\u5b58\u3001\u540e\u7aef\u52a0\u8f7d\u7b49\u3002<\/li>\n<\/ol>\n<h2>\u9879\u76ee\u521d\u59cb\u5316<\/h2>\n<p>\u4ee5 React + TypeScript \u4e3a\u4f8b\uff0c\u642d\u5efa\u4e00\u4e2a\u591a\u8bed\u8a00\u5e94\u7528\u3002<\/p>\n<h3>1. \u5b89\u88c5\u4f9d\u8d56<\/h3>\n<pre><code class=\"language-bash\">\nnpm install i18next react-i18next i18next-browser-languagedetector i18next-http-backend\n<\/code><\/pre>\n<h3>2. \u914d\u7f6e i18n<\/h3>\n<p>\u521b\u5efa <code>i18n.ts<\/code>\uff1a<\/p>\n<pre><code class=\"language-typescript\">\nimport i18n from &#x27;i18next&#x27;;\nimport { initReactI18next } from &#x27;react-i18next&#x27;;\nimport LanguageDetector from &#x27;i18next-browser-languagedetector&#x27;;\nimport Backend from &#x27;i18next-http-backend&#x27;;\n\ni18n\n  .use(Backend) \/\/ \u52a0\u8f7d\u7ffb\u8bd1\u6587\u4ef6\n  .use(LanguageDetector) \/\/ \u81ea\u52a8\u68c0\u6d4b\u7528\u6237\u8bed\u8a00\n  .use(initReactI18next) \/\/ \u7ed1\u5b9a React\n  .init({\n    fallbackLng: &#x27;en&#x27;, \/\/ \u9ed8\u8ba4\u8bed\u8a00\n    debug: process.env.NODE_ENV === &#x27;development&#x27;,\n    interpolation: {\n      escapeValue: false, \/\/ React \u5df2\u7ecf\u5b89\u5168\n    },\n    backend: {\n      loadPath: &#x27;\/locales\/{{lng}}\/{{ns}}.json&#x27;, \/\/ \u7ffb\u8bd1\u6587\u4ef6\u8def\u5f84\n    },\n    detection: {\n      order: [&#x27;querystring&#x27;, &#x27;cookie&#x27;, &#x27;localStorage&#x27;, &#x27;navigator&#x27;, &#x27;htmlTag&#x27;],\n      caches: [&#x27;localStorage&#x27;, &#x27;cookie&#x27;],\n    },\n  });\n\nexport default i18n;\n<\/code><\/pre>\n<h3>3. \u7ffb\u8bd1\u6587\u4ef6\u7ed3\u6784<\/h3>\n<pre><code>\npublic\/\n  locales\/\n    en\/\n      common.json\n      home.json\n    zh\/\n      common.json\n      home.json\n<\/code><\/pre>\n<p><code>en\/common.json<\/code> \u793a\u4f8b\uff1a<\/p>\n<pre><code class=\"language-json\">\n{\n  &quot;app&quot;: {\n    &quot;title&quot;: &quot;My App&quot;,\n    &quot;description&quot;: &quot;Welcome to my app&quot;\n  },\n  &quot;button&quot;: {\n    &quot;submit&quot;: &quot;Submit&quot;,\n    &quot;cancel&quot;: &quot;Cancel&quot;\n  }\n}\n<\/code><\/pre>\n<h2>\u5728 React \u4e2d\u4f7f\u7528<\/h2>\n<h3>1. \u4f7f\u7528 <code>useTranslation<\/code> Hook<\/h3>\n<pre><code class=\"language-tsx\">\nimport { useTranslation } from &#x27;react-i18next&#x27;;\n\nfunction HomePage() {\n  const { t, i18n } = useTranslation(&#x27;home&#x27;);\n\n  const changeLanguage = (lng: string) =&gt; {\n    i18n.changeLanguage(lng);\n  };\n\n  return (\n    &lt;div&gt;\n      &lt;h1&gt;{t(&#x27;welcome&#x27;)}&lt;\/h1&gt;\n      &lt;p&gt;{t(&#x27;intro&#x27;, { name: &#x27;John&#x27; })}&lt;\/p&gt;\n      &lt;button onClick={() =&gt; changeLanguage(&#x27;zh&#x27;)}&gt;\u4e2d\u6587&lt;\/button&gt;\n      &lt;button onClick={() =&gt; changeLanguage(&#x27;en&#x27;)}&gt;English&lt;\/button&gt;\n    &lt;\/div&gt;\n  );\n}\n<\/code><\/pre>\n<p><code>home.json<\/code>\uff1a<\/p>\n<pre><code class=\"language-json\">\n{\n  &quot;welcome&quot;: &quot;Welcome to the Home Page&quot;,\n  &quot;intro&quot;: &quot;Hello, {{name}}!&quot;\n}\n<\/code><\/pre>\n<h3>2. \u4f7f\u7528 <code>Trans<\/code> \u7ec4\u4ef6\u5904\u7406\u5bcc\u6587\u672c<\/h3>\n<pre><code class=\"language-tsx\">\nimport { Trans } from &#x27;react-i18next&#x27;;\n\nfunction MyComponent() {\n  return (\n    &lt;Trans i18nKey=&quot;description&quot;&gt;\n      Read &lt;a href=&quot;\/docs&quot;&gt;the docs&lt;\/a&gt; for more info.\n    &lt;\/Trans&gt;\n  );\n}\n<\/code><\/pre>\n<p>\u7ffb\u8bd1\u6587\u4ef6\uff1a<\/p>\n<pre><code class=\"language-json\">\n{\n  &quot;description&quot;: &quot;Read &lt;1&gt;the docs&lt;\/1&gt; for more info.&quot;\n}\n<\/code><\/pre>\n<p><code><1><\/code> \u5bf9\u5e94\u7ec4\u4ef6\u4e2d\u7b2c\u4e00\u4e2a\u5b50\u5143\u7d20 <code><a><\/code>\u3002<\/p>\n<h2>\u9ad8\u7ea7\u5b9e\u8df5<\/h2>\n<h3>1. \u547d\u540d\u7a7a\u95f4\u7ba1\u7406<\/h3>\n<p>\u5c06\u7ffb\u8bd1\u6587\u4ef6\u6309\u6a21\u5757\u62c6\u5206\uff08\u5982 <code>common<\/code>, <code>home<\/code>, <code>about<\/code>\uff09\uff0c\u907f\u514d\u5355\u4e2a\u6587\u4ef6\u8fc7\u5927\u3002\u5728 <code>useTranslation<\/code> \u4e2d\u6307\u5b9a\u547d\u540d\u7a7a\u95f4\uff1a<\/p>\n<pre><code class=\"language-tsx\">\nconst { t } = useTranslation([&#x27;common&#x27;, &#x27;home&#x27;]);\n\/\/ \u8de8\u547d\u540d\u7a7a\u95f4\u8bbf\u95ee\uff1at(&#x27;common:button.submit&#x27;)\n<\/code><\/pre>\n<h3>2. \u52a8\u6001\u52a0\u8f7d\u7ffb\u8bd1<\/h3>\n<p>\u4f7f\u7528 <code>i18next-http-backend<\/code> \u6309\u9700\u52a0\u8f7d\uff0c\u51cf\u5c11\u521d\u59cb\u5305\u4f53\u79ef\u3002\u914d\u7f6e <code>loadPath<\/code> \u4e3a\u52a8\u6001\u8def\u5f84\uff0c\u5e76\u5728\u5207\u6362\u8bed\u8a00\u65f6\u81ea\u52a8\u52a0\u8f7d\u3002<\/p>\n<h3>3. \u7c7b\u578b\u5b89\u5168<\/h3>\n<p>\u4e3a\u7ffb\u8bd1\u952e\u751f\u6210 TypeScript \u7c7b\u578b\uff0c\u907f\u514d\u62fc\u5199\u9519\u8bef\u3002\u4f7f\u7528 <code>i18next-typescript<\/code> \u6216\u81ea\u5b9a\u4e49\u811a\u672c\uff1a<\/p>\n<pre><code class=\"language-typescript\">\n\/\/ i18n.d.ts\ndeclare module &#x27;i18next&#x27; {\n  interface CustomTypeOptions {\n    defaultNS: &#x27;common&#x27;;\n    resources: {\n      common: typeof import(&#x27;..\/public\/locales\/en\/common.json&#x27;);\n      home: typeof import(&#x27;..\/public\/locales\/en\/home.json&#x27;);\n    };\n  }\n}\n<\/code><\/pre>\n<h3>4. SEO \u4f18\u5316<\/h3>\n<p>\u5bf9\u4e8e SSR \u6846\u67b6\uff08\u5982 Next.js\uff09\uff0c\u9700\u8981\u6839\u636e\u8bed\u8a00\u8bbe\u7f6e <code><html lang><\/code> \u5c5e\u6027\u548c\u6b63\u786e URL\u3002\u793a\u4f8b\uff1a<\/p>\n<pre><code class=\"language-tsx\">\n\/\/ Next.js _app.tsx\nimport { appWithTranslation } from &#x27;next-i18next&#x27;;\n\nfunction MyApp({ Component, pageProps }) {\n  return (\n    &lt;&gt;\n      &lt;Head&gt;\n        &lt;html lang={i18n.language} \/&gt;\n      &lt;\/Head&gt;\n      &lt;Component {...pageProps} \/&gt;\n    &lt;\/&gt;\n  );\n}\n\nexport default appWithTranslation(MyApp);\n<\/code><\/pre>\n<h2>\u81ea\u52a8\u5316\u5de5\u4f5c\u6d41<\/h2>\n<h3>1. \u63d0\u53d6\u7ffb\u8bd1\u952e<\/h3>\n<p>\u4f7f\u7528 <code>i18next-scanner<\/code> \u6216 <code>babel-plugin-i18next-extract<\/code> \u81ea\u52a8\u626b\u63cf\u4ee3\u7801\u4e2d\u7684 <code>t()<\/code> \u8c03\u7528\uff0c\u751f\u6210\u7ffb\u8bd1\u6a21\u677f\u3002<\/p>\n<p>\u5b89\u88c5\uff1a<\/p>\n<pre><code class=\"language-bash\">\nnpm install --save-dev i18next-scanner\n<\/code><\/pre>\n<p>\u914d\u7f6e\u6587\u4ef6 <code>i18next-scanner.config.js<\/code>\uff1a<\/p>\n<pre><code class=\"language-javascript\">\nmodule.exports = {\n  input: [&#x27;src\/**\/*.{js,jsx,ts,tsx}&#x27;, &#x27;!src\/**\/*.spec.*&#x27;],\n  output: &#x27;.\/public\/locales&#x27;,\n  options: {\n    debug: true,\n    func: {\n      list: [&#x27;t&#x27;],\n      extensions: [&#x27;.js&#x27;, &#x27;.jsx&#x27;, &#x27;.ts&#x27;, &#x27;.tsx&#x27;],\n    },\n    lngs: [&#x27;en&#x27;, &#x27;zh&#x27;],\n    ns: [&#x27;common&#x27;, &#x27;home&#x27;],\n    defaultLng: &#x27;en&#x27;,\n    defaultNs: &#x27;common&#x27;,\n    resource: {\n      loadPath: &#x27;{{lng}}\/{{ns}}.json&#x27;,\n      savePath: &#x27;{{lng}}\/{{ns}}.json&#x27;,\n      jsonIndent: 2,\n    },\n  },\n};\n<\/code><\/pre>\n<p>\u8fd0\u884c\uff1a<\/p>\n<pre><code class=\"language-bash\">\nnpx i18next-scanner\n<\/code><\/pre>\n<h3>2. \u7ffb\u8bd1\u7ba1\u7406\u5e73\u53f0<\/h3>\n<p>\u5bf9\u63a5 Lokalise\u3001POEditor \u7b49\u5e73\u53f0\uff0c\u901a\u8fc7 API \u540c\u6b65\u7ffb\u8bd1\u6587\u4ef6\u3002\u793a\u4f8b\u4f7f\u7528 Lokalise CLI\uff1a<\/p>\n<pre><code class=\"language-bash\">\nlokalise2 file upload --token &lt;token&gt; --project-id &lt;id&gt; --file .\/public\/locales\/en\/common.json --lang-iso en\n<\/code><\/pre>\n<h2>\u5e38\u89c1\u5751\u4e0e\u89e3\u51b3\u65b9\u6848<\/h2>\n<h3>\u274c \u95ee\u98981\uff1a\u7ffb\u8bd1\u952e\u91cd\u590d<\/h3>\n<p><strong>\u89e3\u51b3\u65b9\u6848<\/strong>\uff1a\u547d\u540d\u7a7a\u95f4 + \u5c42\u7ea7\u547d\u540d\uff0c\u5982 <code>home.welcome<\/code>\uff0c\u907f\u514d\u5168\u5c40\u6241\u5e73\u5316\u3002<\/p>\n<h3>\u274c \u95ee\u98982\uff1a\u52a8\u6001\u5185\u5bb9\u4e0d\u66f4\u65b0<\/h3>\n<p><strong>\u89e3\u51b3\u65b9\u6848<\/strong>\uff1a\u786e\u4fdd\u7ec4\u4ef6\u5728\u8bed\u8a00\u5207\u6362\u540e\u91cd\u65b0\u6e32\u67d3\u3002\u4f7f\u7528 <code>useTranslation<\/code> \u7684 <code>t<\/code> \u51fd\u6570\u662f\u54cd\u5e94\u5f0f\u7684\u3002<\/p>\n<h3>\u274c \u95ee\u98983\uff1a\u65e5\u671f\/\u6570\u5b57\u683c\u5f0f\u5316<\/h3>\n<p><strong>\u89e3\u51b3\u65b9\u6848<\/strong>\uff1a\u4f7f\u7528 <code>Intl<\/code> \u5bf9\u8c61\u6216 <code>i18next<\/code> \u7684 <code>format<\/code> \u51fd\u6570\u3002<\/p>\n<pre><code class=\"language-typescript\">\ni18n.services.formatter.add(&#x27;date&#x27;, (value, lng) =&gt; {\n  return new Intl.DateTimeFormat(lng).format(value);\n});\n\/\/ \u4f7f\u7528\uff1at(&#x27;dateKey&#x27;, { date: new Date(), formatParams: { date: { format: &#x27;date&#x27; } } })\n<\/code><\/pre>\n<h3>\u274c \u95ee\u98984\uff1a\u6027\u80fd\u95ee\u9898<\/h3>\n<p><strong>\u89e3\u51b3\u65b9\u6848<\/strong>\uff1a<\/p>\n<ul>\n<li>\u6309\u9700\u52a0\u8f7d\u7ffb\u8bd1\u6587\u4ef6<\/li>\n<li>\u907f\u514d\u5728\u5927\u578b\u5217\u8868\u4e2d\u4f7f\u7528 <code>Trans<\/code> \u7ec4\u4ef6<\/li>\n<li>\u4f7f\u7528 <code>t<\/code> \u51fd\u6570\u66ff\u4ee3 <code>Trans<\/code> \u5c3d\u91cf\u9759\u6001\u5316<\/li>\n<\/ul>\n<h2>\u603b\u7ed3<\/h2>\n<p>\u672c\u6587\u4ece\u96f6\u642d\u5efa\u4e86\u4e00\u5957\u5b8c\u6574\u7684\u524d\u7aef\u56fd\u9645\u5316\u65b9\u6848\uff0c\u6db5\u76d6\uff1a<\/p>\n<ul>\n<li>\u9009\u578b i18next \u4f5c\u4e3a\u6838\u5fc3\u5e93<\/li>\n<li>\u914d\u7f6e\u81ea\u52a8\u68c0\u6d4b\u8bed\u8a00\u548c\u52a8\u6001\u52a0\u8f7d<\/li>\n<li>\u5728 React \u4e2d\u4f7f\u7528 hook \u548c\u7ec4\u4ef6<\/li>\n<li>\u7c7b\u578b\u5b89\u5168\u3001SEO \u4f18\u5316\u7b49\u9ad8\u7ea7\u5b9e\u8df5<\/li>\n<li>\u81ea\u52a8\u5316\u63d0\u53d6\u7ffb\u8bd1\u952e\u548c\u7ba1\u7406\u5e73\u53f0\u96c6\u6210<\/li>\n<\/ul>\n<p><strong>\u4e0b\u4e00\u6b65\u5efa\u8bae<\/strong>\uff1a<\/p>\n<ul>\n<li>\u63a2\u7d22 i18next \u7684 ICU MessageFormat \u652f\u6301\u590d\u6570\u3001\u6027\u522b\u7b49<\/li>\n<li>\u7ed3\u5408 CI\/CD \u81ea\u52a8\u5316\u7ffb\u8bd1\u540c\u6b65<\/li>\n<li>\u8003\u8651\u4f7f\u7528 <code>i18next-resources-for-backend<\/code> \u5b9e\u73b0\u670d\u52a1\u7aef\u6e32\u67d3<\/li>\n<\/ul>\n<p>\u5e0c\u671b\u8fd9\u7bc7\u6559\u7a0b\u80fd\u5e2e\u52a9\u4f60\u6784\u5efa\u5065\u58ee\u7684\u591a\u8bed\u8a00\u5e94\u7528\u3002\u5982\u6709\u95ee\u9898\uff0c\u6b22\u8fce\u7559\u8a00\u8ba8\u8bba\uff01<\/p>\n","protected":false},"excerpt":{"rendered":"<p>\u672c\u6587\u6df1\u5165\u63a2\u8ba8\u524d\u7aef\u56fd\u9645\u5316 (i18n) \u7684\u6700\u4f73\u5b9e\u8df5\uff0c\u4ece\u65b9\u6848\u9009\u578b\u3001\u9879\u76ee\u642d\u5efa\u5230\u81ea\u52a8\u5316\u5de5\u5177\uff0c\u624b\u628a\u624b\u6559\u4f60\u6784\u5efa\u53ef\u7ef4\u62a4\u7684\u591a\u8bed\u8a00\u524d\u7aef\u5e94\u7528\u3002\u6db5\u76d6 React\/Vue \u5b9e\u73b0\u3001\u52a8\u6001\u52a0\u8f7d\u3001SEO \u4f18\u5316\u7b49\u5173\u952e\u70b9\uff0c\u52a9\u4f60\u907f\u514d\u5e38\u89c1\u5751\u3002<\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[3,4],"tags":[77,11,40,16,78,45],"class_list":["post-190","post","type-post","status-publish","format-standard","hentry","category-3","category-4","tag-i18n","tag-react","tag-typescript","tag-16","tag-78","tag-45"],"_links":{"self":[{"href":"https:\/\/yykcj.com\/index.php?rest_route=\/wp\/v2\/posts\/190","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/yykcj.com\/index.php?rest_route=\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/yykcj.com\/index.php?rest_route=\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/yykcj.com\/index.php?rest_route=\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/yykcj.com\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=190"}],"version-history":[{"count":0,"href":"https:\/\/yykcj.com\/index.php?rest_route=\/wp\/v2\/posts\/190\/revisions"}],"wp:attachment":[{"href":"https:\/\/yykcj.com\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=190"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/yykcj.com\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=190"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/yykcj.com\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=190"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}