Commit 19a974ef authored by BBeretta's avatar BBeretta

feat: Added support for new languages to enhance game accessibility in...

feat: Added support for new languages to enhance game accessibility in different regions, aiming to reach more players.
parent e24f0887
Pipeline #27120 failed with stages
in 8 minutes and 17 seconds
......@@ -17,6 +17,7 @@
"cookies-ts": "^1.0.5",
"eventemitter3": "^5.0.1",
"google-protobuf": "^3.21.2",
"i18next": "^23.11.4",
"idb-keyval": "^6.2.1",
"lodash-es": "^4.17.21",
"overlayscrollbars-react": "^0.5.1",
......@@ -26,6 +27,7 @@
"react-dnd": "^16.0.1",
"react-dnd-multi-backend": "^8.0.3",
"react-dom": "^18.2.0",
"react-i18next": "^14.1.1",
"react-router-dom": "^6.15.0",
"react-use-websocket": "^4.5.0",
"sql.js": "^1.8.0",
......@@ -825,9 +827,9 @@
}
},
"node_modules/@babel/runtime": {
"version": "7.22.10",
"resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.22.10.tgz",
"integrity": "sha512-21t/fkKLMZI4pqP2wlmsQAWnYW1PDyKyyUV4vCi+B25ydmdaYTKXPwCj0BzSUnZf4seIiYvSA3jcZ3gdsMFkLQ==",
"version": "7.24.5",
"resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.24.5.tgz",
"integrity": "sha512-Nms86NXrsaeU9vbBJKni6gXiEXZ4CVpYVzEjDH9Sb8vmZ3UljyA1GSOJl/6LGPO8EHLuSF9H+IxNXHPX8QHJ4g==",
"dependencies": {
"regenerator-runtime": "^0.14.0"
},
......@@ -4075,6 +4077,14 @@
"react-is": "^16.7.0"
}
},
"node_modules/html-parse-stringify": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/html-parse-stringify/-/html-parse-stringify-3.0.1.tgz",
"integrity": "sha512-KknJ50kTInJ7qIScF3jeaFRpMpE8/lfiTdzf/twXyPBLAGrLRTmkz3AdTnKeh40X8k9L2fdYwEp/42WGXIRGcg==",
"dependencies": {
"void-elements": "3.1.0"
}
},
"node_modules/human-signals": {
"version": "4.3.1",
"resolved": "https://registry.npmjs.org/human-signals/-/human-signals-4.3.1.tgz",
......@@ -4084,6 +4094,28 @@
"node": ">=14.18.0"
}
},
"node_modules/i18next": {
"version": "23.11.4",
"resolved": "https://registry.npmjs.org/i18next/-/i18next-23.11.4.tgz",
"integrity": "sha512-CCUjtd5TfaCl+mLUzAA0uPSN+AVn4fP/kWCYt/hocPUwusTpMVczdrRyOBUwk6N05iH40qiKx6q1DoNJtBIwdg==",
"funding": [
{
"type": "individual",
"url": "https://locize.com"
},
{
"type": "individual",
"url": "https://locize.com/i18next.html"
},
{
"type": "individual",
"url": "https://www.i18next.com/how-to/faq#i18next-is-awesome.-how-can-i-support-the-project"
}
],
"dependencies": {
"@babel/runtime": "^7.23.2"
}
},
"node_modules/idb-keyval": {
"version": "6.2.1",
"resolved": "https://registry.npmjs.org/idb-keyval/-/idb-keyval-6.2.1.tgz",
......@@ -5946,6 +5978,27 @@
"react": "^18.2.0"
}
},
"node_modules/react-i18next": {
"version": "14.1.1",
"resolved": "https://registry.npmjs.org/react-i18next/-/react-i18next-14.1.1.tgz",
"integrity": "sha512-QSiKw+ihzJ/CIeIYWrarCmXJUySHDwQr5y8uaNIkbxoGRm/5DukkxZs+RPla79IKyyDPzC/DRlgQCABHtrQuQQ==",
"dependencies": {
"@babel/runtime": "^7.23.9",
"html-parse-stringify": "^3.0.1"
},
"peerDependencies": {
"i18next": ">= 23.2.3",
"react": ">= 16.8.0"
},
"peerDependenciesMeta": {
"react-dom": {
"optional": true
},
"react-native": {
"optional": true
}
}
},
"node_modules/react-intersection-observer": {
"version": "8.34.0",
"resolved": "https://registry.npmjs.org/react-intersection-observer/-/react-intersection-observer-8.34.0.tgz",
......@@ -7055,6 +7108,14 @@
"integrity": "sha512-4Yn+RxcCXoRLIkCY4w7TU2hZrNiQmBe0X9T3w1Do4GBiuSDHrqAa7jBavhq78+5c2yPNvHTrfmY3g70ziaH62A==",
"dev": true
},
"node_modules/void-elements": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/void-elements/-/void-elements-3.1.0.tgz",
"integrity": "sha512-Dhxzh5HZuiHQhbvTW9AMetFfBHDMYpo23Uo9btPXgdYP+3T5S+p+jgNy7spra+veYhBP2dCSgxR/i2Y02h5/6w==",
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/warning": {
"version": "4.0.3",
"resolved": "https://registry.npmjs.org/warning/-/warning-4.0.3.tgz",
......@@ -7730,9 +7791,9 @@
}
},
"@babel/runtime": {
"version": "7.22.10",
"resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.22.10.tgz",
"integrity": "sha512-21t/fkKLMZI4pqP2wlmsQAWnYW1PDyKyyUV4vCi+B25ydmdaYTKXPwCj0BzSUnZf4seIiYvSA3jcZ3gdsMFkLQ==",
"version": "7.24.5",
"resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.24.5.tgz",
"integrity": "sha512-Nms86NXrsaeU9vbBJKni6gXiEXZ4CVpYVzEjDH9Sb8vmZ3UljyA1GSOJl/6LGPO8EHLuSF9H+IxNXHPX8QHJ4g==",
"requires": {
"regenerator-runtime": "^0.14.0"
}
......@@ -9906,12 +9967,28 @@
"react-is": "^16.7.0"
}
},
"html-parse-stringify": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/html-parse-stringify/-/html-parse-stringify-3.0.1.tgz",
"integrity": "sha512-KknJ50kTInJ7qIScF3jeaFRpMpE8/lfiTdzf/twXyPBLAGrLRTmkz3AdTnKeh40X8k9L2fdYwEp/42WGXIRGcg==",
"requires": {
"void-elements": "3.1.0"
}
},
"human-signals": {
"version": "4.3.1",
"resolved": "https://registry.npmjs.org/human-signals/-/human-signals-4.3.1.tgz",
"integrity": "sha512-nZXjEF2nbo7lIw3mgYjItAfgQXog3OjJogSbKa2CQIIvSGWcKgeJnQlNXip6NglNzYH45nSRiEVimMvYL8DDqQ==",
"dev": true
},
"i18next": {
"version": "23.11.4",
"resolved": "https://registry.npmjs.org/i18next/-/i18next-23.11.4.tgz",
"integrity": "sha512-CCUjtd5TfaCl+mLUzAA0uPSN+AVn4fP/kWCYt/hocPUwusTpMVczdrRyOBUwk6N05iH40qiKx6q1DoNJtBIwdg==",
"requires": {
"@babel/runtime": "^7.23.2"
}
},
"idb-keyval": {
"version": "6.2.1",
"resolved": "https://registry.npmjs.org/idb-keyval/-/idb-keyval-6.2.1.tgz",
......@@ -11185,6 +11262,15 @@
"scheduler": "^0.23.0"
}
},
"react-i18next": {
"version": "14.1.1",
"resolved": "https://registry.npmjs.org/react-i18next/-/react-i18next-14.1.1.tgz",
"integrity": "sha512-QSiKw+ihzJ/CIeIYWrarCmXJUySHDwQr5y8uaNIkbxoGRm/5DukkxZs+RPla79IKyyDPzC/DRlgQCABHtrQuQQ==",
"requires": {
"@babel/runtime": "^7.23.9",
"html-parse-stringify": "^3.0.1"
}
},
"react-intersection-observer": {
"version": "8.34.0",
"resolved": "https://registry.npmjs.org/react-intersection-observer/-/react-intersection-observer-8.34.0.tgz",
......@@ -11925,6 +12011,11 @@
"integrity": "sha512-4Yn+RxcCXoRLIkCY4w7TU2hZrNiQmBe0X9T3w1Do4GBiuSDHrqAa7jBavhq78+5c2yPNvHTrfmY3g70ziaH62A==",
"dev": true
},
"void-elements": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/void-elements/-/void-elements-3.1.0.tgz",
"integrity": "sha512-Dhxzh5HZuiHQhbvTW9AMetFfBHDMYpo23Uo9btPXgdYP+3T5S+p+jgNy7spra+veYhBP2dCSgxR/i2Y02h5/6w=="
},
"warning": {
"version": "4.0.3",
"resolved": "https://registry.npmjs.org/warning/-/warning-4.0.3.tgz",
......
import React, { createContext, useState, useContext } from 'react';
interface LanguageContextType {
language: string;
changeLanguage: (newLanguage: string) => void;
}
const LanguageContext = createContext<LanguageContextType | undefined>(undefined);
export const LanguageProvider: React.FC<{ children: React.ReactNode }> = ({ children }) => {
const [language, setLanguage] = useState<string>('cn'); // default language
const changeLanguage = (newLanguage: string) => {
setLanguage(newLanguage);
};
return (
<LanguageContext.Provider value={{ language, changeLanguage }}>
{children}
</LanguageContext.Provider>
);
};
export const useLanguage = (): LanguageContextType => {
const context = useContext(LanguageContext);
if (!context) {
throw new Error('useLanguage must be used within a LanguageProvider');
}
return context;
};
import React, { useEffect } from 'react';
import { useTranslation } from 'react-i18next';
const LanguageSelector: React.FC = () => {
const { t, i18n } = useTranslation();
const onClickLanguageChange = (e: React.ChangeEvent<HTMLSelectElement>) => {
const language = e.target.value;
i18n.changeLanguage(language);
console.log('Language:: ', language);
};
useEffect(() => {
//Adding language state as a dependency to force re-render when the language changes
}, [i18n.language]);
return (
<select value={i18n.language} onChange={onClickLanguageChange}>
<option value="cn">Chinese</option>
<option value="en">English</option>
<option value="fr">French</option>
<option value="jp">Japanese</option>
<option value="br">Brazilian</option>
<option value="pt">Portuguese</option>
<option value="es">Spanish</option>
</select>
);
};
export default LanguageSelector;
{
"Header": {
"HomePage": "Página Inicial",
"Match": "Match",
"DeckBuilding": "Montagem de baralho",
"PersonalCenter": "Centro pessoal",
"MengkaCommunity": "Comunidade Mengka",
"DuelDatabase": "Base de dados de duelos",
"LogOut": "Sair",
"LoginToMengka": "Entrar no Mengka",
"Fullscreen": "Tela cheia"
},
"Start": {
"Title": "Plataforma de batalha online de Yu-Gi-Oh!",
"Keywords": "Código aberto, gratuito, leve",
"Details": "Neos é uma plataforma de batalha online de Yu-gi-oh de código aberto. No Neos, você pode construir decks, criar salas, convidar amigos para batalhas. Atualmente, o Neos implementou a funcionalidade de batalhar contra jogadores de plataformas como YGOpro, YGOpro2, YGOmobile e KoishiPro, e mais clientes serão suportados no futuro.",
"StartGame": "Iniciar jogo",
"LoginToGame": "Entrar no jogo"
}
}
\ No newline at end of file
{
"Header": {
"HomePage": "主页",
"Match": "匹配",
"DeckBuilding": "组卡",
"PersonalCenter": "个人中心",
"MengkaCommunity": "萌卡社区",
"DuelDatabase": "决斗数据库",
"LogOut": "退出登录",
"LoginToMengka": "登录萌卡",
"Fullscreen": "全屏"
},
"Start": {
"Title": "游戏王网页端对战平台",
"Keywords": "开源、免费、轻量级",
"Details": "Neos是一个开源的游戏王网页端对战平台。在Neos中,你可以组建卡组,创建房间,邀请好友进行对战。目前,Neos已经实现了与来自YGOpro、YGOpro2、YGOmobile和KoishiPro等平台的玩家进行对战的功能,而今后更多客户端也将得到支持。",
"StartGame": "开始游戏",
"LoginToGame": "登录游戏"
}
}
\ No newline at end of file
{
"Header": {
"HomePage": "Home Page",
"Match": "Match",
"DeckBuilding": "Deck Building",
"PersonalCenter": "Personal Center",
"MengkaCommunity": "Mengka Community",
"DuelDatabase": "Duel Database",
"LogOut": "Log out",
"LoginToMengka": "Login to Mengka",
"Fullscreen": "Fullscreen"
},
"Start": {
"Title": "Yu-Gi-Oh! Web Based Battle Platform",
"Keywords": "Open Source, Free, Lightweight",
"Details": "Neos is an open-source Yu-gi-oh web-based battle platform. In Neos, you can build decks, create rooms, invite friends for battles. Currently, Neos has implemented the functionality to battle players from platforms such as YGOpro, YGOpro2, YGOmobile, and KoishiPro, and more clients will be supported in the future.",
"StartGame": "Start game",
"LoginToGame": "Start game"
}
}
\ No newline at end of file
{
"Header": {
"HomePage": "Page d'accueil",
"Match": "Correspondance",
"DeckBuilding": "Construction de Deck",
"PersonalCenter": "Centre personnel",
"MengkaCommunity": "Communauté Mengka",
"DuelDatabase": "Base de données de duels",
"LogOut": "Déconnexion",
"LoginToMengka": "Connexion à Mengka",
"Fullscreen": "Plein écran"
},
"Start": {
"Title": "Plateforme de combat Yu-gi-oh basée sur le Web",
"Keywords": "Open-source, gratuit, léger",
"Details": "Neos est une plateforme de combat Yu-gi-oh basée sur le Web open-source. Sur Neos, vous pouvez construire des decks, créer des salles, inviter des amis à se battre. Actuellement, Neos a mis en œuvre la fonctionnalité de combattre les joueurs des plateformes telles que YGOpro, YGOpro2, YGOmobile et KoishiPro, et plus de clients seront pris en charge à l'avenir.",
"StartGame": "Commencer le jeu",
"LoginToGame": "Se connecter au jeu"
}
}
\ No newline at end of file
{
"Header": {
"HomePage": "ホームページ",
"Match": "マッチ",
"DeckBuilding": "デッキ構築",
"PersonalCenter": "個人センター",
"MengkaCommunity": "萌卡コミュニティ",
"DuelDatabase": "デュエルデータベース",
"LogOut": "ログアウト",
"LoginToMengka": "萌卡にログインする",
"Fullscreen": "フルスクリーン"
},
"Start": {
"Title": "遊戯王ウェブベースのバトルプラットフォーム",
"Keywords": "オープンソース、無料、軽量",
"Details": "Neosはオープンソースの遊戯王ウェブベースのバトルプラットフォームです。Neosでは、デッキを組み、部屋を作成し、友達を招待して対戦することができます。現在、Neosでは、YGOpro、YGOpro2、YGOmobile、KoishiProなどのプラットフォームからのプレイヤーとの対戦機能が実装されており、今後はさらに多くのクライアントがサポートされる予定です。",
"StartGame": "ゲームを開始する",
"LoginToGame": "ゲームにログインする"
}
}
\ No newline at end of file
{
"Header": {
"HomePage": "Página Inicial",
"Match": "Match",
"DeckBuilding": "Montagem de baralho",
"PersonalCenter": "Centro pessoal",
"MengkaCommunity": "Comunidade Mengka",
"DuelDatabase": "Base de dados de duelos",
"LogOut": "Terminar sessão",
"LoginToMengka": "Iniciar sessão no Mengka",
"Fullscreen": "Ecrã completo"
},
"Start": {
"Title": "Plataforma de batalha online de Yu-Gi-Oh!",
"Keywords": "Código aberto, gratuito, leve",
"Details": "Neos é uma plataforma de batalha online de Yu-gi-oh de código aberto. No Neos, você pode construir decks, criar salas, convidar amigos para batalhas. Atualmente, o Neos implementou a funcionalidade de batalhar contra jogadores de plataformas como YGOpro, YGOpro2, YGOmobile e KoishiPro, e mais clientes serão suportados no futuro.",
"StartGame": "Iniciar jogo",
"LoginToGame": "Entrar no jogo"
}
}
\ No newline at end of file
{
"Header": {
"HomePage": " Página Principal",
"Match": "Emparejamiento",
"DeckBuilding": "Construcción de Mazo",
"PersonalCenter": "Centro personal",
"MengkaCommunity": "Comunidad Mengka",
"DuelDatabase": "Base de datos de duelos",
"LogOut": "Cerrar sesión",
"LoginToMengka": "Iniciar sesión en Mengka",
"Fullscreen": "Pantalla completa"
},
"Start": {
"Title": "Plataforma de batalla basada en la web de Yu-gi-oh",
"Keywords": "Código abierto, gratuito, ligero",
"Details": "Neos es una plataforma de batalla Yu-gi-oh basada en la web de código abierto. En Neos, puedes construir mazos, crear salas, invitar a amigos a batallas. Actualmente, Neos ha implementado la funcionalidad de batallar contra jugadores de plataformas como YGOpro, YGOpro2, YGOmobile y KoishiPro, y más clientes serán compatibles en el futuro.",
"StartGame": "Comenzar juego",
"LoginToGame": "Iniciar sesión en el juego"
}
}
\ No newline at end of file
import i18next from "i18next";
import { initReactI18next } from "react-i18next";
//Import all translation files
import translationEnglish from "./Translation/English/translation.json";
import translationSpanish from "./Translation/Spanish/translation.json";
import translationFrench from "./Translation/French/translation.json";
import translationChinese from "./Translation/Chinese/translation.json";
import translationJapanese from "./Translation/Japanese/translation.json";
import translationBrazilian from "./Translation/Brazilian/translation.json";
import translationPortuguese from "./Translation/Portuguese/translation.json";
//---Using translation
// const resources = {
// en: {
// translation: translationEnglish,
// },
// es: {
// translation: translationSpanish,
// },
// fr: {
// translation: translationFrench,
// },
// }
//---Using different namespaces
const resources = {
cn: {
Header: translationChinese.Header,
Start: translationChinese.Start
},
en: {
Header: translationEnglish.Header,
Start: translationEnglish.Start
},
es: {
Header: translationSpanish.Header,
Start: translationSpanish.Start
},
fr: {
Header: translationFrench.Header,
Start: translationFrench.Start
},
jp: {
Header: translationJapanese.Header,
Start: translationJapanese.Start
},
br: {
Header: translationBrazilian.Header,
Start: translationBrazilian.Start
},
pt: {
Header: translationPortuguese.Header,
Start: translationPortuguese.Start
},
}
i18next
.use(initReactI18next)
.init({
resources,
lng:"cn", //default language
});
export default i18next;
\ No newline at end of file
......@@ -29,16 +29,22 @@ import { theme } from "@/ui/theme";
import { NeosRouter } from "./ui/NeosRouter";
import './i18n'
import { LanguageProvider } from './Language/LanguageContext';
const root = ReactDOM.createRoot(
document.getElementById("root") as HTMLElement,
);
root.render(
<LanguageProvider>
<ConfigProvider theme={theme} locale={zhCN}>
<App>
<ProConfigProvider dark>
<NeosRouter />
</ProConfigProvider>
</App>
</ConfigProvider>,
</ConfigProvider>
</LanguageProvider>,
);
......@@ -30,6 +30,9 @@ import {
initWASM,
} from "./utils";
import { useTranslation } from "react-i18next";
import LanguageSelector from "@/Language/LanguageSelector";
const NeosConfig = useConfig();
export const loader: LoaderFunction = async () => {
......@@ -58,6 +61,8 @@ const HeaderBtn: React.FC<
};
export const Component = () => {
const { t } = useTranslation('Header');
// 捕获SSO登录
const routerLocation = useLocation();
useEffect(() => {
......@@ -94,15 +99,15 @@ export const Component = () => {
alt="NEOS"
/>
</a>
<HeaderBtn to="/">主页</HeaderBtn>
<HeaderBtn to="/">{t("HomePage")}</HeaderBtn>
<HeaderBtn to="/match" disabled={!logined}>
匹配
{t("Match")}
</HeaderBtn>
<HeaderBtn to="/build" disabled={!logined}>
组卡
{t("DeckBuilding")}
</HeaderBtn>
<span style={{ flexGrow: 1 }} />
<LanguageSelector />
<span className={styles.profile}>
<Dropdown
arrow
......@@ -111,14 +116,14 @@ export const Component = () => {
{
label: (
<a href={NeosConfig.profileUrl} target="_blank">
个人中心
{t("PersonalCenter")}
</a>
),
},
{
label: (
<a href="https://ygobbs.com" target="_blank">
萌卡社区
{t("MengkaCommunity")}
</a>
),
},
......@@ -128,16 +133,16 @@ export const Component = () => {
href="https://mycard.moe/ygopro/arena/#/"
target="_blank"
>
决斗数据库
{t("DuelDatabase")}
</a>
),
},
{
label: logined ? "退出登录" : "登录萌卡",
label: logined ? t("LogOut") : t("LoginToMengka"),
onClick: logined ? onLogout : onLogin,
},
{
label: "全屏",
label: t("Fullscreen"),
onClick: () => document.documentElement.requestFullscreen(),
danger: true,
},
......@@ -163,4 +168,4 @@ const NeosAvatar = () => {
return (
<Avatar size="small" src={user?.avatar_url} style={{ cursor: "pointer" }} />
);
};
};
\ No newline at end of file
......@@ -8,10 +8,13 @@ import { accountStore } from "@/stores";
import { Background, SpecialButton } from "@/ui/Shared";
import styles from "./index.module.scss";
import { useTranslation } from "react-i18next";
const NeosConfig = useConfig();
export const Component: React.FC = () => {
const { t } = useTranslation('Start');
const { user } = useSnapshot(accountStore);
return (
<>
......@@ -24,11 +27,9 @@ export const Component: React.FC = () => {
src={`${NeosConfig.assetsPath}/neos-logo.svg`}
alt="YGO NEOS"
/>
<div className={styles.title}>游戏王网页端对战平台</div>
<div className={styles.keywords}>开源、免费、轻量级</div>
<div className={styles.details}>
Neos是一个开源的游戏王网页端对战平台。在Neos中,你可以组建卡组,创建房间,邀请好友进行对战。目前,Neos已经实现了与来自YGOpro、YGOpro2、YGOmobile和KoishiPro等平台的玩家进行对战的功能,而今后更多客户端也将得到支持。
</div>
<div className={styles.title}>{t("Title")}</div>
<div className={styles.keywords}>{t("Keywords")}</div>
<div className={styles.details}>{t("Details")}</div>
<LoginBtn logined={Boolean(user)} />
</div>
<div className={styles.right}>
......@@ -49,6 +50,8 @@ export const Component: React.FC = () => {
Component.displayName = "Start";
const LoginBtn: React.FC<{ logined: boolean }> = ({ logined }) => {
const { t } = useTranslation('Start');
const navigate = useNavigate();
const loginViaSSO = () =>
......@@ -62,8 +65,8 @@ const LoginBtn: React.FC<{ logined: boolean }> = ({ logined }) => {
style={{ marginTop: "auto" }}
onClick={logined ? goToMatch : loginViaSSO}
>
<span>{logined ? "开始游戏" : "登录游戏"}</span>
<span>{logined ? t("StartGame") : t("LoginToGame")}</span>
<RightOutlined />
</SpecialButton>
);
};
};
\ No newline at end of file
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment