From a4d58c926c34f11994a991066d83b274ee5a93f2 Mon Sep 17 00:00:00 2001 From: Vitalij Ryndin Date: Thu, 23 Nov 2023 20:04:10 +0800 Subject: [PATCH 1/4] feat: new api `xml-locales` --- packages/xml-locales/package.json | 14 ++- packages/xml-locales/src/index.ts | 7 +- packages/xml-locales/src/modifiers/add.ts | 35 +++----- packages/xml-locales/src/modifiers/change.ts | 28 ------ packages/xml-locales/src/modifiers/delete.ts | 41 ++++----- packages/xml-locales/src/modifiers/sort.ts | 41 ++++----- packages/xml-locales/src/modifiers/update.ts | 27 ++++++ packages/xml-locales/src/utils/helpers.ts | 51 +++-------- packages/xml-locales/src/utils/index.ts | 2 - packages/xml-locales/src/utils/types.ts | 39 +++----- packages/xml-locales/src/utils/xml.ts | 77 ++++++++++++++++ packages/xml-locales/src/xml-locales.ts | 86 ++++++++++++++++++ .../tests/mock/data/stringElements.ts | 32 ------- packages/xml-locales/tests/utils.test.ts | 89 ------------------- pnpm-lock.yaml | 9 +- 15 files changed, 276 insertions(+), 302 deletions(-) delete mode 100644 packages/xml-locales/src/modifiers/change.ts create mode 100644 packages/xml-locales/src/modifiers/update.ts delete mode 100644 packages/xml-locales/src/utils/index.ts create mode 100644 packages/xml-locales/src/utils/xml.ts create mode 100644 packages/xml-locales/src/xml-locales.ts delete mode 100644 packages/xml-locales/tests/mock/data/stringElements.ts delete mode 100644 packages/xml-locales/tests/utils.test.ts diff --git a/packages/xml-locales/package.json b/packages/xml-locales/package.json index 3d19d5c..f23e4c3 100644 --- a/packages/xml-locales/package.json +++ b/packages/xml-locales/package.json @@ -17,21 +17,17 @@ "dist" ], "types": "./dist/index.d.ts", - "typesVersions": { - "*": { - "utils": [ - "./dist/utils/index.d.ts" - ] - } - }, "exports": { - ".": "./dist/index.js", - "./utils": "./dist/utils/index.js" + ".": "./dist/index.js" }, "scripts": { "dev": "tsup --watch", "build": "tsup", "test": "vitest run --coverage", "test:watch": "vitest --ui --watch --coverage" + }, + "dependencies": { + "fast-xml-parser": "4.3.2", + "xml-formatter": "3.6.0" } } diff --git a/packages/xml-locales/src/index.ts b/packages/xml-locales/src/index.ts index aafc8c3..25d21a1 100644 --- a/packages/xml-locales/src/index.ts +++ b/packages/xml-locales/src/index.ts @@ -1,4 +1,3 @@ -export * from './modifiers/sort.js'; -export * from './modifiers/add.js'; -export * from './modifiers/change.js'; -export * from './modifiers/delete.js'; +export * from './xml-locales.js'; +export { XmlJsonData } from './utils/xml.js'; +export type * from './utils/types.js'; diff --git a/packages/xml-locales/src/modifiers/add.ts b/packages/xml-locales/src/modifiers/add.ts index 806e555..bb22caf 100644 --- a/packages/xml-locales/src/modifiers/add.ts +++ b/packages/xml-locales/src/modifiers/add.ts @@ -1,30 +1,19 @@ -import { checkKeyExist, replaceValue } from '../utils/helpers.js'; -import { sort } from './sort.js'; -import type { AddOptions } from '../utils/types.js'; - -export function add({ key, value, sortDirection, jsonXml }: AddOptions) { - const { - resources: { string } - } = jsonXml; - - const hasKey = checkKeyExist(key, string); +import { checkXmlKey, replaceXmlNodeValue } from '../utils/helpers.js'; +import { XmlJsonData } from '../utils/xml.js'; +export function addXmlNode( + xmlData: XmlJsonData, + key: string, + value: string +): XmlJsonData { + const hasKey = checkXmlKey(xmlData, key); if (hasKey) { - const replacedStrings = replaceValue(string, key, value); - - jsonXml.resources.string = replacedStrings; - } - - if (!hasKey) { - jsonXml.resources.string.push({ + return replaceXmlNodeValue(xmlData, key, value); + } else { + xmlData.resources.string.push({ key_name: key, '#text': value }); + return xmlData; } - - if (sortDirection) { - sort({ sortDirection, jsonXml }); - } - - return jsonXml; } diff --git a/packages/xml-locales/src/modifiers/change.ts b/packages/xml-locales/src/modifiers/change.ts deleted file mode 100644 index 6e583ee..0000000 --- a/packages/xml-locales/src/modifiers/change.ts +++ /dev/null @@ -1,28 +0,0 @@ -import { checkKeyValueExist, replace } from '../utils/helpers.js'; -import { sort } from './sort.js'; -import type { ChangeOptions } from '../utils/types.js'; - -export function change({ - oldKey, - newKey, - sortDirection, - jsonXml -}: ChangeOptions) { - const { - resources: { string } - } = jsonXml; - - const hasKey = checkKeyValueExist(oldKey, string); - - if (hasKey) { - const replacedStrings = replace(string, oldKey, newKey); - - jsonXml.resources.string = replacedStrings; - } - - if (sortDirection) { - sort({ sortDirection, jsonXml }); - } - - return jsonXml; -} diff --git a/packages/xml-locales/src/modifiers/delete.ts b/packages/xml-locales/src/modifiers/delete.ts index 0cc13ec..460fa67 100644 --- a/packages/xml-locales/src/modifiers/delete.ts +++ b/packages/xml-locales/src/modifiers/delete.ts @@ -1,26 +1,27 @@ -import { checkKeyValueExist } from '../utils/helpers.js'; -import { sort } from './sort.js'; -import type { DeleteOptions } from '../utils/types.js'; +import { checkXmlKey } from '../utils/helpers.js'; +import { XmlJsonData } from '../utils/xml.js'; -export function del({ keyValue, sort: sortDirection, jsonXml }: DeleteOptions) { - const { - resources: { string } - } = jsonXml; +export function deleteByKeyXmlNode( + xmlData: XmlJsonData, + key: string +): XmlJsonData { + const hasKey = checkXmlKey(xmlData, key); + if (!hasKey) return xmlData; - const hasKeyValue = checkKeyValueExist(keyValue, string); + const filteredNodes = xmlData.resources.string.filter( + (element) => element.key_name !== key + ); - if (hasKeyValue) { - const filteredStrings = string.filter( - (element) => - element['#text'] !== keyValue && element.key_name !== keyValue - ); - - jsonXml.resources.string = filteredStrings; - } + return new XmlJsonData({ resources: { string: filteredNodes } }); +} - if (sortDirection) { - sort({ sortDirection, jsonXml }); - } +export function deleteByValueXmlNode( + xmlData: XmlJsonData, + value: string +): XmlJsonData { + const filteredNodes = xmlData.resources.string.filter( + (element) => element['#text'] !== value + ); - return jsonXml; + return new XmlJsonData({ resources: { string: filteredNodes } }); } diff --git a/packages/xml-locales/src/modifiers/sort.ts b/packages/xml-locales/src/modifiers/sort.ts index 532b4a1..19cc637 100644 --- a/packages/xml-locales/src/modifiers/sort.ts +++ b/packages/xml-locales/src/modifiers/sort.ts @@ -1,32 +1,21 @@ -import type { - SortDirection, - SortOptions, - StringElement -} from '../utils/types.js'; - -export function sortBy(strElements: StringElement[], direction: SortDirection) { - const { compare } = new Intl.Collator(); - - const compareFn = (a: StringElement, b: StringElement) => { +import { XmlJsonData } from '../utils/xml.js'; +import type { SortDirection } from '../utils/types.js'; + +export function sortXmlNodes( + xmlData: XmlJsonData, + direction: SortDirection +): XmlJsonData { + const collator = new Intl.Collator(); + const sortedNodes = xmlData.resources.string.sort((a, b) => { const isAsc = direction === 'asc'; const first = isAsc ? a : b; const second = isAsc ? b : a; - return compare(first.key_name.toLowerCase(), second.key_name.toLowerCase()); - }; - - const sortedElements = strElements.sort(compareFn); - - return sortedElements; -} - -export function sort({ sortDirection, jsonXml }: SortOptions) { - const { - resources: { string } - } = jsonXml; - - const sortedString = sortBy(string, sortDirection); - jsonXml.resources.string = sortedString; + return collator.compare( + first.key_name.toLowerCase(), + second.key_name.toLowerCase() + ); + }); - return jsonXml; + return new XmlJsonData({ resources: { string: sortedNodes } }); } diff --git a/packages/xml-locales/src/modifiers/update.ts b/packages/xml-locales/src/modifiers/update.ts new file mode 100644 index 0000000..dc6ac2c --- /dev/null +++ b/packages/xml-locales/src/modifiers/update.ts @@ -0,0 +1,27 @@ +import { XmlJsonData } from '../utils/xml.js'; + +export function updateXmlNode( + xmlData: XmlJsonData, + oldKeyValue: string, + newKeyValue: string +): XmlJsonData { + const replacedNodes = xmlData.resources.string.map((element) => { + if (element.key_name === oldKeyValue) { + return { + ...element, + key_name: newKeyValue + }; + } + + if (element['#text'] === oldKeyValue) { + return { + ...element, + '#text': newKeyValue + }; + } + + return element; + }); + + return new XmlJsonData({ resources: { string: replacedNodes } }); +} diff --git a/packages/xml-locales/src/utils/helpers.ts b/packages/xml-locales/src/utils/helpers.ts index 9deabfe..5932650 100644 --- a/packages/xml-locales/src/utils/helpers.ts +++ b/packages/xml-locales/src/utils/helpers.ts @@ -1,26 +1,25 @@ -import type { StringElement } from './types.js'; +import { XmlJsonData } from './xml.js'; +import type { XmlNode } from './types.js'; -export function checkKeyExist(key: string, strElements: StringElement[]) { - return strElements.find( - ({ key_name }: { key_name: string }) => key_name === key - ); +export function checkXmlKey(xmlData: XmlJsonData, key: string): boolean { + return xmlData.resources.string.some((element) => element.key_name === key); } export function checkKeyValueExist( keyValue: string, - strElement: StringElement[] -) { - return strElement.find( + strElement: XmlNode[] +): boolean { + return strElement.some( (element) => element['#text'] === keyValue || element.key_name === keyValue ); } -export function replaceValue( - strElements: StringElement[], +export function replaceXmlNodeValue( + xmlData: XmlJsonData, key: string, newValue: string -) { - const replacedStrings = strElements.map((element) => { +): XmlJsonData { + const replacedStrings = xmlData.resources.string.map((element) => { if (element.key_name === key) { return { ...element, @@ -31,31 +30,5 @@ export function replaceValue( return element; }); - return replacedStrings; -} - -export function replace( - strElements: StringElement[], - oldKeyValue: string, - newKeyValue: string -) { - const replaced = strElements.map((element) => { - if (element.key_name === oldKeyValue) { - return { - ...element, - key_name: newKeyValue - }; - } - - if (element['#text'] === oldKeyValue) { - return { - ...element, - '#text': newKeyValue - }; - } - - return element; - }); - - return replaced; + return new XmlJsonData({ resources: { string: replacedStrings } }); } diff --git a/packages/xml-locales/src/utils/index.ts b/packages/xml-locales/src/utils/index.ts deleted file mode 100644 index 93fa190..0000000 --- a/packages/xml-locales/src/utils/index.ts +++ /dev/null @@ -1,2 +0,0 @@ -export * from './helpers.js'; -export type * from './types.js'; diff --git a/packages/xml-locales/src/utils/types.ts b/packages/xml-locales/src/utils/types.ts index 3aa2dd4..1314b66 100644 --- a/packages/xml-locales/src/utils/types.ts +++ b/packages/xml-locales/src/utils/types.ts @@ -1,48 +1,29 @@ -export interface StringElement { +import { XmlJsonData } from './xml.js'; + +export interface XmlNode { key_name: string; '#text': string; } -export interface XmlJson { - resources: { - string: StringElement[]; - }; -} +export type XmlDataTypes = string | Buffer | XmlJsonData; -export interface RawXmlJson { +export interface XmlJson { resources: { - string: StringElement | StringElement[]; + string: XmlNode[]; }; } -export type SortDirection = 'asc' | 'desc'; - -export interface BaseOptions { - jsonXml: XmlJson; - sortDirection?: SortDirection; -} - -export interface AddOptions extends BaseOptions { +export interface AddOptions { key: string; value: string; } -export interface DeleteOptions extends BaseOptions { - keyValue: string; - sort?: SortDirection; -} - -export interface ChangeOptions extends BaseOptions { +export interface UpdateKeyOptions { oldKey: string; newKey: string; } -export interface SortOptions extends BaseOptions { +export type SortDirection = 'asc' | 'desc'; +export interface SortOptions { sortDirection: SortDirection; } - -export type ModifierOptions = - | AddOptions - | DeleteOptions - | ChangeOptions - | SortOptions; diff --git a/packages/xml-locales/src/utils/xml.ts b/packages/xml-locales/src/utils/xml.ts new file mode 100644 index 0000000..0f33c7a --- /dev/null +++ b/packages/xml-locales/src/utils/xml.ts @@ -0,0 +1,77 @@ +import { XMLBuilder, XMLParser } from 'fast-xml-parser'; +import xmlFormatter from 'xml-formatter'; +import type { + X2jOptionsOptional, + XmlBuilderOptionsOptional +} from 'fast-xml-parser'; +import type { XMLFormatterOptions } from 'xml-formatter'; + +import type { XmlJson } from './types.js'; + +export interface XmlConstructor { + parserOptions?: X2jOptionsOptional; + builderOptions?: XmlBuilderOptionsOptional; + formateOptions?: XMLFormatterOptions; +} + +const defaultParserOptions: X2jOptionsOptional = { + trimValues: true, + ignoreDeclaration: true, + attributeNamePrefix: 'key_', + alwaysCreateTextNode: true, + ignoreAttributes: false +}; + +const defaultBuilderOptions: XmlBuilderOptionsOptional = { + ignoreAttributes: false, + attributeNamePrefix: 'key_' +}; + +const defaultFormatterOptions: XMLFormatterOptions = { + collapseContent: true, + indentation: ' ' +}; + +const defaultXmlOptions: XmlConstructor = { + parserOptions: defaultParserOptions, + builderOptions: defaultBuilderOptions, + formateOptions: defaultFormatterOptions +}; + +export class XmlParser { + private parser: XMLParser; + private builder: XMLBuilder; + private formatter: (xmlString: string) => string; + + constructor(xmlOptions?: XmlConstructor) { + const { parserOptions, builderOptions, formateOptions } = { + ...(xmlOptions || {}), + ...defaultXmlOptions + }; + + this.parser = new XMLParser(parserOptions); + this.builder = new XMLBuilder(builderOptions); + this.formatter = (xmlString: string) => + xmlFormatter(xmlString, formateOptions); + } + + xmlToJson(xmlData: string | Buffer): XmlJsonData { + return new XmlJsonData(this.parser.parse(xmlData)); + } + + jsonToXml(xmlJson: XmlJson): string { + return this.builder.build(xmlJson); + } + + formate(xmlString: string): string { + return this.formatter(xmlString); + } +} + +export class XmlJsonData { + resources: XmlJson['resources']; + + constructor(xmlJson: XmlJson) { + this.resources = xmlJson.resources; + } +} diff --git a/packages/xml-locales/src/xml-locales.ts b/packages/xml-locales/src/xml-locales.ts new file mode 100644 index 0000000..6236264 --- /dev/null +++ b/packages/xml-locales/src/xml-locales.ts @@ -0,0 +1,86 @@ +import { addXmlNode } from './modifiers/add.js'; +import { + deleteByKeyXmlNode, + deleteByValueXmlNode +} from './modifiers/delete.js'; +import { sortXmlNodes } from './modifiers/sort.js'; +import { updateXmlNode } from './modifiers/update.js'; +import { XmlJsonData, XmlParser } from './utils/xml.js'; +import type { + AddOptions, + SortDirection, + UpdateKeyOptions, + XmlDataTypes +} from './utils/types.js'; +import type { XmlConstructor } from './utils/xml.js'; + +export class XmlLocales { + private readonly xmlParser: XmlParser; + + private xmlOptions?: XmlConstructor; + private xmlData: XmlJsonData; + + constructor( + xmlData?: string | Buffer | XmlJsonData, + xmlOptions?: XmlConstructor + ) { + this.xmlParser = new XmlParser(xmlOptions); + this.xmlOptions = xmlOptions; + this.parseXml(xmlData); + } + + private parseXml(xmlData?: XmlDataTypes): void { + if (xmlData instanceof XmlJsonData) { + this.xmlData = xmlData; + return; + } + + if (xmlData) { + this.xmlData = this.xmlParser.xmlToJson(xmlData); + return; + } + + if (!this.xmlData) { + throw new Error('XML data is not defined'); + } + } + + private newInstance(): XmlLocales { + return new XmlLocales(this.xmlData, this.xmlOptions); + } + + add(options: AddOptions): XmlLocales { + this.xmlData = addXmlNode(this.xmlData, options.key, options.value); + return this.newInstance(); + } + + updateKey(options: UpdateKeyOptions): XmlLocales { + this.xmlData = updateXmlNode(this.xmlData, options.oldKey, options.newKey); + return this.newInstance(); + } + + deleteByKey(key: string): XmlLocales { + this.xmlData = deleteByKeyXmlNode(this.xmlData, key); + return this.newInstance(); + } + + deleteByValue(value: string): XmlLocales { + this.xmlData = deleteByValueXmlNode(this.xmlData, value); + return this.newInstance(); + } + + sort(sortDirection: SortDirection): XmlLocales { + this.xmlData = sortXmlNodes(this.xmlData, sortDirection); + return this.newInstance(); + } + + toJSON(): XmlJsonData { + return this.xmlData; + } + + toXML(formate = true): string { + const xmlString = this.xmlParser.jsonToXml(this.xmlData); + if (formate) return this.xmlParser.formate(xmlString); + return xmlString; + } +} diff --git a/packages/xml-locales/tests/mock/data/stringElements.ts b/packages/xml-locales/tests/mock/data/stringElements.ts deleted file mode 100644 index 71991d7..0000000 --- a/packages/xml-locales/tests/mock/data/stringElements.ts +++ /dev/null @@ -1,32 +0,0 @@ -export const STRING_ELEMENTS = [ - { - key_name: 'testKey1', - '#text': 'text1' - }, - { - key_name: 'testKey2', - '#text': 'text2' - } -]; - -export const REPLACED_VALUE_ELEMENTS = [ - { - key_name: 'testKey1', - '#text': 'newValue' - }, - { - key_name: 'testKey2', - '#text': 'text2' - } -]; - -export const REPLACED_KEY_ELEMENTS = [ - { - key_name: 'newKey', - '#text': 'text1' - }, - { - key_name: 'testKey2', - '#text': 'text2' - } -]; diff --git a/packages/xml-locales/tests/utils.test.ts b/packages/xml-locales/tests/utils.test.ts deleted file mode 100644 index 9475b05..0000000 --- a/packages/xml-locales/tests/utils.test.ts +++ /dev/null @@ -1,89 +0,0 @@ -import { describe, expect, test } from 'vitest'; - -import { - checkKeyExist, - checkKeyValueExist, - replace, - replaceValue -} from '../src/utils/helpers.js'; -import { - REPLACED_KEY_ELEMENTS, - REPLACED_VALUE_ELEMENTS, - STRING_ELEMENTS -} from './mock/data/stringElements.js'; - -describe('checkKeyExist', () => { - test('has key', () => { - const hasKey = checkKeyExist('testKey1', STRING_ELEMENTS); - - expect(hasKey).toBeTruthy(); - }); - - test('has no key', () => { - const hasKey = checkKeyExist('testKey', STRING_ELEMENTS); - - expect(hasKey).toBeFalsy(); - }); -}); - -describe('replace', () => { - test('replace key', () => { - const replacedStrings = replace(STRING_ELEMENTS, 'testKey1', 'newKey'); - - expect(replacedStrings).toMatchObject(REPLACED_KEY_ELEMENTS); - }); - - test('replace value', () => { - const replacedStrings = replace(STRING_ELEMENTS, 'text1', 'newValue'); - - expect(replacedStrings).toMatchObject(REPLACED_VALUE_ELEMENTS); - }); -}); - -describe('checkKeyValueExist', () => { - describe('key', () => { - test('key exist', () => { - const hasKey = checkKeyValueExist('testKey1', STRING_ELEMENTS); - expect(hasKey).toBeTruthy(); - }); - - test('key no exist', () => { - const hasKey = checkKeyValueExist('someKey', STRING_ELEMENTS); - expect(hasKey).toBeFalsy(); - }); - }); - - describe('value', () => { - test('value exist', () => { - const hasKey = checkKeyValueExist('text1', STRING_ELEMENTS); - expect(hasKey).toBeTruthy(); - }); - - test('value no exist', () => { - const hasKey = checkKeyValueExist('someValue', STRING_ELEMENTS); - expect(hasKey).toBeFalsy(); - }); - }); -}); - -describe('replaceValue', () => { - test('replaced value', () => { - const replacedStrings = replaceValue( - STRING_ELEMENTS, - 'testKey1', - 'newValue' - ); - - expect(replacedStrings).toMatchObject(REPLACED_VALUE_ELEMENTS); - }); - - test('does not replace value', () => { - const replacedStrings = replaceValue( - STRING_ELEMENTS, - 'someKey', - 'someValue' - ); - - expect(replacedStrings).toMatchObject(STRING_ELEMENTS); - }); -}); diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index d58f7e4..fcc391f 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -63,7 +63,14 @@ importers: specifier: 17.7.2 version: 17.7.2 - packages/xml-locales: {} + packages/xml-locales: + dependencies: + fast-xml-parser: + specifier: 4.3.2 + version: 4.3.2 + xml-formatter: + specifier: 3.6.0 + version: 3.6.0 packages: From 165e3cd14be369408933a19478f3ad36e56506e7 Mon Sep 17 00:00:00 2001 From: Vitalij Ryndin Date: Thu, 23 Nov 2023 22:01:26 +0800 Subject: [PATCH 2/4] feat: refactor cli --- packages/cli/src/commands/add.ts | 47 ++++++++++++++ packages/cli/src/commands/addString.ts | 58 ----------------- packages/cli/src/commands/change.ts | 63 ------------------- packages/cli/src/commands/delString.ts | 50 --------------- packages/cli/src/commands/index.ts | 16 +++-- packages/cli/src/commands/newLocale.ts | 43 ------------- packages/cli/src/commands/remove.ts | 53 ++++++++++++++++ packages/cli/src/commands/sort.ts | 36 ++++++----- packages/cli/src/commands/update.ts | 47 ++++++++++++++ packages/cli/src/utils/files.ts | 30 +++++++++ .../cli/src/utils/{queries.ts => prompts.ts} | 0 packages/xml-locales/src/modifiers/update.ts | 12 ++-- packages/xml-locales/src/utils/types.ts | 6 +- packages/xml-locales/src/xml-locales.ts | 10 ++- 14 files changed, 222 insertions(+), 249 deletions(-) create mode 100644 packages/cli/src/commands/add.ts delete mode 100644 packages/cli/src/commands/addString.ts delete mode 100644 packages/cli/src/commands/change.ts delete mode 100644 packages/cli/src/commands/delString.ts delete mode 100644 packages/cli/src/commands/newLocale.ts create mode 100644 packages/cli/src/commands/remove.ts create mode 100644 packages/cli/src/commands/update.ts create mode 100644 packages/cli/src/utils/files.ts rename packages/cli/src/utils/{queries.ts => prompts.ts} (100%) diff --git a/packages/cli/src/commands/add.ts b/packages/cli/src/commands/add.ts new file mode 100644 index 0000000..b75533d --- /dev/null +++ b/packages/cli/src/commands/add.ts @@ -0,0 +1,47 @@ +import type { ArgumentsCamelCase, Argv } from 'yargs'; + +import { readFiles, writeFile } from '../utils/files.js'; + +export const command = 'add'; +export const description = 'Add one localization string in files'; + +export function builder(yargs: Argv) { + return yargs + .option('path', { + alias: 'p', + desc: 'Path of file or directory to adding string', + demandOption: true, + type: 'string', + default: process.cwd() + }) + .option('key', { + alias: 'k', + desc: 'Key of adding string', + demandOption: true, + type: 'string' + }) + .option('value', { + alias: 'v', + desc: 'Value of adding string', + demandOption: true, + type: 'string' + }) + .usage( + `\nExample:\n $0 ${command} --path "path/to/file/or/directory" --key "some_key" --value "some_value"` + ); +} + +export async function handler({ + key, + value, + path +}: ArgumentsCamelCase<{ + key: string; + value: string; + path: string; +}>) { + const files = await readFiles(path); + for (const [path, file] of files) { + await writeFile(path, file.add({ key, value }).toXML()); + } +} diff --git a/packages/cli/src/commands/addString.ts b/packages/cli/src/commands/addString.ts deleted file mode 100644 index 92c5ed7..0000000 --- a/packages/cli/src/commands/addString.ts +++ /dev/null @@ -1,58 +0,0 @@ -import { add } from 'xml-locales'; -import { modificationFile } from 'xml-locales/utils'; -import type { ArgumentsCamelCase, Argv } from 'yargs'; - -export const command = 'add'; -export const description = 'Add one localization string in files'; - -export function builder(yargs: Argv) { - return yargs - .option('key', { - alias: 'k', - desc: 'Key of adding string', - demandOption: true, - type: 'string' - }) - .option('value', { - alias: 'v', - desc: 'Value of adding string', - demandOption: true, - type: 'string' - }) - .option('directory', { - alias: 'dir', - desc: 'Directory of localization files', - type: 'string', - default: 'mock' - }) - .option('sort', { - alias: 's', - desc: 'Sorted keys of strings by asc or desc', - type: 'string' - }) - .option('accept', { - alias: 'y', - desc: 'Accept add in all files', - type: 'boolean', - default: false - }) - .usage( - `\nExample:\n $0 ${command} --key key.of.string --value "locale string"` - ); -} - -export async function handler({ - key, - value, - directory, - sort, - accept -}: ArgumentsCamelCase<{ - key: string; - value: string; - directory: string; - sort?: 'asc' | 'desc'; - accept: boolean; -}>) { - modificationFile(add({ key, directory, value, sort, accept }), directory); -} diff --git a/packages/cli/src/commands/change.ts b/packages/cli/src/commands/change.ts deleted file mode 100644 index 0a70f4f..0000000 --- a/packages/cli/src/commands/change.ts +++ /dev/null @@ -1,63 +0,0 @@ -import { change } from 'xml-locales'; -import { modificationFile } from 'xml-locales/utils'; -import type { ArgumentsCamelCase, Argv } from 'yargs'; - -export const command = 'change'; -export const description = 'Change key or value of localization string'; - -export function builder(yargs: Argv) { - return yargs - .option('oldkey', { - desc: 'Old key or value of changing string', - demandOption: true, - type: 'string' - }) - .option('newkey', { - desc: 'New key or value of changing string', - demandOption: true, - type: 'string' - }) - .option('directory', { - alias: 'dir', - desc: 'Directory of localization files', - type: 'string', - default: 'mock' - }) - .option('sort', { - alias: 's', - desc: 'Sorted keys of strings by asc or desc', - type: 'string' - }) - .option('accept', { - alias: 'y', - desc: 'Accept change in all files', - type: 'boolean', - default: false - }); -} - -export async function handler({ - oldkey: oldKeyValue, - newkey: newKeyValue, - directory, - sort, - accept -}: ArgumentsCamelCase<{ - oldkey: string; - newkey: string; - directory: string; - sort?: 'asc' | 'desc'; - accept: boolean; -}>) { - console.log(oldKeyValue, newKeyValue); - - modificationFile( - change({ - oldKey: oldKeyValue, - newKey: newKeyValue, - sort, - accept - }), - directory - ); -} diff --git a/packages/cli/src/commands/delString.ts b/packages/cli/src/commands/delString.ts deleted file mode 100644 index 2bcf60e..0000000 --- a/packages/cli/src/commands/delString.ts +++ /dev/null @@ -1,50 +0,0 @@ -import { del } from 'xml-locales'; -import { modificationFile } from 'xml-locales/utils'; -import type { ArgumentsCamelCase, Argv } from 'yargs'; - -export const command = 'delete'; -export const description = 'Delete one localization string in files'; - -export function builder(yargs: Argv) { - return yargs - .option('key', { - alias: 'k', - desc: 'Key or value of delete string', - demandOption: true, - type: 'string' - }) - .option('directory', { - alias: 'dir', - desc: 'Directory of localization files', - type: 'string', - default: 'mock' - }) - .option('sort', { - alias: 's', - desc: 'Sorted keys of strings by asc or desc', - type: 'string' - }) - .option('accept', { - alias: 'y', - desc: 'Accept delete in all files', - type: 'boolean', - default: false - }) - .usage( - `\nExample:\n ${command} --key key.of.string --directory src/locales` - ); -} - -export async function handler({ - key: keyValue, - directory, - sort, - accept -}: ArgumentsCamelCase<{ - key: string; - directory: string; - sort?: 'asc' | 'desc'; - accept: boolean; -}>) { - modificationFile(del({ keyValue, sort, accept }), directory); -} diff --git a/packages/cli/src/commands/index.ts b/packages/cli/src/commands/index.ts index ca06293..c8fabce 100644 --- a/packages/cli/src/commands/index.ts +++ b/packages/cli/src/commands/index.ts @@ -1,13 +1,11 @@ -import * as addString from './addString.js'; -import * as change from './change.js'; -import * as delString from './delString.js'; -import * as newLocale from './newLocale.js'; +import * as add from './add.js'; +import * as remove from './remove.js'; import * as sort from './sort.js'; +import * as update from './update.js'; export const commands = [ - addString, - delString, - sort, - change, - newLocale + add, + update, + remove, + sort ]; diff --git a/packages/cli/src/commands/newLocale.ts b/packages/cli/src/commands/newLocale.ts deleted file mode 100644 index 9b7e6d2..0000000 --- a/packages/cli/src/commands/newLocale.ts +++ /dev/null @@ -1,43 +0,0 @@ -import fs from 'node:fs/promises'; -import type { ArgumentsCamelCase, Argv } from 'yargs'; - -export const command = 'new'; -export const description = 'Create new localization file from master file'; - -export function builder(yargs: Argv) { - return yargs - .option('master', { - alias: 'm', - desc: 'Master localization file', - demandOption: true, - type: 'string' - }) - .option('name', { - alias: 'n', - desc: 'Name of new localization file', - demandOption: true, - type: 'string' - }) - .usage(`\nExample:\n $0 ${command} -n strings-kz.xml -m strings.xml`); -} - -export async function handler({ - name, - master -}: ArgumentsCamelCase<{ - name: string; - master: string; -}>) { - await newLocale({ name, master }); -} - -async function newLocale(options: { name: string; master: string }) { - const { name, master } = options; - - try { - await fs.copyFile(master, name); - console.log(`${master} was copied to ${name}`); - } catch (err) { - console.error(err); - } -} diff --git a/packages/cli/src/commands/remove.ts b/packages/cli/src/commands/remove.ts new file mode 100644 index 0000000..786fff8 --- /dev/null +++ b/packages/cli/src/commands/remove.ts @@ -0,0 +1,53 @@ +import type { ArgumentsCamelCase, Argv } from 'yargs'; + +import { readFiles, writeFile } from '../utils/files.js'; + +export const command = 'remove'; +export const description = 'Remove one localization string in files'; + +export function builder(yargs: Argv) { + return yargs + .option('path', { + alias: 'p', + desc: 'Path of file or directory to adding string', + demandOption: true, + type: 'string', + default: process.cwd() + }) + .option('key', { + alias: 'k', + desc: 'Key of remove string', + type: 'string' + }) + .option('value', { + alias: 'v', + desc: 'Value of remove string', + type: 'string' + }) + .usage( + `\nExample:\n ${command} --path "path/to/file/or/directory" --key "some_key" --value "or_some_value"` + ); +} + +export async function handler({ + key, + value, + path +}: ArgumentsCamelCase<{ + key: string; + value: string; + path: string; +}>) { + const files = await readFiles(path); + for (let [path, file] of files) { + if (key) { + file = file.deleteByKey(key); + } + + if (value) { + file = file.deleteByValue(value); + } + + await writeFile(path, file.toXML()); + } +} diff --git a/packages/cli/src/commands/sort.ts b/packages/cli/src/commands/sort.ts index ca86c0d..7ac3cfa 100644 --- a/packages/cli/src/commands/sort.ts +++ b/packages/cli/src/commands/sort.ts @@ -1,30 +1,38 @@ -import { sort } from 'xml-locales'; -import { modificationFile } from 'xml-locales/utils'; +import { SortDirection } from 'xml-locales'; import type { ArgumentsCamelCase, Argv } from 'yargs'; +import { readFiles, writeFile } from '../utils/files.js'; + export const command = 'sort'; export const description = 'Sorted keys of strings by asc or desc'; export function builder(yargs: Argv) { return yargs + .option('path', { + alias: 'p', + desc: 'Path of file or directory to adding string', + demandOption: true, + type: 'string', + default: process.cwd() + }) .option('direction', { alias: 'd', desc: 'Direction of key sort', type: 'string', - default: 'asc' - }) - .option('directory', { - alias: 'dir', - desc: 'Directory of localization files', - type: 'string', - default: 'mock' + default: 'asc', + choices: ['asc', 'desc'] }) - .usage(`\nExample:\n ${command} --direction desc --directory src/locales`); + .usage( + `\nExample:\n ${command} --path "path/to/file/or/directory" --direction "desc"` + ); } export async function handler({ - direction, - directory -}: ArgumentsCamelCase<{ direction: 'asc' | 'desc'; directory: string }>) { - modificationFile(sort({ direction }), directory); + path, + direction +}: ArgumentsCamelCase<{ path: string; direction: SortDirection }>) { + const files = await readFiles(path); + for (const [path, file] of files) { + await writeFile(path, file.sort(direction).toXML()); + } } diff --git a/packages/cli/src/commands/update.ts b/packages/cli/src/commands/update.ts new file mode 100644 index 0000000..36f9fe6 --- /dev/null +++ b/packages/cli/src/commands/update.ts @@ -0,0 +1,47 @@ +import type { ArgumentsCamelCase, Argv } from 'yargs'; + +import { readFiles, writeFile } from '../utils/files.js'; + +export const command = 'update'; +export const description = 'Update key or value of localization string'; + +export function builder(yargs: Argv) { + return yargs + .option('path', { + alias: 'p', + desc: 'Path of file or directory to adding string', + demandOption: true, + type: 'string', + default: process.cwd() + }) + .option('oldValue', { + alias: 'o', + desc: 'Old key or value of changing string', + demandOption: true, + type: 'string' + }) + .option('newValue', { + alias: 'n', + desc: 'New key or value of changing string', + demandOption: true, + type: 'string' + }) + .usage( + `\nExample:\n ${command} --path "path/to/file/or/directory" --old "some_key_or_value" --new "some_new_key_or_value"` + ); +} + +export async function handler({ + oldValue, + newValue, + path +}: ArgumentsCamelCase<{ + oldValue: string; + newValue: string; + path: string; +}>) { + const files = await readFiles(path); + for (const [path, file] of files) { + await writeFile(path, file.update({ oldValue, newValue }).toXML()); + } +} diff --git a/packages/cli/src/utils/files.ts b/packages/cli/src/utils/files.ts new file mode 100644 index 0000000..75756d4 --- /dev/null +++ b/packages/cli/src/utils/files.ts @@ -0,0 +1,30 @@ +import fs from 'node:fs/promises'; +import { XmlLocales } from 'xml-locales'; + +async function scanPath(path: string): Promise { + const statPath = await fs.stat(path); + + if (statPath.isDirectory()) { + const directory = await fs.readdir(path); + return directory.filter((dir) => dir.endsWith('.xml')); + } + + return [path]; +} + +export async function readFiles(path: string): Promise<[string, XmlLocales][]> { + const paths = await scanPath(path); + const files: [string, XmlLocales][] = []; + + for (const path of paths) { + const xmlFile = await fs.readFile(path, 'utf-8'); + const xmlLocales = new XmlLocales(xmlFile); + files.push([path, xmlLocales]); + } + + return files; +} + +export async function writeFile(path: string, content: string) { + await fs.writeFile(path, content, 'utf-8'); +} diff --git a/packages/cli/src/utils/queries.ts b/packages/cli/src/utils/prompts.ts similarity index 100% rename from packages/cli/src/utils/queries.ts rename to packages/cli/src/utils/prompts.ts diff --git a/packages/xml-locales/src/modifiers/update.ts b/packages/xml-locales/src/modifiers/update.ts index dc6ac2c..33caf31 100644 --- a/packages/xml-locales/src/modifiers/update.ts +++ b/packages/xml-locales/src/modifiers/update.ts @@ -2,21 +2,21 @@ import { XmlJsonData } from '../utils/xml.js'; export function updateXmlNode( xmlData: XmlJsonData, - oldKeyValue: string, - newKeyValue: string + oldValue: string, + newValue: string ): XmlJsonData { const replacedNodes = xmlData.resources.string.map((element) => { - if (element.key_name === oldKeyValue) { + if (element.key_name === oldValue) { return { ...element, - key_name: newKeyValue + key_name: newValue }; } - if (element['#text'] === oldKeyValue) { + if (element['#text'] === oldValue) { return { ...element, - '#text': newKeyValue + '#text': newValue }; } diff --git a/packages/xml-locales/src/utils/types.ts b/packages/xml-locales/src/utils/types.ts index 1314b66..e5766bc 100644 --- a/packages/xml-locales/src/utils/types.ts +++ b/packages/xml-locales/src/utils/types.ts @@ -18,9 +18,9 @@ export interface AddOptions { value: string; } -export interface UpdateKeyOptions { - oldKey: string; - newKey: string; +export interface UpdateOptions { + oldValue: string; + newValue: string; } export type SortDirection = 'asc' | 'desc'; diff --git a/packages/xml-locales/src/xml-locales.ts b/packages/xml-locales/src/xml-locales.ts index 6236264..0185e6d 100644 --- a/packages/xml-locales/src/xml-locales.ts +++ b/packages/xml-locales/src/xml-locales.ts @@ -9,7 +9,7 @@ import { XmlJsonData, XmlParser } from './utils/xml.js'; import type { AddOptions, SortDirection, - UpdateKeyOptions, + UpdateOptions, XmlDataTypes } from './utils/types.js'; import type { XmlConstructor } from './utils/xml.js'; @@ -54,8 +54,12 @@ export class XmlLocales { return this.newInstance(); } - updateKey(options: UpdateKeyOptions): XmlLocales { - this.xmlData = updateXmlNode(this.xmlData, options.oldKey, options.newKey); + update(options: UpdateOptions): XmlLocales { + this.xmlData = updateXmlNode( + this.xmlData, + options.oldValue, + options.newValue + ); return this.newInstance(); } From ef6443b6fa8fc1975df4bb99a89975c899717f27 Mon Sep 17 00:00:00 2001 From: Vitalij Ryndin Date: Thu, 23 Nov 2023 22:02:42 +0800 Subject: [PATCH 3/4] chore: remove unused deps from cli --- packages/cli/package.json | 2 -- pnpm-lock.yaml | 6 ------ 2 files changed, 8 deletions(-) diff --git a/packages/cli/package.json b/packages/cli/package.json index 0dc53ba..7e6cd39 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -28,8 +28,6 @@ "dependencies": { "@inquirer/prompts": "3.3.0", "@types/yargs": "17.0.32", - "fast-xml-parser": "^4.3.2", - "xml-formatter": "^3.6.0", "xml-locales": "workspace:0.0.3", "yargs": "17.7.2" } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index fcc391f..c3cd6ce 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -50,12 +50,6 @@ importers: '@types/yargs': specifier: 17.0.32 version: 17.0.32 - fast-xml-parser: - specifier: ^4.3.2 - version: 4.3.2 - xml-formatter: - specifier: ^3.6.0 - version: 3.6.0 xml-locales: specifier: workspace:0.0.3 version: link:../xml-locales From f2b9e6ed780559c53ad7649df0e4589807a5ad0e Mon Sep 17 00:00:00 2001 From: Vitalij Ryndin Date: Thu, 23 Nov 2023 22:16:00 +0800 Subject: [PATCH 4/4] fix: xml resources is now always an array --- packages/xml-locales/src/xml-locales.ts | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/packages/xml-locales/src/xml-locales.ts b/packages/xml-locales/src/xml-locales.ts index 0185e6d..9948110 100644 --- a/packages/xml-locales/src/xml-locales.ts +++ b/packages/xml-locales/src/xml-locales.ts @@ -36,7 +36,14 @@ export class XmlLocales { } if (xmlData) { - this.xmlData = this.xmlParser.xmlToJson(xmlData); + const parsedXml = this.xmlParser.xmlToJson(xmlData); + if (Array.isArray(parsedXml.resources.string)) { + this.xmlData = parsedXml; + } else { + this.xmlData = new XmlJsonData({ + resources: { string: [parsedXml.resources.string] } + }); + } return; }