Skip to content

Commit d5c283b

Browse files
authored
Support compact notation (#35)
1 parent 0774ae5 commit d5c283b

File tree

10 files changed

+95
-75
lines changed

10 files changed

+95
-75
lines changed

packages/compiler/src/compiler.js

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,6 @@ export class Compiler {
3838
.replace(/^[ ,]+|[ ,]+$/g, '')
3939
.replace(/(0\.[0-9])~(1\.[1-9])/g, '$1 1.0 $2')
4040
.split(/[ ,~]+/)
41-
.filter(n => !n.includes('c'))
4241
}
4342

4443
constructor(lc, { cardinals, ordinals } = Compiler) {
@@ -66,9 +65,11 @@ export class Compiler {
6665
)
6766
.replace(/{\s*return\s+([^{}]*);\s*}$/, '$1')
6867
this.test = () => {
68+
const { cardinals, ordinals } = this.types
69+
const ordArg = Boolean(ordinals && cardinals)
6970
for (const type of ['cardinal', 'ordinal'])
7071
for (const [cat, values] of Object.entries(this.examples[type]))
71-
testCat(this.lc, type, cat, values, this.fn)
72+
testCat(this.lc, this.fn, ordArg, type, cat, values)
7273
}
7374
}
7475
return this.fn
@@ -111,10 +112,10 @@ export class Compiler {
111112
body = ` return ${this.buildBody(pt, true)};`
112113
}
113114

115+
const args = this.parser.args(ordinals && cardinals)
114116
const vars = this.parser.vars()
115117
if (vars) body = ` ${vars};\n${body}`
116118

117-
const args = ordinals && cardinals ? 'n, ord' : 'n'
118119
return new Function(args, body) // eslint-disable-line no-new-func
119120
}
120121
}

packages/compiler/src/parser.js

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -8,12 +8,11 @@ export class Parser {
88
}
99
return cond
1010
.replace(/([^=\s])([!=%]+)([^=\s])/g, '$1 $2 $3')
11-
.replace(
12-
/[ce] (!?)= ([0-9]+)(?:\.\.[0-9]+)?/g,
13-
// assume c & e always have the value 0
14-
(m, noteq, x0) => (!noteq === (x0 === '0') ? 'true' : 'false')
15-
)
16-
.replace(/^true and |^false or | and true$| or false$/g, '')
11+
.replace(/[ce] (!?)= 0(?:\.\.([0-9]+))?/g, (m, noteq, max) => {
12+
this.c = 1
13+
if (max) return noteq ? `c > ${max}` : `c <= ${max}`
14+
else return noteq ? 'c' : '!c'
15+
})
1716
.replace(/([tv]) (!?)= 0/g, (m, sym, noteq) => {
1817
const sn = sym + '0'
1918
this[sn] = 1
@@ -52,6 +51,13 @@ export class Parser {
5251
.replace(/ = /g, ' == ')
5352
}
5453

54+
args(ord) {
55+
let args = ['n']
56+
if (ord) args.push('ord')
57+
if (this.c) args.push('c')
58+
return args.join(', ')
59+
}
60+
5561
vars() {
5662
let vars = []
5763
if (this.i) vars.push('i = s[0]')

packages/compiler/src/tests.js

Lines changed: 22 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,36 @@
1-
function errorMsg(lc, n, type, msg) {
2-
const val = JSON.stringify(n)
3-
return `Locale ${lc} ${type} rule self-test failed for ${val} (${msg})`
4-
}
1+
const errorMsg = (lc, n, type, fn, args, msg) => `\
2+
Locale ${lc} ${type} rule self-test failed for ${typeof n} ${n} (${msg})
3+
Function: ${fn.toString()}
4+
Arguments: ${JSON.stringify(args)}`
55

6-
function testCond(lc, n, type, expResult, fn) {
6+
function testCond(lc, fn, ordArg, n, type, expResult) {
7+
const compact = typeof n === 'string' && n.match(/(.*)c(\d+)$/)
8+
let args = [n]
9+
if (ordArg) args.push(type === 'ordinal')
10+
if (compact) {
11+
const c = Number(compact[2])
12+
args[0] = Number(compact[1]) * Math.pow(10, c)
13+
args.push(c)
14+
}
715
try {
8-
var r = fn(n, type === 'ordinal')
16+
var r = fn(...args)
917
} catch (error) {
1018
/* istanbul ignore next: should not happen unless CLDR data is broken */
11-
throw new Error(errorMsg(lc, n, type, error))
19+
throw new Error(errorMsg(lc, n, type, fn, args, error))
1220
}
1321
if (r !== expResult) {
1422
const res = JSON.stringify(r)
1523
const exp = JSON.stringify(expResult)
16-
throw new Error(errorMsg(lc, n, type, `was ${res}, expected ${exp}`))
24+
throw new Error(
25+
errorMsg(lc, n, type, fn, args, `was ${res}, expected ${exp}`)
26+
)
1727
}
1828
}
1929

20-
export function testCat(lc, type, cat, values, fn) {
30+
export function testCat(lc, fn, ordArg, type, cat, values) {
2131
for (const n of values) {
22-
testCond(lc, n, type, cat, fn)
23-
if (!/\.0+$/.test(n)) testCond(lc, Number(n), type, cat, fn)
32+
testCond(lc, fn, ordArg, n, type, cat)
33+
if (!n.includes('c') && !/\.0+$/.test(n))
34+
testCond(lc, fn, ordArg, Number(n), type, cat)
2435
}
2536
}

packages/plurals/README.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,8 @@ Each of the endpoints is available as ES modules only.
3333
returning the pluralization category for the input (either a number or a string representation of a number).
3434
`Plurals` functions also accept a second boolean parameter to return
3535
the ordinal (`true`) rather than cardinal (`false`, default) plural category.
36+
For some locales, an additional argument sets the exponent used in compact notation;
37+
currently, this is only used in Romance languages.
3638
Note that `Ordinals` includes a slightly smaller subset of locales than `Cardinals` and `Plurals`,
3739
due to a lack of data in the CLDR.
3840
- `PluralRanges` provides a set of functions similarly keyed by locale code,

packages/plurals/cardinals.js

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,10 @@ const d = (n) => {
66
return n == 1 && v0 ? 'one' : 'other';
77
};
88
const e = (n) => 'other';
9-
const f = (n) => {
9+
const f = (n, c) => {
1010
const s = String(n).split('.'), i = s[0], v0 = !s[1], i1000000 = i.slice(-6);
1111
return n == 1 && v0 ? 'one'
12-
: i != 0 && i1000000 == 0 && v0 ? 'many'
12+
: !c && i != 0 && i1000000 == 0 && v0 || c > 5 ? 'many'
1313
: 'other';
1414
};
1515
const g = (n) => n == 1 ? 'one'
@@ -120,10 +120,10 @@ export const ee = a;
120120
export const el = a;
121121
export const en = d;
122122
export const eo = a;
123-
export const es = (n) => {
123+
export const es = (n, c) => {
124124
const s = String(n).split('.'), i = s[0], v0 = !s[1], i1000000 = i.slice(-6);
125125
return n == 1 ? 'one'
126-
: i != 0 && i1000000 == 0 && v0 ? 'many'
126+
: !c && i != 0 && i1000000 == 0 && v0 || c > 5 ? 'many'
127127
: 'other';
128128
};
129129
export const et = d;
@@ -136,10 +136,10 @@ export const fil = (n) => {
136136
return v0 && (i == 1 || i == 2 || i == 3) || v0 && i10 != 4 && i10 != 6 && i10 != 9 || !v0 && f10 != 4 && f10 != 6 && f10 != 9 ? 'one' : 'other';
137137
};
138138
export const fo = a;
139-
export const fr = (n) => {
139+
export const fr = (n, c) => {
140140
const s = String(n).split('.'), i = s[0], v0 = !s[1], i1000000 = i.slice(-6);
141141
return n >= 0 && n < 2 ? 'one'
142-
: i != 0 && i1000000 == 0 && v0 ? 'many'
142+
: !c && i != 0 && i1000000 == 0 && v0 || c > 5 ? 'many'
143143
: 'other';
144144
};
145145
export const fur = a;
@@ -331,10 +331,10 @@ export const prg = (n) => {
331331
: 'other';
332332
};
333333
export const ps = a;
334-
export const pt = (n) => {
334+
export const pt = (n, c) => {
335335
const s = String(n).split('.'), i = s[0], v0 = !s[1], i1000000 = i.slice(-6);
336336
return (i == 0 || i == 1) ? 'one'
337-
: i != 0 && i1000000 == 0 && v0 ? 'many'
337+
: !c && i != 0 && i1000000 == 0 && v0 || c > 5 ? 'many'
338338
: 'other';
339339
};
340340
export const pt_PT = f;

0 commit comments

Comments
 (0)