diff --git a/apps/web/src/app/page.tsx b/apps/web/src/app/page.tsx
index 1f405fed..4790baa1 100644
--- a/apps/web/src/app/page.tsx
+++ b/apps/web/src/app/page.tsx
@@ -2,6 +2,7 @@
import Link from 'next/link'
import { useEffect, useState } from 'react'
+import { useTranslations } from 'next-intl'
import { AbacusReact, useAbacusConfig } from '@soroban/abacus-react'
import { HeroAbacus } from '@/components/HeroAbacus'
import { HomeHeroProvider } from '@/contexts/HomeHeroContext'
@@ -74,6 +75,7 @@ function MiniAbacus({
}
export default function HomePage() {
+ const t = useTranslations('home')
const [selectedSkillIndex, setSelectedSkillIndex] = useState(1) // Default to "Friends techniques"
const fullTutorial = getTutorialForEditor()
@@ -83,32 +85,32 @@ export default function HomePage() {
{
...fullTutorial,
id: 'read-numbers-demo',
- title: 'Read and Set Numbers',
- description: 'Master abacus number representation from zero to thousands',
+ title: t('skills.readNumbers.tutorialTitle'),
+ description: t('skills.readNumbers.tutorialDesc'),
steps: fullTutorial.steps.filter((step) => step.id.startsWith('basic-')),
},
// Skill 1: Friends techniques (5 = 2+3)
{
...fullTutorial,
id: 'friends-of-5-demo',
- title: 'Friends of 5',
- description: 'Add and subtract using complement pairs: 5 = 2+3',
+ title: t('skills.friends.tutorialTitle'),
+ description: t('skills.friends.tutorialDesc'),
steps: fullTutorial.steps.filter((step) => step.id === 'complement-2'),
},
// Skill 2: Multiply & divide (12×34)
{
...fullTutorial,
id: 'multiply-demo',
- title: 'Multiplication',
- description: 'Fluent multi-digit calculations with advanced techniques',
+ title: t('skills.multiply.tutorialTitle'),
+ description: t('skills.multiply.tutorialDesc'),
steps: fullTutorial.steps.filter((step) => step.id.includes('complement')).slice(0, 3),
},
// Skill 3: Mental calculation (Speed math)
{
...fullTutorial,
id: 'mental-calc-demo',
- title: 'Mental Calculation',
- description: 'Visualize and compute without the physical tool (Anzan)',
+ title: t('skills.mental.tutorialTitle'),
+ description: t('skills.mental.tutorialDesc'),
steps: fullTutorial.steps.slice(-3),
},
]
@@ -133,10 +135,10 @@ export default function HomePage() {
mb: '2',
})}
>
- Learn by Doing
+ {t('learnByDoing.title')}
- Interactive tutorials teach you step-by-step. Try this example right now:
+ {t('learnByDoing.subtitle')}
@@ -197,39 +199,39 @@ export default function HomePage() {
mb: '6',
})}
>
- What You'll Learn
+ {t('whatYouLearn.title')}
{[
{
- title: '📖 Read and set numbers',
- desc: 'Master abacus number representation from zero to thousands',
- example: '0-9999',
- badge: 'Foundation',
+ title: t('skills.readNumbers.title'),
+ desc: t('skills.readNumbers.desc'),
+ example: t('skills.readNumbers.example'),
+ badge: t('skills.readNumbers.badge'),
values: [0, 1, 2, 3, 4, 5, 10, 50, 100, 500, 999],
columns: 3,
},
{
- title: '🤝 Friends techniques',
- desc: 'Add and subtract using complement pairs and mental shortcuts',
- example: '5 = 2+3',
- badge: 'Core',
+ title: t('skills.friends.title'),
+ desc: t('skills.friends.desc'),
+ example: t('skills.friends.example'),
+ badge: t('skills.friends.badge'),
values: [2, 5, 3],
columns: 1,
},
{
- title: '✖️ Multiply & divide',
- desc: 'Fluent multi-digit calculations with advanced techniques',
- example: '12×34',
- badge: 'Advanced',
+ title: t('skills.multiply.title'),
+ desc: t('skills.multiply.desc'),
+ example: t('skills.multiply.example'),
+ badge: t('skills.multiply.badge'),
values: [12, 24, 36, 48],
columns: 2,
},
{
- title: '🧠 Mental calculation',
- desc: 'Visualize and compute without the physical tool (Anzan)',
- example: 'Speed math',
- badge: 'Expert',
+ title: t('skills.mental.title'),
+ desc: t('skills.mental.desc'),
+ example: t('skills.mental.example'),
+ badge: t('skills.mental.badge'),
values: [7, 14, 21, 28, 35],
columns: 2,
},
@@ -366,20 +368,17 @@ export default function HomePage() {
mb: '2',
})}
>
- The Arcade
+ {t('arcade.title')}
-
- Single-player challenges and multiplayer battles in networked rooms. Invite
- friends to play or watch live.
-
+
{t('arcade.subtitle')}
{getAvailableGames().map((game) => {
const playersText =
game.manifest.maxPlayers === 1
- ? 'Solo challenge'
- : `1-${game.manifest.maxPlayers} players`
+ ? t('arcade.soloChallenge')
+ : t('arcade.playersCount', { min: 1, max: game.manifest.maxPlayers })
return (
- Your Journey
+ {t('journey.title')}
-
- Progress from beginner to master
-
+ {t('journey.subtitle')}
@@ -428,10 +425,10 @@ export default function HomePage() {
mb: '2',
})}
>
- Create Custom Flashcards
+ {t('flashcards.title')}
- Design beautiful flashcards for learning and practice
+ {t('flashcards.subtitle')}
@@ -457,19 +454,19 @@ export default function HomePage() {
{[
{
- icon: '📄',
- title: 'Multiple Formats',
- desc: 'PDF, PNG, SVG, HTML',
+ icon: t('flashcards.features.formats.icon'),
+ title: t('flashcards.features.formats.title'),
+ desc: t('flashcards.features.formats.desc'),
},
{
- icon: '🎨',
- title: 'Customizable',
- desc: 'Bead shapes, colors, layouts',
+ icon: t('flashcards.features.customizable.icon'),
+ title: t('flashcards.features.customizable.title'),
+ desc: t('flashcards.features.customizable.desc'),
},
{
- icon: '📐',
- title: 'All Paper Sizes',
- desc: 'A3, A4, A5, US Letter',
+ icon: t('flashcards.features.paperSizes.icon'),
+ title: t('flashcards.features.paperSizes.title'),
+ desc: t('flashcards.features.paperSizes.desc'),
},
].map((feature, i) => (
- Create Flashcards
+ {t('flashcards.cta')}
→
diff --git a/apps/web/src/i18n/locales/home/en.json b/apps/web/src/i18n/locales/home/en.json
new file mode 100644
index 00000000..4cd98068
--- /dev/null
+++ b/apps/web/src/i18n/locales/home/en.json
@@ -0,0 +1,77 @@
+{
+ "home": {
+ "learnByDoing": {
+ "title": "Learn by Doing",
+ "subtitle": "Interactive tutorials teach you step-by-step. Try this example right now:"
+ },
+ "whatYouLearn": {
+ "title": "What You'll Learn"
+ },
+ "skills": {
+ "readNumbers": {
+ "title": "📖 Read and set numbers",
+ "desc": "Master abacus number representation from zero to thousands",
+ "example": "0-9999",
+ "badge": "Foundation",
+ "tutorialTitle": "Read and Set Numbers",
+ "tutorialDesc": "Master abacus number representation from zero to thousands"
+ },
+ "friends": {
+ "title": "🤝 Friends techniques",
+ "desc": "Add and subtract using complement pairs and mental shortcuts",
+ "example": "5 = 2+3",
+ "badge": "Core",
+ "tutorialTitle": "Friends of 5",
+ "tutorialDesc": "Add and subtract using complement pairs: 5 = 2+3"
+ },
+ "multiply": {
+ "title": "✖️ Multiply & divide",
+ "desc": "Fluent multi-digit calculations with advanced techniques",
+ "example": "12×34",
+ "badge": "Advanced",
+ "tutorialTitle": "Multiplication",
+ "tutorialDesc": "Fluent multi-digit calculations with advanced techniques"
+ },
+ "mental": {
+ "title": "🧠 Mental calculation",
+ "desc": "Visualize and compute without the physical tool (Anzan)",
+ "example": "Speed math",
+ "badge": "Expert",
+ "tutorialTitle": "Mental Calculation",
+ "tutorialDesc": "Visualize and compute without the physical tool (Anzan)"
+ }
+ },
+ "arcade": {
+ "title": "The Arcade",
+ "subtitle": "Single-player challenges and multiplayer battles in networked rooms. Invite friends to play or watch live.",
+ "soloChallenge": "Solo challenge",
+ "playersCount": "{min}-{max} players"
+ },
+ "journey": {
+ "title": "Your Journey",
+ "subtitle": "Progress from beginner to master"
+ },
+ "flashcards": {
+ "title": "Create Custom Flashcards",
+ "subtitle": "Design beautiful flashcards for learning and practice",
+ "features": {
+ "formats": {
+ "icon": "📄",
+ "title": "Multiple Formats",
+ "desc": "PDF, PNG, SVG, HTML"
+ },
+ "customizable": {
+ "icon": "🎨",
+ "title": "Customizable",
+ "desc": "Bead shapes, colors, layouts"
+ },
+ "paperSizes": {
+ "icon": "📐",
+ "title": "All Paper Sizes",
+ "desc": "A3, A4, A5, US Letter"
+ }
+ },
+ "cta": "Create Flashcards"
+ }
+ }
+}
diff --git a/apps/web/src/i18n/locales/home/messages.ts b/apps/web/src/i18n/locales/home/messages.ts
new file mode 100644
index 00000000..d967344b
--- /dev/null
+++ b/apps/web/src/i18n/locales/home/messages.ts
@@ -0,0 +1,15 @@
+import de from './de.json'
+import en from './en.json'
+import es from './es.json'
+import hi from './hi.json'
+import ja from './ja.json'
+import la from './la.json'
+
+export const homeMessages = {
+ en: en.home,
+ de: de.home,
+ ja: ja.home,
+ hi: hi.home,
+ es: es.home,
+ la: la.home,
+} as const
diff --git a/apps/web/src/i18n/messages.ts b/apps/web/src/i18n/messages.ts
new file mode 100644
index 00000000..096cd7c9
--- /dev/null
+++ b/apps/web/src/i18n/messages.ts
@@ -0,0 +1,35 @@
+import { rithmomachiaMessages } from '@/arcade-games/rithmomachia/messages'
+import { homeMessages } from '@/i18n/locales/home/messages'
+
+export type Locale = 'en' | 'de' | 'ja' | 'hi' | 'es' | 'la'
+
+/**
+ * Deep merge messages from multiple sources
+ */
+function mergeMessages(...sources: Record
[]): Record {
+ return sources.reduce((acc, source) => {
+ for (const [key, value] of Object.entries(source)) {
+ if (typeof value === 'object' && value !== null && !Array.isArray(value)) {
+ acc[key] = mergeMessages(acc[key] || {}, value)
+ } else {
+ acc[key] = value
+ }
+ }
+ return acc
+ }, {})
+}
+
+/**
+ * Get all messages for a locale by aggregating co-located translations
+ */
+export async function getMessages(locale: Locale) {
+ // Common app-wide messages (minimal for now, can expand later)
+ const common = {
+ common: {
+ // Add app-wide translations here as needed
+ },
+ }
+
+ // Merge all co-located feature messages
+ return mergeMessages(common, { home: homeMessages[locale] }, rithmomachiaMessages[locale])
+}