diff --git a/README.md b/README.md index 134c2729..f4f777ca 100644 --- a/README.md +++ b/README.md @@ -181,6 +181,7 @@ Spec | Description [portal](cypress/component/advanced/portal) | Component test for `ReactDOM.createPortal` feature [react-bootstrap](cypress/component/advanced/react-bootstrap) | Confirms [react-bootstrap](https://react-bootstrap.github.io/) components are working [select React component](cypress/component/advanced/react-book-example/src/components/ProductsList.spec.js) | Uses [cypress-react-selector](https://github.com/abhinaba-ghosh/cypress-react-selector) to find DOM elements using React component name and state values +[i18n](cypress/component/advanced/i18n) | Uses[react-i18next](https://react.i18next.com/) for localizaiton. ### Full examples diff --git a/cypress/component/advanced/i18n/App.tsx b/cypress/component/advanced/i18n/App.tsx new file mode 100644 index 00000000..c782ac1c --- /dev/null +++ b/cypress/component/advanced/i18n/App.tsx @@ -0,0 +1,12 @@ +import * as React from 'react' +import { I18nextProvider } from 'react-i18next' +import i18n from './i18n' +import { LocalizedComponent } from './LocalizedComponent' + +export function App() { + return ( + + + + ) +} diff --git a/cypress/component/advanced/i18n/LocalizedComponent.tsx b/cypress/component/advanced/i18n/LocalizedComponent.tsx new file mode 100644 index 00000000..a2751bfd --- /dev/null +++ b/cypress/component/advanced/i18n/LocalizedComponent.tsx @@ -0,0 +1,21 @@ +import * as React from 'react' +import { useTranslation, Trans } from 'react-i18next' + +interface LocalizedComponentProps { + name: string + count: number +} + +export function LocalizedComponent({ name, count }: LocalizedComponentProps) { + // See ./App.tsx for localization setup + const { t } = useTranslation() + + return ( + + Hello {{ name }} , you have {{ count }} unread message{' '} + + ) +} diff --git a/cypress/component/advanced/i18n/README.md b/cypress/component/advanced/i18n/README.md new file mode 100644 index 00000000..da42ecdb --- /dev/null +++ b/cypress/component/advanced/i18n/README.md @@ -0,0 +1,15 @@ +## Localization Example + +This example uses [react-i18next](https://react.i18next.com/) for app localization. Make sure that in "real life" application locale related setup performs at the root of application ([App.tsx](./App.tsx)) and the components are using context for localization. + +Thats why in tests we also need to wrap our component with the same provider as our application. Using function composition we can create our own `mount` function which wraps the component with all required providers: + +```js +const localizedMount = (node, { locale }) => { + mount( + + {node} + , + ) +} +``` diff --git a/cypress/component/advanced/i18n/i18n.ts b/cypress/component/advanced/i18n/i18n.ts new file mode 100644 index 00000000..b65e21c2 --- /dev/null +++ b/cypress/component/advanced/i18n/i18n.ts @@ -0,0 +1,33 @@ +import i18n from 'i18next' +import { initReactI18next } from 'react-i18next' + +i18n + .use(initReactI18next) // passes i18n down to react-i18next + .init({ + resources: { + en: { + translation: { + userMessagesUnread: + 'Hello <1>{{name}}, you have {{count}} unread message.', + userMessagesUnread_plural: + 'Hello <1>{{name}}, you have {{count}} unread messages.', + }, + }, + ru: { + translation: { + userMessagesUnread: + 'Привет, <1>{{name}}, y тебя {{count}} непрочитанное сообщение.', + userMessagesUnread_plural: + 'Привет, <1>{{name}}, y тебя {{count}} непрочитанных сообщений.', + }, + }, + }, + lng: 'en', + fallbackLng: 'en', + + interpolation: { + escapeValue: false, + }, + }) + +export default i18n diff --git a/cypress/component/advanced/i18n/i18next-spec.js b/cypress/component/advanced/i18n/i18next-spec.js new file mode 100644 index 00000000..4ff7a529 --- /dev/null +++ b/cypress/component/advanced/i18n/i18next-spec.js @@ -0,0 +1,48 @@ +/// +import * as React from 'react' +import i18n from './i18n' +import { LocalizedComponent } from './LocalizedComponent' +import { mount } from 'cypress-react-unit-test' +import { I18nextProvider } from 'react-i18next' + +describe('i18n', () => { + const localizedMount = (node, { locale }) => { + mount( + + {node} + , + ) + } + + it('Plural in en', () => { + localizedMount(, { + locale: 'en', + }) + + cy.contains('Hello Josh, you have 15 unread messages.') + }) + + it('Single in en', () => { + localizedMount(, { + locale: 'en', + }) + + cy.contains('Hello Josh, you have 1 unread message.') + }) + + it('Plural in ru', () => { + localizedMount(, { + locale: 'ru', + }) + + cy.contains('Привет, Костя, y тебя 15 непрочитанных сообщений.') + }) + + it('Single in ru', () => { + localizedMount(, { + locale: 'ru', + }) + + cy.contains('Привет, Костя, y тебя 1 непрочитанное сообщение.') + }) +}) diff --git a/cypress/plugins/index.js b/cypress/plugins/index.js index 7a40579e..4cb83b47 100644 --- a/cypress/plugins/index.js +++ b/cypress/plugins/index.js @@ -5,6 +5,7 @@ const babelConfig = require('../../babel.config.js') // should we just reuse root webpack config? const webpackOptions = { resolve: { + extensions: ['.js', '.ts', '.jsx', '.tsx'], alias: { react: path.resolve('./node_modules/react'), }, diff --git a/package-lock.json b/package-lock.json index db746c20..3493ef51 100644 --- a/package-lock.json +++ b/package-lock.json @@ -6962,7 +6962,7 @@ "abbrev": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz", - "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==", + "integrity": "sha1-+PLIh60Qv2f2NPAFtph/7TF5qsg=", "dev": true }, "accepts": { @@ -7319,7 +7319,7 @@ "aproba": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/aproba/-/aproba-1.2.0.tgz", - "integrity": "sha512-Y9J6ZjXtoYh8RnXVCMOU/ttDmk1aBjunq9vO0ta5x85WDQiQfUF9sIPBITdbiiIVcBo03Hi3jMxigBtsddlXRw==", + "integrity": "sha1-aALmJk79GMeQobDVF/DyYnvyyUo=", "dev": true }, "arch": { @@ -8764,7 +8764,7 @@ "babylon": { "version": "6.18.0", "resolved": "https://registry.npmjs.org/babylon/-/babylon-6.18.0.tgz", - "integrity": "sha512-q/UEjfGJ2Cm3oKV71DJz9d25TPnq5rhBVL2Q4fA5wcC3jcrdn7+SssEybFIxwAvvP+YCsCYNKughoF33GxgycQ==", + "integrity": "sha1-ry87iPpvXB5MY00aD46sT1WzleM=", "dev": true }, "backo2": { @@ -8988,7 +8988,7 @@ "bn.js": { "version": "4.11.8", "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.11.8.tgz", - "integrity": "sha512-ItfYfPLkWHUjckQCk8xC+LwxgK8NYcXywGigJgSwOP8Y2iyWT4f2vsZnoOXTTbo+o5yXmIUJ4gn5538SO5S3gA==" + "integrity": "sha1-LN4J617jQfSEdGuwMJsyU7GxRC8=" }, "body-parser": { "version": "1.19.0", @@ -9308,7 +9308,7 @@ "browserify-zlib": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/browserify-zlib/-/browserify-zlib-0.2.0.tgz", - "integrity": "sha512-Z942RysHXmJrhqk88FmKBVq/v5tqmSkDz7p54G/MGyjMnCFFnC79XWNbg+Vta8W6Wb2qtSZTSxIGkJrRpCFEiA==", + "integrity": "sha1-KGlFnZqjviRf6P4sofRuLn9U1z8=", "requires": { "pako": "~1.0.5" } @@ -9788,7 +9788,7 @@ "cipher-base": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/cipher-base/-/cipher-base-1.0.4.tgz", - "integrity": "sha512-Kkht5ye6ZGmwv40uUDZztayT2ThLQGfnj/T71N/XzeZeo3nf8foyW7zGTsPYkEya3m5f3cAypH+qe7YOrM1U2Q==", + "integrity": "sha1-h2Dk7MJy9MNjUy+SbYdKriwTl94=", "requires": { "inherits": "^2.0.1", "safe-buffer": "^5.0.1" @@ -10868,7 +10868,7 @@ "crypto-browserify": { "version": "3.12.0", "resolved": "https://registry.npmjs.org/crypto-browserify/-/crypto-browserify-3.12.0.tgz", - "integrity": "sha512-fz4spIh+znjO2VjL+IdhEpRJ3YN6sMzITSBijk6FK2UvTqruSQW+/cCZTSNsMiZNvUeq0CqurF+dAbyiGOY6Wg==", + "integrity": "sha1-OWz58xN/A+S45TLFj2mCVOAPgOw=", "requires": { "browserify-cipher": "^1.0.0", "browserify-sign": "^4.0.0", @@ -13856,7 +13856,7 @@ "evp_bytestokey": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/evp_bytestokey/-/evp_bytestokey-1.0.3.tgz", - "integrity": "sha512-/f2Go4TognH/KvCISP7OUsHn85hT9nUkxxA9BEWxFn+Oj9o8ZNLm/40hdlgSLyuOimsrTKLUMEorQexp/aPQeA==", + "integrity": "sha1-f8vbGY3HGVlDLv4ThCaE4FJaywI=", "requires": { "md5.js": "^1.3.4", "safe-buffer": "^5.1.1" @@ -14569,7 +14569,7 @@ "find-root": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/find-root/-/find-root-1.1.0.tgz", - "integrity": "sha512-NKfW6bec6GfKc0SGx1e07QZY9PE99u0Bft/0rzSD5k3sO/vwkVUpDUKVm5Gpp5Ue3YfShPFTX2070tDs5kB9Ng==", + "integrity": "sha1-q8/Iunb3CMQql7PWhbfpRQv7nOQ=", "dev": true }, "find-up": { @@ -16686,6 +16686,14 @@ } } }, + "html-parse-stringify2": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/html-parse-stringify2/-/html-parse-stringify2-2.0.1.tgz", + "integrity": "sha1-3FZwtyksoVi3vJFsmmc1rIhyg0o=", + "requires": { + "void-elements": "^2.0.1" + } + }, "html-webpack-plugin": { "version": "4.0.0-beta.11", "resolved": "https://registry.npmjs.org/html-webpack-plugin/-/html-webpack-plugin-4.0.0-beta.11.tgz", @@ -17116,6 +17124,29 @@ "integrity": "sha512-EcuixamT82oplpoJ2XU4pDtKGWQ7b00CD9f1ug9IaQ3p1bkHMiKCZ9ut9QDI6qsa6cpUuB+A/I+zLtdNK4n2DQ==", "dev": true }, + "i18next": { + "version": "19.7.0", + "resolved": "https://registry.npmjs.org/i18next/-/i18next-19.7.0.tgz", + "integrity": "sha512-sxZhj6u7HbEYOMx81oGwq5MiXISRBVg2wRY3n6YIbe+HtU8ydzlGzv6ErHdrRKYxATBFssVXYbc3lNZoyB4vfA==", + "requires": { + "@babel/runtime": "^7.10.1" + }, + "dependencies": { + "@babel/runtime": { + "version": "7.11.2", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.11.2.tgz", + "integrity": "sha512-TeWkU52so0mPtDcaCTxNBI/IHiz0pZgr8VEFqXFtZWpYD08ZB6FaSwVAS8MKRQAP3bYKiVjwysOJgMFY28o6Tw==", + "requires": { + "regenerator-runtime": "^0.13.4" + } + }, + "regenerator-runtime": { + "version": "0.13.7", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.7.tgz", + "integrity": "sha512-a54FxoJDIr27pgf7IgeQGxmqUNYrcV338lf/6gH456HZ/PhX+5BcwHXG9ajESmwe6WRO0tAzRUrRmNONWgkrew==" + } + } + }, "iconv-lite": { "version": "0.4.24", "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", @@ -22236,7 +22267,7 @@ "miller-rabin": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/miller-rabin/-/miller-rabin-4.0.1.tgz", - "integrity": "sha512-115fLhvZVqWwHPbClyntxEVfVDfl9DLLTuJvq3g2O/Oxi8AiNouAHvDSzHS0viUJc+V5vm3eq91Xwqn9dp4jRA==", + "integrity": "sha1-8IA1HIZbDcViqEYpZtqlNUPHik0=", "requires": { "bn.js": "^4.0.0", "brorand": "^1.0.1" @@ -26682,7 +26713,7 @@ "npmlog": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/npmlog/-/npmlog-4.1.2.tgz", - "integrity": "sha512-2uUqazuKlTaSI/dC8AzicUck7+IrEaOnN/e0jd3Xtt1KcGpwx30v50mL7oPyr/h9bL3E4aZccVwpwP+5W9Vjkg==", + "integrity": "sha1-CKfyqL9zRgR3mp76StXMcXq7lUs=", "dev": true, "requires": { "are-we-there-yet": "~1.1.2", @@ -29599,7 +29630,7 @@ "private": { "version": "0.1.8", "resolved": "https://registry.npmjs.org/private/-/private-0.1.8.tgz", - "integrity": "sha512-VvivMrbvd2nKkiG38qjULzlc+4Vx4wm/whI9pQD35YrARNnhxeiRktSOhSukRLFNlzg6Br/cJPet5J/u19r/mg==" + "integrity": "sha1-I4Hts2ifelPWUxkAYPz4ItLzaP8=" }, "process": { "version": "0.5.2", @@ -30626,6 +30657,15 @@ "warning": "^3.0.0" } }, + "react-i18next": { + "version": "11.7.2", + "resolved": "https://registry.npmjs.org/react-i18next/-/react-i18next-11.7.2.tgz", + "integrity": "sha512-Djj3K3hh5Tecla2CI9rLO3TZBYGMFrGilm0JY4cLofAQONCi5TK6nVmUPKoB59n1ZffgjfgJt6zlbE9aGF6Q0Q==", + "requires": { + "@babel/runtime": "^7.3.1", + "html-parse-stringify2": "2.0.1" + } + }, "react-is": { "version": "16.8.2", "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.8.2.tgz", @@ -36444,6 +36484,11 @@ "resolved": "https://registry.npmjs.org/vm-browserify/-/vm-browserify-1.1.2.tgz", "integrity": "sha512-2ham8XPWTONajOR0ohOKOHXkm3+gaBmGut3SRuu75xLd/RRaY6vqgh8NBYYk7+RW3u5AtzPQZG8F10LHkl0lAQ==" }, + "void-elements": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/void-elements/-/void-elements-2.0.1.tgz", + "integrity": "sha1-wGavtYK7HLQSjWDqkjkulNXp2+w=" + }, "w3c-hr-time": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/w3c-hr-time/-/w3c-hr-time-1.0.2.tgz", @@ -37250,7 +37295,7 @@ "word-wrap": { "version": "1.2.3", "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.3.tgz", - "integrity": "sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ==", + "integrity": "sha1-YQY29rH3A4kb00dxzLF/uTtHB5w=", "dev": true }, "wordwrap": { diff --git a/package.json b/package.json index be1f89b3..6efbc00c 100644 --- a/package.json +++ b/package.json @@ -133,7 +133,9 @@ "babel-plugin-istanbul": "6.0.0", "debug": "4.1.1", "find-webpack": "2.0.0", + "i18next": "19.7.0", "mime-types": "2.1.26", + "react-i18next": "11.7.2", "unfetch": "4.1.0" }, "release": {