Skip to content

Commit

Permalink
Don't bundle the en.json strings anymore, Promisify locale loading
Browse files Browse the repository at this point in the history
  • Loading branch information
bhousel authored and zlavergne committed Mar 10, 2020
1 parent 352f358 commit 38c43f7
Show file tree
Hide file tree
Showing 5 changed files with 127 additions and 99 deletions.
1 change: 0 additions & 1 deletion data/index.js
@@ -1,2 +1 @@
export { dataLocales } from './locales.json';
export { en as dataEn } from '../dist/locales/en.json';
2 changes: 1 addition & 1 deletion docs/statistics.html

Large diffs are not rendered by default.

137 changes: 71 additions & 66 deletions modules/core/context.js
Expand Up @@ -4,13 +4,13 @@ import { dispatch as d3_dispatch } from 'd3-dispatch';
import { json as d3_json } from 'd3-fetch';
import { select as d3_select } from 'd3-selection';

import { t, currentLocale, addTranslation, setLocale } from '../util/locale';
import { t, localeStrings, setLocale } from '../util/locale';

import { coreData } from './data';
import { coreHistory } from './history';
import { coreValidator } from './validator';
import { coreUploader } from './uploader';
import { dataLocales, dataEn } from '../../data';
import { dataLocales } from '../../data';
import { geoRawMercator } from '../geo/raw_mercator';
import { modeSelect } from '../modes/select';
import { osmSetAreaKeys, osmSetPointTags, osmSetVertexTags } from '../osm/tags';
Expand All @@ -30,29 +30,29 @@ export function coreContext() {
context.version = '2.17.2';
context.privacyVersion = '20191217';

// create a special translation that contains the keys in place of the strings
let tkeys = JSON.parse(JSON.stringify(dataEn)); // clone deep
let parents = [];
// // create a special translation that contains the keys in place of the strings
// let tkeys = JSON.parse(JSON.stringify(dataEn)); // clone deep
// let parents = [];

function traverser(v, k, obj) {
parents.push(k);
if (typeof v === 'object') {
forOwn(v, traverser);
} else if (typeof v === 'string') {
obj[k] = parents.join('.');
}
parents.pop();
}
// function traverser(v, k, obj) {
// parents.push(k);
// if (typeof v === 'object') {
// forOwn(v, traverser);
// } else if (typeof v === 'string') {
// obj[k] = parents.join('.');
// }
// parents.pop();
// }

function forOwn(obj, fn) {
Object.keys(obj).forEach(k => fn(obj[k], k, obj));
}
// function forOwn(obj, fn) {
// Object.keys(obj).forEach(k => fn(obj[k], k, obj));
// }

forOwn(tkeys, traverser);
addTranslation('_tkeys_', tkeys);
// forOwn(tkeys, traverser);
// addLocale('_tkeys_', tkeys);

addTranslation('en', dataEn);
setLocale('en');
// addLocale('en', dataEn);
// setLocale('en');


// https://github.com/openstreetmap/iD/issues/772
Expand Down Expand Up @@ -231,6 +231,8 @@ export function coreContext() {
return context;
};

// Immediately save the user's history to localstorage, if possible
// This is called someteimes, but also on the `window.onbeforeunload` handler
context.save = () => {
// no history save, no message onbeforeunload
if (_inIntro || d3_select('.modal').size()) return;
Expand Down Expand Up @@ -260,6 +262,18 @@ export function coreContext() {
}
};

// Debounce save, since it's a synchronous localStorage write,
// and history changes can happen frequently (e.g. when dragging).
context.debouncedSave = _debounce(context.save, 350);

function withDebouncedSave(fn) {
return function() {
const result = fn.apply(_history, arguments);
context.debouncedSave();
return result;
};
}


/* Graph */
context.hasEntity = (id) => _history.graph().hasEntity(id);
Expand Down Expand Up @@ -417,38 +431,34 @@ export function coreContext() {
context.imagePath = (val) => context.asset(`img/${val}`);


/* locales */
// `locale` letiable contains a "requested locale".
// It won't become the `currentLocale` until after loadLocale() is called.
let _locale, _localePath;
/* Locales */
// Returns a Promise to load the strings for the given locale
context.loadLocale = (requested) => {
let locale = requested;

context.locale = function(loc, path) {
if (!arguments.length) return currentLocale;
_locale = loc;
_localePath = path;
return context;
};
if (!_data) {
return Promise.reject('loadLocale called before init');
}
if (!dataLocales.hasOwnProperty(locale)) { // Not supported, e.g. 'es-FAKE'
locale = locale.split('-')[0]; // Fallback to the first part 'es'
}
if (!dataLocales.hasOwnProperty(locale)) {
return Promise.reject(`Unsupported locale: ${requested}`);
}

context.loadLocale = (callback) => {
if (_locale && _locale !== 'en' && dataLocales.hasOwnProperty(_locale)) {
_localePath = _localePath || context.asset(`locales/${_locale}.json`);
d3_json(_localePath)
.then(result => {
addTranslation(_locale, result[_locale]);
setLocale(_locale);
utilDetect(true);
if (callback) callback();
})
.catch(err => {
if (callback) callback(err.message);
});
} else {
if (_locale) {
setLocale(_locale);
utilDetect(true);
}
if (callback) callback();
if (localeStrings[locale]) { // already loaded
return Promise.resolve(locale);
}

let fileMap = _data.fileMap();
const key = `locale_${locale}`;
fileMap[key] = `locales/${locale}.json`; // .min.json?

return _data.get(key)
.then(d => {
localeStrings[locale] = d[locale];
return locale;
});
};


Expand Down Expand Up @@ -488,12 +498,18 @@ export function coreContext() {
context.init = () => {
const hash = utilStringQs(window.location.hash);

_locale = utilDetect().locale;
if (_locale && !dataLocales.hasOwnProperty(_locale)) {
_locale = _locale.split('-')[0];
}

_data = coreData(context);

// always load the English locale, then load preferred locale.
const requested = utilDetect().locale;
context.loadLocale('en')
.then(() => context.loadLocale(requested))
.then(received => { // `received` may not match `requested`.
setLocale(received); // (e.g. 'es-FAKE' will return 'es')
utilDetect(true); // Then force redetection
})
.catch(err => console.error(err)); // eslint-disable-line

_history = coreHistory(context);
_validator = coreValidator(context);
_uploader = coreUploader(context);
Expand All @@ -504,17 +520,6 @@ export function coreContext() {
context.pauseChangeDispatch = _history.pauseChangeDispatch;
context.resumeChangeDispatch = _history.resumeChangeDispatch;

// Debounce save, since it's a synchronous localStorage write,
// and history changes can happen frequently (e.g. when dragging).
context.debouncedSave = _debounce(context.save, 350);
function withDebouncedSave(fn) {
return function() {
const result = fn.apply(_history, arguments);
context.debouncedSave();
return result;
};
}

context.perform = withDebouncedSave(_history.perform);
context.replace = withDebouncedSave(_history.replace);
context.pop = withDebouncedSave(_history.pop);
Expand Down
47 changes: 33 additions & 14 deletions modules/ui/init.js
Expand Up @@ -4,13 +4,15 @@ import {
selectAll as d3_selectAll
} from 'd3-selection';

import { t, textDirection } from '../util/locale';
import { t, textDirection, setLocale } from '../util/locale';

import { tooltip } from '../util/tooltip';

import { behaviorHash } from '../behavior';
import { modeBrowse } from '../modes/browse';
import { svgDefs, svgIcon } from '../svg';
import { utilGetDimensions } from '../util/dimensions';
import { utilDetect } from '../util/detect';

import { uiAccount } from './account';
import { uiAttribution } from './attribution';
Expand Down Expand Up @@ -337,31 +339,48 @@ export function uiInit(context) {
}


// `ui()` renders the iD interface into the given node, assigning
// that node as the `container`. We need to delay rendering until
// the locale has been loaded (i.e. promise settled), because the
// UI code expects localized strings to be available.
function ui(node, callback) {
_initCallback = callback;
var container = d3_select(node);
context.container(container);
context.loadLocale(function(err) {
if (!err) {

const current = utilDetect().locale;
context.loadLocale(current)
.then(function() {
render(container);
}
if (callback) {
callback(err);
}
});
if (callback) callback();
})
.catch(function(err) {
console.error(err); // eslint-disable-line
if (callback) callback(err);
});
}


ui.restart = function(arg) {
// `ui.restart()` will destroy and rebuild the entire iD interface,
// for example to switch the locale while iD is running.
ui.restart = function(locale) {
context.keybinding().clear();
context.locale(arg);
context.loadLocale(function(err) {
if (!err) {

var requested = locale || utilDetect().locale;
context.loadLocale(requested)
.then(function(received) { // `received` may not match `requested`.
setLocale(received); // (e.g. 'es-FAKE' will return 'es')
utilDetect(true); // Then force redetection

context.container().selectAll('*').remove();
render(context.container());

if (_initCallback) _initCallback();
}
});
})
.catch(function(err) {
console.error(err); // eslint-disable-line
if (_initCallback) _initCallback(err);
});
};

ui.sidebar = uiSidebar(context);
Expand Down
39 changes: 22 additions & 17 deletions modules/util/locale.js
@@ -1,32 +1,37 @@
let _translations = Object.create(null);
let _dataLanguages = {};

export let currentLocale = 'en';

// `localeStrings` is an object containing all loaded locale codes -> string data
// {
// en: {icons: {…}, toolbar: {…}, modes: {…}, operations: {…}, …},
// de: {icons: {…}, toolbar: {…}, modes: {…}, operations: {…}, …},
// …
// }
export let localeStrings = Object.create(null);

export let currentLocale;
export let textDirection = 'ltr';
export let languageNames = {};
export let scriptNames = {};

export function setLocale(val) {
if (_translations[val] !== undefined) {
currentLocale = val;
} else if (_translations[val.split('-')[0]]) {
currentLocale = val.split('-')[0];
}
}

export function addTranslation(id, value) {
_translations[id] = value;
export function setLocale(locale) {
currentLocale = locale;
// if (localeStrings[locale] !== undefined) {
// currentLocale = locale;
// } else if (localeStrings[locale.split('-')[0]]) {
// currentLocale = locale.split('-')[0];
// }
}

/**
* Given a string identifier, try to find that string in the current
* language, and return it. This function will be called recursively
* with locale `en` if a string can not be found in the requested language.
*
* @param {string} s string identifier
* @param {object?} replacements token replacements and default string
* @param {string?} locale locale to use (defaults to currentLocale)
* @returns {string?} localized string
* @param {string} s string identifier
* @param {object?} replacements token replacements and default string
* @param {string?} locale locale to use (defaults to currentLocale)
* @return {string?} localized string
*/
export function t(s, replacements, locale) {
locale = locale || currentLocale;
Expand All @@ -36,7 +41,7 @@ export function t(s, replacements, locale) {
.map(s => s.replace(/<TX_DOT>/g, '.'))
.reverse();

let result = _translations[locale];
let result = localeStrings[locale];

while (result !== undefined && path.length) {
result = result[path.pop()];
Expand Down

0 comments on commit 38c43f7

Please sign in to comment.