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:
parent
112f0aaec7
commit
f1fd6c4e00
6 changed files with 105 additions and 26 deletions
|
@ -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>
|
||||||
|
|
|
@ -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>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -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 };
|
|
||||||
|
|
|
@ -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
56
src/js/lib/i18n.ts
Normal 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"));
|
||||||
|
}
|
|
@ -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} />);
|
||||||
|
|
Loading…
Reference in a new issue