@prosopo/locale 3.2.8 → 3.3.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.
Files changed (159) hide show
  1. package/.turbo/turbo-build$colon$cjs.log +10 -9
  2. package/.turbo/turbo-build$colon$tsc.log +5 -5
  3. package/.turbo/turbo-build.log +11 -10
  4. package/CHANGELOG.md +36 -0
  5. package/dist/cjs/i18SharedOptions.cjs +1 -1
  6. package/dist/cjs/i18nBackend.cjs +4 -1
  7. package/dist/cjs/i18nFrontend.cjs +4 -1
  8. package/dist/cjs/loadI18next.cjs +41 -14
  9. package/dist/cjs/locales/en/translation.cjs +1 -0
  10. package/dist/cjs/process.cjs +17 -0
  11. package/dist/cjs/translationKey.cjs +2 -0
  12. package/dist/i18SharedOptions.js +2 -1
  13. package/dist/i18SharedOptions.js.map +1 -1
  14. package/dist/i18nBackend.d.ts.map +1 -1
  15. package/dist/i18nBackend.js +4 -1
  16. package/dist/i18nBackend.js.map +1 -1
  17. package/dist/i18nFrontend.d.ts.map +1 -1
  18. package/dist/i18nFrontend.js +4 -1
  19. package/dist/i18nFrontend.js.map +1 -1
  20. package/dist/loadI18next.d.ts +1 -0
  21. package/dist/loadI18next.d.ts.map +1 -1
  22. package/dist/loadI18next.js +41 -15
  23. package/dist/loadI18next.js.map +1 -1
  24. package/dist/locales/ar/translation.json +1 -0
  25. package/dist/locales/az/translation.json +1 -0
  26. package/dist/locales/cs/translation.json +1 -0
  27. package/dist/locales/de/translation.json +1 -0
  28. package/dist/locales/el/translation.json +1 -0
  29. package/dist/locales/en/translation.js +1 -0
  30. package/dist/locales/en/translation.json +1 -0
  31. package/dist/locales/es/translation.json +1 -0
  32. package/dist/locales/fi/translation.json +1 -0
  33. package/dist/locales/fr/translation.json +1 -0
  34. package/dist/locales/hi/translation.json +1 -0
  35. package/dist/locales/hu/translation.json +1 -0
  36. package/dist/locales/id/translation.json +1 -0
  37. package/dist/locales/it/translation.json +1 -0
  38. package/dist/locales/ja/translation.json +1 -0
  39. package/dist/locales/jv/translation.json +1 -0
  40. package/dist/locales/ko/translation.json +1 -0
  41. package/dist/locales/ml/translation.json +1 -0
  42. package/dist/locales/ms/translation.json +1 -0
  43. package/dist/locales/nl/translation.json +1 -0
  44. package/dist/locales/no/translation.json +1 -0
  45. package/dist/locales/pl/translation.json +1 -0
  46. package/dist/locales/pt/translation.json +1 -0
  47. package/dist/locales/pt-BR/translation.json +1 -0
  48. package/dist/locales/ro/translation.json +1 -0
  49. package/dist/locales/ru/translation.json +1 -0
  50. package/dist/locales/sr/translation.json +1 -0
  51. package/dist/locales/sv/translation.json +1 -0
  52. package/dist/locales/th/translation.json +1 -0
  53. package/dist/locales/tr/translation.json +1 -0
  54. package/dist/locales/uk/translation.json +1 -0
  55. package/dist/locales/vi/translation.json +1 -0
  56. package/dist/locales/zh-CN/translation.json +1 -0
  57. package/dist/process.d.ts +2 -0
  58. package/dist/process.d.ts.map +1 -0
  59. package/dist/process.js +17 -0
  60. package/dist/process.js.map +1 -0
  61. package/dist/tests/i18SharedOptions.unit.test.d.ts +2 -0
  62. package/dist/tests/i18SharedOptions.unit.test.d.ts.map +1 -0
  63. package/dist/tests/i18SharedOptions.unit.test.js +91 -0
  64. package/dist/tests/i18SharedOptions.unit.test.js.map +1 -0
  65. package/dist/tests/i18nBackend.unit.test.d.ts +2 -0
  66. package/dist/tests/i18nBackend.unit.test.d.ts.map +1 -0
  67. package/dist/tests/i18nBackend.unit.test.js +153 -0
  68. package/dist/tests/i18nBackend.unit.test.js.map +1 -0
  69. package/dist/tests/i18nFrontend.unit.test.d.ts +2 -0
  70. package/dist/tests/i18nFrontend.unit.test.d.ts.map +1 -0
  71. package/dist/tests/i18nFrontend.unit.test.js +199 -0
  72. package/dist/tests/i18nFrontend.unit.test.js.map +1 -0
  73. package/dist/tests/i18nMiddleware.unit.test.d.ts +2 -0
  74. package/dist/tests/i18nMiddleware.unit.test.d.ts.map +1 -0
  75. package/dist/tests/i18nMiddleware.unit.test.js +80 -0
  76. package/dist/tests/i18nMiddleware.unit.test.js.map +1 -0
  77. package/dist/tests/loadI18next.unit.test.d.ts +2 -0
  78. package/dist/tests/loadI18next.unit.test.d.ts.map +1 -0
  79. package/dist/tests/loadI18next.unit.test.js +270 -0
  80. package/dist/tests/loadI18next.unit.test.js.map +1 -0
  81. package/dist/tests/locale.test-d.d.ts +2 -0
  82. package/dist/tests/locale.test-d.d.ts.map +1 -0
  83. package/dist/tests/locale.test-d.js +88 -0
  84. package/dist/tests/locale.test-d.js.map +1 -0
  85. package/dist/tests/process.unit.test.d.ts +2 -0
  86. package/dist/tests/process.unit.test.d.ts.map +1 -0
  87. package/dist/tests/process.unit.test.js +14 -0
  88. package/dist/tests/process.unit.test.js.map +1 -0
  89. package/dist/tests/translationKey.unit.test.d.ts +2 -0
  90. package/dist/tests/translationKey.unit.test.d.ts.map +1 -0
  91. package/dist/tests/translationKey.unit.test.js +108 -0
  92. package/dist/tests/translationKey.unit.test.js.map +1 -0
  93. package/dist/tests/translations.unit.test.d.ts +2 -0
  94. package/dist/tests/translations.unit.test.d.ts.map +1 -0
  95. package/dist/tests/translations.unit.test.js +74 -0
  96. package/dist/tests/translations.unit.test.js.map +1 -0
  97. package/dist/tests/useTranslation.unit.test.d.ts +2 -0
  98. package/dist/tests/useTranslation.unit.test.d.ts.map +1 -0
  99. package/dist/tests/useTranslation.unit.test.js +81 -0
  100. package/dist/tests/useTranslation.unit.test.js.map +1 -0
  101. package/dist/tests/util.unit.test.d.ts +2 -0
  102. package/dist/tests/util.unit.test.d.ts.map +1 -0
  103. package/dist/tests/util.unit.test.js +66 -0
  104. package/dist/tests/util.unit.test.js.map +1 -0
  105. package/dist/translationKey.d.ts +4 -0
  106. package/dist/translationKey.d.ts.map +1 -1
  107. package/dist/translationKey.js +2 -1
  108. package/dist/translationKey.js.map +1 -1
  109. package/package.json +2 -2
  110. package/src/i18SharedOptions.ts +4 -4
  111. package/src/i18nBackend.ts +16 -0
  112. package/src/i18nFrontend.ts +16 -0
  113. package/src/loadI18next.ts +83 -15
  114. package/src/locales/ar/translation.json +1 -0
  115. package/src/locales/az/translation.json +1 -0
  116. package/src/locales/cs/translation.json +1 -0
  117. package/src/locales/de/translation.json +1 -0
  118. package/src/locales/el/translation.json +1 -0
  119. package/src/locales/en/translation.json +1 -0
  120. package/src/locales/es/translation.json +1 -0
  121. package/src/locales/fi/translation.json +1 -0
  122. package/src/locales/fr/translation.json +1 -0
  123. package/src/locales/hi/translation.json +1 -0
  124. package/src/locales/hu/translation.json +1 -0
  125. package/src/locales/id/translation.json +1 -0
  126. package/src/locales/it/translation.json +1 -0
  127. package/src/locales/ja/translation.json +1 -0
  128. package/src/locales/jv/translation.json +1 -0
  129. package/src/locales/ko/translation.json +1 -0
  130. package/src/locales/ml/translation.json +1 -0
  131. package/src/locales/ms/translation.json +1 -0
  132. package/src/locales/nl/translation.json +1 -0
  133. package/src/locales/no/translation.json +1 -0
  134. package/src/locales/pl/translation.json +1 -0
  135. package/src/locales/pt/translation.json +1 -0
  136. package/src/locales/pt-BR/translation.json +1 -0
  137. package/src/locales/ro/translation.json +1 -0
  138. package/src/locales/ru/translation.json +1 -0
  139. package/src/locales/sr/translation.json +1 -0
  140. package/src/locales/sv/translation.json +1 -0
  141. package/src/locales/th/translation.json +1 -0
  142. package/src/locales/tr/translation.json +1 -0
  143. package/src/locales/uk/translation.json +1 -0
  144. package/src/locales/vi/translation.json +1 -0
  145. package/src/locales/zh-CN/translation.json +1 -0
  146. package/src/process.ts +29 -0
  147. package/src/tests/i18SharedOptions.unit.test.ts +139 -0
  148. package/src/tests/i18nBackend.unit.test.ts +241 -0
  149. package/src/tests/i18nFrontend.unit.test.ts +302 -0
  150. package/src/tests/i18nMiddleware.unit.test.ts +140 -0
  151. package/src/tests/loadI18next.unit.test.ts +443 -0
  152. package/src/tests/locale.test-d.ts +156 -0
  153. package/src/tests/process.unit.test.ts +33 -0
  154. package/src/tests/translationKey.unit.test.ts +166 -0
  155. package/src/tests/translations.unit.test.ts +110 -0
  156. package/src/tests/useTranslation.unit.test.ts +134 -0
  157. package/src/tests/util.unit.test.ts +102 -0
  158. package/src/translationKey.ts +18 -3
  159. package/tsconfig.tsbuildinfo +1 -1
@@ -79,9 +79,25 @@ export function initializeI18n(
79
79
  },
80
80
  ...reactOptions,
81
81
  } as InitOptions);
82
+ // "loaded" fires once per namespace, and again after changeLanguage(),
83
+ // so the raw event is not the one-shot signal the callback expects.
84
+ // Latch it here as well as in loadI18next: this callback is public and
85
+ // a caller could reasonably use it to run a side effect.
86
+ let notified = false;
82
87
  i18n.on("loaded", () => {
88
+ if (notified) {
89
+ return;
90
+ }
91
+ notified = true;
83
92
  i18nLoadedCallback?.(i18n);
84
93
  });
94
+ } else {
95
+ // Already initialised (e.g. useTranslation() ran first, or a second
96
+ // widget mounted). The `loaded` handler is only registered in the init
97
+ // branch, so the callback has to fire here or loadI18next() never
98
+ // settles. Resources are already present, so resolving immediately is
99
+ // correct; the caller reconciles the language afterwards.
100
+ i18nLoadedCallback?.(i18n);
85
101
  }
86
102
  return i18n;
87
103
  }
@@ -15,6 +15,18 @@
15
15
  import type { i18n } from "i18next";
16
16
  let i18nInstance: i18n;
17
17
 
18
+ /**
19
+ * How long to wait for i18next to report `loaded` before giving up on it.
20
+ *
21
+ * Resolution is driven by an event, and events are not guaranteed: a backend
22
+ * that never answers (http backend against a dead CDN, fs backend against a
23
+ * missing locales dir) leaves the listener armed and the promise pending for
24
+ * the lifetime of the process. `cli.ts` calls loadI18next(true).then(...) with
25
+ * nothing to time it out, so a pending promise there is a silent boot hang with
26
+ * no log line to explain it.
27
+ */
28
+ export const I18N_LOAD_TIMEOUT_MS = 10_000;
29
+
18
30
  const reconcileLanguage = async (
19
31
  instance: i18n,
20
32
  lng: string | undefined,
@@ -27,26 +39,82 @@ const reconcileLanguage = async (
27
39
 
28
40
  async function loadI18next(backend: boolean, lng?: string): Promise<i18n> {
29
41
  return new Promise((resolve, reject) => {
30
- try {
31
- if (backend) {
32
- import("./i18nBackend.js").then(({ default: initializeI18n }) => {
42
+ // On timeout we resolve with the (possibly not fully loaded) instance
43
+ // rather than rejecting. i18next falls back to the key itself when a
44
+ // resource is missing, so a degraded instance renders untranslated text;
45
+ // a rejection instead takes down the caller, and neither caller handles
46
+ // one — cli.ts has no .catch, and widgetFactory awaits it inside the
47
+ // render path, so the widget would not appear at all. Untranslated beats
48
+ // absent. Genuine errors still reject: this arm only fires on silence.
49
+ let settled = false;
50
+ // No `settled` guard needed here: both latches clear this timer, so it
51
+ // can only fire while nothing has settled.
52
+ const timer = setTimeout(() => {
53
+ settled = true;
54
+ if (i18nInstance) {
55
+ resolve(i18nInstance);
56
+ } else {
57
+ reject(
58
+ new Error(
59
+ `i18next did not load within ${I18N_LOAD_TIMEOUT_MS}ms and no instance was created`,
60
+ ),
61
+ );
62
+ }
63
+ }, I18N_LOAD_TIMEOUT_MS);
64
+ // Node keeps the event loop alive for a pending timer, which would hold
65
+ // a short-lived CLI process open for the full timeout after it had
66
+ // otherwise finished.
67
+ timer.unref?.();
68
+
69
+ // Both latches are one-shot: i18next can emit `loaded` more than once
70
+ // (per namespace, and again after changeLanguage), and a late event
71
+ // arriving after the timeout must not re-settle or leak a second timer.
72
+ const done = (instance: i18n): void => {
73
+ if (settled) {
74
+ return;
75
+ }
76
+ settled = true;
77
+ clearTimeout(timer);
78
+ resolve(instance);
79
+ };
80
+ const failWith = (error: unknown): void => {
81
+ if (settled) {
82
+ return;
83
+ }
84
+ settled = true;
85
+ clearTimeout(timer);
86
+ reject(error);
87
+ };
88
+
89
+ // Every asynchronous branch below terminates in `.catch(failWith)`. A
90
+ // synchronous try/catch cannot see a rejected dynamic import or a
91
+ // rejected changeLanguage(), so without these the executor would
92
+ // simply return and the promise would stay pending forever — callers
93
+ // (i18nMiddleware, and through it the whole server bootstrap) would
94
+ // hang rather than fail.
95
+ if (backend) {
96
+ import("./i18nBackend.js")
97
+ .then(({ default: initializeI18n }) => {
33
98
  if (!i18nInstance) {
34
- // pass the resolver into the i18n init fn which will resolve after i18n connected fires
35
- i18nInstance = initializeI18n(resolve);
99
+ // pass the resolver into the i18n init fn, which fires it on the first
100
+ // "loaded" event
101
+ i18nInstance = initializeI18n(done);
36
102
  } else {
37
103
  // we've already initialised i18n so just return it
38
- resolve(i18nInstance);
104
+ done(i18nInstance);
39
105
  }
40
- });
41
- } else {
42
- import("./i18nFrontend.js").then(({ default: initializeI18n }) => {
106
+ })
107
+ .catch(failWith);
108
+ } else {
109
+ import("./i18nFrontend.js")
110
+ .then(({ default: initializeI18n }) => {
43
111
  if (!i18nInstance) {
44
112
  // Pass `lng` in on first init so browser detection is skipped
45
113
  // entirely when the site owner has supplied a language. The
46
114
  // resolver only fires on the `loaded` event, so at resolve
47
115
  // time the target-language resources are guaranteed present.
48
116
  i18nInstance = initializeI18n((instance) => {
49
- void reconcileLanguage(instance, lng).then(resolve);
117
+ void reconcileLanguage(instance, lng).then(done).catch(failWith);
50
118
  }, lng);
51
119
  } else {
52
120
  // Singleton already exists (e.g. a prior widget mounted with a
@@ -54,12 +122,12 @@ async function loadI18next(backend: boolean, lng?: string): Promise<i18n> {
54
122
  // Reconcile before resolving so callers can render synchronously
55
123
  // against the requested language instead of seeing a flash of
56
124
  // the previous one.
57
- void reconcileLanguage(i18nInstance, lng).then(resolve);
125
+ void reconcileLanguage(i18nInstance, lng)
126
+ .then(done)
127
+ .catch(failWith);
58
128
  }
59
- });
60
- }
61
- } catch (e) {
62
- reject(e);
129
+ })
130
+ .catch(failWith);
63
131
  }
64
132
  });
65
133
  }
@@ -43,6 +43,7 @@
43
43
  "INVALID_BODY": "بيانات غير صالحة",
44
44
  "PARSE_ERROR": "خطأ في تحليل الطلب",
45
45
  "SPAM_EMAIL_RULE": "عنوان البريد الإلكتروني محظور بواسطة قواعد مكافحة البريد العشوائي",
46
+ "SPAM_EMAIL_COUNT_EXCEEDED": "كثرة الطلبات من عنوان البريد الإلكتروني هذا",
46
47
  "VPN_BLOCKED": "لا يُسمح بالطلبات من اتصالات VPN لهذا الموقع",
47
48
  "PROXY_BLOCKED": "لا يُسمح بالطلبات من خوادم البروكسي لهذا الموقع",
48
49
  "TOR_BLOCKED": "لا يُسمح بالطلبات من عقد خروج Tor لهذا الموقع",
@@ -43,6 +43,7 @@
43
43
  "INVALID_BODY": "Yanlış bədən",
44
44
  "PARSE_ERROR": "Tələbi pars edərkən səhv baş verdi",
45
45
  "SPAM_EMAIL_RULE": "E-poçt ünvanı spam filtr qaydaları ilə bloklanıb",
46
+ "SPAM_EMAIL_COUNT_EXCEEDED": "Bu e-poçt ünvanından çox sayda göndəriş",
46
47
  "VPN_BLOCKED": "Bu sayt üçün VPN bağlantılarından sorğulara icazə verilmir",
47
48
  "PROXY_BLOCKED": "Bu sayt üçün proksi serverlərdən sorğulara icazə verilmir",
48
49
  "TOR_BLOCKED": "Bu sayt üçün Tor çıxış qovşaqlarından sorğulara icazə verilmir",
@@ -43,6 +43,7 @@
43
43
  "INVALID_BODY": "Neplatné tělo",
44
44
  "PARSE_ERROR": "Chyba při zpracování požadavku",
45
45
  "SPAM_EMAIL_RULE": "E-mailová adresa blokována pravidly spamového filtru",
46
+ "SPAM_EMAIL_COUNT_EXCEEDED": "Příliš mnoho odeslání z této e-mailové adresy",
46
47
  "VPN_BLOCKED": "Požadavky z VPN připojení nejsou pro tento web povoleny",
47
48
  "PROXY_BLOCKED": "Požadavky z proxy serverů nejsou pro tento web povoleny",
48
49
  "TOR_BLOCKED": "Požadavky z výstupních uzlů Tor nejsou pro tento web povoleny",
@@ -43,6 +43,7 @@
43
43
  "INVALID_BODY": "Ungültiger Körper",
44
44
  "PARSE_ERROR": "Fehler beim Parsen der Anfrage",
45
45
  "SPAM_EMAIL_RULE": "E-Mail-Adresse durch Spamfilter-Regeln blockiert",
46
+ "SPAM_EMAIL_COUNT_EXCEEDED": "Zu viele Einsendungen von dieser E-Mail-Adresse",
46
47
  "VPN_BLOCKED": "Anfragen über VPN-Verbindungen sind für diese Website nicht erlaubt",
47
48
  "PROXY_BLOCKED": "Anfragen über Proxy-Server sind für diese Website nicht erlaubt",
48
49
  "TOR_BLOCKED": "Anfragen über Tor-Exit-Knoten sind für diese Website nicht erlaubt",
@@ -30,6 +30,7 @@
30
30
  "INVALID_BODY": "Μη έγκυρο σώμα",
31
31
  "PARSE_ERROR": "Σφάλμα ανάλυσης αιτήματος",
32
32
  "SPAM_EMAIL_RULE": "Η διεύθυνση email αποκλείστηκε από κανόνες φίλτρου spam",
33
+ "SPAM_EMAIL_COUNT_EXCEEDED": "Πάρα πολλές υποβολές από αυτή τη διεύθυνση email",
33
34
  "VPN_BLOCKED": "Αιτήματα από συνδέσεις VPN δεν επιτρέπονται για αυτόν τον ιστότοπο",
34
35
  "PROXY_BLOCKED": "Αιτήματα από διακομιστές proxy δεν επιτρέπονται για αυτόν τον ιστότοπο",
35
36
  "TOR_BLOCKED": "Αιτήματα από κόμβους εξόδου Tor δεν επιτρέπονται για αυτόν τον ιστότοπο",
@@ -122,6 +122,7 @@
122
122
  "PROVIDER_VERIFY_FAILED": "Provider verification failed",
123
123
  "SPAM_EMAIL_DOMAIN": "Email domain not allowed",
124
124
  "SPAM_EMAIL_RULE": "Email address blocked by spam filter rules",
125
+ "SPAM_EMAIL_COUNT_EXCEEDED": "Too many submissions from this email address",
125
126
  "VPN_BLOCKED": "Requests from VPN connections are not allowed for this site",
126
127
  "PROXY_BLOCKED": "Requests from proxy servers are not allowed for this site",
127
128
  "TOR_BLOCKED": "Requests from Tor exit nodes are not allowed for this site",
@@ -43,6 +43,7 @@
43
43
  "INVALID_BODY": "Cuerpo inválido",
44
44
  "PARSE_ERROR": "Error al analizar la respuesta",
45
45
  "SPAM_EMAIL_RULE": "Dirección de correo electrónico bloqueada por reglas de filtro de spam",
46
+ "SPAM_EMAIL_COUNT_EXCEEDED": "Demasiados envíos desde esta dirección de correo electrónico",
46
47
  "VPN_BLOCKED": "Las solicitudes desde conexiones VPN no están permitidas para este sitio",
47
48
  "PROXY_BLOCKED": "Las solicitudes desde servidores proxy no están permitidas para este sitio",
48
49
  "TOR_BLOCKED": "Las solicitudes desde nodos de salida Tor no están permitidas para este sitio",
@@ -49,6 +49,7 @@
49
49
  "PROVIDER_VERIFY_FAILED": "Palveluntarjoajan tarkistus epäonnistui",
50
50
  "SPAM_EMAIL_DOMAIN": "Sähköpostiverkkotunnus ei ole sallittu",
51
51
  "SPAM_EMAIL_RULE": "Sähköpostiosoite estetty roskapostisuodattimen säännöillä",
52
+ "SPAM_EMAIL_COUNT_EXCEEDED": "Liian monta lähetystä tästä sähköpostiosoitteesta",
52
53
  "VPN_BLOCKED": "VPN-yhteyksien pyynnöt eivät ole sallittuja tälle sivustolle",
53
54
  "PROXY_BLOCKED": "Välityspalvelimien pyynnöt eivät ole sallittuja tälle sivustolle",
54
55
  "TOR_BLOCKED": "Tor-poistumissolmujen pyynnöt eivät ole sallittuja tälle sivustolle",
@@ -43,6 +43,7 @@
43
43
  "INVALID_BODY": "Cuerpo inválido",
44
44
  "PARSE_ERROR": "Erreur d'analyse de la demande",
45
45
  "SPAM_EMAIL_RULE": "Adresse e-mail bloquée par les règles du filtre anti-spam",
46
+ "SPAM_EMAIL_COUNT_EXCEEDED": "Trop de soumissions depuis cette adresse e-mail",
46
47
  "VPN_BLOCKED": "Les requêtes provenant de connexions VPN ne sont pas autorisées pour ce site",
47
48
  "PROXY_BLOCKED": "Les requêtes provenant de serveurs proxy ne sont pas autorisées pour ce site",
48
49
  "TOR_BLOCKED": "Les requêtes provenant de nœuds de sortie Tor ne sont pas autorisées pour ce site",
@@ -342,6 +342,7 @@
342
342
  "INVALID_BODY": "अमनय बड",
343
343
  "PARSE_ERROR": "अनरध वशषण म तरट",
344
344
  "SPAM_EMAIL_RULE": "स्पैम फ़िल्टर नियमों द्वारा ईमेल पता अवरुद्ध",
345
+ "SPAM_EMAIL_COUNT_EXCEEDED": "इस ईमेल पते से बहुत अधिक सबमिशन",
345
346
  "VPN_BLOCKED": "इस साइट के लिए VPN कनेक्शन से अनुरोध की अनुमति नहीं है",
346
347
  "PROXY_BLOCKED": "इस साइट के लिए प्रॉक्सी सर्वर से अनुरोध की अनुमति नहीं है",
347
348
  "TOR_BLOCKED": "इस साइट के लिए Tor एक्ज़िट नोड्स से अनुरोध की अनुमति नहीं है",
@@ -43,6 +43,7 @@
43
43
  "INVALID_BODY": "Érvénytelen test",
44
44
  "PARSE_ERROR": "Hiba az adatkérések elemzése közben",
45
45
  "SPAM_EMAIL_RULE": "Az e-mail cím a spamszűrő szabályai alapján blokkolva",
46
+ "SPAM_EMAIL_COUNT_EXCEEDED": "Túl sok beküldés erről az e-mail címről",
46
47
  "VPN_BLOCKED": "VPN-kapcsolatokról érkező kérések nem engedélyezettek ezen az oldalon",
47
48
  "PROXY_BLOCKED": "Proxy szerverekről érkező kérések nem engedélyezettek ezen az oldalon",
48
49
  "TOR_BLOCKED": "Tor kilépési csomópontokról érkező kérések nem engedélyezettek ezen az oldalon",
@@ -43,6 +43,7 @@
43
43
  "INVALID_BODY": "Invalid body",
44
44
  "PARSE_ERROR": "Error parsing request",
45
45
  "SPAM_EMAIL_RULE": "Alamat email diblokir oleh aturan filter spam",
46
+ "SPAM_EMAIL_COUNT_EXCEEDED": "Terlalu banyak pengiriman dari alamat email ini",
46
47
  "VPN_BLOCKED": "Permintaan dari koneksi VPN tidak diizinkan untuk situs ini",
47
48
  "PROXY_BLOCKED": "Permintaan dari server proxy tidak diizinkan untuk situs ini",
48
49
  "TOR_BLOCKED": "Permintaan dari node keluar Tor tidak diizinkan untuk situs ini",
@@ -43,6 +43,7 @@
43
43
  "INVALID_BODY": "Corpo non valido",
44
44
  "PARSE_ERROR": "Errore nell'analisi della richiesta",
45
45
  "SPAM_EMAIL_RULE": "Indirizzo email bloccato dalle regole del filtro antispam",
46
+ "SPAM_EMAIL_COUNT_EXCEEDED": "Troppi invii da questo indirizzo email",
46
47
  "VPN_BLOCKED": "Le richieste da connessioni VPN non sono consentite per questo sito",
47
48
  "PROXY_BLOCKED": "Le richieste da server proxy non sono consentite per questo sito",
48
49
  "TOR_BLOCKED": "Le richieste da nodi di uscita Tor non sono consentite per questo sito",
@@ -342,6 +342,7 @@
342
342
  "INVALID_BODY": "無効な本体",
343
343
  "PARSE_ERROR": "リクエストの解析エラー",
344
344
  "SPAM_EMAIL_RULE": "スパムフィルタールールによりメールアドレスがブロックされました",
345
+ "SPAM_EMAIL_COUNT_EXCEEDED": "このメールアドレスからの送信が多すぎます",
345
346
  "VPN_BLOCKED": "このサイトではVPN接続からのリクエストは許可されていません",
346
347
  "PROXY_BLOCKED": "このサイトではプロキシサーバーからのリクエストは許可されていません",
347
348
  "TOR_BLOCKED": "このサイトではTor出口ノードからのリクエストは許可されていません",
@@ -43,6 +43,7 @@
43
43
  "INVALID_BODY": "Badan tidak valid",
44
44
  "PARSE_ERROR": "Kesalahan parsing permintaan",
45
45
  "SPAM_EMAIL_RULE": "Alamat email diblokir dening aturan filter spam",
46
+ "SPAM_EMAIL_COUNT_EXCEEDED": "Kakehen kiriman saka alamat email iki",
46
47
  "VPN_BLOCKED": "Panjaluk saka sambungan VPN ora diidini kanggo situs iki",
47
48
  "PROXY_BLOCKED": "Panjaluk saka server proxy ora diidini kanggo situs iki",
48
49
  "TOR_BLOCKED": "Panjaluk saka simpul metu Tor ora diidini kanggo situs iki",
@@ -43,6 +43,7 @@
43
43
  "INVALID_BODY": "유효하지 않은 본문",
44
44
  "PARSE_ERROR": "요청 구문 분석 오류",
45
45
  "SPAM_EMAIL_RULE": "스팸 필터 규칙에 의해 이메일 주소가 차단되었습니다",
46
+ "SPAM_EMAIL_COUNT_EXCEEDED": "이 이메일 주소에서 너무 많은 제출이 있었습니다",
46
47
  "VPN_BLOCKED": "이 사이트에서는 VPN 연결의 요청이 허용되지 않습니다",
47
48
  "PROXY_BLOCKED": "이 사이트에서는 프록시 서버의 요청이 허용되지 않습니다",
48
49
  "TOR_BLOCKED": "이 사이트에서는 Tor 출구 노드의 요청이 허용되지 않습니다",
@@ -43,6 +43,7 @@
43
43
  "INVALID_BODY": "Hindi wastong body",
44
44
  "PARSE_ERROR": "Error sa pagsasalin ng request",
45
45
  "SPAM_EMAIL_RULE": "സ്പാം ഫിൽട്ടർ നിയമങ്ങളാൽ ഇമെയിൽ വിലാസം തടഞ്ഞു",
46
+ "SPAM_EMAIL_COUNT_EXCEEDED": "ഈ ഇമെയിൽ വിലാസത്തിൽ നിന്ന് വളരെയധികം സമർപ്പണങ്ങൾ",
46
47
  "VPN_BLOCKED": "ഈ സൈറ്റിന് VPN കണക്ഷനുകളിൽ നിന്നുള്ള അഭ്യർത്ഥനകൾ അനുവദനീയമല്ല",
47
48
  "PROXY_BLOCKED": "ഈ സൈറ്റിന് പ്രോക്സി സെർവറുകളിൽ നിന്നുള്ള അഭ്യർത്ഥനകൾ അനുവദനീയമല്ല",
48
49
  "TOR_BLOCKED": "ഈ സൈറ്റിന് Tor എക്സിറ്റ് നോഡുകളിൽ നിന്നുള്ള അഭ്യർത്ഥനകൾ അനുവദനീയമല്ല",
@@ -342,6 +342,7 @@
342
342
  "INVALID_BODY": "Invalid body",
343
343
  "PARSE_ERROR": "Error parsing request",
344
344
  "SPAM_EMAIL_RULE": "Alamat e-mel disekat oleh peraturan penapis spam",
345
+ "SPAM_EMAIL_COUNT_EXCEEDED": "Terlalu banyak penghantaran dari alamat e-mel ini",
345
346
  "VPN_BLOCKED": "Permintaan daripada sambungan VPN tidak dibenarkan untuk laman ini",
346
347
  "PROXY_BLOCKED": "Permintaan daripada pelayan proksi tidak dibenarkan untuk laman ini",
347
348
  "TOR_BLOCKED": "Permintaan daripada nod keluar Tor tidak dibenarkan untuk laman ini",
@@ -342,6 +342,7 @@
342
342
  "INVALID_BODY": "Ongeldige body",
343
343
  "PARSE_ERROR": "Fout bij parsen van aanvraag",
344
344
  "SPAM_EMAIL_RULE": "E-mailadres geblokkeerd door spamfilterregels",
345
+ "SPAM_EMAIL_COUNT_EXCEEDED": "Te veel inzendingen vanaf dit e-mailadres",
345
346
  "VPN_BLOCKED": "Verzoeken via VPN-verbindingen zijn niet toegestaan voor deze site",
346
347
  "PROXY_BLOCKED": "Verzoeken via proxyservers zijn niet toegestaan voor deze site",
347
348
  "TOR_BLOCKED": "Verzoeken via Tor-exitnodes zijn niet toegestaan voor deze site",
@@ -43,6 +43,7 @@
43
43
  "INVALID_BODY": "Ugyldig kropp",
44
44
  "PARSE_ERROR": "Feil ved å tolke forespørsel",
45
45
  "SPAM_EMAIL_RULE": "E-postadresse blokkert av spamfilterregler",
46
+ "SPAM_EMAIL_COUNT_EXCEEDED": "For mange innsendinger fra denne e-postadressen",
46
47
  "VPN_BLOCKED": "Forespørsler fra VPN-tilkoblinger er ikke tillatt for dette nettstedet",
47
48
  "PROXY_BLOCKED": "Forespørsler fra proxyservere er ikke tillatt for dette nettstedet",
48
49
  "TOR_BLOCKED": "Forespørsler fra Tor-utgangsnoder er ikke tillatt for dette nettstedet",
@@ -43,6 +43,7 @@
43
43
  "INVALID_BODY": "Nieprawidłowe ciało",
44
44
  "PARSE_ERROR": "Błąd parsowania żądania",
45
45
  "SPAM_EMAIL_RULE": "Adres e-mail zablokowany przez reguły filtra spamu",
46
+ "SPAM_EMAIL_COUNT_EXCEEDED": "Zbyt wiele zgłoszeń z tego adresu e-mail",
46
47
  "VPN_BLOCKED": "Żądania z połączeń VPN nie są dozwolone dla tej strony",
47
48
  "PROXY_BLOCKED": "Żądania z serwerów proxy nie są dozwolone dla tej strony",
48
49
  "TOR_BLOCKED": "Żądania z węzłów wyjściowych Tor nie są dozwolone dla tej strony",
@@ -43,6 +43,7 @@
43
43
  "INVALID_BODY": "Corpo inválido",
44
44
  "PARSE_ERROR": "Erro ao analisar a resposta",
45
45
  "SPAM_EMAIL_RULE": "Endereço de e-mail bloqueado pelas regras do filtro de spam",
46
+ "SPAM_EMAIL_COUNT_EXCEEDED": "Demasiados envios deste endereço de e-mail",
46
47
  "VPN_BLOCKED": "Pedidos a partir de ligações VPN não são permitidos para este site",
47
48
  "PROXY_BLOCKED": "Pedidos a partir de servidores proxy não são permitidos para este site",
48
49
  "TOR_BLOCKED": "Pedidos a partir de nós de saída Tor não são permitidos para este site",
@@ -43,6 +43,7 @@
43
43
  "INVALID_BODY": "Corpo inválido",
44
44
  "PARSE_ERROR": "Erro ao analisar a solicitação",
45
45
  "SPAM_EMAIL_RULE": "Endereço de e-mail bloqueado pelas regras do filtro de spam",
46
+ "SPAM_EMAIL_COUNT_EXCEEDED": "Muitos envios deste endereço de e-mail",
46
47
  "VPN_BLOCKED": "Solicitações de conexões VPN não são permitidas para este site",
47
48
  "PROXY_BLOCKED": "Solicitações de servidores proxy não são permitidas para este site",
48
49
  "TOR_BLOCKED": "Solicitações de nós de saída Tor não são permitidas para este site",
@@ -43,6 +43,7 @@
43
43
  "INVALID_BODY": "Corp invalid",
44
44
  "PARSE_ERROR": "Eroare la parsare",
45
45
  "SPAM_EMAIL_RULE": "Adresa de e-mail blocată de regulile filtrului de spam",
46
+ "SPAM_EMAIL_COUNT_EXCEEDED": "Prea multe trimiteri de la această adresă de e-mail",
46
47
  "VPN_BLOCKED": "Solicitările de la conexiuni VPN nu sunt permise pentru acest site",
47
48
  "PROXY_BLOCKED": "Solicitările de la servere proxy nu sunt permise pentru acest site",
48
49
  "TOR_BLOCKED": "Solicitările de la noduri de ieșire Tor nu sunt permise pentru acest site",
@@ -43,6 +43,7 @@
43
43
  "INVALID_BODY": "Недопустимое тело запроса",
44
44
  "PARSE_ERROR": "Ошибка разбора запроса",
45
45
  "SPAM_EMAIL_RULE": "Адрес электронной почты заблокирован правилами спам-фильтра",
46
+ "SPAM_EMAIL_COUNT_EXCEEDED": "Слишком много отправок с этого адреса электронной почты",
46
47
  "VPN_BLOCKED": "Запросы через VPN-подключения не разрешены для этого сайта",
47
48
  "PROXY_BLOCKED": "Запросы через прокси-серверы не разрешены для этого сайта",
48
49
  "TOR_BLOCKED": "Запросы через выходные узлы Tor не разрешены для этого сайта",
@@ -43,6 +43,7 @@
43
43
  "INVALID_BODY": "Неважеће тело захтева",
44
44
  "PARSE_ERROR": "Грешка при рашчлањивању захтева",
45
45
  "SPAM_EMAIL_RULE": "Адреса е-поште блокирана правилима филтера за нежељену пошту",
46
+ "SPAM_EMAIL_COUNT_EXCEEDED": "Превише слања са ове адресе е-поште",
46
47
  "VPN_BLOCKED": "Захтеви са VPN конекција нису дозвољени за овај сајт",
47
48
  "PROXY_BLOCKED": "З��хтеви са прокси сервера нису дозвољени за овај сајт",
48
49
  "TOR_BLOCKED": "Захтеви са Tor излазних чворова нису дозвољени за овај сајт",
@@ -43,6 +43,7 @@
43
43
  "INVALID_BODY": "Ogiltig body",
44
44
  "PARSE_ERROR": "Fel vid tolkning av förfrågan",
45
45
  "SPAM_EMAIL_RULE": "E-postadressen blockerad av spamfilterregler",
46
+ "SPAM_EMAIL_COUNT_EXCEEDED": "För många inskickningar från denna e-postadress",
46
47
  "VPN_BLOCKED": "Förfrågningar från VPN-anslutningar är inte tillåtna för denna webbplats",
47
48
  "PROXY_BLOCKED": "Förfrågningar från proxyservrar är inte tillåtna för denna webbplats",
48
49
  "TOR_BLOCKED": "Förfrågningar från Tor-utgångsnoder är inte tillåtna för denna webbplats",
@@ -43,6 +43,7 @@
43
43
  "INVALID_BODY": "ข้อมูลไม่ถูกต้อง",
44
44
  "PARSE_ERROR": "ข้อผิดพลาดในการแปลคำขอ",
45
45
  "SPAM_EMAIL_RULE": "ที่อยู่อีเมลถูกบล็อกโดยกฎตัวกรองสแปม",
46
+ "SPAM_EMAIL_COUNT_EXCEEDED": "มีการส่งจากที่อยู่อีเมลนี้มากเกินไป",
46
47
  "VPN_BLOCKED": "คำขอจากการเชื่อมต่อ VPN ไม่ได้รับอนุญาตสำหรับเว็บไซต์นี้",
47
48
  "PROXY_BLOCKED": "คำขอจากเซิร์ฟเวอร์พร็อกซีไม่ได้รับอนุญาตสำหรับเว็บไซต์นี้",
48
49
  "TOR_BLOCKED": "คำขอจากโหนดทางออก Tor ไม่ได้รับอนุญาตสำหรับเว็บไซต์นี้",
@@ -43,6 +43,7 @@
43
43
  "INVALID_BODY": "Geçersiz gövde",
44
44
  "PARSE_ERROR": "İstek ayrıştırma hatası",
45
45
  "SPAM_EMAIL_RULE": "E-posta adresi spam filtresi kuralları tarafından engellendi",
46
+ "SPAM_EMAIL_COUNT_EXCEEDED": "Bu e-posta adresinden çok fazla gönderi",
46
47
  "VPN_BLOCKED": "Bu site için VPN bağlantılarından gelen isteklere izin verilmiyor",
47
48
  "PROXY_BLOCKED": "Bu site için proxy sunucularından gelen isteklere izin verilmiyor",
48
49
  "TOR_BLOCKED": "Bu site için Tor çıkış düğümlerinden gelen isteklere izin verilmiyor",
@@ -43,6 +43,7 @@
43
43
  "INVALID_BODY": "Invalid body",
44
44
  "PARSE_ERROR": "Error parsing request",
45
45
  "SPAM_EMAIL_RULE": "Адресу електронної пошти заблоковано правилами спам-фільтра",
46
+ "SPAM_EMAIL_COUNT_EXCEEDED": "Забагато надсилань з цієї адреси електронної пошти",
46
47
  "VPN_BLOCKED": "Запити з VPN-з'єднань не дозволені для цього сайту",
47
48
  "PROXY_BLOCKED": "Запити з проксі-серверів не дозволені для цього сайту",
48
49
  "TOR_BLOCKED": "Запити з вихідних вузлів Tor не дозволені для цього сайту",
@@ -43,6 +43,7 @@
43
43
  "INVALID_BODY": "Nội dung không hợp lệ",
44
44
  "PARSE_ERROR": "Lỗi phân tích yêu cầu",
45
45
  "SPAM_EMAIL_RULE": "Địa chỉ email bị chặn bởi quy tắc bộ lọc spam",
46
+ "SPAM_EMAIL_COUNT_EXCEEDED": "Quá nhiều lượt gửi từ địa chỉ email này",
46
47
  "VPN_BLOCKED": "Các yêu cầu từ kết nối VPN không được phép cho trang web này",
47
48
  "PROXY_BLOCKED": "Các yêu cầu từ máy chủ proxy không được phép cho trang web này",
48
49
  "TOR_BLOCKED": "Các yêu cầu từ nút thoát Tor không được phép cho trang web này",
@@ -43,6 +43,7 @@
43
43
  "INVALID_BODY": "无效的主体",
44
44
  "PARSE_ERROR": "解析请求时出错",
45
45
  "SPAM_EMAIL_RULE": "电子邮件地址被垃圾邮件过滤规则拦截",
46
+ "SPAM_EMAIL_COUNT_EXCEEDED": "该电子邮件地址提交次数过多",
46
47
  "VPN_BLOCKED": "此网站不允许来自VPN连接的请求",
47
48
  "PROXY_BLOCKED": "此网站不允许来自代理服务器的请求",
48
49
  "TOR_BLOCKED": "此网站不允许来自Tor出口节点的请求",
package/src/process.ts ADDED
@@ -0,0 +1,29 @@
1
+ // Copyright 2021-2026 Prosopo (UK) Ltd.
2
+ //
3
+ // Licensed under the Apache License, Version 2.0 (the "License");
4
+ // you may not use this file except in compliance with the License.
5
+ // You may obtain a copy of the License at
6
+ //
7
+ // http://www.apache.org/licenses/LICENSE-2.0
8
+ //
9
+ // Unless required by applicable law or agreed to in writing, software
10
+ // distributed under the License is distributed on an "AS IS" BASIS,
11
+ // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ // See the License for the specific language governing permissions and
13
+ // limitations under the License.
14
+
15
+ /**
16
+ * The `process` global, or undefined where there is none.
17
+ *
18
+ * This package is reached transitively from browser bundles, where `process`
19
+ * may be missing entirely or present but stripped down. A bare `process.env`
20
+ * read throws a ReferenceError in that case and takes the whole page down.
21
+ *
22
+ * It is a function, and it lives in its own module, so callers can be tested
23
+ * against a runtime that has no `process` at all. Inlining the `typeof` check
24
+ * at the point of use makes that branch untestable: vitest calls
25
+ * `process.memoryUsage()` between suites, so stubbing the real global away
26
+ * fails the run rather than the module under test.
27
+ */
28
+ export const getProcess = (): NodeJS.Process | undefined =>
29
+ typeof process !== "undefined" ? process : undefined;
@@ -0,0 +1,139 @@
1
+ // Copyright 2021-2026 Prosopo (UK) Ltd.
2
+ //
3
+ // Licensed under the Apache License, Version 2.0 (the "License");
4
+ // you may not use this file except in compliance with the License.
5
+ // You may obtain a copy of the License at
6
+ //
7
+ // http://www.apache.org/licenses/LICENSE-2.0
8
+ //
9
+ // Unless required by applicable law or agreed to in writing, software
10
+ // distributed under the License is distributed on an "AS IS" BASIS,
11
+ // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ // See the License for the specific language governing permissions and
13
+ // limitations under the License.
14
+
15
+ import { afterEach, beforeEach, describe, expect, test, vi } from "vitest";
16
+ import { Languages } from "../translations.js";
17
+
18
+ // The module reads process.env at import time, so every expectation about the
19
+ // debug flag has to re-import it under a fresh module registry.
20
+ type SharedOptions =
21
+ typeof import("../i18SharedOptions.js")["i18nSharedOptions"];
22
+
23
+ const loadOptions = async (): Promise<SharedOptions> => {
24
+ vi.resetModules();
25
+ const { i18nSharedOptions } = await import("../i18SharedOptions.js");
26
+ return i18nSharedOptions;
27
+ };
28
+
29
+ const originalLogLevel = process.env.PROSOPO_LOG_LEVEL;
30
+
31
+ beforeEach(() => {
32
+ Reflect.deleteProperty(process.env, "PROSOPO_LOG_LEVEL");
33
+ });
34
+
35
+ afterEach(() => {
36
+ vi.doUnmock("../process.js");
37
+ vi.resetModules();
38
+ if (originalLogLevel === undefined) {
39
+ Reflect.deleteProperty(process.env, "PROSOPO_LOG_LEVEL");
40
+ } else {
41
+ process.env.PROSOPO_LOG_LEVEL = originalLogLevel;
42
+ }
43
+ });
44
+
45
+ describe("i18nSharedOptions", () => {
46
+ test("falls back to English", async () => {
47
+ const options = await loadOptions();
48
+ expect(options.fallbackLng).toBe("en");
49
+ });
50
+
51
+ test("uses the single translation namespace", async () => {
52
+ const options = await loadOptions();
53
+ expect(options.namespace).toBe("translation");
54
+ });
55
+
56
+ test("supports exactly the declared language codes", async () => {
57
+ const options = await loadOptions();
58
+ expect(new Set(options.supportedLngs)).toEqual(
59
+ new Set(Object.values(Languages)),
60
+ );
61
+ });
62
+
63
+ // With this false, i18next will not silently serve `en` resources for a
64
+ // request for `en-GB`. Flipping it would quietly change which file a
65
+ // regional locale resolves to.
66
+ test("does not accept non-explicit regional variants", async () => {
67
+ const options = await loadOptions();
68
+ expect(options.nonExplicitSupportedLngs).toBe(false);
69
+ });
70
+
71
+ describe("debug flag", () => {
72
+ test("is off when PROSOPO_LOG_LEVEL is unset", async () => {
73
+ const options = await loadOptions();
74
+ expect(options.debug).toBe(false);
75
+ });
76
+
77
+ test("is on only for the exact value 'debug'", async () => {
78
+ process.env.PROSOPO_LOG_LEVEL = "debug";
79
+ expect((await loadOptions()).debug).toBe(true);
80
+ });
81
+
82
+ test("is off for any other log level", async () => {
83
+ for (const level of ["info", "warn", "error", "trace", ""]) {
84
+ process.env.PROSOPO_LOG_LEVEL = level;
85
+ expect((await loadOptions()).debug, level).toBe(false);
86
+ }
87
+ });
88
+
89
+ test("is case sensitive — 'DEBUG' does not enable it", async () => {
90
+ process.env.PROSOPO_LOG_LEVEL = "DEBUG";
91
+ expect((await loadOptions()).debug).toBe(false);
92
+ });
93
+ });
94
+
95
+ // This module is reached transitively from browser bundles, where `process`
96
+ // is absent or partial, so both degraded shapes have to load cleanly.
97
+ //
98
+ // The `process` read goes through getProcess() precisely so it can be mocked
99
+ // here. Stubbing the global instead is not viable: vitest itself calls
100
+ // process.memoryUsage() between suites, so removing the real global fails
101
+ // the run rather than the module under test.
102
+ describe("browser runtime, where process may be absent or partial", () => {
103
+ const mockProcess = (value: NodeJS.Process | undefined): void => {
104
+ vi.resetModules();
105
+ vi.doMock("../process.js", () => ({
106
+ getProcess: (): NodeJS.Process | undefined => value,
107
+ }));
108
+ };
109
+
110
+ test("loads when there is no process at all", async () => {
111
+ mockProcess(undefined);
112
+ const { i18nSharedOptions } = await import("../i18SharedOptions.js");
113
+ expect(i18nSharedOptions.debug).toBe(false);
114
+ expect(i18nSharedOptions.fallbackLng).toBe("en");
115
+ });
116
+
117
+ test("loads when process exists but exposes no env", async () => {
118
+ // Prototype-chained off the real process so the shape stays honest;
119
+ // only `env` is shadowed away.
120
+ const partial: NodeJS.Process = Object.create(process);
121
+ Reflect.defineProperty(partial, "env", {
122
+ value: undefined,
123
+ configurable: true,
124
+ });
125
+ mockProcess(partial);
126
+
127
+ const { i18nSharedOptions } = await import("../i18SharedOptions.js");
128
+ expect(i18nSharedOptions.debug).toBe(false);
129
+ expect(i18nSharedOptions.fallbackLng).toBe("en");
130
+ });
131
+
132
+ test("still reads the log level when process is present", async () => {
133
+ process.env.PROSOPO_LOG_LEVEL = "debug";
134
+ mockProcess(process);
135
+ const { i18nSharedOptions } = await import("../i18SharedOptions.js");
136
+ expect(i18nSharedOptions.debug).toBe(true);
137
+ });
138
+ });
139
+ });