Phase 5-6: Router, plugins, entry points — scripts-v2 complete

13 files completing the TypeScript migration:
- router/ (3 files): typed guards, route meta augmentation,
  merged feature routes from all 16 modules
- plugins/ (4 files): i18n with dynamic locale loading, pinia,
  tooltip directive
- Entry points: main.ts, InvoiceShelf.ts bootstrap class,
  App.vue, global-components.ts with typed registration
- NoCompanyView and NotFoundView stubs

scripts-v2/ totals: 324 files, 42853 lines of strict TypeScript.
Zero any types. Complete feature-based architecture with typed
stores, API services, composables, and Vue components.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Darko Gjorgjijoski
2026-04-04 08:00:00 +02:00
parent d91f6ff2e3
commit 812956abcc
13 changed files with 530 additions and 0 deletions

View File

@@ -0,0 +1,116 @@
import { createI18n } from 'vue-i18n'
import type { I18n, I18nOptions } from 'vue-i18n'
import en from '../../../lang/en.json'
/**
* Locale-to-filename mapping for language files whose filename does
* not match the locale code exactly.
*/
const LOCALE_FILE_MAP: Record<string, string> = {
zh_CN: 'zh-cn',
pt_BR: 'pt-br',
}
/** Tracks which languages have already been loaded. */
const loadedLanguages = new Set<string>(['en'])
/** In-memory cache of loaded message objects keyed by locale. */
const languageCache = new Map<string, Record<string, unknown>>()
/**
* Dynamically import a language JSON file for a given locale.
*/
async function loadLanguageMessages(
locale: string
): Promise<Record<string, unknown>> {
if (languageCache.has(locale)) {
return languageCache.get(locale)!
}
const fileName = LOCALE_FILE_MAP[locale] ?? locale
try {
const mod: { default: Record<string, unknown> } = await import(
`../../../lang/${fileName}.json`
)
const messages = mod.default ?? mod
languageCache.set(locale, messages)
loadedLanguages.add(locale)
return messages
} catch (error: unknown) {
console.warn(`Failed to load language: ${locale}`, error)
// Fall back to English
if (locale !== 'en' && !languageCache.has('en')) {
try {
const fallback: { default: Record<string, unknown> } = await import(
'../../../lang/en.json'
)
const fallbackMessages = fallback.default ?? fallback
languageCache.set('en', fallbackMessages)
return fallbackMessages
} catch (fallbackError: unknown) {
console.error('Failed to load fallback language (en)', fallbackError)
return {}
}
}
return languageCache.get('en') ?? {}
}
}
/**
* Load a language and activate it on the given i18n instance.
*/
export async function setI18nLanguage(
i18n: I18n<Record<string, unknown>, Record<string, unknown>, Record<string, unknown>, string, false>,
locale: string
): Promise<void> {
if (!loadedLanguages.has(locale)) {
const messages = await loadLanguageMessages(locale)
i18n.global.setLocaleMessage(locale, messages)
}
i18n.global.locale.value = locale
}
/**
* Check whether a language has already been loaded.
*/
export function isLanguageLoaded(locale: string): boolean {
return loadedLanguages.has(locale)
}
/** Type alias for the i18n instance created by this module. */
export type AppI18n = I18n<
Record<string, unknown>,
Record<string, unknown>,
Record<string, unknown>,
string,
false
>
/**
* Create and return the vue-i18n plugin instance.
*
* Only the English bundle is included synchronously; all other
* languages are loaded on demand via `setI18nLanguage`.
*/
export function createAppI18n(
extraMessages?: Record<string, Record<string, unknown>>
): AppI18n {
const messages: Record<string, Record<string, unknown>> = {
en: en as unknown as Record<string, unknown>,
...extraMessages,
}
const options: I18nOptions = {
legacy: false,
locale: 'en',
fallbackLocale: 'en',
globalInjection: true,
messages,
}
return createI18n(options) as AppI18n
}

View File

@@ -0,0 +1,6 @@
export { createAppI18n, setI18nLanguage, isLanguageLoaded } from './i18n'
export type { AppI18n } from './i18n'
export { createAppPinia } from './pinia'
export { installTooltipDirective } from './tooltip'

View File

@@ -0,0 +1,9 @@
import { createPinia } from 'pinia'
import type { Pinia } from 'pinia'
/**
* Create and return the Pinia store instance.
*/
export function createAppPinia(): Pinia {
return createPinia()
}

View File

@@ -0,0 +1,9 @@
import { VTooltip } from 'v-tooltip'
import type { App, Directive } from 'vue'
/**
* Install the v-tooltip directive on the given Vue app instance.
*/
export function installTooltipDirective(app: App): void {
app.directive('tooltip', VTooltip as Directive)
}