next-intl
Advanced tools
Comparing version
@@ -16,6 +16,8 @@ 'use strict'; | ||
var children = _ref.children, | ||
locale = _ref.locale, | ||
messages = _ref.messages; | ||
return React__default.createElement(NextIntlContext.Provider, { | ||
value: { | ||
messages: messages | ||
messages: messages, | ||
locale: locale | ||
} | ||
@@ -52,6 +54,3 @@ }, children); | ||
var context = React.useContext(NextIntlContext); | ||
var _useRouter = router.useRouter(), | ||
locale = _useRouter.locale; | ||
var nextLocale = router.useRouter().locale; | ||
var cachedFormatsByLocaleRef = React.useRef({}); | ||
@@ -65,2 +64,3 @@ | ||
var locale = context.locale || nextLocale; | ||
var allMessages = context.messages; | ||
@@ -67,0 +67,0 @@ var messages = React.useMemo(function () { |
@@ -1,2 +0,2 @@ | ||
"use strict";function e(e){return e&&"object"==typeof e&&"default"in e?e.default:e}Object.defineProperty(exports,"__esModule",{value:!0});var r=require("react"),t=e(r),n=e(require("intl-messageformat")),o=require("next/router"),u=r.createContext(void 0);function s(e,r){var t=e;return r.split(".").forEach((function(e){t=t[e]})),t}exports.NextIntlProvider=function(e){return t.createElement(u.Provider,{value:{messages:e.messages}},e.children)},exports.useTranslations=function(e){var t=r.useContext(u),i=o.useRouter().locale,a=r.useRef({});if(!t)throw new Error;var f=t.messages,c=r.useMemo((function(){return e?s(f,e):f}),[f,e]);return function(e,r){var t;if(!i)throw new Error;var o,u=a.current;if(null==(t=u[i])?void 0:t[e])o=u[i][e];else{var f=s(c,e);if("object"==typeof f)throw new Error;o=new n(f,i),u[i]||(u[i]={}),u[i][e]=o}return o.format(r)}}; | ||
"use strict";function e(e){return e&&"object"==typeof e&&"default"in e?e.default:e}Object.defineProperty(exports,"__esModule",{value:!0});var r=require("react"),t=e(r),o=e(require("intl-messageformat")),n=require("next/router"),u=r.createContext(void 0);function a(e,r){var t=e;return r.split(".").forEach((function(e){t=t[e]})),t}exports.NextIntlProvider=function(e){return t.createElement(u.Provider,{value:{messages:e.messages,locale:e.locale}},e.children)},exports.useTranslations=function(e){var t=r.useContext(u),s=n.useRouter().locale,i=r.useRef({});if(!t)throw new Error;var l=t.locale||s,c=t.messages,f=r.useMemo((function(){return e?a(c,e):c}),[c,e]);return function(e,r){var t;if(!l)throw new Error;var n,u=i.current;if(null==(t=u[l])?void 0:t[e])n=u[l][e];else{var s=a(f,e);if("object"==typeof s)throw new Error;n=new o(s,l),u[l]||(u[l]={}),u[l][e]=n}return n.format(r)}}; | ||
//# sourceMappingURL=next-intl.cjs.production.min.js.map |
@@ -9,6 +9,8 @@ import React, { createContext, useContext, useRef, useMemo } from 'react'; | ||
var children = _ref.children, | ||
locale = _ref.locale, | ||
messages = _ref.messages; | ||
return React.createElement(NextIntlContext.Provider, { | ||
value: { | ||
messages: messages | ||
messages: messages, | ||
locale: locale | ||
} | ||
@@ -45,6 +47,3 @@ }, children); | ||
var context = useContext(NextIntlContext); | ||
var _useRouter = useRouter(), | ||
locale = _useRouter.locale; | ||
var nextLocale = useRouter().locale; | ||
var cachedFormatsByLocaleRef = useRef({}); | ||
@@ -60,2 +59,3 @@ | ||
var locale = context.locale || nextLocale; | ||
var allMessages = context.messages; | ||
@@ -62,0 +62,0 @@ var messages = useMemo(function () { |
@@ -5,3 +5,4 @@ /// <reference types="react" /> | ||
messages: Messages; | ||
locale?: string | undefined; | ||
} | undefined>; | ||
export default NextIntlContext; |
@@ -6,4 +6,5 @@ import { ReactNode } from 'react'; | ||
messages: NextIntlMessages; | ||
locale?: string; | ||
}; | ||
export default function NextIntlProvider({ children, messages }: Props): JSX.Element; | ||
export default function NextIntlProvider({ children, locale, messages }: Props): JSX.Element; | ||
export {}; |
{ | ||
"name": "next-intl", | ||
"version": "0.0.2", | ||
"version": "0.0.3", | ||
"author": "Jan Amann <jan@amann.me>", | ||
"description": "Minimal, but complete solution for managing translations in Next.js apps.", | ||
"license": "MIT", | ||
"description": "TODO", | ||
"author": "Jan Amann <jan@amann.me>", | ||
"repository": { | ||
"type": "git", | ||
"url": "https://github.com/amannn/react-hooks/tree/master/packages/next-intl" | ||
"url": "https://github.com/amannn/next-intl" | ||
}, | ||
"scripts": { | ||
"start": "tsdx watch --tsconfig tsconfig.json --verbose --noClean", | ||
"build": "tsdx build --tsconfig tsconfig.json", | ||
"start": "tsdx watch", | ||
"build": "tsdx build", | ||
"test": "tsdx test", | ||
"lint": "eslint src test && tsc --noEmit", | ||
"prepublish": "npm run build" | ||
"lint": "eslint src test && tsc", | ||
"prepare": "tsdx build" | ||
}, | ||
@@ -22,5 +22,8 @@ "main": "dist/index.js", | ||
"files": [ | ||
"README.md", | ||
"dist" | ||
"dist", | ||
"src" | ||
], | ||
"dependencies": { | ||
"intl-messageformat": "^9.3.18" | ||
}, | ||
"peerDependencies": { | ||
@@ -31,12 +34,17 @@ "next": "^10.0.0", | ||
}, | ||
"dependencies": { | ||
"intl-messageformat": "^9.3.18", | ||
"tslib": "^2.0.0" | ||
}, | ||
"devDependencies": { | ||
"@testing-library/react": "^11.1.2", | ||
"@types/react": "^16.9.56", | ||
"eslint": "7.4.0", | ||
"eslint-config-molindo": "5.0.1", | ||
"next": "^10.0.2", | ||
"react": "^17.0.0", | ||
"react-dom": "^17.0.0" | ||
"react-dom": "^17.0.0", | ||
"tsdx": "^0.14.1", | ||
"tslib": "^2.0.3", | ||
"typescript": "^4.1.2" | ||
}, | ||
"engines": { | ||
"node": ">=10" | ||
} | ||
} |
181
README.md
@@ -5,32 +5,173 @@ # next-intl | ||
TODO | ||
Minimal, but complete solution for managing translations in Next.js apps. | ||
short icu guide | ||
## The problem | ||
https://formatjs.io/docs/core-concepts/icu-syntax | ||
Next.js has built-in support for [internationalized routing](https://nextjs.org/docs/advanced-features/i18n-routing), but doesn't have an opinion about how you should handle your translations. | ||
same api for react attributes and children | ||
## This solution | ||
make sure you have polyfills | ||
This is my attempt at providing a minimal, but complete solution that fills in the missing pieces. | ||
cache constructor with ast? | ||
```jsx | ||
function LatestFollower({user}) { | ||
const t = useTranslations('LatestFollower'); | ||
// number (https://github.com/unicode-org/icu/blob/master/docs/userguide/format_parse/numbers/skeletons.md#examples) | ||
date https://formatjs.io/docs/intl-messageformat#datetime-skeleton | ||
select https://formatjs.io/docs/core-concepts/icu-syntax/#select-format | ||
get rid of key for rich text? | ||
return ( | ||
<> | ||
<Text>{t('latestFollower', {username: user.name})}</Text> | ||
<IconButton aria-label={t('followBack')} icon={<FollowIcon />} /> | ||
</> | ||
); | ||
} | ||
``` | ||
// optional component name – return all | ||
// otherwise - componentize | ||
```js | ||
// en.json | ||
{ | ||
"LatestFollower": { | ||
"latestFollower": "{username} started following you", | ||
"followBack": "Follow back" | ||
} | ||
} | ||
``` | ||
typically structure by components | ||
## Features | ||
biggest strength of this library is that it's hooks only. therefore the same API for attributes as well as in children | ||
- Based on battle-tested building blocks from [Format.JS](https://formatjs.io/) (used by `react-intl`), this library is a thin wrapper around high-quality APIs for i18n. | ||
- I18n is really essential for usability, therefore this library doesn't compromise on flexibility and never leaves you behind when you need to fine tune a translation. Messages use the proven [ICU syntax](https://formatjs.io/docs/core-concepts/icu-syntax). | ||
- The bundle size comes in at [32.2kb (9.2kb gzipped)](https://bundlephobia.com/result?p=next-intl) which is the tradeoff that's necessary for the flexibility of the library. | ||
- A hooks-only API ensures that you can use the same API for `children` as well as for attributes which expect strings. | ||
- Integration with Next.js ensures only the least amount of configuration is necessary. | ||
cache values as well? | ||
separate repo? | ||
## Installation | ||
TODO: | ||
- Pass currency to number? | ||
- Relative time | ||
- Check other features of react-intl | ||
1. Install `next-intl` in your project | ||
2. Add the provider in `_app.js` | ||
```jsx | ||
import {NextIntlProvider} from 'next-intl'; | ||
import NextApp from 'next/app'; | ||
export default function App({Component, messages, pageProps}) { | ||
return ( | ||
<NextIntlProvider messages={messages}> | ||
<Component {...pageProps} /> | ||
</NextIntlProvider> | ||
); | ||
} | ||
App.getInitialProps = async function getInitialProps(context) { | ||
const {locale} = context.router; | ||
// You can get the messages from anywhere you like, but the recommended | ||
// pattern is to put them in JSON files separated by language and read | ||
// the desired one based on the `locale` received from Next.js. You | ||
// can also separate your messages further (e.g. by page) and read | ||
// them based on the current route. | ||
const messages = locale ? require(`messages/${locale}.json`) : undefined | ||
return {...(await NextApp.getInitialProps(context)), messages}; | ||
}; | ||
``` | ||
3. Based on the features you need and the browsers you support, you might have to provide [polyfills](https://formatjs.io/docs/polyfills). | ||
4. Use translations in your components! | ||
## Usage | ||
```js | ||
// en.json | ||
{ | ||
// The recommended approach is to group messages by components. | ||
"Component": { | ||
"static": "Hello", | ||
// See https://formatjs.io/docs/core-concepts/icu-syntax/#simple-argument | ||
"interpolation": "Hello {name}", | ||
// See https://formatjs.io/docs/core-concepts/icu-syntax/#number-type | ||
"number": "{price, number, ::currency/EUR}", | ||
// See https://formatjs.io/docs/intl-messageformat#datetime-skeleton | ||
"date": "{now, date, medium}", | ||
// See https://formatjs.io/docs/core-concepts/icu-syntax/#plural-format | ||
"plural": "You have {numMessages, plural, =0 {no messages} =1 {one message} other {# messages}}.", | ||
// See https://formatjs.io/docs/core-concepts/icu-syntax/#select-format | ||
"select": "{gender, select, male {He} female {She} other {They}} is online.", | ||
// See https://formatjs.io/docs/core-concepts/icu-syntax/#selectordinal-format | ||
"selectordinal": "It's my cat's {year, selectordinal, one {#st} two {#nd} few {#rd} other {#th}} birthday!", | ||
// See https://formatjs.io/docs/core-concepts/icu-syntax/#rich-text-formatting | ||
"richText": "This is <important>important</important>", | ||
// Messages can be used in attributes | ||
"attributeUrl": "https://example.com", | ||
// Use nesting to provide more structure | ||
"nested": { | ||
"label": "Nested" | ||
} | ||
}, | ||
// You don't have to group messages by components. Use whatever suits your use case. | ||
"generic": { | ||
"cancel": "Cancel" | ||
}, | ||
// You can also put your components behind namespaces. | ||
"fancyComponents": { | ||
"FancyComponent": { | ||
"hello": "Hello" | ||
} | ||
} | ||
} | ||
``` | ||
```js | ||
function Component() { | ||
const t = useTranslations('Component'); | ||
return ( | ||
<p>{t('static')}</p> | ||
<p>{t('interpolation', {name: 'Jane'})}</p> | ||
<p>{t('number', {price: 31918.231})}</p> | ||
<p>{t('date', {date: new Date('2020-11-20T10:36:01.516Z')})}</p> | ||
<p>{t('plural', {date: new Date('2020-11-20T10:36:01.516Z')})}</p> | ||
<p>{t('selectordinal', {year: 1})}</p> | ||
<p>{t('richText', important: (children: ReactNode) => <b key="important">{children}</b>)}</p> | ||
// TypeScript note: You have to cast the attribute to a string, since it | ||
// can potentially return a `ReactNode`: `String(t('attributeUrl'))` | ||
<a href={t('attributeUrl')}>Link</a> | ||
<p>{t('nested.label')}</p> | ||
); | ||
} | ||
function AllTranslations() { | ||
// You can get all translations if you omit the namespace path. | ||
const t = useTranslations(); | ||
} | ||
function FancyComponent() { | ||
// Or you can get messages from a nested namespace. The way the library works | ||
// is that there's a static path of the messages that is resolved in the hook | ||
// and should supply all necessary translations for the component. The remaining | ||
// hierarchy can be resolved by passing the respective path to the `t` function. | ||
const t = useTranslations('fancyComponents.FancyComponent'); | ||
} | ||
``` | ||
## Tradeoffs | ||
- All relevant translations for the components need to be supplied to the provider – there's no concept of lazy loading translations. If your app has a significant number of messages, the page-based approach of Next.js allows you to only provide the minimum of necessary messages based on the route. If you split your components by features, it might make sense to split your translation files the same way. Ideally a build-time plugin would take care of creating message bundles based on the components used on a page (this would have to include potentially lazy loaded components as well though). | ||
- There are smaller libraries for internationalisation, but they typically cover less features than Format.JS. However if your performance budget doesn't allow for the size of this library, you might be better off with an alternative. | ||
## TODO | ||
- get rid of key for rich text? | ||
- cache format result? | ||
- separate repo? | ||
- Pass currency to number? (if currency is sensitive to locale, use the messages) | ||
- Relative time | ||
- Check other features of react-intl |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
43062
41.09%4
-20%22
46.67%383
58.26%177
391.67%0
-100%10
150%+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
- Removed
- Removed
- Removed
- Removed
- Removed