diff --git a/examples/with-react-intl/.babelrc b/examples/with-react-intl/.babelrc new file mode 100644 index 00000000..3adf6a3b --- /dev/null +++ b/examples/with-react-intl/.babelrc @@ -0,0 +1,19 @@ +{ + "presets": [ + "next/babel" + ], + "env": { + "development": { + "plugins": [ + "react-intl" + ] + }, + "production": { + "plugins": [ + ["react-intl", { + "messagesDir": "lang/.messages/" + }] + ] + } + } +} diff --git a/examples/with-react-intl/.gitignore b/examples/with-react-intl/.gitignore new file mode 100644 index 00000000..b0ebee18 --- /dev/null +++ b/examples/with-react-intl/.gitignore @@ -0,0 +1 @@ +lang/.messages/ diff --git a/examples/with-react-intl/README.md b/examples/with-react-intl/README.md new file mode 100644 index 00000000..2bbbe1dc --- /dev/null +++ b/examples/with-react-intl/README.md @@ -0,0 +1,51 @@ +# Example app with [React Intl][] + +## How to use + +Download the example [or clone the repo](https://github.com/zeit/next.js.git): + +```bash +curl https://codeload.github.com/zeit/next.js/tar.gz/master | tar -xz --strip=2 next.js-master/examples/with-react-intl +cd with-react-intl +``` + +Install it and run: + +```bash +npm install +npm run dev +``` + +Deploy it to the cloud with [now](https://zeit.co/now) ([download](https://zeit.co/download)) + +```bash +now +``` + +## The idea behind the example + +This example app shows how to integrate [React Intl][] with Next. + +### Features of this example app + +- Server-side language negotiation +- React Intl locale data loading via `pages/_document.js` customization +- React Intl integration at Next page level via `pageWithIntl()` HOC +- `` creation with `locale`, `messages`, and `initialNow` props +- Default message extraction via `babel-plugin-react-intl` integration +- 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. + +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: + +``` +$ npm run build +$ npm start +``` + +You can then switch your browser's language preferences to French and refresh the page to see the UI update accordingly. + +[React Intl]: https://github.com/yahoo/react-intl diff --git a/examples/with-react-intl/components/Layout.js b/examples/with-react-intl/components/Layout.js new file mode 100644 index 00000000..f7fda6f5 --- /dev/null +++ b/examples/with-react-intl/components/Layout.js @@ -0,0 +1,27 @@ +import React from 'react' +import {defineMessages, injectIntl} 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 injectIntl(({intl, title, children}) => ( +
+ + + {title || intl.formatMessage(messages.title)} + + +
+
+ + {children} + +
+)) diff --git a/examples/with-react-intl/components/Nav.js b/examples/with-react-intl/components/Nav.js new file mode 100644 index 00000000..255a4b4a --- /dev/null +++ b/examples/with-react-intl/components/Nav.js @@ -0,0 +1,28 @@ +import React from 'react' +import {FormattedMessage} from 'react-intl' +import Link from 'next/link' + +export default () => ( + +) diff --git a/examples/with-react-intl/components/PageWithIntl.js b/examples/with-react-intl/components/PageWithIntl.js new file mode 100644 index 00000000..1bcf0afd --- /dev/null +++ b/examples/with-react-intl/components/PageWithIntl.js @@ -0,0 +1,44 @@ +import React, {Component} from 'react' +import {IntlProvider, addLocaleData, injectIntl} from 'react-intl' + +// Register React Intl's locale data for the user's locale in the browser. This +// locale data was added to the page by `pages/_document.js`. This only happens +// once, on initial page load in the browser. +if (typeof window !== 'undefined' && window.ReactIntlLocaleData) { + Object.keys(window.ReactIntlLocaleData).forEach((lang) => { + addLocaleData(window.ReactIntlLocaleData[lang]) + }) +} + +export default (Page) => { + const IntlPage = injectIntl(Page) + + return class PageWithIntl extends Component { + static async getInitialProps (context) { + let props + if (typeof Page.getInitialProps === 'function') { + props = await Page.getInitialProps(context) + } + + // Get the `locale` and `messages` from the request object on the server. + // In the browser, use the same values that the server serialized. + const {req} = context + const {locale, messages} = req || window.__NEXT_DATA__.props + + // Always update the current time on page load/transition because the + // will be a new instance even with pushState routing. + const now = Date.now() + + return {...props, locale, messages, now} + } + + render () { + const {locale, messages, now, ...props} = this.props + return ( + + + + ) + } + } +} diff --git a/examples/with-react-intl/lang/en.json b/examples/with-react-intl/lang/en.json new file mode 100644 index 00000000..d8df06b1 --- /dev/null +++ b/examples/with-react-intl/lang/en.json @@ -0,0 +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!" +} \ No newline at end of file diff --git a/examples/with-react-intl/lang/fr.json b/examples/with-react-intl/lang/fr.json new file mode 100644 index 00000000..60e5ca62 --- /dev/null +++ b/examples/with-react-intl/lang/fr.json @@ -0,0 +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!" +} diff --git a/examples/with-react-intl/package.json b/examples/with-react-intl/package.json new file mode 100644 index 00000000..fbe7b13f --- /dev/null +++ b/examples/with-react-intl/package.json @@ -0,0 +1,21 @@ +{ + "name": "with-react-intl", + "version": "1.0.0", + "scripts": { + "dev": "node server.js", + "build": "next build && node ./scripts/default-lang", + "start": "NODE_ENV=production node server.js" + }, + "dependencies": { + "accepts": "^1.3.3", + "babel-plugin-react-intl": "^2.3.1", + "glob": "^7.1.1", + "intl": "^1.2.5", + "next": "^2.0.0-beta", + "react": "^15.4.2", + "react-dom": "^15.4.2", + "react-intl": "^2.2.3" + }, + "author": "", + "license": "ISC" +} diff --git a/examples/with-react-intl/pages/_document.js b/examples/with-react-intl/pages/_document.js new file mode 100644 index 00000000..1619e91f --- /dev/null +++ b/examples/with-react-intl/pages/_document.js @@ -0,0 +1,31 @@ +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: {localeDataScript}} = context + return { + ...props, + localeDataScript + } + } + + render () { + return ( + + + +
+