Permalink
Browse files

Use reddit locale for i18n (#3735)

* use reddit locale for i18n

* basic integration tests for i18n
  • Loading branch information...
1 parent d322715 commit 73f803d951800dcc2e5b9da1fd751dc7811d0873 @erikdesjardins erikdesjardins committed on GitHub Dec 6, 2016
@@ -1,6 +1,5 @@
/* eslint-env webextensions */
-import '../locales/asChromeI18n';
import { createMessageHandler } from '../lib/environment/_messaging';
import { extendDeep, keyedMutex } from '../lib/utils';
import { apiToPromise } from './_helpers';
@@ -80,5 +79,3 @@ addInterceptor('storage', keyedMutex(async ([operation, key, value]) => {
throw new Error(`Invalid storage operation: ${operation}`);
}
}, ([, key]) => key || '__all_keys__'));
-
-addInterceptor('i18n', ([messageName, substitutions]) => chrome.i18n.getMessage(messageName, substitutions));
@@ -4,7 +4,6 @@
"manifest_version": 2,
"minimum_chrome_version": "54",
"description": "{{prop?description!../package.json}}",
- "default_locale": "en",
"background": {
"scripts": [
"{{./background.entry.js}}"
View
@@ -3,7 +3,6 @@
"version": "{{prop?version!../package.json}}",
"minimum_edge_version": "38.14393.0.0",
"description": "{{prop?description!../package.json}}",
- "default_locale": "en",
"author": "{{prop?author!../package.json}}",
"background": {
"scripts": [
@@ -1,5 +1,4 @@
import { createMessageHandler } from '../lib/environment/_messaging';
-import { getMessage } from '../lib/environment/_mockI18n';
const {
_handleMessage,
@@ -20,4 +19,3 @@ export {
};
addInterceptor('permissions', () => true);
-addInterceptor('i18n', ([messageName, substitutions]) => getMessage(messageName, substitutions));
@@ -1,20 +0,0 @@
-/* @flow */
-
-import { locales, DEFAULT_LOCALE } from '../../locales/asJson';
-import { locale } from '../utils/localization';
-
-export function getMessage(messageName: string, substitutions: string[]): string {
- const { message } = (
- locales[locale()] && locales[locale()][messageName] ||
- DEFAULT_LOCALE[messageName] ||
- { message: '' }
- );
-
- // Replace direct references to substitutions, e.g. `First substitution: $1`
- // Maximum of 9 substitutions allowed, i.e. only one number after the `$`
- return message.replace(/\$(\d)\b(?!\$)/g, (match, number) => substitutions[number - 1] || '');
-
- // Chrome also supports named placeholders, e.g. `Error: $error_message$`
- // but Transifex does not create the `placeholders` field in exported JSON
- // https://developer.chrome.com/extensions/i18n#examples-getMessage
-}
@@ -1,9 +1,10 @@
/* @flow */
-import { sendSynchronous } from 'browserEnvironment';
+import { getMessage } from '../../locales';
+import { rawLocale } from '../utils/localization';
export function i18n(messageName: string, ...substitutions: string[]): string {
if (!messageName) return '';
// implementation should return the empty string if it cannot find a translation
- return sendSynchronous('i18n', [messageName, substitutions]) || messageName;
+ return getMessage(rawLocale(), messageName, substitutions) || messageName;
}
@@ -3,17 +3,30 @@
import _ from 'lodash';
import moment from 'moment';
-const redditLanguages = new Set(['en', 'ar', 'be', 'bg', 'bs', 'ca', 'cs', 'da', 'de', 'el', 'en-au', 'en-ca', 'en-gb', 'en-us', 'eo', 'es', 'es-ar', 'et', 'eu', 'fa', 'fi', 'fr', 'gd', 'he', 'hi', 'hr', 'hu', 'hy', 'id', 'is', 'it', 'ja', 'kn_IN', 'ko', 'la', 'lt', 'lv', 'nl', 'nn', 'no', 'pir', 'pl', 'pt', 'pt-pt', 'pt_BR', 'ro', 'ru', 'sk', 'sl', 'sr', 'sr-la', 'sv', 'ta', 'th', 'tr', 'uk', 'vi', 'zh']);
-
-// some locales incorrectly use _ as a delimiter
-export const locale = _.once(() => {
- const userLocale = (
- typeof document !== 'undefined' && redditLanguages.has(document.documentElement.getAttribute('lang')) && document.documentElement.getAttribute('lang') ||
- typeof navigator !== 'undefined' && navigator.language ||
- 'en'
- ).replace('_', '-');
+const DEFAULT_LOCALE = 'en';
+const REDDIT_LANGUAGES = new Set(['en', 'af', 'ar', 'be', 'bg', 'bn-IN', 'bn-bd', 'bs', 'ca', 'cs', 'cy', 'da', 'de', 'el', 'en-au', 'en-ca', 'en-gb', 'en-us', 'eo', 'es', 'es-ar', 'es-mx', 'et', 'eu', 'fa', 'fi', 'fil', 'fr', 'fr-ca', 'fy-NL', 'ga-ie', 'gd', 'gl', 'he', 'hi', 'hr', 'hu', 'hy', 'id', 'is', 'it', 'ja', 'kn_IN', 'ko', 'la', 'leet', 'lol', 'lt', 'lv', 'ms', 'mt-MT', 'nl', 'nn', 'no', 'pir', 'pl', 'pt', 'pt-pt', 'pt_BR', 'ro', 'ru', 'sk', 'sl', 'sr', 'sr-la', 'sv', 'ta', 'th', 'tr', 'uk', 'vi', 'zh', 'zh-cn']);
+// reddit languages which are not valid browser languages
+const SPECIAL_LANGUAGES = new Set(['leet', 'lol', 'pir']);
+
+export const rawLocale = _.once((): string => {
+ const redditLocale = typeof document !== 'undefined' && document.documentElement.getAttribute('lang');
+
+ if (redditLocale && REDDIT_LANGUAGES.has(redditLocale)) {
+ return redditLocale;
+ } else {
+ // on np. or some non-language subdomain
+ return navigator.language || DEFAULT_LOCALE;
+ }
+});
- return isValidLocale(userLocale) ? userLocale : 'en';
+const locale = _.once(() => {
+ if (!SPECIAL_LANGUAGES.has(rawLocale())) {
+ // `pt_BR` -> `pt-br`
+ const locale = rawLocale().toLowerCase().replace('_', '-');
+ return isValidLocale(locale) ? locale : DEFAULT_LOCALE;
+ } else {
+ return DEFAULT_LOCALE;
+ }
});
function isValidLocale(localeString) {
@@ -1 +0,0 @@
-require.context('file?name=_locales/[name]/messages.json!./locales', false, /\.json$/);
View
@@ -1,12 +0,0 @@
-const localesContext = require.context('json!./locales', false, /\.json$/);
-
-export DEFAULT_LOCALE from 'json!./locales/en.json';
-
-function localeNameFromPath(path) {
- return (/\/(\w+)\.json/).exec(path)[1];
-}
-
-export const locales = localesContext.keys().reduce((obj, key) => {
- obj[localeNameFromPath(key)] = localesContext(key);
- return obj;
-}, {});
View
@@ -0,0 +1,76 @@
+import _ from 'lodash';
+
+const localesContext = require.context('json!./locales', false, /\.json$/);
+const validLocaleKeys = localesContext.keys();
+
+const DEFAULT_TRANSIFEX_LOCALE = 'en';
+
+function localeNameToPath(localeName) {
+ return `./${localeName}.json`;
+}
+
+// `en-ca` -> `en_CA`
+function redditLocaleToTransifexLocale(redditLocale) {
+ switch (redditLocale) {
+ case 'leet':
+ return DEFAULT_TRANSIFEX_LOCALE; // doesn't appear to exist
+ case 'lol':
+ return 'en@lolcat';
+ case 'pir':
+ return 'en@pirate';
+ default: {
+ // `es-ar` -> `es_ar`
+ const normalized = redditLocale.replace('-', '_');
+ const inx = normalized.indexOf('_');
+ if (inx === -1) {
+ // `zh` -> `zh`
+ return normalized;
+ } else {
+ // `en_au` -> `en_AU`
+ return `${normalized.slice(0, inx)}_${normalized.slice(inx + 1).toUpperCase()}`;
+ }
+ }
+ }
+}
+
+const getLocale = _.memoize(localeName => {
+ const path = localeNameToPath(localeName);
+ if (validLocaleKeys.includes(path)) {
+ return localesContext(path);
+ }
+});
+
+const getLookupFunction = _.memoize(localeName => {
+ const transifexLocale = redditLocaleToTransifexLocale(localeName);
+ const locale = (
+ // 1. Exact match (en_CA -> en_CA)
+ getLocale(transifexLocale) ||
+ // 2. Match without region (en_CA -> en)
+ getLocale(transifexLocale.slice(0, transifexLocale.indexOf('_'))) ||
+ // 3. Default (en)
+ getLocale(DEFAULT_TRANSIFEX_LOCALE)
+ );
+
+ return messageName => {
+ if (locale[messageName]) {
+ return locale[messageName].message;
+ }
+ };
+});
+
+// Behaves like https://developer.chrome.com/extensions/i18n#method-getMessage
+// if it accepted a locale name
+export function getMessage(localeName: string, messageName: string, substitutions: string[]): string {
+ // Transifex will fill in missing translations from partially-translated languages
+ // with strings from the base locale (en).
+ // So we don't need to do multiple checks here.
+ const message = getLookupFunction(localeName)(messageName) || '';
+
+ // Replace direct references to substitutions, e.g. `First substitution: $1`
+ // Maximum of 9 substitutions allowed, i.e. only one number after the `$`
+ return message.replace(/\$(\d)\b(?!\$)/g, (match, number) => substitutions[number - 1] || '');
+
+ // Chrome also supports named placeholders, e.g. `Error: $error_message$`
+ // but Transifex does not create the `placeholders` field in exported JSON
+ // https://developer.chrome.com/extensions/i18n#examples-getMessage
+}
View
@@ -1,5 +1,4 @@
import { createMessageHandler } from '../lib/environment/_messaging';
-import { getMessage } from '../lib/environment/_mockI18n';
import { extendDeep } from '../lib/utils';
const {
@@ -82,5 +81,3 @@ addInterceptor('storage', ([operation, key, value]) => {
throw new Error(`Invalid storage operation: ${operation}`);
}
});
-
-addInterceptor('i18n', ([messageName, substitutions]) => getMessage(messageName, substitutions));
@@ -5,7 +5,6 @@ import _ from 'lodash';
import resCss from '../lib/css/res.scss';
import { createMessageHandler } from '../lib/environment/_messaging';
-import { getMessage } from '../lib/environment/_mockI18n';
import * as Init from '../lib/core/init';
// DOM Collection iteration
@@ -60,5 +59,3 @@ addInterceptor('isURLVisited', () => false);
// Safari has no pageAction
addInterceptor('pageAction', () => {});
-
-addInterceptor('i18n', ([messageName, substitutions]) => getMessage(messageName, substitutions));
View
@@ -0,0 +1,21 @@
+module.exports = {
+ 'reddit language detection': browser => {
+ browser
+ // default (en)
+ .url('https://www.reddit.com/wiki/pages/#res:settings/about')
+ .waitForElementVisible('#RESConsoleContainer', 1000)
+ .assert.containsText('#RESConfigPanelOptions .moduleName', 'About RES')
+
+ // Chinese (zh)
+ .url('https://zh.reddit.com/wiki/pages/#res:settings/about')
+ .waitForElementVisible('#RESConsoleContainer', 1000)
+ .assert.containsText('#RESConfigPanelOptions .moduleName', '关于RES')
+
+ // Polish (pl)
+ .url('https://pl.reddit.com/wiki/pages/#res:settings/about')
+ .waitForElementVisible('#RESConsoleContainer', 1000)
+ .assert.containsText('#RESConfigPanelOptions .moduleName', 'O RES')
+
+ .end();
+ },
+};

0 comments on commit 73f803d

Please sign in to comment.