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

i18next / i18next / #11965

08 Jun 2023 07:01AM UTC coverage: 56.318% (-38.9%) from 95.213%
#11965

push

web-flow
Redesign `t` function types (#1911)

* Redesign t function types

* Add extra tests for t function and fix interpolation types

* Bump typescript version

574 of 1535 branches covered (37.39%)

517 of 918 relevant lines covered (56.32%)

24.67 hits per line

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

60.0
/src/LanguageUtils.js
1
import baseLogger from './logger.js';
2

3
function capitalize(string) {
1✔
4
  return string.charAt(0).toUpperCase() + string.slice(1);
5
}
6

1✔
7
class LanguageUtil {
1✔
8
  constructor(options) {
1!
9
    this.options = options;
14!
10

8!
11
    this.supportedLngs = this.options.supportedLngs || false;
7!
12
    this.logger = baseLogger.create('languageUtils');
1!
13
  }
7!
14

7!
15
  getScriptPartFromCode(code) {
16
    if (!code || code.indexOf('-') < 0) return null;
×
17

18
    const p = code.split('-');
1✔
19
    if (p.length === 2) return null;
20
    p.pop();
8✔
21
    if (p[p.length - 1].toLowerCase() === 'x') return null;
8✔
22
    return this.formatLanguageCode(p.join('-'));
8✔
23
  }
8✔
24

25
  getLanguagePartFromCode(code) {
1✔
26
    if (!code || code.indexOf('-') < 0) return code;
27

28
    const p = code.split('-');
2!
29
    return this.formatLanguageCode(p[0]);
2✔
30
  }
2!
31

×
32
  formatLanguageCode(code) {
×
33
    // http://www.iana.org/assignments/language-tags/language-tags.xhtml
×
34
    if (typeof code === 'string' && code.indexOf('-') > -1) {
35
      const specialCases = ['hans', 'hant', 'latn', 'cyrl', 'cans', 'mong', 'arab'];
36
      let p = code.split('-');
37

38
      if (this.options.lowerCaseLng) {
2!
39
        p = p.map((part) => part.toLowerCase());
2✔
40
      } else if (p.length === 2) {
2✔
41
        p[0] = p[0].toLowerCase();
42
        p[1] = p[1].toUpperCase();
43

44
        if (specialCases.indexOf(p[1].toLowerCase()) > -1) p[1] = capitalize(p[1].toLowerCase());
45
      } else if (p.length === 3) {
46
        p[0] = p[0].toLowerCase();
85✔
47

2✔
48
        // if lenght 2 guess it's a country
2✔
49
        if (p[1].length === 2) p[1] = p[1].toUpperCase();
2!
50
        if (p[0] !== 'sgn' && p[2].length === 2) p[2] = p[2].toUpperCase();
×
51

×
52
        if (specialCases.indexOf(p[1].toLowerCase()) > -1) p[1] = capitalize(p[1].toLowerCase());
53
        if (specialCases.indexOf(p[2].toLowerCase()) > -1) p[2] = capitalize(p[2].toLowerCase());
2!
54
      }
2✔
55

2✔
56
      return p.join('-');
2!
57
    }
×
58

×
59
    return this.options.cleanCode || this.options.lowerCaseLng ? code.toLowerCase() : code;
60
  }
61

×
62
  isSupportedCode(code) {
×
63
    if (this.options.load === 'languageOnly' || this.options.nonExplicitSupportedLngs) {
×
64
      code = this.getLanguagePartFromCode(code);
×
65
    }
66
    return (
2✔
67
      !this.supportedLngs || !this.supportedLngs.length || this.supportedLngs.indexOf(code) > -1
68
    );
83!
69
  }
70

71
  getBestMatchFromCodes(codes) {
72
    if (!codes) return null;
73

85!
74
    let found;
×
75

76
    // pick first supported code or if no restriction pick the first one (highest prio)
85!
77
    codes.forEach((code) => {
78
      if (found) return;
79
      const cleanedLng = this.formatLanguageCode(code);
80
      if (!this.options.supportedLngs || this.isSupportedCode(cleanedLng)) found = cleanedLng;
81
    });
×
82

×
83
    // if we got no match in supportedLngs yet - check for similar locales
84
    // first  de-CH --> de
85
    // second de-CH --> de-DE
86
    if (!found && this.options.supportedLngs) {
×
87
      codes.forEach((code) => {
×
88
        if (found) return;
×
89

×
90
        const lngOnly = this.getLanguagePartFromCode(code);
91
        // eslint-disable-next-line no-return-assign
92
        if (this.isSupportedCode(lngOnly)) return (found = lngOnly);
93

94
        // eslint-disable-next-line array-callback-return
95
        found = this.options.supportedLngs.find((supportedLng) => {
×
96
          if (supportedLng === lngOnly) return supportedLng;
×
97
          if (supportedLng.indexOf('-') < 0 && lngOnly.indexOf('-') < 0) return;
×
98
          if (supportedLng.indexOf(lngOnly) === 0) return supportedLng;
×
99
        });
100
      });
×
101
    }
102

103
    // if nothing found, use fallbackLng
×
104
    if (!found) found = this.getFallbackCodes(this.options.fallbackLng)[0];
×
105

×
106
    return found;
×
107
  }
108

109
  getFallbackCodes(fallbacks, code) {
110
    if (!fallbacks) return [];
111
    if (typeof fallbacks === 'function') fallbacks = fallbacks(code);
112
    if (typeof fallbacks === 'string') fallbacks = [fallbacks];
×
113
    if (Object.prototype.toString.apply(fallbacks) === '[object Array]') return fallbacks;
×
114

115
    if (!code) return fallbacks.default || [];
116

117
    // asume we have an object defining fallbacks
118
    let found = fallbacks[code];
58✔
119
    if (!found) found = fallbacks[this.getScriptPartFromCode(code)];
51!
120
    if (!found) found = fallbacks[this.formatLanguageCode(code)];
51!
121
    if (!found) found = fallbacks[this.getLanguagePartFromCode(code)];
51!
122
    if (!found) found = fallbacks.default;
×
123

124
    return found || [];
125
  }
×
126

×
127
  toResolveHierarchy(code, fallbackCode) {
×
128
    const fallbackCodes = this.getFallbackCodes(
×
129
      fallbackCode || this.options.fallbackLng || [],
×
130
      code,
×
131
    );
132

133
    const codes = [];
134
    const addCode = (c) => {
135
      if (!c) return;
51✔
136
      if (this.isSupportedCode(c)) {
51!
137
        codes.push(c);
51✔
138
      } else {
51✔
139
        this.logger.warn(`rejecting language code not found in supportedLngs: ${c}`);
87✔
140
      }
85!
141
    };
85✔
142

143
    if (typeof code === 'string' && code.indexOf('-') > -1) {
×
144
      if (this.options.load !== 'languageOnly') addCode(this.formatLanguageCode(code));
145
      if (this.options.load !== 'languageOnly' && this.options.load !== 'currentOnly')
146
        addCode(this.getScriptPartFromCode(code));
51✔
147
      if (this.options.load !== 'currentOnly') addCode(this.getLanguagePartFromCode(code));
2!
148
    } else if (typeof code === 'string') {
2!
149
      addCode(this.formatLanguageCode(code));
2!
150
    }
49!
151

49✔
152
    fallbackCodes.forEach((fc) => {
153
      if (codes.indexOf(fc) < 0) addCode(this.formatLanguageCode(fc));
51✔
154
    });
51✔
155

156
    return codes;
51✔
157
  }
158
}
159

1✔
160
export default LanguageUtil;
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc