xertica-ui 2.3.0 → 2.4.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/CHANGELOG.md +22 -2
- package/README.md +33 -22
- package/bin/cli.ts +136 -47
- package/bin/language-config.ts +5 -8
- package/components/assistant/modern-chat-input/ModernChatInput.tsx +17 -7
- package/components/assistant/xertica-assistant/parts/AssistantConversationList.tsx +1 -3
- package/components/assistant/xertica-assistant/parts/AssistantFeedbackDialog.tsx +13 -3
- package/components/assistant/xertica-assistant/parts/AssistantMessageBubble.tsx +10 -6
- package/components/assistant/xertica-assistant/xertica-assistant.tsx +1 -3
- package/components/blocks/card-patterns/FeatureCardSkeleton.tsx +1 -6
- package/components/blocks/card-patterns/ProfileCard.tsx +1 -3
- package/components/blocks/card-patterns/ProjectCardSkeleton.tsx +1 -6
- package/components/brand/language-selector/language-selector.stories.tsx +1 -4
- package/components/brand/theme-toggle/ThemeToggle.tsx +5 -1
- package/components/brand/xertica-provider/XerticaProvider.tsx +1 -4
- package/components/index.ts +1 -5
- package/components/layout/sidebar/sidebar.tsx +9 -3
- package/components/media/audio-player/AudioPlayer.tsx +4 -2
- package/components/pages/forgot-password-page/ForgotPasswordPage.tsx +188 -188
- package/components/pages/home-content/HomeContent.tsx +55 -55
- package/components/pages/home-page/HomePage.tsx +5 -1
- package/components/pages/login-page/LoginPage.tsx +4 -2
- package/components/pages/reset-password-page/ResetPasswordPage.tsx +7 -3
- package/components/pages/template-content/TemplateContent.tsx +268 -149
- package/components/pages/verify-email-page/VerifyEmailPage.tsx +9 -9
- package/components/shared/error-boundary.stories.tsx +114 -132
- package/components/shared/error-boundary.tsx +150 -154
- package/components/shared/error-fallbacks.tsx +222 -226
- package/components/ui/stats-card/stats-card-skeleton.tsx +1 -3
- package/components/ui/stats-card/stats-card.stories.tsx +18 -0
- package/components/ui/stats-card/stats-card.tsx +18 -2
- package/components.json +512 -892
- package/contexts/AuthContext.tsx +121 -118
- package/contexts/LanguageContext.tsx +1 -2
- package/dist/AssistantChart-BKVtGUKF.js +3383 -0
- package/dist/AssistantChart-WeycT5Pd.cjs +3551 -0
- package/dist/VerifyEmailPage-Bp1XXl3H.cjs +3305 -0
- package/dist/VerifyEmailPage-DGhuIqkb.js +3296 -0
- package/dist/XerticaProvider-BErr83Bg.js +42 -0
- package/dist/XerticaProvider-CwOkHxiT.cjs +44 -0
- package/dist/XerticaXLogo-BX3ueACh.js +255 -0
- package/dist/XerticaXLogo-qBPhwK3g.cjs +260 -0
- package/dist/assistant.cjs.js +1 -1
- package/dist/assistant.es.js +1 -1
- package/dist/brand.cjs.js +2 -2
- package/dist/brand.es.js +2 -2
- package/dist/cli.js +90 -37
- package/dist/components/brand/theme-toggle/ThemeToggle.d.ts +1 -1
- package/dist/components/index.d.ts +1 -1
- package/dist/components/ui/stats-card/stats-card.d.ts +10 -0
- package/dist/index.cjs.js +6 -6
- package/dist/index.es.js +6 -6
- package/dist/layout.cjs.js +1 -1
- package/dist/layout.es.js +1 -1
- package/dist/pages.cjs.js +1 -1
- package/dist/pages.es.js +1 -1
- package/dist/sidebar-B4ZWaMrE.js +792 -0
- package/dist/sidebar-BS1p2V7t.cjs +795 -0
- package/dist/ui.cjs.js +1 -1
- package/dist/ui.es.js +1 -1
- package/dist/xertica-assistant-B1NaSFFj.js +2173 -0
- package/dist/xertica-assistant-CIaUlbIt.cjs +2180 -0
- package/dist/xertica-ui.css +1 -1
- package/docs/architecture-improvements.md +5 -5
- package/docs/architecture.md +16 -10
- package/docs/components/card-patterns.md +19 -17
- package/docs/components/error-boundary.md +201 -191
- package/docs/components/hooks.md +15 -13
- package/docs/components/language-selector.md +20 -16
- package/docs/components/pages.md +323 -309
- package/docs/components/stats-card.md +20 -2
- package/docs/doc-audit.md +12 -11
- package/docs/getting-started.md +41 -28
- package/docs/guidelines.md +14 -12
- package/docs/i18n.md +61 -57
- package/docs/installation.md +268 -267
- package/docs/llms.md +17 -17
- package/docs/state-management.md +17 -17
- package/guidelines/Guidelines.md +17 -14
- package/llms-compact.txt +1 -1
- package/llms-full.txt +11553 -7133
- package/llms.txt +1 -1
- package/package.json +1 -1
- package/styles/xertica/base.css +90 -84
- package/templates/CLAUDE.md +16 -1
- package/templates/guidelines/Guidelines.md +42 -18
- package/templates/package.json +3 -3
- package/templates/src/app/components/AuthGuard.tsx +131 -82
- package/templates/src/features/auth/ui/AuthPageShell.tsx +32 -32
- package/templates/src/features/auth/ui/ForgotPasswordContent.tsx +1 -3
- package/templates/src/features/auth/ui/ResetPasswordContent.tsx +6 -2
- package/templates/src/features/auth/ui/VerifyEmailContent.tsx +2 -6
- package/templates/src/features/home/data/mock.ts +41 -35
- package/templates/src/features/home/ui/HomeContent.tsx +62 -64
- package/templates/src/features/template/ui/CrudTemplate.tsx +1 -4
- package/templates/src/features/template/ui/LoginTemplate.tsx +1 -1
- package/templates/src/features/template/ui/TemplateContent.tsx +28 -20
- package/templates/src/locales/en/pages/templates.json +17 -17
- package/templates/src/locales/es/pages/templates.json +17 -17
- package/templates/src/locales/pt-BR/pages/templates.json +17 -17
- package/templates/src/pages/AssistantPage.tsx +26 -20
- package/templates/src/pages/HomePage.tsx +5 -1
- package/templates/src/shared/error-boundary.tsx +150 -154
- package/templates/src/shared/error-fallbacks.tsx +222 -226
- package/templates/vite.config.ts +12 -9
|
@@ -29,7 +29,9 @@ Built on top of `<Card>`. Use it for dashboard stat widgets where a consistent m
|
|
|
29
29
|
| `description` | `string` | No | Additional supplementary text below the value |
|
|
30
30
|
| `trend` | `{ value: number; label?: string }` | No | Trend object. `value` is a number (positive = up, negative = down). `label` overrides `description` text. |
|
|
31
31
|
| `icon` | `ReactNode` | No | Icon from `lucide-react` |
|
|
32
|
-
| `
|
|
32
|
+
| `iconColor` | `string` | No | Tailwind class for the icon text/foreground color (defaults to `text-muted-foreground`) |
|
|
33
|
+
| `iconBg` | `string` | No | Tailwind class for the icon background color (defaults to `bg-muted`) |
|
|
34
|
+
| `className` | `string` | No | Additional CSS classes |
|
|
33
35
|
|
|
34
36
|
### trend
|
|
35
37
|
|
|
@@ -59,6 +61,22 @@ import { DollarSign } from 'lucide-react';
|
|
|
59
61
|
/>;
|
|
60
62
|
```
|
|
61
63
|
|
|
64
|
+
### Custom Icon Colors
|
|
65
|
+
|
|
66
|
+
```tsx
|
|
67
|
+
import { StatsCard } from 'xertica-ui/ui';
|
|
68
|
+
import { Users } from 'lucide-react';
|
|
69
|
+
|
|
70
|
+
<StatsCard
|
|
71
|
+
title="Active Users"
|
|
72
|
+
value="2,350"
|
|
73
|
+
trend={{ value: 8.2, label: 'new this week' }}
|
|
74
|
+
icon={<Users className="size-5" />}
|
|
75
|
+
iconBg="bg-primary/10"
|
|
76
|
+
iconColor="text-primary"
|
|
77
|
+
/>;
|
|
78
|
+
```
|
|
79
|
+
|
|
62
80
|
### Negative Trend
|
|
63
81
|
|
|
64
82
|
```tsx
|
|
@@ -108,7 +126,7 @@ import { DollarSign } from 'lucide-react';
|
|
|
108
126
|
- `trend.value` is a **numeric percentage** (e.g., `20.1` for +20.1%) — the component displays `Math.abs(value)%` automatically.
|
|
109
127
|
- Do NOT pre-format the trend value as a string — pass the raw number.
|
|
110
128
|
- `value` IS pre-formatted as a display string: `"$45,231"`, `"3.6%"`, `"2,350"`.
|
|
111
|
-
- Icons render inside a
|
|
129
|
+
- Icons render inside a rounded background. By default, background is `bg-muted` and color is `text-muted-foreground`, but they can be customized using `iconBg` and `iconColor` props. Use `size-5` for the icon.
|
|
112
130
|
- Always use in a 4-column responsive grid for dashboard overviews.
|
|
113
131
|
- `trend.label` takes precedence over `description` for below-value text.
|
|
114
132
|
|
package/docs/doc-audit.md
CHANGED
|
@@ -4,19 +4,20 @@
|
|
|
4
4
|
>
|
|
5
5
|
> Antes de seguir qualquer recomendação aqui, verifique se a documentação correspondente já existe em `docs/components/`. A inventário real abaixo foi atualizado para refletir o estado em 2026-05-21:
|
|
6
6
|
>
|
|
7
|
-
> | Categoria
|
|
8
|
-
>
|
|
9
|
-
> | Componentes UI (`components/ui/`)
|
|
10
|
-
> | Componentes Assistant (`components/assistant/`) | 5
|
|
11
|
-
> | Componentes Brand (`components/brand/`)
|
|
12
|
-
> | Componentes Layout (`components/layout/`)
|
|
13
|
-
> | Componentes Media (`components/media/`)
|
|
14
|
-
> | Componentes Blocks (`components/blocks/`)
|
|
15
|
-
> | Páginas (`components/pages/`)
|
|
16
|
-
> | Hooks/Contextos
|
|
17
|
-
> | Utilitários (`components/figma/`)
|
|
7
|
+
> | Categoria | Total | Com Doc | Sem Doc |
|
|
8
|
+
> | ----------------------------------------------- | --------------------- | ------------------------------------------ | ------- |
|
|
9
|
+
> | Componentes UI (`components/ui/`) | 65 | 65 | 0 |
|
|
10
|
+
> | Componentes Assistant (`components/assistant/`) | 5 | 5 | 0 |
|
|
11
|
+
> | Componentes Brand (`components/brand/`) | 7 | 7 | 0 |
|
|
12
|
+
> | Componentes Layout (`components/layout/`) | 2 | 2 | 0 |
|
|
13
|
+
> | Componentes Media (`components/media/`) | 3 | 3 | 0 |
|
|
14
|
+
> | Componentes Blocks (`components/blocks/`) | 6 cards + 6 skeletons | card-patterns.md (cobre cards + skeletons) | 0 |
|
|
15
|
+
> | Páginas (`components/pages/`) | 8 | pages.md (cobertura agregada) | 0 |
|
|
16
|
+
> | Hooks/Contextos | 9 hooks | hooks.md (cobertura agregada) | 0 |
|
|
17
|
+
> | Utilitários (`components/figma/`) | 1 | image-with-fallback.md | 0 |
|
|
18
18
|
>
|
|
19
19
|
> **Lacunas ainda em aberto (verificar antes de fechar):**
|
|
20
|
+
>
|
|
20
21
|
> - Storybook `.mdx` para os 6 card skeletons (existe `.md`, mas não `.mdx`)
|
|
21
22
|
> - `lib/query-client.ts` — singleton não documentado externamente (intencionalmente interno)
|
|
22
23
|
>
|
package/docs/getting-started.md
CHANGED
|
@@ -34,13 +34,14 @@ npx xertica-ui@latest init
|
|
|
34
34
|
|
|
35
35
|
The CLI walks you through an interactive setup:
|
|
36
36
|
|
|
37
|
-
| Prompt
|
|
38
|
-
|
|
39
|
-
| Pages to include
|
|
40
|
-
| Languages to support
|
|
41
|
-
| Default color theme
|
|
42
|
-
| **
|
|
43
|
-
|
|
|
37
|
+
| Prompt | Choices |
|
|
38
|
+
| ----------------------------- | ---------------------------------------------- |
|
|
39
|
+
| Pages to include | Login, Home, Template (multi-select) |
|
|
40
|
+
| Languages to support | pt-BR, English, Español (multi-select, min 1) |
|
|
41
|
+
| Default color theme | Xertica, Slate, Blue, Violet, Rose, Emerald, … |
|
|
42
|
+
| **Enable dark mode support?** | yes (default) / no |
|
|
43
|
+
| **Include AI Assistant** | yes (default) / no |
|
|
44
|
+
| Install dependencies | yes / no |
|
|
44
45
|
|
|
45
46
|
This command:
|
|
46
47
|
|
|
@@ -50,7 +51,7 @@ This command:
|
|
|
50
51
|
- Injects the theme token system (`tokens.css`)
|
|
51
52
|
- Copies only the locale files for the selected languages
|
|
52
53
|
- Conditionally scaffolds the AI Assistant (`AssistantPage`, `features/assistant/`) based on your answer
|
|
53
|
-
- Persists your choices in `src/locales/.languages.json` and `.xertica.json`
|
|
54
|
+
- Persists your choices in `src/locales/.languages.json` and `.xertica.json` (e.g. `hasAssistant`, `disableDarkMode`)
|
|
54
55
|
|
|
55
56
|
### Step 1b — Update an Existing Project (CLI)
|
|
56
57
|
|
|
@@ -62,12 +63,13 @@ npx xertica-ui@latest update
|
|
|
62
63
|
|
|
63
64
|
The menu offers:
|
|
64
65
|
|
|
65
|
-
| Option
|
|
66
|
-
|
|
67
|
-
| **Theme only**
|
|
68
|
-
| **Languages**
|
|
69
|
-
| **
|
|
70
|
-
| **
|
|
66
|
+
| Option | What it does |
|
|
67
|
+
| ----------------- | ---------------------------------------------------------------------------------------------------------------------- |
|
|
68
|
+
| **Theme only** | Re-prompts for color theme, overwrites `tokens.css` |
|
|
69
|
+
| **Languages** | Add/remove language support, regenerates `i18n.ts` and `App.tsx` |
|
|
70
|
+
| **Dark Mode** | Enable or disable dark mode support (updates `.xertica.json` and regenerates `App.tsx` to set `disableDarkMode` flag) |
|
|
71
|
+
| **Assistant** | Add or remove the AI Assistant (copies/deletes files, regenerates `AuthGuard.tsx`, `HomePage.tsx`, `TemplatePage.tsx`) |
|
|
72
|
+
| **Project files** | Updates `app/`, `shared/`, `features/`, `pages/` to a newer version |
|
|
71
73
|
|
|
72
74
|
> **Tip:** You can also manually edit `src/styles/xertica/tokens.css` to tweak individual color values — the file is fully commented and designed to be human-readable.
|
|
73
75
|
|
|
@@ -100,12 +102,18 @@ const queryClient = new QueryClient({
|
|
|
100
102
|
|
|
101
103
|
function App() {
|
|
102
104
|
return (
|
|
103
|
-
<AppErrorBoundary>
|
|
105
|
+
<AppErrorBoundary>
|
|
106
|
+
{' '}
|
|
107
|
+
{/* catches provider/context crashes */}
|
|
104
108
|
<QueryClientProvider client={queryClient}>
|
|
105
109
|
<XerticaProvider>
|
|
106
110
|
<Router>
|
|
107
|
-
<AuthProvider>
|
|
108
|
-
|
|
111
|
+
<AuthProvider>
|
|
112
|
+
{' '}
|
|
113
|
+
{/* must be inside Router */}
|
|
114
|
+
<PageErrorBoundary>
|
|
115
|
+
{' '}
|
|
116
|
+
{/* catches lazy-chunk + page errors */}
|
|
109
117
|
<YourRoutes />
|
|
110
118
|
</PageErrorBoundary>
|
|
111
119
|
</AuthProvider>
|
|
@@ -183,12 +191,12 @@ function MyPage() {
|
|
|
183
191
|
|
|
184
192
|
`useAuth()` returns:
|
|
185
193
|
|
|
186
|
-
| Property | Type
|
|
187
|
-
| ----------- |
|
|
188
|
-
| `user` | `AuthUser \| null`
|
|
189
|
-
| `isLoading` | `boolean`
|
|
190
|
-
| `login` | `(email, password) => boolean`
|
|
191
|
-
| `logout` | `() => void`
|
|
194
|
+
| Property | Type | Description |
|
|
195
|
+
| ----------- | ------------------------------ | ------------------------------------------ |
|
|
196
|
+
| `user` | `AuthUser \| null` | Current user (`email`, `name?`, `avatar?`) |
|
|
197
|
+
| `isLoading` | `boolean` | `true` while the session is being hydrated |
|
|
198
|
+
| `login` | `(email, password) => boolean` | Attempt login; returns `true` on success |
|
|
199
|
+
| `logout` | `() => void` | Clear session and redirect to `/login` |
|
|
192
200
|
|
|
193
201
|
### Route Guards
|
|
194
202
|
|
|
@@ -198,7 +206,7 @@ Use `ProtectedRoute` and `GuestRoute` wrappers (provided in the CLI-generated `A
|
|
|
198
206
|
// Redirect unauthenticated users to /login
|
|
199
207
|
function ProtectedRoute({ children }) {
|
|
200
208
|
const { user, isLoading } = useAuth();
|
|
201
|
-
if (isLoading) return null;
|
|
209
|
+
if (isLoading) return null; // wait for localStorage hydration
|
|
202
210
|
if (!user) return <Navigate to="/login" replace />;
|
|
203
211
|
return <>{children}</>;
|
|
204
212
|
}
|
|
@@ -221,15 +229,20 @@ All routes should use `React.lazy()` + `<Suspense>` to split each page into a se
|
|
|
221
229
|
```tsx
|
|
222
230
|
import React, { Suspense } from 'react';
|
|
223
231
|
|
|
224
|
-
const HomePage = React.lazy(() =>
|
|
225
|
-
import('./pages/HomePage').then(m => ({ default: m.HomePage }))
|
|
226
|
-
);
|
|
232
|
+
const HomePage = React.lazy(() => import('./pages/HomePage').then(m => ({ default: m.HomePage })));
|
|
227
233
|
|
|
228
234
|
function AppRoutes() {
|
|
229
235
|
return (
|
|
230
236
|
<Suspense fallback={null}>
|
|
231
237
|
<Routes>
|
|
232
|
-
<Route
|
|
238
|
+
<Route
|
|
239
|
+
path="/home"
|
|
240
|
+
element={
|
|
241
|
+
<ProtectedRoute>
|
|
242
|
+
<HomePage />
|
|
243
|
+
</ProtectedRoute>
|
|
244
|
+
}
|
|
245
|
+
/>
|
|
233
246
|
</Routes>
|
|
234
247
|
</Suspense>
|
|
235
248
|
);
|
package/docs/guidelines.md
CHANGED
|
@@ -201,9 +201,9 @@ Always render a skeleton placeholder — never a spinner — for data-bearing su
|
|
|
201
201
|
```tsx
|
|
202
202
|
import { ActivityCard, ActivityCardSkeleton } from 'xertica-ui';
|
|
203
203
|
|
|
204
|
-
{
|
|
205
|
-
? <ActivityCardSkeleton rows={5} />
|
|
206
|
-
|
|
204
|
+
{
|
|
205
|
+
isLoading ? <ActivityCardSkeleton rows={5} /> : <ActivityCard items={items} />;
|
|
206
|
+
}
|
|
207
207
|
```
|
|
208
208
|
|
|
209
209
|
Available skeleton companions: `ActivityCardSkeleton`, `ProfileCardSkeleton`, `ProjectCardSkeleton`, `NotificationCardSkeleton`, `QuickActionCardSkeleton`, `FeatureCardSkeleton`, `StatsCardSkeleton`.
|
|
@@ -211,15 +211,17 @@ Available skeleton companions: `ActivityCardSkeleton`, `ProfileCardSkeleton`, `P
|
|
|
211
211
|
**For grids**, render one skeleton per expected card so layout shift is minimized when data arrives:
|
|
212
212
|
|
|
213
213
|
```tsx
|
|
214
|
-
{
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
)
|
|
214
|
+
{
|
|
215
|
+
isLoading ? (
|
|
216
|
+
<>
|
|
217
|
+
<FeatureCardSkeleton showAction />
|
|
218
|
+
<FeatureCardSkeleton showAction />
|
|
219
|
+
<FeatureCardSkeleton showAction />
|
|
220
|
+
</>
|
|
221
|
+
) : (
|
|
222
|
+
data.map(item => <FeatureCard key={item.id} {...item} />)
|
|
223
|
+
);
|
|
224
|
+
}
|
|
223
225
|
```
|
|
224
226
|
|
|
225
227
|
**For tables**, render skeleton `<TableRow>`s using the `<Skeleton>` primitive (`xertica-ui/ui`) sized to match each cell:
|
package/docs/i18n.md
CHANGED
|
@@ -97,15 +97,13 @@ const es = bundleLang(
|
|
|
97
97
|
);
|
|
98
98
|
|
|
99
99
|
const savedLanguage =
|
|
100
|
-
typeof window !== 'undefined'
|
|
101
|
-
? (localStorage.getItem('xertica_language') ?? 'pt-BR')
|
|
102
|
-
: 'pt-BR';
|
|
100
|
+
typeof window !== 'undefined' ? (localStorage.getItem('xertica_language') ?? 'pt-BR') : 'pt-BR';
|
|
103
101
|
|
|
104
102
|
i18n.use(initReactI18next).init({
|
|
105
103
|
resources: {
|
|
106
104
|
'pt-BR': { translation: ptBR },
|
|
107
|
-
en:
|
|
108
|
-
es:
|
|
105
|
+
en: { translation: en },
|
|
106
|
+
es: { translation: es },
|
|
109
107
|
},
|
|
110
108
|
lng: savedLanguage,
|
|
111
109
|
fallbackLng: 'pt-BR',
|
|
@@ -150,7 +148,7 @@ function HomeContent() {
|
|
|
150
148
|
```
|
|
151
149
|
|
|
152
150
|
```tsx
|
|
153
|
-
t('team.showing', { count: 5, total: 127 })
|
|
151
|
+
t('team.showing', { count: 5, total: 127 });
|
|
154
152
|
// → "Exibindo 5 de 127 usuários"
|
|
155
153
|
```
|
|
156
154
|
|
|
@@ -176,11 +174,11 @@ setLanguage('en'); // persists + calls i18n.changeLanguage('en') + invalidates R
|
|
|
176
174
|
|
|
177
175
|
### Language codes
|
|
178
176
|
|
|
179
|
-
| Code
|
|
180
|
-
|
|
177
|
+
| Code | Display | Stored as |
|
|
178
|
+
| --------- | -------------- | --------------------------- |
|
|
181
179
|
| `'pt-BR'` | Português (BR) | `'pt-BR'` in `localStorage` |
|
|
182
|
-
| `'en'`
|
|
183
|
-
| `'es'`
|
|
180
|
+
| `'en'` | English | `'en'` |
|
|
181
|
+
| `'es'` | Español | `'es'` |
|
|
184
182
|
|
|
185
183
|
---
|
|
186
184
|
|
|
@@ -190,41 +188,41 @@ The project uses a single `translation` namespace. Each top-level key maps to a
|
|
|
190
188
|
|
|
191
189
|
**Root files** (`locales/<lang>/<key>.json`):
|
|
192
190
|
|
|
193
|
-
| Namespace
|
|
194
|
-
|
|
195
|
-
| `common`
|
|
196
|
-
| `nav`
|
|
197
|
-
| `errors`
|
|
198
|
-
| `languageSelector` | `languageSelector.json` | `languageSelector.label`, `languageSelector.ptBR`
|
|
199
|
-
| `themeToggle`
|
|
191
|
+
| Namespace | File | Example keys |
|
|
192
|
+
| ------------------ | ----------------------- | ------------------------------------------------------------------------------- |
|
|
193
|
+
| `common` | `common.json` | `common.view`, `common.edit`, `common.loading`, `common.close`, `common.copied` |
|
|
194
|
+
| `nav` | `nav.json` | `nav.home`, `nav.designSystem`, `nav.settings` |
|
|
195
|
+
| `errors` | `errors.json` | `errors.somethingWentWrong`, `errors.tryAgain`, `errors.pageLoadError` |
|
|
196
|
+
| `languageSelector` | `languageSelector.json` | `languageSelector.label`, `languageSelector.ptBR` |
|
|
197
|
+
| `themeToggle` | `themeToggle.json` | `themeToggle.switchToLight`, `themeToggle.darkMode` |
|
|
200
198
|
|
|
201
199
|
**Page files** (`locales/<lang>/pages/<key>.json`):
|
|
202
200
|
|
|
203
|
-
| Namespace
|
|
204
|
-
|
|
205
|
-
| `home`
|
|
206
|
-
| `templates`
|
|
207
|
-
| `login`
|
|
208
|
-
| `resetPassword`
|
|
209
|
-
| `verifyEmail`
|
|
210
|
-
| `loginTemplate`
|
|
211
|
-
| `formTemplate`
|
|
212
|
-
| `dashboardTemplate` | `pages/dashboardTemplate.json` | `dashboardTemplate.title`, `dashboardTemplate.stats.totalRevenue`
|
|
213
|
-
| `crudTemplate`
|
|
201
|
+
| Namespace | File | Example keys |
|
|
202
|
+
| ------------------- | ------------------------------ | --------------------------------------------------------------------------------- |
|
|
203
|
+
| `home` | `pages/home.json` | `home.welcome`, `home.subtitle`, `home.templateCliTitle` |
|
|
204
|
+
| `templates` | `pages/templates.json` | `templates.title`, `templates.alerts.infoTitle`, `templates.forms.firstName` |
|
|
205
|
+
| `login` | `pages/login.json` | `login.heading`, `login.submit`, `login.forgotPassword` |
|
|
206
|
+
| `resetPassword` | `pages/resetPassword.json` | `resetPassword.heading`, `resetPassword.errorMismatch` |
|
|
207
|
+
| `verifyEmail` | `pages/verifyEmail.json` | `verifyEmail.heading`, `verifyEmail.resend` |
|
|
208
|
+
| `loginTemplate` | `pages/loginTemplate.json` | `loginTemplate.title`, `loginTemplate.submit` |
|
|
209
|
+
| `formTemplate` | `pages/formTemplate.json` | `formTemplate.title`, `formTemplate.save`, `formTemplate.errors.fullNameRequired` |
|
|
210
|
+
| `dashboardTemplate` | `pages/dashboardTemplate.json` | `dashboardTemplate.title`, `dashboardTemplate.stats.totalRevenue` |
|
|
211
|
+
| `crudTemplate` | `pages/crudTemplate.json` | `crudTemplate.title`, `crudTemplate.actions.editProfile` |
|
|
214
212
|
|
|
215
213
|
**Component files** (`locales/<lang>/components/<key>.json`):
|
|
216
214
|
|
|
217
|
-
| Namespace
|
|
218
|
-
|
|
219
|
-
| `assistant`
|
|
220
|
-
| `sidebar`
|
|
221
|
-
| `media`
|
|
222
|
-
| `projectCard`
|
|
223
|
-
| `profileCard`
|
|
224
|
-
| `notificationCard` | `components/notificationCard.json` | `notificationCard.title`, `notificationCard.markAllRead`
|
|
225
|
-
| `activityCard`
|
|
226
|
-
| `stats`
|
|
227
|
-
| `team`
|
|
215
|
+
| Namespace | File | Example keys |
|
|
216
|
+
| ------------------ | ---------------------------------- | -------------------------------------------------------------------------------------------------------- |
|
|
217
|
+
| `assistant` | `components/assistant.json` | `assistant.title`, `assistant.inputPlaceholder`, `assistant.tabs.chat`, `assistant.feedbackDialog.title` |
|
|
218
|
+
| `sidebar` | `components/sidebar.json` | `sidebar.collapse`, `sidebar.logout`, `sidebar.moreOptions` |
|
|
219
|
+
| `media` | `components/media.json` | `media.play`, `media.pause`, `media.downloadAudio`, `media.floatingMode` |
|
|
220
|
+
| `projectCard` | `components/projectCard.json` | `projectCard.progress`, `projectCard.status.active` |
|
|
221
|
+
| `profileCard` | `components/profileCard.json` | `profileCard.status.online`, `profileCard.status.busy` |
|
|
222
|
+
| `notificationCard` | `components/notificationCard.json` | `notificationCard.title`, `notificationCard.markAllRead` |
|
|
223
|
+
| `activityCard` | `components/activityCard.json` | `activityCard.title`, `activityCard.type.create` |
|
|
224
|
+
| `stats` | `components/stats.json` | `stats.totalUsers`, `stats.last30Days` |
|
|
225
|
+
| `team` | `components/team.json` | `team.name`, `team.roles.Developer`, `team.showing` |
|
|
228
226
|
|
|
229
227
|
---
|
|
230
228
|
|
|
@@ -237,11 +235,13 @@ Mock data fetch functions use `i18n.t()` (the instance, not the hook) so they re
|
|
|
237
235
|
import i18n from '../../../i18n';
|
|
238
236
|
|
|
239
237
|
export async function fetchFeatureCards(): Promise<FeatureCard[]> {
|
|
240
|
-
return [
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
238
|
+
return [
|
|
239
|
+
{
|
|
240
|
+
id: 'template-cli',
|
|
241
|
+
title: i18n.t('home.templateCliTitle'), // ← translated at query time
|
|
242
|
+
description: i18n.t('home.templateCliDescription'),
|
|
243
|
+
},
|
|
244
|
+
];
|
|
245
245
|
}
|
|
246
246
|
```
|
|
247
247
|
|
|
@@ -264,6 +264,7 @@ export function useFeatureCards() {
|
|
|
264
264
|
```
|
|
265
265
|
|
|
266
266
|
**Why this works:**
|
|
267
|
+
|
|
267
268
|
- Switching from `pt-BR` → `en` changes the queryKey to `['home', 'feature-cards', 'en']`
|
|
268
269
|
- React Query finds no cache entry for this key → triggers an immediate refetch
|
|
269
270
|
- `fetchFeatureCards()` runs again → `i18n.t()` now returns English strings
|
|
@@ -278,9 +279,7 @@ When you need static fallback data while a query loads, use **factory functions*
|
|
|
278
279
|
```ts
|
|
279
280
|
// ✅ Correct — evaluated at call time, always returns current language
|
|
280
281
|
export function getMockRichSuggestions(): Suggestion[] {
|
|
281
|
-
return [
|
|
282
|
-
{ id: 'rich-1', text: i18n.t('assistant.richSuggestions.viewPerformance') },
|
|
283
|
-
];
|
|
282
|
+
return [{ id: 'rich-1', text: i18n.t('assistant.richSuggestions.viewPerformance') }];
|
|
284
283
|
}
|
|
285
284
|
|
|
286
285
|
// ❌ Wrong — i18n.t() runs once at module load, frozen in initial language
|
|
@@ -312,11 +311,13 @@ $ npx xertica-ui init my-app
|
|
|
312
311
|
```
|
|
313
312
|
|
|
314
313
|
Pick **all three** (default), **two**, or **just one**:
|
|
314
|
+
|
|
315
315
|
- **All three** — the CLI omits the `availableLanguages` prop entirely (the library default already matches).
|
|
316
316
|
- **Two or one** — the CLI injects the explicit `availableLanguages` array into `src/app/App.tsx`.
|
|
317
317
|
- **Just one (monolingual)** — additionally, the `LanguageSelector` auto-hides because there is nothing to switch between. A header banner comment in `App.tsx` documents this.
|
|
318
318
|
|
|
319
319
|
The CLI also:
|
|
320
|
+
|
|
320
321
|
- Copies **only** the locale **folders** for the selected languages into `src/locales/` (no orphan files). Each language is a directory tree with split JSON files.
|
|
321
322
|
- Generates `src/i18n.ts` with `import.meta.glob` calls for exactly those languages — Vite auto-discovers all JSON files in the folder at build time.
|
|
322
323
|
- Persists the selection in `src/locales/.languages.json` so the `update` command can preserve it.
|
|
@@ -341,6 +342,7 @@ Current languages: Português (BR), English
|
|
|
341
342
|
```
|
|
342
343
|
|
|
343
344
|
The command:
|
|
345
|
+
|
|
344
346
|
1. Computes the add/remove diff and shows it to you.
|
|
345
347
|
2. Copies the newly-added locale **folders** from `node_modules/xertica-ui/templates/src/locales/<lang>/`.
|
|
346
348
|
3. Removes the **folders** of unselected languages from your project (also removes any legacy flat `<lang>.json` files if upgrading from pre-2.2.0).
|
|
@@ -358,7 +360,7 @@ import { XerticaProvider } from 'xertica-ui';
|
|
|
358
360
|
|
|
359
361
|
<XerticaProvider>
|
|
360
362
|
{/* pt-BR, en, es are all available — selector shows all three */}
|
|
361
|
-
</XerticaProvider
|
|
363
|
+
</XerticaProvider>;
|
|
362
364
|
```
|
|
363
365
|
|
|
364
366
|
### Monolingual — single language, no selector
|
|
@@ -390,7 +392,7 @@ import fr from './locales/fr.json';
|
|
|
390
392
|
]}
|
|
391
393
|
>
|
|
392
394
|
{/* French now appears in the selector; its strings are registered automatically */}
|
|
393
|
-
</XerticaProvider
|
|
395
|
+
</XerticaProvider>;
|
|
394
396
|
```
|
|
395
397
|
|
|
396
398
|
The provider calls `i18n.addResourceBundle()` on mount for any entry that carries `resources`.
|
|
@@ -414,10 +416,12 @@ registerLanguageResource('fr', fr);
|
|
|
414
416
|
Just omit them from `availableLanguages`. The translation bundles registered in `src/i18n.ts` remain loaded (they're cheap) but they're invisible to the UI:
|
|
415
417
|
|
|
416
418
|
```tsx
|
|
417
|
-
<XerticaProvider
|
|
418
|
-
{
|
|
419
|
-
|
|
420
|
-
|
|
419
|
+
<XerticaProvider
|
|
420
|
+
availableLanguages={[
|
|
421
|
+
{ code: 'pt-BR', label: 'Português' },
|
|
422
|
+
{ code: 'en', label: 'English' },
|
|
423
|
+
]}
|
|
424
|
+
>
|
|
421
425
|
{/* Only Portuguese and English appear — Spanish is hidden */}
|
|
422
426
|
</XerticaProvider>
|
|
423
427
|
```
|
|
@@ -451,10 +455,10 @@ interface LanguageDefinition {
|
|
|
451
455
|
|
|
452
456
|
```ts
|
|
453
457
|
const {
|
|
454
|
-
language,
|
|
455
|
-
setLanguage,
|
|
456
|
-
availableLanguages,
|
|
457
|
-
isMonolingual,
|
|
458
|
+
language, // current locale code
|
|
459
|
+
setLanguage, // change locale + persist + invalidate React Query
|
|
460
|
+
availableLanguages, // LanguageDefinition[] currently configured
|
|
461
|
+
isMonolingual, // true when availableLanguages.length === 1
|
|
458
462
|
} = useLanguage();
|
|
459
463
|
```
|
|
460
464
|
|