diff --git a/.travis.yml b/.travis.yml index bdb5d08..2281cfd 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,6 +1,8 @@ language: node_js cache: yarn node_js: + - "6" +# - "7" NOT SUPPORTED BY fs-extra - "8" - "9" - "10" diff --git a/README.md b/README.md index ca1a220..f18e49f 100644 --- a/README.md +++ b/README.md @@ -1,23 +1,14 @@ - +# @zakkudo/translation-static-analyzer -## TranslationStaticAnalyzer A library for scanning javscript files to build translation mappings in json automatically. -

- - Build Status - - Coverage Status - - Known Vulnerabilities -

- -Why use this? +[![Build Status](https://travis-ci.org/zakkudo/translation-static-analyzer.svg?branch=master)](https://travis-ci.org/zakkudo/translation-static-analyzer) +[![Coverage Status](https://coveralls.io/repos/github/zakkudo/translation-static-analyzer/badge.svg?branch=master)](https://coveralls.io/github/zakkudo/translation-static-analyzer?branch=master) +[![Known Vulnerabilities](https://snyk.io/test/github/zakkudo/translation-static-analyzer/badge.svg)](https://snyk.io/test/github/zakkudo/translation-static-analyzer) +[![Node](https://img.shields.io/node/v/@zakkudo/translation-static-analyzer.svg)](https://nodejs.org/) +[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT) + +## Why use this? - You no longer have to manage hierarchies of translations - Templates are automatically generated for the translators @@ -26,40 +17,138 @@ Why use this? - Any string wrapped in `__()` or `__n()`, will be picked up as a translatable making usage extremely easy for developers -What does it do? +## What does it do? - I generates a locales directory filled with templates where the program was run, used by humans to translate - It generates .locale directories optimized for loading in each of the directories passed to targets - You load those translations from .locales as you need them -Install with: +## Install ```console +# Install using npm +npm install @zakkudo/translation-static-analyzer +``` + +``` console +# Install using yarn yarn add @zakkudo/translation-static-analyzer ``` -Also consider `@zakkudo/translate-webpack-plugin` which is a wrapper for this library -for webpack and `@zakkudo/translator` for a library that can read the localization with -no fuss and apply the translations. See the [Polymer 3 Starter Project](https://github.com/zakkudo/polymer-3-starter-project) +## Setup +1. Wrap strings you want to be translated in `__('text')` or `__n('singlular', 'plural', number)` using a library like `@zakkudo/translator` +2. Initialize the analyzer in your build scripts similar to below. +``` javascript +const TranslationStaticAnalyzer = require('@zakkudo/translation-static-analyzer'); +const analyzer = new TransalationStaticAnalyzer({ + // Analyzes all javscript files in the src directory, which is a good initial value + files: 'src/**/*.js', + // Use verbose output to see what files are parsed, what keys are extracted, and where they are being written to + debug: true, + // You do not need to add your default language (which for most people will be English) + locales: ['fr'], + // Consolidate all of the optimized localizations into `src/.locale`, good as an initial configuration + target: 'src' +}); + +// Use `read` and `write` to brute force updates to the translations, avoiding optimizations that reduce disk usage. + +// Reads the source files that match `src/**/*.js` and parses out any translation keys, merging it into the database +analyzer.read(); + +// Updates the `locales` translation templates for the translators and then writes the optimized `src/.locales` templates for the developers +analyzer.write(); +``` +3. Add `.locales` to your `.gitignore` so it isn't commited. It is a dynamic source file that has no value being added to a repository. Its existance in the `src` directory is simply to facilitate importing it. +4. Add `find src -name '.locales' | xargs rm -r` to your clean scripts for an easy way to remove the auto generated `src/.locales` from your source code +5. Import locales into your source code from the `src/.locales` folder so you can merge it into the lookup of `@zakkudo/translator`. It is plain old json with the untranslated and unexisting values optimized out. +6. Have your localization team use the files from `locales` (without a period.) It's annoted with information about new, unused, and existing usages of the translations to help them audit what needs updating. + +You'll end up with a file structure similar to below. +``` +File Structure +├── locales <- For your translators +│ ├── en.json +│ └── fr.json +└── src + ├── .locales <- For your developers + │ ├── en.json + │ └── fr.json + └── pages + ├── About + │ └── index.js + └── Search + └── index.js +``` + +Where `locales/fr.json` will look like this for use by your translators: +``` json5 +{ + // NEW + // src/pages/AboutPage/index.js:14 + "About": "", + // UNUSED + "Search Page": "French translation", + // src/pages/AboutPage/index.js:40 + "There is one user": {"one":"French translation", "other":"French translation"}, + // src/pages/AboutPage/index.js:38 + "Welcome to the about page!": "French translation" +} +``` + +And the optimized `src/.locales/fr.json` will look like this for use by your developers: +``` json +{ + "There is one user": {"one":"French translation", "other":"French translation"}, + "Welcome to the about page!": "French translation" +} +``` + +Your developers will use the translation similar to the below +``` javascript +import Translator from '@zakkudo/translator'; +import fr from 'src/.locales/fr.json'; +const translator = new Translator(); +const {__, __n} = translator; +const language = navigator.language.split('-')[0]; + +translator.setLocalization('fr', fr); +translator.setLocale(language); + +document.title = __('About'); +document.body.innerHTML = __n("There is one user", There are %d users", 2); +``` + +## Also see + +- Also consider `@zakkudo/translate-webpack-plugin` which is a wrapper for this library +for webpack +- `@zakkudo/translator` for a library that can read the localization with +no fuss and apply the translations. +- See the +[Polymer 3 Starter Project](https://github.com/zakkudo/polymer-3-starter-project) for an example of using this library. -**Example** *(Usage for just translating everything in a project)* -```js +## Examples + +### Usage for just translating everything in a project +``` javascript const TranslationStaticAnalyzer = require('@zakkudo/translation-static-analyzer'); const analyzer = new TransalationStaticAnalyzer({ - files: 'src/**/*.js', // Analyzes all javscript files in the src directory + files: 'src/**\/*.js', // Analyzes all javscript files in the src directory debug: true, // Enables verbose output locales: ['fr', 'en'], // generate a locales/fr.json as well as a locales/en.json target: 'src' // Consolidate all of the localizations into src }); analyzer.update(); +/* File Structure -├── locales <- Your translators translate this +├── locales <- Your translators translate this. It's json5 with comments │ ├── en.json │ └── fr.json └── src - ├── .locales <- Auto generated, should probably be added to .gitignore + ├── .locales <- Auto generated, you import from this, but make sure to add `.locales` to your `.gitignore` │ ├── en.json │ └── fr.json └── pages @@ -67,37 +156,43 @@ File Structure │ └── index.js └── Search └── index.js +*/ ``` -**Example** *(Usage for splitting transaltions between dynamically imported pages of a web app)* -```js + +### Usage for splitting translations between dynamically imported pages of a web app +``` javascript const TranslationStaticAnalyzer = require('@zakkudo/translation-static-analyzer'); const analyzer = new TransalationStaticAnalyzer({ - files: 'src/**/*.js', // Analyzes all javscript files in the src directory + files: 'src/**\/*.js', // Analyzes all javscript files in the src directory debug: true, // Enables verbose output locales: ['fr', 'en'], // generate a locales/fr.json as well as a locales/en.json target: 'src/pages/*' // Each page in the folder will get it's own subset of translations }); analyzer.update(); +/* File Structure -├── locales <- Your translators translate this +├── locales <- Your translators translate this. It's json5 with comments │ ├── en.json │ └── fr.json └── src └── pages ├── About - │ ├── .locales <- Auto generated, should probably be added to .gitignore + │ ├── .locales <- Auto generated, you import from this, but make sure to add `.locales` to your `.gitignore` │ │ ├── en.json │ │ └── fr.json │ └── index.js └── Search - ├── .locales <- Auto generated, should probably be added to .gitignore + ├── .locales <- Auto generated, you import from this, but make sure to add `.locales` to your `.gitignore` + │ ├── en.json │ └── fr.json └── index.js +*/ ``` -**Example** *(Generated translation templates)* -```js + +### Generated translation templates +``` javascript { // NEW // src/Application/pages/AboutPage/index.js:14 @@ -108,8 +203,9 @@ File Structure "Welcome to the about page!": "ようこそ" } ``` -**Example** *(Use the translations with @zakkudo/translator)* -```js + +### Use the translations with @zakkudo/translator +``` javascript import Translator from '@zakkudo/translator'; import localization = from './src/.locales/ja.json'; //Generated by the analyzer @@ -121,29 +217,25 @@ const translated = translator.__('I love fish'); //Translate! const translated = translator.__n('There is a duck in the pond.', 'There are %d ducks in the pond', 3); //Translate! ``` -* [TranslationStaticAnalyzer](#module_TranslationStaticAnalyzer) - * [~TranslationStaticAnalyzer](#module_TranslationStaticAnalyzer..TranslationStaticAnalyzer) - * [new TranslationStaticAnalyzer(options)](#new_module_TranslationStaticAnalyzer..TranslationStaticAnalyzer_new) - * [.templateDirectory](#module_TranslationStaticAnalyzer..TranslationStaticAnalyzer+templateDirectory) ⇒ String - * [.read([requestFiles])](#module_TranslationStaticAnalyzer..TranslationStaticAnalyzer+read) ⇒ Boolean - * [.write()](#module_TranslationStaticAnalyzer..TranslationStaticAnalyzer+write) - * [.update([requestFiles])](#module_TranslationStaticAnalyzer..TranslationStaticAnalyzer+update) +## API + + - + -### TranslationStaticAnalyzer~TranslationStaticAnalyzer +### @zakkudo/translation-static-analyzer~TranslationStaticAnalyzer ⏏ Class description. -**Kind**: inner class of [TranslationStaticAnalyzer](#module_TranslationStaticAnalyzer) +**Kind**: Exported class -* [~TranslationStaticAnalyzer](#module_TranslationStaticAnalyzer..TranslationStaticAnalyzer) - * [new TranslationStaticAnalyzer(options)](#new_module_TranslationStaticAnalyzer..TranslationStaticAnalyzer_new) - * [.templateDirectory](#module_TranslationStaticAnalyzer..TranslationStaticAnalyzer+templateDirectory) ⇒ String - * [.read([requestFiles])](#module_TranslationStaticAnalyzer..TranslationStaticAnalyzer+read) ⇒ Boolean - * [.write()](#module_TranslationStaticAnalyzer..TranslationStaticAnalyzer+write) - * [.update([requestFiles])](#module_TranslationStaticAnalyzer..TranslationStaticAnalyzer+update) +* [~TranslationStaticAnalyzer](#module_@zakkudo/translation-static-analyzer..TranslationStaticAnalyzer) + * [new TranslationStaticAnalyzer(options)](#new_module_@zakkudo/translation-static-analyzer..TranslationStaticAnalyzer_new) + * [.templateDirectory](#module_@zakkudo/translation-static-analyzer..TranslationStaticAnalyzer+templateDirectory) ⇒ String + * [.read([requestFiles])](#module_@zakkudo/translation-static-analyzer..TranslationStaticAnalyzer+read) ⇒ Boolean + * [.write()](#module_@zakkudo/translation-static-analyzer..TranslationStaticAnalyzer+write) + * [.update([requestFiles])](#module_@zakkudo/translation-static-analyzer..TranslationStaticAnalyzer+update) - + #### new TranslationStaticAnalyzer(options) @@ -156,20 +248,20 @@ Class description. | [options.templates] | String | | The location to store the translator translatable templates for each language | | [options.target] | String | | Where to write the final translations, which can be split between multiple directories for modularity. | - + #### translationStaticAnalyzer.templateDirectory ⇒ String -**Kind**: instance property of [TranslationStaticAnalyzer](#module_TranslationStaticAnalyzer..TranslationStaticAnalyzer) +**Kind**: instance property of [TranslationStaticAnalyzer](#module_@zakkudo/translation-static-analyzer..TranslationStaticAnalyzer) **Returns**: String - The path to the directory which holds the translation templates that are dynamically updated by code changes and should be used by translators to add the localizations. - + #### translationStaticAnalyzer.read([requestFiles]) ⇒ Boolean Read changes from the source files and update the language templates. -**Kind**: instance method of [TranslationStaticAnalyzer](#module_TranslationStaticAnalyzer..TranslationStaticAnalyzer) +**Kind**: instance method of [TranslationStaticAnalyzer](#module_@zakkudo/translation-static-analyzer..TranslationStaticAnalyzer) **Returns**: Boolean - True if some some of the modified files matches the file option passed on initialization @@ -177,20 +269,20 @@ file option passed on initialization | --- | --- | --- | --- | | [requestFiles] | Array.<String> | [] | The files or none to update everything in the options.files glob pattern. | - + #### translationStaticAnalyzer.write() Write to the targets. Use to force an update of the targets if a language file template in the templateDirectory is updated without updating a source file. -**Kind**: instance method of [TranslationStaticAnalyzer](#module_TranslationStaticAnalyzer..TranslationStaticAnalyzer) - +**Kind**: instance method of [TranslationStaticAnalyzer](#module_@zakkudo/translation-static-analyzer..TranslationStaticAnalyzer) + #### translationStaticAnalyzer.update([requestFiles]) Updates the translations to match the source files. -**Kind**: instance method of [TranslationStaticAnalyzer](#module_TranslationStaticAnalyzer..TranslationStaticAnalyzer) +**Kind**: instance method of [TranslationStaticAnalyzer](#module_@zakkudo/translation-static-analyzer..TranslationStaticAnalyzer) | Param | Type | Default | Description | | --- | --- | --- | --- | diff --git a/package.json b/package.json index 769cf48..bc08a89 100644 --- a/package.json +++ b/package.json @@ -15,14 +15,19 @@ "files": [ "*" ], + "engines": { + "node": ">=6.0.0 <7.0.0 || >=8.0.0" + }, "repository": "github:zakkudo/translation-static-analyzer", "license": "BSD-3-Clause", "devDependencies": { "@babel/cli": "^7.0.0-beta.56", "@babel/core": "^7.0.0-beta.56", + "@babel/plugin-transform-runtime": "^7.0.0", "@babel/preset-env": "^7.0.0-beta.56", "babel-core": "^7.0.0-0", "babel-jest": "^23.4.2", + "babel-plugin-transform-undefined-to-void": "^6.9.4", "eslint": "^4.19.1", "eslint-plugin-jasmine": "^2.10.1", "eslint-plugin-jest": "^21.21.0", @@ -30,10 +35,10 @@ "jest": "^23.4.2", "jest-cli": "^23.4.2", "jsdoc": "^3.5.5", - "jsdoc-to-markdown": "^4.0.1", - "regenerator-runtime": "^0.12.1" + "jsdoc-to-markdown": "^4.0.1" }, "dependencies": { + "@babel/runtime-corejs2": "^7.0.0", "deep-equal": "^1.0.1", "fs-extra": "^7.0.0", "glob": "^7.1.2", @@ -49,8 +54,5 @@ "lint": "scripts/lint.sh", "deploy": "scripts/deploy.sh", "test": "scripts/test.sh" - }, - "engines": { - "node": ">=8.0.0" } } diff --git a/scripts/build.sh b/scripts/build.sh index aae5062..d767af8 100755 --- a/scripts/build.sh +++ b/scripts/build.sh @@ -2,4 +2,25 @@ set -e -./node_modules/.bin/babel src --out-dir build --source-maps inline --ignore "src/*.test.js" --ignore "src/test.js" +export NODE_ENV="build" + +CURRENT_DIR=$(pwd) +PROJECT_DIR=$(git rev-parse --show-toplevel) + +cd $PROJECT_DIR + +./scripts/clean.sh +./scripts/document.sh + +mkdir build + +cp package.json build/package.json +cp README.md build/README.md + +./node_modules/.bin/babel src \ + --out-dir build \ + --source-maps inline \ + --ignore "src/test.js" \ + --ignore "src/*.test.js" \ + --verbose \ + "$@" diff --git a/scripts/clean.sh b/scripts/clean.sh index 74a1c92..88169a1 100755 --- a/scripts/clean.sh +++ b/scripts/clean.sh @@ -2,15 +2,13 @@ set -e +CURRENT_DIR=$(pwd) PROJECT_DIR=$(git rev-parse --show-toplevel) -BIN_DIR=$(npm bin) -JSDOC="$BIN_DIR/jsdoc" -rm -rf $PROJECT_DIR/build -rm -rf $PROJECT_DIR/coverage -rm -rf $PROJECT_DIR/documentation -rm -rf $PROJECT_DIR/demo -rm -f $PROJECT_DIR/.karma-test-results.json -rm -f $PROJECT_DIR/.jest-test-results.json -rm -f $PROJECT_DIR/jsdoc.*.conf.tmp -rm -f $PROJECT_DIR/yarn-error.log +cd $PROJECT_DIR + +rm -rf build +rm -rf coverage +rm -rf documentation +rm -f jsdoc.*.conf.tmp +rm -f yarn-error.log diff --git a/scripts/cover.sh b/scripts/cover.sh index faadd02..36805ef 100755 --- a/scripts/cover.sh +++ b/scripts/cover.sh @@ -1,3 +1,11 @@ -#!/bin/sh +#!/bin/bash -./node_modules/.bin/jest --coverage --config jest.config.js +set -e + +CURRENT_DIR=$(pwd) +PROJECT_DIR=$(git rev-parse --show-toplevel) + +cd $PROJECT_DIR + +./scripts/clean.sh +./scripts/test.sh --coverage --coveragePathIgnorePatterns '.*TestHelper.js' "$@" diff --git a/scripts/deploy.sh b/scripts/deploy.sh index 5a5ee94..f765a35 100755 --- a/scripts/deploy.sh +++ b/scripts/deploy.sh @@ -2,11 +2,17 @@ set -e -yarn build -yarn document -yarn cover +CURRENT_DIR=$(pwd) +PROJECT_DIR=$(git rev-parse --show-toplevel) -cp README.md build/README.md -cp package.json build/package.json +cd $PROJECT_DIR + +./scripts/cover.sh +./scripts/document.sh +./scripts/build.sh + +yarn version + +./scripts/build.sh yarn publish --access public --cwd build --no-git-tag-version diff --git a/scripts/document.sh b/scripts/document.sh index 2bf9927..b1b2bec 100755 --- a/scripts/document.sh +++ b/scripts/document.sh @@ -2,10 +2,23 @@ set -e +export NODE_ENV="document" + +CURRENT_DIR=$(pwd) PROJECT_DIR=$(git rev-parse --show-toplevel) BIN_DIR=$(npm bin) JSDOC="$BIN_DIR/jsdoc" +OPTIONS="--module-index-format none --global-index-format none --example-lang js --heading-depth 3" + +cd $PROJECT_DIR + +$JSDOC -c jsdoc.config.json "$@" +cat src/README.md > README.md + +echo "" >> README.md +echo "## API" >> README.md +echo "" >> README.md + +./node_modules/.bin/jsdoc2md src/index.js $OPTIONS >> README.md -$JSDOC -c $PROJECT_DIR/jsdoc.config.json "$@" -./node_modules/.bin/jsdoc2md src/*.js > README.md -sed -i '' 's/\\\//\//g' README.md +./scripts/postProcessReadme.js README.md diff --git a/scripts/lint.sh b/scripts/lint.sh index bfc6de9..e8a6b43 100755 --- a/scripts/lint.sh +++ b/scripts/lint.sh @@ -1,3 +1,12 @@ -#!/bin/sh +#!/bin/bash -./node_modules/.bin/eslint src +set -e + +export NODE_ENV="lint" + +CURRENT_DIR=$(pwd) +PROJECT_DIR=$(git rev-parse --show-toplevel) + +cd $PROJECT_DIR + +./node_modules/.bin/eslint src "$@" diff --git a/scripts/postProcessReadme.js b/scripts/postProcessReadme.js new file mode 100755 index 0000000..95015e3 --- /dev/null +++ b/scripts/postProcessReadme.js @@ -0,0 +1,54 @@ +#!/usr/bin/env node + +const fs = require('fs'); + +const arguments = process.argv.slice(2);; +const filename = arguments[0]; +const contents = String(fs.readFileSync(filename)); +const lines = contents.split('\n'); + +let isIndex = false; +let isApiSection = false; + +const newContents = lines.filter((l) => { + if (l.startsWith('* [@')) { + isIndex = true; + } else if (l === '') { + isIndex = false; + } + + return !isIndex; +}).filter((l) => { + return !l.startsWith('### @'); +}).map((l, index, lines) => { + const innerClassOfPrefix = '**Kind**: inner class of'; + const innerMethodOfPrefix = '**Kind**: inner method of'; + + if (l.startsWith('#### @')) { + return l + ' ⏏'; + } + + if (l.startsWith(innerClassOfPrefix)) { + return '\n**Kind**: Exported class\n'; + } + + if (l.startsWith(innerMethodOfPrefix)) { + return '\n**Kind**: Exported function\n'; + } + + return l; +}).map((l, index, lines) => { + if (l === '## API') { + isApiSection = true; + return l; + } + + if (isApiSection && l.startsWith('##')) { + return l.slice(1); + } + + return l; +}).join('\n').replace(/\n\n+/gm, '\n\n').replace('@', '@').replace('/', '/'); + +fs.writeFileSync(filename, newContents); + diff --git a/scripts/test.sh b/scripts/test.sh index 040cf71..a4e1107 100755 --- a/scripts/test.sh +++ b/scripts/test.sh @@ -1,3 +1,13 @@ -#!/bin/sh +#!/bin/bash -./node_modules/.bin/jest --runInBand +set -e + +export NODE_ENV="test" + +CURRENT_DIR=$(pwd) +PROJECT_DIR=$(git rev-parse --show-toplevel) + +cd $PROJECT_DIR + +./scripts/clean.sh +./node_modules/.bin/jest --runInBand "$@" diff --git a/src/.babelrc b/src/.babelrc deleted file mode 100644 index 309070e..0000000 --- a/src/.babelrc +++ /dev/null @@ -1,3 +0,0 @@ -{ - "presets": ["@babel/env"] -} diff --git a/src/.babelrc.js b/src/.babelrc.js new file mode 100644 index 0000000..f3de39d --- /dev/null +++ b/src/.babelrc.js @@ -0,0 +1,20 @@ +module.exports = { + "presets": [ + [ + "@babel/env", { + "debug": process.env.NODE_ENV === 'build', + "targets": {"browsers": [ + "last 1 version", + "> 1%", + "not dead" + ], "node": "6"} + } + ] + ], + "plugins": [ + ["@babel/transform-runtime", {"corejs": 2}], + "transform-undefined-to-void" + ], + minified: true, + comments: false +} diff --git a/src/README.md b/src/README.md new file mode 100644 index 0000000..7fb5820 --- /dev/null +++ b/src/README.md @@ -0,0 +1,219 @@ +# @zakkudo/translation-static-analyzer + +A library for scanning javscript files to build translation mappings in json automatically. + +[![Build Status](https://travis-ci.org/zakkudo/translation-static-analyzer.svg?branch=master)](https://travis-ci.org/zakkudo/translation-static-analyzer) +[![Coverage Status](https://coveralls.io/repos/github/zakkudo/translation-static-analyzer/badge.svg?branch=master)](https://coveralls.io/github/zakkudo/translation-static-analyzer?branch=master) +[![Known Vulnerabilities](https://snyk.io/test/github/zakkudo/translation-static-analyzer/badge.svg)](https://snyk.io/test/github/zakkudo/translation-static-analyzer) +[![Node](https://img.shields.io/node/v/@zakkudo/translation-static-analyzer.svg)](https://nodejs.org/) +[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT) + +## Why use this? + +- You no longer have to manage hierarchies of translations +- Templates are automatically generated for the translators +- The translations are noted if they are new, unused and what files +- It allows splitting the translations easily for dynamic imports to allow sliced loading +- Any string wrapped in `__()` or `__n()`, will be picked up as a + translatable making usage extremely easy for developers + +## What does it do? + +- I generates a locales directory filled with templates where the program was run, used by humans to translate +- It generates .locale directories optimized for loading in each of the directories passed to targets +- You load those translations from .locales as you need them + +## Install + +```console +# Install using npm +npm install @zakkudo/translation-static-analyzer +``` + +``` console +# Install using yarn +yarn add @zakkudo/translation-static-analyzer +``` + +## Setup +1. Wrap strings you want to be translated in `__('text')` or `__n('singlular', 'plural', number)` using a library like `@zakkudo/translator` +2. Initialize the analyzer in your build scripts similar to below. +``` javascript +const TranslationStaticAnalyzer = require('@zakkudo/translation-static-analyzer'); +const analyzer = new TransalationStaticAnalyzer({ + // Analyzes all javscript files in the src directory, which is a good initial value + files: 'src/**/*.js', + // Use verbose output to see what files are parsed, what keys are extracted, and where they are being written to + debug: true, + // You do not need to add your default language (which for most people will be English) + locales: ['fr'], + // Consolidate all of the optimized localizations into `src/.locale`, good as an initial configuration + target: 'src' +}); + +// Use `read` and `write` to brute force updates to the translations, avoiding optimizations that reduce disk usage. + +// Reads the source files that match `src/**/*.js` and parses out any translation keys, merging it into the database +analyzer.read(); + +// Updates the `locales` translation templates for the translators and then writes the optimized `src/.locales` templates for the developers +analyzer.write(); +``` +3. Add `.locales` to your `.gitignore` so it isn't commited. It is a dynamic source file that has no value being added to a repository. Its existance in the `src` directory is simply to facilitate importing it. +4. Add `find src -name '.locales' | xargs rm -r` to your clean scripts for an easy way to remove the auto generated `src/.locales` from your source code +5. Import locales into your source code from the `src/.locales` folder so you can merge it into the lookup of `@zakkudo/translator`. It is plain old json with the untranslated and unexisting values optimized out. +6. Have your localization team use the files from `locales` (without a period.) It's annoted with information about new, unused, and existing usages of the translations to help them audit what needs updating. + +You'll end up with a file structure similar to below. +``` +File Structure +├── locales <- For your translators +│ ├── en.json +│ └── fr.json +└── src + ├── .locales <- For your developers + │ ├── en.json + │ └── fr.json + └── pages + ├── About + │ └── index.js + └── Search + └── index.js +``` + +Where `locales/fr.json` will look like this for use by your translators: +``` json5 +{ + // NEW + // src/pages/AboutPage/index.js:14 + "About": "", + // UNUSED + "Search Page": "French translation", + // src/pages/AboutPage/index.js:40 + "There is one user": {"one":"French translation", "other":"French translation"}, + // src/pages/AboutPage/index.js:38 + "Welcome to the about page!": "French translation" +} +``` + +And the optimized `src/.locales/fr.json` will look like this for use by your developers: +``` json +{ + "There is one user": {"one":"French translation", "other":"French translation"}, + "Welcome to the about page!": "French translation" +} +``` + +Your developers will use the translation similar to the below +``` javascript +import Translator from '@zakkudo/translator'; +import fr from 'src/.locales/fr.json'; +const translator = new Translator(); +const {__, __n} = translator; +const language = navigator.language.split('-')[0]; + +translator.setLocalization('fr', fr); +translator.setLocale(language); + +document.title = __('About'); +document.body.innerHTML = __n("There is one user", There are %d users", 2); +``` + +## Also see + +- Also consider `@zakkudo/translate-webpack-plugin` which is a wrapper for this library +for webpack +- `@zakkudo/translator` for a library that can read the localization with +no fuss and apply the translations. +- See the +[Polymer 3 Starter Project](https://github.com/zakkudo/polymer-3-starter-project) +for an example of using this library. + +## Examples + +### Usage for just translating everything in a project +``` javascript +const TranslationStaticAnalyzer = require('@zakkudo/translation-static-analyzer'); +const analyzer = new TransalationStaticAnalyzer({ + files: 'src/**\/*.js', // Analyzes all javscript files in the src directory + debug: true, // Enables verbose output + locales: ['fr', 'en'], // generate a locales/fr.json as well as a locales/en.json + target: 'src' // Consolidate all of the localizations into src +}); +analyzer.update(); + +/* +File Structure +├── locales <- Your translators translate this. It's json5 with comments +│ ├── en.json +│ └── fr.json +└── src + ├── .locales <- Auto generated, you import from this, but make sure to add `.locales` to your `.gitignore` + │ ├── en.json + │ └── fr.json + └── pages + ├── About + │ └── index.js + └── Search + └── index.js +*/ +``` + + +### Usage for splitting translations between dynamically imported pages of a web app +``` javascript +const TranslationStaticAnalyzer = require('@zakkudo/translation-static-analyzer'); +const analyzer = new TransalationStaticAnalyzer({ + files: 'src/**\/*.js', // Analyzes all javscript files in the src directory + debug: true, // Enables verbose output + locales: ['fr', 'en'], // generate a locales/fr.json as well as a locales/en.json + target: 'src/pages/*' // Each page in the folder will get it's own subset of translations +}); +analyzer.update(); + +/* +File Structure +├── locales <- Your translators translate this. It's json5 with comments +│ ├── en.json +│ └── fr.json +└── src + └── pages + ├── About + │ ├── .locales <- Auto generated, you import from this, but make sure to add `.locales` to your `.gitignore` + │ │ ├── en.json + │ │ └── fr.json + │ └── index.js + └── Search + ├── .locales <- Auto generated, you import from this, but make sure to add `.locales` to your `.gitignore` + + │ ├── en.json + │ └── fr.json + └── index.js +*/ +``` + +### Generated translation templates +``` javascript +{ + // NEW + // src/Application/pages/AboutPage/index.js:14 + "About": "", + // UNUSED + "This isn't used anymore": "So the text here doesn't really do anything", + // src/Application/pages/AboutPage/index.js:38 + "Welcome to the about page!": "ようこそ" +} +``` + +### Use the translations with @zakkudo/translator +``` javascript +import Translator from '@zakkudo/translator'; +import localization = from './src/.locales/ja.json'; //Generated by the analyzer + +const translator = new Translator(); +translator.mergeLocalization('ja', localization); //Load the localization +translator.setLocale('ja'); //Tell the translator to use it + +const translated = translator.__('I love fish'); //Translate! +const translated = translator.__n('There is a duck in the pond.', 'There are %d ducks in the pond', 3); //Translate! +``` diff --git a/src/index.js b/src/index.js index 654d83b..5159134 100644 --- a/src/index.js +++ b/src/index.js @@ -1,122 +1,5 @@ /** - * A library for scanning javscript files to build translation mappings in json automatically. - * - *

- * - * Build Status - * - * Coverage Status - * - * Known Vulnerabilities - *

- * - * Why use this? - * - * - You no longer have to manage hierarchies of translations - * - Templates are automatically generated for the translators - * - The translations are noted if they are new, unused and what files - * - It allows splitting the translations easily for dynamic imports to allow sliced loading - * - Any string wrapped in `__()` or `__n()`, will be picked up as a - * translatable making usage extremely easy for developers - * - * What does it do? - * - * - I generates a locales directory filled with templates where the program was run, used by humans to translate - * - It generates .locale directories optimized for loading in each of the directories passed to targets - * - You load those translations from .locales as you need them - * - * Install with: - * - * ```console - * yarn add @zakkudo/translation-static-analyzer - * ``` - * - * Also consider `@zakkudo/translate-webpack-plugin` which is a wrapper for this library - * for webpack and `@zakkudo/translator` for a library that can read the localization with - * no fuss and apply the translations. See the [Polymer 3 Starter Project]{@link https://github.com/zakkudo/polymer-3-starter-project} - * for an example of using this library. - * - * @example Usage for just translating everything in a project - * const TranslationStaticAnalyzer = require('@zakkudo/translation-static-analyzer'); - * const analyzer = new TransalationStaticAnalyzer({ - * files: 'src/**\/*.js', // Analyzes all javscript files in the src directory - * debug: true, // Enables verbose output - * locales: ['fr', 'en'], // generate a locales/fr.json as well as a locales/en.json - * target: 'src' // Consolidate all of the localizations into src - * }); - * analyzer.update(); - * - * File Structure - * ├── locales <- Your translators translate this - * │ ├── en.json - * │ └── fr.json - * └── src - * ├── .locales <- Auto generated, should probably be added to .gitignore - * │ ├── en.json - * │ └── fr.json - * └── pages - * ├── About - * │ └── index.js - * └── Search - * └── index.js - * - * - * @example Usage for splitting transaltions between dynamically imported pages of a web app - * const TranslationStaticAnalyzer = require('@zakkudo/translation-static-analyzer'); - * const analyzer = new TransalationStaticAnalyzer({ - * files: 'src/**\/*.js', // Analyzes all javscript files in the src directory - * debug: true, // Enables verbose output - * locales: ['fr', 'en'], // generate a locales/fr.json as well as a locales/en.json - * target: 'src/pages/*' // Each page in the folder will get it's own subset of translations - * }); - * analyzer.update(); - * - * File Structure - * ├── locales <- Your translators translate this - * │ ├── en.json - * │ └── fr.json - * └── src - * └── pages - * ├── About - * │ ├── .locales <- Auto generated, should probably be added to .gitignore - * │ │ ├── en.json - * │ │ └── fr.json - * │ └── index.js - * └── Search - * ├── .locales <- Auto generated, should probably be added to .gitignore - * │ ├── en.json - * │ └── fr.json - * └── index.js - * - * @example Generated translation templates - * { - * // NEW - * // src/Application/pages/AboutPage/index.js:14 - * "About": "", - * // UNUSED - * "This isn't used anymore": "So the text here doesn't really do anything", - * // src/Application/pages/AboutPage/index.js:38 - * "Welcome to the about page!": "ようこそ" - * } - * - * - * @example Use the translations with @zakkudo/translator - * import Translator from '@zakkudo/translator'; - * import localization = from './src/.locales/ja.json'; //Generated by the analyzer - * - * const translator = new Translator(); - * translator.mergeLocalization('ja', localization); //Load the localization - * translator.setLocale('ja'); //Tell the translator to use it - * - * const translated = translator.__('I love fish'); //Translate! - * const translated = translator.__n('There is a duck in the pond.', 'There are %d ducks in the pond', 3); //Translate! - * - * @module TranslationStaticAnalyzer + * @module @zakkudo/translation-static-analyzer */ const JSON5 = require('json5'); diff --git a/yarn.lock b/yarn.lock index b24c9a3..f898088 100644 --- a/yarn.lock +++ b/yarn.lock @@ -466,6 +466,14 @@ dependencies: regenerator-transform "^0.13.3" +"@babel/plugin-transform-runtime@^7.0.0": + version "7.0.0" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-runtime/-/plugin-transform-runtime-7.0.0.tgz#0f1443c07bac16dba8efa939e0c61d6922740062" + dependencies: + "@babel/helper-module-imports" "^7.0.0" + "@babel/helper-plugin-utils" "^7.0.0" + resolve "^1.8.1" + "@babel/plugin-transform-shorthand-properties@^7.0.0": version "7.0.0" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.0.0.tgz#85f8af592dcc07647541a0350e8c95c7bf419d15" @@ -552,6 +560,13 @@ js-levenshtein "^1.1.3" semver "^5.3.0" +"@babel/runtime-corejs2@^7.0.0": + version "7.0.0" + resolved "https://registry.yarnpkg.com/@babel/runtime-corejs2/-/runtime-corejs2-7.0.0.tgz#786711ee099c2c2af7875638866c1259eff30a8c" + dependencies: + core-js "^2.5.7" + regenerator-runtime "^0.12.0" + "@babel/template@7.0.0-beta.51": version "7.0.0-beta.51" resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.0.0-beta.51.tgz#9602a40aebcf357ae9677e2532ef5fc810f5fbff" @@ -926,6 +941,10 @@ babel-plugin-syntax-object-rest-spread@^6.13.0: version "6.13.0" resolved "http://registry.npmjs.org/babel-plugin-syntax-object-rest-spread/-/babel-plugin-syntax-object-rest-spread-6.13.0.tgz#fd6536f2bce13836ffa3a5458c4903a597bb3bf5" +babel-plugin-transform-undefined-to-void@^6.9.4: + version "6.9.4" + resolved "https://registry.yarnpkg.com/babel-plugin-transform-undefined-to-void/-/babel-plugin-transform-undefined-to-void-6.9.4.tgz#be241ca81404030678b748717322b89d0c8fe280" + babel-preset-jest@^23.2.0: version "23.2.0" resolved "https://registry.yarnpkg.com/babel-preset-jest/-/babel-preset-jest-23.2.0.tgz#8ec7a03a138f001a1a8fb1e8113652bf1a55da46" @@ -1358,7 +1377,7 @@ copy-descriptor@^0.1.0: version "0.1.1" resolved "https://registry.yarnpkg.com/copy-descriptor/-/copy-descriptor-0.1.1.tgz#676f6eb3c39997c2ee1ac3a924fd6124748f578d" -core-js@^2.4.0, core-js@^2.5.0: +core-js@^2.4.0, core-js@^2.5.0, core-js@^2.5.7: version "2.5.7" resolved "https://registry.yarnpkg.com/core-js/-/core-js-2.5.7.tgz#f972608ff0cead68b841a16a932d0b183791814e" @@ -3775,7 +3794,7 @@ regenerator-runtime@^0.11.0: version "0.11.1" resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.11.1.tgz#be05ad7f9bf7d22e056f9726cee5017fbf19e2e9" -regenerator-runtime@^0.12.1: +regenerator-runtime@^0.12.0: version "0.12.1" resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.12.1.tgz#fa1a71544764c036f8c49b13a08b2594c9f8a0de"