From 9e91b0ca74aae2d471a44513af19823452e8ad8f Mon Sep 17 00:00:00 2001 From: Cheton Wu Date: Fri, 8 Jul 2022 20:54:51 +0800 Subject: [PATCH] feat: rework the `nodesToString` function to output expected element tags (#234) * feat: rework the "nodes-to-string" function to output expected element tags * chore: bump to v4 * fix: filter out empty nodes * rollback: rollback to CJS exports * chore: fix eslint errors * docs: update README.md * docs: update README.md --- README.md | 53 +++++++++++++++++++------ bin/cli.js | 4 +- package.json | 4 +- src/index.js | 16 ++++---- src/nodes-to-string.js | 66 +++++++++++++++++++++++------- src/parser.js | 20 ++++++++-- test/jsx-parser.test.js | 75 ++++++++++++++++++++++++++++++++--- test/parser.test.js | 57 +++++++++++++------------- test/transform-stream.test.js | 26 ++++++------ 9 files changed, 235 insertions(+), 86 deletions(-) diff --git a/README.md b/README.md index 7d6b6f0..afc4b43 100755 --- a/README.md +++ b/README.md @@ -123,10 +123,15 @@ module.exports = { fallbackKey: function(ns, value) { return value; }, + + // https://react.i18next.com/latest/trans-component#usage-with-simple-html-elements-like-less-than-br-greater-than-and-others-v10.4.0 + supportBasicHtmlNodes: true, // Enables keeping the name of simple nodes (e.g.
) in translations instead of indexed keys. + keepBasicHtmlNodesFor: ['br', 'strong', 'i', 'p'], // Which nodes are allowed to be kept in translations during defaultValue generation of . + + // https://github.com/acornjs/acorn/tree/master/acorn#interface acorn: { ecmaVersion: 2020, sourceType: 'module', // defaults to 'module' - // Check out https://github.com/acornjs/acorn/tree/master/acorn#interface for additional options } }, lngs: ['en','de'], @@ -148,7 +153,9 @@ module.exports = { interpolation: { prefix: '{{', suffix: '}}' - } + }, + metadata: {}, + allowDynamicKeys: false, }, transform: function customTransform(file, enc, done) { "use strict"; @@ -498,18 +505,28 @@ Below are the configuration options with their default values: sort: false, attr: { list: ['data-i18n'], - extensions: ['.html', '.htm'] + extensions: ['.html', '.htm'], }, func: { list: ['i18next.t', 'i18n.t'], - extensions: ['.js', '.jsx'] + extensions: ['.js', '.jsx'], }, trans: { component: 'Trans', i18nKey: 'i18nKey', defaultsKey: 'defaults', extensions: ['.js', '.jsx'], - fallbackKey: false + fallbackKey: false, + + // https://react.i18next.com/latest/trans-component#usage-with-simple-html-elements-like-less-than-br-greater-than-and-others-v10.4.0 + supportBasicHtmlNodes: true, // Enables keeping the name of simple nodes (e.g.
) in translations instead of indexed keys. + keepBasicHtmlNodesFor: ['br', 'strong', 'i', 'p'], // Which nodes are allowed to be kept in translations during defaultValue generation of . + + // https://github.com/acornjs/acorn/tree/master/acorn#interface + acorn: { + ecmaVersion: 2020, + sourceType: 'module', // defaults to 'module' + }, }, lngs: ['en'], ns: ['translation'], @@ -520,7 +537,7 @@ Below are the configuration options with their default values: loadPath: 'i18n/{{lng}}/{{ns}}.json', savePath: 'i18n/{{lng}}/{{ns}}.json', jsonIndent: 2, - lineEnding: '\n' + lineEnding: '\n', }, nsSeparator: ':', keySeparator: '.', @@ -529,8 +546,10 @@ Below are the configuration options with their default values: contextDefaultValues: [], interpolation: { prefix: '{{', - suffix: '}}' - } + suffix: '}}', + }, + metadata: {}, + allowDynamicKeys: false, } ``` @@ -606,7 +625,17 @@ If an `Object` is supplied, you can specify a list of extensions, or override th i18nKey: 'i18nKey', defaultsKey: 'defaults', extensions: ['.js', '.jsx'], - fallbackKey: false + fallbackKey: false, + + // https://react.i18next.com/latest/trans-component#usage-with-simple-html-elements-like-less-than-br-greater-than-and-others-v10.4.0 + supportBasicHtmlNodes: true, // Enables keeping the name of simple nodes (e.g.
) in translations instead of indexed keys. + keepBasicHtmlNodesFor: ['br', 'strong', 'i', 'p'], // Which nodes are allowed to be kept in translations during defaultValue generation of . + + // https://github.com/acornjs/acorn/tree/master/acorn#interface + acorn: { + ecmaVersion: 2020, + sourceType: 'module', // defaults to 'module' + }, } } ``` @@ -819,9 +848,6 @@ interpolation options } ``` -## Integration Guide -Checkout [Integration Guide](https://github.com/i18next/i18next-scanner/wiki/Integration-Guide) to learn how to integrate with [React](https://github.com/i18next/i18next-scanner/wiki/Integration-Guide#react), [Gettext Style I18n](https://github.com/i18next/i18next-scanner/wiki/Integration-Guide#gettext-style-i18n), and [Handlebars](https://github.com/i18next/i18next-scanner/wiki/Integration-Guide#handlebars). - #### metadata Type: `Object` Default: `{}` @@ -881,6 +907,9 @@ Example Usage: done(); ``` +## Integration Guide +Checkout [Integration Guide](https://github.com/i18next/i18next-scanner/wiki/Integration-Guide) to learn how to integrate with [React](https://github.com/i18next/i18next-scanner/wiki/Integration-Guide#react), [Gettext Style I18n](https://github.com/i18next/i18next-scanner/wiki/Integration-Guide#gettext-style-i18n), and [Handlebars](https://github.com/i18next/i18next-scanner/wiki/Integration-Guide#handlebars). + ## License MIT diff --git a/bin/cli.js b/bin/cli.js index 76873fb..157c7eb 100755 --- a/bin/cli.js +++ b/bin/cli.js @@ -2,10 +2,10 @@ const path = require('path'); const program = require('commander'); -const ensureArray = require('ensure-array'); +const { ensureArray } = require('ensure-type'); const sort = require('gulp-sort'); const vfs = require('vinyl-fs'); -const scanner = require('../lib').default; +const scanner = require('../lib'); const pkg = require('../package.json'); program diff --git a/package.json b/package.json index d32e408..bbea9fa 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "i18next-scanner", - "version": "3.3.0", + "version": "4.0.0", "description": "Scan your code, extract translation keys/values, and merge them into i18n resource files.", "homepage": "https://github.com/i18next/i18next-scanner", "author": "Cheton Wu ", @@ -57,7 +57,7 @@ "clone-deep": "^4.0.0", "commander": "^9.0.0", "deepmerge": "^4.0.0", - "ensure-array": "^1.0.0", + "ensure-type": "^1.5.0", "eol": "^0.9.1", "esprima-next": "^5.7.0", "gulp-sort": "^2.0.0", diff --git a/src/index.js b/src/index.js index d2b9abe..fdb003b 100644 --- a/src/index.js +++ b/src/index.js @@ -1,4 +1,4 @@ -/* eslint-disable no-buffer-constructor */ +/* eslint-disable import/no-import-module-exports */ import fs from 'fs'; import path from 'path'; import eol from 'eol'; @@ -93,7 +93,7 @@ const flush = (parser, customFlush) => { contents = Buffer.from(text); } catch (e) { // Fallback to "new Buffer(string[, encoding])" which is deprecated since Node.js v6.0.0 - contents = new Buffer(text); + contents = new Buffer(text); // eslint-disable-line no-buffer-constructor } this.push(new VirtualFile({ @@ -121,9 +121,11 @@ const createStream = (options, customTransform, customFlush) => { return stream; }; -export default (...args) => createStream(...args); +// Convenience API +module.exports = (...args) => module.exports.createStream(...args); -export { - createStream, - Parser, -}; +// Basic API +module.exports.createStream = createStream; + +// Parser +module.exports.Parser = Parser; diff --git a/src/nodes-to-string.js b/src/nodes-to-string.js index 49778ab..c9de2ac 100644 --- a/src/nodes-to-string.js +++ b/src/nodes-to-string.js @@ -1,3 +1,4 @@ +import { ensureArray, ensureBoolean, ensureString } from 'ensure-type'; import _get from 'lodash/get'; const isJSXText = (node) => { @@ -32,16 +33,26 @@ const isObjectExpression = (node) => { return node.type === 'ObjectExpression'; }; -const nodesToString = (nodes, code) => { - let memo = ''; - let nodeIndex = 0; - nodes.forEach((node, i) => { - if (isJSXText(node) || isStringLiteral(node)) { - const value = (node.value) - .replace(/^[\r\n]+\s*/g, '') // remove leading spaces containing a leading newline character - .replace(/[\r\n]+\s*$/g, '') // remove trailing spaces containing a leading newline character - .replace(/[\r\n]+\s*/g, ' '); // replace spaces containing a leading newline character with a single space character +const trimValue = value => ensureString(value) + .replace(/^[\r\n]+\s*/g, '') // remove leading spaces containing a leading newline character + .replace(/[\r\n]+\s*$/g, '') // remove trailing spaces containing a leading newline character + .replace(/[\r\n]+\s*/g, ' '); // replace spaces containing a leading newline character with a single space character + +const nodesToString = (nodes, options) => { + const supportBasicHtmlNodes = ensureBoolean(options?.supportBasicHtmlNodes); + const keepBasicHtmlNodesFor = ensureArray(options?.keepBasicHtmlNodesFor); + const filteredNodes = ensureArray(nodes) + .filter(node => { + if (isJSXText(node)) { + return trimValue(node.value); + } + return true; + }); + let memo = ''; + filteredNodes.forEach((node, nodeIndex) => { + if (isJSXText(node)) { + const value = trimValue(node.value); if (!value) { return; } @@ -55,17 +66,44 @@ const nodesToString = (nodes, code) => { } if (isStringLiteral(expression)) { memo += expression.value; } else if (isObjectExpression(expression) && (_get(expression, 'properties[0].type') === 'Property')) { - memo += `<${nodeIndex}>{{${expression.properties[0].key.name}}}`; + memo += `{{${expression.properties[0].key.name}}}`; } else { console.error(`Unsupported JSX expression. Only static values or {{interpolation}} blocks are supported. Got ${expression.type}:`); - console.error(code.slice(node.start, node.end)); + console.error(ensureString(options?.code).slice(node.start, node.end)); console.error(node.expression); } } else if (node.children) { - memo += `<${nodeIndex}>${nodesToString(node.children, code)}`; - } + const nodeType = node.openingElement?.name?.name; + const selfClosing = node.openingElement?.selfClosing; + const attributeCount = ensureArray(node.openingElement?.attributes).length; + const filteredChildNodes = ensureArray(node.children) + .filter(childNode => { + if (isJSXText(childNode)) { + return trimValue(childNode.value); + } + return true; + }); + const childCount = filteredChildNodes.length; + const firstChildNode = filteredChildNodes[0]; + const shouldKeepChild = supportBasicHtmlNodes && keepBasicHtmlNodesFor.indexOf(node.openingElement?.name?.name) > -1; - ++nodeIndex; + if (selfClosing && shouldKeepChild && (attributeCount === 0)) { + // actual e.g. lorem
ipsum + // expected e.g. lorem
ipsum + memo += `<${nodeType}/>`; + } else if ((childCount === 0 && !shouldKeepChild) || (childCount === 0 && attributeCount !== 0)) { + // actual e.g. lorem
ipsum + // expected e.g. lorem <0> ipsum + memo += `<${nodeIndex}>`; + } else if (shouldKeepChild && (attributeCount === 0) && (childCount === 1) && (isJSXText(firstChildNode) || isStringLiteral(firstChildNode?.expression))) { + // actual e.g. dolor bold amet + // expected e.g. dolor bold amet + memo += `<${nodeType}>${nodesToString(node.children, options)}`; + } else { + // regular case mapping the inner children + memo += `<${nodeIndex}>${nodesToString(node.children, options)}`; + } + } }); return memo; diff --git a/src/parser.js b/src/parser.js index 53ce926..9ae78c2 100644 --- a/src/parser.js +++ b/src/parser.js @@ -7,7 +7,7 @@ import acornStage3 from 'acorn-stage3'; import chalk from 'chalk'; import cloneDeep from 'clone-deep'; import deepMerge from 'deepmerge'; -import ensureArray from 'ensure-array'; +import { ensureArray } from 'ensure-type'; import { parse } from 'esprima-next'; import _ from 'lodash'; import parse5 from 'parse5'; @@ -43,11 +43,13 @@ const defaults = { defaultsKey: 'defaults', extensions: ['.js', '.jsx'], fallbackKey: false, + supportBasicHtmlNodes: true, // Enables keeping the name of simple nodes (e.g.
) in translations instead of indexed keys. + keepBasicHtmlNodesFor: ['br', 'strong', 'i', 'p'], // Which nodes are allowed to be kept in translations during defaultValue generation of . acorn: { ecmaVersion: 2020, // defaults to 2020 sourceType: 'module', // defaults to 'module' // Check out https://github.com/acornjs/acorn/tree/master/acorn#interface for additional options - } + }, }, lngs: ['en'], // array of supported languages @@ -171,6 +173,12 @@ const normalizeOptions = (options) => { if (_.isUndefined(_.get(options, 'trans.acorn'))) { _.set(options, 'trans.acorn', defaults.trans.acorn); } + if (_.isUndefined(_.get(options, 'trans.supportBasicHtmlNodes'))) { + _.set(options, 'trans.supportBasicHtmlNodes', defaults.trans.supportBasicHtmlNodes); + } + if (_.isUndefined(_.get(options, 'trans.keepBasicHtmlNodesFor'))) { + _.set(options, 'trans.keepBasicHtmlNodesFor', defaults.trans.keepBasicHtmlNodesFor); + } } // Resource @@ -538,6 +546,8 @@ class Parser { defaultsKey = this.options.trans.defaultsKey, // string fallbackKey, // boolean|function acorn: acornOptions = this.options.trans.acorn, // object + supportBasicHtmlNodes = this.options.trans.supportBasicHtmlNodes, // boolean + keepBasicHtmlNodesFor = this.options.trans.keepBasicHtmlNodesFor, // array } = { ...opts }; const parseJSXElement = (node, code) => { @@ -634,7 +644,11 @@ class Parser { const tOptions = attr.tOptions; const options = { ...tOptions, - defaultValue: defaultsString || nodesToString(node.children, code), + defaultValue: defaultsString || nodesToString(node.children, { + code, + supportBasicHtmlNodes, + keepBasicHtmlNodesFor, + }), fallbackKey: fallbackKey || this.options.trans.fallbackKey }; diff --git a/test/jsx-parser.test.js b/test/jsx-parser.test.js index f7d98a4..a808ccf 100644 --- a/test/jsx-parser.test.js +++ b/test/jsx-parser.test.js @@ -1,6 +1,6 @@ import { Parser } from 'acorn'; import jsx from 'acorn-jsx'; -import ensureArray from 'ensure-array'; +import { ensureArray } from 'ensure-type'; import _get from 'lodash/get'; import nodesToString from '../src/nodes-to-string'; @@ -13,20 +13,83 @@ const jsxToString = (code) => { return ''; } - return nodesToString(nodes, code); + const options = { + code, + supportBasicHtmlNodes: true, + keepBasicHtmlNodesFor: ['br', 'strong', 'i', 'p'], + }; + + return nodesToString(nodes, options); } catch (e) { console.error(e); return ''; } }; -test('JSX to i18next', () => { +test('Usage with text nodes and HTML entities', () => { expect(jsxToString('Basic text')).toBe('Basic text'); - expect(jsxToString('Hello, {{name}}')).toBe('Hello, <1>{{name}}'); + expect(jsxToString('Hello, {{name}}')).toBe('Hello, {{name}}'); expect(jsxToString('I agree to the terms.')).toBe('I agree to the <1>terms.'); expect(jsxToString('One & two')).toBe('One & two'); + expect(jsxToString('Don't do this Dave')).toBe('Don\'t do this Dave'); + expect(jsxToString('Hello, {{name}}')).toBe('Hello, <1>{{name}}'); + expect(jsxToString('Hello John. See my profile')).toBe('Hello John. <3>See my profile'); + expect(jsxToString('Hello {{name}}. See my profile')).toBe('Hello <1>{{name}}. <3>See my profile'); + expect(jsxToString(` + Example test, + + Wow + + `)).toBe('<0>Example test, <1>Wow'); + expect(jsxToString(` + lorem + + + ipsum + `)).toBe('lorem<1><2>ipsum'); }); -test('HTML entities', () => { - expect(jsxToString('Don't do this Dave')).toBe('Don\'t do this <1>Dave'); +test('Usage with simple HTML elements', () => { + /** + * There are two options that allow you to have basic HTML tags inside your translations, instead of numeric indexes. + * However, this only works for elements without additional attributes (like className), having none or a single text children. + */ + + // Examples of elements that will be readable in translation strings: + expect(jsxToString('bold')).toBe('bold'); + expect(jsxToString('some italic text')).toBe('some italic text'); + expect(jsxToString('

some paragraph

')).toBe('

some paragraph

'); + expect(jsxToString('
')).toBe('
'); + expect(jsxToString('Some newlines
would be
fine')).toBe('Some newlines
would be
fine'); + + // Examples that will be converted to indexed nodes: + expect(jsxToString('')).toBe('<0>'); // no attributes allowed + expect(jsxToString('{{name}}')).toBe('<0>{{name}}'); // only text nodes allowed + expect(jsxToString('bold italic')).toBe('<0>bold italic'); // no nested elements, even simple ones + + // Examples of mixed use + expect(jsxToString(` + Lorem Ipsum is simply dummy text of the printing and typesetting industry. +

+ Lorem Ipsum is simply dummy text of the printing and typesetting industry. +

+ {'Lorem Ipsum is simply dummy text of the printing and typesetting industry.'} +

+

+

+ Lorem Ipsum has been the industry's standard dummy text ever since the 1500s +

+ `)).toBe('Lorem Ipsum is simply dummy text of the printing and typesetting industry.<1>Lorem Ipsum is simply dummy text of the printing and typesetting industry.

Lorem Ipsum is simply dummy text of the printing and typesetting industry.

Lorem Ipsum has been the industry\'s standard dummy text ever since the 1500s

'); + expect(jsxToString(` + Lorem Ipsum is simply dummy text of the printing and typesetting industry. +

+ Lorem Ipsum is simply dummy text of the printing and typesetting industry. +

+ xxx{'Lorem Ipsum is simply dummy text of the printing and typesetting industry.'} +

+

+

+ Lorem Ipsum has been the industry's standard dummy text ever since the 1500s +

+ `)).toBe('Lorem Ipsum is simply dummy text of the printing and typesetting industry.<1>Lorem Ipsum is simply dummy text of the printing and typesetting industry.<1>xxxLorem Ipsum is simply dummy text of the printing and typesetting industry.

Lorem Ipsum has been the industry\'s standard dummy text ever since the 1500s

'); }); diff --git a/test/parser.test.js b/test/parser.test.js index b559b9f..c26608d 100644 --- a/test/parser.test.js +++ b/test/parser.test.js @@ -160,8 +160,8 @@ test('Parse Trans components', () => { expect(parser.get()).toEqual({ en: { dev: { - 'Hello <1>World, you have <3>{{count}} unread message.': 'Hello <1>World, you have <3>{{count}} unread message.', - 'Hello <1>World, you have <3>{{count}} unread message._plural': 'Hello <1>World, you have <3>{{count}} unread message.' + 'Hello World, you have {{count}} unread message.': 'Hello World, you have {{count}} unread message.', + 'Hello World, you have {{count}} unread message._plural': 'Hello World, you have {{count}} unread message.' }, translation: { // quote style @@ -169,8 +169,8 @@ test('Parse Trans components', () => { 'jsx-quotes-single': 'Use single quote for the i18nKey attribute', // plural - 'plural': 'You have <1>{{count}} apples', - 'plural_plural': 'You have <1>{{count}} apples', + 'plural': 'You have {{count}} apples', + 'plural_plural': 'You have {{count}} apples', // context 'context': 'A boyfriend', @@ -178,22 +178,23 @@ test('Parse Trans components', () => { // i18nKey 'multiline-text-string': 'multiline text string', - 'string-literal': 'This is a <1>test', - 'object-expression': 'This is a <1><0>{{test}}', - 'arithmetic-expression': '2 + 2 = <1>{{result}}', + 'string-literal': 'This is a test', + 'object-expression': 'This is a <1>{{test}}', + 'arithmetic-expression': '2 + 2 = {{result}}', 'components': 'Go to <1>Administration > Tools to download administrative tools.', - 'lorem-ipsum': '<0>Lorem Ipsum is simply dummy text of the printing and typesetting industry.Lorem Ipsum is simply dummy text of the printing and typesetting industry.<2>Lorem Ipsum has been the industry\'s standard dummy text ever since the 1500s', - 'lorem-ipsum-nested': 'Lorem Ipsum is simply dummy text of the printing and typesetting industry.<1>Lorem Ipsum is simply dummy text of the printing and typesetting industry.<1>Lorem Ipsum is simply dummy text of the printing and typesetting industry.<2>Lorem Ipsum has been the industry\'s standard dummy text ever since the 1500s', + + "lorem-ipsum": "

Lorem Ipsum is simply dummy text of the printing and typesetting industry.

Lorem Ipsum is simply dummy text of the printing and typesetting industry.

Lorem Ipsum has been the industry's standard dummy text ever since the 1500s

", + "lorem-ipsum-nested": "Lorem Ipsum is simply dummy text of the printing and typesetting industry.<1>Lorem Ipsum is simply dummy text of the printing and typesetting industry.

Lorem Ipsum is simply dummy text of the printing and typesetting industry.

Lorem Ipsum has been the industry's standard dummy text ever since the 1500s

", // fallback key 'Hello, World!': 'Hello, World!', 'multiline text string': 'multiline text string', - 'This is a <1>test': 'This is a <1>test', - 'This is a <1><0>{{test}}': 'This is a <1><0>{{test}}', - '2 + 2 = <1>{{result}}': '2 + 2 = <1>{{result}}', + 'This is a test': 'This is a test', + 'This is a <1>{{test}}': 'This is a <1>{{test}}', + '2 + 2 = {{result}}': '2 + 2 = {{result}}', 'Go to <1>Administration > Tools to download administrative tools.': 'Go to <1>Administration > Tools to download administrative tools.', - '<0>Lorem Ipsum is simply dummy text of the printing and typesetting industry.Lorem Ipsum is simply dummy text of the printing and typesetting industry.<2>Lorem Ipsum has been the industry\'s standard dummy text ever since the 1500s': '<0>Lorem Ipsum is simply dummy text of the printing and typesetting industry.Lorem Ipsum is simply dummy text of the printing and typesetting industry.<2>Lorem Ipsum has been the industry\'s standard dummy text ever since the 1500s', - 'Lorem Ipsum is simply dummy text of the printing and typesetting industry.<1>Lorem Ipsum is simply dummy text of the printing and typesetting industry.<1>Lorem Ipsum is simply dummy text of the printing and typesetting industry.<2>Lorem Ipsum has been the industry\'s standard dummy text ever since the 1500s': 'Lorem Ipsum is simply dummy text of the printing and typesetting industry.<1>Lorem Ipsum is simply dummy text of the printing and typesetting industry.<1>Lorem Ipsum is simply dummy text of the printing and typesetting industry.<2>Lorem Ipsum has been the industry\'s standard dummy text ever since the 1500s', + "

Lorem Ipsum is simply dummy text of the printing and typesetting industry.

Lorem Ipsum is simply dummy text of the printing and typesetting industry.

Lorem Ipsum has been the industry's standard dummy text ever since the 1500s

": "

Lorem Ipsum is simply dummy text of the printing and typesetting industry.

Lorem Ipsum is simply dummy text of the printing and typesetting industry.

Lorem Ipsum has been the industry's standard dummy text ever since the 1500s

", + "Lorem Ipsum is simply dummy text of the printing and typesetting industry.<1>Lorem Ipsum is simply dummy text of the printing and typesetting industry.

Lorem Ipsum is simply dummy text of the printing and typesetting industry.

Lorem Ipsum has been the industry's standard dummy text ever since the 1500s

": "Lorem Ipsum is simply dummy text of the printing and typesetting industry.<1>Lorem Ipsum is simply dummy text of the printing and typesetting industry.

Lorem Ipsum is simply dummy text of the printing and typesetting industry.

Lorem Ipsum has been the industry's standard dummy text ever since the 1500s

", // defaults 'The component might be self-closing': 'The component might be self-closing', @@ -233,8 +234,8 @@ test('Parse Trans components with fallback key', () => { expect(parser.get()).toEqual({ en: { dev: { - '2290678f8f33c49494499fe5e32b4ebd124d9292': 'Hello <1>World, you have <3>{{count}} unread message.', - '2290678f8f33c49494499fe5e32b4ebd124d9292_plural': 'Hello <1>World, you have <3>{{count}} unread message.' + "2f9bdac18af14a38ed4c147f8076c70b740c2ca6": "Hello World, you have {{count}} unread message.", + "2f9bdac18af14a38ed4c147f8076c70b740c2ca6_plural": "Hello World, you have {{count}} unread message.", }, translation: { // quote style @@ -242,8 +243,8 @@ test('Parse Trans components with fallback key', () => { 'jsx-quotes-single': 'Use single quote for the i18nKey attribute', // plural - 'plural': 'You have <1>{{count}} apples', - 'plural_plural': 'You have <1>{{count}} apples', + 'plural': 'You have {{count}} apples', + 'plural_plural': 'You have {{count}} apples', // context 'context': 'A boyfriend', @@ -251,22 +252,22 @@ test('Parse Trans components with fallback key', () => { // i18nKey 'multiline-text-string': 'multiline text string', - 'string-literal': 'This is a <1>test', - 'object-expression': 'This is a <1><0>{{test}}', - 'arithmetic-expression': '2 + 2 = <1>{{result}}', + "string-literal": "This is a test", + "object-expression": "This is a <1>{{test}}", + 'arithmetic-expression': '2 + 2 = {{result}}', 'components': 'Go to <1>Administration > Tools to download administrative tools.', - 'lorem-ipsum': '<0>Lorem Ipsum is simply dummy text of the printing and typesetting industry.Lorem Ipsum is simply dummy text of the printing and typesetting industry.<2>Lorem Ipsum has been the industry\'s standard dummy text ever since the 1500s', - 'lorem-ipsum-nested': 'Lorem Ipsum is simply dummy text of the printing and typesetting industry.<1>Lorem Ipsum is simply dummy text of the printing and typesetting industry.<1>Lorem Ipsum is simply dummy text of the printing and typesetting industry.<2>Lorem Ipsum has been the industry\'s standard dummy text ever since the 1500s', + "lorem-ipsum": "

Lorem Ipsum is simply dummy text of the printing and typesetting industry.

Lorem Ipsum is simply dummy text of the printing and typesetting industry.

Lorem Ipsum has been the industry's standard dummy text ever since the 1500s

", + "lorem-ipsum-nested": "Lorem Ipsum is simply dummy text of the printing and typesetting industry.<1>Lorem Ipsum is simply dummy text of the printing and typesetting industry.

Lorem Ipsum is simply dummy text of the printing and typesetting industry.

Lorem Ipsum has been the industry's standard dummy text ever since the 1500s

", // fallback key '0a0a9f2a6772942557ab5355d76af442f8f65e01': 'Hello, World!', '32876cbad378f3153c900c297ed2efa06243e0e2': 'multiline text string', - 'e4ca61dff6bc759d214e32c4e37c8ae594ca163d': 'This is a <1>test', - '0ce90193dd25c93cdc12f25a36d31004a74c63de': 'This is a <1><0>{{test}}', - '493781e20cd3cfd5b3137963519571c3d97ab383': '2 + 2 = <1>{{result}}', + "e2eff612e7754fb3954034c9be7e600a0456b84b": "This is a test", + "49794e6e13cb1be2987c790d7e6d21f724cc3d5b": "This is a <1>{{test}}", + 'd9ad3431d982619e3b7bd34ed248205312e95bff': '2 + 2 = {{result}}', '083eac6b4f73ec317824caaaeea57fba3b83c1d9': 'Go to <1>Administration > Tools to download administrative tools.', - '938c04be9e14562b7532a19458fe92b65c6ef941': '<0>Lorem Ipsum is simply dummy text of the printing and typesetting industry.Lorem Ipsum is simply dummy text of the printing and typesetting industry.<2>Lorem Ipsum has been the industry\'s standard dummy text ever since the 1500s', - '9c3ca5d5d8089e96135c8c7c9f42ba34a635fb47': 'Lorem Ipsum is simply dummy text of the printing and typesetting industry.<1>Lorem Ipsum is simply dummy text of the printing and typesetting industry.<1>Lorem Ipsum is simply dummy text of the printing and typesetting industry.<2>Lorem Ipsum has been the industry\'s standard dummy text ever since the 1500s', + "11a5b6b7b39fc588da3a1a82d77051bc0e80ad71": "

Lorem Ipsum is simply dummy text of the printing and typesetting industry.

Lorem Ipsum is simply dummy text of the printing and typesetting industry.

Lorem Ipsum has been the industry's standard dummy text ever since the 1500s

", + "5bdc93d2ec3db7b40698abcdfcd5dbd53ef3620b": "Lorem Ipsum is simply dummy text of the printing and typesetting industry.<1>Lorem Ipsum is simply dummy text of the printing and typesetting industry.

Lorem Ipsum is simply dummy text of the printing and typesetting industry.

Lorem Ipsum has been the industry's standard dummy text ever since the 1500s

", // defaults '7551746c2d33a1d0a24658c22821c8700fa58a0d': 'Hello <1>{{planet}}!', diff --git a/test/transform-stream.test.js b/test/transform-stream.test.js index cdaf9bb..9b0b594 100644 --- a/test/transform-stream.test.js +++ b/test/transform-stream.test.js @@ -185,8 +185,9 @@ test('[Trans Component] fallbackKey', done => { 'jsx-quotes-single': 'Use single quote for the i18nKey attribute', // plural - 'plural': 'You have <1>{{count}} apples', - 'plural_plural': 'You have <1>{{count}} apples', + "plural": "You have {{count}} apples", + "plural_plural": "You have {{count}} apples", + // context 'context': 'A boyfriend', @@ -194,22 +195,23 @@ test('[Trans Component] fallbackKey', done => { // i18nKey 'multiline-text-string': 'multiline text string', - 'string-literal': 'This is a <1>test', - 'object-expression': 'This is a <1><0>{{test}}', - 'arithmetic-expression': '2 + 2 = <1>{{result}}', + 'string-literal': 'This is a test', + "object-expression": "This is a <1>{{test}}", + "arithmetic-expression": "2 + 2 = {{result}}", 'components': 'Go to <1>Administration > Tools to download administrative tools.', - 'lorem-ipsum': '<0>Lorem Ipsum is simply dummy text of the printing and typesetting industry.Lorem Ipsum is simply dummy text of the printing and typesetting industry.<2>Lorem Ipsum has been the industry\'s standard dummy text ever since the 1500s', - 'lorem-ipsum-nested': 'Lorem Ipsum is simply dummy text of the printing and typesetting industry.<1>Lorem Ipsum is simply dummy text of the printing and typesetting industry.<1>Lorem Ipsum is simply dummy text of the printing and typesetting industry.<2>Lorem Ipsum has been the industry\'s standard dummy text ever since the 1500s', + "lorem-ipsum": "

Lorem Ipsum is simply dummy text of the printing and typesetting industry.

Lorem Ipsum is simply dummy text of the printing and typesetting industry.

Lorem Ipsum has been the industry's standard dummy text ever since the 1500s

", + "lorem-ipsum-nested": "Lorem Ipsum is simply dummy text of the printing and typesetting industry.<1>Lorem Ipsum is simply dummy text of the printing and typesetting industry.

Lorem Ipsum is simply dummy text of the printing and typesetting industry.

Lorem Ipsum has been the industry's standard dummy text ever since the 1500s

", // fallback key 'Hello, World!': 'Hello, World!', 'multiline text string': 'multiline text string', - 'This is a <1>test': 'This is a <1>test', - 'This is a <1><0>{{test}}': 'This is a <1><0>{{test}}', - '2 + 2 = <1>{{result}}': '2 + 2 = <1>{{result}}', + "This is a test": "This is a test", + "This is a <1>{{test}}": "This is a <1>{{test}}", + "2 + 2 = {{result}}": "2 + 2 = {{result}}", 'Go to <1>Administration > Tools to download administrative tools.': 'Go to <1>Administration > Tools to download administrative tools.', - '<0>Lorem Ipsum is simply dummy text of the printing and typesetting industry.Lorem Ipsum is simply dummy text of the printing and typesetting industry.<2>Lorem Ipsum has been the industry\'s standard dummy text ever since the 1500s': '<0>Lorem Ipsum is simply dummy text of the printing and typesetting industry.Lorem Ipsum is simply dummy text of the printing and typesetting industry.<2>Lorem Ipsum has been the industry\'s standard dummy text ever since the 1500s', - 'Lorem Ipsum is simply dummy text of the printing and typesetting industry.<1>Lorem Ipsum is simply dummy text of the printing and typesetting industry.<1>Lorem Ipsum is simply dummy text of the printing and typesetting industry.<2>Lorem Ipsum has been the industry\'s standard dummy text ever since the 1500s': 'Lorem Ipsum is simply dummy text of the printing and typesetting industry.<1>Lorem Ipsum is simply dummy text of the printing and typesetting industry.<1>Lorem Ipsum is simply dummy text of the printing and typesetting industry.<2>Lorem Ipsum has been the industry\'s standard dummy text ever since the 1500s', + + '

Lorem Ipsum is simply dummy text of the printing and typesetting industry.

Lorem Ipsum is simply dummy text of the printing and typesetting industry.

Lorem Ipsum has been the industry\'s standard dummy text ever since the 1500s

': '

Lorem Ipsum is simply dummy text of the printing and typesetting industry.

Lorem Ipsum is simply dummy text of the printing and typesetting industry.

Lorem Ipsum has been the industry\'s standard dummy text ever since the 1500s

', + "Lorem Ipsum is simply dummy text of the printing and typesetting industry.<1>Lorem Ipsum is simply dummy text of the printing and typesetting industry.

Lorem Ipsum is simply dummy text of the printing and typesetting industry.

Lorem Ipsum has been the industry's standard dummy text ever since the 1500s

": "Lorem Ipsum is simply dummy text of the printing and typesetting industry.<1>Lorem Ipsum is simply dummy text of the printing and typesetting industry.

Lorem Ipsum is simply dummy text of the printing and typesetting industry.

Lorem Ipsum has been the industry's standard dummy text ever since the 1500s

", // defaults 'The component might be self-closing': 'The component might be self-closing',