commit c89e9633598a7b5834fcfc9a91ab997050bb58ec Author: Vitaliy Filippov Date: Sat Aug 28 01:27:40 2021 +0300 JS/JSX auto-translator for Russian language: initial commit diff --git a/index.js b/index.js new file mode 100644 index 0000000..763972a --- /dev/null +++ b/index.js @@ -0,0 +1,114 @@ +const fs = require('fs'); + +module.exports = function(babel) +{ + const t = babel.types; + const importAdded = new WeakSet(); + const arg0 = new WeakSet(); + const ru = /[А-ЯЁа-яё]/; + const strings = {}; + const addString = function(path, str) + { + strings[path.hub.file.opts.filename][str] = str; + }; + const addImport = function(path) + { + const program = path.findParent(p => t.isProgram(p)); + if (!program) + { + throw new Error(' AST element not found, can\'t add import'); + } + if (!importAdded.has(program)) + { + program.unshiftContainer('body', t.importDeclaration( + [ t.importSpecifier(t.identifier('L'), t.identifier('L')) ], + t.stringLiteral('babel-plugin-react-translate/runtime') + )); + importAdded.add(program); + } + }; + return { + visitor: { + Program: { + enter(path, state) + { + strings[path.hub.file.opts.filename] = {}; + }, + exit(path, state) + { + let found = false; + for (let k in strings[path.hub.file.opts.filename]) + { + found = true; + break; + } + if (!found) + delete strings[path.hub.file.opts.filename]; + fs.writeFileSync( + path.hub.file.opts.root+'/'+(state.opts['output'] || 'react-translate-output.json'), + JSON.stringify(strings, null, 2) + ); + }, + }, + Literal(path) + { + if (ru.exec(path.node.value)) + { + addString(path, path.node.value); + addImport(path); + const parent = path.findParent(() => true); + if (parent.isJSXAttribute()) + path.replaceWith(t.jsxExpressionContainer(t.callExpression(t.identifier('L'), [ path.node ]))); + else if (!arg0.has(path.node) && !path.findParent(parent => arg0.has(parent.node))) + { + // Stop the original string from being visited again + arg0.add(path.node); + path.replaceWith(t.callExpression(t.identifier('L'), [ path.node ])); + } + } + }, + CallExpression(path) + { + if (path.node.callee.type === 'Identifier' && path.node.callee.name === 'L') + { + // Skip the first argument + arg0.add(path.node.arguments[0]); + } + }, + JSXText(path) + { + if (ru.exec(path.node.value)) + { + addImport(path); + const lwhite = /^\s+/.exec(path.node.value); + const rwhite = /\s+$/.exec(path.node.value); + const llen = lwhite ? lwhite[0].length : 0; + const text = path.node.value.substr(llen, path.node.value.length - llen - (rwhite ? rwhite[0].length : 0)); + addString(path, text); + const repl = []; + if (lwhite) + repl.push(t.jsxText(lwhite[0])); + repl.push(t.jsxExpressionContainer(t.callExpression(t.identifier('L'), [ t.stringLiteral(text) ]))); + if (rwhite) + repl.push(t.jsxText(rwhite[0])); + path.replaceWithMultiple(repl); + } + }, + TemplateLiteral(path) + { + if (path.node.quasis.find(q => ru.exec(q.value.cooked))) + { + addImport(path); + let text = path.node.quasis[0].value.cooked; + for (let i = 1; i < path.node.quasis.length; i++) + text += '{'+i+'}'+path.node.quasis[i].value.cooked; + addString(path, text); + // Stop the string literal from being visited again + text = t.stringLiteral(text); + arg0.add(text); + path.replaceWith(t.callExpression(t.identifier('L'), [ text, ...path.node.expressions ])); + } + }, + }, + }; +} diff --git a/plural_ru.js b/plural_ru.js new file mode 100644 index 0000000..b85d7b2 --- /dev/null +++ b/plural_ru.js @@ -0,0 +1,19 @@ +export default function plural_ru(count, one, few, many) +{ + var sto = count % 100; + var r; + if (sto >= 10 && sto <= 20) + r = many; + else + { + switch (count % 10) + { + case 1: r = one; break; + case 2: + case 3: + case 4: r = few; break; + default: r = many; break; + } + } + return r.replace('%d', count); +} diff --git a/runtime.js b/runtime.js new file mode 100644 index 0000000..027d004 --- /dev/null +++ b/runtime.js @@ -0,0 +1,45 @@ +import plural_ru from './plural_ru.js'; + +const strings = {}; + +export function setStrings(lang, strHash) +{ + strings[lang] = strHash; +} + +export function addStrings(lang, strHash) +{ + Object.assign(strings[lang], strHash); +} + +let fallback = 'en', language = 'ru'; + +export function setLanguage(lang) +{ + language = lang; +} + +export function setFallback(lang) +{ + fallback = lang; +} + +export function L(s) +{ + s = strings[language] && strings[language][s] || strings[fallback] && strings[fallback][s] || s; + if (arguments.length > 1) + { + const arg = arguments; + s = s.replace(/\{(\d+)\}/g, (m, m1) => (arg[parseInt(m1)]||'')); + s = s.replace( + /\{N:(\d+):((?:[^:\\]+|\\.)*):((?:[^:\\]+|\\.)*):((?:[^:\\]+|\\.)*)\}/g, + (m, m1, m2, m3, m4) => plural_ru( + arg[parseInt(m1)]||'', + m2.replace(/\\(.)/g, '$1'), + m3.replace(/\\(.)/g, '$1'), + m4.replace(/\\(.)/g, '$1') + ) + ); + } + return s; +}