@kemdict/gettext 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE ADDED
@@ -0,0 +1,16 @@
1
+ Copyright (c) 2011-2012 Andris Reinman
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining a copy
4
+ of this software and associated documentation files (the "Software"), to deal
5
+ in the Software without restriction, including without limitation the rights
6
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7
+ copies of the Software, and to permit persons to whom the Software is
8
+ furnished to do so, subject to the following conditions:
9
+
10
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
11
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
12
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
13
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
14
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
15
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
16
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,158 @@
1
+ # @kemdict/gettext
2
+
3
+ A fork of [node-gettext](https://github.com/alexanderwallin/node-gettext). The hope is to add more stuff to the runtime behavior of node-gettext, as well as add extraction features.
4
+
5
+ The main use is for [Kemdict](https://github.com/kemdict/kemdict).
6
+
7
+ The design of not directly depending on gettext-parser will be kept.
8
+
9
+ ## Stability
10
+
11
+ I do not commit to any backwards compatibility whatsoever at this early stage.
12
+
13
+ ---
14
+
15
+ ## Features
16
+
17
+ - Supports contexts and plurals
18
+ - Load translations in the format returned by [gettext-parser](https://github.com/smhg/gettext-parser), so .json, .mo, and .po files can all be loaded
19
+ - Use plural forms from the PO file, with fallback for many languages
20
+ - Safe by default (there is effectively an allowlist of `plurals=` expressions), if more complicated expressions are needed and you trust the PO files then use trusted mode.
21
+
22
+ ### Comparison with GNU gettext and node-gettext
23
+
24
+ (This is modified from node-gettext's explanation.)
25
+
26
+ 1. **There is no global locale, nor is the current locale a state of the class.** When the Gettext instance itself holds the current locale and is used asynchronously (like on a server handling requests), [a request may set the locale to something else before another one has finished using it](https://github.com/alexanderwallin/node-gettext/issues/67). This library instead binds the “current” locale per set of translation functions.
27
+
28
+ To infer the current locale from environment variables, use the `guessEnvLocale` function, which should read the LANG etc. variables basically just like GNU gettext does.
29
+
30
+ 2. **There are no categories.** GNU gettext features [categories such as `LC_MESSAGES`, `LC_NUMERIC` and `LC_MONETARY`](https://www.gnu.org/software/gettext/manual/gettext.html#Locale-Environment-Variables), which are better handled with other libraries in the JS world. This is just like node-gettext.
31
+ 3. **There are no domains.** Like [Python's gettext](https://docs.python.org/3/library/gettext.html), this library takes a class-based approach, so if you need multiple sets of translations simply use multiple instances of the `Gettext` class. node-gettext retains domains.
32
+ 4. **Translations have to be loaded from the file system in a separate step.**
33
+
34
+ GNU gettext is a C library that reads files from the file system: after using `bindtextdomain(domain, localesDirPath)` and `setlocale(category, locale)`, those four parameters are then used to read the appropriate translations file.
35
+
36
+ This library (like `node-gettext`) has a goal to work both on the server and in browsers, and the file system may not always be available. Therefore it is up to the developer to read translation files from disk and provide them in the constructor.
37
+
38
+ Various loaders are also provided as wrappers over `gettext-parser`; see below. In environments supporting Rollup plugins, use rollup-plugin-gettext (also developed in this repository) to import and bundle translation data.
39
+
40
+ ## Installation
41
+
42
+ TODO. This package is not yet published; the name isn't even final yet (I'm not sure I actually want to publish this as @kemdict/gettext).
43
+
44
+ ## Usage
45
+
46
+ ```js
47
+ import Gettext from '@kemdict/gettext'
48
+ import swedishTranslations from './translations/sv-SE.json'
49
+
50
+ const gt = new Gettext({
51
+ translations: {
52
+ 'sv-SE': swedishTranslations
53
+ }
54
+ })
55
+ const { gettext } = gt.bindLocale('sv-SE')
56
+
57
+ gettext('The world is a funny place')
58
+ // -> "Världen är en underlig plats"
59
+ ```
60
+
61
+ ### Recipes
62
+
63
+ #### Load and add translations from .mo or .po files
64
+
65
+ `@kemdict/gettext` expects translations to be in the format specified by [`gettext-parser`](https://github.com/smhg/gettext-parser). We also provide various loaders to conveniently read translation files on disk.
66
+
67
+ ```js
68
+ import Gettext from '@kemdict/gettext'
69
+ import { loadTranslations } from '@kemdict/gettext/loaders'
70
+ const gt = new Gettext({
71
+ // this reads and parses all .po files under path/to/locales
72
+ // their filenames are the locale names ('de.po' creates an entry for 'de')
73
+ translations: loadTranslations('path/to/locales')
74
+ })
75
+ ```
76
+
77
+ ## Reference
78
+
79
+ ### "@kemdict/gettext"
80
+
81
+ #### guessEnvLocale(env) → string[]
82
+
83
+ Guess or lookup the preferred language list from environment variables.
84
+
85
+ `env` defaults to `process.env`.
86
+
87
+ #### class Gettext({ sourceLocale, translations, trusted })
88
+
89
+ The main class. This class holds the loaded translation catalogs.
90
+
91
+ `sourceLocale` specifies which locale source text is written in. I am not sure this is necessary or useful.
92
+
93
+ `translations` is in the shape of `Record<string, GetTextTranslations>` where `GetTextTranslations` is the return type of `gettext-parser`'s parsers.
94
+
95
+ `trusted` specifies whether the PO file is trusted to be safe. By default the Plural-Forms expression is matched against a lookup table; with `trusted` as true the Plural-Forms expression will be directly used to create a JS function.
96
+
97
+ ##### gettext.getLocales() → string[]
98
+
99
+ Return the list of locales that are in this gettext instance's catalog.
100
+
101
+ ##### gettext.bindLocale(locales) → Translators
102
+
103
+ Return an object of translator functions that will translate as if `locales` is the “current” locale.
104
+
105
+ #### Translators
106
+
107
+ This object actually doesn't have a name in code but I'm abstracting them like this here.
108
+
109
+ This is an object of functions which can all be used standalone without needing to `.bind(this)`.
110
+
111
+ ##### Translators.gettext(msgid), _(msgid)
112
+
113
+ Translate a string like gettext.
114
+
115
+ ##### Translators.ngettext(msgid, msgidPlural, count)
116
+
117
+ Translate a string with plural handling like ngettext.
118
+
119
+ ##### Translators.pgettext(msgctxt, msgid)
120
+
121
+ Translate a string with context like pgettext.
122
+
123
+ ##### Translators.npgettext(msgctxt, msgid, msgidPlural, count)
124
+
125
+ Translate a string with context and plural handling like npgettext.
126
+
127
+ ### Loaders ("@kemdict/gettext/loaders.js")
128
+
129
+ #### bindtextdomain(domain, ...localesDirs) → Record<string, GetTextTranslations>
130
+
131
+ Load MO files from the directories `localesDirs`. These directories should be arranged like /usr/share/locale, i.e. `<locale>/LC_MESSAGES/<domain>.mo`.
132
+
133
+ Loading translations from [Elisa](http://apps.kde.org/elisa) if it's installed for example:
134
+
135
+ ```typescript
136
+ import Gettext, { guessEnvLocale } from "@kemdict/gettext";
137
+ import { bindtextdomain } from "@kemdict/gettext/loaders";
138
+ const gt = new Gettext({
139
+ translations: bindtextdomain("elisa", "/usr/share/locale/"),
140
+ });
141
+ const { pgettext } = gt.bindLocale(guessEnvLocale());
142
+ pgettext("@title:window", "Choose Folder") // 選擇資料夾 in zh_TW
143
+ ```
144
+
145
+ #### loadTranslations(dir) → Record<string, GetTextTranslations>
146
+
147
+ Load PO files from `dir`. `dir` should contain one PO file for each locale, like `<dir>/zh_TW.po`, `<dir>/de.po`, `<dir>/sv.po`, and so on.
148
+
149
+ ## License
150
+
151
+ MIT
152
+
153
+ ## See also
154
+
155
+ - [node-gettext](https://github.com/alexanderwallin/node-gettext) - where this library forked from, because I have way too many major changes that I want to implement
156
+ - [gettext-parser](https://github.com/smhg/gettext-parser) - Parsing and compiling gettext translations between .po/.mo files and JSON
157
+ - [lioness](https://github.com/alexanderwallin/lioness) - Gettext library for React
158
+ - [react-gettext-parser](https://github.com/laget-se/react-gettext-parser) - Extracting gettext translatable strings from JS(X) code
package/lib/gettext.js ADDED
@@ -0,0 +1,233 @@
1
+ import { parsePluralForms, fallbackPluralForms } from "./plurals.js";
2
+
3
+ /**
4
+ * Guess or lookup the preferred language list from environment variables.
5
+ * @param {Record<string, string | undefined>} env
6
+ * A map of environment variables. Defaults to process.env.
7
+ * @returns string[] | undefined
8
+ */
9
+ export function guessEnvLocale(env = process?.env) {
10
+ if (!env) return;
11
+ // If $LANG is C, C.<encoding>, or POSIX: return msgid untranslated.
12
+ // Prefer LANGUAGE, then LC_ALL, then LC_MESSAGES, then LANG.
13
+ const LANG = env["LANG"];
14
+ if (LANG && (LANG === "C" || LANG.startsWith("C.") || LANG === "POSIX")) {
15
+ return;
16
+ }
17
+ /** @type Set<string> */
18
+ const locales = new Set();
19
+ const LANGUAGE = env["LANGUAGE"];
20
+ if (LANGUAGE)
21
+ LANGUAGE.split(":").forEach((lang) => {
22
+ locales.add(lang);
23
+ });
24
+ const LC_ALL = env["LC_ALL"];
25
+ if (LC_ALL) locales.add(LC_ALL);
26
+ const LC_MESSAGES = env["LC_MESSAGES"];
27
+ if (LC_MESSAGES) locales.add(LC_MESSAGES);
28
+ if (LANG) locales.add(LANG);
29
+ return [...locales];
30
+ }
31
+
32
+ /**
33
+ * @import { GetTextTranslations } from "gettext-parser";
34
+ * @typedef {string} Locale
35
+ * @typedef {{ eventName: string, callback: Function }} Listener
36
+ * @typedef {GetTextTranslations} Catalog
37
+ */
38
+
39
+ export default class Gettext {
40
+ /** @type Map<Locale, Catalog> */
41
+ catalogs = new Map();
42
+ /** @type Array<Listener> */
43
+ listeners = [];
44
+ trusted = false;
45
+ /**
46
+ * Creates and returns a new Gettext instance.
47
+ *
48
+ * @typedef {Object} Options - a set of options
49
+ * @property {string} [sourceLocale] - The locale that the source code and its
50
+ * texts are written in. Translations for
51
+ * this locale is not necessary.
52
+ * @property {Record<Locale, Catalog>} [translations] - Translations to add to the catalog
53
+ * @property {boolean} [trusted] - Trust that the plural forms code in
54
+ * `translations` are safe to evaluate.
55
+ * @param {Options} [options]
56
+ */
57
+ constructor(options) {
58
+ options = options || {};
59
+
60
+ // Set source locale
61
+ this.sourceLocale = "";
62
+ if (options.sourceLocale) {
63
+ this.sourceLocale = options.sourceLocale;
64
+ }
65
+
66
+ if (options.translations) {
67
+ for (const [locale, catalog] of Object.entries(
68
+ options.translations,
69
+ )) {
70
+ this.catalogs.set(locale, catalog);
71
+ }
72
+ }
73
+ if (options.trusted) this.trusted = true;
74
+ }
75
+ /**
76
+ * Return locales currently added to the catalogs.
77
+ */
78
+ getLocales() {
79
+ return this.catalogs.keys();
80
+ }
81
+ // NOTE: This function is actually relatively hot, since every component and
82
+ // every module would call it. But caching this could only really be faster
83
+ // if we generate the key from the arguments with something with maybe 2
84
+ // inputs, anything more complex would literally just be slower. At best
85
+ // it's a 2x speed increase, but we're talking about going from 0.0002ms per
86
+ // bindLocale call to 0.0001ms here. It's not worth it.
87
+ /**
88
+ * Return functions that translate strings into `locale`.
89
+ * This allows not having global state while also not having to pass the
90
+ * locale for every call.
91
+ *
92
+ * @param {Locale[] | Locale | undefined} locales
93
+ * A string to use as a locale, or an array of locales to try to match for,
94
+ * or undefined which means to not do any translations.
95
+ */
96
+ bindLocale(locales) {
97
+ const localesArr = !locales
98
+ ? []
99
+ : Array.isArray(locales)
100
+ ? locales
101
+ : [locales];
102
+
103
+ // The value of `this` would no longer be our instance if we call each
104
+ // of the functions as standalone functions. This reference to our
105
+ // instance, on the other hand, will not change even when the returned
106
+ // functions are called as standalone functions.
107
+ const self = this;
108
+
109
+ /**
110
+ * The base function for all variants.
111
+ * This does not need to take `locale` as an input, because all functions
112
+ * resulting from a given `.with` call all use the same locale.
113
+ *
114
+ * @param {string | null | undefined} msgctxt - Translation context. undefined or empty string means no context.
115
+ * @param {string} msgid - String to be translated
116
+ * @param {string} [msgidPlural] - If no translation was found, return this on count!=1
117
+ * @param {number} [count] - Number count for the plural
118
+ * @return {string}
119
+ *
120
+ */
121
+ const baseGettext = (msgctxt, msgid, msgidPlural, count) => {
122
+ const context = msgctxt || "";
123
+
124
+ let defaultTranslation = msgid;
125
+ if (count !== undefined && !isNaN(count) && count !== 1) {
126
+ defaultTranslation = msgidPlural || msgid;
127
+ }
128
+
129
+ for (const locale of localesArr) {
130
+ if (
131
+ locale === "C" ||
132
+ locale === "POSIX" ||
133
+ locale.startsWith("C.")
134
+ ) {
135
+ return defaultTranslation;
136
+ }
137
+ const pluralFunc = self._getCatalogPluralForms(locale).plural;
138
+ const catalog = self.catalogs.get(locale);
139
+ const translation = catalog?.translations?.[context]?.[msgid];
140
+ if (!translation) continue;
141
+
142
+ /** @type {boolean | number} */
143
+ let index = typeof count === "number" ? pluralFunc(count) : 0;
144
+ if (typeof index === "boolean") {
145
+ index = index ? 1 : 0;
146
+ }
147
+ const msgstr = translation.msgstr[index];
148
+ if (msgstr) return msgstr;
149
+ }
150
+ return defaultTranslation;
151
+ };
152
+ return {
153
+ /**
154
+ * Translate a string.
155
+ * The locale is implicit.
156
+ *
157
+ * @param {string} msgid - String to be translated
158
+ * @return {string} Translation or the original string if no translation was found
159
+ */
160
+ gettext(msgid) {
161
+ return baseGettext(undefined, msgid);
162
+ },
163
+ /**
164
+ * Translate a string.
165
+ * Same as `gettext`.
166
+ * The locale is implicit.
167
+ *
168
+ * @param {string} msgid - String to be translated
169
+ * @return {string} Translation or the original string if no translation was found
170
+ */
171
+ _(msgid) {
172
+ return baseGettext(undefined, msgid);
173
+ },
174
+ /**
175
+ * Translate a plural string.
176
+ * The locale is implicit.
177
+ *
178
+ * @param {string} msgid String to be translated when count is not plural
179
+ * @param {string} msgidPlural String to be translated when count is plural
180
+ * @param {number} count Number count for the plural
181
+ * @return {string} Translation or the original string if no translation was found
182
+ */
183
+ ngettext(msgid, msgidPlural, count) {
184
+ return baseGettext(undefined, msgid, msgidPlural, count);
185
+ },
186
+ /**
187
+ * Translate a string from a specific context.
188
+ * The locale is implicit.
189
+ *
190
+ * @param {string} msgctxt Translation context
191
+ * @param {string} msgid String to be translated
192
+ * @return {string} Translation or the original string if no translation was found
193
+ */
194
+ pgettext(msgctxt, msgid) {
195
+ return baseGettext(msgctxt, msgid);
196
+ },
197
+ /**
198
+ * Translate a plural string from a specific context.
199
+ * The locale is implicit.
200
+ *
201
+ * @param {string} msgctxt Translation context
202
+ * @param {string} msgid String to be translated when count is not plural
203
+ * @param {string} msgidPlural String to be translated when count is plural
204
+ * @param {number} count Number count for the plural
205
+ * @return {string} Translation or the original string if no translation was found
206
+ */
207
+ npgettext(msgctxt, msgid, msgidPlural, count) {
208
+ return baseGettext(msgctxt, msgid, msgidPlural, count);
209
+ },
210
+ };
211
+ }
212
+ /**
213
+ * Cache for computed plural forms.
214
+ * Right now the computation is just normalizing the value then looking it
215
+ * up, but this already benefits from a cache.
216
+ * @type {Map<string, import("./plural-data.js").PluralFormsObj>}
217
+ */
218
+ _pluralForms = new Map();
219
+
220
+ /**
221
+ * Return plural forms header for the current catalogs for `locale`.
222
+ * @param {string} locale - The locale name
223
+ */
224
+ _getCatalogPluralForms(locale) {
225
+ return this._pluralForms.getOrInsertComputed(locale, () => {
226
+ const header = this.catalogs.get(locale)?.headers["Plural-Forms"];
227
+ return header
228
+ ? parsePluralForms(header, this.trusted) ||
229
+ fallbackPluralForms(locale)
230
+ : fallbackPluralForms(locale);
231
+ });
232
+ }
233
+ }
package/lib/loaders.js ADDED
@@ -0,0 +1,54 @@
1
+ /** @import {Catalog, Locale} from "./gettext.js" */
2
+
3
+ import * as fs from "node:fs";
4
+ import * as path from "node:path";
5
+ import { po, mo } from "gettext-parser";
6
+
7
+ /**
8
+ * Load MO translations the directories `localesDirs`.
9
+ *
10
+ * These directories should be arranged similar to /usr/share/locale, like
11
+ * <dir>/<locale>/LC_MESSAGES/<domain>.mo.
12
+ *
13
+ * @param {string} domain
14
+ * @param {...string} localesDirs
15
+ */
16
+ export function bindtextdomain(domain, ...localesDirs) {
17
+ /** @type Record<Locale, Catalog> */
18
+ const catalogs = {};
19
+ for (const localesDir of localesDirs) {
20
+ if (!fs.existsSync(localesDir)) continue;
21
+ for (const locale of fs.readdirSync(localesDir)) {
22
+ const localePath = path.join(localesDir, locale);
23
+ if (!fs.statSync(localePath).isDirectory()) continue;
24
+ const messagesPath = path.join(localePath, "LC_MESSAGES");
25
+ if (!fs.existsSync(messagesPath)) continue;
26
+ if (!fs.statSync(messagesPath).isDirectory()) continue;
27
+ const domainPath = path.join(messagesPath, `${domain}.mo`);
28
+ if (!fs.existsSync(domainPath)) continue;
29
+
30
+ const translations = mo.parse(fs.readFileSync(domainPath));
31
+ catalogs[locale] = translations;
32
+ }
33
+ }
34
+ return catalogs;
35
+ }
36
+
37
+ /**
38
+ * Load PO translations from `dir`.
39
+ * `dir` should be structured like <dir>/<locale>.po.
40
+ * @param {string} dir
41
+ */
42
+ export function loadTranslations(dir) {
43
+ /** @type Record<Locale, Catalog> */
44
+ const catalogs = {};
45
+ for (const file of fs.readdirSync(dir)) {
46
+ if (file.endsWith(".po")) {
47
+ const translations = po.parse(
48
+ fs.readFileSync(path.join(dir, file)),
49
+ );
50
+ catalogs[file.slice(0, -3)] = translations;
51
+ }
52
+ }
53
+ return catalogs;
54
+ }
@@ -0,0 +1,245 @@
1
+ // The fallback plurals of this file are from GNU Gettext's plural-table.c as
2
+ // well as converted from Unicode CLDR.
3
+
4
+ // FIXME obviously this is not ideal!
5
+
6
+ /**
7
+ * @typedef {((n: number) => number) | ((n: number) => boolean)} PluralFunc
8
+ * @typedef {{
9
+ * nplurals: number;
10
+ * plural: PluralFunc
11
+ * }} PluralFormsObj */
12
+
13
+ /**
14
+ * Lookup table for existing "plural=" expressions to "parsed" function values.
15
+ * The keys are normalized to always start and end with parens to reduce (though
16
+ * not eliminate) duplicate keys.
17
+ * @type Record<string, PluralFunc>
18
+ */
19
+ export const pluralFuncTable = {
20
+ "(n==1?0:n==2?1:n==0||(n%100>=3&&n%100<=10)?2:n%100>=11&&n%100<=19?3:4)": (
21
+ n,
22
+ ) =>
23
+ n == 1
24
+ ? 0
25
+ : n == 2
26
+ ? 1
27
+ : n == 0 || (n % 100 >= 3 && n % 100 <= 10)
28
+ ? 2
29
+ : n % 100 >= 11 && n % 100 <= 19
30
+ ? 3
31
+ : 4,
32
+ "(n%10==1&&n%100!=11)": (n) => n % 10 == 1 && n % 100 != 11,
33
+ "(n==0?0:n==1?1:n==2?2:n%100>=3&&n%100<=10?3:n%100>=11&&n%100<=99?4:5)": (
34
+ n,
35
+ ) =>
36
+ n == 0
37
+ ? 0
38
+ : n == 1
39
+ ? 1
40
+ : n == 2
41
+ ? 2
42
+ : n % 100 >= 3 && n % 100 <= 10
43
+ ? 3
44
+ : n % 100 >= 11 && n % 100 <= 99
45
+ ? 4
46
+ : 5,
47
+ "((n==1)?0:(n>=2&&n<=4)?1:2)": (n) =>
48
+ n == 1 ? 0 : n >= 2 && n <= 4 ? 1 : 2,
49
+ "((n==1||n==11)?0:(n==2||n==12)?1:(n>2&&n<20)?2:3)": (n) =>
50
+ n == 1 || n == 11 ? 0 : n == 2 || n == 12 ? 1 : n > 2 && n < 20 ? 2 : 3,
51
+ "(0)": () => 0,
52
+ "(1)": () => 1,
53
+ "(n!=1)": (n) => n != 1,
54
+ "(n%100==1?0:n%100==2?1:n%100==3||n%100==4?2:3)": (n) =>
55
+ n % 100 == 1
56
+ ? 0
57
+ : n % 100 == 2
58
+ ? 1
59
+ : n % 100 == 3 || n % 100 == 4
60
+ ? 2
61
+ : 3,
62
+ "(n%100==1?0:n%100==2?1:n%100>=3&&n%100<=4?2:3)": (n) =>
63
+ n % 100 == 1
64
+ ? 0
65
+ : n % 100 == 2
66
+ ? 1
67
+ : n % 100 >= 3 && n % 100 <= 4
68
+ ? 2
69
+ : 3,
70
+ "(n%100==1?1:n%100==2?2:n%100==3||n%100==4?3:0)": (n) =>
71
+ n % 100 == 1
72
+ ? 1
73
+ : n % 100 == 2
74
+ ? 2
75
+ : n % 100 == 3 || n % 100 == 4
76
+ ? 3
77
+ : 0,
78
+ "(n%10==1&&n%100!=11?0:n!=0?1:2)": (n) =>
79
+ n % 10 == 1 && n % 100 != 11 ? 0 : n != 0 ? 1 : 2,
80
+ "(n%10==1&&n%100!=11?0:n%10>=2&&(n%100<10||n%100>=20)?1:2)": (n) =>
81
+ n % 10 == 1 && n % 100 != 11
82
+ ? 0
83
+ : n % 10 >= 2 && (n % 100 < 10 || n % 100 >= 20)
84
+ ? 1
85
+ : 2,
86
+ "(n%10==1&&n%100!=11?0:n%10>=2&&n%10<=4&&(n%100<10||n%100>=20)?1:2)": (
87
+ n,
88
+ ) =>
89
+ n % 10 == 1 && n % 100 != 11
90
+ ? 0
91
+ : n % 10 >= 2 && n % 10 <= 4 && (n % 100 < 10 || n % 100 >= 20)
92
+ ? 1
93
+ : 2,
94
+ "(n%10==1&&n%100!=11?0:n%10>=2&&n%10<=4&&(n%100<12||n%100>14)?1:n%10==0||n%10>=5&&n%10<=9||n%100>=11&&n%100<=14?2:3)":
95
+ (n) =>
96
+ n % 10 == 1 && n % 100 != 11
97
+ ? 0
98
+ : n % 10 >= 2 && n % 10 <= 4 && (n % 100 < 12 || n % 100 > 14)
99
+ ? 1
100
+ : n % 10 == 0 ||
101
+ (n % 10 >= 5 && n % 10 <= 9) ||
102
+ (n % 100 >= 11 && n % 100 <= 14)
103
+ ? 2
104
+ : 3,
105
+ "(n%10==1?0:n%10==2?1:2)": (n) => (n % 10 == 1 ? 0 : n % 10 == 2 ? 1 : 2),
106
+ "(n==0?0:n==1?1:n==2?2:3)": (n) =>
107
+ n == 0 ? 0 : n == 1 ? 1 : n == 2 ? 2 : 3,
108
+ "(n==0?0:n==1?1:n==2?2:n%100>=3&&n%100<=10?3:n%100>=11?4:5)": (n) =>
109
+ n == 0
110
+ ? 0
111
+ : n == 1
112
+ ? 1
113
+ : n == 2
114
+ ? 2
115
+ : n % 100 >= 3 && n % 100 <= 10
116
+ ? 3
117
+ : n % 100 >= 11
118
+ ? 4
119
+ : 5,
120
+ "(n==0||n==1)": (n) => n == 0 || n == 1,
121
+ "(n==1)?0:((n==2)?1:((n>10&&n%10==0)?2:3))": (n) =>
122
+ n == 1 ? 0 : n == 2 ? 1 : n > 10 && n % 10 == 0 ? 2 : 3,
123
+ "(n==1?0:(n==0||(n%100>0&&n%100<20))?1:2)": (n) =>
124
+ n == 1 ? 0 : n == 0 || (n % 100 > 0 && n % 100 < 20) ? 1 : 2,
125
+ "(n==1?0:n%10>=2&&(n%100<10||n%100>=20)?1:n%10==0||(n%100>10&&n%100<20)?2:3)":
126
+ (n) =>
127
+ n == 1
128
+ ? 0
129
+ : n % 10 >= 2 && (n % 100 < 10 || n % 100 >= 20)
130
+ ? 1
131
+ : n % 10 == 0 || (n % 100 > 10 && n % 100 < 20)
132
+ ? 2
133
+ : 3,
134
+ "(n==1?0:n%10>=2&&n%10<=4&&(n%100<10||n%100>=20)?1:2)": (n) =>
135
+ n == 1
136
+ ? 0
137
+ : n % 10 >= 2 && n % 10 <= 4 && (n % 100 < 10 || n % 100 >= 20)
138
+ ? 1
139
+ : 2,
140
+ "(n==1?0:n==2?1:2)": (n) => (n == 1 ? 0 : n == 2 ? 1 : 2),
141
+ "(n==1?0:n==2?1:n<7?2:n<11?3:4)": (n) =>
142
+ n == 1 ? 0 : n == 2 ? 1 : n < 7 ? 2 : n < 11 ? 3 : 4,
143
+ "(n==1?3:n%10==1&&n%100!=11?0:n%10>=2&&n%10<=4&&(n%100<10||n%100>=20)?1:2)":
144
+ (n) =>
145
+ n == 1
146
+ ? 3
147
+ : n % 10 == 1 && n % 100 != 11
148
+ ? 0
149
+ : n % 10 >= 2 &&
150
+ n % 10 <= 4 &&
151
+ (n % 100 < 10 || n % 100 >= 20)
152
+ ? 1
153
+ : 2,
154
+ "(n>1)": (n) => n > 1,
155
+ "(n>2)": (n) => n > 2,
156
+ };
157
+
158
+ // from Gettext's plural-table.c
159
+ // prettier-ignore
160
+ const pluralTable = {
161
+ "ja vi ko": "nplurals=1; plural=0;",
162
+ "en de nl sv da no nb nn fo es pt it bg el fi et he eo hu tr ca": "nplurals=2; plural=(n != 1);",
163
+ "pt_BR fr": "nplurals=2; plural=(n > 1);",
164
+ "lv": "nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n != 0 ? 1 : 2);",
165
+ "ga": "nplurals=3; plural=n==1 ? 0 : n==2 ? 1 : 2;",
166
+ "ro": "nplurals=3; plural=n==1 ? 0 : (n==0 || (n%100 > 0 && n%100 < 20)) ? 1 : 2;",
167
+ "lt": "nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && (n%100<10 || n%100>=20) ? 1 : 2);",
168
+ "ru uk be sr hr": "nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2);",
169
+ "cs sk": "nplurals=3; plural=(n==1) ? 0 : (n>=2 && n<=4) ? 1 : 2;",
170
+ "pl": "nplurals=3; plural=(n==1 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2);",
171
+ "sl": "nplurals=4; plural=(n%100==1 ? 0 : n%100==2 ? 1 : n%100==3 || n%100==4 ? 2 : 3);",
172
+ };
173
+
174
+ // from Unicode CLDR (plurals.xml), converted with Gettext's cldr-plurals program
175
+ // prettier-ignore
176
+ const cldrPlurals = {
177
+ "bm bo dz hnj id ig ii in ja jbo jv jw kde kea km ko lkt lo ms my nqo osa root sah ses sg su th to tpi vi wo yo yue zh":
178
+ "nplurals=1; plural=0;",
179
+ "am as bn doi fa gu hi kn kok kok_Latn pcm zu":
180
+ "nplurals=2; plural=(n==0 || n==1);",
181
+ "ff hy kab": "nplurals=2; plural=(n > 1);",
182
+ "ast de en et fi fy gl ia ie io ji lij nl sc sv sw ur yi":
183
+ "nplurals=2; plural=(n != 1);",
184
+ "si": "nplurals=2; plural=(n > 1);",
185
+ "ak bho csw guw ln mg nso pa ti wa": "nplurals=2; plural=(n > 1);",
186
+ "tzm": "nplurals=2; plural=(n<=1 || (n>=11 && n<=99));",
187
+ "af an asa az bal bem bez bg brx ce cgg chr ckb dv ee el eo eu fo fur gsw ha haw hu jgo jmc ka kaj kcg kk kkj kl ks ksb ku ky lb lg mas mgo ml mn mr nah nb nd ne nn nnh no nr ny nyn om or os pap ps rm rof rwk saq sd sdh seh sn so sq ss ssy st syr ta te teo tig tk tn tr ts ug uz ve vo vun wae xh xog":
188
+ "nplurals=2; plural=(n != 1);",
189
+ "da": "nplurals=2; plural=(n != 1);",
190
+ "is": "nplurals=2; plural=(n%10==1 && n%100!=11);",
191
+ "mk": "nplurals=2; plural=(n%10==1 && n%100!=11);",
192
+ "ceb fil tl":
193
+ "nplurals=2; plural=(n==1 || n==2 || n==3 || (n%10!=4 && n%10!=6 && n%10!=9));",
194
+ "lv prg":
195
+ "nplurals=3; plural=(n%10==0 || (n%100>=11 && n%100<=19) ? 0 : n%10==1 && n%100!=11 ? 1 : 2);",
196
+ "lag": "nplurals=3; plural=(n==0 ? 0 : (n==0 || n==1) && n!=0 ? 1 : 2);",
197
+ "blo cv ksh": "nplurals=3; plural=(n==0 ? 0 : n==1 ? 1 : 2);",
198
+ "he iw": "nplurals=3; plural=(n==1 ? 0 : n==2 ? 1 : 2);",
199
+ "iu naq sat se sma smi smj smn sms":
200
+ "nplurals=3; plural=(n==1 ? 0 : n==2 ? 1 : 2);",
201
+ "shi": "nplurals=3; plural=(n==0 || n==1 ? 0 : n>=2 && n<=10 ? 1 : 2);",
202
+ "mo ro":
203
+ "nplurals=3; plural=(n==1 ? 0 : n==0 || (n!=1 && n%100>=1 && n%100<=19) ? 1 : 2);",
204
+ "bs hr sh sr":
205
+ "nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && (n%100<12 || n%100>14) ? 1 : 2);",
206
+ "fr": "nplurals=3; plural=(n==0 || n==1 ? 0 : n!=0 && n%1000000==0 ? 1 : 2);",
207
+ "pt": "nplurals=3; plural=(n<=1 ? 0 : n!=0 && n%1000000==0 ? 1 : 2);",
208
+ "ca it lld pt_PT scn vec":
209
+ "nplurals=3; plural=(n==1 ? 0 : n!=0 && n%1000000==0 ? 1 : 2);",
210
+ "es": "nplurals=3; plural=(n==1 ? 0 : n!=0 && n%1000000==0 ? 1 : 2);",
211
+ "gd": "nplurals=4; plural=(n==1 || n==11 ? 0 : n==2 || n==12 ? 1 : (n>=3 && n<=10) || (n>=13 && n<=19) ? 2 : 3);",
212
+ "sl": "nplurals=4; plural=(n%100==1 ? 0 : n%100==2 ? 1 : n%100>=3 && n%100<=4 ? 2 : 3);",
213
+ "dsb hsb":
214
+ "nplurals=4; plural=(n%100==1 ? 0 : n%100==2 ? 1 : n%100>=3 && n%100<=4 ? 2 : 3);",
215
+ "cs sk": "nplurals=3; plural=(n==1 ? 0 : n>=2 && n<=4 ? 1 : 2);",
216
+ "pl": "nplurals=3; plural=(n==1 ? 0 : n%10>=2 && n%10<=4 && (n%100<12 || n%100>14) ? 1 : 2);",
217
+ "be": "nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && (n%100<12 || n%100>14) ? 1 : 2);",
218
+ "lt": "nplurals=3; plural=(n%10==1 && (n%100<11 || n%100>19) ? 0 : n%10>=2 && n%10<=9 && (n%100<11 || n%100>19) ? 1 : 2);",
219
+ "ru uk":
220
+ "nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && (n%100<12 || n%100>14) ? 1 : 2);",
221
+ "sgs": "nplurals=4; plural=(n%10==1 && n%100!=11 ? 0 : n==2 ? 1 : n!=2 && n%10>=2 && n%10<=9 && (n%100<11 || n%100>19) ? 2 : 3);",
222
+ "br": "nplurals=5; plural=(n%10==1 && n%100!=11 && n%100!=71 && n%100!=91 ? 0 : n%10==2 && n%100!=12 && n%100!=72 && n%100!=92 ? 1 : ((n%10>=3 && n%10<=4) || n%10==9) && (n%100<10 || n%100>19) && (n%100<70 || n%100>79) && (n%100<90 || n%100>99) ? 2 : n!=0 && n%1000000==0 ? 3 : 4);",
223
+ "mt": "nplurals=5; plural=(n==1 ? 0 : n==2 ? 1 : n==0 || (n%100>=3 && n%100<=10) ? 2 : n%100>=11 && n%100<=19 ? 3 : 4);",
224
+ "ga": "nplurals=5; plural=(n==1 ? 0 : n==2 ? 1 : n>=3 && n<=6 ? 2 : n>=7 && n<=10 ? 3 : 4);",
225
+ "gv": "nplurals=4; plural=(n%10==1 ? 0 : n%10==2 ? 1 : n%100==0 || n%100==20 || n%100==40 || n%100==60 || n%100==80 ? 2 : 3);",
226
+ "kw": "nplurals=6; plural=(n==0 ? 0 : n==1 ? 1 : n%100==2 || n%100==22 || n%100==42 || n%100==62 || n%100==82 || (n%1000==0 && ((n%100000>=1000 && n%100000<=20000) || n%100000==40000 || n%100000==60000 || n%100000==80000)) || (n!=0 && n%1000000==100000) ? 2 : n%100==3 || n%100==23 || n%100==43 || n%100==63 || n%100==83 ? 3 : n!=1 && (n%100==1 || n%100==21 || n%100==41 || n%100==61 || n%100==81) ? 4 : 5);",
227
+ "ar ars":
228
+ "nplurals=6; plural=(n==0 ? 0 : n==1 ? 1 : n==2 ? 2 : n%100>=3 && n%100<=10 ? 3 : n%100>=11 && n%100<=99 ? 4 : 5);",
229
+ "cy": "nplurals=6; plural=(n==0 ? 0 : n==1 ? 1 : n==2 ? 2 : n==3 ? 3 : n==6 ? 4 : 5);",
230
+ };
231
+
232
+ /**
233
+ * Lookup table mapping locales to plural forms.
234
+ * @param {string} locale - The locale to look up.
235
+ */
236
+ export function localePluralForms(locale) {
237
+ // Try gettext's first
238
+ for (const [langs, value] of Object.entries(pluralTable)) {
239
+ if (langs.split(" ").includes(locale)) return value;
240
+ }
241
+ // Then try CLDR
242
+ for (const [langs, value] of Object.entries(cldrPlurals)) {
243
+ if (langs.split(" ").includes(locale)) return value;
244
+ }
245
+ }
package/lib/plurals.js ADDED
@@ -0,0 +1,76 @@
1
+ import { pluralFuncTable, localePluralForms } from "./plural-data.js";
2
+
3
+ // The function can only always return numbers or always return booleans
4
+ // regardless of the input.
5
+ /**
6
+ * @import { PluralFormsObj } from "./plural-data.js";
7
+ * @type PluralFormsObj
8
+ */
9
+ const defaultPluralForms = {
10
+ nplurals: 2,
11
+ plural: (n) => n !== 1,
12
+ };
13
+
14
+ /**
15
+ * "Parse" the plural forms.
16
+ *
17
+ * Ideally this would actually parse it instead of using a lookup table, except
18
+ * that would be too complicated and also questionably safe. So maybe using a
19
+ * lookup table is fine.
20
+ *
21
+ * @param {string} pluralForms - The plural forms string from the header
22
+ * @param {boolean} trusted - Trust that the code in `pluralForms` is safe to run
23
+ */
24
+ export function parsePluralForms(pluralForms, trusted = false) {
25
+ const normalized = pluralForms.replaceAll(/ |(?:;(?:\\n)?$)/g, "");
26
+ const match = normalized.match(/^nplurals=(\d);plural=(.*)/);
27
+ if (!match) {
28
+ console.log(
29
+ `Warning: matching pluralForms failed! Original value: ${pluralForms}`,
30
+ );
31
+ return undefined;
32
+ }
33
+ const nplurals = parseInt(match[1]);
34
+ let pluralStr = match[2];
35
+ if (!pluralStr.match(/^\(.*\)$/)) {
36
+ pluralStr = "(" + pluralStr + ")";
37
+ }
38
+
39
+ const func = trusted
40
+ ? /** @type import("./plural-data.js").PluralFunc */ (
41
+ new Function("n", `return ${pluralStr}`)
42
+ )
43
+ : pluralFuncTable[pluralStr];
44
+ if (func) {
45
+ return {
46
+ nplurals,
47
+ plural: func,
48
+ };
49
+ }
50
+ console.log(`Warning: unknown pluralForms! "${pluralForms}"`);
51
+ return undefined;
52
+ }
53
+
54
+ /**
55
+ * Return the fallback plural forms for `locale`.
56
+ *
57
+ * If the locale with a region does not match, try without its region.
58
+ * @param {string} locale - The locale to get the fallback value for.
59
+ */
60
+ export function fallbackPluralForms(locale) {
61
+ const pluralForms =
62
+ localePluralForms(locale) ||
63
+ // if not found, try with underscore. GNU Gettext usually prefers underscore
64
+ localePluralForms(locale.replace("-", "_")) ||
65
+ // if not found, try the top level code. The top level code can only
66
+ // be 2 ~ 3 characters.
67
+ localePluralForms(locale.slice(0, 3).replace(/[-_]/, ""));
68
+ if (pluralForms) {
69
+ const parsed = parsePluralForms(pluralForms, true);
70
+ if (parsed) return parsed;
71
+ }
72
+ console.log(
73
+ `Warning: no fallback plurals found for locale "${locale}". Using default plurals (Germanic).`,
74
+ );
75
+ return defaultPluralForms;
76
+ }
package/package.json ADDED
@@ -0,0 +1,45 @@
1
+ {
2
+ "name": "@kemdict/gettext",
3
+ "description": "A JavaScript implementation of gettext, a localization framework",
4
+ "version": "0.1.0",
5
+ "//": "Messy setup for this fork. The original author Andris Reinman is the original author but not the one responsible for this fork.",
6
+ "author": "Kisaragi Hiu",
7
+ "contributors": ["Andris Reinman", "Kisaragi Hiu"],
8
+ "maintainers": ["Kisaragi Hiu <mail@kisaragi-hiu.com>"],
9
+ "homepage": "http://github.com/kemdict/gettext",
10
+ "repository": {
11
+ "type": "git",
12
+ "url": "git+https://github.com/kemdict/gettext.git"
13
+ },
14
+ "type": "module",
15
+ "license": "MIT",
16
+ "files": ["lib", "types", "README.md", "LICENSE", "package.json"],
17
+ "exports": {
18
+ ".": {
19
+ "types": "./types/gettext.d.ts",
20
+ "default": "./lib/gettext.js"
21
+ },
22
+ "./loaders.js": {
23
+ "types": "./types/loaders.d.ts",
24
+ "default": "./lib/loaders.js"
25
+ }
26
+ },
27
+ "devDependencies": {
28
+ "@types/gettext-parser": "^8.0.0",
29
+ "@types/node": "^26.0.1",
30
+ "gettext-parser": "^9.0.0",
31
+ "prettier": "^3.7.4",
32
+ "typescript": "^6.0.3"
33
+ },
34
+ "engine": {
35
+ "node": ">=26"
36
+ },
37
+ "keywords": [
38
+ "i18n",
39
+ "l10n",
40
+ "internationalization",
41
+ "localization",
42
+ "translation",
43
+ "gettext"
44
+ ]
45
+ }
@@ -0,0 +1,132 @@
1
+ /**
2
+ * Guess or lookup the preferred language list from environment variables.
3
+ * @param {Record<string, string | undefined>} env
4
+ * A map of environment variables. Defaults to process.env.
5
+ * @returns string[] | undefined
6
+ */
7
+ export function guessEnvLocale(env?: Record<string, string | undefined>): string[] | undefined;
8
+ /**
9
+ * @import { GetTextTranslations } from "gettext-parser";
10
+ * @typedef {string} Locale
11
+ * @typedef {{ eventName: string, callback: Function }} Listener
12
+ * @typedef {GetTextTranslations} Catalog
13
+ */
14
+ export default class Gettext {
15
+ /**
16
+ * Creates and returns a new Gettext instance.
17
+ *
18
+ * @typedef {Object} Options - a set of options
19
+ * @property {string} [sourceLocale] - The locale that the source code and its
20
+ * texts are written in. Translations for
21
+ * this locale is not necessary.
22
+ * @property {Record<Locale, Catalog>} [translations] - Translations to add to the catalog
23
+ * @property {boolean} [trusted] - Trust that the plural forms code in
24
+ * `translations` are safe to evaluate.
25
+ * @param {Options} [options]
26
+ */
27
+ constructor(options?: {
28
+ /**
29
+ * - The locale that the source code and its
30
+ * texts are written in. Translations for
31
+ * this locale is not necessary.
32
+ */
33
+ sourceLocale?: string | undefined;
34
+ /**
35
+ * - Translations to add to the catalog
36
+ */
37
+ translations?: Record<string, GetTextTranslations> | undefined;
38
+ /**
39
+ * - Trust that the plural forms code in
40
+ * `translations` are safe to evaluate.
41
+ */
42
+ trusted?: boolean | undefined;
43
+ });
44
+ /** @type Map<Locale, Catalog> */
45
+ catalogs: Map<Locale, Catalog>;
46
+ /** @type Array<Listener> */
47
+ listeners: Array<Listener>;
48
+ trusted: boolean;
49
+ sourceLocale: string;
50
+ /**
51
+ * Return locales currently added to the catalogs.
52
+ */
53
+ getLocales(): MapIterator<string>;
54
+ /**
55
+ * Return functions that translate strings into `locale`.
56
+ * This allows not having global state while also not having to pass the
57
+ * locale for every call.
58
+ *
59
+ * @param {Locale[] | Locale | undefined} locales
60
+ * A string to use as a locale, or an array of locales to try to match for,
61
+ * or undefined which means to not do any translations.
62
+ */
63
+ bindLocale(locales: Locale[] | Locale | undefined): {
64
+ /**
65
+ * Translate a string.
66
+ * The locale is implicit.
67
+ *
68
+ * @param {string} msgid - String to be translated
69
+ * @return {string} Translation or the original string if no translation was found
70
+ */
71
+ gettext(msgid: string): string;
72
+ /**
73
+ * Translate a string.
74
+ * Same as `gettext`.
75
+ * The locale is implicit.
76
+ *
77
+ * @param {string} msgid - String to be translated
78
+ * @return {string} Translation or the original string if no translation was found
79
+ */
80
+ _(msgid: string): string;
81
+ /**
82
+ * Translate a plural string.
83
+ * The locale is implicit.
84
+ *
85
+ * @param {string} msgid String to be translated when count is not plural
86
+ * @param {string} msgidPlural String to be translated when count is plural
87
+ * @param {number} count Number count for the plural
88
+ * @return {string} Translation or the original string if no translation was found
89
+ */
90
+ ngettext(msgid: string, msgidPlural: string, count: number): string;
91
+ /**
92
+ * Translate a string from a specific context.
93
+ * The locale is implicit.
94
+ *
95
+ * @param {string} msgctxt Translation context
96
+ * @param {string} msgid String to be translated
97
+ * @return {string} Translation or the original string if no translation was found
98
+ */
99
+ pgettext(msgctxt: string, msgid: string): string;
100
+ /**
101
+ * Translate a plural string from a specific context.
102
+ * The locale is implicit.
103
+ *
104
+ * @param {string} msgctxt Translation context
105
+ * @param {string} msgid String to be translated when count is not plural
106
+ * @param {string} msgidPlural String to be translated when count is plural
107
+ * @param {number} count Number count for the plural
108
+ * @return {string} Translation or the original string if no translation was found
109
+ */
110
+ npgettext(msgctxt: string, msgid: string, msgidPlural: string, count: number): string;
111
+ };
112
+ /**
113
+ * Cache for computed plural forms.
114
+ * Right now the computation is just normalizing the value then looking it
115
+ * up, but this already benefits from a cache.
116
+ * @type {Map<string, import("./plural-data.js").PluralFormsObj>}
117
+ */
118
+ _pluralForms: Map<string, import("./plural-data.js").PluralFormsObj>;
119
+ /**
120
+ * Return plural forms header for the current catalogs for `locale`.
121
+ * @param {string} locale - The locale name
122
+ */
123
+ _getCatalogPluralForms(locale: string): import("./plural-data.js").PluralFormsObj;
124
+ }
125
+ export type Locale = string;
126
+ export type Listener = {
127
+ eventName: string;
128
+ callback: Function;
129
+ };
130
+ export type Catalog = GetTextTranslations;
131
+ import type { GetTextTranslations } from "gettext-parser";
132
+ //# sourceMappingURL=gettext.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"gettext.d.ts","sourceRoot":"","sources":["../lib/gettext.js"],"names":[],"mappings":"AAEA;;;;;GAKG;AACH,qCAJW,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAC,wBAyB5C;AAED;;;;;GAKG;AAEH;IAMI;;;;;;;;;;;OAWG;IACH;;;;;;;;;;;;;;;;OAiBC;IAlCD,iCAAiC;IACjC,UADU,GAAG,CAAC,MAAM,EAAE,OAAO,CAAC,CACT;IACrB,4BAA4B;IAC5B,WADU,KAAK,CAAC,QAAQ,CAAC,CACV;IACf,iBAAgB;IAiBZ,qBAAsB;IAc1B;;OAEG;IACH,kCAEC;IAOD;;;;;;;;OAQG;IACH,oBAJW,MAAM,EAAE,GAAG,MAAM,GAAG,SAAS;QA6DhC;;;;;;WAMG;uBAFS,MAAM,GACN,MAAM;QAKlB;;;;;;;WAOG;iBAFS,MAAM,GACN,MAAM;QAKlB;;;;;;;;WAQG;wBAJS,MAAM,eACN,MAAM,SACN,MAAM,GACN,MAAM;QAKlB;;;;;;;WAOG;0BAHS,MAAM,SACN,MAAM,GACN,MAAM;QAKlB;;;;;;;;;WASG;2BALS,MAAM,SACN,MAAM,eACN,MAAM,SACN,MAAM,GACN,MAAM;MAMzB;IACD;;;;;OAKG;IACH,cAFU,GAAG,CAAC,MAAM,EAAE,OAAO,kBAAkB,EAAE,cAAc,CAAC,CAEvC;IAEzB;;;OAGG;IACH,+BAFW,MAAM,6CAUhB;CACJ;qBAvMY,MAAM;uBACN;IAAE,SAAS,EAAE,MAAM,CAAC;IAAC,QAAQ,WAAU;CAAE;sBACzC,mBAAmB;yCAHQ,gBAAgB"}
@@ -0,0 +1,17 @@
1
+ /**
2
+ * Load MO translations the directories `localesDirs`.
3
+ *
4
+ * These directories should be arranged similar to /usr/share/locale, like
5
+ * <dir>/<locale>/LC_MESSAGES/<domain>.mo.
6
+ *
7
+ * @param {string} domain
8
+ * @param {...string} localesDirs
9
+ */
10
+ export function bindtextdomain(domain: string, ...localesDirs: string[]): Record<string, import("gettext-parser").GetTextTranslations>;
11
+ /**
12
+ * Load PO translations from `dir`.
13
+ * `dir` should be structured like <dir>/<locale>.po.
14
+ * @param {string} dir
15
+ */
16
+ export function loadTranslations(dir: string): Record<string, import("gettext-parser").GetTextTranslations>;
17
+ //# sourceMappingURL=loaders.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"loaders.d.ts","sourceRoot":"","sources":["../lib/loaders.js"],"names":[],"mappings":"AAMA;;;;;;;;GAQG;AACH,uCAHW,MAAM,kBACH,MAAM,EAAA,gEAqBnB;AAED;;;;GAIG;AACH,sCAFW,MAAM,gEAchB"}
@@ -0,0 +1,24 @@
1
+ /**
2
+ * Lookup table mapping locales to plural forms.
3
+ * @param {string} locale - The locale to look up.
4
+ */
5
+ export function localePluralForms(locale: string): string | undefined;
6
+ /**
7
+ * @typedef {((n: number) => number) | ((n: number) => boolean)} PluralFunc
8
+ * @typedef {{
9
+ * nplurals: number;
10
+ * plural: PluralFunc
11
+ * }} PluralFormsObj */
12
+ /**
13
+ * Lookup table for existing "plural=" expressions to "parsed" function values.
14
+ * The keys are normalized to always start and end with parens to reduce (though
15
+ * not eliminate) duplicate keys.
16
+ * @type Record<string, PluralFunc>
17
+ */
18
+ export const pluralFuncTable: Record<string, PluralFunc>;
19
+ export type PluralFunc = ((n: number) => number) | ((n: number) => boolean);
20
+ export type PluralFormsObj = {
21
+ nplurals: number;
22
+ plural: PluralFunc;
23
+ };
24
+ //# sourceMappingURL=plural-data.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"plural-data.d.ts","sourceRoot":"","sources":["../lib/plural-data.js"],"names":[],"mappings":"AAuOA;;;GAGG;AACH,0CAFW,MAAM,sBAWhB;AA/OD;;;;;uBAKuB;AAEvB;;;;;GAKG;AACH,8BAFS,MAAM,CAAC,MAAM,EAAE,UAAU,CAAC,CA2IjC;yBArJY,CAAC,CAAC,CAAC,EAAE,MAAM,KAAK,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,MAAM,KAAK,OAAO,CAAC;6BAClD;IACR,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,UAAU,CAAA;CACpB"}
@@ -0,0 +1,23 @@
1
+ /**
2
+ * "Parse" the plural forms.
3
+ *
4
+ * Ideally this would actually parse it instead of using a lookup table, except
5
+ * that would be too complicated and also questionably safe. So maybe using a
6
+ * lookup table is fine.
7
+ *
8
+ * @param {string} pluralForms - The plural forms string from the header
9
+ * @param {boolean} trusted - Trust that the code in `pluralForms` is safe to run
10
+ */
11
+ export function parsePluralForms(pluralForms: string, trusted?: boolean): {
12
+ nplurals: number;
13
+ plural: import("./plural-data.js").PluralFunc;
14
+ } | undefined;
15
+ /**
16
+ * Return the fallback plural forms for `locale`.
17
+ *
18
+ * If the locale with a region does not match, try without its region.
19
+ * @param {string} locale - The locale to get the fallback value for.
20
+ */
21
+ export function fallbackPluralForms(locale: string): PluralFormsObj;
22
+ import type { PluralFormsObj } from "./plural-data.js";
23
+ //# sourceMappingURL=plurals.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"plurals.d.ts","sourceRoot":"","sources":["../lib/plurals.js"],"names":[],"mappings":"AAaA;;;;;;;;;GASG;AACH,8CAHW,MAAM,YACN,OAAO;;;cA8BjB;AAED;;;;;GAKG;AACH,4CAFW,MAAM,kBAkBhB;oCAtEkC,kBAAkB"}