bobril-g11n
Advanced tools
Comparing version 4.4.1 to 5.0.0
# CHANGELOG | ||
## 5.0.0 | ||
Needs TypeScript 3.7+, directly depends on Bobril. Support formatting of elements of virtual dom. | ||
```tsx | ||
f("Hello {1}{world}{/1}", { 1: (p: b.IBobrilChildren) => <b>{p}</b>, world: "World" }); | ||
f("Simple {1/}", { 1: () => <App /> }); | ||
// Next thing also needs support in bobril-build | ||
<T hint="translation hint" param1={42}> | ||
Answer is <strong>{t("{param1}")}</strong>! | ||
</T>; | ||
``` | ||
## 4.4.1 | ||
@@ -4,0 +18,0 @@ |
@@ -0,0 +0,0 @@ export * from './src/msgFormatParser'; |
{ | ||
"name": "bobril-g11n", | ||
"version": "4.4.1", | ||
"version": "5.0.0", | ||
"description": "Bobril globalization", | ||
@@ -10,6 +10,6 @@ "main": "index.js", | ||
"dependencies": { | ||
"bobril": "*", | ||
"moment": "^2.10.6" | ||
}, | ||
"devDependencies": { | ||
"bobril": "*", | ||
"typescript": "*" | ||
@@ -16,0 +16,0 @@ }, |
@@ -0,0 +0,0 @@ # bobril-g11n |
@@ -0,15 +1,18 @@ | ||
import { MsgAst } from "./msgFormatParser"; | ||
import { isString, isArray } from "bobril"; | ||
export function extractUsedParams(msgAst: any): string[] { | ||
let params = Object.create(null); | ||
extractUsedParamsRec(params, msgAst); | ||
let params = Object.create(null); | ||
extractUsedParamsRec(params, msgAst); | ||
return Object.keys(params).sort(); | ||
} | ||
function extractUsedParamsRec(usedParams: { [name:string]:boolean }, msgAst: any) { | ||
if (typeof msgAst === 'string') { | ||
function extractUsedParamsRec(usedParams: { [name: string]: boolean }, msgAst: MsgAst) { | ||
if (isString(msgAst)) { | ||
return; | ||
} | ||
if (Array.isArray(msgAst)) { | ||
if (isArray(msgAst)) { | ||
for (let i = 0; i < msgAst.length; i++) { | ||
let item = msgAst[i]; | ||
extractUsedParamsRec(usedParams, item); | ||
extractUsedParamsRec(usedParams, item); | ||
} | ||
@@ -19,35 +22,40 @@ return; | ||
switch (msgAst.type) { | ||
case 'arg': | ||
usedParams[msgAst.id] = true; | ||
case "arg": | ||
usedParams[msgAst.id] = true; | ||
return; | ||
case 'hash': | ||
return; | ||
case 'format': | ||
usedParams[msgAst.id] = true; | ||
case "hash": | ||
return; | ||
case "concat": | ||
extractUsedParamsRec(usedParams, msgAst.values); | ||
return; | ||
case "el": | ||
usedParams[msgAst.id] = true; | ||
if (msgAst.value != undefined) extractUsedParamsRec(usedParams, msgAst.value); | ||
return; | ||
case "format": | ||
usedParams[msgAst.id] = true; | ||
let type = msgAst.format.type; | ||
switch (type) { | ||
case 'plural': | ||
case 'select': | ||
{ | ||
let options = msgAst.format.options; | ||
case "plural": | ||
case "select": { | ||
let options = msgAst.format.options; | ||
for (let i = 0; i < options.length; i++) { | ||
let opt = options[i]; | ||
extractUsedParamsRec(usedParams, opt.value); | ||
} | ||
break; | ||
} | ||
case "number": | ||
case "date": | ||
case "time": { | ||
let options = msgAst.format.options; | ||
if (options) { | ||
for (let i = 0; i < options.length; i++) { | ||
let opt = options[i]; | ||
extractUsedParamsRec(usedParams, opt.value); | ||
if (typeof options[i].value === "object") { | ||
extractUsedParamsRec(usedParams, options[i].value); | ||
} | ||
} | ||
break; | ||
} | ||
case 'number': | ||
case 'date': | ||
case 'time': | ||
{ | ||
//let style = msgAst.format.style || 'default'; | ||
let options = msgAst.format.options; | ||
if (options) { | ||
for (let i = 0; i < options.length; i++) { | ||
if (typeof options[i].value === 'object') { | ||
extractUsedParamsRec(usedParams, options[i].value); | ||
} | ||
} | ||
} | ||
} | ||
break; | ||
} | ||
} | ||
@@ -54,0 +62,0 @@ return; |
@@ -0,0 +0,0 @@ export interface ILocaleRules { |
@@ -0,1 +1,17 @@ | ||
import { isObject, isArray } from "bobril"; | ||
type MsgAstError = { type: "error"; msg: string; pos: number; line: number; col: number }; | ||
export type MsgAst = | ||
| MsgAstError | ||
| string | ||
| Array<MsgAst> | ||
| { type: "hash" } | ||
| { type: "arg"; id: string } | ||
| { type: "el"; id: number; value?: MsgAst } | ||
| { type: "format"; id: string; format: Record<string, any> } | ||
| { type: "concat"; values: Array<MsgAst> }; | ||
type MsgAstInternal = MsgAst | { type: "open"; id: number } | { type: "close"; id: number }; | ||
let sourceText: string; | ||
@@ -9,3 +25,3 @@ let pos: number; | ||
let curToken: number; | ||
let errorMsg: string; | ||
let errorMsg: string | undefined; | ||
@@ -18,3 +34,3 @@ const EOFToken = -1; | ||
function advanceNextToken() { | ||
function advanceNextToken(): void { | ||
curLine = nextLine; | ||
@@ -28,3 +44,4 @@ curCol = nextCol; | ||
if (ch === 13 || ch === 10) { | ||
nextLine++; nextCol = 1; | ||
nextLine++; | ||
nextCol = 1; | ||
if (ch === 13 && pos < length && sourceText.charCodeAt(pos) === 10) { | ||
@@ -37,3 +54,4 @@ pos++; | ||
nextCol++; | ||
if (ch === 92) { // \ | ||
if (ch === 92) { | ||
// \ | ||
if (pos === length) { | ||
@@ -45,10 +63,12 @@ curToken = 92; | ||
nextCol++; | ||
if (ch === 92 || ch === 123 || ch === 125 || ch === 35) { // \ { } # | ||
if (ch === 92 || ch === 123 || ch === 125 || ch === 35) { | ||
// \ { } # | ||
curToken = ch; | ||
return; | ||
} | ||
if (ch === 117) { // u | ||
if (ch === 117) { | ||
// u | ||
if (pos + 4 <= length) { | ||
let hexcode = sourceText.substr(pos, 4); | ||
if (/^[0-9a-f]+$/ig.test(hexcode)) { | ||
if (/^[0-9a-f]+$/gi.test(hexcode)) { | ||
curToken = parseInt(hexcode, 16); | ||
@@ -60,15 +80,18 @@ pos += 4; | ||
} | ||
errorMsg = 'After \\u there must be 4 hex characters'; | ||
errorMsg = "After \\u there must be 4 hex characters"; | ||
curToken = ErrorToken; | ||
return; | ||
} | ||
errorMsg = 'After \\ there coud be only one of \\{}#u characters'; | ||
errorMsg = "After \\ there coud be only one of \\{}#u characters"; | ||
curToken = ErrorToken; | ||
return; | ||
} | ||
if (ch === 123) { // { | ||
if (ch === 123) { | ||
// { | ||
curToken = OpenBracketToken; | ||
} else if (ch === 125) { // } | ||
} else if (ch === 125) { | ||
// } | ||
curToken = CloseBracketToken; | ||
} else if (ch === 35) { // # | ||
} else if (ch === 35) { | ||
// # | ||
curToken = HashToken; | ||
@@ -80,9 +103,9 @@ } else { | ||
function isError(val: any): boolean { | ||
return (val != null && typeof val === 'object' && val.type === 'error'); | ||
export function isParserError(val: MsgAst): val is MsgAstError { | ||
return isObject(val) && !isArray(val) && val.type === "error"; | ||
} | ||
function buildError(msg?: string): any { | ||
if (msg === undefined) msg = errorMsg; | ||
return { type: 'error', msg, pos: pos - 1, line: curLine, col: curCol }; | ||
function buildError(msg?: string): MsgAstError { | ||
if (msg === undefined) msg = errorMsg || "Error"; | ||
return { type: "error", msg, pos: pos - 1, line: curLine, col: curCol }; | ||
} | ||
@@ -96,12 +119,22 @@ | ||
function parseIdentificator(): any { | ||
let identificator = ''; | ||
if (curToken >= 65 && curToken <= 90 || curToken >= 97 && curToken <= 122 || curToken === 95) { | ||
function parseIdentificator(): string | MsgAstError { | ||
let identificator = ""; | ||
if ((curToken >= 65 && curToken <= 90) || (curToken >= 97 && curToken <= 122) || curToken === 95) { | ||
do { | ||
identificator += String.fromCharCode(curToken); | ||
advanceNextToken(); | ||
} while (curToken >= 65 && curToken <= 90 || curToken >= 97 && curToken <= 122 || curToken === 95 || curToken >= 48 && curToken <= 57); | ||
} else { | ||
return buildError('Expecting identifier [a-zA-Z_]'); | ||
} | ||
} while ( | ||
(curToken >= 65 && curToken <= 90) || | ||
(curToken >= 97 && curToken <= 122) || | ||
curToken === 95 || | ||
(curToken >= 48 && curToken <= 57) | ||
); | ||
} else if (curToken >= 47 && curToken <= 57) { | ||
do { | ||
identificator += String.fromCharCode(curToken); | ||
advanceNextToken(); | ||
} while (curToken >= 47 && curToken <= 57); | ||
if (identificator.charCodeAt(0) == 47 && identificator.charCodeAt(identificator.length - 1) == 47) | ||
return buildError("Slash could be only on one side of number"); | ||
} else return buildError("Expecting identifier [a-zA-Z_] or number"); | ||
return identificator; | ||
@@ -111,3 +144,3 @@ } | ||
function parseChars(): string { | ||
let res = ''; | ||
let res = ""; | ||
do { | ||
@@ -121,3 +154,3 @@ res += String.fromCharCode(curToken); | ||
function parseNumber(): number { | ||
let number = ''; | ||
let number = ""; | ||
do { | ||
@@ -143,7 +176,20 @@ number += String.fromCharCode(curToken); | ||
const numClasses: { [name: string]: number } = { zero: 1, one: 1, two: 1, few: 1, many: 1, other: 1 }; | ||
function parseFormat(): any { | ||
function parseFormat(): MsgAstInternal { | ||
skipWs(); | ||
if (curToken === ErrorToken) return buildError(); | ||
let identificator = parseIdentificator(); | ||
if (isError(identificator)) return identificator; | ||
if (isParserError(identificator)) return identificator; | ||
if (identificator[0] <= "9") { | ||
if (isCloseBracketToken()) { | ||
advanceNextToken(); | ||
if (identificator[identificator.length - 1] == "/") { | ||
return { type: "el", id: parseInt(identificator.substr(0, identificator.length - 1)) }; | ||
} | ||
if (identificator[0] == "/") { | ||
return { type: "close", id: parseInt(identificator.substr(1, identificator.length - 1)) }; | ||
} | ||
return { type: "open", id: parseInt(identificator) }; | ||
} | ||
return buildError("element could not have parameters"); | ||
} | ||
skipWs(); | ||
@@ -153,5 +199,6 @@ if (curToken === ErrorToken) return buildError(); | ||
advanceNextToken(); | ||
return { type: 'arg', id: identificator }; | ||
return { type: "arg", id: identificator }; | ||
} | ||
if (!isComma()) { // , | ||
if (!isComma()) { | ||
// , | ||
return buildError('Expecting "}" or ","'); | ||
@@ -163,11 +210,11 @@ } | ||
let res = { | ||
type: 'format', | ||
type: "format", | ||
id: identificator, | ||
format | ||
}; | ||
} as const; | ||
let name = parseIdentificator(); | ||
if (isError(name)) return name; | ||
if (isParserError(name)) return name; | ||
skipWs(); | ||
if (curToken === ErrorToken) return buildError(); | ||
if (name === 'number' || name === 'time' || name === 'date') { | ||
if (name === "number" || name === "time" || name === "date") { | ||
format.type = name; | ||
@@ -180,7 +227,8 @@ format.style = null; | ||
} | ||
if (isComma()) { // , | ||
if (isComma()) { | ||
// , | ||
advanceNextToken(); | ||
skipWs(); | ||
let style = parseIdentificator(); | ||
if (isError(style)) return name; | ||
if (isParserError(style)) return name; | ||
format.style = style; | ||
@@ -195,8 +243,10 @@ format.options = []; | ||
} | ||
if (isComma()) { // , | ||
if (isComma()) { | ||
// , | ||
advanceNextToken(); | ||
skipWs(); | ||
let optionName = parseIdentificator(); | ||
if (isError(optionName)) return optionName; | ||
if (curToken === 58) { // : | ||
if (isParserError(optionName)) return optionName; | ||
if (curToken === 58) { | ||
// : | ||
advanceNextToken(); | ||
@@ -213,3 +263,3 @@ skipWs(); | ||
} | ||
if (isError(val)) return val; | ||
if (isParserError(val)) return val; | ||
format.options.push({ key: optionName, value: val }); | ||
@@ -225,9 +275,9 @@ } else { | ||
return buildError('Expecting "," or "}"'); | ||
} else if (name === 'plural' || name === 'selectordinal') { | ||
} else if (name === "plural" || name === "selectordinal") { | ||
let options: any[] = []; | ||
format.type = 'plural'; | ||
format.ordinal = name !== 'plural'; | ||
format.type = "plural"; | ||
format.ordinal = name !== "plural"; | ||
format.offset = 0; | ||
format.options = options; | ||
if (!isComma()) { // , | ||
if (!isComma()) { | ||
return buildError('Expecting ","'); | ||
@@ -248,6 +298,6 @@ } | ||
format.offset = parseInt(m[1], 10); | ||
} else if (chars === 'offset:') { | ||
} else if (chars === "offset:") { | ||
skipWs(); | ||
if (curToken < 48 || curToken > 57) { | ||
return buildError('Expecting number'); | ||
return buildError("Expecting number"); | ||
} | ||
@@ -265,3 +315,4 @@ format.offset = parseNumber(); | ||
selector = chars; | ||
if (!numClasses[selector]) return buildError("Selector " + selector + " is not one of " + Object.keys(numClasses).join(", ")); | ||
if (!numClasses[selector]) | ||
return buildError("Selector " + selector + " is not one of " + Object.keys(numClasses).join(", ")); | ||
} | ||
@@ -273,3 +324,3 @@ if (!isOpenBracketToken()) { | ||
let value = parseMsg(false); | ||
if (isError(value)) return value; | ||
if (isParserError(value)) return value; | ||
options.push({ selector, value }); | ||
@@ -280,7 +331,8 @@ skipWs(); | ||
return res; | ||
} else if (name === 'select') { | ||
} else if (name === "select") { | ||
let options: any[] = []; | ||
format.type = 'select'; | ||
format.type = "select"; | ||
format.options = options; | ||
if (!isComma()) { // , | ||
if (!isComma()) { | ||
// , | ||
return buildError('Expecting ","'); | ||
@@ -307,3 +359,3 @@ } | ||
let value = parseMsg(false); | ||
if (isError(value)) return value; | ||
if (isParserError(value)) return value; | ||
options.push({ selector, value }); | ||
@@ -318,4 +370,10 @@ skipWs(); | ||
function parseMsg(endWithEOF: boolean): any { | ||
let res: any = null; | ||
function parseMsg(endWithEOF: boolean | number): MsgAst { | ||
let res: MsgAst | null = null; | ||
let wrapByConcat = false; | ||
function normalize(res: MsgAst | null): MsgAst { | ||
if (res === null) return ""; | ||
if (!isArray(res) || !wrapByConcat) return res; | ||
return { type: "concat", values: res }; | ||
} | ||
while (true) { | ||
@@ -326,24 +384,39 @@ if (curToken === ErrorToken) { | ||
if (curToken === EOFToken) { | ||
if (endWithEOF) { | ||
if (res === null) return ''; | ||
return res; | ||
if (endWithEOF === true) { | ||
return normalize(res); | ||
} | ||
return buildError('Unexpected end of message missing "}"'); | ||
if (endWithEOF === false) return buildError('Unexpected end of message missing "}"'); | ||
return buildError('Unexpected end of message missing "{/' + endWithEOF + '}"'); | ||
} | ||
let val: any; | ||
let val: MsgAst; | ||
if (curToken === OpenBracketToken) { | ||
advanceNextToken(); | ||
val = parseFormat(); | ||
let format = parseFormat(); | ||
if (isObject(format) && !isArray(format)) { | ||
if (format.type == "open") { | ||
const nested = parseMsg(format.id); | ||
if (isParserError(nested)) return nested; | ||
format = { type: "el", id: format.id, value: nested }; | ||
wrapByConcat = true; | ||
} else if (format.type == "close") { | ||
if (format.id === endWithEOF) { | ||
return normalize(res); | ||
} | ||
return buildError('Missing closing "{/' + endWithEOF + '}" got "{/' + format.id + '}" instead.'); | ||
} else if (format.type == "el") { | ||
wrapByConcat = true; | ||
} | ||
} | ||
val = format; | ||
} else if (curToken === HashToken) { | ||
advanceNextToken(); | ||
val = { type: 'hash' }; | ||
val = { type: "hash" }; | ||
} else if (curToken === CloseBracketToken) { | ||
if (endWithEOF) { | ||
if (endWithEOF !== false) { | ||
return buildError('Unexpected "}". Maybe you forgot to prefix it with "\\".'); | ||
} | ||
advanceNextToken(); | ||
if (res === null) return ''; | ||
return res; | ||
return normalize(res); | ||
} else { | ||
val = ''; | ||
val = ""; | ||
while (curToken >= 0) { | ||
@@ -354,4 +427,5 @@ val += String.fromCharCode(curToken); | ||
} | ||
if (isError(val)) return val; | ||
if (res === null) res = val; else { | ||
if (isParserError(val)) return val; | ||
if (res === null) res = val; | ||
else { | ||
if (Array.isArray(res)) { | ||
@@ -366,3 +440,3 @@ res.push(val); | ||
export function parse(text: string): any { | ||
export function parse(text: string): MsgAst { | ||
pos = 0; | ||
@@ -369,0 +443,0 @@ sourceText = text; |
@@ -5,3 +5,5 @@ import * as moment from "moment"; | ||
import * as numberFormatter from "./numberFormatter"; | ||
import { MsgAst } from "./msgFormatParser"; | ||
import { f } from "./translate"; | ||
import { isString, isArray } from "bobril"; | ||
@@ -120,7 +122,7 @@ (<any>window).moment = moment; | ||
export function compile(locale: string, msgAst: any): (params?: Object, hashArg?: string) => string { | ||
if (typeof msgAst === "string") { | ||
export function compile(locale: string, msgAst: MsgAst): (params?: Object, hashArg?: string) => string { | ||
if (isString(msgAst)) { | ||
return () => msgAst; | ||
} | ||
if (Array.isArray(msgAst)) { | ||
if (isArray(msgAst)) { | ||
if (msgAst.length === 0) return () => ""; | ||
@@ -151,2 +153,29 @@ let comp = new RuntimeFunctionGenerator(); | ||
}; | ||
case "concat": { | ||
const vals = msgAst.values; | ||
if (vals.length === 0) return () => ""; | ||
let comp = new RuntimeFunctionGenerator(); | ||
let argParams = comp.addArg(0); | ||
let argHash = comp.addArg(1); | ||
comp.addBody("return ["); | ||
for (let i = 0; i < vals.length; i++) { | ||
if (i > 0) comp.addBody(","); | ||
let item = vals[i]; | ||
if (isString(item)) { | ||
comp.addBody(comp.addConstant(item)); | ||
} else { | ||
comp.addBody(comp.addConstant(compile(locale, item)) + `(${argParams},${argHash})`); | ||
} | ||
} | ||
comp.addBody("];"); | ||
return <(params?: Object, hashArg?: string) => string>comp.build(); | ||
} | ||
case "el": | ||
if (msgAst.value != undefined) { | ||
return ((id: number, valueFactory: (params?: Object, hashArg?: string) => any) => ( | ||
params?: Object, | ||
hashArg?: string | ||
) => (<any>params)[id](valueFactory(params, hashArg)))(msgAst.id, compile(locale, msgAst.value)); | ||
} | ||
return ((id: number) => (params?: Object) => (<any>params)[id]())(msgAst.id); | ||
case "format": | ||
@@ -153,0 +182,0 @@ let comp = new RuntimeFunctionGenerator(); |
@@ -0,0 +0,0 @@ import { ILocaleRules } from "./localeDataStorage"; |
@@ -0,0 +0,0 @@ export class RuntimeFunctionGenerator { |
@@ -0,1 +1,2 @@ | ||
import * as b from "bobril"; | ||
import * as moment from "moment"; | ||
@@ -8,7 +9,2 @@ import * as msgFormatParser from "./msgFormatParser"; | ||
declare var b: { | ||
setBeforeInit(callback: (cb: () => void) => void): void; | ||
ignoreShouldChange(): void; | ||
}; | ||
export interface IG11NConfig { | ||
@@ -95,3 +91,3 @@ defaultLocale?: string; | ||
let ast = msgFormatParser.parse(currentTranslationMessage(message)); | ||
if (ast.type === "error") { | ||
if (msgFormatParser.isParserError(ast)) { | ||
throw new Error("message " + message + " in " + currentLocale + " has error: " + ast.msg); | ||
@@ -107,3 +103,3 @@ } | ||
let ast = msgFormatParser.parse(message); | ||
if (ast.type === "error") { | ||
if (msgFormatParser.isParserError(ast)) { | ||
throw new Error('message "' + message + '" has error: ' + ast.msg + " on position: " + ast.pos); | ||
@@ -318,1 +314,5 @@ } | ||
} | ||
export function T(data?: b.IFragmentData | Record<string, any>) { | ||
return data; | ||
} |
@@ -6,2 +6,3 @@ { | ||
"declaration": true, | ||
"downlevelIteration": true, | ||
"experimentalDecorators": true, | ||
@@ -15,2 +16,3 @@ "jsx": "react", | ||
"es2015.iterable", | ||
"es2015.generator", | ||
"es2015.collection" | ||
@@ -29,3 +31,3 @@ ], | ||
"removeComments": false, | ||
"skipLibCheck": true, | ||
"skipLibCheck": false, | ||
"skipDefaultLibCheck": true, | ||
@@ -35,7 +37,7 @@ "sourceMap": true, | ||
"strictNullChecks": true, | ||
"target": "es5" | ||
"target": "es5", | ||
"resolveJsonModule": true | ||
}, | ||
"files": [ | ||
"node_modules/bobril/jsx.d.ts", | ||
"C:/Users/b.letocha/.bbcore/tools/jasmine.d.ts" | ||
"C:/Users/boris/.bbcore/tools/jasmine330.d.ts" | ||
], | ||
@@ -42,0 +44,0 @@ "include": [ |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Wildcard dependency
QualityPackage has a dependency with a floating version range. This can cause issues if the dependency publishes a new major version.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
57687
1
1294
2
16
1
+ Addedbobril@*
+ Addedbobril@20.10.1(transitive)