From 38b409dfabde44fc694558776c2cdc1ca3fc5ef1 Mon Sep 17 00:00:00 2001 From: Thai Pangsakulyanont Date: Wed, 27 Mar 2019 11:34:34 +0700 Subject: [PATCH 1/3] Initial l10n --- src/components/Footer.js | 16 +++++++-- src/components/LinkToSimulator.js | 3 +- src/components/LocalizedText.js | 58 +++++++++++++++++++++++++++++++ src/components/NavBar.js | 7 ++-- src/components/ZoneFilterPanel.js | 16 +++++++-- src/pages/index.js | 3 +- 6 files changed, 93 insertions(+), 10 deletions(-) create mode 100644 src/components/LocalizedText.js diff --git a/src/components/Footer.js b/src/components/Footer.js index e188e65..5455508 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 "./LocalizedText" 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/LinkToSimulator.js b/src/components/LinkToSimulator.js index 374b652..88fd1ed 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 "./LocalizedText" const stylesLink = { color: "#FFFFFF", @@ -50,7 +51,7 @@ export default function LinkToSimulator() { window.open(url, "_blank") }} > - ลองตั้งรัฐบาล + {__("ลองตั้งรัฐบาล", "Election Simulator")} ) } diff --git a/src/components/LocalizedText.js b/src/components/LocalizedText.js new file mode 100644 index 0000000..1d61a6d --- /dev/null +++ b/src/components/LocalizedText.js @@ -0,0 +1,58 @@ +import React, { useState, useEffect } from "react" +import _ from "lodash" +import { Debug } from "../util/Debug" +import { EventEmitter } from "events" + +/** @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 LocalizedText({ thai, english = thai }) { + 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, english) { + 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/NavBar.js b/src/components/NavBar.js index 6114b48..2e2163d 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 "./LocalizedText" const menues = [ { name: "by-area", route: "/", - label: "ดูผลตามพื้นที่", + label: __("ดูผลตามพื้นที่", "View by Area"), }, { name: "by-party", route: "/party", - label: "ดูผลตามพรรค", + label: __("ดูผลตามพรรค", "View by Party"), }, // { // name: "overview", @@ -23,7 +24,7 @@ const menues = [ { name: "about", route: "/about", - label: "เกี่ยวกับ ELECT Live", + label: __("เกี่ยวกับ ELECT Live", "About ELECT Live!"), }, ] diff --git a/src/components/ZoneFilterPanel.js b/src/components/ZoneFilterPanel.js index 8371c98..8559b1c 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 { __ } from "./LocalizedText" export const ZoneFilterContext = createContext( /** @type {ZoneFilterName} */ ("all") @@ -25,7 +26,9 @@ export function ZoneFilterPanel({ onFilterSelect, autoFocus }) { return (
-
เขตพื้นที่
+
+ {__("เขตพื้นที่", "Filter by Region")} +
    {renderFilter("all")} {renderFilter("northern")} @@ -34,7 +37,9 @@ export function ZoneFilterPanel({ onFilterSelect, autoFocus }) { {renderFilter("southern")} {renderFilterSearch()}
-
ตัวเลือกพิเศษ
+
+ {__("ตัวเลือกพิเศษ", "Special Filters")} +
    {renderFilter("urban")} {renderFilter("rural")} @@ -130,7 +135,12 @@ export function ZoneFilterPanel({ onFilterSelect, autoFocus }) { {!current && } - {label.length > 0 ? label : areaFilters[filterName].name.th} + {label.length > 0 + ? label + : __( + areaFilters[filterName].name.th, + areaFilters[filterName].name.en + )} {areaFilters[filterName].description ? ( checkFilter(currentFilter, zone)) .length - const title = currentFilter.name.th + const title = __(currentFilter.name.th, currentFilter.name.en) if (!summaryState.completed) { return ( From 7e0ae951a46f2f2841f0c6c582b8b520e4c27c17 Mon Sep 17 00:00:00 2001 From: Thai Pangsakulyanont Date: Wed, 27 Mar 2019 12:03:06 +0700 Subject: [PATCH 2/3] Use a lookup table to get the globalized text --- src/components/LinkToSimulator.js | 2 +- src/components/LocalizedText.js | 15 +++++++++++++-- src/components/NavBar.js | 6 +++--- src/components/ZoneFilterPanel.js | 22 +++++++++++----------- src/pages/index.js | 9 +++++++-- 5 files changed, 35 insertions(+), 19 deletions(-) diff --git a/src/components/LinkToSimulator.js b/src/components/LinkToSimulator.js index 88fd1ed..fe803d0 100644 --- a/src/components/LinkToSimulator.js +++ b/src/components/LinkToSimulator.js @@ -51,7 +51,7 @@ export default function LinkToSimulator() { window.open(url, "_blank") }} > - {__("ลองตั้งรัฐบาล", "Election Simulator")} + {__("ลองตั้งรัฐบาล")} ) } diff --git a/src/components/LocalizedText.js b/src/components/LocalizedText.js index 1d61a6d..7e059dc 100644 --- a/src/components/LocalizedText.js +++ b/src/components/LocalizedText.js @@ -3,6 +3,16 @@ 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() @@ -10,7 +20,7 @@ languageChangeEmitter.setMaxListeners(10000) let currentLanguage = /** @type {Language} */ ("th") let loaded = false -export function LocalizedText({ thai, english = thai }) { +export function LocalizedText({ thai, english }) { const [language, setLanguage] = useState(currentLanguage) useEffect(() => { const listener = () => setLanguage(currentLanguage) @@ -37,7 +47,8 @@ export function LocalizedText({ thai, english = thai }) { return {language === "en" ? english : thai} } -export function __(thai, english) { +export function __(thai) { + const english = strings[thai] || thai return } diff --git a/src/components/NavBar.js b/src/components/NavBar.js index 2e2163d..c4dd28e 100644 --- a/src/components/NavBar.js +++ b/src/components/NavBar.js @@ -9,12 +9,12 @@ const menues = [ { name: "by-area", route: "/", - label: __("ดูผลตามพื้นที่", "View by Area"), + label: __("ดูผลตามพื้นที่"), }, { name: "by-party", route: "/party", - label: __("ดูผลตามพรรค", "View by Party"), + label: __("ดูผลตามพรรค"), }, // { // name: "overview", @@ -24,7 +24,7 @@ const menues = [ { name: "about", route: "/about", - label: __("เกี่ยวกับ ELECT Live", "About ELECT Live!"), + label: __("เกี่ยวกับ ELECT Live"), }, ] diff --git a/src/components/ZoneFilterPanel.js b/src/components/ZoneFilterPanel.js index 8559b1c..ffcd199 100644 --- a/src/components/ZoneFilterPanel.js +++ b/src/components/ZoneFilterPanel.js @@ -10,7 +10,7 @@ import { } from "../models/information" import { trackEvent } from "../util/analytics" import HelpTooltip from "./HelpTooltip" -import { __ } from "./LocalizedText" +import { __, LocalizedText } from "./LocalizedText" export const ZoneFilterContext = createContext( /** @type {ZoneFilterName} */ ("all") @@ -26,9 +26,7 @@ export function ZoneFilterPanel({ onFilterSelect, autoFocus }) { return (
    -
    - {__("เขตพื้นที่", "Filter by Region")} -
    +
    {__("เขตพื้นที่")}
      {renderFilter("all")} {renderFilter("northern")} @@ -38,7 +36,7 @@ export function ZoneFilterPanel({ onFilterSelect, autoFocus }) { {renderFilterSearch()}
    - {__("ตัวเลือกพิเศษ", "Special Filters")} + {__("ตัวเลือกพิเศษ")}
      {renderFilter("urban")} @@ -135,12 +133,14 @@ export function ZoneFilterPanel({ onFilterSelect, autoFocus }) { {!current && } - {label.length > 0 - ? label - : __( - areaFilters[filterName].name.th, - areaFilters[filterName].name.en - )} + {label.length > 0 ? ( + label + ) : ( + + )} {areaFilters[filterName].description ? ( checkFilter(currentFilter, zone)) .length - const title = __(currentFilter.name.th, currentFilter.name.en) + const title = ( + + ) if (!summaryState.completed) { return ( From 8853fa47c3711693daf495a4b90c00fe8c750b92 Mon Sep 17 00:00:00 2001 From: Thai Pangsakulyanont Date: Wed, 27 Mar 2019 12:05:38 +0700 Subject: [PATCH 3/3] =?UTF-8?q?Ain=E2=80=99t=20it=20the=20other=20way=20ar?= =?UTF-8?q?ound=3F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/Footer.js | 2 +- src/components/{LocalizedText.js => InternationalizedText.js} | 4 ++-- src/components/LinkToSimulator.js | 2 +- src/components/NavBar.js | 2 +- src/components/ZoneFilterPanel.js | 4 ++-- src/pages/index.js | 4 ++-- 6 files changed, 9 insertions(+), 9 deletions(-) rename src/components/{LocalizedText.js => InternationalizedText.js} (94%) diff --git a/src/components/Footer.js b/src/components/Footer.js index 5455508..418046f 100644 --- a/src/components/Footer.js +++ b/src/components/Footer.js @@ -5,7 +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 "./LocalizedText" +import { __, setLanguage } from "./InternationalizedText" export default function Footer() { // @todo #1 Polish the footer diff --git a/src/components/LocalizedText.js b/src/components/InternationalizedText.js similarity index 94% rename from src/components/LocalizedText.js rename to src/components/InternationalizedText.js index 7e059dc..865a1bc 100644 --- a/src/components/LocalizedText.js +++ b/src/components/InternationalizedText.js @@ -20,7 +20,7 @@ languageChangeEmitter.setMaxListeners(10000) let currentLanguage = /** @type {Language} */ ("th") let loaded = false -export function LocalizedText({ thai, english }) { +export function InternationalizedText({ thai, english }) { const [language, setLanguage] = useState(currentLanguage) useEffect(() => { const listener = () => setLanguage(currentLanguage) @@ -49,7 +49,7 @@ export function LocalizedText({ thai, english }) { export function __(thai) { const english = strings[thai] || thai - return + return } /** diff --git a/src/components/LinkToSimulator.js b/src/components/LinkToSimulator.js index fe803d0..c7b96ee 100644 --- a/src/components/LinkToSimulator.js +++ b/src/components/LinkToSimulator.js @@ -4,7 +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 "./LocalizedText" +import { __ } from "./InternationalizedText" const stylesLink = { color: "#FFFFFF", diff --git a/src/components/NavBar.js b/src/components/NavBar.js index c4dd28e..1a6346a 100644 --- a/src/components/NavBar.js +++ b/src/components/NavBar.js @@ -3,7 +3,7 @@ import { Link } from "gatsby" import { WIDE_NAV_MIN_WIDTH, media } from "../styles" import _ from "lodash" import LinkToSimulator from "./LinkToSimulator" -import { __ } from "./LocalizedText" +import { __ } from "./InternationalizedText" const menues = [ { diff --git a/src/components/ZoneFilterPanel.js b/src/components/ZoneFilterPanel.js index ffcd199..ec214d5 100644 --- a/src/components/ZoneFilterPanel.js +++ b/src/components/ZoneFilterPanel.js @@ -10,7 +10,7 @@ import { } from "../models/information" import { trackEvent } from "../util/analytics" import HelpTooltip from "./HelpTooltip" -import { __, LocalizedText } from "./LocalizedText" +import { __, InternationalizedText } from "./InternationalizedText" export const ZoneFilterContext = createContext( /** @type {ZoneFilterName} */ ("all") @@ -136,7 +136,7 @@ export function ZoneFilterPanel({ onFilterSelect, autoFocus }) { {label.length > 0 ? ( label ) : ( - diff --git a/src/pages/index.js b/src/pages/index.js index 578b547..2914512 100644 --- a/src/pages/index.js +++ b/src/pages/index.js @@ -22,7 +22,7 @@ import { partyStatsFromSummaryJSON, partyStatsRowTotalSeats, } from "../models/PartyStats" -import { __, LocalizedText } from "../components/LocalizedText" +import { __, InternationalizedText } from "../components/InternationalizedText" export const MobileTabContext = createContext( /** @type {import('../components/ZoneMasterView').MobileTab} */ ("summary") @@ -105,7 +105,7 @@ function SummaryHeaderContainer({ filterName }) { const totalZoneCount = zones.filter(zone => checkFilter(currentFilter, zone)) .length const title = ( -