@asafarim/shared-i18n 0.5.2 → 0.6.1
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/README.md +170 -168
- package/demo/index.html +13 -0
- package/demo/node_modules/.bin/tsc +21 -0
- package/demo/node_modules/.bin/tsserver +21 -0
- package/demo/node_modules/.bin/vite +21 -0
- package/demo/package.json +24 -0
- package/demo/public/favicon.svg +4 -0
- package/demo/public/logo.svg +24 -0
- package/demo/src/App.tsx +112 -0
- package/demo/src/components/GetStartedSection.tsx +56 -0
- package/demo/src/components/KeyTable.tsx +29 -0
- package/demo/src/components/LanguageBar.tsx +19 -0
- package/demo/src/components/LanguageSwitcherDemo.module.css +113 -0
- package/demo/src/components/LanguageSwitcherDemo.tsx +184 -0
- package/demo/src/components/OverviewSection.tsx +43 -0
- package/demo/src/components/Panel.tsx +15 -0
- package/demo/src/components/StatusCard.tsx +109 -0
- package/demo/src/index.css +569 -0
- package/demo/src/locales/en/demo.json +85 -0
- package/demo/src/locales/fr/demo.json +85 -0
- package/demo/src/locales/nl/demo.json +85 -0
- package/demo/src/main.tsx +24 -0
- package/demo/tsconfig.json +18 -0
- package/demo/tsconfig.node.json +10 -0
- package/demo/vite-env.d.ts +7 -0
- package/demo/vite.config.ts +11 -0
- package/dist/components/LanguageSwitcher.d.ts +20 -0
- package/dist/components/LanguageSwitcher.d.ts.map +1 -0
- package/dist/components/LanguageSwitcher.js +72 -0
- package/dist/components/LanguageSwitcher.module.css +205 -0
- package/dist/config/i18n.d.ts +46 -0
- package/dist/config/i18n.d.ts.map +1 -0
- package/dist/config/i18n.js +66 -0
- package/dist/hooks/useLanguage.d.ts +12 -0
- package/dist/hooks/useLanguage.d.ts.map +1 -0
- package/dist/hooks/useLanguage.js +61 -0
- package/dist/index.d.ts +9 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +10 -0
- package/dist/locales/en/common.json +68 -0
- package/{locales → dist/locales}/en/identity-portal.json +69 -69
- package/dist/locales/nl/common.json +68 -0
- package/{locales → dist/locales}/nl/identity-portal.json +69 -69
- package/dist/tsconfig.tsbuildinfo +1 -0
- package/dist/utils/languageIcons.d.ts +4 -0
- package/dist/utils/languageIcons.d.ts.map +1 -0
- package/dist/utils/languageIcons.js +12 -0
- package/dist/utils/languageUtils.d.ts +45 -0
- package/dist/utils/languageUtils.d.ts.map +1 -0
- package/dist/utils/languageUtils.js +144 -0
- package/package.json +34 -22
- package/LICENSE +0 -23
- package/config/i18n.ts +0 -89
- package/hooks/useLanguage.ts +0 -80
- package/index.ts +0 -21
- package/locales/en/common.json +0 -66
- package/locales/en/web.json +0 -343
- package/locales/nl/common.json +0 -66
- package/locales/nl/web.json +0 -343
- package/tsconfig.json +0 -32
- package/utils/languageUtils.ts +0 -141
package/README.md
CHANGED
|
@@ -1,168 +1,170 @@
|
|
|
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
|
-
## Features
|
|
6
|
-
|
|
7
|
-
- 🌍 Works in any React + TypeScript project (monorepo or standalone)
|
|
8
|
-
- 🗂️ JSON-based translations per language and namespace
|
|
9
|
-
- 🔄 Cookie-based language persistence (browser) with automatic detection
|
|
10
|
-
- ⚙️ Optional backend sync for user language preferences
|
|
11
|
-
- ⚡ Lazy loading support for app-specific translations
|
|
12
|
-
- 🪝 React hooks for language management (useLanguage) and translations (useTranslation)
|
|
13
|
-
|
|
14
|
-
## Installation
|
|
15
|
-
|
|
16
|
-
```bash
|
|
17
|
-
pnpm add @asafarim/shared-i18n
|
|
18
|
-
# or
|
|
19
|
-
npm i @asafarim/shared-i18n
|
|
20
|
-
# or
|
|
21
|
-
yarn add @asafarim/shared-i18n
|
|
22
|
-
```
|
|
23
|
-
|
|
24
|
-
## Usage
|
|
25
|
-
|
|
26
|
-
### 1) Initialize i18n in your app
|
|
27
|
-
|
|
28
|
-
In your app's main entry point (e.g., `main.tsx`):
|
|
29
|
-
|
|
30
|
-
```tsx
|
|
31
|
-
import { StrictMode } from 'react';
|
|
32
|
-
import { createRoot } from 'react-dom/client';
|
|
33
|
-
import { initI18n } from '@asafarim/shared-i18n';
|
|
34
|
-
import App from './App';
|
|
35
|
-
|
|
36
|
-
// Optional: import your app-specific translations
|
|
37
|
-
import enApp from './locales/en/app.json';
|
|
38
|
-
import nlApp from './locales/nl/app.json';
|
|
39
|
-
|
|
40
|
-
initI18n({
|
|
41
|
-
defaultNS: 'common',
|
|
42
|
-
ns: ['common', 'app'],
|
|
43
|
-
resources: {
|
|
44
|
-
en: { app: enApp },
|
|
45
|
-
nl: { app: nlApp }
|
|
46
|
-
}
|
|
47
|
-
});
|
|
48
|
-
|
|
49
|
-
createRoot(document.getElementById('root')!).render(
|
|
50
|
-
<StrictMode>
|
|
51
|
-
<App />
|
|
52
|
-
</StrictMode>
|
|
53
|
-
);
|
|
54
|
-
```
|
|
55
|
-
|
|
56
|
-
### 2) Use translations in components
|
|
57
|
-
|
|
58
|
-
```tsx
|
|
59
|
-
import { useTranslation } from '@asafarim/shared-i18n';
|
|
60
|
-
|
|
61
|
-
function MyComponent() {
|
|
62
|
-
const { t } = useTranslation();
|
|
63
|
-
return (
|
|
64
|
-
<div>
|
|
65
|
-
<h1>{t('welcome')}</h1>
|
|
66
|
-
<p>{t('app:customKey')}</p>
|
|
67
|
-
</div>
|
|
68
|
-
);
|
|
69
|
-
}
|
|
70
|
-
```
|
|
71
|
-
|
|
72
|
-
### 3) Add a language switcher
|
|
73
|
-
|
|
74
|
-
If you use @asafarim/shared-ui-react you can leverage its LanguageSwitcher component. Otherwise, call useLanguage directly.
|
|
75
|
-
|
|
76
|
-
```tsx
|
|
77
|
-
import { useLanguage } from '@asafarim/shared-i18n';
|
|
78
|
-
|
|
79
|
-
export function LanguageToggle() {
|
|
80
|
-
const { language, changeLanguage, isChanging } = useLanguage();
|
|
81
|
-
return (
|
|
82
|
-
<button onClick={() => changeLanguage(language === 'en' ? 'nl' : 'en')} disabled={isChanging}>
|
|
83
|
-
Switch language (current: {language})
|
|
84
|
-
</button>
|
|
85
|
-
);
|
|
86
|
-
}
|
|
87
|
-
```
|
|
88
|
-
|
|
89
|
-
## Add more languages
|
|
90
|
-
|
|
91
|
-
Yes — you can support any language by adding the required JSON files to your locales folder. For example:
|
|
92
|
-
|
|
93
|
-
```
|
|
94
|
-
your-app/
|
|
95
|
-
src/
|
|
96
|
-
locales/
|
|
97
|
-
en/
|
|
98
|
-
app.json
|
|
99
|
-
nl/
|
|
100
|
-
app.json
|
|
101
|
-
anotherLang/
|
|
102
|
-
new.json
|
|
103
|
-
```
|
|
104
|
-
|
|
105
|
-
Then include them when initializing:
|
|
106
|
-
|
|
107
|
-
```tsx
|
|
108
|
-
import anotherLang from './locales/anotherLang/new.json';
|
|
109
|
-
|
|
110
|
-
initI18n({
|
|
111
|
-
ns: ['common', 'app', 'new'],
|
|
112
|
-
resources: {
|
|
113
|
-
anotherLang: { new: anotherLang }
|
|
114
|
-
},
|
|
115
|
-
supportedLngs: ['en', 'nl', 'anotherLang'],
|
|
116
|
-
defaultLanguage: 'en'
|
|
117
|
-
});
|
|
118
|
-
```
|
|
119
|
-
|
|
120
|
-
Notes:
|
|
121
|
-
|
|
122
|
-
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
- config.
|
|
134
|
-
- config.
|
|
135
|
-
- config.
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
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
|
+
## Features
|
|
6
|
+
|
|
7
|
+
- 🌍 Works in any React + TypeScript project (monorepo or standalone)
|
|
8
|
+
- 🗂️ JSON-based translations per language and namespace
|
|
9
|
+
- 🔄 Cookie-based language persistence (browser) with automatic detection
|
|
10
|
+
- ⚙️ Optional backend sync for user language preferences
|
|
11
|
+
- ⚡ Lazy loading support for app-specific translations
|
|
12
|
+
- 🪝 React hooks for language management (useLanguage) and translations (useTranslation)
|
|
13
|
+
|
|
14
|
+
## Installation
|
|
15
|
+
|
|
16
|
+
```bash
|
|
17
|
+
pnpm add @asafarim/shared-i18n
|
|
18
|
+
# or
|
|
19
|
+
npm i @asafarim/shared-i18n
|
|
20
|
+
# or
|
|
21
|
+
yarn add @asafarim/shared-i18n
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
## Usage
|
|
25
|
+
|
|
26
|
+
### 1) Initialize i18n in your app
|
|
27
|
+
|
|
28
|
+
In your app's main entry point (e.g., `main.tsx`):
|
|
29
|
+
|
|
30
|
+
```tsx
|
|
31
|
+
import { StrictMode } from 'react';
|
|
32
|
+
import { createRoot } from 'react-dom/client';
|
|
33
|
+
import { initI18n } from '@asafarim/shared-i18n';
|
|
34
|
+
import App from './App';
|
|
35
|
+
|
|
36
|
+
// Optional: import your app-specific translations
|
|
37
|
+
import enApp from './locales/en/app.json';
|
|
38
|
+
import nlApp from './locales/nl/app.json';
|
|
39
|
+
|
|
40
|
+
initI18n({
|
|
41
|
+
defaultNS: 'common',
|
|
42
|
+
ns: ['common', 'app'],
|
|
43
|
+
resources: {
|
|
44
|
+
en: { app: enApp },
|
|
45
|
+
nl: { app: nlApp }
|
|
46
|
+
}
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
createRoot(document.getElementById('root')!).render(
|
|
50
|
+
<StrictMode>
|
|
51
|
+
<App />
|
|
52
|
+
</StrictMode>
|
|
53
|
+
);
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
### 2) Use translations in components
|
|
57
|
+
|
|
58
|
+
```tsx
|
|
59
|
+
import { useTranslation } from '@asafarim/shared-i18n';
|
|
60
|
+
|
|
61
|
+
function MyComponent() {
|
|
62
|
+
const { t } = useTranslation();
|
|
63
|
+
return (
|
|
64
|
+
<div>
|
|
65
|
+
<h1>{t('welcome')}</h1>
|
|
66
|
+
<p>{t('app:customKey')}</p>
|
|
67
|
+
</div>
|
|
68
|
+
);
|
|
69
|
+
}
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
### 3) Add a language switcher
|
|
73
|
+
|
|
74
|
+
If you use @asafarim/shared-ui-react you can leverage its LanguageSwitcher component. Otherwise, call useLanguage directly.
|
|
75
|
+
|
|
76
|
+
```tsx
|
|
77
|
+
import { useLanguage } from '@asafarim/shared-i18n';
|
|
78
|
+
|
|
79
|
+
export function LanguageToggle() {
|
|
80
|
+
const { language, changeLanguage, isChanging } = useLanguage();
|
|
81
|
+
return (
|
|
82
|
+
<button onClick={() => changeLanguage(language === 'en' ? 'nl' : 'en')} disabled={isChanging}>
|
|
83
|
+
Switch language (current: {language})
|
|
84
|
+
</button>
|
|
85
|
+
);
|
|
86
|
+
}
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
## Add more languages
|
|
90
|
+
|
|
91
|
+
Yes — you can support any language by adding the required JSON files to your locales folder. For example:
|
|
92
|
+
|
|
93
|
+
```
|
|
94
|
+
your-app/
|
|
95
|
+
src/
|
|
96
|
+
locales/
|
|
97
|
+
en/
|
|
98
|
+
app.json
|
|
99
|
+
nl/
|
|
100
|
+
app.json
|
|
101
|
+
anotherLang/
|
|
102
|
+
new.json
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
Then include them when initializing:
|
|
106
|
+
|
|
107
|
+
```tsx
|
|
108
|
+
import anotherLang from './locales/anotherLang/new.json';
|
|
109
|
+
|
|
110
|
+
initI18n({
|
|
111
|
+
ns: ['common', 'app', 'new'],
|
|
112
|
+
resources: {
|
|
113
|
+
anotherLang: { new: anotherLang }
|
|
114
|
+
},
|
|
115
|
+
supportedLngs: ['en', 'nl', 'anotherLang'],
|
|
116
|
+
defaultLanguage: 'en'
|
|
117
|
+
});
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
Notes:
|
|
121
|
+
|
|
122
|
+
- If you pass supportedLngs, it will override the default supported languages.
|
|
123
|
+
- defaultLanguage overrides the default fallback (which is English).
|
|
124
|
+
|
|
125
|
+
## API Reference
|
|
126
|
+
|
|
127
|
+
### initI18n(config?: I18nConfig)
|
|
128
|
+
|
|
129
|
+
Initialize i18next with the shared configuration.
|
|
130
|
+
|
|
131
|
+
Parameters:
|
|
132
|
+
|
|
133
|
+
- config.defaultNS — Default namespace (default: 'common')
|
|
134
|
+
- config.ns — Namespaces to load (default: ['common'])
|
|
135
|
+
- config.resources — App-specific translation resources
|
|
136
|
+
- config.supportedLngs — Optional list of supported language codes to enable
|
|
137
|
+
- config.defaultLanguage — Optional fallback language code
|
|
138
|
+
|
|
139
|
+
### useLanguage()
|
|
140
|
+
|
|
141
|
+
Hook for managing language preferences.
|
|
142
|
+
|
|
143
|
+
Returns:
|
|
144
|
+
|
|
145
|
+
- language — Current language code
|
|
146
|
+
- changeLanguage(lang) — Function to change language
|
|
147
|
+
- isChanging — Boolean indicating if language change is in progress
|
|
148
|
+
|
|
149
|
+
### useTranslation()
|
|
150
|
+
|
|
151
|
+
Re-exported from react-i18next. See official docs.
|
|
152
|
+
|
|
153
|
+
## Cookie and backend integration
|
|
154
|
+
|
|
155
|
+
- A preferredLanguage cookie is used to persist the selected language in the browser.
|
|
156
|
+
- If your environment provides an Identity API, updateUserLanguagePreference can sync the preference server-side. If not, the library still works fully client-side.
|
|
157
|
+
|
|
158
|
+
To point to a backend, optionally set:
|
|
159
|
+
|
|
160
|
+
```env
|
|
161
|
+
VITE_IDENTITY_API_URL=https://your-identity.example.com
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
## Built-in translations
|
|
165
|
+
|
|
166
|
+
This package ships with default English and Dutch common translations. You can ignore them and supply your own resources if preferred.
|
|
167
|
+
|
|
168
|
+
## License
|
|
169
|
+
|
|
170
|
+
MIT © ASafariM
|
package/demo/index.html
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
<!doctype html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="UTF-8" />
|
|
5
|
+
<link rel="icon" type="image/svg+xml" href="./public/favicon.svg" />
|
|
6
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
7
|
+
<title>@asafarim/shared-i18n Demo</title>
|
|
8
|
+
</head>
|
|
9
|
+
<body>
|
|
10
|
+
<div id="root"></div>
|
|
11
|
+
<script type="module" src="./src/main.tsx"></script>
|
|
12
|
+
</body>
|
|
13
|
+
</html>
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
#!/bin/sh
|
|
2
|
+
basedir=$(dirname "$(echo "$0" | sed -e 's,\\,/,g')")
|
|
3
|
+
|
|
4
|
+
case `uname` in
|
|
5
|
+
*CYGWIN*|*MINGW*|*MSYS*)
|
|
6
|
+
if command -v cygpath > /dev/null 2>&1; then
|
|
7
|
+
basedir=`cygpath -w "$basedir"`
|
|
8
|
+
fi
|
|
9
|
+
;;
|
|
10
|
+
esac
|
|
11
|
+
|
|
12
|
+
if [ -z "$NODE_PATH" ]; then
|
|
13
|
+
export NODE_PATH="/home/runner/work/shared-i18n/shared-i18n/node_modules/.pnpm/typescript@5.8.3/node_modules/typescript/bin/node_modules:/home/runner/work/shared-i18n/shared-i18n/node_modules/.pnpm/typescript@5.8.3/node_modules/typescript/node_modules:/home/runner/work/shared-i18n/shared-i18n/node_modules/.pnpm/typescript@5.8.3/node_modules:/home/runner/work/shared-i18n/shared-i18n/node_modules/.pnpm/node_modules"
|
|
14
|
+
else
|
|
15
|
+
export NODE_PATH="/home/runner/work/shared-i18n/shared-i18n/node_modules/.pnpm/typescript@5.8.3/node_modules/typescript/bin/node_modules:/home/runner/work/shared-i18n/shared-i18n/node_modules/.pnpm/typescript@5.8.3/node_modules/typescript/node_modules:/home/runner/work/shared-i18n/shared-i18n/node_modules/.pnpm/typescript@5.8.3/node_modules:/home/runner/work/shared-i18n/shared-i18n/node_modules/.pnpm/node_modules:$NODE_PATH"
|
|
16
|
+
fi
|
|
17
|
+
if [ -x "$basedir/node" ]; then
|
|
18
|
+
exec "$basedir/node" "$basedir/../typescript/bin/tsc" "$@"
|
|
19
|
+
else
|
|
20
|
+
exec node "$basedir/../typescript/bin/tsc" "$@"
|
|
21
|
+
fi
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
#!/bin/sh
|
|
2
|
+
basedir=$(dirname "$(echo "$0" | sed -e 's,\\,/,g')")
|
|
3
|
+
|
|
4
|
+
case `uname` in
|
|
5
|
+
*CYGWIN*|*MINGW*|*MSYS*)
|
|
6
|
+
if command -v cygpath > /dev/null 2>&1; then
|
|
7
|
+
basedir=`cygpath -w "$basedir"`
|
|
8
|
+
fi
|
|
9
|
+
;;
|
|
10
|
+
esac
|
|
11
|
+
|
|
12
|
+
if [ -z "$NODE_PATH" ]; then
|
|
13
|
+
export NODE_PATH="/home/runner/work/shared-i18n/shared-i18n/node_modules/.pnpm/typescript@5.8.3/node_modules/typescript/bin/node_modules:/home/runner/work/shared-i18n/shared-i18n/node_modules/.pnpm/typescript@5.8.3/node_modules/typescript/node_modules:/home/runner/work/shared-i18n/shared-i18n/node_modules/.pnpm/typescript@5.8.3/node_modules:/home/runner/work/shared-i18n/shared-i18n/node_modules/.pnpm/node_modules"
|
|
14
|
+
else
|
|
15
|
+
export NODE_PATH="/home/runner/work/shared-i18n/shared-i18n/node_modules/.pnpm/typescript@5.8.3/node_modules/typescript/bin/node_modules:/home/runner/work/shared-i18n/shared-i18n/node_modules/.pnpm/typescript@5.8.3/node_modules/typescript/node_modules:/home/runner/work/shared-i18n/shared-i18n/node_modules/.pnpm/typescript@5.8.3/node_modules:/home/runner/work/shared-i18n/shared-i18n/node_modules/.pnpm/node_modules:$NODE_PATH"
|
|
16
|
+
fi
|
|
17
|
+
if [ -x "$basedir/node" ]; then
|
|
18
|
+
exec "$basedir/node" "$basedir/../typescript/bin/tsserver" "$@"
|
|
19
|
+
else
|
|
20
|
+
exec node "$basedir/../typescript/bin/tsserver" "$@"
|
|
21
|
+
fi
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
#!/bin/sh
|
|
2
|
+
basedir=$(dirname "$(echo "$0" | sed -e 's,\\,/,g')")
|
|
3
|
+
|
|
4
|
+
case `uname` in
|
|
5
|
+
*CYGWIN*|*MINGW*|*MSYS*)
|
|
6
|
+
if command -v cygpath > /dev/null 2>&1; then
|
|
7
|
+
basedir=`cygpath -w "$basedir"`
|
|
8
|
+
fi
|
|
9
|
+
;;
|
|
10
|
+
esac
|
|
11
|
+
|
|
12
|
+
if [ -z "$NODE_PATH" ]; then
|
|
13
|
+
export NODE_PATH="/home/runner/work/shared-i18n/shared-i18n/node_modules/.pnpm/vite@6.4.1_@types+node@24.10.4/node_modules/vite/bin/node_modules:/home/runner/work/shared-i18n/shared-i18n/node_modules/.pnpm/vite@6.4.1_@types+node@24.10.4/node_modules/vite/node_modules:/home/runner/work/shared-i18n/shared-i18n/node_modules/.pnpm/vite@6.4.1_@types+node@24.10.4/node_modules:/home/runner/work/shared-i18n/shared-i18n/node_modules/.pnpm/node_modules"
|
|
14
|
+
else
|
|
15
|
+
export NODE_PATH="/home/runner/work/shared-i18n/shared-i18n/node_modules/.pnpm/vite@6.4.1_@types+node@24.10.4/node_modules/vite/bin/node_modules:/home/runner/work/shared-i18n/shared-i18n/node_modules/.pnpm/vite@6.4.1_@types+node@24.10.4/node_modules/vite/node_modules:/home/runner/work/shared-i18n/shared-i18n/node_modules/.pnpm/vite@6.4.1_@types+node@24.10.4/node_modules:/home/runner/work/shared-i18n/shared-i18n/node_modules/.pnpm/node_modules:$NODE_PATH"
|
|
16
|
+
fi
|
|
17
|
+
if [ -x "$basedir/node" ]; then
|
|
18
|
+
exec "$basedir/node" "$basedir/../vite/bin/vite.js" "$@"
|
|
19
|
+
else
|
|
20
|
+
exec node "$basedir/../vite/bin/vite.js" "$@"
|
|
21
|
+
fi
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "shared-i18n-demo",
|
|
3
|
+
"version": "0.6.0",
|
|
4
|
+
"private": true,
|
|
5
|
+
"type": "module",
|
|
6
|
+
"scripts": {
|
|
7
|
+
"dev": "vite",
|
|
8
|
+
"build": "tsc -b && vite build",
|
|
9
|
+
"preview": "vite preview"
|
|
10
|
+
},
|
|
11
|
+
"dependencies": {
|
|
12
|
+
"react": "^19.0.0",
|
|
13
|
+
"react-dom": "^19.0.0",
|
|
14
|
+
"@asafarim/shared-i18n": "workspace:*",
|
|
15
|
+
"@asafarim/design-tokens": "^0.5.0"
|
|
16
|
+
},
|
|
17
|
+
"devDependencies": {
|
|
18
|
+
"@types/react": "^19.0.0",
|
|
19
|
+
"@types/react-dom": "^19.0.0",
|
|
20
|
+
"@vitejs/plugin-react": "^4.3.0",
|
|
21
|
+
"typescript": "~5.8.3",
|
|
22
|
+
"vite": "^6.0.0"
|
|
23
|
+
}
|
|
24
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
<svg width="180" height="180" viewBox="0 0 180 180" fill="none" xmlns="http://www.w3.org/2000/svg">
|
|
2
|
+
<defs>
|
|
3
|
+
<linearGradient id="bg" x1="0%" y1="0%" x2="100%" y2="100%">
|
|
4
|
+
<stop offset="0%" stop-color="#3A5AFE" />
|
|
5
|
+
<stop offset="100%" stop-color="#14B8A6" />
|
|
6
|
+
</linearGradient>
|
|
7
|
+
<linearGradient id="accent" x1="0%" y1="0%" x2="0%" y2="100%">
|
|
8
|
+
<stop offset="0%" stop-color="#FFFFFF" stop-opacity="0.9" />
|
|
9
|
+
<stop offset="100%" stop-color="#FFFFFF" stop-opacity="0.2" />
|
|
10
|
+
</linearGradient>
|
|
11
|
+
<filter id="shadow" x="-20%" y="-20%" width="140%" height="140%" color-interpolation-filters="sRGB">
|
|
12
|
+
<feDropShadow dx="0" dy="8" stdDeviation="12" flood-color="#0B1220" flood-opacity="0.28" />
|
|
13
|
+
</filter>
|
|
14
|
+
</defs>
|
|
15
|
+
<rect width="180" height="180" rx="32" fill="url(#bg)" filter="url(#shadow)" />
|
|
16
|
+
<g transform="translate(45 45)">
|
|
17
|
+
<path d="M45 0C20.147 0 0 20.147 0 45C0 69.853 20.147 90 45 90C69.853 90 90 69.853 90 45C90 20.147 69.853 0 45 0ZM45 72C29.088 72 15.75 58.662 15.75 42.75C15.75 26.838 29.088 13.5 45 13.5C60.912 13.5 74.25 26.838 74.25 42.75C74.25 58.662 60.912 72 45 72Z" fill="white" opacity="0.2" />
|
|
18
|
+
<path d="M24 27H66C70.971 27 75 31.029 75 36V54C75 58.971 70.971 63 66 63H24C19.029 63 15 58.971 15 54V36C15 31.029 19.029 27 24 27Z" fill="url(#accent)" />
|
|
19
|
+
<path d="M36 54V45C36 40.029 40.029 36 45 36C49.971 36 54 40.029 54 45V54" stroke="#0B1220" stroke-width="4" stroke-linecap="round" />
|
|
20
|
+
<circle cx="45" cy="21" r="6" fill="white" />
|
|
21
|
+
<path d="M33 69H57" stroke="white" stroke-width="4" stroke-linecap="round" opacity="0.7" />
|
|
22
|
+
<path d="M27 15H63" stroke="white" stroke-width="4" stroke-linecap="round" opacity="0.4" />
|
|
23
|
+
</g>
|
|
24
|
+
</svg>
|
package/demo/src/App.tsx
ADDED
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
import { useState } from 'react'
|
|
2
|
+
import { useTranslation } from '@asafarim/shared-i18n'
|
|
3
|
+
import LanguageBar from './components/LanguageBar'
|
|
4
|
+
import Panel from './components/Panel'
|
|
5
|
+
import KeyTable from './components/KeyTable'
|
|
6
|
+
import StatusCard from './components/StatusCard'
|
|
7
|
+
import OverviewSection from './components/OverviewSection'
|
|
8
|
+
import GetStartedSection from './components/GetStartedSection'
|
|
9
|
+
import LanguageSwitcherDemo from './components/LanguageSwitcherDemo'
|
|
10
|
+
|
|
11
|
+
type TabType = 'overview' | 'getStarted' | 'demo'
|
|
12
|
+
|
|
13
|
+
export default function App() {
|
|
14
|
+
const { t } = useTranslation('demo')
|
|
15
|
+
const [activeTab, setActiveTab] = useState<TabType>('overview')
|
|
16
|
+
|
|
17
|
+
return (
|
|
18
|
+
<div className="app-container">
|
|
19
|
+
<header className="app-header">
|
|
20
|
+
<div className="header-content">
|
|
21
|
+
<h1>{t('title')}</h1>
|
|
22
|
+
<p className="subtitle">{t('subtitle')}</p>
|
|
23
|
+
</div>
|
|
24
|
+
<LanguageBar />
|
|
25
|
+
</header>
|
|
26
|
+
|
|
27
|
+
<main className="app-main">
|
|
28
|
+
<div className="nav-tabs">
|
|
29
|
+
<button
|
|
30
|
+
className={`nav-tab ${activeTab === 'overview' ? 'active' : ''}`}
|
|
31
|
+
onClick={() => setActiveTab('overview')}
|
|
32
|
+
>
|
|
33
|
+
Overview
|
|
34
|
+
</button>
|
|
35
|
+
<button
|
|
36
|
+
className={`nav-tab ${activeTab === 'getStarted' ? 'active' : ''}`}
|
|
37
|
+
onClick={() => setActiveTab('getStarted')}
|
|
38
|
+
>
|
|
39
|
+
Get Started
|
|
40
|
+
</button>
|
|
41
|
+
<button
|
|
42
|
+
className={`nav-tab ${activeTab === 'demo' ? 'active' : ''}`}
|
|
43
|
+
onClick={() => setActiveTab('demo')}
|
|
44
|
+
>
|
|
45
|
+
Demo
|
|
46
|
+
</button>
|
|
47
|
+
</div>
|
|
48
|
+
|
|
49
|
+
{activeTab === 'overview' && <OverviewSection />}
|
|
50
|
+
|
|
51
|
+
{activeTab === 'getStarted' && <GetStartedSection />}
|
|
52
|
+
|
|
53
|
+
{activeTab === 'demo' && (
|
|
54
|
+
<>
|
|
55
|
+
<Panel title="LanguageSwitcher Component">
|
|
56
|
+
<LanguageSwitcherDemo />
|
|
57
|
+
</Panel>
|
|
58
|
+
|
|
59
|
+
<Panel title="Translations">
|
|
60
|
+
<div className="panel-grid">
|
|
61
|
+
<div>
|
|
62
|
+
<h3>Common Namespace</h3>
|
|
63
|
+
<KeyTable
|
|
64
|
+
namespace="common"
|
|
65
|
+
keys={['welcome', 'language', 'apps.appName.identity', 'apps.description.identity']}
|
|
66
|
+
/>
|
|
67
|
+
</div>
|
|
68
|
+
<div>
|
|
69
|
+
<h3>Identity Portal Namespace</h3>
|
|
70
|
+
<KeyTable
|
|
71
|
+
namespace="identityPortal"
|
|
72
|
+
keys={[
|
|
73
|
+
'navbar.admin-area',
|
|
74
|
+
'navbar.auth.signIn',
|
|
75
|
+
'navbar.auth.signOut',
|
|
76
|
+
'dashboard.title',
|
|
77
|
+
'dashboard.user-management.title'
|
|
78
|
+
]}
|
|
79
|
+
/>
|
|
80
|
+
</div>
|
|
81
|
+
</div>
|
|
82
|
+
</Panel>
|
|
83
|
+
|
|
84
|
+
<Panel title="Interpolation & Trans Component">
|
|
85
|
+
<InterpolationDemo />
|
|
86
|
+
</Panel>
|
|
87
|
+
|
|
88
|
+
<StatusCard />
|
|
89
|
+
</>
|
|
90
|
+
)}
|
|
91
|
+
</main>
|
|
92
|
+
</div>
|
|
93
|
+
)
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
function InterpolationDemo() {
|
|
97
|
+
const { t, i18n } = useTranslation('identityPortal')
|
|
98
|
+
|
|
99
|
+
return (
|
|
100
|
+
<div className="interpolation-demo">
|
|
101
|
+
<div className="demo-item">
|
|
102
|
+
<h4>navbar.auth.welcome with interpolation</h4>
|
|
103
|
+
<p className="demo-output">
|
|
104
|
+
{t('navbar.auth.welcome', { userName: 'Ali' })}
|
|
105
|
+
</p>
|
|
106
|
+
</div>
|
|
107
|
+
<div className="demo-item">
|
|
108
|
+
<h4>Current language: {i18n.language}</h4>
|
|
109
|
+
</div>
|
|
110
|
+
</div>
|
|
111
|
+
)
|
|
112
|
+
}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { useState } from 'react'
|
|
2
|
+
import { useTranslation } from '@asafarim/shared-i18n'
|
|
3
|
+
|
|
4
|
+
export default function GetStartedSection() {
|
|
5
|
+
const { t } = useTranslation('demo')
|
|
6
|
+
const [expandedStep, setExpandedStep] = useState<number>(0)
|
|
7
|
+
const getStarted = t('getStarted', { returnObjects: true }) as any
|
|
8
|
+
|
|
9
|
+
return (
|
|
10
|
+
<div className="get-started-section">
|
|
11
|
+
<div className="gs-header">
|
|
12
|
+
<h2 className="gs-title">{getStarted.heading}</h2>
|
|
13
|
+
<p className="gs-intro">{getStarted.intro}</p>
|
|
14
|
+
</div>
|
|
15
|
+
|
|
16
|
+
<div className="steps-container">
|
|
17
|
+
{getStarted.steps.map((step: any, idx: number) => (
|
|
18
|
+
<div key={idx} className="step-item">
|
|
19
|
+
<button
|
|
20
|
+
className={`step-header ${expandedStep === idx ? 'expanded' : ''}`}
|
|
21
|
+
onClick={() => setExpandedStep(expandedStep === idx ? -1 : idx)}
|
|
22
|
+
>
|
|
23
|
+
<div className="step-number">{idx + 1}</div>
|
|
24
|
+
<div className="step-info">
|
|
25
|
+
<h3>{step.title}</h3>
|
|
26
|
+
<p>{step.description}</p>
|
|
27
|
+
</div>
|
|
28
|
+
<div className="step-toggle">
|
|
29
|
+
{expandedStep === idx ? '−' : '+'}
|
|
30
|
+
</div>
|
|
31
|
+
</button>
|
|
32
|
+
{expandedStep === idx && (
|
|
33
|
+
<div className="step-content">
|
|
34
|
+
<pre className="code-block">
|
|
35
|
+
<code>{step.code}</code>
|
|
36
|
+
</pre>
|
|
37
|
+
</div>
|
|
38
|
+
)}
|
|
39
|
+
</div>
|
|
40
|
+
))}
|
|
41
|
+
</div>
|
|
42
|
+
|
|
43
|
+
<div className="tips-section">
|
|
44
|
+
<div className="tips-icon">💡</div>
|
|
45
|
+
<div className="tips-content">
|
|
46
|
+
<h3>{getStarted.tips.title}</h3>
|
|
47
|
+
<ul className="tips-list">
|
|
48
|
+
{getStarted.tips.items.map((tip: string, idx: number) => (
|
|
49
|
+
<li key={idx}>{tip}</li>
|
|
50
|
+
))}
|
|
51
|
+
</ul>
|
|
52
|
+
</div>
|
|
53
|
+
</div>
|
|
54
|
+
</div>
|
|
55
|
+
)
|
|
56
|
+
}
|