From 914c918eaa60e50fe75ae5e99a1826d38c877efa Mon Sep 17 00:00:00 2001 From: 0x1eef <0x1eef@protonmail.com> Date: Fri, 10 Mar 2023 16:36:52 -0300 Subject: [PATCH 1/5] Add a simpler, more organized i18n.ts implementation --- .eslintrc.js | 1 + Rules | 5 +++ src/html/pages/surah/index.html.erb | 3 ++ src/html/pages/surah/stream.html.erb | 3 ++ src/i18n.json | 16 +++++++++ src/js/components/Stream.tsx | 17 +++++---- src/js/lib/i18n.ts | 52 ++++++++++------------------ src/js/pages/surah/index.tsx | 13 +++---- src/js/pages/surah/stream.tsx | 13 +++---- 9 files changed, 68 insertions(+), 55 deletions(-) create mode 100644 src/i18n.json diff --git a/.eslintrc.js b/.eslintrc.js index 4d8b1c1c8..e245e40b5 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -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, diff --git a/Rules b/Rules index 598e9fc3a..4661e768d 100644 --- a/Rules +++ b/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) } diff --git a/src/html/pages/surah/index.html.erb b/src/html/pages/surah/index.html.erb index e17a76e5f..717957f38 100644 --- a/src/html/pages/surah/index.html.erb +++ b/src/html/pages/surah/index.html.erb @@ -14,5 +14,8 @@
+ diff --git a/src/html/pages/surah/stream.html.erb b/src/html/pages/surah/stream.html.erb index 089371b43..5c4392c20 100644 --- a/src/html/pages/surah/stream.html.erb +++ b/src/html/pages/surah/stream.html.erb @@ -14,5 +14,8 @@
+ diff --git a/src/i18n.json b/src/i18n.json new file mode 100644 index 000000000..fd0076e1e --- /dev/null +++ b/src/i18n.json @@ -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": "،" + } +} diff --git a/src/js/components/Stream.tsx b/src/js/components/Stream.tsx index 59d29e335..d8dcf954b 100644 --- a/src/js/components/Stream.tsx +++ b/src/js/components/Stream.tsx @@ -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 (
  • - {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)}

    {ayah.text}

  • diff --git a/src/js/lib/i18n.ts b/src/js/lib/i18n.ts index b0a6ec01c..936b0001a 100644 --- a/src/js/lib/i18n.ts +++ b/src/js/lib/i18n.ts @@ -1,25 +1,4 @@ -import { Locale } from 'lib/Quran'; -type Strings = 'decimal' | 'surah' | 'ayah' | 'comma' | - 'TheNobleQuran' | 'ChooseRandomChapter'; - -const sTable: Record> = { - 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 = { +export const DelayPerWord: Record = { en: 500, ar: 750 }; -export function numbers (locale: Locale) { - return function(number: number): string { - return Number(number).toLocaleString(locale); +type PhraseMap = { + [key: string]: undefined | string | PhraseMap +}; + +export type TFunction = (locale: Quran.Locale, key: string) => string; + +export function i18n(json: string): TFunction { + const phrases: PhraseMap = 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(' '); diff --git a/src/js/pages/surah/index.tsx b/src/js/pages/surah/index.tsx index 8553dafef..1da0e58cc 100644 --- a/src/js/pages/surah/index.tsx +++ b/src/js/pages/surah/index.tsx @@ -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) { -
    {s('TheNobleQuran')}
    +
    {t(locale, 'TheNobleQuran')}
    ); @@ -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('.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( - + ); })(); diff --git a/src/js/pages/surah/stream.tsx b/src/js/pages/surah/stream.tsx index fe72ed35e..6d29667ca 100644 --- a/src/js/pages/surah/stream.tsx +++ b/src/js/pages/surah/stream.tsx @@ -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([]); const [isPaused, setIsPaused] = useState(paused); const [theme, setTheme] = useState(getCookie('theme') || 'moon'); const [surah] = useState(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) { {readyToRender && ( <> -
    {s('TheNobleQuran')}
    +
    {t(locale, 'TheNobleQuran')}
    @@ -87,6 +86,7 @@ function SurahStream({ node, locale, surahId, slice, paused }: Props) { locale={locale} endOfStream={endOfStream} isPaused={isPaused} + t={t} /> }
    @@ -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('.i18n')!.innerText); ReactDOM .createRoot(root) @@ -125,9 +126,9 @@ function SurahStream({ node, locale, surahId, slice, paused }: Props) { ); })(); From 4e383ec1078543cf9ce33b9b06e4a2953b216da5 Mon Sep 17 00:00:00 2001 From: 0x1eef <0x1eef@protonmail.com> Date: Fri, 10 Mar 2023 18:53:38 -0300 Subject: [PATCH 2/5] numberToDecimal -> formatNumber --- src/js/components/Timer.tsx | 4 ++-- src/js/lib/i18n.ts | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/js/components/Timer.tsx b/src/js/components/Timer.tsx index b216ef38b..bbbb02f0f 100644 --- a/src/js/components/Timer.tsx +++ b/src/js/components/Timer.tsx @@ -1,6 +1,6 @@ import React, { useEffect, useState } from 'react'; import * as Quran from 'lib/Quran'; -import { numberToDecimal } from 'lib/i18n'; +import { formatNumber } from 'lib/i18n'; interface Props { surah: Quran.Surah @@ -25,7 +25,7 @@ export function Timer ({ surah, stream, setStream, locale, isPaused }: Props) { }, [ms, isPaused]); return (
    - {numberToDecimal(ms / 1000, locale)} + {formatNumber(ms / 1000, locale)}
    ); } diff --git a/src/js/lib/i18n.ts b/src/js/lib/i18n.ts index 936b0001a..07597b248 100644 --- a/src/js/lib/i18n.ts +++ b/src/js/lib/i18n.ts @@ -33,7 +33,7 @@ export function i18n(json: string): TFunction { }; } -export function numberToDecimal(number: number, locale: Quran.Locale): string { +export function formatNumber(number: number, locale: Quran.Locale): string { return number.toLocaleString(locale, { maximumFractionDigits: 1 }) .split(/([^\d])/) .join(' '); From ce2a5d68bf1588414627b94b6fb61c31799c8335 Mon Sep 17 00:00:00 2001 From: 0x1eef <0x1eef@protonmail.com> Date: Sat, 11 Mar 2023 09:24:30 -0300 Subject: [PATCH 3/5] Fix bug: generate /en/1/index.html id_redirect.rules didn't scope the rule by locale, and because of that /ar/X/index.html was generated but not /en/X/index.html --- rules/pages/surah/id_redirect.rules | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rules/pages/surah/id_redirect.rules b/rules/pages/surah/id_redirect.rules index 1c96fc6c8..e82827144 100644 --- a/rules/pages/surah/id_redirect.rules +++ b/rules/pages/surah/id_redirect.rules @@ -6,7 +6,7 @@ 1.upto(114) do |id| locales.each do |locale| - compile "/html/pages/surah/id_redirect.html.erb", rep: "redirect_id/#{id}" do + compile "/html/pages/surah/id_redirect.html.erb", rep: "redirect_id/#{locale}/#{id}" do filter(:erb) write("/#{locale}/#{id}/index.html") end From 3bbe8cf1655e2a78094a1dd7f11ba9e4dd3af291 Mon Sep 17 00:00:00 2001 From: 0x1eef <0x1eef@protonmail.com> Date: Sat, 11 Mar 2023 09:27:31 -0300 Subject: [PATCH 4/5] Inline surahs.json, slugs.json, and i18n.json --- Rules | 14 +++++++++++++- lib/helper.rb | 9 +++++++++ rules/pages/surah/index.rules | 5 ----- src/html/pages/surah/id_redirect.html.erb | 4 +--- src/html/pages/surah/index.html.erb | 5 ++--- src/html/pages/surah/stream.html.erb | 4 +--- src/js/pages/surah/id_redirect.ts | 2 +- src/js/pages/surah/index.tsx | 4 ++-- src/js/pages/surah/stream.tsx | 2 +- 9 files changed, 30 insertions(+), 19 deletions(-) create mode 100644 lib/helper.rb diff --git a/Rules b/Rules index 4661e768d..14f663c4b 100644 --- a/Rules +++ b/Rules @@ -36,9 +36,21 @@ require_rules "rules/pages/surah/index", {locales:} require_rules "rules/pages/surah/redirect" require_rules "rules/pages/surah/id_redirect", {locales:} +## +# Inline JSON rules compile "/i18n.json" do filter(:minify_json) - write("/i18n.json") + write(nil) +end + +compile "/surahs.json" do + filter(:minify_json) + write(nil) +end + +compile "/slugs.json" do + filter(:minify_json) + write(nil) end ## diff --git a/lib/helper.rb b/lib/helper.rb new file mode 100644 index 000000000..f88faf81a --- /dev/null +++ b/lib/helper.rb @@ -0,0 +1,9 @@ +module Helper + def inline_json(path) + className = File.basename(path, File.extname(path)) + "" + end +end +use_helper Helper diff --git a/rules/pages/surah/index.rules b/rules/pages/surah/index.rules index 61c5fe15e..b92486d90 100644 --- a/rules/pages/surah/index.rules +++ b/rules/pages/surah/index.rules @@ -30,8 +30,3 @@ compile "/css/pages/surah/index.scss" do filter :rainpress write("/css/pages/surah/index.css") end - -compile "/surahs.json" do - filter(:minify_json) - write "/surahs.json" -end diff --git a/src/html/pages/surah/id_redirect.html.erb b/src/html/pages/surah/id_redirect.html.erb index 7c1772537..f15519778 100644 --- a/src/html/pages/surah/id_redirect.html.erb +++ b/src/html/pages/surah/id_redirect.html.erb @@ -4,9 +4,7 @@ - + <%= inline_json('/slugs.json') %> diff --git a/src/html/pages/surah/index.html.erb b/src/html/pages/surah/index.html.erb index 717957f38..0e804c27e 100644 --- a/src/html/pages/surah/index.html.erb +++ b/src/html/pages/surah/index.html.erb @@ -13,9 +13,8 @@
    + <%= inline_json("/i18n.json") %> + <%= inline_json("/surahs.json") %> - diff --git a/src/html/pages/surah/stream.html.erb b/src/html/pages/surah/stream.html.erb index 5c4392c20..4cce04847 100644 --- a/src/html/pages/surah/stream.html.erb +++ b/src/html/pages/surah/stream.html.erb @@ -13,9 +13,7 @@
    + <%= inline_json("/i18n.json") %> - diff --git a/src/js/pages/surah/id_redirect.ts b/src/js/pages/surah/id_redirect.ts index a829b7365..6917aba94 100644 --- a/src/js/pages/surah/id_redirect.ts +++ b/src/js/pages/surah/id_redirect.ts @@ -3,7 +3,7 @@ .split('/') .filter(function (s) { return s.length; }) .slice(-2); - const el: HTMLElement = document.querySelector('.surah-id-to-slug')!; + const el: HTMLElement = document.querySelector('.json.slugs')!; const slugs = JSON.parse(el.innerText); const path = ['', locale, slugs[surahId]].join('/'); location.replace([path, location.search].join('')); diff --git a/src/js/pages/surah/index.tsx b/src/js/pages/surah/index.tsx index 1da0e58cc..20a31c766 100644 --- a/src/js/pages/surah/index.tsx +++ b/src/js/pages/surah/index.tsx @@ -62,8 +62,8 @@ function SurahIndex({ locale, surahs, t }: Props) { (function() { 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('.i18n')!.innerText); + const script: HTMLScriptElement = document.querySelector('.json.surahs')!; + const t = i18n(document.querySelector('.json.i18n')!.innerText); const surahs: Quran.Surah[] = JSON.parse(script.innerText) .map((el: Quran.JSON.Surah) => { return Quran.Surah.fromJSON(locale, el); diff --git a/src/js/pages/surah/stream.tsx b/src/js/pages/surah/stream.tsx index 6d29667ca..6e5d48cae 100644 --- a/src/js/pages/surah/stream.tsx +++ b/src/js/pages/surah/stream.tsx @@ -118,7 +118,7 @@ function SurahStream({ node, locale, slice, paused, t }: 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('.i18n')!.innerText); + const t = i18n(document.querySelector('.json.i18n')!.innerText); ReactDOM .createRoot(root) From 145d5a4399cf820878c84c94202d5d4a5927257f Mon Sep 17 00:00:00 2001 From: 0x1eef <0x1eef@protonmail.com> Date: Sat, 11 Mar 2023 09:31:37 -0300 Subject: [PATCH 5/5] Run rubocop -A --- Gemfile | 1 + Rules | 1 + lib/helper.rb | 6 ++++-- 3 files changed, 6 insertions(+), 2 deletions(-) diff --git a/Gemfile b/Gemfile index 5513edc44..55436ce79 100644 --- a/Gemfile +++ b/Gemfile @@ -1,4 +1,5 @@ # frozen_string_literal: true + source "https://rubygems.org" ## diff --git a/Rules b/Rules index 14f663c4b..e3de35ca9 100644 --- a/Rules +++ b/Rules @@ -1,5 +1,6 @@ #!/usr/bin/env ruby # frozen_string_literal: true + require "ryo" require "nanoc-gunzip" require "nanoc-webpack" diff --git a/lib/helper.rb b/lib/helper.rb index f88faf81a..142bdc6bd 100644 --- a/lib/helper.rb +++ b/lib/helper.rb @@ -1,7 +1,9 @@ +# frozen_string_literal: true + module Helper def inline_json(path) - className = File.basename(path, File.extname(path)) - "" end