Merge pull request #100 from ReflectsLight/i18n
Add a simpler, more organized i18n.ts implementation
This commit is contained in:
commit
f75a1764d4
16 changed files with 96 additions and 68 deletions
|
@ -13,6 +13,7 @@ module.exports = {
|
||||||
"@typescript-eslint/prefer-nullish-coalescing": 0,
|
"@typescript-eslint/prefer-nullish-coalescing": 0,
|
||||||
"@typescript-eslint/restrict-template-expressions": 0,
|
"@typescript-eslint/restrict-template-expressions": 0,
|
||||||
"@typescript-eslint/promise-function-async": 0,
|
"@typescript-eslint/promise-function-async": 0,
|
||||||
|
"@typescript-eslint/consistent-type-definitions": 0,
|
||||||
"@typescript-eslint/no-misused-promises": ["error", {"checksConditionals": false}],
|
"@typescript-eslint/no-misused-promises": ["error", {"checksConditionals": false}],
|
||||||
"@typescript-eslint/no-redeclare": 0,
|
"@typescript-eslint/no-redeclare": 0,
|
||||||
"@typescript-eslint/no-non-null-assertion": 0,
|
"@typescript-eslint/no-non-null-assertion": 0,
|
||||||
|
|
1
Gemfile
1
Gemfile
|
@ -1,4 +1,5 @@
|
||||||
# frozen_string_literal: true
|
# frozen_string_literal: true
|
||||||
|
|
||||||
source "https://rubygems.org"
|
source "https://rubygems.org"
|
||||||
|
|
||||||
##
|
##
|
||||||
|
|
18
Rules
18
Rules
|
@ -1,5 +1,6 @@
|
||||||
#!/usr/bin/env ruby
|
#!/usr/bin/env ruby
|
||||||
# frozen_string_literal: true
|
# frozen_string_literal: true
|
||||||
|
|
||||||
require "ryo"
|
require "ryo"
|
||||||
require "nanoc-gunzip"
|
require "nanoc-gunzip"
|
||||||
require "nanoc-webpack"
|
require "nanoc-webpack"
|
||||||
|
@ -36,6 +37,23 @@ require_rules "rules/pages/surah/index", {locales:}
|
||||||
require_rules "rules/pages/surah/redirect"
|
require_rules "rules/pages/surah/redirect"
|
||||||
require_rules "rules/pages/surah/id_redirect", {locales:}
|
require_rules "rules/pages/surah/id_redirect", {locales:}
|
||||||
|
|
||||||
|
##
|
||||||
|
# Inline 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
|
||||||
|
|
||||||
##
|
##
|
||||||
# Defaults
|
# Defaults
|
||||||
compile("/**/*") { write(nil) }
|
compile("/**/*") { write(nil) }
|
||||||
|
|
11
lib/helper.rb
Normal file
11
lib/helper.rb
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
module Helper
|
||||||
|
def inline_json(path)
|
||||||
|
class_name = File.basename(path, File.extname(path))
|
||||||
|
"<script class='json #{class_name}' type='application/json'>" \
|
||||||
|
"#{items[path].compiled_content}" \
|
||||||
|
"</script>"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
use_helper Helper
|
|
@ -6,7 +6,7 @@
|
||||||
|
|
||||||
1.upto(114) do |id|
|
1.upto(114) do |id|
|
||||||
locales.each do |locale|
|
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)
|
filter(:erb)
|
||||||
write("/#{locale}/#{id}/index.html")
|
write("/#{locale}/#{id}/index.html")
|
||||||
end
|
end
|
||||||
|
|
|
@ -30,8 +30,3 @@ compile "/css/pages/surah/index.scss" do
|
||||||
filter :rainpress
|
filter :rainpress
|
||||||
write("/css/pages/surah/index.css")
|
write("/css/pages/surah/index.css")
|
||||||
end
|
end
|
||||||
|
|
||||||
compile "/surahs.json" do
|
|
||||||
filter(:minify_json)
|
|
||||||
write "/surahs.json"
|
|
||||||
end
|
|
||||||
|
|
|
@ -4,9 +4,7 @@
|
||||||
<title></title>
|
<title></title>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<script class="surah-id-to-slug" type="application/json">
|
<%= inline_json('/slugs.json') %>
|
||||||
<%= File.read(File.join(Dir.getwd, "src", "slugs.json")) %>
|
|
||||||
</script>
|
|
||||||
<script src="/js/pages/surah/id_redirect.js"></script>
|
<script src="/js/pages/surah/id_redirect.js"></script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|
|
@ -13,6 +13,8 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="root" data-locale="<%= locale %>"></div>
|
<div class="root" data-locale="<%= locale %>"></div>
|
||||||
|
<%= inline_json("/i18n.json") %>
|
||||||
|
<%= inline_json("/surahs.json") %>
|
||||||
<script src="/js/pages/surah/index/loader.js"></script>
|
<script src="/js/pages/surah/index/loader.js"></script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|
|
@ -13,6 +13,7 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="root" data-locale="<%= locale %>" data-surah-id="<%= surah_id %>"></div>
|
<div class="root" data-locale="<%= locale %>" data-surah-id="<%= surah_id %>"></div>
|
||||||
|
<%= inline_json("/i18n.json") %>
|
||||||
<script src="/js/pages/surah/stream/loader.js"></script>
|
<script src="/js/pages/surah/stream/loader.js"></script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</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 * as Quran from 'lib/Quran';
|
||||||
import React, { useEffect } from 'react';
|
import React, { useEffect } from 'react';
|
||||||
import { numbers, strings } from 'lib/i18n';
|
import { TFunction } from 'lib/i18n';
|
||||||
import { Slice } from 'lib/Quran/Slice';
|
import { Slice } from 'lib/Quran/Slice';
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
|
|
||||||
|
@ -11,21 +11,20 @@ interface Props {
|
||||||
slice: Slice
|
slice: Slice
|
||||||
endOfStream: boolean
|
endOfStream: boolean
|
||||||
isPaused: boolean
|
isPaused: boolean
|
||||||
|
t: TFunction
|
||||||
}
|
}
|
||||||
|
|
||||||
export function Stream({ surah, stream, locale, slice, endOfStream, isPaused }: Props) {
|
export function Stream({ surah, stream, locale, slice, endOfStream, isPaused, t }: Props) {
|
||||||
const n = numbers(locale);
|
|
||||||
const s = strings(locale);
|
|
||||||
const className = classNames('body', 'stream', { 'scroll-y': endOfStream || isPaused });
|
const className = classNames('body', 'stream', { 'scroll-y': endOfStream || isPaused });
|
||||||
const ayat = stream.map((ayah: Quran.Ayah) => {
|
const ayat = stream.map((ayah: Quran.Ayah) => {
|
||||||
return (
|
return (
|
||||||
<li key={ayah.id} className="ayah fade">
|
<li key={ayah.id} className="ayah fade">
|
||||||
<span className="surah-id ayah-id">
|
<span className="surah-id ayah-id">
|
||||||
{s('surah')}{' '}
|
{t(locale, 'surah')}{' '}
|
||||||
{n(surah.id)}
|
{surah.id.toLocaleString(locale)}
|
||||||
{s('comma')}{' '}
|
{t(locale, 'comma')}{' '}
|
||||||
{s('ayah')}{' '}
|
{t(locale, 'ayah')}{' '}
|
||||||
{n(ayah.id)}
|
{ayah.id.toLocaleString(locale)}
|
||||||
</span>
|
</span>
|
||||||
<p>{ayah.text}</p>
|
<p>{ayah.text}</p>
|
||||||
</li>
|
</li>
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import React, { useEffect, useState } from 'react';
|
import React, { useEffect, useState } from 'react';
|
||||||
import * as Quran from 'lib/Quran';
|
import * as Quran from 'lib/Quran';
|
||||||
import { numberToDecimal } from 'lib/i18n';
|
import { formatNumber } from 'lib/i18n';
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
surah: Quran.Surah
|
surah: Quran.Surah
|
||||||
|
@ -25,7 +25,7 @@ export function Timer ({ surah, stream, setStream, locale, isPaused }: Props) {
|
||||||
}, [ms, isPaused]);
|
}, [ms, isPaused]);
|
||||||
return (
|
return (
|
||||||
<div className='timer'>
|
<div className='timer'>
|
||||||
{numberToDecimal(ms / 1000, locale)}
|
{formatNumber(ms / 1000, locale)}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,25 +1,4 @@
|
||||||
import { Locale } from 'lib/Quran';
|
import * as Quran 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}'
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The read time baseline - as a number milliseconds -
|
* 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,
|
* The read time for each word in an Ayah,
|
||||||
* relative to the active locale.
|
* relative to the active locale.
|
||||||
*/
|
*/
|
||||||
export const DelayPerWord: Record<Locale, number> = {
|
export const DelayPerWord: Record<Quran.Locale, number> = {
|
||||||
en: 500,
|
en: 500,
|
||||||
ar: 750
|
ar: 750
|
||||||
};
|
};
|
||||||
|
|
||||||
export function numbers (locale: Locale) {
|
type PhraseMap<T> = {
|
||||||
return function(number: number): string {
|
[key: string]: undefined | string | PhraseMap<T>
|
||||||
return Number(number).toLocaleString(locale);
|
};
|
||||||
|
|
||||||
|
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) {
|
export function formatNumber(number: number, locale: Quran.Locale): string {
|
||||||
return function(key: Strings): string {
|
|
||||||
const table = sTable[locale];
|
|
||||||
return table[key];
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
export function numberToDecimal(number: number, locale: Locale): string {
|
|
||||||
return number.toLocaleString(locale, { maximumFractionDigits: 1 })
|
return number.toLocaleString(locale, { maximumFractionDigits: 1 })
|
||||||
.split(/([^\d])/)
|
.split(/([^\d])/)
|
||||||
.join(' ');
|
.join(' ');
|
||||||
|
|
|
@ -3,7 +3,7 @@
|
||||||
.split('/')
|
.split('/')
|
||||||
.filter(function (s) { return s.length; })
|
.filter(function (s) { return s.length; })
|
||||||
.slice(-2);
|
.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 slugs = JSON.parse(el.innerText);
|
||||||
const path = ['', locale, slugs[surahId]].join('/');
|
const path = ['', locale, slugs[surahId]].join('/');
|
||||||
location.replace([path, location.search].join(''));
|
location.replace([path, location.search].join(''));
|
||||||
|
|
|
@ -6,16 +6,16 @@ import * as Quran from 'lib/Quran';
|
||||||
import { SelectOption } from 'components/Select';
|
import { SelectOption } from 'components/Select';
|
||||||
import { ThemeSelect } from 'components/ThemeSelect';
|
import { ThemeSelect } from 'components/ThemeSelect';
|
||||||
import { LanguageSelect } from 'components/LanguageSelect';
|
import { LanguageSelect } from 'components/LanguageSelect';
|
||||||
import { strings } from 'lib/i18n';
|
import { i18n, TFunction } from 'lib/i18n';
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
locale: Quran.Locale
|
locale: Quran.Locale
|
||||||
surahs: Quran.Surah[]
|
surahs: Quran.Surah[]
|
||||||
|
t: TFunction
|
||||||
}
|
}
|
||||||
|
|
||||||
function SurahIndex({ locale, surahs }: Props) {
|
function SurahIndex({ locale, surahs, t }: Props) {
|
||||||
const [theme, setTheme] = useState(getCookie('theme') || 'moon');
|
const [theme, setTheme] = useState(getCookie('theme') || 'moon');
|
||||||
const s = strings(locale);
|
|
||||||
const onLanguageChange = (o: SelectOption) => {
|
const onLanguageChange = (o: SelectOption) => {
|
||||||
document.location.replace(`/${o.value}/`);
|
document.location.replace(`/${o.value}/`);
|
||||||
};
|
};
|
||||||
|
@ -29,7 +29,7 @@ function SurahIndex({ locale, surahs }: Props) {
|
||||||
<div className="header">
|
<div className="header">
|
||||||
<a href={'/' + locale} className="image" />
|
<a href={'/' + locale} className="image" />
|
||||||
</div>
|
</div>
|
||||||
<div className="row title">{s('TheNobleQuran')}</div>
|
<div className="row title">{t(locale, 'TheNobleQuran')}</div>
|
||||||
<div className="row dropdown-row">
|
<div className="row dropdown-row">
|
||||||
<ThemeSelect theme={theme} setTheme={setTheme} />
|
<ThemeSelect theme={theme} setTheme={setTheme} />
|
||||||
<LanguageSelect locale={locale} onChange={onLanguageChange} />
|
<LanguageSelect locale={locale} onChange={onLanguageChange} />
|
||||||
|
@ -52,7 +52,7 @@ function SurahIndex({ locale, surahs }: Props) {
|
||||||
))}
|
))}
|
||||||
</ul>
|
</ul>
|
||||||
<a href={`/${locale}/random`} className="row surah choose-random">
|
<a href={`/${locale}/random`} className="row surah choose-random">
|
||||||
{s('ChooseRandomChapter')}
|
{t(locale, 'ChooseRandomChapter')}
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
@ -62,7 +62,8 @@ function SurahIndex({ locale, surahs }: Props) {
|
||||||
(function() {
|
(function() {
|
||||||
const root: HTMLElement = document.querySelector('.root')!;
|
const root: HTMLElement = document.querySelector('.root')!;
|
||||||
const locale = root.getAttribute('data-locale') as Quran.Locale;
|
const locale = root.getAttribute('data-locale') as Quran.Locale;
|
||||||
const script: HTMLScriptElement = document.querySelector('script[src="/surahs.json"]')!;
|
const script: HTMLScriptElement = document.querySelector('.json.surahs')!;
|
||||||
|
const t = i18n(document.querySelector<HTMLElement>('.json.i18n')!.innerText);
|
||||||
const surahs: Quran.Surah[] = JSON.parse(script.innerText)
|
const surahs: Quran.Surah[] = JSON.parse(script.innerText)
|
||||||
.map((el: Quran.JSON.Surah) => {
|
.map((el: Quran.JSON.Surah) => {
|
||||||
return Quran.Surah.fromJSON(locale, el);
|
return Quran.Surah.fromJSON(locale, el);
|
||||||
|
@ -71,6 +72,6 @@ function SurahIndex({ locale, surahs }: Props) {
|
||||||
ReactDOM
|
ReactDOM
|
||||||
.createRoot(root)
|
.createRoot(root)
|
||||||
.render(
|
.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 { PlayShape, PauseShape } from 'components/Shape';
|
||||||
import * as Quran from 'lib/Quran';
|
import * as Quran from 'lib/Quran';
|
||||||
import { Slice } from 'lib/Quran/Slice';
|
import { Slice } from 'lib/Quran/Slice';
|
||||||
import { strings } from 'lib/i18n';
|
import { i18n, TFunction } from 'lib/i18n';
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
node: HTMLScriptElement
|
node: HTMLScriptElement
|
||||||
locale: Quran.Locale
|
locale: Quran.Locale
|
||||||
surahId: number
|
|
||||||
slice: Slice
|
slice: Slice
|
||||||
paused: boolean
|
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 [stream, setStream] = useState<Quran.Ayat>([]);
|
||||||
const [isPaused, setIsPaused] = useState<boolean>(paused);
|
const [isPaused, setIsPaused] = useState<boolean>(paused);
|
||||||
const [theme, setTheme] = useState(getCookie('theme') || 'moon');
|
const [theme, setTheme] = useState(getCookie('theme') || 'moon');
|
||||||
const [surah] = useState<Quran.Surah>(Quran.Surah.fromDOMNode(locale, node));
|
const [surah] = useState<Quran.Surah>(Quran.Surah.fromDOMNode(locale, node));
|
||||||
const readyToRender = stream.length > 0;
|
const readyToRender = stream.length > 0;
|
||||||
const s = strings(locale);
|
|
||||||
const onLanguageChange = (o: SelectOption) => {
|
const onLanguageChange = (o: SelectOption) => {
|
||||||
const locale = o.value;
|
const locale = o.value;
|
||||||
const params = [
|
const params = [
|
||||||
|
@ -66,7 +65,7 @@ function SurahStream({ node, locale, surahId, slice, paused }: Props) {
|
||||||
</div>
|
</div>
|
||||||
{readyToRender && (
|
{readyToRender && (
|
||||||
<>
|
<>
|
||||||
<div className="row title">{s('TheNobleQuran')}</div>
|
<div className="row title">{t(locale, 'TheNobleQuran')}</div>
|
||||||
<div className="row dropdown-row">
|
<div className="row dropdown-row">
|
||||||
<ThemeSelect theme={theme} setTheme={setTheme} />
|
<ThemeSelect theme={theme} setTheme={setTheme} />
|
||||||
<LanguageSelect locale={locale} onChange={onLanguageChange} />
|
<LanguageSelect locale={locale} onChange={onLanguageChange} />
|
||||||
|
@ -87,6 +86,7 @@ function SurahStream({ node, locale, surahId, slice, paused }: Props) {
|
||||||
locale={locale}
|
locale={locale}
|
||||||
endOfStream={endOfStream}
|
endOfStream={endOfStream}
|
||||||
isPaused={isPaused}
|
isPaused={isPaused}
|
||||||
|
t={t}
|
||||||
/>
|
/>
|
||||||
}
|
}
|
||||||
<div className="row">
|
<div className="row">
|
||||||
|
@ -118,6 +118,7 @@ function SurahStream({ node, locale, surahId, slice, paused }: Props) {
|
||||||
const params = new URLSearchParams(location.search);
|
const params = new URLSearchParams(location.search);
|
||||||
const slice = Slice.fromParam(params.get('ayah'));
|
const slice = Slice.fromParam(params.get('ayah'));
|
||||||
const paused = toBoolean(params.get('paused'));
|
const paused = toBoolean(params.get('paused'));
|
||||||
|
const t = i18n(document.querySelector<HTMLElement>('.json.i18n')!.innerText);
|
||||||
|
|
||||||
ReactDOM
|
ReactDOM
|
||||||
.createRoot(root)
|
.createRoot(root)
|
||||||
|
@ -125,9 +126,9 @@ function SurahStream({ node, locale, surahId, slice, paused }: Props) {
|
||||||
<SurahStream
|
<SurahStream
|
||||||
node={node}
|
node={node}
|
||||||
locale={locale}
|
locale={locale}
|
||||||
surahId={surahId}
|
|
||||||
slice={slice}
|
slice={slice}
|
||||||
paused={paused}
|
paused={paused}
|
||||||
|
t={t}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
})();
|
})();
|
||||||
|
|
Loading…
Reference in a new issue