Compare commits

...

1 Commits

Author SHA1 Message Date
Thomas Hallock
02e49e9601 feat: localize flashcard generator 2025-11-01 17:50:43 -05:00
14 changed files with 1554 additions and 129 deletions

View File

@@ -3,6 +3,7 @@
import { useAbacusConfig } from '@soroban/abacus-react'
import { useForm } from '@tanstack/react-form'
import { useState } from 'react'
import { useTranslations } from 'next-intl'
import { ConfigurationFormWithoutGenerate } from '@/components/ConfigurationFormWithoutGenerate'
import { GenerationProgress } from '@/components/GenerationProgress'
import { LivePreview } from '@/components/LivePreview'
@@ -107,6 +108,7 @@ export default function CreatePage() {
const [generationStatus, setGenerationStatus] = useState<GenerationStatus>('idle')
const [error, setError] = useState<string | null>(null)
const globalConfig = useAbacusConfig()
const t = useTranslations('create.page')
const form = useForm<FlashcardFormState>({
defaultValues: {
@@ -184,7 +186,7 @@ export default function CreatePage() {
}
return (
<PageWithNav navTitle="Create Flashcards" navEmoji="✨">
<PageWithNav navTitle={t('navTitle')} navEmoji="✨">
<div className={css({ minHeight: '100vh', bg: 'gray.50' })}>
{/* Main Content */}
<div className={container({ maxW: '7xl', px: '4', py: '8' })}>
@@ -197,7 +199,7 @@ export default function CreatePage() {
color: 'gray.900',
})}
>
Create Your Flashcards
{t('hero.title')}
</h1>
<p
className={css({
@@ -205,7 +207,7 @@ export default function CreatePage() {
color: 'gray.600',
})}
>
Configure content and style, preview instantly, then generate your flashcards
{t('hero.subtitle')}
</p>
</div>
</div>
@@ -248,7 +250,7 @@ export default function CreatePage() {
color: 'gray.900',
})}
>
🎨 Visual Style
{t('style.title')}
</h3>
<p
className={css({
@@ -256,7 +258,7 @@ export default function CreatePage() {
color: 'gray.600',
})}
>
See changes instantly in the preview
{t('style.subtitle')}
</p>
</div>
@@ -337,12 +339,12 @@ export default function CreatePage() {
animation: 'spin 1s linear infinite',
})}
/>
Generating Your Flashcards...
{t('generateButton.loading')}
</>
) : (
<>
<div className={css({ fontSize: 'xl' })}></div>
Generate Flashcards
{t('generateButton.cta')}
</>
)}
</span>
@@ -374,7 +376,7 @@ export default function CreatePage() {
color: 'red.800',
})}
>
Generation Failed
{t('error.title')}
</h3>
</div>
<p
@@ -399,7 +401,7 @@ export default function CreatePage() {
_hover: { bg: 'red.700' },
})}
>
Try Again
{t('error.retry')}
</button>
</div>
</div>

View File

@@ -8,6 +8,7 @@ import * as Switch from '@radix-ui/react-switch'
import * as Tabs from '@radix-ui/react-tabs'
import type { FormApi } from '@tanstack/react-form'
import { ChevronDown } from 'lucide-react'
import { useTranslations } from 'next-intl'
import type { FlashcardFormState } from '@/app/create/page'
import { css } from '../../styled-system/css'
import { grid, hstack, stack } from '../../styled-system/patterns'
@@ -18,6 +19,8 @@ interface ConfigurationFormProps {
}
export function ConfigurationFormWithoutGenerate({ form }: ConfigurationFormProps) {
const t = useTranslations('create.form')
return (
<div className={stack({ gap: '6' })}>
<div className={stack({ gap: '2' })}>
@@ -28,14 +31,14 @@ export function ConfigurationFormWithoutGenerate({ form }: ConfigurationFormProp
color: 'gray.900',
})}
>
Configuration
{t('title')}
</h2>
<p
className={css({
color: 'gray.600',
})}
>
Content, layout, and output settings
{t('subtitle')}
</p>
</div>
@@ -57,8 +60,8 @@ export function ConfigurationFormWithoutGenerate({ form }: ConfigurationFormProp
})}
>
{[
{ value: 'content', label: '📝 Content', icon: '🔢' },
{ value: 'output', label: '💾 Output', icon: '💾' },
{ value: 'content', label: t('tabs.content'), icon: '🔢' },
{ value: 'output', label: t('tabs.output'), icon: '💾' },
].map((tab) => (
<Tabs.Trigger
key={tab.value}
@@ -90,15 +93,15 @@ export function ConfigurationFormWithoutGenerate({ form }: ConfigurationFormProp
<Tabs.Content value="content" className={css({ mt: '6' })}>
<div className={stack({ gap: '6' })}>
<FormField
label="Number Range"
description="Define which numbers to include (e.g., '0-99' or '1,2,5,10')"
label={t('content.range.label')}
description={t('content.range.description')}
>
<form.Field name="range">
{(field) => (
<input
value={field.state.value || ''}
onChange={(e) => field.handleChange(e.target.value)}
placeholder="0-99"
placeholder={t('content.range.placeholder')}
className={inputStyles}
/>
)}
@@ -106,7 +109,7 @@ export function ConfigurationFormWithoutGenerate({ form }: ConfigurationFormProp
</FormField>
<div className={grid({ columns: 2, gap: '4' })}>
<FormField label="Step Size" description="For ranges, increment by this amount">
<FormField label={t('content.step.label')} description={t('content.step.description')}>
<form.Field name="step">
{(field) => (
<input
@@ -120,7 +123,7 @@ export function ConfigurationFormWithoutGenerate({ form }: ConfigurationFormProp
</form.Field>
</FormField>
<FormField label="Shuffle Cards" description="Randomize the order">
<FormField label={t('content.shuffle.label')} description={t('content.shuffle.description')}>
<form.Field name="shuffle">
{(field) => (
<SwitchField
@@ -137,7 +140,10 @@ export function ConfigurationFormWithoutGenerate({ form }: ConfigurationFormProp
{/* Output Tab */}
<Tabs.Content value="output" className={css({ mt: '6' })}>
<div className={stack({ gap: '6' })}>
<FormField label="Output Format" description="Choose your preferred file format">
<FormField
label={t('output.format.label')}
description={t('output.format.description')}
>
<form.Field name="format">
{(field) => (
<FormatSelectField
@@ -173,7 +179,7 @@ export function ConfigurationFormWithoutGenerate({ form }: ConfigurationFormProp
color: 'blue.800',
})}
>
📄 PDF Layout Options
{t('output.pdf.sectionTitle')}
</h3>
<p
className={css({
@@ -181,14 +187,14 @@ export function ConfigurationFormWithoutGenerate({ form }: ConfigurationFormProp
color: 'blue.700',
})}
>
Configure page layout and printing options for your PDF
{t('output.pdf.sectionDescription')}
</p>
</div>
<div className={grid({ columns: 2, gap: '4' })}>
<FormField
label="Cards Per Page"
description="Number of flashcards on each page"
label={t('output.pdf.cardsPerPage.label')}
description={t('output.pdf.cardsPerPage.description')}
>
<form.Field name="cardsPerPage">
{(field) => (
@@ -198,13 +204,18 @@ export function ConfigurationFormWithoutGenerate({ form }: ConfigurationFormProp
min={1}
max={12}
step={1}
formatValue={(value) => `${value} cards`}
formatValue={(value) =>
t('output.pdf.cardsPerPage.value', { count: value })
}
/>
)}
</form.Field>
</FormField>
<FormField label="Paper Size" description="Output paper dimensions">
<FormField
label={t('output.pdf.paperSize.label')}
description={t('output.pdf.paperSize.description')}
>
<form.Field name="paperSize">
{(field) => (
<SelectField
@@ -213,28 +224,32 @@ export function ConfigurationFormWithoutGenerate({ form }: ConfigurationFormProp
options={[
{
value: 'us-letter',
label: 'US Letter (8.5×11")',
label: t('output.pdf.paperSize.options.us-letter'),
},
{
value: 'a4',
label: 'A4 (210×297mm)',
label: t('output.pdf.paperSize.options.a4'),
},
{
value: 'a3',
label: 'A3 (297×420mm)',
label: t('output.pdf.paperSize.options.a3'),
},
{
value: 'a5',
label: 'A5 (148×210mm)',
label: t('output.pdf.paperSize.options.a5'),
},
]}
placeholder={t('shared.selectPlaceholder')}
/>
)}
</form.Field>
</FormField>
</div>
<FormField label="Orientation" description="Page layout direction">
<FormField
label={t('output.pdf.orientation.label')}
description={t('output.pdf.orientation.description')}
>
<form.Field name="orientation">
{(field) => (
<RadioGroupField
@@ -243,13 +258,17 @@ export function ConfigurationFormWithoutGenerate({ form }: ConfigurationFormProp
options={[
{
value: 'portrait',
label: '📄 Portrait',
desc: 'Taller than wide',
label: t('output.pdf.orientation.options.portrait.label'),
desc: t(
'output.pdf.orientation.options.portrait.description'
),
},
{
value: 'landscape',
label: '📃 Landscape',
desc: 'Wider than tall',
label: t('output.pdf.orientation.options.landscape.label'),
desc: t(
'output.pdf.orientation.options.landscape.description'
),
},
]}
/>
@@ -259,8 +278,8 @@ export function ConfigurationFormWithoutGenerate({ form }: ConfigurationFormProp
<div className={grid({ columns: 2, gap: '4' })}>
<FormField
label="Show Cut Marks"
description="Add guides for cutting cards"
label={t('output.pdf.showCutMarks.label')}
description={t('output.pdf.showCutMarks.description')}
>
<form.Field name="showCutMarks">
{(field) => (
@@ -273,8 +292,8 @@ export function ConfigurationFormWithoutGenerate({ form }: ConfigurationFormProp
</FormField>
<FormField
label="Registration Marks"
description="Alignment guides for duplex printing"
label={t('output.pdf.showRegistration.label')}
description={t('output.pdf.showRegistration.description')}
>
<form.Field name="showRegistration">
{(field) => (
@@ -294,8 +313,8 @@ export function ConfigurationFormWithoutGenerate({ form }: ConfigurationFormProp
</form.Field>
<FormField
label="Scale Factor"
description="Adjust the overall size of flashcards"
label={t('output.scaleFactor.label')}
description={t('output.scaleFactor.description')}
>
<form.Field name="scaleFactor">
{(field) => (
@@ -305,7 +324,11 @@ export function ConfigurationFormWithoutGenerate({ form }: ConfigurationFormProp
min={0.5}
max={1.0}
step={0.05}
formatValue={(value) => `${Math.round(value * 100)}%`}
formatValue={(value) =>
t('output.scaleFactor.value', {
percent: Math.round(value * 100),
})
}
/>
)}
</form.Field>

View File

@@ -4,48 +4,30 @@ import * as Select from '@radix-ui/react-select'
import { ChevronDown } from 'lucide-react'
import { css } from '../../styled-system/css'
import { hstack, stack } from '../../styled-system/patterns'
interface FormatOption {
value: string
label: string
icon: string
description: string
}
import { useTranslations } from 'next-intl'
interface FormatSelectFieldProps {
value: string
onValueChange: (value: string) => void
}
const formatOptions: FormatOption[] = [
{
value: 'pdf',
label: 'PDF',
icon: '📄',
description: 'Print-ready vector document with layout options',
},
{
value: 'html',
label: 'HTML',
icon: '🌐',
description: 'Interactive web flashcards',
},
{
value: 'svg',
label: 'SVG',
icon: '🖼️',
description: 'Scalable vector images',
},
{
value: 'png',
label: 'PNG',
icon: '📷',
description: 'High-resolution images',
},
]
export function FormatSelectField({ value, onValueChange }: FormatSelectFieldProps) {
const selectedOption = formatOptions.find((option) => option.value === value) || formatOptions[0]
const t = useTranslations('create.form.formatOptions')
const formatOptions = (['pdf', 'html', 'svg', 'png'] as const).map((option) => ({
value: option,
icon:
{
pdf: '📄',
html: '🌐',
svg: '🖼️',
png: '📷',
}[option],
label: t(`${option}.label`),
description: t(`${option}.description`),
}))
const selectedOption =
formatOptions.find((option) => option.value === value) || formatOptions[0]
return (
<Select.Root value={value} onValueChange={onValueChange}>

View File

@@ -2,7 +2,8 @@
import * as Progress from '@radix-ui/react-progress'
import { CheckCircle, Sparkles, Zap } from 'lucide-react'
import { useEffect, useState } from 'react'
import { useEffect, useMemo, useState } from 'react'
import { useTranslations } from 'next-intl'
import type { FlashcardFormState } from '@/app/create/page'
import { css } from '../../styled-system/css'
import { hstack, stack } from '../../styled-system/patterns'
@@ -23,35 +24,40 @@ export function GenerationProgress({ config }: GenerationProgressProps) {
const [progress, setProgress] = useState(0)
const [currentStep, setCurrentStep] = useState(0)
const [steps, setSteps] = useState<ProgressStep[]>([])
const t = useTranslations('create.progress')
useEffect(() => {
// Initialize steps based on config
const generationSteps: ProgressStep[] = [
{
id: 'validate',
label: 'Validating Configuration',
description: 'Checking parameters and dependencies',
label: t('steps.validate.label'),
description: t('steps.validate.description'),
icon: <CheckCircle size={20} />,
status: 'pending',
},
{
id: 'generate',
label: 'Generating Soroban Patterns',
description: `Creating ${getEstimatedCardCount(config)} flashcard patterns`,
label: t('steps.generate.label'),
description: t('steps.generate.description', {
count: getEstimatedCardCount(config),
}),
icon: <Sparkles size={20} />,
status: 'pending',
},
{
id: 'render',
label: `Rendering ${config.format?.toUpperCase() || 'PDF'}`,
description: 'Converting to your chosen format',
label: t('steps.render.label', {
format: config.format?.toUpperCase() || t('steps.render.defaultFormat'),
}),
description: t('steps.render.description'),
icon: <Zap size={20} />,
status: 'pending',
},
{
id: 'finalize',
label: 'Finalizing Download',
description: 'Preparing your flashcards for download',
label: t('steps.finalize.label'),
description: t('steps.finalize.description'),
icon: <CheckCircle size={20} />,
status: 'pending',
},
@@ -73,7 +79,7 @@ export function GenerationProgress({ config }: GenerationProgressProps) {
}, 500)
return () => clearInterval(progressInterval)
}, [config])
}, [config, t])
useEffect(() => {
// Update step statuses based on current step
@@ -87,6 +93,8 @@ export function GenerationProgress({ config }: GenerationProgressProps) {
const estimatedTime = getEstimatedTime(config)
const currentStepData = steps[currentStep]
const funFacts = (t.raw('funFacts') as string[]) || []
const funFact = useMemo(() => getFunFact(funFacts), [funFacts])
return (
<div className={stack({ gap: '6' })}>
@@ -99,7 +107,7 @@ export function GenerationProgress({ config }: GenerationProgressProps) {
color: 'gray.900',
})}
>
Generating Your Flashcards
{t('title')}
</h3>
<div
className={css({
@@ -108,7 +116,7 @@ export function GenerationProgress({ config }: GenerationProgressProps) {
fontWeight: 'medium',
})}
>
~{estimatedTime} seconds
{t('estimatedTime', { seconds: estimatedTime })}
</div>
</div>
@@ -181,7 +189,7 @@ export function GenerationProgress({ config }: GenerationProgressProps) {
color: 'gray.600',
})}
>
{Math.round(progress)}% complete
{t('percentComplete', { percent: Math.round(progress) })}
</span>
<span
className={css({
@@ -190,7 +198,7 @@ export function GenerationProgress({ config }: GenerationProgressProps) {
color: 'brand.600',
})}
>
Step {currentStep + 1} of {steps.length}
{t('stepCount', { current: currentStep + 1, total: steps.length })}
</span>
</div>
</div>
@@ -308,7 +316,7 @@ export function GenerationProgress({ config }: GenerationProgressProps) {
mb: '2',
})}
>
💡 Did you know?
{t('funFactTitle')}
</h4>
<p
className={css({
@@ -317,7 +325,7 @@ export function GenerationProgress({ config }: GenerationProgressProps) {
lineHeight: 'relaxed',
})}
>
{getFunFact(config)}
{funFact}
</p>
</div>
</div>
@@ -355,17 +363,10 @@ function getEstimatedTime(config: FlashcardFormState): number {
return Math.round((baseTime + cardTime) * formatMultiplier)
}
function getFunFact(_config: FlashcardFormState): string {
const facts = [
'The soroban is a Japanese counting tool that dates back over 400 years!',
'Master soroban users can calculate faster than electronic calculators.',
'Each bead position on a soroban represents a specific numeric value.',
'The word "soroban" comes from ancient Chinese "suanpan" (counting board).',
'Soroban training improves mathematical intuition and mental calculation speed.',
'Modern soroban competitions feature lightning-fast calculations.',
'The soroban method strengthens both logical and creative thinking.',
'Japanese students often learn soroban alongside traditional mathematics.',
]
function getFunFact(facts: string[]): string {
if (!Array.isArray(facts) || facts.length === 0) {
return ''
}
return facts[Math.floor(Math.random() * facts.length)]
}

View File

@@ -3,6 +3,7 @@
import { AbacusReact } from '@soroban/abacus-react'
import { Eye } from 'lucide-react'
import { useMemo, useState } from 'react'
import { useTranslations } from 'next-intl'
import type { FlashcardFormState } from '@/app/create/page'
import { css } from '../../styled-system/css'
import { grid, hstack, stack } from '../../styled-system/patterns'
@@ -12,12 +13,14 @@ interface LivePreviewProps {
}
export function LivePreview({ config }: LivePreviewProps) {
const t = useTranslations('create.preview')
// Generate preview numbers directly from config
const previewNumbers = useMemo(() => {
return getPreviewNumbers(config.range || '1-10')
}, [config.range])
const previewCount = previewNumbers.length
const formatLabel = config.format?.toUpperCase() || t('summary.format.default')
return (
<div className={stack({ gap: '6' })}>
@@ -30,7 +33,7 @@ export function LivePreview({ config }: LivePreviewProps) {
color: 'gray.900',
})}
>
Live Preview
{t('title')}
</h3>
<p
className={css({
@@ -38,7 +41,7 @@ export function LivePreview({ config }: LivePreviewProps) {
color: 'gray.600',
})}
>
See how your flashcards will look
{t('subtitle')}
</p>
</div>
<div className={hstack({ gap: '3', alignItems: 'center' })}>
@@ -53,7 +56,7 @@ export function LivePreview({ config }: LivePreviewProps) {
rounded: 'full',
})}
>
{previewCount} cards {config.format?.toUpperCase()}
{t('badge', { count: previewCount, format: formatLabel })}
</div>
</div>
</div>
@@ -88,13 +91,25 @@ export function LivePreview({ config }: LivePreviewProps) {
mb: '2',
})}
>
Configuration Summary
{t('summary.title')}
</h4>
<div className={grid({ columns: { base: 1, md: 2 }, gap: '3' })}>
<ConfigItem label="Range" value={config.range || 'Not set'} />
<ConfigItem label="Format" value={config.format?.toUpperCase() || 'PDF'} />
<ConfigItem label="Cards per page" value={config.cardsPerPage?.toString() || '6'} />
<ConfigItem label="Paper size" value={config.paperSize?.toUpperCase() || 'US-LETTER'} />
<ConfigItem
label={t('summary.range.label')}
value={config.range || t('summary.range.empty')}
/>
<ConfigItem
label={t('summary.format.label')}
value={config.format?.toUpperCase() || t('summary.format.default')}
/>
<ConfigItem
label={t('summary.cardsPerPage.label')}
value={config.cardsPerPage?.toString() || t('summary.cardsPerPage.default')}
/>
<ConfigItem
label={t('summary.paperSize.label')}
value={config.paperSize?.toUpperCase() || t('summary.paperSize.default')}
/>
</div>
</div>
</div>

View File

@@ -6,6 +6,7 @@ import * as Switch from '@radix-ui/react-switch'
import { useAbacusDisplay } from '@soroban/abacus-react'
import type { FormApi } from '@tanstack/react-form'
import { useEffect } from 'react'
import { useTranslations } from 'next-intl'
import type { FlashcardFormState } from '@/app/create/page'
import { css } from '../../styled-system/css'
import { grid, hstack, stack } from '../../styled-system/patterns'
@@ -16,6 +17,7 @@ interface StyleControlsProps {
export function StyleControls({ form }: StyleControlsProps) {
const { config, updateConfig } = useAbacusDisplay()
const t = useTranslations('create.styleControls')
// Sync form values with global context
useEffect(() => {
@@ -26,7 +28,10 @@ export function StyleControls({ form }: StyleControlsProps) {
}, [config, form])
return (
<div className={stack({ gap: '4' })}>
<FormField label="Color Scheme" description="Choose how colors are applied to beads">
<FormField
label={t('colorScheme.label')}
description={t('colorScheme.description')}
>
<form.Field name="colorScheme">
{(field) => (
<RadioGroupField
@@ -38,23 +43,23 @@ export function StyleControls({ form }: StyleControlsProps) {
options={[
{
value: 'monochrome',
label: 'Monochrome',
desc: 'Classic black and white',
label: t('colorScheme.options.monochrome.label'),
desc: t('colorScheme.options.monochrome.description'),
},
{
value: 'place-value',
label: 'Place Value',
desc: 'Colors by digit position',
label: t('colorScheme.options.place-value.label'),
desc: t('colorScheme.options.place-value.description'),
},
{
value: 'heaven-earth',
label: 'Heaven-Earth',
desc: 'Different colors for 5s and 1s',
label: t('colorScheme.options.heaven-earth.label'),
desc: t('colorScheme.options.heaven-earth.description'),
},
{
value: 'alternating',
label: 'Alternating',
desc: 'Alternating column colors',
label: t('colorScheme.options.alternating.label'),
desc: t('colorScheme.options.alternating.description'),
},
]}
/>
@@ -62,7 +67,7 @@ export function StyleControls({ form }: StyleControlsProps) {
</form.Field>
</FormField>
<FormField label="Bead Shape" description="Choose the visual style of the beads">
<FormField label={t('beadShape.label')} description={t('beadShape.description')}>
<form.Field name="beadShape">
{(field) => (
<RadioGroupField
@@ -74,18 +79,18 @@ export function StyleControls({ form }: StyleControlsProps) {
options={[
{
value: 'diamond',
label: '💎 Diamond',
desc: 'Realistic 3D appearance',
label: t('beadShape.options.diamond.label'),
desc: t('beadShape.options.diamond.description'),
},
{
value: 'circle',
label: '⭕ Circle',
desc: 'Traditional round beads',
label: t('beadShape.options.circle.label'),
desc: t('beadShape.options.circle.description'),
},
{
value: 'square',
label: '⬜ Square',
desc: 'Modern geometric style',
label: t('beadShape.options.square.label'),
desc: t('beadShape.options.square.description'),
},
]}
/>
@@ -94,7 +99,10 @@ export function StyleControls({ form }: StyleControlsProps) {
</FormField>
<div className={grid({ columns: 1, gap: '4' })}>
<FormField label="Colored Numerals" description="Match numeral colors to bead colors">
<FormField
label={t('coloredNumerals.label')}
description={t('coloredNumerals.description')}
>
<form.Field name="coloredNumerals">
{(field) => (
<SwitchField
@@ -108,7 +116,10 @@ export function StyleControls({ form }: StyleControlsProps) {
</form.Field>
</FormField>
<FormField label="Hide Inactive Beads" description="Show only active beads for clarity">
<FormField
label={t('hideInactiveBeads.label')}
description={t('hideInactiveBeads.description')}
>
<form.Field name="hideInactiveBeads">
{(field) => (
<SwitchField

View File

@@ -0,0 +1,229 @@
{
"create": {
"page": {
"navTitle": "Karteikarten erstellen",
"hero": {
"title": "Erstelle deine Karteikarten",
"subtitle": "Konfiguriere Inhalt und Stil, sieh dir sofort eine Vorschau an und generiere dann deine Karteikarten"
},
"style": {
"title": "🎨 Visueller Stil",
"subtitle": "Sieh Änderungen sofort in der Vorschau"
},
"generateButton": {
"loading": "Karteikarten werden erstellt...",
"cta": "Karteikarten generieren"
},
"error": {
"title": "Erstellung fehlgeschlagen",
"retry": "Erneut versuchen"
}
},
"form": {
"title": "Konfiguration",
"subtitle": "Einstellungen für Inhalt, Layout und Ausgabe",
"tabs": {
"content": "📝 Inhalt",
"output": "💾 Ausgabe"
},
"content": {
"range": {
"label": "Zahlenbereich",
"description": "Lege fest, welche Zahlen enthalten sind (z.B. '0-99' oder '1,2,5,10')",
"placeholder": "0-99"
},
"step": {
"label": "Schrittweite",
"description": "Bei Bereichen wird um diesen Wert erhöht"
},
"shuffle": {
"label": "Karten mischen",
"description": "Reihenfolge zufällig anordnen"
}
},
"output": {
"format": {
"label": "Ausgabeformat",
"description": "Wähle dein bevorzugtes Dateiformat"
},
"pdf": {
"sectionTitle": "📄 PDF-Layout-Optionen",
"sectionDescription": "Konfiguriere Seitenlayout und Druckoptionen für dein PDF",
"cardsPerPage": {
"label": "Karten pro Seite",
"description": "Anzahl der Karteikarten je Seite",
"value": "{count, plural, one {{count} Karte} other {{count} Karten}}"
},
"paperSize": {
"label": "Papierformat",
"description": "Ausgabemaße des Papiers",
"options": {
"us-letter": "US Letter (8.5×11\")",
"a4": "A4 (210×297mm)",
"a3": "A3 (297×420mm)",
"a5": "A5 (148×210mm)"
}
},
"orientation": {
"label": "Ausrichtung",
"description": "Ausrichtung des Seitenlayouts",
"options": {
"portrait": {
"label": "📄 Hochformat",
"description": "Höher als breit"
},
"landscape": {
"label": "📃 Querformat",
"description": "Breiter als hoch"
}
}
},
"showCutMarks": {
"label": "Schnittmarken anzeigen",
"description": "Hilfslinien zum Zuschneiden hinzufügen"
},
"showRegistration": {
"label": "Passkreuze",
"description": "Ausrichtungshilfen für Duplexdruck"
}
},
"scaleFactor": {
"label": "Skalierungsfaktor",
"description": "Gesamtgröße der Karteikarten anpassen",
"value": "{percent}%"
}
},
"shared": {
"selectPlaceholder": "Auswählen..."
},
"formatOptions": {
"pdf": {
"label": "PDF",
"description": "Druckfertiges Vektordokument mit Layoutoptionen"
},
"html": {
"label": "HTML",
"description": "Interaktive Web-Karteikarten"
},
"svg": {
"label": "SVG",
"description": "Skalierbare Vektorgrafiken"
},
"png": {
"label": "PNG",
"description": "Hochauflösende Bilder"
}
}
},
"styleControls": {
"colorScheme": {
"label": "Farbschema",
"description": "Lege fest, wie Farben auf die Perlen angewendet werden",
"options": {
"monochrome": {
"label": "Monochrom",
"description": "Klassisches Schwarz-Weiß"
},
"place-value": {
"label": "Stellenwert",
"description": "Farben nach Stellenwert"
},
"heaven-earth": {
"label": "Himmel-Erde",
"description": "Unterschiedliche Farben für Fünfer und Einer"
},
"alternating": {
"label": "Alternierend",
"description": "Abwechselnde Spaltenfarben"
}
}
},
"beadShape": {
"label": "Perlenform",
"description": "Bestimme den visuellen Stil der Perlen",
"options": {
"diamond": {
"label": "💎 Diamant",
"description": "Realistische 3D-Anmutung"
},
"circle": {
"label": "⭕ Kreis",
"description": "Traditionelle runde Perlen"
},
"square": {
"label": "⬜ Quadrat",
"description": "Moderner geometrischer Stil"
}
}
},
"coloredNumerals": {
"label": "Farbige Ziffern",
"description": "Ziffernfarben an die Perlenfarben anpassen"
},
"hideInactiveBeads": {
"label": "Inaktive Perlen ausblenden",
"description": "Nur aktive Perlen für bessere Übersicht zeigen"
}
},
"progress": {
"title": "Karteikarten werden erstellt",
"estimatedTime": "~{seconds} Sekunden",
"percentComplete": "{percent}% abgeschlossen",
"stepCount": "Schritt {current} von {total}",
"steps": {
"validate": {
"label": "Konfiguration wird geprüft",
"description": "Parameter und Abhängigkeiten werden kontrolliert"
},
"generate": {
"label": "Soroban-Muster werden erzeugt",
"description": "{count} Kartenmuster werden erstellt"
},
"render": {
"label": "{format} wird gerendert",
"defaultFormat": "PDF",
"description": "Umwandlung in dein ausgewähltes Format"
},
"finalize": {
"label": "Download wird vorbereitet",
"description": "Karteikarten für den Download bereitstellen"
}
},
"funFactTitle": "💡 Schon gewusst?",
"funFacts": [
"Das Soroban ist ein japanisches Rechenbrett, das über 400 Jahre alt ist!",
"Geübte Soroban-Anwender rechnen schneller als elektronische Taschenrechner.",
"Jede Perlenposition auf dem Soroban steht für einen bestimmten Zahlenwert.",
"Das Wort \"Soroban\" stammt vom chinesischen \"Suanpan\" (Rechenbrett).",
"Soroban-Training verbessert Intuition und Geschwindigkeit beim Kopfrechnen.",
"Moderne Soroban-Wettbewerbe zeigen blitzschnelle Rechenkünstler.",
"Die Soroban-Methode stärkt logisches und kreatives Denken gleichermaßen.",
"In Japan lernen Kinder Soroban oft parallel zum Mathematikunterricht."
]
},
"preview": {
"title": "Live-Vorschau",
"subtitle": "So werden deine Karteikarten aussehen",
"badge": "{count} {count, plural, one {Karte} other {Karten}} • {format}",
"summary": {
"title": "Konfigurationsübersicht",
"range": {
"label": "Bereich",
"empty": "Nicht festgelegt"
},
"format": {
"label": "Format",
"default": "PDF"
},
"cardsPerPage": {
"label": "Karten pro Seite",
"default": "6"
},
"paperSize": {
"label": "Papierformat",
"default": "US-LETTER"
}
}
}
}
}

View File

@@ -0,0 +1,229 @@
{
"create": {
"page": {
"navTitle": "Create Flashcards",
"hero": {
"title": "Create Your Flashcards",
"subtitle": "Configure content and style, preview instantly, then generate your flashcards"
},
"style": {
"title": "🎨 Visual Style",
"subtitle": "See changes instantly in the preview"
},
"generateButton": {
"loading": "Generating Your Flashcards...",
"cta": "Generate Flashcards"
},
"error": {
"title": "Generation Failed",
"retry": "Try Again"
}
},
"form": {
"title": "Configuration",
"subtitle": "Content, layout, and output settings",
"tabs": {
"content": "📝 Content",
"output": "💾 Output"
},
"content": {
"range": {
"label": "Number Range",
"description": "Define which numbers to include (e.g., '0-99' or '1,2,5,10')",
"placeholder": "0-99"
},
"step": {
"label": "Step Size",
"description": "For ranges, increment by this amount"
},
"shuffle": {
"label": "Shuffle Cards",
"description": "Randomize the order"
}
},
"output": {
"format": {
"label": "Output Format",
"description": "Choose your preferred file format"
},
"pdf": {
"sectionTitle": "📄 PDF Layout Options",
"sectionDescription": "Configure page layout and printing options for your PDF",
"cardsPerPage": {
"label": "Cards Per Page",
"description": "Number of flashcards on each page",
"value": "{count, plural, one {{count} card} other {{count} cards}}"
},
"paperSize": {
"label": "Paper Size",
"description": "Output paper dimensions",
"options": {
"us-letter": "US Letter (8.5×11\")",
"a4": "A4 (210×297mm)",
"a3": "A3 (297×420mm)",
"a5": "A5 (148×210mm)"
}
},
"orientation": {
"label": "Orientation",
"description": "Page layout direction",
"options": {
"portrait": {
"label": "📄 Portrait",
"description": "Taller than wide"
},
"landscape": {
"label": "📃 Landscape",
"description": "Wider than tall"
}
}
},
"showCutMarks": {
"label": "Show Cut Marks",
"description": "Add guides for cutting cards"
},
"showRegistration": {
"label": "Registration Marks",
"description": "Alignment guides for duplex printing"
}
},
"scaleFactor": {
"label": "Scale Factor",
"description": "Adjust the overall size of flashcards",
"value": "{percent}%"
}
},
"shared": {
"selectPlaceholder": "Select..."
},
"formatOptions": {
"pdf": {
"label": "PDF",
"description": "Print-ready vector document with layout options"
},
"html": {
"label": "HTML",
"description": "Interactive web flashcards"
},
"svg": {
"label": "SVG",
"description": "Scalable vector images"
},
"png": {
"label": "PNG",
"description": "High-resolution images"
}
}
},
"styleControls": {
"colorScheme": {
"label": "Color Scheme",
"description": "Choose how colors are applied to beads",
"options": {
"monochrome": {
"label": "Monochrome",
"description": "Classic black and white"
},
"place-value": {
"label": "Place Value",
"description": "Colors by digit position"
},
"heaven-earth": {
"label": "Heaven-Earth",
"description": "Different colors for 5s and 1s"
},
"alternating": {
"label": "Alternating",
"description": "Alternating column colors"
}
}
},
"beadShape": {
"label": "Bead Shape",
"description": "Choose the visual style of the beads",
"options": {
"diamond": {
"label": "💎 Diamond",
"description": "Realistic 3D appearance"
},
"circle": {
"label": "⭕ Circle",
"description": "Traditional round beads"
},
"square": {
"label": "⬜ Square",
"description": "Modern geometric style"
}
}
},
"coloredNumerals": {
"label": "Colored Numerals",
"description": "Match numeral colors to bead colors"
},
"hideInactiveBeads": {
"label": "Hide Inactive Beads",
"description": "Show only active beads for clarity"
}
},
"progress": {
"title": "Generating Your Flashcards",
"estimatedTime": "~{seconds} seconds",
"percentComplete": "{percent}% complete",
"stepCount": "Step {current} of {total}",
"steps": {
"validate": {
"label": "Validating Configuration",
"description": "Checking parameters and dependencies"
},
"generate": {
"label": "Generating Soroban Patterns",
"description": "Creating {count} flashcard patterns"
},
"render": {
"label": "Rendering {format}",
"defaultFormat": "PDF",
"description": "Converting to your chosen format"
},
"finalize": {
"label": "Finalizing Download",
"description": "Preparing your flashcards for download"
}
},
"funFactTitle": "💡 Did you know?",
"funFacts": [
"The soroban is a Japanese counting tool that dates back over 400 years!",
"Master soroban users can calculate faster than electronic calculators.",
"Each bead position on a soroban represents a specific numeric value.",
"The word \"soroban\" comes from ancient Chinese \"suanpan\" (counting board).",
"Soroban training improves mathematical intuition and mental calculation speed.",
"Modern soroban competitions feature lightning-fast calculations.",
"The soroban method strengthens both logical and creative thinking.",
"Japanese students often learn soroban alongside traditional mathematics."
]
},
"preview": {
"title": "Live Preview",
"subtitle": "See how your flashcards will look",
"badge": "{count} {count, plural, one {card} other {cards}} • {format}",
"summary": {
"title": "Configuration Summary",
"range": {
"label": "Range",
"empty": "Not set"
},
"format": {
"label": "Format",
"default": "PDF"
},
"cardsPerPage": {
"label": "Cards per page",
"default": "6"
},
"paperSize": {
"label": "Paper size",
"default": "US-LETTER"
}
}
}
}
}

View File

@@ -0,0 +1,229 @@
{
"create": {
"page": {
"navTitle": "Crear tarjetas didácticas",
"hero": {
"title": "Crea tus tarjetas didácticas",
"subtitle": "Configura el contenido y el estilo, observa la vista previa al instante y luego genera tus tarjetas"
},
"style": {
"title": "🎨 Estilo visual",
"subtitle": "Observa los cambios al instante en la vista previa"
},
"generateButton": {
"loading": "Generando tus tarjetas...",
"cta": "Generar tarjetas"
},
"error": {
"title": "La generación falló",
"retry": "Intentar de nuevo"
}
},
"form": {
"title": "Configuración",
"subtitle": "Ajustes de contenido, diseño y salida",
"tabs": {
"content": "📝 Contenido",
"output": "💾 Salida"
},
"content": {
"range": {
"label": "Rango numérico",
"description": "Define qué números se incluirán (p. ej., '0-99' o '1,2,5,10')",
"placeholder": "0-99"
},
"step": {
"label": "Tamaño de paso",
"description": "Para rangos, aumenta en esta cantidad"
},
"shuffle": {
"label": "Mezclar tarjetas",
"description": "Aleatorizar el orden"
}
},
"output": {
"format": {
"label": "Formato de salida",
"description": "Elige tu formato de archivo preferido"
},
"pdf": {
"sectionTitle": "📄 Opciones de diseño PDF",
"sectionDescription": "Configura el diseño de página y las opciones de impresión para tu PDF",
"cardsPerPage": {
"label": "Tarjetas por página",
"description": "Cantidad de tarjetas en cada página",
"value": "{count, plural, one {{count} tarjeta} other {{count} tarjetas}}"
},
"paperSize": {
"label": "Tamaño de papel",
"description": "Dimensiones del papel de salida",
"options": {
"us-letter": "US Letter (8.5×11\")",
"a4": "A4 (210×297mm)",
"a3": "A3 (297×420mm)",
"a5": "A5 (148×210mm)"
}
},
"orientation": {
"label": "Orientación",
"description": "Dirección del diseño de página",
"options": {
"portrait": {
"label": "📄 Vertical",
"description": "Más alto que ancho"
},
"landscape": {
"label": "📃 Horizontal",
"description": "Más ancho que alto"
}
}
},
"showCutMarks": {
"label": "Mostrar guías de corte",
"description": "Agregar guías para recortar las tarjetas"
},
"showRegistration": {
"label": "Marcas de registro",
"description": "Guías de alineación para impresión a doble cara"
}
},
"scaleFactor": {
"label": "Factor de escala",
"description": "Ajusta el tamaño general de las tarjetas",
"value": "{percent}%"
}
},
"shared": {
"selectPlaceholder": "Seleccionar..."
},
"formatOptions": {
"pdf": {
"label": "PDF",
"description": "Documento vectorial listo para imprimir con opciones de diseño"
},
"html": {
"label": "HTML",
"description": "Tarjetas web interactivas"
},
"svg": {
"label": "SVG",
"description": "Imágenes vectoriales escalables"
},
"png": {
"label": "PNG",
"description": "Imágenes de alta resolución"
}
}
},
"styleControls": {
"colorScheme": {
"label": "Esquema de color",
"description": "Elige cómo se aplican los colores a las cuentas",
"options": {
"monochrome": {
"label": "Monocromático",
"description": "Clásico blanco y negro"
},
"place-value": {
"label": "Valor posicional",
"description": "Colores según la posición de cada dígito"
},
"heaven-earth": {
"label": "Cielo-Tierra",
"description": "Colores distintos para cuentas de cinco y de uno"
},
"alternating": {
"label": "Alternado",
"description": "Colores alternados por columna"
}
}
},
"beadShape": {
"label": "Forma de las cuentas",
"description": "Elige el estilo visual de las cuentas",
"options": {
"diamond": {
"label": "💎 Diamante",
"description": "Apariencia realista en 3D"
},
"circle": {
"label": "⭕ Círculo",
"description": "Cuentas redondas tradicionales"
},
"square": {
"label": "⬜ Cuadrado",
"description": "Estilo geométrico moderno"
}
}
},
"coloredNumerals": {
"label": "Números coloreados",
"description": "Igualar el color de los números con el de las cuentas"
},
"hideInactiveBeads": {
"label": "Ocultar cuentas inactivas",
"description": "Mostrar solo las cuentas activas para mayor claridad"
}
},
"progress": {
"title": "Generando tus tarjetas",
"estimatedTime": "~{seconds} segundos",
"percentComplete": "{percent}% completado",
"stepCount": "Paso {current} de {total}",
"steps": {
"validate": {
"label": "Validando la configuración",
"description": "Comprobando parámetros y dependencias"
},
"generate": {
"label": "Generando patrones de soroban",
"description": "Creando {count} patrones de tarjetas"
},
"render": {
"label": "Renderizando {format}",
"defaultFormat": "PDF",
"description": "Convirtiendo al formato elegido"
},
"finalize": {
"label": "Finalizando la descarga",
"description": "Preparando tus tarjetas para descargar"
}
},
"funFactTitle": "💡 ¿Sabías que...?",
"funFacts": [
"El soroban es una herramienta japonesa de cálculo con más de 400 años de historia.",
"Los expertos del soroban pueden calcular más rápido que las calculadoras electrónicas.",
"Cada posición de cuenta en el soroban representa un valor numérico específico.",
"La palabra \"soroban\" proviene del antiguo chino \"suanpan\" (tablero de conteo).",
"El entrenamiento con soroban mejora la intuición matemática y la velocidad mental.",
"Las competencias modernas de soroban muestran cálculos a gran velocidad.",
"El método soroban fortalece tanto el pensamiento lógico como el creativo.",
"En Japón, el soroban suele aprenderse junto con las matemáticas tradicionales."
]
},
"preview": {
"title": "Vista previa en vivo",
"subtitle": "Observa cómo se verán tus tarjetas",
"badge": "{count} {count, plural, one {tarjeta} other {tarjetas}} • {format}",
"summary": {
"title": "Resumen de configuración",
"range": {
"label": "Rango",
"empty": "Sin definir"
},
"format": {
"label": "Formato",
"default": "PDF"
},
"cardsPerPage": {
"label": "Tarjetas por página",
"default": "6"
},
"paperSize": {
"label": "Tamaño de papel",
"default": "US-LETTER"
}
}
}
}
}

View File

@@ -0,0 +1,229 @@
{
"create": {
"page": {
"navTitle": "फ्लैश कार्ड बनाएं",
"hero": {
"title": "अपने फ्लैश कार्ड बनाएं",
"subtitle": "सामग्री और शैली कॉन्फ़िगर करें, तुरंत पूर्वावलोकन देखें और फिर अपने फ्लैश कार्ड जनरेट करें"
},
"style": {
"title": "🎨 दृश्य शैली",
"subtitle": "पूर्वावलोकन में बदलाव तुरंत देखें"
},
"generateButton": {
"loading": "आपके फ्लैश कार्ड बन रहे हैं...",
"cta": "फ्लैश कार्ड जनरेट करें"
},
"error": {
"title": "जनरेशन विफल",
"retry": "फिर से प्रयास करें"
}
},
"form": {
"title": "कॉन्फ़िगरेशन",
"subtitle": "सामग्री, लेआउट और आउटपुट सेटिंग्स",
"tabs": {
"content": "📝 सामग्री",
"output": "💾 आउटपुट"
},
"content": {
"range": {
"label": "संख्या सीमा",
"description": "किन संख्याओं को शामिल करना है (जैसे '0-99' या '1,2,5,10')",
"placeholder": "0-99"
},
"step": {
"label": "चरण अंतर",
"description": "सीमा के लिए, इतनी मात्रा से वृद्धि करें"
},
"shuffle": {
"label": "कार्ड मिलाएँ",
"description": "क्रम को यादृच्छिक करें"
}
},
"output": {
"format": {
"label": "आउटपुट फ़ॉर्मेट",
"description": "अपना पसंदीदा फ़ाइल फ़ॉर्मेट चुनें"
},
"pdf": {
"sectionTitle": "📄 PDF लेआउट विकल्प",
"sectionDescription": "अपने PDF के लिए पेज लेआउट और प्रिंट विकल्प कॉन्फ़िगर करें",
"cardsPerPage": {
"label": "प्रति पृष्ठ कार्ड",
"description": "प्रत्येक पृष्ठ पर फ्लैश कार्ड की संख्या",
"value": "{count, plural, one {{count} कार्ड} other {{count} कार्ड}}"
},
"paperSize": {
"label": "पेपर आकार",
"description": "आउटपुट पेपर के आयाम",
"options": {
"us-letter": "US Letter (8.5×11\")",
"a4": "A4 (210×297मिमी)",
"a3": "A3 (297×420मिमी)",
"a5": "A5 (148×210मिमी)"
}
},
"orientation": {
"label": "अभिविन्यास",
"description": "पेज लेआउट की दिशा",
"options": {
"portrait": {
"label": "📄 लंबवत",
"description": "ऊँचाई चौड़ाई से अधिक"
},
"landscape": {
"label": "📃 क्षैतिज",
"description": "चौड़ाई ऊँचाई से अधिक"
}
}
},
"showCutMarks": {
"label": "कट मार्क दिखाएँ",
"description": "कार्ड काटने के लिए गाइड जोड़ें"
},
"showRegistration": {
"label": "रजिस्ट्रेशन मार्क",
"description": "दोनों तरफ प्रिंट के लिए संरेखण गाइड"
}
},
"scaleFactor": {
"label": "स्केल फ़ैक्टर",
"description": "फ्लैश कार्ड का समग्र आकार समायोजित करें",
"value": "{percent}%"
}
},
"shared": {
"selectPlaceholder": "चुनें..."
},
"formatOptions": {
"pdf": {
"label": "PDF",
"description": "लेआउट विकल्पों वाला प्रिंट-रेडी वेक्टर दस्तावेज़"
},
"html": {
"label": "HTML",
"description": "इंटरैक्टिव वेब फ्लैश कार्ड"
},
"svg": {
"label": "SVG",
"description": "स्केलेबल वेक्टर चित्र"
},
"png": {
"label": "PNG",
"description": "उच्च-रिज़ॉल्यूशन छवियाँ"
}
}
},
"styleControls": {
"colorScheme": {
"label": "रंग योजना",
"description": "निर्धारित करें कि रंग मोतियों पर कैसे लागू हों",
"options": {
"monochrome": {
"label": "मोनोक्रोम",
"description": "क्लासिक काला और सफेद"
},
"place-value": {
"label": "स्थान-मूल्य",
"description": "प्रत्येक अंक की स्थिति के अनुसार रंग"
},
"heaven-earth": {
"label": "स्वर्ग-पृथ्वी",
"description": "पाँच और एक की मोतियों के लिए अलग रंग"
},
"alternating": {
"label": "बारी-बारी",
"description": "हर स्तंभ में वैकल्पिक रंग"
}
}
},
"beadShape": {
"label": "मोती का आकार",
"description": "मोती की दृश्य शैली चुनें",
"options": {
"diamond": {
"label": "💎 हीरा",
"description": "यथार्थवादी 3D रूप"
},
"circle": {
"label": "⭕ वृत्त",
"description": "पारंपरिक गोल मोती"
},
"square": {
"label": "⬜ वर्ग",
"description": "आधुनिक ज्यामितीय शैली"
}
}
},
"coloredNumerals": {
"label": "रंगीन अंक",
"description": "अंकों के रंग को मोतियों से मिलाएँ"
},
"hideInactiveBeads": {
"label": "निष्क्रिय मोती छिपाएँ",
"description": "स्पष्टता के लिए केवल सक्रिय मोती दिखाएँ"
}
},
"progress": {
"title": "आपके फ्लैश कार्ड तैयार हो रहे हैं",
"estimatedTime": "~{seconds} सेकंड",
"percentComplete": "{percent}% पूर्ण",
"stepCount": "चरण {current} में से {total}",
"steps": {
"validate": {
"label": "कॉन्फ़िगरेशन जाँची जा रही है",
"description": "पैरामीटर और निर्भरताएँ सत्यापित की जा रही हैं"
},
"generate": {
"label": "सोरबन पैटर्न बनाए जा रहे हैं",
"description": "{count} कार्ड पैटर्न तैयार किए जा रहे हैं"
},
"render": {
"label": "{format} रेंडर हो रहा है",
"defaultFormat": "PDF",
"description": "आपके चुने हुए फ़ॉर्मेट में परिवर्तित किया जा रहा है"
},
"finalize": {
"label": "डाउनलोड अंतिम रूप में",
"description": "डाउनलोड के लिए आपके कार्ड तैयार किए जा रहे हैं"
}
},
"funFactTitle": "💡 क्या आप जानते हैं?",
"funFacts": [
"सोरबन एक जापानी गणना उपकरण है जो 400 से अधिक वर्ष पुराना है।",
"महारथी सोरबन उपयोगकर्ता इलेक्ट्रॉनिक कैलकुलेटर से भी तेज़ गणना कर सकते हैं।",
"सोरबन पर प्रत्येक मोती की स्थिति एक विशिष्ट संख्यात्मक मान दर्शाती है।",
"\"सोरबन\" शब्द प्राचीन चीनी \"सुआनपान\" (गणना पट्ट) से आया है।",
"सोरबन का अभ्यास गणितीय अंतर्ज्ञान और मानसिक गति को बढ़ाता है।",
"आधुनिक सोरबन प्रतियोगिताओं में बिजली की गति से गणना होती है।",
"सोरबन विधि तार्किक और रचनात्मक दोनों सोच को मजबूत करती है।",
"जापान में छात्र अक्सर पारंपरिक गणित के साथ-साथ सोरबन भी सीखते हैं।"
]
},
"preview": {
"title": "लाइव पूर्वावलोकन",
"subtitle": "देखें कि आपके फ्लैश कार्ड कैसे दिखेंगे",
"badge": "{count} {count, plural, one {कार्ड} other {कार्ड}} • {format}",
"summary": {
"title": "कॉन्फ़िगरेशन सारांश",
"range": {
"label": "सीमा",
"empty": "निर्धारित नहीं"
},
"format": {
"label": "फ़ॉर्मेट",
"default": "PDF"
},
"cardsPerPage": {
"label": "प्रति पृष्ठ कार्ड",
"default": "6"
},
"paperSize": {
"label": "पेपर आकार",
"default": "US-LETTER"
}
}
}
}
}

View File

@@ -0,0 +1,229 @@
{
"create": {
"page": {
"navTitle": "フラッシュカードを作成",
"hero": {
"title": "フラッシュカードを作りましょう",
"subtitle": "内容とスタイルを設定し、すぐにプレビューしてからフラッシュカードを生成しましょう"
},
"style": {
"title": "🎨 ビジュアルスタイル",
"subtitle": "プレビューで変更をすぐに確認"
},
"generateButton": {
"loading": "フラッシュカードを生成しています...",
"cta": "フラッシュカードを生成"
},
"error": {
"title": "生成に失敗しました",
"retry": "もう一度試す"
}
},
"form": {
"title": "設定",
"subtitle": "コンテンツ・レイアウト・出力の設定",
"tabs": {
"content": "📝 コンテンツ",
"output": "💾 出力"
},
"content": {
"range": {
"label": "数値範囲",
"description": "含める数字を指定します(例:'0-99' や '1,2,5,10'",
"placeholder": "0-99"
},
"step": {
"label": "刻み幅",
"description": "範囲の場合はこの値ずつ増加します"
},
"shuffle": {
"label": "カードをシャッフル",
"description": "順番をランダムに並べ替えます"
}
},
"output": {
"format": {
"label": "出力形式",
"description": "お好みのファイル形式を選択"
},
"pdf": {
"sectionTitle": "📄 PDF レイアウトオプション",
"sectionDescription": "PDF のページレイアウトと印刷オプションを設定します",
"cardsPerPage": {
"label": "1ページあたりのカード数",
"description": "各ページに配置するカードの数",
"value": "{count}枚のカード"
},
"paperSize": {
"label": "用紙サイズ",
"description": "出力する用紙の寸法",
"options": {
"us-letter": "USレター (8.5×11\")",
"a4": "A4 (210×297mm)",
"a3": "A3 (297×420mm)",
"a5": "A5 (148×210mm)"
}
},
"orientation": {
"label": "向き",
"description": "ページレイアウトの向き",
"options": {
"portrait": {
"label": "📄 縦向き",
"description": "高さが幅より長い"
},
"landscape": {
"label": "📃 横向き",
"description": "幅が高さより長い"
}
}
},
"showCutMarks": {
"label": "カットマークを表示",
"description": "カードを切り分けるためのガイドを追加"
},
"showRegistration": {
"label": "見当合わせのマーク",
"description": "両面印刷のための位置合わせガイド"
}
},
"scaleFactor": {
"label": "縮尺",
"description": "カード全体のサイズを調整",
"value": "{percent}%"
}
},
"shared": {
"selectPlaceholder": "選択..."
},
"formatOptions": {
"pdf": {
"label": "PDF",
"description": "レイアウト設定が可能な印刷向けベクター文書"
},
"html": {
"label": "HTML",
"description": "インタラクティブなウェブカード"
},
"svg": {
"label": "SVG",
"description": "スケーラブルなベクター画像"
},
"png": {
"label": "PNG",
"description": "高解像度の画像"
}
}
},
"styleControls": {
"colorScheme": {
"label": "配色",
"description": "ビーズに色を適用する方法を選択",
"options": {
"monochrome": {
"label": "モノクロ",
"description": "クラシックな白黒"
},
"place-value": {
"label": "位取り",
"description": "桁の位置ごとに色分け"
},
"heaven-earth": {
"label": "天地",
"description": "五玉と一玉で別の色"
},
"alternating": {
"label": "交互",
"description": "列ごとに交互の色"
}
}
},
"beadShape": {
"label": "ビーズの形",
"description": "ビーズの見た目のスタイルを選択",
"options": {
"diamond": {
"label": "💎 ダイヤ",
"description": "リアルな立体感"
},
"circle": {
"label": "⭕ 円",
"description": "伝統的な丸いビーズ"
},
"square": {
"label": "⬜ 四角",
"description": "モダンな幾何学スタイル"
}
}
},
"coloredNumerals": {
"label": "数字の色を付ける",
"description": "数字の色をビーズの色に合わせる"
},
"hideInactiveBeads": {
"label": "非アクティブなビーズを隠す",
"description": "見やすさのためにアクティブなビーズのみ表示"
}
},
"progress": {
"title": "フラッシュカードを生成しています",
"estimatedTime": "~{seconds}秒",
"percentComplete": "{percent}% 完了",
"stepCount": "全{total}ステップ中 {current} ステップ目",
"steps": {
"validate": {
"label": "設定を検証しています",
"description": "パラメーターと依存関係を確認中"
},
"generate": {
"label": "そろばんパターンを生成",
"description": "{count} 件のカードパターンを作成中"
},
"render": {
"label": "{format} をレンダリング",
"defaultFormat": "PDF",
"description": "選択した形式に変換中"
},
"finalize": {
"label": "ダウンロードを最終準備",
"description": "カードをダウンロード用に整えています"
}
},
"funFactTitle": "💡 豆知識",
"funFacts": [
"そろばんは400年以上の歴史を持つ日本の計算器です。",
"熟練したそろばん使いは電子計算機よりも速く計算できます。",
"そろばんの各ビーズの位置は特定の数値を表します。",
"「そろばん」という言葉は中国古来の「算盤」から来ています。",
"そろばんの練習は数学的直感と暗算の速度を高めます。",
"現代のそろばん大会では驚異的な速さの計算が披露されます。",
"そろばんの学習は論理的思考と創造的思考の両方を鍛えます。",
"日本では学校で数学と一緒にそろばんを学ぶことがよくあります。"
]
},
"preview": {
"title": "ライブプレビュー",
"subtitle": "フラッシュカードの仕上がりを確認しましょう",
"badge": "{count}枚のカード • {format}",
"summary": {
"title": "設定のサマリー",
"range": {
"label": "範囲",
"empty": "未設定"
},
"format": {
"label": "形式",
"default": "PDF"
},
"cardsPerPage": {
"label": "1ページあたりのカード数",
"default": "6"
},
"paperSize": {
"label": "用紙サイズ",
"default": "US-LETTER"
}
}
}
}
}

View File

@@ -0,0 +1,229 @@
{
"create": {
"page": {
"navTitle": "Chartas memoriales crea",
"hero": {
"title": "Chartas tuas memoriales para",
"subtitle": "Argumentum et formam dispone, statim praenotam aspice, deinde chartas tuas gignito"
},
"style": {
"title": "🎨 Stilus visualis",
"subtitle": "Mutationes in praenota continuo vide"
},
"generateButton": {
"loading": "Chartae tuae gignuntur...",
"cta": "Chartas gignere"
},
"error": {
"title": "Generatio defecit",
"retry": "Iterum conare"
}
},
"form": {
"title": "Configuratio",
"subtitle": "Praecepta de argumento, dispositione et exitu",
"tabs": {
"content": "📝 Argumentum",
"output": "💾 Exitus"
},
"content": {
"range": {
"label": "Intervallum numerorum",
"description": "Quos numeros includas determina (exempli gratia '0-99' vel '1,2,5,10')",
"placeholder": "0-99"
},
"step": {
"label": "Incrementum gradus",
"description": "In intervallis hoc quantulum augetur"
},
"shuffle": {
"label": "Chartas misce",
"description": "Ordinem fortuito verte"
}
},
"output": {
"format": {
"label": "Forma exitus",
"description": "Formam tabellae quam mavis selige"
},
"pdf": {
"sectionTitle": "📄 Optiones dispositionis PDF",
"sectionDescription": "Paginam et optiones imprimendi pro PDF dispone",
"cardsPerPage": {
"label": "Chartae per paginam",
"description": "Quot chartae singulae paginae habeant",
"value": "{count, plural, one {{count} charta} other {{count} chartae}}"
},
"paperSize": {
"label": "Magnitudo chartae",
"description": "Dimensiones chartae exitus",
"options": {
"us-letter": "US Letter (8.5×11\")",
"a4": "A4 (210×297mm)",
"a3": "A3 (297×420mm)",
"a5": "A5 (148×210mm)"
}
},
"orientation": {
"label": "Directio",
"description": "Quo modo pagina vertatur",
"options": {
"portrait": {
"label": "📄 Verticalis",
"description": "Altior quam lata"
},
"landscape": {
"label": "📃 Horizontali",
"description": "Latior quam alta"
}
}
},
"showCutMarks": {
"label": "Signa sectionis ostende",
"description": "Lineas ad chartas secandas adde"
},
"showRegistration": {
"label": "Signa directionis",
"description": "Ducet alignmenti pro impressione duplici"
}
},
"scaleFactor": {
"label": "Factor scalae",
"description": "Magnitudinem totam chartarum tempera",
"value": "{percent}%"
}
},
"shared": {
"selectPlaceholder": "Elige..."
},
"formatOptions": {
"pdf": {
"label": "PDF",
"description": "Documentum vectorium ad imprimendum paratum cum optionibus dispositionis"
},
"html": {
"label": "HTML",
"description": "Chartae interretiales interactivae"
},
"svg": {
"label": "SVG",
"description": "Imagines vectoriae scalabiles"
},
"png": {
"label": "PNG",
"description": "Imagines altae resolutionis"
}
}
},
"styleControls": {
"colorScheme": {
"label": "Schema colorum",
"description": "Defini quomodo colores in globulos applicantur",
"options": {
"monochrome": {
"label": "Monochromum",
"description": "Classica nigro-alba"
},
"place-value": {
"label": "Valor locorum",
"description": "Colores secundum ordinem digitorum"
},
"heaven-earth": {
"label": "Caelum-Terra",
"description": "Diversi colores globulis quinariis et singularibus"
},
"alternating": {
"label": "Alternans",
"description": "Columnae alterno colore"
}
}
},
"beadShape": {
"label": "Figura globulorum",
"description": "Genus aspectus globulorum elige",
"options": {
"diamond": {
"label": "💎 Adamans",
"description": "Species quasi tridimensionalis"
},
"circle": {
"label": "⭕ Orbis",
"description": "Globuli rotundi traditi"
},
"square": {
"label": "⬜ Quadratum",
"description": "Moderna forma geometrica"
}
}
},
"coloredNumerals": {
"label": "Numeri colorati",
"description": "Numerorum colores cum globulis compone"
},
"hideInactiveBeads": {
"label": "Globulos inactivos occulta",
"description": "Ut clarius sit, tantum globulos activos ostende"
}
},
"progress": {
"title": "Chartae tuae generantur",
"estimatedTime": "~{seconds} secundae",
"percentComplete": "{percent}% perfectum",
"stepCount": "Gradus {current} ex {total}",
"steps": {
"validate": {
"label": "Configuratio probatur",
"description": "Parametri et dependentiae recognoscuntur"
},
"generate": {
"label": "Forma soroban paratur",
"description": "{count} schemata chartarum creantur"
},
"render": {
"label": "{format} redditur",
"defaultFormat": "PDF",
"description": "In formam electam convertitur"
},
"finalize": {
"label": "Download paratur",
"description": "Chartae ad detrahendum componuntur"
}
},
"funFactTitle": "💡 Scisne?",
"funFacts": [
"Soroban instrumentum Iaponicum numerandi est quod plus quam annos quadringentos superest.",
"Periti soroban citius quam calculatrices electronicae rationes conficiunt.",
"Quisque locus globuli in soroban certum numerum repraesentat.",
"Vocabulum \"soroban\" ex antiquo Sinico \"suanpan\" (tabula computatoria) derivatur.",
"Exercitatio soroban sensum mathematicum et velocitatem mentis auget.",
"Certamina soroban hodierna fulmineas computationes ostendunt.",
"Methodus soroban tam cogitationem logicam quam creatricem firmat.",
"Pueri Iaponici saepe soroban una cum mathematica tradita discunt."
]
},
"preview": {
"title": "Praenotio viva",
"subtitle": "Vide quomodo chartae tuae apparebunt",
"badge": "{count} {count, plural, one {charta} other {chartae}} • {format}",
"summary": {
"title": "Summarium configurationis",
"range": {
"label": "Intervallum",
"empty": "Nondum definitum"
},
"format": {
"label": "Forma",
"default": "PDF"
},
"cardsPerPage": {
"label": "Chartae per paginam",
"default": "6"
},
"paperSize": {
"label": "Magnitudo chartae",
"default": "US-LETTER"
}
}
}
}
}

View File

@@ -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 createMessages = {
en: en.create,
de: de.create,
ja: ja.create,
hi: hi.create,
es: es.create,
la: la.create,
} as const

View File

@@ -1,4 +1,5 @@
import { rithmomachiaMessages } from '@/arcade-games/rithmomachia/messages'
import { createMessages } from '@/i18n/locales/create/messages'
import { homeMessages } from '@/i18n/locales/home/messages'
import { tutorialMessages } from '@/i18n/locales/tutorial/messages'
@@ -35,6 +36,7 @@ export async function getMessages(locale: Locale) {
return mergeMessages(
common,
{ home: homeMessages[locale] },
{ create: createMessages[locale] },
{ tutorial: tutorialMessages[locale] },
rithmomachiaMessages[locale]
)