diff --git a/.gitignore b/.gitignore index fa736f8..704020f 100644 --- a/.gitignore +++ b/.gitignore @@ -41,3 +41,6 @@ next-env.d.ts # claude code .claude/ CLAUDE.local.md + +# implementation md file +proposal_issue_74.md diff --git a/CONTRIBUTING.es.md b/CONTRIBUTING.es.md index 16246eb..8ac58f1 100644 --- a/CONTRIBUTING.es.md +++ b/CONTRIBUTING.es.md @@ -35,6 +35,14 @@ pnpm build Es el tipo de contribución más valioso. Los pasos están en el [README](README.es.md#agregar-un-lenguaje). Incluye siempre un test del analyzer. +## Agregar una traducción + +¡Damos la bienvenida a las traducciones para que Weftmap sea accesible en todo el mundo! +Para agregar una traducción: +1. Registra el nuevo código de locale (por ejemplo, `fr`) en [src/i18n/config.ts](file:///src/i18n/config.ts). +2. Copia [src/i18n/dictionaries/en.json](file:///src/i18n/dictionaries/en.json) a `src/i18n/dictionaries/.json` y traduce los valores. +3. Ejecuta `pnpm test` para verificar que el diccionario de traducción coincida con las claves de la plantilla de origen. + ## Estilo - TypeScript, sin `any` en código de aplicación. diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 12b9a3b..77437d4 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -34,6 +34,14 @@ pnpm build This is the most valuable kind of contribution. The steps are in the [README](README.md#adding-a-language). Always include an analyzer test. +## Adding a translation + +We welcome translations to make Weftmap accessible worldwide! +To add a translation: +1. Register the new locale code (e.g., `fr`) in [src/i18n/config.ts](file:///src/i18n/config.ts). +2. Copy [src/i18n/dictionaries/en.json](file:///src/i18n/dictionaries/en.json) to `src/i18n/dictionaries/.json` and translate the values. +3. Run `pnpm test` to verify the translation dictionary matches the source template keys. + ## Style - TypeScript, no `any` in application code. diff --git a/src/app/[lang]/docs/[slug]/page.tsx b/src/app/[lang]/docs/[slug]/page.tsx index ba03ff0..46fc014 100644 --- a/src/app/[lang]/docs/[slug]/page.tsx +++ b/src/app/[lang]/docs/[slug]/page.tsx @@ -2,6 +2,7 @@ import type { Metadata } from "next"; import Link from "next/link"; import { notFound } from "next/navigation"; import { isLocale, locales, type Locale } from "@/i18n/config"; +import { getDictionary } from "@/i18n/dictionaries"; import { DOC_NAV, DOC_SLUGS, type DocSlug } from "@/lib/docs"; import { DOC_COMPONENTS } from "@/components/docs/registry"; @@ -17,7 +18,7 @@ export async function generateMetadata({ const { lang, slug } = await params; const item = DOC_NAV.find((d) => d.slug === slug); const locale: Locale = isLocale(lang) ? lang : "en"; - const title = item ? item.title[locale] : "Docs"; + const title = item ? (item.title[locale] ?? item.title["en"]) : "Docs"; return { title: `${title} — Weftmap` }; } @@ -28,6 +29,7 @@ export default async function DocPage({ }) { const { lang, slug } = await params; if (!isLocale(lang)) notFound(); + const t = getDictionary(lang as Locale); const index = DOC_NAV.findIndex((d) => d.slug === slug); if (index === -1) notFound(); @@ -47,10 +49,10 @@ export default async function DocPage({ className="group rounded-xl border border-[#e2e8f0] bg-white p-4 transition-all hover:-translate-y-0.5 hover:border-[#c7d2fe] hover:shadow-[0_8px_24px_-12px_rgba(15,23,42,0.18)]" > - {lang === "es" ? "Anterior" : "Previous"} + {t.previous} - ← {prev.title[lang]} + ← {prev.title[lang] ?? prev.title["en"]} ) : ( @@ -62,10 +64,10 @@ export default async function DocPage({ className="group rounded-xl border border-[#e2e8f0] bg-white p-4 text-right transition-all hover:-translate-y-0.5 hover:border-[#c7d2fe] hover:shadow-[0_8px_24px_-12px_rgba(15,23,42,0.18)]" > - {lang === "es" ? "Siguiente" : "Next"} + {t.next} - {next.title[lang]} → + {next.title[lang] ?? next.title["en"]} → ) : ( diff --git a/src/app/[lang]/docs/layout.tsx b/src/app/[lang]/docs/layout.tsx index b66045b..8097911 100644 --- a/src/app/[lang]/docs/layout.tsx +++ b/src/app/[lang]/docs/layout.tsx @@ -1,6 +1,7 @@ import type { ReactNode } from "react"; import { notFound } from "next/navigation"; import { isLocale, locales, type Locale } from "@/i18n/config"; +import { getDictionary } from "@/i18n/dictionaries"; import DocsSidebar from "@/components/docs/DocsSidebar"; export function generateStaticParams() { @@ -16,13 +17,14 @@ export default async function DocsLayout({ }) { const { lang } = await params; if (!isLocale(lang)) notFound(); + const t = getDictionary(lang as Locale); return (
{/* Mobile docs nav (sidebar is hidden below lg). */}
- {lang === "es" ? "Documentación" : "Documentation"} + {t.documentation} diff --git a/src/app/[lang]/layout.tsx b/src/app/[lang]/layout.tsx index 6cb6262..bc08cdb 100644 --- a/src/app/[lang]/layout.tsx +++ b/src/app/[lang]/layout.tsx @@ -27,10 +27,10 @@ export default async function RootLayout({ }) { const { lang } = await params; if (!isLocale(lang)) notFound(); - const t = getDictionary(lang); + const t = getDictionary(lang as Locale); return ( - +