@xivdyetools/bot-i18n 1.0.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.
@@ -0,0 +1,45 @@
1
+ import type { LocaleCode, LocaleData, TranslatorLogger } from './types.js';
2
+ /**
3
+ * Translator for a specific locale.
4
+ * Falls back to English for missing keys.
5
+ */
6
+ export declare class Translator {
7
+ private locale;
8
+ private data;
9
+ private fallbackData;
10
+ private logger?;
11
+ constructor(locale: LocaleCode, logger?: TranslatorLogger);
12
+ /**
13
+ * Get a translated string by dot-notation key.
14
+ * Falls back to English if the key is missing in the current locale.
15
+ * Returns the raw key if not found in any locale.
16
+ *
17
+ * @example
18
+ * t.t('errors.dyeNotFound', { name: 'Snow White' })
19
+ * // → 'Could not find a dye named "Snow White".'
20
+ */
21
+ t(key: string, variables?: Record<string, string | number>): string;
22
+ /** Get the current locale code. */
23
+ getLocale(): LocaleCode;
24
+ /** Get locale metadata (name, nativeName, flag). */
25
+ getMeta(): LocaleData['meta'];
26
+ }
27
+ /**
28
+ * Create a translator for a specific locale.
29
+ * Locale data is bundled — no I/O required.
30
+ */
31
+ export declare function createTranslator(locale: LocaleCode, logger?: TranslatorLogger): Translator;
32
+ /**
33
+ * Quick one-off translation helper.
34
+ */
35
+ export declare function translate(locale: LocaleCode, key: string, variables?: Record<string, string | number>, logger?: TranslatorLogger): string;
36
+ /**
37
+ * Get all available locales with metadata.
38
+ * Used by language selection commands to show available options.
39
+ */
40
+ export declare function getAvailableLocales(): Array<LocaleData['meta']>;
41
+ /**
42
+ * Type guard: check if a string is a supported locale code.
43
+ */
44
+ export declare function isLocaleSupported(locale: string): locale is LocaleCode;
45
+ //# sourceMappingURL=translator.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"translator.d.ts","sourceRoot":"","sources":["../src/translator.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,UAAU,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAC;AA8C3E;;;GAGG;AACH,qBAAa,UAAU;IACrB,OAAO,CAAC,MAAM,CAAa;IAC3B,OAAO,CAAC,IAAI,CAAa;IACzB,OAAO,CAAC,YAAY,CAAa;IACjC,OAAO,CAAC,MAAM,CAAC,CAAmB;gBAEtB,MAAM,EAAE,UAAU,EAAE,MAAM,CAAC,EAAE,gBAAgB;IAOzD;;;;;;;;OAQG;IACH,CAAC,CAAC,GAAG,EAAE,MAAM,EAAE,SAAS,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAAC,GAAG,MAAM;IAenE,mCAAmC;IACnC,SAAS,IAAI,UAAU;IAIvB,oDAAoD;IACpD,OAAO,IAAI,UAAU,CAAC,MAAM,CAAC;CAG9B;AAMD;;;GAGG;AACH,wBAAgB,gBAAgB,CAAC,MAAM,EAAE,UAAU,EAAE,MAAM,CAAC,EAAE,gBAAgB,GAAG,UAAU,CAE1F;AAED;;GAEG;AACH,wBAAgB,SAAS,CACvB,MAAM,EAAE,UAAU,EAClB,GAAG,EAAE,MAAM,EACX,SAAS,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAAC,EAC3C,MAAM,CAAC,EAAE,gBAAgB,GACxB,MAAM,CAER;AAED;;;GAGG;AACH,wBAAgB,mBAAmB,IAAI,KAAK,CAAC,UAAU,CAAC,MAAM,CAAC,CAAC,CAE/D;AAED;;GAEG;AACH,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,IAAI,UAAU,CAEtE"}
@@ -0,0 +1,112 @@
1
+ // Bundled locale data (static imports work in both CF Workers and Node.js)
2
+ import enLocale from './locales/en.json';
3
+ import jaLocale from './locales/ja.json';
4
+ import deLocale from './locales/de.json';
5
+ import frLocale from './locales/fr.json';
6
+ import koLocale from './locales/ko.json';
7
+ import zhLocale from './locales/zh.json';
8
+ const locales = {
9
+ en: enLocale,
10
+ ja: jaLocale,
11
+ de: deLocale,
12
+ fr: frLocale,
13
+ ko: koLocale,
14
+ zh: zhLocale,
15
+ };
16
+ // ============================================================================
17
+ // Internal helpers
18
+ // ============================================================================
19
+ function getNestedValue(obj, path) {
20
+ const keys = path.split('.');
21
+ let current = obj;
22
+ for (const key of keys) {
23
+ if (current === null || current === undefined)
24
+ return undefined;
25
+ if (typeof current !== 'object')
26
+ return undefined;
27
+ current = current[key];
28
+ }
29
+ return current;
30
+ }
31
+ function interpolate(template, variables) {
32
+ return template.replace(/\{(\w+)\}/g, (match, key) => {
33
+ return variables[key]?.toString() ?? match;
34
+ });
35
+ }
36
+ // ============================================================================
37
+ // Translator class
38
+ // ============================================================================
39
+ /**
40
+ * Translator for a specific locale.
41
+ * Falls back to English for missing keys.
42
+ */
43
+ export class Translator {
44
+ locale;
45
+ data;
46
+ fallbackData;
47
+ logger;
48
+ constructor(locale, logger) {
49
+ this.locale = locale;
50
+ this.data = locales[locale] || locales.en;
51
+ this.fallbackData = locales.en;
52
+ this.logger = logger;
53
+ }
54
+ /**
55
+ * Get a translated string by dot-notation key.
56
+ * Falls back to English if the key is missing in the current locale.
57
+ * Returns the raw key if not found in any locale.
58
+ *
59
+ * @example
60
+ * t.t('errors.dyeNotFound', { name: 'Snow White' })
61
+ * // → 'Could not find a dye named "Snow White".'
62
+ */
63
+ t(key, variables) {
64
+ let value = getNestedValue(this.data, key);
65
+ if (value === undefined && this.locale !== 'en') {
66
+ value = getNestedValue(this.fallbackData, key);
67
+ }
68
+ if (value === undefined || typeof value !== 'string') {
69
+ this.logger?.warn(`Missing translation: ${key} for locale ${this.locale}`);
70
+ return key;
71
+ }
72
+ return variables ? interpolate(value, variables) : value;
73
+ }
74
+ /** Get the current locale code. */
75
+ getLocale() {
76
+ return this.locale;
77
+ }
78
+ /** Get locale metadata (name, nativeName, flag). */
79
+ getMeta() {
80
+ return this.data.meta;
81
+ }
82
+ }
83
+ // ============================================================================
84
+ // Factory functions
85
+ // ============================================================================
86
+ /**
87
+ * Create a translator for a specific locale.
88
+ * Locale data is bundled — no I/O required.
89
+ */
90
+ export function createTranslator(locale, logger) {
91
+ return new Translator(locale, logger);
92
+ }
93
+ /**
94
+ * Quick one-off translation helper.
95
+ */
96
+ export function translate(locale, key, variables, logger) {
97
+ return new Translator(locale, logger).t(key, variables);
98
+ }
99
+ /**
100
+ * Get all available locales with metadata.
101
+ * Used by language selection commands to show available options.
102
+ */
103
+ export function getAvailableLocales() {
104
+ return Object.values(locales).map((data) => data.meta);
105
+ }
106
+ /**
107
+ * Type guard: check if a string is a supported locale code.
108
+ */
109
+ export function isLocaleSupported(locale) {
110
+ return locale in locales;
111
+ }
112
+ //# sourceMappingURL=translator.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"translator.js","sourceRoot":"","sources":["../src/translator.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,25 @@
1
+ /**
2
+ * Supported locale codes.
3
+ * Matches XIVAPI languages plus Korean and Chinese (from manual data).
4
+ */
5
+ export type LocaleCode = 'en' | 'ja' | 'de' | 'fr' | 'ko' | 'zh';
6
+ /**
7
+ * Structure of a locale JSON file.
8
+ */
9
+ export interface LocaleData {
10
+ meta: {
11
+ locale: string;
12
+ name: string;
13
+ nativeName: string;
14
+ flag: string;
15
+ };
16
+ [key: string]: unknown;
17
+ }
18
+ /**
19
+ * Minimal logger interface accepted by Translator.
20
+ * Compatible with @xivdyetools/logger's ExtendedLogger.
21
+ */
22
+ export interface TranslatorLogger {
23
+ warn: (msg: string) => void;
24
+ }
25
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,MAAM,MAAM,UAAU,GAAG,IAAI,GAAG,IAAI,GAAG,IAAI,GAAG,IAAI,GAAG,IAAI,GAAG,IAAI,CAAC;AAEjE;;GAEG;AACH,MAAM,WAAW,UAAU;IACzB,IAAI,EAAE;QACJ,MAAM,EAAE,MAAM,CAAC;QACf,IAAI,EAAE,MAAM,CAAC;QACb,UAAU,EAAE,MAAM,CAAC;QACnB,IAAI,EAAE,MAAM,CAAC;KACd,CAAC;IACF,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;CACxB;AAED;;;GAGG;AACH,MAAM,WAAW,gBAAgB;IAC/B,IAAI,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,IAAI,CAAC;CAC7B"}
package/dist/types.js ADDED
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":""}
package/package.json ADDED
@@ -0,0 +1,49 @@
1
+ {
2
+ "name": "@xivdyetools/bot-i18n",
3
+ "version": "1.0.0",
4
+ "description": "Platform-agnostic translation engine for bot UI strings — error messages, help text, command descriptions, and status messages",
5
+ "author": "XIV Dye Tools",
6
+ "license": "MIT",
7
+ "type": "module",
8
+ "main": "dist/index.js",
9
+ "types": "dist/index.d.ts",
10
+ "exports": {
11
+ ".": {
12
+ "types": "./dist/index.d.ts",
13
+ "import": "./dist/index.js"
14
+ }
15
+ },
16
+ "files": [
17
+ "dist",
18
+ "README.md"
19
+ ],
20
+ "sideEffects": false,
21
+ "devDependencies": {
22
+ "@vitest/coverage-v8": "^4.0.18",
23
+ "vitest": "^4.0.18"
24
+ },
25
+ "engines": {
26
+ "node": ">=18.0.0"
27
+ },
28
+ "keywords": [
29
+ "xivdyetools",
30
+ "i18n",
31
+ "localization",
32
+ "translation",
33
+ "typescript"
34
+ ],
35
+ "repository": {
36
+ "type": "git",
37
+ "url": "https://github.com/FlashGalatine/xivdyetools.git",
38
+ "directory": "packages/bot-i18n"
39
+ },
40
+ "scripts": {
41
+ "build": "tsc -p tsconfig.build.json",
42
+ "type-check": "tsc --noEmit",
43
+ "clean": "rimraf dist coverage",
44
+ "lint": "eslint src",
45
+ "test": "vitest run",
46
+ "test:watch": "vitest",
47
+ "test:coverage": "vitest run --coverage"
48
+ }
49
+ }