diff options
author | Zhongheng Liu <z.liu@outlook.com.gr> | 2024-01-14 22:42:56 +0200 |
---|---|---|
committer | Zhongheng Liu <z.liu@outlook.com.gr> | 2024-01-14 22:42:56 +0200 |
commit | e85f77877479d27ae55e3b5633c655f1d3b93469 (patch) | |
tree | 950b585264e96d1a8fd4a1471ccd2c5744b196ad | |
parent | 24eb8e8067dcd0b9702fb02dd50bb600b875b5a1 (diff) | |
download | epq-web-e85f77877479d27ae55e3b5633c655f1d3b93469.tar.gz epq-web-e85f77877479d27ae55e3b5633c655f1d3b93469.tar.bz2 epq-web-e85f77877479d27ae55e3b5633c655f1d3b93469.zip |
Added basis for internationlisation
Currently supported: el_GR, en_US, ar_SA, zh_TW
-rw-r--r-- | src/App.tsx | 55 | ||||
-rw-r--r-- | src/Chat/Chat.tsx | 23 | ||||
-rw-r--r-- | src/Chat/MessageContainer.tsx | 73 | ||||
-rw-r--r-- | src/Chat/types.tsx | 72 | ||||
-rw-r--r-- | src/Intl/strings.json | 64 | ||||
-rw-r--r-- | src/context.ts | 4 | ||||
-rw-r--r-- | src/index.tsx | 19 | ||||
-rw-r--r-- | tsconfig.json | 42 |
8 files changed, 241 insertions, 111 deletions
diff --git a/src/App.tsx b/src/App.tsx index 8c4c748..f6511b0 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -1,31 +1,56 @@ -import React, { useState } from "react"; +import React, { createContext, useContext, useState } from "react"; import Chat from "./Chat/Chat"; import "./App.css"; -import { Message } from "./Chat/types"; +import { LangType, Message } from "./Chat/types"; import { MessageContainer } from "./Chat/MessageContainer"; -const App = (): React.ReactElement => { +import strings from "./Intl/strings.json"; +import { LangContext } from "./context"; + +// what we call "in the business" type gymnastics +const Wrapper = (): React.ReactElement => { + const [lang, setLang] = useState<LangType>("en_US"); + + return ( + <LangContext.Provider value={lang}> + <App + changeLang={(value: string) => { + setLang(value as LangType); + }} + /> + </LangContext.Provider> + ); +}; +const App = ({ + changeLang, +}: { + changeLang: (value: string) => void; +}): React.ReactElement => { const [username, setUsername] = useState<string>(); const [messages, setMessages] = useState<Message[]>([]); + // const [lang, setLang] = useState<LangType>("en_US"); + const lang = useContext(LangContext); + const home = strings[lang].homepage; if (!username) { - const newName = prompt("Username:") as string; + const newName = prompt(home.userNamePrompt) as string; setUsername(newName); } return ( <div className="App"> - <h1>Local Area Network Chat Application</h1> - <pre> - This web application was built for the purposes of an EPQ - project. - </pre> + <h1>{home.title}</h1> + <pre>{home.description}</pre> + <button + onClick={(ev) => { + const selection = prompt(home.newLangPrompt); + changeLang(selection ? (selection as LangType) : lang); + }} + > + {home.switchLang} + </button> {messages.map((message) => { return <MessageContainer {...message} />; })} - { - <Chat - user={username as string} - /> - } + {<Chat user={username as string} />} </div> ); }; -export default App; +export default Wrapper; diff --git a/src/Chat/Chat.tsx b/src/Chat/Chat.tsx index e2edfcb..76d97fa 100644 --- a/src/Chat/Chat.tsx +++ b/src/Chat/Chat.tsx @@ -1,9 +1,17 @@ -import React, { ReactElement, useEffect, useRef, useState } from "react"; +import React, { + ReactElement, + useContext, + useEffect, + useRef, + useState, +} from "react"; import { MessageContainer } from "./MessageContainer"; import { Client, Stomp, StompHeaders } from "@stomp/stompjs"; -import { Message, MessageType } from "./types"; +import { LangType, Message, MessageType } from "./types"; import { renderToStaticMarkup } from "react-dom/server"; import "./Chat.css"; +import strings from "../Intl/strings.json"; +import { LangContext } from "../context"; // The last bit of magic sauce to make this work // EXPLANATION // @@ -16,6 +24,8 @@ const endpoints = { history: "/api/v1/msg/", }; const Chat = ({ user }: { user: string }): React.ReactElement => { + const lang = useContext(LangContext); + const chatPage = strings[lang].chat; const [messages, setMessages] = useState<ReactElement[]>([]); let stompClientRef = useRef( new Client({ @@ -61,7 +71,6 @@ const Chat = ({ user }: { user: string }): React.ReactElement => { // Button press event handler. const sendData = () => { - const entryElement: HTMLInputElement = document.getElementById( "data-entry" ) as HTMLInputElement; @@ -103,12 +112,12 @@ const Chat = ({ user }: { user: string }): React.ReactElement => { }); return ( <div className="chat"> - <div className="chat-inner-wrapper"> - {messages} - </div> + <div className="chat-inner-wrapper">{messages}</div> <span className="entry-box"> <input id="data-entry"></input> - <button onClick={() => sendData()}>Send</button> + <button onClick={() => sendData()}> + {chatPage.sendButtonPrompt} + </button> </span> </div> ); diff --git a/src/Chat/MessageContainer.tsx b/src/Chat/MessageContainer.tsx index dd75fc2..345da36 100644 --- a/src/Chat/MessageContainer.tsx +++ b/src/Chat/MessageContainer.tsx @@ -1,24 +1,55 @@ -import React from "react"; +import React, { useContext } from "react"; import { Message, MessageType } from "./types"; +import { LangContext } from "../context"; +import strings from "../Intl/strings.json"; +export const MessageContainer = ({ + type, + fromUserId, + toUserId, + content, + timeMillis, +}: Message): React.ReactElement<Message> => { + const dateTime: Date = new Date(timeMillis); + const lang = useContext(LangContext); + const msgPage = strings[lang].chat; + /* FIXED funny error + * DESCRIPTION + * The line below was + * return (<p>[{dateTime.toLocaleString(Intl.DateTimeFormat().resolvedOptions().timeZone)}]...</p>) + * The line incorrectly generated a value of "UTC" as the parameter to toLocaleString() + * While "UTC" is an accepted string value, in EEST, aka. "Europe/Athens" timezone string is not an acceptable parameter. + * This caused the return statement to fail, and the message fails to render, despite it being correctly committed to the db. + * Funny clown moment 🤡 + */ -export const MessageContainer = ( - { - type, - fromUserId, - toUserId, - content, - timeMillis, - }: Message -): React.ReactElement<Message> => { - const dateTime: Date = new Date(timeMillis); - /* FIXED funny error - * DESCRIPTION - * The line below was - * return (<p>[{dateTime.toLocaleString(Intl.DateTimeFormat().resolvedOptions().timeZone)}]...</p>) - * The line incorrectly generated a value of "UTC" as the parameter to toLocaleString() - * While "UTC" is an accepted string value, in EEST, aka. "Europe/Athens" timezone string is not an acceptable parameter. - * This caused the return statement to fail, and the message fails to render, despite it being correctly committed to the db. - * Funny clown moment 🤡 - */ - return (<p>[{dateTime.toLocaleString()}] Message from {fromUserId}: {content}</p>); + switch (type) { + case MessageType.HELLO as MessageType: + return ( + <p> + [{dateTime.toLocaleString()}]{" "} + {msgPage.joinMessage.replace("$userName", fromUserId)} + </p> + ); + case MessageType.MESSAGE as MessageType: + return ( + <p> + [{dateTime.toLocaleString()}]{" "} + {msgPage.serverMessage + .replace("$userName", fromUserId) + .replace("$content", content)} + </p> + ); + case MessageType.DATA as MessageType: + return <></>; + case MessageType.SYSTEM as MessageType: + return <></>; + default: + console.error("Illegal MessageType reported!"); + return ( + <p> + [{dateTime.toLocaleString()}] **THIS MESSAGE CANNOT BE + CORRECTLY SHOWN BECAUSE THE CLIENT ENCOUNTERED AN ERROR** + </p> + ); + } }; diff --git a/src/Chat/types.tsx b/src/Chat/types.tsx index f7ff7be..cdbbd22 100644 --- a/src/Chat/types.tsx +++ b/src/Chat/types.tsx @@ -1,46 +1,46 @@ -export enum MessageType { - MESSAGE, - SYSTEM, - HELLO, - DATA, +export const enum MessageType { + MESSAGE = "MESSAGE", + SYSTEM = "SYSTEM", + HELLO = "HELLO", + DATA = "DATA", } export enum SystemMessageCode { - REQ, - RES, - ERR, + REQ, + RES, + ERR, } export type HistoryFetchResult = { - count: number, - items: Array<ChatMessage>, -} + count: number; + items: Array<ChatMessage>; +}; export type ErrorResult = { - text: string, -} + text: string; +}; export type TimestampSendRequest = { - ts: number, -} + ts: number; +}; export type SystemMessage = { - code: SystemMessageCode - data: HistoryFetchResult | ErrorResult | TimestampSendRequest -} + code: SystemMessageCode; + data: HistoryFetchResult | ErrorResult | TimestampSendRequest; +}; export type ChatMessage = { - fromUserId: string, - toUserId: string, - content: string, - timeMillis: number -} + fromUserId: string; + toUserId: string; + content: string; + timeMillis: number; +}; export type HelloMessage = { - fromUserId: string, - timeMillis: number, -} -export type DataMessage = { - -} + fromUserId: string; + timeMillis: number; +}; +export type DataMessage = {}; export type Message = { - type: MessageType, - // data: SystemMessage | ChatMessage | HelloMessage - fromUserId: string, - toUserId: string, - content: string, - timeMillis: number -}
\ No newline at end of file + type: MessageType; + // data: SystemMessage | ChatMessage | HelloMessage + fromUserId: string; + toUserId: string; + content: string; + timeMillis: number; +}; +export const acceptedLangs = ["en_US", "zh_TW", "el_GR"] as const; +export type LangType = (typeof acceptedLangs)[number]; diff --git a/src/Intl/strings.json b/src/Intl/strings.json new file mode 100644 index 0000000..7dd6bc1 --- /dev/null +++ b/src/Intl/strings.json @@ -0,0 +1,64 @@ +{ + "variableNames": ["userName", "content"], + "acceptedLangs": ["en_US", "zh_TW", "el_GR"], + "en_US": { + "homepage": { + "userNamePrompt": "Your username: ", + "title": "LAN Chat Server", + "description": "This web application was built for the purposes of an EPQ project.", + "copyrightText": "Copyright 2024 - 2025 Zhongheng Liu @ Byron College", + "switchLang": "Switch Language", + "newLangPrompt": "Input your ISO-639_ISO-3166 language-contry code below - for example: \"en_US\": " + }, + "chat": { + "sendButtonPrompt": "Send", + "serverMessage": "Message from $userName: $content", + "joinMessage": "$userName joined the server" + } + }, + "zh_TW": { + "homepage": { + "userNamePrompt": "您的用戶名: ", + "title": "本地聊天伺服器", + "description": "該網絡伺服器應用程式專爲Edexcel Level 3 EPQ而製作", + "copyrightText": "版權所有 2024 - 2025 Zhongheng Liu @ Byron College", + "switchLang": "切換語言", + "newLangPrompt": "在下方輸入您想使用的語言的ISO-639_ISO-3166組合語言代碼 - 例如:\"en_US\":" + }, + "chat": { + "sendButtonPrompt": "發送", + "serverMessage": "來自 $userName 的訊息:$content", + "joinMessage": "$userName 加入了伺服器!" + } + }, + "el_GR": { + "homepage": { + "userNamePrompt": "το όνομα χρήστη σας: ", + "title": "Διακομιστής τοπικού δικτύου συνομιλίας", + "description": "Αυτή η διαδικτυακή εφαρμογή δημιουργήθηκε για τους σκοπούς ενός έργου EPQ.", + "copyrightText": "Πνευματικά δικαιώματα 2024 - 2025 Zhongheng Liu @ Byron College", + "switchLang": "Αλλαγή γλώσσας", + "newLangPrompt": "Εισαγάγετε τον κωδικό γλώσσας-χώρας ISO-639 ISO-3166 παρακάτω - για παράδειγμα: \"en_US\":" + }, + "chat": { + "sendButtonPrompt": "Στείλετε", + "joinMessage": "$userName έγινε μέλος του διακομιστή", + "serverMessage": "μήνυμα από $userName: $content" + } + }, + "ar_SA": { + "homepage": { + "userNamePrompt": "اسم المستخدم الخاص بك:", + "title": "خادم الدردشة LAN", + "description": "تم إنشاء تطبيق الويب هذا لأغراض مشروع EPQ.", + "copyrightText": "حقوق الطبع والنشر 2024 - 2025 Zhongheng Liu @ كلية بايرون", + "switchLang": "تبديل اللغة", + "newLangPrompt": "أدخل رمز اللغة ISO-639_ISO-3166 أدناه - على سبيل المثال: \"en_US\":" + }, + "chat": { + "sendButtonPrompt": "يرسل", + "joinMessage": "$userName انضم إلى الخادم", + "serverMessage": "رسالة من $userName: $content" + } + } +} diff --git a/src/context.ts b/src/context.ts new file mode 100644 index 0000000..f021d13 --- /dev/null +++ b/src/context.ts @@ -0,0 +1,4 @@ +import { createContext } from "react"; +import { LangType } from "./Chat/types"; + +export const LangContext = createContext<LangType>("en_US");
\ No newline at end of file diff --git a/src/index.tsx b/src/index.tsx index 1dafd93..afe2f98 100644 --- a/src/index.tsx +++ b/src/index.tsx @@ -1,11 +1,14 @@ -import React from 'react'; -import ReactDOM from 'react-dom/client'; -import './index.css'; -import App from './App'; - +import React, { createContext } from "react"; +import ReactDOM from "react-dom/client"; +import "./index.css"; +import App from "./App"; +import Wrapper from "./App"; +const LangContext = createContext("en_US"); const root = ReactDOM.createRoot( - document.getElementById('root') as HTMLElement + document.getElementById("root") as HTMLElement ); root.render( - <App /> -);
\ No newline at end of file + <LangContext.Provider value="en_US"> + <Wrapper /> + </LangContext.Provider> +); diff --git a/tsconfig.json b/tsconfig.json index a273b0c..2eae7b2 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,26 +1,20 @@ { - "compilerOptions": { - "target": "es5", - "lib": [ - "dom", - "dom.iterable", - "esnext" - ], - "allowJs": true, - "skipLibCheck": true, - "esModuleInterop": true, - "allowSyntheticDefaultImports": true, - "strict": true, - "forceConsistentCasingInFileNames": true, - "noFallthroughCasesInSwitch": true, - "module": "esnext", - "moduleResolution": "node", - "resolveJsonModule": true, - "isolatedModules": true, - "noEmit": true, - "jsx": "react-jsx" - }, - "include": [ - "src" - ] + "compilerOptions": { + "target": "es5", + "lib": ["dom", "dom.iterable", "esnext"], + "allowJs": true, + "skipLibCheck": true, + "esModuleInterop": true, + "allowSyntheticDefaultImports": true, + "strict": true, + "forceConsistentCasingInFileNames": true, + "noFallthroughCasesInSwitch": true, + "module": "esnext", + "moduleResolution": "node", + "resolveJsonModule": true, + "isolatedModules": true, + "noEmit": true, + "jsx": "react-jsx" + }, + "include": ["src"] } |