---
createdAt: 2025-08-23
updatedAt: 2025-09-29
title: next-i18next vs next-intl vs Intlayer
description: Next.jsã¢ããªã®åœéåïŒi18nïŒã«ãããnext-i18nextãnext-intlãIntlayerã®æ¯èŒ
keywords:
- next-intl
- next-i18next
- Intlayer
- åœéå
- ããã°
- Next.js
- JavaScript
- React
slugs:
- blog
- next-i18next-vs-next-intl-vs-intlayer
---
# next-i18next VS next-intl VS intlayer | Next.jsã®åœéåïŒi18nïŒ
<TOC/>

Next.jsåãã®3ã€ã®i18nãªãã·ã§ã³ãnext-i18nextãnext-intlãIntlayerã®é¡äŒŒç¹ãšçžéç¹ãèŠãŠãããŸãããã
ããã¯å®å
šãªãã¥ãŒããªã¢ã«ã§ã¯ãªããéžæã®åèãšãªãæ¯èŒã§ãã
ç§ãã¡ã¯**Next.js 13+ã®App Router**ïŒ**React Server Components**察å¿ïŒã«çŠç¹ãåœãŠã以äžãè©äŸ¡ããŸãïŒ
1. **ã¢ãŒããã¯ãã£ãšã³ã³ãã³ãã®æ§æ**
2. **TypeScriptãšå®å
šæ§**
3. **ç¿»èš³æ¬ èœã®åŠç**
4. **ã«ãŒãã£ã³ã°ãšããã«ãŠã§ã¢**
5. **ããã©ãŒãã³ã¹ãšèªã¿èŸŒã¿æå**
6. **éçºè
äœéšïŒDXïŒãããŒã«ãšã¡ã³ããã³ã¹**
7. **SEOãšå€§èŠæš¡ãããžã§ã¯ãã®æ¡åŒµæ§**
> **èŠçŽ**: 3ã€ã®ããããNext.jsã¢ããªã®ããŒã«ã©ã€ãºãå¯èœã§ãããã**ã³ã³ããŒãã³ãåäœã®ã³ã³ãã³ã管ç**ã**峿 ŒãªTypeScriptå**ã**ãã«ãæã®æ¬ èœããŒæ€åº**ã**ããªãŒã·ã§ã€ã¯å¯èœãªèŸæž**ããããŠ**äžæµã®App Router + SEOãã«ããŒ**ãæ±ãããªãã**Intlayer**ãæãå®å
šã§ã¢ãã³ãªéžæè¢ã§ãã
> éçºè
ãããæ··åããã¡ãªã®ã¯ã`next-intl`ã`react-intl`ã®Next.jsçã ãšæãããšã§ããããã§ã¯ãããŸããã`next-intl`ã¯[Amann](https://github.com/amannn)ã«ãã£ãŠã¡ã³ããã³ã¹ãããŠããã`react-intl`ã¯[FormatJS](https://github.com/formatjs/formatjs)ã«ãã£ãŠã¡ã³ããã³ã¹ãããŠããŸãã
---
## ç°¡åã«èšããš
- **next-intl** - 軜éã§ã·ã³ãã«ãªã¡ãã»ãŒãžãã©ãŒããããæäŸããNext.jsã®ãµããŒãããã£ããããŠããŸããã«ã¿ãã°ã¯äžå€®éæš©çã§ããããšãå€ããéçºè
äœéšïŒDXïŒã¯ã·ã³ãã«ã§ãããå®å
šæ§ãå€§èŠæš¡ãªã¡ã³ããã³ã¹ã¯äž»ã«ããªãã®è²¬ä»»ãšãªããŸãã
- **next-i18next** - Next.jsåãã«ã©ãããããi18nextã§ããæçãããšã³ã·ã¹ãã ãšãã©ã°ã€ã³ïŒäŸïŒICUïŒã«ããæ©èœãæã¡ãŸãããèšå®ãåé·ã«ãªããã¡ã§ããããžã§ã¯ãã倧ãããªãã«ã€ããŠã«ã¿ãã°ã¯äžå€®éæš©åããããã§ãã
- **Intlayer** - Next.jsåãã®ã³ã³ããŒãã³ãäžå¿ã®ã³ã³ãã³ãã¢ãã«ã**峿 ŒãªTSåä»ã**ã**ãã«ãæãã§ãã¯**ã**ããªãŒã·ã§ã€ãã³ã°**ã**çµã¿èŸŒã¿ã®ããã«ãŠã§ã¢ïŒSEOãã«ããŒ**ããªãã·ã§ã³ã®**ããžã¥ã¢ã«ãšãã£ã¿ãŒ/CMS**ãããã³**AIæ¯æŽç¿»èš³**ã
---
| Library | GitHub Stars | Total Commits | Last Commit | First Version | NPM Version | NPM Downloads |
| ---------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------- | ------------- | ------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------ |
| `aymericzip/intlayer` | [](https://github.com/aymericzip/intlayer/stargazers) | [](https://github.com/aymericzip/intlayer/commits) | [](https://github.com/aymericzip/intlayer/commits) | April 2024 | [](https://www.npmjs.com/package/intlayer) | [](https://www.npmjs.com/package/intlayer) |
| `amannn/next-intl` | [](https://github.com/amannn/next-intl/stargazers) | [](https://github.com/amannn/next-intl/commits) | [](https://github.com/amannn/next-intl/commits) | Nov 2020 | [](https://www.npmjs.com/package/next-intl) | [](https://www.npmjs.com/package/next-intl) |
| `i18next/i18next` | [](https://github.com/i18next/i18next/stargazers) | [](https://github.com/i18next/i18next/commits) | [](https://github.com/i18next/i18next/commits) | Jan 2012 | [](https://www.npmjs.com/package/i18next) | [](https://www.npmjs.com/package/i18next) |
| `i18next/next-i18next` | [](https://github.com/i18next/next-i18next/stargazers) | [](https://github.com/i18next/next-i18next/commits) | [](https://github.com/i18next/next-i18next/commits) | Nov 2018 | [](https://www.npmjs.com/package/next-i18next) | [](https://www.npmjs.com/package/next-i18next) |
> ãããžã¯èªåçã«æŽæ°ãããŸããã¹ãããã·ã§ããã¯æéãšãšãã«å€åããŸãã
---
## äžŠåæ©èœæ¯èŒïŒNext.jsã«ç¹åïŒ
| æ©èœ | `next-intlayer` (Intlayer) | `next-intl` | `next-i18next` |
> ãããžã¯èªåçã«æŽæ°ãããŸããã¹ãããã·ã§ããã¯æéãšãšãã«å€åããŸãã
---
## äžŠåæ©èœæ¯èŒïŒNext.jsã«ç¹åïŒ
| æ©èœ | `next-intlayer` (Intlayer) | `next-intl` | `next-i18next` |
| ------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------ | -------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------- |
| **ã³ã³ããŒãã³ãè¿ãã®ç¿»èš³** | â
ã¯ããåã³ã³ããŒãã³ãã«ã³ã³ãã³ããé
眮ãããŠããŸã | â ããã | â ããã |
| **TypeScript çµ±å** | â
é«åºŠãèªåçæãããå³å¯ãªå | â
è¯å¥œ | â ïž åºæ¬ |
| **翻蚳æŒãæ€åº** | â
TypeScript ãšã©ãŒã®ãã€ã©ã€ãããã³ãã«ãæã®ãšã©ãŒ/èŠå | â ïž ã©ã³ã¿ã€ã ãã©ãŒã«ãã㯠| â ïž ã©ã³ã¿ã€ã ãã©ãŒã«ãã㯠|
| **ãªããã³ã³ãã³ãïŒJSX/Markdown/ã³ã³ããŒãã³ãïŒ** | â
çŽæ¥ãµããŒã | â ãªããããŒãåãã«èšèšãããŠããŸãã | â ïž å¶éãã |
| **AIæèŒç¿»èš³** | â
ã¯ããè€æ°ã®AIãããã€ããŒããµããŒããç¬èªã®APIããŒã䜿çšå¯èœãã¢ããªã±ãŒã·ã§ã³ã®ã³ã³ããã¹ããšã³ã³ãã³ãã®ç¯å²ãèæ
®ããŸã | â ããã | â ããã |
| **ããžã¥ã¢ã«ãšãã£ã¿ãŒ** | â
ã¯ããããŒã«ã«ã®ããžã¥ã¢ã«ãšãã£ã¿ãŒïŒãªãã·ã§ã³ã®CMSïŒã³ãŒãããŒã¹ã®ã³ã³ãã³ããå€éšåå¯èœïŒåã蟌ã¿å¯èœ | â ãããïŒå€éšã®ããŒã«ãªãŒãŒã·ã§ã³ãã©ãããã©ãŒã çµç±ã§å©çšå¯èœ | â ãããïŒå€éšã®ããŒã«ãªãŒãŒã·ã§ã³ãã©ãããã©ãŒã çµç±ã§å©çšå¯èœ |
| **ããŒã«ã©ã€ãºãããã«ãŒãã£ã³ã°** | â
ã¯ããæšæºã§ããŒã«ã©ã€ãºããããã¹ããµããŒãïŒNext.js & Viteã§åäœïŒ | â
çµã¿èŸŒã¿ãApp Routerã¯`[locale]`ã»ã°ã¡ã³ãããµããŒã | â
çµã¿èŸŒã¿ |
| **åçã«ãŒãçæ** | â
ã¯ã | â
ã¯ã | â
ã¯ã |
| **è€æ°åœ¢å¯Ÿå¿** | â
åæããŒã¹ã®ãã¿ãŒã³ | â
è¯å¥œ | â
è¯å¥œ |
| **ãã©ãŒãããïŒæ¥æãæ°å€ãé貚ïŒ** | â
æé©åããããã©ãŒããã¿ãŒïŒå
éšã§Intlã䜿çšïŒ | â
è¯å¥œïŒIntlãã«ããŒïŒ | â
è¯å¥œïŒIntlãã«ããŒïŒ |
| **ã³ã³ãã³ããã©ãŒããã** | â
.tsxã.tsã.jsã.jsonã.mdã.txtãïŒ.yaml éçºäžïŒ | â
.jsonã.jsã.ts | â ïž .json |
| **ICUãµããŒã** | â ïž äœæ¥äž | â
ãã | â ïž ãã©ã°ã€ã³çµç±ïŒ`i18next-icu`ïŒ |
| **SEOãã«ããŒïŒhreflangããµã€ããããïŒ** | â
çµã¿èŸŒã¿ããŒã«ïŒãµã€ãããããrobots.txtãã¡ã¿ããŒã¿ã®ãã«ã㌠| â
è¯å¥œ | â
è¯å¥œ |
| **ãšã³ã·ã¹ãã / ã³ãã¥ããã£** | â ïž å°èŠæš¡ã ãæ¥éã«æé·ããåå¿ãè¯ã | â
è¯å¥œ | â
è¯å¥œ |
| **ãµãŒããŒãµã€ãã¬ã³ããªã³ã° & ãµãŒããŒã³ã³ããŒãã³ã** | â
ã¯ããSSR / ReactãµãŒããŒã³ã³ããŒãã³ãåãã«æé©å | â ïž ããŒãžã¬ãã«ã§ãµããŒããããŠããããåã®ãµãŒããŒã³ã³ããŒãã³ãã«å¯ŸããŠt颿°ãã³ã³ããŒãã³ãããªãŒã«æž¡ãå¿
èŠããã | â ïž ããŒãžã¬ãã«ã§ãµããŒããããŠããããåã®ãµãŒããŒã³ã³ããŒãã³ãã«å¯ŸããŠt颿°ãã³ã³ããŒãã³ãããªãŒã«æž¡ãå¿
èŠããã |
| **ããªãŒã·ã§ã€ãã³ã°ïŒäœ¿çšãããã³ã³ãã³ãã®ã¿ãèªã¿èŸŒãïŒ** | â
ã¯ããBabel/SWCãã©ã°ã€ã³ã䜿çšãããã«ãæã®ã³ã³ããŒãã³ãåäœã§å¯Ÿå¿ | â ïž éšåçã«å¯Ÿå¿ | â ïž éšåçã«å¯Ÿå¿ |
| **é
å»¶èªã¿èŸŒã¿** | â
ã¯ãããã±ãŒã«åäœ / èŸæžåäœã§å¯Ÿå¿ | â
ã¯ãïŒã«ãŒãåäœ/ãã±ãŒã«åäœïŒãåå空é管çãå¿
èŠ | â
ã¯ãïŒã«ãŒãåäœ/ãã±ãŒã«åäœïŒãåå空é管çãå¿
èŠ |
| **æªäœ¿çšã³ã³ãã³ãã®åé€** | â
ã¯ãããã«ãæã«èŸæžåäœã§å¯Ÿå¿ | â ããããåå空é管çã§æå察å¿å¯èœ | â ããããåå空é管çã§æå察å¿å¯èœ |
| **å€§èŠæš¡ãããžã§ã¯ãã®ç®¡ç** | â
ã¢ãžã¥ãŒã«åãæšå¥šãããã¶ã€ã³ã·ã¹ãã ã«é©ããŠãã | â
ã»ããã¢ããã«ããã¢ãžã¥ãŒã«åå¯Ÿå¿ | â
ã»ããã¢ããã«ããã¢ãžã¥ãŒã«åå¯Ÿå¿ |
| **翻蚳æŒãã®ãã¹ãïŒCLI/CIïŒ** | â
CLI: `npx intlayer content test`ïŒCIã«é©ããç£æ»ïŒ | â ïž çµã¿èŸŒã¿ã§ã¯ãªãïŒããã¥ã¡ã³ãã§ã¯ `npx @lingual/i18n-check` ãæšå¥š | â ïž çµã¿èŸŒã¿ã§ã¯ãªãïŒi18nextããŒã«ãã©ã³ã¿ã€ã ã® `saveMissing` ã«äŸå |
---
## ã¯ããã«
Next.jsã¯åœéåãããã«ãŒãã£ã³ã°ïŒäŸïŒãã±ãŒã«ã»ã°ã¡ã³ãïŒãçµã¿èŸŒã¿ã§ãµããŒãããŠããŸãããããããã®æ©èœã ãã§ã¯ç¿»èš³ã¯è¡ãããŸããããŠãŒã¶ãŒã«ããŒã«ã©ã€ãºãããã³ã³ãã³ãã衚瀺ããã«ã¯ãå¥éã©ã€ãã©ãªãå¿
èŠã§ãã
å€ãã®i18nã©ã€ãã©ãªãååšããŸãããNext.jsã®äžçã§ã¯çŸåšãnext-i18nextãnext-intlããããŠIntlayerã®3ã€ã泚ç®ãããŠããŸãã
---
## ã¢ãŒããã¯ãã£ãšã¹ã±ãŒã©ããªãã£
- **next-intl / next-i18next**: ãã±ãŒã«ããšã« **éäžç®¡çãããã«ã¿ãã°**ïŒããã³ i18next ã®å Žå㯠**ããŒã ã¹ããŒã¹**ïŒãããã©ã«ããšããŸããåææ®µéã§ã¯åé¡ãªãæ©èœããŸãããçµå床ãé«ãŸãããŒã®å€æŽãé »ç¹ã«ãªããšã倧ããªå
±æé åãšãªã£ãŠããŸããŸãã
- **Intlayer**: ãµãŒãã¹ããã³ãŒããš **å
±çœ®** ããã **ã³ã³ããŒãã³ãåäœ**ïŒãŸãã¯æ©èœåäœïŒã®èŸæžãæšå¥šããŸããããã«ããèªç¥è² è·ã軜æžãããUIããŒãã®éè€ãç§»è¡ã容æã«ãªããããŒã éã®ç«¶åãæžå°ããŸããæªäœ¿çšã®ã³ã³ãã³ããèªç¶ã«èŠã€ããããåé€ãããããªããŸãã
**ãªãéèŠã:** å€§èŠæš¡ãªã³ãŒãããŒã¹ããã¶ã€ã³ã·ã¹ãã ã®ã»ããã¢ããã§ã¯ã**ã¢ãžã¥ãŒã«åãããã³ã³ãã³ã**ã®æ¹ãã¢ããªã·ãã¯ãªã«ã¿ãã°ãããã¹ã±ãŒã«ããããã§ãã
---
## ãã³ãã«ãµã€ãºãšäŸåé¢ä¿
ã¢ããªã±ãŒã·ã§ã³ããã«ãããåŸããã³ãã«ãšã¯ãã©ãŠã¶ãããŒãžãã¬ã³ããªã³ã°ããããã«èªã¿èŸŒãJavaScriptã®ããšã§ãããããã£ãŠããã³ãã«ãµã€ãºã¯ã¢ããªã±ãŒã·ã§ã³ã®ããã©ãŒãã³ã¹ã«ãšã£ãŠéèŠã§ãã
å€èšèªã¢ããªã±ãŒã·ã§ã³ã®ãã³ãã«ã«ãããŠéèŠãª2ã€ã®èŠçŽ ã¯ä»¥äžã®éãã§ãïŒ
- ã¢ããªã±ãŒã·ã§ã³ã³ãŒã
- ãã©ãŠã¶ã«ãã£ãŠèªã¿èŸŒãŸããã³ã³ãã³ã
## ã¢ããªã±ãŒã·ã§ã³ã³ãŒã
ãã®å Žåãã¢ããªã±ãŒã·ã§ã³ã³ãŒãã®éèŠæ§ã¯æå°éã§ãã3ã€ã®ãœãªã¥ãŒã·ã§ã³ãã¹ãŠãããªãŒã·ã§ã€ã«ãã«ã§ãããæªäœ¿çšã®ã³ãŒãéšåã¯ãã³ãã«ã«å«ãŸããŸããã
以äžã¯ã3ã€ã®ãœãªã¥ãŒã·ã§ã³ãçšããå€èšèªã¢ããªã±ãŒã·ã§ã³ã§ãã©ãŠã¶ãèªã¿èŸŒãJavaScriptãã³ãã«ãµã€ãºã®æ¯èŒã§ãã
ã¢ããªã±ãŒã·ã§ã³å
ã§ãã©ãŒããã¿ãŒãå¿
èŠãšããªãå ŽåãããªãŒã·ã§ã€ãã³ã°åŸã«ãšã¯ã¹ããŒãããã颿°ã®ãªã¹ãã¯ä»¥äžã®ããã«ãªããŸãïŒ
- **next-intlayer**: `useIntlayer`, `useLocale`, `NextIntlClientProvider`ãïŒãã³ãã«ãµã€ãºã¯180.6 kB -> 78.6 kBïŒgzipïŒïŒ
- **next-intl**: `useTranslations`, `useLocale`, `NextIntlClientProvider`ãïŒãã³ãã«ãµã€ãºã¯101.3 kB -> 31.4 kBïŒgzipïŒïŒ
- **next-i18next**: `useTranslation`, `useI18n`, `I18nextProvider`ãïŒãã³ãã«ãµã€ãºã¯80.7 kB -> 25.5 kBïŒgzipïŒïŒ
ãããã®é¢æ°ã¯Reactã®ã³ã³ããã¹ã/ã¹ããŒãã®ã©ãããŒã«éããªããããi18nã©ã€ãã©ãªããã³ãã«ãµã€ãºã«äžãã圱é¿ã¯æå°éã§ãã
> Intlayerã¯ã`useIntlayer`颿°ã«ããå€ãã®ããžãã¯ãå«ãã§ããããã`next-intl`ã`next-i18next`ãããããã«å€§ãããªã£ãŠããŸããããã¯ããŒã¯ããŠã³ã`intlayer-editor`ã®çµ±åã«é¢é£ããŠããŸãã
## ã³ã³ãã³ããšç¿»èš³
ãã®éšåã¯éçºè
ã«ãã£ãŠãã°ãã°ç¡èŠãããŸããã10ããŒãžã§æ§æããã10èšèªã«å¯Ÿå¿ããã¢ããªã±ãŒã·ã§ã³ã®å ŽåãèããŠã¿ãŸããããèšç®ãç°¡åã«ããããã«ãåããŒãžã100ïŒ
ãŠããŒã¯ãªã³ã³ãã³ããçµ±åããŠãããšä»®å®ããŸãïŒå®éã«ã¯ãããŒãžã¿ã€ãã«ãããããŒãããã¿ãŒãªã©ãããŒãžéã§éè€ããã³ã³ãã³ããå€ããããŸãïŒã
`/fr/about` ããŒãžã蚪ããããŠãŒã¶ãŒã¯ãç¹å®ã®èšèªã§1ããŒãžåã®ã³ã³ãã³ããèªã¿èŸŒã¿ãŸããã³ã³ãã³ãã®æé©åãç¡èŠãããšãã¢ããªã±ãŒã·ã§ã³ã®ã³ã³ãã³ãã®8,200ïŒ
`((1 + (((10ããŒãž - 1) à (10èšèª - 1)))) à 100)` ãäžå¿
èŠã«èªã¿èŸŒãããšã«ãªããŸãããã®åé¡ãããããŸããïŒãã®ã³ã³ãã³ããããã¹ãã®ãŸãŸã§ãã£ãŠãããããããµã€ãã®ç»åã®æé©åãèããæ¹ãå€ãã§ãããããç¡é§ãªã³ã³ãã³ããäžçäžã«éä¿¡ãããŠãŒã¶ãŒã®ã³ã³ãã¥ãŒã¿ãŒã«ç¡æå³ãªåŠçããããŠããã®ã§ãã
2ã€ã®éèŠãªåé¡ïŒ
- **ã«ãŒãã«ããåå²ïŒ**
> `/about` ããŒãžã«ããå Žåã`/home` ããŒãžã®ã³ã³ãã³ããèªã¿èŸŒã¿ãããªã
- **ãã±ãŒã«ã«ããåå²ïŒ**
> `/fr/about` ããŒãžã«ããå Žåã`/en/about` ããŒãžã®ã³ã³ãã³ããèªã¿èŸŒã¿ãããªã
æ¹ããŠãããã3ã€ã®ãœãªã¥ãŒã·ã§ã³ã¯ãããã®åé¡ãèªèããŠããããããã®æé©åã管çããããšãã§ããŸãã3ã€ã®ãœãªã¥ãŒã·ã§ã³ã®éãã¯DXïŒéçºè
äœéšïŒã«ãããŸãã
`next-intl` ãš `next-i18next` ã¯ç¿»èš³ã管çããããã«éäžç®¡çåã®ã¢ãããŒãã䜿çšããŠããããã±ãŒã«ããµããã¡ã€ã«ããšã«JSONãåå²ããããšãå¯èœã§ãã`next-i18next` ã§ã¯JSONãã¡ã€ã«ããããŒã ã¹ããŒã¹ããšåŒã³ã`next-intl` ã§ã¯ã¡ãã»ãŒãžã宣èšããããšãã§ããŸãã`intlayer` ã§ã¯JSONãã¡ã€ã«ããèŸæžããšåŒã³ãŸãã
- `next-intl`ã®å Žåã¯ã`next-i18next`ãšåæ§ã«ãã³ã³ãã³ãã¯ããŒãžãã¬ã€ã¢ãŠãã¬ãã«ã§èªã¿èŸŒãŸãããã®åŸãã®ã³ã³ãã³ããã³ã³ããã¹ããããã€ããŒã«èªã¿èŸŒãŸããŸããã€ãŸããéçºè
ã¯åããŒãžã§èªã¿èŸŒãŸããJSONãã¡ã€ã«ãæåã§ç®¡çããå¿
èŠããããŸãã
> å®éã«ã¯ãéçºè
ã¯ãã®æé©åãçç¥ããåçŽãã®ããã«ããŒãžã®ã³ã³ããã¹ããããã€ããŒã«ãã¹ãŠã®ã³ã³ãã³ããèªã¿èŸŒãããšã奜ãããšãå€ãã§ãã
- `intlayer`ã®å Žåã¯ããã¹ãŠã®ã³ã³ãã³ããã¢ããªã±ãŒã·ã§ã³å
ã§èªã¿èŸŒãŸããŸãããã®åŸããã©ã°ã€ã³ïŒ`@intlayer/babel` / `@intlayer/swc`ïŒããã³ãã«ãæé©åããããŒãžã§äœ¿çšãããã³ã³ãã³ãã®ã¿ãèªã¿èŸŒã¿ãŸãããããã£ãŠãéçºè
ã¯èªã¿èŸŒãŸããèŸæžãæåã§ç®¡çããå¿
èŠããããŸãããããã«ãããããè¯ãæé©åãããè¯ãä¿å®æ§ããããŠéçºæéã®ççž®ãå¯èœã«ãªããŸãã
ã¢ããªã±ãŒã·ã§ã³ãæé·ããã«ã€ããŠïŒç¹ã«è€æ°ã®éçºè
ãã¢ããªã±ãŒã·ã§ã³ã«é¢ãã£ãŠããå ŽåïŒãJSONãã¡ã€ã«ãããã¯ã䜿çšãããŠããªãã³ã³ãã³ããåé€ãå¿ããããšããããããŸãã
> ãã¹ãŠã®JSONã¯ãã¹ãŠã®å Žåã«èªã¿èŸŒãŸããããšã«æ³šæããŠãã ããïŒnext-intlãnext-i18nextãintlayerïŒã
ãããIntlayerã®ã¢ãããŒããããããã©ãŒãã³ã¹ã«åªããŠããçç±ã§ããã³ã³ããŒãã³ãããã¯ã䜿çšãããŠããªãå Žåããã®èŸæžã¯ãã³ãã«ã«èªã¿èŸŒãŸããŸããã
ã©ã€ãã©ãªããã©ãŒã«ããã¯ãã©ã®ããã«åŠçããããéèŠã§ããã¢ããªã±ãŒã·ã§ã³ãããã©ã«ãã§è±èªã§ããããŠãŒã¶ãŒã`/fr/about`ããŒãžã蚪ãããšããŸãããã©ã³ã¹èªã®ç¿»èš³ãæ¬ ããŠããå Žåãè±èªã®ãã©ãŒã«ããã¯ãèæ
®ãããŸãã
`next-intl` ãš `next-i18next` ã®å Žåãã©ã€ãã©ãªã¯çŸåšã®ãã±ãŒã«ã«é¢é£ãã JSON ã«å ããŠããã©ãŒã«ããã¯ãã±ãŒã«ã® JSON ãèªã¿èŸŒãå¿
èŠããããŸãããããã£ãŠããã¹ãŠã®ã³ã³ãã³ãã翻蚳ãããŠãããšä»®å®ãããšãåããŒãžã¯ 100% äžèŠãªã³ã³ãã³ããèªã¿èŸŒãããšã«ãªããŸãã**ããã«å¯ŸããŠã`intlayer` ã¯èŸæžã®ãã«ãæã«ãã©ãŒã«ããã¯ãåŠçããŸãããããã£ãŠãåããŒãžã¯äœ¿çšãããã³ã³ãã³ãã®ã¿ãèªã¿èŸŒã¿ãŸãã**
以äžã¯ãvite + react ã¢ããªã±ãŒã·ã§ã³ã§ `intlayer` ã䜿çšãããã³ãã«ãµã€ãºæé©åã®åœ±é¿ã®äŸã§ãïŒ
| æé©åããããã³ãã« | æé©åãããŠããªããã³ãã« |
| --------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------- |
|  |  |
---
## TypeScript ãšå®å
šæ§
<Columns>
<Column>
**next-intl**
- å®å®ãã TypeScript ãµããŒããæäŸããŸããã**ããŒã¯ããã©ã«ãã§å³å¯ã«åä»ããããŠããŸãã**ãå®å
šæ§ã®ãã¿ãŒã³ã¯æåã§ç¶æããå¿
èŠããããŸãã
</Column>
<Column>
**next-i18next**
- ããã¯ã®åºæ¬çãªåå®çŸ©ããããŸããã**å³å¯ãªããŒã®åä»ãã«ã¯è¿œå ã®ããŒã«ãèšå®ãå¿
èŠã§ã**ã
</Column>
<Column>
**intlayer**
- **ã³ã³ãã³ãããå³å¯ãªåãçæ**ããŸãã**IDEã®ãªãŒãã³ã³ããªãŒã**ã**ã³ã³ãã€ã«æãšã©ãŒ**ã«ããããããã€åã«ã¿ã€ããã¹ãããŒã®æ¬ èœãæ€åºããŸãã
</Column>
</Columns>
**ãªãéèŠã:** 匷ãåä»ãã«ããã倱æã**å³ïŒå®è¡æïŒ**ã§ã¯ãªã**å·ŠïŒCI/ãã«ãæïŒ**ã«ã·ãããããŸãã
---
## ç¿»èš³æ¬ èœã®åãæ±ã
**next-intl**
- **å®è¡æã®ãã©ãŒã«ããã¯**ã«äŸåïŒäŸïŒããŒãããã©ã«ããã±ãŒã«ã衚瀺ïŒããã«ãã¯å€±æããŸããã
**next-i18next**
- **å®è¡æã®ãã©ãŒã«ããã¯**ã«äŸåïŒäŸïŒããŒãããã©ã«ããã±ãŒã«ã衚瀺ïŒããã«ãã¯å€±æããŸããã
**intlayer**
- **ãã«ãææ€åº**ã«ããããã±ãŒã«ãããŒã®æ¬ èœã«å¯ŸããŠ**èŠå/ãšã©ãŒ**ãåºããŸãã
**ãªãéèŠã:** ãã«ãæã«æ¬ èœãæ€åºããããšã§ãæ¬çªç°å¢ã§ã®ãè¬ã®æååãçºçãé²ãã峿 ŒãªãªãªãŒã¹ã²ãŒãã«é©åããŸãã
---
## ã«ãŒãã£ã³ã°ãããã«ãŠã§ã¢ & URLæŠç¥
<Columns>
<Column>
**next-intl**
- App Routeräžã®**Next.jsã®ããŒã«ã©ã€ãºãããã«ãŒãã£ã³ã°**ã«å¯Ÿå¿ã
</Column>
<Column>
**next-i18next**
- App Routeräžã®**Next.jsã®ããŒã«ã©ã€ãºãããã«ãŒãã£ã³ã°**ã«å¯Ÿå¿ã
</Column>
<Column>
**intlayer**
- äžèšãã¹ãŠã«å ãã**i18nããã«ãŠã§ã¢**ïŒããããŒãã¯ãããŒã«ãããã±ãŒã«æ€åºïŒããã³ããŒã«ã©ã€ãºãããURLã`<link rel="alternate" hreflang="âŠ">`ã¿ã°ãçæãã**ãã«ããŒ**ãæäŸã
</Column>
</Columns>
**éèŠãªçç±:** ã«ã¹ã¿ã ã®æ¥çå±€ãæžãã**äžè²«ããUX**ãš**ã¯ãªãŒã³ãªSEO**ããã±ãŒã«éã§å®çŸã
---
## ãµãŒããŒã³ã³ããŒãã³ãïŒRSCïŒå¯Ÿå¿
<Columns>
<Column>
**next-intl**
- Next.js 13+ããµããŒãããã€ããªããæ§æã§ã¯ãt颿°ããã©ãŒããã¿ãŒãã³ã³ããŒãã³ãããªãŒã«æž¡ãããšãå€ãã
</Column>
<Column>
**next-i18next**
- Next.js 13+ ããµããŒãã翻蚳ãŠãŒãã£ãªãã£ãå¢çãè¶ããŠæž¡ãéã«é¡äŒŒã®å¶çŽããããŸãã
</Column>
<Column>
**intlayer**
- Next.js 13+ ããµããŒãããäžè²«ãã API ãš RSC æåã®ãããã€ããŒã§ **ãµãŒããŒ/ã¯ã©ã€ã¢ã³ãã®å¢ç** ãã¹ã ãŒãºã«ãããã©ãŒããã¿ãŒã t-颿°ã®ããåããåé¿ããŸãã
</Column>
</Columns>
**éèŠãªçç±:** ãã€ããªããããªãŒã«ãããã¡ã³ã¿ã«ã¢ãã«ãããæç¢ºã«ãªãããšããžã±ãŒã¹ãæžå°ããŸãã
---
## DXãããŒã«ïŒã¡ã³ããã³ã¹
<Columns>
<Column>
**next-intl**
- å€éšã®ããŒã«ãªãŒãŒã·ã§ã³ãã©ãããã©ãŒã ãç·šéã¯ãŒã¯ãããŒãšçµã¿åãããŠäœ¿ãããããšãå€ãã§ãã
</Column>
<Column>
**next-i18next**
- å€éšã®ããŒã«ãªãŒãŒã·ã§ã³ãã©ãããã©ãŒã ãç·šéã¯ãŒã¯ãããŒãšçµã¿åãããŠäœ¿ãããããšãå€ãã§ãã
</Column>
<Column>
**intlayer**
- ç¡æã®**ããžã¥ã¢ã«ãšãã£ã¿ãŒ**ãš**ãªãã·ã§ã³ã®CMS**ïŒGit察å¿ãŸãã¯å€éšåå¯èœïŒãæäŸããããã«**VSCodeæ¡åŒµæ©èœ**ãšãç¬èªã®ãããã€ããŒããŒã䜿çšãã**AIæ¯æŽç¿»èš³**ãåããŠããŸãã
</Column>
</Columns>
**éèŠãªçç±:** éçšã³ã¹ããåæžããéçºè
ãšã³ã³ãã³ãäœæè
éã®ãã£ãŒãããã¯ã«ãŒããççž®ããŸãã
## ããŒã«ãªãŒãŒã·ã§ã³ãã©ãããã©ãŒã ïŒTMSïŒãšã®çµ±å
å€§èŠæš¡ãªçµç¹ã§ã¯ã**Crowdin**ã**Phrase**ã**Lokalise**ã**Localizely**ã**Localazy**ãªã©ã®ç¿»èš³ç®¡çã·ã¹ãã ïŒTMSïŒã«äŸåããããšãå€ãã§ãã
- **äŒæ¥ãéèŠããçç±**
- **ååãšåœ¹å²åæ
**ïŒè€æ°ã®é¢ä¿è
ãé¢äžããŸããéçºè
ããããã¯ããããŒãžã£ãŒã翻蚳è
ãã¬ãã¥ã¢ãŒãããŒã±ãã£ã³ã°ããŒã ãªã©ã
- **èŠæš¡ãšå¹çæ§**ïŒç¶ç¶çãªããŒã«ãªãŒãŒã·ã§ã³ãã³ã³ããã¹ãå
ã¬ãã¥ãŒã
- **next-intl / next-i18next**
- éåžžã¯**éäžç®¡çãããJSONã«ã¿ãã°**ã䜿çšãããããTMSãšã®ãšã¯ã¹ããŒã/ã€ã³ããŒããç°¡åã§ãã
- äžèšãã©ãããã©ãŒã åãã®æçãããšã³ã·ã¹ãã ãäŸ/çµ±åããããŸãã
- **Intlayer**
- **忣åã®ã³ã³ããŒãã³ãããšã®èŸæž**ãæšå¥šãã**TypeScript/TSX/JS/JSON/MD**ã³ã³ãã³ãããµããŒãããŸãã
- ããã«ããã³ãŒãã®ã¢ãžã¥ãŒã«æ§ãåäžããŸãããããŒã«ãéäžç®¡çããããã©ãããªJSONãã¡ã€ã«ãæåŸ
ããå Žåããã©ã°ã¢ã³ããã¬ã€ã®TMSçµ±åãé£ãããªãããšããããŸãã
- Intlayerã¯ä»£æ¿ææ®µãæäŸããŸãïŒ**AIæ¯æŽç¿»èš³**ïŒãèªèº«ã®ãããã€ããŒããŒã䜿çšïŒã**ããžã¥ã¢ã«ãšãã£ã¿ãŒ/CMS**ãããã³ã®ã£ãããæ€åºããŠäºåå
¥åããããã®**CLI/CI**ã¯ãŒã¯ãããŒã
> 泚æ: `next-intl` ãš `i18next` 㯠TypeScript ã«ã¿ãã°ãåãå
¥ããŸããããããŒã ãã¡ãã»ãŒãžã `.ts` ãã¡ã€ã«ã«ä¿åããããæ©èœããšã«åæ£ç®¡çããŠããå Žåãåæ§ã® TMS ã®æ©æŠã«çŽé¢ããããšããããŸããããããå€ãã® `next-intl` ã®ã»ããã¢ãã㯠`locales/` ãã©ã«ãã«éäžããŠãããTMS çšã« JSON ã«ãªãã¡ã¯ã¿ãªã³ã°ããã®ãå°ã容æã§ãã
## éçºè
äœéš
ãã®éšåã§ã¯ã3ã€ã®ãœãªã¥ãŒã·ã§ã³ãæ·±ãæ¯èŒããŸããåãœãªã¥ãŒã·ã§ã³ã®ãã¯ããã«ãããã¥ã¡ã³ãã«èšèŒãããŠããåçŽãªã±ãŒã¹ãèæ
®ããã®ã§ã¯ãªããããå®éã®ãããžã§ã¯ãã«è¿ãå®çšçãªãŠãŒã¹ã±ãŒã¹ãèããŸãã
### ã¢ããªæ§é
ã¢ããªã®æ§é ã¯ãã³ãŒãããŒã¹ã®è¯å¥œãªä¿å®æ§ã確ä¿ããããã«éèŠã§ãã
<Tabs defaultTab="next-intl" group='techno'>
<Tab label="next-i18next" value="next-i18next">
```bash
.
âââ i18n.config.ts
âââ src
âââ locales
â âââ en
â â âââ common.json
â â âââ about.json
â âââ fr
â âââ common.json
â âââ about.json
âââ app
â âââ i18n
â â âââ server.ts
â âââ [locale]
â âââ layout.tsx
â âââ about.tsx
âââ components
âââ I18nProvider.tsx
âââ ClientComponent.tsx
âââ ServerComponent.tsx
```
</Tab>
<Tab label="next-intl" value="next-intl">
```bash
.
âââ i18n.ts
âââ locales
â âââ en
â â âââ home.json
â â âââ navbar.json
â âââ fr
â â âââ home.json
â â âââ navbar.json
â âââ es
â âââ home.json
â âââ navbar.json
âââ src
âââ middleware.ts
âââ app
â âââ i18n
â â âââ server.ts
â âââ [locale]
â âââ home.tsx
âââ components
âââ Navbar
âââ index.tsx
```
</Tab>
<Tab label="intlayer" value="intlayer">
```bash
.
âââ intlayer.config.ts
âââ src
âââ middleware.ts
âââ app
â âââ [locale]
â âââ layout.tsx
â âââ home
â âââ index.tsx
â âââ index.content.ts
âââ components
âââ Navbar
âââ index.tsx
âââ index.content.ts
```
</Tab>
</Tabs>
#### æ¯èŒ
- **next-intl / next-i18next**: éäžç®¡çãããã«ã¿ãã°ïŒJSON; åå空é/ã¡ãã»ãŒãžïŒãæ§é ãæç¢ºã§ç¿»èš³ãã©ãããã©ãŒã ãšããçµ±åãããããã¢ããªã倧ãããªããšãã¡ã€ã«éã®ç·šéãå¢ããå¯èœæ§ãããã
- **Intlayer**: ã³ã³ããŒãã³ãããšã« `.content.{ts|js|json}` èŸæžãã³ã³ããŒãã³ããšåãå Žæã«é
眮ãããŠãããã³ã³ããŒãã³ãã®åå©çšã屿çãªçè§£ã容æã«ãªããããã¡ã€ã«ãå¢ãããã«ãæã®ããŒã«ã«äŸåããã
#### ã»ããã¢ãããšã³ã³ãã³ãã®èªã¿èŸŒã¿
åè¿°ã®ããã«ãåJSONãã¡ã€ã«ã®ã€ã³ããŒãæ¹æ³ãæé©åããå¿
èŠããããŸãã
ã©ã€ãã©ãªãã³ã³ãã³ãã®èªã¿èŸŒã¿ãã©ã®ããã«åŠçããããéèŠã§ãã
<Tabs defaultTab="next-intl" group='techno'>
<Tab label="next-i18next" value="next-i18next">
```tsx fileName="next-i18next.config.js"
module.exports = {
i18n: {
locales: ["en", "fr", "es"],
defaultLocale: "en",
},
};
```
```tsx fileName="src/app/_app.tsx"
import { appWithTranslation } from "next-i18next";
const MyApp = ({ Component, pageProps }) => <Component {...pageProps} />;
export default appWithTranslation(MyApp);
```
```tsx fileName="src/app/[locale]/about/page.tsx"
import type { GetStaticProps } from "next";
import { serverSideTranslations } from "next-i18next/serverSideTranslations";
import { useTranslation } from "next-i18next";
import { I18nextProvider, initReactI18next } from "react-i18next";
import { createInstance } from "i18next";
import { ClientComponent, ServerComponent } from "@components";
export default function HomePage({ locale }: { locale: string }) {
// ãã®ã³ã³ããŒãã³ãã§äœ¿çšããåå空éãæç€ºçã«å®£èšããŸã
const resources = await loadMessagesFor(locale); // ããªãã®ããŒããŒïŒJSONãªã©ïŒ
const i18n = createInstance();
i18n.use(initReactI18next).init({
lng: locale,
fallbackLng: "en",
resources,
ns: ["common", "about"],
defaultNS: "common",
interpolation: { escapeValue: false },
});
const { t } = useTranslation("about");
return (
<I18nextProvider i18n={i18n}>
<main>
<h1>{t("title")}</h1>
<ClientComponent />
<ServerComponent />
</main>
</I18nextProvider>
);
}
export const getStaticProps: GetStaticProps = async ({ locale }) => {
// ãã®ããŒãžã«å¿
èŠãªåå空éã®ã¿ãããªããŒãããŸã
return {
props: {
...(await serverSideTranslations(locale ?? "en", ["common", "about"])),
},
};
};
```
</Tab>
<Tab label="next-intl" value="next-intl">
```tsx fileName="i18n.ts"
import { getRequestConfig } from "next-intl/server";
import { notFound } from "next/navigation";
// å
±æèšå®ããã€ã³ããŒãå¯èœ
const locales = ["en", "fr", "es"];
export default getRequestConfig(async ({ locale }) => {
// åãåã£ã `locale` ãã©ã¡ãŒã¿ãæå¹ãæ€èšŒããŸã
if (!locales.includes(locale as any)) notFound();
return {
messages: (await import(`../messages/${locale}.json`)).default,
};
});
```
```tsx fileName="src/app/[locale]/about/layout.tsx"
import { NextIntlClientProvider } from "next-intl";
import { getMessages, unstable_setRequestLocale } from "next-intl/server";
import pick from "lodash/pick";
export default async function LocaleLayout({
children,
params,
}: {
children: React.ReactNode;
params: { locale: string };
}) {
const { locale } = params;
// ãã®ãµãŒããŒã¬ã³ããªã³ã°ïŒRSCïŒçšã«ã¢ã¯ãã£ããªãªã¯ãšã¹ããã±ãŒã«ãèšå®ããŸã
unstable_setRequestLocale(locale);
// ã¡ãã»ãŒãžã¯ src/i18n/request.ts çµç±ã§ãµãŒããŒåŽã§èªã¿èŸŒãŸããŸã
// ïŒnext-intl ã®ããã¥ã¡ã³ãåç
§ïŒãããã§ã¯ã¯ã©ã€ã¢ã³ãã³ã³ããŒãã³ãã«å¿
èŠãª
// ãµãã»ããã®ã¿ãã¯ã©ã€ã¢ã³ãã«æž¡ããŸãïŒãã€ããŒãæé©åïŒã
const messages = await getMessages();
const clientMessages = pick(messages, ["common", "about"]);
const rtlLocales = ["ar", "he", "fa", "ur"];
return (
<html lang={locale} dir={rtlLocales.includes(locale) ? "rtl" : "ltr"}>
<body>
<NextIntlClientProvider locale={locale} messages={clientMessages}>
{children}
</NextIntlClientProvider>
</body>
</html>
);
}
```
```tsx fileName="src/app/[locale]/about/page.tsx"
import { getTranslations } from "next-intl/server";
import { ClientComponent, ServerComponent } from "@components";
export default async function LandingPage({
params,
}: {
params: { locale: string };
}) {
// å®å
šã«ãµãŒããŒåŽã§ã®èªã¿èŸŒã¿ïŒã¯ã©ã€ã¢ã³ãåŽã§ã®ãã€ãã¬ãŒã·ã§ã³ãªãïŒ
const t = await getTranslations("about");
return (
<main>
<h1>{t("title")}</h1>
<ClientComponent />
<ServerComponent />
</main>
);
}
```
</Tab>
<Tab label="intlayer" value="intlayer">
```tsx fileName="intlayer.config.ts"
export default {
internationalization: {
locales: ["en", "fr", "es"],
defaultLocale: "en",
},
};
```
```tsx fileName="src/app/[locale]/layout.tsx"
import { getHTMLTextDir } from "intlayer";
import {
IntlayerClientProvider,
generateStaticParams,
type NextLayoutIntlayer,
} from "next-intlayer";
export const dynamic = "force-static";
const LandingLayout: NextLayoutIntlayer = async ({ children, params }) => {
const { locale } = await params;
return (
<html lang={locale} dir={getHTMLTextDir(locale)}>
<IntlayerClientProvider locale={locale}>
{children}
</IntlayerClientProvider>
</html>
);
};
export default LandingLayout;
```
```tsx fileName="src/app/[locale]/about/page.tsx"
import { PageContent } from "@components/PageContent";
import type { NextPageIntlayer } from "next-intlayer";
import { IntlayerServerProvider, useIntlayer } from "next-intlayer/server";
import { ClientComponent, ServerComponent } from "@components";
const LandingPage: NextPageIntlayer = async ({ params }) => {
const { locale } = await params;
const { title } = useIntlayer("about", locale);
return (
<IntlayerServerProvider locale={locale}>
<main>
<h1>{title}</h1>
<ClientComponent />
<ServerComponent />
</main>
</IntlayerServerProvider>
);
};
export default LandingPage;
```
</Tab>
</Tabs>
#### æ¯èŒ
3ã€ãã¹ãŠããã±ãŒã«ããšã®ã³ã³ãã³ãèªã¿èŸŒã¿ãšãããã€ããŒããµããŒãããŠããŸãã
- **next-intl/next-i18next** ã§ã¯ãéåžžãã«ãŒãããšã«éžæãããã¡ãã»ãŒãžãåå空éãèªã¿èŸŒã¿ãå¿
èŠãªå Žæã«ãããã€ããŒãé
眮ããŸãã
- **Intlayer** ã§ã¯ããã«ãæã®è§£æã远å ããŠäœ¿çšç¶æ³ãæšæž¬ããæåã®é
ç·ãæžãããåäžã®ã«ãŒããããã€ããŒãèš±å¯ããå ŽåããããŸãã
ããŒã ã®å¥œã¿ã«å¿ããŠãæç€ºçãªå¶åŸ¡ãšèªååã®ã©ã¡ãããéžæããŠãã ããã
### ã¯ã©ã€ã¢ã³ãã³ã³ããŒãã³ãã§ã®äœ¿çšäŸ
ã«ãŠã³ã¿ãŒãã¬ã³ããªã³ã°ããã¯ã©ã€ã¢ã³ãã³ã³ããŒãã³ãã®äŸãèŠãŠã¿ãŸãããã
<Tabs defaultTab="next-intl" group='techno'>
<Tab label="next-i18next" value="next-i18next">
**翻蚳ïŒ`public/locales/...` ã«å®éã®JSONãã¡ã€ã«ãå¿
èŠã§ãïŒ**
```json fileName="public/locales/en/about.json"
{
"counter": {
"label": "Counter",
"increment": "Increment"
}
}
```
```json fileName="public/locales/fr/about.json"
{
"counter": {
"label": "Compteur",
"increment": "Incrémenter"
}
}
```
**ã¯ã©ã€ã¢ã³ãã³ã³ããŒãã³ã**
```tsx fileName="src/components/ClientComponentExample.tsx"
"use client";
import React, { useMemo, useState } from "react";
import { useTranslation } from "next-i18next";
const ClientComponentExample = () => {
const { t, i18n } = useTranslation("about");
const [count, setCount] = useState(0);
// next-i18nextã¯useNumberãå
¬éããŠããªããããIntl.NumberFormatã䜿çš
const numberFormat = new Intl.NumberFormat(i18n.language);
return (
<div>
<p>{numberFormat.format(count)}</p>
<button
aria-label={t("counter.label")}
onClick={() => setCount((count) => count + 1)}
>
{t("counter.increment")}
</button>
</div>
);
};
```
> ããŒãžã® serverSideTranslations ã« "about" ããŒã ã¹ããŒã¹ã远å ããã®ãå¿ããªãã§ãã ãã
> ããã§ã¯ React 19.x.x ã®ããŒãžã§ã³ã䜿çšããŠããŸãããããäœãããŒãžã§ã³ã®å Žåã¯ãéã颿°ã§ãããããã©ãŒããã¿ãŒã®ã€ã³ã¹ã¿ã³ã¹ãä¿æããããã« useMemo ã䜿çšããå¿
èŠããããŸã
</Tab>
<Tab label="next-intl" value="next-intl">
**翻蚳ïŒåœ¢ç¶ã¯åå©çšå¯èœïŒã奜ã¿ã§ next-intl ã®ã¡ãã»ãŒãžã«ããŒãããŠãã ããïŒ**
```json fileName="locales/en/about.json"
{
"counter": {
"label": "Counter",
"increment": "Increment"
}
}
```
```json fileName="locales/fr/about.json"
{
"counter": {
"label": "Compteur",
"increment": "Incrémenter"
}
}
```
**ã¯ã©ã€ã¢ã³ãã³ã³ããŒãã³ã**
```tsx fileName="src/components/ClientComponentExample.tsx"
"use client";
import React, { useState } from "react";
import { useTranslations, useFormatter } from "next-intl";
const ClientComponentExample = () => {
// ãã¹ãããããªããžã§ã¯ãã«çŽæ¥ã¹ã³ãŒããèšå®
const t = useTranslations("about.counter");
const format = useFormatter();
const [count, setCount] = useState(0);
return (
<div>
<p>{format.number(count)}</p>
<button
aria-label={t("label")}
onClick={() => setCount((count) => count + 1)}
>
{t("increment")}
</button>
</div>
);
};
```
> ããŒãžã®ã¯ã©ã€ã¢ã³ãã¡ãã»ãŒãžã« "about" ã¡ãã»ãŒãžã远å ããã®ãå¿ããªãã§ãã ãã
</Tab>
<Tab label="intlayer" value="intlayer">
**ã³ã³ãã³ã**
```ts fileName="src/components/ClientComponentExample/index.content.ts"
import { t, type Dictionary } from "intlayer";
const counterContent = {
key: "counter",
content: {
label: t({ ja: "ã«ãŠã³ã¿ãŒ", en: "Counter", fr: "Compteur" }),
increment: t({ ja: "ã€ã³ã¯ãªã¡ã³ã", en: "Increment", fr: "Incrémenter" }),
},
} satisfies Dictionary;
export default counterContent;
```
**ã¯ã©ã€ã¢ã³ãã³ã³ããŒãã³ã**
```tsx fileName="src/components/ClientComponentExample/index.tsx"
"use client";
import React, { useState } from "react";
import { useNumber, useIntlayer } from "next-intlayer";
const ClientComponentExample = () => {
const [count, setCount] = useState(0);
const { label, increment } = useIntlayer("counter"); // æååãè¿ã
const { number } = useNumber();
return (
<div>
<p>{number(count)}</p>
<button aria-label={label} onClick={() => setCount((count) => count + 1)}>
{increment}
</button>
</div>
);
};
```
</Tab>
</Tabs>
#### æ¯èŒ
- **æ°å€ãã©ãŒããã**
- **next-i18next**: `useNumber` ã¯ãªãã`Intl.NumberFormat`ïŒãŸã㯠i18next-icuïŒã䜿çšã
- **next-intl**: `useFormatter().number(value)` ã䜿çšã
- **Intlayer**: çµã¿èŸŒã¿ã® `useNumber()` ã䜿çšã
- **ããŒ**
- ãã¹ããããæ§é ïŒ`about.counter.label`ïŒãç¶æããããã¯ã®ã¹ã³ãŒããé©åã«èšå®ããïŒ`useTranslation("about")` + `t("counter.label")` ãŸã㯠`useTranslations("about.counter")` + `t("label")`ïŒã
- **ãã¡ã€ã«ã®å Žæ**
- **next-i18next** 㯠`public/locales/{lng}/{ns}.json` ã« JSON ãæåŸ
ã
- **next-intl** ã¯æè»ã§ãèšå®ã«å¿ããŠã¡ãã»ãŒãžãããŒãå¯èœã
- **Intlayer** 㯠TS/JS ã®èŸæžã«ã³ã³ãã³ããæ ŒçŽããããŒã§è§£æ±ºã
---
### ãµãŒããŒã³ã³ããŒãã³ãã§ã®äœ¿çš
UIã³ã³ããŒãã³ãã®å ŽåãèããŸãããã®ã³ã³ããŒãã³ãã¯ãµãŒããŒã³ã³ããŒãã³ãã§ãããã¯ã©ã€ã¢ã³ãã³ã³ããŒãã³ãã®åãšããŠæ¿å
¥ã§ããå¿
èŠããããŸããïŒããŒãžïŒãµãŒããŒã³ã³ããŒãã³ãïŒ -> ã¯ã©ã€ã¢ã³ãã³ã³ããŒãã³ã -> ãµãŒããŒã³ã³ããŒãã³ãïŒããã®ã³ã³ããŒãã³ãã¯ã¯ã©ã€ã¢ã³ãã³ã³ããŒãã³ãã®åãšããŠæ¿å
¥ã§ãããããéåæã«ã¯ã§ããŸããã
<Tabs defaultTab="next-intl" group='techno'>
<Tab label="next-i18next" value="next-i18next">
```tsx fileName="src/pages/about.tsx"
import type { GetStaticProps } from "next";
import { useTranslation } from "next-i18next";
type ServerComponentProps = {
count: number;
};
const ServerComponent = ({ count }: ServerComponentProps) => {
const { t, i18n } = useTranslation("about");
const formatted = new Intl.NumberFormat(i18n.language).format(count);
return (
<div>
<p>{formatted}</p>
<button aria-label={t("counter.label")}>{t("counter.increment")}</button>
</div>
);
};
```
</Tab>
<Tab label="next-intl" value="next-intl">
```tsx fileName="src/components/ServerComponent.tsx"
type ServerComponentProps = {
count: number;
t: (key: string) => string;
formatter: Intl.NumberFormat;
};
const ServerComponent = ({ t, count, formatter }: ServerComponentProps) => {
const formatted = formatter.format(count);
return (
<div>
<p>{formatted}</p>
<button aria-label={t("label")}>{t("increment")}</button>
</div>
);
};
```
> ãµãŒããŒã³ã³ããŒãã³ãã¯éåæã«ã§ããªããããç¿»èš³é¢æ°ãšãã©ãŒããã¿ãŒé¢æ°ãããããã£ãšããŠæž¡ãå¿
èŠããããŸãã
>
> - `const t = await getTranslations("about.counter");`
> - `const formatter = await getFormatter().then((formatter) => formatter.number());`
</Tab>
<Tab label="intlayer" value="intlayer">
```tsx fileName="src/components/ServerComponent.tsx"
import { useIntlayer, useNumber } from "next-intlayer/server";
const ServerComponent = ({ count }: { count: number }) => {
const { label, increment } = useIntlayer("counter");
const { number } = useNumber();
return (
<div>
<p>{number(count)}</p>
<button aria-label={label}>{increment}</button>
</div>
);
};
```
</Tab>
</Tabs>
> Intlayerã¯`next-intlayer/server`ãéããŠ**ãµãŒããŒã»ãŒã**ãªããã¯ãæäŸããŸããåäœããããã«ã`useIntlayer`ãš`useNumber`ã¯ã¯ã©ã€ã¢ã³ãããã¯ã«äŒŒãããã¯ã®ãããªæ§æã䜿çšããŸãããå
éšçã«ã¯ãµãŒããŒã³ã³ããã¹ãïŒ`IntlayerServerProvider`ïŒã«äŸåããŠããŸãã
### ã¡ã¿ããŒã¿ / ãµã€ãããã / ãããã
ã³ã³ãã³ãã®ç¿»èš³ã¯çŽ æŽãããããšã§ããããããå€ãã®äººã¯åœéåã®äž»ãªç®çãããªãã®ãŠã§ããµã€ããäžçã«ããèŠããããã«ããããšã ãšããããšãå¿ããã¡ã§ããI18nã¯ããªãã®ãŠã§ããµã€ãã®å¯èŠæ§ãåäžãããããã®éåžžã«åŒ·åãªææ®µã§ãã
以äžã¯å€èšèªSEOã«é¢ãããã¹ããã©ã¯ãã£ã¹ã®ãªã¹ãã§ãã
- `<head>`ã¿ã°å
ã«hreflangã¡ã¿ã¿ã°ãèšå®ãã
> ããã¯æ€çŽ¢ãšã³ãžã³ãããŒãžã§å©çšå¯èœãªèšèªãçè§£ããã®ã«åœ¹ç«ã¡ãŸã
- sitemap.xml ã«ãã¹ãŠã®ããŒãžã®ç¿»èš³ã `http://www.w3.org/1999/xhtml` XML ã¹ããŒãã䜿ã£ãŠãªã¹ãã¢ãããã
>
- robots.txt ãããã¬ãã£ãã¯ã¹ä»ãããŒãžãé€å€ããã®ãå¿ããªãïŒäŸïŒ`/dashboard`ãããã³ `/fr/dashboard`ã`/es/dashboard`ïŒ
>
- ã«ã¹ã¿ã Link ã³ã³ããŒãã³ãã䜿ã£ãŠæãããŒã«ã©ã€ãºãããããŒãžãžãªãã€ã¬ã¯ãããïŒäŸïŒãã©ã³ã¹èªã§ã¯ `<a href="/fr/about">A propos</a>`ïŒ
>
éçºè
ã¯ãã°ãã°ãã±ãŒã«éã§ããŒãžãé©åã«åç
§ããããšãå¿ããã¡ã§ãã
<Tabs defaultTab="next-intl" group='techno'>
<Tab label="next-i18next" value="next-i18next">
```ts fileName="i18n.config.ts"
export const locales = ["en", "fr"] as const;
export type Locale = (typeof locales)[number];
export const defaultLocale: Locale = "en";
export function localizedPath(locale: string, path: string) {
return locale === defaultLocale ? path : "/" + locale + path;
}
const ORIGIN = "https://example.com";
export function abs(locale: string, path: string) {
return ORIGIN + localizedPath(locale, path);
}
```
```tsx fileName="src/app/[locale]/about/layout.tsx"
import type { Metadata } from "next";
import { locales, defaultLocale, localizedPath } from "@/i18n.config";
export async function generateMetadata({
params,
}: {
params: { locale: string };
}): Promise<Metadata> {
const { locale } = params;
// æ£ããJSONãã¡ã€ã«ãåçã«ã€ã³ããŒããã
const messages = (
await import("@/../public/locales/" + locale + "/about.json")
).default;
const languages = Object.fromEntries(
locales.map((locale) => [locale, localizedPath(locale, "/about")])
);
return {
title: messages.title,
description: messages.description,
alternates: {
canonical: localizedPath(locale, "/about"),
languages: { ...languages, "x-default": "/about" },
},
};
}
export default async function AboutPage() {
return <h1>æŠèŠ</h1>; // ãAboutãã®æ¥æ¬èªèš³
}
```
```ts fileName="src/app/sitemap.ts"
import type { MetadataRoute } from "next";
import { locales, defaultLocale, abs } from "@/i18n.config";
export default function sitemap(): MetadataRoute.Sitemap {
const languages = Object.fromEntries(
locales.map((locale) => [locale, abs(locale, "/about")])
);
return [
{
url: abs(defaultLocale, "/about"),
lastModified: new Date(),
changeFrequency: "monthly", // æŽæ°é »åºŠïŒæ¯æ
priority: 0.7, // åªå
床
alternates: { languages },
},
];
}
```
```ts fileName="src/app/robots.ts"
import type { MetadataRoute } from "next";
import { locales, defaultLocale, localizedPath } from "@/i18n.config";
const ORIGIN = "https://example.com";
const expandAllLocales = (path: string) => [
localizedPath(defaultLocale, path),
...locales
.filter((locale) => locale !== defaultLocale)
.map((locale) => localizedPath(locale, path)),
];
export default function robots(): MetadataRoute.Robots {
const disallow = [
...expandAllLocales("/dashboard"),
...expandAllLocales("/admin"),
];
return {
rules: { userAgent: "*", allow: ["/"], disallow },
host: ORIGIN,
sitemap: ORIGIN + "/sitemap.xml",
};
}
```
</Tab>
<Tab label="next-intl" value="next-intl">
```tsx fileName="src/app/[locale]/about/layout.tsx"
import type { Metadata } from "next";
import { locales, defaultLocale } from "@/i18n";
import { getTranslations } from "next-intl/server";
function localizedPath(locale: string, path: string) {
return locale === defaultLocale ? path : "/" + locale + path;
}
export async function generateMetadata({
params,
}: {
params: { locale: string };
}): Promise<Metadata> {
const { locale } = params;
const t = await getTranslations({ locale, namespace: "about" });
const url = "/about";
const languages = Object.fromEntries(
locales.map((locale) => [locale, localizedPath(locale, url)])
);
return {
title: t("title"),
description: t("description"),
alternates: {
canonical: localizedPath(locale, url),
languages: { ...languages, "x-default": url },
},
};
}
// ... ããŒãžã®æ®ãã®ã³ãŒã
```
```tsx fileName="src/app/sitemap.ts"
import type { MetadataRoute } from "next";
import { locales, defaultLocale } from "@/i18n";
const origin = "https://example.com";
const formatterLocalizedPath = (locale: string, path: string) =>
locale === defaultLocale ? origin + path : origin + "/" + locale + path;
export default function sitemap(): MetadataRoute.Sitemap {
const aboutLanguages = Object.fromEntries(
locales.map((l) => [l, formatterLocalizedPath(l, "/about")])
);
return [
{
url: formatterLocalizedPath(defaultLocale, "/about"),
lastModified: new Date(),
changeFrequency: "monthly",
priority: 0.7,
alternates: { languages: aboutLanguages },
},
];
}
```
```tsx fileName="src/app/robots.ts"
import type { MetadataRoute } from "next";
import { locales, defaultLocale } from "@/i18n";
const origin = "https://example.com";
const withAllLocales = (path: string) => [
path,
...locales
.filter((locale) => locale !== defaultLocale)
.map((locale) => "/" + locale + path),
];
export default function robots(): MetadataRoute.Robots {
const disallow = [
...withAllLocales("/dashboard"),
...withAllLocales("/admin"),
];
return {
rules: { userAgent: "*", allow: ["/"], disallow },
host: origin,
sitemap: origin + "/sitemap.xml",
};
}
```
</Tab>
<Tab label="intlayer" value="intlayer">
```typescript fileName="src/app/[locale]/about/layout.tsx"
import { getIntlayer, getMultilingualUrls } from "intlayer";
import type { Metadata } from "next";
import type { LocalPromiseParams } from "next-intlayer";
export const generateMetadata = async ({
params,
}: LocalPromiseParams): Promise<Metadata> => {
const { locale } = await params;
const metadata = getIntlayer("page-metadata", locale);
const multilingualUrls = getMultilingualUrls("/about");
return {
...metadata,
alternates: {
canonical: multilingualUrls[locale as keyof typeof multilingualUrls],
languages: { ...multilingualUrls, "x-default": "/about" },
},
};
};
// ... ããŒãžã®æ®ãã®ã³ãŒã
```
```tsx fileName="src/app/sitemap.ts"
import { getMultilingualUrls } from "intlayer";
import type { MetadataRoute } from "next";
const sitemap = (): MetadataRoute.Sitemap => [
{
url: "https://example.com/about",
alternates: {
languages: { ...getMultilingualUrls("https://example.com/about") },
},
},
];
```
```tsx fileName="src/app/robots.ts"
import { getMultilingualUrls } from "intlayer";
import type { MetadataRoute } from "next";
const getAllMultilingualUrls = (urls: string[]) =>
urls.flatMap((url) => Object.values(getMultilingualUrls(url)) as string[]);
const robots = (): MetadataRoute.Robots => ({
rules: {
userAgent: "*",
allow: ["/"],
disallow: getAllMultilingualUrls(["/dashboard"]),
},
host: "https://example.com",
sitemap: "https://example.com/sitemap.xml",
});
export default robots;
```
</Tab>
</Tabs>
> Intlayerã¯ããµã€ããããçšã®å€èšèªURLãçæããããã®`getMultilingualUrls`颿°ãæäŸããŠããŸãã
---
---
## ãããŠåè
ã¯âŠ
ç°¡åã§ã¯ãããŸãããåãªãã·ã§ã³ã«ã¯ãã¬ãŒããªãããããŸããç§ã®èŠè§£ã¯ä»¥äžã®éãã§ãïŒ
<Columns>
<Column>
**next-intl**
- æãã·ã³ãã«ã§è»œéã匷å¶ãããæ±ºå®ãå°ãªãã§ãã**æå°é**ã®ãœãªã¥ãŒã·ã§ã³ãæ±ããŠããŠãéäžç®¡çãããã«ã¿ãã°ã«æ
£ããŠãããã¢ããªã**å°èŠæš¡ããäžèŠæš¡**ã®å Žåã«é©ããŠããŸãã
</Column>
<Column>
**next-i18next**
- æçããŠãããæ©èœãè±å¯ã§ã³ãã¥ããã£ãã©ã°ã€ã³ãå€ãã§ãããã»ããã¢ããã³ã¹ãã¯é«ãã§ãã**i18nextã®ãã©ã°ã€ã³ãšã³ã·ã¹ãã **ïŒäŸïŒãã©ã°ã€ã³çµç±ã®é«åºŠãªICUã«ãŒã«ïŒãå¿
èŠã§ãããŒã ããã§ã«i18nextãç¥ã£ãŠããŠãæè»æ§ã®ããã«**ããå€ãã®èšå®**ãåãå
¥ããããå Žåã«é©ããŠããŸãã
</Column>
<Column>
**Intlayer**
- ã¢ãã³ãª Next.js åãã«æ§ç¯ãããŠãããã¢ãžã¥ã©ãŒã³ã³ãã³ããåå®å
šæ§ãããŒã«çŸ€ããããŠãã€ã©ãŒãã¬ãŒãã®åæžãå®çŸããŠããŸãã**ã³ã³ããŒãã³ãåäœã®ã³ã³ãã³ã管ç**ã**峿 Œãª TypeScript**ã**ãã«ãæã®ä¿èšŒ**ã**ããªãŒã·ã§ã€ãã³ã°**ããããŠ**ã«ãŒãã£ã³ã°/SEO/ãšãã£ã¿ãŒããŒã«ãããããªãŒèŸŒã¿**ã§æäŸãããããšãéèŠããå Žåãç¹ã« **Next.js App Router**ããã¶ã€ã³ã·ã¹ãã ããããŠ**å€§èŠæš¡ã§ã¢ãžã¥ã©ãŒãªã³ãŒãããŒã¹**ã«æé©ã§ãã
</Column>
</Columns>
ã»ããã¢ãããæå°éã«æããå€å°ã®æåèšå®ã蚱容ã§ãããªã next-intl ãè¯ãéžæã§ãããã¹ãŠã®æ©èœãå¿
èŠã§è€éããæ°ã«ããªããªã next-i18next ãé©ããŠããŸããããããã¢ãã³ã§ã¹ã±ãŒã©ãã«ãã¢ãžã¥ã©ãŒãªãœãªã¥ãŒã·ã§ã³ããã«ãã€ã³ããŒã«ãšå
±ã«æ±ãããªããIntlayer ã¯ãããããã«æäŸããããšãç®æããŠããŸãã
> **ãšã³ã¿ãŒãã©ã€ãºããŒã åãã®ä»£æ¿æ¡**: **Crowdin**ã**Phrase**ããŸãã¯ãã®ä»ã®ãããã§ãã·ã§ãã«ãªç¿»èš³ç®¡çã·ã¹ãã ã®ãããªç¢ºç«ãããããŒã«ãªãŒãŒã·ã§ã³ãã©ãããã©ãŒã ãšå®å
šã«é£æºãããå®çžŸã®ãããœãªã¥ãŒã·ã§ã³ãå¿
èŠãªå Žåã¯ãæçãããšã³ã·ã¹ãã ãšå®èšŒæžã¿ã®çµ±åãæã€ **next-intl** ãŸã㯠**next-i18next** ãæ€èšããŠãã ããã
> **ä»åŸã®ããŒãããã**: Intlayer ã¯ã**i18next** ããã³ **next-intl** ãœãªã¥ãŒã·ã§ã³ã®äžã«åäœãããã©ã°ã€ã³ã®éçºãèšç»ããŠããŸããããã«ãããIntlayer ã®èªååãæ§æãããã³ã³ã³ãã³ã管çã®å©ç¹ã享åãã€ã€ããããã®ç¢ºç«ããããœãªã¥ãŒã·ã§ã³ãã¢ããªã±ãŒã·ã§ã³ã³ãŒãã«æäŸããã»ãã¥ãªãã£ãšå®å®æ§ãç¶æã§ããŸãã
## GitHub STARs
GitHubã®ã¹ã¿ãŒã¯ããããžã§ã¯ãã®äººæ°ãã³ãã¥ããã£ã®ä¿¡é ŒããããŠé·æçãªé¢é£æ§ã瀺ã匷åãªææšã§ããæè¡çãªå質ã®çŽæ¥çãªå°ºåºŠã§ã¯ãããŸããããã©ãã ãå€ãã®éçºè
ããã®ãããžã§ã¯ããæçšãšæãã鲿ã远ããæ¡çšããå¯èœæ§ãé«ãããåæ ããŠããŸãããããžã§ã¯ãã®äŸ¡å€ãè©äŸ¡ããéãã¹ã¿ãŒã¯ä»£æ¿æ¡éã®ãã©ã¯ã·ã§ã³ãæ¯èŒãããšã³ã·ã¹ãã ã®æé·ã«é¢ããæŽå¯ãæäŸããã®ã«åœ¹ç«ã¡ãŸãã
[](https://www.star-history.com/#i18next/next-i18next&amannn/next-intl&aymericzip/intlayer)
---
## çµè«
3ã€ã®ã©ã€ãã©ãªã¯ãã¹ãŠã³ã¢ãªããŒã«ãªãŒãŒã·ã§ã³ã«æåããŠããŸããéãã¯ã**ã¢ãã³ãª Next.js** ã§å
ç¢ã§ã¹ã±ãŒã©ãã«ãªã»ããã¢ãããå®çŸããããã«ã**ã©ãã ãã®äœæ¥ãå¿
èŠã**ãšããç¹ã§ãã
- **Intlayer** ã§ã¯ã**ã¢ãžã¥ã©ãŒã³ã³ãã³ã**ã**峿 ŒãªTS**ã**ãã«ãæã®å®å
šæ§**ã**ããªãŒã·ã§ã€ã¯ããããã³ãã«**ãããã³ **äžæµã®App Router + SEOããŒã«** ã **ããã©ã«ã** ã§ãããæéã§ã¯ãããŸããã
- ããŒã ãå€èšèªå¯Ÿå¿ã®ã³ã³ããŒãã³ãé§ååã¢ããªã«ãããŠã**ä¿å®æ§ãšé床**ãéèŠãããªããIntlayerã¯ä»æ¥æã**å®å
šãª**äœéšãæäŸããŸãã
詳现㯠['Why Intlayer?' ããã¥ã¡ã³ã](https://intlayer.org/doc/why) ãåç
§ããŠãã ããã