diff --git a/Gemfile.lock b/Gemfile.lock index 73679ec..01b5cdb 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -110,7 +110,7 @@ GEM rubocop (>= 1.48.1, < 2.0) rubocop-ast (>= 1.30.0, < 2.0) ruby-progressbar (1.13.0) - ryo.rb (0.4.7) + ryo.rb (0.5.1) sass (3.7.4) sass-listen (~> 4.0.0) sass-listen (4.0.0) diff --git a/Rules b/Rules index 855ad90..db14163 100644 --- a/Rules +++ b/Rules @@ -2,23 +2,19 @@ # frozen_string_literal: true require "ryo" +require "ryo/json" require "nanoc-gzip" require "nanoc-webpack" require "nanoc-tidy" +require_relative "nanoc/lib/require_rules" -locales = %w[ar en] -slugs = Ryo.from( - JSON.parse( - File.read(File.join(Dir.getwd, "src", "json", "slugs.json")) - ) -) -i18n = Ryo.from( - JSON.parse( - File.read(File.join(Dir.getwd, "src", "json", "i18n.json")) - ) -) - -buildenv = ENV["buildenv"] || "development" +## +# Common vars +locales = %w[ar en] +json_dir = File.join(Dir.getwd, "src", "json") +name_by_id = Ryo.from_json_file("#{json_dir}/nameById.json") +i18n = Ryo.from_json_file("#{json_dir}/i18n.json") +buildenv = ENV["buildenv"] || "development" Nanoc::Webpack.default_options.merge!( "--config" => "webpack.#{buildenv}.js" ) @@ -26,44 +22,8 @@ Nanoc::Tidy.default_options.merge!( "-upper" => true ) -def require_rules(rules, locals = {}, target = binding) - locals.each { target.local_variable_set(_1, _2) } - path = File.join(Dir.getwd, rules) - target.eval( - if File.readable?(path) - File.read(path) - elsif File.readable?("#{path}.rb") - File.read("#{path}.rb") - elsif File.readable?("#{path}.rules") - File.read("#{path}.rules") - else - raise LoadError, "#{path} is not readable" - end - ) -end - ## -# Inline CSS / JSON rules -compile "/i18n.json" do - filter(:minify_json) - write(nil) -end - -compile "/surahs.json" do - filter(:minify_json) - write(nil) -end - -compile "/slugs.json" do - filter(:minify_json) - write(nil) -end - -compile "/recitations.json" do - filter(:minify_json) - write(nil) -end - +# See packages/typescript/postman compile "/css/postman.scss" do filter :sass, syntax: :scss, style: :compact filter :rainpress @@ -73,7 +33,7 @@ end ## # /sitemap.xml compile "/sitemap.xml.erb" do - filter(:erb, locals: {locales:, slugs:}) + filter(:erb, locals: {locales:, name_by_id:}) filter(:strip) write("/sitemap.xml") end @@ -88,8 +48,8 @@ end # Require rules require_rules "nanoc/rules/assets" require_rules "nanoc/rules/redirect" -require_rules "nanoc/rules/random", {locales:, i18n:, slugs:} -require_rules "nanoc/rules/stream", {locales:, i18n:, slugs:} +require_rules "nanoc/rules/random", {locales:} +require_rules "nanoc/rules/stream", {locales:, i18n:, name_by_id:} require_rules "nanoc/rules/index", {locales:, i18n:} ## diff --git a/nanoc/lib/require_rules.rb b/nanoc/lib/require_rules.rb new file mode 100644 index 0000000..aafba45 --- /dev/null +++ b/nanoc/lib/require_rules.rb @@ -0,0 +1,15 @@ +def require_rules(rules, locals = {}, target = binding) + locals.each { target.local_variable_set(_1, _2) } + path = File.join(Dir.getwd, rules) + target.eval( + if File.readable?(path) + File.read(path) + elsif File.readable?("#{path}.rb") + File.read("#{path}.rb") + elsif File.readable?("#{path}.rules") + File.read("#{path}.rules") + else + raise LoadError, "#{path} is not readable" + end + ) +end diff --git a/nanoc/rules/random.rules b/nanoc/rules/random.rules index 9186b9c..58606d3 100644 --- a/nanoc/rules/random.rules +++ b/nanoc/rules/random.rules @@ -15,6 +15,6 @@ locales.each do |locale| end compile "/js/main/random.ts" do - filter(:webpack) + filter(:webpack, depend_on: ["/js/lib/"]) write("/js/main/random.js") end diff --git a/nanoc/rules/redirect.rules b/nanoc/rules/redirect.rules index 8273425..f3509db 100644 --- a/nanoc/rules/redirect.rules +++ b/nanoc/rules/redirect.rules @@ -12,6 +12,6 @@ compile "/html/redirect.html.erb" do end compile "/js/main/redirect.ts" do - filter(:webpack) + filter(:webpack, depend_on: ["/js/lib/"]) write("/js/main/redirect.js") end diff --git a/nanoc/rules/stream.rules b/nanoc/rules/stream.rules index 86d9c1a..45da629 100644 --- a/nanoc/rules/stream.rules +++ b/nanoc/rules/stream.rules @@ -12,22 +12,22 @@ compile "/*/*/surah.json" do end end -Ryo.each(slugs) do |id, slug| +Ryo.each(name_by_id) do |id, transliterated_name| writer = ->(locale, identifier:) do name = i18n[locale].surahs.names[id.to_i - 1] context = Ryo.from( filename: "stream.html.erb", locale:, locales:, - surah: {id:, name:, slug:} + surah: {id:, name:, transliterated_name:} ) filter(:erb, locals: {context:}) filter(:tidy) write "/#{locale}/#{identifier}/index.html" end locales.each do |locale| - compile "/html/stream.html.erb", rep: "/#{locale}/#{slug}/index.html" do - instance_exec(locale, identifier: slug, &writer) + compile "/html/stream.html.erb", rep: "/#{locale}/#{transliterated_name}/index.html" do + instance_exec(locale, identifier: transliterated_name, &writer) end compile "/html/stream.html.erb", rep: "/#{locale}/#{id}/index.html" do instance_exec(locale, identifier: id, &writer) @@ -37,8 +37,7 @@ end compile "/js/main/surah-stream.tsx" do filter :webpack, - depend_on: ["/js/components", "/js/lib/", "/js/hooks"], - reject: proc { _1.include?("WebPackage") } + depend_on: ["/js/components", "/js/lib/", "/js/hooks"] write "/js/main/surah-stream.js" filter :gzip write "/js/main/surah-stream.js.gz" diff --git a/src/html/random.html.erb b/src/html/random.html.erb index 5055821..7c2c737 100644 --- a/src/html/random.html.erb +++ b/src/html/random.html.erb @@ -5,7 +5,6 @@ <%= erb("partials/favicon.html.erb") %> - <%= inline_json("/json/slugs.json") %> diff --git a/src/html/stream.html.erb b/src/html/stream.html.erb index fb0e260..1354112 100644 --- a/src/html/stream.html.erb +++ b/src/html/stream.html.erb @@ -10,14 +10,14 @@ <% context.locales.each do |locale| %> <% end %> diff --git a/src/js/components/AudioControl.tsx b/src/js/components/AudioControl.tsx index e03d189..542d980 100644 --- a/src/js/components/AudioControl.tsx +++ b/src/js/components/AudioControl.tsx @@ -1,7 +1,7 @@ import url from "url"; -import * as Quran from "lib/Quran"; +import * as Quran from "~/lib/Quran"; import React, { useEffect, useMemo, useState } from "react"; -import { SoundOnIcon, SoundOffIcon } from "components/Icon"; +import { SoundOnIcon, SoundOffIcon } from "~/components/Icon"; type Props = { recitation: Quran.Recitation; diff --git a/src/js/components/LanguageSelect.tsx b/src/js/components/LanguageSelect.tsx index 2c82876..dbea33f 100644 --- a/src/js/components/LanguageSelect.tsx +++ b/src/js/components/LanguageSelect.tsx @@ -1,5 +1,5 @@ import React from "react"; -import { Select } from "components/Select"; +import { Select } from "~/components/Select"; interface Props { locale: string; diff --git a/src/js/components/Stream.tsx b/src/js/components/Stream.tsx index dd28683..815aba0 100644 --- a/src/js/components/Stream.tsx +++ b/src/js/components/Stream.tsx @@ -1,7 +1,7 @@ import React, { useEffect, useMemo, useRef } from "react"; -import * as Quran from "lib/Quran"; -import { AudioControl } from "components/AudioControl"; -import { formatNumber, TFunction } from "lib/i18n"; +import * as Quran from "~/lib/Quran"; +import { AudioControl } from "~/components/AudioControl"; +import { formatNumber, TFunction } from "~/lib/i18n"; import classNames from "classnames"; interface Props { diff --git a/src/js/components/SurahIndex.tsx b/src/js/components/SurahIndex.tsx index ea5529e..49a36b2 100644 --- a/src/js/components/SurahIndex.tsx +++ b/src/js/components/SurahIndex.tsx @@ -1,11 +1,11 @@ -import * as Quran from "lib/Quran"; import React, { useRef, useState, useEffect } from "react"; -import { useTheme } from "hooks/useTheme"; -import { ThemeSelect } from "components/ThemeSelect"; -import { LanguageSelect } from "components/LanguageSelect"; -import { formatNumber, TFunction } from "lib/i18n"; -import { RightArrow } from "components/Icon"; -import { SurahIndexFilter } from "components/SurahIndexFilter"; +import * as Quran from "~/lib/Quran"; +import { useTheme } from "~/hooks/useTheme"; +import { ThemeSelect } from "~/components/ThemeSelect"; +import { LanguageSelect } from "~/components/LanguageSelect"; +import { formatNumber, TFunction } from "~/lib/i18n"; +import { RightArrow } from "~/components/Icon"; +import { SurahIndexFilter } from "~/components/SurahIndexFilter"; import classNames from "classnames"; interface Props { diff --git a/src/js/components/SurahIndexFilter.tsx b/src/js/components/SurahIndexFilter.tsx index 661eda2..dc8a606 100644 --- a/src/js/components/SurahIndexFilter.tsx +++ b/src/js/components/SurahIndexFilter.tsx @@ -1,6 +1,6 @@ import React from "react"; -import { TFunction } from "lib/i18n"; -import * as Quran from "lib/Quran"; +import { TFunction } from "~/lib/i18n"; +import * as Quran from "~/lib/Quran"; type Props = { t: TFunction; diff --git a/src/js/components/SurahStream.tsx b/src/js/components/SurahStream.tsx index 6fed1ab..da5e253 100644 --- a/src/js/components/SurahStream.tsx +++ b/src/js/components/SurahStream.tsx @@ -1,14 +1,19 @@ -import * as Quran from "lib/Quran"; import React, { useState, useEffect, useRef } from "react"; -import { useTheme } from "hooks/useTheme"; -import { Timer } from "components/Timer"; -import { Stream } from "components/Stream"; -import { ThemeSelect } from "components/ThemeSelect"; -import { LanguageSelect } from "components/LanguageSelect"; -import { AudioControl } from "components/AudioControl"; -import { PlayIcon, PauseIcon, RefreshIcon, StalledIcon } from "components/Icon"; import classNames from "classnames"; -import { TFunction } from "lib/i18n"; +import * as Quran from "~/lib/Quran"; +import { useTheme } from "~/hooks/useTheme"; +import { Timer } from "~/components/Timer"; +import { Stream } from "~/components/Stream"; +import { ThemeSelect } from "~/components/ThemeSelect"; +import { LanguageSelect } from "~/components/LanguageSelect"; +import { AudioControl } from "~/components/AudioControl"; +import { + PlayIcon, + PauseIcon, + RefreshIcon, + StalledIcon, +} from "~/components/Icon"; +import { TFunction } from "~/lib/i18n"; interface Props { node: HTMLScriptElement; diff --git a/src/js/components/ThemeSelect.tsx b/src/js/components/ThemeSelect.tsx index 5063482..5870c23 100644 --- a/src/js/components/ThemeSelect.tsx +++ b/src/js/components/ThemeSelect.tsx @@ -1,6 +1,7 @@ import React from "react"; -import { Select } from "components/Select"; -import type { Theme } from "hooks/useTheme"; +import { Select } from "~/components/Select"; +import type { Theme } from "~/hooks/useTheme"; + interface Props { theme: string; setTheme: (theme: Theme) => void; diff --git a/src/js/components/Timer.tsx b/src/js/components/Timer.tsx index 6a0905d..f844a23 100644 --- a/src/js/components/Timer.tsx +++ b/src/js/components/Timer.tsx @@ -1,6 +1,6 @@ import React, { useEffect } from "react"; -import * as Quran from "lib/Quran"; -import { formatNumber } from "lib/i18n"; +import * as Quran from "~/lib/Quran"; +import { formatNumber } from "~/lib/i18n"; interface Props { surah: Quran.Surah; diff --git a/src/js/lib/Locale.ts b/src/js/lib/Locale.ts deleted file mode 100644 index 9cc9a74..0000000 --- a/src/js/lib/Locale.ts +++ /dev/null @@ -1,27 +0,0 @@ -export interface Locale { - fromBrowser: () => string; - fromPath: () => string | undefined; -} - -export function Locale(window: Window): Locale { - const self = Object.create(null); - const { navigator, location } = window; - const locales = ["ar", "en"]; - - self.fromBrowser = () => { - return ( - navigator.languages - .map(lang => lang.substr(0, 2)) - .find(locale => locales.includes(locale)) || "en" - ); - }; - - self.fromPath = () => { - return location.pathname - .split("/") - .filter(s => s.length) - .at(0); - }; - - return self; -} diff --git a/src/js/lib/Quran.ts b/src/js/lib/Quran.ts index 0ac621a..25de97c 100644 --- a/src/js/lib/Quran.ts +++ b/src/js/lib/Quran.ts @@ -1,6 +1,6 @@ -import * as JSON from "lib/Quran/JSON"; -import { Ayah } from "lib/Quran/Ayah"; -import { Surah } from "lib/Quran/Surah"; +import * as JSON from "~/lib/Quran/JSON"; +import { Ayah } from "~/lib/Quran/Ayah"; +import { Surah } from "~/lib/Quran/Surah"; type Locale = "ar" | "en"; type Ayat = Ayah[]; diff --git a/src/js/lib/Quran/Ayah.ts b/src/js/lib/Quran/Ayah.ts index 7f30133..105da0a 100644 --- a/src/js/lib/Quran/Ayah.ts +++ b/src/js/lib/Quran/Ayah.ts @@ -1,4 +1,4 @@ -import * as Quran from "lib/Quran"; +import * as Quran from "~/lib/Quran"; export type Ayah = { id: number; diff --git a/src/js/lib/Quran/Surah.ts b/src/js/lib/Quran/Surah.ts index ec204b9..6f5a9ea 100644 --- a/src/js/lib/Quran/Surah.ts +++ b/src/js/lib/Quran/Surah.ts @@ -1,4 +1,4 @@ -import * as Quran from "lib/Quran"; +import * as Quran from "~/lib/Quran"; type TimeSlot = [number, number]; type TimeSlots = [TimeSlot]; diff --git a/src/js/lib/i18n.ts b/src/js/lib/i18n.ts index 59b0701..2406358 100644 --- a/src/js/lib/i18n.ts +++ b/src/js/lib/i18n.ts @@ -1,4 +1,4 @@ -import * as Quran from "lib/Quran"; +import * as Quran from "~/lib/Quran"; type PhraseMap = { [key: string]: undefined | string | PhraseMap; diff --git a/src/js/loaders/SurahStreamLoader.ts b/src/js/loaders/SurahStreamLoader.ts index 3ada292..af4d22c 100644 --- a/src/js/loaders/SurahStreamLoader.ts +++ b/src/js/loaders/SurahStreamLoader.ts @@ -1,5 +1,5 @@ import postman, { item } from "postman"; -import * as Quran from "lib/Quran"; +import * as Quran from "~/lib/Quran"; (function () { const parent: HTMLElement = document.querySelector(".postman.loader")!; diff --git a/src/js/main/random.ts b/src/js/main/random.ts index f53aebd..2113c5f 100644 --- a/src/js/main/random.ts +++ b/src/js/main/random.ts @@ -1,9 +1,7 @@ -import { Locale } from "lib/Locale"; - (function () { + const nameById = require("@json/nameById.json"); const surahId: number = Math.ceil(Math.random() * 114); - const locale = Locale(window); - const el: HTMLElement = document.querySelector(".json.slugs")!; - const slugs = JSON.parse(el.innerText); - location.replace(`/${locale.fromPath()}/${slugs[surahId]}`); + const name = nameById[surahId]; + const locale = location.pathname.slice(1, 3); + location.replace(["", locale, name, ""].join("/")); })(); diff --git a/src/js/main/redirect.ts b/src/js/main/redirect.ts index 58f3887..1efbde0 100644 --- a/src/js/main/redirect.ts +++ b/src/js/main/redirect.ts @@ -1,6 +1,12 @@ -import { Locale } from "lib/Locale"; - -(function (window, location) { - const locale = Locale(window).fromBrowser(); - location.replace(`/${locale}/`); -})(window, location); +(function () { + const LOCALES = ["ar", "en"]; + const DEFAULT_LOCALE = "en"; + function getUserLocale() { + return ( + navigator.languages + .map(s => s.slice(0, 2)) + .find(s => LOCALES.includes(s)) || DEFAULT_LOCALE + ); + } + location.replace(["", getUserLocale(), ""].join("/")); +})(); diff --git a/src/js/main/surah-index.tsx b/src/js/main/surah-index.tsx index cf97453..5d7c899 100644 --- a/src/js/main/surah-index.tsx +++ b/src/js/main/surah-index.tsx @@ -1,8 +1,8 @@ -import * as Quran from "lib/Quran"; import React from "react"; import ReactDOM from "react-dom/client"; -import { i18n } from "lib/i18n"; -import { SurahIndex } from "components/SurahIndex"; +import * as Quran from "~/lib/Quran"; +import { i18n } from "~/lib/i18n"; +import { SurahIndex } from "~/components/SurahIndex"; (function () { const root: HTMLElement = document.querySelector(".root")!; diff --git a/src/js/main/surah-stream.tsx b/src/js/main/surah-stream.tsx index 7856bf2..6bd2911 100644 --- a/src/js/main/surah-stream.tsx +++ b/src/js/main/surah-stream.tsx @@ -1,8 +1,8 @@ -import * as Quran from "lib/Quran"; +import * as Quran from "~/lib/Quran"; import React from "react"; import ReactDOM from "react-dom/client"; -import { i18n } from "lib/i18n"; -import { SurahStream } from "components/SurahStream"; +import { i18n } from "~/lib/i18n"; +import { SurahStream } from "~/components/SurahStream"; (function () { const root: HTMLElement = document.querySelector(".root")!; diff --git a/src/json/slugs.json b/src/json/nameById.json similarity index 100% rename from src/json/slugs.json rename to src/json/nameById.json diff --git a/src/sitemap.xml.erb b/src/sitemap.xml.erb index 3c5717d..da1e548 100644 --- a/src/sitemap.xml.erb +++ b/src/sitemap.xml.erb @@ -7,9 +7,9 @@ 1.0 weekly - <% Ryo.each(slugs) do |_id, slug| %> + <% Ryo.each(name_by_id) do |_id, transliterated_name| %> - https://al-quran.reflectslight.io/<%= locale %>/<%= slug %>/ + https://al-quran.reflectslight.io/<%= locale %>/<%= transliterated_name %>/ 0.7 weekly diff --git a/tsconfig.json b/tsconfig.json index 15a5eaf..8e64f73 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -12,6 +12,9 @@ "lib": [ "ES2020", "DOM" ], "baseUrl": "src/", - "paths": { "*": ["js/*"] }, + "paths": { + "~/*": ["./js/*"], + "@json/*": ["./json/*"] + }, } } diff --git a/webpack.common.js b/webpack.common.js index eadcdf2..4cba40a 100644 --- a/webpack.common.js +++ b/webpack.common.js @@ -3,9 +3,12 @@ const process = require("process"); module.exports = { resolve: { - roots: [path.resolve("src/js"), path.resolve("node_modules")], - modules: [path.resolve("src/js"), path.resolve("node_modules")], - extensions: [".js", ".ts", ".tsx"], + modules: [path.resolve(__dirname, "node_modules")], + alias: { + "@json": path.resolve(__dirname, "src/json"), + "~": path.resolve(__dirname, "src/js"), + }, + extensions: [".js", ".ts", ".tsx", ".json"], }, module: { rules: [