-
-
Notifications
You must be signed in to change notification settings - Fork 5
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: add export to_csv & import from_csv (#10)
* feat: add export CSV * test: update files with new export format * docs: export to_csv * feat: add import CSV * docs: import from_csv * test: import from_csv * test: test from_csv Fix imports * test: fix imports * test: fix imports * fix: import from_csv Cannot use fast-csv option "headers" = true with exceljs
- Loading branch information
Showing
22 changed files
with
1,677 additions
and
23 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,139 @@ | ||
// for fs ops | ||
import path from 'path'; | ||
import Excel from 'exceljs'; | ||
|
||
// common fct | ||
import { merge_i18n_files, setUpCommonsOptions } from './export_commons'; | ||
import { parsePathToJSON } from '../../middlewares/middlewares'; | ||
|
||
// checks import | ||
import { resolveChecksInOrder, EXPORT_CHECKS } from '../../checks/index'; | ||
|
||
// For typing | ||
// eslint-disable-next-line | ||
import { Argv } from "yargs"; | ||
import { CSVExportArguments, I18N_Merged_Data } from '../../types/exportTypes'; | ||
|
||
// checks for this command | ||
const CHECKS = [...EXPORT_CHECKS.CHECKS, ...EXPORT_CHECKS.CSV.CHECKS]; | ||
|
||
// named exports | ||
export const command = 'to_csv'; | ||
export const description = 'Export i18n files into a csv file'; | ||
|
||
export const builder = function(y: Argv) { | ||
return ( | ||
setUpCommonsOptions(y) // set up common options for export | ||
.option('columns', { | ||
description: | ||
'Absolute path to a JSON array of objects, to control the columns. Example : [{ "locale": "FR", "label": "French translation" }]', | ||
demandOption: true, | ||
}) | ||
.option('delimiter', { | ||
description: 'Specify an field delimiter such as | or \\t', | ||
choices: [',', ';', '\t', ' ', '|'], | ||
default: ';', | ||
}) | ||
.option('rowDelimiter', { | ||
description: 'Specify an alternate row delimiter (i.e \\r\\n)', | ||
type: 'string', | ||
default: '\n', | ||
}) | ||
.option('quote', { | ||
description: 'String to quote fields that contain a delimiter', | ||
type: 'string', | ||
default: '"', | ||
}) | ||
.option('escape', { | ||
description: | ||
'The character to use when escaping a value that is quoted and contains a quote character that is not the end of the field', | ||
type: 'string', | ||
default: '"', | ||
}) | ||
.option('writeBOM', { | ||
description: | ||
'Set to true if you want the first character written to the stream to be a utf-8 BOM character.', | ||
type: 'boolean', | ||
default: false, | ||
}) | ||
.option('quoteHeaders', { | ||
description: 'If true then all headers will be quoted', | ||
type: 'boolean', | ||
default: true, | ||
}) | ||
// coerce columns into Object | ||
.middleware(parsePathToJSON('columns'), true) | ||
// validations | ||
.check(resolveChecksInOrder(CHECKS)) | ||
); | ||
}; | ||
|
||
export const handler = async function(argv: CSVExportArguments) { | ||
try { | ||
let data: I18N_Merged_Data = await merge_i18n_files(argv); | ||
const CSV_FILE = path.resolve(argv.outputDir, argv.filename + '.csv'); | ||
await export_as_csv(CSV_FILE, argv, data); | ||
console.log(`${CSV_FILE} successfully written`); | ||
return Promise.resolve(undefined); | ||
} catch (/* istanbul ignore next */ err) { | ||
return Promise.reject(err); | ||
} | ||
}; | ||
|
||
// write | ||
async function export_as_csv( | ||
CSV_FILE: string, | ||
argv: CSVExportArguments, | ||
data: I18N_Merged_Data | ||
) { | ||
console.log('Preparing CSV file ...'); | ||
|
||
// prepare data | ||
const workbook = new Excel.Workbook(); | ||
let worksheet = workbook.addWorksheet(); | ||
|
||
// Set up columns | ||
worksheet.columns = [ | ||
{ header: 'Technical Key', key: 'technical_key' }, | ||
].concat( | ||
argv.columns.map(({ label, locale }) => ({ | ||
header: label, | ||
key: `labels.${locale}`, | ||
})) | ||
); | ||
|
||
// workaround as Exceljs doesn't support nested key | ||
worksheet.addRows( | ||
data.map(item => | ||
argv.columns.reduce( | ||
(acc: { [x: string]: string }, { locale }) => { | ||
acc[`labels.${locale}`] = item['labels'][locale] || ''; | ||
return acc; | ||
}, | ||
{ technical_key: item['technical_key'] } | ||
) | ||
) | ||
); | ||
|
||
// finally write this file | ||
const options = { | ||
// https://c2fo.io/fast-csv/docs/formatting/options | ||
formatterOptions: { | ||
delimiter: argv.delimiter, | ||
rowDelimiter: argv.rowDelimiter, | ||
quote: argv.quote, | ||
escape: argv.escape, | ||
writeBOM: argv.writeBOM, | ||
quoteHeaders: argv.quoteHeaders, | ||
}, | ||
}; | ||
return workbook.csv.writeFile(CSV_FILE, options); | ||
} | ||
|
||
// default export | ||
export default { | ||
command: command, | ||
description: description, | ||
builder: builder, | ||
handler: handler, | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,141 @@ | ||
import Excel from 'exceljs'; | ||
|
||
// common fct | ||
import { setUpCommonsOptions, generate_i18n_filepaths, extractedTranslations_to_i18n_files } from "./import_commons"; | ||
import { parsePathToJSON } from "../../middlewares/middlewares"; | ||
|
||
// lodash methods | ||
import flattenDeep from "lodash/flattenDeep"; | ||
|
||
// checks import | ||
import { | ||
resolveChecksInOrder, | ||
IMPORT_CHECKS | ||
} from "../../checks/index"; | ||
|
||
// For typing | ||
// eslint-disable-next-line | ||
import type { Argv } from "yargs"; | ||
import { CSVImportArguments } from "../../types/importTypes"; | ||
|
||
// checks for this command | ||
const CHECKS = [...IMPORT_CHECKS.CHECKS, ...IMPORT_CHECKS.CSV.CHECKS]; | ||
|
||
// named exports | ||
export const command = "from_csv"; | ||
export const description = "Turn a csv file to i18n file(s)"; | ||
|
||
export const builder = function (y : Argv) { | ||
return setUpCommonsOptions(y) // set up common options for import | ||
.options("columns", { | ||
describe: "Absolute path to a JSON object that describe headers of the excel columns used to store translations", | ||
demandOption: true | ||
}) | ||
.option('delimiter', { | ||
description: 'Specify an field delimiter such as | or \\t', | ||
choices: [',', ';', '\t', ' ', '|'], | ||
default: ';', | ||
}) | ||
.option('quote', { | ||
description: 'String used to quote fields that contain a delimiter', | ||
type: 'string', | ||
default: '"', | ||
}) | ||
.option('escape', { | ||
description: | ||
'The character used when escaping a value that is quoted and contains a quote character that is not the end of the field', | ||
type: 'string', | ||
default: '"', | ||
}) | ||
.option('encoding', { | ||
description: "Input file encoding", | ||
choices: ['utf8', 'utf16le', 'latin1'], | ||
default: 'utf8' | ||
}) | ||
// coerce columns into Object | ||
.middleware(parsePathToJSON("columns"), true) | ||
// validations | ||
.check(resolveChecksInOrder(CHECKS)) | ||
} | ||
|
||
export const handler = async function (argv : CSVImportArguments) { | ||
try { | ||
const translations = await csv_2_translation_objects(argv); | ||
const files = generate_i18n_filepaths(argv); | ||
await extractedTranslations_to_i18n_files(files, translations); | ||
console.log("Successfully exported found locale(s) to i18n json file(s)"); | ||
return Promise.resolve(undefined); | ||
} catch (error) { | ||
return Promise.reject(error); | ||
} | ||
} | ||
|
||
// Extract translations from csv file | ||
async function csv_2_translation_objects(argv : CSVImportArguments) { | ||
const options = { | ||
// https://c2fo.io/fast-csv/docs/parsing/options | ||
parserOptions: { | ||
delimiter: argv.delimiter, | ||
quote: argv.quote, | ||
escape: argv.escape, | ||
encoding: argv.encoding | ||
} | ||
}; | ||
const workbook = new Excel.Workbook(); | ||
const worksheet = await workbook.csv.readFile(argv.input, options); | ||
let rowCount = worksheet.rowCount; | ||
|
||
// columns properties to load | ||
let columns = argv.columns; | ||
|
||
// retrieve the headers of the table | ||
// Warning : Exceljs put for some reason a undefined value at the 0 index | ||
let headers = worksheet.getRow(1).values as (undefined | string)[]; | ||
// retrieve data of the table | ||
let data = (worksheet.getRows(2, rowCount-1) || /* istanbul ignore next */ []).map(item => item.values); | ||
|
||
// find out where the technical key is | ||
const technical_key_index = headers.findIndex(h => (h || '').includes(columns.technical_key)); | ||
|
||
if (technical_key_index === -1) { | ||
return Promise.reject(new Error("Couldn't find index for technical_key with provided label")); | ||
} | ||
|
||
// find out where the translations are positioned in the value | ||
const locales_index = Object | ||
.entries(columns.locales) | ||
.map( ([key, value]) => ({ [key]: headers.findIndex(h => (h || '').includes(value)) })) | ||
.reduce( (prev, curr) => Object.assign(prev, curr), {}) | ||
|
||
// Warn users if some locale translations couldn't be found | ||
let missing_indexes = Object | ||
.entries(locales_index) | ||
.filter( ([_, idx]) => idx === -1); | ||
|
||
for(let [locale, ] of missing_indexes) { | ||
/* istanbul ignore next Not worthy to create a test case for that*/ | ||
console.warn(`Couldn't find index for ${locale} locale with provided label`) | ||
} | ||
|
||
// build results | ||
let results = data.map( | ||
(row : any) => Object | ||
.entries(locales_index) | ||
// skip translation(s) where index couldn't be found | ||
.filter( ([_, idx]) => idx !== -1) | ||
.map( ([locale, localeIndex]) => ({ | ||
"technical_key": row[technical_key_index], | ||
"label": row[localeIndex], | ||
"locale": locale | ||
})) | ||
) | ||
return Promise.resolve(flattenDeep(results)); | ||
} | ||
|
||
// default export | ||
export default { | ||
command : command, | ||
description: description, | ||
builder : builder, | ||
handler: handler | ||
} |
Oops, something went wrong.