Skip to content

Commit

Permalink
bug 815852: inline l10n resources to avoid flickering on all localize…
Browse files Browse the repository at this point in the history
…d apps

+ don't zip */locales/* if l10n resources are inlined
+ ensure we don't have to clobber the profile to rebuild Gaia
r=ochameau, a=21
  • Loading branch information
fabi1cazenave committed Dec 26, 2012
1 parent 2a977d7 commit d5ab257
Show file tree
Hide file tree
Showing 4 changed files with 116 additions and 25 deletions.
3 changes: 3 additions & 0 deletions Makefile
Expand Up @@ -81,6 +81,7 @@ GAIA_LOCALES_PATH?=locales
LOCALES_FILE?=shared/resources/languages.json
GAIA_LOCALE_SRCDIRS=shared $(GAIA_APP_SRCDIRS)
GAIA_DEFAULT_LOCALE?=en-US
GAIA_INLINE_LOCALES?=1

###############################################################################
# The above rules generate the profile/ folder and all its content. #
Expand Down Expand Up @@ -316,10 +317,12 @@ define run-js-command
const HOMESCREEN = "$(HOMESCREEN)"; const GAIA_PORT = "$(GAIA_PORT)"; \
const GAIA_APP_SRCDIRS = "$(GAIA_APP_SRCDIRS)"; \
const GAIA_LOCALES_PATH = "$(GAIA_LOCALES_PATH)"; \
const LOCALES_FILE = "$(LOCALES_FILE)"; \
const BUILD_APP_NAME = "$(BUILD_APP_NAME)"; \
const PRODUCTION = "$(PRODUCTION)"; \
const OFFICIAL = "$(MOZILLA_OFFICIAL)"; \
const GAIA_DEFAULT_LOCALE = "$(GAIA_DEFAULT_LOCALE)"; \
const GAIA_INLINE_LOCALES = "$(GAIA_INLINE_LOCALES)"; \
const GAIA_ENGINE = "xpcshell"; \
'; \
$(XULRUNNERSDK) $(XPCSHELLSDK) -e "$$JS_CONSTS" -f build/utils.js "build/$(strip $1).js"
Expand Down
92 changes: 76 additions & 16 deletions build/webapp-l10n.js
Expand Up @@ -5,12 +5,25 @@ function debug(str) {


/**
* Expose a global `l10nTarget' object and load `l10n.js' in it
* Expose a global `l10nTarget' object and load `l10n.js' in it --
* note: the `?reload' trick ensures we don't load a cached `l10njs' library.
*/

var l10nTarget = { navigator: {} };
Services.scriptloader.
loadSubScript('file:///' + GAIA_DIR + '/shared/js/l10n.js', l10nTarget);
Services.scriptloader.loadSubScript('file:///' + GAIA_DIR +
'/shared/js/l10n.js?reload=' + new Date().getTime(), l10nTarget);


/**
* Locale list -- by default, only the default one
*/

var l10nLocales = [GAIA_DEFAULT_LOCALE];
var l10nDictionary = {
locales: {},
default_locale: GAIA_DEFAULT_LOCALE
};
l10nDictionary.locales[GAIA_DEFAULT_LOCALE] = {};


/**
Expand Down Expand Up @@ -47,7 +60,22 @@ function l10n_getFileContent(webapp, htmlFile, relativePath) {
}
}

function l10n_serializeHTMLDocument(file, doc) {
function l10n_embedExternalResources(doc, dictionary) {
// remove all external l10n resource nodes
var resources = doc.querySelectorAll('link[type="application/l10n"]');
for (let i = 0; i < resources.length; i++) {
let res = resources[i].outerHTML;
resources[i].outerHTML = '<!-- ' + res + ' -->';
}

// put the current dictionary in an inline JSON script
let script = doc.createElement('script');
script.type = 'application/l10n';
script.innerHTML = '\n ' + JSON.stringify(dictionary) + '\n';
doc.documentElement.appendChild(script);
}

function l10n_serializeHTMLDocument(doc, file) {
debug('saving: ' + file.path);

// the doctype string should always be '<!DOCTYPE html>' but just in case...
Expand All @@ -72,7 +100,7 @@ function l10n_serializeHTMLDocument(file, doc) {
htmlStr += ' ' + attrs[i].nodeName.toLowerCase() +
'="' + attrs[i].nodeValue + '"';
}
let innerHTML = docElt.innerHTML.replace(/ \n\n\n<\/body>$/, ' </body>');
let innerHTML = docElt.innerHTML.replace(/ \n*<\/body>\n*/, ' </body>\n');
htmlStr += '>\n ' + innerHTML + '\n</html>\n';

writeContent(file, doctypeStr + htmlStr);
Expand All @@ -81,6 +109,9 @@ function l10n_serializeHTMLDocument(file, doc) {
function l10n_compile(webapp, file) {
let mozL10n = l10nTarget.navigator.mozL10n;

let processedLocales = 0;
let dictionary = l10nDictionary;

// catch console.[log|warn|info] calls and redirect them to `dump()'
// XXX for some reason, this won't work if gDEBUG >= 2 in l10n.js
function l10n_dump(str) {
Expand Down Expand Up @@ -111,17 +142,28 @@ function l10n_compile(webapp, file) {

// catch the `localized' event dispatched by `fireL10nReadyEvent()'
l10nTarget.dispatchEvent = function() {
debug('fireL10nReadyEvent');
let docElt = l10nTarget.document.documentElement;

// set the lang/dir attributes of the current document
docElt.dir = mozL10n.language.direction;
docElt.lang = mozL10n.language.code;
processedLocales++;
debug('fireL10nReadyEvent - ' +
processedLocales + '/' + l10nLocales.length);

// save localized document
let newPath = file.path + '.' + docElt.lang;
let newFile = new FileUtils.File(newPath);
l10n_serializeHTMLDocument(newFile, l10nTarget.document);
let docElt = l10nTarget.document.documentElement;
dictionary.locales[mozL10n.language.code] = mozL10n.dictionary;

if (processedLocales < l10nLocales.length) {
// load next locale
mozL10n.language.code = l10nLocales[processedLocales];
} else {
// we expect the last locale to be the default one:
// set the lang/dir attributes of the current document
docElt.dir = mozL10n.language.direction;
docElt.lang = mozL10n.language.code;

// save localized document
let newPath = file.path + '.' + GAIA_DEFAULT_LOCALE;
let newFile = new FileUtils.File(newPath);
l10n_embedExternalResources(l10nTarget.document, dictionary);
l10n_serializeHTMLDocument(l10nTarget.document, newFile);
}
};

// load and parse the HTML document
Expand All @@ -133,7 +175,7 @@ function l10n_compile(webapp, file) {
// selecting a language triggers `XMLHttpRequest' and `dispatchEvent' above
if (l10nTarget.document.querySelector('script[src$="l10n.js"]')) {
debug('localizing: ' + file.path);
mozL10n.language.code = GAIA_DEFAULT_LOCALE;
mozL10n.language.code = l10nLocales[processedLocales];
}
}

Expand All @@ -144,6 +186,24 @@ function l10n_compile(webapp, file) {

debug('Begin');

if (GAIA_INLINE_LOCALES) {
l10nLocales = [];
l10nDictionary.locales = {};

let file = getFile(GAIA_DIR + '/' + LOCALES_FILE);
let locales = JSON.parse(getFileContent(file));

// we keep the default locale order for `l10nDictionary.locales',
// but we ensure the default locale comes last in `l10nLocales'.
for (let lang in locales) {
if (lang != GAIA_DEFAULT_LOCALE) {
l10nLocales.push(lang);
}
l10nDictionary.locales[lang] = {};
}
l10nLocales.push(GAIA_DEFAULT_LOCALE);
}

Gaia.webapps.forEach(function(webapp) {
// if BUILD_APP_NAME isn't `*`, we only accept one webapp
if (BUILD_APP_NAME != '*' && webapp.sourceDirectoryName != BUILD_APP_NAME)
Expand Down
13 changes: 10 additions & 3 deletions build/webapp-zip.js
Expand Up @@ -65,6 +65,7 @@ function addToZip(zip, pathInZip, file) {
// Case 2/ Directory
else if (file.isDirectory()) {
debug(' +directory to zip ' + pathInZip);

if (!zip.hasEntry(pathInZip))
zip.addEntryDirectory(pathInZip, file.lastModifiedTime, false);

Expand Down Expand Up @@ -150,6 +151,10 @@ Gaia.webapps.forEach(function(webapp) {
debug('# Create zip for: ' + webapp.domain);
let files = ls(webapp.sourceDirectoryFile);
files.forEach(function(file) {
// Ignore l10n files if they have been inlined
if (GAIA_INLINE_LOCALES &&
(file.leafName === 'locales' || file.leafName === 'locales.ini'))
return;
// Ignore files from /shared directory (these files were created by
// Makefile code). Also ignore files in the /test directory.
if (file.leafName !== 'shared' && file.leafName !== 'test')
Expand Down Expand Up @@ -190,9 +195,11 @@ Gaia.webapps.forEach(function(webapp) {
used.js.push(path);
break;
case 'locales':
let localeName = path.substr(0, path.lastIndexOf('.'));
if (used.locales.indexOf(localeName) == -1) {
used.locales.push(localeName);
if (!GAIA_INLINE_LOCALES) {
let localeName = path.substr(0, path.lastIndexOf('.'));
if (used.locales.indexOf(localeName) == -1) {
used.locales.push(localeName);
}
}
break;
case 'resources':
Expand Down
33 changes: 27 additions & 6 deletions shared/js/l10n.js
Expand Up @@ -65,6 +65,12 @@
return document.querySelectorAll('link[type="application/l10n"]');
}

function getL10nDictionary() {
var script = document.querySelector('script[type="application/l10n"]');
// TODO: support multiple and external JSON dictionaries
return script ? JSON.parse(script.innerHTML) : null;
}

function getTranslatableChildren(element) {
return element ? element.querySelectorAll('*[data-l10n-id]') : [];
}
Expand Down Expand Up @@ -262,6 +268,8 @@

// load and parse all resources for the specified locale
function loadLocale(lang, callback) {
callback = callback || function _callback() {};

clear();
gLanguage = lang;

Expand All @@ -270,7 +278,16 @@
var langLinks = getL10nResourceLinks();
var langCount = langLinks.length;
if (langCount == 0) {
consoleLog('no resource to load, early way out');
// we might have a pre-compiled dictionary instead
var dict = getL10nDictionary();
if (dict && dict.locales && dict.default_locale) {
consoleLog('using the embedded JSON directory, early way out');
gL10nData = dict.locales[lang] || dict.locales[dict.default_locale];
callback();
} else {
consoleLog('no resource to load, early way out');
}
// early way out
fireL10nReadyEvent(lang);
gReadyState = 'complete';
return;
Expand All @@ -282,9 +299,7 @@
onResourceLoaded = function() {
gResourceCount++;
if (gResourceCount >= langCount) {
if (callback) { // execute the [optional] callback
callback();
}
callback();
fireL10nReadyEvent(lang);
gReadyState = 'complete';
}
Expand Down Expand Up @@ -910,6 +925,9 @@

/**
* Startup & Public API
*
* This section is quite specific to the B2G project: old browsers are not
* supported and the API is slightly different from the standard webl10n one.
*/

// load the default locale on startup
Expand Down Expand Up @@ -943,7 +961,7 @@
});
}

// Public API
// public API
navigator.mozL10n = {
// get a localized string
get: function l10n_get(key, args, fallback) {
Expand Down Expand Up @@ -974,7 +992,10 @@
// translate an element or document fragment
translate: translateFragment,

// this can be used to avoid race conditions
// get (a clone of) the dictionary for the current locale
get dictionary() { return JSON.parse(JSON.stringify(gL10nData)); },

// this can be used to prevent race conditions
get readyState() { return gReadyState; }
};

Expand Down

0 comments on commit d5ab257

Please sign in to comment.