diff --git a/.pnp.cjs b/.pnp.cjs index 5d2279875..e011a9d8c 100755 --- a/.pnp.cjs +++ b/.pnp.cjs @@ -25,6 +25,10 @@ const RAW_RUNTIME_STATE = "name": "@atls-ui-generators/input",\ "reference": "workspace:generators/input"\ },\ + {\ + "name": "@atls-ui-generators/locales",\ + "reference": "workspace:generators/locales"\ + },\ {\ "name": "@atls-ui-generators/utils",\ "reference": "workspace:generators/utils"\ @@ -306,6 +310,7 @@ const RAW_RUNTIME_STATE = ["@atls-ui-generators/button", ["workspace:generators/button"]],\ ["@atls-ui-generators/icons", ["workspace:generators/icons"]],\ ["@atls-ui-generators/input", ["workspace:generators/input"]],\ + ["@atls-ui-generators/locales", ["workspace:generators/locales"]],\ ["@atls-ui-generators/utils", ["workspace:generators/utils"]],\ ["@atls-ui-parts/autocomplete", ["virtual:8381890b8511aa50e898a623e8465ca96dd655e72313751f9b1a345f18667b4568ac277502099f1ca2312635f21b6b32a24edcdf15c982ed301584a66d8eb4c6#workspace:ui-parts/autocomplete", "workspace:ui-parts/autocomplete"]],\ ["@atls-ui-parts/avatar", ["virtual:1196f39950bf2b0a316261deeb4310a00ba305e04a5bf622f4e9e1d8df591de5ee1b8d8816cd30ff7b60de58948220af28d2cc7d3c7d56ab050903d1e8a62ce0#workspace:ui-parts/avatar", "virtual:269db25ff15e150519c32b592d49982dffad5b740f8cce135d98226664559a13ebece62873ae2592752c22f1382bc5dc1bd84e7aac7abaacacd8dc810761fc25#workspace:ui-parts/avatar", "workspace:ui-parts/avatar"]],\ @@ -1103,6 +1108,23 @@ const RAW_RUNTIME_STATE = "linkType": "SOFT"\ }]\ ]],\ + ["@atls-ui-generators/locales", [\ + ["workspace:generators/locales", {\ + "packageLocation": "./generators/locales/",\ + "packageDependencies": [\ + ["@atls-ui-generators/locales", "workspace:generators/locales"],\ + ["@atls/config-prettier", "npm:0.0.5"],\ + ["@atls/prettier-plugin", "npm:0.0.7"],\ + ["@babel/standalone", "npm:7.22.20"],\ + ["@types/babel__standalone", "npm:7.1.6"],\ + ["@types/prettier", "npm:2.7.3"],\ + ["camelcase", "npm:6.3.0"],\ + ["commander", "npm:9.5.0"],\ + ["prettier", "npm:2.8.8"]\ + ],\ + "linkType": "SOFT"\ + }]\ + ]],\ ["@atls-ui-generators/utils", [\ ["workspace:generators/utils", {\ "packageLocation": "./generators/utils/",\ diff --git a/generators/locales/README.md b/generators/locales/README.md new file mode 100644 index 000000000..fc73cac2c --- /dev/null +++ b/generators/locales/README.md @@ -0,0 +1,74 @@ +# LOCALES-GENERATOR + +Скрипт для автоматической генерации переводов `react-inl`. + +Создаёт на уровне энтрипоинта `locales/[localName].json` файл, содержащий все переводы энтрипоинта на основе `id` и `defaultMessages` + +Для работы обязательно указывать в `formatMessage` или `FormattedMessage` уникальный `id` и текст перевода в `defaultMessages`. + +Пример валидного компонента: + +```html + + + + +``` + +--- + +## Установка + +1. В каждый энтрипоинт, где используются переводы, устанавливаем пакет: `yarn add -D @atls-ui-generators/locales` +2. Добавляем в корневой `package.json` проекта следующий скрипт: + ```json + { + "scripts": { + "postinstall": "yarn workspaces changed foreach --parallel run generate-locales" + } + } + ``` +3. Вносим изменения в энтрипоинты и вызываем `yarn postinstall` из корня проекта. + +_\* достаточно изменить хотя бы один компонент, например фрагмент, в каждом энтрипоинте_ + +--- + +## Запуск + +Вызов `yarn postinstall` из корня проекта вызовет генерацию переводов для энтрипоинтов, если в них произошли изменения. + +Вызов `yarn generate-locales` из энтрипоинта вызовет генерацию для исходного энтрипоинта. + +После успешной генерации в проекте должны сгенерироваться папки `locales`, содержащие актуальные переводы. + +### Входные аргументы + +`generate-locales` принимает следующие аргументы: + +1. **componentPaths** - Относительный список путей от `package.json` энтрипоинта по которым будет производится поиск переводов \*_необязательный_. + - Принимается неограниченное кол-во аргументов. "generate-locales path1 path2 ...pathN" + - Если не указать **componentPaths** , скрипт будет проходить по фрагментам и страницам текущего энтрипоинта +2. **localName** - Название локали \*_необязательный_. + - Указывается после `-out=` + - Если не указывать **localName**, то умолчанию берётся локаль `ru` + +Если необходимо вызвать скрипт с параметрами отличными от исходных, то в энтрипоинт необходимо добавить новый скрипт. + +Пример расширенного скрипта: + +```json +{ + "scripts": { + "generate-locales": "generate-locales ../test-folder --out=en" + } +} +``` + +--- + +# Changelog + +--- diff --git a/generators/locales/package.json b/generators/locales/package.json new file mode 100644 index 000000000..d6639a1c7 --- /dev/null +++ b/generators/locales/package.json @@ -0,0 +1,34 @@ +{ + "name": "@atls-ui-generators/locales", + "version": "0.0.0", + "license": "BSD-3-Clause", + "main": "src/index.ts", + "bin": { + "generate-locales": "dist/generator.js" + }, + "files": [ + "dist" + ], + "scripts": { + "build": "yarn library build", + "prepack": "yarn run build", + "postpack": "rm -rf dist" + }, + "dependencies": { + "@atls/config-prettier": "0.0.5", + "@atls/prettier-plugin": "0.0.7", + "@babel/standalone": "7.22.20", + "camelcase": "6.3.0", + "commander": "9.5.0", + "prettier": "2.8.8" + }, + "devDependencies": { + "@types/babel__standalone": "7.1.6", + "@types/prettier": "2.7.3" + }, + "publishConfig": { + "access": "public", + "main": "dist/index.js", + "typings": "dist/index.d.ts" + } +} diff --git a/generators/locales/src/generator.ts b/generators/locales/src/generator.ts new file mode 100644 index 000000000..1a1ce1940 --- /dev/null +++ b/generators/locales/src/generator.ts @@ -0,0 +1,22 @@ +import { defaultPaths } from './locales-generator.constants' +import { mergeLocales } from './merge-locales' +import { processDirectory } from './process-directory' + +const allLocales = [] +let outputFile = 'ru' +const argPaths: string[] = [] + +// @ts-ignore +process.argv.slice(2).forEach((arg) => { + if (!arg.startsWith('--out=')) { + argPaths.push(arg) + } else { + // eslint-disable-next-line prefer-destructuring + outputFile = arg.split('=')[1] + } +}) +const paths = argPaths.length ? argPaths : defaultPaths + +paths.forEach((path) => processDirectory(path, 'locales', allLocales, outputFile)) + +mergeLocales(allLocales, `./locales/${outputFile}.json`) diff --git a/generators/locales/src/index.ts b/generators/locales/src/index.ts new file mode 100644 index 000000000..3845dca97 --- /dev/null +++ b/generators/locales/src/index.ts @@ -0,0 +1 @@ +export * from './generator' diff --git a/generators/locales/src/locales-generator.constants.ts b/generators/locales/src/locales-generator.constants.ts new file mode 100644 index 000000000..b9f1e0beb --- /dev/null +++ b/generators/locales/src/locales-generator.constants.ts @@ -0,0 +1 @@ +export const defaultPaths = ['../../fragments', '../../pages'] diff --git a/generators/locales/src/merge-locales/index.ts b/generators/locales/src/merge-locales/index.ts new file mode 100644 index 000000000..697872a10 --- /dev/null +++ b/generators/locales/src/merge-locales/index.ts @@ -0,0 +1 @@ +export * from './merge-locales' diff --git a/generators/locales/src/merge-locales/merge-locales.interfaces.ts b/generators/locales/src/merge-locales/merge-locales.interfaces.ts new file mode 100644 index 000000000..8f82ec0c1 --- /dev/null +++ b/generators/locales/src/merge-locales/merge-locales.interfaces.ts @@ -0,0 +1 @@ +export type MergeLocalesType = (files: string[], outputPath: string) => void diff --git a/generators/locales/src/merge-locales/merge-locales.ts b/generators/locales/src/merge-locales/merge-locales.ts new file mode 100644 index 000000000..f83a89077 --- /dev/null +++ b/generators/locales/src/merge-locales/merge-locales.ts @@ -0,0 +1,26 @@ +import { existsSync } from 'fs' +import { readFileSync } from 'fs' +import { mkdirSync } from 'fs' +import { writeFileSync } from 'fs' +import { dirname } from 'path' + +import { MergeLocalesType } from './merge-locales.interfaces' + +export const mergeLocales: MergeLocalesType = (files, outputPath) => { + if (!files.length) return + + const mergedLocales = {} + files.forEach((file) => { + if (existsSync(file)) { + const content = JSON.parse(readFileSync(file, 'utf8')) + Object.assign(mergedLocales, content) + } + }) + const directory = dirname(outputPath) + + if (!existsSync(directory)) { + mkdirSync(directory, { recursive: true }) + } + + writeFileSync(outputPath, JSON.stringify(mergedLocales, null, 2), 'utf8') +} diff --git a/generators/locales/src/process-directory/index.ts b/generators/locales/src/process-directory/index.ts new file mode 100644 index 000000000..0997d7c4e --- /dev/null +++ b/generators/locales/src/process-directory/index.ts @@ -0,0 +1 @@ +export * from './process-directory' diff --git a/generators/locales/src/process-directory/process-directory.interfaces.ts b/generators/locales/src/process-directory/process-directory.interfaces.ts new file mode 100644 index 000000000..8e5bf9d92 --- /dev/null +++ b/generators/locales/src/process-directory/process-directory.interfaces.ts @@ -0,0 +1,6 @@ +export type ProcessDirectoryType = ( + startPath: string, + folderName: string, + allLocales: string[], + outputLocale: string +) => void diff --git a/generators/locales/src/process-directory/process-directory.ts b/generators/locales/src/process-directory/process-directory.ts new file mode 100644 index 000000000..c5b80572d --- /dev/null +++ b/generators/locales/src/process-directory/process-directory.ts @@ -0,0 +1,37 @@ +/* eslint-disable no-console */ + +import { execSync } from 'child_process' +import { existsSync } from 'fs' +import { readdirSync } from 'fs' +import { join } from 'path' + +import { ProcessDirectoryType } from './process-directory.interfaces' +import { removeEmptyLocale } from '../remove-empty-locale' + +export const processDirectory: ProcessDirectoryType = ( + startPath, + folderName, + allLocales, + outputLocale +) => { + if (!existsSync(startPath)) { + console.error(new Error(`No directory ${startPath}`)) + return + } + + const directories = readdirSync(startPath, { withFileTypes: true }) + .filter((dirent) => dirent.isDirectory()) + .map((dirent) => dirent.name) + + directories.forEach((dir) => { + const localePath = join(startPath, dir) + if (existsSync(localePath)) { + const outputFilePath = `${localePath}/${folderName}/${outputLocale}.json` + const command = `formatjs extract "${localePath}/**/*.tsx" --out-file "${outputFilePath}" --format simple` + console.log(`Running: ${command}`) + execSync(command, { stdio: 'inherit' }) + + removeEmptyLocale(outputFilePath, localePath, folderName, allLocales) + } + }) +} diff --git a/generators/locales/src/remove-empty-locale/index.ts b/generators/locales/src/remove-empty-locale/index.ts new file mode 100644 index 000000000..eb4114d83 --- /dev/null +++ b/generators/locales/src/remove-empty-locale/index.ts @@ -0,0 +1 @@ +export * from './remove-empty-locale' diff --git a/generators/locales/src/remove-empty-locale/remove-empty-locale.interfaces.ts b/generators/locales/src/remove-empty-locale/remove-empty-locale.interfaces.ts new file mode 100644 index 000000000..24bbcbb13 --- /dev/null +++ b/generators/locales/src/remove-empty-locale/remove-empty-locale.interfaces.ts @@ -0,0 +1,6 @@ +export type RemoveEmptyLocaleType = ( + outputFilePath: string, + localePath: string, + folderName: string, + allLocales: string[] +) => void diff --git a/generators/locales/src/remove-empty-locale/remove-empty-locale.ts b/generators/locales/src/remove-empty-locale/remove-empty-locale.ts new file mode 100644 index 000000000..98d18e6ab --- /dev/null +++ b/generators/locales/src/remove-empty-locale/remove-empty-locale.ts @@ -0,0 +1,28 @@ +import { existsSync } from 'fs' +import { readFileSync } from 'fs' +import { readdirSync } from 'fs' +import { rmSync } from 'fs' +import { unlinkSync } from 'fs' +import { join } from 'path' + +import { RemoveEmptyLocaleType } from './remove-empty-locale.interfaces' + +export const removeEmptyLocale: RemoveEmptyLocaleType = ( + outputFilePath, + localePath, + folderName, + allLocales +) => { + if (existsSync(outputFilePath)) { + const content = readFileSync(outputFilePath, 'utf8').trim() + if (content === '{}' || content === '{\n}') { + unlinkSync(outputFilePath) + + if (readdirSync(join(localePath, folderName)).length === 0) { + rmSync(join(localePath, folderName), { recursive: true, force: true }) + } + } else { + allLocales.push(outputFilePath) + } + } +} diff --git a/generators/locales/src/unit/locales-generator.test.ts b/generators/locales/src/unit/locales-generator.test.ts new file mode 100644 index 000000000..abd7e94c7 --- /dev/null +++ b/generators/locales/src/unit/locales-generator.test.ts @@ -0,0 +1,42 @@ +import { existsSync } from 'fs' +import { readFileSync } from 'fs' + +import { mergeLocales } from '../merge-locales' +import { removeEmptyLocale } from '../remove-empty-locale' + +describe('Locale processing script', () => { + describe('mergeLocales', () => { + const outputFileName = 'merged' + const outputPath = `generators/locales/src/unit/locales/${outputFileName}.json` + + it('should merge locale files into one', () => { + mergeLocales( + [ + 'generators/locales/src/unit/mocks/test.json', + 'generators/locales/src/unit/mocks/test1.json', + ], + outputPath + ) + + const mergedContent = JSON.parse(readFileSync(outputPath, 'utf8')) + expect(mergedContent).toEqual({ + test_id: 'test_message', + test_id1: 'test_message1', + }) + }) + it('should remove empty locale file', () => { + mergeLocales([''], outputPath) + + const allLocales = [] + const mergedContent = JSON.parse(readFileSync(outputPath, 'utf8')) + expect(mergedContent).toEqual({}) + removeEmptyLocale( + 'generators/locales/src/unit/locales/merged.json', + 'generators/locales/src/unit', + 'locales', + allLocales + ) + expect(existsSync(outputPath)).toEqual(false) + }) + }) +}) diff --git a/generators/locales/src/unit/mocks/test.json b/generators/locales/src/unit/mocks/test.json new file mode 100644 index 000000000..5ec83e8e9 --- /dev/null +++ b/generators/locales/src/unit/mocks/test.json @@ -0,0 +1,3 @@ +{ + "test_id": "test_message" +} diff --git a/generators/locales/src/unit/mocks/test1.json b/generators/locales/src/unit/mocks/test1.json new file mode 100644 index 000000000..316a6393a --- /dev/null +++ b/generators/locales/src/unit/mocks/test1.json @@ -0,0 +1,3 @@ +{ + "test_id1": "test_message1" +} diff --git a/yarn.lock b/yarn.lock index 37a66ee84..0d3c7ed1e 100644 --- a/yarn.lock +++ b/yarn.lock @@ -481,6 +481,23 @@ __metadata: languageName: unknown linkType: soft +"@atls-ui-generators/locales@workspace:generators/locales": + version: 0.0.0-use.local + resolution: "@atls-ui-generators/locales@workspace:generators/locales" + dependencies: + "@atls/config-prettier": "npm:0.0.5" + "@atls/prettier-plugin": "npm:0.0.7" + "@babel/standalone": "npm:7.22.20" + "@types/babel__standalone": "npm:7.1.6" + "@types/prettier": "npm:2.7.3" + camelcase: "npm:6.3.0" + commander: "npm:9.5.0" + prettier: "npm:2.8.8" + bin: + generate-locales: dist/generator.js + languageName: unknown + linkType: soft + "@atls-ui-generators/utils@workspace:*, @atls-ui-generators/utils@workspace:generators/utils": version: 0.0.0-use.local resolution: "@atls-ui-generators/utils@workspace:generators/utils"