@asafarim/shared-i18n 0.8.0 β†’ 0.9.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 (94) hide show
  1. package/README.md +266 -280
  2. package/demo/README.md +119 -0
  3. package/demo/index.html +12 -1
  4. package/demo/node_modules/.bin/kill-port +17 -0
  5. package/demo/node_modules/.bin/tsc +5 -9
  6. package/demo/node_modules/.bin/tsserver +5 -9
  7. package/demo/node_modules/.bin/vite +5 -9
  8. package/demo/package.json +7 -4
  9. package/demo/public/404.html +24 -0
  10. package/demo/public/favicon.svg +4 -4
  11. package/demo/public/logo.svg +24 -24
  12. package/demo/src/App.tsx +178 -129
  13. package/demo/src/components/CountryLanguageSelectorsPage.tsx +240 -0
  14. package/demo/src/components/GetStartedSection.tsx +56 -56
  15. package/demo/src/components/KeyTable.tsx +29 -29
  16. package/demo/src/components/LanguageBar.tsx +145 -103
  17. package/demo/src/components/LanguageSwitcherDemo.module.css +114 -114
  18. package/demo/src/components/LanguageSwitchersPage.tsx +245 -0
  19. package/demo/src/components/Logo.tsx +6 -6
  20. package/demo/src/components/OverviewSection.tsx +58 -43
  21. package/demo/src/components/Panel.tsx +15 -15
  22. package/demo/src/components/RoutingLabPage.tsx +147 -0
  23. package/demo/src/components/StatusCard.tsx +109 -109
  24. package/demo/src/data/countries.ts +48 -0
  25. package/demo/src/i18n/localeAdapter.ts +91 -0
  26. package/demo/src/i18n/localeRouting.ts +77 -0
  27. package/demo/src/index.css +1075 -644
  28. package/demo/src/locales/de/demo.json +202 -84
  29. package/demo/src/locales/en/demo.json +201 -85
  30. package/demo/src/locales/fr/demo.json +203 -85
  31. package/demo/src/locales/it/demo.json +202 -84
  32. package/demo/src/locales/lb/demo.json +201 -0
  33. package/demo/src/locales/nl/demo.json +203 -85
  34. package/demo/src/main.tsx +32 -29
  35. package/demo/tsconfig.json +18 -18
  36. package/demo/tsconfig.node.json +10 -10
  37. package/demo/tsconfig.tsbuildinfo +1 -1
  38. package/demo/vite-env.d.ts +7 -7
  39. package/demo/vite.config.d.ts +2 -2
  40. package/demo/vite.config.js +10 -10
  41. package/dist/components/LanguageSwitcher.module.css +303 -303
  42. package/dist/country-language-selector.css +431 -0
  43. package/dist/index.d.ts +2 -0
  44. package/dist/index.d.ts.map +1 -1
  45. package/dist/index.js +2 -0
  46. package/dist/tsconfig.tsbuildinfo +1 -1
  47. package/package.json +87 -85
  48. package/demo/dist/Icon Dropdown_Limited Languages.png +0 -0
  49. package/demo/dist/Select Dropdown_Text Only.png +0 -0
  50. package/demo/dist/assets/favicon-BZYZvBLo.svg +0 -4
  51. package/demo/dist/assets/index-BdjqKw_N.css +0 -1
  52. package/demo/dist/assets/index-C1Tq1uEr.js +0 -191
  53. package/demo/dist/favicon.svg +0 -4
  54. package/demo/dist/index.html +0 -27
  55. package/demo/dist/logo.svg +0 -24
  56. package/demo/node_modules/.bin/browserslist +0 -21
  57. package/demo/node_modules/.bin/browserslist.CMD +0 -12
  58. package/demo/node_modules/.bin/browserslist.ps1 +0 -41
  59. package/demo/node_modules/.bin/tsc.CMD +0 -12
  60. package/demo/node_modules/.bin/tsc.ps1 +0 -41
  61. package/demo/node_modules/.bin/tsserver.CMD +0 -12
  62. package/demo/node_modules/.bin/tsserver.ps1 +0 -41
  63. package/demo/node_modules/.bin/vite.CMD +0 -12
  64. package/demo/node_modules/.bin/vite.ps1 +0 -41
  65. package/demo/node_modules/.vite/deps/@asafarim_country-language-selector.js +0 -848
  66. package/demo/node_modules/.vite/deps/@asafarim_country-language-selector.js.map +0 -7
  67. package/demo/node_modules/.vite/deps/_metadata.json +0 -76
  68. package/demo/node_modules/.vite/deps/chunk-5WRI5ZAA.js +0 -30
  69. package/demo/node_modules/.vite/deps/chunk-5WRI5ZAA.js.map +0 -7
  70. package/demo/node_modules/.vite/deps/chunk-B3AHR5EX.js +0 -1004
  71. package/demo/node_modules/.vite/deps/chunk-B3AHR5EX.js.map +0 -7
  72. package/demo/node_modules/.vite/deps/chunk-E6BG6WAU.js +0 -292
  73. package/demo/node_modules/.vite/deps/chunk-E6BG6WAU.js.map +0 -7
  74. package/demo/node_modules/.vite/deps/chunk-MVARZQEG.js +0 -280
  75. package/demo/node_modules/.vite/deps/chunk-MVARZQEG.js.map +0 -7
  76. package/demo/node_modules/.vite/deps/i18next-browser-languagedetector.js +0 -400
  77. package/demo/node_modules/.vite/deps/i18next-browser-languagedetector.js.map +0 -7
  78. package/demo/node_modules/.vite/deps/i18next.js +0 -2392
  79. package/demo/node_modules/.vite/deps/i18next.js.map +0 -7
  80. package/demo/node_modules/.vite/deps/package.json +0 -3
  81. package/demo/node_modules/.vite/deps/react-dom.js +0 -6
  82. package/demo/node_modules/.vite/deps/react-dom.js.map +0 -7
  83. package/demo/node_modules/.vite/deps/react-dom_client.js +0 -20217
  84. package/demo/node_modules/.vite/deps/react-dom_client.js.map +0 -7
  85. package/demo/node_modules/.vite/deps/react-i18next.js +0 -869
  86. package/demo/node_modules/.vite/deps/react-i18next.js.map +0 -7
  87. package/demo/node_modules/.vite/deps/react.js +0 -5
  88. package/demo/node_modules/.vite/deps/react.js.map +0 -7
  89. package/demo/node_modules/.vite/deps/react_jsx-dev-runtime.js +0 -278
  90. package/demo/node_modules/.vite/deps/react_jsx-dev-runtime.js.map +0 -7
  91. package/demo/node_modules/.vite/deps/react_jsx-runtime.js +0 -6
  92. package/demo/node_modules/.vite/deps/react_jsx-runtime.js.map +0 -7
  93. package/demo/src/components/CountryLanguageDemo.tsx +0 -140
  94. package/demo/src/components/LanguageSwitcherDemo.tsx +0 -256
package/README.md CHANGED
@@ -1,280 +1,266 @@
1
- # @asafarim/shared-i18n
2
-
3
- Lightweight, simple translation module for any React + TypeScript app, built on top of i18next and react-i18next. It ships with sensible defaults (English and Dutch) but can support any language by adding JSON files to your locales folder.
4
-
5
- * see Demo at: [https://alisafari-it.github.io/shared-i18n/](https://alisafari-it.github.io/shared-i18n/)
6
-
7
- ## Features
8
-
9
- - 🌍 Works in any React + TypeScript project (monorepo or standalone)
10
- - πŸ—‚οΈ JSON-based translations per language and namespace
11
- - πŸ”„ Cookie-based language persistence (browser) with automatic detection
12
- - βš™οΈ Optional backend sync for user language preferences
13
- - ⚑ Lazy loading support for app-specific translations
14
- - πŸͺ React hooks for language management (useLanguage) and translations (useTranslation)
15
- - 🎨 Built-in LanguageSwitcher component with multiple variants (buttons, select, icon-dropdown)
16
- - πŸš€ Configurable API URL resolution for flexible backend integration
17
-
18
- ## Installation
19
-
20
- ```bash
21
- pnpm add @asafarim/shared-i18n
22
- # or
23
- npm i @asafarim/shared-i18n
24
- # or
25
- yarn add @asafarim/shared-i18n
26
- ```
27
-
28
- ## Usage
29
-
30
- ### 1) Initialize i18n in your app
31
-
32
- In your app's main entry point (e.g., `main.tsx`):
33
-
34
- ```tsx
35
- import { StrictMode } from 'react';
36
- import { createRoot } from 'react-dom/client';
37
- import { initI18n } from '@asafarim/shared-i18n';
38
- import App from './App';
39
-
40
- // Optional: import your app-specific translations
41
- import enApp from './locales/en/app.json';
42
- import nlApp from './locales/nl/app.json';
43
-
44
- initI18n({
45
- defaultNS: 'common',
46
- ns: ['common', 'app'],
47
- resources: {
48
- en: { app: enApp },
49
- nl: { app: nlApp }
50
- }
51
- });
52
-
53
- createRoot(document.getElementById('root')!).render(
54
- <StrictMode>
55
- <App />
56
- </StrictMode>
57
- );
58
- ```
59
-
60
- ### 2) Use translations in components
61
-
62
- ```tsx
63
- import { useTranslation } from '@asafarim/shared-i18n';
64
-
65
- function MyComponent() {
66
- const { t } = useTranslation();
67
- return (
68
- <div>
69
- <h1>{t('welcome')}</h1>
70
- <p>{t('app:customKey')}</p>
71
- </div>
72
- );
73
- }
74
- ```
75
-
76
- ### 3) Add a language switcher
77
-
78
- Use the built-in LanguageSwitcher component with multiple variants:
79
-
80
- ```tsx
81
- import { LanguageSwitcher } from '@asafarim/shared-i18n/components';
82
-
83
- // Buttons variant (default)
84
- <LanguageSwitcher variant="buttons" />
85
-
86
- // Select dropdown
87
- <LanguageSwitcher variant="select" />
88
-
89
- // Icon-only dropdown with flag emojis
90
- <LanguageSwitcher variant="icon-dropdown" />
91
-
92
- // Custom languages subset
93
- <LanguageSwitcher variant="buttons" languages={['en', 'nl']} />
94
- ```
95
-
96
- Or use the hook directly:
97
-
98
- ```tsx
99
- import { useLanguage } from '@asafarim/shared-i18n';
100
-
101
- export function LanguageToggle() {
102
- const { language, changeLanguage, isChanging } = useLanguage();
103
- return (
104
- <button onClick={() => changeLanguage(language === 'en' ? 'nl' : 'en')} disabled={isChanging}>
105
- Switch language (current: {language})
106
- </button>
107
- );
108
- }
109
- ```
110
-
111
- ## Add more languages
112
-
113
- Yes β€” you can support any language by adding the required JSON files to your locales folder. For example:
114
-
115
- ```
116
- your-app/
117
- src/
118
- locales/
119
- en/
120
- app.json
121
- nl/
122
- app.json
123
- anotherLang/
124
- new.json
125
- ```
126
-
127
- Then include them when initializing:
128
-
129
- ```tsx
130
- import anotherLang from './locales/anotherLang/new.json';
131
-
132
- initI18n({
133
- ns: ['common', 'app', 'new'],
134
- resources: {
135
- anotherLang: { new: anotherLang }
136
- },
137
- supportedLngs: ['en', 'nl', 'anotherLang'],
138
- defaultLanguage: 'en'
139
- });
140
- ```
141
-
142
- Notes:
143
-
144
- - If you pass supportedLngs, it will override the default supported languages.
145
- - defaultLanguage overrides the default fallback (which is English).
146
-
147
- ## LanguageSwitcher Component
148
-
149
- The LanguageSwitcher component provides multiple UI variants for language selection with built-in styling using ASafariM design tokens.
150
-
151
- ### Props
152
-
153
- ```tsx
154
- interface LanguageSwitcherProps {
155
- className?: string;
156
- style?: React.CSSProperties;
157
- languages?: readonly SupportedLanguage[];
158
- variant?: "buttons" | "select" | "icon-dropdown";
159
- disabled?: boolean;
160
- getLabel?: (lang: SupportedLanguage) => string;
161
- getIcon?: (lang: SupportedLanguage) => React.ReactNode;
162
- onChanged?: (lang: SupportedLanguage) => void;
163
- buttonClassName?: string;
164
- selectClassName?: string;
165
- showLabel?: boolean;
166
- showIcon?: boolean;
167
- showEmoji?: boolean;
168
- unstyled?: boolean;
169
- isToggler?: boolean;
170
- }
171
- ```
172
-
173
- ### Variants
174
-
175
- - **buttons** β€” Individual buttons for each language (default)
176
- - **select** β€” Native dropdown select element
177
- ![Select Dropdown](https://github.com/AliSafari-IT/shared-i18n/blob/main/demo/public/Select%20Dropdown_Text%20Only.png?raw=true)
178
- - **icon-dropdown** β€” Custom dropdown showing flag emojis with language codes
179
- ![Icon Dropdown](https://github.com/AliSafari-IT/shared-i18n/blob/main/demo/public/Icon%20Dropdown_Limited%20Languages.png?raw=true)
180
-
181
- ### Examples
182
-
183
- ```tsx
184
- // Buttons with custom styling
185
- <LanguageSwitcher
186
- variant="buttons"
187
- buttonClassName="custom-btn"
188
- languages={['en', 'nl', 'fr']}
189
- />
190
-
191
- // Icon-only dropdown
192
- <LanguageSwitcher
193
- variant="icon-dropdown"
194
- showEmoji={true}
195
- />
196
-
197
- // Select with callback
198
- <LanguageSwitcher
199
- variant="select"
200
- onChanged={(lang) => console.log(`Language changed to ${lang}`)}
201
- />
202
- ```
203
-
204
- ## API Reference
205
-
206
- ### initI18n(config?: I18nConfig)
207
-
208
- Initialize i18next with the shared configuration.
209
-
210
- Parameters:
211
-
212
- - config.defaultNS β€” Default namespace (default: 'common')
213
- - config.ns β€” Namespaces to load (default: ['common'])
214
- - config.resources β€” App-specific translation resources
215
- - config.supportedLngs β€” Optional list of supported language codes to enable
216
- - config.defaultLanguage β€” Optional fallback language code
217
-
218
- ### useLanguage()
219
-
220
- Hook for managing language preferences.
221
-
222
- Returns:
223
-
224
- - language β€” Current language code
225
- - changeLanguage(lang) β€” Function to change language
226
- - isChanging β€” Boolean indicating if language change is in progress
227
-
228
- ### useTranslation()
229
-
230
- Re-exported from react-i18next. See official docs.
231
-
232
- ### getApiUrl(envVarName?, defaultUrl?)
233
-
234
- Configurable API URL resolver for flexible backend integration.
235
-
236
- Parameters:
237
-
238
- - envVarName β€” Environment variable name to check (default: 'VITE_API_URL')
239
- - defaultUrl β€” Default URL if no env var is set (default: 'http://localhost')
240
-
241
- Returns: Resolved API URL string
242
-
243
- ### setApiUrlResolver(resolver)
244
-
245
- Set a custom API URL resolver function for complete control over URL resolution.
246
-
247
- Parameters:
248
-
249
- - resolver β€” Function that returns the API URL string
250
-
251
- Example:
252
-
253
- ```tsx
254
- import { setApiUrlResolver, getApiUrl } from '@asafarim/shared-i18n/utils';
255
-
256
- // Custom resolver
257
- setApiUrlResolver(() => 'https://my-custom-api.example.com');
258
-
259
- // Or use environment variables
260
- const url = getApiUrl('VITE_CUSTOM_API_URL', 'https://fallback.example.com');
261
- ```
262
-
263
- ## Cookie and backend integration
264
-
265
- - A preferredLanguage cookie is used to persist the selected language in the browser.
266
- - If your environment provides an Identity API, updateUserLanguagePreference can sync the preference server-side. If not, the library still works fully client-side.
267
-
268
- To point to a backend, optionally set:
269
-
270
- ```env
271
- VITE_IDENTITY_API_URL=https://your-identity.example.com
272
- ```
273
-
274
- ## Built-in translations
275
-
276
- This package ships with default English and Dutch common translations. You can ignore them and supply your own resources if preferred.
277
-
278
- ## License
279
-
280
- MIT Β© ASafariM
1
+ # @asafarim/shared-i18n
2
+
3
+ Lightweight React + TypeScript i18n package built on top of i18next and react-i18next. Ships with sensible defaults (English and Dutch) but supports any language. Includes a **LanguageSwitcher** and, new in v0.9, re-exports the full **CountryLanguageSelector** from `@asafarim/country-language-selector` β€” so consumers install only one package.
4
+
5
+ * Live demo: [https://alisafari-it.github.io/shared-i18n/](https://alisafari-it.github.io/shared-i18n/)
6
+
7
+ ## Features
8
+
9
+ - 🌍 Works in any React + TypeScript project (monorepo or standalone)
10
+ - πŸ—‚οΈ JSON-based translations per language and namespace
11
+ - πŸ”„ Cookie-based language persistence with automatic browser detection
12
+ - βš™οΈ Optional backend sync for user language preferences
13
+ - ⚑ Lazy loading support for app-specific translations
14
+ - πŸͺ React hooks: `useLanguage`, `useTranslation`
15
+ - 🎨 **LanguageSwitcher** β€” three variants: buttons, select, icon-dropdown
16
+ - πŸ—ΊοΈ **CountryLanguageSelector** β€” re-exported, image or emoji flags, locale-aware URLs
17
+ - πŸš€ Configurable API URL resolution for flexible backend integration
18
+
19
+ ## Installation
20
+
21
+ ```bash
22
+ pnpm add @asafarim/shared-i18n
23
+ # or: npm i @asafarim/shared-i18n
24
+ ```
25
+
26
+ That is the only package you need. `@asafarim/country-language-selector` is a bundled dependency and re-exported for you.
27
+
28
+ ## Quick Start
29
+
30
+ ### 1. Initialize i18n
31
+
32
+ ```tsx
33
+ // main.tsx
34
+ import { initI18n } from '@asafarim/shared-i18n';
35
+ import '@asafarim/shared-i18n/country-language-selector.css'; // if using CountryLanguageSelector
36
+
37
+ import enApp from './locales/en/app.json';
38
+ import nlApp from './locales/nl/app.json';
39
+
40
+ initI18n({
41
+ defaultLanguage: 'en',
42
+ defaultNS: 'common',
43
+ ns: ['common', 'app'],
44
+ resources: {
45
+ en: { app: enApp },
46
+ nl: { app: nlApp }
47
+ }
48
+ });
49
+ ```
50
+
51
+ ### 2. Translate in components
52
+
53
+ ```tsx
54
+ import { useTranslation } from '@asafarim/shared-i18n';
55
+
56
+ function MyComponent() {
57
+ const { t } = useTranslation('app');
58
+ return <h1>{t('welcome')}</h1>;
59
+ }
60
+ ```
61
+
62
+ ---
63
+
64
+ ## LanguageSwitcher
65
+
66
+ Language-only switcher with cookie persistence.
67
+
68
+ ```tsx
69
+ import { LanguageSwitcher } from '@asafarim/shared-i18n';
70
+
71
+ // Buttons
72
+ <LanguageSwitcher variant="buttons" />
73
+
74
+ // Select dropdown (no emoji)
75
+ <LanguageSwitcher variant="select" showEmoji={false} />
76
+
77
+ // Icon dropdown with flag emoji
78
+ <LanguageSwitcher variant="icon-dropdown" />
79
+
80
+ // Toggle between exactly 2 languages
81
+ <LanguageSwitcher variant="select" languages={['en', 'nl']} isToggler={true} />
82
+ ```
83
+
84
+ ### Props
85
+
86
+ | Prop | Type | Default | Description |
87
+ |------|------|---------|-------------|
88
+ | `variant` | `"buttons" \| "select" \| "icon-dropdown"` | `"buttons"` | UI variant |
89
+ | `languages` | `SupportedLanguage[]` | all supported | Subset of languages to show |
90
+ | `showEmoji` | `boolean` | `true` | Show flag emoji in select/icon variants |
91
+ | `showLabel` | `boolean` | `true` | Show language name label |
92
+ | `isToggler` | `boolean` | `true` | Renders as toggle when exactly 2 languages given |
93
+ | `onChanged` | `(lang: SupportedLanguage) => void` | β€” | Callback on language change |
94
+ | `unstyled` | `boolean` | `false` | Omit built-in styles |
95
+
96
+ ### Limitation β€” country awareness
97
+
98
+ `LanguageSwitcher` emits only a language code (e.g. `"en"`). It cannot distinguish `be-en` from `gb-en`. For locale-aware URLs, use `resolveLocaleFromLanguage` as an adapter:
99
+
100
+ ```tsx
101
+ import { LanguageSwitcher } from '@asafarim/shared-i18n';
102
+ import { resolveLocaleFromLanguage } from './i18n/localeAdapter';
103
+
104
+ <LanguageSwitcher
105
+ variant="buttons"
106
+ onChanged={(lang) => {
107
+ const { locale, reason, message } = resolveLocaleFromLanguage(currentLocale, lang, countries);
108
+ if (reason === 'fallback-country') showNotice(message);
109
+ if (reason !== 'unsupported') navigate(locale);
110
+ }}
111
+ />
112
+ ```
113
+
114
+ ---
115
+
116
+ ## CountryLanguageSelector
117
+
118
+ Country + language selector. Re-exported from `@asafarim/country-language-selector`.
119
+
120
+ ```tsx
121
+ import {
122
+ CountryLanguageSelector,
123
+ type Country,
124
+ type Locale,
125
+ } from '@asafarim/shared-i18n';
126
+
127
+ const countries: Country[] = [
128
+ {
129
+ code: 'BE', name: 'Belgium', nativeName: 'BelgiΓ«', flag: 'πŸ‡§πŸ‡ͺ',
130
+ languages: [
131
+ { code: 'en', label: 'English', nativeLabel: 'English' },
132
+ { code: 'nl', label: 'Dutch', nativeLabel: 'Nederlands' },
133
+ { code: 'fr', label: 'French', nativeLabel: 'FranΓ§ais' },
134
+ ]
135
+ },
136
+ {
137
+ code: 'NL', name: 'Netherlands', nativeName: 'Nederland', flag: 'πŸ‡³πŸ‡±',
138
+ languages: [
139
+ { code: 'nl', label: 'Dutch', nativeLabel: 'Nederlands' },
140
+ { code: 'en', label: 'English', nativeLabel: 'English' },
141
+ ]
142
+ },
143
+ {
144
+ code: 'GB', name: 'United Kingdom', nativeName: 'United Kingdom', flag: 'πŸ‡¬πŸ‡§',
145
+ languages: [
146
+ { code: 'en', label: 'English', nativeLabel: 'English' },
147
+ ]
148
+ }
149
+ ];
150
+
151
+ function LocaleBar({ locale, onLocaleChange }) {
152
+ const { i18n } = useTranslation();
153
+ return (
154
+ <CountryLanguageSelector
155
+ countries={countries}
156
+ value={locale}
157
+ onChange={(next) => {
158
+ i18n.changeLanguage(next.language);
159
+ onLocaleChange(next);
160
+ }}
161
+ triggerVariant="compact"
162
+ flagMode="image"
163
+ />
164
+ );
165
+ }
166
+ ```
167
+
168
+ ### Key Props
169
+
170
+ | Prop | Type | Default | Description |
171
+ |------|------|---------|-------------|
172
+ | `countries` | `Country[]` | built-in list | Countries to offer |
173
+ | `value` | `Locale` | β€” | Controlled value |
174
+ | `defaultValue` | `Locale` | β€” | Uncontrolled initial value |
175
+ | `onChange` | `(locale, meta) => void` | β€” | Fired on every change |
176
+ | `triggerVariant` | `"compact" \| "full" \| "flag"` | `"compact"` | Trigger display |
177
+ | `flagMode` | `"emoji" \| "image"` | `"emoji"` | Flag rendering strategy |
178
+ | `align` | `"start" \| "end"` | `"end"` | Popover alignment |
179
+ | `renderTrigger` | `(ctx) => ReactNode` | β€” | Custom trigger render prop |
180
+ | `persistKey` | `string` | β€” | localStorage key (uncontrolled only) |
181
+
182
+ ### Comparison: LanguageSwitcher vs CountryLanguageSelector
183
+
184
+ | Capability | LanguageSwitcher | CountryLanguageSelector |
185
+ |---|---|---|
186
+ | Changes i18n language | βœ… | βœ… via `locale.language` |
187
+ | Knows country | ❌ | βœ… |
188
+ | Represents `be-en` | ❌ | βœ… |
189
+ | Distinguishes `be-en` from `gb-en` | ❌ | βœ… |
190
+ | Best for localized URLs | ❌ needs adapter | βœ… |
191
+ | Best for translation-only apps | βœ… | optional |
192
+
193
+ ---
194
+
195
+ ## CSS Import
196
+
197
+ When using `CountryLanguageSelector`, import its stylesheet once in your entry point:
198
+
199
+ ```tsx
200
+ import '@asafarim/shared-i18n/country-language-selector.css';
201
+ ```
202
+
203
+ ---
204
+
205
+ ## More Languages
206
+
207
+ Add any language by including its JSON files:
208
+
209
+ ```tsx
210
+ initI18n({
211
+ ns: ['common', 'app'],
212
+ resources: {
213
+ en: { app: enApp },
214
+ nl: { app: nlApp },
215
+ fr: { app: frApp }
216
+ },
217
+ supportedLngs: ['en', 'nl', 'fr'],
218
+ defaultLanguage: 'en'
219
+ });
220
+ ```
221
+
222
+ ---
223
+
224
+ ## API Reference
225
+
226
+ ### `initI18n(config?)`
227
+
228
+ | Param | Type | Description |
229
+ |-------|------|-------------|
230
+ | `defaultNS` | `string` | Default namespace (default: `'common'`) |
231
+ | `ns` | `string[]` | Namespaces to load |
232
+ | `resources` | object | App-specific translation resources |
233
+ | `supportedLngs` | `string[]` | Override supported languages |
234
+ | `defaultLanguage` | `string` | Fallback language code |
235
+
236
+ ### `useLanguage()`
237
+
238
+ Returns `{ language, changeLanguage, isChanging }`.
239
+
240
+ ### `useTranslation(ns?)`
241
+
242
+ Re-exported from react-i18next.
243
+
244
+ ### `getApiUrl(envVarName?, defaultUrl?)`
245
+
246
+ Configurable API URL resolver.
247
+
248
+ ---
249
+
250
+ ## Cookie & Backend Integration
251
+
252
+ User language preference is persisted in a `preferredLanguage` cookie. To sync with a backend, set:
253
+
254
+ ```env
255
+ VITE_IDENTITY_API_URL=https://your-identity.example.com
256
+ ```
257
+
258
+ ---
259
+
260
+ ## Built-in Translations
261
+
262
+ Ships with English and Dutch for the `common` namespace. Supply your own resources via `initI18n` to override or extend.
263
+
264
+ ## License
265
+
266
+ MIT Β© ASafariM