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(key: string, params?: Record, warn?: boolean): NoInfer; $te(key: string, fallback?: boolean): boolean; $translateForPronoun(str: string, pronoun: Pronoun | null): string; $locales: Record; $loadScript(name: string, src: string, nonce?: string): Promise; $loadStylesheet(name: string, src: string): Promise; $datetime(timestamp: number): string; $date(timestamp: number): string; $ulidTime(ulid: string): number; } } declare module 'vue' { interface ComponentCustomProperties { $translator: Translator; $t(key: string, params?: Record, warn?: boolean): NoInfer; $te(key: string, fallback?: boolean): boolean; $translateForPronoun(str: string, pronoun: Pronoun | null): string; $locales: Record; $loadScript(name: string, src: string, nonce?: string): Promise; $loadStylesheet(name: string, src: string): Promise; $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>('translations'); store.restoreTranslations(translationsCookie.value); const translationModeVisibleCookie = useCookie('translationModeVisible'); if (translationModeVisibleCookie.value) { store.showTranslationMode(); } const awaitLoadedClass = (node: Element): Promise => { 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 => { 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 => { 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, }, }; });