├── .babelrc
├── .circleci
└── config.yml
├── .editorconfig
├── .eslintrc
├── .gitignore
├── CONTRIBUTING.md
├── LICENSE.md
├── README.md
├── gifs
├── table.gif
└── warnings.gif
├── jest.config.js
├── jsconfig.json
├── package-lock.json
├── package.json
├── rollup.config.js
├── scripts
├── config.js
├── publish.js
└── version.js
└── src
├── Dictionary.js
├── Reporting.js
├── __tests__
├── Dictionary.spec.js
├── Reporting.spec.js
├── dictionary.fixture.js
├── index.es.spec.js
├── index.node.spec.js
├── mapUtilities.spec.js
└── validateTranslations.spec.js
├── constants.js
├── env.js
├── index.js
├── mapUtilities.js
├── utils
├── __tests__
│ └── get.spec.js
└── get.js
└── validateTranslations.js
/.babelrc:
--------------------------------------------------------------------------------
1 | {
2 | "presets": [
3 | [
4 | "@babel/preset-env",
5 | {
6 | "targets": {
7 | "browsers": [
8 | "last 2 versions",
9 | "safari >= 7"
10 | ],
11 | "node": "8"
12 | }
13 | }
14 | ]
15 | ],
16 | "plugins": [
17 | "@babel/plugin-proposal-class-properties"
18 | ]
19 | }
20 |
--------------------------------------------------------------------------------
/.circleci/config.yml:
--------------------------------------------------------------------------------
1 | # Javascript Node CircleCI 2.0 configuration file
2 | #
3 | # Check https://circleci.com/docs/2.0/language-javascript/ for more details
4 |
5 | defaults: &defaults
6 | working_directory: ~/repo
7 | docker:
8 | - image: circleci/node:10.13.0
9 |
10 | tag_matcher: &tag_matcher
11 | tags:
12 | only: /^v.*/
13 | branches:
14 | ignore: /.*/
15 |
16 | version: 2
17 | jobs:
18 | test:
19 | <<: *defaults
20 |
21 | steps:
22 | - checkout
23 |
24 | # Download and cache dependencies
25 | - restore_cache:
26 | keys:
27 | - v1-dependencies-{{ checksum "package.json" }}
28 |
29 | - run:
30 | name: 'Install dependencies'
31 | command: npm install
32 |
33 | - save_cache:
34 | paths:
35 | - node_modules
36 | key: v1-dependencies-{{ checksum "package.json" }}
37 |
38 | - run:
39 | name: 'Lint project'
40 | command: npm run lint -- --format junit -o reports/eslint/results.xml
41 |
42 | - run:
43 | name: 'Run tests'
44 | environment:
45 | JEST_JUNIT_OUTPUT: reports/jest/results.xml
46 | command: npm run test:all -- --runInBand --ci --reporters=default --reporters=jest-junit
47 |
48 | - store_test_results:
49 | path: reports/
50 |
51 | - store_artifacts:
52 | path: reports/
53 |
54 | - persist_to_workspace:
55 | root: ~/repo
56 | paths: .
57 |
58 | build:
59 | <<: *defaults
60 |
61 | steps:
62 | - attach_workspace:
63 | at: ~/repo
64 |
65 | - run: npm run build
66 |
67 | - persist_to_workspace:
68 | root: ~/repo
69 | paths: .
70 |
71 | deploy:
72 | <<: *defaults
73 |
74 | steps:
75 | - attach_workspace:
76 | at: ~/repo
77 | - run:
78 | name: Authenticate with registry
79 | command: echo "//registry.npmjs.org/:_authToken=$NPM_TOKEN" > ~/repo/.npmrc
80 | - run:
81 | name: Release/Publish to github and npm
82 | command: npm run publish
83 |
84 | workflows:
85 | version: 2
86 | test-build:
87 | jobs:
88 | - test
89 | - build:
90 | requires:
91 | - test
92 |
93 | test-build-and-publish:
94 | jobs:
95 | - test:
96 | filters:
97 | <<: *tag_matcher
98 | - build:
99 | requires:
100 | - test
101 | filters:
102 | <<: *tag_matcher
103 | # - approve-deployment:
104 | # type: approval
105 | # requires:
106 | # - build
107 | # filters:
108 | # <<: *tag_matcher
109 | - deploy:
110 | requires:
111 | - build
112 | filters:
113 | <<: *tag_matcher
114 |
--------------------------------------------------------------------------------
/.editorconfig:
--------------------------------------------------------------------------------
1 | # http://editorconfig.org
2 | root = true
3 |
4 | [*]
5 | end_of_line = lf
6 | indent_style = space
7 | indent_size = 2
8 | charset = utf-8
9 | trim_trailing_whitespace = true
10 | insert_final_newline = true
11 |
12 | [*.cmd]
13 | end_of_line = crlf
14 |
15 | [*.md]
16 | trim_trailing_whitespace = false
17 |
18 | [{package.json}]
19 | indent_style = space
20 | indent_size = 2
21 |
--------------------------------------------------------------------------------
/.eslintrc:
--------------------------------------------------------------------------------
1 | {
2 | "parser": "babel-eslint",
3 | "parserOptions": {
4 | "ecmaFeatures": {
5 | "jsx": true,
6 | "modules": true
7 | }
8 | },
9 | "plugins": [
10 | "jest"
11 | ],
12 | "extends": [
13 | "eslint:recommended",
14 | "airbnb-base"
15 | ],
16 | "rules": {
17 | "import/prefer-default-export": "off",
18 | "no-restricted-globals": "off",
19 | "no-plusplus": "off"
20 | },
21 | "env": {
22 | "jest/globals": true
23 | },
24 | "globals": {
25 | "__DEV__": true,
26 | "TEST": true
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # See https://help.github.com/ignore-files/ for more about ignoring files.
2 |
3 | # IDES and tools
4 | .vscode/
5 | .idea/
6 | .DS_Store
7 |
8 | # eslint
9 | .eslintcache
10 |
11 | # Builds and temps
12 | lib/
13 | .coverage/
14 | .reports/
15 |
16 | # dependencies
17 | /node_modules
18 |
19 | npm-debug.log*
20 | yarn-debug.log*
21 | yarn-error.log*
22 |
--------------------------------------------------------------------------------
/CONTRIBUTING.md:
--------------------------------------------------------------------------------
1 | # Contributing to reword-js
2 |
3 | We love your input! We want to make contributing to this project as easy and transparent as possible, whether it's:
4 |
5 | - Reporting a bug
6 | - Discussing the current state of the code
7 | - Submitting a fix
8 | - Proposing new features
9 |
10 | ## We Develop with Github
11 |
12 | We use github to host code, to track issues and feature requests, as well as accept pull requests.
13 |
14 | ## We Use [Github Flow](https://guides.github.com/introduction/flow/index.html), So All Code Changes Happen Through Pull Requests
15 |
16 | Pull requests are the best way to propose changes to the codebase (we use [Github Flow](https://guides.github.com/introduction/flow/index.html)). We actively welcome your pull requests:
17 |
18 | 1. Create your branch from `master`.
19 | 2. If you've added code that should be tested, add tests.
20 | 3. If you've added or changed APIs, update the documentation.
21 | 4. Ensure the test suite passes.
22 | 5. Make sure your code lints.
23 | 6. Issue that pull request!
24 |
25 | ## Any contributions you make will be under the MIT Software License
26 |
27 | In short, when you submit code changes, your submissions are understood to be under the same [MIT License](http://choosealicense.com/licenses/mit/) that covers the project. Feel free to contact the maintainers if that's a concern.
28 |
29 | ## Report bugs using Github's [issues](https://github.com/briandk/transcriptase-atom/issues)
30 |
31 | We use GitHub issues to track public bugs. Report a bug by [opening a new issue](); it's that easy!
32 |
33 | ## Write bug reports with detail, background, and sample code
34 |
35 | [This is an example](http://stackoverflow.com/q/12488905/180626) of a bug report I wrote, and I think it's not a bad model. Here's [another example](http://www.openradar.me/11905408).
36 |
37 | **Great Bug Reports** tend to have:
38 |
39 | - A quick summary and/or background
40 | - Steps to reproduce
41 | - Be specific!
42 | - Give sample code if you can
43 | - What you expected would happen
44 | - What actually happens
45 | - Notes (possibly including why you think this might be happening, or stuff you tried that didn't work)
46 |
47 | People _love_ thorough bug reports. I'm not even kidding.
48 |
49 | ## Use a Consistent Coding Style
50 |
51 | - Use prettier with eslint integration for formatting code
52 | - Use an `editor config` extendsion for your editor to respect indentation rules. (2 spaces)
53 | - You can try running `npm run lint` for style unification
54 |
55 | ## License
56 |
57 | By contributing, you agree that your contributions will be licensed under its MIT License.
58 |
59 | ## References
60 |
61 | This document was adapted from the open-source contribution guidelines for [Facebook's Draft](https://github.com/facebook/draft-js/blob/a9316a723f9e918afde44dea68b5f9f39b7d9b00/CONTRIBUTING.md)
62 |
--------------------------------------------------------------------------------
/LICENSE.md:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2018 Devbridge group, https://www.devbridge.com
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # reword-js ·      
2 |
3 | **reword-js** is a zero dependency, minimal translation library written in JavaScript. Working in the browser and nodejs applications, **reword-js** provides an easy way to translate texts either by key or text content, with possibility to add dynamic parameters.
4 |
5 | # Installation
6 |
7 | via [npm](https://www.npmjs.com/)
8 |
9 | `$ npm install reword-js`
10 |
11 | via [yarn](https://yarnpkg.com/lang/en/)
12 |
13 | `$ yarn add reword-js`
14 |
15 | # Usage
16 |
17 | As a node module
18 |
19 | ```js
20 | const reword = require('reword-js');
21 |
22 | reword.config(
23 | {
24 | /* ...dictionary */
25 | },
26 | {
27 | /* ...options */
28 | }
29 | );
30 |
31 | reword.translate`Default language text`; // translated language text
32 | reword.translateKey('someKey'); // translated language text
33 | ```
34 |
35 | As an ecmascript module
36 |
37 | ```js
38 | import { config, translate, translateKey } from 'reword-js';
39 |
40 | config(
41 | {
42 | /* ...dictionary */
43 | },
44 | {
45 | /* ...options */
46 | }
47 | );
48 |
49 | translate`Default language text`; // translated language text
50 | translateKey('someKey'); // translated language text
51 | ```
52 |
53 | As a script tag
54 |
55 | ```html
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
76 |
77 |
78 | ```
79 |
80 | # Initialization
81 |
82 | Reword can be used as a global instance, or separate instances created manually.
83 |
84 | Example:
85 |
86 | ```js
87 | import { Dictionary, config } from 'reword-js';
88 |
89 | //initialize as a standalone instance
90 | const reword = new Dictionary(dictionary, options);
91 |
92 | //initialize as a global instance
93 | config(dictionary, options);
94 | ```
95 |
96 | # Translation
97 |
98 | Translating text can be acomplished in two ways. Either by key or by actual text. Reasoning behind text translation is that we have a natural fallback if translation key does not exist, interpolation also works just like a plain string which is more easier to read. Key translation is implemented for convenience as it is commonly used throughout other packages/languages.
99 |
100 | Example:
101 |
102 | ```js
103 | import { translate, translateKey } from 'reword-js';
104 |
105 | const dictionary = {
106 | 'en-US': {
107 | example: 'Translated language text'
108 | },
109 | 'xx-XX': {
110 | example: 'Translated other language text'
111 | }
112 | };
113 |
114 | config(dictionary, { locale: 'xx-XX' });
115 |
116 | // Translate by text.
117 | reword.translate`Translated language text`; // Translated language text
118 |
119 | // Translate by key.
120 | reword.translateKey('example'); // Translated other language text
121 | ```
122 |
123 | # Interpolation
124 |
125 | Adding dynamic values to translations is as easy as adding them to a hard coded string. Reword will also change variable order if the destination language has them ordered differently.
126 |
127 | Example:
128 |
129 | ```js
130 | import { translate, translateKey, config } from 'reword-js';
131 |
132 | const dictionary = {
133 | 'en-US': {
134 | example: 'Text with param {one} and param {two}'
135 | },
136 | 'xx-XX': {
137 | example: 'Text replaced with param {two} and param {one}'
138 | }
139 | };
140 |
141 | config(dictionary, { locale: 'xx-XX' });
142 |
143 | const one = 'Foo';
144 | const two = 'Bar';
145 |
146 | // Text based translation
147 | translate`Text with param ${one} and param ${two}`; // Text replaced with param Bar and param Foo
148 |
149 | // Key based translation
150 | translateKey('example', one, two); // Text replaced with param Bar and param Foo
151 | ```
152 |
153 | # Dictionary object
154 |
155 | Dictionary is the primary object that holds all of the languages and translations. Must include a default language (see options object) and at least one of the translations. dictionary object can be nested as well.
156 |
157 | Example:
158 |
159 | ```js
160 | const dictionary = {
161 | // Default language which is specified in options
162 | 'en-US': {
163 | example: 'Translated language text',
164 | nested: {
165 | example: 'Translated nested language text'
166 | }
167 | },
168 | // One or more languages with coresponding keys.
169 | 'xx-XX': {
170 | example: 'Translated other language text',
171 | nested: {
172 | example: 'Translated nested other language text'
173 | }
174 | }
175 | };
176 | ```
177 |
178 | # Options object
179 |
180 | `config` or `Dictionary` instance accepts an `options` object, which is shallow merged with the default options:
181 |
182 | - `defaultLocale`: Sets base locale for reword all of the translations are based on the default language (defaults to `en-US`)
183 | - `locale`: Sets initial locale so reword know which is the destination language (defaults to `en-US`)
184 | - `variableMatcher`: Regular expression pattern which identifies variables for interpolation (defaults to `/\{(.*?)\}/g`)
185 | - `translationNotFoundMessage`: Content display when a translation is not found. Only applies when translating by key. (defaults to `TRANSLATION_NOT_FOUND`)
186 | - `debug`: Debugging option which provides information on missing translations/parameters see [Debugging](#debugging). (defaults to `production` when used as a `umd` module and respects `process.env.NODE_ENV` while using `cjs` or `es` modules)
187 |
188 | Example:
189 |
190 | ```js
191 | import { Dictionary, config } from 'reword-js';
192 |
193 | const dictionary = {};
194 | const options = {
195 | defaultLocale: 'en-US', // defaults to en-US
196 | locale: 'es-ES'
197 | variableMatcher: /\{(.*?)\}/g,
198 | translationNotFoundMessage: 'Could not find any key to translate'
199 | debug: true // or false
200 | };
201 |
202 | // Using with global instance
203 | config(dictionary, options)
204 |
205 | // Using with dedicated instance
206 | const translations = new Dictionary(dictionary, options);
207 | ```
208 |
209 | # API Reference
210 |
211 | Reword public instance is initialized on module import thus contains all of the methods described in the api refrence.
212 |
213 | ### `Dictionary.prototype.config(dictionary, options)`
214 |
215 | Works like a constructor method, used to re-initialize the dictionary.
216 |
217 | ### `Dictionary.prototype.changeLocale(localeString)`
218 |
219 | Changes destination language to a desired one.
220 |
221 | ### `Dictionary.prototype.updateDictionary(dictionary)`
222 |
223 | Overwrites dictionary with a new one. Does not update any options.
224 |
225 | ### `Dictionary.prototype.translate(string, [...parameters])`
226 |
227 | Can be called as template string or as a regular function.
228 |
229 | ```js
230 | const translateString = `String with {numberOfParams}`;
231 |
232 | translate`String with ${numberOfParams}`;
233 |
234 | translate(translateString, numberOfParams);
235 | ```
236 |
237 | ### `Dictionary.prototype.translateKey(key, [...parameters])`
238 |
239 | A dictionary key can be provided to translate via key instead of text. If no key was found it will show text defined in the options object see [Options object](#options-object)
240 |
241 | ```js
242 | translateKey('example'); // Translated text is returned;
243 |
244 | translateKey('example', param1, param2); // Translated text with parameters returned;
245 | ```
246 |
247 | # Debugging
248 |
249 | **reword-js** provides some debugging capabilities if `debug` option is enabled. see [Options object](#options-object).
250 |
251 | ## Console warning
252 |
253 | If translation is not found **reword-js** will throw a `console.warn` message.
254 |
255 | Example:
256 |
257 | 
258 |
259 | ## Console table
260 |
261 | When loading up dictionary it's being validated and outputs information on what's missing.
262 |
263 | Example:
264 |
265 | 
266 |
267 | # Integration
268 |
269 | ## React application
270 |
271 | Since **reword-js** is not tied to the state or store in react applications, thus it does not trigger a re-render. The easiest way is to trigger a re-render when language changes is by setting a `key` prop on the top most component in your React application. Once the key changes, React will re-render the DOM tree underneath.
272 |
273 | Example:
274 |
275 | ```js
276 | import React, { PureComponent } from 'react';
277 | import { config, translate, changeLocale } from 'reword-js';
278 |
279 | class App extends PureComponent {
280 | constructor() {
281 | super();
282 | config({}, { locale: this.state.locale });
283 | }
284 |
285 | state = {
286 | locale: 'en-US'
287 | };
288 |
289 | changeLanguage = ({ target }) => {
290 | changeLocale(target.value);
291 | this.setState({
292 | locale: target.value
293 | });
294 | };
295 |
296 | render() {
297 | return (
298 |