Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.

Expand Down Expand Up @@ -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:

Expand Down
52 changes: 52 additions & 0 deletions locales/hi.json
Original file line number Diff line number Diff line change
@@ -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": "डेटा लोड होने में बहुत अधिक समय लग गया। फिर से कोशिश करें, या डेस्कटॉप ब्राउज़र पर प्रयास करें।"
}
}
}
8 changes: 8 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
10 changes: 8 additions & 2 deletions test/locales.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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
Expand All @@ -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);
}
});

Expand Down
2 changes: 1 addition & 1 deletion test/readme.contract.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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/);
});
});
Loading