ngx-translate / core

The internationalization (i18n) library for Angular
MIT License
4.53k stars 578 forks source link

Translation keys are ignored when dot format and nested format are mixed #1456

Open marianschiller93 opened 1 year ago

marianschiller93 commented 1 year ago

Current behavior

If the dot format and the nested format are mixed, a translation key in dot format is ignored if there is a nested block with a prefix of the translation key.

In this example

{
    "i18n.fruits.apple": "Apfel",
    "i18n.fruits.lemon": "Zitrone",
    "i18n.fruits.lime": "Limette",
    "i18n.fruits.yellowWatermelon": "Gelbe Wassermelone",
    "i18n.fruits.pomegranate": "Granatapfel",
    "i18n.fruits.orange": "Orange",
    "i18n.fruits.strawberry": "Erdbeere",
    "i18n.fruits": {
        "cherry": "Kirsche"
    }
}

the nested block

"i18n.fruits": {
    "cherry": "Kirsche"
}

swallows all "i18n.fruits" translations. Only the key "i18n.fruits.cherry" is translated.

Expected behavior

If the dot format and the nested format are combined, the translation keys should be merged.

How do you think that we should fix this?

In the getValue method of the TranslateDefaultParser the target variable is set to the nested object whose key is a prefix of the translation key. Thus the keys of the previous target (initially the complete json file) are ignored. One option here would be to merge the matching keys of the previous target.

target = {
  ...target[key],
  ...Object.fromEntries(
    Object.entries(target)
      .filter(([k]) => k.startsWith(key + '.'))
      .map(([k, v]) => [k.slice(key.length + 1), v])
  )
};

Here is a suggestion for the complete adapted getValue method (v15.0.0):

getValue(target: any, key: string): any {
  let keys = typeof key === 'string' ? key.split('.') : [key];
  key = '';
  do {
    key += keys.shift();
    if (isDefined(target) && isDefined(target[key]) && (typeof target[key] === 'object' || !keys.length)) {
      target = {
        ...target[key],
        ...Object.fromEntries(
          Object.entries(target)
            .filter(([k]) => k.startsWith(key + '.'))
            .map(([k, v]) => [k.slice(key.length + 1), v])
        )
      };
      key = '';
    } else if (!keys.length) {
      target = undefined;
    } else {
      key += '.';
    }
  } while (keys.length);

  return target;
}