From 23b329ea50ac902e45009c47a5989c88aba6fe52 Mon Sep 17 00:00:00 2001 From: Andrew Duthie Date: Mon, 16 May 2022 16:54:37 -0400 Subject: [PATCH 1/4] i18n: Detect array keys for Webpack string extraction **Why**: In order for array key support implemented in #6328 to appear as translated, the keys must be extracted, which means we need additional support in the Webpack plugin to detect and extract array keys. changelog: Internal, Localization, Improve browser localization to support array of messages --- .../extract-keys-webpack-plugin.js | 6 ++-- .../extract-keys-webpack-plugin.spec.js | 28 +++++++++++-------- .../spec/fixtures/expected1.en.js | 2 +- .../spec/fixtures/expected1.es.js | 2 +- .../spec/fixtures/expected1.fr.js | 2 +- .../spec/fixtures/in1.js | 1 + .../spec/fixtures/locales/forms/en.yml | 2 ++ .../spec/fixtures/locales/forms/es.yml | 2 ++ .../spec/fixtures/locales/forms/fr.yml | 2 ++ 9 files changed, 31 insertions(+), 16 deletions(-) diff --git a/app/javascript/packages/rails-i18n-webpack-plugin/extract-keys-webpack-plugin.js b/app/javascript/packages/rails-i18n-webpack-plugin/extract-keys-webpack-plugin.js index 83f20f14318..a8ec723da17 100644 --- a/app/javascript/packages/rails-i18n-webpack-plugin/extract-keys-webpack-plugin.js +++ b/app/javascript/packages/rails-i18n-webpack-plugin/extract-keys-webpack-plugin.js @@ -17,7 +17,7 @@ const PLUGIN = 'ExtractKeysWebpackPlugin'; * * @type {RegExp} */ -const TRANSLATE_CALL = /(?:^|[^\w'-])(?:I18n\.)?t\)?\(\s*['"](.+?)['"][,\s)]/g; +const TRANSLATE_CALL = /(?:^|[^\w'-])t\)?\((\[.+?]|['"].+?['"])[, )]/g; /** * Given an original file name and locale, returns a modified file name with the locale injected @@ -42,7 +42,9 @@ function getAdditionalAssetFilename(filename, locale) { * @return {string[]} Translation keys. */ const getTranslationKeys = (source) => - Array.from(source.matchAll(TRANSLATE_CALL)).map(([, key]) => key); + Array.from(source.matchAll(TRANSLATE_CALL)).flatMap(([, keys]) => + keys.split(',').map((key) => key.replace(/[ '"\[\]]/g, '')), + ); /** * Given a file name, returns true if the file is a JavaScript file, or false otherwise. diff --git a/app/javascript/packages/rails-i18n-webpack-plugin/extract-keys-webpack-plugin.spec.js b/app/javascript/packages/rails-i18n-webpack-plugin/extract-keys-webpack-plugin.spec.js index dbeedc1a214..022ec3a6801 100644 --- a/app/javascript/packages/rails-i18n-webpack-plugin/extract-keys-webpack-plugin.spec.js +++ b/app/javascript/packages/rails-i18n-webpack-plugin/extract-keys-webpack-plugin.spec.js @@ -16,24 +16,30 @@ describe('getAdditionalAssetFilename', () => { describe('getTranslationKeys', () => { const source = ` - import { t } from '@18f/identity-i18n'; + const text = t('forms.button.submit'); + const message = t('forms.messages', { count: 2 }); + const values = t(['forms.key1', 'forms.key2']); - const text = t('explicit.call'); - - // i18n-tasks-use t('comment.added.1') - /* i18n-tasks-use t('comment.added.2') */ + // i18n-tasks-use t('item.1') + /* i18n-tasks-use t('item.2') */ /** - * i18n-tasks-use t('comment.added.3') + * i18n-tasks-use t('item.3') */ - Array.from({ length: 3 }, (_, i) => t(\`comment.added.\${i + 1}\`)) + Array.from({ length: 3 }, (_, i) => t(\`item.$\{i + 1}\`)); + // Emulate Babel template literal transpilation + // See: https://babeljs.io/repl#?browsers=ie%2011&code_lz=C4CgBglsCmC2B0ASA3hABAajQRgL5gEog + Array.from({ length: 3 }, (_, i) => t('item.'.concat(i + 1))); `; it('returns keys', () => { expect(getTranslationKeys(source)).to.deep.equal([ - 'explicit.call', - 'comment.added.1', - 'comment.added.2', - 'comment.added.3', + 'forms.button.submit', + 'forms.messages', + 'forms.key1', + 'forms.key2', + 'item.1', + 'item.2', + 'item.3', ]); }); }); diff --git a/app/javascript/packages/rails-i18n-webpack-plugin/spec/fixtures/expected1.en.js b/app/javascript/packages/rails-i18n-webpack-plugin/spec/fixtures/expected1.en.js index 51472608553..efa2763e950 100644 --- a/app/javascript/packages/rails-i18n-webpack-plugin/spec/fixtures/expected1.en.js +++ b/app/javascript/packages/rails-i18n-webpack-plugin/spec/fixtures/expected1.en.js @@ -1 +1 @@ -!function(){var k,o={"forms.button.submit":"Submit","forms.messages":{"one":"One message","other":"%{count} messages"},"item.1":"First","item.2":"Second","item.3":"","forms.button.reset":"Reset"},l=window._locale_data=window._locale_data||{};for(k in o)l[k]=o[k]}() \ No newline at end of file +!function(){var k,o={"forms.button.submit":"Submit","forms.messages":{"one":"One message","other":"%{count} messages"},"forms.key1":"value1-en","forms.key2":"value2-en","item.1":"First","item.2":"Second","item.3":"","forms.button.reset":"Reset"},l=window._locale_data=window._locale_data||{};for(k in o)l[k]=o[k]}() \ No newline at end of file diff --git a/app/javascript/packages/rails-i18n-webpack-plugin/spec/fixtures/expected1.es.js b/app/javascript/packages/rails-i18n-webpack-plugin/spec/fixtures/expected1.es.js index 3ab4db0ac6e..814a80003c2 100644 --- a/app/javascript/packages/rails-i18n-webpack-plugin/spec/fixtures/expected1.es.js +++ b/app/javascript/packages/rails-i18n-webpack-plugin/spec/fixtures/expected1.es.js @@ -1 +1 @@ -!function(){var k,o={"forms.button.submit":"Enviar","forms.messages":{"one":"Un mensaje","other":"%{count} mensajes"},"item.1":"First","item.2":"Second","item.3":"","forms.button.reset":"Reiniciar"},l=window._locale_data=window._locale_data||{};for(k in o)l[k]=o[k]}() \ No newline at end of file +!function(){var k,o={"forms.button.submit":"Enviar","forms.messages":{"one":"Un mensaje","other":"%{count} mensajes"},"forms.key1":"value1-es","forms.key2":"value2-es","item.1":"First","item.2":"Second","item.3":"","forms.button.reset":"Reiniciar"},l=window._locale_data=window._locale_data||{};for(k in o)l[k]=o[k]}() \ No newline at end of file diff --git a/app/javascript/packages/rails-i18n-webpack-plugin/spec/fixtures/expected1.fr.js b/app/javascript/packages/rails-i18n-webpack-plugin/spec/fixtures/expected1.fr.js index 4b0a91f05d9..da9b0654b94 100644 --- a/app/javascript/packages/rails-i18n-webpack-plugin/spec/fixtures/expected1.fr.js +++ b/app/javascript/packages/rails-i18n-webpack-plugin/spec/fixtures/expected1.fr.js @@ -1 +1 @@ -!function(){var k,o={"forms.button.submit":"Submit","forms.messages":{"one":"Un message","other":"%{count} messages"},"item.1":"Premier","item.2":"Second","item.3":"","forms.button.reset":"Réinitialiser"},l=window._locale_data=window._locale_data||{};for(k in o)l[k]=o[k]}() \ No newline at end of file +!function(){var k,o={"forms.button.submit":"Submit","forms.messages":{"one":"Un message","other":"%{count} messages"},"forms.key1":"value1-fr","forms.key2":"value2-fr","item.1":"Premier","item.2":"Second","item.3":"","forms.button.reset":"Réinitialiser"},l=window._locale_data=window._locale_data||{};for(k in o)l[k]=o[k]}() \ No newline at end of file diff --git a/app/javascript/packages/rails-i18n-webpack-plugin/spec/fixtures/in1.js b/app/javascript/packages/rails-i18n-webpack-plugin/spec/fixtures/in1.js index e371fddba6c..37f6055ddcc 100644 --- a/app/javascript/packages/rails-i18n-webpack-plugin/spec/fixtures/in1.js +++ b/app/javascript/packages/rails-i18n-webpack-plugin/spec/fixtures/in1.js @@ -3,6 +3,7 @@ import './resolved'; const text = t('forms.button.submit'); const message = t('forms.messages', { count: 2 }); +const values = t(['forms.key1', 'forms.key2']); // i18n-tasks-use t('item.1') /* i18n-tasks-use t('item.2') */ diff --git a/app/javascript/packages/rails-i18n-webpack-plugin/spec/fixtures/locales/forms/en.yml b/app/javascript/packages/rails-i18n-webpack-plugin/spec/fixtures/locales/forms/en.yml index 65f13755638..63b599722fc 100644 --- a/app/javascript/packages/rails-i18n-webpack-plugin/spec/fixtures/locales/forms/en.yml +++ b/app/javascript/packages/rails-i18n-webpack-plugin/spec/fixtures/locales/forms/en.yml @@ -8,3 +8,5 @@ en: submit: Submit cancel: Cancel reset: Reset + key1: value1-en + key2: value2-en diff --git a/app/javascript/packages/rails-i18n-webpack-plugin/spec/fixtures/locales/forms/es.yml b/app/javascript/packages/rails-i18n-webpack-plugin/spec/fixtures/locales/forms/es.yml index 54a23c35acc..4795074366c 100644 --- a/app/javascript/packages/rails-i18n-webpack-plugin/spec/fixtures/locales/forms/es.yml +++ b/app/javascript/packages/rails-i18n-webpack-plugin/spec/fixtures/locales/forms/es.yml @@ -8,3 +8,5 @@ es: submit: Enviar cancel: Cancelar reset: Reiniciar + key1: value1-es + key2: value2-es diff --git a/app/javascript/packages/rails-i18n-webpack-plugin/spec/fixtures/locales/forms/fr.yml b/app/javascript/packages/rails-i18n-webpack-plugin/spec/fixtures/locales/forms/fr.yml index 2a5bc747cfb..4e0d70d3094 100644 --- a/app/javascript/packages/rails-i18n-webpack-plugin/spec/fixtures/locales/forms/fr.yml +++ b/app/javascript/packages/rails-i18n-webpack-plugin/spec/fixtures/locales/forms/fr.yml @@ -7,3 +7,5 @@ fr: button: cancel: Annuler reset: Réinitialiser + key1: value1-fr + key2: value2-fr From c3c4f63fcdb1f3523354cb5518ce6a2e31a10f78 Mon Sep 17 00:00:00 2001 From: Andrew Duthie Date: Tue, 17 May 2022 08:13:37 -0400 Subject: [PATCH 2/4] Fix lint error --- .../rails-i18n-webpack-plugin/extract-keys-webpack-plugin.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/javascript/packages/rails-i18n-webpack-plugin/extract-keys-webpack-plugin.js b/app/javascript/packages/rails-i18n-webpack-plugin/extract-keys-webpack-plugin.js index a8ec723da17..056f8d66535 100644 --- a/app/javascript/packages/rails-i18n-webpack-plugin/extract-keys-webpack-plugin.js +++ b/app/javascript/packages/rails-i18n-webpack-plugin/extract-keys-webpack-plugin.js @@ -43,7 +43,7 @@ function getAdditionalAssetFilename(filename, locale) { */ const getTranslationKeys = (source) => Array.from(source.matchAll(TRANSLATE_CALL)).flatMap(([, keys]) => - keys.split(',').map((key) => key.replace(/[ '"\[\]]/g, '')), + keys.split(',').map((key) => key.replace(/[ '"[\]]/g, '')), ); /** From 5bc28891e15400c940995e1b05e39bc19a208553 Mon Sep 17 00:00:00 2001 From: Andrew Duthie Date: Tue, 17 May 2022 09:20:47 -0400 Subject: [PATCH 3/4] Fix extra transpilation cases with mid-string interpolation --- .../extract-keys-webpack-plugin.js | 4 ++-- .../extract-keys-webpack-plugin.spec.js | 7 +++++-- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/app/javascript/packages/rails-i18n-webpack-plugin/extract-keys-webpack-plugin.js b/app/javascript/packages/rails-i18n-webpack-plugin/extract-keys-webpack-plugin.js index 056f8d66535..49214620f88 100644 --- a/app/javascript/packages/rails-i18n-webpack-plugin/extract-keys-webpack-plugin.js +++ b/app/javascript/packages/rails-i18n-webpack-plugin/extract-keys-webpack-plugin.js @@ -17,7 +17,7 @@ const PLUGIN = 'ExtractKeysWebpackPlugin'; * * @type {RegExp} */ -const TRANSLATE_CALL = /(?:^|[^\w'-])t\)?\((\[.+?]|['"].+?['"])[, )]/g; +const TRANSLATE_CALL = /(?:^|[^\w'-])t\)?\(\[?(['"][a-z\d\s_.,'"]+['"])]?[,\s)]/g; /** * Given an original file name and locale, returns a modified file name with the locale injected @@ -43,7 +43,7 @@ function getAdditionalAssetFilename(filename, locale) { */ const getTranslationKeys = (source) => Array.from(source.matchAll(TRANSLATE_CALL)).flatMap(([, keys]) => - keys.split(',').map((key) => key.replace(/[ '"[\]]/g, '')), + keys.split(',').map((key) => key.replace(/[ '"]/g, '')), ); /** diff --git a/app/javascript/packages/rails-i18n-webpack-plugin/extract-keys-webpack-plugin.spec.js b/app/javascript/packages/rails-i18n-webpack-plugin/extract-keys-webpack-plugin.spec.js index 022ec3a6801..304a837c7cb 100644 --- a/app/javascript/packages/rails-i18n-webpack-plugin/extract-keys-webpack-plugin.spec.js +++ b/app/javascript/packages/rails-i18n-webpack-plugin/extract-keys-webpack-plugin.spec.js @@ -25,10 +25,13 @@ describe('getTranslationKeys', () => { /** * i18n-tasks-use t('item.3') */ - Array.from({ length: 3 }, (_, i) => t(\`item.$\{i + 1}\`)); + Array.from({ length: 3 }, (_, i) => t(\`item.\${i + 1}\`)); // Emulate Babel template literal transpilation - // See: https://babeljs.io/repl#?browsers=ie%2011&code_lz=C4CgBglsCmC2B0ASA3hABAajQRgL5gEog + // 1. https://babeljs.io/repl#?browsers=ie%2011&code_lz=C4CgBglsCmC2B0ASA3hABAajQRgL5gEog Array.from({ length: 3 }, (_, i) => t('item.'.concat(i + 1))); + // 2. https://babeljs.io/repl#?browsers=ie%2011&code_lz=C4CgBgZg9gTgtgZwHQBIDeBrApgTwL5JQB2WYAlEA + t("forms.".concat(key, ".one")); + t(["forms.".concat(key, ".one")]); `; it('returns keys', () => { From 27c7e1a72cd2578f032fe6874cb3f3c9dce84fdb Mon Sep 17 00:00:00 2001 From: Andrew Duthie Date: Wed, 18 May 2022 08:00:03 -0400 Subject: [PATCH 4/4] Use array syntax for idv forgot password warnings This previously wasn't able to use array syntax because string extraction wasn't implemented, and now it is --- .../verify-flow/steps/password-confirm/forgot-password.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/javascript/packages/verify-flow/steps/password-confirm/forgot-password.tsx b/app/javascript/packages/verify-flow/steps/password-confirm/forgot-password.tsx index 4093b5d26d7..3ad4a245001 100644 --- a/app/javascript/packages/verify-flow/steps/password-confirm/forgot-password.tsx +++ b/app/javascript/packages/verify-flow/steps/password-confirm/forgot-password.tsx @@ -26,7 +26,7 @@ export function ForgotPassword({ goBack }: ForgotPasswordProps) { /> {t('idv.forgot_password.modal_header')}
    - {(t('idv.forgot_password.warnings') as unknown as string[]).map((warning) => ( + {t(['idv.forgot_password.warnings']).map((warning) => (
  • {warning}
  • ))}