Skip to content

Commit

Permalink
feat: upgrade react-intl workflow in example
Browse files Browse the repository at this point in the history
  • Loading branch information
longlho committed Aug 15, 2020
1 parent 2e8068f commit 30fed18
Show file tree
Hide file tree
Showing 13 changed files with 73 additions and 144 deletions.
10 changes: 10 additions & 0 deletions examples/with-react-intl/.babelrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
{
"presets": ["next/babel"],
"plugins": [
["babel-plugin-react-intl", {
"ast": true,
"idInterpolationPattern": "[sha512:contenthash:base64:6]",
"extractFromFormatMessageCall": true
}]
]
}
1 change: 1 addition & 0 deletions examples/with-react-intl/.gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -33,3 +33,4 @@ yarn-error.log*

# vercel
.vercel
compiled-lang
24 changes: 4 additions & 20 deletions examples/with-react-intl/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -41,12 +41,13 @@ Deploy it to the cloud with [Vercel](https://vercel.com/import?filter=next.js&ut
- React Intl locale data loading via `pages/_document.js` customization
- React Intl integration with [custom App](https://github.com/vercel/next.js#custom-app) component
- `<IntlProvider>` creation with `locale`, `messages` props
- Default message extraction via `babel-plugin-react-intl` integration
- Default message extraction via `@formatjs/cli` integration
- Pre-compile messages into AST with `babel-plugin-react-intl` for performance
- Translation management via build script and customized Next server

### Translation Management

This app stores translations and default strings in the `lang/` dir. This dir has `.messages/` subdir which is where React Intl's Babel plugin outputs the default messages it extracts from the source code. The default messages (`en.json` in this example app) is also generated by the build script. This file can then be sent to a translation service to perform localization for the other locales the app should support.
This app stores translations and default strings in the `lang/` dir. The default messages (`en.json` in this example app) is also generated by the build script. This file can then be sent to a translation service to perform localization for the other locales the app should support.

The translated messages files that exist at `lang/*.json` are only used during production, and are automatically provided to the `<IntlProvider>`. During development the `defaultMessage`s defined in the source code are used. To prepare the example app for localization and production run the build script and start the server in production mode:

Expand All @@ -57,21 +58,4 @@ $ npm start

You can then switch your browser's language preferences to French and refresh the page to see the UI update accordingly.

### FormattedHTMLMessage support (react-intl pre-v4)

Out of the box, this example does not support the use of the `FormattedHTMLMessage` component on the server due to `DOMParser` not being present in a Node environment.
This functionality is deprecated and has been removed as of react-intl 4.0
If you still want to enable this feature, you should install a `DOMParser` implementation (e.g. `xmldom` or `jsdom`) and enable the polyfill in `server.js`:

```js
// Polyfill Node with `DOMParser` required by formatjs.
// See: https://github.com/vercel/next.js/issues/10533
const { DOMParser } = require('xmldom')
global.DOMParser = DOMParser
```

[react intl]: https://github.com/yahoo/react-intl

### Transpile react-intl

According to [react-intl docs](https://github.com/formatjs/react-intl/blob/53f2c826c7b1e50ad37215ce46b5e1c6f5d142cc/docs/Getting-Started.md#esm-build), react-intl and its underlying libraries must be transpiled to support older browsers (eg IE11). This is done by [next-transpile-modules](https://www.npmjs.com/package/next-transpile-modules) in next.config.js.
[react intl]: https://formatjs.io
16 changes: 7 additions & 9 deletions examples/with-react-intl/components/Layout.js
Original file line number Diff line number Diff line change
@@ -1,22 +1,20 @@
import { defineMessages, useIntl } from 'react-intl'
import { useIntl } from 'react-intl'
import Head from 'next/head'
import Nav from './Nav'

const messages = defineMessages({
title: {
id: 'title',
defaultMessage: 'React Intl Next.js Example',
},
})

export default function Layout({ title, children }) {
const intl = useIntl()

return (
<div>
<Head>
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>{title || intl.formatMessage(messages.title)}</title>
<title>
{title ||
intl.formatMessage({
defaultMessage: 'React Intl Next.js Example',
})}
</title>
</Head>

<header>
Expand Down
4 changes: 2 additions & 2 deletions examples/with-react-intl/components/Nav.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,14 +7,14 @@ export default function Nav() {
<li>
<Link href="/">
<a>
<FormattedMessage id="nav.home" defaultMessage="Home" />
<FormattedMessage defaultMessage="Home" />
</a>
</Link>
</li>
<li>
<Link href="/about">
<a>
<FormattedMessage id="nav.about" defaultMessage="About" />
<FormattedMessage defaultMessage="About" />
</a>
</Link>
</li>
Expand Down
10 changes: 5 additions & 5 deletions examples/with-react-intl/lang/en.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"title": "React Intl Next.js Example",
"nav.home": "Home",
"nav.about": "About",
"description": "An example app integrating React Intl with Next.js",
"greeting": "Hello, World!"
"11754": "An example app integrating React Intl with Next.js",
"65a8e": "Hello, World!",
"8cf04": "Home",
"8f7f4": "About",
"9c817": "React Intl Next.js Example"
}
10 changes: 5 additions & 5 deletions examples/with-react-intl/lang/fr.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"title": "React Intl Next.js Exemple",
"nav.home": "Accueil",
"nav.about": "À propos de nous",
"description": "Un exemple d'application intégrant React Intl avec Next.js",
"greeting": "Bonjour le monde!"
"11754": "Un exemple d'application intégrant React Intl avec Next.js",
"65a8e": "Bonjour le monde!",
"8cf04": "Accueil",
"8f7f4": "À propos de nous",
"9c817": "React Intl Next.js Exemple"
}
10 changes: 0 additions & 10 deletions examples/with-react-intl/next.config.js

This file was deleted.

21 changes: 11 additions & 10 deletions examples/with-react-intl/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,27 +3,28 @@
"version": "1.0.0",
"scripts": {
"dev": "node --icu-data-dir=node_modules/full-icu server.js",
"build": "next build && npm run extract",
"extract": "node ./scripts/extract '{pages,components}/*.{js,ts,tsx}'",
"build": "next build && npm run extract:i18n && npm run compile:i18n",
"extract:i18n": "formatjs extract '{pages,components}/*.{js,ts,tsx}' --format simple --out-file lang/en.json",
"compile:i18n": "formatjs compile-folder --ast --format simple lang/ compiled-lang/",
"start": "NODE_ENV=production node --icu-data-dir=node_modules/full-icu server.js"
},
"dependencies": {
"@formatjs/cli": "1.1.12",
"@formatjs/intl-relativetimeformat": "^2.8.2",
"@formatjs/intl-utils": "^0.6.1",
"@formatjs/cli": "^2.7.3",
"@formatjs/intl-datetimeformat": "^2.4.3",
"@formatjs/intl-getcanonicallocales": "^1.3.2",
"@formatjs/intl-numberformat": "^5.4.1",
"@formatjs/intl-relativetimeformat": "^7.1.1",
"accepts": "^1.3.7",
"babel-plugin-react-intl": "^8.1.1",
"full-icu": "^1.3.0",
"glob": "^7.1.4",
"intl": "^1.2.5",
"intl-locales-supported": "1.8.4",
"next": "latest",
"react": "^16.9.0",
"react-dom": "^16.9.0",
"react-intl": "^3.1.12"
"react-intl": "^5.6.3"
},
"license": "ISC",
"devDependencies": {
"cross-spawn": "7.0.3",
"next-transpile-modules": "^4.0.2"
"cross-spawn": "7.0.3"
}
}
9 changes: 1 addition & 8 deletions examples/with-react-intl/pages/about.js
Original file line number Diff line number Diff line change
@@ -1,18 +1,11 @@
import { FormattedRelativeTime } from 'react-intl'
import { selectUnit } from '@formatjs/intl-utils'
import Layout from '../components/Layout'

export default function About() {
const { value, unit } = selectUnit(Date.now())
return (
<Layout>
<p>
<FormattedRelativeTime
numeric="auto"
value={value}
unit={unit}
updateIntervalInSeconds={1}
/>
<FormattedRelativeTime numeric="auto" value={1} unit="hour" />
</p>
</Layout>
)
Expand Down
24 changes: 9 additions & 15 deletions examples/with-react-intl/pages/index.js
Original file line number Diff line number Diff line change
@@ -1,29 +1,23 @@
import {
FormattedMessage,
FormattedNumber,
defineMessages,
useIntl,
} from 'react-intl'
import { FormattedMessage, FormattedNumber, useIntl } from 'react-intl'
import Head from 'next/head'
import Layout from '../components/Layout'

const { description } = defineMessages({
description: {
id: 'description',
defaultMessage: 'An example app integrating React Intl with Next.js',
},
})

export default function Home() {
const intl = useIntl()

return (
<Layout>
<Head>
<meta name="description" content={intl.formatMessage(description)} />
<meta
name="description"
content={intl.formatMessage({
defaultMessage:
'An example app integrating React Intl with Next.js',
})}
/>
</Head>
<p>
<FormattedMessage id="greeting" defaultMessage="Hello, World!" />
<FormattedMessage defaultMessage="Hello, World!" />
</p>
<p>
<FormattedNumber value={1000} />
Expand Down
35 changes: 0 additions & 35 deletions examples/with-react-intl/scripts/extract.js

This file was deleted.

43 changes: 18 additions & 25 deletions examples/with-react-intl/server.js
Original file line number Diff line number Diff line change
@@ -1,41 +1,34 @@
const { basename } = require('path')
const glob = require('glob')
const areIntlLocalesSupported = require('intl-locales-supported').default

// Get the supported languages by looking for translations in the `lang/` dir.
const supportedLanguages = glob
.sync('./lang/*.json')
.sync('./compiled-lang/*.json')
.map((f) => basename(f, '.json'))

// Polyfill Node with `Intl` that has data for all locales.
// See: https://formatjs.io/guides/runtime-environments/#server
if (global.Intl) {
// Determine if the built-in `Intl` has the locale data we need.
if (!areIntlLocalesSupported(supportedLanguages)) {
// `Intl` exists, but it doesn't have the data we need, so load the
// polyfill and patch the constructors we need with the polyfills.
const IntlPolyfill = require('intl')
Intl.NumberFormat = IntlPolyfill.NumberFormat
Intl.DateTimeFormat = IntlPolyfill.DateTimeFormat
Intl.__disableRegExpRestore = IntlPolyfill.__disableRegExpRestore
if (!Intl.getCanonicalLocales) {
require('@formatjs/intl-getcanonicallocales/polyfill')
}
if (!Intl.PluralRules) {
require('@formatjs/intl-pluralrules/polyfill')
}
if (!Intl.NumberFormat) {
require('@formatjs/intl-numberformat/polyfill')
}
if (!Intl.DateTimeFormat) {
require('@formatjs/intl-datetimeformat/polyfill')
}
} else {
// No `Intl`, so use and load the polyfill.
global.Intl = require('intl')
require('@formatjs/intl-getcanonicallocales/polyfill')
require('@formatjs/intl-pluralrules/polyfill')
require('@formatjs/intl-numberformat/polyfill')
require('@formatjs/intl-datetimeformat/polyfill')
}

// Fix: https://github.com/vercel/next.js/issues/11777
// See related issue: https://github.com/andyearnshaw/Intl.js/issues/308
if (Intl.__disableRegExpRestore) {
Intl.__disableRegExpRestore()
}

// Polyfill DOMParser for **pre-v4** react-intl used by formatjs.
// Only needed when using FormattedHTMLMessage. Make sure to install `xmldom`.
// See: https://github.com/vercel/next.js/issues/10533
// const { DOMParser } = require('xmldom')
// global.DOMParser = DOMParser

const { readFileSync } = require('fs')
const { createServer } = require('http')
const accepts = require('accepts')
Expand All @@ -53,7 +46,7 @@ const getLocaleDataScript = (locale) => {
const lang = locale.split('-')[0]
if (!localeDataCache.has(lang)) {
const localeDataFile = require.resolve(
`@formatjs/intl-relativetimeformat/dist/locale-data/${lang}`
`@formatjs/intl-relativetimeformat/locale-data/${lang}`
)
const localeDataScript = readFileSync(localeDataFile, 'utf8')
localeDataCache.set(lang, localeDataScript)
Expand All @@ -65,7 +58,7 @@ const getLocaleDataScript = (locale) => {
// locale. These will only be used in production, in dev the `defaultMessage` in
// each message description in the source code will be used.
const getMessages = (locale) => {
return require(`./lang/${locale}.json`)
return require(`./compiled-lang/${locale}.json`)
}

app.prepare().then(() => {
Expand Down

0 comments on commit 30fed18

Please sign in to comment.