Skip to content

Instantly share code, notes, and snippets.

@bitsmanent
Last active July 23, 2023 22:12
Show Gist options
  • Save bitsmanent/ee6eea3010021bc62d188739b42c86cd to your computer and use it in GitHub Desktop.
Save bitsmanent/ee6eea3010021bc62d188739b42c86cd to your computer and use it in GitHub Desktop.
Simple translations with parametrized strings and infinite plural forms
/* Do not use code you have not read first.
* Original file: https://gist.github.com/clamiax/ee6eea3010021bc62d188739b42c86cd */
let T9N_DEFLANG = null;
function _() {
if(!T9N_DEFLANG)
return "";
return t9n_format(T9N_DEFLANG, ...Array.from(arguments));
}
function t9n_use(lang) {
T9N_DEFLANG = lang;
}
function t9n_format(lang, label, ...argv) {
const node = lang.catalog[label];
const idx = lang.getpidx(argv[0]) || 0;
const fmt = typeof node == "object" && node[idx] ? node[idx] : node;
if(!fmt)
return label;
return t9n_printf(fmt, ...argv);
}
function t9n_printf(fmt, ...argv) {
let ret = "", len, i, ai;
for(i = 0, len = fmt.length; i < len; ++i) {
if(fmt[i] == '%') {
++i;
if(fmt[i] != '%') {
ai = "";
while(/\d/.test(fmt[i]))
ai += fmt[i++];
ret += ai !== "" ? argv[ai] : fmt[i-1];
--i;
continue;
}
}
ret += fmt[i];
}
return ret;
}
/* export the module (e.g. in React) */
//export {t9n_use,t9n_format,_}
@bitsmanent
Copy link
Author

bitsmanent commented Jan 20, 2020

Sample usage (number for plural form must be the first argument):

const testlang = {
	getpidx: (n) => !n ? 0 : n >= 1 && n <= 9 ? 1 : n < 50 ? 2 : 3,
	catalog: {
		HELLO: "Hello %0",
		CURVAL: "Current value is %0",
		ABOUTME: "My name is %1 and I'm %0 years old",
		NUMITEMS: [
			"No items in %1",
			"few items in %1",
			"%0 items in %1",
			"mani items in %1"
		],
		"This is a sample text": "th1s 1s 4 s4mpl3 t3xt"
	},
};

t9n_use(testlang);
console.log(_("HELLO", "World"));
console.log(_("CURVAL", "Foo"));
console.log(_("ABOUTME", 35, "Bar"));
[0, 5, 10, 25, 50].forEach((n) => {
	console.log(_("NUMITEMS", n, "Baz"));
});
console.log(_("This is a sample text"));
console.log(_("Unexistent label"));

Output:

Hello World
Current value is Foo
My name is Bar and I'm 35 years old
No items in Baz
few items in Baz
10 items in Baz
25 items in Baz
mani items in Baz
th1s 1s 4 s4mpl3 t3xt
Unexistent label

Plural form index is taken via getpidx which say for english or italian looks like n == 1 ? 0 : 1.

@bitsmanent
Copy link
Author

bitsmanent commented Jan 21, 2020

React Native sample usage:

/* translations */
import {t9n_use,_} from "./src/t9n";
import it_IT from "./languages/it_IT";

global._ = _; /* t9n._ anywhere */

const Languages = [
        {name: "Italiano", locale: "it_IT", data: it_IT},
];

The file ./languages/it_IT.js is like the following:

export default {
        getpidx: (n) => n == 1 ? 0 : 1,
        catalog: {
                SLOGAN: "t9n rocks (it_IT)",
                /* ... */
        }
};

A getlocale implementation may looks like this:

import {NativeModules} from "react-native";

function getlocale() {
        let locid;

        if(Platform.OS == "ios") {
                locid = NativeModules.SettingsManager.settings.AppleLanguages[0];
                locid = locid.replace('-', '_'); /* always use the same format */
        }
        else
                locid = NativeModules.I18nManager.localeIdentifier;
        return locid;
}

Then at some point during startup (possibly before the first render, in App constructor):

/* set the App language */
const locale = getlocale();
const lang = Languages.find(x => x.locale == locale) || Languages[0];
t9n_use(lang.data);

This take the language from the OS locale. If language is not supported then take the first element from Languages. You may also want to check user preferences if any and force the specified language instead.

Now you can use the _ function to translate anything in the app.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment