diff --git a/src/components/Footer.js b/src/components/Footer.js index e188e65..418046f 100644 --- a/src/components/Footer.js +++ b/src/components/Footer.js @@ -5,6 +5,7 @@ import { labelColor, DESKTOP_MIN_WIDTH, media } from "../styles" import { appVersion } from "../util/appVersion" import { TimeMachine } from "./TimeMachine" import ContentWrapper from "./ContentWrapper" +import { __, setLanguage } from "./InternationalizedText" export default function Footer() { // @todo #1 Polish the footer @@ -15,6 +16,9 @@ export default function Footer() { padding: "2rem 0.5rem 4rem", marginTop: "1rem", color: labelColor, + a: { + color: "#666", + }, }} > @@ -35,9 +39,17 @@ export default function Footer() { href="https://github.com/codeforthailand/election-live" target="_blank" > - Github + GitHub -
v{appVersion} +
v{appVersion} - View this page in{" "} + {__( + setLanguage("en")}> + English + , + setLanguage("th")}> + Thai + + )} ) diff --git a/src/components/InternationalizedText.js b/src/components/InternationalizedText.js new file mode 100644 index 0000000..865a1bc --- /dev/null +++ b/src/components/InternationalizedText.js @@ -0,0 +1,69 @@ +import React, { useState, useEffect } from "react" +import _ from "lodash" +import { Debug } from "../util/Debug" +import { EventEmitter } from "events" + +const strings = { + ดูผลตามพื้นที่: "View by Area", + ดูผลตามพรรค: "View by Party", + ลองตั้งรัฐบาล: "Election Simulator", + "เกี่ยวกับ ELECT Live": "About ELECT Live!", + + เขตพื้นที่: "Filter by Region", + ตัวเลือกพิเศษ: "Special Filters", +} + +/** @typedef {'th' | 'en'} Language */ +const debug = Debug("elect:l10n") +const languageChangeEmitter = new EventEmitter() +languageChangeEmitter.setMaxListeners(10000) +let currentLanguage = /** @type {Language} */ ("th") +let loaded = false + +export function InternationalizedText({ thai, english }) { + const [language, setLanguage] = useState(currentLanguage) + useEffect(() => { + const listener = () => setLanguage(currentLanguage) + languageChangeEmitter.on("change", listener) + return () => languageChangeEmitter.removeListener("change", listener) + }, []) + useEffect(() => { + if (loaded) return + try { + const targetLanguage = localStorage.ELECT_LANGUAGE === "en" ? "en" : "th" + requestAnimationFrame(() => { + debug("Set language to", targetLanguage, "from localStorage") + if (currentLanguage !== targetLanguage) { + currentLanguage = targetLanguage + languageChangeEmitter.emit("change") + } + }) + } catch (e) { + debug("Cannot load language from localStorage", e) + } finally { + loaded = true + } + }, []) + return {language === "en" ? english : thai} +} + +export function __(thai) { + const english = strings[thai] || thai + return +} + +/** + * @param {Language} lang + */ +export function setLanguage(lang = "th") { + currentLanguage = lang + try { + if (localStorage.ELECT_LANGUAGE !== lang) { + localStorage.ELECT_LANGUAGE = lang + debug("Persisted language", lang, "to localStorage") + } + } catch (e) { + debug("Cannot persist language to", lang, e) + } + languageChangeEmitter.emit("change") +} diff --git a/src/components/LinkToSimulator.js b/src/components/LinkToSimulator.js index 374b652..c7b96ee 100644 --- a/src/components/LinkToSimulator.js +++ b/src/components/LinkToSimulator.js @@ -4,6 +4,7 @@ import { WIDE_NAV_MIN_WIDTH, media } from "../styles" import { nationwidePartyStatsFromSummaryJSON } from "../models/PartyStats" import { useSummaryData } from "../models/LiveDataSubscription" import createSimulatorUrl from "../util/createSimulatorUrl" +import { __ } from "./InternationalizedText" const stylesLink = { color: "#FFFFFF", @@ -50,7 +51,7 @@ export default function LinkToSimulator() { window.open(url, "_blank") }} > - ลองตั้งรัฐบาล + {__("ลองตั้งรัฐบาล")} ) } diff --git a/src/components/NavBar.js b/src/components/NavBar.js index 6114b48..1a6346a 100644 --- a/src/components/NavBar.js +++ b/src/components/NavBar.js @@ -3,17 +3,18 @@ import { Link } from "gatsby" import { WIDE_NAV_MIN_WIDTH, media } from "../styles" import _ from "lodash" import LinkToSimulator from "./LinkToSimulator" +import { __ } from "./InternationalizedText" const menues = [ { name: "by-area", route: "/", - label: "ดูผลตามพื้นที่", + label: __("ดูผลตามพื้นที่"), }, { name: "by-party", route: "/party", - label: "ดูผลตามพรรค", + label: __("ดูผลตามพรรค"), }, // { // name: "overview", @@ -23,7 +24,7 @@ const menues = [ { name: "about", route: "/about", - label: "เกี่ยวกับ ELECT Live", + label: __("เกี่ยวกับ ELECT Live"), }, ] diff --git a/src/components/ZoneFilterPanel.js b/src/components/ZoneFilterPanel.js index 8371c98..ec214d5 100644 --- a/src/components/ZoneFilterPanel.js +++ b/src/components/ZoneFilterPanel.js @@ -10,6 +10,7 @@ import { } from "../models/information" import { trackEvent } from "../util/analytics" import HelpTooltip from "./HelpTooltip" +import { __, InternationalizedText } from "./InternationalizedText" export const ZoneFilterContext = createContext( /** @type {ZoneFilterName} */ ("all") @@ -25,7 +26,7 @@ export function ZoneFilterPanel({ onFilterSelect, autoFocus }) { return (
-
เขตพื้นที่
+
{__("เขตพื้นที่")}
    {renderFilter("all")} {renderFilter("northern")} @@ -34,7 +35,9 @@ export function ZoneFilterPanel({ onFilterSelect, autoFocus }) { {renderFilter("southern")} {renderFilterSearch()}
-
ตัวเลือกพิเศษ
+
+ {__("ตัวเลือกพิเศษ")} +
    {renderFilter("urban")} {renderFilter("rural")} @@ -130,7 +133,14 @@ export function ZoneFilterPanel({ onFilterSelect, autoFocus }) { {!current && } - {label.length > 0 ? label : areaFilters[filterName].name.th} + {label.length > 0 ? ( + label + ) : ( + + )} {areaFilters[filterName].description ? ( checkFilter(currentFilter, zone)) .length - const title = currentFilter.name.th + const title = ( + + ) if (!summaryState.completed) { return (