Google Translate your source folder and compile to static dist language directories without writing convoluted references to json files... I dislike i18n libraries so much for the seperation of concern, so here is my personal fix.
- Translates all strings inside
<t>Some String</t>
- Supports all file types.
- Copies your source files into a generated/translated folder of your choice.
- Generate a named JSON file with all your translations.
- Load specified JSON file to generate files.
.
├── src
| └── components
| | └── Header.svelte
| | └── NoTranslations.svelte
| └── helpers.js
| └── App.svelte
| └── main.js
<!-- ./src/components/Header.svelte -->
<header>
<t>Welcome</t>
</header>
// ./src/helpers.js
const string = `<t>Welcome to ${name}</t>`
.
├── src
| ├── __generated__
| | └── es
| | | └── components
| | | | └── Header.svelte
| | | | └── NoTranslations.svelte
| | | └── helpers.js
| | | └── App.svelte
| | └── fr
| | | └── components
| | | | └── Header.svelte
| | | | └── NoTranslations.svelte
| | | └── helpers.js
| | | └── App.svelte
| └── components
| | └── Header.svelte
| | └── NoTranslations.js
| └── helpers.js
| └── App.svelte
| └── main.js
<!-- ./src/__generated__/fr/components/Header.svelte -->
<header>
<t>Bienvenue</t>
</header>
// ./src/__generated__/fr/helpers.js
const string = `<t>Bienvenue sur ${name}</t>`
The package also produces a (specified) JSON file with your translations, so you may load from file, adjusting translations after initial run.
[
{
"_src": "./src/components/Header.svelte",
"en": "Welcome",
"es": "Bienvenidos",
"fr": "Bienvenue"
},
{
"_src": "./src/helpers.js",
"en": "Welcome to ${name}",
"es": "Bienvenido a ${name}",
"fr": "Bienvenue sur ${name}"
},
]
This file allows version control of your translations, also allows easy A/B testing for your translations quite easily.
See the Example Svelte.js or Example Vue.js for how your project can look.
- Node.js > 10
- Google Cloud Translate account + API Key
npm install --save-dev jamstack-translate
Create a file in your root directory (ex: translate.js)
require('dotenv').config()
const translate = require('../index.js');
const GOOGLEKEY = process.env.GOOGLE_API_KEY
const OPTIONS = {
targetLanguages: [
'fr',
'es',
],
targetFiles: [
'./src/App.svelte',
'./src/components/**/*.svelte',
'./src/views/**/*.svelte',
'./src/helpers.js',
// etc
],
targetDirectory: './src/__generated__/',
sourceDirectory: './src/',
translationFile: './translations.json',
loadTranslationsFromFile: true,
}
const init = async () => {
const result = await translate(GOOGLEKEY, OPTIONS);
console.log(result);
}
init();
then simply run
node translate.js
The pacakge does some rudimentary regex/replace in order to address most issues (mostly caused by Google translate), here are those issues currenlty active and observed.
For most issues, you can manually fix these issues in your created JSON file, and compile with fixes.
Currently, this...
<t><span class="text--green">hello</span> there</t>
Becomes...
<t><span class="text - green"> bonjour </span> là</t>
Currently, if you translate a string inside a JS file, like so...
const string = '<t>Please</t>'
Becomes
const string = '<t>S'il vous plaît</t>'
Which is unnescaped, and will cause and compile/runtime error.
Best to use backticks instead of single quotes.
const string = `<t>Please</t>`
If you can't use backticks, you must manually escape the single quotes created in your created JSON file, and compile with fixes.
[
{
_file: "file.html",
en: "Please",
fr: "S\\'il vous plaît"
}
]
For every new language folder you create, create a new entry file (multi input) for your application and point the respective imports to their lanuage folder. Then, in your index.html
file, dynamically load your bundle.js
depending on your method of choosing languages (I prefer URL parameters ?lng={language}
).
Note: Below is with Rollup/Svelte v3, other docs for webpack etc. will coming soon.
Create new main.js files for each new langauge,
.
├── src
| └── App.svelte
| └── main-es.js <<<< New
| └── main-fr.js <<<< New
| └── main.js
Edit each new main file, and point to your new entry file
main-fr.js
import App from './__generated__/fr/App.svelte';
const app = new App({
target: document.body,
props: {
name: 'world'
}
});
export default app;
Generate a new bundle for each main file
Rollup.config.js
//...
import multiInput from 'rollup-plugin-multi-input';
export default {
input: [{
bundle_en: 'src/main.js',
bundle_fr: 'src/main-fr.js',
bundle_es: 'src/main-es.js',
}],
output: {
sourcemap: true,
name: 'app',
format: 'es',
dir: 'public/build'
},
plugins: [
multiInput(),
// ...
In your HTML, instead of a script tag for bundle.js, dynamically load your new bundle depending on query param ?lng={language}
<!-- <script defer src='/build/bundle.js'></script> -->
<script>
var urlParams = new URLSearchParams(window.location.search);
var language = urlParams.get('lng') || 'en';
language = language.toLowerCase()
var head = document.getElementsByTagName('head')[0];
var script = document.createElement('script');
script.type = 'module';
script.src = '/build/bundle_' + language + '.js';
head.appendChild(script);
</script>
And then use like so:
http://localhost:5000/?lng=es
- Test static HTML files (
index.html
=>fr/index.html
) - Parse target folder (src) for all tags.
- Build JSON file of output.
- Load JSON file for input.
- Copy
src/
tosrc/__generated__/${lang}/
- Replace each
src/__generated__/${lang}/
with their translations. - Clean up translation.json file, duplicates in there.
- Generate
__generated__
folder, and language folders inside. - Refactor, ready for tests
- Copy of other folders (non translated) into
dist
. - Allow
<title>
translations, as this is parsed as a string inside the html. Do replace? - Test React.js cli starter
- Framework agnostic
- Test Svelte.js cli starter
- Test Vue.js cli starter
- Typescript Support
- Tests
- Look into an "escape keywork" for variables. Without handlebar variable ${var}, google translate can transform our variables:
{name}
becomes{nombre}
etc.- Triple underscore idea:
<t>Hello ___{name}___</t>
=><t>Hola {name}</t>
- Triple underscore idea: