feat(mobx): implement locale options

This commit is contained in:
Paul
2021-12-11 11:56:33 +00:00
parent 3fc49a3835
commit 7c665b3b40
5 changed files with 155 additions and 144 deletions

View File

@@ -3,11 +3,12 @@ import calendar from "dayjs/plugin/calendar";
import format from "dayjs/plugin/localizedFormat";
import update from "dayjs/plugin/updateLocale";
import defaultsDeep from "lodash.defaultsdeep";
import { observer } from "mobx-react-lite";
import { IntlProvider } from "preact-i18n";
import { useCallback, useEffect, useState } from "preact/hooks";
import { connectState } from "../redux/connector";
import { useApplicationState } from "../mobx/State";
import definition from "../../external/lang/en.json";
@@ -222,59 +223,14 @@ export interface Dictionary {
| undefined;
}
function Locale({ children, locale }: Props) {
const [defns, setDefinition] = useState<Dictionary>(
export default observer(({ children }: Props) => {
const locale = useApplicationState().locale;
const [definitions, setDefinition] = useState<Dictionary>(
definition as Dictionary,
);
// Load relevant language information, fallback to English if invalid.
const lang = Languages[locale] ?? Languages.en;
function transformLanguage(source: Dictionary) {
// Fallback untranslated strings to English (UK)
const obj = defaultsDeep(source, definition);
// Take relevant objects out, dayjs and defaults
// should exist given we just took defaults above.
const { dayjs } = obj;
const { defaults } = dayjs;
// Determine whether we are using 12-hour clock.
const twelvehour = defaults?.twelvehour
? defaults.twelvehour === "yes"
: false;
// Determine what date separator we are using.
const separator: string = defaults?.date_separator ?? "/";
// Determine what date format we are using.
const date: "traditional" | "simplified" | "ISO8601" =
defaults?.date_format ?? "traditional";
// Available date formats.
const DATE_FORMATS = {
traditional: `DD${separator}MM${separator}YYYY`,
simplified: `MM${separator}DD${separator}YYYY`,
ISO8601: "YYYY-MM-DD",
};
// Replace data in dayjs object, make sure to provide fallbacks.
dayjs["sameElse"] = DATE_FORMATS[date] ?? DATE_FORMATS.traditional;
dayjs["timeFormat"] = twelvehour ? "hh:mm A" : "HH:mm";
// Replace {{time}} format string in dayjs strings with the time format.
Object.keys(dayjs)
.filter((k) => typeof dayjs[k] === "string")
.forEach(
(k) =>
(dayjs[k] = dayjs[k].replace(
/{{time}}/g,
dayjs["timeFormat"],
)),
);
return obj;
}
const lang = locale.getLanguage();
const source = Languages[lang];
const loadLanguage = useCallback(
(locale: string) => {
@@ -288,13 +244,13 @@ function Locale({ children, locale }: Props) {
return;
}
import(`../../external/lang/${lang.i18n}.json`).then(
import(`../../external/lang/${source.i18n}.json`).then(
async (lang_file) => {
// Transform the definitions data.
const defn = transformLanguage(lang_file.default);
// Determine and load dayjs locales.
const target = lang.dayjs ?? lang.i18n;
const target = source.dayjs ?? source.i18n;
const dayjs_locale = await import(
`../../node_modules/dayjs/esm/locale/${target}.js`
);
@@ -312,25 +268,63 @@ function Locale({ children, locale }: Props) {
},
);
},
[lang.dayjs, lang.i18n],
[source.dayjs, source.i18n],
);
useEffect(() => loadLanguage(locale), [locale, lang, loadLanguage]);
useEffect(() => loadLanguage(lang), [lang, source, loadLanguage]);
useEffect(() => {
// Apply RTL language format.
document.body.style.direction = lang.rtl ? "rtl" : "";
}, [lang.rtl]);
document.body.style.direction = source.rtl ? "rtl" : "";
}, [source.rtl]);
return <IntlProvider definition={defns}>{children}</IntlProvider>;
return <IntlProvider definition={definitions}>{children}</IntlProvider>;
});
/**
* Apply defaults and process dayjs entries for a langauge.
* @param source Dictionary definition to transform
* @returns Transformed dictionary definition
*/
function transformLanguage(source: Dictionary) {
// Fallback untranslated strings to English (UK)
const obj = defaultsDeep(source, definition);
// Take relevant objects out, dayjs and defaults
// should exist given we just took defaults above.
const { dayjs } = obj;
const { defaults } = dayjs;
// Determine whether we are using 12-hour clock.
const twelvehour = defaults?.twelvehour
? defaults.twelvehour === "yes"
: false;
// Determine what date separator we are using.
const separator: string = defaults?.date_separator ?? "/";
// Determine what date format we are using.
const date: "traditional" | "simplified" | "ISO8601" =
defaults?.date_format ?? "traditional";
// Available date formats.
const DATE_FORMATS = {
traditional: `DD${separator}MM${separator}YYYY`,
simplified: `MM${separator}DD${separator}YYYY`,
ISO8601: "YYYY-MM-DD",
};
// Replace data in dayjs object, make sure to provide fallbacks.
dayjs["sameElse"] = DATE_FORMATS[date] ?? DATE_FORMATS.traditional;
dayjs["timeFormat"] = twelvehour ? "hh:mm A" : "HH:mm";
// Replace {{time}} format string in dayjs strings with the time format.
Object.keys(dayjs)
.filter((k) => typeof dayjs[k] === "string")
.forEach(
(k) =>
(dayjs[k] = dayjs[k].replace(/{{time}}/g, dayjs["timeFormat"])),
);
return obj;
}
export default connectState<Omit<Props, "locale">>(
Locale,
(state) => {
return {
locale: state.locale,
};
},
true,
);