@translation/angular
Advanced tools
Comparing version 1.1.0 to 1.2.0
# Changelog | ||
## [v1.2.0](https://github.com/translation/angular/releases/tag/v1.2.0) (2022-10-13) | ||
#### New features: | ||
* Handle custom `source_file_path` and `target_files_path`. | ||
* Start by validating the configuration file with nice errors before Init/Sync. | ||
## [v1.1.0](https://github.com/translation/angular/releases/tag/v1.1.0) (2022-09-30) | ||
@@ -4,0 +11,0 @@ |
{ | ||
"name": "@translation/angular", | ||
"version": "1.1.0", | ||
"version": "1.2.0", | ||
"description": "Translation.io client for Angular applications", | ||
@@ -5,0 +5,0 @@ "repository": { |
139
README.md
@@ -20,3 +20,2 @@ # [Translation.io](https://translation.io/angular) client for Angular | ||
Table of contents | ||
@@ -42,4 +41,4 @@ ================= | ||
## Localization syntax overview | ||
## Localization syntax overview | ||
### i18n attribute in templates | ||
@@ -49,3 +48,3 @@ | ||
```html | ||
~~~html | ||
<!-- Simple use of the i18n attribute --> | ||
@@ -73,3 +72,3 @@ <h1 i18n>Welcome to our Angular application!</h1> | ||
``` | ||
~~~ | ||
@@ -80,6 +79,6 @@ ### $localize in classes and functions | ||
```javascript | ||
~~~javascript | ||
// Simple use of the $localize function | ||
let text = $localize `Welcome to our Angular application!`; | ||
``` | ||
~~~ | ||
@@ -95,5 +94,5 @@ To explore the syntax more in details (specifying metadata, using plurals and interpolations), please check out the "[Localization syntax in details](#localization-syntax-in-details)" section below. | ||
```bash | ||
~~~bash | ||
ng add @angular/localize | ||
``` | ||
~~~ | ||
@@ -106,3 +105,3 @@ Configure the [i18n options](https://angular.io/guide/i18n-common-merge#define-locales-in-the-build-configuration) in the `angular.json` file at the root of your project. | ||
```bash | ||
~~~bash | ||
# NPM | ||
@@ -113,3 +112,3 @@ npm install @translation/angular | ||
yarn add @translation/angular | ||
``` | ||
~~~ | ||
@@ -123,3 +122,4 @@ ### 3. Create a new translation project | ||
This configuration file should look like this: | ||
```json | ||
~~~json | ||
{ | ||
@@ -130,3 +130,3 @@ "api_key": "abcdefghijklmnopqrstuvwxyz123456", | ||
} | ||
``` | ||
~~~ | ||
@@ -137,3 +137,3 @@ ### 5. Add scripts to your package.json | ||
```json | ||
~~~json | ||
{ | ||
@@ -146,3 +146,3 @@ "scripts": { | ||
} | ||
``` | ||
~~~ | ||
@@ -155,3 +155,3 @@ N.B. If you are using Angular version 10 or lower, replace **extract-i18n** by **xi18n** in the "extract" command. | ||
```bash | ||
~~~bash | ||
# NPM | ||
@@ -162,5 +162,4 @@ npm run translation:init | ||
yarn translation:init | ||
``` | ||
~~~ | ||
## Usage | ||
@@ -172,3 +171,3 @@ | ||
```bash | ||
~~~bash | ||
# NPM | ||
@@ -179,3 +178,3 @@ npm run translation:sync | ||
yarn translation:sync | ||
``` | ||
~~~ | ||
@@ -186,3 +185,3 @@ ### Read-only Sync | ||
```bash | ||
~~~bash | ||
# NPM | ||
@@ -193,3 +192,3 @@ npm run translation:sync -- --readonly | ||
yarn translation:sync -- --readonly | ||
``` | ||
~~~ | ||
@@ -200,3 +199,3 @@ ### Sync & Purge | ||
```bash | ||
~~~bash | ||
# NPM | ||
@@ -207,7 +206,6 @@ npm run translation:sync -- --purge | ||
yarn translation:sync -- --purge | ||
``` | ||
~~~ | ||
**Warning:** all source keys/strings that are not present in your current local branch will be **permanently deleted from Translation.io**. | ||
## Manage locales | ||
@@ -244,21 +242,22 @@ | ||
```html | ||
~~~html | ||
<h1 i18n>Welcome to our Angular application!</h1> | ||
``` | ||
~~~ | ||
The attributes of HTML elements can also be marked as translatable by using `i18n-{attribute_name}` attributes. | ||
```html | ||
~~~html | ||
<img [src]="cat.png" i18n-alt alt="A fluffy cat" /> | ||
``` | ||
~~~ | ||
You can interpolate variables (component properties) into translatable strings. | ||
```html | ||
~~~html | ||
<!-- Translators will see "Hi {name}, welcome to your dashboard!" --> | ||
<p i18n>Hi {{ name }}, welcome to your dashboard!</p> | ||
``` | ||
~~~ | ||
And you can also interpolate **valid** HTML tags. | ||
```html | ||
~~~html | ||
<!-- Translators will see "Text with <1>HTML</1> tags." --> | ||
@@ -269,15 +268,16 @@ <p i18n>Text with <em>HTML</em> tags.</p> | ||
<p i18n>Text with a <a href="#"><em>partly-emphasized</em> link</a>.</p> | ||
``` | ||
~~~ | ||
Literal strings in your component classes and functions can also be marked as translatable using `$localize` and surrounding the source text with backticks ( \` ). | ||
```javascript | ||
~~~javascript | ||
let text = $localize `Hello, we hope you will enjoy this app.`; | ||
``` | ||
~~~ | ||
This syntax also allows for variable interpolation. | ||
```javascript | ||
~~~javascript | ||
// Translators will see "Hi {name}, welcome to your dashboard!" | ||
let text = $localize `Hi ${name}, welcome to your dashboard!`; | ||
``` | ||
~~~ | ||
@@ -292,3 +292,3 @@ The official Angular documentation for the syntax can be found [here](https://angular.io/guide/i18n-common-prepare). | ||
```html | ||
~~~html | ||
<!-- Specifying only the meaning (the pipe | is required) --> | ||
@@ -305,7 +305,7 @@ <h1 i18n="Welcome message|">Welcome to our app!</h1> | ||
<h1 i18n="Welcome message|Message used on the homepage@@home-welcome-message">Welcome to our app!</h1> | ||
``` | ||
~~~ | ||
Metadata can also be used with `$localize`, but it must then be formatted as follows: `:{meaning}|{description}@@{custom_id}:{source_text}`. | ||
```javascript | ||
~~~javascript | ||
// Specifying only the meaning (the pipe | is required) | ||
@@ -322,3 +322,3 @@ let text = $localize `:Welcome message|:Welcome to our Angular app!`; | ||
let text = $localize `:Welcome message|Message used on the homepage@@home-welcome-message:Welcome to our Angular app!`; | ||
``` | ||
~~~ | ||
@@ -336,3 +336,3 @@ The official Angular documentation for optional metadata can be found [here](https://angular.io/guide/i18n-optional-manage-marked-text). | ||
```html | ||
~~~html | ||
<!-- Good use cases: --> | ||
@@ -370,3 +370,3 @@ | ||
<h2 i18n="@@section-title">Second section</h2> | ||
``` | ||
~~~ | ||
@@ -379,3 +379,3 @@ ### ICU expressions (plural and select) | ||
```html | ||
~~~html | ||
<!-- Translators will see "There are no cats", "There is one cat", "There are {x} cats" --> | ||
@@ -387,3 +387,3 @@ <p i18n>{count, plural, | ||
}</p> | ||
``` | ||
~~~ | ||
@@ -396,5 +396,5 @@ The official Angular documentation for plurals can be found [here](https://angular.io/guide/i18n-common-prepare#mark-plurals). | ||
```html | ||
~~~html | ||
<span i18n>The user is {gender, select, male {a man} female {a woman} other { other }}.</span> | ||
``` | ||
~~~ | ||
@@ -411,2 +411,44 @@ The official Angular documentation for select clauses can be found [here](https://angular.io/guide/i18n-common-prepare#mark-alternates-and-nested-expressions). | ||
We always favor "[_convention over configuration_](https://en.wikipedia.org/wiki/Convention_over_configuration)", so we strongly recommend that you use the default paths and file names in your localization process, but you may specify custom source and target paths for your application if necessary. | ||
### Custom path for the source locale file | ||
If your source locale file (XLF) is not located in the default `src/locale` directory and/or is not named `messages.xlf` (default name), you may specify a custom source locale path in your `tio.config.json` file: | ||
```json | ||
{ | ||
"source_file_path" : "src/translations/sources.xlf" | ||
} | ||
``` | ||
**Warning!** The name of your source file should match the one generated by the `extract` script. Make sure to stay consistent in your `package.json`: | ||
```json | ||
{ | ||
"scripts": { | ||
"extract": "ng extract-i18n --output-path=src/translations --out-file=sources.xlf" | ||
} | ||
} | ||
``` | ||
### Custom path for target locale files | ||
You may also specify a custom path for the target locale files (XLF) if you need them to have a name other than the defaut `messages.{lang}.xlf` or to be located in a directory other than the default `src/locale` directory. Simply add the following line to your `tio.config.json`, but make sure that it contains the `{lang}` placeholder as such: | ||
```json | ||
{ | ||
"target_files_path": "src/translations/translations.{lang}.xlf" | ||
} | ||
``` | ||
or | ||
```json | ||
{ | ||
"target_files_path": "src/locale/{lang}/translations.xlf" | ||
} | ||
``` | ||
Note: our package will attempt to create any missing directories. If it fails (for example, if permissions are strict on your side), please make sure to create the required directories manually. | ||
### Proxy | ||
@@ -416,8 +458,7 @@ | ||
```json | ||
~~~json | ||
{ | ||
... | ||
"proxy": "http://login:pass@127.0.0.1:8080" | ||
} | ||
``` | ||
~~~ | ||
@@ -424,0 +465,0 @@ ## List of clients for Translation.io |
const Interpolation = require('../utils/interpolation') | ||
const fs = require('fs') | ||
const path = require('path') | ||
const { XMLParser, XMLBuilder } = require('fast-xml-parser') | ||
@@ -17,3 +18,41 @@ const { parse: icuParse } = require('@formatjs/icu-messageformat-parser') | ||
/*------------------*/ | ||
validateConfig(action) { | ||
let valid = true | ||
try { | ||
this.options() | ||
} catch (error) { | ||
valid = false | ||
} | ||
if (! valid) { | ||
console.error(`\n⚠️ Your ${this.configFile} config file seems to be missing or is not a valid JSON.`) | ||
} else if (! this.apiKey().length) { | ||
console.error(`\n⚠️ The "api_key" parameter in your ${this.configFile} file seems to be missing.`) | ||
valid = false | ||
} else if (! this.sourceLanguage().length) { | ||
console.error(`\n⚠️ The "source_locale" parameter in your ${this.configFile} file seems to be missing.`) | ||
valid = false | ||
} else if (! this.targetLanguages().length || this.targetLanguages().some(language => ! language.length)) { | ||
console.error(`\n⚠️ The "target_locales" parameter in your ${this.configFile} file is missing or invalid.`) | ||
console.error(`\nPlease make sure that its value is an array of locale codes (e.g.: ["fr", "it"])`) | ||
valid = false | ||
} else if (this.targetLanguages().includes(this.sourceLanguage())) { | ||
console.error(`\n⚠️ The "target_locales" parameter in your ${this.configFile} file contains your source language (${this.sourceLanguage()}).`) | ||
console.error(`\nThis will not work with Translation.io. Please remove it from the "target_locales".`) | ||
valid = false | ||
} else if (! this.targetFilesPath().includes('{lang}')) { | ||
console.error(`\n⚠️ The "target_files_path" parameter in your ${this.configFile} file does not contain the "{lang}" placeholder.`) | ||
console.error(`\nPlease update this parameter so that it contains "{lang}", in order for the process to work.`) | ||
valid = false | ||
} | ||
if (! valid) { | ||
console.error(`\n❌ The ${action} process could not be executed, because some of the parameters in your ${this.configFile} file are invalid ❌`) | ||
process.exitCode = 1 | ||
} | ||
return valid | ||
} | ||
options() { | ||
@@ -26,11 +65,15 @@ return JSON.parse( | ||
sourceLanguage() { | ||
return this.options()['source_locale'].trim() | ||
return (this.options()['source_locale'] || '').trim() | ||
} | ||
targetLanguages() { | ||
return this.options()['target_locales'].map(locale => locale.trim()) | ||
let locales = this.options()['target_locales'] | ||
// The method should always return an array | ||
// The validateConfig() method will trigger a console error if it is an empty array | ||
return Array.isArray(locales) ? locales.map(locale => locale.trim()) : [] | ||
} | ||
apiKey() { | ||
return this.options()['api_key'] | ||
return this.options()['api_key'] || '' | ||
} | ||
@@ -42,12 +85,22 @@ | ||
// localeTemplatePath() { | ||
// return this.options()['locale_template_path'] || `./src/locale/messages.{locale}.xlf` | ||
// } | ||
sourceFilePath() { | ||
return this.options()['source_file_path'] || './src/locale/messages.xlf' | ||
} | ||
targetFilesPath() { | ||
return this.options()['target_files_path'] || './src/locale/messages.{lang}.xlf' | ||
} | ||
sourceFile() { | ||
return './src/locale/messages.xlf' | ||
return this.sourceFilePath() | ||
} | ||
targetFile(language) { | ||
return `./src/locale/messages.${language}.xlf` | ||
const regex = new RegExp('\{lang\}', 'g') | ||
const targetFile = this.targetFilesPath().replace(regex, language) | ||
const targetDir = path.dirname(targetFile) | ||
fs.mkdirSync(targetDir, { recursive: true }) | ||
return targetFile | ||
} | ||
@@ -54,0 +107,0 @@ |
@@ -13,2 +13,6 @@ const Base = require('./base') | ||
if (! this.validateConfig('Init')) { | ||
return false | ||
} | ||
// 1. Prepare Translation.io request | ||
@@ -71,3 +75,3 @@ let request = { | ||
}, | ||
error => { | ||
error => { | ||
console.error('HTTP REQUEST ERROR') | ||
@@ -74,0 +78,0 @@ console.error(error.message) |
@@ -16,2 +16,6 @@ const Base = require('./base') | ||
if (! this.validateConfig('Sync')) { | ||
return false | ||
} | ||
// 1. Extract source segments | ||
@@ -50,3 +54,3 @@ const sourceRaw = fs.readFileSync(this.sourceFile()) | ||
}, | ||
error => { | ||
error => { | ||
console.error('HTTP REQUEST ERROR') | ||
@@ -53,0 +57,0 @@ console.error(error.message) |
@@ -22,4 +22,4 @@ const Init = require('./calls/init') | ||
else { | ||
console.log('No action selected') | ||
console.error('No action selected') | ||
process.exitCode = 1 | ||
} |
50130
675
492