@nextsparkjs/ai-workflow 0.1.0-beta.100
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/LICENSE +21 -0
- package/README.md +115 -0
- package/claude/_docs/workflows-optimizations.md +359 -0
- package/claude/agents/api-tester.md +634 -0
- package/claude/agents/architecture-supervisor.md +1351 -0
- package/claude/agents/backend-developer.md +997 -0
- package/claude/agents/backend-validator.md +417 -0
- package/claude/agents/bdd-docs-writer.md +737 -0
- package/claude/agents/block-developer.md +677 -0
- package/claude/agents/code-reviewer.md +1432 -0
- package/claude/agents/db-developer.md +721 -0
- package/claude/agents/db-validator.md +407 -0
- package/claude/agents/demo-video-generator.md +493 -0
- package/claude/agents/documentation-writer.md +1268 -0
- package/claude/agents/frontend-developer.md +1234 -0
- package/claude/agents/frontend-validator.md +777 -0
- package/claude/agents/functional-validator.md +630 -0
- package/claude/agents/mock-analyst.md +387 -0
- package/claude/agents/product-manager.md +963 -0
- package/claude/agents/qa-automation.md +1762 -0
- package/claude/agents/release-manager.md +634 -0
- package/claude/agents/selectors-translator.md +262 -0
- package/claude/agents/unit-test-writer.md +785 -0
- package/claude/agents/visual-comparator.md +329 -0
- package/claude/agents/workflow-maintainer.md +352 -0
- package/claude/commands/do/README.md +88 -0
- package/claude/commands/do/create-api.md +64 -0
- package/claude/commands/do/create-entity.md +66 -0
- package/claude/commands/do/create-migration.md +64 -0
- package/claude/commands/do/create-plugin.md +56 -0
- package/claude/commands/do/create-theme.md +70 -0
- package/claude/commands/do/mock-data.md +67 -0
- package/claude/commands/do/reset-db.md +71 -0
- package/claude/commands/do/setup-scheduled-action.md +75 -0
- package/claude/commands/do/sync-code-review.md +117 -0
- package/claude/commands/do/update-selectors.md +112 -0
- package/claude/commands/do/use-skills.md +90 -0
- package/claude/commands/do/validate-blocks.md +69 -0
- package/claude/commands/how-to/README.md +261 -0
- package/claude/commands/how-to/add-metadata.md +692 -0
- package/claude/commands/how-to/add-taxonomies.md +806 -0
- package/claude/commands/how-to/add-translations.md +571 -0
- package/claude/commands/how-to/create-api.md +577 -0
- package/claude/commands/how-to/create-block.md +575 -0
- package/claude/commands/how-to/create-child-entities.md +771 -0
- package/claude/commands/how-to/create-entity.md +597 -0
- package/claude/commands/how-to/create-migrations.md +605 -0
- package/claude/commands/how-to/create-plugin.md +654 -0
- package/claude/commands/how-to/customize-app.md +481 -0
- package/claude/commands/how-to/customize-dashboard.md +553 -0
- package/claude/commands/how-to/customize-theme.md +438 -0
- package/claude/commands/how-to/define-features-flows.md +632 -0
- package/claude/commands/how-to/deploy.md +507 -0
- package/claude/commands/how-to/handle-file-uploads.md +746 -0
- package/claude/commands/how-to/implement-search.md +1001 -0
- package/claude/commands/how-to/install-plugins.md +352 -0
- package/claude/commands/how-to/manage-test-coverage.md +984 -0
- package/claude/commands/how-to/run-tests.md +400 -0
- package/claude/commands/how-to/set-app-languages.md +601 -0
- package/claude/commands/how-to/set-plans-and-permissions.md +575 -0
- package/claude/commands/how-to/set-scheduled-actions.md +527 -0
- package/claude/commands/how-to/set-user-roles-and-permissions.md +550 -0
- package/claude/commands/how-to/setup-authentication.md +388 -0
- package/claude/commands/how-to/setup-claude-code.md +440 -0
- package/claude/commands/how-to/setup-database.md +274 -0
- package/claude/commands/how-to/setup-email-providers.md +598 -0
- package/claude/commands/how-to/setup-mobile-dev.md +627 -0
- package/claude/commands/how-to/start.md +500 -0
- package/claude/commands/how-to/use-devtools.md +639 -0
- package/claude/commands/how-to/use-superadmin.md +622 -0
- package/claude/commands/session/README.md +193 -0
- package/claude/commands/session/block-create.md +190 -0
- package/claude/commands/session/block-list.md +203 -0
- package/claude/commands/session/block-update.md +192 -0
- package/claude/commands/session/block-validate.md +218 -0
- package/claude/commands/session/changelog.md +115 -0
- package/claude/commands/session/close.md +225 -0
- package/claude/commands/session/commit.md +174 -0
- package/claude/commands/session/db-entity.md +206 -0
- package/claude/commands/session/db-fix.md +212 -0
- package/claude/commands/session/db-sample.md +206 -0
- package/claude/commands/session/demo.md +178 -0
- package/claude/commands/session/doc-bdd.md +207 -0
- package/claude/commands/session/doc-feature.md +218 -0
- package/claude/commands/session/doc-read.md +225 -0
- package/claude/commands/session/execute.md +204 -0
- package/claude/commands/session/explain.md +202 -0
- package/claude/commands/session/fix-bug.md +210 -0
- package/claude/commands/session/fix-build.md +182 -0
- package/claude/commands/session/fix-test.md +189 -0
- package/claude/commands/session/pending.md +232 -0
- package/claude/commands/session/refine.md +188 -0
- package/claude/commands/session/resume.md +192 -0
- package/claude/commands/session/review.md +192 -0
- package/claude/commands/session/scope-change.md +181 -0
- package/claude/commands/session/start-blocks.md +347 -0
- package/claude/commands/session/start.md +604 -0
- package/claude/commands/session/status.md +169 -0
- package/claude/commands/session/test-fix.md +221 -0
- package/claude/commands/session/test-run.md +203 -0
- package/claude/commands/session/test-write.md +242 -0
- package/claude/commands/session/validate.md +162 -0
- package/claude/config/context.json +40 -0
- package/claude/config/github.json +69 -0
- package/claude/config/github.schema.json +106 -0
- package/claude/config/team.json +46 -0
- package/claude/config/team.schema.json +106 -0
- package/claude/config/workspace.json +43 -0
- package/claude/config/workspace.schema.json +75 -0
- package/claude/skills/README.md +228 -0
- package/claude/skills/accessibility/SKILL.md +573 -0
- package/claude/skills/api-bypass-layers/SKILL.md +550 -0
- package/claude/skills/asana-integration/SKILL.md +499 -0
- package/claude/skills/better-auth/SKILL.md +666 -0
- package/claude/skills/billing-subscriptions/SKILL.md +660 -0
- package/claude/skills/block-decision-matrix/SKILL.md +359 -0
- package/claude/skills/clickup-integration/SKILL.md +434 -0
- package/claude/skills/core-theme-responsibilities/SKILL.md +485 -0
- package/claude/skills/create-plugin/SKILL.md +425 -0
- package/claude/skills/create-theme/SKILL.md +331 -0
- package/claude/skills/cypress-api/SKILL.md +511 -0
- package/claude/skills/cypress-api/scripts/generate-api-controller.py +329 -0
- package/claude/skills/cypress-api/scripts/generate-api-test.py +930 -0
- package/claude/skills/cypress-e2e/SKILL.md +526 -0
- package/claude/skills/cypress-e2e/scripts/extract-selectors.py +383 -0
- package/claude/skills/cypress-e2e/scripts/generate-uat-test.py +788 -0
- package/claude/skills/cypress-selectors/SKILL.md +309 -0
- package/claude/skills/cypress-selectors/scripts/extract-missing.py +243 -0
- package/claude/skills/cypress-selectors/scripts/generate-block-selectors.py +283 -0
- package/claude/skills/cypress-selectors/scripts/validate-selectors.py +145 -0
- package/claude/skills/database-migrations/SKILL.md +335 -0
- package/claude/skills/database-migrations/scripts/generate-sample-data.py +284 -0
- package/claude/skills/database-migrations/scripts/validate-migration.py +323 -0
- package/claude/skills/design-system/SKILL.md +682 -0
- package/claude/skills/documentation/SKILL.md +540 -0
- package/claude/skills/entity-api/SKILL.md +482 -0
- package/claude/skills/entity-system/SKILL.md +635 -0
- package/claude/skills/entity-system/scripts/generate-child-migration.py +298 -0
- package/claude/skills/entity-system/scripts/generate-metas-migration.py +233 -0
- package/claude/skills/entity-system/scripts/generate-migration.py +382 -0
- package/claude/skills/entity-system/scripts/generate-sample-data.py +418 -0
- package/claude/skills/entity-system/scripts/scaffold-entity.py +661 -0
- package/claude/skills/github/SKILL.md +467 -0
- package/claude/skills/i18n-nextintl/SKILL.md +302 -0
- package/claude/skills/i18n-nextintl/scripts/add-translation.py +243 -0
- package/claude/skills/i18n-nextintl/scripts/extract-hardcoded.py +246 -0
- package/claude/skills/i18n-nextintl/scripts/validate-translations.py +260 -0
- package/claude/skills/impact-analysis/SKILL.md +203 -0
- package/claude/skills/jest-unit/SKILL.md +306 -0
- package/claude/skills/jest-unit/references/component-testing.md +371 -0
- package/claude/skills/jest-unit/references/mocking-patterns.md +380 -0
- package/claude/skills/jest-unit/references/service-hook-testing.md +454 -0
- package/claude/skills/jira-integration/SKILL.md +539 -0
- package/claude/skills/media-library/SKILL.md +743 -0
- package/claude/skills/mock-analysis/SKILL.md +276 -0
- package/claude/skills/monorepo-architecture/SKILL.md +162 -0
- package/claude/skills/nextjs-api-development/SKILL.md +364 -0
- package/claude/skills/nextjs-api-development/scripts/generate-crud-tests.py +456 -0
- package/claude/skills/nextjs-api-development/scripts/scaffold-endpoint.py +481 -0
- package/claude/skills/nextjs-api-development/scripts/validate-api.py +283 -0
- package/claude/skills/notion-integration/SKILL.md +641 -0
- package/claude/skills/npm-development-workflow/SKILL.md +480 -0
- package/claude/skills/page-builder-blocks/SKILL.md +530 -0
- package/claude/skills/page-builder-blocks/scripts/scaffold-block.py +444 -0
- package/claude/skills/permissions-system/SKILL.md +619 -0
- package/claude/skills/plugins/SKILL.md +340 -0
- package/claude/skills/plugins/references/plugin-templates.md +414 -0
- package/claude/skills/plugins/references/plugin-testing.md +353 -0
- package/claude/skills/plugins/references/plugin-types.md +198 -0
- package/claude/skills/plugins/scripts/scaffold-plugin.py +443 -0
- package/claude/skills/pom-patterns/SKILL.md +452 -0
- package/claude/skills/pom-patterns/scripts/generate-pom.py +392 -0
- package/claude/skills/rate-limiting/SKILL.md +342 -0
- package/claude/skills/react-best-practices/AGENTS.md +2410 -0
- package/claude/skills/react-best-practices/README.md +123 -0
- package/claude/skills/react-best-practices/SKILL.md +125 -0
- package/claude/skills/react-best-practices/metadata.json +15 -0
- package/claude/skills/react-best-practices/rules/_sections.md +46 -0
- package/claude/skills/react-best-practices/rules/_template.md +28 -0
- package/claude/skills/react-best-practices/rules/advanced-event-handler-refs.md +55 -0
- package/claude/skills/react-best-practices/rules/advanced-use-latest.md +49 -0
- package/claude/skills/react-best-practices/rules/async-api-routes.md +38 -0
- package/claude/skills/react-best-practices/rules/async-defer-await.md +80 -0
- package/claude/skills/react-best-practices/rules/async-dependencies.md +36 -0
- package/claude/skills/react-best-practices/rules/async-parallel.md +28 -0
- package/claude/skills/react-best-practices/rules/async-suspense-boundaries.md +99 -0
- package/claude/skills/react-best-practices/rules/bundle-barrel-imports.md +59 -0
- package/claude/skills/react-best-practices/rules/bundle-conditional.md +31 -0
- package/claude/skills/react-best-practices/rules/bundle-defer-third-party.md +49 -0
- package/claude/skills/react-best-practices/rules/bundle-dynamic-imports.md +35 -0
- package/claude/skills/react-best-practices/rules/bundle-preload.md +50 -0
- package/claude/skills/react-best-practices/rules/client-event-listeners.md +74 -0
- package/claude/skills/react-best-practices/rules/client-localstorage-schema.md +71 -0
- package/claude/skills/react-best-practices/rules/client-passive-event-listeners.md +48 -0
- package/claude/skills/react-best-practices/rules/client-swr-dedup.md +56 -0
- package/claude/skills/react-best-practices/rules/js-batch-dom-css.md +82 -0
- package/claude/skills/react-best-practices/rules/js-cache-function-results.md +80 -0
- package/claude/skills/react-best-practices/rules/js-cache-property-access.md +28 -0
- package/claude/skills/react-best-practices/rules/js-cache-storage.md +70 -0
- package/claude/skills/react-best-practices/rules/js-combine-iterations.md +32 -0
- package/claude/skills/react-best-practices/rules/js-early-exit.md +50 -0
- package/claude/skills/react-best-practices/rules/js-hoist-regexp.md +45 -0
- package/claude/skills/react-best-practices/rules/js-index-maps.md +37 -0
- package/claude/skills/react-best-practices/rules/js-length-check-first.md +49 -0
- package/claude/skills/react-best-practices/rules/js-min-max-loop.md +82 -0
- package/claude/skills/react-best-practices/rules/js-set-map-lookups.md +24 -0
- package/claude/skills/react-best-practices/rules/js-tosorted-immutable.md +57 -0
- package/claude/skills/react-best-practices/rules/rendering-activity.md +26 -0
- package/claude/skills/react-best-practices/rules/rendering-animate-svg-wrapper.md +47 -0
- package/claude/skills/react-best-practices/rules/rendering-conditional-render.md +40 -0
- package/claude/skills/react-best-practices/rules/rendering-content-visibility.md +38 -0
- package/claude/skills/react-best-practices/rules/rendering-hoist-jsx.md +46 -0
- package/claude/skills/react-best-practices/rules/rendering-hydration-no-flicker.md +82 -0
- package/claude/skills/react-best-practices/rules/rendering-svg-precision.md +28 -0
- package/claude/skills/react-best-practices/rules/rerender-defer-reads.md +39 -0
- package/claude/skills/react-best-practices/rules/rerender-dependencies.md +45 -0
- package/claude/skills/react-best-practices/rules/rerender-derived-state.md +29 -0
- package/claude/skills/react-best-practices/rules/rerender-functional-setstate.md +74 -0
- package/claude/skills/react-best-practices/rules/rerender-lazy-state-init.md +58 -0
- package/claude/skills/react-best-practices/rules/rerender-memo.md +44 -0
- package/claude/skills/react-best-practices/rules/rerender-transitions.md +40 -0
- package/claude/skills/react-best-practices/rules/server-after-nonblocking.md +73 -0
- package/claude/skills/react-best-practices/rules/server-cache-lru.md +41 -0
- package/claude/skills/react-best-practices/rules/server-cache-react.md +76 -0
- package/claude/skills/react-best-practices/rules/server-parallel-fetching.md +83 -0
- package/claude/skills/react-best-practices/rules/server-serialization.md +38 -0
- package/claude/skills/react-patterns/SKILL.md +688 -0
- package/claude/skills/registry-system/SKILL.md +331 -0
- package/claude/skills/scheduled-actions/SKILL.md +671 -0
- package/claude/skills/scope-enforcement/SKILL.md +542 -0
- package/claude/skills/scope-enforcement/scripts/validate-scope.py +357 -0
- package/claude/skills/server-actions/SKILL.md +493 -0
- package/claude/skills/service-layer/SKILL.md +587 -0
- package/claude/skills/session-management/SKILL.md +266 -0
- package/claude/skills/session-management/scripts/create-session.py +166 -0
- package/claude/skills/session-management/scripts/iteration-close.sh +105 -0
- package/claude/skills/session-management/scripts/iteration-init.sh +180 -0
- package/claude/skills/session-management/scripts/session-archive.sh +87 -0
- package/claude/skills/session-management/scripts/session-close.sh +133 -0
- package/claude/skills/session-management/scripts/session-init.sh +225 -0
- package/claude/skills/session-management/scripts/session-list.sh +163 -0
- package/claude/skills/session-management/scripts/split-plan.sh +116 -0
- package/claude/skills/shadcn-components/SKILL.md +586 -0
- package/claude/skills/shadcn-theming/SKILL.md +446 -0
- package/claude/skills/suspense-loading/SKILL.md +280 -0
- package/claude/skills/tailwind-theming/SKILL.md +507 -0
- package/claude/skills/tanstack-query/SKILL.md +608 -0
- package/claude/skills/test-coverage/SKILL.md +239 -0
- package/claude/skills/web-design-guidelines/SKILL.md +39 -0
- package/claude/skills/zod-validation/SKILL.md +537 -0
- package/claude/templates/blocks/progress.md +86 -0
- package/claude/templates/iteration/changes.md +61 -0
- package/claude/templates/iteration/progress.md +55 -0
- package/claude/templates/log.md +31 -0
- package/claude/templates/story/context.md +77 -0
- package/claude/templates/story/pendings.md +37 -0
- package/claude/templates/story/plan.md +299 -0
- package/claude/templates/story/requirements.md +109 -0
- package/claude/templates/story/scope.json +10 -0
- package/claude/templates/story/tests.md +91 -0
- package/claude/templates/task/progress.md +58 -0
- package/claude/templates/task/requirements.md +54 -0
- package/claude/workflows/README.md +154 -0
- package/claude/workflows/blocks.md +614 -0
- package/claude/workflows/story.md +1207 -0
- package/claude/workflows/task.md +927 -0
- package/claude/workflows/tweak.md +527 -0
- package/cursor/.gitkeep +0 -0
- package/package.json +35 -0
- package/scripts/postinstall.mjs +198 -0
- package/scripts/setup.mjs +282 -0
- package/scripts/sync.mjs +209 -0
|
@@ -0,0 +1,302 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: i18n-nextintl
|
|
3
|
+
description: |
|
|
4
|
+
Internationalization with next-intl for Next.js 15.
|
|
5
|
+
Supports core/theme/entity message layers, namespace groups, and translation registry.
|
|
6
|
+
Use this skill when adding translations, validating i18n, or working with localized content.
|
|
7
|
+
allowed-tools: Read, Glob, Grep, Bash(python:*)
|
|
8
|
+
version: 1.0.0
|
|
9
|
+
---
|
|
10
|
+
|
|
11
|
+
# i18n with next-intl Skill
|
|
12
|
+
|
|
13
|
+
Internationalization patterns for Next.js 15 using next-intl with auto-generated translation registry.
|
|
14
|
+
|
|
15
|
+
## Architecture Overview
|
|
16
|
+
|
|
17
|
+
```
|
|
18
|
+
┌─────────────────────────────────────────────────────────────────┐
|
|
19
|
+
│ TRANSLATION SOURCES │
|
|
20
|
+
├─────────────────────────────────────────────────────────────────┤
|
|
21
|
+
│ │
|
|
22
|
+
│ 1. CORE MESSAGES (Application-wide) │
|
|
23
|
+
│ core/messages/{locale}/ │
|
|
24
|
+
│ ├── index.ts # Re-exports all namespaces │
|
|
25
|
+
│ ├── common.json # Buttons, labels, etc. │
|
|
26
|
+
│ ├── auth.json # Authentication messages │
|
|
27
|
+
│ ├── dashboard.json # Dashboard UI │
|
|
28
|
+
│ └── ... │
|
|
29
|
+
│ │
|
|
30
|
+
│ 2. THEME MESSAGES (Theme-specific overrides) │
|
|
31
|
+
│ contents/themes/{theme}/messages/{locale}.json │
|
|
32
|
+
│ - Extends/overrides core translations │
|
|
33
|
+
│ - Custom roles (editor, moderator) ONLY here │
|
|
34
|
+
│ │
|
|
35
|
+
│ 3. ENTITY MESSAGES (Entity-specific) │
|
|
36
|
+
│ contents/themes/{theme}/entities/{entity}/messages/ │
|
|
37
|
+
│ - Field labels, placeholders, descriptions │
|
|
38
|
+
│ │
|
|
39
|
+
└─────────────────────────────────────────────────────────────────┘
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
## When to Use This Skill
|
|
43
|
+
|
|
44
|
+
- Adding new translations to components
|
|
45
|
+
- Validating translation completeness (EN/ES)
|
|
46
|
+
- Finding hardcoded strings in UI
|
|
47
|
+
- Understanding namespace structure
|
|
48
|
+
- Working with custom team roles (theme-level only)
|
|
49
|
+
|
|
50
|
+
## Supported Locales
|
|
51
|
+
|
|
52
|
+
- `en` - English (default)
|
|
53
|
+
- `es` - Spanish
|
|
54
|
+
|
|
55
|
+
## Key Concepts
|
|
56
|
+
|
|
57
|
+
### Namespace Groups
|
|
58
|
+
|
|
59
|
+
The system uses optimized namespace loading based on page context:
|
|
60
|
+
|
|
61
|
+
```typescript
|
|
62
|
+
const NAMESPACE_GROUPS = {
|
|
63
|
+
// Public pages (includes auth for login/signup buttons)
|
|
64
|
+
PUBLIC_INITIAL: ['common', 'public', 'auth'],
|
|
65
|
+
|
|
66
|
+
// Dashboard (authenticated users)
|
|
67
|
+
DASHBOARD_AUTHENTICATED: ['common', 'dashboard', 'settings', 'public', 'teams'],
|
|
68
|
+
|
|
69
|
+
// Auth-specific pages
|
|
70
|
+
AUTH_ONLY: ['common', 'auth', 'validation'],
|
|
71
|
+
|
|
72
|
+
// Fallback (all namespaces)
|
|
73
|
+
ALL: ['common', 'dashboard', 'settings', 'auth', 'public', 'validation', 'teams']
|
|
74
|
+
}
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
### Translation Registry
|
|
78
|
+
|
|
79
|
+
Auto-generated at `core/lib/registries/translation-registry.ts`:
|
|
80
|
+
|
|
81
|
+
```typescript
|
|
82
|
+
// ✅ CORRECT - Use registry functions
|
|
83
|
+
import { loadThemeTranslation } from '@/core/lib/registries/translation-registry'
|
|
84
|
+
|
|
85
|
+
const translations = await loadThemeTranslation(themeName, locale)
|
|
86
|
+
|
|
87
|
+
// ❌ NEVER - Runtime string interpolation
|
|
88
|
+
const translations = await import(`@/contents/themes/${theme}/messages/${locale}.json`)
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
## Component Patterns
|
|
92
|
+
|
|
93
|
+
### Server Components
|
|
94
|
+
|
|
95
|
+
```typescript
|
|
96
|
+
import { getTranslations } from 'next-intl/server'
|
|
97
|
+
|
|
98
|
+
export default async function WelcomePage() {
|
|
99
|
+
const t = await getTranslations('welcome')
|
|
100
|
+
|
|
101
|
+
return (
|
|
102
|
+
<div>
|
|
103
|
+
<h1>{t('title')}</h1>
|
|
104
|
+
<p>{t('description')}</p>
|
|
105
|
+
</div>
|
|
106
|
+
)
|
|
107
|
+
}
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
### Client Components
|
|
111
|
+
|
|
112
|
+
```typescript
|
|
113
|
+
'use client'
|
|
114
|
+
import { useTranslations } from 'next-intl'
|
|
115
|
+
|
|
116
|
+
export function WelcomeCard() {
|
|
117
|
+
const t = useTranslations('welcome')
|
|
118
|
+
|
|
119
|
+
return (
|
|
120
|
+
<div>
|
|
121
|
+
<h2>{t('title')}</h2>
|
|
122
|
+
<p>{t('description')}</p>
|
|
123
|
+
</div>
|
|
124
|
+
)
|
|
125
|
+
}
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
### With Dynamic Values
|
|
129
|
+
|
|
130
|
+
```typescript
|
|
131
|
+
// Translation file
|
|
132
|
+
{
|
|
133
|
+
"welcome": {
|
|
134
|
+
"greeting": "Hello, {name}!",
|
|
135
|
+
"items": "{count, plural, =0 {No items} =1 {One item} other {# items}}"
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
// Component
|
|
140
|
+
const t = useTranslations('welcome')
|
|
141
|
+
<p>{t('greeting', { name: user.name })}</p>
|
|
142
|
+
<p>{t('items', { count: items.length })}</p>
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
## Custom Roles Rule (CRITICAL)
|
|
146
|
+
|
|
147
|
+
**Custom role translations MUST NEVER be in core/messages/. They belong ONLY in theme messages.**
|
|
148
|
+
|
|
149
|
+
| Translation Key | Core (`core/messages/`) | Theme (`contents/themes/*/messages/`) |
|
|
150
|
+
|-----------------|-------------------------|---------------------------------------|
|
|
151
|
+
| `teams.roles.owner` | MUST define here | Can override |
|
|
152
|
+
| `teams.roles.admin` | MUST define here | Can override |
|
|
153
|
+
| `teams.roles.member` | MUST define here | Can override |
|
|
154
|
+
| `teams.roles.viewer` | MUST define here | Can override |
|
|
155
|
+
| `teams.roles.editor` | MUST NOT define | MUST define here |
|
|
156
|
+
| `teams.roles.moderator` | MUST NOT define | MUST define here |
|
|
157
|
+
|
|
158
|
+
### Correct Pattern
|
|
159
|
+
|
|
160
|
+
```json
|
|
161
|
+
// core/messages/en/teams.json - ONLY core roles
|
|
162
|
+
{
|
|
163
|
+
"teams": {
|
|
164
|
+
"roles": {
|
|
165
|
+
"owner": "Owner",
|
|
166
|
+
"admin": "Administrator",
|
|
167
|
+
"member": "Member",
|
|
168
|
+
"viewer": "Viewer"
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
// contents/themes/default/messages/en.json - Theme extends
|
|
174
|
+
{
|
|
175
|
+
"teams": {
|
|
176
|
+
"roles": {
|
|
177
|
+
"editor": "Editor",
|
|
178
|
+
"moderator": "Moderator"
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
```
|
|
183
|
+
|
|
184
|
+
## Translation Key Naming
|
|
185
|
+
|
|
186
|
+
```typescript
|
|
187
|
+
// ✅ CORRECT - Hierarchical, descriptive
|
|
188
|
+
{
|
|
189
|
+
"auth": {
|
|
190
|
+
"login": {
|
|
191
|
+
"title": "Sign In",
|
|
192
|
+
"emailLabel": "Email Address",
|
|
193
|
+
"errors": {
|
|
194
|
+
"invalidCredentials": "Invalid email or password"
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
// ❌ FORBIDDEN - Flat, unclear keys
|
|
201
|
+
{
|
|
202
|
+
"login_title": "Sign In",
|
|
203
|
+
"email": "Email"
|
|
204
|
+
}
|
|
205
|
+
```
|
|
206
|
+
|
|
207
|
+
## Scripts
|
|
208
|
+
|
|
209
|
+
### Find Hardcoded Strings
|
|
210
|
+
```bash
|
|
211
|
+
# Find hardcoded strings in components
|
|
212
|
+
python .claude/skills/i18n-nextintl/scripts/extract-hardcoded.py \
|
|
213
|
+
--path contents/themes/default/components/
|
|
214
|
+
|
|
215
|
+
# Preview without file details
|
|
216
|
+
python .claude/skills/i18n-nextintl/scripts/extract-hardcoded.py \
|
|
217
|
+
--path app/ \
|
|
218
|
+
--dry-run
|
|
219
|
+
```
|
|
220
|
+
|
|
221
|
+
### Validate Translation Completeness
|
|
222
|
+
```bash
|
|
223
|
+
# Compare EN and ES translations
|
|
224
|
+
python .claude/skills/i18n-nextintl/scripts/validate-translations.py
|
|
225
|
+
|
|
226
|
+
# Check theme-specific translations
|
|
227
|
+
python .claude/skills/i18n-nextintl/scripts/validate-translations.py \
|
|
228
|
+
--theme default
|
|
229
|
+
|
|
230
|
+
# Strict mode (exit with error if missing keys)
|
|
231
|
+
python .claude/skills/i18n-nextintl/scripts/validate-translations.py \
|
|
232
|
+
--strict
|
|
233
|
+
```
|
|
234
|
+
|
|
235
|
+
### Add Translation Key
|
|
236
|
+
```bash
|
|
237
|
+
# Add key to both locales
|
|
238
|
+
python .claude/skills/i18n-nextintl/scripts/add-translation.py \
|
|
239
|
+
--key "settings.profile.title" \
|
|
240
|
+
--en "Profile Settings" \
|
|
241
|
+
--es "Configuracion de Perfil"
|
|
242
|
+
|
|
243
|
+
# Add to theme messages instead of core
|
|
244
|
+
python .claude/skills/i18n-nextintl/scripts/add-translation.py \
|
|
245
|
+
--key "teams.roles.editor" \
|
|
246
|
+
--en "Editor" \
|
|
247
|
+
--es "Editor" \
|
|
248
|
+
--theme default
|
|
249
|
+
|
|
250
|
+
# Preview without writing
|
|
251
|
+
python .claude/skills/i18n-nextintl/scripts/add-translation.py \
|
|
252
|
+
--key "new.key" \
|
|
253
|
+
--en "Value" \
|
|
254
|
+
--es "Valor" \
|
|
255
|
+
--dry-run
|
|
256
|
+
```
|
|
257
|
+
|
|
258
|
+
## Anti-Patterns
|
|
259
|
+
|
|
260
|
+
```typescript
|
|
261
|
+
// ❌ NEVER: Hardcoded user-facing text
|
|
262
|
+
export function WelcomeCard() {
|
|
263
|
+
return <h1>Welcome to Dashboard</h1> // Wrong!
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
// ❌ NEVER: String concatenation
|
|
267
|
+
const message = "Welcome, " + userName + "!"
|
|
268
|
+
|
|
269
|
+
// ❌ NEVER: Runtime string interpolation in imports
|
|
270
|
+
const translations = await import(`@/core/messages/${locale}/`)
|
|
271
|
+
|
|
272
|
+
// ✅ CORRECT: Use translations
|
|
273
|
+
export function WelcomeCard() {
|
|
274
|
+
const t = useTranslations('dashboard')
|
|
275
|
+
return <h1>{t('welcome')}</h1>
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
// ✅ CORRECT: Use interpolation
|
|
279
|
+
const message = t('greeting', { name: userName })
|
|
280
|
+
```
|
|
281
|
+
|
|
282
|
+
## File Locations
|
|
283
|
+
|
|
284
|
+
| Type | Location | Who Modifies |
|
|
285
|
+
|------|----------|--------------|
|
|
286
|
+
| Core translations | `core/messages/{locale}/` | Core maintainers |
|
|
287
|
+
| Theme translations | `contents/themes/{theme}/messages/{locale}.json` | Theme developers |
|
|
288
|
+
| Entity translations | Entity config `i18n.loaders` | Entity developers |
|
|
289
|
+
| Translation registry | `core/lib/registries/translation-registry.ts` | Auto-generated |
|
|
290
|
+
| i18n config | `core/i18n.ts` | Core maintainers |
|
|
291
|
+
|
|
292
|
+
## Checklist
|
|
293
|
+
|
|
294
|
+
Before committing i18n changes:
|
|
295
|
+
|
|
296
|
+
- [ ] All user-facing text uses translation keys (no hardcoded strings)
|
|
297
|
+
- [ ] Translation keys exist in ALL supported locales (en, es)
|
|
298
|
+
- [ ] Translation keys follow hierarchical naming convention
|
|
299
|
+
- [ ] Pluralization uses ICU message format
|
|
300
|
+
- [ ] Dynamic values use translation interpolation (no concatenation)
|
|
301
|
+
- [ ] Custom roles defined ONLY in theme messages (never core)
|
|
302
|
+
- [ ] No runtime string interpolation in dynamic imports
|
|
@@ -0,0 +1,243 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
Add Translation Script
|
|
4
|
+
|
|
5
|
+
Adds a translation key to both EN and ES locales.
|
|
6
|
+
Supports both core messages and theme messages.
|
|
7
|
+
|
|
8
|
+
Usage:
|
|
9
|
+
python add-translation.py --key KEY --en VALUE --es VALUE [--theme THEME]
|
|
10
|
+
|
|
11
|
+
Options:
|
|
12
|
+
--key KEY Dot-notation key (e.g., "settings.profile.title")
|
|
13
|
+
--en VALUE English translation value
|
|
14
|
+
--es VALUE Spanish translation value
|
|
15
|
+
--theme THEME Add to theme messages instead of core
|
|
16
|
+
--namespace NS Core namespace file (e.g., "dashboard", "common")
|
|
17
|
+
--dry-run Preview without writing files
|
|
18
|
+
"""
|
|
19
|
+
|
|
20
|
+
import os
|
|
21
|
+
import sys
|
|
22
|
+
import json
|
|
23
|
+
import argparse
|
|
24
|
+
from pathlib import Path
|
|
25
|
+
from typing import Dict, Any
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def get_active_theme() -> str:
|
|
29
|
+
"""Get active theme from environment or default."""
|
|
30
|
+
return os.environ.get('NEXT_PUBLIC_ACTIVE_THEME', 'default')
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def load_json_file(file_path: Path) -> dict:
|
|
34
|
+
"""Load JSON file and return dict."""
|
|
35
|
+
try:
|
|
36
|
+
with open(file_path, 'r', encoding='utf-8') as f:
|
|
37
|
+
return json.load(f)
|
|
38
|
+
except FileNotFoundError:
|
|
39
|
+
return {}
|
|
40
|
+
except json.JSONDecodeError as e:
|
|
41
|
+
print(f" Error: Invalid JSON in {file_path}: {e}")
|
|
42
|
+
sys.exit(1)
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def save_json_file(file_path: Path, data: dict, dry_run: bool = False) -> None:
|
|
46
|
+
"""Save dict to JSON file with proper formatting."""
|
|
47
|
+
if dry_run:
|
|
48
|
+
print(f" Would write to: {file_path}")
|
|
49
|
+
return
|
|
50
|
+
|
|
51
|
+
# Ensure parent directory exists
|
|
52
|
+
file_path.parent.mkdir(parents=True, exist_ok=True)
|
|
53
|
+
|
|
54
|
+
with open(file_path, 'w', encoding='utf-8') as f:
|
|
55
|
+
json.dump(data, f, indent=2, ensure_ascii=False)
|
|
56
|
+
f.write('\n') # Add trailing newline
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
def set_nested_value(data: dict, key_path: str, value: Any) -> dict:
|
|
60
|
+
"""Set a value in a nested dict using dot notation."""
|
|
61
|
+
keys = key_path.split('.')
|
|
62
|
+
current = data
|
|
63
|
+
|
|
64
|
+
# Navigate to the parent of the target key
|
|
65
|
+
for key in keys[:-1]:
|
|
66
|
+
if key not in current:
|
|
67
|
+
current[key] = {}
|
|
68
|
+
elif not isinstance(current[key], dict):
|
|
69
|
+
# Key exists but is not a dict, cannot proceed
|
|
70
|
+
raise ValueError(f"Cannot set '{key_path}': '{key}' is not an object")
|
|
71
|
+
current = current[key]
|
|
72
|
+
|
|
73
|
+
# Set the final value
|
|
74
|
+
current[keys[-1]] = value
|
|
75
|
+
return data
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
def get_nested_value(data: dict, key_path: str) -> Any:
|
|
79
|
+
"""Get a value from a nested dict using dot notation."""
|
|
80
|
+
keys = key_path.split('.')
|
|
81
|
+
current = data
|
|
82
|
+
|
|
83
|
+
for key in keys:
|
|
84
|
+
if not isinstance(current, dict) or key not in current:
|
|
85
|
+
return None
|
|
86
|
+
current = current[key]
|
|
87
|
+
|
|
88
|
+
return current
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
def infer_namespace(key: str) -> str:
|
|
92
|
+
"""Infer namespace from key path."""
|
|
93
|
+
# First part of key is typically the namespace
|
|
94
|
+
parts = key.split('.')
|
|
95
|
+
if len(parts) >= 1:
|
|
96
|
+
return parts[0]
|
|
97
|
+
return 'common'
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
def add_to_core(key: str, en_value: str, es_value: str, namespace: str, dry_run: bool) -> bool:
|
|
101
|
+
"""Add translation to core messages."""
|
|
102
|
+
# Determine file paths
|
|
103
|
+
en_path = Path(f'core/messages/en/{namespace}.json')
|
|
104
|
+
es_path = Path(f'core/messages/es/{namespace}.json')
|
|
105
|
+
|
|
106
|
+
# Load existing data
|
|
107
|
+
en_data = load_json_file(en_path)
|
|
108
|
+
es_data = load_json_file(es_path)
|
|
109
|
+
|
|
110
|
+
# Remove namespace prefix from key if it matches
|
|
111
|
+
key_without_ns = key
|
|
112
|
+
if key.startswith(f'{namespace}.'):
|
|
113
|
+
key_without_ns = key[len(namespace) + 1:]
|
|
114
|
+
|
|
115
|
+
# Check if key already exists
|
|
116
|
+
if get_nested_value(en_data, key_without_ns) is not None:
|
|
117
|
+
print(f" Warning: Key '{key}' already exists in EN")
|
|
118
|
+
|
|
119
|
+
# Set values
|
|
120
|
+
en_data = set_nested_value(en_data, key_without_ns, en_value)
|
|
121
|
+
es_data = set_nested_value(es_data, key_without_ns, es_value)
|
|
122
|
+
|
|
123
|
+
# Save files
|
|
124
|
+
save_json_file(en_path, en_data, dry_run)
|
|
125
|
+
save_json_file(es_path, es_data, dry_run)
|
|
126
|
+
|
|
127
|
+
return True
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
def add_to_theme(key: str, en_value: str, es_value: str, theme: str, dry_run: bool) -> bool:
|
|
131
|
+
"""Add translation to theme messages."""
|
|
132
|
+
en_path = Path(f'contents/themes/{theme}/messages/en.json')
|
|
133
|
+
es_path = Path(f'contents/themes/{theme}/messages/es.json')
|
|
134
|
+
|
|
135
|
+
# Check if theme exists
|
|
136
|
+
theme_path = Path(f'contents/themes/{theme}')
|
|
137
|
+
if not theme_path.exists():
|
|
138
|
+
print(f" Error: Theme '{theme}' not found at {theme_path}")
|
|
139
|
+
return False
|
|
140
|
+
|
|
141
|
+
# Load existing data
|
|
142
|
+
en_data = load_json_file(en_path)
|
|
143
|
+
es_data = load_json_file(es_path)
|
|
144
|
+
|
|
145
|
+
# Check if key already exists
|
|
146
|
+
if get_nested_value(en_data, key) is not None:
|
|
147
|
+
print(f" Warning: Key '{key}' already exists in EN")
|
|
148
|
+
|
|
149
|
+
# Set values
|
|
150
|
+
en_data = set_nested_value(en_data, key, en_value)
|
|
151
|
+
es_data = set_nested_value(es_data, key, es_value)
|
|
152
|
+
|
|
153
|
+
# Save files
|
|
154
|
+
save_json_file(en_path, en_data, dry_run)
|
|
155
|
+
save_json_file(es_path, es_data, dry_run)
|
|
156
|
+
|
|
157
|
+
return True
|
|
158
|
+
|
|
159
|
+
|
|
160
|
+
def main():
|
|
161
|
+
parser = argparse.ArgumentParser(description='Add translation key')
|
|
162
|
+
parser.add_argument('--key', required=True, help='Dot-notation key path')
|
|
163
|
+
parser.add_argument('--en', required=True, help='English value')
|
|
164
|
+
parser.add_argument('--es', required=True, help='Spanish value')
|
|
165
|
+
parser.add_argument('--theme', default=None, help='Add to theme instead of core')
|
|
166
|
+
parser.add_argument('--namespace', default=None, help='Core namespace file')
|
|
167
|
+
parser.add_argument('--dry-run', action='store_true', help='Preview without writing')
|
|
168
|
+
|
|
169
|
+
args = parser.parse_args()
|
|
170
|
+
|
|
171
|
+
print(f"\n{'=' * 60}")
|
|
172
|
+
print(f"ADDING TRANSLATION")
|
|
173
|
+
print(f"{'=' * 60}")
|
|
174
|
+
print(f"Key: {args.key}")
|
|
175
|
+
print(f"EN: {args.en}")
|
|
176
|
+
print(f"ES: {args.es}")
|
|
177
|
+
|
|
178
|
+
if args.theme:
|
|
179
|
+
print(f"Target: Theme ({args.theme})")
|
|
180
|
+
else:
|
|
181
|
+
namespace = args.namespace or infer_namespace(args.key)
|
|
182
|
+
print(f"Target: Core (namespace: {namespace})")
|
|
183
|
+
|
|
184
|
+
print(f"Dry run: {args.dry_run}")
|
|
185
|
+
print(f"{'=' * 60}\n")
|
|
186
|
+
|
|
187
|
+
try:
|
|
188
|
+
if args.theme:
|
|
189
|
+
# Add to theme messages
|
|
190
|
+
success = add_to_theme(args.key, args.en, args.es, args.theme, args.dry_run)
|
|
191
|
+
else:
|
|
192
|
+
# Add to core messages
|
|
193
|
+
namespace = args.namespace or infer_namespace(args.key)
|
|
194
|
+
success = add_to_core(args.key, args.en, args.es, namespace, args.dry_run)
|
|
195
|
+
|
|
196
|
+
if success:
|
|
197
|
+
if args.dry_run:
|
|
198
|
+
print(" DRY RUN: No files were modified")
|
|
199
|
+
else:
|
|
200
|
+
print(" Translation added successfully!")
|
|
201
|
+
|
|
202
|
+
print(f"\n{'=' * 60}")
|
|
203
|
+
print("USAGE IN COMPONENT:")
|
|
204
|
+
print("=" * 60)
|
|
205
|
+
|
|
206
|
+
# Show usage example
|
|
207
|
+
namespace = args.namespace or infer_namespace(args.key)
|
|
208
|
+
key_parts = args.key.split('.')
|
|
209
|
+
if len(key_parts) > 1:
|
|
210
|
+
t_namespace = key_parts[0]
|
|
211
|
+
t_key = '.'.join(key_parts[1:])
|
|
212
|
+
else:
|
|
213
|
+
t_namespace = namespace
|
|
214
|
+
t_key = args.key
|
|
215
|
+
|
|
216
|
+
print(f"""
|
|
217
|
+
// Client component
|
|
218
|
+
import {{ useTranslations }} from 'next-intl'
|
|
219
|
+
|
|
220
|
+
const t = useTranslations('{t_namespace}')
|
|
221
|
+
<span>{{t('{t_key}')}}</span>
|
|
222
|
+
|
|
223
|
+
// Server component
|
|
224
|
+
import {{ getTranslations }} from 'next-intl/server'
|
|
225
|
+
|
|
226
|
+
const t = await getTranslations('{t_namespace}')
|
|
227
|
+
<span>{{t('{t_key}')}}</span>
|
|
228
|
+
""")
|
|
229
|
+
print("=" * 60 + "\n")
|
|
230
|
+
return 0
|
|
231
|
+
else:
|
|
232
|
+
return 1
|
|
233
|
+
|
|
234
|
+
except ValueError as e:
|
|
235
|
+
print(f" Error: {e}")
|
|
236
|
+
return 1
|
|
237
|
+
except Exception as e:
|
|
238
|
+
print(f" Unexpected error: {e}")
|
|
239
|
+
return 1
|
|
240
|
+
|
|
241
|
+
|
|
242
|
+
if __name__ == '__main__':
|
|
243
|
+
sys.exit(main())
|