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 { numbers, strings } from "lib/i18n";
import classNames from "classnames";
interface StreamProps {
surah: Surah;
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 ayat = stream.map((ayah: Ayah) => {
return (
<li key={ayah.id} className="ayah fade">
<li key={ayah.id.number} className="ayah fade">
<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>
<p>{ayah.text}</p>
</li>

View file

@ -1,28 +1,30 @@
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 {
surah: Surah
ayah: Ayah
locale: Locale
stream: Ayat
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);
useEffect(() => setMs(ayah.readTimeMs), [ayah]);
useEffect(() => {
if (stream.length === surah.ayat.length) {
return;
} else if (ms <= 0) {
setStream([...stream, surah.ayat[ayah.id]]);
setStream([...stream, surah.ayat[ayah.id.number]]);
} else {
setTimeout(() => setMs(ms - 100), 100);
}
}, [ms]);
return (
<div className='timer'>
{(ms / 1000).toFixed(1)}
{numberToDecimal(ms / 1000, locale)}
</div>
);
}

View file

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

View file

@ -9,13 +9,18 @@ interface SurahDetails {
}
export interface Ayah {
id: number;
id: IDObject,
text: string;
readTimeMs: number;
}
export type Ayat = Ayah[];
interface IDObject {
number: number,
localeKey: string[]
}
export class Surah {
#details: SurahDetails;
ayat: Ayat;
@ -25,7 +30,10 @@ export class Surah {
details,
ayat.map(([id, text]) => {
return {
id,
id: {
number: id,
localeKey: String(id).split("")
},
text,
readTimeMs: text.split(" ").length * 500,
};
@ -38,8 +46,11 @@ export class Surah {
this.ayat = ayat;
}
get id() {
return this.#details.id;
get id(): IDObject {
return {
number: Number(this.#details.id),
localeKey: this.#details.id.split("")
};
}
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 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 { Stream } from "components/TheQuran/Stream";
import { AboutSurah } from "components/TheQuran/AboutSurah";
import { ThemeSelect } from "components/TheQuran/ThemeSelect";
import classNames from "classnames";
import { get as getCookie } from "es-cookie";
import { Locale } from "lib/Quran";
import useSurah from "hooks/useSurah";
interface PageProps {
locale: string;
locale: Locale;
surahId: number;
}
@ -17,7 +19,7 @@ function TheSurahPage({ locale, surahId }: PageProps) {
const { surahIsLoaded, surah } = useSurah(locale, surahId);
const [stream, setStream] = useState([]);
const [theme, setTheme] = useState(getCookie("theme") || "moon");
const streamIsLoaded = !(stream.length === 0);
const readyToRender = stream.length > 0;
useEffect(() => {
if (surahIsLoaded) {
@ -35,21 +37,22 @@ function TheSurahPage({ locale, surahId }: PageProps) {
<a href="/" className="flex-image">
<div className="image" />
</a>
{streamIsLoaded && (
{readyToRender && (
<div className="flex-row">
<span />
<ThemeSelect theme={theme} setTheme={setTheme} />
<span />
</div>
)}
{streamIsLoaded && <AboutSurah locale={locale} surah={surah} />}
{streamIsLoaded && <Stream surah={surah} stream={stream} />}
{streamIsLoaded && stream.length < surah.numberOfAyah && (
{readyToRender && <AboutSurah locale={locale} surah={surah}/>}
{readyToRender && <Stream surah={surah} stream={stream} locale={locale}/>}
{readyToRender && stream.length < surah.numberOfAyah && (
<Timer
surah={surah}
ayah={surah.ayat[stream.length - 1]}
setStream={setStream}
stream={stream}
locale={locale}
/>
)}
</div>
@ -57,7 +60,7 @@ function TheSurahPage({ locale, surahId }: PageProps) {
}
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 root = ReactDOM.createRoot(el);
root.render(<TheSurahPage locale={locale} surahId={surahId} />);