aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorZhongheng Liu <z.liu@outlook.com.gr>2024-01-14 22:42:56 +0200
committerZhongheng Liu <z.liu@outlook.com.gr>2024-01-14 22:42:56 +0200
commite85f77877479d27ae55e3b5633c655f1d3b93469 (patch)
tree950b585264e96d1a8fd4a1471ccd2c5744b196ad
parent24eb8e8067dcd0b9702fb02dd50bb600b875b5a1 (diff)
downloadepq-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.tsx55
-rw-r--r--src/Chat/Chat.tsx23
-rw-r--r--src/Chat/MessageContainer.tsx73
-rw-r--r--src/Chat/types.tsx72
-rw-r--r--src/Intl/strings.json64
-rw-r--r--src/context.ts4
-rw-r--r--src/index.tsx19
-rw-r--r--tsconfig.json42
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"]
}