Repository: ember-intl/ember-intl
Pull request #1341 (2020-05-29 10:14:39)

feat: filter locales options
hi, @jasonmit , i see the `locales` option is OBSOLETE_OPTIONS in v5 .
at first, i think we can keep using it just for this filter feature,
util i see loading translations asynchronously which used old `locales` .

so, i add two optional options do this: `includeLocales` `excludeLocales`.

related issue: https://github.com/ember-intl/ember-intl/issues/1337


houfeng0923: @jasonmit, i review `broccoli-caching-writer` lib, and push a new commit for better filter and cache .
and the problem of case insensitive is resolved now.


Patch:
diff --git a/index.js b/index.js
index 8bc98f79..7bba605c 100644
--- a/index.js
+++ b/index.js
@@ -53,6 +53,8 @@ module.exports = {
     const {
       outputPath,
       fallbackLocale,
+      includeLocales,
+      excludeLocales,
       requiresTranslation,
       errorOnMissingTranslations,
       errorOnNamedArgumentMismatch,
@@ -68,6 +70,8 @@ module.exports = {
 
     return new TranslationReducer([translationTree], {
       fallbackLocale,
+      includeLocales,
+      excludeLocales,
       requiresTranslation,
       errorOnMissingTranslations,
       errorOnNamedArgumentMismatch,
diff --git a/lib/broccoli/translation-reducer/index.js b/lib/broccoli/translation-reducer/index.js
index 76e1d773..9aa2c700 100644
--- a/lib/broccoli/translation-reducer/index.js
+++ b/lib/broccoli/translation-reducer/index.js
@@ -42,14 +42,27 @@ function normalizeLocale(locale) {
   return locale;
 }
 
+function filterPatterns(locales) {
+  if (Array.isArray(locales)) {
+    return locales.map((locale) => new RegExp(`${normalizeLocale(locale)}.(json|yaml|yml)$`, 'i'));
+  }
+
+  return null;
+}
+
 class TranslationReducer extends CachingWriter {
   constructor(inputNode, options) {
     if (!Array.isArray(inputNode)) {
       inputNode = [inputNode];
     }
 
+    const cacheInclude = filterPatterns(options && options.includeLocales);
+    const cacheExclude = filterPatterns(options && options.excludeLocales);
+
     super(inputNode, {
       annotation: 'Translation Reducer',
+      cacheInclude,
+      cacheExclude,
     });
 
     this.options = {
diff --git a/lib/default-config.js b/lib/default-config.js
index 212459f9..46ee7e2a 100644
--- a/lib/default-config.js
+++ b/lib/default-config.js
@@ -1,6 +1,8 @@
 module.exports = {
   publicOnly: false,
   fallbackLocale: null,
+  includeLocales: null,
+  excludeLocales: null,
   inputPath: 'translations',
   outputPath: 'translations',
   errorOnMissingTranslations: false,


Test patch:
diff --git a/tests-node/unit/broccoli/translation-reducer/index-test.js b/tests-node/unit/broccoli/translation-reducer/index-test.js
index 1674286d..8c9ddadb 100644
--- a/tests-node/unit/broccoli/translation-reducer/index-test.js
+++ b/tests-node/unit/broccoli/translation-reducer/index-test.js
@@ -135,6 +135,103 @@ describe('translation-reducer', function () {
     });
   });
 
+  describe('includeLocales', function () {
+    it('should only include specified locales', function () {
+      const subject = new TranslationReducer(this.input.path(), {
+        includeLocales: ['en-us'],
+        errorOnMissingTranslations: true,
+      });
+
+      this.input.write({
+        'de-de.json': `{ "greet": "hallo" }`,
+        'en-us.json': `{ "greet": "hello" }`,
+      });
+
+      return build(createBuilder(subject), (fs) => {
+        expect(fs).to.deep.equal({
+          'en-us.json': `{"greet":"hello"}`,
+        });
+      });
+    });
+
+    it('should be case-insensitive for includeLocales', function () {
+      const subject = new TranslationReducer(this.input.path(), {
+        includeLocales: ['en-us', 'zh-CN'],
+        errorOnMissingTranslations: true,
+      });
+
+      this.input.write({
+        'de-DE.json': `{ "greet": "hallo" }`,
+        'en-US.json': `{ "greet": "hello" }`,
+        'zh-CN.json': `{ "greet": "你好" }`,
+      });
+
+      return build(createBuilder(subject), (fs) => {
+        expect(fs).to.deep.equal({
+          'en-us.json': `{"greet":"hello"}`,
+          'zh-cn.json': `{"greet":"你好"}`,
+        });
+      });
+    });
+  });
+
+  describe('excludeLocales', function () {
+    it('should exclude specified locales', function () {
+      const subject = new TranslationReducer(this.input.path(), {
+        excludeLocales: ['en-us'],
+        errorOnMissingTranslations: true,
+      });
+
+      this.input.write({
+        'de-de.json': `{ "greet": "hallo" }`,
+        'en-us.json': `{ "greet": "hello" }`,
+      });
+
+      return build(createBuilder(subject), (fs) => {
+        expect(fs).to.deep.equal({
+          'de-de.json': `{"greet":"hallo"}`,
+        });
+      });
+    });
+
+    it('should be case-insensitive for excludeLocales', function () {
+      const subject = new TranslationReducer(this.input.path(), {
+        excludeLocales: ['en-us', 'zh-CN'],
+        errorOnMissingTranslations: true,
+      });
+
+      this.input.write({
+        'de-DE.json': `{ "greet": "hallo" }`,
+        'en-US.json': `{ "greet": "hello" }`,
+        'zh-cn.json': `{ "greet": "你好" }`,
+      });
+
+      return build(createBuilder(subject), (fs) => {
+        expect(fs).to.deep.equal({
+          'de-de.json': `{"greet":"hallo"}`,
+        });
+      });
+    });
+
+    it('should ignore includeLocales when excludeLocales exist', function () {
+      const subject = new TranslationReducer(this.input.path(), {
+        includeLocales: ['en-us'],
+        excludeLocales: ['en-us', 'zh-cn'],
+        errorOnMissingTranslations: true,
+      });
+
+      this.input.write({
+        'de-de.json': `{ "greet": "hallo" }`,
+        'en-us.json': `{ "greet": "hello" }`,
+        'zh-cn.json': `{ "greet": "你好" }`,
+      });
+
+      return build(createBuilder(subject), (fs) => {
+        expect(fs).to.deep.equal({});
+      });
+    });
+  });
+
   describe('linting', function () {
     beforeEach(function () {
       this.icuFixture = {
diff --git a/tests/dummy/app/pods/docs/cookbook/addon-configs/template.md b/tests/dummy/app/pods/docs/cookbook/addon-configs/template.md
new file mode 100644
index 00000000..09c6db8b
--- /dev/null
+++ b/tests/dummy/app/pods/docs/cookbook/addon-configs/template.md
@@ -0,0 +1,18 @@
+
+# Addon Configs
+
+## Filter Locales
+
+If you need to distribute applications separately in different languages, now we can use `includeLocales` or `excludeLocales` configuration options.
+
+```
+// config/ember-intl.js
+{
+  includeLocales: ['en-us', 'zh-cn'],
+  ...
+}
+```
+
+*Note, If you set both `includeLocales` and `excludeLocales` options, the `excludeLocales`  wins!*
+
+
diff --git a/tests/dummy/app/pods/docs/template.hbs b/tests/dummy/app/pods/docs/template.hbs
index 7e4ce882..d8931ea8 100644
--- a/tests/dummy/app/pods/docs/template.hbs
+++ b/tests/dummy/app/pods/docs/template.hbs
@@ -23,6 +23,7 @@
     {{nav.item 'format-relative' 'docs.helpers.format-relative'}}
 
     {{nav.section 'Cookbook'}}
+    {{nav.item 'Addon configs' 'docs.cookbook.addon-configs'}}
     {{nav.item 'Common errors' 'docs.cookbook.common-errors'}}
 
     {{nav.section 'Advanced'}}
diff --git a/tests/dummy/app/router.js b/tests/dummy/app/router.js
index d54693eb..d8a8cce4 100644
--- a/tests/dummy/app/router.js
+++ b/tests/dummy/app/router.js
@@ -30,6 +30,7 @@ Router.map(function () {
       this.route('format-time');
     });
     this.route('cookbook', function () {
+      this.route('addon-configs');
       this.route('common-errors');
     });
     this.route('advanced', function () {