packages/fxa-content-server/app/scripts/lib/translator.js (55 lines of code) (raw):
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
// Translate `en` strings into a language suitable for the user.
// Translated strings are fetched from the server. The translation
// is chosen on the backend based on the user's `Accept-Language`
// headers.
import $ from 'jquery';
import _ from 'underscore';
import Strings from './strings';
import xhr from './xhr';
/**
* @param {Object} options - translator options
* @param {Boolean} [options.forceEnglish] - force English translations, used in tests.
* @param {Object} [options.xhr=xhr] XHR module to use.
* @constructor
*/
const Translator = function (options = {}) {
if (options.forceEnglish) {
this._forceEnglish = true;
}
this._xhr = options.xhr || xhr;
};
Translator.prototype = {
// In dev mode, translations are requested in `fetch`.
// In prod mode, __translations__ will be replaced with the actual translations.
// `__translations__` is used in hopes that it's a slight bit less likely
// than `translations` to be used in another module, a collision would
// bork the build.
// DO NOT EDIT BELOW HERE W/O CHECKING LOCALIZED BUILDS
__translations__: {},
// DO NOT EDIT ABOVE HERE W/O CHECKING LOCALIZED BUILDS
fetch() {
// fetch translations for dev mode. In prod, __translations__: {}
// is replaced with translations as part of the build step.
return new Promise((resolve, reject) => {
if (this._forceEnglish || Object.keys(this.__translations__).length) {
resolve();
} else {
this._xhr.getJSON('/i18n/client.json').then((translations) => {
this.__translations__ = translations;
resolve();
}, reject);
}
});
},
set(translations) {
this.__translations__ = translations;
},
/**
* Gets a translated value by key but returns the key if nothing is found.
* Does string interpolation on %s and %(named)s.
* @method get
* @param {String} stringToTranslate
* @param {Object} [context={}]
* @returns {String}
*/
get(stringToTranslate, context = {}) {
const translations = this.__translations__;
let translation;
if (context.msgctxt) {
const stringWithContextPrefix = `${context.msgctxt}\u0004${stringToTranslate}`;
// If a translation exists with a context prefix, use that. If no translation exists
// with the context prefix, try to find a string without the context prefix.
translation =
translations[stringWithContextPrefix] ||
translations[stringToTranslate];
} else {
translation = translations[stringToTranslate];
}
/**
* See http://www.lehman.cuny.edu/cgi-bin/man-cgi?msgfmt+1
* and
* https://github.com/mikeedwards/po2json/blob/62e17c999a8e95923ffa24fcd5972fc48a3d3ddf/test/fixtures/pl.json#L23-L27
*
* the .json files appear to be in the format
* non-pluralized:
* {
* "msgid": [null, "translated string"]
* }
* pluralized:
* {
* "msgid": [
* "untranslated_string_plural",
* "translated - 0 items",
* "translated - 1 item",
* "translated - n items"
* ]
* }
*/
if (_.isArray(translation) && translation.length >= 2) {
translation = translation[1];
}
translation = $.trim(translation);
if (!translation) {
translation = stringToTranslate;
}
return this.interpolate(translation, context);
},
interpolate: Strings.interpolate,
/**
* Return a helper function to be used by the template engine
* to translate a string
*
* @param {String} [forceText] - text to translate
* @param {Object} [context] - context to pass to translator
* @returns {Function}
*/
translateInTemplate(forceText, context) {
return (templateText) => {
return this.get(forceText || templateText, context);
};
},
};
export default Translator;