// ============================================================================ // i18n — English / Spanish translation layer for Black Craft // Dictionary keyed by section. Names (Charlie, Mabu, Black Craft, version // strings) and internal image placeholders are intentionally NOT translated. // ============================================================================ const { createContext: createI18nContext, useContext: useI18nContext } = React; const DICT = { en: { code: "en", nav: { game: "The Game", team: "The Team", devlog: "Devlog" }, hero: { subtitle: "A 2D toon survival RPG. Build a home, raid the dungeon, and try not to wake the things sleeping down there.", tags: ["Survival RPG", "Dungeon Crawler", "Base Building", "Single-Player"], ctaWishlist: "Wishlist on Steam", ctaSee: "See the Game", rune1: "Day 41. The compass spins anti-clockwise now. We took it as decoration. It was a warning.", rune2: "The bottles catch a wind no draught makes. The bubbles always rise toward whatever is watching.", }, game: { label: "The Game", title1: "Build by day.", title2: "Dig by night.", lede: "Dimension Disrupt is a 2D toon survival RPG with dungeon-crawler combat and base-building roots. Made by two people. Drawn by hand.", pillarWord: "PILLAR", pillars: [ { title: "Survive", copy: "Hunger. Cold. Sanity. Whatever's in the cupboard. The world only pretends to sit still while you sleep." }, { title: "Disrupt", copy: "Procedural dungeons that hold a grudge. Loot is a polite word for what you bring back up." }, { title: "Build", copy: "Workshop, garden, shrine, fence. Decide what to put near the door — something out there is taking notes." }, ], featuresLabel: "Features", features: [ { t: "Hand-drawn, hand-animated", c: "Every leaf, lantern and lurking horror toon-shaded frame by frame." }, { t: "Sanity is a stat", c: "See too much, lose the thread. Your character will start to lie to you. The UI joins in eventually." }, { t: "Dimensions, plural", c: "Five worlds bleed through one map. Find the seams. Don't pull on them." }, { t: "A base that's yours", c: "Modular building, tools and traps. Lay it out, light it up, lock it down." }, { t: "Pets that pull weight", c: "Companions that fight, fetch, and occasionally judge your choices." }, ], rune: "The first thing the dungeon takes is your shadow. The second is your name. The third is negotiable.", }, studio: { label: "The Team", title1: "Two people.", title2: "One small studio.", lede: "Black Craft is Charlie and Mabu. We work from our home, on our own hours, with our own pets at our feet.", mainsK: "MAINS", fearsK: "FEARS", members: [ { role: "Developer · Technical Artist", bio: "Writes the code, builds the tools, teaches sprites to behave. Charlie keeps the engine humming.", mains: "Engine, tools", fears: "French lawyers", }, { role: "Artist · Animator · Writer", bio: "Draws everything you see and writes everything you read. Painstakingly animates hand-drawn sprites, the old school way.", mains: "Art, anim, narrative", fears: "Blank pages", }, ], rune: "Mabu's first drawing of the protagonist had four eyes. Charlie said no. Mabu compromised at three. Two are usually closed.", }, devlog: { label: "Devlog", title1: "Notes from", title2: "the desk.", all: "All Entries", posts: [ { tag: "Patchwork", title: "Stitching a dungeon that remembers you", excerpt: "On procedural dungeons that aren't actually procedural — how rooms carry grudges between runs, and why we let the map keep a diary." }, { tag: "Art", title: "Sixty frames of one nervous slime", excerpt: "Mabu walks through the wobble-to-burst animation pipeline." }, { tag: "Build", title: "Sanity, the worst stat we've ever shipped", excerpt: "Lies, hallucinations, and the UI elements that fight back." }, ], rune: "The dungeon was supposed to forget. We left the variable in. We are leaving it in.", }, support: { label: "Supported by", blurb: "Demo development backed by ACAU's 2024 Videogame Development grant — strengthening Uruguay's audiovisual sector.", }, footer: { tagline: "A two-person studio making small, stubborn games. Currently making Dimension Disrupt.", colGame: { title: "The Game", links: ["Pillars", "Features", "Press Kit", "Wishlist"] }, colStudio: { title: "Studio", links: ["About", "Devlog", "Contact"] }, colFollow: { title: "Follow", links: ["Twitter / X", "Bluesky", "Discord", "Mailing list"] }, copyright: "© 2026 Black Craft", }, }, es: { code: "es", nav: { game: "El Juego", team: "El Equipo", devlog: "Diario" }, hero: { subtitle: "Un RPG de supervivencia toon en 2D. Construye un hogar, saquea la mazmorra e intenta no despertar a las cosas que duermen ahí abajo.", tags: ["Supervivencia", "Exploración", "Construcción de Base", "1 Jugador"], ctaWishlist: "Añadir a deseados en Steam", ctaSee: "Ver el Juego", rune1: "Día 41. La brújula ahora gira en sentido antihorario. La tomamos por decoración. Era una advertencia.", rune2: "Las botellas atrapan un viento que ninguna corriente produce. Las burbujas siempre suben hacia lo que sea que esté observando.", }, game: { label: "El Juego", title1: "Construye de día.", title2: "Excava de noche.", lede: "Dimension Disrupt es un RPG de supervivencia toon en 2D con combate, construcción de bases y exploración de distintas dimensiones. Hecho por dos personas. Dibujado a mano.", pillarWord: "PILAR", pillars: [ { title: "Sobrevive", copy: "Hambre. Frío. Cordura. Lo que sea que haya en la alacena. El mundo solo finge quedarse quieto mientras duermes." }, { title: "Altera", copy: "Dimensiones que guardan rencor. «Botín» es una palabra educada para lo que logras traer de vuelta." }, { title: "Construye", copy: "Taller, jardín, santuario, valla. Decide qué poner cerca de la puerta: algo allá afuera está tomando notas." }, ], featuresLabel: "Características", features: [ { t: "Dibujado y animado a mano", c: "Cada hoja, linterna y horror al acecho sombreado en estilo toon, fotograma a fotograma." }, { t: "La cordura es una estadística", c: "Si ves demasiado, pierdes el hilo. Tu personaje empezará a mentirte. La interfaz acabará por unirse." }, { t: "Dimensiones, en plural", c: "Cinco mundos se filtran a través de un solo mapa. Encuentra las costuras. No tires de ellas." }, { t: "Una base que es tuya", c: "Construcción modular, herramientas y trampas. Distribúyela, ilumínala, asegúrala." }, { t: "Mascotas que aportan", c: "Compañeros que luchan, recogen cosas y, de vez en cuando, juzgan tus decisiones." }, ], rune: "Lo primero que la mazmorra te quita es tu sombra. Lo segundo, tu nombre. Lo tercero es negociable.", }, studio: { label: "El Equipo", title1: "Dos personas.", title2: "Un pequeño estudio.", lede: "Black Craft son Charlie y Mabu. Trabajamos desde casa, con nuestros propios horarios y con nuestras mascotas a los pies.", mainsK: "DOMINA", fearsK: "TEME", members: [ { role: "Desarrollador · Artista Técnico", bio: "Escribe el código, crea las herramientas y enseña a los sprites a comportarse. Charlie mantiene el motor en marcha.", mains: "Motor, herramientas", fears: "Abogados franceses", }, { role: "Artista · Animadora · Escritora", bio: "Dibuja todo lo que ves y escribe todo lo que lees. Anima minuciosamente sprites dibujados a mano, a la vieja usanza.", mains: "Arte, animación, narrativa", fears: "Páginas en blanco", }, ], rune: "El primer dibujo de Mabu del protagonista tenía cuatro ojos. Charlie dijo que no. Mabu transigió con tres. Dos suelen estar cerrados.", }, devlog: { label: "Diario", title1: "Notas", title2: "de desarrollo.", all: "Todas las Entradas", posts: [ { tag: "Remiendos", title: "Cosiendo una mazmorra que te recuerda", excerpt: "Sobre mazmorras procedurales que en realidad no lo son: cómo las salas guardan rencor entre partidas y por qué dejamos que el mapa lleve un diario." }, { tag: "Arte", title: "Sesenta fotogramas de un slime nervioso", excerpt: "Mabu repasa el proceso de animación, del temblor al estallido." }, { tag: "Construcción", title: "La cordura, la peor estadística que hemos lanzado", excerpt: "Mentiras, alucinaciones y los elementos de interfaz que se resisten." }, ], rune: "La mazmorra debía olvidar. Dejamos la variable. La vamos a dejar.", }, support: { label: "Apoyado por", blurb: "Desarrollo de la demo apoyado por la convocatoria Desarrollo de Videojuegos 2024 de ACAU — fortaleciendo el sector audiovisual uruguayo.", }, footer: { tagline: "Un estudio de dos personas que hace juegos pequeños y tercos. Actualmente desarrollando Dimension Disrupt.", colGame: { title: "El Juego", links: ["Pilares", "Características", "Kit de Prensa", "Lista de Deseados"] }, colStudio: { title: "Estudio", links: ["Acerca de", "Devlog", "Contacto"] }, colFollow: { title: "Síguenos", links: ["Twitter / X", "Bluesky", "Discord", "Lista de correo"] }, copyright: "© 2026 Black Craft", }, }, }; const LANGS = [ { code: "en", short: "EN", label: "English", flag: "https://flagcdn.com/gb.svg" }, { code: "es", short: "ES", label: "Español", flag: "https://flagcdn.com/es.svg" }, ]; const LangContext = createI18nContext({ lang: "en", t: DICT.en, setLang: () => {} }); const LANG_STORAGE_KEY = "blackcraft.lang"; function LanguageProvider({ children }) { const [lang, setLangState] = React.useState(() => { try { const saved = localStorage.getItem(LANG_STORAGE_KEY); if (saved && DICT[saved]) return saved; } catch (e) {} // fall back to browser preference, default English const nav = (navigator.language || "en").slice(0, 2).toLowerCase(); return DICT[nav] ? nav : "en"; }); const setLang = React.useCallback((next) => { if (!DICT[next]) return; setLangState(next); try { localStorage.setItem(LANG_STORAGE_KEY, next); } catch (e) {} }, []); React.useEffect(() => { document.documentElement.lang = lang; }, [lang]); const value = { lang, setLang, t: DICT[lang] }; return {children}; } function useLang() { return useI18nContext(LangContext); } // ===== Language picker — flag dropdown ===== function LangPicker() { const { lang, setLang } = useLang(); const [open, setOpen] = React.useState(false); const ref = React.useRef(null); const current = LANGS.find((l) => l.code === lang) || LANGS[0]; React.useEffect(() => { if (!open) return; const onDoc = (e) => { if (ref.current && !ref.current.contains(e.target)) setOpen(false); }; const onKey = (e) => { if (e.key === "Escape") setOpen(false); }; document.addEventListener("mousedown", onDoc); document.addEventListener("keydown", onKey); return () => { document.removeEventListener("mousedown", onDoc); document.removeEventListener("keydown", onKey); }; }, [open]); return (
{open && }
); } Object.assign(window, { DICT, LANGS, LanguageProvider, useLang, LangPicker });