Skip to content

adamleithp/jamstack-translate

Repository files navigation

jamstack-translate (proof of concept)

Generate static translations for any front end project.

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.

What does this package do?

  • 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.

Example

Before...
.
├── 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>`
After...
.
├── 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.

Requirements

  • Node.js > 10
  • Google Cloud Translate account + API Key

Getting started

Install

npm install --save-dev jamstack-translate

Usage

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();

Run

then simply run node translate.js


Current Issues and best practices and how to avoid them

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.

CSS BEM (Modifier) classes

Currently, this...

<t><span class="text--green">hello</span> there</t>

Becomes...

<t><span class="text - green"> bonjour </span></t>

Single quotes when inside a JS file

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"
 }
]

Usage with JS frameworks

TLDR

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}).

Full Example

Note: Below is with Rollup/Svelte v3, other docs for webpack etc. will coming soon.

Entry files

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;

Bundler (Rollup.js)

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(),
		// ...

Index.html (using new bundle)

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>

Testing each language

And then use like so:

http://localhost:5000/?lng=es

TODO

  • 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/ to src/__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

Stretch goals

  • 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>

About

Generate static translations for any front end project.

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published