mirror of
https://gitlab.com/PronounsPage/PronounsPage.git
synced 2025-09-24 05:05:20 -04:00
220 lines
8.1 KiB
TypeScript
220 lines
8.1 KiB
TypeScript
import { DateTime, Settings } from 'luxon';
|
|
import { defineNuxtPlugin, useCookie, useRouter } from 'nuxt/app';
|
|
import type { Pinia } from 'pinia';
|
|
import { decodeTime } from 'ulid';
|
|
|
|
import config from '../data/config.suml';
|
|
import translations from '../data/translations.suml';
|
|
import baseTranslations from '../locale/_base/translations.suml';
|
|
import type { LocaleDescription } from '../locale/locales.ts';
|
|
import buildLocaleList from '../src/buildLocaleList.ts';
|
|
import type { Pronoun } from '../src/classes.ts';
|
|
import { LoadScriptError } from '../src/errors.ts';
|
|
import { buildDict } from '../src/helpers.ts';
|
|
import { Translator } from '../src/translator.ts';
|
|
import { useMainStore } from '../store/index.ts';
|
|
|
|
declare global {
|
|
interface Window {
|
|
fusetag: any;
|
|
}
|
|
}
|
|
|
|
declare module '#app' {
|
|
interface NuxtApp {
|
|
$translator: Translator;
|
|
$t<T extends string | string[] = string>(key: string, params?: Record<string, string | number>, warn?: boolean): NoInfer<T>;
|
|
$te(key: string, fallback?: boolean): boolean;
|
|
$translateForPronoun(str: string, pronoun: Pronoun | null): string;
|
|
$locales: Record<string, LocaleDescription>;
|
|
$loadScript(name: string, src: string, nonce?: string): Promise<void>;
|
|
$loadStylesheet(name: string, src: string): Promise<void>;
|
|
$datetime(timestamp: number): string;
|
|
$date(timestamp: number): string;
|
|
$ulidTime(ulid: string): number;
|
|
}
|
|
}
|
|
|
|
declare module 'vue' {
|
|
interface ComponentCustomProperties {
|
|
$translator: Translator;
|
|
$t<T extends string | string[] = string>(key: string, params?: Record<string, string | number>, warn?: boolean): NoInfer<T>;
|
|
$te(key: string, fallback?: boolean): boolean;
|
|
$translateForPronoun(str: string, pronoun: Pronoun | null): string;
|
|
$locales: Record<string, LocaleDescription>;
|
|
$loadScript(name: string, src: string, nonce?: string): Promise<void>;
|
|
$loadStylesheet(name: string, src: string): Promise<void>;
|
|
$datetime(timestamp: number): string;
|
|
$date(timestamp: number): string;
|
|
$ulidTime(ulid: string): number;
|
|
}
|
|
}
|
|
|
|
export default defineNuxtPlugin((nuxtApp) => {
|
|
const translator = new Translator(translations, baseTranslations, config);
|
|
const t = (key: string, params = {}, warn = false): string => translator.translate(key, params, warn);
|
|
const te = (key: string, fallback = false): boolean => {
|
|
if (translator.has(key)) {
|
|
return true;
|
|
}
|
|
if (fallback && translator.hasFallback(key)) {
|
|
return true;
|
|
}
|
|
return false;
|
|
};
|
|
const translateForPronoun = (str: string, pronoun: Pronoun | null): string => {
|
|
let translation = translator.translate(`flags.${str.replace(/ /g, '_').replace(/'/g, '*')}`, {}, false) || str;
|
|
if (pronoun) {
|
|
translation = pronoun.format(translation);
|
|
}
|
|
return translation;
|
|
};
|
|
|
|
const locales = buildDict(function* () {
|
|
const locales = buildLocaleList(config.locale);
|
|
if (config.locale !== '_') {
|
|
yield [config.locale, locales[config.locale]];
|
|
}
|
|
for (const [locale, localeDescription] of Object.entries(locales)) {
|
|
if (locale !== config.locale) {
|
|
yield [locale, localeDescription];
|
|
}
|
|
}
|
|
});
|
|
|
|
const store = useMainStore(nuxtApp.$pinia as Pinia);
|
|
const spellingCookie = useCookie('spelling');
|
|
store.setSpelling(spellingCookie.value ?? null);
|
|
const translationsCookie = useCookie<Record<string, string>>('translations');
|
|
store.restoreTranslations(translationsCookie.value);
|
|
const translationModeVisibleCookie = useCookie('translationModeVisible');
|
|
if (translationModeVisibleCookie.value) {
|
|
store.showTranslationMode();
|
|
}
|
|
|
|
const awaitLoadedClass = (node: Element): Promise<void> => {
|
|
return new Promise((resolve) => {
|
|
if (node.classList.contains('loaded')) {
|
|
resolve();
|
|
} else {
|
|
const observer = new MutationObserver((mutations) => {
|
|
mutations.forEach((mutation) => {
|
|
const target = mutation.target as Element;
|
|
if (target.classList.contains('loaded')) {
|
|
observer.disconnect();
|
|
resolve();
|
|
}
|
|
});
|
|
});
|
|
|
|
observer.observe(node, { attributes: true, attributeFilter: ['class'] });
|
|
}
|
|
});
|
|
};
|
|
|
|
const loadScript = (name: string, src: string, nonce: string | undefined = undefined): Promise<void> => {
|
|
if (!import.meta.client) {
|
|
return new Promise((resolve) => {
|
|
resolve();
|
|
});
|
|
}
|
|
|
|
const existingScriptNode = document.querySelector(`script.${name}-script`);
|
|
if (existingScriptNode) {
|
|
return awaitLoadedClass(existingScriptNode);
|
|
}
|
|
|
|
return new Promise((resolve, reject) => {
|
|
const script = document.createElement('script');
|
|
script.setAttribute('src', src);
|
|
if (nonce) {
|
|
script.setAttribute('nonce', nonce);
|
|
}
|
|
script.classList.add(`${name}-script`);
|
|
// script.crossOrigin = 'true'; TODO proper fix (adding it breaks publift)
|
|
script.addEventListener('load', () => {
|
|
script.classList.add('loaded');
|
|
resolve();
|
|
});
|
|
script.addEventListener('error', (event) => {
|
|
script.classList.add('loaded'); // not really loaded, but let's stop other calls from waiting forever, see !493
|
|
reject(new LoadScriptError(name, src, typeof event === 'string' ? event : event.type));
|
|
});
|
|
document.body.appendChild(script);
|
|
});
|
|
};
|
|
|
|
const loadStylesheet = (name: string, src: string): Promise<void> => {
|
|
if (!import.meta.client) {
|
|
return new Promise((resolve) => {
|
|
resolve();
|
|
});
|
|
}
|
|
|
|
const existingLinkNode = document.querySelector(`link.${name}-stylesheet`);
|
|
if (existingLinkNode) {
|
|
return awaitLoadedClass(existingLinkNode);
|
|
}
|
|
|
|
return new Promise((resolve, reject) => {
|
|
const link = document.createElement('link');
|
|
link.setAttribute('rel', 'stylesheet');
|
|
link.setAttribute('href', src);
|
|
link.classList.add(`${name}-stylesheet`);
|
|
link.crossOrigin = 'true';
|
|
link.addEventListener('load', () => {
|
|
link.classList.add('loaded');
|
|
resolve();
|
|
});
|
|
link.addEventListener('error', (event) => {
|
|
link.classList.add('loaded'); // not really loaded, but let's stop other calls from waiting forever, see !493
|
|
reject(new LoadScriptError(name, src, typeof event === 'string' ? event : event.type));
|
|
});
|
|
document.body.appendChild(link);
|
|
});
|
|
};
|
|
|
|
try {
|
|
Settings.defaultLocale = config.intlLocale || config.locale;
|
|
DateTime.now().toFormat('y-MM-dd HH:mm'); // test if locale is supported by luxon
|
|
} catch {
|
|
Settings.defaultLocale = 'en';
|
|
}
|
|
|
|
const datetime = (timestamp: number): string => {
|
|
const dt = DateTime.fromSeconds(timestamp);
|
|
return dt.toFormat('y-MM-dd HH:mm');
|
|
};
|
|
|
|
const date = (timestamp: number): string => {
|
|
const dt = DateTime.fromSeconds(timestamp);
|
|
return dt.toFormat('y-MM-dd');
|
|
};
|
|
|
|
const ulidTime = (ulid: string): number => {
|
|
return decodeTime(ulid) / 1000;
|
|
};
|
|
|
|
const router = useRouter();
|
|
router.afterEach(() => {
|
|
if (typeof window !== 'undefined' && window.fusetag && window.fusetag.pageInit) {
|
|
window.fusetag.pageInit();
|
|
}
|
|
});
|
|
|
|
return {
|
|
provide: {
|
|
translator,
|
|
t,
|
|
te,
|
|
translateForPronoun,
|
|
locales,
|
|
loadScript,
|
|
loadStylesheet,
|
|
datetime,
|
|
date,
|
|
ulidTime,
|
|
},
|
|
};
|
|
});
|