start-vibing-stacks 2.0.3 → 2.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/ui.js
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* Start Vibing Stacks — Terminal UI
|
|
3
3
|
*/
|
|
4
4
|
import chalk from 'chalk';
|
|
5
|
-
const VERSION = '2.0
|
|
5
|
+
const VERSION = '2.1.0';
|
|
6
6
|
const gradient = (text) => {
|
|
7
7
|
const colors = [chalk.hex('#FF6B6B'), chalk.hex('#FF8E53'), chalk.hex('#FFBD2E'), chalk.hex('#48BB78'), chalk.hex('#4299E1'), chalk.hex('#9F7AEA')];
|
|
8
8
|
return text.split('').map((c, i) => colors[i % colors.length](c)).join('');
|
package/package.json
CHANGED
|
@@ -49,27 +49,139 @@ export default function Dashboard() {
|
|
|
49
49
|
|
|
50
50
|
**Rule:** Never leave raw `console.log`. Always use controlled debug pattern.
|
|
51
51
|
|
|
52
|
-
## TailwindCSS Class Organization
|
|
52
|
+
## TailwindCSS Class Organization (CONST Pattern)
|
|
53
|
+
|
|
54
|
+
**MANDATORY:** Define all CSS classes as constants at the TOP of the file, before the component.
|
|
55
|
+
|
|
56
|
+
### Why
|
|
57
|
+
|
|
58
|
+
1. **No re-renders** — string constants have stable references (no new object per render)
|
|
59
|
+
2. **Single source of truth** — change style once, updates everywhere
|
|
60
|
+
3. **Clean JSX** — readable templates, no class soup
|
|
61
|
+
4. **Prevents React state warnings** — no inline objects/strings changing reference
|
|
62
|
+
5. **Easy theming** — swap tokens in one place
|
|
63
|
+
|
|
64
|
+
### Pattern
|
|
53
65
|
|
|
54
66
|
```tsx
|
|
55
|
-
//
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
};
|
|
67
|
+
// ═══════════════════════════════════════════
|
|
68
|
+
// 1. TRANSLATIONS (before hooks)
|
|
69
|
+
// ═══════════════════════════════════════════
|
|
70
|
+
const LABELS = {
|
|
71
|
+
title: __('dashboard.title'),
|
|
72
|
+
save: __('common.save'),
|
|
73
|
+
} as const;
|
|
62
74
|
|
|
75
|
+
// ═══════════════════════════════════════════
|
|
76
|
+
// 2. STYLES (semantic tokens, not raw colors)
|
|
77
|
+
// ═══════════════════════════════════════════
|
|
78
|
+
const STYLES = {
|
|
79
|
+
// Layout
|
|
80
|
+
page: 'max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-8',
|
|
81
|
+
section: 'space-y-6',
|
|
82
|
+
grid: 'grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4',
|
|
83
|
+
|
|
84
|
+
// Cards
|
|
85
|
+
card: 'bg-card border border-card-line rounded-xl p-6 hover:shadow-md transition-shadow',
|
|
86
|
+
cardHeader: 'flex items-center justify-between border-b border-card-divider pb-4 mb-4',
|
|
87
|
+
cardTitle: 'text-lg font-semibold text-foreground',
|
|
88
|
+
cardDescription: 'text-sm text-muted-foreground mt-1',
|
|
89
|
+
|
|
90
|
+
// Table
|
|
91
|
+
table: 'w-full text-sm',
|
|
92
|
+
tableHeader: 'text-left text-muted-foreground font-medium border-b border-border',
|
|
93
|
+
tableRow: 'border-b border-border hover:bg-muted/50 transition-colors',
|
|
94
|
+
tableCell: 'px-4 py-3 text-foreground',
|
|
95
|
+
|
|
96
|
+
// Buttons
|
|
97
|
+
btnPrimary: 'px-4 py-2 bg-primary text-primary-foreground rounded-lg hover:bg-primary-hover font-medium disabled:opacity-50 disabled:cursor-not-allowed transition-colors',
|
|
98
|
+
btnSecondary: 'px-4 py-2 bg-layer border border-layer-line text-layer-foreground rounded-lg hover:bg-layer-hover font-medium transition-colors',
|
|
99
|
+
btnDestructive: 'px-4 py-2 bg-destructive text-destructive-foreground rounded-lg hover:bg-destructive-hover font-medium transition-colors',
|
|
100
|
+
btnGhost: 'px-4 py-2 text-muted-foreground hover:bg-muted rounded-lg transition-colors',
|
|
101
|
+
|
|
102
|
+
// Forms
|
|
103
|
+
input: 'w-full h-10 px-3 rounded-md border border-border bg-background text-foreground placeholder:text-muted-foreground focus:outline-none focus:ring-2 focus:ring-ring focus:border-transparent transition-colors',
|
|
104
|
+
label: 'block text-sm font-medium text-foreground mb-1',
|
|
105
|
+
fieldError: 'mt-1 text-sm text-destructive',
|
|
106
|
+
|
|
107
|
+
// Status badges
|
|
108
|
+
badgeSuccess: 'inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium bg-green-100 text-green-800 dark:bg-green-900/30 dark:text-green-400',
|
|
109
|
+
badgeWarning: 'inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium bg-yellow-100 text-yellow-800 dark:bg-yellow-900/30 dark:text-yellow-400',
|
|
110
|
+
badgeDanger: 'inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium bg-red-100 text-red-800 dark:bg-red-900/30 dark:text-red-400',
|
|
111
|
+
|
|
112
|
+
// Typography
|
|
113
|
+
heading: 'text-2xl font-bold text-foreground',
|
|
114
|
+
subheading: 'text-lg font-semibold text-foreground',
|
|
115
|
+
body: 'text-sm text-foreground',
|
|
116
|
+
muted: 'text-sm text-muted-foreground',
|
|
117
|
+
} as const;
|
|
118
|
+
|
|
119
|
+
// ═══════════════════════════════════════════
|
|
120
|
+
// 3. COMPONENT
|
|
121
|
+
// ═══════════════════════════════════════════
|
|
63
122
|
export default function Dashboard() {
|
|
123
|
+
const [data, setData] = useState(null);
|
|
124
|
+
|
|
64
125
|
return (
|
|
65
|
-
<div className={STYLES.
|
|
66
|
-
<h1 className={STYLES.
|
|
126
|
+
<div className={STYLES.page}>
|
|
127
|
+
<h1 className={STYLES.heading}>{LABELS.title}</h1>
|
|
128
|
+
<div className={STYLES.grid}>
|
|
129
|
+
<div className={STYLES.card}>
|
|
130
|
+
<h2 className={STYLES.cardTitle}>Stats</h2>
|
|
131
|
+
<p className={STYLES.cardDescription}>Overview</p>
|
|
132
|
+
</div>
|
|
133
|
+
</div>
|
|
67
134
|
</div>
|
|
68
135
|
);
|
|
69
136
|
}
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
### Composing Styles
|
|
70
140
|
|
|
71
|
-
|
|
72
|
-
|
|
141
|
+
```tsx
|
|
142
|
+
// ✅ Combine with template literal when conditional
|
|
143
|
+
<tr className={`${STYLES.tableRow} ${isSelected ? 'bg-primary/5' : ''}`}>
|
|
144
|
+
|
|
145
|
+
// ✅ clsx/cn for complex conditions
|
|
146
|
+
import { cn } from '@/lib/utils';
|
|
147
|
+
<button className={cn(STYLES.btnPrimary, isFullWidth && 'w-full', className)}>
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
### Rules
|
|
151
|
+
|
|
152
|
+
1. **CONST at top** — before hooks, before component
|
|
153
|
+
2. **`as const`** — TypeScript ensures immutability
|
|
154
|
+
3. **Semantic tokens** — `bg-card` not `bg-white`, `text-foreground` not `text-gray-900`
|
|
155
|
+
4. **No inline class strings > 3 utilities** — extract to STYLES
|
|
156
|
+
5. **Shared styles** — create a `styles.ts` file for cross-component constants
|
|
157
|
+
|
|
158
|
+
```tsx
|
|
159
|
+
// resources/js/styles.ts — shared across components
|
|
160
|
+
export const SHARED_STYLES = {
|
|
161
|
+
page: 'max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-8',
|
|
162
|
+
btnPrimary: 'px-4 py-2 bg-primary text-primary-foreground rounded-lg hover:bg-primary-hover ...',
|
|
163
|
+
input: 'w-full h-10 px-3 rounded-md border border-border bg-background ...',
|
|
164
|
+
} as const;
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
### ❌ FORBIDDEN
|
|
168
|
+
|
|
169
|
+
```tsx
|
|
170
|
+
// ❌ Inline class soup — unreadable, unstable reference
|
|
171
|
+
<div className="flex flex-col gap-4 p-6 bg-white rounded-lg shadow-sm">
|
|
172
|
+
|
|
173
|
+
// ❌ Raw colors instead of tokens
|
|
174
|
+
const STYLES = { card: 'bg-white text-gray-900' }; // ❌ Breaks dark mode
|
|
175
|
+
|
|
176
|
+
// ❌ Dynamic class construction (breaks Tailwind purge)
|
|
177
|
+
const color = 'blue';
|
|
178
|
+
<div className={`bg-${color}-500`}> // ❌ Purged!
|
|
179
|
+
|
|
180
|
+
// ❌ Styles inside component (new object every render)
|
|
181
|
+
export default function Bad() {
|
|
182
|
+
const styles = { card: 'bg-card p-4' }; // ❌ Inside = new ref every render
|
|
183
|
+
return <div className={styles.card} />;
|
|
184
|
+
}
|
|
73
185
|
```
|
|
74
186
|
|
|
75
187
|
## SVG Icons
|
|
@@ -0,0 +1,294 @@
|
|
|
1
|
+
# Laravel + Inertia i18n — Centralized Translations
|
|
2
|
+
|
|
3
|
+
**ALWAYS invoke when adding translations, creating new pages, or working with multilingual content.**
|
|
4
|
+
|
|
5
|
+
## Architecture
|
|
6
|
+
|
|
7
|
+
```
|
|
8
|
+
Laravel Backend Inertia Share React Frontend
|
|
9
|
+
┌─────────────────┐ ┌──────────────┐ ┌──────────────────┐
|
|
10
|
+
│ lang/en/*.php │──→ config ──→│ InertiaShare │──→ props →│ __('key') │
|
|
11
|
+
│ lang/pt/*.php │ mapping │ translations │ │ via usePage() │
|
|
12
|
+
│ lang/en.json │ │ + cache │ │ │
|
|
13
|
+
└─────────────────┘ └──────────────┘ └──────────────────┘
|
|
14
|
+
|
|
15
|
+
Key decisions:
|
|
16
|
+
- Translations live in Laravel (single source of truth)
|
|
17
|
+
- Only NEEDED translations sent per page (not all)
|
|
18
|
+
- Cached forever (invalidate on deploy)
|
|
19
|
+
- Frontend reads via usePage().props.translations
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
## Backend: Translation Files
|
|
23
|
+
|
|
24
|
+
### File Structure
|
|
25
|
+
|
|
26
|
+
```
|
|
27
|
+
lang/
|
|
28
|
+
├── en/
|
|
29
|
+
│ ├── messages.php # Global (loaded on all pages)
|
|
30
|
+
│ ├── validation.php # Laravel validation messages
|
|
31
|
+
│ ├── auth.php # Auth pages
|
|
32
|
+
│ └── admin/
|
|
33
|
+
│ ├── dashboard.php # Admin dashboard specific
|
|
34
|
+
│ └── ai-models.php # AI models page specific
|
|
35
|
+
├── pt/
|
|
36
|
+
│ ├── messages.php
|
|
37
|
+
│ ├── validation.php
|
|
38
|
+
│ └── admin/
|
|
39
|
+
│ ├── dashboard.php
|
|
40
|
+
│ └── ai-models.php
|
|
41
|
+
├── en.json # JSON format (optional, merged)
|
|
42
|
+
└── pt.json
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
### Translation File Example
|
|
46
|
+
|
|
47
|
+
```php
|
|
48
|
+
// lang/en/messages.php
|
|
49
|
+
return [
|
|
50
|
+
'common' => [
|
|
51
|
+
'buttons' => [
|
|
52
|
+
'save' => 'Save',
|
|
53
|
+
'cancel' => 'Cancel',
|
|
54
|
+
'close' => 'Close',
|
|
55
|
+
'saving' => 'Saving...',
|
|
56
|
+
'add_ai_model' => 'Add AI Model',
|
|
57
|
+
],
|
|
58
|
+
],
|
|
59
|
+
'errors' => [
|
|
60
|
+
'error' => 'Error',
|
|
61
|
+
'api_save_failed_generic' => 'Failed to save. Please try again.',
|
|
62
|
+
'validation_failed_check_fields' => 'Validation failed. Please check the fields.',
|
|
63
|
+
'invalid_json' => 'Invalid JSON format.',
|
|
64
|
+
],
|
|
65
|
+
'admin' => [
|
|
66
|
+
'ai' => [
|
|
67
|
+
'ai-models' => [
|
|
68
|
+
'new_model_button' => 'New AI Model',
|
|
69
|
+
'add_modal_description' => 'Fill in the details to add a new AI model.',
|
|
70
|
+
'fields' => [
|
|
71
|
+
'name' => 'Name',
|
|
72
|
+
'slug' => 'Slug',
|
|
73
|
+
'system_prompt' => 'System Prompt',
|
|
74
|
+
'temperature' => 'Temperature',
|
|
75
|
+
// ...
|
|
76
|
+
],
|
|
77
|
+
],
|
|
78
|
+
],
|
|
79
|
+
],
|
|
80
|
+
];
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
## Backend: Page-to-Translation Mapping
|
|
84
|
+
|
|
85
|
+
### Config File
|
|
86
|
+
|
|
87
|
+
```php
|
|
88
|
+
// config/translations_inertia.php
|
|
89
|
+
return [
|
|
90
|
+
// Global: loaded on EVERY page
|
|
91
|
+
'global' => [
|
|
92
|
+
'messages', // lang/{locale}/messages.php
|
|
93
|
+
],
|
|
94
|
+
|
|
95
|
+
// Per-page: loaded only when visiting that route
|
|
96
|
+
'pages' => [
|
|
97
|
+
'admin/dashboard' => ['admin/dashboard'],
|
|
98
|
+
'admin/ai-models' => ['admin/ai-models'],
|
|
99
|
+
'auth/login' => ['auth'],
|
|
100
|
+
'auth/register' => ['auth'],
|
|
101
|
+
],
|
|
102
|
+
];
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
**Rule:** Only send translations the page actually needs — not the entire `lang/` folder.
|
|
106
|
+
|
|
107
|
+
## Backend: InertiaShare Middleware
|
|
108
|
+
|
|
109
|
+
```php
|
|
110
|
+
// app/Support/InertiaShare.php
|
|
111
|
+
class InertiaShare
|
|
112
|
+
{
|
|
113
|
+
public static function getProps(Request $request): array
|
|
114
|
+
{
|
|
115
|
+
$locale = App::getLocale();
|
|
116
|
+
|
|
117
|
+
return [
|
|
118
|
+
'auth' => [
|
|
119
|
+
'user' => $request->user()?->only('id', 'name', 'email', 'role', 'timezone', 'avatar_url'),
|
|
120
|
+
],
|
|
121
|
+
'locale' => $locale,
|
|
122
|
+
'translations' => static::getTranslations($locale, $request->route()->uri),
|
|
123
|
+
'flash' => [
|
|
124
|
+
'success' => session('success'),
|
|
125
|
+
'error' => session('error'),
|
|
126
|
+
],
|
|
127
|
+
];
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
protected static function getTranslations(string $locale, string $pageUri): array
|
|
131
|
+
{
|
|
132
|
+
$cacheKey = "translations_{$locale}_{$pageUri}";
|
|
133
|
+
|
|
134
|
+
return Cache::rememberForever($cacheKey, function () use ($locale, $pageUri) {
|
|
135
|
+
$globalFiles = config('translations_inertia.global', []);
|
|
136
|
+
$pageFiles = config("translations_inertia.pages.{$pageUri}", []);
|
|
137
|
+
$files = array_unique(array_merge($globalFiles, $pageFiles));
|
|
138
|
+
|
|
139
|
+
$translations = [];
|
|
140
|
+
$langPath = lang_path($locale);
|
|
141
|
+
|
|
142
|
+
foreach ($files as $file) {
|
|
143
|
+
$filePath = "{$langPath}/{$file}.php";
|
|
144
|
+
if (File::exists($filePath)) {
|
|
145
|
+
$translations[$file] = require $filePath;
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
// Merge JSON translations (lang/{locale}.json)
|
|
150
|
+
$jsonPath = lang_path("{$locale}.json");
|
|
151
|
+
if (File::exists($jsonPath)) {
|
|
152
|
+
$json = json_decode(File::get($jsonPath), true);
|
|
153
|
+
if (is_array($json)) {
|
|
154
|
+
$translations = array_merge($translations, $json);
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
return $translations;
|
|
159
|
+
});
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
### Register in Middleware
|
|
165
|
+
|
|
166
|
+
```php
|
|
167
|
+
// app/Http/Middleware/HandleInertiaRequests.php
|
|
168
|
+
use App\Support\InertiaShare;
|
|
169
|
+
|
|
170
|
+
public function share(Request $request): array
|
|
171
|
+
{
|
|
172
|
+
return array_merge(parent::share($request), InertiaShare::getProps($request));
|
|
173
|
+
}
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
## Frontend: translate.js Utility
|
|
177
|
+
|
|
178
|
+
```js
|
|
179
|
+
// resources/js/Utils/translate.js
|
|
180
|
+
import { usePage } from '@inertiajs/react';
|
|
181
|
+
|
|
182
|
+
/**
|
|
183
|
+
* Get translated string by dot-notation key.
|
|
184
|
+
* @param {string} key - e.g. 'messages.common.buttons.save'
|
|
185
|
+
* @param {object} replacements - e.g. { name: 'John' } for ':name'
|
|
186
|
+
* @returns {string} Translated string or key as fallback
|
|
187
|
+
*/
|
|
188
|
+
export default function __(key, replacements = {}) {
|
|
189
|
+
const { translations } = usePage().props;
|
|
190
|
+
|
|
191
|
+
let translation = key.split('.').reduce(
|
|
192
|
+
(obj, part) => (obj && obj[part] !== undefined ? obj[part] : null),
|
|
193
|
+
translations
|
|
194
|
+
);
|
|
195
|
+
|
|
196
|
+
// Fallback: return key if not found
|
|
197
|
+
if (translation === null || translation === undefined) {
|
|
198
|
+
return key;
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
// Replace :placeholders
|
|
202
|
+
if (typeof translation === 'string' && Object.keys(replacements).length > 0) {
|
|
203
|
+
Object.keys(replacements).forEach((placeholder) => {
|
|
204
|
+
translation = translation.replace(
|
|
205
|
+
new RegExp(`:${placeholder}`, 'g'),
|
|
206
|
+
replacements[placeholder]
|
|
207
|
+
);
|
|
208
|
+
});
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
return translation;
|
|
212
|
+
}
|
|
213
|
+
```
|
|
214
|
+
|
|
215
|
+
## Frontend: Component Pattern (CONST)
|
|
216
|
+
|
|
217
|
+
```tsx
|
|
218
|
+
import __ from '@/Utils/translate';
|
|
219
|
+
|
|
220
|
+
// ═══════════════════════════════════════════
|
|
221
|
+
// 1. TRANSLATIONS — before hooks, outside component
|
|
222
|
+
// ═══════════════════════════════════════════
|
|
223
|
+
const LABELS = {
|
|
224
|
+
title: __('messages.admin.ai.ai-models.new_model_button'),
|
|
225
|
+
description: __('messages.admin.ai.ai-models.add_modal_description'),
|
|
226
|
+
nameField: __('messages.admin.ai.ai-models.fields.name'),
|
|
227
|
+
slugField: __('messages.admin.ai.ai-models.fields.slug'),
|
|
228
|
+
save: __('messages.common.buttons.save'),
|
|
229
|
+
saving: __('messages.common.buttons.saving'),
|
|
230
|
+
cancel: __('messages.common.buttons.cancel'),
|
|
231
|
+
errorTitle: __('messages.errors.error'),
|
|
232
|
+
validationFailed: __('messages.errors.validation_failed_check_fields'),
|
|
233
|
+
} as const;
|
|
234
|
+
|
|
235
|
+
// ═══════════════════════════════════════════
|
|
236
|
+
// 2. CSS STYLES — semantic tokens
|
|
237
|
+
// ═══════════════════════════════════════════
|
|
238
|
+
const STYLES = {
|
|
239
|
+
modal: 'relative w-full max-w-[558px] rounded-3xl bg-card p-6 lg:p-10',
|
|
240
|
+
title: 'mb-6 text-xl font-semibold text-foreground',
|
|
241
|
+
description: 'mb-7 text-sm leading-6 text-muted-foreground',
|
|
242
|
+
form: 'space-y-4',
|
|
243
|
+
gridTwo: 'grid grid-cols-2 gap-4',
|
|
244
|
+
actions: 'mt-8 flex w-full flex-col sm:flex-row items-center justify-between gap-3',
|
|
245
|
+
fieldError: 'text-sm text-destructive mt-1',
|
|
246
|
+
btnFull: 'w-full',
|
|
247
|
+
} as const;
|
|
248
|
+
|
|
249
|
+
// ═══════════════════════════════════════════
|
|
250
|
+
// 3. COMPONENT
|
|
251
|
+
// ═══════════════════════════════════════════
|
|
252
|
+
export default function AiModelsAddModal({ isOpen, onClose, onAdded }) {
|
|
253
|
+
// hooks, state, handlers...
|
|
254
|
+
|
|
255
|
+
return (
|
|
256
|
+
<Modal isOpen={isOpen} onClose={onClose} className={STYLES.modal}>
|
|
257
|
+
<h4 className={STYLES.title}>{LABELS.title}</h4>
|
|
258
|
+
<p className={STYLES.description}>{LABELS.description}</p>
|
|
259
|
+
{/* form fields using LABELS and STYLES */}
|
|
260
|
+
</Modal>
|
|
261
|
+
);
|
|
262
|
+
}
|
|
263
|
+
```
|
|
264
|
+
|
|
265
|
+
## Adding New Translations
|
|
266
|
+
|
|
267
|
+
### Checklist
|
|
268
|
+
|
|
269
|
+
1. Add string to `lang/en/{file}.php` **and** `lang/pt/{file}.php`
|
|
270
|
+
2. If new file: add to `config/translations_inertia.php` (global or page-specific)
|
|
271
|
+
3. Clear cache: `php artisan cache:clear`
|
|
272
|
+
4. In React component: add to `LABELS` const using `__('key')`
|
|
273
|
+
5. **NEVER** call `__()` inside JSX directly — always via LABELS const
|
|
274
|
+
|
|
275
|
+
### Cache Invalidation
|
|
276
|
+
|
|
277
|
+
```bash
|
|
278
|
+
# After adding/changing translations:
|
|
279
|
+
php artisan cache:clear
|
|
280
|
+
|
|
281
|
+
# For Octane (in-memory):
|
|
282
|
+
php artisan octane:reload
|
|
283
|
+
```
|
|
284
|
+
|
|
285
|
+
## FORBIDDEN
|
|
286
|
+
|
|
287
|
+
| ❌ Don't | ✅ Do |
|
|
288
|
+
|---|---|
|
|
289
|
+
| `__()` inside JSX return | `LABELS.title` via const |
|
|
290
|
+
| Send ALL translations to every page | Map per-page in config |
|
|
291
|
+
| Hardcode strings in components | Use translation keys |
|
|
292
|
+
| Skip Portuguese translation | Always add both `en` + `pt` |
|
|
293
|
+
| `Cache::forget()` for translations | `cache:clear` on deploy |
|
|
294
|
+
| Translation keys in camelCase | Use dot.notation `messages.admin.title` |
|