From b9b20c52edfe7525017f98daed8a6f89fadd0748 Mon Sep 17 00:00:00 2001 From: Daniel Jacobs Date: Mon, 2 Jun 2025 14:56:57 -0400 Subject: [PATCH 1/6] i18n: Install next-export-i18n fork --- package-lock.json | 26 +++++++++++++++++++++++++- package.json | 3 ++- 2 files changed, 27 insertions(+), 2 deletions(-) diff --git a/package-lock.json b/package-lock.json index e9126230..8ff8f0e2 100644 --- a/package-lock.json +++ b/package-lock.json @@ -18,7 +18,8 @@ "react": "^18", "react-device-detect": "^2.2.3", "react-dom": "^18", - "recharts": "^2.15.3" + "recharts": "^2.15.3", + "ruffle-next-export-i18n": "^3.0.0" }, "devDependencies": { "@eslint/eslintrc": "^3.3.1", @@ -6818,6 +6819,15 @@ "dev": true, "license": "MIT" }, + "node_modules/mustache": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/mustache/-/mustache-4.2.0.tgz", + "integrity": "sha512-71ippSywq5Yb7/tVYyGbkBggbU8H3u5Rz56fH60jGFgr8uHwxs+aSKeqmluIVzM0m0kB7xQjKS6qPfd0b2ZoqQ==", + "license": "MIT", + "bin": { + "mustache": "bin/mustache" + } + }, "node_modules/nanoid": { "version": "3.3.11", "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", @@ -7944,6 +7954,20 @@ "@unrs/rspack-resolver-binding-win32-x64-msvc": "1.2.2" } }, + "node_modules/ruffle-next-export-i18n": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/ruffle-next-export-i18n/-/ruffle-next-export-i18n-3.0.0.tgz", + "integrity": "sha512-7kllOHnMdqrCHaEhYxU8cqALKCrF2kIXnv8IRKS0dGGLi2BOya+qqHdOMH62F6O0tdkoKxW2X1kJs87xJtyVNA==", + "license": "MIT", + "dependencies": { + "mustache": "^4.2.0" + }, + "peerDependencies": { + "next": ">=13.0.0", + "react": "^18.0.0", + "react-dom": "^18.0.0" + } + }, "node_modules/run-parallel": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", diff --git a/package.json b/package.json index 43a0f292..ebee0bab 100644 --- a/package.json +++ b/package.json @@ -19,7 +19,8 @@ "react": "^18", "react-device-detect": "^2.2.3", "react-dom": "^18", - "recharts": "^2.15.3" + "recharts": "^2.15.3", + "ruffle-next-export-i18n": "^3.0.0" }, "devDependencies": { "@eslint/eslintrc": "^3.3.1", From e1427d04530987efd585382589b784373959e5a7 Mon Sep 17 00:00:00 2001 From: Daniel Jacobs Date: Wed, 7 May 2025 14:40:34 -0400 Subject: [PATCH 2/6] i18n: Add translation files --- i18n/index.js | 56 ++++++++++++++ i18n/translations.ar.json | 1 + i18n/translations.ca.json | 1 + i18n/translations.cs.json | 1 + i18n/translations.de.json | 1 + i18n/translations.en.json | 150 ++++++++++++++++++++++++++++++++++++++ i18n/translations.es.json | 1 + i18n/translations.fr.json | 1 + i18n/translations.he.json | 1 + i18n/translations.hu.json | 1 + i18n/translations.id.json | 1 + i18n/translations.it.json | 1 + i18n/translations.ja.json | 1 + i18n/translations.ko.json | 1 + i18n/translations.nl.json | 1 + i18n/translations.pl.json | 1 + i18n/translations.pt.json | 1 + i18n/translations.ro.json | 1 + i18n/translations.ru.json | 1 + i18n/translations.sk.json | 1 + i18n/translations.sv.json | 1 + i18n/translations.tr.json | 1 + i18n/translations.uk.json | 1 + i18n/translations.zh.json | 1 + 24 files changed, 228 insertions(+) create mode 100644 i18n/index.js create mode 100644 i18n/translations.ar.json create mode 100644 i18n/translations.ca.json create mode 100644 i18n/translations.cs.json create mode 100644 i18n/translations.de.json create mode 100644 i18n/translations.en.json create mode 100644 i18n/translations.es.json create mode 100644 i18n/translations.fr.json create mode 100644 i18n/translations.he.json create mode 100644 i18n/translations.hu.json create mode 100644 i18n/translations.id.json create mode 100644 i18n/translations.it.json create mode 100644 i18n/translations.ja.json create mode 100644 i18n/translations.ko.json create mode 100644 i18n/translations.nl.json create mode 100644 i18n/translations.pl.json create mode 100644 i18n/translations.pt.json create mode 100644 i18n/translations.ro.json create mode 100644 i18n/translations.ru.json create mode 100644 i18n/translations.sk.json create mode 100644 i18n/translations.sv.json create mode 100644 i18n/translations.tr.json create mode 100644 i18n/translations.uk.json create mode 100644 i18n/translations.zh.json diff --git a/i18n/index.js b/i18n/index.js new file mode 100644 index 00000000..8e891917 --- /dev/null +++ b/i18n/index.js @@ -0,0 +1,56 @@ +const en = require("./translations.en.json"); +const ar = require("./translations.ar.json"); +const ca = require("./translations.ca.json"); +const zh = require("./translations.zh.json"); +const cs = require("./translations.cs.json"); +const nl = require("./translations.nl.json"); +const fr = require("./translations.fr.json"); +const de = require("./translations.de.json"); +const he = require("./translations.he.json"); +const hu = require("./translations.hu.json"); +const id = require("./translations.id.json"); +const it = require("./translations.it.json"); +const ja = require("./translations.ja.json"); +const ko = require("./translations.ko.json"); +const pl = require("./translations.pl.json"); +const pt = require("./translations.pt.json"); +const ro = require("./translations.ro.json"); +const ru = require("./translations.ru.json"); +const sk = require("./translations.sk.json"); +const es = require("./translations.es.json"); +const sv = require("./translations.sv.json"); +const tr = require("./translations.tr.json"); +const uk = require("./translations.uk.json"); + +const i18n = { + translations: { + en, + ar, + ca, + zh, + cs, + nl, + fr, + de, + he, + hu, + id, + it, + ja, + ko, + pl, + pt, + ro, + ru, + sk, + es, + sv, + tr, + uk, + }, + defaultLang: "en", + useBrowserDefault: true, + languageDataStore: "localStorage", +}; + +module.exports = i18n; diff --git a/i18n/translations.ar.json b/i18n/translations.ar.json new file mode 100644 index 00000000..0967ef42 --- /dev/null +++ b/i18n/translations.ar.json @@ -0,0 +1 @@ +{} diff --git a/i18n/translations.ca.json b/i18n/translations.ca.json new file mode 100644 index 00000000..0967ef42 --- /dev/null +++ b/i18n/translations.ca.json @@ -0,0 +1 @@ +{} diff --git a/i18n/translations.cs.json b/i18n/translations.cs.json new file mode 100644 index 00000000..0967ef42 --- /dev/null +++ b/i18n/translations.cs.json @@ -0,0 +1 @@ +{} diff --git a/i18n/translations.de.json b/i18n/translations.de.json new file mode 100644 index 00000000..0967ef42 --- /dev/null +++ b/i18n/translations.de.json @@ -0,0 +1 @@ +{} diff --git a/i18n/translations.en.json b/i18n/translations.en.json new file mode 100644 index 00000000..a091e724 --- /dev/null +++ b/i18n/translations.en.json @@ -0,0 +1,150 @@ +{ + "home": { + "title": "An open source Flash Player emulator", + "intro": "Made to run natively on all modern operating systems and browsers, Ruffle brings Flash content back to life with no extra fuss.", + "safe": "Safe to use", + "safe-description": "{{safe}} - Using the guarantees of Rust and WASM, we avoid the security pitfalls Flash was known for.", + "easy": "Easy to install", + "easy-description": "{{easy}} - Whether you're a user or a website owner, we've made it as easy as possible to get up and running.", + "free": "Free and open source", + "free-description": "{{free}} - Licensed MIT/Apache 2.0, you're free to use Ruffle how you please!", + "alt-tag": "Person installing Ruffle" + }, + "header": { + "about": "About Ruffle", + "downloads": "Downloads", + "compatibility": "Compatibility", + "contribute": "Get Involved", + "blog": "Blog", + "demo": "Demo", + "discord": "Discord", + "github": "GitHub" + }, + "footer": { + "github": "GitHub", + "social-x": "X", + "tiktok": "TikTok", + "instagram": "Instagram", + "mastodon": "Mastodon", + "discord": "Discord", + "tagline": "Putting Flash back on the web" + }, + "logo": { + "alt-tag": "Ruffle Logo" + }, + "404": { + "not-found": "Page not found", + "not-found-description": "The requested page could not be found." + }, + "installers": { + "windows-64-short-name": "Windows (64-bit)", + "windows-long-name": "Windows Executable", + "windows-32-short-name": "Windows (32-bit)", + "macos-short-name": "macOS", + "macos-long-name": "Mac Application", + "flatpak-short-name": "Flatpak", + "flatpak-long-name": "Flatpak App", + "linux-short-name": "Linux", + "linux-long-name": "Linux Executable", + "chrome-short-name": "Chrome", + "chrome-long-name": "Chrome Extension", + "edge-short-name": "Edge", + "edge-long-name": "Edge Extension", + "firefox-short-name": "Firefox", + "firefox-long-name": "Firefox Extension", + "safari-short-name": "Safari", + "safari-long-name": "Safari Extension", + "selfhosted-short-name": "Self Hosted", + "selfhosted-long-name": "Website Package", + "other-downloads": "Other Downloads" + }, + "downloads": { + "version": "Version", + "desktop-app": "Desktop Application", + "desktop-app-description": "If you want to run Flash content on your computer without a browser in-between, we have native applications that will take full advantage of your GPU and system resources to get those extra frames when playing intense games.", + "browser-extension": "Browser Extension", + "browser-extension-description": "If you visit websites that have Flash content but aren't using Ruffle, or you want to ensure you're using the latest and greatest version of Ruffle on every website, then our browser extension is the perfect thing for you!", + "nightly-releases": "Nightly Releases", + "nightly-releases-description": "If none of the above are suitable for you, you can manually download the latest Nightly release. These are automatically built every day (approximately midnight UTC), unless there are no changes on that day. Older nightly releases are available on {{link}}.", + "web-package": "Web Package", + "web-package-description": "You can install Ruffle onto a website using one single line of code by using a CDN, no extra work required! It'll always stay up to date with the latest available version of Ruffle.", + "self-host-description": "If you'd like to host it yourself, you can grab {{link}} and upload it to your server. Then, include it on your page like so:", + "self-host-description-link": "the latest self-hosted package", + "advanced-usage-description": "For advanced usage, consult {{link}} for our JavaScript API and installation options.", + "advanced-usage-description-link": "our documentation", + "chrome-extension-alt": "Available in the Chrome Web Store", + "firefox-extension-alt": "Get the Add-On for Firefox", + "edge-extension-alt": "Get it from Microsoft for Edge" + }, + "compatibility": { + "title": "ActionScript Compatibility", + "description": "The biggest factor in content compatibility is ActionScript; the language that powers interactivity in games and applications made with Flash. All Flash content falls in one of two categories, depending on which version of the language was used to create it.", + "tracking": "We track our progress in each AVM by splitting them up into two different areas:", + "language-description": "The {{language}} is the underlying virtual machine itself and the language concepts that it understands, like variables and classes and how they all interact together.", + "language": "Language", + "api-description": "The {{api}} is the underlying virtual machine itself and the language concepts that it understands, like variables and classes and how they all interact together.", + "api": "API", + "avm1-title": "AVM 1: ActionScript 1 & 2", + "avm1-description": "AVM 1 is the original ActionScript Virtual Machine. All movies made before Flash Player 9 (June 2006) will be made with AVM 1, and it remained supported & available to authors until the release of Flash Professional CC (2013), after which point content started moving to AVM 2.", + "avm1-support": "We believe that most AVM 1 content will work, but we are aware of some graphical inaccuracies and smaller bugs here and there. Please feel free to report any issues you find that are not present in the original Flash Player!", + "avm2-title": "AVM 2: ActionScript 3", + "avm2-description": "AVM 2 was introduced with Flash Player 9 (June 2006), to replace the earlier AVM 1. After the release of Flash Professional CC (2013), authors are required to use ActionScript 3 - making any movie made after that date very likely to fall under this category.", + "avm2-support": "Ruffle now has decent support for AVM 2, and it's our experience that most games will work well enough to be played. We're still rapidly improving in this area though, so bug reports about any broken content are always welcome!", + "weekly-contributions": "Weekly Contributions", + "done": "done", + "partial": "partially done", + "more": "More Info", + "commits-description": "{{commitNumber}} commits on the week of {{week}}", + "loading": "Loading...", + "alt-tag": "Person comparing Ruffle compatibility", + "avm2": { + "title": "ActionScript 3 API Progress", + "description": "ActionScript 3 contains many different methods and classes - not all of which is ultimately useful to every application. The majority of content only uses a small portion of the available API, so even if we aren't 100% \"complete\" across the entirely of AVM 2, we may have enough for that content to run completely fine.", + "classification": "On this page, we list every single ActionScript 3 API that exists but Ruffle does not yet 100% implement. We classify items into three different stages:", + "implemented": "Implemented", + "implemented-description": "{{implemented}} items are marked as \"Done\", and we believe they are fully functional. For brevity, we do not list completed items on this page.", + "partial": "Partial", + "partial-description": "{{partial}} items exist and are enough for most content to work, but are incomplete. A partial class may be missing items, or a method may just simply return a value without performing its intended function.", + "missing": "Missing", + "missing-description": "{{missing}} items do not exist at all in Ruffle yet, and trying to use them will give an error.", + "tree": "You can also visualize the progress {{link}}.", + "tree-link": "as a tree graph", + "top-level": "(Top Level)", + "package-level": "(Package Level)", + "hide": "Hide", + "show": "Show", + "missing-members": "Missing Members", + "done": "Done" + } + }, + "contribute": { + "alt-tag": "Person thanking you", + "involved": "Get Involved", + "involved-description": "Ruffle is an entirely open source project, maintained by volunteers like you who just want to help preserve a slice of history. We rely on contributions of any kind to keep this project going, and absolutely would not have come as far as we have without the amazing support of our community who came together to make Ruffle happen. If you'd like to join them, there are many ways to help make Ruffle better than ever!", + "code": "Contribute code", + "code-description": "There's a few different codebases in couple of different languages, and we'd welcome any help to try and maintain and improve them.", + "rust": "The actual {{emulator}} itself, and all of the {{desktop-player}}, is written in Rust.", + "emulator": "emulator", + "desktop-player": "desktop player", + "typescript": "The {{web-player}}, the {{extension}} and our {{website}} is written in TypeScript.", + "web-player": "web player", + "extension": "extension", + "website": "website", + "getting-started": "Check out our {{guidelines}} for information on how to start, and come join our {{discord}} if you need help!", + "guidelines": "Contributing Guidelines", + "test": "Test content", + "test-description": "Arguably more important than contributing code is testing Ruffle out. Go install Ruffle and try out your favourite games and animations. Look for any difference from the official Flash Player, and report your findings to us.", + "report-bugs": "If you find any bugs, changes of behaviour, performance issues or any visual differences then please report those to our {{bug-tracker}}.", + "bug-tracker": "our bug tracker", + "working": "If it runs flawlessly, come share the good news on {{our-discord}}!", + "our-discord": "our Discord", + "sponsor": "Sponsor the project", + "sponsor-description": "If you are able and willing to, we welcome any and all financial support to help us fund the project going forward. With your help, we can afford to spend more time dedicated to Ruffle, as well as pay for expenses such as build servers & hosting. We accept donations and sponsorships of any kind, big or small, through Open Source Collective 501(c)(6).", + "sponsor-info": "For more information, or to view the options available for sponsoring the project, please visit {{opencollective}}.", + "opencollective": "our Open Collective page", + "spread-the-word": "Spread the word!", + "spread-the-word-description": "Is your favourite Flash-based site shutting down? Let them know they can add one JavaScript file and keep it running! Feeling nostalgic for some old Flash games? Go play some on Newgrounds with Ruffle installed, and tell your friends about it! Maybe you're a streamer and looking for some silly content? There's literally decades worth, now unlocked and accessible once more.", + "diamond": "Diamond Sponsors", + "diamond-description": "We'd like to thank all of our sponsors, who help make this project possible. Below are our Diamond level sponsors, without whom we would not be here. Thank you." + } +} diff --git a/i18n/translations.es.json b/i18n/translations.es.json new file mode 100644 index 00000000..0967ef42 --- /dev/null +++ b/i18n/translations.es.json @@ -0,0 +1 @@ +{} diff --git a/i18n/translations.fr.json b/i18n/translations.fr.json new file mode 100644 index 00000000..0967ef42 --- /dev/null +++ b/i18n/translations.fr.json @@ -0,0 +1 @@ +{} diff --git a/i18n/translations.he.json b/i18n/translations.he.json new file mode 100644 index 00000000..0967ef42 --- /dev/null +++ b/i18n/translations.he.json @@ -0,0 +1 @@ +{} diff --git a/i18n/translations.hu.json b/i18n/translations.hu.json new file mode 100644 index 00000000..0967ef42 --- /dev/null +++ b/i18n/translations.hu.json @@ -0,0 +1 @@ +{} diff --git a/i18n/translations.id.json b/i18n/translations.id.json new file mode 100644 index 00000000..0967ef42 --- /dev/null +++ b/i18n/translations.id.json @@ -0,0 +1 @@ +{} diff --git a/i18n/translations.it.json b/i18n/translations.it.json new file mode 100644 index 00000000..0967ef42 --- /dev/null +++ b/i18n/translations.it.json @@ -0,0 +1 @@ +{} diff --git a/i18n/translations.ja.json b/i18n/translations.ja.json new file mode 100644 index 00000000..0967ef42 --- /dev/null +++ b/i18n/translations.ja.json @@ -0,0 +1 @@ +{} diff --git a/i18n/translations.ko.json b/i18n/translations.ko.json new file mode 100644 index 00000000..0967ef42 --- /dev/null +++ b/i18n/translations.ko.json @@ -0,0 +1 @@ +{} diff --git a/i18n/translations.nl.json b/i18n/translations.nl.json new file mode 100644 index 00000000..0967ef42 --- /dev/null +++ b/i18n/translations.nl.json @@ -0,0 +1 @@ +{} diff --git a/i18n/translations.pl.json b/i18n/translations.pl.json new file mode 100644 index 00000000..0967ef42 --- /dev/null +++ b/i18n/translations.pl.json @@ -0,0 +1 @@ +{} diff --git a/i18n/translations.pt.json b/i18n/translations.pt.json new file mode 100644 index 00000000..0967ef42 --- /dev/null +++ b/i18n/translations.pt.json @@ -0,0 +1 @@ +{} diff --git a/i18n/translations.ro.json b/i18n/translations.ro.json new file mode 100644 index 00000000..0967ef42 --- /dev/null +++ b/i18n/translations.ro.json @@ -0,0 +1 @@ +{} diff --git a/i18n/translations.ru.json b/i18n/translations.ru.json new file mode 100644 index 00000000..0967ef42 --- /dev/null +++ b/i18n/translations.ru.json @@ -0,0 +1 @@ +{} diff --git a/i18n/translations.sk.json b/i18n/translations.sk.json new file mode 100644 index 00000000..0967ef42 --- /dev/null +++ b/i18n/translations.sk.json @@ -0,0 +1 @@ +{} diff --git a/i18n/translations.sv.json b/i18n/translations.sv.json new file mode 100644 index 00000000..0967ef42 --- /dev/null +++ b/i18n/translations.sv.json @@ -0,0 +1 @@ +{} diff --git a/i18n/translations.tr.json b/i18n/translations.tr.json new file mode 100644 index 00000000..0967ef42 --- /dev/null +++ b/i18n/translations.tr.json @@ -0,0 +1 @@ +{} diff --git a/i18n/translations.uk.json b/i18n/translations.uk.json new file mode 100644 index 00000000..0967ef42 --- /dev/null +++ b/i18n/translations.uk.json @@ -0,0 +1 @@ +{} diff --git a/i18n/translations.zh.json b/i18n/translations.zh.json new file mode 100644 index 00000000..0967ef42 --- /dev/null +++ b/i18n/translations.zh.json @@ -0,0 +1 @@ +{} From 9f8e35c5e4ee967af7dcebbc119ba23609c28d5d Mon Sep 17 00:00:00 2001 From: Daniel Jacobs Date: Wed, 7 May 2025 16:16:27 -0400 Subject: [PATCH 3/6] i18n: Add language selection dropdown --- src/components/header.module.css | 6 ++ src/components/header.tsx | 7 +- src/components/language-picker.module.css | 36 +++++++++ src/components/language-picker.tsx | 90 +++++++++++++++++++++++ 4 files changed, 138 insertions(+), 1 deletion(-) create mode 100644 src/components/language-picker.module.css create mode 100644 src/components/language-picker.tsx diff --git a/src/components/header.module.css b/src/components/header.module.css index df9d3a36..25df8dd6 100644 --- a/src/components/header.module.css +++ b/src/components/header.module.css @@ -34,6 +34,12 @@ } } +.hiddenOnDesktop { + @media (min-width: $mantine-breakpoint-md) { + display: none; + } +} + .burger { --burger-color: var(--ruffle-orange); } diff --git a/src/components/header.tsx b/src/components/header.tsx index 112fb98d..432207b2 100644 --- a/src/components/header.tsx +++ b/src/components/header.tsx @@ -3,6 +3,7 @@ import { Burger, Container, Drawer, Group } from "@mantine/core"; import { useDisclosure } from "@mantine/hooks"; import classes from "./header.module.css"; +import { LanguagePicker } from "./language-picker"; import Image from "next/image"; import Link from "next/link"; import { usePathname } from "next/navigation"; @@ -43,7 +44,7 @@ export function Header() { return (
- + {items} + {" "} {items} + + + { + if (typeof window !== "undefined") { + const browserLang = ( + (window.navigator.languages && window.navigator.languages[0]) || + window.navigator.language + ) + .split("-")[0] + .toLowerCase(); + const currentLocale = data.some((item) => item.locale === browserLang) + ? browserLang + : "en"; + const storedLang = + localStorage.getItem("next-export-i18n-lang") || currentLocale; + const match = data.find((item) => item.locale === storedLang); + if (match) setSelected(match); + } + }, []); + + const items = data.map((item) => ( + + setSelected(item)} + > + {item.label} + + + )); + + return ( + setOpened(true)} + onClose={() => setOpened(false)} + radius="md" + width="target" + withinPortal + > + + + + {selected.label} + + + + + {items} + + ); +} From 27c6ef1ba33f4740c0cdf3a0d9ef11e7699d5174 Mon Sep 17 00:00:00 2001 From: Daniel Jacobs Date: Wed, 7 May 2025 16:17:25 -0400 Subject: [PATCH 4/6] i18n: Wrap layout in Suspense (https://github.com/martinkr/next-export-i18n/issues/74) --- src/app/layout.tsx | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/app/layout.tsx b/src/app/layout.tsx index 87428efa..6cd709d1 100644 --- a/src/app/layout.tsx +++ b/src/app/layout.tsx @@ -5,6 +5,7 @@ import { MantineProvider, ColorSchemeScript } from "@mantine/core"; import { cssResolver, theme } from "@/theme"; import { Header } from "@/components/header"; import { FooterSocial } from "@/components/footer"; +import { Suspense } from "react"; export const metadata: Metadata = { title: "Ruffle - Flash Emulator", @@ -41,9 +42,11 @@ export default function RootLayout({ -
- {children} - + +
+ {children} + + From e3fc5783039cc51e45f5d5e00ce010d0db4fb83f Mon Sep 17 00:00:00 2001 From: Daniel Jacobs Date: Wed, 14 May 2025 10:50:19 -0400 Subject: [PATCH 5/6] i18n: Add Trans (https://github.com/martinkr/next-export-i18n/issues/23) --- src/components/trans.tsx | 54 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 54 insertions(+) create mode 100644 src/components/trans.tsx diff --git a/src/components/trans.tsx b/src/components/trans.tsx new file mode 100644 index 00000000..e4264052 --- /dev/null +++ b/src/components/trans.tsx @@ -0,0 +1,54 @@ +import React, { useEffect, useState } from "react"; +import { useTranslation } from "next-export-i18n"; + +export type TransProps = { + i18nKey: string; + values?: Record; + components?: { [key: string]: React.ReactNode }; +}; + +const Trans: React.FC = ({ + i18nKey, + values = {}, + components = {}, +}) => { + const { t } = useTranslation(); + const [mounted, setMounted] = useState(false); + + useEffect(() => { + setMounted(true); + }, []); + + if (!mounted) return null; + + const translation = t(i18nKey); + + const renderWithPlaceholders = (template: string) => { + const parts = template.split(/({{\s*.*?\s*}})/g); // Split on placeholders like {{key}} + + return parts.map((part, index) => { + const match = part.match(/^{{\s*(.*?)\s*}}$/); + if (match) { + const key = match[1]; + if (key in values) { + const value = values[key]; + return {value}; + } else if (key in components) { + return ( + + {components[key]} + + ); + } else { + // Return raw placeholder if key not found + return part; + } + } + return {part}; + }); + }; + + return <>{renderWithPlaceholders(translation)}; +}; + +export default Trans; From daf866fea4e54b2df73e8a75987234428f70d34f Mon Sep 17 00:00:00 2001 From: Daniel Jacobs Date: Wed, 14 May 2025 13:21:30 -0400 Subject: [PATCH 6/6] i18n: Translate header/footer/logo components --- src/components/footer.tsx | 21 ++++++++++++--------- src/components/header.tsx | 28 ++++++++++++++++++---------- src/components/logo.tsx | 5 ++++- 3 files changed, 34 insertions(+), 20 deletions(-) diff --git a/src/components/footer.tsx b/src/components/footer.tsx index 507e6190..98a38a11 100644 --- a/src/components/footer.tsx +++ b/src/components/footer.tsx @@ -2,6 +2,7 @@ import { Container, Group, ActionIcon, rem, Text } from "@mantine/core"; import Link from "next/link"; +import { useTranslation } from "next-export-i18n"; import { IconBrandX, @@ -18,36 +19,37 @@ const allSocials = [ { type: IconBrandGithub, url: "https://github.com/ruffle-rs", - title: "GitHub", + title: "footer.github", }, { type: IconBrandX, url: "https://twitter.com/ruffle_rs", - title: "X", + title: "footer.social-x", }, { type: IconBrandTiktok, url: "https://www.tiktok.com/@ruffle.rs", - title: "Tiktok", + title: "footer.tiktok", }, { type: IconBrandInstagram, url: "https://www.instagram.com/ruffle.rs/", - title: "Instagram", + title: "footer.instagram", }, { type: IconBrandMastodon, url: "https://mastodon.gamedev.place/@ruffle", - title: "Mastodon", + title: "footer.mastodon", }, { type: IconBrandDiscord, url: "https://discord.gg/ruffle", - title: "Discord", + title: "footer.discord", }, ]; export function FooterSocial() { + const { t } = useTranslation(); const socials = allSocials.map((social, i) => ( @@ -74,8 +77,8 @@ export function FooterSocial() { width={91} priority /> - - Putting Flash back on the web + + {t("footer.tagline")} ( - {link.label} + {t(link.label)} )); @@ -47,8 +54,9 @@ export function Header() { Ruffle Logo(null); const [player, setPlayer] = useState(null); @@ -80,8 +82,9 @@ export default function InteractiveLogo({ className }: LogoProps) { />