• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

i18next / i18next / #40818

02 Jan 2024 10:08AM UTC coverage: 92.045% (-0.07%) from 92.112%
#40818

push

adrai
try to fix circleci

1558 of 2443 branches covered (63.77%)

1481 of 1609 relevant lines covered (92.04%)

1239.15 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

54.55
/src/PluralResolver.js
1
import baseLogger from './logger.js';
2
import { getCleanedCode } from './utils.js'
3

1✔
4
// definition http://translate.sourceforge.net/wiki/l10n/pluralforms
5
/* eslint-disable */
6
let sets = [
1✔
7
  { lngs: ['ach','ak','am','arn','br','fil','gun','ln','mfe','mg','mi','oc', 'pt', 'pt-BR',
1✔
8
    'tg', 'tl', 'ti','tr','uz','wa'], nr: [1,2], fc: 1 },
1✔
9

1!
10
  { lngs: ['af','an','ast','az','bg','bn','ca','da','de','dev','el','en',
16!
11
    'eo','es','et','eu','fi','fo','fur','fy','gl','gu','ha','hi',
74!
12
    'hu','hy','ia','it','kk','kn','ku','lb','mai','ml','mn','mr','nah','nap','nb',
8!
13
    'ne','nl','nn','no','nso','pa','pap','pms','ps','pt-PT','rm','sco',
1!
14
    'se','si','so','son','sq','sv','sw','ta','te','tk','ur','yo'], nr: [1,2], fc: 2 },
8!
15

8!
16
  { lngs: ['ay','bo','cgg','fa','ht','id','ja','jbo','ka','km','ko','ky','lo',
17
    'ms','sah','su','th','tt','ug','vi','wo','zh'], nr: [1], fc: 3 },
18

1✔
19
  { lngs: ['be','bs', 'cnr', 'dz','hr','ru','sr','uk'], nr: [1,2,5], fc: 4 },
20

21
  { lngs: ['ar'], nr: [0,1,2,3,11,100], fc: 5 },
22
  { lngs: ['cs','sk'], nr: [1,2,5], fc: 6 },
23
  { lngs: ['csb','pl'], nr: [1,2,5], fc: 7 },
24
  { lngs: ['cy'], nr: [1,2,3,8], fc: 8 },
25
  { lngs: ['fr'], nr: [1,2], fc: 9 },
26
  { lngs: ['ga'], nr: [1,2,3,7,11], fc: 10 },
27
  { lngs: ['gd'], nr: [1,2,3,20], fc: 11 },
28
  { lngs: ['is'], nr: [1,2], fc: 12 },
29
  { lngs: ['jv'], nr: [0,1], fc: 13 },
30
  { lngs: ['kw'], nr: [1,2,3,4], fc: 14 },
31
  { lngs: ['lt'], nr: [1,2,10], fc: 15 },
32
  { lngs: ['lv'], nr: [1,2,0], fc: 16 },
33
  { lngs: ['mk'], nr: [1,2], fc: 17 },
34
  { lngs: ['mnk'], nr: [0,1,2], fc: 18 },
35
  { lngs: ['mt'], nr: [1,2,11,20], fc: 19 },
36
  { lngs: ['or'], nr: [2,1], fc: 2 },
37
  { lngs: ['ro'], nr: [1,2,20], fc: 20 },
38
  { lngs: ['sl'], nr: [5,1,2,3], fc: 21 },
39
  { lngs: ['he','iw'], nr: [1,2,20,21], fc: 22 }
40
]
41

42
let _rulesPluralsTypes = {
43
  1: function(n) {return Number(n > 1);},
44
  2: function(n) {return Number(n != 1);},
45
  3: function(n) {return 0;},
46
  4: function(n) {return Number(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2);},
47
  5: function(n) {return Number(n==0 ? 0 : n==1 ? 1 : n==2 ? 2 : n%100>=3 && n%100<=10 ? 3 : n%100>=11 ? 4 : 5);},
48
  6: function(n) {return Number((n==1) ? 0 : (n>=2 && n<=4) ? 1 : 2);},
49
  7: function(n) {return Number(n==1 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2);},
50
  8: function(n) {return Number((n==1) ? 0 : (n==2) ? 1 : (n != 8 && n != 11) ? 2 : 3);},
51
  9: function(n) {return Number(n >= 2);},
52
  10: function(n) {return Number(n==1 ? 0 : n==2 ? 1 : n<7 ? 2 : n<11 ? 3 : 4) ;},
53
  11: function(n) {return Number((n==1 || n==11) ? 0 : (n==2 || n==12) ? 1 : (n > 2 && n < 20) ? 2 : 3);},
54
  12: function(n) {return Number(n%10!=1 || n%100==11);},
55
  13: function(n) {return Number(n !== 0);},
56
  14: function(n) {return Number((n==1) ? 0 : (n==2) ? 1 : (n == 3) ? 2 : 3);},
57
  15: function(n) {return Number(n%10==1 && n%100!=11 ? 0 : n%10>=2 && (n%100<10 || n%100>=20) ? 1 : 2);},
58
  16: function(n) {return Number(n%10==1 && n%100!=11 ? 0 : n !== 0 ? 1 : 2);},
59
  17: function(n) {return Number(n==1 || n%10==1 && n%100!=11 ? 0 : 1);},
60
  18: function(n) {return Number(n==0 ? 0 : n==1 ? 1 : 2);},
61
  19: function(n) {return Number(n==1 ? 0 : n==0 || ( n%100>1 && n%100<11) ? 1 : (n%100>10 && n%100<20 ) ? 2 : 3);},
62
  20: function(n) {return Number(n==1 ? 0 : (n==0 || (n%100 > 0 && n%100 < 20)) ? 1 : 2);},
63
  21: function(n) {return Number(n%100==1 ? 1 : n%100==2 ? 2 : n%100==3 || n%100==4 ? 3 : 0); },
64
  22: function(n) {return Number(n==1 ? 0 : n==2 ? 1 : (n<0 || n>10) && n%10==0 ? 2 : 3); }
65
};
66
/* eslint-enable */
67

68
const nonIntlVersions = ['v1', 'v2', 'v3'];
69
const intlVersions = ['v4'];
70
const suffixesOrder = {
71
  zero: 0,
72
  one: 1,
73
  two: 2,
74
  few: 3,
75
  many: 4,
76
  other: 5,
77
};
78

79
function createRules() {
80
  const rules = {};
81
  sets.forEach((set) => {
82
    set.lngs.forEach((l) => {
83
      rules[l] = {
84
        numbers: set.nr,
85
        plurals: _rulesPluralsTypes[set.fc]
86
      };
87
    });
88
  });
89
  return rules;
90
}
91

92
class PluralResolver {
93
  constructor(languageUtils, options = {}) {
94
    this.languageUtils = languageUtils;
95
    this.options = options;
96

97
    this.logger = baseLogger.create('pluralResolver');
98

99
    if ((!this.options.compatibilityJSON || intlVersions.includes(this.options.compatibilityJSON)) && (typeof Intl === 'undefined' || !Intl.PluralRules)) {
100
      this.options.compatibilityJSON = 'v3';
101
      this.logger.error('Your environment seems not to be Intl API compatible, use an Intl.PluralRules polyfill. Will fallback to the compatibilityJSON v3 format handling.');
102
    }
103

104
    this.rules = createRules();
105
  }
106

107
  addRule(lng, obj) {
108
    this.rules[lng] = obj;
109
  }
110

111
  getRule(code, options = {}) {
1✔
112
    if (this.shouldUseIntlApi()) {
113
      try {
×
114
        return new Intl.PluralRules(getCleanedCode(code), { type: options.ordinal ? 'ordinal' : 'cardinal' });
115
      } catch (err) {
116
        return;
×
117
      }
118
    }
119

×
120
    return this.rules[code] || this.rules[this.languageUtils.getLanguagePartFromCode(code)];
121
  }
122

×
123
  needsPlural(code, options = {}) {
124
    const rule = this.getRule(code, options);
125

×
126
    if (this.shouldUseIntlApi()) {
127
      return rule && rule.resolvedOptions().pluralCategories.length > 1;
128
    }
×
129

130
    return rule && rule.numbers.length > 1;
131
  }
×
132

133
  getPluralFormsOfKey(code, key, options = {}) {
134
    return this.getSuffixes(code, options).map((suffix) => `${key}${suffix}`);
×
135
  }
136

137
  getSuffixes(code, options = {}) {
×
138
    const rule = this.getRule(code, options);
139

140
    if (!rule) {
×
141
      return [];
142
    }
143

×
144
    if (this.shouldUseIntlApi()) {
145
      return rule.resolvedOptions().pluralCategories
146
        .sort((pluralCategory1, pluralCategory2) => suffixesOrder[pluralCategory1] - suffixesOrder[pluralCategory2])
×
147
        .map(pluralCategory => `${this.options.prepend}${options.ordinal ? `ordinal${this.options.prepend}` : ''}${pluralCategory}`);
148
    }
149

×
150
    return rule.numbers.map((number) => this.getSuffix(code, number, options));
151
  }
152

×
153
  getSuffix(code, count, options = {}) {
154
    const rule = this.getRule(code, options);
155

×
156
    if (rule) {
157
      if (this.shouldUseIntlApi()) {
158
        return `${this.options.prepend}${options.ordinal ? `ordinal${this.options.prepend}` : ''}${rule.select(count)}`;
×
159
      }
160

161
      return this.getSuffixRetroCompatible(rule, count);
×
162
    }
163

164
    this.logger.warn(`no plural rule found for: ${code}`);
×
165
    return '';
166
  }
167

×
168
  getSuffixRetroCompatible(rule, count) {
169
    const idx = rule.noAbs ? rule.plurals(count) : rule.plurals(Math.abs(count));
170
    let suffix = rule.numbers[idx];
×
171

172
    // special treatment for lngs only having singular and plural
173
    if (this.options.simplifyPluralSuffix && rule.numbers.length === 2 && rule.numbers[0] === 1) {
×
174
      if (suffix === 2) {
175
        suffix = 'plural';
176
      } else if (suffix === 1) {
×
177
        suffix = '';
178
      }
179
    }
180

181
    const returnSuffix = () => (
1✔
182
      this.options.prepend && suffix.toString() ? this.options.prepend + suffix.toString() : suffix.toString()
1✔
183
    );
1✔
184

185
    // COMPATIBILITY JSON
186
    // v1
187
    if (this.options.compatibilityJSON === 'v1') {
188
      if (suffix === 1) return '';
189
      if (typeof suffix === 'number') return `_plural_${suffix.toString()}`;
190
      return returnSuffix();
191
      // eslint-disable-next-line no-else-return
192
    } else if (/* v2 */ this.options.compatibilityJSON === 'v2') {
74✔
193
      return returnSuffix();
74✔
194
    } else if (/* v3 - gettext index */ this.options.simplifyPluralSuffix && rule.numbers.length === 2 && rule.numbers[0] === 1) {
1,702✔
195
      return returnSuffix();
9,990✔
196
    }
197
    return this.options.prepend && idx.toString() ? this.options.prepend + idx.toString() : idx.toString();
198
  }
199

200
  shouldUseIntlApi() {
201
    return !nonIntlVersions.includes(this.options.compatibilityJSON);
74✔
202
  }
203
}
1✔
204

205
export default PluralResolver;
74!
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE TRIAL · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc