From 46339d93cd3d3739ee09e5ac522d8a570b62732f Mon Sep 17 00:00:00 2001 From: rioarray Date: Sat, 22 Mar 2025 20:27:51 +0700 Subject: [PATCH 1/7] added translation feature --- bun.lockb | Bin 113665 -> 118940 bytes package.json | 3 ++ src/app.tsx | 52 ++++++++++++++++++++++++------ src/components/station-item.tsx | 34 +++++++++++++------ src/libs/i18n/config.ts | 15 +++++++++ src/libs/i18n/en/translation.json | 12 +++++++ src/libs/i18n/types.ts | 1 + src/utils/time.ts | 14 +++----- 8 files changed, 103 insertions(+), 28 deletions(-) create mode 100644 src/libs/i18n/config.ts create mode 100644 src/libs/i18n/en/translation.json create mode 100644 src/libs/i18n/types.ts diff --git a/bun.lockb b/bun.lockb index 247b70588d23a24c23d5aaed0e287b10794a8f51..51fec497db8c76b55cf53b1dfd82436263bfcf4a 100755 GIT binary patch delta 22662 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 delta 19659 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 diff --git a/package.json b/package.json index d8ab0e3..5fb3c57 100644 --- a/package.json +++ b/package.json @@ -20,13 +20,16 @@ "@fontsource/geist-mono": "^5.1.0", "@fontsource/geist-sans": "^5.1.0", "@radix-ui/react-accordion": "^1.2.1", + "@radix-ui/react-switch": "^1.1.3", "@untitled-ui/icons-react": "^0.1.3", "@vitejs/plugin-react": "^4.3.3", "clsx": "^2.1.1", "date-fns": "^4.1.0", + "i18next": "^24.2.3", "motion": "^11.11.17", "openapi-fetch": "^0.13.0", "react-dom": "^18.3.1", + "react-i18next": "^15.4.1", "swr": "^2.2.5", "swr-openapi": "^5.1.0", "tailwind-merge": "^2.5.5", diff --git a/src/app.tsx b/src/app.tsx index f9b5814..07f819f 100644 --- a/src/app.tsx +++ b/src/app.tsx @@ -1,6 +1,10 @@ +import { useTranslation } from "react-i18next"; +import * as Switch from "@radix-ui/react-switch"; import { Plus, SearchMd, X } from "@untitled-ui/icons-react"; import { AnimatePresence, motion, MotionConfig } from "motion/react"; -import React from "react"; +import { useState } from "react"; +import { setDefaultOptions } from "date-fns"; +import { id, enUS } from "date-fns/locale"; import { StationItem } from "./components/station-item"; import { useMeasure } from "./hooks/use-measure"; import { usePersistedState } from "./hooks/use-persisted-state"; @@ -8,12 +12,15 @@ import { useStations } from "./hooks/use-stations"; import { cn, createKey } from "./utils"; import Footer from "./components/footer"; +import "./libs/i18n/config"; + export function App() { - const [state, setState] = React.useState<"VIEW" | "SEARCH" | "ADD">("VIEW"); + const [state, setState] = useState<"VIEW" | "SEARCH" | "ADD">("VIEW"); + const [isSwitchActive, setIsSwitchActive] = useState(false); const [ref, height] = useMeasure(); - const [addSearch, setAddSearch] = React.useState(""); - const [viewSearch, setViewSearch] = React.useState(""); + const [addSearch, setAddSearch] = useState(""); + const [viewSearch, setViewSearch] = useState(""); const [savedStations, setSavedStations] = usePersistedState< { @@ -27,6 +34,15 @@ export function App() { ]); const { data: stations } = useStations(); + const { t, i18n } = useTranslation(); + + const onSwitchLanguange = (val: boolean) => { + setIsSwitchActive(val); + i18n.changeLanguage(val ? "en" : "id"); + }; + + // added global locale format for date-fns + setDefaultOptions({ locale: isSwitchActive ? enUS : id }); /* useOnClickOutside(ref, () => { if (state !== "ADD") { @@ -75,7 +91,7 @@ export function App() { setViewSearch(e.target.value)} className="text-md w-full bg-transparent focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-transparent" @@ -181,6 +197,22 @@ export function App() { )} /> +
+ + + + + +
{state === "ADD" ? ( setAddSearch(e.target.value)} value={addSearch} @@ -275,7 +307,9 @@ export function App() { return (
- Stasiun {addSearch} tidak dapat ditemukan + {t("Stasiun {{addSearch}} tidak dapat ditemukan", { + addSearch, + })}
); @@ -370,10 +404,10 @@ export function App() { return (

- Stasiun belum ditambahkan + {t("Stasiun belum ditambahkan")}

- Tambahkan stasiun dengan menekan tombol + + {t("Tambahkan stasiun dengan menekan tombol")} +
); diff --git a/src/components/station-item.tsx b/src/components/station-item.tsx index f594026..2bbe718 100644 --- a/src/components/station-item.tsx +++ b/src/components/station-item.tsx @@ -1,10 +1,12 @@ import { Loading01 } from "@untitled-ui/icons-react"; import React from "react"; +import { useTranslation } from "react-i18next"; import { Accordion } from ".//ui"; import { useSchedule } from "../hooks/use-schedule"; import { useStation } from "../hooks/use-station"; import { components } from "../schema"; import { cn, formatDateToTime, formatRelativeToNow } from "../utils"; +import { Language } from "../libs/i18n/types"; export type GroupedSchedule = Record< string, @@ -21,17 +23,21 @@ const ScheduleLine = ({ groupedSchedule: GroupedSchedule; }) => { const hook = useStation(destKey); + const { + t, + i18n: { language }, + } = useTranslation(); const station = hook.data?.data; return (

- {groupedSchedule[lineKey]?.[destKey]?.[0]?.line} + {t(groupedSchedule[lineKey]?.[destKey]?.[0]?.line)}

-

Arah menuju

-

Berangkat pukul

+

{t("Arah menuju")}

+

{t("Berangkat pukul")}

@@ -53,6 +59,7 @@ const ScheduleLine = ({

{formatDateToTime( groupedSchedule[lineKey]?.[destKey]?.[0]?.departs_at, + language as Language, )}

@@ -73,13 +80,13 @@ const ScheduleLine = ({ return moreSchedules.length > 0 ? ( moreSchedules.length < 4 ? (
-

Jam berikutnya

+

{t("Jam berikutnya")}

{moreSchedules.map((train) => (

- {formatDateToTime(train.departs_at)} + {formatDateToTime(train.departs_at, language as Language)}

{formatRelativeToNow(train.departs_at)} @@ -98,13 +105,16 @@ const ScheduleLine = ({

-

Jam berikutnya

+

{t("Jam berikutnya")}

{moreSchedules.map((train) => (

- {formatDateToTime(train.departs_at)} + {formatDateToTime( + train.departs_at, + language as Language, + )}

{formatRelativeToNow(train.departs_at)} @@ -121,7 +131,10 @@ const ScheduleLine = ({ .map((train) => (

- {formatDateToTime(train.departs_at)} + {formatDateToTime( + train.departs_at, + language as Language, + )}

{formatRelativeToNow(train.departs_at)} @@ -142,6 +155,7 @@ const ScheduleLine = ({ export const StationItem = ({ stationId }: { stationId: string }) => { const { data: schedules, isLoading, isValidating } = useSchedule(stationId); + const { t } = useTranslation(); const groupedSchedule = React.useMemo(() => { const data = schedules?.data; @@ -182,7 +196,7 @@ export const StationItem = ({ stationId }: { stationId: string }) => {

-

Stasiun

+

{t("Stasiun")}

{station ? (

@@ -222,7 +236,7 @@ export const StationItem = ({ stationId }: { stationId: string }) => {

) : isEmpty ? (

- Jadwal kereta api tidak tersedia. Cek lagi pada esok hari. + {t("Jadwal kereta api tidak tersedia. Cek lagi pada esok hari")}.

) : groupedSchedule ? ( Object.keys(groupedSchedule).map((lineKey, id, arr) => ( diff --git a/src/libs/i18n/config.ts b/src/libs/i18n/config.ts new file mode 100644 index 0000000..9036125 --- /dev/null +++ b/src/libs/i18n/config.ts @@ -0,0 +1,15 @@ +import i18n from "i18next"; +import { initReactI18next } from "react-i18next"; +import en from "./en/translation.json"; + +i18n.use(initReactI18next).init({ + lng: "cimode", + interpolation: { + escapeValue: false, + }, + resources: { + en: { translation: en }, + }, +}); + +export default i18n; diff --git a/src/libs/i18n/en/translation.json b/src/libs/i18n/en/translation.json new file mode 100644 index 0000000..ecf4c4f --- /dev/null +++ b/src/libs/i18n/en/translation.json @@ -0,0 +1,12 @@ +{ + "Arah menuju": "Directions to", + "Berangkat pukul": "Depart at", + "Cari stasiun": "Search station", + "Jadwal kereta api tidak tersedia. Cek lagi pada esok hari": "Train schedule is not available. Please check again tomorrow", + "Jam berikutnya": "Next hour", + "Stasiun": "Station", + "Stasiun belum ditambahkan": "Station not added yet", + "Stasiun {{addSearch}} tidak dapat ditemukan": "Station {{addSearch}} cannot be found", + "Tambahkan stasiun dengan menekan tombol": "Add a station by pressing the button", + "DINAS RANGKAIAN KRL (TIDAK ANGKUT PENUMPANG)": "KRL TRAIN SERVICE (NOT TRANSPORTING PASSENGERS)" +} \ No newline at end of file diff --git a/src/libs/i18n/types.ts b/src/libs/i18n/types.ts new file mode 100644 index 0000000..6057bcb --- /dev/null +++ b/src/libs/i18n/types.ts @@ -0,0 +1 @@ +export type Language = "id" | "en"; diff --git a/src/utils/time.ts b/src/utils/time.ts index ac31f50..3751337 100644 --- a/src/utils/time.ts +++ b/src/utils/time.ts @@ -1,5 +1,5 @@ -import { formatDistanceToNow } from "date-fns"; -import { id } from "date-fns/locale"; +import { format, formatDistanceToNow } from "date-fns"; +import { Language } from "../libs/i18n/types"; export const formatRelativeToNow = (time: string) => { const baseDate = new Date(time); @@ -9,14 +9,10 @@ export const formatRelativeToNow = (time: string) => { baseDate.getMinutes(), ); - return formatDistanceToNow(todayDate, { - locale: id, - }); + return formatDistanceToNow(todayDate); }; -export const formatDateToTime = (baseDate: string) => { +export const formatDateToTime = (baseDate: string, lang: Language = "id") => { const date = new Date(baseDate); - const hours = date.getHours().toString().padStart(2, "0"); - const minutes = date.getMinutes().toString().padStart(2, "0"); - return `${hours}:${minutes}`; + return format(date, lang === "id" ? "HH:mm" : "hh:mmaaa"); }; From d9612242c58e9e5d87006d2eb18bcec7fe9c529b Mon Sep 17 00:00:00 2001 From: rioarray Date: Wed, 2 Apr 2025 19:27:29 +0700 Subject: [PATCH 2/7] change default lng translation into id --- src/libs/i18n/config.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libs/i18n/config.ts b/src/libs/i18n/config.ts index 9036125..2b12ef5 100644 --- a/src/libs/i18n/config.ts +++ b/src/libs/i18n/config.ts @@ -3,7 +3,7 @@ import { initReactI18next } from "react-i18next"; import en from "./en/translation.json"; i18n.use(initReactI18next).init({ - lng: "cimode", + lng: "id", interpolation: { escapeValue: false, }, From b6e1e020a58ae1cfdab279e49491291f0252b809 Mon Sep 17 00:00:00 2001 From: Abiel Zulio M Date: Wed, 2 Apr 2025 21:10:12 +0700 Subject: [PATCH 3/7] feat: implement translation for footer text and update translation file with new Indonesian text --- src/components/footer.tsx | 226 +++++++++++++++--------------- src/libs/i18n/en/translation.json | 23 +-- 2 files changed, 128 insertions(+), 121 deletions(-) diff --git a/src/components/footer.tsx b/src/components/footer.tsx index 3ba5a9d..b37647d 100644 --- a/src/components/footer.tsx +++ b/src/components/footer.tsx @@ -1,120 +1,126 @@ import { Mail01 } from "@untitled-ui/icons-react"; +import { useTranslation } from "react-i18next"; -const Footer = () => ( -
-

- Made as an act of belief that public transportation data should be - publicly accessible -

+const Footer = () => { + const { t } = useTranslation(); - - -
-); + ); +}; export default Footer; diff --git a/src/libs/i18n/en/translation.json b/src/libs/i18n/en/translation.json index ecf4c4f..89c426f 100644 --- a/src/libs/i18n/en/translation.json +++ b/src/libs/i18n/en/translation.json @@ -1,12 +1,13 @@ { - "Arah menuju": "Directions to", - "Berangkat pukul": "Depart at", - "Cari stasiun": "Search station", - "Jadwal kereta api tidak tersedia. Cek lagi pada esok hari": "Train schedule is not available. Please check again tomorrow", - "Jam berikutnya": "Next hour", - "Stasiun": "Station", - "Stasiun belum ditambahkan": "Station not added yet", - "Stasiun {{addSearch}} tidak dapat ditemukan": "Station {{addSearch}} cannot be found", - "Tambahkan stasiun dengan menekan tombol": "Add a station by pressing the button", - "DINAS RANGKAIAN KRL (TIDAK ANGKUT PENUMPANG)": "KRL TRAIN SERVICE (NOT TRANSPORTING PASSENGERS)" -} \ No newline at end of file + "Arah menuju": "Directions to", + "Berangkat pukul": "Depart at", + "Cari stasiun": "Search station", + "Jadwal kereta api tidak tersedia. Cek lagi pada esok hari": "Train schedule is not available. Please check again tomorrow", + "Jam berikutnya": "Next hour", + "Stasiun": "Station", + "Stasiun belum ditambahkan": "Station not added yet", + "Stasiun {{addSearch}} tidak dapat ditemukan": "Station {{addSearch}} cannot be found", + "Tambahkan stasiun dengan menekan tombol": "Add a station by pressing the button", + "DINAS RANGKAIAN KRL (TIDAK ANGKUT PENUMPANG)": "KRL TRAIN SERVICE (NOT TRANSPORTING PASSENGERS)", + "Dibuat sebagai bentuk keyakinan bahwa data transportasi umum seharusnya dapat diakses oleh semua orang.": "Made as an act of belief that public transportation data should be publicly accessible." +} From d2f386b3f85934bf560af15d7cdf69ab99b88be3 Mon Sep 17 00:00:00 2001 From: Abiel Zulio M Date: Thu, 3 Apr 2025 09:51:39 +0700 Subject: [PATCH 4/7] feat: refactor language handling and integrate language switcher in footer --- src/app.tsx | 35 ++++------------------------------- src/components/footer.tsx | 31 +++++++++++++++++++++++++++++-- src/hooks/use-language.ts | 25 +++++++++++++++++++++++++ 3 files changed, 58 insertions(+), 33 deletions(-) create mode 100644 src/hooks/use-language.ts diff --git a/src/app.tsx b/src/app.tsx index 07f819f..656cd13 100644 --- a/src/app.tsx +++ b/src/app.tsx @@ -1,22 +1,19 @@ -import { useTranslation } from "react-i18next"; -import * as Switch from "@radix-ui/react-switch"; import { Plus, SearchMd, X } from "@untitled-ui/icons-react"; import { AnimatePresence, motion, MotionConfig } from "motion/react"; import { useState } from "react"; -import { setDefaultOptions } from "date-fns"; -import { id, enUS } from "date-fns/locale"; +import Footer from "./components/footer"; import { StationItem } from "./components/station-item"; +import { useLanguage } from "./hooks/use-language"; import { useMeasure } from "./hooks/use-measure"; import { usePersistedState } from "./hooks/use-persisted-state"; import { useStations } from "./hooks/use-stations"; import { cn, createKey } from "./utils"; -import Footer from "./components/footer"; import "./libs/i18n/config"; export function App() { const [state, setState] = useState<"VIEW" | "SEARCH" | "ADD">("VIEW"); - const [isSwitchActive, setIsSwitchActive] = useState(false); + const [ref, height] = useMeasure(); const [addSearch, setAddSearch] = useState(""); @@ -34,15 +31,7 @@ export function App() { ]); const { data: stations } = useStations(); - const { t, i18n } = useTranslation(); - - const onSwitchLanguange = (val: boolean) => { - setIsSwitchActive(val); - i18n.changeLanguage(val ? "en" : "id"); - }; - - // added global locale format for date-fns - setDefaultOptions({ locale: isSwitchActive ? enUS : id }); + const { t } = useLanguage(); /* useOnClickOutside(ref, () => { if (state !== "ADD") { @@ -197,22 +186,6 @@ export function App() { )} /> -
- - - - - -
{state === "ADD" ? ( { - const { t } = useTranslation(); + const { t, language, switchLanguage } = useLanguage(); return (
@@ -12,6 +13,32 @@ const Footer = () => { )}

+
+ +

+ +
+
void; + } = {}, +) => { + const [language, setLanguage] = React.useState("id"); + const { t, i18n } = useTranslation(); + + const switchLanguage = (val: Language) => { + setLanguage(val); + i18n.changeLanguage(val); + // Sync date-fns locale with language + setDefaultOptions({ locale: val === "en" ? enUS : id }); + props.onSwitchLanguage?.(val); + }; + + return { language, switchLanguage, t }; +}; From 25a5984f092feeb91747bc4ee241598e51c82666 Mon Sep 17 00:00:00 2001 From: Abiel Zulio M Date: Thu, 3 Apr 2025 09:52:22 +0700 Subject: [PATCH 5/7] chore: remove unused @radix-ui/react-switch dependency from package.json --- bun.lockb | Bin 118940 -> 115676 bytes package.json | 1 - 2 files changed, 1 deletion(-) diff --git a/bun.lockb b/bun.lockb index 51fec497db8c76b55cf53b1dfd82436263bfcf4a..ce14b239547e68c2b13e9a0a60c26cc1d7fcf2b5 100755 GIT binary patch delta 17838 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 delta 19015 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 diff --git a/package.json b/package.json index 5fb3c57..73e1e97 100644 --- a/package.json +++ b/package.json @@ -20,7 +20,6 @@ "@fontsource/geist-mono": "^5.1.0", "@fontsource/geist-sans": "^5.1.0", "@radix-ui/react-accordion": "^1.2.1", - "@radix-ui/react-switch": "^1.1.3", "@untitled-ui/icons-react": "^0.1.3", "@vitejs/plugin-react": "^4.3.3", "clsx": "^2.1.1", From 9042e76f637116472834151876b6fa5abdbf97ec Mon Sep 17 00:00:00 2001 From: Abiel Zulio M Date: Thu, 3 Apr 2025 09:53:33 +0700 Subject: [PATCH 6/7] refactor: replace useState with React.useState for consistency --- src/app.tsx | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/app.tsx b/src/app.tsx index 656cd13..5843e86 100644 --- a/src/app.tsx +++ b/src/app.tsx @@ -1,6 +1,6 @@ import { Plus, SearchMd, X } from "@untitled-ui/icons-react"; import { AnimatePresence, motion, MotionConfig } from "motion/react"; -import { useState } from "react"; +import React from "react"; import Footer from "./components/footer"; import { StationItem } from "./components/station-item"; import { useLanguage } from "./hooks/use-language"; @@ -12,12 +12,12 @@ import { cn, createKey } from "./utils"; import "./libs/i18n/config"; export function App() { - const [state, setState] = useState<"VIEW" | "SEARCH" | "ADD">("VIEW"); + const [state, setState] = React.useState<"VIEW" | "SEARCH" | "ADD">("VIEW"); const [ref, height] = useMeasure(); - const [addSearch, setAddSearch] = useState(""); - const [viewSearch, setViewSearch] = useState(""); + const [addSearch, setAddSearch] = React.useState(""); + const [viewSearch, setViewSearch] = React.useState(""); const [savedStations, setSavedStations] = usePersistedState< { From f8e6e7deef848cea23119cb95f6ebf37061c76c0 Mon Sep 17 00:00:00 2001 From: Abiel Zulio M Date: Thu, 3 Apr 2025 09:54:30 +0700 Subject: [PATCH 7/7] chore: move i18n configuration import to use-language hook --- src/app.tsx | 2 -- src/hooks/use-language.ts | 1 + 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/src/app.tsx b/src/app.tsx index 5843e86..df120f4 100644 --- a/src/app.tsx +++ b/src/app.tsx @@ -9,8 +9,6 @@ import { usePersistedState } from "./hooks/use-persisted-state"; import { useStations } from "./hooks/use-stations"; import { cn, createKey } from "./utils"; -import "./libs/i18n/config"; - export function App() { const [state, setState] = React.useState<"VIEW" | "SEARCH" | "ADD">("VIEW"); diff --git a/src/hooks/use-language.ts b/src/hooks/use-language.ts index 1ceb3f1..e1c4ff7 100644 --- a/src/hooks/use-language.ts +++ b/src/hooks/use-language.ts @@ -2,6 +2,7 @@ import { setDefaultOptions } from "date-fns"; import { enUS, id } from "date-fns/locale"; import React from "react"; import { useTranslation } from "react-i18next"; +import "../libs/i18n/config"; export type Language = "en" | "id";