Add a simpler, more organized i18n.ts implementation
This commit is contained in:
parent
e96795f077
commit
914c918eaa
9 changed files with 68 additions and 55 deletions
|
@ -13,6 +13,7 @@ module.exports = {
|
|||
"@typescript-eslint/prefer-nullish-coalescing": 0,
|
||||
"@typescript-eslint/restrict-template-expressions": 0,
|
||||
"@typescript-eslint/promise-function-async": 0,
|
||||
"@typescript-eslint/consistent-type-definitions": 0,
|
||||
"@typescript-eslint/no-misused-promises": ["error", {"checksConditionals": false}],
|
||||
"@typescript-eslint/no-redeclare": 0,
|
||||
"@typescript-eslint/no-non-null-assertion": 0,
|
||||
|
|
5
Rules
5
Rules
|
@ -36,6 +36,11 @@ require_rules "rules/pages/surah/index", {locales:}
|
|||
require_rules "rules/pages/surah/redirect"
|
||||
require_rules "rules/pages/surah/id_redirect", {locales:}
|
||||
|
||||
compile "/i18n.json" do
|
||||
filter(:minify_json)
|
||||
write("/i18n.json")
|
||||
end
|
||||
|
||||
##
|
||||
# Defaults
|
||||
compile("/**/*") { write(nil) }
|
||||
|
|
|
@ -14,5 +14,8 @@
|
|||
</div>
|
||||
<div class="root" data-locale="<%= locale %>"></div>
|
||||
<script src="/js/pages/surah/index/loader.js"></script>
|
||||
<script class="i18n" type="application/json">
|
||||
<%= items["/i18n.json"].compiled_content %>
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
|
|
@ -14,5 +14,8 @@
|
|||
</div>
|
||||
<div class="root" data-locale="<%= locale %>" data-surah-id="<%= surah_id %>"></div>
|
||||
<script src="/js/pages/surah/stream/loader.js"></script>
|
||||
<script class="i18n" type="application/json">
|
||||
<%= items["/i18n.json"].compiled_content %>
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
|
16
src/i18n.json
Normal file
16
src/i18n.json
Normal file
|
@ -0,0 +1,16 @@
|
|||
{
|
||||
"en": {
|
||||
"TheNobleQuran": "The Noble Quran",
|
||||
"ChooseRandomChapter": "Choose a random chapter",
|
||||
"surah": "Surah",
|
||||
"ayah": "Ayah",
|
||||
"comma": ","
|
||||
},
|
||||
"ar": {
|
||||
"TheNobleQuran": "القرآن الكريم",
|
||||
"ChooseRandomChapter": "اختر سورة عشوائية",
|
||||
"surah": "سورة",
|
||||
"ayah": "آية",
|
||||
"comma": "،"
|
||||
}
|
||||
}
|
|
@ -1,6 +1,6 @@
|
|||
import * as Quran from 'lib/Quran';
|
||||
import React, { useEffect } from 'react';
|
||||
import { numbers, strings } from 'lib/i18n';
|
||||
import { TFunction } from 'lib/i18n';
|
||||
import { Slice } from 'lib/Quran/Slice';
|
||||
import classNames from 'classnames';
|
||||
|
||||
|
@ -11,21 +11,20 @@ interface Props {
|
|||
slice: Slice
|
||||
endOfStream: boolean
|
||||
isPaused: boolean
|
||||
t: TFunction
|
||||
}
|
||||
|
||||
export function Stream({ surah, stream, locale, slice, endOfStream, isPaused }: Props) {
|
||||
const n = numbers(locale);
|
||||
const s = strings(locale);
|
||||
export function Stream({ surah, stream, locale, slice, endOfStream, isPaused, t }: Props) {
|
||||
const className = classNames('body', 'stream', { 'scroll-y': endOfStream || isPaused });
|
||||
const ayat = stream.map((ayah: Quran.Ayah) => {
|
||||
return (
|
||||
<li key={ayah.id} className="ayah fade">
|
||||
<span className="surah-id ayah-id">
|
||||
{s('surah')}{' '}
|
||||
{n(surah.id)}
|
||||
{s('comma')}{' '}
|
||||
{s('ayah')}{' '}
|
||||
{n(ayah.id)}
|
||||
{t(locale, 'surah')}{' '}
|
||||
{surah.id.toLocaleString(locale)}
|
||||
{t(locale, 'comma')}{' '}
|
||||
{t(locale, 'ayah')}{' '}
|
||||
{ayah.id.toLocaleString(locale)}
|
||||
</span>
|
||||
<p>{ayah.text}</p>
|
||||
</li>
|
||||
|
|
|
@ -1,25 +1,4 @@
|
|||
import { Locale } from 'lib/Quran';
|
||||
type Strings = 'decimal' | 'surah' | 'ayah' | 'comma' |
|
||||
'TheNobleQuran' | 'ChooseRandomChapter';
|
||||
|
||||
const sTable: Record<Locale, Record<Strings, string>> = {
|
||||
en: {
|
||||
TheNobleQuran: 'The Noble Quran',
|
||||
ChooseRandomChapter: 'Choose a random chapter',
|
||||
decimal: '.',
|
||||
surah: 'Surah',
|
||||
ayah: 'Ayah',
|
||||
comma: ','
|
||||
},
|
||||
ar: {
|
||||
TheNobleQuran: '\u{627}\u{644}\u{642}\u{631}\u{622}\u{646}\u{20}\u{627}\u{644}\u{643}\u{631}\u{64a}\u{645}',
|
||||
ChooseRandomChapter: '\u{627}\u{62e}\u{62a}\u{631}\u{20}\u{633}\u{648}\u{631}\u{629}\u{20}\u{639}\u{634}\u{648}\u{627}\u{626}\u{64a}\u{629}',
|
||||
decimal: '\u{066B}',
|
||||
surah: '\u{633}\u{648}\u{631}\u{629}',
|
||||
ayah: '\u{622}\u{64a}\u{629}',
|
||||
comma: '\u{60c}'
|
||||
}
|
||||
};
|
||||
import * as Quran from 'lib/Quran';
|
||||
|
||||
/**
|
||||
* The read time baseline - as a number milliseconds -
|
||||
|
@ -31,25 +10,30 @@ export const DelayBaseLine = 2000;
|
|||
* The read time for each word in an Ayah,
|
||||
* relative to the active locale.
|
||||
*/
|
||||
export const DelayPerWord: Record<Locale, number> = {
|
||||
export const DelayPerWord: Record<Quran.Locale, number> = {
|
||||
en: 500,
|
||||
ar: 750
|
||||
};
|
||||
|
||||
export function numbers (locale: Locale) {
|
||||
return function(number: number): string {
|
||||
return Number(number).toLocaleString(locale);
|
||||
type PhraseMap<T> = {
|
||||
[key: string]: undefined | string | PhraseMap<T>
|
||||
};
|
||||
|
||||
export type TFunction = (locale: Quran.Locale, key: string) => string;
|
||||
|
||||
export function i18n(json: string): TFunction {
|
||||
const phrases: PhraseMap<string> = JSON.parse(json);
|
||||
return function (locale: Quran.Locale, key: string) {
|
||||
const path = key.split('.');
|
||||
const phrase = path.reduce(
|
||||
(o, k) => typeof(o) === 'object' ? o[k] : o,
|
||||
phrases[locale]
|
||||
);
|
||||
return typeof phrase === 'string' ? phrase : key;
|
||||
};
|
||||
}
|
||||
|
||||
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 {
|
||||
export function numberToDecimal(number: number, locale: Quran.Locale): string {
|
||||
return number.toLocaleString(locale, { maximumFractionDigits: 1 })
|
||||
.split(/([^\d])/)
|
||||
.join(' ');
|
||||
|
|
|
@ -6,16 +6,16 @@ import * as Quran from 'lib/Quran';
|
|||
import { SelectOption } from 'components/Select';
|
||||
import { ThemeSelect } from 'components/ThemeSelect';
|
||||
import { LanguageSelect } from 'components/LanguageSelect';
|
||||
import { strings } from 'lib/i18n';
|
||||
import { i18n, TFunction } from 'lib/i18n';
|
||||
|
||||
interface Props {
|
||||
locale: Quran.Locale
|
||||
surahs: Quran.Surah[]
|
||||
t: TFunction
|
||||
}
|
||||
|
||||
function SurahIndex({ locale, surahs }: Props) {
|
||||
function SurahIndex({ locale, surahs, t }: Props) {
|
||||
const [theme, setTheme] = useState(getCookie('theme') || 'moon');
|
||||
const s = strings(locale);
|
||||
const onLanguageChange = (o: SelectOption) => {
|
||||
document.location.replace(`/${o.value}/`);
|
||||
};
|
||||
|
@ -29,7 +29,7 @@ function SurahIndex({ locale, surahs }: Props) {
|
|||
<div className="header">
|
||||
<a href={'/' + locale} className="image" />
|
||||
</div>
|
||||
<div className="row title">{s('TheNobleQuran')}</div>
|
||||
<div className="row title">{t(locale, 'TheNobleQuran')}</div>
|
||||
<div className="row dropdown-row">
|
||||
<ThemeSelect theme={theme} setTheme={setTheme} />
|
||||
<LanguageSelect locale={locale} onChange={onLanguageChange} />
|
||||
|
@ -52,7 +52,7 @@ function SurahIndex({ locale, surahs }: Props) {
|
|||
))}
|
||||
</ul>
|
||||
<a href={`/${locale}/random`} className="row surah choose-random">
|
||||
{s('ChooseRandomChapter')}
|
||||
{t(locale, 'ChooseRandomChapter')}
|
||||
</a>
|
||||
</div>
|
||||
);
|
||||
|
@ -63,6 +63,7 @@ function SurahIndex({ locale, surahs }: Props) {
|
|||
const root: HTMLElement = document.querySelector('.root')!;
|
||||
const locale = root.getAttribute('data-locale') as Quran.Locale;
|
||||
const script: HTMLScriptElement = document.querySelector('script[src="/surahs.json"]')!;
|
||||
const t = i18n(document.querySelector<HTMLElement>('.i18n')!.innerText);
|
||||
const surahs: Quran.Surah[] = JSON.parse(script.innerText)
|
||||
.map((el: Quran.JSON.Surah) => {
|
||||
return Quran.Surah.fromJSON(locale, el);
|
||||
|
@ -71,6 +72,6 @@ function SurahIndex({ locale, surahs }: Props) {
|
|||
ReactDOM
|
||||
.createRoot(root)
|
||||
.render(
|
||||
<SurahIndex locale={locale} surahs={surahs} />
|
||||
<SurahIndex locale={locale} surahs={surahs} t={t} />
|
||||
);
|
||||
})();
|
||||
|
|
|
@ -10,23 +10,22 @@ import { LanguageSelect } from 'components/LanguageSelect';
|
|||
import { PlayShape, PauseShape } from 'components/Shape';
|
||||
import * as Quran from 'lib/Quran';
|
||||
import { Slice } from 'lib/Quran/Slice';
|
||||
import { strings } from 'lib/i18n';
|
||||
import { i18n, TFunction } from 'lib/i18n';
|
||||
|
||||
interface Props {
|
||||
node: HTMLScriptElement
|
||||
locale: Quran.Locale
|
||||
surahId: number
|
||||
slice: Slice
|
||||
paused: boolean
|
||||
t: TFunction
|
||||
}
|
||||
|
||||
function SurahStream({ node, locale, surahId, slice, paused }: Props) {
|
||||
function SurahStream({ node, locale, slice, paused, t }: Props) {
|
||||
const [stream, setStream] = useState<Quran.Ayat>([]);
|
||||
const [isPaused, setIsPaused] = useState<boolean>(paused);
|
||||
const [theme, setTheme] = useState(getCookie('theme') || 'moon');
|
||||
const [surah] = useState<Quran.Surah>(Quran.Surah.fromDOMNode(locale, node));
|
||||
const readyToRender = stream.length > 0;
|
||||
const s = strings(locale);
|
||||
const onLanguageChange = (o: SelectOption) => {
|
||||
const locale = o.value;
|
||||
const params = [
|
||||
|
@ -66,7 +65,7 @@ function SurahStream({ node, locale, surahId, slice, paused }: Props) {
|
|||
</div>
|
||||
{readyToRender && (
|
||||
<>
|
||||
<div className="row title">{s('TheNobleQuran')}</div>
|
||||
<div className="row title">{t(locale, 'TheNobleQuran')}</div>
|
||||
<div className="row dropdown-row">
|
||||
<ThemeSelect theme={theme} setTheme={setTheme} />
|
||||
<LanguageSelect locale={locale} onChange={onLanguageChange} />
|
||||
|
@ -87,6 +86,7 @@ function SurahStream({ node, locale, surahId, slice, paused }: Props) {
|
|||
locale={locale}
|
||||
endOfStream={endOfStream}
|
||||
isPaused={isPaused}
|
||||
t={t}
|
||||
/>
|
||||
}
|
||||
<div className="row">
|
||||
|
@ -118,6 +118,7 @@ function SurahStream({ node, locale, surahId, slice, paused }: Props) {
|
|||
const params = new URLSearchParams(location.search);
|
||||
const slice = Slice.fromParam(params.get('ayah'));
|
||||
const paused = toBoolean(params.get('paused'));
|
||||
const t = i18n(document.querySelector<HTMLElement>('.i18n')!.innerText);
|
||||
|
||||
ReactDOM
|
||||
.createRoot(root)
|
||||
|
@ -125,9 +126,9 @@ function SurahStream({ node, locale, surahId, slice, paused }: Props) {
|
|||
<SurahStream
|
||||
node={node}
|
||||
locale={locale}
|
||||
surahId={surahId}
|
||||
slice={slice}
|
||||
paused={paused}
|
||||
t={t}
|
||||
/>
|
||||
);
|
||||
})();
|
||||
|
|
Loading…
Reference in a new issue