diff --git a/README.md b/README.md index 3a36697..e017a64 100644 --- a/README.md +++ b/README.md @@ -19,7 +19,7 @@ whatever failed (an HTTP status, a thrown error, a network drop) and it gives back one code such as `ai.provider.out_of_credits`, one sentence a person can read, and the same error as a standard JSON body your API can send. The code is the same in every language. The sentence comes in the user's -language through your own i18next, and Spanish and Portuguese translations of +language through your own i18next, and Spanish, Portuguese, and Hindi translations of every built-in code ship with the package. It does not log, retry, or report errors. Your app still decides what to do with them. @@ -108,7 +108,7 @@ translations have that key, the user sees it. If not, they see the English sentence from your list of codes, never a raw key. The code itself never changes, so your logs and API stay the same in every language. -The package ships Spanish (`es`) and Brazilian Portuguese (`pt`) for all 24 +The package ships Spanish (`es`), Brazilian Portuguese (`pt`), and Hindi (`hi`) for all 24 built-in codes, as plain i18next JSON files. Load them into the i18next you already use: diff --git a/locales/hi.json b/locales/hi.json new file mode 100644 index 0000000..dd3802e --- /dev/null +++ b/locales/hi.json @@ -0,0 +1,52 @@ +{ + "errors": { + "http": { + "unauthorized": "आपको यह करने की अनुमति नहीं है। अपनी लॉगिन जानकारी जाँचें और फिर से कोशिश करें।", + "not_found": "सर्वर को वह नहीं मिला जो आपने माँगा था।", + "rate_limited": "बहुत अधिक अनुरोध भेजे गए हैं। कुछ क्षण प्रतीक्षा करें और फिर से कोशिश करें।", + "server_error": "सर्वर में कोई त्रुटि हुई। कुछ देर बाद फिर से कोशिश करें।" + }, + "net": { + "unreachable": "सर्वर से संपर्क नहीं हो सका। अपना इंटरनेट कनेक्शन जाँचें और फिर से कोशिश करें।" + }, + "request": { + "timeout": "अनुरोध में बहुत अधिक समय लग गया। फिर से कोशिश करें।", + "cancelled": "अनुरोध रद्द कर दिया गया।" + }, + "config": { + "missing": "एक आवश्यक सेटिंग अनुपस्थित है: {{field}}।", + "invalid": "एक सेटिंग अमान्य है: {{field}}।" + }, + "internal": { + "unknown": "कुछ गड़बड़ हो गई। फिर से कोशिश करें।" + }, + "ai": { + "config": { + "no_key": "कोई AI प्रदाता कुंजी सेट नहीं है। वैकल्पिक AI सुविधाएँ चालू करने के लिए अपनी सेटिंग्स में कुंजी जोड़ें।" + }, + "provider": { + "unauthorized": "आपके AI प्रदाता ने कुंजी अस्वीकार कर दी। अपनी सेटिंग्स में कुंजी जाँचें।", + "out_of_credits": "आपके प्रदाता खाते में क्रेडिट समाप्त हो गए हैं। क्रेडिट जोड़ें और फिर से कोशिश करें।", + "rate_limited": "AI प्रदाता अनुरोधों को सीमित कर रहा है। कुछ क्षण प्रतीक्षा करें और फिर से कोशिश करें।", + "server_error": "AI प्रदाता के सर्वर में कोई त्रुटि हुई। कुछ देर बाद फिर से कोशिश करें।" + }, + "model": { + "unavailable": "चयनित मॉडल उपलब्ध नहीं है। कोई दूसरा मॉडल चुनें।" + }, + "request": { + "timeout": "AI अनुरोध में बहुत अधिक समय लग गया। फिर से कोशिश करें।", + "cancelled": "AI अनुरोध रद्द कर दिया गया।" + }, + "privacy": { + "violation": "आपके निजी डेटा की सुरक्षा के लिए यह अनुरोध इस डिवाइस से बाहर जाने से पहले रोक दिया गया।" + } + }, + "bundle": { + "download_failed": "डेटा बंडल डाउनलोड नहीं हो सका। अपना कनेक्शन जाँचें और फिर से कोशिश करें।", + "integrity_failed": "डाउनलोड की गई एक फ़ाइल सत्यता जाँच में विफल रही। उसे फिर से डाउनलोड करने के लिए पुनः प्रयास करें।", + "quota_exceeded": "इस डिवाइस पर डेटा लोड करने के लिए पर्याप्त खाली स्थान नहीं है। स्थान खाली करके फिर से कोशिश करें, या डेस्कटॉप ब्राउज़र का उपयोग करें।", + "device_unsupported": "यह डिवाइस या ब्राउज़र इस ऐप की आवश्यकताओं का समर्थन नहीं करता। किसी नए डेस्कटॉप ब्राउज़र का उपयोग करें।", + "timeout": "डेटा लोड होने में बहुत अधिक समय लग गया। फिर से कोशिश करें, या डेस्कटॉप ब्राउज़र पर प्रयास करें।" + } + } +} diff --git a/package.json b/package.json index 4fa413c..df6b9c9 100644 --- a/package.json +++ b/package.json @@ -7,10 +7,18 @@ "errors", "error-handling", "error-codes", + "error-classification", + "structured-errors", + "http-errors", "problem-details", "rfc9457", "i18n", "i18next", + "localization", + "spanish", + "portuguese", + "hindi", + "zero-dependencies", "typescript" ], "license": "MIT", diff --git a/test/locales.test.ts b/test/locales.test.ts index 822ee84..46ea2c6 100644 --- a/test/locales.test.ts +++ b/test/locales.test.ts @@ -20,7 +20,7 @@ import { } from "../src/index.js"; const ROOT = fileURLToPath(new URL("..", import.meta.url)); -const LOCALES = ["es", "pt"] as const; +const LOCALES = ["es", "pt", "hi"] as const; interface Resource { readonly [key: string]: string | Resource; @@ -117,13 +117,15 @@ describe("the shipped translations in a real i18next", () => { resources: { es: { translation: load("es") }, pt: { translation: load("pt") }, + hi: { translation: load("hi") }, }, }); }); - it("describes every registered code in Spanish and Portuguese", () => { + it("describes every registered code in Spanish, Portuguese, and Hindi", () => { const es = flatten(load("es")); const pt = flatten(load("pt")); + const hi = flatten(load("hi")); for (const code of errors.codes) { const params = { field: "API_KEY" }; const expectEs = es @@ -132,8 +134,12 @@ describe("the shipped translations in a real i18next", () => { const expectPt = pt .get(`errors.${code}`) ?.replace("{{field}}", "API_KEY"); + const expectHi = hi + .get(`errors.${code}`) + ?.replace("{{field}}", "API_KEY"); expect(errors.describe(code, params, tFor("es")), code).toBe(expectEs); expect(errors.describe(code, params, tFor("pt")), code).toBe(expectPt); + expect(errors.describe(code, params, tFor("hi")), code).toBe(expectHi); } }); diff --git a/test/readme.contract.test.ts b/test/readme.contract.test.ts index 8d4e6fb..6726081 100644 --- a/test/readme.contract.test.ts +++ b/test/readme.contract.test.ts @@ -213,6 +213,6 @@ describe("README says up front that messages come in the user's language", () => it("mentions languages and the shipped translations before 'Try it'", () => { const intro = README.slice(0, at("## Try it\n")); expect(intro).toMatch(/user's\s+language/); - expect(intro).toMatch(/Spanish\s+and\s+Portuguese/); + expect(intro).toMatch(/Spanish,\s+Portuguese,\s+and\s+Hindi/); }); });