Repository: ember-intl/ember-intl (ts)

Issue:
Should "stripEmptyTranslations" also strip actual empty strings?
- [x] I am on the [latest](https://github.com/ember-intl/ember-intl/releases/latest) ember-intl version
- [x] I have searched the [issues](https://github.com/ember-intl/ember-intl/issues) of this repo and believe that this is not a duplicate

### Environment

- Ember Version: **v3.24.0**
- Ember CLI Version: **v3.24.0**
- Ember Intl Version: **v5.6.2**
- Browser(s): /
- Node Version: **v12.20.1**

`stripEmptyTranslations` description in the docs is as follows:
> At build time, removes empty translation keys if their values are falsey.

Reading this (and also based on the config name) I would expect that empty strings (`""`) would be stripped from the final build as well, however this doesn't seem to be the case.

Would it be possible to strip actual empty strings as well or is this not advised/possible?

If you guys think this is okay, I'm willing to create a PR.

If not, can we update the docs to state that only `null` values are stripped to make it more clear?


Pull request description:
fix: strip translations with an empty string as value
Closes #1512.

Patch:
diff --git a/blueprints/ember-intl/files/__config__/ember-intl.js b/blueprints/ember-intl/files/__config__/ember-intl.js
index 2e1bc33b..307ef82b 100644
--- a/blueprints/ember-intl/files/__config__/ember-intl.js
+++ b/blueprints/ember-intl/files/__config__/ember-intl.js
@@ -71,7 +71,7 @@ module.exports = function(/* environment */) {
     errorOnMissingTranslations: false,
 
     /**
-     * removes empty translations from the build output.
+     * Removes empty translations from the build output.
      *
      * @property stripEmptyTranslations
      * @type {Boolean}
diff --git a/lib/broccoli/translation-reducer/index.js b/lib/broccoli/translation-reducer/index.js
index 9aa2c700..d4f96b46 100644
--- a/lib/broccoli/translation-reducer/index.js
+++ b/lib/broccoli/translation-reducer/index.js
@@ -16,7 +16,7 @@ const fs = require('fs');
 const Linter = require('./linter');
 const forEachMessage = require('./utils/for-each-message');
 const validateMessage = require('../../message-validator/validate-message');
-const stripNestedNulls = require('./utils/strip-nested-nulls');
+const stripEmptyTranslations = require('./utils/strip-empty-translations');
 const wrapWithNamespaceIfNeeded = require('./utils/wrap-with-namespace-if-needed');
 const isKnownLanguage = require('./utils/is-known-language');
 const enums = require('../enums');
@@ -129,7 +129,7 @@ class TranslationReducer extends CachingWriter {
 
       // TODO: make the default in 6.0.0
       if (this.options.stripEmptyTranslations === true) {
-        translation = stripNestedNulls(translation);
+        translation = stripEmptyTranslations(translation);
       }
 
       if (!translation) {
diff --git a/lib/broccoli/translation-reducer/utils/strip-empty-translations.js b/lib/broccoli/translation-reducer/utils/strip-empty-translations.js
new file mode 100644
index 00000000..44f0ff60
--- /dev/null
+++ b/lib/broccoli/translation-reducer/utils/strip-empty-translations.js
@@ -0,0 +1,33 @@
+/**
+ * Removes nested translations that are empty recursively (`null` or `""`)
+ *
+ * stripEmptyTranslations({ a: { b: null, c: 'c' }, d: '' }) => { a: { c: 'c' } }
+ *
+ * @method stripEmptyTranslations
+ * @param {Object} object
+ * @param {String} [objectKey]
+ * @param {Object} [objectParent]
+ * @return {Object} Returns the input object
+ * @private
+ */
+function stripEmptyTranslations(object, objectKey, objectParent) {
+  Object.keys(object).forEach((key) => {
+    const keyValue = object[key];
+
+    if (keyValue === null || keyValue === '') {
+      delete object[key];
+
+      const isObjectEmpty = Object.keys(object).length === 0;
+
+      if (isObjectEmpty && objectParent) {
+        delete objectParent[objectKey];
+      }
+    } else if (typeof keyValue === 'object') {
+      stripEmptyTranslations(object[key], key, object);
+    }
+  });
+
+  return object;
+}
+
+module.exports = stripEmptyTranslations;
diff --git a/lib/broccoli/translation-reducer/utils/strip-nested-nulls.js b/lib/broccoli/translation-reducer/utils/strip-nested-nulls.js
deleted file mode 100644
index 41eaa2e7..00000000
--- a/lib/broccoli/translation-reducer/utils/strip-nested-nulls.js
+++ /dev/null
@@ -1,26 +0,0 @@
-/**
- * Removes any nested object field that is null recursively
- *
- * stripNestedNulls({ a: {b: null, c: 'c'}, d: null }) => {a: {c: 'c'}}
- *
- * @method stripNestedNulls
- * @param {Object} object
- * @return {Object} Returns the input object
- * @private
- */
-function stripNestedNulls(object) {
-  Object.keys(object).forEach((key) => {
-    const keyValue = object[key];
-
-    // check for null first as null is also typeof 'object'
-    if (keyValue === null) {
-      delete object[key];
-    } else if (typeof keyValue === 'object') {
-      stripNestedNulls(object[key]);
-    }
-  });
-
-  return object;
-}
-
-module.exports = stripNestedNulls;


Test patch:
diff --git a/tests-node/fixtures/strip-nested-nulls/de-de.yml b/tests-node/fixtures/strip-empty-translations/de-de.yml
similarity index 56%
rename from tests-node/fixtures/strip-nested-nulls/de-de.yml
rename to tests-node/fixtures/strip-empty-translations/de-de.yml
index 4d66bb2b..4eaa5419 100644
--- a/tests-node/fixtures/strip-nested-nulls/de-de.yml
+++ b/tests-node/fixtures/strip-empty-translations/de-de.yml
@@ -1,3 +1,4 @@
 sample_translation:
   translation_1: Lorem ipsum
-  translation_2:
+  translation_2: ""
+  translation_3:
diff --git a/tests-node/fixtures/strip-nested-nulls/en-us.yml b/tests-node/fixtures/strip-empty-translations/en-us.yml
similarity index 100%
rename from tests-node/fixtures/strip-nested-nulls/en-us.yml
rename to tests-node/fixtures/strip-empty-translations/en-us.yml
diff --git a/tests-node/unit/broccoli/translation-reducer/index-test.js b/tests-node/unit/broccoli/translation-reducer/index-test.js
index 8c9ddadb..ebfd41bb 100644
--- a/tests-node/unit/broccoli/translation-reducer/index-test.js
+++ b/tests-node/unit/broccoli/translation-reducer/index-test.js
@@ -363,7 +363,7 @@ describe('translation-reducer', function () {
     });
 
     describe('mergeTranslations', function () {
-      const testDirectory = path.join(__dirname, '../../../fixtures/strip-nested-nulls');
+      const testDirectory = path.join(__dirname, '../../../fixtures/strip-empty-translations');
       const listFiles = ['de-de.yml', 'en-us.yml'].map((file) => path.join(testDirectory, file));
 
       it('strips empty translations if enabled', function () {
@@ -382,7 +382,7 @@ describe('translation-reducer', function () {
         const translations = subject.mergeTranslations(listFiles);
 
         expect(translations).to.deep.equal({
-          'de-de': { sample_translation: { translation_1: 'Lorem ipsum', translation_2: null } },
+          'de-de': { sample_translation: { translation_1: 'Lorem ipsum', translation_2: '', translation_3: null } },
           'en-us': { sample_translation: { translation_1: 'Lorem ipsum', translation_2: 'dolor sit amet' } },
         });
       });
diff --git a/tests-node/unit/broccoli/translation-reducer/strip-nested-nulls-test.js b/tests-node/unit/broccoli/translation-reducer/strip-empty-translations-test.js
similarity index 50%
rename from tests-node/unit/broccoli/translation-reducer/strip-nested-nulls-test.js
rename to tests-node/unit/broccoli/translation-reducer/strip-empty-translations-test.js
index 0db4ed97..72e6e92b 100644
--- a/tests-node/unit/broccoli/translation-reducer/strip-nested-nulls-test.js
+++ b/tests-node/unit/broccoli/translation-reducer/strip-empty-translations-test.js
@@ -1,21 +1,18 @@
 const expect = require('chai').expect;
-const stripNestedNulls = require('../../../../lib/broccoli/translation-reducer/utils/strip-nested-nulls');
+const stripEmptyTranslations = require('../../../../lib/broccoli/translation-reducer/utils/strip-empty-translations');
 
-describe('stripNestedNulls', function () {
+describe('stripEmptyTranslations', function () {
   [
     [{ a: true, b: null }, { a: true }],
-    [{ a: { b: null } }, { a: {} }],
-    [
-      { a: { b: null, c: '' }, d: '' },
-      { a: { c: '' }, d: '' },
-    ],
+    [{ a: { b: null } }, {}],
+    [{ a: { b: null, c: '' }, d: '' }, {}],
     [
       { a: true, b: { c: true, d: { e: null, f: true } } },
       { a: true, b: { c: true, d: { f: true } } },
     ],
   ].forEach(([object, expected]) => {
     it(`${JSON.stringify(object)} -> ${JSON.stringify(expected)}`, function () {
-      expect(stripNestedNulls(object)).to.deep.equal(expected);
+      expect(stripEmptyTranslations(object)).to.deep.equal(expected);
     });
   });
 });
diff --git a/tests/dummy/app/pods/docs/guide/addon-configs/template.md b/tests/dummy/app/pods/docs/guide/addon-configs/template.md
index 5e8cc1c7..5d457994 100644
--- a/tests/dummy/app/pods/docs/guide/addon-configs/template.md
+++ b/tests/dummy/app/pods/docs/guide/addon-configs/template.md
@@ -62,7 +62,7 @@ Cause a build error if missing translations are detected.
 
 ## Strip Empty Translations
 
-At build time, removes empty translation keys if their values are falsey.
+Removes empty translations from the build output.
 
 ```
 // config/ember-intl.js