From 0d6ffc0b64385fb64dd337946eaae6701df45f0e Mon Sep 17 00:00:00 2001 From: Long Ho Date: Sat, 15 Aug 2020 15:15:42 -0400 Subject: [PATCH] feat: upgrade react-intl workflow in example --- examples/with-react-intl/.babelrc | 10 ++ examples/with-react-intl/.gitignore | 1 + .../with-react-intl/.vscode/settings.json | 3 + examples/with-react-intl/README.md | 24 +--- examples/with-react-intl/components/Layout.js | 29 ---- .../with-react-intl/components/Layout.tsx | 28 ++++ .../components/{Nav.js => Nav.tsx} | 11 +- examples/with-react-intl/lang/en.json | 10 +- examples/with-react-intl/lang/fr.json | 10 +- examples/with-react-intl/next-env.d.ts | 2 + examples/with-react-intl/next.config.js | 10 -- examples/with-react-intl/package.json | 31 +++-- examples/with-react-intl/pages/_app.js | 36 ----- examples/with-react-intl/pages/_app.tsx | 30 +++++ examples/with-react-intl/pages/_document.js | 38 ------ examples/with-react-intl/pages/about.js | 19 --- examples/with-react-intl/pages/about.tsx | 14 ++ examples/with-react-intl/pages/index.js | 33 ----- examples/with-react-intl/pages/index.tsx | 32 +++++ examples/with-react-intl/polyfills.ts | 124 ++++++++++++++++++ examples/with-react-intl/scripts/extract.js | 35 ----- examples/with-react-intl/server.js | 83 ------------ examples/with-react-intl/server.ts | 36 +++++ examples/with-react-intl/tsconfig.json | 19 +++ 24 files changed, 340 insertions(+), 328 deletions(-) create mode 100644 examples/with-react-intl/.babelrc create mode 100644 examples/with-react-intl/.vscode/settings.json delete mode 100644 examples/with-react-intl/components/Layout.js create mode 100644 examples/with-react-intl/components/Layout.tsx rename examples/with-react-intl/components/{Nav.js => Nav.tsx} (65%) create mode 100644 examples/with-react-intl/next-env.d.ts delete mode 100644 examples/with-react-intl/next.config.js delete mode 100644 examples/with-react-intl/pages/_app.js create mode 100644 examples/with-react-intl/pages/_app.tsx delete mode 100644 examples/with-react-intl/pages/_document.js delete mode 100644 examples/with-react-intl/pages/about.js create mode 100644 examples/with-react-intl/pages/about.tsx delete mode 100644 examples/with-react-intl/pages/index.js create mode 100644 examples/with-react-intl/pages/index.tsx create mode 100644 examples/with-react-intl/polyfills.ts delete mode 100644 examples/with-react-intl/scripts/extract.js delete mode 100644 examples/with-react-intl/server.js create mode 100644 examples/with-react-intl/server.ts create mode 100644 examples/with-react-intl/tsconfig.json diff --git a/examples/with-react-intl/.babelrc b/examples/with-react-intl/.babelrc new file mode 100644 index 00000000000000..ece588bc02628f --- /dev/null +++ b/examples/with-react-intl/.babelrc @@ -0,0 +1,10 @@ +{ + "presets": ["next/babel"], + "plugins": [ + ["babel-plugin-react-intl", { + "ast": true, + "idInterpolationPattern": "[sha512:contenthash:base64:6]", + "extractFromFormatMessageCall": true + }] + ] + } \ No newline at end of file diff --git a/examples/with-react-intl/.gitignore b/examples/with-react-intl/.gitignore index eda331b4cb8416..b26bddaa72bb5c 100644 --- a/examples/with-react-intl/.gitignore +++ b/examples/with-react-intl/.gitignore @@ -33,3 +33,4 @@ yarn-error.log* # vercel .vercel +compiled-lang \ No newline at end of file diff --git a/examples/with-react-intl/.vscode/settings.json b/examples/with-react-intl/.vscode/settings.json new file mode 100644 index 00000000000000..ad92582bd0913c --- /dev/null +++ b/examples/with-react-intl/.vscode/settings.json @@ -0,0 +1,3 @@ +{ + "editor.formatOnSave": true +} diff --git a/examples/with-react-intl/README.md b/examples/with-react-intl/README.md index 7cc945bae9817a..56d55220772d5e 100644 --- a/examples/with-react-intl/README.md +++ b/examples/with-react-intl/README.md @@ -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 - `` 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 ``. 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: @@ -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 diff --git a/examples/with-react-intl/components/Layout.js b/examples/with-react-intl/components/Layout.js deleted file mode 100644 index 79f6933240037c..00000000000000 --- a/examples/with-react-intl/components/Layout.js +++ /dev/null @@ -1,29 +0,0 @@ -import { defineMessages, 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 ( -
- - - {title || intl.formatMessage(messages.title)} - - -
-
- - {children} -
- ) -} diff --git a/examples/with-react-intl/components/Layout.tsx b/examples/with-react-intl/components/Layout.tsx new file mode 100644 index 00000000000000..1a1f976f7143c5 --- /dev/null +++ b/examples/with-react-intl/components/Layout.tsx @@ -0,0 +1,28 @@ +import * as React from 'react'; +import {useIntl} from 'react-intl'; +import Head from 'next/head'; +import Nav from './Nav'; + +export default function Layout({title, children}) { + const intl = useIntl(); + + return ( +
+ + + + {title || + intl.formatMessage({ + defaultMessage: 'React Intl Next.js Example', + })} + + + +
+
+ + {children} +
+ ); +} diff --git a/examples/with-react-intl/components/Nav.js b/examples/with-react-intl/components/Nav.tsx similarity index 65% rename from examples/with-react-intl/components/Nav.js rename to examples/with-react-intl/components/Nav.tsx index 4268db6a552f03..84032cb3593707 100644 --- a/examples/with-react-intl/components/Nav.js +++ b/examples/with-react-intl/components/Nav.tsx @@ -1,5 +1,6 @@ -import { FormattedMessage } from 'react-intl' -import Link from 'next/link' +import * as React from 'react'; +import {FormattedMessage} from 'react-intl'; +import Link from 'next/link'; export default function Nav() { return ( @@ -7,14 +8,14 @@ export default function Nav() {
  • - +
  • - +
  • @@ -29,5 +30,5 @@ export default function Nav() { } `} - ) + ); } diff --git a/examples/with-react-intl/lang/en.json b/examples/with-react-intl/lang/en.json index d6de3be22bb5e4..e6cd30bb2f93ad 100644 --- a/examples/with-react-intl/lang/en.json +++ b/examples/with-react-intl/lang/en.json @@ -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" } diff --git a/examples/with-react-intl/lang/fr.json b/examples/with-react-intl/lang/fr.json index 60e5ca62287efd..10c7641a614e71 100644 --- a/examples/with-react-intl/lang/fr.json +++ b/examples/with-react-intl/lang/fr.json @@ -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" } diff --git a/examples/with-react-intl/next-env.d.ts b/examples/with-react-intl/next-env.d.ts new file mode 100644 index 00000000000000..7b7aa2c7727d88 --- /dev/null +++ b/examples/with-react-intl/next-env.d.ts @@ -0,0 +1,2 @@ +/// +/// diff --git a/examples/with-react-intl/next.config.js b/examples/with-react-intl/next.config.js deleted file mode 100644 index 53fbb721c7ebd5..00000000000000 --- a/examples/with-react-intl/next.config.js +++ /dev/null @@ -1,10 +0,0 @@ -const withTM = require('next-transpile-modules')([ - '@formatjs/intl-relativetimeformat', - '@formatjs/intl-utils', - 'react-intl', - 'intl-format-cache', - 'intl-messageformat-parser', - 'intl-messageformat', -]) - -module.exports = withTM() diff --git a/examples/with-react-intl/package.json b/examples/with-react-intl/package.json index c097fa35cd980c..0c4f7a142ee4de 100644 --- a/examples/with-react-intl/package.json +++ b/examples/with-react-intl/package.json @@ -2,28 +2,39 @@ "name": "with-react-intl", "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}'", + "dev": "next dev", + "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-pluralrules": "^3.4.0", + "@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": { + "@types/accepts": "^1.3.5", "cross-spawn": "7.0.3", - "next-transpile-modules": "^4.0.2" + "prettier": "2.0.5" + }, + "prettier": { + "singleQuote": true, + "trailingComma": "es5", + "bracketSpacing": false, + "endOfLine": "lf", + "arrowParens": "avoid" } } diff --git a/examples/with-react-intl/pages/_app.js b/examples/with-react-intl/pages/_app.js deleted file mode 100644 index 87acb14fac4a43..00000000000000 --- a/examples/with-react-intl/pages/_app.js +++ /dev/null @@ -1,36 +0,0 @@ -import { createIntl, createIntlCache, RawIntlProvider } from 'react-intl' - -// This is optional but highly recommended -// since it prevents memory leak -const cache = createIntlCache() - -function MyApp({ Component, pageProps, locale, messages }) { - const intl = createIntl( - { - locale, - messages, - }, - cache - ) - return ( - - - - ) -} - -MyApp.getInitialProps = async ({ Component, ctx }) => { - let pageProps = {} - - const { req } = ctx - const locale = req?.locale ?? '' - const messages = req?.messages ?? {} - - if (Component.getInitialProps) { - Object.assign(pageProps, await Component.getInitialProps(ctx)) - } - - return { pageProps, locale, messages } -} - -export default MyApp diff --git a/examples/with-react-intl/pages/_app.tsx b/examples/with-react-intl/pages/_app.tsx new file mode 100644 index 00000000000000..fc020343ba62cb --- /dev/null +++ b/examples/with-react-intl/pages/_app.tsx @@ -0,0 +1,30 @@ +import * as React from 'react'; +import {IntlProvider} from 'react-intl'; +import {polyfill} from '../polyfills'; +import App from 'next/app'; + +function MyApp({Component, pageProps, locale, messages}) { + return ( + + + + ); +} + +const getInitialProps: typeof App.getInitialProps = async appContext => { + const { + ctx: {req}, + } = appContext; + const locale = (req as any)?.locale ?? 'en'; + const messages = (req as any)?.messages ?? {}; + const [appProps] = await Promise.all([ + polyfill(locale), + App.getInitialProps(appContext), + ]); + + return {...appProps, locale, messages}; +}; + +MyApp.getInitialProps = getInitialProps; + +export default MyApp; diff --git a/examples/with-react-intl/pages/_document.js b/examples/with-react-intl/pages/_document.js deleted file mode 100644 index 13cbb5612fc622..00000000000000 --- a/examples/with-react-intl/pages/_document.js +++ /dev/null @@ -1,38 +0,0 @@ -import Document, { Head, Main, NextScript } from 'next/document' - -// The document (which is SSR-only) needs to be customized to expose the locale -// data for the user's locale for React Intl to work in the browser. -export default class IntlDocument extends Document { - static async getInitialProps(context) { - const props = await super.getInitialProps(context) - const { - req: { locale, localeDataScript }, - } = context - return { - ...props, - locale, - localeDataScript, - } - } - - render() { - // Polyfill Intl API for older browsers - const polyfill = `https://cdn.polyfill.io/v3/polyfill.min.js?features=Intl.~locale.${this.props.locale}` - - return ( - - - -
    -