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"