@prosopo/locale 3.4.2 → 3.5.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 (112) hide show
  1. package/.turbo/turbo-build$colon$cjs.log +6 -6
  2. package/.turbo/turbo-build$colon$tsc.log +5 -5
  3. package/.turbo/turbo-build.log +7 -7
  4. package/CHANGELOG.md +33 -0
  5. package/dist/cjs/i18nFrontend.cjs +11 -15
  6. package/dist/cjs/index.cjs +2 -2
  7. package/dist/cjs/locales/en/translation.cjs +1 -0
  8. package/dist/cjs/translator.cjs +30 -0
  9. package/dist/i18nFrontend.d.ts.map +1 -1
  10. package/dist/i18nFrontend.js +11 -15
  11. package/dist/i18nFrontend.js.map +1 -1
  12. package/dist/index.d.ts +2 -1
  13. package/dist/index.d.ts.map +1 -1
  14. package/dist/index.js +2 -2
  15. package/dist/index.js.map +1 -1
  16. package/dist/locales/ar/translation.json +1 -0
  17. package/dist/locales/az/translation.json +1 -0
  18. package/dist/locales/cs/translation.json +1 -0
  19. package/dist/locales/de/translation.json +1 -0
  20. package/dist/locales/el/translation.json +1 -0
  21. package/dist/locales/en/translation.js +1 -0
  22. package/dist/locales/en/translation.json +1 -0
  23. package/dist/locales/es/translation.json +1 -0
  24. package/dist/locales/fi/translation.json +1 -0
  25. package/dist/locales/fr/translation.json +1 -0
  26. package/dist/locales/hi/translation.json +1 -0
  27. package/dist/locales/hu/translation.json +1 -0
  28. package/dist/locales/id/translation.json +1 -0
  29. package/dist/locales/it/translation.json +1 -0
  30. package/dist/locales/ja/translation.json +1 -0
  31. package/dist/locales/jv/translation.json +1 -0
  32. package/dist/locales/ko/translation.json +1 -0
  33. package/dist/locales/ml/translation.json +1 -0
  34. package/dist/locales/ms/translation.json +1 -0
  35. package/dist/locales/nl/translation.json +1 -0
  36. package/dist/locales/no/translation.json +1 -0
  37. package/dist/locales/pl/translation.json +1 -0
  38. package/dist/locales/pt/translation.json +1 -0
  39. package/dist/locales/pt-BR/translation.json +1 -0
  40. package/dist/locales/ro/translation.json +1 -0
  41. package/dist/locales/ru/translation.json +1 -0
  42. package/dist/locales/sr/translation.json +1 -0
  43. package/dist/locales/sv/translation.json +1 -0
  44. package/dist/locales/th/translation.json +1 -0
  45. package/dist/locales/tr/translation.json +1 -0
  46. package/dist/locales/uk/translation.json +1 -0
  47. package/dist/locales/vi/translation.json +1 -0
  48. package/dist/locales/zh-CN/translation.json +1 -0
  49. package/dist/tests/i18nFrontend.unit.test.js +2 -5
  50. package/dist/tests/i18nFrontend.unit.test.js.map +1 -1
  51. package/dist/tests/locale.test-d.js +13 -10
  52. package/dist/tests/locale.test-d.js.map +1 -1
  53. package/dist/tests/translator.unit.test.d.ts +2 -0
  54. package/dist/tests/translator.unit.test.d.ts.map +1 -0
  55. package/dist/tests/translator.unit.test.js +134 -0
  56. package/dist/tests/translator.unit.test.js.map +1 -0
  57. package/dist/translator.d.ts +10 -0
  58. package/dist/translator.d.ts.map +1 -0
  59. package/dist/translator.js +30 -0
  60. package/dist/translator.js.map +1 -0
  61. package/package.json +5 -6
  62. package/src/i18nFrontend.ts +3 -8
  63. package/src/index.ts +2 -1
  64. package/src/loadI18next.ts +1 -1
  65. package/src/locales/ar/translation.json +1 -0
  66. package/src/locales/az/translation.json +1 -0
  67. package/src/locales/cs/translation.json +1 -0
  68. package/src/locales/de/translation.json +1 -0
  69. package/src/locales/el/translation.json +1 -0
  70. package/src/locales/en/translation.json +1 -0
  71. package/src/locales/es/translation.json +1 -0
  72. package/src/locales/fi/translation.json +1 -0
  73. package/src/locales/fr/translation.json +1 -0
  74. package/src/locales/hi/translation.json +1 -0
  75. package/src/locales/hu/translation.json +1 -0
  76. package/src/locales/id/translation.json +1 -0
  77. package/src/locales/it/translation.json +1 -0
  78. package/src/locales/ja/translation.json +1 -0
  79. package/src/locales/jv/translation.json +1 -0
  80. package/src/locales/ko/translation.json +1 -0
  81. package/src/locales/ml/translation.json +1 -0
  82. package/src/locales/ms/translation.json +1 -0
  83. package/src/locales/nl/translation.json +1 -0
  84. package/src/locales/no/translation.json +1 -0
  85. package/src/locales/pl/translation.json +1 -0
  86. package/src/locales/pt/translation.json +1 -0
  87. package/src/locales/pt-BR/translation.json +1 -0
  88. package/src/locales/ro/translation.json +1 -0
  89. package/src/locales/ru/translation.json +1 -0
  90. package/src/locales/sr/translation.json +1 -0
  91. package/src/locales/sv/translation.json +1 -0
  92. package/src/locales/th/translation.json +1 -0
  93. package/src/locales/tr/translation.json +1 -0
  94. package/src/locales/uk/translation.json +1 -0
  95. package/src/locales/vi/translation.json +1 -0
  96. package/src/locales/zh-CN/translation.json +1 -0
  97. package/src/tests/i18nFrontend.unit.test.ts +2 -5
  98. package/src/tests/locale.test-d.ts +12 -9
  99. package/src/tests/translator.unit.test.ts +213 -0
  100. package/src/translator.ts +69 -0
  101. package/tsconfig.tsbuildinfo +1 -1
  102. package/dist/cjs/useTranslation.cjs +0 -12
  103. package/dist/tests/useTranslation.unit.test.d.ts +0 -2
  104. package/dist/tests/useTranslation.unit.test.d.ts.map +0 -1
  105. package/dist/tests/useTranslation.unit.test.js +0 -81
  106. package/dist/tests/useTranslation.unit.test.js.map +0 -1
  107. package/dist/useTranslation.d.ts +0 -7
  108. package/dist/useTranslation.d.ts.map +0 -1
  109. package/dist/useTranslation.js +0 -11
  110. package/dist/useTranslation.js.map +0 -1
  111. package/src/tests/useTranslation.unit.test.ts +0 -134
  112. package/src/useTranslation.ts +0 -32
@@ -47,6 +47,7 @@
47
47
  "SPAM_EMAIL_COUNT_EXCEEDED": "Túl sok beküldés erről az e-mail címről",
48
48
  "VPN_BLOCKED": "VPN-kapcsolatokról érkező kérések nem engedélyezettek ezen az oldalon",
49
49
  "PROXY_BLOCKED": "Proxy szerverekről érkező kérések nem engedélyezettek ezen az oldalon",
50
+ "TOO_MANY_LOCALHOST": "Túl sok CAPTCHA-kérés a localhostról ehhez a webhelykulcshoz",
50
51
  "TOR_BLOCKED": "Tor kilépési csomópontokról érkező kérések nem engedélyezettek ezen az oldalon",
51
52
  "ABUSER_BLOCKED": "Visszaélésért megjelölt hálózatokról érkező kérések nem engedélyezettek ezen az oldalon",
52
53
  "DATACENTER_BLOCKED": "Adatközponti IP-címekről érkező kérések nem engedélyezettek ezen az oldalon",
@@ -47,6 +47,7 @@
47
47
  "SPAM_EMAIL_COUNT_EXCEEDED": "Terlalu banyak pengiriman dari alamat email ini",
48
48
  "VPN_BLOCKED": "Permintaan dari koneksi VPN tidak diizinkan untuk situs ini",
49
49
  "PROXY_BLOCKED": "Permintaan dari server proxy tidak diizinkan untuk situs ini",
50
+ "TOO_MANY_LOCALHOST": "Terlalu banyak permintaan CAPTCHA dari localhost untuk kunci situs ini",
50
51
  "TOR_BLOCKED": "Permintaan dari node keluar Tor tidak diizinkan untuk situs ini",
51
52
  "ABUSER_BLOCKED": "Permintaan dari jaringan yang ditandai karena penyalahgunaan tidak diizinkan untuk situs ini",
52
53
  "DATACENTER_BLOCKED": "Permintaan dari alamat IP pusat data tidak diizinkan untuk situs ini",
@@ -47,6 +47,7 @@
47
47
  "SPAM_EMAIL_COUNT_EXCEEDED": "Troppi invii da questo indirizzo email",
48
48
  "VPN_BLOCKED": "Le richieste da connessioni VPN non sono consentite per questo sito",
49
49
  "PROXY_BLOCKED": "Le richieste da server proxy non sono consentite per questo sito",
50
+ "TOO_MANY_LOCALHOST": "Troppe richieste CAPTCHA da localhost per questa chiave del sito",
50
51
  "TOR_BLOCKED": "Le richieste da nodi di uscita Tor non sono consentite per questo sito",
51
52
  "ABUSER_BLOCKED": "Le richieste da reti segnalate per abuso non sono consentite per questo sito",
52
53
  "DATACENTER_BLOCKED": "Le richieste da indirizzi IP di data center non sono consentite per questo sito",
@@ -346,6 +346,7 @@
346
346
  "SPAM_EMAIL_COUNT_EXCEEDED": "このメールアドレスからの送信が多すぎます",
347
347
  "VPN_BLOCKED": "このサイトではVPN接続からのリクエストは許可されていません",
348
348
  "PROXY_BLOCKED": "このサイトではプロキシサーバーからのリクエストは許可されていません",
349
+ "TOO_MANY_LOCALHOST": "このサイトキーに対する localhost からの CAPTCHA リクエストが多すぎます",
349
350
  "TOR_BLOCKED": "このサイトではTor出口ノードからのリクエストは許可されていません",
350
351
  "ABUSER_BLOCKED": "このサイトでは不正行為としてフラグが付けられたネットワークからのリクエストは許可されていません",
351
352
  "DATACENTER_BLOCKED": "このサイトではデータセンターのIPアドレスからのリクエストは許可されていません",
@@ -47,6 +47,7 @@
47
47
  "SPAM_EMAIL_COUNT_EXCEEDED": "Kakehen kiriman saka alamat email iki",
48
48
  "VPN_BLOCKED": "Panjaluk saka sambungan VPN ora diidini kanggo situs iki",
49
49
  "PROXY_BLOCKED": "Panjaluk saka server proxy ora diidini kanggo situs iki",
50
+ "TOO_MANY_LOCALHOST": "Kakehan panjaluk CAPTCHA saka localhost kanggo kunci situs iki",
50
51
  "TOR_BLOCKED": "Panjaluk saka simpul metu Tor ora diidini kanggo situs iki",
51
52
  "ABUSER_BLOCKED": "Panjaluk saka jaringan sing ditandhani amarga penyalahgunaan ora diidini kanggo situs iki",
52
53
  "DATACENTER_BLOCKED": "Panjaluk saka alamat IP pusat data ora diidini kanggo situs iki",
@@ -47,6 +47,7 @@
47
47
  "SPAM_EMAIL_COUNT_EXCEEDED": "이 이메일 주소에서 너무 많은 제출이 있었습니다",
48
48
  "VPN_BLOCKED": "이 사이트에서는 VPN 연결의 요청이 허용되지 않습니다",
49
49
  "PROXY_BLOCKED": "이 사이트에서는 프록시 서버의 요청이 허용되지 않습니다",
50
+ "TOO_MANY_LOCALHOST": "이 사이트 키에 대한 localhost의 CAPTCHA 요청이 너무 많습니다",
50
51
  "TOR_BLOCKED": "이 사이트에서는 Tor 출구 노드의 요청이 허용되지 않습니다",
51
52
  "ABUSER_BLOCKED": "악용으로 표시된 네트워크의 요청은 이 사이트에서 허용되지 않습니다",
52
53
  "DATACENTER_BLOCKED": "이 사이트에서는 데이터센터 IP 주소의 요청이 허용되지 않습니다",
@@ -47,6 +47,7 @@
47
47
  "SPAM_EMAIL_COUNT_EXCEEDED": "ഈ ഇമെയിൽ വിലാസത്തിൽ നിന്ന് വളരെയധികം സമർപ്പണങ്ങൾ",
48
48
  "VPN_BLOCKED": "ഈ സൈറ്റിന് VPN കണക്ഷനുകളിൽ നിന്നുള്ള അഭ്യർത്ഥനകൾ അനുവദനീയമല്ല",
49
49
  "PROXY_BLOCKED": "ഈ സൈറ്റിന് പ്രോക്സി സെർവറുകളിൽ നിന്നുള്ള അഭ്യർത്ഥനകൾ അനുവദനീയമല്ല",
50
+ "TOO_MANY_LOCALHOST": "ഈ സൈറ്റ് കീയ്ക്കായി localhost-ൽ നിന്ന് വളരെയധികം CAPTCHA അഭ്യർത്ഥനകൾ",
50
51
  "TOR_BLOCKED": "ഈ സൈറ്റിന് Tor എക്സിറ്റ് നോഡുകളിൽ നിന്നുള്ള അഭ്യർത്ഥനകൾ അനുവദനീയമല്ല",
51
52
  "ABUSER_BLOCKED": "ദുരുപയോഗത്തിന് ഫ്ലാഗ് ചെയ്ത നെറ്റ്വർക്കുകളിൽ നിന്നുള്ള അഭ്യർത്ഥനകൾ ഈ സൈറ്റിന് അനുവദനീയമല്ല",
52
53
  "DATACENTER_BLOCKED": "ഈ സൈറ്റിന് ഡാറ്റാസെന്റർ IP വിലാസങ്ങളിൽ നിന്നുള്ള അഭ്യർത്ഥനകൾ അനുവദനീയമല്ല",
@@ -346,6 +346,7 @@
346
346
  "SPAM_EMAIL_COUNT_EXCEEDED": "Terlalu banyak penghantaran dari alamat e-mel ini",
347
347
  "VPN_BLOCKED": "Permintaan daripada sambungan VPN tidak dibenarkan untuk laman ini",
348
348
  "PROXY_BLOCKED": "Permintaan daripada pelayan proksi tidak dibenarkan untuk laman ini",
349
+ "TOO_MANY_LOCALHOST": "Terlalu banyak permintaan CAPTCHA dari localhost untuk kunci tapak ini",
349
350
  "TOR_BLOCKED": "Permintaan daripada nod keluar Tor tidak dibenarkan untuk laman ini",
350
351
  "ABUSER_BLOCKED": "Permintaan daripada rangkaian yang ditandakan kerana penyalahgunaan tidak dibenarkan untuk laman ini",
351
352
  "DATACENTER_BLOCKED": "Permintaan daripada alamat IP pusat data tidak dibenarkan untuk laman ini",
@@ -346,6 +346,7 @@
346
346
  "SPAM_EMAIL_COUNT_EXCEEDED": "Te veel inzendingen vanaf dit e-mailadres",
347
347
  "VPN_BLOCKED": "Verzoeken via VPN-verbindingen zijn niet toegestaan voor deze site",
348
348
  "PROXY_BLOCKED": "Verzoeken via proxyservers zijn niet toegestaan voor deze site",
349
+ "TOO_MANY_LOCALHOST": "Te veel CAPTCHA-verzoeken vanaf localhost voor deze sitesleutel",
349
350
  "TOR_BLOCKED": "Verzoeken via Tor-exitnodes zijn niet toegestaan voor deze site",
350
351
  "ABUSER_BLOCKED": "Verzoeken van netwerken die zijn gemarkeerd voor misbruik zijn niet toegestaan voor deze site",
351
352
  "DATACENTER_BLOCKED": "Verzoeken van datacenter-IP-adressen zijn niet toegestaan voor deze site",
@@ -47,6 +47,7 @@
47
47
  "SPAM_EMAIL_COUNT_EXCEEDED": "For mange innsendinger fra denne e-postadressen",
48
48
  "VPN_BLOCKED": "Forespørsler fra VPN-tilkoblinger er ikke tillatt for dette nettstedet",
49
49
  "PROXY_BLOCKED": "Forespørsler fra proxyservere er ikke tillatt for dette nettstedet",
50
+ "TOO_MANY_LOCALHOST": "For mange CAPTCHA-førespurnader frå localhost for denne nettstadsnøkkelen",
50
51
  "TOR_BLOCKED": "Forespørsler fra Tor-utgangsnoder er ikke tillatt for dette nettstedet",
51
52
  "ABUSER_BLOCKED": "Forespørsler fra nettverk flagget for misbruk er ikke tillatt for dette nettstedet",
52
53
  "DATACENTER_BLOCKED": "Forespørsler fra datasenter-IP-adresser er ikke tillatt for dette nettstedet",
@@ -47,6 +47,7 @@
47
47
  "SPAM_EMAIL_COUNT_EXCEEDED": "Zbyt wiele zgłoszeń z tego adresu e-mail",
48
48
  "VPN_BLOCKED": "Żądania z połączeń VPN nie są dozwolone dla tej strony",
49
49
  "PROXY_BLOCKED": "Żądania z serwerów proxy nie są dozwolone dla tej strony",
50
+ "TOO_MANY_LOCALHOST": "Zbyt wiele żądań CAPTCHA z localhost dla tego klucza witryny",
50
51
  "TOR_BLOCKED": "Żądania z węzłów wyjściowych Tor nie są dozwolone dla tej strony",
51
52
  "ABUSER_BLOCKED": "Żądania z sieci oznaczonych za nadużycia nie są dozwolone dla tej strony",
52
53
  "DATACENTER_BLOCKED": "Żądania z adresów IP centrów danych nie są dozwolone dla tej strony",
@@ -47,6 +47,7 @@
47
47
  "SPAM_EMAIL_COUNT_EXCEEDED": "Demasiados envios deste endereço de e-mail",
48
48
  "VPN_BLOCKED": "Pedidos a partir de ligações VPN não são permitidos para este site",
49
49
  "PROXY_BLOCKED": "Pedidos a partir de servidores proxy não são permitidos para este site",
50
+ "TOO_MANY_LOCALHOST": "Demasiados pedidos de CAPTCHA a partir de localhost para esta chave de site",
50
51
  "TOR_BLOCKED": "Pedidos a partir de nós de saída Tor não são permitidos para este site",
51
52
  "ABUSER_BLOCKED": "Pedidos de redes sinalizadas por abuso não são permitidos para este site",
52
53
  "DATACENTER_BLOCKED": "Pedidos de endereços IP de centros de dados não são permitidos para este site",
@@ -47,6 +47,7 @@
47
47
  "SPAM_EMAIL_COUNT_EXCEEDED": "Muitos envios deste endereço de e-mail",
48
48
  "VPN_BLOCKED": "Solicitações de conexões VPN não são permitidas para este site",
49
49
  "PROXY_BLOCKED": "Solicitações de servidores proxy não são permitidas para este site",
50
+ "TOO_MANY_LOCALHOST": "Muitas solicitações de CAPTCHA do localhost para esta chave de site",
50
51
  "TOR_BLOCKED": "Solicitações de nós de saída Tor não são permitidas para este site",
51
52
  "ABUSER_BLOCKED": "Solicitações de redes sinalizadas por abuso não são permitidas para este site",
52
53
  "DATACENTER_BLOCKED": "Solicitações de endereços IP de datacenters não são permitidas para este site",
@@ -47,6 +47,7 @@
47
47
  "SPAM_EMAIL_COUNT_EXCEEDED": "Prea multe trimiteri de la această adresă de e-mail",
48
48
  "VPN_BLOCKED": "Solicitările de la conexiuni VPN nu sunt permise pentru acest site",
49
49
  "PROXY_BLOCKED": "Solicitările de la servere proxy nu sunt permise pentru acest site",
50
+ "TOO_MANY_LOCALHOST": "Prea multe solicitări CAPTCHA de la localhost pentru această cheie de site",
50
51
  "TOR_BLOCKED": "Solicitările de la noduri de ieșire Tor nu sunt permise pentru acest site",
51
52
  "ABUSER_BLOCKED": "Solicitările de la rețele semnalate pentru abuz nu sunt permise pentru acest site",
52
53
  "DATACENTER_BLOCKED": "Solicitările de la adrese IP de centre de date nu sunt permise pentru acest site",
@@ -47,6 +47,7 @@
47
47
  "SPAM_EMAIL_COUNT_EXCEEDED": "Слишком много отправок с этого адреса электронной почты",
48
48
  "VPN_BLOCKED": "Запросы через VPN-подключения не разрешены для этого сайта",
49
49
  "PROXY_BLOCKED": "Запросы через прокси-серверы не разрешены для этого сайта",
50
+ "TOO_MANY_LOCALHOST": "Слишком много запросов CAPTCHA с localhost для этого ключа сайта",
50
51
  "TOR_BLOCKED": "Запросы через выходные узлы Tor не разрешены для этого сайта",
51
52
  "ABUSER_BLOCKED": "Запросы из сетей, отмеченных за злоупотребления, не разрешены для этого сайта",
52
53
  "DATACENTER_BLOCKED": "Запросы с IP-адресов центров обработки данных не разрешены для этого сайта",
@@ -47,6 +47,7 @@
47
47
  "SPAM_EMAIL_COUNT_EXCEEDED": "Превише слања са ове адресе е-поште",
48
48
  "VPN_BLOCKED": "Захтеви са VPN конекција нису дозвољени за овај сајт",
49
49
  "PROXY_BLOCKED": "З��хтеви са прокси сервера нису дозвољени за овај сајт",
50
+ "TOO_MANY_LOCALHOST": "Превише CAPTCHA захтева са localhost-а за овај кључ сајта",
50
51
  "TOR_BLOCKED": "Захтеви са Tor излазних чворова нису дозвољени за овај сајт",
51
52
  "ABUSER_BLOCKED": "Захтеви са мрежа означених за злоупотребу нису дозвољени за овај сајт",
52
53
  "DATACENTER_BLOCKED": "Захтеви са IP адреса центара података нису дозвољени за овај сајт",
@@ -47,6 +47,7 @@
47
47
  "SPAM_EMAIL_COUNT_EXCEEDED": "För många inskickningar från denna e-postadress",
48
48
  "VPN_BLOCKED": "Förfrågningar från VPN-anslutningar är inte tillåtna för denna webbplats",
49
49
  "PROXY_BLOCKED": "Förfrågningar från proxyservrar är inte tillåtna för denna webbplats",
50
+ "TOO_MANY_LOCALHOST": "För många CAPTCHA-förfrågningar från localhost för denna webbplatsnyckel",
50
51
  "TOR_BLOCKED": "Förfrågningar från Tor-utgångsnoder är inte tillåtna för denna webbplats",
51
52
  "ABUSER_BLOCKED": "Förfrågningar från nätverk markerade för missbruk är inte tillåtna för denna webbplats",
52
53
  "DATACENTER_BLOCKED": "Förfrågningar från datacenter-IP-adresser är inte tillåtna för denna webbplats",
@@ -47,6 +47,7 @@
47
47
  "SPAM_EMAIL_COUNT_EXCEEDED": "มีการส่งจากที่อยู่อีเมลนี้มากเกินไป",
48
48
  "VPN_BLOCKED": "คำขอจากการเชื่อมต่อ VPN ไม่ได้รับอนุญาตสำหรับเว็บไซต์นี้",
49
49
  "PROXY_BLOCKED": "คำขอจากเซิร์ฟเวอร์พร็อกซีไม่ได้รับอนุญาตสำหรับเว็บไซต์นี้",
50
+ "TOO_MANY_LOCALHOST": "มีคำขอ CAPTCHA จาก localhost มากเกินไปสำหรับคีย์ไซต์นี้",
50
51
  "TOR_BLOCKED": "คำขอจากโหนดทางออก Tor ไม่ได้รับอนุญาตสำหรับเว็บไซต์นี้",
51
52
  "ABUSER_BLOCKED": "คำขอจากเครือข่ายที่ถูกระบุว่ามีการใช้งานในทางที่ผิดไม่ได้รับอนุญาตสำหรับเว็บไซต์นี้",
52
53
  "DATACENTER_BLOCKED": "คำขอจากที่อยู่ IP ของศูนย์ข้อมูลไม่ได้รับอนุญาตสำหรับเว็บไซต์นี้",
@@ -47,6 +47,7 @@
47
47
  "SPAM_EMAIL_COUNT_EXCEEDED": "Bu e-posta adresinden çok fazla gönderi",
48
48
  "VPN_BLOCKED": "Bu site için VPN bağlantılarından gelen isteklere izin verilmiyor",
49
49
  "PROXY_BLOCKED": "Bu site için proxy sunucularından gelen isteklere izin verilmiyor",
50
+ "TOO_MANY_LOCALHOST": "Bu site anahtarı için localhost'tan çok fazla CAPTCHA isteği",
50
51
  "TOR_BLOCKED": "Bu site için Tor çıkış düğümlerinden gelen isteklere izin verilmiyor",
51
52
  "ABUSER_BLOCKED": "Bu site için kötüye kullanım olarak işaretlenen ağlardan gelen isteklere izin verilmiyor",
52
53
  "DATACENTER_BLOCKED": "Bu site için veri merkezi IP adreslerinden gelen isteklere izin verilmiyor",
@@ -47,6 +47,7 @@
47
47
  "SPAM_EMAIL_COUNT_EXCEEDED": "Забагато надсилань з цієї адреси електронної пошти",
48
48
  "VPN_BLOCKED": "Запити з VPN-з'єднань не дозволені для цього сайту",
49
49
  "PROXY_BLOCKED": "Запити з проксі-серверів не дозволені для цього сайту",
50
+ "TOO_MANY_LOCALHOST": "Забагато запитів CAPTCHA з localhost для цього ключа сайту",
50
51
  "TOR_BLOCKED": "Запити з вихідних вузлів Tor не дозволені для цього сайту",
51
52
  "ABUSER_BLOCKED": "Запити з мереж, позначених за зловживання, не дозволені для цього сайту",
52
53
  "DATACENTER_BLOCKED": "Запити з IP-адрес дата-центрів не дозволені для цього сайту",
@@ -47,6 +47,7 @@
47
47
  "SPAM_EMAIL_COUNT_EXCEEDED": "Quá nhiều lượt gửi từ địa chỉ email này",
48
48
  "VPN_BLOCKED": "Các yêu cầu từ kết nối VPN không được phép cho trang web này",
49
49
  "PROXY_BLOCKED": "Các yêu cầu từ máy chủ proxy không được phép cho trang web này",
50
+ "TOO_MANY_LOCALHOST": "Quá nhiều yêu cầu CAPTCHA từ localhost cho khóa trang web này",
50
51
  "TOR_BLOCKED": "Các yêu cầu từ nút thoát Tor không được phép cho trang web này",
51
52
  "ABUSER_BLOCKED": "Các yêu cầu từ mạng bị gắn cờ lạm dụng không được phép cho trang web này",
52
53
  "DATACENTER_BLOCKED": "Các yêu cầu từ địa chỉ IP trung tâm dữ liệu không được phép cho trang web này",
@@ -47,6 +47,7 @@
47
47
  "SPAM_EMAIL_COUNT_EXCEEDED": "该电子邮件地址提交次数过多",
48
48
  "VPN_BLOCKED": "此网站不允许来自VPN连接的请求",
49
49
  "PROXY_BLOCKED": "此网站不允许来自代理服务器的请求",
50
+ "TOO_MANY_LOCALHOST": "此站点密钥来自 localhost 的 CAPTCHA 请求过多",
50
51
  "TOR_BLOCKED": "此网站不允许来自Tor出口节点的请求",
51
52
  "ABUSER_BLOCKED": "此网站不允许来自被标记为滥用的网络的请求",
52
53
  "DATACENTER_BLOCKED": "此网站不允许来自数据中心IP地址的请求",
@@ -96,7 +96,6 @@ const load = async (
96
96
  return "InMemoryBackend";
97
97
  },
98
98
  }));
99
- vi.doMock("react-i18next", () => ({ initReactI18next: "initReactI18next" }));
100
99
 
101
100
  const imported = await import("../i18nFrontend.js");
102
101
  return { initializeI18n: imported.default, harness };
@@ -118,7 +117,6 @@ afterEach(() => {
118
117
  "i18next-http-backend",
119
118
  "i18next-browser-languagedetector",
120
119
  "i18next-resources-to-backend",
121
- "react-i18next",
122
120
  ]) {
123
121
  vi.doUnmock(module);
124
122
  }
@@ -131,12 +129,11 @@ describe("i18nFrontend, first initialisation", () => {
131
129
  expect(harness.initOptions).toHaveLength(1);
132
130
  });
133
131
 
134
- test("registers the chained backend, the detector and the react binding", async () => {
132
+ test("registers the chained backend and the language detector", async () => {
135
133
  const { initializeI18n, harness } = await load();
136
134
  initializeI18n();
137
- expect(harness.plugins).toHaveLength(3);
135
+ expect(harness.plugins).toHaveLength(2);
138
136
  expect(harness.plugins[0]).toBe("ChainedBackend");
139
- expect(harness.plugins[2]).toBe("initReactI18next");
140
137
  });
141
138
 
142
139
  test("carries the shared options through to init", async () => {
@@ -23,10 +23,10 @@ import {
23
23
  type Ti18n,
24
24
  type TranslationKey,
25
25
  TranslationKeysSchema,
26
+ createTranslator,
26
27
  i18nMiddleware,
27
28
  isClientSide,
28
29
  loadI18next,
29
- useTranslation,
30
30
  } from "../index.js";
31
31
 
32
32
  describe("Languages", () => {
@@ -140,17 +140,20 @@ describe("i18nMiddleware", () => {
140
140
  });
141
141
  });
142
142
 
143
- describe("useTranslation", () => {
144
- it("is callable with no options", () => {
145
- expectTypeOf(useTranslation).toBeCallableWith();
143
+ describe("createTranslator", () => {
144
+ it("is callable with no instance, so a standalone widget can boot one", () => {
145
+ expectTypeOf(createTranslator).toBeCallableWith();
146
146
  });
147
147
 
148
- it("returns a t function", () => {
149
- expectTypeOf(useTranslation).returns.toHaveProperty("t");
148
+ it("returns the translator surface the widgets render against", () => {
149
+ expectTypeOf(createTranslator).returns.toHaveProperty("t");
150
+ expectTypeOf(createTranslator).returns.toHaveProperty("isReady");
151
+ expectTypeOf(createTranslator).returns.toHaveProperty("subscribe");
152
+ expectTypeOf(createTranslator).returns.toHaveProperty("i18n");
150
153
  });
151
154
 
152
- it("rejects an unknown option", () => {
153
- // @ts-expect-error notAnOption is not a UseTranslationOptions field
154
- useTranslation({ notAnOption: 1 });
155
+ it("rejects anything that is not an i18next instance", () => {
156
+ // @ts-expect-error the only parameter is an existing i18next instance
157
+ createTranslator({ notAnInstance: 1 });
155
158
  });
156
159
  });
@@ -0,0 +1,213 @@
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 type { i18n } from "i18next";
16
+ import { afterEach, describe, expect, test, vi } from "vitest";
17
+ import type { Translator } from "../translator.js";
18
+
19
+ // The translator is a thin wrapper: it forces the package's own i18next
20
+ // instance in rather than letting a host app's provider decide, and exposes the
21
+ // three events that used to drive a React re-render. i18next itself is mocked
22
+ // so the tests observe the wiring rather than i18next's own behaviour.
23
+
24
+ interface FakeI18n {
25
+ readonly instance: i18n;
26
+ readonly handlers: Map<string, Set<() => void>>;
27
+ isInitialized: boolean;
28
+ loadedNamespaces: string[];
29
+ translations: Record<string, string>;
30
+ }
31
+
32
+ const fakeI18n = (): FakeI18n => {
33
+ const handlers = new Map<string, Set<() => void>>();
34
+ const fake: FakeI18n = {
35
+ handlers,
36
+ isInitialized: true,
37
+ loadedNamespaces: ["translation"],
38
+ translations: {},
39
+ instance: {
40
+ t: (key: string): string => fake.translations[key] ?? key,
41
+ get isInitialized(): boolean {
42
+ return fake.isInitialized;
43
+ },
44
+ hasLoadedNamespace: (namespace: string): boolean =>
45
+ fake.loadedNamespaces.includes(namespace),
46
+ on: (event: string, listener: () => void): void => {
47
+ const existing = handlers.get(event) ?? new Set<() => void>();
48
+ existing.add(listener);
49
+ handlers.set(event, existing);
50
+ },
51
+ off: (event: string, listener: () => void): void => {
52
+ handlers.get(event)?.delete(listener);
53
+ },
54
+ } as unknown as i18n,
55
+ };
56
+ return fake;
57
+ };
58
+
59
+ const emit = (fake: FakeI18n, event: string): void => {
60
+ for (const listener of fake.handlers.get(event) ?? []) {
61
+ listener();
62
+ }
63
+ };
64
+
65
+ const load = async (): Promise<{
66
+ createTranslator: (existing?: i18n) => Translator;
67
+ packageInstance: i18n;
68
+ initCount: () => number;
69
+ }> => {
70
+ vi.resetModules();
71
+
72
+ const packageInstance = fakeI18n().instance;
73
+ let initCalls = 0;
74
+
75
+ vi.doMock("../i18nFrontend.js", () => ({
76
+ default: (): i18n => {
77
+ initCalls += 1;
78
+ return packageInstance;
79
+ },
80
+ }));
81
+
82
+ const imported = await import("../translator.js");
83
+ return {
84
+ createTranslator: imported.createTranslator,
85
+ packageInstance,
86
+ initCount: (): number => initCalls,
87
+ };
88
+ };
89
+
90
+ afterEach(() => {
91
+ vi.resetModules();
92
+ vi.doUnmock("../i18nFrontend.js");
93
+ });
94
+
95
+ describe("which i18next instance the translator talks to", () => {
96
+ test("initialises the package's own instance when given none", async () => {
97
+ // Without this, a host application running its own i18next would capture
98
+ // the widget's translations and render its keys instead.
99
+ const { createTranslator, packageInstance, initCount } = await load();
100
+
101
+ const translator = createTranslator();
102
+
103
+ expect(translator.i18n).toBe(packageInstance);
104
+ expect(initCount()).toBe(1);
105
+ });
106
+
107
+ test("adopts an instance the caller already has", async () => {
108
+ // The bundle loads i18next once and hands the same instance to every
109
+ // widget on the page, so a second init would be wasted work.
110
+ const { createTranslator, initCount } = await load();
111
+ const existing = fakeI18n().instance;
112
+
113
+ const translator = createTranslator(existing);
114
+
115
+ expect(translator.i18n).toBe(existing);
116
+ expect(initCount()).toBe(0);
117
+ });
118
+ });
119
+
120
+ describe("translating", () => {
121
+ test("delegates to the instance", async () => {
122
+ const { createTranslator } = await load();
123
+ const fake = fakeI18n();
124
+ fake.translations = { "WIDGET.SUBMIT": "Submit" };
125
+
126
+ expect(createTranslator(fake.instance).t("WIDGET.SUBMIT")).toBe("Submit");
127
+ });
128
+
129
+ test("reads the instance at call time, not at creation time", async () => {
130
+ // A language switch mutates the instance in place; a translator that
131
+ // cached `t` would keep serving the previous language.
132
+ const { createTranslator } = await load();
133
+ const fake = fakeI18n();
134
+ const translator = createTranslator(fake.instance);
135
+
136
+ fake.translations = { "WIDGET.SUBMIT": "Envoyer" };
137
+
138
+ expect(translator.t("WIDGET.SUBMIT")).toBe("Envoyer");
139
+ });
140
+ });
141
+
142
+ describe("readiness", () => {
143
+ test("is ready once the instance has loaded the translation namespace", async () => {
144
+ const { createTranslator } = await load();
145
+ const fake = fakeI18n();
146
+
147
+ expect(createTranslator(fake.instance).isReady()).toBe(true);
148
+ });
149
+
150
+ test("is not ready before initialisation", async () => {
151
+ const { createTranslator } = await load();
152
+ const fake = fakeI18n();
153
+ fake.isInitialized = false;
154
+
155
+ expect(createTranslator(fake.instance).isReady()).toBe(false);
156
+ });
157
+
158
+ test("is not ready while the namespace is still loading", async () => {
159
+ // Widgets render an empty label until this flips, rather than flashing a
160
+ // raw translation key at the user.
161
+ const { createTranslator } = await load();
162
+ const fake = fakeI18n();
163
+ fake.loadedNamespaces = [];
164
+
165
+ expect(createTranslator(fake.instance).isReady()).toBe(false);
166
+ });
167
+ });
168
+
169
+ describe("subscribing", () => {
170
+ test.each(["initialized", "loaded", "languageChanged"])(
171
+ "notifies the listener on %s",
172
+ async (event: string) => {
173
+ const { createTranslator } = await load();
174
+ const fake = fakeI18n();
175
+ const listener = vi.fn<() => void>();
176
+
177
+ createTranslator(fake.instance).subscribe(listener);
178
+ emit(fake, event);
179
+
180
+ expect(listener).toHaveBeenCalledTimes(1);
181
+ },
182
+ );
183
+
184
+ test("stops notifying once unsubscribed", async () => {
185
+ // Widgets unsubscribe on destroy; a leaked listener would keep a torn-down
186
+ // widget alive for the lifetime of the page.
187
+ const { createTranslator } = await load();
188
+ const fake = fakeI18n();
189
+ const listener = vi.fn<() => void>();
190
+
191
+ const unsubscribe = createTranslator(fake.instance).subscribe(listener);
192
+ unsubscribe();
193
+ emit(fake, "languageChanged");
194
+
195
+ expect(listener).not.toHaveBeenCalled();
196
+ });
197
+
198
+ test("keeps independent subscriptions apart", async () => {
199
+ const { createTranslator } = await load();
200
+ const fake = fakeI18n();
201
+ const first = vi.fn<() => void>();
202
+ const second = vi.fn<() => void>();
203
+ const translator = createTranslator(fake.instance);
204
+
205
+ const unsubscribeFirst = translator.subscribe(first);
206
+ translator.subscribe(second);
207
+ unsubscribeFirst();
208
+ emit(fake, "loaded");
209
+
210
+ expect(first).not.toHaveBeenCalled();
211
+ expect(second).toHaveBeenCalledTimes(1);
212
+ });
213
+ });
@@ -0,0 +1,69 @@
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 type { TOptions } from "i18next";
16
+ import initializeI18n from "./i18nFrontend.js";
17
+ import type { Ti18n } from "./loadI18next.js";
18
+
19
+ const NAMESPACE = "translation";
20
+
21
+ export interface Translator {
22
+ /**
23
+ * Translate a key against the current language. `options` carries
24
+ * interpolation values and the `defaultValue` a widget falls back to when
25
+ * its key has not been added to the bundled catalogues yet.
26
+ */
27
+ t(key: string, options?: TOptions): string;
28
+ /**
29
+ * Whether the namespace for the active language has finished loading.
30
+ * Mirrors `ready` from the old react-i18next hook: widgets render an empty
31
+ * label until it flips, rather than flashing a raw translation key.
32
+ */
33
+ isReady(): boolean;
34
+ /**
35
+ * Notified when the translation output could have changed — initialisation,
36
+ * a namespace load, or a language switch. Returns an unsubscribe function.
37
+ */
38
+ subscribe(listener: () => void): () => void;
39
+ readonly i18n: Ti18n;
40
+ }
41
+
42
+ /**
43
+ * Vanilla replacement for the `useTranslation` hook. i18next is already
44
+ * framework-agnostic and event-driven; this just exposes its `t` plus the three
45
+ * events that used to trigger a React re-render.
46
+ */
47
+ export const createTranslator = (existing?: Ti18n): Translator => {
48
+ const i18n: Ti18n = existing ?? initializeI18n();
49
+
50
+ const isReady = (): boolean =>
51
+ i18n.isInitialized && i18n.hasLoadedNamespace(NAMESPACE);
52
+
53
+ return {
54
+ t: (key: string, options?: TOptions): string => i18n.t(key, options),
55
+ isReady,
56
+ i18n,
57
+ subscribe: (listener: () => void): (() => void) => {
58
+ const events = ["initialized", "loaded", "languageChanged"] as const;
59
+ for (const event of events) {
60
+ i18n.on(event, listener);
61
+ }
62
+ return () => {
63
+ for (const event of events) {
64
+ i18n.off(event, listener);
65
+ }
66
+ };
67
+ },
68
+ };
69
+ };