diff --git a/.circleci/config.yml b/.circleci/config.yml
index 3f59d9481..4529184df 100644
--- a/.circleci/config.yml
+++ b/.circleci/config.yml
@@ -46,6 +46,10 @@ jobs:
name: Lint
command: yarn run lint
+ - run:
+ name: Locale
+ command: yarn run locale:verify
+
- run:
name: Unit tests
command: |
diff --git a/apis/nucleus/src/components/LongRunningQuery.jsx b/apis/nucleus/src/components/LongRunningQuery.jsx
index 5e2da3090..d4062ddd0 100644
--- a/apis/nucleus/src/components/LongRunningQuery.jsx
+++ b/apis/nucleus/src/components/LongRunningQuery.jsx
@@ -1,7 +1,8 @@
/* eslint-disable react/jsx-props-no-spreading */
-import React, { useState } from 'react';
+import React, { useState, useContext } from 'react';
import { makeStyles, Grid, Typography, Button } from '@material-ui/core';
import WarningTriangle from '@nebula.js/ui/icons/warning-triangle-2';
+import LocaleContext from '../contexts/LocaleContext';
import Progress from './Progress';
@@ -22,7 +23,7 @@ const useStyles = makeStyles(() => ({
},
}));
-const Cancel = ({ cancel, ...props }) => (
+const Cancel = ({ cancel, translator, ...props }) => (
<>
@@ -30,19 +31,19 @@ const Cancel = ({ cancel, ...props }) => (
- Updating data
+ {translator.get('Object.Update.Active')}
>
);
-const Retry = ({ retry, ...props }) => (
+const Retry = ({ retry, translator, ...props }) => (
<>
@@ -50,11 +51,12 @@ const Retry = ({ retry, ...props }) => (
Data update was cancelled
+ {translator.get('Object.Update.Cancelled')}
>
@@ -64,6 +66,8 @@ export default function LongRunningQuery({ onCancel, onRetry }) {
const { stripes, cancel, retry } = useStyles();
const [canCancel, setCanCancel] = useState(!!onCancel);
const [canRetry, setCanRetry] = useState(!!onRetry);
+ const translator = useContext(LocaleContext);
+
const handleCancel = () => {
setCanCancel(false);
setCanRetry(true);
@@ -90,8 +94,8 @@ export default function LongRunningQuery({ onCancel, onRetry }) {
}}
spacing={2}
>
- {canCancel && }
- {canRetry && }
+ {canCancel && }
+ {canRetry && }
);
}
diff --git a/apis/nucleus/src/components/selections/OneField.jsx b/apis/nucleus/src/components/selections/OneField.jsx
index e120d6db9..d6894ffd6 100644
--- a/apis/nucleus/src/components/selections/OneField.jsx
+++ b/apis/nucleus/src/components/selections/OneField.jsx
@@ -61,8 +61,6 @@ export default function OneField({ field, api, stateIx = 0, skipHandleShowListBo
label = translator.get('CurrentSelections.All');
} else if (numSelected > 1 && selection.qTotal) {
label = translator.get('CurrentSelections.Of', [numSelected, selection.qTotal]);
- } else if (noSegments) {
- label = translator.get('CurrentSelections.None');
} else {
label = selection.qSelectedFieldSelectionInfo.map(v => v.qName).join(', ');
}
diff --git a/apis/nucleus/src/components/selections/__tests__/one-field.spec.jsx b/apis/nucleus/src/components/selections/__tests__/one-field.spec.jsx
index cec2a3f46..71b8cdb34 100644
--- a/apis/nucleus/src/components/selections/__tests__/one-field.spec.jsx
+++ b/apis/nucleus/src/components/selections/__tests__/one-field.spec.jsx
@@ -91,6 +91,7 @@ describe('', () => {
selections: [
{
qField: 'my-field',
+ qSelectedFieldSelectionInfo: [],
},
],
states: ['$'],
@@ -111,6 +112,7 @@ describe('', () => {
{
qField: 'my-field',
qLocked: true,
+ qSelectedFieldSelectionInfo: [],
},
],
states: ['$'],
diff --git a/apis/nucleus/src/locale/app-locale.js b/apis/nucleus/src/locale/app-locale.js
index 2cba7be85..4915ecc4e 100644
--- a/apis/nucleus/src/locale/app-locale.js
+++ b/apis/nucleus/src/locale/app-locale.js
@@ -1,18 +1,13 @@
import localeFn from '@nebula.js/locale';
-import en from './translations/en-US';
+import all from './translations/all.json';
export default function appLocaleFn({ language }) {
const l = localeFn({
initial: language,
});
- Object.keys(en).forEach(id => {
- l.translator.add({
- id,
- locale: {
- 'en-US': en[id],
- },
- });
+ Object.keys(all).forEach(key => {
+ l.translator.add(all[key]);
});
return {
diff --git a/apis/nucleus/src/locale/translations/all.json b/apis/nucleus/src/locale/translations/all.json
new file mode 100644
index 000000000..6d063fbe9
--- /dev/null
+++ b/apis/nucleus/src/locale/translations/all.json
@@ -0,0 +1,394 @@
+{
+ "Object_Update_Active": {
+ "id": "Object.Update.Active",
+ "locale": {
+ "en-US": "Updating data"
+ }
+ },
+ "Object_Update_Cancelled": {
+ "id": "Object.Update.Cancelled",
+ "locale": {
+ "en-US": "Data update was cancelled"
+ }
+ },
+ "Supernova_Incomplete": {
+ "id": "Supernova.Incomplete",
+ "locale": {
+ "en-US": "Incomplete visualization",
+ "it-IT": "Visualizzazione incompleta",
+ "zh-CN": "不完整的可视化",
+ "zh-TW": "視覺化未完成",
+ "ko-KR": "완료되지 않은 시각화",
+ "de-DE": "Unvollständige Visualisierung",
+ "sv-SE": "Ofullständig visualisering",
+ "es-ES": "Visualización incompleta",
+ "pt-BR": "Visualização incompleta",
+ "ja-JP": "未完了のビジュアライゼーション",
+ "fr-FR": "Visualisation incomplète",
+ "nl-NL": "Onvolledige visualisatie",
+ "tr-TR": "Tamamlanmamış görselleştirme",
+ "pl-PL": "Niekompletna wizualizacja",
+ "ru-RU": "Незавершенная визуализация"
+ }
+ },
+ "Cancel": {
+ "id": "Common.Cancel",
+ "locale": {
+ "en-US": "Cancel",
+ "de-DE": "Abbrechen",
+ "es-ES": "Cancelar",
+ "fr-FR": "Annuler",
+ "ja-JP": "キャンセル",
+ "nl-NL": "Annuleren",
+ "it-IT": "Annulla",
+ "ko-KR": "취소",
+ "pl-PL": "Anuluj",
+ "ru-RU": "Отмена",
+ "pt-BR": "Cancelar",
+ "sv-SE": "Avbryt",
+ "zh-CN": "取消",
+ "tr-TR": "İptal",
+ "zh-TW": "取消"
+ }
+ },
+ "OK": {
+ "id": "Common.OK",
+ "locale": {
+ "en-US": "OK",
+ "de-DE": "OK",
+ "es-ES": "Aceptar",
+ "fr-FR": "OK",
+ "ja-JP": "OK",
+ "nl-NL": "OK",
+ "it-IT": "OK",
+ "ko-KR": "확인",
+ "pl-PL": "OK",
+ "ru-RU": "ОК",
+ "pt-BR": "OK",
+ "sv-SE": "OK",
+ "zh-CN": "确定",
+ "tr-TR": "Tamam",
+ "zh-TW": "確定"
+ }
+ },
+ "Retry": {
+ "id": "Common.Retry",
+ "locale": {
+ "en-US": "Retry",
+ "it-IT": "Riprova",
+ "zh-CN": "重试",
+ "zh-TW": "重試",
+ "ko-KR": "다시 시도",
+ "de-DE": "Wiederholen",
+ "sv-SE": "Försök igen",
+ "es-ES": "Intentar de nuevo",
+ "pt-BR": "Tentar Novamente",
+ "ja-JP": "再試行",
+ "fr-FR": "Réessayer",
+ "nl-NL": "Opnieuw",
+ "tr-TR": "Yeniden dene",
+ "pl-PL": "Ponów próbę",
+ "ru-RU": "Повторить попытку"
+ }
+ },
+ "CurrentSelections_All": {
+ "id": "CurrentSelections.All",
+ "locale": {
+ "en-US": "ALL",
+ "it-IT": "TUTTO",
+ "zh-CN": "全部",
+ "zh-TW": "全部",
+ "ko-KR": "모두",
+ "de-DE": "ALLES",
+ "sv-SE": "ALLA",
+ "es-ES": "TODOS",
+ "pt-BR": "TODOS",
+ "ja-JP": "すべて",
+ "fr-FR": "TOUS",
+ "nl-NL": "ALLE",
+ "tr-TR": "TÜMÜ",
+ "pl-PL": "WSZYSTKO",
+ "ru-RU": "ВСЕ"
+ }
+ },
+ "CurrentSelections_Of": {
+ "id": "CurrentSelections.Of",
+ "locale": {
+ "en-US": "{0} of {1}",
+ "it-IT": "{0} di {1}",
+ "zh-CN": "{0}/{1}",
+ "zh-TW": "{0} / {1}",
+ "ko-KR": "{0} / {1}",
+ "de-DE": "{0} von {1}",
+ "sv-SE": "{0} av {1}",
+ "es-ES": "{0} de {1}",
+ "pt-BR": "{0} de {1}",
+ "ja-JP": "{0} / {1}",
+ "fr-FR": "{0} sur {1}",
+ "nl-NL": "{0} van {1}",
+ "tr-TR": "{0} / {1}",
+ "pl-PL": "{0} z {1}",
+ "ru-RU": "{0} из {1}"
+ }
+ },
+ "Listbox_Search": {
+ "id": "Listbox.Search",
+ "locale": {
+ "en-US": "Search in listbox",
+ "it-IT": "Cerca nella casella di elenco",
+ "zh-CN": "在列表框中搜索",
+ "zh-TW": "在清單方塊中搜尋",
+ "ko-KR": "목록 상자에서 검색",
+ "de-DE": "In Listenfeld suchen",
+ "sv-SE": "Sök i listruta",
+ "es-ES": "Buscar en cuadro de lista",
+ "pt-BR": "Pesquisar na caixa de listagem",
+ "ja-JP": "リストボックス内を検索",
+ "fr-FR": "Rechercher dans la liste de sélection",
+ "nl-NL": "Zoeken in keuzelijst",
+ "tr-TR": "Liste kutusunda ara",
+ "pl-PL": "Wyszukaj w liście wartości",
+ "ru-RU": "Поиск в списке"
+ }
+ },
+ "Navigate_Forward": {
+ "id": "Navigate.Forward",
+ "locale": {
+ "en-US": "Step forward",
+ "it-IT": "Vai avanti",
+ "zh-CN": "前进",
+ "zh-TW": "前進",
+ "ko-KR": "다음 단계",
+ "de-DE": "Schritt vor",
+ "sv-SE": "Gå framåt",
+ "es-ES": "Avanzar",
+ "pt-BR": "Avançar uma etapa",
+ "ja-JP": "1段階進む",
+ "fr-FR": "Étape suivante",
+ "nl-NL": "Stap vooruit",
+ "tr-TR": "Bir adım ileri",
+ "pl-PL": "Krok do przodu",
+ "ru-RU": "Шаг вперед"
+ }
+ },
+ "Navigate_Back": {
+ "id": "Navigate.Back",
+ "locale": {
+ "en-US": "Step back",
+ "it-IT": "Torna indietro",
+ "zh-CN": "后退",
+ "zh-TW": "倒退",
+ "ko-KR": "이전 단계",
+ "de-DE": "Schritt zurück",
+ "sv-SE": "Gå bakåt",
+ "es-ES": "Retroceder",
+ "pt-BR": "Voltar uma etapa",
+ "ja-JP": "1 段階戻る",
+ "fr-FR": "Retour en arrière",
+ "nl-NL": "Stap terug",
+ "tr-TR": "Bir adım geri",
+ "pl-PL": "Krok do tyłu",
+ "ru-RU": "Шаг назад"
+ }
+ },
+ "Selection_ClearAll": {
+ "id": "Selection.ClearAll",
+ "locale": {
+ "en-US": "Clear all selections",
+ "it-IT": "Cancella tutte le selezioni",
+ "zh-CN": "清除所有选择项",
+ "zh-TW": "清除所有選項",
+ "ko-KR": "모든 선택 해제",
+ "de-DE": "Auswahl aufheben (alle Felder)",
+ "sv-SE": "Rensa alla urval",
+ "es-ES": "Borrar todas las selecciones",
+ "pt-BR": "Limpar todas as seleções",
+ "ja-JP": "選択をすべてクリアする",
+ "fr-FR": "Effacer toutes les sélections",
+ "nl-NL": "Alle selecties wissen",
+ "tr-TR": "Tüm seçimleri temizle",
+ "pl-PL": "Wyczyść wszystkie selekcje",
+ "ru-RU": "Очистить от всех выборок"
+ }
+ },
+ "Selection_ClearAllStates": {
+ "id": "Selection.ClearAllStates",
+ "locale": {
+ "en-US": "Clear all states",
+ "it-IT": "Cancella tutti gli stati",
+ "zh-CN": "清除所有状态",
+ "zh-TW": "清除所有狀態",
+ "ko-KR": "모든 상태 지우기",
+ "de-DE": "Alle Status löschen",
+ "sv-SE": "Rensa alla tillstånd",
+ "es-ES": "Borrar todos los estados",
+ "pt-BR": "Limpar todos os estados",
+ "ja-JP": "全ステートをクリア",
+ "fr-FR": "Effacer tous les états",
+ "nl-NL": "Alle states wissen",
+ "tr-TR": "Tüm durumları temizle",
+ "pl-PL": "Wyczyść wszystkie stany",
+ "ru-RU": "Очистить все состояния"
+ }
+ },
+ "Selection_Confirm": {
+ "id": "Selection.Confirm",
+ "locale": {
+ "en-US": "Confirm selection",
+ "it-IT": "Conferma selezione",
+ "zh-CN": "确认选择",
+ "zh-TW": "確認選取",
+ "ko-KR": "선택 확인",
+ "de-DE": "Auswahl bestätigen",
+ "sv-SE": "Bekräfta urval",
+ "es-ES": "Confirmar selección",
+ "pt-BR": "Confirmar seleção",
+ "ja-JP": "選択の確認",
+ "fr-FR": "Confirmer la sélection",
+ "nl-NL": "Selectie bevestigen",
+ "tr-TR": "Seçimi onayla",
+ "pl-PL": "Potwierdź selekcję",
+ "ru-RU": "Подтвердить выборку"
+ }
+ },
+ "Selection_Cancel": {
+ "id": "Selection.Cancel",
+ "locale": {
+ "en-US": "Cancel selection",
+ "it-IT": "Annulla selezione",
+ "zh-CN": "取消选择",
+ "zh-TW": "取消選取",
+ "ko-KR": "선택 취소",
+ "de-DE": "Auswahl abbrechen",
+ "sv-SE": "Avbryt urval",
+ "es-ES": "Cancelar selección",
+ "pt-BR": "Cancelar seleção",
+ "ja-JP": "選択のキャンセル",
+ "fr-FR": "Annuler la sélection",
+ "nl-NL": "Selectie annuleren",
+ "tr-TR": "Seçimi iptal et",
+ "pl-PL": "Anuluj selekcję",
+ "ru-RU": "Отменить выборку"
+ }
+ },
+ "Selection_Clear": {
+ "id": "Selection.Clear",
+ "locale": {
+ "en-US": "Clear selection",
+ "it-IT": "Cancella selezione",
+ "zh-CN": "清除选择",
+ "zh-TW": "清除選項",
+ "ko-KR": "선택 해제",
+ "de-DE": "Auswahl löschen",
+ "sv-SE": "Rensa urval",
+ "es-ES": "Borrar selección",
+ "pt-BR": "Limpar seleção",
+ "ja-JP": "選択をクリア",
+ "fr-FR": "Effacer la sélection",
+ "nl-NL": "Selectie wissen",
+ "tr-TR": "Seçimi temizle",
+ "pl-PL": "Wyczyść selekcję",
+ "ru-RU": "Очистить выбор"
+ }
+ },
+ "Selection_SelectAll": {
+ "id": "Selection.SelectAll",
+ "locale": {
+ "en-US": "Select all",
+ "it-IT": "Seleziona tutto",
+ "zh-CN": "全选",
+ "zh-TW": "全選",
+ "ko-KR": "모두 선택",
+ "de-DE": "Alle Werte auswählen",
+ "sv-SE": "Markera alla",
+ "es-ES": "Seleccionar todo",
+ "pt-BR": "Selecionar todos",
+ "ja-JP": "すべて選択",
+ "fr-FR": "Sélectionner tout",
+ "nl-NL": "Alles selecteren",
+ "tr-TR": "Tümünü seç",
+ "pl-PL": "Wybierz wszystko",
+ "ru-RU": "Выбрать все"
+ }
+ },
+ "Selection_SelectAlternative": {
+ "id": "Selection.SelectAlternative",
+ "locale": {
+ "en-US": "Select alternative",
+ "it-IT": "Seleziona alternativi",
+ "zh-CN": "选择替代项",
+ "zh-TW": "選取替代選項",
+ "ko-KR": "대안 선택",
+ "de-DE": "Alternative Werte auswählen",
+ "sv-SE": "Välj alternativ",
+ "es-ES": "Seleccionar alternativos",
+ "pt-BR": "Selecionar alternativa",
+ "ja-JP": "代替値を選択",
+ "fr-FR": "Sélectionner des valeurs alternatives",
+ "nl-NL": "Alternatief selecteren",
+ "tr-TR": "Alternatifi seç",
+ "pl-PL": "Wybierz alternatywę",
+ "ru-RU": "Выбрать альтернативные"
+ }
+ },
+ "Selection_SelectExcluded": {
+ "id": "Selection.SelectExcluded",
+ "locale": {
+ "en-US": "Select excluded",
+ "it-IT": "Seleziona esclusi",
+ "zh-CN": "选择排除项",
+ "zh-TW": "選取排除值",
+ "ko-KR": "제외 항목 선택",
+ "de-DE": "Ausgeschlossene Werte auswählen",
+ "sv-SE": "Välj uteslutna",
+ "es-ES": "Seleccionar excluidos",
+ "pt-BR": "Selecionar excluído",
+ "ja-JP": "除外値を選択",
+ "fr-FR": "Sélectionner les valeurs exclues",
+ "nl-NL": "Uitgesloten selecteren",
+ "tr-TR": "Hariç tutulanı seç",
+ "pl-PL": "Wybierz wykluczone",
+ "ru-RU": "Выбрать исключенные"
+ }
+ },
+ "Selection_SelectPossible": {
+ "id": "Selection.SelectPossible",
+ "locale": {
+ "en-US": "Select possible",
+ "it-IT": "Seleziona possibili",
+ "zh-CN": "选择可能值",
+ "zh-TW": "選取可能值",
+ "ko-KR": "사용 가능 항목 선택",
+ "de-DE": "Wählbare Werte auswählen",
+ "sv-SE": "Välj möjliga",
+ "es-ES": "Seleccionar posibles",
+ "pt-BR": "Selecionar possível",
+ "ja-JP": "絞込値を選択",
+ "fr-FR": "Sélectionner les valeurs possibles",
+ "nl-NL": "Mogelijke selecteren",
+ "tr-TR": "Olasıyı seç",
+ "pl-PL": "Wybierz możliwe",
+ "ru-RU": "Выбрать возможные"
+ }
+ },
+ "Selection_Menu": {
+ "id": "Selection.Menu",
+ "locale": {
+ "en-US": "Selection menu",
+ "it-IT": "Menu Selezione",
+ "zh-CN": "选择菜单",
+ "zh-TW": "選項功能表",
+ "ko-KR": "선택 메뉴",
+ "de-DE": "Auswahlmenü",
+ "sv-SE": "Urvalsmeny",
+ "es-ES": "Menú de selección",
+ "pt-BR": "Menu de seleção",
+ "ja-JP": "選択メニュー",
+ "fr-FR": "Menu Sélection",
+ "nl-NL": "Selectiemenu",
+ "tr-TR": "Seçim menüsü",
+ "pl-PL": "Menu selekcji",
+ "ru-RU": "Меню \"Выборка\""
+ }
+ }
+}
diff --git a/apis/nucleus/src/locale/translations/en-US.js b/apis/nucleus/src/locale/translations/en-US.js
deleted file mode 100644
index c7817adf2..000000000
--- a/apis/nucleus/src/locale/translations/en-US.js
+++ /dev/null
@@ -1,22 +0,0 @@
-export default {
- 'Common.Cancel': 'Cancel',
- 'Common.OK': 'Ok',
- 'Common.English': 'English',
- 'CurrentSelections.All': 'ALL',
- 'CurrentSelections.Of': '{0} of {1}',
- 'CurrentSelections.None': 'NONE',
- 'Listbox.Search': 'Search in listbox',
- 'Navigate.Forward': 'Step forward',
- 'Navigate.Back': 'Step back',
- 'Selection.ClearAll': 'Clear all selections',
- 'Selection.ClearAllStates': 'Clear all states',
- 'Selection.Confirm': 'Confirm selection',
- 'Selection.Cancel': 'Cancel selection',
- 'Selection.Clear': 'Clear selection',
- 'Selection.SelectAll': 'Select all',
- 'Selection.SelectAlternative': 'Select alternative',
- 'Selection.SelectExcluded': 'Select excluded',
- 'Selection.SelectPossible': 'Select possible',
- 'Selection.Menu': 'Selection menu',
- 'Supernova.Incomplete': 'Incomplete visualization',
-};
diff --git a/package.json b/package.json
index dfae388ce..73fa22f8b 100644
--- a/package.json
+++ b/package.json
@@ -5,6 +5,7 @@
"build": "cross-env NODE_ENV=production FORCE_COLOR=1 lerna run build --stream",
"build:codesandbox": "cross-env NODE_ENV=production CODESANDBOX=1 FORCE_COLOR=1 lerna run build --stream --scope \"@nebula.js/{nucleus,supernova,theme}\"",
"build:watch": "FORCE_COLOR=1 lerna run build:watch --stream --concurrency 99 --no-sort",
+ "locale:verify": "node tools/verify-translations.js",
"lint": "eslint packages apis commands --ext .js,.jsx",
"lint:check": "eslint --print-config ./aw.config.js | eslint-config-prettier-check",
"start": "MONO=true ./commands/cli/lib/index.js serve --entry ./test/integration/sn.js",
@@ -33,6 +34,7 @@
"@after-work.js/aw": "6.0.10",
"@babel/cli": "7.7.4",
"@babel/core": "7.7.4",
+ "@babel/helper-plugin-utils": "^7.0.0",
"@babel/plugin-transform-react-jsx": "7.7.4",
"@babel/preset-env": "7.7.4",
"@babel/preset-react": "7.7.4",
diff --git a/rollup.config.js b/rollup.config.js
index c9080aaa0..47c416803 100644
--- a/rollup.config.js
+++ b/rollup.config.js
@@ -6,6 +6,8 @@ const replace = require('rollup-plugin-replace');
const json = require('rollup-plugin-json');
const { terser } = require('rollup-plugin-terser');
+const localeStringValidator = require('./tools/locale-string-validator');
+
const cwd = process.cwd();
const pkg = require(path.join(cwd, 'package.json')); // eslint-disable-line
const { name, version, license } = pkg;
@@ -150,7 +152,7 @@ const config = isEsm => {
},
],
],
- plugins: [['@babel/plugin-transform-react-jsx']],
+ plugins: [['@babel/plugin-transform-react-jsx'], [localeStringValidator, {}]],
}),
],
};
diff --git a/tools/locale-string-validator.js b/tools/locale-string-validator.js
new file mode 100644
index 000000000..c459ce691
--- /dev/null
+++ b/tools/locale-string-validator.js
@@ -0,0 +1,56 @@
+const { declare } = require('@babel/helper-plugin-utils');
+
+const vars = require('../apis/nucleus/src/locale/translations/all.json');
+
+const ids = {};
+Object.keys(vars).forEach(key => {
+ ids[vars[key].id] = key;
+});
+
+const used = [];
+const warnings = {};
+
+const warn = s => console.warn(`\x1b[43m\x1b[30m WARN \x1b[0m \x1b[1m\x1b[33m ${s}\x1b[0m`);
+
+const find = declare((/* api, options */) => {
+ function useString(id) {
+ if (used.indexOf(id) !== -1) {
+ return;
+ }
+
+ used.push(id);
+
+ if (typeof ids[id] === 'undefined') {
+ warn(`String '${id}' does not exist in locale registry`);
+ }
+ }
+ return {
+ name: 'find',
+ visitor: {
+ CallExpression(path) {
+ if (!path.get('callee').isMemberExpression()) {
+ return;
+ }
+ if (
+ path.node.callee.object &&
+ path.node.callee.object.name === 'translator' &&
+ path.node.callee.property &&
+ path.node.callee.property.name === 'get'
+ ) {
+ const { type, value } = path.node.arguments[0];
+ if (type === 'StringLiteral') {
+ useString(value, path);
+ } else {
+ const s = `${this.file.opts.filename}:${path.node.loc.start.line}:${path.node.loc.start.column}`;
+ if (!warnings[s]) {
+ warnings[s] = true;
+ warn(`Could not verify used string at ${s}`);
+ }
+ }
+ }
+ },
+ },
+ };
+});
+
+module.exports = find;
diff --git a/tools/verify-translations.js b/tools/verify-translations.js
new file mode 100644
index 000000000..66db0f98c
--- /dev/null
+++ b/tools/verify-translations.js
@@ -0,0 +1,32 @@
+const vars = require('../apis/nucleus/src/locale/translations/all.json');
+
+const languages = [
+ 'en-US',
+ 'it-IT',
+ 'zh-CN',
+ 'zh-TW',
+ 'ko-KR',
+ 'de-DE',
+ 'sv-SE',
+ 'es-ES',
+ 'pt-BR',
+ 'ja-JP',
+ 'fr-FR',
+ 'nl-NL',
+ 'tr-TR',
+ 'pl-PL',
+ 'ru-RU',
+];
+
+Object.keys(vars).forEach(key => {
+ const supportLanguagesForString = Object.keys(vars[key].locale);
+ if (supportLanguagesForString.indexOf('en-US') === -1) {
+ // en-US must exist
+ throw new Error(`String '${vars[key].id}' is missing value for 'en-US'`);
+ }
+ for (let i = 0; i < languages.length; i++) {
+ if (supportLanguagesForString.indexOf(languages[i]) === -1) {
+ console.warn(`String '${vars[key].id}' is missing value for '${languages[i]}'`);
+ }
+ }
+});