export to_xlsx
Export i18n files into a xlsx file, created by exceljs
Command
# Display help for export to_xlsx
npx @jy95/i18n-tools export to_xlsx --help
Purpose
Suppose you have several i18n locales such as :
- fr.json
 - nl.json
 - de.json
 
fr.json
{
  "commons":{
    "myNestedKey":"Hello world FR",
    "myNestedArray":[
      "1 FR",
      "2 FR",
      "3 FR"
    ]
  },
  "array":[
    "1 FR",
    "2 FR",
    "3 FR"
  ],
  "simpleKey":"[FR] not setted key",
  "Key with spaces":[
    {
      "test":"42 is the answer"
    }
  ],
  "Missing key in DE":"present"
}
nl.json
{
  "commons":{
    "myNestedKey":"Hello world NL",
    "myNestedArray":[
      "1 NL",
      "2 NL",
      "3 NL"
    ]
  },
  "array":[
    "1 NL",
    "2 NL",
    "3 NL"
  ],
  "simpleKey":"[NL] not setted key",
  "Key with spaces":[
    {
      "test":"42 is the answer"
    }
  ],
  "Missing key in DE":"present"
}
de.json
{
  "commons":{
    "myNestedKey":"Hello world DE",
    "myNestedArray":[
      "1 DE",
      "2 DE",
      "3 DE"
    ]
  },
  "array":[
    "1 DE",
    "2 DE",
    "3 DE"
  ],
  "simpleKey":"[DE] not setted key",
  "Key with spaces":[
    {
      "test":"42 is the answer"
    }
  ]
}
This command helps you to turn them into a single xlsx file such as this one.

Examples of settings
- Paths
 - Objects/Arrays
 - Customize xlsx file
 - Settings.js
 
npx @jy95/i18n-tools export to_xlsx --settings "/absolutePath/to/settings1.json"
settings1.json
{
   "files":"D:\\TEMP\\TEMP\\tests-for-export\\correct\\files.json",
   "columns":"D:\\TEMP\\TEMP\\tests-for-export\\correct\\columns.json",
   "worksheetName":"Settings 1 - Worksheet",
   "filename":"settings1-output",
   "outputDir":"D:\\TEMP\\TEMP"
}
files.json
{
    "FR":"D:\\TEMP\\TEMP\\tests-for-export\\correct\\fr.json",
    "NL":"D:\\TEMP\\TEMP\\tests-for-export\\correct\\nl.json",
    "DE":"D:\\TEMP\\TEMP\\tests-for-export\\correct\\de.json"
}
columns.json
[
    {
        "locale":"FR",
        "label":"French translation"
    },
    {
        "locale":"NL",
        "label":"Dutch translation"
    },
    {
        "locale":"DE",
        "label":"German translation"
    }
]
npx @jy95/i18n-tools export to_xlsx --settings "/absolutePath/to/settings2.json"
settings2.json
{
   "files":{
      "FR":"D:\\TEMP\\TEMP\\tests-for-export\\correct\\fr.json",
      "NL":"D:\\TEMP\\TEMP\\tests-for-export\\correct\\nl.json",
      "DE":"D:\\TEMP\\TEMP\\tests-for-export\\correct\\de.json"
   },
   "columns":[
      {
         "locale":"FR",
         "label":"French translation"
      },
      {
         "locale":"NL",
         "label":"Dutch translation"
      },
      {
         "locale":"DE",
         "label":"German translation"
      }
   ],
   "worksheetName":"Settings 2 - Worksheet",
   "filename":"settings2-output",
   "outputDir":"D:\\TEMP\\TEMP"
}
npx @jy95/i18n-tools export to_xlsx --settings "/absolutePath/to/settings3.json"
settings3.json
{
   "files":{
      "FR":"D:\\TEMP\\TEMP\\tests-for-export\\correct\\fr.json",
      "NL":"D:\\TEMP\\TEMP\\tests-for-export\\correct\\nl.json",
      "DE":"D:\\TEMP\\TEMP\\tests-for-export\\correct\\de.json"
   },
   "columns":[
      {
         "locale":"FR",
         "label":"French translation"
      },
      {
         "locale":"NL",
         "label":"Dutch translation"
      },
      {
         "locale":"DE",
         "label":"German translation"
      }
   ],
   "worksheetCustomizer":"D:\\workspace\\i18n-tools\\test\\fixtures\\export-xlsx\\worksheetCustomizer-dynamic.js",
   "worksheetName":"Settings 3 - Worksheet",
   "filename":"settings3-output",
   "outputDir":"D:\\TEMP\\TEMP"
}
worksheetCustomizer-dynamic.js
// I keep in comments the stuff needed to convert that into a TS file
// (So I can easily update this script in the future)
//import { Worksheet } from "exceljs";
module.exports = async function(worksheet /*: Worksheet*/) {
  // Conditionaly formatting (to better view stuff)
  let rowCount = worksheet.rowCount;
  let columnCount = worksheet.columnCount;
  // for easiness in the future, for arbitrary number of translations
  // As table have a least one language (starting to 'B'), pretty simple to compute last column letter
  let lastColumnLetter = String.fromCharCode(66 + (columnCount - 2));
  // domain for rules ; All the cells
  // Ex : "B2:D" + rowCount + 2" for three languages
  let computedRef = `B2:${lastColumnLetter + rowCount + 2}`;
  worksheet.addConditionalFormatting({
    ref: computedRef,
    rules: [
      // cell is empty : put it in red
      {
        type: 'containsText',
        operator: 'containsBlanks',
        style: {
          fill: {
            type: 'pattern',
            pattern: 'solid',
            bgColor: { argb: 'FF5733' },
          },
        },
        priority: 1,
      },
      // cell contains either [FR], [NL] or [DE] : put it in orange
      {
        type: 'containsText',
        operator: 'containsText',
        text: '[FR]',
        style: {
          fill: {
            type: 'pattern',
            pattern: 'solid',
            bgColor: { argb: 'FF9633' },
          },
        },
        priority: 2,
      },
      {
        type: 'containsText',
        operator: 'containsText',
        text: '[NL]',
        style: {
          fill: {
            type: 'pattern',
            pattern: 'solid',
            bgColor: { argb: 'FF9633' },
          },
        },
        priority: 2,
      },
      {
        type: 'containsText',
        operator: 'containsText',
        text: '[DE]',
        style: {
          fill: {
            type: 'pattern',
            pattern: 'solid',
            bgColor: { argb: 'FF9633' },
          },
        },
        priority: 2,
      },
    ],
  });
  return worksheet;
};
npx @jy95/i18n-tools export to_xlsx --settings "/absolutePath/to/settings4.js"
settings4.js
// I keep in comments the stuff needed for the typings
// (So I can easily update this script in the future)
//import { Worksheet } from "exceljs";
module.exports = {
  files: ["fr", "nl", "de"].reduce(
    (prev, curr) =>
      Object.assign(prev, {
        [curr.toUpperCase()]: `D:\\TEMP\\TEMP\\tests-for-export\\correct\\${curr}.json`,
      }),
    {}
  ),
  columns: [
    ["FR", "French translation"],
    ["NL", "Dutch translation"],
    ["DE", "German translation"],
  ].map(([locale, label]) => ({ locale: locale, label: label })),
  worksheetCustomizer: async function (worksheet /*: Worksheet*/) {
    // Conditionaly formatting (to better view stuff)
    let rowCount = worksheet.rowCount;
    let columnCount = worksheet.columnCount;
    // for easiness in the future, for arbitrary number of translations
    // As table have a least one language (starting to 'B'), pretty simple to compute last column letter
    let lastColumnLetter = String.fromCharCode(66 + (columnCount - 2));
    // domain for rules ; All the cells
    // Ex : "B2:D" + rowCount + 2" for three languages
    let computedRef = `B2:${lastColumnLetter + rowCount + 2}`;
    worksheet.addConditionalFormatting({
      ref: computedRef,
      rules: [
        // cell is empty : put it in red
        {
          type: "containsText",
          operator: "containsBlanks",
          style: {
            fill: {
              type: "pattern",
              pattern: "solid",
              bgColor: { argb: "FF5733" },
            },
          },
          priority: 1,
        },
        // cell contains either [FR], [NL] or [DE] : put it in orange
        {
          type: "containsText",
          operator: "containsText",
          text: "[FR]",
          style: {
            fill: {
              type: "pattern",
              pattern: "solid",
              bgColor: { argb: "FF9633" },
            },
          },
          priority: 2,
        },
        {
          type: "containsText",
          operator: "containsText",
          text: "[NL]",
          style: {
            fill: {
              type: "pattern",
              pattern: "solid",
              bgColor: { argb: "FF9633" },
            },
          },
          priority: 2,
        },
        {
          type: "containsText",
          operator: "containsText",
          text: "[DE]",
          style: {
            fill: {
              type: "pattern",
              pattern: "solid",
              bgColor: { argb: "FF9633" },
            },
          },
          priority: 2,
        },
      ],
    });
    return worksheet;
  },
  worksheetName: "Settings 4 - Worksheet",
  filename: "settings4-output",
  outputDir: "D:\\TEMP\\TEMP"
};
FAQ
I only want a subset of the data. How can I achieve that ?
Simply add the resultsFilter option in your settings.json or settings.js :
tip
Reminder - the type of the function parameter :
type I18N_Merged_Data = {
  technical_key: string;
  labels: {
    [locale: string]: string;
  };
}[];
settings.js
"resultsFilter": function(data /*: I18N_Merged_Data*/) {
  return data.filter((row) => 
    // Takes rows that have at least a missing label in one i18n file such as "Missing key in DE" case
    // Object.keys(row.labels).length !== 3 ||
    Object
      .values(row.labels)
      // Takes rows that have at least one empty label or contains a given prefix
      .some(
        (label) =>
          label.length === 0 ||
          ["[FR]", "[NL]", "[DE]"].some((prefix) => label.startsWith(prefix))
      )
  );
}
OR
settings.json
"resultsFilter": "D:\\TEMP\\TEMP\\resultsFilter.js"
resultsFilter.js
module.exports = function(data /*: I18N_Merged_Data*/) {
  return data.filter((row) => 
    // Takes rows that have at least a missing label in one i18n file such as "Missing key in DE" case
    // Object.keys(row.labels).length !== 3 ||
    Object
      .values(row.labels)
      // Takes rows that have at least one empty label or contains a given prefix
      .some(
        (label) =>
          label.length === 0 ||
          ["[FR]", "[NL]", "[DE]"].some((prefix) => label.startsWith(prefix))
      )
  );
}
I want the locales in a given order in the result file. How can I achieve that ?
Simply update the columns option with your given order in your settings.json or settings.js, such as :
settings.js
"columns": [
  {
      "locale":"NL",
      "label":"Dutch translation"
  },
  {
      "locale":"FR",
      "label":"French translation"
  }
]
I only work with flat JSON file(s). How can I make this command work ?
Simply set option keySeparator to false in your settings.json or settings.js, such as :
settings.json
{
  "keySeparator": false
}