├── .eslintignore ├── src ├── translate │ └── index.ts ├── generated │ ├── telegram.ts │ ├── iso_9_1954.ts │ ├── iso_9_1968.ts │ ├── scientific.ts │ ├── ala_lc.ts │ ├── ala_lc_alt.ts │ ├── gost_16876.ts │ ├── iso_9_1968_alt.ts │ ├── gost_779.ts │ ├── bs_2979_alt.ts │ ├── gost_16876_alt.ts │ ├── gost_52535.ts │ ├── gost_779_alt.ts │ ├── TransliterationSchema.ts │ ├── ungegn_1987.ts │ ├── yandex_money.ts │ ├── mvd_310.ts │ ├── bs_2979.ts │ ├── _definitions.ts │ ├── icao_doc_9303.ts │ ├── gost_7034.ts │ ├── mvd_310_fr.ts │ ├── yandex_maps.ts │ ├── gost_52290.ts │ ├── mvd_782.ts │ ├── wikipedia.ts │ ├── mosmetro.ts │ ├── bgn_pcgn_alt.ts │ └── bgn_pcgn.ts ├── schemas.ts ├── engine.ts ├── schema.ts ├── index.ts └── mapping.ts ├── .prettierrc ├── .gitignore ├── integration-test ├── webpack │ ├── src │ │ └── index.js │ └── webpack.config.js └── bundles.test.ts ├── jest.config.js ├── babel.config.js ├── tsconfig.json ├── .eslintrc.yml ├── tsconfig.umd.json ├── tsconfig.esm.json ├── webpack.config.js ├── .devcontainer ├── Dockerfile └── devcontainer.json ├── LICENSE ├── .github └── workflows │ ├── checks-on-push.yml │ └── checks-and-publish-on-release.yml ├── test ├── translate.test.ts ├── schemas.test.ts └── mapping.test.ts ├── CHANGELOG.md ├── package.json ├── scripts └── generate.ts └── README.md /.eslintignore: -------------------------------------------------------------------------------- 1 | dist 2 | coverage 3 | node_modules 4 | -------------------------------------------------------------------------------- /src/translate/index.ts: -------------------------------------------------------------------------------- 1 | export { translate } from "../engine"; 2 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "printWidth": 100, 3 | "tabWidth": 4 4 | } 5 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .idea 2 | node_modules 3 | dist 4 | coverage 5 | .data 6 | .tmp 7 | -------------------------------------------------------------------------------- /integration-test/webpack/src/index.js: -------------------------------------------------------------------------------- 1 | import { MOSMETRO, translate } from "../../../"; 2 | 3 | console.log(translate("Привет, мир!", MOSMETRO)); 4 | -------------------------------------------------------------------------------- /jest.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | clearMocks: true, 3 | collectCoverage: true, 4 | coverageDirectory: "coverage", 5 | coverageProvider: "v8", 6 | }; 7 | -------------------------------------------------------------------------------- /babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | presets: [ 3 | ["@babel/preset-env", { targets: { node: "current" } }], 4 | "@babel/preset-typescript", 5 | ], 6 | }; 7 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es5", 4 | "module": "CommonJS", 5 | "moduleResolution": "node", 6 | "lib": ["es2015", "es2016", "es2017", "es2020", "dom"], 7 | "downlevelIteration": true, 8 | "declaration": true, 9 | "strict": true, 10 | "outDir": "./dist/cjs", 11 | "skipLibCheck": true 12 | }, 13 | "include": ["src"], 14 | "exclude": ["node_modules", "test"] 15 | } 16 | -------------------------------------------------------------------------------- /.eslintrc.yml: -------------------------------------------------------------------------------- 1 | env: 2 | es2021: true 3 | node: true 4 | extends: 5 | - eslint:recommended 6 | - plugin:@typescript-eslint/recommended 7 | parser: '@typescript-eslint/parser' 8 | parserOptions: 9 | ecmaVersion: 13 10 | sourceType: module 11 | plugins: 12 | - '@typescript-eslint' 13 | rules: 14 | indent: 15 | - error 16 | - 4 17 | linebreak-style: 18 | - error 19 | - unix 20 | quotes: 21 | - error 22 | - double 23 | - avoidEscape: true 24 | semi: 25 | - error 26 | - always 27 | -------------------------------------------------------------------------------- /integration-test/webpack/webpack.config.js: -------------------------------------------------------------------------------- 1 | // eslint-disable-next-line @typescript-eslint/no-var-requires 2 | const path = require("path"); 3 | 4 | /** 5 | * @type {import("webpack").Configuration} 6 | */ 7 | module.exports = { 8 | entry: path.resolve(__dirname, "src/index.js"), 9 | mode: "development", 10 | devtool: "source-map", 11 | output: { 12 | path: path.resolve(__dirname, "dist"), 13 | filename: "bundle.js", 14 | }, 15 | optimization: { 16 | minimize: true, 17 | innerGraph: true, 18 | usedExports: true, 19 | }, 20 | }; 21 | -------------------------------------------------------------------------------- /tsconfig.umd.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es5", 4 | "module": "CommonJS", 5 | "moduleResolution": "node", 6 | "lib": [ 7 | "es2015", 8 | "es2016", 9 | "es2017", 10 | "es2020", 11 | "dom" 12 | ], 13 | "downlevelIteration": true, 14 | "declaration": true, 15 | "strict": true, 16 | "outDir": "./.tmp", 17 | "skipLibCheck": true 18 | }, 19 | "include": [ 20 | "src" 21 | ], 22 | "exclude": [ 23 | "node_modules", 24 | "test" 25 | ] 26 | } -------------------------------------------------------------------------------- /tsconfig.esm.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ESNext", 4 | "module": "ESNext", 5 | "moduleResolution": "node", 6 | "lib": [ 7 | "es2015", 8 | "es2016", 9 | "es2017", 10 | "es2020", 11 | "dom" 12 | ], 13 | "sourceMap": true, 14 | "downlevelIteration": true, 15 | "declaration": true, 16 | "strict": true, 17 | "outDir": "./dist/esm", 18 | "skipLibCheck": true 19 | }, 20 | "include": [ 21 | "src" 22 | ], 23 | "exclude": [ 24 | "node_modules", 25 | "test" 26 | ] 27 | } -------------------------------------------------------------------------------- /webpack.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | entry: "./src/index.ts", 3 | mode: "production", 4 | 5 | optimization: { 6 | minimize: true, 7 | }, 8 | 9 | module: { 10 | rules: [ 11 | { 12 | test: /\.tsx?$/, 13 | use: { 14 | loader: "ts-loader", 15 | options: { 16 | configFile: "tsconfig.umd.json", 17 | }, 18 | }, 19 | exclude: /node_modules/, 20 | }, 21 | ], 22 | }, 23 | resolve: { 24 | extensions: [".tsx", ".ts", ".js"], 25 | }, 26 | 27 | output: { 28 | filename: "umd/iuliia.js", 29 | library: { 30 | type: "umd", 31 | name: "iuliia", 32 | }, 33 | globalObject: "this", 34 | }, 35 | }; 36 | -------------------------------------------------------------------------------- /.devcontainer/Dockerfile: -------------------------------------------------------------------------------- 1 | # See here for image contents: https://github.com/microsoft/vscode-dev-containers/tree/v0.209.6/containers/javascript-node/.devcontainer/base.Dockerfile 2 | 3 | # [Choice] Node.js version (use -bullseye variants on local arm64/Apple Silicon): 16, 14, 12, 16-bullseye, 14-bullseye, 12-bullseye, 16-buster, 14-buster, 12-buster 4 | ARG VARIANT="16-bullseye" 5 | FROM mcr.microsoft.com/vscode/devcontainers/javascript-node:0-${VARIANT} 6 | 7 | # [Optional] Uncomment this section to install additional OS packages. 8 | # RUN apt-get update && export DEBIAN_FRONTEND=noninteractive \ 9 | # && apt-get -y install --no-install-recommends 10 | 11 | # [Optional] Uncomment if you want to install an additional version of node using nvm 12 | # ARG EXTRA_NODE_VERSION=10 13 | # RUN su node -c "source /usr/local/share/nvm/nvm.sh && nvm install ${EXTRA_NODE_VERSION}" 14 | 15 | # [Optional] Uncomment if you want to install more global node modules 16 | # RUN su node -c "npm install -g " 17 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Anton Zhiyanov 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /src/generated/telegram.ts: -------------------------------------------------------------------------------- 1 | import { TransliterationSchema } from "./TransliterationSchema"; 2 | 3 | export default { 4 | name: "telegram", 5 | description: "Telegram transliteration schema", 6 | url: "https://iuliia.ru/telegram/", 7 | mapping: { 8 | а: "a", 9 | б: "b", 10 | в: "v", 11 | г: "g", 12 | д: "d", 13 | е: "e", 14 | ё: "e", 15 | ж: "j", 16 | з: "z", 17 | и: "i", 18 | й: "i", 19 | к: "k", 20 | л: "l", 21 | м: "m", 22 | н: "n", 23 | о: "o", 24 | п: "p", 25 | р: "r", 26 | с: "s", 27 | т: "t", 28 | у: "u", 29 | ф: "f", 30 | х: "h", 31 | ц: "c", 32 | ч: "ch", 33 | ш: "sh", 34 | щ: "sc", 35 | ъ: "", 36 | ы: "y", 37 | ь: "", 38 | э: "e", 39 | ю: "iu", 40 | я: "ia", 41 | }, 42 | prev_mapping: null, 43 | next_mapping: null, 44 | ending_mapping: null, 45 | samples: [ 46 | [ 47 | "Юлия, съешь ещё этих мягких французских булок из Йошкар-Олы, да выпей алтайского чаю", 48 | "Iuliia, sesh esce etih miagkih francuzskih bulok iz Ioshkar-Oly, da vypei altaiskogo chaiu", 49 | ], 50 | ], 51 | } as TransliterationSchema; 52 | -------------------------------------------------------------------------------- /src/generated/iso_9_1954.ts: -------------------------------------------------------------------------------- 1 | import { TransliterationSchema } from "./TransliterationSchema"; 2 | 3 | export default { 4 | name: "iso_9_1954", 5 | description: "ISO/R 9:1954 transliteration schema", 6 | url: "https://iuliia.ru/iso-9-1954/", 7 | mapping: { 8 | а: "a", 9 | б: "b", 10 | в: "v", 11 | г: "g", 12 | д: "d", 13 | е: "e", 14 | ё: "ë", 15 | ж: "ž", 16 | з: "z", 17 | и: "i", 18 | й: "j", 19 | к: "k", 20 | л: "l", 21 | м: "m", 22 | н: "n", 23 | о: "o", 24 | п: "p", 25 | р: "r", 26 | с: "s", 27 | т: "t", 28 | у: "u", 29 | ф: "f", 30 | х: "h", 31 | ц: "c", 32 | ч: "č", 33 | ш: "š", 34 | щ: "šč", 35 | ъ: '"', 36 | ы: "y", 37 | ь: "ʹ", 38 | э: "ė", 39 | ю: "ju", 40 | я: "ja", 41 | }, 42 | prev_mapping: null, 43 | next_mapping: null, 44 | ending_mapping: null, 45 | samples: [ 46 | [ 47 | "Юлия, съешь ещё этих мягких французских булок из Йошкар-Олы, да выпей алтайского чаю", 48 | 'Julija, s"ešʹ eščë ėtih mjagkih francuzskih bulok iz Joškar-Oly, da vypej altajskogo čaju', 49 | ], 50 | ], 51 | } as TransliterationSchema; 52 | -------------------------------------------------------------------------------- /src/generated/iso_9_1968.ts: -------------------------------------------------------------------------------- 1 | import { TransliterationSchema } from "./TransliterationSchema"; 2 | 3 | export default { 4 | name: "iso_9_1968", 5 | description: "ISO/R 9:1968 transliteration schema", 6 | url: "https://iuliia.ru/iso-9-1968/", 7 | mapping: { 8 | а: "a", 9 | б: "b", 10 | в: "v", 11 | г: "g", 12 | д: "d", 13 | е: "e", 14 | ё: "ë", 15 | ж: "ž", 16 | з: "z", 17 | и: "i", 18 | й: "j", 19 | к: "k", 20 | л: "l", 21 | м: "m", 22 | н: "n", 23 | о: "o", 24 | п: "p", 25 | р: "r", 26 | с: "s", 27 | т: "t", 28 | у: "u", 29 | ф: "f", 30 | х: "h", 31 | ц: "c", 32 | ч: "č", 33 | ш: "š", 34 | щ: "šč", 35 | ъ: "ʺ", 36 | ы: "y", 37 | ь: "ʹ", 38 | э: "ė", 39 | ю: "ju", 40 | я: "ja", 41 | }, 42 | prev_mapping: null, 43 | next_mapping: null, 44 | ending_mapping: null, 45 | samples: [ 46 | [ 47 | "Юлия, съешь ещё этих мягких французских булок из Йошкар-Олы, да выпей алтайского чаю", 48 | "Julija, sʺešʹ eščë ėtih mjagkih francuzskih bulok iz Joškar-Oly, da vypej altajskogo čaju", 49 | ], 50 | ], 51 | } as TransliterationSchema; 52 | -------------------------------------------------------------------------------- /src/generated/scientific.ts: -------------------------------------------------------------------------------- 1 | import { TransliterationSchema } from "./TransliterationSchema"; 2 | 3 | export default { 4 | name: "scientific", 5 | description: "Scientific transliteration schema", 6 | url: "https://iuliia.ru/scientific/", 7 | mapping: { 8 | а: "a", 9 | б: "b", 10 | в: "v", 11 | г: "g", 12 | д: "d", 13 | е: "e", 14 | ё: "ё", 15 | ж: "ž", 16 | з: "z", 17 | и: "i", 18 | й: "j", 19 | к: "k", 20 | л: "l", 21 | м: "m", 22 | н: "n", 23 | о: "o", 24 | п: "p", 25 | р: "r", 26 | с: "s", 27 | т: "t", 28 | у: "u", 29 | ф: "f", 30 | х: "x", 31 | ц: "c", 32 | ч: "č", 33 | ш: "š", 34 | щ: "šč", 35 | ъ: "ʺ", 36 | ы: "y", 37 | ь: "ʹ", 38 | э: "è", 39 | ю: "ju", 40 | я: "ja", 41 | }, 42 | prev_mapping: null, 43 | next_mapping: null, 44 | ending_mapping: null, 45 | samples: [ 46 | [ 47 | "Юлия, съешь ещё этих мягких французских булок из Йошкар-Олы, да выпей алтайского чаю", 48 | "Julija, sʺešʹ eščё ètix mjagkix francuzskix bulok iz Joškar-Oly, da vypej altajskogo čaju", 49 | ], 50 | ], 51 | } as TransliterationSchema; 52 | -------------------------------------------------------------------------------- /src/generated/ala_lc.ts: -------------------------------------------------------------------------------- 1 | import { TransliterationSchema } from "./TransliterationSchema"; 2 | 3 | export default { 4 | name: "ala_lc", 5 | description: "ALA-LC transliteration schema.", 6 | url: "https://iuliia.ru/ala-lc/", 7 | mapping: { 8 | а: "a", 9 | б: "b", 10 | в: "v", 11 | г: "g", 12 | д: "d", 13 | е: "e", 14 | ё: "ё", 15 | ж: "zh", 16 | з: "z", 17 | и: "i", 18 | й: "ĭ", 19 | к: "k", 20 | л: "l", 21 | м: "m", 22 | н: "n", 23 | о: "o", 24 | п: "p", 25 | р: "r", 26 | с: "s", 27 | т: "t", 28 | у: "u", 29 | ф: "f", 30 | х: "kh", 31 | ц: "t͡s", 32 | ч: "ch", 33 | ш: "sh", 34 | щ: "shch", 35 | ъ: "ʺ", 36 | ы: "y", 37 | ь: "ʹ", 38 | э: "ė", 39 | ю: "i͡u", 40 | я: "i͡a", 41 | }, 42 | prev_mapping: null, 43 | next_mapping: null, 44 | ending_mapping: null, 45 | samples: [ 46 | [ 47 | "Юлия, съешь ещё этих мягких французских булок из Йошкар-Олы, да выпей алтайского чаю", 48 | "I͡ulii͡a, sʺeshʹ eshchё ėtikh mi͡agkikh frant͡suzskikh bulok iz Ĭoshkar-Oly, da vypeĭ altaĭskogo chai͡u", 49 | ], 50 | ], 51 | } as TransliterationSchema; 52 | -------------------------------------------------------------------------------- /src/generated/ala_lc_alt.ts: -------------------------------------------------------------------------------- 1 | import { TransliterationSchema } from "./TransliterationSchema"; 2 | 3 | export default { 4 | name: "ala_lc_alt", 5 | description: "ALA-LC transliteration schema.", 6 | url: "https://iuliia.ru/ala-lc/", 7 | mapping: { 8 | а: "a", 9 | б: "b", 10 | в: "v", 11 | г: "g", 12 | д: "d", 13 | е: "e", 14 | ё: "e", 15 | ж: "zh", 16 | з: "z", 17 | и: "i", 18 | й: "i", 19 | к: "k", 20 | л: "l", 21 | м: "m", 22 | н: "n", 23 | о: "o", 24 | п: "p", 25 | р: "r", 26 | с: "s", 27 | т: "t", 28 | у: "u", 29 | ф: "f", 30 | х: "kh", 31 | ц: "ts", 32 | ч: "ch", 33 | ш: "sh", 34 | щ: "shch", 35 | ъ: '"', 36 | ы: "y", 37 | ь: "'", 38 | э: "e", 39 | ю: "iu", 40 | я: "ia", 41 | }, 42 | prev_mapping: null, 43 | next_mapping: null, 44 | ending_mapping: null, 45 | samples: [ 46 | [ 47 | "Юлия, съешь ещё этих мягких французских булок из Йошкар-Олы, да выпей алтайского чаю", 48 | "Iuliia, s\"esh' eshche etikh miagkikh frantsuzskikh bulok iz Ioshkar-Oly, da vypei altaiskogo chaiu", 49 | ], 50 | ], 51 | } as TransliterationSchema; 52 | -------------------------------------------------------------------------------- /src/generated/gost_16876.ts: -------------------------------------------------------------------------------- 1 | import { TransliterationSchema } from "./TransliterationSchema"; 2 | 3 | export default { 4 | name: "gost_16876", 5 | description: "GOST 16876-71 (aka GOST 1983) transliteration schema", 6 | url: "https://iuliia.ru/gost-16876/", 7 | mapping: { 8 | а: "a", 9 | б: "b", 10 | в: "v", 11 | г: "g", 12 | д: "d", 13 | е: "e", 14 | ё: "ё", 15 | ж: "ž", 16 | з: "z", 17 | и: "i", 18 | й: "j", 19 | к: "k", 20 | л: "l", 21 | м: "m", 22 | н: "n", 23 | о: "o", 24 | п: "p", 25 | р: "r", 26 | с: "s", 27 | т: "t", 28 | у: "u", 29 | ф: "f", 30 | х: "h", 31 | ц: "c", 32 | ч: "č", 33 | ш: "š", 34 | щ: "ŝ", 35 | ъ: "ʺ", 36 | ы: "y", 37 | ь: "ʹ", 38 | э: "è", 39 | ю: "û", 40 | я: "â", 41 | }, 42 | prev_mapping: null, 43 | next_mapping: null, 44 | ending_mapping: null, 45 | samples: [ 46 | [ 47 | "Юлия, съешь ещё этих мягких французских булок из Йошкар-Олы, да выпей алтайского чаю", 48 | "Ûliâ, sʺešʹ eŝё ètih mâgkih francuzskih bulok iz Joškar-Oly, da vypej altajskogo čaû", 49 | ], 50 | ], 51 | } as TransliterationSchema; 52 | -------------------------------------------------------------------------------- /src/generated/iso_9_1968_alt.ts: -------------------------------------------------------------------------------- 1 | import { TransliterationSchema } from "./TransliterationSchema"; 2 | 3 | export default { 4 | name: "iso_9_1968_alt", 5 | description: "ISO/R 9:1968 transliteration schema", 6 | url: "https://iuliia.ru/iso-9-1968/", 7 | mapping: { 8 | а: "a", 9 | б: "b", 10 | в: "v", 11 | г: "g", 12 | д: "d", 13 | е: "e", 14 | ё: "ë", 15 | ж: "zh", 16 | з: "z", 17 | и: "y", 18 | й: "ĭ", 19 | к: "k", 20 | л: "l", 21 | м: "m", 22 | н: "n", 23 | о: "o", 24 | п: "p", 25 | р: "r", 26 | с: "s", 27 | т: "t", 28 | у: "u", 29 | ф: "f", 30 | х: "kh", 31 | ц: "ts", 32 | ч: "ch", 33 | ш: "sh", 34 | щ: "shch", 35 | ъ: "ʺ", 36 | ы: "y", 37 | ь: "ʹ", 38 | э: "ė", 39 | ю: "yu", 40 | я: "ya", 41 | }, 42 | prev_mapping: null, 43 | next_mapping: null, 44 | ending_mapping: null, 45 | samples: [ 46 | [ 47 | "Юлия, съешь ещё этих мягких французских булок из Йошкар-Олы, да выпей алтайского чаю", 48 | "Yulyya, sʺeshʹ eshchë ėtykh myagkykh frantsuzskykh bulok yz Ĭoshkar-Oly, da vypeĭ altaĭskogo chayu", 49 | ], 50 | ], 51 | } as TransliterationSchema; 52 | -------------------------------------------------------------------------------- /src/generated/gost_779.ts: -------------------------------------------------------------------------------- 1 | import { TransliterationSchema } from "./TransliterationSchema"; 2 | 3 | export default { 4 | name: "gost_779", 5 | aliases: ["iso_9_1995"], 6 | description: "GOST 7.79-2000 (aka ISO 9:1995) transliteration schema", 7 | url: "https://iuliia.ru/gost-779/", 8 | mapping: { 9 | а: "a", 10 | б: "b", 11 | в: "v", 12 | г: "g", 13 | д: "d", 14 | е: "e", 15 | ё: "ё", 16 | ж: "ž", 17 | з: "z", 18 | и: "i", 19 | й: "j", 20 | к: "k", 21 | л: "l", 22 | м: "m", 23 | н: "n", 24 | о: "o", 25 | п: "p", 26 | р: "r", 27 | с: "s", 28 | т: "t", 29 | у: "u", 30 | ф: "f", 31 | х: "h", 32 | ц: "c", 33 | ч: "č", 34 | ш: "š", 35 | щ: "ŝ", 36 | ъ: "ʺ", 37 | ы: "y", 38 | ь: "ʹ", 39 | э: "è", 40 | ю: "û", 41 | я: "â", 42 | }, 43 | prev_mapping: null, 44 | next_mapping: null, 45 | ending_mapping: null, 46 | samples: [ 47 | [ 48 | "Юлия, съешь ещё этих мягких французских булок из Йошкар-Олы, да выпей алтайского чаю", 49 | "Ûliâ, sʺešʹ eŝё ètih mâgkih francuzskih bulok iz Joškar-Oly, da vypej altajskogo čaû", 50 | ], 51 | ], 52 | } as TransliterationSchema; 53 | -------------------------------------------------------------------------------- /src/generated/bs_2979_alt.ts: -------------------------------------------------------------------------------- 1 | import { TransliterationSchema } from "./TransliterationSchema"; 2 | 3 | export default { 4 | name: "bs_2979_alt", 5 | description: "British Standard 2979:1958 transliteration schema", 6 | url: "https://iuliia.ru/bs-2979/", 7 | mapping: { 8 | а: "a", 9 | б: "b", 10 | в: "v", 11 | г: "g", 12 | д: "d", 13 | е: "e", 14 | ё: "e", 15 | ж: "zh", 16 | з: "z", 17 | и: "i", 18 | й: "i", 19 | к: "k", 20 | л: "l", 21 | м: "m", 22 | н: "n", 23 | о: "o", 24 | п: "p", 25 | р: "r", 26 | с: "s", 27 | т: "t", 28 | у: "u", 29 | ф: "f", 30 | х: "kh", 31 | ц: "ts", 32 | ч: "ch", 33 | ш: "sh", 34 | щ: "shch", 35 | ъ: '"', 36 | ы: "y", 37 | ь: "'", 38 | э: "e", 39 | ю: "yu", 40 | я: "ya", 41 | }, 42 | prev_mapping: null, 43 | next_mapping: null, 44 | ending_mapping: { ий: "y", ый: "y" }, 45 | samples: [ 46 | [ 47 | "Юлия, съешь ещё этих мягких французских булок из Йошкар-Олы, да выпей алтайского чаю", 48 | "Yuliya, s\"esh' eshche etikh myagkikh frantsuzskikh bulok iz Ioshkar-Oly, da vypei altaiskogo chayu", 49 | ], 50 | ], 51 | } as TransliterationSchema; 52 | -------------------------------------------------------------------------------- /src/generated/gost_16876_alt.ts: -------------------------------------------------------------------------------- 1 | import { TransliterationSchema } from "./TransliterationSchema"; 2 | 3 | export default { 4 | name: "gost_16876_alt", 5 | description: "GOST 16876-71 (aka GOST 1983) transliteration schema", 6 | url: "https://iuliia.ru/gost-16876/", 7 | mapping: { 8 | а: "a", 9 | б: "b", 10 | в: "v", 11 | г: "g", 12 | д: "d", 13 | е: "e", 14 | ё: "jo", 15 | ж: "zh", 16 | з: "z", 17 | и: "i", 18 | й: "jj", 19 | к: "k", 20 | л: "l", 21 | м: "m", 22 | н: "n", 23 | о: "o", 24 | п: "p", 25 | р: "r", 26 | с: "s", 27 | т: "t", 28 | у: "u", 29 | ф: "f", 30 | х: "kh", 31 | ц: "c", 32 | ч: "ch", 33 | ш: "sh", 34 | щ: "shh", 35 | ъ: '"', 36 | ы: "y", 37 | ь: "'", 38 | э: "eh", 39 | ю: "ju", 40 | я: "ja", 41 | }, 42 | prev_mapping: null, 43 | next_mapping: null, 44 | ending_mapping: null, 45 | samples: [ 46 | [ 47 | "Юлия, съешь ещё этих мягких французских булок из Йошкар-Олы, да выпей алтайского чаю", 48 | "Julija, s\"esh' eshhjo ehtikh mjagkikh francuzskikh bulok iz Jjoshkar-Oly, da vypejj altajjskogo chaju", 49 | ], 50 | ], 51 | } as TransliterationSchema; 52 | -------------------------------------------------------------------------------- /src/generated/gost_52535.ts: -------------------------------------------------------------------------------- 1 | import { TransliterationSchema } from "./TransliterationSchema"; 2 | 3 | export default { 4 | name: "gost_52535", 5 | description: "GOST R 52535.1-2006 transliteration schema", 6 | url: "https://iuliia.ru/gost-52535/", 7 | mapping: { 8 | а: "a", 9 | б: "b", 10 | в: "v", 11 | г: "g", 12 | д: "d", 13 | е: "e", 14 | ё: "e", 15 | ж: "zh", 16 | з: "z", 17 | и: "i", 18 | й: "i", 19 | к: "k", 20 | л: "l", 21 | м: "m", 22 | н: "n", 23 | о: "o", 24 | п: "p", 25 | р: "r", 26 | с: "s", 27 | т: "t", 28 | у: "u", 29 | ф: "f", 30 | х: "kh", 31 | ц: "tc", 32 | ч: "ch", 33 | ш: "sh", 34 | щ: "shch", 35 | ъ: "", 36 | ы: "y", 37 | ь: "", 38 | э: "e", 39 | ю: "iu", 40 | я: "ia", 41 | }, 42 | prev_mapping: null, 43 | next_mapping: null, 44 | ending_mapping: null, 45 | samples: [ 46 | ["Юлия Щеглова", "Iuliia Shcheglova"], 47 | [ 48 | "Юлия, съешь ещё этих мягких французских булок из Йошкар-Олы, да выпей алтайского чаю", 49 | "Iuliia, sesh eshche etikh miagkikh frantcuzskikh bulok iz Ioshkar-Oly, da vypei altaiskogo chaiu", 50 | ], 51 | ], 52 | } as TransliterationSchema; 53 | -------------------------------------------------------------------------------- /src/generated/gost_779_alt.ts: -------------------------------------------------------------------------------- 1 | import { TransliterationSchema } from "./TransliterationSchema"; 2 | 3 | export default { 4 | name: "gost_779_alt", 5 | description: "GOST 7.79-2000 (aka ISO 9:1995) transliteration schema", 6 | url: "https://iuliia.ru/gost-779/", 7 | mapping: { 8 | а: "a", 9 | б: "b", 10 | в: "v", 11 | г: "g", 12 | д: "d", 13 | е: "e", 14 | ё: "yo", 15 | ж: "zh", 16 | з: "z", 17 | и: "i", 18 | й: "j", 19 | к: "k", 20 | л: "l", 21 | м: "m", 22 | н: "n", 23 | о: "o", 24 | п: "p", 25 | р: "r", 26 | с: "s", 27 | т: "t", 28 | у: "u", 29 | ф: "f", 30 | х: "x", 31 | ц: "cz", 32 | ч: "ch", 33 | ш: "sh", 34 | щ: "shh", 35 | ъ: "``", 36 | ы: "y`", 37 | ь: "`", 38 | э: "e`", 39 | ю: "yu", 40 | я: "ya", 41 | }, 42 | prev_mapping: null, 43 | next_mapping: { це: "c", ци: "c", цй: "c", цы: "c" }, 44 | ending_mapping: null, 45 | samples: [ 46 | [ 47 | "Юлия, съешь ещё этих мягких французских булок из Йошкар-Олы, да выпей алтайского чаю", 48 | "Yuliya, s``esh` eshhyo e`tix myagkix franczuzskix bulok iz Joshkar-Oly`, da vy`pej altajskogo chayu", 49 | ], 50 | ], 51 | } as TransliterationSchema; 52 | -------------------------------------------------------------------------------- /src/schemas.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Transliteration schema registry. 3 | */ 4 | 5 | import { Schema } from "./schema"; 6 | import DEFINITIONS from "./generated/_definitions"; 7 | 8 | /** 9 | * All supported transliteration schemas. 10 | */ 11 | export class Schemas { 12 | private static _all: Map; 13 | 14 | /** 15 | * Lazy-loaded schema map. 16 | */ 17 | private static get all() { 18 | if (!this._all) { 19 | this._all = loadSchemas(); 20 | } 21 | return this._all; 22 | } 23 | 24 | /** 25 | * Array of schema names. 26 | */ 27 | public static names(): string[] { 28 | return Array.from(this.all.keys()).sort(); 29 | } 30 | 31 | /** 32 | * Array of schemas. 33 | */ 34 | public static values(): Schema[] { 35 | return Array.from(this.all.values()); 36 | } 37 | 38 | /** 39 | * Get schema by name. 40 | */ 41 | public static get(name: string): Schema { 42 | const schema = this.all.get(name); 43 | if (!schema) { 44 | throw new Error(`No such schema: ${name}`); 45 | } 46 | return schema; 47 | } 48 | } 49 | 50 | function loadSchemas() { 51 | const map = new Map(); 52 | for (const defn of DEFINITIONS) { 53 | const schema = Schema.load(defn); 54 | map.set(schema.name, schema); 55 | } 56 | return map; 57 | } 58 | -------------------------------------------------------------------------------- /src/generated/TransliterationSchema.ts: -------------------------------------------------------------------------------- 1 | /* tslint:disable */ 2 | /** 3 | * This file was automatically generated by json-schema-to-typescript. 4 | * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, 5 | * and run json-schema-to-typescript to regenerate this file. 6 | */ 7 | 8 | /** 9 | * Transliteration Schema 10 | */ 11 | export interface TransliterationSchema { 12 | /** 13 | * Schema name 14 | */ 15 | name: string; 16 | /** 17 | * Schema name aliases 18 | */ 19 | aliases?: string[]; 20 | /** 21 | * Schema description 22 | */ 23 | description: string; 24 | /** 25 | * Schema description url 26 | */ 27 | url: string; 28 | /** 29 | * Schema comments 30 | */ 31 | comments?: string[]; 32 | /** 33 | * Mapping for individual letters 34 | */ 35 | mapping: { 36 | [k: string]: string; 37 | }; 38 | /** 39 | * Mapping for letters with respect to previous sibling 40 | */ 41 | prev_mapping?: { 42 | [k: string]: string; 43 | } | null; 44 | /** 45 | * Mapping for letters with respect to next sibling 46 | */ 47 | next_mapping?: { 48 | [k: string]: string; 49 | } | null; 50 | /** 51 | * Mapping for word endings 52 | */ 53 | ending_mapping?: { 54 | [k: string]: string; 55 | } | null; 56 | /** 57 | * Transliteraton samples 58 | */ 59 | samples: string[][]; 60 | } 61 | -------------------------------------------------------------------------------- /.devcontainer/devcontainer.json: -------------------------------------------------------------------------------- 1 | // For format details, see https://aka.ms/devcontainer.json. For config options, see the README at: 2 | // https://github.com/microsoft/vscode-dev-containers/tree/v0.209.6/containers/javascript-node 3 | { 4 | "name": "Node.js", 5 | "build": { 6 | "dockerfile": "Dockerfile", 7 | // Update 'VARIANT' to pick a Node version: 16, 14, 12. 8 | // Append -bullseye or -buster to pin to an OS version. 9 | // Use -bullseye variants on local arm64/Apple Silicon. 10 | "args": { "VARIANT": "14" } 11 | }, 12 | 13 | // Set *default* container specific settings.json values on container create. 14 | "settings": {}, 15 | 16 | // Add the IDs of extensions you want installed when the container is created. 17 | "extensions": [ 18 | "dbaeumer.vscode-eslint", 19 | "wmaurer.change-case", 20 | "ryanluker.vscode-coverage-gutters", 21 | "github.vscode-pull-request-github", 22 | "eamodio.gitlens", 23 | "orta.vscode-jest", 24 | "eg2.vscode-npm-script", 25 | "esbenp.prettier-vscode", 26 | "2gua.rainbow-brackets" 27 | ], 28 | 29 | // Use 'forwardPorts' to make a list of ports inside the container available locally. 30 | // "forwardPorts": [], 31 | 32 | // Use 'postCreateCommand' to run commands after the container is created. 33 | // "postCreateCommand": "yarn install", 34 | 35 | // Comment out connect as root instead. More info: https://aka.ms/vscode-remote/containers/non-root. 36 | "remoteUser": "node", 37 | "features": { 38 | "github-cli": "latest" 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/generated/ungegn_1987.ts: -------------------------------------------------------------------------------- 1 | import { TransliterationSchema } from "./TransliterationSchema"; 2 | 3 | export default { 4 | name: "ungegn_1987", 5 | description: "UNGEGN 1987 V/18 transliteration schema", 6 | url: "https://iuliia.ru/ungegn-1987/", 7 | mapping: { 8 | а: "a", 9 | б: "b", 10 | в: "v", 11 | г: "g", 12 | д: "d", 13 | е: "e", 14 | ё: "ё", 15 | ж: "ž", 16 | з: "z", 17 | и: "i", 18 | й: "j", 19 | к: "k", 20 | л: "l", 21 | м: "m", 22 | н: "n", 23 | о: "o", 24 | п: "p", 25 | р: "r", 26 | с: "s", 27 | т: "t", 28 | у: "u", 29 | ф: "f", 30 | х: "h", 31 | ц: "c", 32 | ч: "č", 33 | ш: "š", 34 | щ: "šč", 35 | ъ: "ʺ", 36 | ы: "y", 37 | ь: "ʹ", 38 | э: "è", 39 | ю: "ju", 40 | я: "ja", 41 | }, 42 | prev_mapping: null, 43 | next_mapping: null, 44 | ending_mapping: null, 45 | samples: [ 46 | [ 47 | "Юлия, съешь ещё этих мягких французских булок из Йошкар-Олы, да выпей алтайского чаю", 48 | "Julija, sʺešʹ eščё ètih mjagkih francuzskih bulok iz Joškar-Oly, da vypej altajskogo čaju", 49 | ], 50 | [ 51 | "Россия, город Йошкар-Ола, улица Яна Крастыня", 52 | "Rossija, gorod Joškar-Ola, ulica Jana Krastynja", 53 | ], 54 | ], 55 | } as TransliterationSchema; 56 | -------------------------------------------------------------------------------- /src/generated/yandex_money.ts: -------------------------------------------------------------------------------- 1 | import { TransliterationSchema } from "./TransliterationSchema"; 2 | 3 | export default { 4 | name: "yandex_money", 5 | description: "Yandex.Money transliteration schema", 6 | url: "https://iuliia.ru/yandex-money/", 7 | mapping: { 8 | а: "a", 9 | б: "b", 10 | в: "v", 11 | г: "g", 12 | д: "d", 13 | е: "e", 14 | ё: "e", 15 | ж: "zh", 16 | з: "z", 17 | и: "i", 18 | й: "i", 19 | к: "k", 20 | л: "l", 21 | м: "m", 22 | н: "n", 23 | о: "o", 24 | п: "p", 25 | р: "r", 26 | с: "s", 27 | т: "t", 28 | у: "u", 29 | ф: "f", 30 | х: "kh", 31 | ц: "ts", 32 | ч: "ch", 33 | ш: "sh", 34 | щ: "sch", 35 | ъ: "", 36 | ы: "y", 37 | ь: "", 38 | э: "e", 39 | ю: "yu", 40 | я: "ya", 41 | }, 42 | prev_mapping: null, 43 | next_mapping: null, 44 | ending_mapping: null, 45 | samples: [ 46 | [ 47 | "Юлия, съешь ещё этих мягких французских булок из Йошкар-Олы, да выпей алтайского чаю", 48 | "Yuliya, sesh esche etikh myagkikh frantsuzskikh bulok iz Ioshkar-Oly, da vypei altaiskogo chayu", 49 | ], 50 | ["Юлия Щеглова", "Yuliya Scheglova"], 51 | ["Иван Брызгальский", "Ivan Bryzgalskii"], 52 | ["Ксения Стрый", "Kseniya Stryi"], 53 | ], 54 | } as TransliterationSchema; 55 | -------------------------------------------------------------------------------- /src/generated/mvd_310.ts: -------------------------------------------------------------------------------- 1 | import { TransliterationSchema } from "./TransliterationSchema"; 2 | 3 | export default { 4 | name: "mvd_310", 5 | description: "MVD 310-1997 transliteration schema", 6 | url: "https://iuliia.ru/mvd-310/", 7 | mapping: { 8 | а: "a", 9 | б: "b", 10 | в: "v", 11 | г: "g", 12 | д: "d", 13 | е: "e", 14 | ё: "e", 15 | ж: "zh", 16 | з: "z", 17 | и: "i", 18 | й: "y", 19 | к: "k", 20 | л: "l", 21 | м: "m", 22 | н: "n", 23 | о: "o", 24 | п: "p", 25 | р: "r", 26 | с: "s", 27 | т: "t", 28 | у: "u", 29 | ф: "f", 30 | х: "kh", 31 | ц: "ts", 32 | ч: "ch", 33 | ш: "sh", 34 | щ: "shch", 35 | ъ: '"', 36 | ы: "y", 37 | ь: "'", 38 | э: "e", 39 | ю: "yu", 40 | я: "ya", 41 | }, 42 | prev_mapping: { ье: "ye", ъе: "ye" }, 43 | next_mapping: { ье: "", ъе: "" }, 44 | ending_mapping: null, 45 | samples: [ 46 | [ 47 | "Юлия, съешь ещё этих мягких французских булок из Йошкар-Олы, да выпей алтайского чаю", 48 | "Yuliya, syesh' eshche etikh myagkikh frantsuzskikh bulok iz Yoshkar-Oly, da vypey altayskogo chayu", 49 | ], 50 | ["Юлия Щеглова", "Yuliya Shcheglova"], 51 | ["Гайа Васильева", "Gaya Vasilyeva"], 52 | ["Андрей Видный", "Andrey Vidnyy"], 53 | ], 54 | } as TransliterationSchema; 55 | -------------------------------------------------------------------------------- /src/generated/bs_2979.ts: -------------------------------------------------------------------------------- 1 | import { TransliterationSchema } from "./TransliterationSchema"; 2 | 3 | export default { 4 | name: "bs_2979", 5 | description: "British Standard 2979:1958 transliteration schema", 6 | url: "https://iuliia.ru/bs-2979/", 7 | comments: [ 8 | "This schema defines two alternative translations for `Ы`:", 9 | " - `Ы` → `Ȳ` (used by the Oxford University Press)", 10 | " - `Ы` → `UI` (used by the British Library).", 11 | "`iuliia` uses `Ы` → `Ȳ`.", 12 | ], 13 | mapping: { 14 | а: "a", 15 | б: "b", 16 | в: "v", 17 | г: "g", 18 | д: "d", 19 | е: "e", 20 | ё: "ё", 21 | ж: "zh", 22 | з: "z", 23 | и: "i", 24 | й: "ĭ", 25 | к: "k", 26 | л: "l", 27 | м: "m", 28 | н: "n", 29 | о: "o", 30 | п: "p", 31 | р: "r", 32 | с: "s", 33 | т: "t", 34 | у: "u", 35 | ф: "f", 36 | х: "kh", 37 | ц: "ts", 38 | ч: "ch", 39 | ш: "sh", 40 | щ: "shch", 41 | ъ: "ʺ", 42 | ы: "ȳ", 43 | ь: "ʹ", 44 | э: "é", 45 | ю: "yu", 46 | я: "ya", 47 | }, 48 | prev_mapping: null, 49 | next_mapping: null, 50 | ending_mapping: { ий: "y", ый: "y" }, 51 | samples: [ 52 | [ 53 | "Юлия, съешь ещё этих мягких французских булок из Йошкар-Олы, да выпей алтайского чаю", 54 | "Yuliya, sʺeshʹ eshchё étikh myagkikh frantsuzskikh bulok iz Ĭoshkar-Olȳ, da vȳpeĭ altaĭskogo chayu", 55 | ], 56 | ], 57 | } as TransliterationSchema; 58 | -------------------------------------------------------------------------------- /.github/workflows/checks-on-push.yml: -------------------------------------------------------------------------------- 1 | name: Checks on Push 2 | 3 | on: [ push ] 4 | 5 | jobs: 6 | build: 7 | name: Build 8 | runs-on: ubuntu-latest 9 | steps: 10 | - uses: actions/checkout@v2 11 | - uses: actions/setup-node@v2 12 | with: 13 | node-version: 16 14 | cache: 'npm' 15 | - run: npm ci 16 | - run: npm run build 17 | 18 | lint: 19 | name: Lint 20 | runs-on: ubuntu-latest 21 | steps: 22 | - uses: actions/checkout@v2 23 | - uses: actions/setup-node@v2 24 | with: 25 | node-version: 16 26 | cache: 'npm' 27 | - run: npm ci 28 | - run: npm run lint 29 | 30 | test: 31 | name: Test 32 | runs-on: ubuntu-latest 33 | strategy: 34 | matrix: 35 | node-version: [12, 14, 16] 36 | steps: 37 | - uses: actions/checkout@v2 38 | - name: Use Node.js ${{ matrix.node-version }} 39 | uses: actions/setup-node@v2 40 | with: 41 | node-version: ${{ matrix.node-version }} 42 | cache: 'npm' 43 | - run: npm ci 44 | - run: npm test 45 | - name: Coveralls 46 | uses: coverallsapp/github-action@1.1.3 47 | with: 48 | github-token: ${{ secrets.GITHUB_TOKEN }} 49 | 50 | integration-test: 51 | name: Integration Test 52 | runs-on: ubuntu-latest 53 | steps: 54 | - uses: actions/checkout@v2 55 | - name: Use Node.js 16 56 | uses: actions/setup-node@v2 57 | with: 58 | node-version: 16 59 | cache: 'npm' 60 | - run: npm ci 61 | - run: npm run build 62 | - run: npm run test:integration 63 | -------------------------------------------------------------------------------- /src/generated/_definitions.ts: -------------------------------------------------------------------------------- 1 | import ala_lc from "./ala_lc"; 2 | import ala_lc_alt from "./ala_lc_alt"; 3 | import bgn_pcgn from "./bgn_pcgn"; 4 | import bgn_pcgn_alt from "./bgn_pcgn_alt"; 5 | import bs_2979 from "./bs_2979"; 6 | import bs_2979_alt from "./bs_2979_alt"; 7 | import gost_16876 from "./gost_16876"; 8 | import gost_16876_alt from "./gost_16876_alt"; 9 | import gost_52290 from "./gost_52290"; 10 | import gost_52535 from "./gost_52535"; 11 | import gost_7034 from "./gost_7034"; 12 | import gost_779 from "./gost_779"; 13 | import gost_779_alt from "./gost_779_alt"; 14 | import icao_doc_9303 from "./icao_doc_9303"; 15 | import iso_9_1954 from "./iso_9_1954"; 16 | import iso_9_1968 from "./iso_9_1968"; 17 | import iso_9_1968_alt from "./iso_9_1968_alt"; 18 | import mosmetro from "./mosmetro"; 19 | import mvd_310 from "./mvd_310"; 20 | import mvd_310_fr from "./mvd_310_fr"; 21 | import mvd_782 from "./mvd_782"; 22 | import scientific from "./scientific"; 23 | import telegram from "./telegram"; 24 | import ungegn_1987 from "./ungegn_1987"; 25 | import wikipedia from "./wikipedia"; 26 | import yandex_maps from "./yandex_maps"; 27 | import yandex_money from "./yandex_money"; 28 | 29 | export default [ 30 | ala_lc, 31 | ala_lc_alt, 32 | bgn_pcgn, 33 | bgn_pcgn_alt, 34 | bs_2979, 35 | bs_2979_alt, 36 | gost_16876, 37 | gost_16876_alt, 38 | gost_52290, 39 | gost_52535, 40 | gost_7034, 41 | gost_779, 42 | gost_779_alt, 43 | icao_doc_9303, 44 | iso_9_1954, 45 | iso_9_1968, 46 | iso_9_1968_alt, 47 | mosmetro, 48 | mvd_310, 49 | mvd_310_fr, 50 | mvd_782, 51 | scientific, 52 | telegram, 53 | ungegn_1987, 54 | wikipedia, 55 | yandex_maps, 56 | yandex_money, 57 | ]; 58 | -------------------------------------------------------------------------------- /src/generated/icao_doc_9303.ts: -------------------------------------------------------------------------------- 1 | import { TransliterationSchema } from "./TransliterationSchema"; 2 | 3 | export default { 4 | name: "icao_doc_9303", 5 | description: "ICAO DOC 9303 transliteration schema", 6 | url: "https://iuliia.ru/icao-doc-9303/", 7 | mapping: { 8 | а: "a", 9 | б: "b", 10 | в: "v", 11 | г: "g", 12 | д: "d", 13 | е: "e", 14 | ё: "e", 15 | ж: "zh", 16 | з: "z", 17 | и: "i", 18 | й: "i", 19 | к: "k", 20 | л: "l", 21 | м: "m", 22 | н: "n", 23 | о: "o", 24 | п: "p", 25 | р: "r", 26 | с: "s", 27 | т: "t", 28 | у: "u", 29 | ф: "f", 30 | х: "kh", 31 | ц: "ts", 32 | ч: "ch", 33 | ш: "sh", 34 | щ: "shch", 35 | ъ: "ie", 36 | ы: "y", 37 | ь: "", 38 | э: "e", 39 | ю: "iu", 40 | я: "ia", 41 | }, 42 | prev_mapping: null, 43 | next_mapping: null, 44 | ending_mapping: null, 45 | samples: [ 46 | [ 47 | "Юлия, съешь ещё этих мягких французских булок из Йошкар-Олы, да выпей алтайского чаю", 48 | "Iuliia, sieesh eshche etikh miagkikh frantsuzskikh bulok iz Ioshkar-Oly, da vypei altaiskogo chaiu", 49 | ], 50 | ["Юлия Щеглова", "Iuliia Shcheglova"], 51 | ["Гайа Васильева", "Gaia Vasileva"], 52 | ["Андрей Видный", "Andrei Vidnyi"], 53 | ["Артём Краевой", "Artem Kraevoi"], 54 | ["Мадыр Чёткий", "Madyr Chetkii"], 55 | ["Оксана Клеёнкина", "Oksana Kleenkina"], 56 | ["Игорь Ильин", "Igor Ilin"], 57 | ["Ян Разъездной", "Ian Razieezdnoi"], 58 | ], 59 | } as TransliterationSchema; 60 | -------------------------------------------------------------------------------- /src/generated/gost_7034.ts: -------------------------------------------------------------------------------- 1 | import { TransliterationSchema } from "./TransliterationSchema"; 2 | 3 | export default { 4 | name: "gost_7034", 5 | description: "GOST R 7.0.34-2014 transliteration schema", 6 | url: "https://iuliia.ru/gost-7034/", 7 | comments: [ 8 | "This schema defines alternatives for many letters, but does not specify when to use which:", 9 | " е → e (ye)", 10 | " ё → yo (jo)", 11 | " й → j (i,y)", 12 | " х → x (kh)", 13 | " ц → c (tz,cz)", 14 | " ъ → '' (empty)", 15 | " ь → ' (empty)", 16 | " ю → yu (ju)", 17 | " я → ya (ja)", 18 | "`iuliia` uses the first of suggested translations for each such letter.", 19 | ], 20 | mapping: { 21 | а: "a", 22 | б: "b", 23 | в: "v", 24 | г: "g", 25 | д: "d", 26 | е: "e", 27 | ё: "yo", 28 | ж: "zh", 29 | з: "z", 30 | и: "i", 31 | й: "j", 32 | к: "k", 33 | л: "l", 34 | м: "m", 35 | н: "n", 36 | о: "o", 37 | п: "p", 38 | р: "r", 39 | с: "s", 40 | т: "t", 41 | у: "u", 42 | ф: "f", 43 | х: "x", 44 | ц: "c", 45 | ч: "ch", 46 | ш: "sh", 47 | щ: "shh", 48 | ъ: "''", 49 | ы: "y", 50 | ь: "'", 51 | э: "e", 52 | ю: "yu", 53 | я: "ya", 54 | }, 55 | prev_mapping: null, 56 | next_mapping: null, 57 | ending_mapping: null, 58 | samples: [ 59 | [ 60 | "Юлия, съешь ещё этих мягких французских булок из Йошкар-Олы, да выпей алтайского чаю", 61 | "Yuliya, s''esh' eshhyo etix myagkix francuzskix bulok iz Joshkar-Oly, da vypej altajskogo chayu", 62 | ], 63 | ], 64 | } as TransliterationSchema; 65 | -------------------------------------------------------------------------------- /test/translate.test.ts: -------------------------------------------------------------------------------- 1 | import { translate, splitSentence } from "../src/engine"; 2 | import { Schema } from "../src/schema"; 3 | 4 | test("translate without schema", () => { 5 | const schema = new Schema("test", new Map()); 6 | expect(translate("Iuliia", schema)).toBe("Iuliia"); 7 | }); 8 | 9 | test("translate according to schema", () => { 10 | const schema = new Schema( 11 | "test", 12 | new Map([ 13 | ["a", "1"], 14 | ["i", "2"], 15 | ["l", "3"], 16 | ["u", "4"], 17 | ]) 18 | ); 19 | expect(translate("Iuliia", schema)).toBe("243221"); 20 | }); 21 | 22 | test("translate with respect to prev letter", () => { 23 | const schema = new Schema("test", new Map(), new Map([["li", ""]])); 24 | expect(translate("Iuliia", schema)).toBe("Iulia"); 25 | }); 26 | 27 | test("translate with respect to next letter", () => { 28 | const schema = new Schema("test", new Map(), new Map(), new Map([["iu", "y"]])); 29 | expect(translate("Iuliia", schema)).toBe("Yuliia"); 30 | }); 31 | 32 | test("translate word ending", () => { 33 | const schema = new Schema("test", new Map(), new Map(), new Map(), new Map([["ia", "ya"]])); 34 | expect(translate("Iuliia", schema)).toBe("Iuliya"); 35 | }); 36 | 37 | test("translate short word", () => { 38 | const schema = new Schema("test", new Map()); 39 | expect(translate("Iu", schema)).toBe("Iu"); 40 | }); 41 | 42 | test("translate empty word", () => { 43 | const schema = new Schema("test", new Map()); 44 | expect(translate("", schema)).toBe(""); 45 | }); 46 | 47 | test("split sentence", () => { 48 | expect(splitSentence("Hello, mankind!")).toEqual(["Hello", ", ", "mankind", "!"]); 49 | expect(splitSentence("Привет, (привет), человечество!")).toEqual([ 50 | "Привет", 51 | ", (", 52 | "привет", 53 | "), ", 54 | "человечество", 55 | "!", 56 | ]); 57 | }); 58 | -------------------------------------------------------------------------------- /.github/workflows/checks-and-publish-on-release.yml: -------------------------------------------------------------------------------- 1 | name: Checks and Publish on Release 2 | 3 | on: 4 | release: 5 | types: [created] 6 | 7 | jobs: 8 | lint: 9 | name: Lint 10 | runs-on: ubuntu-latest 11 | steps: 12 | - uses: actions/checkout@v2 13 | - uses: actions/setup-node@v2 14 | with: 15 | node-version: 16 16 | cache: 'npm' 17 | - run: npm ci 18 | - run: npm run lint 19 | 20 | test: 21 | name: Test 22 | runs-on: ubuntu-latest 23 | strategy: 24 | matrix: 25 | node-version: [12, 14, 16] 26 | steps: 27 | - uses: actions/checkout@v2 28 | - name: Use Node.js ${{ matrix.node-version }} 29 | uses: actions/setup-node@v2 30 | with: 31 | node-version: ${{ matrix.node-version }} 32 | cache: 'npm' 33 | - run: npm ci 34 | - run: npm test 35 | - name: Coveralls 36 | uses: coverallsapp/github-action@1.1.3 37 | with: 38 | github-token: ${{ secrets.GITHUB_TOKEN }} 39 | 40 | publish: 41 | needs: [lint, test, integration-test] 42 | name: Publish to NPM 43 | runs-on: ubuntu-latest 44 | steps: 45 | - uses: actions/checkout@v2 46 | - uses: actions/setup-node@v2 47 | with: 48 | node-version: 16 49 | registry-url: 'https://registry.npmjs.org' 50 | cache: 'npm' 51 | - run: npm ci 52 | - run: npm run build 53 | - run: npm publish --access=public 54 | env: 55 | NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} 56 | 57 | integration-test: 58 | name: Integration Test 59 | runs-on: ubuntu-latest 60 | steps: 61 | - uses: actions/checkout@v2 62 | - name: Use Node.js 16 63 | uses: actions/setup-node@v2 64 | with: 65 | node-version: 16 66 | cache: 'npm' 67 | - run: npm ci 68 | - run: npm run build 69 | - run: npm run test:integration 70 | -------------------------------------------------------------------------------- /src/generated/mvd_310_fr.ts: -------------------------------------------------------------------------------- 1 | import { TransliterationSchema } from "./TransliterationSchema"; 2 | 3 | export default { 4 | name: "mvd_310_fr", 5 | description: "MVD 310-1997 transliteration schema", 6 | url: "https://iuliia.ru/mvd-310/", 7 | comments: [ 8 | "This schema defines the following rule for the French mapping:", 9 | "> `С` between two vowels → `SS`", 10 | "There is no such rule in other schemas, and MVD-310 itself is deprecated,", 11 | "so I decided to ignore this specific rule for the sake of code simplicity.", 12 | ], 13 | mapping: { 14 | а: "a", 15 | б: "b", 16 | в: "v", 17 | г: "g", 18 | д: "d", 19 | е: "e", 20 | ё: "e", 21 | ж: "j", 22 | з: "z", 23 | и: "i", 24 | й: "i", 25 | к: "k", 26 | л: "l", 27 | м: "m", 28 | н: "n", 29 | о: "o", 30 | п: "p", 31 | р: "r", 32 | с: "s", 33 | т: "t", 34 | у: "ou", 35 | ф: "f", 36 | х: "kh", 37 | ц: "ts", 38 | ч: "tch", 39 | ш: "ch", 40 | щ: "chtch", 41 | ъ: "", 42 | ы: "y", 43 | ь: "", 44 | э: "e", 45 | ю: "iou", 46 | я: "ia", 47 | }, 48 | prev_mapping: { ге: "ue", ги: "ui", гы: "uy", ье: "ie", кс: "x" }, 49 | next_mapping: { кс: "" }, 50 | ending_mapping: { ин: "ine" }, 51 | samples: [ 52 | [ 53 | "Юлия, съешь ещё этих мягких французских булок из Йошкар-Олы, да выпей алтайского чаю", 54 | "Iouliia, sech echtche etikh miagkikh frantsouzskikh boulok iz Iochkar-Oly, da vypei altaiskogo tchaiou", 55 | ], 56 | ["Юлия Щеглова", "Iouliia Chtcheglova"], 57 | ["Гайа Васильева", "Gaia Vasilieva"], 58 | ["Андрей Видный", "Andrei Vidnyi"], 59 | ["Оксана Снегирёва", "Oxana Sneguireva"], 60 | ["Юрий Васин", "Iourii Vasine"], 61 | ], 62 | } as TransliterationSchema; 63 | -------------------------------------------------------------------------------- /src/generated/yandex_maps.ts: -------------------------------------------------------------------------------- 1 | import { TransliterationSchema } from "./TransliterationSchema"; 2 | 3 | export default { 4 | name: "yandex_maps", 5 | description: "Yandex.Maps transliteration schema", 6 | url: "https://iuliia.ru/yandex-maps/", 7 | mapping: { 8 | а: "a", 9 | б: "b", 10 | в: "v", 11 | г: "g", 12 | д: "d", 13 | е: "e", 14 | ё: "yo", 15 | ж: "zh", 16 | з: "z", 17 | и: "i", 18 | й: "y", 19 | к: "k", 20 | л: "l", 21 | м: "m", 22 | н: "n", 23 | о: "o", 24 | п: "p", 25 | р: "r", 26 | с: "s", 27 | т: "t", 28 | у: "u", 29 | ф: "f", 30 | х: "kh", 31 | ц: "ts", 32 | ч: "ch", 33 | ш: "sh", 34 | щ: "sch", 35 | ъ: "", 36 | ы: "y", 37 | ь: "", 38 | э: "e", 39 | ю: "yu", 40 | я: "ya", 41 | }, 42 | prev_mapping: { е: "ye", ае: "ye", ие: "ye", ое: "ye", уе: "ye", эе: "ye", юе: "ye", яе: "ye" }, 43 | next_mapping: { ъе: "y" }, 44 | ending_mapping: { ый: "iy" }, 45 | samples: [ 46 | [ 47 | "Юлия, съешь ещё этих мягких французских булок из Йошкар-Олы, да выпей алтайского чаю", 48 | "Yuliya, syesh yeschyo etikh myagkikh frantsuzskikh bulok iz Yoshkar-Oly, da vypey altayskogo chayu", 49 | ], 50 | [ 51 | "Россия, город Йошкар-Ола, улица Яна Крастыня", 52 | "Rossiya, gorod Yoshkar-Ola, ulitsa Yana Krastynya", 53 | ], 54 | ["Санкт-Петербург, Подъездной пер", "Sankt-Peterburg, Podyezdnoy per"], 55 | ["Москва, ул Подъёмная", "Moskva, ul Podyomnaya"], 56 | ["Астрахань, ул Подъяпольского", "Astrakhan, ul Podyapolskogo"], 57 | ["Щегловитовка", "Scheglovitovka"], 58 | ["Новый Уренгой", "Noviy Urengoy"], 59 | ["Елабуга", "Yelabuga"], 60 | ["Бабаево", "Babayevo"], 61 | ["Белово", "Belovo"], 62 | ], 63 | } as TransliterationSchema; 64 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ## [0.7.0](https://github.com/nalgeon/iuliia-js/compare/v0.6.2...v0.7.0) (2022-01-07) 2 | 3 | ### Features 4 | 5 | - Modular `mosmetro` and `yandex_money` schemas ([c19be1a](https://github.com/nalgeon/iuliia-js/commit/c19be1a03e4bea8ad6f6208e07b157524c2abf9d)) 6 | 7 | ## [0.6.2](https://github.com/nalgeon/iuliia-js/compare/v0.6.1...v0.6.2) (2022-01-03) 8 | 9 | ### Bug Fixes 10 | 11 | - Do not use lookbehinds ([96cc00a](https://github.com/nalgeon/iuliia-js/commit/96cc00a403247d64faef2cde6c1200ca039545a2)) 12 | 13 | ## [0.6.1](https://github.com/nalgeon/iuliia-js/compare/v0.6.0...v0.6.1) (2020-05-04) 14 | 15 | ### Bug Fixes 16 | 17 | - Split sentence by word boundary ([b7ba64c](https://github.com/nalgeon/iuliia-js/commit/b7ba64cc37faa7942a21cc2fef6bea705786c2ca)) 18 | 19 | # [0.6.0](https://github.com/nalgeon/iuliia-js/compare/v0.5.0...v0.6.0) (2020-04-29) 20 | 21 | ### Features 22 | 23 | - Moscow Metro map schema ([b63d982](https://github.com/nalgeon/iuliia-js/commit/b63d98223e002bdd28300e163e08f1af504f104d)) 24 | 25 | # [0.5.0](https://github.com/nalgeon/iuliia-js/compare/v0.4.1...v0.5.0) (2020-04-27) 26 | 27 | ### Features 28 | 29 | - Declarative schema definition ([479d6b2](https://github.com/nalgeon/iuliia-js/commit/479d6b23d8cd0701c5b0540d9db4a45d2d62ff23)) 30 | 31 | # [0.4.0](https://github.com/nalgeon/iuliia-js/compare/v0.3.0...v0.4.0) (2020-04-24) 32 | 33 | ### Features 34 | 35 | - bundle for the browser ([d4f0386](https://github.com/nalgeon/iuliia-js/commit/d4f038655d0844ae3ddcb200d396f8198d4eaa8e)) 36 | 37 | # [0.3.0](https://github.com/nalgeon/iuliia-js/compare/v0.2.0...v0.3.0) (2020-04-24) 38 | 39 | ### Features 40 | 41 | - 18 more schemas ([b61400e](https://github.com/nalgeon/iuliia-js/commit/b61400e3a12cbfaf83652f5e9809d35fb94ce2c1)) 42 | - Yandex.Money schema ([7a43554](https://github.com/nalgeon/iuliia-js/commit/7a43554385f951fae39b10a49411e6a080380b5c)) 43 | 44 | # 0.2.0 (2020-04-24) 45 | 46 | ### Features 47 | 48 | - basic translate and schema features ([1ab1532](https://github.com/nalgeon/iuliia-js/commit/1ab1532392adf36cbc8b9d62e63e38435e53a7d3)) 49 | -------------------------------------------------------------------------------- /integration-test/bundles.test.ts: -------------------------------------------------------------------------------- 1 | import { readFile, stat } from "fs/promises"; 2 | import { join } from "path"; 3 | import { createContext, runInContext, runInNewContext } from "vm"; 4 | import webpack from "webpack"; 5 | import webpackConfig from "./webpack/webpack.config"; 6 | 7 | describe("CommonJS", () => { 8 | it("should load and execute a Node module", async () => { 9 | // eslint-disable-next-line @typescript-eslint/no-var-requires 10 | 11 | const p = require.resolve("../"); 12 | expect(p).toContain("dist/cjs/index.js"); 13 | 14 | // eslint-disable-next-line @typescript-eslint/no-var-requires 15 | const iu = require("../"); 16 | expect(iu.translate("Привет, мир!", iu.WIKIPEDIA)).toEqual("Privet, mir!"); 17 | }); 18 | }); 19 | 20 | describe("UMD", () => { 21 | it("should load and execute an UMD bundle", async () => { 22 | const library = await readFile(join(__dirname, "../dist/umd/iuliia.js"), "utf8"); 23 | const invocation = 'iuliia.translate("Привет, мир!", iuliia.WIKIPEDIA);'; 24 | const res = runInNewContext(`${library}\n${invocation}`); 25 | expect(res).toEqual("Privet, mir!"); 26 | }); 27 | }); 28 | 29 | describe("Webpack Tree-Shaking", () => { 30 | it("should create a tree-shaked bundle with only necesssary schemas included", async () => { 31 | const bundle = join(webpackConfig.output.path, webpackConfig.output.filename as string); 32 | await new Promise((res, rej) => 33 | webpack(webpackConfig, (err, stats) => (err ? rej(err) : res(stats))) 34 | ); 35 | 36 | const { size } = await stat(bundle); 37 | expect(size).toBeLessThan(5_700); 38 | expect(size).toBeGreaterThan(5_000); 39 | const contents = await readFile(bundle, "utf8"); 40 | const matches = contents.match(/https:\/\/iuliia.ru\/([^/]+)/gim); 41 | expect(matches).toEqual(["https://iuliia.ru/mosmetro"]); 42 | 43 | const consoleLog = jest.fn(); 44 | 45 | runInContext( 46 | await readFile(bundle, "utf8"), 47 | createContext({ console: { log: consoleLog } }) 48 | ); 49 | expect(consoleLog).toBeCalledWith("Privet, mir!"); 50 | }); 51 | }); 52 | -------------------------------------------------------------------------------- /test/schemas.test.ts: -------------------------------------------------------------------------------- 1 | import { Schemas, translate, WIKIPEDIA } from "../src/index"; 2 | 3 | test("schema names", () => { 4 | const names = Schemas.names(); 5 | expect(names).toEqual([ 6 | "ala_lc", 7 | "ala_lc_alt", 8 | "bgn_pcgn", 9 | "bgn_pcgn_alt", 10 | "bs_2979", 11 | "bs_2979_alt", 12 | "gost_16876", 13 | "gost_16876_alt", 14 | "gost_52290", 15 | "gost_52535", 16 | "gost_7034", 17 | "gost_779", 18 | "gost_779_alt", 19 | "icao_doc_9303", 20 | "iso_9_1954", 21 | "iso_9_1968", 22 | "iso_9_1968_alt", 23 | "mosmetro", 24 | "mvd_310", 25 | "mvd_310_fr", 26 | "mvd_782", 27 | "scientific", 28 | "telegram", 29 | "ungegn_1987", 30 | "wikipedia", 31 | "yandex_maps", 32 | "yandex_money", 33 | ]); 34 | }); 35 | 36 | test("get schema by name", () => { 37 | const schema = Schemas.get("wikipedia"); 38 | expect(schema).toEqual(WIKIPEDIA); 39 | }); 40 | 41 | test("schema not found", () => { 42 | expect(() => { 43 | Schemas.get("whatever"); 44 | }).toThrowError("No such schema: whatever"); 45 | }); 46 | 47 | test("translate", () => { 48 | const schema = Schemas.get("wikipedia"); 49 | const translated = translate("Юлия", schema); 50 | expect(translated).toBe("Yuliya"); 51 | }); 52 | 53 | test("translate", () => { 54 | const schema = Schemas.get("wikipedia"); 55 | const translated = translate("Привет, (привет), человечество!", schema); 56 | expect(translated).toBe("Privet, (privet), chelovechestvo!"); 57 | }); 58 | 59 | function samples(): Array<[string, number, string, string]> { 60 | const samples: Array<[string, number, string, string]> = []; 61 | for (const schema of Schemas.values()) { 62 | let idx = 1; 63 | for (const sample of schema.samples) { 64 | const source = sample[0]; 65 | const expected = sample[1]; 66 | samples.push([schema.name, idx++, source, expected]); 67 | } 68 | } 69 | return samples; 70 | } 71 | 72 | test.each(samples())("%s %d: %s", (name, idx, source, expected) => { 73 | const schema = Schemas.get(name); 74 | expect(translate(source, schema)).toBe(expected); 75 | }); 76 | -------------------------------------------------------------------------------- /src/generated/gost_52290.ts: -------------------------------------------------------------------------------- 1 | import { TransliterationSchema } from "./TransliterationSchema"; 2 | 3 | export default { 4 | name: "gost_52290", 5 | description: "GOST R 52290-2004 transliteration schema", 6 | url: "https://iuliia.ru/gost-52290/", 7 | mapping: { 8 | а: "a", 9 | б: "b", 10 | в: "v", 11 | г: "g", 12 | д: "d", 13 | е: "e", 14 | ё: "yo", 15 | ж: "zh", 16 | з: "z", 17 | и: "i", 18 | й: "y", 19 | к: "k", 20 | л: "l", 21 | м: "m", 22 | н: "n", 23 | о: "o", 24 | п: "p", 25 | р: "r", 26 | с: "s", 27 | т: "t", 28 | у: "u", 29 | ф: "f", 30 | х: "kh", 31 | ц: "ts", 32 | ч: "ch", 33 | ш: "sh", 34 | щ: "shch", 35 | ъ: "'", 36 | ы: "y", 37 | ь: "'", 38 | э: "e", 39 | ю: "yu", 40 | я: "ya", 41 | }, 42 | prev_mapping: { 43 | ае: "ye", 44 | ее: "ye", 45 | ёе: "ye", 46 | ие: "ye", 47 | ое: "ye", 48 | уе: "ye", 49 | ъе: "ye", 50 | ые: "ye", 51 | ье: "ye", 52 | эе: "ye", 53 | юе: "ye", 54 | яе: "ye", 55 | бё: "ye", 56 | вё: "ye", 57 | гё: "ye", 58 | дё: "ye", 59 | зё: "ye", 60 | кё: "ye", 61 | лё: "ye", 62 | мё: "ye", 63 | нё: "ye", 64 | пё: "ye", 65 | рё: "ye", 66 | сё: "ye", 67 | тё: "ye", 68 | фё: "ye", 69 | хё: "ye", 70 | цё: "ye", 71 | жё: "e", 72 | чё: "e", 73 | шё: "e", 74 | щё: "e", 75 | }, 76 | next_mapping: { ъе: "", ье: "", ъё: "", ьё: "" }, 77 | ending_mapping: null, 78 | samples: [ 79 | [ 80 | "Россия, город Йошкар-Ола, улица Яна Крастыня", 81 | "Rossiya, gorod Yoshkar-Ola, ulitsa Yana Krastynya", 82 | ], 83 | [ 84 | "Юлия, съешь ещё этих мягких французских булок из Йошкар-Олы, да выпей алтайского чаю", 85 | "Yuliya, syesh' eshche etikh myagkikh frantsuzskikh bulok iz Yoshkar-Oly, da vypey altayskogo chayu", 86 | ], 87 | ["Ё Крё Мякоё", "Yo Krye Myakoyo"], 88 | ], 89 | } as TransliterationSchema; 90 | -------------------------------------------------------------------------------- /test/mapping.test.ts: -------------------------------------------------------------------------------- 1 | import { Mapping, LetterMapping, PrevMapping, NextMapping, EndingMapping } from "../src/mapping"; 2 | 3 | const map = new Map([ 4 | ["a", "x"], 5 | ["b", "yy"], 6 | ]); 7 | 8 | test("mapping value", () => { 9 | const mapping = new Mapping(map); 10 | expect(mapping.valueOf()).toEqual(map); 11 | }); 12 | 13 | test("get mapped letter", () => { 14 | const mapping = new Mapping(map); 15 | expect(mapping.get("a")).toEqual("x"); 16 | expect(mapping.get("b")).toEqual("yy"); 17 | expect(mapping.get("c")).toBeUndefined(); 18 | expect(mapping.get("c", "d")).toEqual("d"); 19 | }); 20 | 21 | test("mapping for individual letter", () => { 22 | const mapping = new LetterMapping(map); 23 | expect(mapping.valueOf()).toEqual( 24 | new Map([ 25 | ["a", "x"], 26 | ["A", "X"], 27 | ["b", "yy"], 28 | ["B", "Yy"], 29 | ]) 30 | ); 31 | }); 32 | 33 | test("mapping with respect to prev letter", () => { 34 | const mapping = new PrevMapping( 35 | new Map([ 36 | ["ax", "xx"], 37 | ["bx", "xxx"], 38 | ]) 39 | ); 40 | expect(mapping.valueOf()).toEqual( 41 | new Map([ 42 | ["ax", "xx"], 43 | ["Ax", "xx"], 44 | ["AX", "Xx"], 45 | ["bx", "xxx"], 46 | ["Bx", "xxx"], 47 | ["BX", "Xxx"], 48 | ]) 49 | ); 50 | }); 51 | 52 | test("mapping with respect to next letter", () => { 53 | const mapping = new NextMapping( 54 | new Map([ 55 | ["xa", "xx"], 56 | ["xb", "xxx"], 57 | ]) 58 | ); 59 | expect(mapping.valueOf()).toEqual( 60 | new Map([ 61 | ["xa", "xx"], 62 | ["Xa", "Xx"], 63 | ["XA", "Xx"], 64 | ["xb", "xxx"], 65 | ["Xb", "Xxx"], 66 | ["XB", "Xxx"], 67 | ]) 68 | ); 69 | }); 70 | 71 | test("mapping for word ending", () => { 72 | const mapping = new EndingMapping( 73 | new Map([ 74 | ["aa", "xx"], 75 | ["bb", "yy"], 76 | ]) 77 | ); 78 | expect(mapping.valueOf()).toEqual( 79 | new Map([ 80 | ["aa", "xx"], 81 | ["AA", "XX"], 82 | ["bb", "yy"], 83 | ["BB", "YY"], 84 | ]) 85 | ); 86 | }); 87 | -------------------------------------------------------------------------------- /src/engine.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Translate engine. 3 | */ 4 | 5 | import { Schema } from "./schema"; 6 | 7 | // JS lacks unicode support for word boundary (\b), 8 | // so we have to emulate it 9 | const SPLITTER = /([^\s,.:;?!()"']+)|([\s,.:;?!()"'])+/g; 10 | 11 | /** 12 | * Translate source Cyrillic string into Latin using specified schema. 13 | * Translates sentences word by word, delegating specifics of transliteration 14 | * to specified schema. 15 | */ 16 | export function translate(source: string, schema: Schema) { 17 | const translated = splitSentence(source).map((word) => translateWord(word, schema)); 18 | return translated.join(""); 19 | } 20 | 21 | export function splitSentence(source: string) { 22 | return [...source.matchAll(SPLITTER)].map((y) => y[0]); 23 | } 24 | 25 | function translateWord(word: string, schema: Schema) { 26 | const [stem, ending] = splitWord(word); 27 | const translatedEnding = ending ? schema.translateEnding(ending) : undefined; 28 | let translated; 29 | if (translatedEnding) { 30 | translated = translateLetters(stem, schema); 31 | translated.push(translatedEnding); 32 | } else { 33 | translated = translateLetters(word, schema); 34 | } 35 | return translated.join(""); 36 | } 37 | 38 | function translateLetters(word: string, schema: Schema) { 39 | const translated = []; 40 | for (const [prev, curr, next] of letterReader(word)) { 41 | const letter = schema.translateLetter(prev, curr, next); 42 | translated.push(letter); 43 | } 44 | return translated; 45 | } 46 | 47 | function splitWord(word: string) { 48 | const endingLength = 2; 49 | let stem; 50 | let ending; 51 | if (word.length > endingLength) { 52 | const pivotIdx = word.length - endingLength; 53 | stem = word.substr(0, pivotIdx); 54 | ending = word.substr(pivotIdx); 55 | } else { 56 | stem = word; 57 | ending = ""; 58 | } 59 | return [stem, ending]; 60 | } 61 | 62 | function* letterReader(stem: string) { 63 | let prev = ""; 64 | let curr = ""; 65 | let next = ""; 66 | for (let idx = 0; idx < stem.length; idx++) { 67 | if (curr !== "") { 68 | prev = curr; 69 | } 70 | curr = next || stem[idx]; 71 | if (idx < stem.length - 1) { 72 | next = stem[idx + 1]; 73 | } else { 74 | next = ""; 75 | } 76 | yield [prev, curr, next]; 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /src/generated/mvd_782.ts: -------------------------------------------------------------------------------- 1 | import { TransliterationSchema } from "./TransliterationSchema"; 2 | 3 | export default { 4 | name: "mvd_782", 5 | description: "MVD 782-2000 transliteration schema", 6 | url: "https://iuliia.ru/mvd-782/", 7 | mapping: { 8 | а: "a", 9 | б: "b", 10 | в: "v", 11 | г: "g", 12 | д: "d", 13 | е: "e", 14 | ё: "yo", 15 | ж: "zh", 16 | з: "z", 17 | и: "i", 18 | й: "y", 19 | к: "k", 20 | л: "l", 21 | м: "m", 22 | н: "n", 23 | о: "o", 24 | п: "p", 25 | р: "r", 26 | с: "s", 27 | т: "t", 28 | у: "u", 29 | ф: "f", 30 | х: "kh", 31 | ц: "ts", 32 | ч: "ch", 33 | ш: "sh", 34 | щ: "shch", 35 | ъ: "'", 36 | ы: "y", 37 | ь: "'", 38 | э: "e", 39 | ю: "yu", 40 | я: "ya", 41 | }, 42 | prev_mapping: { 43 | ае: "ye", 44 | ее: "ye", 45 | ёе: "ye", 46 | ие: "ye", 47 | ое: "ye", 48 | уе: "ye", 49 | ъе: "ye", 50 | ые: "ye", 51 | ье: "ye", 52 | эе: "ye", 53 | юе: "ye", 54 | яе: "ye", 55 | бё: "ye", 56 | вё: "ye", 57 | гё: "ye", 58 | дё: "ye", 59 | зё: "ye", 60 | кё: "ye", 61 | лё: "ye", 62 | мё: "ye", 63 | нё: "ye", 64 | пё: "ye", 65 | рё: "ye", 66 | сё: "ye", 67 | тё: "ye", 68 | фё: "ye", 69 | хё: "ye", 70 | цё: "ye", 71 | жё: "e", 72 | чё: "e", 73 | шё: "e", 74 | щё: "e", 75 | ьи: "yi", 76 | }, 77 | next_mapping: { ъе: "", ье: "", ъё: "", ьё: "", ьи: "" }, 78 | ending_mapping: null, 79 | samples: [ 80 | [ 81 | "Юлия, съешь ещё этих мягких французских булок из Йошкар-Олы, да выпей алтайского чаю", 82 | "Yuliya, syesh' eshche etikh myagkikh frantsuzskikh bulok iz Yoshkar-Oly, da vypey altayskogo chayu", 83 | ], 84 | ["Юлия Щеглова", "Yuliya Shcheglova"], 85 | ["Гайа Васильева", "Gaya Vasilyeva"], 86 | ["Андрей Видный", "Andrey Vidnyy"], 87 | ["Артём Краевой", "Artyem Krayevoy"], 88 | ["Мадыр Чёткий", "Madyr Chetkiy"], 89 | ["Оксана Клеёнкина", "Oksana Kleyonkina"], 90 | ["Игорь Ильин", "Igor' Ilyin"], 91 | ], 92 | } as TransliterationSchema; 93 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "iuliia", 3 | "version": "0.8.2", 4 | "description": "Transliterate Cyrillic → Latin in every possible way", 5 | "keywords": [ 6 | "transliterate", 7 | "transliteration", 8 | "romanization", 9 | "cyrillic to latin", 10 | "iso 9", 11 | "icao doc 9303" 12 | ], 13 | "repository": { 14 | "type": "git", 15 | "url": "git+https://github.com/nalgeon/iuliia-js.git" 16 | }, 17 | "license": "MIT", 18 | "author": "Anton Zhiyanov ", 19 | "sideEffects": false, 20 | "main": "dist/cjs/index.js", 21 | "module": "dist/esm/index.js", 22 | "types": "dist/cjs/index.d.ts", 23 | "files": [ 24 | "dist/**/*" 25 | ], 26 | "scripts": { 27 | "prebuild": "npm run clean", 28 | "build": "concurrently 'npm:build:*'", 29 | "postbuild": "npm run build_stats", 30 | "build:esm": "tsc -p tsconfig.esm.json", 31 | "build:node": "tsc", 32 | "build:umd": "webpack --no-optimization-minimize --output-filename=umd/iuliia.js && rm -rf .tmp", 33 | "build:umd:min": "webpack --optimization-minimize --output-filename=umd/iuliia.min.js && rm -rf .tmp", 34 | "build_stats": "cd dist/umd && ls -lh *.js | awk '{print $5,$9}'", 35 | "clean": "rm -rf dist", 36 | "format": "prettier --write \"src/**/*.ts\"", 37 | "generate": "ts-node --skip-project ./scripts/generate.ts", 38 | "postgenerate": "npm run format", 39 | "lint": "eslint .", 40 | "test": "jest /test/", 41 | "test:integration": "jest /integration-test/" 42 | }, 43 | "devDependencies": { 44 | "@babel/core": "^7.16.7", 45 | "@babel/preset-env": "^7.16.8", 46 | "@babel/preset-typescript": "^7.16.7", 47 | "@rollup/plugin-typescript": "^8.3.0", 48 | "@types/jest": "^27.4.0", 49 | "@typescript-eslint/eslint-plugin": "^5.9.1", 50 | "@typescript-eslint/parser": "^5.9.1", 51 | "ajv": "^8.9.0", 52 | "concurrently": "^7.0.0", 53 | "coveralls": "^3.0.14", 54 | "eslint": "^8.7.0", 55 | "execa": "^5.1.1", 56 | "jest": "^27.4.7", 57 | "json-schema-to-typescript": "^10.1.5", 58 | "prettier": "^2.0.5", 59 | "ts-loader": "^9.2.6", 60 | "ts-node": "^10.4.0", 61 | "typescript": "^4.5.4", 62 | "webpack": "^5.67.0", 63 | "webpack-cli": "^4.9.1" 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /src/schema.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Transliteration schema base features. 3 | */ 4 | import { TransliterationSchema } from "./generated/TransliterationSchema"; 5 | import { LetterMapping, PrevMapping, NextMapping, EndingMapping } from "./mapping"; 6 | 7 | /** Transliteration schema. Defines the way to translate individual letters. */ 8 | export class Schema { 9 | public name: string; 10 | public samples: string[][]; 11 | 12 | private map: LetterMapping; 13 | private prevMap: PrevMapping; 14 | private nextMap: NextMapping; 15 | private endingMap: EndingMapping; 16 | 17 | constructor( 18 | name: string, 19 | mapping: Map, 20 | prevMapping?: Map, 21 | nextMapping?: Map, 22 | endingMapping?: Map, 23 | samples?: string[][] 24 | ) { 25 | this.name = name; 26 | this.map = new LetterMapping(mapping); 27 | this.prevMap = new PrevMapping(prevMapping || new Map()); 28 | this.nextMap = new NextMapping(nextMapping || new Map()); 29 | this.endingMap = new EndingMapping(endingMapping || new Map()); 30 | this.samples = samples || []; 31 | } 32 | 33 | /** 34 | * Translate `curr` letter according to schema mappings. 35 | * `prev` and `next` are taken into consideration according to corresponding mappings. 36 | * @param prev previous letter 37 | * @param curr current letter 38 | * @param next next letter 39 | */ 40 | public translateLetter(prev: string, curr: string, next: string) { 41 | let letter = this.prevMap.get(prev + curr); 42 | if (letter === undefined) { 43 | letter = this.nextMap.get(curr + next); 44 | } 45 | if (letter === undefined) { 46 | letter = this.map.get(curr, curr); 47 | } 48 | return letter; 49 | } 50 | 51 | /** Translate word ending according to schema mapping. */ 52 | public translateEnding(ending: string) { 53 | return this.endingMap.get(ending); 54 | } 55 | 56 | public static load(definition: TransliterationSchema) { 57 | return new Schema( 58 | definition.name, 59 | new Map(Object.entries(definition.mapping)), 60 | new Map(Object.entries(definition.prev_mapping || {})), 61 | new Map(Object.entries(definition.next_mapping || {})), 62 | new Map(Object.entries(definition.ending_mapping || {})), 63 | definition.samples 64 | ); 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /src/generated/wikipedia.ts: -------------------------------------------------------------------------------- 1 | import { TransliterationSchema } from "./TransliterationSchema"; 2 | 3 | export default { 4 | name: "wikipedia", 5 | description: "Wikipedia transliteration schema", 6 | url: "https://iuliia.ru/wikipedia/", 7 | mapping: { 8 | а: "a", 9 | б: "b", 10 | в: "v", 11 | г: "g", 12 | д: "d", 13 | е: "e", 14 | ё: "yo", 15 | ж: "zh", 16 | з: "z", 17 | и: "i", 18 | й: "y", 19 | к: "k", 20 | л: "l", 21 | м: "m", 22 | н: "n", 23 | о: "o", 24 | п: "p", 25 | р: "r", 26 | с: "s", 27 | т: "t", 28 | у: "u", 29 | ф: "f", 30 | х: "kh", 31 | ц: "ts", 32 | ч: "ch", 33 | ш: "sh", 34 | щ: "shch", 35 | ъ: "", 36 | ы: "y", 37 | ь: "", 38 | э: "e", 39 | ю: "yu", 40 | я: "ya", 41 | }, 42 | prev_mapping: { 43 | е: "ye", 44 | ае: "ye", 45 | ие: "ye", 46 | ое: "ye", 47 | уе: "ye", 48 | эе: "ye", 49 | юе: "ye", 50 | яе: "ye", 51 | ье: "ye", 52 | ъе: "ye", 53 | }, 54 | next_mapping: { 55 | ъа: "y", 56 | ъи: "y", 57 | ъо: "y", 58 | ъу: "y", 59 | ъы: "y", 60 | ъэ: "y", 61 | ьа: "y", 62 | ьи: "y", 63 | ьо: "y", 64 | ьу: "y", 65 | ьы: "y", 66 | ьэ: "y", 67 | }, 68 | ending_mapping: { ий: "y", ый: "y" }, 69 | samples: [ 70 | [ 71 | "Юлия, съешь ещё этих мягких французских булок из Йошкар-Олы, да выпей алтайского чаю", 72 | "Yuliya, syesh yeshchyo etikh myagkikh frantsuzskikh bulok iz Yoshkar-Oly, da vypey altayskogo chayu", 73 | ], 74 | [ 75 | "Россия, город Йошкар-Ола, улица Яна Крастыня", 76 | "Rossiya, gorod Yoshkar-Ola, ulitsa Yana Krastynya", 77 | ], 78 | ["Ельцин", "Yeltsin"], 79 | ["Раздольное", "Razdolnoye"], 80 | ["Юрьев", "Yuryev"], 81 | ["Белкин", "Belkin"], 82 | ["Бийск", "Biysk"], 83 | ["Подъярский", "Podyarsky"], 84 | ["Мусийкъонгийкоте", "Musiykyongiykote"], 85 | ["Давыдов", "Davydov"], 86 | ["Усолье", "Usolye"], 87 | ["Выхухоль", "Vykhukhol"], 88 | ["Дальнегорск", "Dalnegorsk"], 89 | ["Ильинский", "Ilyinsky"], 90 | ["Красный", "Krasny"], 91 | ["Великий", "Veliky"], 92 | ["Набережные Челны", "Naberezhnye Chelny"], 93 | ], 94 | } as TransliterationSchema; 95 | -------------------------------------------------------------------------------- /src/generated/mosmetro.ts: -------------------------------------------------------------------------------- 1 | import { TransliterationSchema } from "./TransliterationSchema"; 2 | 3 | export default { 4 | name: "mosmetro", 5 | description: "Moscow Metro map transliteration schema", 6 | url: "https://iuliia.ru/mosmetro/", 7 | mapping: { 8 | а: "a", 9 | б: "b", 10 | в: "v", 11 | г: "g", 12 | д: "d", 13 | е: "e", 14 | ё: "e", 15 | ж: "zh", 16 | з: "z", 17 | и: "i", 18 | й: "y", 19 | к: "k", 20 | л: "l", 21 | м: "m", 22 | н: "n", 23 | о: "o", 24 | п: "p", 25 | р: "r", 26 | с: "s", 27 | т: "t", 28 | у: "u", 29 | ф: "f", 30 | х: "kh", 31 | ц: "ts", 32 | ч: "ch", 33 | ш: "sh", 34 | щ: "sch", 35 | ъ: "", 36 | ы: "y", 37 | ь: "", 38 | э: "e", 39 | ю: "yu", 40 | я: "ya", 41 | }, 42 | prev_mapping: { тц: "s", ьё: "o", ъё: "o" }, 43 | next_mapping: { 44 | ьа: "y", 45 | ье: "y", 46 | ьё: "y", 47 | ьи: "y", 48 | ьо: "y", 49 | ьу: "y", 50 | ьэ: "y", 51 | ъа: "y", 52 | ъе: "y", 53 | ъё: "y", 54 | ъи: "y", 55 | ъо: "y", 56 | ъу: "y", 57 | ъэ: "y", 58 | }, 59 | ending_mapping: { ий: "y", ый: "y" }, 60 | samples: [ 61 | [ 62 | "Юлия, съешь ещё этих мягких французских булок из Йошкар-Олы, да выпей алтайского чаю", 63 | "Yuliya, syesh esche etikh myagkikh frantsuzskikh bulok iz Yoshkar-Oly, da vypey altayskogo chayu", 64 | ], 65 | ["Битцевский парк", "Bitsevsky park"], 66 | ["Верхние Лихоборы", "Verkhnie Likhobory"], 67 | ["Воробьёвы горы", "Vorobyovy gory"], 68 | ["Выхино", "Vykhino"], 69 | ["Зябликово", "Zyablikovo"], 70 | ["Измайловская", "Izmaylovskaya"], 71 | ["Кожуховская", "Kozhukhovskaya"], 72 | ["Крылатское", "Krylatskoe"], 73 | ["Марьина Роща", "Maryina Roscha"], 74 | ["Марьино", "Maryino"], 75 | ["Молодёжная", "Molodezhnaya"], 76 | ["Октябрьская", "Oktyabrskaya"], 77 | ["Ольховая", "Olkhovaya"], 78 | ["Парк Победы", "Park Pobedy"], 79 | ["Площадь Ильича", "Ploschad Ilyicha"], 80 | ["Площадь Революции", "Ploschad Revolyutsii"], 81 | ["Пятницкое шоссе", "Pyatnitskoe shosse"], 82 | ["Румянцево", "Rumyantsevo"], 83 | ["Саларьево", "Salaryevo"], 84 | ["Семёновская", "Semenovskaya"], 85 | ["Сходненская", "Skhodnenskaya"], 86 | ["Текстильщики", "Tekstilschiki"], 87 | ["Тёплый стан", "Teply stan"], 88 | ["Третьяковская", "Tretyakovskaya"], 89 | ["Тропарёво", "Troparevo"], 90 | ["Фонвизинская", "Fonvizinskaya"], 91 | ["Чистые пруды", "Chistye prudy"], 92 | ["Шоссе Энтузиастов", "Shosse Entuziastov"], 93 | ["Щёлковская", "Schelkovskaya"], 94 | ["Электрозаводская", "Elektrozavodskaya"], 95 | ["Юго-Западная", "Yugo-Zapadnaya"], 96 | ], 97 | } as TransliterationSchema; 98 | -------------------------------------------------------------------------------- /src/generated/bgn_pcgn_alt.ts: -------------------------------------------------------------------------------- 1 | import { TransliterationSchema } from "./TransliterationSchema"; 2 | 3 | export default { 4 | name: "bgn_pcgn_alt", 5 | description: "BGN/PCGN transliteration schema", 6 | url: "https://iuliia.ru/bgn-pcgn/", 7 | mapping: { 8 | а: "a", 9 | б: "b", 10 | в: "v", 11 | г: "g", 12 | д: "d", 13 | е: "e", 14 | ё: "ё", 15 | ж: "zh", 16 | з: "z", 17 | и: "i", 18 | й: "y", 19 | к: "k", 20 | л: "l", 21 | м: "m", 22 | н: "n", 23 | о: "o", 24 | п: "p", 25 | р: "r", 26 | с: "s", 27 | т: "t", 28 | у: "u", 29 | ф: "f", 30 | х: "kh", 31 | ц: "ts", 32 | ч: "ch", 33 | ш: "sh", 34 | щ: "shch", 35 | ъ: "”", 36 | ы: "y", 37 | ь: "’", 38 | э: "e", 39 | ю: "yu", 40 | я: "ya", 41 | }, 42 | prev_mapping: { 43 | е: "ye", 44 | ае: "ye", 45 | ее: "ye", 46 | ёе: "ye", 47 | ие: "ye", 48 | йе: "ye", 49 | ое: "ye", 50 | уе: "ye", 51 | ъе: "ye", 52 | ые: "ye", 53 | ье: "ye", 54 | эе: "ye", 55 | юе: "ye", 56 | яе: "ye", 57 | ё: "yё", 58 | аё: "yё", 59 | её: "yё", 60 | ёё: "yё", 61 | иё: "yё", 62 | йё: "yё", 63 | оё: "yё", 64 | уё: "yё", 65 | ъё: "yё", 66 | ыё: "yё", 67 | ьё: "yё", 68 | эё: "yё", 69 | юё: "yё", 70 | яё: "yё", 71 | }, 72 | next_mapping: null, 73 | ending_mapping: null, 74 | samples: [ 75 | [ 76 | "Юлия, съешь ещё этих мягких французских булок из Йошкар-Олы, да выпей алтайского чаю", 77 | "Yuliya, s”yesh’ yeshchё etikh myagkikh frantsuzskikh bulok iz Yoshkar-Oly, da vypey altayskogo chayu", 78 | ], 79 | [ 80 | "Россия, город Йошкар-Ола, улица Яна Крастыня", 81 | "Rossiya, gorod Yoshkar-Ola, ulitsa Yana Krastynya", 82 | ], 83 | ["Елизово", "Yelizovo"], 84 | ["Чапаевск", "Chapayevsk"], 85 | ["Мейеровка", "Meyyerovka"], 86 | ["Юрьев объезд", "Yur’yev ob”yezd"], 87 | ["Белкино", "Belkino"], 88 | ["Ёдва", "Yёdva"], 89 | ["Змииёвка", "Zmiiyёvka"], 90 | ["Айёган", "Ayyёgan"], 91 | ["Воробьёво", "Vorob’yёvo"], 92 | ["Кебанъёль", "Keban”yёl’"], 93 | ["Озёрный", "Ozёrnyy"], 94 | ["Тыайа", "Tyaya"], 95 | ["Сайылык", "Sayylyk"], 96 | ["Ойусардах", "Oyusardakh"], 97 | ["Йошкар-Ола", "Yoshkar-Ola"], 98 | ["Бийск", "Biysk"], 99 | ["Тыэкан", "Tyekan"], 100 | ["Суык-Су", "Suyk-Su"], 101 | ["Тында", "Tynda"], 102 | ["Улан-Удэ", "Ulan-Ude"], 103 | ["Электрогорск", "Elektrogorsk"], 104 | ["Руэм", "Ruem"], 105 | ["Вяртсиля", "Vyartsilya"], 106 | ["Ташчишма", "Tashchishma"], 107 | ], 108 | } as TransliterationSchema; 109 | -------------------------------------------------------------------------------- /scripts/generate.ts: -------------------------------------------------------------------------------- 1 | import Ajv, { ValidateFunction } from "ajv"; 2 | import execa from "execa"; 3 | import { access, readdir, readFile, writeFile } from "fs/promises"; 4 | import { compileFromFile } from "json-schema-to-typescript"; 5 | import { basename, join, resolve } from "path"; 6 | 7 | const INPUT_DIR = resolve(join(__dirname, "..", ".data")); 8 | const OUTPUT_DIR = resolve(join(__dirname, "..", "src/generated")); 9 | const SOURCE_REPO = "https://github.com/nalgeon/iuliia"; 10 | const DEFINITIONS_FILE = join(OUTPUT_DIR, "_definitions.ts"); 11 | const TRANSLITERATION_SCHEMA_FILE = join(OUTPUT_DIR, "TransliterationSchema.ts"); 12 | const JSON_SCHEMA_FILE = join(INPUT_DIR, "schema.jsd"); 13 | 14 | async function clone() { 15 | try { 16 | await access(join(INPUT_DIR, ".git")); 17 | } catch (e) { 18 | const { all: output } = await execa("git", ["clone", SOURCE_REPO, INPUT_DIR], { 19 | all: true, 20 | }); 21 | console.log(output); 22 | } 23 | } 24 | 25 | async function jsonToTS(inputFile: string, v: ValidateFunction): Promise { 26 | const input = await readFile(inputFile, "utf8"); 27 | const data = JSON.parse(input); 28 | if (!v(data)) { 29 | console.error(`Validation failed for file ${inputFile}:`); 30 | console.error(v.errors); 31 | process.exit(1); 32 | } 33 | return ( 34 | "import {TransliterationSchema} from './TransliterationSchema';\n\n" + 35 | `export default ${JSON.stringify(data)} as TransliterationSchema;` 36 | ); 37 | } 38 | 39 | function createDefinitions(moduleNames: string[]): string { 40 | const imports = moduleNames.map((mod) => `import ${mod} from "./${mod}";`).join("\n"); 41 | const defaultExport = `export default [${moduleNames.join(",")}]`; 42 | return `${imports}\n\n${defaultExport}`; 43 | } 44 | 45 | async function createValidator() { 46 | const ajv = new Ajv(); 47 | return ajv.compile(JSON.parse(await readFile(JSON_SCHEMA_FILE, "utf8"))); 48 | } 49 | 50 | async function generate() { 51 | const validator = await createValidator(); 52 | const jsonFiles = (await readdir(INPUT_DIR)).filter((file) => file.endsWith(".json")); 53 | 54 | for (const file of jsonFiles) { 55 | const inputFile = join(INPUT_DIR, file); 56 | const outputFile = join(OUTPUT_DIR, basename(file, ".json") + ".ts"); 57 | console.log(`Writing file: ${inputFile}`); 58 | const output = await jsonToTS(inputFile, validator); 59 | await writeFile(outputFile, output, "utf8"); 60 | } 61 | 62 | console.log(`Writing file: ${DEFINITIONS_FILE}`); 63 | const moduleNames = jsonFiles.map((file) => basename(file, ".json")); 64 | const definitions = createDefinitions(moduleNames); 65 | await writeFile(DEFINITIONS_FILE, definitions, "utf8"); 66 | } 67 | 68 | async function generateSchemaDefinitionInterface() { 69 | console.log(`Writing file: ${TRANSLITERATION_SCHEMA_FILE}`); 70 | const schemaDefinitionInterfaceSource = await compileFromFile(JSON_SCHEMA_FILE); 71 | await writeFile(TRANSLITERATION_SCHEMA_FILE, schemaDefinitionInterfaceSource, "utf8"); 72 | } 73 | 74 | (async () => { 75 | await clone(); 76 | await generate(); 77 | await generateSchemaDefinitionInterface(); 78 | })(); 79 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | import { translate } from "./engine"; 2 | import { Schema } from "./schema"; 3 | import { Schemas } from "./schemas"; 4 | 5 | export { translate, Schema, Schemas }; 6 | 7 | import ala_lc from "./generated/ala_lc"; 8 | import ala_lc_alt from "./generated/ala_lc_alt"; 9 | import bgn_pcgn from "./generated/bgn_pcgn"; 10 | import bgn_pcgn_alt from "./generated/bgn_pcgn_alt"; 11 | import bs_2979 from "./generated/bs_2979"; 12 | import bs_2979_alt from "./generated/bs_2979_alt"; 13 | import gost_16876 from "./generated/gost_16876"; 14 | import gost_16876_alt from "./generated/gost_16876_alt"; 15 | import gost_52290 from "./generated/gost_52290"; 16 | import gost_52535 from "./generated/gost_52535"; 17 | import gost_7034 from "./generated/gost_7034"; 18 | import gost_779 from "./generated/gost_779"; 19 | import gost_779_alt from "./generated/gost_779_alt"; 20 | import icao_doc_9303 from "./generated/icao_doc_9303"; 21 | import iso_9_1954 from "./generated/iso_9_1954"; 22 | import iso_9_1968 from "./generated/iso_9_1968"; 23 | import iso_9_1968_alt from "./generated/iso_9_1968_alt"; 24 | import mosmetro from "./generated/mosmetro"; 25 | import mvd_310 from "./generated/mvd_310"; 26 | import mvd_310_fr from "./generated/mvd_310_fr"; 27 | import mvd_782 from "./generated/mvd_782"; 28 | import scientific from "./generated/scientific"; 29 | import telegram from "./generated/telegram"; 30 | import ungegn_1987 from "./generated/ungegn_1987"; 31 | import wikipedia from "./generated/wikipedia"; 32 | import yandex_maps from "./generated/yandex_maps"; 33 | import yandex_money from "./generated/yandex_money"; 34 | 35 | export const ALA_LC = /*#__PURE__*/ Schema.load(ala_lc); 36 | export const ALA_LC_ALT = /*#__PURE__*/ Schema.load(ala_lc_alt); 37 | export const BGN_PCGN = /*#__PURE__*/ Schema.load(bgn_pcgn); 38 | export const BGN_PCGN_ALT = /*#__PURE__*/ Schema.load(bgn_pcgn_alt); 39 | export const BS_2979 = /*#__PURE__*/ Schema.load(bs_2979); 40 | export const BS_2979_ALT = /*#__PURE__*/ Schema.load(bs_2979_alt); 41 | export const GOST_16876 = /*#__PURE__*/ Schema.load(gost_16876); 42 | export const GOST_16876_ALT = /*#__PURE__*/ Schema.load(gost_16876_alt); 43 | export const GOST_52290 = /*#__PURE__*/ Schema.load(gost_52290); 44 | export const GOST_52535 = /*#__PURE__*/ Schema.load(gost_52535); 45 | export const GOST_7034 = /*#__PURE__*/ Schema.load(gost_7034); 46 | export const GOST_779 = /*#__PURE__*/ Schema.load(gost_779); 47 | export const GOST_779_ALT = /*#__PURE__*/ Schema.load(gost_779_alt); 48 | export const ICAO_DOC_9303 = /*#__PURE__*/ Schema.load(icao_doc_9303); 49 | export const ISO_9_1954 = /*#__PURE__*/ Schema.load(iso_9_1954); 50 | export const ISO_9_1968 = /*#__PURE__*/ Schema.load(iso_9_1968); 51 | export const ISO_9_1968_ALT = /*#__PURE__*/ Schema.load(iso_9_1968_alt); 52 | export const MOSMETRO = /*#__PURE__*/ Schema.load(mosmetro); 53 | export const MVD_310 = /*#__PURE__*/ Schema.load(mvd_310); 54 | export const MVD_310_FR = /*#__PURE__*/ Schema.load(mvd_310_fr); 55 | export const MVD_782 = /*#__PURE__*/ Schema.load(mvd_782); 56 | export const SCIENTIFIC = /*#__PURE__*/ Schema.load(scientific); 57 | export const TELEGRAM = /*#__PURE__*/ Schema.load(telegram); 58 | export const UNGEGN_1987 = /*#__PURE__*/ Schema.load(ungegn_1987); 59 | export const WIKIPEDIA = /*#__PURE__*/ Schema.load(wikipedia); 60 | export const YANDEX_MAPS = /*#__PURE__*/ Schema.load(yandex_maps); 61 | export const YANDEX_MONEY = /*#__PURE__*/ Schema.load(yandex_money); 62 | -------------------------------------------------------------------------------- /src/mapping.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Transliteration mappings. 3 | */ 4 | 5 | export const BASE_MAPPING = new Map([ 6 | ["а", "a"], 7 | ["б", "b"], 8 | ["в", "v"], 9 | ["г", "g"], 10 | ["д", "d"], 11 | ["е", "e"], 12 | ["з", "z"], 13 | ["и", "i"], 14 | ["к", "k"], 15 | ["л", "l"], 16 | ["м", "m"], 17 | ["н", "n"], 18 | ["о", "o"], 19 | ["п", "p"], 20 | ["р", "r"], 21 | ["с", "s"], 22 | ["т", "t"], 23 | ["у", "u"], 24 | ["ф", "f"], 25 | ]); 26 | 27 | /** Letter map for transliteration schema. */ 28 | export class Mapping { 29 | protected map: Map; 30 | 31 | constructor(mapping: Map) { 32 | this.map = new Map(mapping); 33 | } 34 | 35 | public valueOf(): Map { 36 | return this.map; 37 | } 38 | 39 | /** 40 | * Return mapped value for `key` if key is in the map, else `defaultValue`. 41 | */ 42 | public get(key: string, defaultValue?: string): string | undefined { 43 | const value = this.map.get(key); 44 | return value !== undefined ? value : defaultValue; 45 | } 46 | } 47 | 48 | /** Mapping for individual letters. */ 49 | export class LetterMapping extends Mapping { 50 | constructor(mapping: Map) { 51 | super(mapping); 52 | const upperMap = new Map(); 53 | for (const [key, value] of mapping) { 54 | upperMap.set(capitalize(key), capitalize(value)); 55 | } 56 | this.map = new Map([...this.map, ...upperMap]); 57 | } 58 | } 59 | 60 | /** Mapping for letters with respect to previous sibling. */ 61 | export class PrevMapping extends Mapping { 62 | constructor(mapping: Map) { 63 | super(mapping); 64 | const upperMap1 = new Map(); 65 | for (const [key, value] of mapping) { 66 | upperMap1.set(capitalize(key), value); 67 | } 68 | const upperMap2 = new Map(); 69 | for (const [key, value] of mapping) { 70 | upperMap2.set(key.toUpperCase(), capitalize(value)); 71 | } 72 | this.map = new Map([...this.map, ...upperMap1, ...upperMap2]); 73 | } 74 | } 75 | 76 | /** Mapping for letters with respect to next sibling. */ 77 | export class NextMapping extends Mapping { 78 | constructor(mapping: Map) { 79 | super(mapping); 80 | const upperMap1 = new Map(); 81 | for (const [key, value] of mapping) { 82 | upperMap1.set(capitalize(key), capitalize(value)); 83 | } 84 | const upperMap2 = new Map(); 85 | for (const [key, value] of mapping) { 86 | upperMap2.set(key.toUpperCase(), capitalize(value)); 87 | } 88 | this.map = new Map([...this.map, ...upperMap1, ...upperMap2]); 89 | } 90 | } 91 | 92 | /** Mapping for word endings. */ 93 | export class EndingMapping extends Mapping { 94 | constructor(mapping: Map) { 95 | super(mapping); 96 | const upperMap = new Map(); 97 | for (const [key, value] of mapping) { 98 | upperMap.set(key.toUpperCase(), value.toUpperCase()); 99 | } 100 | this.map = new Map([...this.map, ...upperMap]); 101 | } 102 | } 103 | 104 | /** Capitalize string */ 105 | function capitalize(s: string): string { 106 | return s.charAt(0).toUpperCase() + s.slice(1); 107 | } 108 | -------------------------------------------------------------------------------- /src/generated/bgn_pcgn.ts: -------------------------------------------------------------------------------- 1 | import { TransliterationSchema } from "./TransliterationSchema"; 2 | 3 | export default { 4 | name: "bgn_pcgn", 5 | description: "BGN/PCGN transliteration schema", 6 | url: "https://iuliia.ru/bgn-pcgn/", 7 | mapping: { 8 | а: "a", 9 | б: "b", 10 | в: "v", 11 | г: "g", 12 | д: "d", 13 | е: "e", 14 | ё: "ё", 15 | ж: "zh", 16 | з: "z", 17 | и: "i", 18 | й: "y", 19 | к: "k", 20 | л: "l", 21 | м: "m", 22 | н: "n", 23 | о: "o", 24 | п: "p", 25 | р: "r", 26 | с: "s", 27 | т: "t", 28 | у: "u", 29 | ф: "f", 30 | х: "kh", 31 | ц: "ts", 32 | ч: "ch", 33 | ш: "sh", 34 | щ: "shch", 35 | ъ: "”", 36 | ы: "y", 37 | ь: "’", 38 | э: "e", 39 | ю: "yu", 40 | я: "ya", 41 | }, 42 | prev_mapping: { 43 | е: "ye", 44 | ае: "ye", 45 | ее: "ye", 46 | ёе: "ye", 47 | ие: "ye", 48 | йе: "ye", 49 | ое: "ye", 50 | уе: "ye", 51 | ъе: "ye", 52 | ые: "ye", 53 | ье: "ye", 54 | эе: "ye", 55 | юе: "ye", 56 | яе: "ye", 57 | ё: "yё", 58 | аё: "yё", 59 | её: "yё", 60 | ёё: "yё", 61 | иё: "yё", 62 | йё: "yё", 63 | оё: "yё", 64 | уё: "yё", 65 | ъё: "yё", 66 | ыё: "yё", 67 | ьё: "yё", 68 | эё: "yё", 69 | юё: "yё", 70 | яё: "yё", 71 | аы: "·y", 72 | еы: "·y", 73 | ёы: "·y", 74 | иы: "·y", 75 | оы: "·y", 76 | уы: "·y", 77 | ыы: "·y", 78 | эы: "·y", 79 | юы: "·y", 80 | яы: "·y", 81 | бэ: "·e", 82 | вэ: "·e", 83 | гэ: "·e", 84 | дэ: "·e", 85 | жэ: "·e", 86 | зэ: "·e", 87 | кэ: "·e", 88 | лэ: "·e", 89 | мэ: "·e", 90 | нэ: "·e", 91 | пэ: "·e", 92 | рэ: "·e", 93 | сэ: "·e", 94 | тэ: "·e", 95 | фэ: "·e", 96 | хэ: "·e", 97 | цэ: "·e", 98 | чэ: "·e", 99 | шэ: "·e", 100 | щэ: "·e", 101 | }, 102 | next_mapping: { 103 | йа: "y·", 104 | йу: "y·", 105 | йы: "y·", 106 | йэ: "y·", 107 | ыа: "y·", 108 | ыу: "y·", 109 | ыы: "y·", 110 | ыэ: "y·", 111 | тс: "t·", 112 | шч: "sh·", 113 | }, 114 | ending_mapping: null, 115 | samples: [ 116 | [ 117 | "Юлия, съешь ещё этих мягких французских булок из Йошкар-Олы, да выпей алтайского чаю", 118 | "Yuliya, s”yesh’ yeshchё etikh myagkikh frantsuzskikh bulok iz Yoshkar-Oly, da vypey altayskogo chayu", 119 | ], 120 | [ 121 | "Россия, город Йошкар-Ола, улица Яна Крастыня", 122 | "Rossiya, gorod Yoshkar-Ola, ulitsa Yana Krastynya", 123 | ], 124 | ["Елизово", "Yelizovo"], 125 | ["Чапаевск", "Chapayevsk"], 126 | ["Мейеровка", "Meyyerovka"], 127 | ["Юрьев объезд", "Yur’yev ob”yezd"], 128 | ["Белкино", "Belkino"], 129 | ["Ёдва", "Yёdva"], 130 | ["Змииёвка", "Zmiiyёvka"], 131 | ["Айёган", "Ayyёgan"], 132 | ["Воробьёво", "Vorob’yёvo"], 133 | ["Кебанъёль", "Keban”yёl’"], 134 | ["Озёрный", "Ozёrnyy"], 135 | ["Тыайа", "Ty·ay·a"], 136 | ["Сайылык", "Say·ylyk"], 137 | ["Ойусардах", "Oy·usardakh"], 138 | ["Йошкар-Ола", "Yoshkar-Ola"], 139 | ["Бийск", "Biysk"], 140 | ["Тыэкан", "Ty·ekan"], 141 | ["Суык-Су", "Su·yk-Su"], 142 | ["Тында", "Tynda"], 143 | ["Улан-Удэ", "Ulan-Ud·e"], 144 | ["Электрогорск", "Elektrogorsk"], 145 | ["Руэм", "Ruem"], 146 | ["Вяртсиля", "Vyart·silya"], 147 | ["Ташчишма", "Tash·chishma"], 148 | ], 149 | } as TransliterationSchema; 150 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # `Iuliia` 2 | 3 | > Transliterate Cyrillic → Latin in every possible way 4 | 5 | > This is the TypeScript port of the Python [iuliia](https://github.com/nalgeon/iuliia-py) package 6 | 7 | [![NPM Version][npm-image]][npm-url] 8 | [![Build Status][build-image]][build-url] 9 | [![Code Coverage][coverage-image]][coverage-url] 10 | [![Code Quality][quality-image]][quality-url] 11 | 12 | Transliteration means representing Cyrillic data (mainly names and geographic locations) with Latin letters. It is used for international passports, visas, green cards, driving licenses, mail and goods delivery etc. 13 | 14 | `Iuliia` makes transliteration as easy as: 15 | 16 | ```ts 17 | // Typescript, ES6 18 | import * as iuliia from "iuliia"; 19 | iuliia.translate("Юлия Щеглова", iuliia.WIKIPEDIA); 20 | 'Yuliya Shcheglova' 21 | ``` 22 | 23 | ```js 24 | // CommonJS 25 | const iuliia = require("iuliia"); 26 | iuliia.translate("Юлия Щеглова", iuliia.WIKIPEDIA); 27 | 'Yuliya Shcheglova' 28 | ``` 29 | 30 | ## Why use `Iuliia` 31 | 32 | - [20 transliteration schemas](https://github.com/nalgeon/iuliia) (rule sets), including all main international and Russian standards. 33 | - Correctly implements not only the base mapping, but all the special rules for letter combinations and word endings. 34 | - Simple API and zero third-party dependencies. 35 | 36 | For schema details and other information, see (in Russian). 37 | 38 | [Issues and limitations](https://github.com/nalgeon/iuliia/blob/master/README.md#issues-and-limitations) 39 | 40 | ## Installation 41 | 42 | ```sh 43 | npm install iuliia 44 | ``` 45 | 46 | ## Usage 47 | 48 | ES2015 JavaScript: 49 | 50 | ```js 51 | import iuliia from "iuliia"; 52 | 53 | // list all supported schemas 54 | for (let schemaName of iuliia.Schemas.names()) { 55 | console.log(schemaName); 56 | } 57 | 58 | // transliterate using specified schema 59 | let source = "Юлия Щеглова"; 60 | iuliia.translate(source, iuliia.ICAO_DOC_9303); 61 | // "Iuliia Shcheglova" 62 | 63 | // or pick schema by name 64 | let schema = iuliia.Schemas.get("wikipedia"); 65 | iuliia.translate(source, schema); 66 | // "Yuliya Shcheglova" 67 | ``` 68 | 69 | ES5 browser JavaScript: 70 | 71 | ```html 72 | 73 | 78 | ``` 79 | 80 | Supported schemas: 81 | 82 | ``` 83 | ala_lc iuliia.ALA_LC 84 | ala_lc_alt iuliia.ALA_LC_ALT 85 | bgn_pcgn iuliia.BGN_PCGN 86 | bgn_pcgn_alt iuliia.BGN_PCGN_ALT 87 | bs_2979 iuliia.BS_2979 88 | bs_2979_alt iuliia.BS_2979_ALT 89 | gost_779 iuliia.GOST_779 90 | gost_779_alt iuliia.GOST_779_ALT 91 | gost_7034 iuliia.GOST_7034 92 | gost_16876 iuliia.GOST_16876 93 | gost_16876_alt iuliia.GOST_16876_ALT 94 | gost_52290 iuliia.GOST_52290 95 | gost_52535 iuliia.GOST_52535 96 | icao_doc_9303 iuliia.ICAO_DOC_9303 97 | iso_9_1954 iuliia.ISO_9_1954 98 | iso_9_1968 iuliia.ISO_9_1968 99 | iso_9_1968_alt iuliia.ISO_9_1968_ALT 100 | mosmetro iuliia.MOSMETRO 101 | mvd_310 iuliia.MVD_310 102 | mvd_310_fr iuliia.MVD_310_FR 103 | mvd_782 iuliia.MVD_782 104 | scientific iuliia.SCIENTIFIC 105 | telegram iuliia.TELEGRAM 106 | ungegn_1987 iuliia.UNGEGN_1987 107 | wikipedia iuliia.WIKIPEDIA 108 | yandex_maps iuliia.YANDEX_MAPS 109 | yandex_money iuliia.YANDEX_MONEY 110 | ``` 111 | 112 | ## Development setup 113 | 114 | Install dependencies: 115 | 116 | ```sh 117 | npm ci 118 | ``` 119 | 120 | Generate schemas from the [schemas repository](https://github.com/nalgeon/iuliia): 121 | 122 | ```sh 123 | npm run generate 124 | ``` 125 | 126 | Format the source code: 127 | 128 | ```sh 129 | npm run format 130 | ``` 131 | 132 | Run ESLint checks: 133 | 134 | 135 | ```sh 136 | npm run lint 137 | ``` 138 | 139 | Run unit tests: 140 | 141 | ```sh 142 | npm test 143 | ``` 144 | 145 | Build JS: 146 | 147 | ```sh 148 | npm run build 149 | ``` 150 | 151 | ## Contributing 152 | 153 | Pull requests are welcome. For major changes, please open an issue first to discuss what you would like to change. 154 | 155 | Make sure to add or update tests as appropriate. 156 | 157 | Use [Conventional Commits](https://www.conventionalcommits.org/en/v1.0.0-beta.4/) for commit messages. 158 | 159 | ## [Changelog](CHANGELOG.md) 160 | 161 | ## License 162 | 163 | [MIT](https://choosealicense.com/licenses/mit/) 164 | 165 | 166 | 167 | [npm-image]: https://img.shields.io/npm/v/iuliia?style=flat-square 168 | [npm-url]: https://www.npmjs.com/package/iuliia 169 | [build-image]: https://img.shields.io/travis/nalgeon/iuliia-js?style=flat-square 170 | [build-url]: https://travis-ci.org/nalgeon/iuliia-js 171 | [coverage-image]: https://img.shields.io/coveralls/github/nalgeon/iuliia-js?style=flat-square 172 | [coverage-url]: https://coveralls.io/github/nalgeon/iuliia-js 173 | [quality-image]: https://img.shields.io/codeclimate/maintainability/nalgeon/iuliia-js?style=flat-square 174 | [quality-url]: https://codeclimate.com/github/nalgeon/iuliia-js 175 | --------------------------------------------------------------------------------