@asafarim/shared-i18n 0.5.2 → 0.6.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/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
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { useTranslation } from '@asafarim/shared-i18n'
|
|
2
|
+
|
|
3
|
+
interface KeyTableProps {
|
|
4
|
+
namespace: string
|
|
5
|
+
keys: string[]
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export default function KeyTable({ namespace, keys }: KeyTableProps) {
|
|
9
|
+
const { t } = useTranslation(namespace)
|
|
10
|
+
|
|
11
|
+
return (
|
|
12
|
+
<table className="key-table">
|
|
13
|
+
<thead>
|
|
14
|
+
<tr>
|
|
15
|
+
<th>Key</th>
|
|
16
|
+
<th>Value</th>
|
|
17
|
+
</tr>
|
|
18
|
+
</thead>
|
|
19
|
+
<tbody>
|
|
20
|
+
{keys.map((key) => (
|
|
21
|
+
<tr key={key}>
|
|
22
|
+
<td>{key}</td>
|
|
23
|
+
<td>{t(key)}</td>
|
|
24
|
+
</tr>
|
|
25
|
+
))}
|
|
26
|
+
</tbody>
|
|
27
|
+
</table>
|
|
28
|
+
)
|
|
29
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { LanguageSwitcher } from '@asafarim/shared-i18n'
|
|
2
|
+
|
|
3
|
+
export default function LanguageBar() {
|
|
4
|
+
return (
|
|
5
|
+
<div className="language-bar">
|
|
6
|
+
<LanguageSwitcher
|
|
7
|
+
variant="icon-dropdown"
|
|
8
|
+
showIcon={true}
|
|
9
|
+
showLabel={false}
|
|
10
|
+
showEmoji={true}
|
|
11
|
+
className="language-switcher"
|
|
12
|
+
buttonClassName="language-switcher-button"
|
|
13
|
+
selectClassName="language-switcher-select"
|
|
14
|
+
// languages={undefined} // Use default languages
|
|
15
|
+
languages={["en", "nl", "fr"] as const}
|
|
16
|
+
/>
|
|
17
|
+
</div>
|
|
18
|
+
)
|
|
19
|
+
}
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
@import "@asafarim/design-tokens/css";
|
|
2
|
+
|
|
3
|
+
.container {
|
|
4
|
+
display: flex;
|
|
5
|
+
flex-direction: column;
|
|
6
|
+
gap: var(--asm-space-6);
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
.demoItem {
|
|
10
|
+
border: 1px solid var(--asm-color-border);
|
|
11
|
+
border-radius: var(--asm-radius-lg);
|
|
12
|
+
overflow: hidden;
|
|
13
|
+
background: var(--asm-color-surface);
|
|
14
|
+
transition: box-shadow var(--asm-motion-duration-fast) var(--asm-motion-easing-standard);
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
.demoItem:hover {
|
|
18
|
+
box-shadow: var(--asm-effect-shadow-md);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
.demoHeader {
|
|
22
|
+
padding: var(--asm-space-4);
|
|
23
|
+
border-bottom: 1px solid var(--asm-color-border);
|
|
24
|
+
background: var(--asm-color-surface-muted);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
.demoTitle {
|
|
28
|
+
margin: 0 0 var(--asm-space-2) 0;
|
|
29
|
+
font-size: var(--asm-font-size-sm);
|
|
30
|
+
font-weight: 600;
|
|
31
|
+
color: var(--asm-color-text);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
.demoDescription {
|
|
35
|
+
margin: 0;
|
|
36
|
+
font-size: var(--asm-font-size-xs);
|
|
37
|
+
color: var(--asm-color-text-muted);
|
|
38
|
+
line-height: 1.5;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
.demoContent {
|
|
42
|
+
display: grid;
|
|
43
|
+
grid-template-columns: 1fr 1fr;
|
|
44
|
+
gap: 0;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
.demoPreview {
|
|
48
|
+
padding: var(--asm-space-4);
|
|
49
|
+
border-right: 1px solid var(--asm-color-border);
|
|
50
|
+
display: flex;
|
|
51
|
+
flex-direction: column;
|
|
52
|
+
gap: var(--asm-space-3);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
.demoCode {
|
|
56
|
+
padding: var(--asm-space-4);
|
|
57
|
+
display: flex;
|
|
58
|
+
flex-direction: column;
|
|
59
|
+
gap: var(--asm-space-3);
|
|
60
|
+
background: #fafafa;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
.previewLabel,
|
|
64
|
+
.codeLabel {
|
|
65
|
+
font-size: var(--asm-font-size-xs);
|
|
66
|
+
font-weight: 600;
|
|
67
|
+
color: var(--asm-color-text-muted);
|
|
68
|
+
text-transform: uppercase;
|
|
69
|
+
letter-spacing: 0.5px;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
.codeBlock {
|
|
73
|
+
margin: 0;
|
|
74
|
+
padding: var(--asm-space-3);
|
|
75
|
+
background: var(--asm-color-surface);
|
|
76
|
+
border: 1px solid var(--asm-color-border);
|
|
77
|
+
border-radius: var(--asm-radius-md);
|
|
78
|
+
font-size: 0.8rem;
|
|
79
|
+
line-height: 1.6;
|
|
80
|
+
color: var(--asm-color-text);
|
|
81
|
+
font-family: 'Monaco', 'Menlo', 'Ubuntu Mono', monospace;
|
|
82
|
+
overflow-x: auto;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
@media (max-width: 1024px) {
|
|
86
|
+
.demoContent {
|
|
87
|
+
grid-template-columns: 1fr;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
.demoPreview {
|
|
91
|
+
border-right: none;
|
|
92
|
+
border-bottom: 1px solid var(--asm-color-border);
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
@media (max-width: 640px) {
|
|
97
|
+
.demoItem {
|
|
98
|
+
border-radius: var(--asm-radius-md);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
.demoHeader {
|
|
102
|
+
padding: var(--asm-space-3);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
.demoPreview,
|
|
106
|
+
.demoCode {
|
|
107
|
+
padding: var(--asm-space-3);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
.codeBlock {
|
|
111
|
+
font-size: 0.75rem;
|
|
112
|
+
}
|
|
113
|
+
}
|
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
import { LanguageSwitcher } from '@asafarim/shared-i18n'
|
|
2
|
+
import styles from './LanguageSwitcherDemo.module.css'
|
|
3
|
+
|
|
4
|
+
interface DemoItemProps {
|
|
5
|
+
title: string
|
|
6
|
+
description: string
|
|
7
|
+
code: string
|
|
8
|
+
children: React.ReactNode
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
function DemoItem({ title, description, code, children }: DemoItemProps) {
|
|
12
|
+
return (
|
|
13
|
+
<div className={styles.demoItem}>
|
|
14
|
+
<div className={styles.demoHeader}>
|
|
15
|
+
<h4 className={styles.demoTitle}>{title}</h4>
|
|
16
|
+
<p className={styles.demoDescription}>{description}</p>
|
|
17
|
+
</div>
|
|
18
|
+
<div className={styles.demoContent}>
|
|
19
|
+
<div className={styles.demoPreview}>
|
|
20
|
+
<div className={styles.previewLabel}>Preview</div>
|
|
21
|
+
{children}
|
|
22
|
+
</div>
|
|
23
|
+
<div className={styles.demoCode}>
|
|
24
|
+
<div className={styles.codeLabel}>Code</div>
|
|
25
|
+
<pre className={styles.codeBlock}>{code}</pre>
|
|
26
|
+
</div>
|
|
27
|
+
</div>
|
|
28
|
+
</div>
|
|
29
|
+
)
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export default function LanguageSwitcherDemo() {
|
|
33
|
+
return (
|
|
34
|
+
<div className={styles.container}>
|
|
35
|
+
<DemoItem
|
|
36
|
+
title="Buttons Variant"
|
|
37
|
+
description="Display all languages as individual button options"
|
|
38
|
+
code={`<LanguageSwitcher
|
|
39
|
+
variant="buttons"
|
|
40
|
+
buttonClassName="lang-button"
|
|
41
|
+
unstyled={false}
|
|
42
|
+
/>`}
|
|
43
|
+
>
|
|
44
|
+
<LanguageSwitcher
|
|
45
|
+
variant="buttons"
|
|
46
|
+
buttonClassName="lang-button"
|
|
47
|
+
unstyled={false}
|
|
48
|
+
/>
|
|
49
|
+
</DemoItem>
|
|
50
|
+
|
|
51
|
+
<DemoItem
|
|
52
|
+
title="Select Dropdown - Text Only"
|
|
53
|
+
description="Dropdown with language names, no emojis"
|
|
54
|
+
code={`<LanguageSwitcher
|
|
55
|
+
variant="select"
|
|
56
|
+
selectClassName="lang-select"
|
|
57
|
+
unstyled={false}
|
|
58
|
+
showEmoji={false}
|
|
59
|
+
/>`}
|
|
60
|
+
>
|
|
61
|
+
<LanguageSwitcher
|
|
62
|
+
variant="select"
|
|
63
|
+
selectClassName="lang-select"
|
|
64
|
+
unstyled={false}
|
|
65
|
+
showEmoji={false}
|
|
66
|
+
/>
|
|
67
|
+
</DemoItem>
|
|
68
|
+
|
|
69
|
+
<DemoItem
|
|
70
|
+
title="Select Dropdown - Emoji Only (2 Languages)"
|
|
71
|
+
description="Compact dropdown showing only emojis for language selection"
|
|
72
|
+
code={`<LanguageSwitcher
|
|
73
|
+
variant="select"
|
|
74
|
+
selectClassName="lang-select"
|
|
75
|
+
unstyled={true}
|
|
76
|
+
showEmoji={true}
|
|
77
|
+
languages={['en', 'nl']}
|
|
78
|
+
isToggler={false}
|
|
79
|
+
/>`}
|
|
80
|
+
>
|
|
81
|
+
<LanguageSwitcher
|
|
82
|
+
variant="select"
|
|
83
|
+
selectClassName="lang-select"
|
|
84
|
+
unstyled={true}
|
|
85
|
+
showEmoji={true}
|
|
86
|
+
languages={['en', 'nl']}
|
|
87
|
+
isToggler={false}
|
|
88
|
+
/>
|
|
89
|
+
</DemoItem>
|
|
90
|
+
|
|
91
|
+
<DemoItem
|
|
92
|
+
title="Toggle Button (2 Languages)"
|
|
93
|
+
description="Automatic toggle button when exactly 2 languages are provided"
|
|
94
|
+
code={`<LanguageSwitcher
|
|
95
|
+
variant="select"
|
|
96
|
+
selectClassName="lang-select"
|
|
97
|
+
unstyled={true}
|
|
98
|
+
showEmoji={true}
|
|
99
|
+
languages={['en', 'nl']}
|
|
100
|
+
isToggler={true}
|
|
101
|
+
/>`}
|
|
102
|
+
>
|
|
103
|
+
<LanguageSwitcher
|
|
104
|
+
variant="select"
|
|
105
|
+
selectClassName="lang-select"
|
|
106
|
+
unstyled={true}
|
|
107
|
+
showEmoji={true}
|
|
108
|
+
languages={['en', 'nl']}
|
|
109
|
+
isToggler={true}
|
|
110
|
+
/>
|
|
111
|
+
</DemoItem>
|
|
112
|
+
|
|
113
|
+
<DemoItem
|
|
114
|
+
title="Icon Dropdown"
|
|
115
|
+
description="Minimal icon-based dropdown for all languages"
|
|
116
|
+
code={`<LanguageSwitcher
|
|
117
|
+
variant="icon-dropdown"
|
|
118
|
+
unstyled={false}
|
|
119
|
+
/>`}
|
|
120
|
+
>
|
|
121
|
+
<LanguageSwitcher
|
|
122
|
+
variant="icon-dropdown"
|
|
123
|
+
unstyled={false}
|
|
124
|
+
/>
|
|
125
|
+
</DemoItem>
|
|
126
|
+
|
|
127
|
+
<DemoItem
|
|
128
|
+
title="Icon Dropdown - Limited Languages"
|
|
129
|
+
description="Icon dropdown restricted to specific languages"
|
|
130
|
+
code={`<LanguageSwitcher
|
|
131
|
+
variant="icon-dropdown"
|
|
132
|
+
languages={['en', 'nl']}
|
|
133
|
+
unstyled={false}
|
|
134
|
+
/>`}
|
|
135
|
+
>
|
|
136
|
+
<LanguageSwitcher
|
|
137
|
+
variant="icon-dropdown"
|
|
138
|
+
languages={['en', 'nl']}
|
|
139
|
+
unstyled={false}
|
|
140
|
+
/>
|
|
141
|
+
</DemoItem>
|
|
142
|
+
|
|
143
|
+
<DemoItem
|
|
144
|
+
title="Select Dropdown - Text Only (2 Languages)"
|
|
145
|
+
description="Standard dropdown with text labels for 2 languages"
|
|
146
|
+
code={`<LanguageSwitcher
|
|
147
|
+
variant="select"
|
|
148
|
+
selectClassName="lang-select"
|
|
149
|
+
unstyled={false}
|
|
150
|
+
showEmoji={false}
|
|
151
|
+
languages={['en', 'nl']}
|
|
152
|
+
isToggler={false}
|
|
153
|
+
/>`}
|
|
154
|
+
>
|
|
155
|
+
<LanguageSwitcher
|
|
156
|
+
variant="select"
|
|
157
|
+
selectClassName="lang-select"
|
|
158
|
+
unstyled={false}
|
|
159
|
+
showEmoji={false}
|
|
160
|
+
languages={['en', 'nl']}
|
|
161
|
+
isToggler={false}
|
|
162
|
+
/>
|
|
163
|
+
</DemoItem>
|
|
164
|
+
|
|
165
|
+
<DemoItem
|
|
166
|
+
title="Buttons Variant - Limited Languages"
|
|
167
|
+
description="Button variant restricted to specific languages"
|
|
168
|
+
code={`<LanguageSwitcher
|
|
169
|
+
variant="buttons"
|
|
170
|
+
buttonClassName="lang-button"
|
|
171
|
+
unstyled={false}
|
|
172
|
+
languages={['en', 'nl']}
|
|
173
|
+
/>`}
|
|
174
|
+
>
|
|
175
|
+
<LanguageSwitcher
|
|
176
|
+
variant="buttons"
|
|
177
|
+
buttonClassName="lang-button"
|
|
178
|
+
unstyled={false}
|
|
179
|
+
languages={['en', 'nl']}
|
|
180
|
+
/>
|
|
181
|
+
</DemoItem>
|
|
182
|
+
</div>
|
|
183
|
+
)
|
|
184
|
+
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { useTranslation } from '@asafarim/shared-i18n'
|
|
2
|
+
|
|
3
|
+
export default function OverviewSection() {
|
|
4
|
+
const { t } = useTranslation('demo')
|
|
5
|
+
const overview = t('overview', { returnObjects: true }) as any
|
|
6
|
+
|
|
7
|
+
return (
|
|
8
|
+
<div className="overview-section">
|
|
9
|
+
<div className="overview-header">
|
|
10
|
+
<h2 className="overview-title">{overview.heading}</h2>
|
|
11
|
+
<p className="overview-description">{overview.description}</p>
|
|
12
|
+
</div>
|
|
13
|
+
|
|
14
|
+
<div className="overview-grid">
|
|
15
|
+
<div className="feature-card">
|
|
16
|
+
<div className="feature-icon">✨</div>
|
|
17
|
+
<h3>{overview.features.title}</h3>
|
|
18
|
+
<ul className="feature-list">
|
|
19
|
+
{overview.features.items.map((item: string, idx: number) => (
|
|
20
|
+
<li key={idx}>
|
|
21
|
+
<span className="feature-dot">•</span>
|
|
22
|
+
{item}
|
|
23
|
+
</li>
|
|
24
|
+
))}
|
|
25
|
+
</ul>
|
|
26
|
+
</div>
|
|
27
|
+
|
|
28
|
+
<div className="feature-card">
|
|
29
|
+
<div className="feature-icon">🎯</div>
|
|
30
|
+
<h3>{overview.useCases.title}</h3>
|
|
31
|
+
<ul className="feature-list">
|
|
32
|
+
{overview.useCases.items.map((item: string, idx: number) => (
|
|
33
|
+
<li key={idx}>
|
|
34
|
+
<span className="feature-dot">→</span>
|
|
35
|
+
{item}
|
|
36
|
+
</li>
|
|
37
|
+
))}
|
|
38
|
+
</ul>
|
|
39
|
+
</div>
|
|
40
|
+
</div>
|
|
41
|
+
</div>
|
|
42
|
+
)
|
|
43
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { ReactNode } from 'react'
|
|
2
|
+
|
|
3
|
+
interface PanelProps {
|
|
4
|
+
title: string
|
|
5
|
+
children: ReactNode
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export default function Panel({ title, children }: PanelProps) {
|
|
9
|
+
return (
|
|
10
|
+
<div className="panel">
|
|
11
|
+
<h2 className="panel-title">{title}</h2>
|
|
12
|
+
{children}
|
|
13
|
+
</div>
|
|
14
|
+
)
|
|
15
|
+
}
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
import { useState, useEffect } from 'react'
|
|
2
|
+
import {
|
|
3
|
+
useTranslation,
|
|
4
|
+
SUPPORTED_LANGUAGES,
|
|
5
|
+
LANGUAGE_NAMES,
|
|
6
|
+
LANGUAGE_COOKIE_NAME,
|
|
7
|
+
getLanguageFromCookie,
|
|
8
|
+
getInitialLanguage,
|
|
9
|
+
updateUserLanguagePreference,
|
|
10
|
+
isSupportedLanguage
|
|
11
|
+
} from '@asafarim/shared-i18n'
|
|
12
|
+
import Panel from './Panel'
|
|
13
|
+
|
|
14
|
+
export default function StatusCard() {
|
|
15
|
+
const { t, i18n } = useTranslation('demo')
|
|
16
|
+
const [cookieValue, setCookieValue] = useState<string | null>(null)
|
|
17
|
+
const [initialLang, setInitialLang] = useState<string>('')
|
|
18
|
+
const [syncResult, setSyncResult] = useState<{ type: 'success' | 'error'; message: string } | null>(null)
|
|
19
|
+
const [isSyncing, setIsSyncing] = useState(false)
|
|
20
|
+
|
|
21
|
+
useEffect(() => {
|
|
22
|
+
setCookieValue(getLanguageFromCookie())
|
|
23
|
+
setInitialLang(getInitialLanguage())
|
|
24
|
+
}, [i18n.language])
|
|
25
|
+
|
|
26
|
+
const backendUrl = import.meta.env.VITE_IDENTITY_API_URL || 'not set'
|
|
27
|
+
|
|
28
|
+
const handleSync = async () => {
|
|
29
|
+
setIsSyncing(true)
|
|
30
|
+
setSyncResult(null)
|
|
31
|
+
try {
|
|
32
|
+
const currentLanguage = i18n.language
|
|
33
|
+
if (isSupportedLanguage(currentLanguage)) {
|
|
34
|
+
await updateUserLanguagePreference(currentLanguage)
|
|
35
|
+
} else {
|
|
36
|
+
throw new Error(`Unsupported language: ${currentLanguage}`)
|
|
37
|
+
}
|
|
38
|
+
setSyncResult({
|
|
39
|
+
type: 'success',
|
|
40
|
+
message: t('status.resultOk')
|
|
41
|
+
})
|
|
42
|
+
} catch (error) {
|
|
43
|
+
setSyncResult({
|
|
44
|
+
type: 'error',
|
|
45
|
+
message: `${t('status.resultFail')}: ${error instanceof Error ? error.message : 'Unknown error'}`
|
|
46
|
+
})
|
|
47
|
+
} finally {
|
|
48
|
+
setIsSyncing(false)
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
return (
|
|
53
|
+
<Panel title={t('status.heading')}>
|
|
54
|
+
<div className="status-grid">
|
|
55
|
+
<div className="status-card">
|
|
56
|
+
<h4>Current Language</h4>
|
|
57
|
+
<p>{i18n.language}</p>
|
|
58
|
+
</div>
|
|
59
|
+
|
|
60
|
+
<div className="status-card">
|
|
61
|
+
<h4>{t('status.cookie')}</h4>
|
|
62
|
+
<p>
|
|
63
|
+
<strong>Name:</strong> {LANGUAGE_COOKIE_NAME}
|
|
64
|
+
</p>
|
|
65
|
+
<p>
|
|
66
|
+
<strong>Value:</strong> {cookieValue || '(not set)'}
|
|
67
|
+
</p>
|
|
68
|
+
</div>
|
|
69
|
+
|
|
70
|
+
<div className="status-card">
|
|
71
|
+
<h4>Initial Language</h4>
|
|
72
|
+
<p>{initialLang}</p>
|
|
73
|
+
</div>
|
|
74
|
+
|
|
75
|
+
<div className="status-card">
|
|
76
|
+
<h4>Supported Languages</h4>
|
|
77
|
+
<ul className="status-list">
|
|
78
|
+
{SUPPORTED_LANGUAGES.map((lang: string) => (
|
|
79
|
+
<li key={lang}>
|
|
80
|
+
{lang}: {LANGUAGE_NAMES[lang as keyof typeof LANGUAGE_NAMES]}
|
|
81
|
+
</li>
|
|
82
|
+
))}
|
|
83
|
+
</ul>
|
|
84
|
+
</div>
|
|
85
|
+
|
|
86
|
+
<div className="status-card">
|
|
87
|
+
<h4>{t('status.backend')}</h4>
|
|
88
|
+
<p>{backendUrl}</p>
|
|
89
|
+
</div>
|
|
90
|
+
|
|
91
|
+
<div className="status-card">
|
|
92
|
+
<h4>Backend Sync</h4>
|
|
93
|
+
<button
|
|
94
|
+
className="sync-button"
|
|
95
|
+
onClick={handleSync}
|
|
96
|
+
disabled={isSyncing}
|
|
97
|
+
>
|
|
98
|
+
{isSyncing ? 'Syncing...' : t('status.simulate')}
|
|
99
|
+
</button>
|
|
100
|
+
{syncResult && (
|
|
101
|
+
<div className={`sync-result ${syncResult.type}`}>
|
|
102
|
+
{syncResult.message}
|
|
103
|
+
</div>
|
|
104
|
+
)}
|
|
105
|
+
</div>
|
|
106
|
+
</div>
|
|
107
|
+
</Panel>
|
|
108
|
+
)
|
|
109
|
+
}
|