diff --git a/modules/core/localizer.js b/modules/core/localizer.js index 39985b266..b68b24338 100644 --- a/modules/core/localizer.js +++ b/modules/core/localizer.js @@ -94,14 +94,20 @@ export function coreLocalizer() { .then(() => { let requestedLocales = (_preferredLocaleCodes || []) // List of locales preferred by the browser in priority order. - // This always includes an `en` fallback, so we know at least one is valid. - .concat(utilDetect().browserLocales); + .concat(utilDetect().browserLocales) + // fallback to English since it's the only guaranteed complete language + .concat(['en']); _localeCodes = localesToUseFrom(requestedLocales); - // run iD in the highest-priority locale; the rest are fallbacks + // Run iD in the highest-priority locale; the rest are fallbacks _localeCode = _localeCodes[0]; - const loadStringsPromises = _localeCodes.map(function(code) { + // Will always return the index for `en` if nothing else + const fullCoverageIndex = _localeCodes.findIndex(function(locale) { + return _dataLocales[locale].pct === 1; + }); + // We only need to load locales up until we find one with full coverage + const loadStringsPromises = _localeCodes.slice(0, fullCoverageIndex + 1).map(function(code) { return localizer.loadLocale(code); }); return Promise.all(loadStringsPromises); @@ -116,32 +122,19 @@ export function coreLocalizer() { function localesToUseFrom(requestedLocales) { let supportedLocales = _dataLocales; - let toLoad = []; - + let toUse = []; for (let i in requestedLocales) { let locale = requestedLocales[i]; - if (supportedLocales[locale]) { - toLoad.push(locale); - } + if (supportedLocales[locale]) toUse.push(locale); if (locale.includes('-')) { // Full locale ('es-ES'), add fallback to the base ('es') let langPart = locale.split('-')[0]; - if (supportedLocales[langPart]) { - toLoad.push(langPart); - } + if (supportedLocales[langPart]) toUse.push(langPart); } } - - toLoad = utilArrayUniq(toLoad); - - // this is guaranteed to always return an index since `en` is always listed - // and `en` always has full coverage - let fullCoverageIndex = toLoad.findIndex(function(locale) { - return supportedLocales[locale].pct === 1; - }); - // we only need to load locales up until we find one with full coverage - return toLoad.slice(0, fullCoverageIndex + 1); + // remove duplicates + return utilArrayUniq(toUse); } function updateForCurrentLocale() { @@ -329,7 +322,11 @@ export function coreLocalizer() { // Returns the localized text wrapped in an HTML element encoding the locale info localizer.t.html = function(stringId, replacements, locale) { const info = localizer.tInfo(stringId, replacements, locale); - return `${info.text}`; + return localizer.htmlForLocalizedText(info.text, info.locale); + }; + + localizer.htmlForLocalizedText = function(text, localeCode) { + return `${text}`; }; localizer.languageName = (code, options) => { diff --git a/modules/services/osm_wikibase.js b/modules/services/osm_wikibase.js index f56f825b5..d2bc29ed7 100644 --- a/modules/services/osm_wikibase.js +++ b/modules/services/osm_wikibase.js @@ -32,22 +32,6 @@ function request(url, callback) { } -/** - * Get the best string value from the descriptions/labels result - * Note that if mediawiki doesn't recognize language code, it will return all values. - * In that case, fallback to use English. - * @param values object - either descriptions or labels - * @param langCode String - * @returns localized string - */ -function localizedToString(values, langCode) { - if (values) { - values = values[langCode] || values.en; - } - return values ? values.value : ''; -} - - export default { init: function() { @@ -137,12 +121,16 @@ export default { var tagSitelink = (params.key && params.value) ? this.toSitelink(params.key, params.value) : false; var localeSitelink; - if (params.langCode && _localeIDs[params.langCode] === undefined) { - // If this is the first time we are asking about this locale, - // fetch corresponding entity (if it exists), and cache it. - // If there is no such entry, cache `false` value to avoid re-requesting it. - localeSitelink = ('Locale:' + params.langCode).replace(/_/g, ' ').trim(); - titles.push(localeSitelink); + if (params.langCodes) { + params.langCodes.forEach(function(langCode) { + if (_localeIDs[langCode] === undefined) { + // If this is the first time we are asking about this locale, + // fetch corresponding entity (if it exists), and cache it. + // If there is no such entry, cache `false` value to avoid re-requesting it. + localeSitelink = ('Locale:' + langCode).replace(/_/g, ' ').trim(); + titles.push(localeSitelink); + } + }); } if (rtypeSitelink) { @@ -183,7 +171,7 @@ export default { action: 'wbgetentities', sites: 'wiki', titles: titles.join('|'), - languages: params.langCode, + languages: params.langCodes.join('|'), languagefallback: 1, origin: '*', format: 'json', @@ -202,9 +190,6 @@ export default { var localeID = false; Object.values(d.entities).forEach(function(res) { if (res.missing !== '') { - // Simplify access to the localized values - res.description = localizedToString(res.descriptions, params.langCode); - res.label = localizedToString(res.labels, params.langCode); var title = res.sitelinks.wiki.title; if (title === rtypeSitelink) { @@ -226,7 +211,7 @@ export default { if (localeSitelink) { // If locale ID is not found, store false to prevent repeated queries - that.addLocale(params.langCode, localeID); + that.addLocale(params.langCodes[0], localeID); } callback(null, result); @@ -253,8 +238,10 @@ export default { // getDocs: function(params, callback) { var that = this; - var langCode = localizer.localeCode().toLowerCase(); - params.langCode = langCode; + var langCodes = localizer.localeCodes().map(function(code) { + return code.toLowerCase(); + }); + params.langCodes = langCodes; this.getEntity(params, function(err, data) { if (err) { @@ -268,21 +255,33 @@ export default { return; } + var i; + var description; + for (i in langCodes) { + let code = langCodes[i]; + if (entity.descriptions[code] && entity.descriptions[code].language === code) { + description = entity.descriptions[code]; + break; + } + } + if (!description && Object.values(entity.descriptions).length) description = Object.values(entity.descriptions)[0]; + // prepare result var result = { title: entity.title, - description: entity.description, + description: description ? description.value : '', + descriptionLocaleCode: description ? description.language : '', editURL: 'https://wiki.openstreetmap.org/wiki/' + entity.title }; // add image if (entity.claims) { var imageroot; - var image = that.claimToValue(entity, 'P4', langCode); + var image = that.claimToValue(entity, 'P4', langCodes[0]); if (image) { imageroot = 'https://commons.wikimedia.org/w/index.php'; } else { - image = that.claimToValue(entity, 'P28', langCode); + image = that.claimToValue(entity, 'P28', langCodes[0]); if (image) { imageroot = 'https://wiki.openstreetmap.org/w/index.php'; } @@ -302,21 +301,20 @@ export default { var tagWiki = that.monolingualClaimToValueObj(data.tag, 'P31'); var keyWiki = that.monolingualClaimToValueObj(data.key, 'P31'); - // If exact language code does not exist, try to find the first part before the '-' - // BUG: in some cases, a more elaborate fallback logic might be needed - var langPrefix = langCode.split('-', 2)[0]; - - // use the first acceptable wiki page - result.wiki = - getWikiInfo(rtypeWiki, langCode, 'inspector.wiki_reference') || - getWikiInfo(rtypeWiki, langPrefix, 'inspector.wiki_reference') || - getWikiInfo(rtypeWiki, 'en', 'inspector.wiki_en_reference') || - getWikiInfo(tagWiki, langCode, 'inspector.wiki_reference') || - getWikiInfo(tagWiki, langPrefix, 'inspector.wiki_reference') || - getWikiInfo(tagWiki, 'en', 'inspector.wiki_en_reference') || - getWikiInfo(keyWiki, langCode, 'inspector.wiki_reference') || - getWikiInfo(keyWiki, langPrefix, 'inspector.wiki_reference') || - getWikiInfo(keyWiki, 'en', 'inspector.wiki_en_reference'); + var wikis = [rtypeWiki, tagWiki, keyWiki]; + for (i in wikis) { + var wiki = wikis[i]; + for (var j in langCodes) { + var code = langCodes[j]; + var referenceId = (langCodes[0].split('-')[0] !== 'en' && code.split('-')[0] === 'en') ? 'inspector.wiki_en_reference' : 'inspector.wiki_reference'; + var info = getWikiInfo(wiki, code, referenceId); + if (info) { + result.wiki = info; + break; + } + } + if (result.wiki) break; + } callback(null, result); diff --git a/modules/services/wikidata.js b/modules/services/wikidata.js index d81ec0d5d..a3cd287fb 100644 --- a/modules/services/wikidata.js +++ b/modules/services/wikidata.js @@ -1,6 +1,6 @@ import { json as d3_json } from 'd3-fetch'; -import { utilArrayUniq, utilQsString } from '../util'; +import { utilQsString } from '../util'; import { localizer } from '../core/localizer'; var apibase = 'https://www.wikidata.org/w/api.php?'; @@ -85,15 +85,9 @@ export default { languagesToQuery: function() { - var localeCode = localizer.localeCode().toLowerCase(); - // HACK: en-us isn't a wikidata language. We should really be filtering by - // the languages known to be supported by wikidata. - if (localeCode === 'en-us') localeCode = 'en'; - return utilArrayUniq([ - localeCode, - localizer.languageCode().toLowerCase(), - 'en' - ]); + return localizer.localeCodes().map(function(code) { + return code.toLowerCase(); + }); }, @@ -157,14 +151,20 @@ export default { var i; var description; - if (entity.descriptions && Object.keys(entity.descriptions).length > 0) { - description = entity.descriptions[Object.keys(entity.descriptions)[0]].value; + for (i in langs) { + let code = langs[i]; + if (entity.descriptions[code] && entity.descriptions[code].language === code) { + description = entity.descriptions[code]; + break; + } } + if (!description && Object.values(entity.descriptions).length) description = Object.values(entity.descriptions)[0]; // prepare result var result = { title: entity.id, - description: description, + description: description ? description.value : '', + descriptionLocaleCode: description ? description.language : '', editURL: 'https://www.wikidata.org/wiki/' + entity.id }; diff --git a/modules/ui/tag_reference.js b/modules/ui/tag_reference.js index da8f2236c..5e000de08 100644 --- a/modules/ui/tag_reference.js +++ b/modules/ui/tag_reference.js @@ -3,7 +3,7 @@ import { select as d3_select } from 'd3-selection'; -import { t } from '../core/localizer'; +import { t, localizer } from '../core/localizer'; import { services } from '../services'; import { svgIcon } from '../svg/icon'; @@ -64,7 +64,7 @@ export function uiTagReference(what) { _body .append('p') .attr('class', 'tag-reference-description') - .html(docs.description || t.html('inspector.no_documentation_key')) + .html(docs.description ? localizer.htmlForLocalizedText(docs.description, docs.descriptionLocaleCode) : t.html('inspector.no_documentation_key')) .append('a') .attr('class', 'tag-reference-edit') .attr('target', '_blank') diff --git a/modules/util/detect.js b/modules/util/detect.js index 1eafa9c7b..974e682a3 100644 --- a/modules/util/detect.js +++ b/modules/util/detect.js @@ -94,9 +94,7 @@ export function utilDetect(refresh) { .concat(navigator.languages || []) .concat([ // old property for backwards compatibility - navigator.userLanguage, - // fallback to English - 'en' + navigator.userLanguage ]) // remove any undefined values .filter(Boolean)