Add localization for hard-coded English strings

* Replace "Surah X, Ayah Y" with its Arabic equivalenets.

* Add support for Eastern Arabic Numerals . For use with
  the Arabic, and Farsi languages.
This commit is contained in:
0x1eef 2022-12-24 18:19:29 -03:00 committed by Robert
parent 112f0aaec7
commit f1fd6c4e00
6 changed files with 105 additions and 26 deletions

View file

@ -1,19 +1,27 @@
import { Surah, Ayat, Ayah } from "lib/Quran"; import { Surah, Ayat, Ayah, Locale } from "lib/Quran";
import React, { useEffect } from "react"; import React, { useEffect } from "react";
import { numbers, strings } from "lib/i18n";
import classNames from "classnames"; import classNames from "classnames";
interface StreamProps { interface StreamProps {
surah: Surah; surah: Surah;
stream: Ayat; stream: Ayat;
locale: Locale;
} }
export function Stream({ surah, stream }: StreamProps) { export function Stream({ surah, stream, locale }: StreamProps) {
const n = numbers(locale);
const s = strings(locale);
const endOfStream = stream.length === surah.ayat.length; const endOfStream = stream.length === surah.ayat.length;
const ayat = stream.map((ayah: Ayah) => { const ayat = stream.map((ayah: Ayah) => {
return ( return (
<li key={ayah.id} className="ayah fade"> <li key={ayah.id.number} className="ayah fade">
<span className="surah-id ayah-id"> <span className="surah-id ayah-id">
Surah {surah.id}, Ayah {ayah.id} {s("surah")}{" "}
{n(surah.id.localeKey)}
{s("comma")}{" "}
{s("ayah")}{" "}
{n(ayah.id.localeKey)}
</span> </span>
<p>{ayah.text}</p> <p>{ayah.text}</p>
</li> </li>

View file

@ -1,28 +1,30 @@
import React, { useEffect, useState } from 'react'; import React, { useEffect, useState } from 'react';
import { Surah, Ayah, Ayat } from 'lib/Quran'; import { Surah, Ayah, Ayat, Locale } from 'lib/Quran';
import { numberToDecimal } from "lib/i18n";
interface TimerProps { interface TimerProps {
surah: Surah surah: Surah
ayah: Ayah ayah: Ayah
locale: Locale
stream: Ayat stream: Ayat
setStream: (stream: Ayat) => void setStream: (stream: Ayat) => void
} }
export function Timer ({ surah, ayah, stream, setStream }: TimerProps) { export function Timer ({ surah, ayah, stream, setStream, locale }: TimerProps) {
const [ms, setMs] = useState(ayah.readTimeMs); const [ms, setMs] = useState(ayah.readTimeMs);
useEffect(() => setMs(ayah.readTimeMs), [ayah]); useEffect(() => setMs(ayah.readTimeMs), [ayah]);
useEffect(() => { useEffect(() => {
if (stream.length === surah.ayat.length) { if (stream.length === surah.ayat.length) {
return; return;
} else if (ms <= 0) { } else if (ms <= 0) {
setStream([...stream, surah.ayat[ayah.id]]); setStream([...stream, surah.ayat[ayah.id.number]]);
} else { } else {
setTimeout(() => setMs(ms - 100), 100); setTimeout(() => setMs(ms - 100), 100);
} }
}, [ms]); }, [ms]);
return ( return (
<div className='timer'> <div className='timer'>
{(ms / 1000).toFixed(1)} {numberToDecimal(ms / 1000, locale)}
</div> </div>
); );
} }

View file

@ -1,5 +1,4 @@
import { Surah, Ayah, Ayat } from './Quran/Surah'; import { Surah, Ayah, Ayat } from './Quran/Surah';
const Quran = { type Locale = "ar" | "en";
Surah const Quran = { Surah };
}; export { Quran, Surah, Ayah, Ayat, Locale };
export { Quran, Surah, Ayah, Ayat };

View file

@ -9,13 +9,18 @@ interface SurahDetails {
} }
export interface Ayah { export interface Ayah {
id: number; id: IDObject,
text: string; text: string;
readTimeMs: number; readTimeMs: number;
} }
export type Ayat = Ayah[]; export type Ayat = Ayah[];
interface IDObject {
number: number,
localeKey: string[]
}
export class Surah { export class Surah {
#details: SurahDetails; #details: SurahDetails;
ayat: Ayat; ayat: Ayat;
@ -25,7 +30,10 @@ export class Surah {
details, details,
ayat.map(([id, text]) => { ayat.map(([id, text]) => {
return { return {
id, id: {
number: id,
localeKey: String(id).split("")
},
text, text,
readTimeMs: text.split(" ").length * 500, readTimeMs: text.split(" ").length * 500,
}; };
@ -38,8 +46,11 @@ export class Surah {
this.ayat = ayat; this.ayat = ayat;
} }
get id() { get id(): IDObject {
return this.#details.id; return {
number: Number(this.#details.id),
localeKey: this.#details.id.split("")
};
} }
get name() { get name() {

56
src/js/lib/i18n.ts Normal file
View file

@ -0,0 +1,56 @@
import { Locale } from "lib/Quran";
type Digit = "0" | "1" | "2" | "3" | "4" | "5" | "6" | "7" | "8" | "9";
type Strings = "decimal" | "surah" | "ayah" | "comma";
const nTable: Record<Locale, Record<Digit, string>> = {
en: {
0: "0", 1: "1", 2: "2",
3: "3", 4: "4", 5: "5",
6: "6", 7: "7", 8: "8",
9: "9"
},
ar: {
0: "\u{0660}", 1: "\u{0661}", 2: "\u{0662}",
3: "\u{0663}", 4: "\u{0664}", 5: "\u{0665}",
6: "\u{0666}", 7: "\u{0667}", 8: "\u{0668}",
9: "\u{0669}"
}
};
const sTable: Record<Locale, Record<Strings, string>> = {
en: {
decimal: ".",
surah: "Surah",
ayah: "Ayah",
comma: ","
},
ar: {
decimal: "\u{066B}",
surah: "\u{633}\u{648}\u{631}\u{629}",
ayah: "\u{622}\u{64a}\u{629}",
comma: "\u{60c}"
}
};
export function numbers (locale: Locale) {
return function(keys: string | string[]): string {
const table = nTable[locale];
return [...keys].map((k: Digit) => table[k]).join("");
};
}
export function strings (locale: Locale) {
return function(key: Strings): string {
const table = sTable[locale];
return table[key];
};
}
export function numberToDecimal(number: number, locale: Locale): string {
const decimal = number.toFixed(1);
const s = strings(locale);
const n = numbers(locale);
return decimal.split(".")
.map((num: Digit) => n(num))
.join(s("decimal"));
}

View file

@ -1,15 +1,17 @@
import React, { useState, useEffect } from "react"; import React, { useState, useEffect } from "react";
import ReactDOM from "react-dom/client"; import ReactDOM from "react-dom/client";
import useSurah from "hooks/useSurah"; import classNames from "classnames";
import { get as getCookie } from "es-cookie";
import { Timer } from "components/TheQuran/Timer"; import { Timer } from "components/TheQuran/Timer";
import { Stream } from "components/TheQuran/Stream"; import { Stream } from "components/TheQuran/Stream";
import { AboutSurah } from "components/TheQuran/AboutSurah"; import { AboutSurah } from "components/TheQuran/AboutSurah";
import { ThemeSelect } from "components/TheQuran/ThemeSelect"; import { ThemeSelect } from "components/TheQuran/ThemeSelect";
import classNames from "classnames"; import { Locale } from "lib/Quran";
import { get as getCookie } from "es-cookie"; import useSurah from "hooks/useSurah";
interface PageProps { interface PageProps {
locale: string; locale: Locale;
surahId: number; surahId: number;
} }
@ -17,7 +19,7 @@ function TheSurahPage({ locale, surahId }: PageProps) {
const { surahIsLoaded, surah } = useSurah(locale, surahId); const { surahIsLoaded, surah } = useSurah(locale, surahId);
const [stream, setStream] = useState([]); const [stream, setStream] = useState([]);
const [theme, setTheme] = useState(getCookie("theme") || "moon"); const [theme, setTheme] = useState(getCookie("theme") || "moon");
const streamIsLoaded = !(stream.length === 0); const readyToRender = stream.length > 0;
useEffect(() => { useEffect(() => {
if (surahIsLoaded) { if (surahIsLoaded) {
@ -35,21 +37,22 @@ function TheSurahPage({ locale, surahId }: PageProps) {
<a href="/" className="flex-image"> <a href="/" className="flex-image">
<div className="image" /> <div className="image" />
</a> </a>
{streamIsLoaded && ( {readyToRender && (
<div className="flex-row"> <div className="flex-row">
<span /> <span />
<ThemeSelect theme={theme} setTheme={setTheme} /> <ThemeSelect theme={theme} setTheme={setTheme} />
<span /> <span />
</div> </div>
)} )}
{streamIsLoaded && <AboutSurah locale={locale} surah={surah} />} {readyToRender && <AboutSurah locale={locale} surah={surah}/>}
{streamIsLoaded && <Stream surah={surah} stream={stream} />} {readyToRender && <Stream surah={surah} stream={stream} locale={locale}/>}
{streamIsLoaded && stream.length < surah.numberOfAyah && ( {readyToRender && stream.length < surah.numberOfAyah && (
<Timer <Timer
surah={surah} surah={surah}
ayah={surah.ayat[stream.length - 1]} ayah={surah.ayat[stream.length - 1]}
setStream={setStream} setStream={setStream}
stream={stream} stream={stream}
locale={locale}
/> />
)} )}
</div> </div>
@ -57,7 +60,7 @@ function TheSurahPage({ locale, surahId }: PageProps) {
} }
const el = document.querySelector(".surah"); const el = document.querySelector(".surah");
const locale = el.getAttribute("data-locale"); const locale = el.getAttribute("data-locale") as Locale;
const surahId = parseInt(el.getAttribute("data-surah-id")); const surahId = parseInt(el.getAttribute("data-surah-id"));
const root = ReactDOM.createRoot(el); const root = ReactDOM.createRoot(el);
root.render(<TheSurahPage locale={locale} surahId={surahId} />); root.render(<TheSurahPage locale={locale} surahId={surahId} />);