@cellarnode/i18n 0.1.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.
- package/LICENSE +21 -0
- package/README.md +116 -0
- package/dist/__tests__/constants.test.d.ts +1 -0
- package/dist/__tests__/constants.test.js +33 -0
- package/dist/__tests__/create-instance.test.d.ts +1 -0
- package/dist/__tests__/create-instance.test.js +30 -0
- package/dist/__tests__/display-names.test.d.ts +1 -0
- package/dist/__tests__/display-names.test.js +13 -0
- package/dist/__tests__/resolve-language.test.d.ts +1 -0
- package/dist/__tests__/resolve-language.test.js +40 -0
- package/dist/constants.d.ts +4 -0
- package/dist/constants.js +13 -0
- package/dist/create-instance.d.ts +12 -0
- package/dist/create-instance.js +44 -0
- package/dist/display-names.d.ts +6 -0
- package/dist/display-names.js +8 -0
- package/dist/index.d.ts +5 -0
- package/dist/index.js +4 -0
- package/dist/resolve-language.d.ts +7 -0
- package/dist/resolve-language.js +23 -0
- package/dist/scripts/copy-common.d.ts +1 -0
- package/dist/scripts/copy-common.js +20 -0
- package/dist/types.d.ts +14 -0
- package/dist/types.js +1 -0
- package/locales/de/common.json +56 -0
- package/locales/en/common.json +56 -0
- package/locales/es/common.json +56 -0
- package/locales/fr/common.json +56 -0
- package/locales/it/common.json +56 -0
- package/locales/sv/common.json +56 -0
- package/locales/zh/common.json +56 -0
- package/package.json +55 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 CellarNode
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
# @cellarnode/i18n
|
|
2
|
+
|
|
3
|
+
Shared i18n configuration for CellarNode frontends — language constants, i18next factory, browser locale detection, and common translations.
|
|
4
|
+
|
|
5
|
+
## Install
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
pnpm add @cellarnode/i18n react-i18next i18next i18next-http-backend i18next-browser-languagedetector
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Usage
|
|
12
|
+
|
|
13
|
+
### Initialize i18next
|
|
14
|
+
|
|
15
|
+
```ts
|
|
16
|
+
// src/lib/i18n.ts
|
|
17
|
+
import { createI18nInstance } from "@cellarnode/i18n";
|
|
18
|
+
|
|
19
|
+
const i18n = createI18nInstance({
|
|
20
|
+
defaultNS: "common",
|
|
21
|
+
ns: ["common", "navigation"],
|
|
22
|
+
loadPath: "/locales/{{lng}}/{{ns}}.json",
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
export default i18n;
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
### Wrap your app
|
|
29
|
+
|
|
30
|
+
```tsx
|
|
31
|
+
import { I18nextProvider } from "react-i18next";
|
|
32
|
+
import i18n from "@/lib/i18n";
|
|
33
|
+
|
|
34
|
+
function App() {
|
|
35
|
+
return (
|
|
36
|
+
<I18nextProvider i18n={i18n}>
|
|
37
|
+
<YourApp />
|
|
38
|
+
</I18nextProvider>
|
|
39
|
+
);
|
|
40
|
+
}
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
### Translate strings
|
|
44
|
+
|
|
45
|
+
```tsx
|
|
46
|
+
import { useTranslation } from "react-i18next";
|
|
47
|
+
|
|
48
|
+
function MyComponent() {
|
|
49
|
+
const { t } = useTranslation("common");
|
|
50
|
+
return <button>{t("save")}</button>;
|
|
51
|
+
}
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
### Detect browser language
|
|
55
|
+
|
|
56
|
+
```ts
|
|
57
|
+
import { resolveLanguageFromBrowser } from "@cellarnode/i18n";
|
|
58
|
+
|
|
59
|
+
// Maps regional variants to supported languages
|
|
60
|
+
resolveLanguageFromBrowser("fr-CA"); // "fr"
|
|
61
|
+
resolveLanguageFromBrowser("zh-Hans-CN"); // "zh"
|
|
62
|
+
resolveLanguageFromBrowser("ja"); // "en" (fallback)
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
### Copy common translations (build script)
|
|
66
|
+
|
|
67
|
+
Add to your `package.json` scripts to copy shared `common.json` files into your project:
|
|
68
|
+
|
|
69
|
+
```json
|
|
70
|
+
{
|
|
71
|
+
"scripts": {
|
|
72
|
+
"predev": "node --input-type=module -e \"import '@cellarnode/i18n/scripts/copy-common'\"",
|
|
73
|
+
"prebuild": "node --input-type=module -e \"import '@cellarnode/i18n/scripts/copy-common'\""
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
This copies `locales/{lang}/common.json` from the installed package into `public/locales/{lang}/common.json`.
|
|
79
|
+
|
|
80
|
+
## Exports
|
|
81
|
+
|
|
82
|
+
### `@cellarnode/i18n`
|
|
83
|
+
|
|
84
|
+
| Export | Description |
|
|
85
|
+
|--------|-------------|
|
|
86
|
+
| `createI18nInstance(options)` | i18next factory with HttpBackend + LanguageDetector |
|
|
87
|
+
| `resolveLanguageFromBrowser(locale)` | Maps browser locale to supported language |
|
|
88
|
+
| `getLanguageDisplayName(lang)` | Returns native display name (e.g., "Svenska") |
|
|
89
|
+
| `SUPPORTED_LANGUAGES` | `['en', 'zh', 'fr', 'de', 'it', 'es', 'sv']` |
|
|
90
|
+
| `DEFAULT_LANGUAGE` | `'en'` |
|
|
91
|
+
| `LANGUAGE_DISPLAY_NAMES` | Map of codes to native names |
|
|
92
|
+
| `SupportedLanguage` | TypeScript union type |
|
|
93
|
+
|
|
94
|
+
### `@cellarnode/i18n/locales/*`
|
|
95
|
+
|
|
96
|
+
JSON translation files for all 7 languages. Currently includes `common.json`.
|
|
97
|
+
|
|
98
|
+
### `@cellarnode/i18n/scripts/copy-common`
|
|
99
|
+
|
|
100
|
+
CLI script that copies common locale files from the package into `public/locales/`.
|
|
101
|
+
|
|
102
|
+
## Supported Languages
|
|
103
|
+
|
|
104
|
+
| Code | Language | Native |
|
|
105
|
+
|------|----------|--------|
|
|
106
|
+
| `en` | English | English |
|
|
107
|
+
| `zh` | Chinese (Simplified) | 中文 |
|
|
108
|
+
| `fr` | French | Francais |
|
|
109
|
+
| `de` | German | Deutsch |
|
|
110
|
+
| `it` | Italian | Italiano |
|
|
111
|
+
| `es` | Spanish | Espanol |
|
|
112
|
+
| `sv` | Swedish | Svenska |
|
|
113
|
+
|
|
114
|
+
## License
|
|
115
|
+
|
|
116
|
+
MIT
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { describe, it, expect } from 'vitest';
|
|
2
|
+
import { SUPPORTED_LANGUAGES, DEFAULT_LANGUAGE, LANGUAGE_DISPLAY_NAMES, } from '../constants';
|
|
3
|
+
describe('SUPPORTED_LANGUAGES', () => {
|
|
4
|
+
it('contains exactly 7 languages', () => {
|
|
5
|
+
expect(SUPPORTED_LANGUAGES).toHaveLength(7);
|
|
6
|
+
});
|
|
7
|
+
it('contains all required language codes', () => {
|
|
8
|
+
expect(SUPPORTED_LANGUAGES).toEqual(expect.arrayContaining(['en', 'zh', 'fr', 'de', 'it', 'es', 'sv']));
|
|
9
|
+
});
|
|
10
|
+
it('has en as the first element', () => {
|
|
11
|
+
expect(SUPPORTED_LANGUAGES[0]).toBe('en');
|
|
12
|
+
});
|
|
13
|
+
});
|
|
14
|
+
describe('DEFAULT_LANGUAGE', () => {
|
|
15
|
+
it('is en', () => {
|
|
16
|
+
expect(DEFAULT_LANGUAGE).toBe('en');
|
|
17
|
+
});
|
|
18
|
+
it('is included in SUPPORTED_LANGUAGES', () => {
|
|
19
|
+
expect(SUPPORTED_LANGUAGES).toContain(DEFAULT_LANGUAGE);
|
|
20
|
+
});
|
|
21
|
+
});
|
|
22
|
+
describe('LANGUAGE_DISPLAY_NAMES', () => {
|
|
23
|
+
it('has an entry for every supported language', () => {
|
|
24
|
+
for (const lang of SUPPORTED_LANGUAGES) {
|
|
25
|
+
expect(LANGUAGE_DISPLAY_NAMES[lang]).toBeDefined();
|
|
26
|
+
}
|
|
27
|
+
});
|
|
28
|
+
it('returns native display names', () => {
|
|
29
|
+
expect(LANGUAGE_DISPLAY_NAMES['sv']).toBe('Svenska');
|
|
30
|
+
expect(LANGUAGE_DISPLAY_NAMES['de']).toBe('Deutsch');
|
|
31
|
+
expect(LANGUAGE_DISPLAY_NAMES['zh']).toBe('中文');
|
|
32
|
+
});
|
|
33
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { describe, it, expect } from 'vitest';
|
|
2
|
+
import { createI18nInstance } from '../create-instance';
|
|
3
|
+
import { SUPPORTED_LANGUAGES } from '../constants';
|
|
4
|
+
describe('createI18nInstance', () => {
|
|
5
|
+
it('returns an i18next instance', () => {
|
|
6
|
+
const i18n = createI18nInstance({ ns: ['common'] });
|
|
7
|
+
expect(i18n).toBeDefined();
|
|
8
|
+
expect(typeof i18n.t).toBe('function');
|
|
9
|
+
});
|
|
10
|
+
it('sets fallback language to en', () => {
|
|
11
|
+
const i18n = createI18nInstance({ ns: ['common'] });
|
|
12
|
+
expect(i18n.options.fallbackLng).toEqual(['en']);
|
|
13
|
+
});
|
|
14
|
+
it('sets supported languages', () => {
|
|
15
|
+
const i18n = createI18nInstance({ ns: ['common'] });
|
|
16
|
+
expect(i18n.options.supportedLngs).toEqual([...SUPPORTED_LANGUAGES, 'cimode']);
|
|
17
|
+
});
|
|
18
|
+
it('respects explicit lng option', () => {
|
|
19
|
+
const i18n = createI18nInstance({ ns: ['common'], lng: 'sv' });
|
|
20
|
+
expect(i18n.options.lng).toBe('sv');
|
|
21
|
+
});
|
|
22
|
+
it('sets default namespace', () => {
|
|
23
|
+
const i18n = createI18nInstance({ defaultNS: 'dashboard', ns: ['common', 'dashboard'] });
|
|
24
|
+
expect(i18n.options.defaultNS).toBe('dashboard');
|
|
25
|
+
});
|
|
26
|
+
it('configures fallback namespace as common', () => {
|
|
27
|
+
const i18n = createI18nInstance({ ns: ['common'] });
|
|
28
|
+
expect(i18n.options.fallbackNS).toEqual(['common']);
|
|
29
|
+
});
|
|
30
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { describe, it, expect } from 'vitest';
|
|
2
|
+
import { getLanguageDisplayName } from '../display-names';
|
|
3
|
+
describe('getLanguageDisplayName', () => {
|
|
4
|
+
it('returns native name', () => {
|
|
5
|
+
expect(getLanguageDisplayName('sv')).toBe('Svenska');
|
|
6
|
+
expect(getLanguageDisplayName('de')).toBe('Deutsch');
|
|
7
|
+
expect(getLanguageDisplayName('zh')).toBe('中文');
|
|
8
|
+
expect(getLanguageDisplayName('en')).toBe('English');
|
|
9
|
+
});
|
|
10
|
+
it('returns English name for unknown codes', () => {
|
|
11
|
+
expect(getLanguageDisplayName('xx')).toBe('English');
|
|
12
|
+
});
|
|
13
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { describe, it, expect } from 'vitest';
|
|
2
|
+
import { resolveLanguageFromBrowser } from '../resolve-language';
|
|
3
|
+
describe('resolveLanguageFromBrowser', () => {
|
|
4
|
+
it('returns exact match for supported language', () => {
|
|
5
|
+
expect(resolveLanguageFromBrowser('en')).toBe('en');
|
|
6
|
+
expect(resolveLanguageFromBrowser('sv')).toBe('sv');
|
|
7
|
+
expect(resolveLanguageFromBrowser('fr')).toBe('fr');
|
|
8
|
+
});
|
|
9
|
+
it('maps regional variants to base language', () => {
|
|
10
|
+
expect(resolveLanguageFromBrowser('fr-CA')).toBe('fr');
|
|
11
|
+
expect(resolveLanguageFromBrowser('de-AT')).toBe('de');
|
|
12
|
+
expect(resolveLanguageFromBrowser('de-CH')).toBe('de');
|
|
13
|
+
expect(resolveLanguageFromBrowser('es-MX')).toBe('es');
|
|
14
|
+
expect(resolveLanguageFromBrowser('es-AR')).toBe('es');
|
|
15
|
+
});
|
|
16
|
+
it('maps all Chinese variants to zh', () => {
|
|
17
|
+
expect(resolveLanguageFromBrowser('zh')).toBe('zh');
|
|
18
|
+
expect(resolveLanguageFromBrowser('zh-CN')).toBe('zh');
|
|
19
|
+
expect(resolveLanguageFromBrowser('zh-TW')).toBe('zh');
|
|
20
|
+
expect(resolveLanguageFromBrowser('zh-Hans')).toBe('zh');
|
|
21
|
+
expect(resolveLanguageFromBrowser('zh-Hant')).toBe('zh');
|
|
22
|
+
expect(resolveLanguageFromBrowser('zh-Hans-CN')).toBe('zh');
|
|
23
|
+
});
|
|
24
|
+
it('returns en for unsupported languages', () => {
|
|
25
|
+
expect(resolveLanguageFromBrowser('ja')).toBe('en');
|
|
26
|
+
expect(resolveLanguageFromBrowser('ko')).toBe('en');
|
|
27
|
+
expect(resolveLanguageFromBrowser('ar')).toBe('en');
|
|
28
|
+
expect(resolveLanguageFromBrowser('pt-BR')).toBe('en');
|
|
29
|
+
});
|
|
30
|
+
it('handles empty, null, and undefined input', () => {
|
|
31
|
+
expect(resolveLanguageFromBrowser('')).toBe('en');
|
|
32
|
+
expect(resolveLanguageFromBrowser(null)).toBe('en');
|
|
33
|
+
expect(resolveLanguageFromBrowser(undefined)).toBe('en');
|
|
34
|
+
});
|
|
35
|
+
it('is case-insensitive', () => {
|
|
36
|
+
expect(resolveLanguageFromBrowser('EN')).toBe('en');
|
|
37
|
+
expect(resolveLanguageFromBrowser('Fr-ca')).toBe('fr');
|
|
38
|
+
expect(resolveLanguageFromBrowser('ZH-HANS')).toBe('zh');
|
|
39
|
+
});
|
|
40
|
+
});
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
export declare const SUPPORTED_LANGUAGES: readonly ["en", "zh", "fr", "de", "it", "es", "sv"];
|
|
2
|
+
export declare const DEFAULT_LANGUAGE: "en";
|
|
3
|
+
export type SupportedLanguage = (typeof SUPPORTED_LANGUAGES)[number];
|
|
4
|
+
export declare const LANGUAGE_DISPLAY_NAMES: Record<SupportedLanguage, string>;
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
export const SUPPORTED_LANGUAGES = [
|
|
2
|
+
'en', 'zh', 'fr', 'de', 'it', 'es', 'sv',
|
|
3
|
+
];
|
|
4
|
+
export const DEFAULT_LANGUAGE = 'en';
|
|
5
|
+
export const LANGUAGE_DISPLAY_NAMES = {
|
|
6
|
+
en: 'English',
|
|
7
|
+
zh: '中文',
|
|
8
|
+
fr: 'Français',
|
|
9
|
+
de: 'Deutsch',
|
|
10
|
+
it: 'Italiano',
|
|
11
|
+
es: 'Español',
|
|
12
|
+
sv: 'Svenska',
|
|
13
|
+
};
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { type i18n } from 'i18next';
|
|
2
|
+
import type { I18nInstanceOptions } from './types';
|
|
3
|
+
/**
|
|
4
|
+
* Creates and initializes an i18next instance.
|
|
5
|
+
* Each frontend project calls this once in src/lib/i18n.ts.
|
|
6
|
+
*
|
|
7
|
+
* NOTE: init() is called on the instance but translation loading is async.
|
|
8
|
+
* Options (fallbackLng, supportedLngs, etc.) are set immediately.
|
|
9
|
+
* Translations load in the background via i18next-http-backend.
|
|
10
|
+
* react-i18next handles the loading state via its `ready` prop / Suspense.
|
|
11
|
+
*/
|
|
12
|
+
export declare function createI18nInstance(options: I18nInstanceOptions): i18n;
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import i18next from 'i18next';
|
|
2
|
+
import HttpBackend from 'i18next-http-backend';
|
|
3
|
+
import LanguageDetector from 'i18next-browser-languagedetector';
|
|
4
|
+
import { SUPPORTED_LANGUAGES, DEFAULT_LANGUAGE } from './constants';
|
|
5
|
+
/**
|
|
6
|
+
* Creates and initializes an i18next instance.
|
|
7
|
+
* Each frontend project calls this once in src/lib/i18n.ts.
|
|
8
|
+
*
|
|
9
|
+
* NOTE: init() is called on the instance but translation loading is async.
|
|
10
|
+
* Options (fallbackLng, supportedLngs, etc.) are set immediately.
|
|
11
|
+
* Translations load in the background via i18next-http-backend.
|
|
12
|
+
* react-i18next handles the loading state via its `ready` prop / Suspense.
|
|
13
|
+
*/
|
|
14
|
+
export function createI18nInstance(options) {
|
|
15
|
+
const { defaultNS = 'common', ns = ['common'], loadPath = '/locales/{{lng}}/{{ns}}.json', lng, debug = false, } = options;
|
|
16
|
+
const instance = i18next.createInstance();
|
|
17
|
+
instance
|
|
18
|
+
.use(HttpBackend)
|
|
19
|
+
.use(LanguageDetector)
|
|
20
|
+
.init({
|
|
21
|
+
lng,
|
|
22
|
+
fallbackLng: [DEFAULT_LANGUAGE],
|
|
23
|
+
supportedLngs: [...SUPPORTED_LANGUAGES],
|
|
24
|
+
defaultNS,
|
|
25
|
+
ns,
|
|
26
|
+
fallbackNS: 'common',
|
|
27
|
+
debug,
|
|
28
|
+
interpolation: {
|
|
29
|
+
escapeValue: false, // React already escapes
|
|
30
|
+
},
|
|
31
|
+
detection: {
|
|
32
|
+
order: ['localStorage', 'navigator'],
|
|
33
|
+
lookupLocalStorage: 'cellarnode-language',
|
|
34
|
+
caches: ['localStorage'],
|
|
35
|
+
},
|
|
36
|
+
backend: {
|
|
37
|
+
loadPath,
|
|
38
|
+
},
|
|
39
|
+
react: {
|
|
40
|
+
useSuspense: false,
|
|
41
|
+
},
|
|
42
|
+
});
|
|
43
|
+
return instance;
|
|
44
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { LANGUAGE_DISPLAY_NAMES, DEFAULT_LANGUAGE } from './constants';
|
|
2
|
+
/**
|
|
3
|
+
* Returns the native display name for a language code.
|
|
4
|
+
* e.g., "Svenska" for sv, "Deutsch" for de.
|
|
5
|
+
*/
|
|
6
|
+
export function getLanguageDisplayName(lang) {
|
|
7
|
+
return LANGUAGE_DISPLAY_NAMES[lang] ?? LANGUAGE_DISPLAY_NAMES[DEFAULT_LANGUAGE];
|
|
8
|
+
}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
export { SUPPORTED_LANGUAGES, DEFAULT_LANGUAGE, LANGUAGE_DISPLAY_NAMES, type SupportedLanguage, } from './constants';
|
|
2
|
+
export type { TranslationNamespace, I18nInstanceOptions } from './types';
|
|
3
|
+
export { resolveLanguageFromBrowser } from './resolve-language';
|
|
4
|
+
export { createI18nInstance } from './create-instance';
|
|
5
|
+
export { getLanguageDisplayName } from './display-names';
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
export { SUPPORTED_LANGUAGES, DEFAULT_LANGUAGE, LANGUAGE_DISPLAY_NAMES, } from './constants';
|
|
2
|
+
export { resolveLanguageFromBrowser } from './resolve-language';
|
|
3
|
+
export { createI18nInstance } from './create-instance';
|
|
4
|
+
export { getLanguageDisplayName } from './display-names';
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { type SupportedLanguage } from './constants';
|
|
2
|
+
/**
|
|
3
|
+
* Maps a browser navigator.language string to a supported CellarNode language.
|
|
4
|
+
* Handles regional variants (fr-CA -> fr), Chinese scripts (zh-Hans -> zh),
|
|
5
|
+
* and unsupported languages (ja -> en).
|
|
6
|
+
*/
|
|
7
|
+
export declare function resolveLanguageFromBrowser(navigatorLanguage: string): SupportedLanguage;
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { SUPPORTED_LANGUAGES, DEFAULT_LANGUAGE } from './constants';
|
|
2
|
+
const supportedSet = new Set(SUPPORTED_LANGUAGES);
|
|
3
|
+
/**
|
|
4
|
+
* Maps a browser navigator.language string to a supported CellarNode language.
|
|
5
|
+
* Handles regional variants (fr-CA -> fr), Chinese scripts (zh-Hans -> zh),
|
|
6
|
+
* and unsupported languages (ja -> en).
|
|
7
|
+
*/
|
|
8
|
+
export function resolveLanguageFromBrowser(navigatorLanguage) {
|
|
9
|
+
if (!navigatorLanguage || typeof navigatorLanguage !== 'string') {
|
|
10
|
+
return DEFAULT_LANGUAGE;
|
|
11
|
+
}
|
|
12
|
+
const normalized = navigatorLanguage.toLowerCase().trim();
|
|
13
|
+
// Exact match
|
|
14
|
+
if (supportedSet.has(normalized)) {
|
|
15
|
+
return normalized;
|
|
16
|
+
}
|
|
17
|
+
// Extract base language (first segment before hyphen)
|
|
18
|
+
const base = normalized.split('-')[0];
|
|
19
|
+
if (supportedSet.has(base)) {
|
|
20
|
+
return base;
|
|
21
|
+
}
|
|
22
|
+
return DEFAULT_LANGUAGE;
|
|
23
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { cpSync, mkdirSync, existsSync, readdirSync } from 'node:fs';
|
|
2
|
+
import { join, dirname } from 'node:path';
|
|
3
|
+
import { createRequire } from 'node:module';
|
|
4
|
+
// Resolve the locales directory relative to the installed package,
|
|
5
|
+
// NOT relative to import.meta.url (which is wrong when run via `node -e`)
|
|
6
|
+
const require = createRequire(import.meta.url);
|
|
7
|
+
const pkgDir = dirname(require.resolve('@cellarnode/i18n/package.json'));
|
|
8
|
+
const localesSource = join(pkgDir, 'locales');
|
|
9
|
+
const targetDir = join(process.cwd(), 'public', 'locales');
|
|
10
|
+
const languages = readdirSync(localesSource).filter((f) => !f.startsWith('.'));
|
|
11
|
+
for (const lang of languages) {
|
|
12
|
+
const src = join(localesSource, lang, 'common.json');
|
|
13
|
+
const dest = join(targetDir, lang, 'common.json');
|
|
14
|
+
if (existsSync(src)) {
|
|
15
|
+
mkdirSync(dirname(dest), { recursive: true });
|
|
16
|
+
cpSync(src, dest);
|
|
17
|
+
console.log(`Copied common.json -> ${lang}`);
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
console.log(`Done. Copied ${languages.length} locale files.`);
|
package/dist/types.d.ts
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { SupportedLanguage } from './constants';
|
|
2
|
+
export type TranslationNamespace = string;
|
|
3
|
+
export interface I18nInstanceOptions {
|
|
4
|
+
/** Default namespace to use */
|
|
5
|
+
defaultNS?: string;
|
|
6
|
+
/** Namespaces to load */
|
|
7
|
+
ns?: string[];
|
|
8
|
+
/** Path template for loading translations. Use {{lng}} and {{ns}} placeholders. */
|
|
9
|
+
loadPath?: string;
|
|
10
|
+
/** Initial language (overrides detection) */
|
|
11
|
+
lng?: SupportedLanguage;
|
|
12
|
+
/** Enable debug logging */
|
|
13
|
+
debug?: boolean;
|
|
14
|
+
}
|
package/dist/types.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
{
|
|
2
|
+
"save": "Save",
|
|
3
|
+
"cancel": "Cancel",
|
|
4
|
+
"delete": "Delete",
|
|
5
|
+
"edit": "Edit",
|
|
6
|
+
"create": "Create",
|
|
7
|
+
"confirm": "Confirm",
|
|
8
|
+
"close": "Close",
|
|
9
|
+
"back": "Back",
|
|
10
|
+
"next": "Next",
|
|
11
|
+
"submit": "Submit",
|
|
12
|
+
"loading": "Loading...",
|
|
13
|
+
"error": "Error",
|
|
14
|
+
"success": "Success",
|
|
15
|
+
"warning": "Warning",
|
|
16
|
+
"search": "Search",
|
|
17
|
+
"filter": "Filter",
|
|
18
|
+
"sort": "Sort",
|
|
19
|
+
"refresh": "Refresh",
|
|
20
|
+
"retry": "Retry",
|
|
21
|
+
"yes": "Yes",
|
|
22
|
+
"no": "No",
|
|
23
|
+
"ok": "OK",
|
|
24
|
+
"noResults": "No results found",
|
|
25
|
+
"required": "This field is required",
|
|
26
|
+
"invalidEmail": "Please enter a valid email",
|
|
27
|
+
"somethingWentWrong": "Something went wrong",
|
|
28
|
+
"tryAgain": "Please try again",
|
|
29
|
+
"unsavedChanges": "You have unsaved changes",
|
|
30
|
+
"areYouSure": "Are you sure?",
|
|
31
|
+
"copiedToClipboard": "Copied to clipboard",
|
|
32
|
+
"actions": "Actions",
|
|
33
|
+
"status": "Status",
|
|
34
|
+
"name": "Name",
|
|
35
|
+
"description": "Description",
|
|
36
|
+
"date": "Date",
|
|
37
|
+
"type": "Type",
|
|
38
|
+
"viewAll": "View all",
|
|
39
|
+
"showMore": "Show more",
|
|
40
|
+
"showLess": "Show less",
|
|
41
|
+
"selectAll": "Select all",
|
|
42
|
+
"deselectAll": "Deselect all",
|
|
43
|
+
"pagination": {
|
|
44
|
+
"previous": "Previous",
|
|
45
|
+
"next": "Next",
|
|
46
|
+
"page": "Page {{current}} of {{total}}",
|
|
47
|
+
"showing": "Showing {{from}}-{{to}} of {{total}}"
|
|
48
|
+
},
|
|
49
|
+
"validation": {
|
|
50
|
+
"required": "This field is required",
|
|
51
|
+
"minLength": "Must be at least {{min}} characters",
|
|
52
|
+
"maxLength": "Must be at most {{max}} characters",
|
|
53
|
+
"positiveNumber": "Must be a positive number",
|
|
54
|
+
"invalidFormat": "Invalid format"
|
|
55
|
+
}
|
|
56
|
+
}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
{
|
|
2
|
+
"save": "Save",
|
|
3
|
+
"cancel": "Cancel",
|
|
4
|
+
"delete": "Delete",
|
|
5
|
+
"edit": "Edit",
|
|
6
|
+
"create": "Create",
|
|
7
|
+
"confirm": "Confirm",
|
|
8
|
+
"close": "Close",
|
|
9
|
+
"back": "Back",
|
|
10
|
+
"next": "Next",
|
|
11
|
+
"submit": "Submit",
|
|
12
|
+
"loading": "Loading...",
|
|
13
|
+
"error": "Error",
|
|
14
|
+
"success": "Success",
|
|
15
|
+
"warning": "Warning",
|
|
16
|
+
"search": "Search",
|
|
17
|
+
"filter": "Filter",
|
|
18
|
+
"sort": "Sort",
|
|
19
|
+
"refresh": "Refresh",
|
|
20
|
+
"retry": "Retry",
|
|
21
|
+
"yes": "Yes",
|
|
22
|
+
"no": "No",
|
|
23
|
+
"ok": "OK",
|
|
24
|
+
"noResults": "No results found",
|
|
25
|
+
"required": "This field is required",
|
|
26
|
+
"invalidEmail": "Please enter a valid email",
|
|
27
|
+
"somethingWentWrong": "Something went wrong",
|
|
28
|
+
"tryAgain": "Please try again",
|
|
29
|
+
"unsavedChanges": "You have unsaved changes",
|
|
30
|
+
"areYouSure": "Are you sure?",
|
|
31
|
+
"copiedToClipboard": "Copied to clipboard",
|
|
32
|
+
"actions": "Actions",
|
|
33
|
+
"status": "Status",
|
|
34
|
+
"name": "Name",
|
|
35
|
+
"description": "Description",
|
|
36
|
+
"date": "Date",
|
|
37
|
+
"type": "Type",
|
|
38
|
+
"viewAll": "View all",
|
|
39
|
+
"showMore": "Show more",
|
|
40
|
+
"showLess": "Show less",
|
|
41
|
+
"selectAll": "Select all",
|
|
42
|
+
"deselectAll": "Deselect all",
|
|
43
|
+
"pagination": {
|
|
44
|
+
"previous": "Previous",
|
|
45
|
+
"next": "Next",
|
|
46
|
+
"page": "Page {{current}} of {{total}}",
|
|
47
|
+
"showing": "Showing {{from}}-{{to}} of {{total}}"
|
|
48
|
+
},
|
|
49
|
+
"validation": {
|
|
50
|
+
"required": "This field is required",
|
|
51
|
+
"minLength": "Must be at least {{min}} characters",
|
|
52
|
+
"maxLength": "Must be at most {{max}} characters",
|
|
53
|
+
"positiveNumber": "Must be a positive number",
|
|
54
|
+
"invalidFormat": "Invalid format"
|
|
55
|
+
}
|
|
56
|
+
}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
{
|
|
2
|
+
"save": "Save",
|
|
3
|
+
"cancel": "Cancel",
|
|
4
|
+
"delete": "Delete",
|
|
5
|
+
"edit": "Edit",
|
|
6
|
+
"create": "Create",
|
|
7
|
+
"confirm": "Confirm",
|
|
8
|
+
"close": "Close",
|
|
9
|
+
"back": "Back",
|
|
10
|
+
"next": "Next",
|
|
11
|
+
"submit": "Submit",
|
|
12
|
+
"loading": "Loading...",
|
|
13
|
+
"error": "Error",
|
|
14
|
+
"success": "Success",
|
|
15
|
+
"warning": "Warning",
|
|
16
|
+
"search": "Search",
|
|
17
|
+
"filter": "Filter",
|
|
18
|
+
"sort": "Sort",
|
|
19
|
+
"refresh": "Refresh",
|
|
20
|
+
"retry": "Retry",
|
|
21
|
+
"yes": "Yes",
|
|
22
|
+
"no": "No",
|
|
23
|
+
"ok": "OK",
|
|
24
|
+
"noResults": "No results found",
|
|
25
|
+
"required": "This field is required",
|
|
26
|
+
"invalidEmail": "Please enter a valid email",
|
|
27
|
+
"somethingWentWrong": "Something went wrong",
|
|
28
|
+
"tryAgain": "Please try again",
|
|
29
|
+
"unsavedChanges": "You have unsaved changes",
|
|
30
|
+
"areYouSure": "Are you sure?",
|
|
31
|
+
"copiedToClipboard": "Copied to clipboard",
|
|
32
|
+
"actions": "Actions",
|
|
33
|
+
"status": "Status",
|
|
34
|
+
"name": "Name",
|
|
35
|
+
"description": "Description",
|
|
36
|
+
"date": "Date",
|
|
37
|
+
"type": "Type",
|
|
38
|
+
"viewAll": "View all",
|
|
39
|
+
"showMore": "Show more",
|
|
40
|
+
"showLess": "Show less",
|
|
41
|
+
"selectAll": "Select all",
|
|
42
|
+
"deselectAll": "Deselect all",
|
|
43
|
+
"pagination": {
|
|
44
|
+
"previous": "Previous",
|
|
45
|
+
"next": "Next",
|
|
46
|
+
"page": "Page {{current}} of {{total}}",
|
|
47
|
+
"showing": "Showing {{from}}-{{to}} of {{total}}"
|
|
48
|
+
},
|
|
49
|
+
"validation": {
|
|
50
|
+
"required": "This field is required",
|
|
51
|
+
"minLength": "Must be at least {{min}} characters",
|
|
52
|
+
"maxLength": "Must be at most {{max}} characters",
|
|
53
|
+
"positiveNumber": "Must be a positive number",
|
|
54
|
+
"invalidFormat": "Invalid format"
|
|
55
|
+
}
|
|
56
|
+
}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
{
|
|
2
|
+
"save": "Save",
|
|
3
|
+
"cancel": "Cancel",
|
|
4
|
+
"delete": "Delete",
|
|
5
|
+
"edit": "Edit",
|
|
6
|
+
"create": "Create",
|
|
7
|
+
"confirm": "Confirm",
|
|
8
|
+
"close": "Close",
|
|
9
|
+
"back": "Back",
|
|
10
|
+
"next": "Next",
|
|
11
|
+
"submit": "Submit",
|
|
12
|
+
"loading": "Loading...",
|
|
13
|
+
"error": "Error",
|
|
14
|
+
"success": "Success",
|
|
15
|
+
"warning": "Warning",
|
|
16
|
+
"search": "Search",
|
|
17
|
+
"filter": "Filter",
|
|
18
|
+
"sort": "Sort",
|
|
19
|
+
"refresh": "Refresh",
|
|
20
|
+
"retry": "Retry",
|
|
21
|
+
"yes": "Yes",
|
|
22
|
+
"no": "No",
|
|
23
|
+
"ok": "OK",
|
|
24
|
+
"noResults": "No results found",
|
|
25
|
+
"required": "This field is required",
|
|
26
|
+
"invalidEmail": "Please enter a valid email",
|
|
27
|
+
"somethingWentWrong": "Something went wrong",
|
|
28
|
+
"tryAgain": "Please try again",
|
|
29
|
+
"unsavedChanges": "You have unsaved changes",
|
|
30
|
+
"areYouSure": "Are you sure?",
|
|
31
|
+
"copiedToClipboard": "Copied to clipboard",
|
|
32
|
+
"actions": "Actions",
|
|
33
|
+
"status": "Status",
|
|
34
|
+
"name": "Name",
|
|
35
|
+
"description": "Description",
|
|
36
|
+
"date": "Date",
|
|
37
|
+
"type": "Type",
|
|
38
|
+
"viewAll": "View all",
|
|
39
|
+
"showMore": "Show more",
|
|
40
|
+
"showLess": "Show less",
|
|
41
|
+
"selectAll": "Select all",
|
|
42
|
+
"deselectAll": "Deselect all",
|
|
43
|
+
"pagination": {
|
|
44
|
+
"previous": "Previous",
|
|
45
|
+
"next": "Next",
|
|
46
|
+
"page": "Page {{current}} of {{total}}",
|
|
47
|
+
"showing": "Showing {{from}}-{{to}} of {{total}}"
|
|
48
|
+
},
|
|
49
|
+
"validation": {
|
|
50
|
+
"required": "This field is required",
|
|
51
|
+
"minLength": "Must be at least {{min}} characters",
|
|
52
|
+
"maxLength": "Must be at most {{max}} characters",
|
|
53
|
+
"positiveNumber": "Must be a positive number",
|
|
54
|
+
"invalidFormat": "Invalid format"
|
|
55
|
+
}
|
|
56
|
+
}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
{
|
|
2
|
+
"save": "Save",
|
|
3
|
+
"cancel": "Cancel",
|
|
4
|
+
"delete": "Delete",
|
|
5
|
+
"edit": "Edit",
|
|
6
|
+
"create": "Create",
|
|
7
|
+
"confirm": "Confirm",
|
|
8
|
+
"close": "Close",
|
|
9
|
+
"back": "Back",
|
|
10
|
+
"next": "Next",
|
|
11
|
+
"submit": "Submit",
|
|
12
|
+
"loading": "Loading...",
|
|
13
|
+
"error": "Error",
|
|
14
|
+
"success": "Success",
|
|
15
|
+
"warning": "Warning",
|
|
16
|
+
"search": "Search",
|
|
17
|
+
"filter": "Filter",
|
|
18
|
+
"sort": "Sort",
|
|
19
|
+
"refresh": "Refresh",
|
|
20
|
+
"retry": "Retry",
|
|
21
|
+
"yes": "Yes",
|
|
22
|
+
"no": "No",
|
|
23
|
+
"ok": "OK",
|
|
24
|
+
"noResults": "No results found",
|
|
25
|
+
"required": "This field is required",
|
|
26
|
+
"invalidEmail": "Please enter a valid email",
|
|
27
|
+
"somethingWentWrong": "Something went wrong",
|
|
28
|
+
"tryAgain": "Please try again",
|
|
29
|
+
"unsavedChanges": "You have unsaved changes",
|
|
30
|
+
"areYouSure": "Are you sure?",
|
|
31
|
+
"copiedToClipboard": "Copied to clipboard",
|
|
32
|
+
"actions": "Actions",
|
|
33
|
+
"status": "Status",
|
|
34
|
+
"name": "Name",
|
|
35
|
+
"description": "Description",
|
|
36
|
+
"date": "Date",
|
|
37
|
+
"type": "Type",
|
|
38
|
+
"viewAll": "View all",
|
|
39
|
+
"showMore": "Show more",
|
|
40
|
+
"showLess": "Show less",
|
|
41
|
+
"selectAll": "Select all",
|
|
42
|
+
"deselectAll": "Deselect all",
|
|
43
|
+
"pagination": {
|
|
44
|
+
"previous": "Previous",
|
|
45
|
+
"next": "Next",
|
|
46
|
+
"page": "Page {{current}} of {{total}}",
|
|
47
|
+
"showing": "Showing {{from}}-{{to}} of {{total}}"
|
|
48
|
+
},
|
|
49
|
+
"validation": {
|
|
50
|
+
"required": "This field is required",
|
|
51
|
+
"minLength": "Must be at least {{min}} characters",
|
|
52
|
+
"maxLength": "Must be at most {{max}} characters",
|
|
53
|
+
"positiveNumber": "Must be a positive number",
|
|
54
|
+
"invalidFormat": "Invalid format"
|
|
55
|
+
}
|
|
56
|
+
}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
{
|
|
2
|
+
"save": "Save",
|
|
3
|
+
"cancel": "Cancel",
|
|
4
|
+
"delete": "Delete",
|
|
5
|
+
"edit": "Edit",
|
|
6
|
+
"create": "Create",
|
|
7
|
+
"confirm": "Confirm",
|
|
8
|
+
"close": "Close",
|
|
9
|
+
"back": "Back",
|
|
10
|
+
"next": "Next",
|
|
11
|
+
"submit": "Submit",
|
|
12
|
+
"loading": "Loading...",
|
|
13
|
+
"error": "Error",
|
|
14
|
+
"success": "Success",
|
|
15
|
+
"warning": "Warning",
|
|
16
|
+
"search": "Search",
|
|
17
|
+
"filter": "Filter",
|
|
18
|
+
"sort": "Sort",
|
|
19
|
+
"refresh": "Refresh",
|
|
20
|
+
"retry": "Retry",
|
|
21
|
+
"yes": "Yes",
|
|
22
|
+
"no": "No",
|
|
23
|
+
"ok": "OK",
|
|
24
|
+
"noResults": "No results found",
|
|
25
|
+
"required": "This field is required",
|
|
26
|
+
"invalidEmail": "Please enter a valid email",
|
|
27
|
+
"somethingWentWrong": "Something went wrong",
|
|
28
|
+
"tryAgain": "Please try again",
|
|
29
|
+
"unsavedChanges": "You have unsaved changes",
|
|
30
|
+
"areYouSure": "Are you sure?",
|
|
31
|
+
"copiedToClipboard": "Copied to clipboard",
|
|
32
|
+
"actions": "Actions",
|
|
33
|
+
"status": "Status",
|
|
34
|
+
"name": "Name",
|
|
35
|
+
"description": "Description",
|
|
36
|
+
"date": "Date",
|
|
37
|
+
"type": "Type",
|
|
38
|
+
"viewAll": "View all",
|
|
39
|
+
"showMore": "Show more",
|
|
40
|
+
"showLess": "Show less",
|
|
41
|
+
"selectAll": "Select all",
|
|
42
|
+
"deselectAll": "Deselect all",
|
|
43
|
+
"pagination": {
|
|
44
|
+
"previous": "Previous",
|
|
45
|
+
"next": "Next",
|
|
46
|
+
"page": "Page {{current}} of {{total}}",
|
|
47
|
+
"showing": "Showing {{from}}-{{to}} of {{total}}"
|
|
48
|
+
},
|
|
49
|
+
"validation": {
|
|
50
|
+
"required": "This field is required",
|
|
51
|
+
"minLength": "Must be at least {{min}} characters",
|
|
52
|
+
"maxLength": "Must be at most {{max}} characters",
|
|
53
|
+
"positiveNumber": "Must be a positive number",
|
|
54
|
+
"invalidFormat": "Invalid format"
|
|
55
|
+
}
|
|
56
|
+
}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
{
|
|
2
|
+
"save": "Save",
|
|
3
|
+
"cancel": "Cancel",
|
|
4
|
+
"delete": "Delete",
|
|
5
|
+
"edit": "Edit",
|
|
6
|
+
"create": "Create",
|
|
7
|
+
"confirm": "Confirm",
|
|
8
|
+
"close": "Close",
|
|
9
|
+
"back": "Back",
|
|
10
|
+
"next": "Next",
|
|
11
|
+
"submit": "Submit",
|
|
12
|
+
"loading": "Loading...",
|
|
13
|
+
"error": "Error",
|
|
14
|
+
"success": "Success",
|
|
15
|
+
"warning": "Warning",
|
|
16
|
+
"search": "Search",
|
|
17
|
+
"filter": "Filter",
|
|
18
|
+
"sort": "Sort",
|
|
19
|
+
"refresh": "Refresh",
|
|
20
|
+
"retry": "Retry",
|
|
21
|
+
"yes": "Yes",
|
|
22
|
+
"no": "No",
|
|
23
|
+
"ok": "OK",
|
|
24
|
+
"noResults": "No results found",
|
|
25
|
+
"required": "This field is required",
|
|
26
|
+
"invalidEmail": "Please enter a valid email",
|
|
27
|
+
"somethingWentWrong": "Something went wrong",
|
|
28
|
+
"tryAgain": "Please try again",
|
|
29
|
+
"unsavedChanges": "You have unsaved changes",
|
|
30
|
+
"areYouSure": "Are you sure?",
|
|
31
|
+
"copiedToClipboard": "Copied to clipboard",
|
|
32
|
+
"actions": "Actions",
|
|
33
|
+
"status": "Status",
|
|
34
|
+
"name": "Name",
|
|
35
|
+
"description": "Description",
|
|
36
|
+
"date": "Date",
|
|
37
|
+
"type": "Type",
|
|
38
|
+
"viewAll": "View all",
|
|
39
|
+
"showMore": "Show more",
|
|
40
|
+
"showLess": "Show less",
|
|
41
|
+
"selectAll": "Select all",
|
|
42
|
+
"deselectAll": "Deselect all",
|
|
43
|
+
"pagination": {
|
|
44
|
+
"previous": "Previous",
|
|
45
|
+
"next": "Next",
|
|
46
|
+
"page": "Page {{current}} of {{total}}",
|
|
47
|
+
"showing": "Showing {{from}}-{{to}} of {{total}}"
|
|
48
|
+
},
|
|
49
|
+
"validation": {
|
|
50
|
+
"required": "This field is required",
|
|
51
|
+
"minLength": "Must be at least {{min}} characters",
|
|
52
|
+
"maxLength": "Must be at most {{max}} characters",
|
|
53
|
+
"positiveNumber": "Must be a positive number",
|
|
54
|
+
"invalidFormat": "Invalid format"
|
|
55
|
+
}
|
|
56
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@cellarnode/i18n",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Shared i18n configuration for CellarNode frontends — language constants, i18next factory, browser locale detection, and common translations.",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"type": "module",
|
|
7
|
+
"repository": {
|
|
8
|
+
"type": "git",
|
|
9
|
+
"url": "git+https://github.com/CellarNode/i18n.git"
|
|
10
|
+
},
|
|
11
|
+
"publishConfig": {
|
|
12
|
+
"access": "public"
|
|
13
|
+
},
|
|
14
|
+
"main": "./dist/index.js",
|
|
15
|
+
"types": "./dist/index.d.ts",
|
|
16
|
+
"exports": {
|
|
17
|
+
".": {
|
|
18
|
+
"types": "./dist/index.d.ts",
|
|
19
|
+
"default": "./dist/index.js"
|
|
20
|
+
},
|
|
21
|
+
"./locales/*": "./locales/*",
|
|
22
|
+
"./scripts/copy-common": "./dist/scripts/copy-common.js"
|
|
23
|
+
},
|
|
24
|
+
"sideEffects": false,
|
|
25
|
+
"files": [
|
|
26
|
+
"dist",
|
|
27
|
+
"locales"
|
|
28
|
+
],
|
|
29
|
+
"keywords": [
|
|
30
|
+
"cellarnode",
|
|
31
|
+
"i18n",
|
|
32
|
+
"i18next",
|
|
33
|
+
"internationalization",
|
|
34
|
+
"localization"
|
|
35
|
+
],
|
|
36
|
+
"scripts": {
|
|
37
|
+
"build": "tsc",
|
|
38
|
+
"test": "vitest run",
|
|
39
|
+
"typecheck": "tsc --noEmit",
|
|
40
|
+
"prepublishOnly": "pnpm build"
|
|
41
|
+
},
|
|
42
|
+
"dependencies": {
|
|
43
|
+
"i18next": "^24.0.0",
|
|
44
|
+
"i18next-browser-languagedetector": "^8.0.0",
|
|
45
|
+
"i18next-http-backend": "^3.0.0"
|
|
46
|
+
},
|
|
47
|
+
"devDependencies": {
|
|
48
|
+
"@types/node": "^25.5.0",
|
|
49
|
+
"typescript": "^5.8.0",
|
|
50
|
+
"vitest": "^3.0.0"
|
|
51
|
+
},
|
|
52
|
+
"engines": {
|
|
53
|
+
"node": ">=20.0.0"
|
|
54
|
+
}
|
|
55
|
+
}
|