From d2fd64ca9b0bd17797171c12417a99c628420452 Mon Sep 17 00:00:00 2001 From: Roberto Simonetti Date: Fri, 28 Jul 2023 18:06:51 +0200 Subject: [PATCH 01/21] Tools(Inline): start inlining in dev mode --- .../qwik-speak/src/qwik-speak-component.tsx | 6 +- packages/qwik-speak/tools/core/types.ts | 4 + packages/qwik-speak/tools/inline/plugin.ts | 176 ++++++++++++------ 3 files changed, 132 insertions(+), 54 deletions(-) diff --git a/packages/qwik-speak/src/qwik-speak-component.tsx b/packages/qwik-speak/src/qwik-speak-component.tsx index 88f33d5..57de1be 100644 --- a/packages/qwik-speak/src/qwik-speak-component.tsx +++ b/packages/qwik-speak/src/qwik-speak-component.tsx @@ -1,5 +1,5 @@ import { $, component$, Slot, useContextProvider, useServerData, useTask$ } from '@builder.io/qwik'; -import { isDev } from '@builder.io/qwik/build'; +import { isDev, isServer } from '@builder.io/qwik/build'; import type { SpeakConfig, SpeakLocale, SpeakState, TranslationFn } from './types'; import { SpeakContext } from './context'; @@ -46,6 +46,10 @@ export const QwikSpeakProvider = component$((props: QwikSpeakProps) => { } else if (isDev) { logDebug(`Resolved locale: ${resolvedLocale.lang}`); } + if (isDev && isServer) { + // To inline plugin + (globalThis as any).__qsLang = resolvedLocale.lang; + } // Set initial state as object (no reactive) const state: SpeakState = { diff --git a/packages/qwik-speak/tools/core/types.ts b/packages/qwik-speak/tools/core/types.ts index 627ec5d..f53f375 100644 --- a/packages/qwik-speak/tools/core/types.ts +++ b/packages/qwik-speak/tools/core/types.ts @@ -44,6 +44,10 @@ export interface QwikSpeakExtractOptions { * Qwik Speak Inline Vite Plugin Options */ export interface QwikSpeakInlineOptions { + /** + * Application environment:. Default to 'both' serve and build + */ + env?: 'build' | 'both'; /** * The base path. Default to './' */ diff --git a/packages/qwik-speak/tools/inline/plugin.ts b/packages/qwik-speak/tools/inline/plugin.ts index 1e22743..b852a39 100644 --- a/packages/qwik-speak/tools/inline/plugin.ts +++ b/packages/qwik-speak/tools/inline/plugin.ts @@ -1,4 +1,4 @@ -import type { Plugin } from 'vite'; +import type { Plugin, ViteDevServer } from 'vite'; import type { NormalizedOutputOptions, OutputBundle, OutputAsset, OutputChunk } from 'rollup'; import { readFile, readdir, writeFile } from 'fs/promises'; import { createWriteStream, existsSync, mkdirSync } from 'fs'; @@ -19,11 +19,17 @@ const signalAlias = '\\b_fnSignal'; // Logs const missingValues: string[] = []; -const dynamicKeys: string[] = []; -const dynamicParams: string[] = []; +const dynamics: string[] = []; +const missingValueText = (lang: string, key: string) => `${lang} - missing value for key: ${key}`; +const dynamicText = (originalFn: string, text: string) => `dynamic ${text}: ${originalFn.replace(/\s+/g, ' ')} - Make sure the keys are in 'runtimeAssets'`; let baseUrl = false; +// Config +let target: 'ssr' | 'lib' | 'test' | 'client'; +let mode: 'dev' | 'prod'; +let input: string | undefined; + /** * Qwik Speak Inline Vite plugin */ @@ -31,6 +37,7 @@ export function qwikSpeakInline(options: QwikSpeakInlineOptions): Plugin { // Resolve options const resolvedOptions: Required = { ...options, + env: options.env ?? 'both', basePath: options.basePath ?? './', assetsPath: options.assetsPath ?? 'i18n', outDir: options.outDir ?? 'dist', @@ -41,18 +48,50 @@ export function qwikSpeakInline(options: QwikSpeakInlineOptions): Plugin { // Translation data const translation: Translation = Object.fromEntries(resolvedOptions.supportedLangs.map(value => [value, {}])); - // Client or server files - let target: string; - let input: string | undefined; + // Vite dev server + let server: ViteDevServer; + + let devLang: string; + + // Listen to the language change + if (!('__qsLang' in globalThis)) { + Object.defineProperties(globalThis, { + qsLang: { + value: 'string', + writable: true + }, + __qsLang: { + get: function () { + return this.qsLang; + }, + set: function (val) { + this.qsLang = val; + if (devLang && devLang !== this.qsLang) { + // Invalidate to inline new translations + if (server) server.moduleGraph.invalidateAll(); + } + devLang = this.qsLang; + } + } + }); + } const plugin: Plugin = { name: 'vite-plugin-qwik-speak-inline', enforce: 'post', - // Apply only on build - apply: 'build', + apply: resolvedOptions.env === 'build' ? resolvedOptions.env : undefined, configResolved(resolvedConfig) { - target = resolvedConfig.build?.ssr || resolvedConfig.mode === 'ssr' ? 'ssr' : 'client'; + if (resolvedConfig.build?.ssr || resolvedConfig.mode === 'ssr') { + target = 'ssr'; + } else if (resolvedConfig.mode === 'lib') { + target = 'lib'; + } else if (resolvedConfig.mode === 'test') { + target = 'test'; + } else { + target = 'client'; + } + mode = resolvedConfig.isProduction || resolvedConfig.mode === 'production' ? 'prod' : 'dev'; const inputOption = resolvedConfig.build?.rollupOptions?.input; if (inputOption) { @@ -64,15 +103,19 @@ export function qwikSpeakInline(options: QwikSpeakInlineOptions): Plugin { input = input?.split('/')?.pop(); }, + configureServer(_server) { + server = _server; + }, + /** * Load translation files when build starts */ async buildStart() { - if (target === 'client') { - // For all langs - await Promise.all(resolvedOptions.supportedLangs.map(async lang => { - const baseDir = normalize(`${resolvedOptions.basePath}/${resolvedOptions.assetsPath}/${lang}`); - // For all files + // For all langs + await Promise.all(resolvedOptions.supportedLangs.map(async lang => { + const baseDir = normalize(`${resolvedOptions.basePath}/${resolvedOptions.assetsPath}/${lang}`); + // For all files + if (existsSync(baseDir)) { const files = await readdir(baseDir); if (files.length > 0) { @@ -98,16 +141,16 @@ export function qwikSpeakInline(options: QwikSpeakInlineOptions): Plugin { translation[lang] = { ...translation[lang], ...data }; // Shallow merge } - })); - } + } + })); }, /** * Transform functions * Prefer transform hook because unused imports will be removed, unlike renderChunk */ - async transform(code: string, id: string) { - if (target === 'client') { + async transform(code: string, id: string, options) { + if (target === 'client' || options?.ssr === false) { // Filter id if (/\/src\//.test(id) && /\.(js|cjs|mjs|jsx|ts|tsx)$/.test(id)) { // Filter code: usePlural @@ -122,6 +165,12 @@ export function qwikSpeakInline(options: QwikSpeakInlineOptions): Plugin { if (/inlineTranslate/.test(code)) { code = transformInline(code); } + + // Inline + if (mode === 'dev') { + code = inlineAll(code, devLang, resolvedOptions, translation); + } + return code; } } @@ -154,12 +203,11 @@ export function qwikSpeakInline(options: QwikSpeakInlineOptions): Plugin { log.write(`${target}: ` + (input ?? '-') + '\n'); missingValues.forEach(x => log.write(x + '\n')); - dynamicKeys.forEach(x => log.write(x + '\n')); - dynamicParams.forEach(x => log.write(x + '\n')); + dynamics.forEach(x => log.write(x + '\n')); log.write((`Qwik Speak Inline: build ends at ${new Date().toLocaleString()}\n`)); - if (missingValues.length > 0 || dynamicKeys.length > 0 || dynamicParams.length > 0) { + if (missingValues.length > 0 || dynamics.length > 0) { console.log( '\n\x1b[33mQwik Speak Inline warn\x1b[0m\n%s', 'There are missing values or dynamic keys: see ./qwik-speak-inline.log' @@ -201,15 +249,7 @@ export async function writeChunks( // Inline let code = chunk.code; - if (code.includes(inlinePluralPlaceholder)) { - code = inlinePlural(code, inlinePluralPlaceholder, inlinePlaceholder, lang, opts); - } - if (code.includes(inlinePlaceholder)) { - code = inline(code, translation, inlinePlaceholder, lang, opts); - } - if (code.includes(inlineTranslatePlaceholder)) { - code = inline(code, translation, inlineTranslatePlaceholder, lang, opts); - } + code = inlineAll(code, lang, opts, translation); tasks.push(writeFile(filename, code)); // Original chunks to default lang @@ -360,6 +400,24 @@ export function transformPlural(code: string): string { return code; } +export function inlineAll( + code: string, + lang: string, + opts: Required, + translation: Translation +) { + if (code.includes(inlinePluralPlaceholder)) { + code = inlinePlural(code, inlinePluralPlaceholder, inlinePlaceholder, lang, opts); + } + if (code.includes(inlinePlaceholder)) { + code = inline(code, translation, inlinePlaceholder, lang, opts); + } + if (code.includes(inlineTranslatePlaceholder)) { + code = inline(code, translation, inlineTranslatePlaceholder, lang, opts); + } + return code; +} + export function inline( code: string, translation: Translation, @@ -396,7 +454,7 @@ export function inline( opts.keySeparator ); if (!value) { - missingValues.push(`${resolvedLang} - missing value for key: ${key}`); + logMissingValue(resolvedLang, key); if (defaultValue) { keyValues.push(quoteValue(defaultValue)); } else { @@ -417,7 +475,7 @@ export function inline( opts.keySeparator ); if (!value) { - missingValues.push(`${resolvedLang} - missing value for key: ${key}`); + logMissingValue(resolvedLang, key); if (defaultValue) { resolvedValue = quoteValue(defaultValue); } @@ -536,16 +594,12 @@ export function checkDynamic(args: Argument[], originalFn: string): boolean { if (args?.[0]?.value) { // Dynamic key if (args[0].type === 'Identifier') { - dynamicKeys.push( - `dynamic key: ${originalFn.replace(/\s+/g, ' ')} - Make sure the keys are in 'runtimeAssets'` - ) + logDynamic(originalFn, 'key'); return true; } if (args[0].type === 'Literal') { if (/\${.*}/.test(args[0].value)) { - dynamicKeys.push( - `dynamic key: ${originalFn.replace(/\s+/g, ' ')} - Make sure the keys are in 'runtimeAssets'` - ) + logDynamic(originalFn, 'key'); return true; } } @@ -553,9 +607,7 @@ export function checkDynamic(args: Argument[], originalFn: string): boolean { // Dynamic argument (params, lang) if (args[1]?.type === 'Identifier' || args[1]?.type === 'CallExpression' || args[2]?.type === 'Identifier' || args[2]?.type === 'CallExpression') { - dynamicParams.push( - `dynamic params: ${originalFn.replace(/\s+/g, ' ')} - Make sure the keys are in 'runtimeAssets'` - ); + logDynamic(originalFn, 'params'); return true; } } @@ -566,16 +618,12 @@ export function checkDynamicInline(args: Argument[], originalFn: string): boolea if (args?.[0]?.value) { // Dynamic key if (args[0].type === 'Identifier') { - dynamicKeys.push( - `dynamic key: ${originalFn.replace(/\s+/g, ' ')} - Make sure the keys are in 'runtimeAssets'` - ) + logDynamic(originalFn, 'key'); return true; } if (args[0].type === 'Literal') { if (/\${.*}/.test(args[0].value)) { - dynamicKeys.push( - `dynamic key: ${originalFn.replace(/\s+/g, ' ')} - Make sure the keys are in 'runtimeAssets'` - ) + logDynamic(originalFn, 'key'); return true; } } @@ -583,9 +631,7 @@ export function checkDynamicInline(args: Argument[], originalFn: string): boolea // Dynamic argument (params, lang) if (args[2]?.type === 'Identifier' || args[2]?.type === 'CallExpression' || args[3]?.type === 'Identifier' || args[3]?.type === 'CallExpression') { - dynamicParams.push(` - dynamic params: ${originalFn.replace(/\s+/g, ' ')} - Make sure the keys are in 'runtimeAssets'` - ); + logDynamic(originalFn, 'params'); return true; } } @@ -599,9 +645,7 @@ export function checkDynamicPlural(args: Argument[], originalFn: string): boolea args[2]?.type === 'Identifier' || args[2]?.type === 'CallExpression' || args[3]?.type === 'Identifier' || args[3]?.type === 'CallExpression' || args[4]?.type === 'Identifier' || args[4]?.type === 'CallExpression') { - dynamicParams.push( - `dynamic plural: ${originalFn.replace(/\s+/g, ' ')} - Make sure the keys are in 'runtimeAssets'` - ); + logDynamic(originalFn, 'params'); return true; } } @@ -714,6 +758,32 @@ export function stringifyObject(value: Translation): string { return strValue; } +export function logMissingValue(lang: string, key: string) { + const text = missingValueText(lang, key); + if (!missingValues.includes(text)) { + missingValues.push(text); + if (mode === 'dev') { + console.log( + '\x1b[33mQwik Speak Inline warn\x1b[0m %s', + missingValueText(lang, key) + ); + } + } +} + +export function logDynamic(originalFn: string, type: 'key' | 'params') { + const text = dynamicText(originalFn, type); + if (!dynamics.includes(text)) { + dynamics.push(text); + if (mode === 'dev') { + console.log( + '\x1b[36mQwik Speak Inline info\x1b[0m %s', + dynamicText(originalFn, type) + ); + } + } +} + /** * Replace quoted values with a placeholder */ From f59a39ed3be0b579bb0ce38d6b455ed05b2803d8 Mon Sep 17 00:00:00 2001 From: Roberto Simonetti Date: Mon, 31 Jul 2023 09:22:49 +0200 Subject: [PATCH 02/21] Tools(Inline): add QwikSpeakInline component --- packages/qwik-speak/src/index.ts | 1 + .../qwik-speak/src/qwik-speak-component.tsx | 6 +- .../src/qwik-speak-inline-component.tsx | 27 +++++ packages/qwik-speak/tools/inline/plugin.ts | 106 ++++++++++-------- .../qwik-speak/tools/tests/inline.test.ts | 5 +- src/root.tsx | 3 +- 6 files changed, 95 insertions(+), 53 deletions(-) create mode 100644 packages/qwik-speak/src/qwik-speak-inline-component.tsx diff --git a/packages/qwik-speak/src/index.ts b/packages/qwik-speak/src/index.ts index c392d15..d4de80e 100644 --- a/packages/qwik-speak/src/index.ts +++ b/packages/qwik-speak/src/index.ts @@ -19,6 +19,7 @@ export type { DisplayNameFn } from './use-display-name'; // Components export { QwikSpeakProvider } from './qwik-speak-component'; export { Speak } from './speak-component'; +export { QwikSpeakInline } from './qwik-speak-inline-component'; // Functions export { inlineTranslate } from './inline-translate'; // Use functions diff --git a/packages/qwik-speak/src/qwik-speak-component.tsx b/packages/qwik-speak/src/qwik-speak-component.tsx index 57de1be..88f33d5 100644 --- a/packages/qwik-speak/src/qwik-speak-component.tsx +++ b/packages/qwik-speak/src/qwik-speak-component.tsx @@ -1,5 +1,5 @@ import { $, component$, Slot, useContextProvider, useServerData, useTask$ } from '@builder.io/qwik'; -import { isDev, isServer } from '@builder.io/qwik/build'; +import { isDev } from '@builder.io/qwik/build'; import type { SpeakConfig, SpeakLocale, SpeakState, TranslationFn } from './types'; import { SpeakContext } from './context'; @@ -46,10 +46,6 @@ export const QwikSpeakProvider = component$((props: QwikSpeakProps) => { } else if (isDev) { logDebug(`Resolved locale: ${resolvedLocale.lang}`); } - if (isDev && isServer) { - // To inline plugin - (globalThis as any).__qsLang = resolvedLocale.lang; - } // Set initial state as object (no reactive) const state: SpeakState = { diff --git a/packages/qwik-speak/src/qwik-speak-inline-component.tsx b/packages/qwik-speak/src/qwik-speak-inline-component.tsx new file mode 100644 index 0000000..2fec861 --- /dev/null +++ b/packages/qwik-speak/src/qwik-speak-inline-component.tsx @@ -0,0 +1,27 @@ +import { component$, Slot, useVisibleTask$ } from '@builder.io/qwik'; +import { isDev } from '@builder.io/qwik/build'; + +import { useSpeakLocale } from './use-speak'; + +/** + * Create and provide Qwik Speak inline in dev mode + */ +export const QwikSpeakInline = component$(() => { + const locale = useSpeakLocale(); + + // In dev mode, send lang from client to the server + useVisibleTask$(() => { + if (isDev) { + console.debug( + '%cQwik Speak Inline', + 'background: #0c75d2; color: white; padding: 2px 3px; border-radius: 2px; font-size: 0.8em;', + 'ready' + ); + if (import.meta.hot) { + import.meta.hot.send('qwik-speak:lang', { msg: locale.lang }); + } + } + }, { strategy: 'document-ready' }); + + return ; +}); diff --git a/packages/qwik-speak/tools/inline/plugin.ts b/packages/qwik-speak/tools/inline/plugin.ts index b852a39..ef51941 100644 --- a/packages/qwik-speak/tools/inline/plugin.ts +++ b/packages/qwik-speak/tools/inline/plugin.ts @@ -1,4 +1,4 @@ -import type { Plugin, ViteDevServer } from 'vite'; +import type { Plugin } from 'vite'; import type { NormalizedOutputOptions, OutputBundle, OutputAsset, OutputChunk } from 'rollup'; import { readFile, readdir, writeFile } from 'fs/promises'; import { createWriteStream, existsSync, mkdirSync } from 'fs'; @@ -23,13 +23,14 @@ const dynamics: string[] = []; const missingValueText = (lang: string, key: string) => `${lang} - missing value for key: ${key}`; const dynamicText = (originalFn: string, text: string) => `dynamic ${text}: ${originalFn.replace(/\s+/g, ' ')} - Make sure the keys are in 'runtimeAssets'`; -let baseUrl = false; - // Config let target: 'ssr' | 'lib' | 'test' | 'client'; let mode: 'dev' | 'prod'; let input: string | undefined; +// Inlined modules +const moduleIds = new Set(); + /** * Qwik Speak Inline Vite plugin */ @@ -48,38 +49,13 @@ export function qwikSpeakInline(options: QwikSpeakInlineOptions): Plugin { // Translation data const translation: Translation = Object.fromEntries(resolvedOptions.supportedLangs.map(value => [value, {}])); - // Vite dev server - let server: ViteDevServer; - + // Current lang let devLang: string; - // Listen to the language change - if (!('__qsLang' in globalThis)) { - Object.defineProperties(globalThis, { - qsLang: { - value: 'string', - writable: true - }, - __qsLang: { - get: function () { - return this.qsLang; - }, - set: function (val) { - this.qsLang = val; - if (devLang && devLang !== this.qsLang) { - // Invalidate to inline new translations - if (server) server.moduleGraph.invalidateAll(); - } - devLang = this.qsLang; - } - } - }); - } - const plugin: Plugin = { name: 'vite-plugin-qwik-speak-inline', enforce: 'post', - apply: resolvedOptions.env === 'build' ? resolvedOptions.env : undefined, + apply: resolvedOptions.env === 'build' ? resolvedOptions.env : undefined, // both configResolved(resolvedConfig) { if (resolvedConfig.build?.ssr || resolvedConfig.mode === 'ssr') { @@ -103,8 +79,22 @@ export function qwikSpeakInline(options: QwikSpeakInlineOptions): Plugin { input = input?.split('/')?.pop(); }, - configureServer(_server) { - server = _server; + configureServer(server) { + // In dev mode, listen to lang from client + if (mode === 'dev') { + server.ws.on('qwik-speak:lang', (data) => { + if (devLang && devLang !== data.msg) { + // Invalidate inlined modules + for (const id of moduleIds) { + const module = server.moduleGraph.getModuleById(id); + if (module) server.moduleGraph.invalidateModule(module); + } + moduleIds.clear(); + } + // Update current lang + devLang = data.msg; + }); + } }, /** @@ -166,18 +156,51 @@ export function qwikSpeakInline(options: QwikSpeakInlineOptions): Plugin { code = transformInline(code); } - // Inline + // Inline in dev mode if (mode === 'dev') { - code = inlineAll(code, devLang, resolvedOptions, translation); + if (code.includes(inlinePlaceholder) || + code.includes(inlineTranslatePlaceholder) || + code.includes(inlinePluralPlaceholder)) { + code = inlineAll(code, devLang, resolvedOptions, translation); + + moduleIds.add(id); + } } return code; } } + + // Check Inline component in dev mode + if (target === 'ssr' && mode === 'dev') { + if (id.endsWith('root.tsx' || id.endsWith('root.jsx'))) { + if (!/QwikSpeakInline/.test(code)) { + console.log( + '\n\x1b[31mQwik Speak Inline error\x1b[0m\n%s', + "Missing 'QwikSpeakInline' component in 'root.tsx' file: see https://robisim74.gitbook.io/qwik-speak/tools/inline#usage" + ); + process.exit(1) + } + } + } + // Remove Inline component in prod mode + if (target === 'ssr' && mode === 'prod') { + if (id.endsWith('root.tsx' || id.endsWith('root.jsx'))) { + code = code.replace(/_jsxC\(QwikSpeakInline.*\)/, ''); + return code; + } + } + + // Check base url if (target === 'ssr') { - // Base url - if (/(? { '__qsInline', 'en-US', { + env: 'build', supportedLangs: ['en-US', 'it-IT'], defaultLang: 'en-US', keySeparator: '.', @@ -199,6 +200,7 @@ describe('inline', () => { '__qsInline', 'en-US', { + env: 'build', supportedLangs: ['en-US', 'it-IT'], defaultLang: 'en-US', keySeparator: '.', @@ -223,6 +225,7 @@ describe('inline', () => { '__qsInlineTranslate', 'en-US', { + env: 'build', supportedLangs: ['en-US', 'it-IT'], defaultLang: 'en-US', keySeparator: '.', @@ -239,7 +242,7 @@ describe('inline', () => { defaultLang: 'en-US', basePath: '../../' }) as any; - await plugin.configResolved?.({}); + await plugin.configResolved?.({ isProduction: true }); await plugin.buildStart?.(); const transformed = await plugin.transform?.(mockCode, '/src/mock.code.js'); expect(transformed).toBe(mockTransformedCode); diff --git a/src/root.tsx b/src/root.tsx index 4d7618f..c10e00c 100644 --- a/src/root.tsx +++ b/src/root.tsx @@ -1,6 +1,6 @@ import { component$ } from '@builder.io/qwik'; import { QwikCityProvider, RouterOutlet, ServiceWorkerRegister } from '@builder.io/qwik-city'; -import { QwikSpeakProvider } from 'qwik-speak'; +import { QwikSpeakInline, QwikSpeakProvider } from 'qwik-speak'; import { RouterHead } from './components/router-head/router-head'; import { config } from './speak-config'; @@ -23,6 +23,7 @@ export default component$(() => { + From 53383f72bc8c9c1aeab3e48a6d6d643269078091 Mon Sep 17 00:00:00 2001 From: Roberto Simonetti Date: Mon, 31 Jul 2023 20:54:46 +0200 Subject: [PATCH 03/21] Tools(Inline): fixes --- packages/qwik-speak/tools/inline/plugin.ts | 60 +++++++++++----------- 1 file changed, 31 insertions(+), 29 deletions(-) diff --git a/packages/qwik-speak/tools/inline/plugin.ts b/packages/qwik-speak/tools/inline/plugin.ts index ef51941..ae64a48 100644 --- a/packages/qwik-speak/tools/inline/plugin.ts +++ b/packages/qwik-speak/tools/inline/plugin.ts @@ -101,38 +101,40 @@ export function qwikSpeakInline(options: QwikSpeakInlineOptions): Plugin { * Load translation files when build starts */ async buildStart() { - // For all langs - await Promise.all(resolvedOptions.supportedLangs.map(async lang => { - const baseDir = normalize(`${resolvedOptions.basePath}/${resolvedOptions.assetsPath}/${lang}`); - // For all files - if (existsSync(baseDir)) { - const files = await readdir(baseDir); - - if (files.length > 0) { - const ext = extname(files[0]); - let data: Translation = {}; - - const tasks = files.map(filename => readFile(`${baseDir}/${filename}`, 'utf8')); - const sources = await Promise.all(tasks); - - for (const source of sources) { - if (source) { - let parsed: Translation = {}; - - switch (ext) { - case '.json': - parsed = parseJson(source); - break; + if (target === 'client' || mode === 'dev') { + // For all langs + await Promise.all(resolvedOptions.supportedLangs.map(async lang => { + const baseDir = normalize(`${resolvedOptions.basePath}/${resolvedOptions.assetsPath}/${lang}`); + // For all files + if (existsSync(baseDir)) { + const files = await readdir(baseDir); + + if (files.length > 0) { + const ext = extname(files[0]); + let data: Translation = {}; + + const tasks = files.map(filename => readFile(`${baseDir}/${filename}`, 'utf8')); + const sources = await Promise.all(tasks); + + for (const source of sources) { + if (source) { + let parsed: Translation = {}; + + switch (ext) { + case '.json': + parsed = parseJson(source); + break; + } + + data = merge(data, parsed); } - - data = merge(data, parsed); } - } - translation[lang] = { ...translation[lang], ...data }; // Shallow merge + translation[lang] = { ...translation[lang], ...data }; // Shallow merge + } } - } - })); + })); + } }, /** @@ -140,7 +142,7 @@ export function qwikSpeakInline(options: QwikSpeakInlineOptions): Plugin { * Prefer transform hook because unused imports will be removed, unlike renderChunk */ async transform(code: string, id: string, options) { - if (target === 'client' || options?.ssr === false) { + if (target === 'client' || (target === 'ssr' && options?.ssr === false)) { // Filter id if (/\/src\//.test(id) && /\.(js|cjs|mjs|jsx|ts|tsx)$/.test(id)) { // Filter code: usePlural From 29410f984c176a224caed88fa96a7438dee4004e Mon Sep 17 00:00:00 2001 From: Roberto Simonetti Date: Sat, 11 Nov 2023 17:07:54 +0100 Subject: [PATCH 04/21] Tools(Inline): useInline in dev --- packages/qwik-speak/src/core.ts | 13 +++++---- packages/qwik-speak/src/index.ts | 2 ++ .../src/qwik-speak-inline-component.tsx | 27 ------------------- packages/qwik-speak/src/useInline.ts | 19 +++++++++++++ packages/qwik-speak/tools/core/types.ts | 4 --- packages/qwik-speak/tools/inline/plugin.ts | 27 ++++++------------- src/root.tsx | 2 +- 7 files changed, 36 insertions(+), 58 deletions(-) delete mode 100644 packages/qwik-speak/src/qwik-speak-inline-component.tsx create mode 100644 packages/qwik-speak/src/useInline.ts diff --git a/packages/qwik-speak/src/core.ts b/packages/qwik-speak/src/core.ts index 5be6522..5ca10cf 100644 --- a/packages/qwik-speak/src/core.ts +++ b/packages/qwik-speak/src/core.ts @@ -21,10 +21,9 @@ export const memoize = (fn: LoadTranslationFn) => { /** * Load translations when: - * - dev mode * - on server * - or runtime assets - * In prod mode, assets are not serialized + * Assets are not serialized */ export const loadTranslations = async ( ctx: SpeakState, @@ -32,7 +31,7 @@ export const loadTranslations = async ( runtimeAssets?: string[], langs?: string[] ): Promise => { - if (isDev || isServer || runtimeAssets) { + if (isServer || runtimeAssets) { const { locale, translation, translationFn, config } = ctx; if (isDev) { @@ -45,7 +44,7 @@ export const loadTranslations = async ( } let resolvedAssets: string[]; - if (isDev || isServer) { + if (isServer) { resolvedAssets = [...assets ?? [], ...runtimeAssets ?? []]; } else { resolvedAssets = [...runtimeAssets ?? []]; @@ -67,8 +66,8 @@ export const loadTranslations = async ( for (const data of assetSources) { if (data?.source) { - if (!isDev && isServer && assets?.includes(data.asset)) { - // In prod mode, assets are not serialized + if (isServer && assets?.includes(data.asset)) { + // Assets are not serialized for (let [key, value] of Object.entries(data.source)) { // Depth 0: convert string to String object if (typeof value === 'string') { @@ -77,7 +76,7 @@ export const loadTranslations = async ( translation[lang][key] = noSerialize(value); } } else { - // Serialize whether dev mode, or runtime assets + // Serialize whether runtime assets Object.assign(translation[lang], data.source); } } diff --git a/packages/qwik-speak/src/index.ts b/packages/qwik-speak/src/index.ts index 15df14b..0b266bb 100644 --- a/packages/qwik-speak/src/index.ts +++ b/packages/qwik-speak/src/index.ts @@ -36,3 +36,5 @@ export { useSpeakLocale, useSpeakConfig, } from './use-speak'; +// Internals +export { useInline } from './useInline'; diff --git a/packages/qwik-speak/src/qwik-speak-inline-component.tsx b/packages/qwik-speak/src/qwik-speak-inline-component.tsx deleted file mode 100644 index 2fec861..0000000 --- a/packages/qwik-speak/src/qwik-speak-inline-component.tsx +++ /dev/null @@ -1,27 +0,0 @@ -import { component$, Slot, useVisibleTask$ } from '@builder.io/qwik'; -import { isDev } from '@builder.io/qwik/build'; - -import { useSpeakLocale } from './use-speak'; - -/** - * Create and provide Qwik Speak inline in dev mode - */ -export const QwikSpeakInline = component$(() => { - const locale = useSpeakLocale(); - - // In dev mode, send lang from client to the server - useVisibleTask$(() => { - if (isDev) { - console.debug( - '%cQwik Speak Inline', - 'background: #0c75d2; color: white; padding: 2px 3px; border-radius: 2px; font-size: 0.8em;', - 'ready' - ); - if (import.meta.hot) { - import.meta.hot.send('qwik-speak:lang', { msg: locale.lang }); - } - } - }, { strategy: 'document-ready' }); - - return ; -}); diff --git a/packages/qwik-speak/src/useInline.ts b/packages/qwik-speak/src/useInline.ts new file mode 100644 index 0000000..67c2073 --- /dev/null +++ b/packages/qwik-speak/src/useInline.ts @@ -0,0 +1,19 @@ +import { useVisibleTask$ } from '@builder.io/qwik'; + +import { useSpeakLocale } from './use-speak'; + +export const useInline = () => { + const locale = useSpeakLocale(); + + // In dev mode, send lang from client to the server + useVisibleTask$(() => { + console.debug( + '%cQwik Speak Inline', + 'background: #0c75d2; color: white; padding: 2px 3px; border-radius: 2px; font-size: 0.8em;', + 'ready' + ); + if (import.meta.hot) { + import.meta.hot.send('qwik-speak:lang', { msg: locale.lang }); + } + }, { strategy: 'document-ready' }); +}; diff --git a/packages/qwik-speak/tools/core/types.ts b/packages/qwik-speak/tools/core/types.ts index f53f375..627ec5d 100644 --- a/packages/qwik-speak/tools/core/types.ts +++ b/packages/qwik-speak/tools/core/types.ts @@ -44,10 +44,6 @@ export interface QwikSpeakExtractOptions { * Qwik Speak Inline Vite Plugin Options */ export interface QwikSpeakInlineOptions { - /** - * Application environment:. Default to 'both' serve and build - */ - env?: 'build' | 'both'; /** * The base path. Default to './' */ diff --git a/packages/qwik-speak/tools/inline/plugin.ts b/packages/qwik-speak/tools/inline/plugin.ts index ae64a48..a571ab2 100644 --- a/packages/qwik-speak/tools/inline/plugin.ts +++ b/packages/qwik-speak/tools/inline/plugin.ts @@ -38,7 +38,6 @@ export function qwikSpeakInline(options: QwikSpeakInlineOptions): Plugin { // Resolve options const resolvedOptions: Required = { ...options, - env: options.env ?? 'both', basePath: options.basePath ?? './', assetsPath: options.assetsPath ?? 'i18n', outDir: options.outDir ?? 'dist', @@ -55,7 +54,7 @@ export function qwikSpeakInline(options: QwikSpeakInlineOptions): Plugin { const plugin: Plugin = { name: 'vite-plugin-qwik-speak-inline', enforce: 'post', - apply: resolvedOptions.env === 'build' ? resolvedOptions.env : undefined, // both + apply: undefined, // both configResolved(resolvedConfig) { if (resolvedConfig.build?.ssr || resolvedConfig.mode === 'ssr') { @@ -173,29 +172,17 @@ export function qwikSpeakInline(options: QwikSpeakInlineOptions): Plugin { } } - // Check Inline component in dev mode + // Add useInline in dev mode if (target === 'ssr' && mode === 'dev') { - if (id.endsWith('root.tsx' || id.endsWith('root.jsx'))) { - if (!/QwikSpeakInline/.test(code)) { - console.log( - '\n\x1b[31mQwik Speak Inline error\x1b[0m\n%s', - "Missing 'QwikSpeakInline' component in 'root.tsx' file: see https://robisim74.gitbook.io/qwik-speak/tools/inline#usage" - ); - process.exit(1) - } - } - } - // Remove Inline component in prod mode - if (target === 'ssr' && mode === 'prod') { - if (id.endsWith('root.tsx' || id.endsWith('root.jsx'))) { - code = code.replace(/_jsxC\(QwikSpeakInline.*\)/, ''); - return code; + if (id.endsWith('router-head.tsx') || id.endsWith('router-head.jsx')) { + code = code.replace(/^/, `import { useInline } from 'qwik-speak';\n`); + code = code.replace('return /*#__PURE__*/ _jsxC', `useInline();\nreturn /*#__PURE__*/ _jsxC`); } } // Check base url if (target === 'ssr') { - if (id.endsWith('entry.ssr.tsx' || id.endsWith('entry.ssr.jsx'))) { + if (id.endsWith('entry.ssr.tsx') || id.endsWith('entry.ssr.jsx')) { if (!/(? Date: Sun, 12 Nov 2023 21:40:11 +0100 Subject: [PATCH 05/21] Fix: handle json changes --- packages/qwik-speak/src/core.ts | 11 +- packages/qwik-speak/tools/inline/plugin.ts | 115 ++++++++++-------- .../qwik-speak/tools/tests/inline.test.ts | 3 - 3 files changed, 76 insertions(+), 53 deletions(-) diff --git a/packages/qwik-speak/src/core.ts b/packages/qwik-speak/src/core.ts index 5ca10cf..66cba2d 100644 --- a/packages/qwik-speak/src/core.ts +++ b/packages/qwik-speak/src/core.ts @@ -56,8 +56,15 @@ export const loadTranslations = async ( resolvedLangs.add(locale.lang); for (const lang of resolvedLangs) { - const memoized = memoize(translationFn.loadTranslation$); - const tasks = resolvedAssets.map(asset => memoized(lang, asset)); + let tasks: Promise[]; + // Cache requests in prod mode + if (!isDev) { + const memoized = memoize(translationFn.loadTranslation$); + tasks = resolvedAssets.map(asset => memoized(lang, asset)); + } else { + tasks = resolvedAssets.map(asset => translationFn.loadTranslation$(lang, asset)); + } + const sources = await Promise.all(tasks); const assetSources = sources.map((source, i) => ({ asset: resolvedAssets[i], diff --git a/packages/qwik-speak/tools/inline/plugin.ts b/packages/qwik-speak/tools/inline/plugin.ts index a571ab2..a67b08e 100644 --- a/packages/qwik-speak/tools/inline/plugin.ts +++ b/packages/qwik-speak/tools/inline/plugin.ts @@ -96,43 +96,34 @@ export function qwikSpeakInline(options: QwikSpeakInlineOptions): Plugin { } }, + handleHotUpdate({ file, server }) { + // Filter json + if (new RegExp(resolvedOptions.assetsPath).test(file) && /\.(json)$/.test(file)) { + for (const lang of resolvedOptions.supportedLangs) { + if (new RegExp(lang).test(file)) { + loadTranslations(translation, [lang], resolvedOptions.basePath, resolvedOptions.assetsPath); + } + } + // Invalidate inlined modules + for (const id of moduleIds) { + const module = server.moduleGraph.getModuleById(id); + if (module) server.moduleGraph.invalidateModule(module); + } + moduleIds.clear(); + } + }, + /** * Load translation files when build starts */ async buildStart() { if (target === 'client' || mode === 'dev') { - // For all langs - await Promise.all(resolvedOptions.supportedLangs.map(async lang => { - const baseDir = normalize(`${resolvedOptions.basePath}/${resolvedOptions.assetsPath}/${lang}`); - // For all files - if (existsSync(baseDir)) { - const files = await readdir(baseDir); - - if (files.length > 0) { - const ext = extname(files[0]); - let data: Translation = {}; - - const tasks = files.map(filename => readFile(`${baseDir}/${filename}`, 'utf8')); - const sources = await Promise.all(tasks); - - for (const source of sources) { - if (source) { - let parsed: Translation = {}; - - switch (ext) { - case '.json': - parsed = parseJson(source); - break; - } - - data = merge(data, parsed); - } - } - - translation[lang] = { ...translation[lang], ...data }; // Shallow merge - } - } - })); + await loadTranslations( + translation, + resolvedOptions.supportedLangs, + resolvedOptions.basePath, + resolvedOptions.assetsPath + ); } }, @@ -177,11 +168,13 @@ export function qwikSpeakInline(options: QwikSpeakInlineOptions): Plugin { if (id.endsWith('router-head.tsx') || id.endsWith('router-head.jsx')) { code = code.replace(/^/, `import { useInline } from 'qwik-speak';\n`); code = code.replace('return /*#__PURE__*/ _jsxC', `useInline();\nreturn /*#__PURE__*/ _jsxC`); + + return code; } } - // Check base url - if (target === 'ssr') { + // Check base url in prod mode + if (target === 'ssr' && mode === 'prod') { if (id.endsWith('entry.ssr.tsx') || id.endsWith('entry.ssr.jsx')) { if (!/(? { + const baseDir = normalize(`${basePath}/${assetsPath}/${lang}`); + // For all files + if (existsSync(baseDir)) { + const files = await readdir(baseDir); + + if (files.length > 0) { + const ext = extname(files[0]); + let data: Translation = {}; + + const tasks = files.map(filename => readFile(`${baseDir}/${filename}`, 'utf8')); + const sources = await Promise.all(tasks); + + for (const source of sources) { + if (source) { + let parsed: Translation = {}; + + switch (ext) { + case '.json': + parsed = parseJson(source); + break; + } + + data = merge(data, parsed); + } + } + + translation[lang] = { ...translation[lang], ...data }; // Shallow merge + } + } + })); +} + export async function writeChunks( lang: string, bundles: (OutputAsset | OutputChunk)[], @@ -767,12 +798,6 @@ export function logMissingValue(lang: string, key: string) { const text = missingValueText(lang, key); if (!missingValues.includes(text)) { missingValues.push(text); - if (mode === 'dev') { - console.log( - '\x1b[33mQwik Speak Inline warn\x1b[0m %s', - missingValueText(lang, key) - ); - } } } @@ -780,12 +805,6 @@ export function logDynamic(originalFn: string, type: 'key' | 'params') { const text = dynamicText(originalFn, type); if (!dynamics.includes(text)) { dynamics.push(text); - if (mode === 'dev') { - console.log( - '\x1b[36mQwik Speak Inline info\x1b[0m %s', - dynamicText(originalFn, type) - ); - } } } diff --git a/packages/qwik-speak/tools/tests/inline.test.ts b/packages/qwik-speak/tools/tests/inline.test.ts index c0dc015..7599eb1 100644 --- a/packages/qwik-speak/tools/tests/inline.test.ts +++ b/packages/qwik-speak/tools/tests/inline.test.ts @@ -170,7 +170,6 @@ describe('inline', () => { '__qsInline', 'en-US', { - env: 'build', supportedLangs: ['en-US', 'it-IT'], defaultLang: 'en-US', keySeparator: '.', @@ -200,7 +199,6 @@ describe('inline', () => { '__qsInline', 'en-US', { - env: 'build', supportedLangs: ['en-US', 'it-IT'], defaultLang: 'en-US', keySeparator: '.', @@ -225,7 +223,6 @@ describe('inline', () => { '__qsInlineTranslate', 'en-US', { - env: 'build', supportedLangs: ['en-US', 'it-IT'], defaultLang: 'en-US', keySeparator: '.', From d6b8706533a9a7ad22dcb35b7f591901c6698d01 Mon Sep 17 00:00:00 2001 From: Roberto Simonetti Date: Sun, 12 Nov 2023 22:47:55 +0100 Subject: [PATCH 06/21] Tools(Inline): fix default value --- packages/qwik-speak/tools/inline/plugin.ts | 70 +++++++++---------- .../qwik-speak/tools/tests/inline.test.ts | 23 +++--- 2 files changed, 43 insertions(+), 50 deletions(-) diff --git a/packages/qwik-speak/tools/inline/plugin.ts b/packages/qwik-speak/tools/inline/plugin.ts index a67b08e..a9857f6 100644 --- a/packages/qwik-speak/tools/inline/plugin.ts +++ b/packages/qwik-speak/tools/inline/plugin.ts @@ -477,47 +477,35 @@ export function inline( let resolvedValue: string | Translation = quoteValue(''); - // Get array of keys or key if (args[0].type === 'ArrayExpression') { - const keys = getKeys(args[0], opts.keyValueSeparator); + const keys = getKeys(args[0]); const keyValues: (string | Translation)[] = []; - for (const { key, defaultValue } of keys) { + for (const key of keys) { const value = getValue( key, translation[resolvedLang], placeholder === inlinePlaceholder ? args[1] : args[2], - opts.keySeparator + opts.keySeparator, + opts.keyValueSeparator, + resolvedLang ); - if (!value) { - logMissingValue(resolvedLang, key); - if (defaultValue) { - keyValues.push(quoteValue(defaultValue)); - } else { - keyValues.push(quoteValue('')); - } - } else { - keyValues.push(value); - } + keyValues.push(value); } resolvedValue = keyValues; } else if (args?.[0]?.value) { - const { key, defaultValue } = getKey(args[0].value, opts.keyValueSeparator); + const key = getKey(args[0]); const value = getValue( key, translation[resolvedLang], placeholder === inlinePlaceholder ? args[1] : args[2], - opts.keySeparator + opts.keySeparator, + opts.keyValueSeparator, + resolvedLang ); - if (!value) { - logMissingValue(resolvedLang, key); - if (defaultValue) { - resolvedValue = quoteValue(defaultValue); - } - } else { - resolvedValue = value; - } + + resolvedValue = value; } // Transpile @@ -699,21 +687,16 @@ export function withLang(lang: string, arg: Argument, opts: Required (acc && acc[cur] !== undefined) ? acc[cur] : @@ -738,9 +727,18 @@ export function getValue( if (value) { if (typeof value === 'string') return params ? transpileParams(value, params) : quoteValue(value); if (typeof value === 'object') return params ? transpileObjectParams(value, params) : value; + } else if (lang) { + logMissingValue(lang, key); + } + + if (defaultValue) { + if (!/^[[{].*[\]}]$/.test(defaultValue) || /^{{/.test(defaultValue)) + return params ? transpileParams(defaultValue, params) : quoteValue(defaultValue); + // Default value is an array/object + return params ? transpileObjectParams(JSON.parse(defaultValue), params) : JSON.parse(defaultValue); } - return undefined; + return mode === 'dev' ? key : quoteValue(''); } export function transpileObjectParams(value: Translation, params?: Argument): Translation { diff --git a/packages/qwik-speak/tools/tests/inline.test.ts b/packages/qwik-speak/tools/tests/inline.test.ts index 7599eb1..4e23a32 100644 --- a/packages/qwik-speak/tools/tests/inline.test.ts +++ b/packages/qwik-speak/tools/tests/inline.test.ts @@ -33,18 +33,13 @@ vi.mock('fs/promises', async () => { }); describe('inline', () => { - test('getKey', () => { - const { key, defaultValue } = getKey('key1@@Key1', '@@'); - expect(key).toBe('key1'); - expect(defaultValue).toBe('Key1'); - }); test('getValue', () => { - let value = getValue('key1', { key1: 'Key1' }, undefined, '.'); + let value = getValue('key1', { key1: 'Key1' }, undefined, '.', '@@'); expect(value).toBe('`Key1`'); - value = getValue('key1.subkey1', { key1: { subkey1: 'Subkey1' } }, undefined, '.'); + value = getValue('key1.subkey1', { key1: { subkey1: 'Subkey1' } }, undefined, '.', '@@'); expect(value).toBe('`Subkey1`'); - value = getValue('key1.subkey2', { key1: { subkey1: 'Subkey1' } }, undefined, '.'); - expect(value).toBeUndefined(); + value = getValue('key1.subkey2', { key1: { subkey1: 'Subkey1' } }, undefined, '.', '@@'); + expect(value).toBe('``'); value = getValue('key1', { key1: 'Key1 {{param1}}' }, { type: 'ObjectExpression', properties: [ { @@ -53,7 +48,7 @@ describe('inline', () => { value: { type: 'Literal', value: 'Param1' } } ] - }, '.'); + }, '.', '@@'); expect(value).toBe('`Key1 Param1`'); value = getValue('key1', { key1: 'Key1 {{param1}} and {{param2}}' }, { @@ -69,7 +64,7 @@ describe('inline', () => { value: { type: 'Expression', value: 'variable' } } ] - }, '.'); + }, '.', '@@'); expect(value).toBe('`Key1 Param1 and ${variable}`'); value = getValue('key1', { key1: 'Key1' }, { type: 'ObjectExpression', properties: [ @@ -79,7 +74,7 @@ describe('inline', () => { value: { type: 'Literal', value: 'Param1' } } ] - }, '.'); + }, '.', '@@'); expect(value).toBe('`Key1`'); value = getValue('key1', { key1: 'Key1 {{param1}}' }, { type: 'ObjectExpression', properties: [ @@ -89,7 +84,7 @@ describe('inline', () => { value: { type: 'Literal', value: 'Param2' } } ] - }, '.'); + }, '.', '@@'); expect(value).toBe('`Key1 {{param1}}`'); value = getValue('key1', { key1: 'Key1 {{param1}}' }, { @@ -100,7 +95,7 @@ describe('inline', () => { value: { type: 'Literal', value: 'Param1 ${variable}' } } ] - }, '.'); + }, '.', '@@'); expect(value).toBe('`Key1 Param1 ${variable}`'); }); test('transpileFn', () => { From 5268455f080e18cd1750ad59b83cbbe90a58d821 Mon Sep 17 00:00:00 2001 From: Roberto Simonetti Date: Mon, 13 Nov 2023 18:27:16 +0100 Subject: [PATCH 07/21] Tools(Inline): fix useInline --- packages/qwik-speak/src/index.ts | 2 +- packages/qwik-speak/src/{useInline.ts => use-inline.ts} | 0 packages/qwik-speak/tools/inline/plugin.ts | 2 +- packages/qwik-speak/tools/tests/inline.test.ts | 2 +- packages/qwik-speak/vite.config.ts | 1 + 5 files changed, 4 insertions(+), 3 deletions(-) rename packages/qwik-speak/src/{useInline.ts => use-inline.ts} (100%) diff --git a/packages/qwik-speak/src/index.ts b/packages/qwik-speak/src/index.ts index 0b266bb..186627b 100644 --- a/packages/qwik-speak/src/index.ts +++ b/packages/qwik-speak/src/index.ts @@ -37,4 +37,4 @@ export { useSpeakConfig, } from './use-speak'; // Internals -export { useInline } from './useInline'; +export { useInline } from './use-inline'; diff --git a/packages/qwik-speak/src/useInline.ts b/packages/qwik-speak/src/use-inline.ts similarity index 100% rename from packages/qwik-speak/src/useInline.ts rename to packages/qwik-speak/src/use-inline.ts diff --git a/packages/qwik-speak/tools/inline/plugin.ts b/packages/qwik-speak/tools/inline/plugin.ts index a9857f6..1a97248 100644 --- a/packages/qwik-speak/tools/inline/plugin.ts +++ b/packages/qwik-speak/tools/inline/plugin.ts @@ -738,7 +738,7 @@ export function getValue( return params ? transpileObjectParams(JSON.parse(defaultValue), params) : JSON.parse(defaultValue); } - return mode === 'dev' ? key : quoteValue(''); + return mode === 'dev' ? quoteValue(key) : quoteValue(''); } export function transpileObjectParams(value: Translation, params?: Argument): Translation { diff --git a/packages/qwik-speak/tools/tests/inline.test.ts b/packages/qwik-speak/tools/tests/inline.test.ts index 4e23a32..b4e6d59 100644 --- a/packages/qwik-speak/tools/tests/inline.test.ts +++ b/packages/qwik-speak/tools/tests/inline.test.ts @@ -4,7 +4,7 @@ import { writeFile } from 'fs/promises'; import { normalize } from 'path'; import { getRules } from '../core/intl-parser'; -import { getKey, getValue, inline, qwikSpeakInline, transform, transformInline, transpileFn, transpilePluralFn } from '../inline/plugin'; +import { getValue, inline, qwikSpeakInline, transform, transformInline, transpileFn, transpilePluralFn } from '../inline/plugin'; import { mockChunkCode, mockCode, mockInlinedCode, mockInlinedCodeByLang, mockTransformedCode, mockTranslatedAsset, mockTranslatedAssetByLang } from './mock'; // Mock part of 'fs' module diff --git a/packages/qwik-speak/vite.config.ts b/packages/qwik-speak/vite.config.ts index fc0c0e4..38622e6 100644 --- a/packages/qwik-speak/vite.config.ts +++ b/packages/qwik-speak/vite.config.ts @@ -18,5 +18,6 @@ export default defineConfig(() => { } }, plugins: [qwikVite()], + define: { 'import.meta.hot': 'import.meta.hot' } }; }); From ab3b11ad9cbcf1befb7165706f61dcdc5166fc85 Mon Sep 17 00:00:00 2001 From: Roberto Simonetti Date: Tue, 14 Nov 2023 16:38:49 +0100 Subject: [PATCH 08/21] Tools(Inline): validate props --- docs/quick-start.md | 2 +- docs/translate.md | 28 ++- docs/tutorial-routing-rewrite.md | 2 +- docs/tutorial-routing.md | 2 +- package.json | 2 +- packages/qwik-speak/src/use-display-name.ts | 3 +- packages/qwik-speak/src/use-format-date.ts | 3 +- packages/qwik-speak/src/use-format-number.ts | 3 +- packages/qwik-speak/src/use-plural.ts | 3 +- packages/qwik-speak/src/use-relative-time.ts | 3 +- packages/qwik-speak/src/use-translate-path.ts | 3 +- packages/qwik-speak/src/use-translate.ts | 3 +- .../qwik-speak/tests/use-translate.test.tsx | 10 +- packages/qwik-speak/tools/core/parser.ts | 2 +- packages/qwik-speak/tools/inline/plugin.ts | 152 +++++++----- .../qwik-speak/tools/tests/inline.test.ts | 2 +- packages/qwik-speak/tools/tests/mock.ts | 231 ++++++++---------- src/routes/[...lang]/index.tsx | 6 +- src/routes/[...lang]/page/[slug]/index.tsx | 23 -- 19 files changed, 242 insertions(+), 241 deletions(-) delete mode 100644 src/routes/[...lang]/page/[slug]/index.tsx diff --git a/docs/quick-start.md b/docs/quick-start.md index d23dec8..2beb043 100644 --- a/docs/quick-start.md +++ b/docs/quick-start.md @@ -104,7 +104,7 @@ export const Home = component$(() => { const fd = useFormatDate(); const fn = useFormatNumber(); - // Prefer translating inside components rather than on props + // Translate inside components rather than on props const title = t('app.title@@{{name}} demo', { name: 'Qwik Speak' }); return ( diff --git a/docs/translate.md b/docs/translate.md index bd47a02..a4e6852 100644 --- a/docs/translate.md +++ b/docs/translate.md @@ -78,14 +78,19 @@ You can have Html in translations, like: ``` but you have to use `dangerouslySetInnerHTML`: ```tsx -

+export const Home = component$(() => { + const t = useTranslate(); + + const text = t('home.text'); + return ( +

+ ); +}); ``` > On the client the text is _inlined_ during build, so there are no XSS risks -## component$ props -> A component can wake up independently from the parent component. If the component wakes up, it needs to be able to know its props - -Prefer translating inside components rather than on props: +## Component props and jsx attributes +Translate inside components rather than on props: ```tsx export const Title = component$((props) => { @@ -117,6 +122,19 @@ export const Home = component$(() => { ``` In the latter case, `app.title` will have to be placed in the `runtimeAssets`, as a dynamic key is passed to the `t` function. +Translate the attributes into the components as well: + +```tsx +export const Home = component$(() => { + const t = useTranslate(); + + const text = t('home.text'); + return ( +

+ ); +}); +``` + ## inlineTranslate `inlineTranslate` function has the same behavior as the function returned by `useTranslate`, but can be used outside the `component$`, for example in _Inline components_, passing the Speak context as second argument: ```tsx diff --git a/docs/tutorial-routing-rewrite.md b/docs/tutorial-routing-rewrite.md index 8b3cbdf..d656462 100644 --- a/docs/tutorial-routing-rewrite.md +++ b/docs/tutorial-routing-rewrite.md @@ -177,7 +177,7 @@ export const Home = component$(() => { const fd = useFormatDate(); const fn = useFormatNumber(); - // Prefer translating inside components rather than on props + // Translate inside components rather than on props const title = t('app.title@@{{name}} demo', { name: 'Qwik Speak' }); return ( diff --git a/docs/tutorial-routing.md b/docs/tutorial-routing.md index 6886e69..4b5951b 100644 --- a/docs/tutorial-routing.md +++ b/docs/tutorial-routing.md @@ -141,7 +141,7 @@ export const Home = component$(() => { const fd = useFormatDate(); const fn = useFormatNumber(); - // Prefer translating inside components rather than on props + // Translate inside components rather than on props const title = t('app.title@@{{name}} demo', { name: 'Qwik Speak' }); return ( diff --git a/package.json b/package.json index 29f646b..8059545 100644 --- a/package.json +++ b/package.json @@ -12,7 +12,7 @@ "build.types": "tsc --incremental --noEmit", "dev": "vite --mode ssr", "dev.debug": "node --inspect-brk ./node_modules/vite/bin/vite.js --mode ssr --force", - "lint": "eslint src/**/*.ts*", + "lint": "eslint \"src/**/*.ts*\"", "preview": "qwik build preview && vite preview --open", "qwik-speak-extract": "node ./packages/qwik-speak/extract/cli.js --supportedLangs=en-US,it-IT,de-DE --assetsPath=i18n", "start": "vite --open --mode ssr", diff --git a/packages/qwik-speak/src/use-display-name.ts b/packages/qwik-speak/src/use-display-name.ts index 1bfd067..bfdf2f4 100644 --- a/packages/qwik-speak/src/use-display-name.ts +++ b/packages/qwik-speak/src/use-display-name.ts @@ -1,4 +1,3 @@ -import { noSerialize } from '@builder.io/qwik'; import { useSpeakLocale } from './use-speak'; export type DisplayNameFn = { @@ -21,5 +20,5 @@ export const useDisplayName = (): DisplayNameFn => { return new Intl.DisplayNames(lang, options).of(code) || code; }; - return noSerialize(displayName) as DisplayNameFn; + return displayName as DisplayNameFn; }; diff --git a/packages/qwik-speak/src/use-format-date.ts b/packages/qwik-speak/src/use-format-date.ts index 64554fd..9fb8591 100644 --- a/packages/qwik-speak/src/use-format-date.ts +++ b/packages/qwik-speak/src/use-format-date.ts @@ -1,4 +1,3 @@ -import { noSerialize } from '@builder.io/qwik'; import { useSpeakLocale } from './use-speak'; export type FormatDateFn = { @@ -33,5 +32,5 @@ export const useFormatDate = (): FormatDateFn => { return new Intl.DateTimeFormat(lang, options).format(value); }; - return noSerialize(formateDate) as FormatDateFn; + return formateDate as FormatDateFn; }; diff --git a/packages/qwik-speak/src/use-format-number.ts b/packages/qwik-speak/src/use-format-number.ts index 566f9a0..6b7f97b 100644 --- a/packages/qwik-speak/src/use-format-number.ts +++ b/packages/qwik-speak/src/use-format-number.ts @@ -1,4 +1,3 @@ -import { noSerialize } from '@builder.io/qwik'; import { useSpeakLocale } from './use-speak'; export type FormatNumberFn = { @@ -33,5 +32,5 @@ export const useFormatNumber = (): FormatNumberFn => { return new Intl.NumberFormat(lang, options).format(value); }; - return noSerialize(formatNumber) as FormatNumberFn; + return formatNumber as FormatNumberFn; }; diff --git a/packages/qwik-speak/src/use-plural.ts b/packages/qwik-speak/src/use-plural.ts index 65bff67..85eb15e 100644 --- a/packages/qwik-speak/src/use-plural.ts +++ b/packages/qwik-speak/src/use-plural.ts @@ -1,4 +1,3 @@ -import { noSerialize } from '@builder.io/qwik'; import { useSpeakContext } from './use-speak'; import { getValue } from './core'; @@ -43,5 +42,5 @@ export const usePlural = (): PluralFn => { return getValue(key, translation[lang], { value, ...params }, config.keySeparator, config.keyValueSeparator); }; - return noSerialize(plural) as PluralFn; + return plural as PluralFn; }; diff --git a/packages/qwik-speak/src/use-relative-time.ts b/packages/qwik-speak/src/use-relative-time.ts index dbfffe5..f9de108 100644 --- a/packages/qwik-speak/src/use-relative-time.ts +++ b/packages/qwik-speak/src/use-relative-time.ts @@ -1,4 +1,3 @@ -import { noSerialize } from '@builder.io/qwik'; import { useSpeakLocale } from './use-speak'; export type RelativeTimeFn = { @@ -33,5 +32,5 @@ export const useRelativeTime = (): RelativeTimeFn => { return new Intl.RelativeTimeFormat(lang, options).format(value, unit); }; - return noSerialize(relativeTime) as RelativeTimeFn; + return relativeTime as RelativeTimeFn; }; diff --git a/packages/qwik-speak/src/use-translate-path.ts b/packages/qwik-speak/src/use-translate-path.ts index d656a81..9c69d0c 100644 --- a/packages/qwik-speak/src/use-translate-path.ts +++ b/packages/qwik-speak/src/use-translate-path.ts @@ -1,4 +1,3 @@ -import { noSerialize } from '@builder.io/qwik'; import { isDev } from '@builder.io/qwik/build'; import { useSpeakContext } from './use-speak'; import { logWarn } from './log'; @@ -111,5 +110,5 @@ export const useTranslatePath = (): TranslatePathFn => { return translateOne(pathname, lang); }; - return noSerialize(translate) as TranslatePathFn; + return translate as TranslatePathFn; }; diff --git a/packages/qwik-speak/src/use-translate.ts b/packages/qwik-speak/src/use-translate.ts index 28b1fe7..5cbd81f 100644 --- a/packages/qwik-speak/src/use-translate.ts +++ b/packages/qwik-speak/src/use-translate.ts @@ -1,4 +1,3 @@ -import { noSerialize } from '@builder.io/qwik'; import { useSpeakContext } from './use-speak'; import { getValue } from './core'; @@ -40,5 +39,5 @@ export const useTranslate = (): TranslateFn => { return getValue(keys, translation[lang], params, config.keySeparator, config.keyValueSeparator); }; - return noSerialize(translate) as TranslateFn; + return translate as TranslateFn; }; diff --git a/packages/qwik-speak/tests/use-translate.test.tsx b/packages/qwik-speak/tests/use-translate.test.tsx index 7909637..0c0cd49 100644 --- a/packages/qwik-speak/tests/use-translate.test.tsx +++ b/packages/qwik-speak/tests/use-translate.test.tsx @@ -22,6 +22,8 @@ const ChildComponent = component$((props: ChildComponentProps) => { const TestComponent = component$(() => { const t = useTranslate(); + const value = t('test'); + return (
{t('test')}
@@ -48,8 +50,8 @@ const TestComponent = component$(() => { }
{true && t('test')}
-
- +
+ ); }); @@ -108,10 +110,10 @@ describe('useTranslate function', async () => { test('inline conditional rendering', () => { expect((screen.querySelector('#A13') as HTMLDivElement).innerHTML).toContain('Test'); }); - test('html attributes', () => { + test('jsx attributes', () => { expect((screen.querySelector('#A14') as HTMLDivElement).getAttribute('title')).toContain('Test'); }); - test('no reactive component props', () => { + test('component props', () => { expect((screen.querySelector('#B') as HTMLDivElement).innerHTML).toContain('Test'); }) }); diff --git a/packages/qwik-speak/tools/core/parser.ts b/packages/qwik-speak/tools/core/parser.ts index d76de8a..09634d1 100644 --- a/packages/qwik-speak/tools/core/parser.ts +++ b/packages/qwik-speak/tools/core/parser.ts @@ -394,7 +394,7 @@ export function parseSequenceExpressions(code: string, alias: string): CallExpre } catch (ex: any) { // Report Call expression console.error(ex); - console.error('\x1b[31m\nQwik Speak Inline error parsing \x1b[0m %s', + console.error('\n\x1b[31mQwik Speak Parser error\x1b[0m\n%s', code.substring(i, tokens[tokens.length - 1].position.end) + '\n'); } diff --git a/packages/qwik-speak/tools/inline/plugin.ts b/packages/qwik-speak/tools/inline/plugin.ts index 1a97248..908f40b 100644 --- a/packages/qwik-speak/tools/inline/plugin.ts +++ b/packages/qwik-speak/tools/inline/plugin.ts @@ -20,17 +20,14 @@ const signalAlias = '\\b_fnSignal'; // Logs const missingValues: string[] = []; const dynamics: string[] = []; -const missingValueText = (lang: string, key: string) => `${lang} - missing value for key: ${key}`; -const dynamicText = (originalFn: string, text: string) => `dynamic ${text}: ${originalFn.replace(/\s+/g, ' ')} - Make sure the keys are in 'runtimeAssets'`; +const missingValueText = (lang: string, key: string) => `${lang} - ${key}`; +const dynamicText = (originalFn: string, text: string) => `dynamic ${text}: ${originalFn}`; // Config let target: 'ssr' | 'lib' | 'test' | 'client'; let mode: 'dev' | 'prod'; let input: string | undefined; -// Inlined modules -const moduleIds = new Set(); - /** * Qwik Speak Inline Vite plugin */ @@ -51,6 +48,9 @@ export function qwikSpeakInline(options: QwikSpeakInlineOptions): Plugin { // Current lang let devLang: string; + // Inlined modules + const moduleIds = new Set(); + const plugin: Plugin = { name: 'vite-plugin-qwik-speak-inline', enforce: 'post', @@ -137,11 +137,11 @@ export function qwikSpeakInline(options: QwikSpeakInlineOptions): Plugin { if (/\/src\//.test(id) && /\.(js|cjs|mjs|jsx|ts|tsx)$/.test(id)) { // Filter code: usePlural if (/usePlural/.test(code)) { - code = transformPlural(code); + code = transformPlural(code, id); } // Filter code: useTranslate if (/useTranslate/.test(code)) { - code = transform(code); + code = transform(code, id); } // Filter code: inlineTranslate if (/inlineTranslate/.test(code)) { @@ -163,6 +163,21 @@ export function qwikSpeakInline(options: QwikSpeakInlineOptions): Plugin { } } + // Validate server code + if (target === 'ssr' && mode === 'dev') { + if (/\/src\//.test(id) && /\.(js|cjs|mjs|jsx|ts|tsx)$/.test(id)) { + if (/usePlural/.test(code)) { + transformPlural(code, id); + } + if (/useTranslate/.test(code)) { + transform(code, id); + } + if (/inlineTranslate/.test(code)) { + transformInline(code); + } + } + } + // Add useInline in dev mode if (target === 'ssr' && mode === 'dev') { if (id.endsWith('router-head.tsx') || id.endsWith('router-head.jsx')) { @@ -181,7 +196,7 @@ export function qwikSpeakInline(options: QwikSpeakInlineOptions): Plugin { '\n\x1b[31mQwik Speak Inline error\x1b[0m\n%s', "Missing 'base' option in 'entry.ssr.tsx' file: see https://robisim74.gitbook.io/qwik-speak/tools/inline#usage" ); - process.exit(1) + process.exit(1); } } } @@ -207,10 +222,13 @@ export function qwikSpeakInline(options: QwikSpeakInlineOptions): Plugin { log.write(`${target}: ` + (input ?? '-') + '\n'); + log.write('\nMissing value for keys:\n'); missingValues.forEach(x => log.write(x + '\n')); + + log.write("\nMake sure the keys are in 'runtimeAssets':\n"); dynamics.forEach(x => log.write(x + '\n')); - log.write((`Qwik Speak Inline: build ends at ${new Date().toLocaleString()}\n`)); + log.write((`\nQwik Speak Inline: build ends at ${new Date().toLocaleString()}\n`)); if (missingValues.length > 0 || dynamics.length > 0) { console.log( @@ -302,7 +320,7 @@ export async function writeChunks( /** * Transform useTranslate to placeholder */ -export function transform(code: string): string { +export function transform(code: string, id: string): string { const alias = getUseTranslateAlias(code); if (alias) { @@ -326,49 +344,9 @@ export function transform(code: string): string { code = code.replace(originalFn, transpiled); } } - } - - // Props - const sequence = parseSequenceExpressions(code, signalAlias); - - if (sequence.length === 0) return code; - - for (const expr of sequence) { - // Arguments - const args = expr.arguments; - - // Check identifier - if (args?.length > 2) { - if (args[args.length - 1].type === 'ArrayExpression') { - const elements = args[args.length - 1].elements; - if (elements && elements.find(element => new RegExp(`^${alias}$`).test(element.value))) { - const index = elements.findIndex(element => new RegExp(`^${alias}$`).test(element.value)); - if (args[index].type === 'Identifier' && args[args.length - 2].type === 'CallExpression') { - // Transformed function - const transformedFn = args[args.length - 2].value; - if (transformedFn && args[index].value) { - const transformedAlias = `\\b${args[index].value}`; - const tokens = tokenize(transformedFn); - const transformedExpr = parse(tokens, transformedFn, transformedAlias); - if (transformedExpr) { - // Arguments - const transformedArgs = transformedExpr.arguments; - - if (transformedArgs?.length > 0) { - if (checkDynamic(transformedArgs, transformedFn)) continue; - - // Transpile with placeholder - const transpiled = transformedFn.replace(new RegExp(`${transformedAlias}\\(`), `${inlinePlaceholder}(`); - // Replace - code = code.replace(transformedFn, transpiled); - } - } - } - } - } - } - } + // Props + code = validateProps(id, code, alias); } return code; @@ -407,7 +385,7 @@ export function transformInline(code: string): string { /** * Transform usePlural to placeholder */ -export function transformPlural(code: string): string { +export function transformPlural(code: string, id: string): string { const alias = getUsePluralAlias(code); if (alias) { @@ -431,6 +409,66 @@ export function transformPlural(code: string): string { code = code.replace(originalFn, transpiled); } } + + // Props + code = validateProps(id, code, alias); + } + + return code; +} + +export function validateProps(id: string, code: string, alias: string) { + const sequence = parseSequenceExpressions(code, signalAlias); + + if (sequence.length === 0) return code; + + for (const expr of sequence) { + // Arguments + const args = expr.arguments; + + // Check identifier + if (args?.length > 2) { + const arrayIndex = args.findIndex(arg => arg.type === 'ArrayExpression'); + const callIndex = args.findIndex(arg => arg.type === 'CallExpression') + if (arrayIndex !== -1 && callIndex !== -1) { + const elements = args[arrayIndex].elements; + if (elements) { + const index = elements.findIndex(element => new RegExp(`^${alias}$`).test(element.value)); + if (index !== -1 && args[index].type === 'Identifier') { + // Transformed function + let transformedFn = args[callIndex].value; + if (transformedFn && args[index].value) { + const transformedAlias = `\\b${args[index].value}`; + const tokens = tokenize(transformedFn); + const transformedExpr = parse(tokens, transformedFn, transformedAlias); + + if (transformedExpr) { + // Arguments + const transformedArgs = transformedExpr.arguments; + + if (transformedArgs?.length > 0) { + // Invalid props or attributes + transformedFn = trimFn(transformedFn); + transformedFn = transformedFn.replace(new RegExp(`^${args[index].value}`), elements[index].value); + if (mode === 'dev') { + console.log( + '\n\x1b[31mQwik Speak Inline error\x1b[0m\n%s', + `${transformedFn} is used as a prop or attribute in the following file:\n${id}\nSee https://robisim74.gitbook.io/qwik-speak/library/translate#component-props-and-jsx-attributes` + ); + } else { + console.log( + '\n\x1b[31mQwik Speak Inline error\x1b[0m\n%s', + `${transformedFn} is used as a prop or attribute\nSee https://robisim74.gitbook.io/qwik-speak/library/translate#component-props-and-jsx-attributes` + ); + } + process.exit(1); + } + } + } + } + } + } + } } return code; @@ -800,12 +838,16 @@ export function logMissingValue(lang: string, key: string) { } export function logDynamic(originalFn: string, type: 'key' | 'params') { - const text = dynamicText(originalFn, type); + const text = dynamicText(trimFn(originalFn), type); if (!dynamics.includes(text)) { dynamics.push(text); } } +export function trimFn(fn: string): string { + return fn.replace(/\s+/g, ' ').trim(); +} + /** * Replace quoted values with a placeholder */ @@ -813,5 +855,3 @@ function replacer(key: string, value: string | Translation): string | Translatio if (typeof value === 'string' && /^`.*`$/.test(value)) return value.replace(/^`/, '__qsOpenBt').replace(/`$/, '__qsCloseBt'); return value; } - - diff --git a/packages/qwik-speak/tools/tests/inline.test.ts b/packages/qwik-speak/tools/tests/inline.test.ts index b4e6d59..76b3bde 100644 --- a/packages/qwik-speak/tools/tests/inline.test.ts +++ b/packages/qwik-speak/tools/tests/inline.test.ts @@ -177,7 +177,7 @@ describe('inline', () => { }); test('transform & inline multilingual', async () => { const code = `import { useTranslate } from "qwik-speak";const t = useTranslate();const value = t('app.subtitle', undefined, 'it-IT')`; - const transformed = transform(code); + const transformed = transform(code, ''); const inlined = inline(transformed, { 'en-US': { diff --git a/packages/qwik-speak/tools/tests/mock.ts b/packages/qwik-speak/tools/tests/mock.ts index ce975e2..10318ef 100644 --- a/packages/qwik-speak/tools/tests/mock.ts +++ b/packages/qwik-speak/tools/tests/mock.ts @@ -50,9 +50,11 @@ export const Home = component$(() => { Object.values(tObject).map((x) => console.log(x)); tArrayObjects.map((x) => console.log(x['num'])); + const [title, text] = t(['app.title', 'home.text']); + return (
- + <Title name={title} /> <SubTitle ctx={ctx} /> @@ -60,7 +62,7 @@ export const Home = component$(() => { <p>{t('home.greeting', { name: 'Qwik Speak' })}</p> <h3>{t('home.tags')}</h3> - <p dangerouslySetInnerHTML={t('home.text')}></p> + <p dangerouslySetInnerHTML={text}></p> <h3>{t('home.plural')}</h3> <p class="counter">{p(count.value, 'home.devs')}</p> @@ -130,18 +132,15 @@ export const s_dYGb4b0cyCA = ()=>{ console.log(item); Object.values(tObject).map((x)=>console.log(x)); tArrayObjects.map((x)=>console.log(x['num'])); + const [title, text] = t([ + 'app.title', + 'home.text' + ]); return /*#__PURE__*/ _jsxQ("div", null, { class: "content" }, [ /*#__PURE__*/ _jsxC(Title, { - get name () { - return t('app.title'); - }, - [_IMMUTABLE]: { - name: _fnSignal((p0)=>p0('app.title'), [ - t - ]) - } + name: title }, 3, "1L_2"), /*#__PURE__*/ _jsxC(SubTitle, { ctx: ctx, @@ -154,11 +153,9 @@ export const s_dYGb4b0cyCA = ()=>{ name: 'Qwik Speak' }), 1, null), /*#__PURE__*/ _jsxQ("h3", null, null, t('home.tags'), 1, null), - /*#__PURE__*/ _jsxQ("p", null, { - dangerouslySetInnerHTML: _fnSignal((p0)=>p0('home.text'), [ - t - ]) - }, null, 3, null), + /*#__PURE__*/ _jsxQ("p", { + dangerouslySetInnerHTML: text + }, null, null, 3, null), /*#__PURE__*/ _jsxQ("h3", null, null, t('home.plural'), 1, null), /*#__PURE__*/ _jsxQ("p", null, { class: "counter" @@ -223,18 +220,15 @@ export const s_dYGb4b0cyCA = ()=>{ console.log(item); Object.values(tObject).map((x)=>console.log(x)); tArrayObjects.map((x)=>console.log(x['num'])); + const [title, text] = __qsInline([ + 'app.title', + 'home.text' + ]); return /*#__PURE__*/ _jsxQ("div", null, { class: "content" }, [ /*#__PURE__*/ _jsxC(Title, { - get name () { - return __qsInline('app.title'); - }, - [_IMMUTABLE]: { - name: _fnSignal((p0)=>__qsInline('app.title'), [ - t - ]) - } + name: title }, 3, "1L_2"), /*#__PURE__*/ _jsxC(SubTitle, { ctx: ctx, @@ -247,11 +241,9 @@ export const s_dYGb4b0cyCA = ()=>{ name: 'Qwik Speak' }), 1, null), /*#__PURE__*/ _jsxQ("h3", null, null, __qsInline('home.tags'), 1, null), - /*#__PURE__*/ _jsxQ("p", null, { - dangerouslySetInnerHTML: _fnSignal((p0)=>__qsInline('home.text'), [ - t - ]) - }, null, 3, null), + /*#__PURE__*/ _jsxQ("p", { + dangerouslySetInnerHTML: text + }, null, null, 3, null), /*#__PURE__*/ _jsxQ("h3", null, null, __qsInline('home.plural'), 1, null), /*#__PURE__*/ _jsxQ("p", null, { class: "counter" @@ -281,14 +273,14 @@ export const s_dYGb4b0cyCA = ()=>{ };`; export const mockChunkCode = `const s_dYGb4b0cyCA = () => { - const t = useTranslate(); + useTranslate(); usePlural(); const fd = useFormatDate(); const rt = useRelativeTime(); const fn = useFormatNumber(); const ctx = useSpeakContext(); const locale = useSpeakLocale(); - const count = Sc(0); + const count = Wc(0); const tParam = __qsInline("home.greeting", { name: __qsInline("app.title") }); @@ -301,57 +293,52 @@ export const mockChunkCode = `const s_dYGb4b0cyCA = () => { console.log(item); Object.values(tObject).map((x) => console.log(x)); tArrayObjects.map((x) => console.log(x["num"])); - return /* @__PURE__ */ Nr("div", null, { + const [title, text] = __qsInline([ + 'app.title', + 'home.text' + ]); + return /* @__PURE__ */ Sr("div", null, { class: "content" }, [ - /* @__PURE__ */ Ur(Title, { - get name() { - return __qsInline("app.title"); - }, - [Pt]: { - name: Ot((p0) => __qsInline("app.title"), [ - t - ]) - } + /* @__PURE__ */ jr(Title, { + name: title }, 3, "1L_2"), - /* @__PURE__ */ Ur(SubTitle, { + /* @__PURE__ */ jr(SubTitle, { ctx, - [Pt]: { - ctx: Pt + [Y]: { + ctx: Y } }, 3, "1L_3"), - /* @__PURE__ */ Nr("h3", null, null, __qsInline("home.params"), 1, null), - /* @__PURE__ */ Nr("p", null, null, __qsInline("home.greeting", { + /* @__PURE__ */ Sr("h3", null, null, __qsInline("home.params"), 1, null), + /* @__PURE__ */ Sr("p", null, null, __qsInline("home.greeting", { name: "Qwik Speak" }), 1, null), - /* @__PURE__ */ Nr("h3", null, null, __qsInline("home.tags"), 1, null), - /* @__PURE__ */ Nr("p", null, { - dangerouslySetInnerHTML: Ot((p0) => __qsInline("home.text"), [ - t - ]) - }, null, 3, null), - /* @__PURE__ */ Nr("h3", null, null, __qsInline("home.plural"), 1, null), - /* @__PURE__ */ Nr("p", null, { + /* @__PURE__ */ Sr("h3", null, null, __qsInline("home.tags"), 1, null), + /* @__PURE__ */ Sr("p", { + dangerouslySetInnerHTML: text + }, null, null, 3, null), + /* @__PURE__ */ Sr("h3", null, null, __qsInline("home.plural"), 1, null), + /* @__PURE__ */ Sr("p", null, { class: "counter" }, __qsInlinePlural(count.value, "home.devs"), 1, null), - /* @__PURE__ */ Nr("button", null, { + /* @__PURE__ */ Sr("button", null, { class: "btn-counter", - onClick$: /* @__PURE__ */ z(() => __vitePreload(() => Promise.resolve().then(() => entry_Home), true ? void 0 : void 0), "s_UVYDAmatcag", [ + onClick$: /* @__PURE__ */ hs(() => __vitePreload(() => Promise.resolve().then(() => entry_Home), true ? void 0 : void 0), "s_UVYDAmatcag", [ count ]) }, __qsInline("home.increment"), 1, null), - /* @__PURE__ */ Nr("h3", null, null, __qsInline("home.dates"), 1, null), - /* @__PURE__ */ Nr("p", null, null, fd(Date.now(), { + /* @__PURE__ */ Sr("h3", null, null, __qsInline("home.dates"), 1, null), + /* @__PURE__ */ Sr("p", null, null, fd(Date.now(), { dateStyle: "full", timeStyle: "short" }), 1, null), - /* @__PURE__ */ Nr("p", null, null, rt(-1, "second"), 1, null), - /* @__PURE__ */ Nr("h3", null, null, __qsInline("home.numbers"), 1, null), - /* @__PURE__ */ Nr("p", null, null, fn(1e6), 1, null), - /* @__PURE__ */ Nr("p", null, null, fn(1e6, { + /* @__PURE__ */ Sr("p", null, null, rt(-1, "second"), 1, null), + /* @__PURE__ */ Sr("h3", null, null, __qsInline("home.numbers"), 1, null), + /* @__PURE__ */ Sr("p", null, null, fn(1e6), 1, null), + /* @__PURE__ */ Sr("p", null, null, fn(1e6, { style: "currency" }), 1, null), - /* @__PURE__ */ Nr("p", null, null, fn(1, { + /* @__PURE__ */ Sr("p", null, null, fn(1, { style: "unit", unit: locale.units["length"] }), 1, null) @@ -359,14 +346,14 @@ export const mockChunkCode = `const s_dYGb4b0cyCA = () => { };`; export const mockInlinedCode = `const s_dYGb4b0cyCA = () => { - const t = useTranslate(); + useTranslate(); usePlural(); const fd = useFormatDate(); const rt = useRelativeTime(); const fn = useFormatNumber(); const ctx = useSpeakContext(); const locale = useSpeakLocale(); - const count = Sc(0); + const count = Wc(0); const tParam = \`Hi! I am \${\`\`}\`; const tArray = [\`n. one\`,\`n. two\`,\`n. three\`]; const item = \`n. three\`; @@ -377,55 +364,47 @@ export const mockInlinedCode = `const s_dYGb4b0cyCA = () => { console.log(item); Object.values(tObject).map((x) => console.log(x)); tArrayObjects.map((x) => console.log(x["num"])); - return /* @__PURE__ */ Nr("div", null, { + const [title, text] = [\`\`,\`<em>Internationalization (i18n) library to translate texts, dates and numbers in Qwik apps</em>\`]; + return /* @__PURE__ */ Sr("div", null, { class: "content" }, [ - /* @__PURE__ */ Ur(Title, { - get name() { - return \`\`; - }, - [Pt]: { - name: Ot((p0) => \`\`, [ - t - ]) - } + /* @__PURE__ */ jr(Title, { + name: title }, 3, "1L_2"), - /* @__PURE__ */ Ur(SubTitle, { + /* @__PURE__ */ jr(SubTitle, { ctx, - [Pt]: { - ctx: Pt + [Y]: { + ctx: Y } }, 3, "1L_3"), - /* @__PURE__ */ Nr("h3", null, null, \`Parameters\`, 1, null), - /* @__PURE__ */ Nr("p", null, null, \`Hi! I am Qwik Speak\`, 1, null), - /* @__PURE__ */ Nr("h3", null, null, \`Html tags\`, 1, null), - /* @__PURE__ */ Nr("p", null, { - dangerouslySetInnerHTML: Ot((p0) => \`<em>Internationalization (i18n) library to translate texts, dates and numbers in Qwik apps</em>\`, [ - t - ]) - }, null, 3, null), - /* @__PURE__ */ Nr("h3", null, null, \`Plural\`, 1, null), - /* @__PURE__ */ Nr("p", null, { + /* @__PURE__ */ Sr("h3", null, null, \`Parameters\`, 1, null), + /* @__PURE__ */ Sr("p", null, null, \`Hi! I am Qwik Speak\`, 1, null), + /* @__PURE__ */ Sr("h3", null, null, \`Html tags\`, 1, null), + /* @__PURE__ */ Sr("p", { + dangerouslySetInnerHTML: text + }, null, null, 3, null), + /* @__PURE__ */ Sr("h3", null, null, \`Plural\`, 1, null), + /* @__PURE__ */ Sr("p", null, { class: "counter" }, (new Intl.PluralRules(\`en-US\`).select(+count.value) === \`other\` && \`\${count.value} software developers\` || \`\${count.value} software developer\`), 1, null), - /* @__PURE__ */ Nr("button", null, { + /* @__PURE__ */ Sr("button", null, { class: "btn-counter", - onClick$: /* @__PURE__ */ z(() => __vitePreload(() => Promise.resolve().then(() => entry_Home), true ? void 0 : void 0), "s_UVYDAmatcag", [ + onClick$: /* @__PURE__ */ hs(() => __vitePreload(() => Promise.resolve().then(() => entry_Home), true ? void 0 : void 0), "s_UVYDAmatcag", [ count ]) }, \`Increment\`, 1, null), - /* @__PURE__ */ Nr("h3", null, null, \`Dates & relative time\`, 1, null), - /* @__PURE__ */ Nr("p", null, null, fd(Date.now(), { + /* @__PURE__ */ Sr("h3", null, null, \`Dates & relative time\`, 1, null), + /* @__PURE__ */ Sr("p", null, null, fd(Date.now(), { dateStyle: "full", timeStyle: "short" }), 1, null), - /* @__PURE__ */ Nr("p", null, null, rt(-1, "second"), 1, null), - /* @__PURE__ */ Nr("h3", null, null, \`Numbers & currencies\`, 1, null), - /* @__PURE__ */ Nr("p", null, null, fn(1e6), 1, null), - /* @__PURE__ */ Nr("p", null, null, fn(1e6, { + /* @__PURE__ */ Sr("p", null, null, rt(-1, "second"), 1, null), + /* @__PURE__ */ Sr("h3", null, null, \`Numbers & currencies\`, 1, null), + /* @__PURE__ */ Sr("p", null, null, fn(1e6), 1, null), + /* @__PURE__ */ Sr("p", null, null, fn(1e6, { style: "currency" }), 1, null), - /* @__PURE__ */ Nr("p", null, null, fn(1, { + /* @__PURE__ */ Sr("p", null, null, fn(1, { style: "unit", unit: locale.units["length"] }), 1, null) @@ -433,14 +412,14 @@ export const mockInlinedCode = `const s_dYGb4b0cyCA = () => { };`; export const mockInlinedCodeByLang = `const s_dYGb4b0cyCA = () => { - const t = useTranslate(); + useTranslate(); usePlural(); const fd = useFormatDate(); const rt = useRelativeTime(); const fn = useFormatNumber(); const ctx = useSpeakContext(); const locale = useSpeakLocale(); - const count = Sc(0); + const count = Wc(0); const tParam = \`Ciao! Sono \${\`\`}\`; const tArray = [\`n. uno\`,\`n. due\`,\`n. tre\`]; const item = \`n. tre\`; @@ -451,55 +430,47 @@ export const mockInlinedCodeByLang = `const s_dYGb4b0cyCA = () => { console.log(item); Object.values(tObject).map((x) => console.log(x)); tArrayObjects.map((x) => console.log(x["num"])); - return /* @__PURE__ */ Nr("div", null, { + const [title, text] = [\`\`,\`<em>Libreria di internazionalizzazione (i18n) per tradurre testi, date e numeri nelle app Qwik</em>\`]; + return /* @__PURE__ */ Sr("div", null, { class: "content" }, [ - /* @__PURE__ */ Ur(Title, { - get name() { - return \`\`; - }, - [Pt]: { - name: Ot((p0) => \`\`, [ - t - ]) - } + /* @__PURE__ */ jr(Title, { + name: title }, 3, "1L_2"), - /* @__PURE__ */ Ur(SubTitle, { + /* @__PURE__ */ jr(SubTitle, { ctx, - [Pt]: { - ctx: Pt + [Y]: { + ctx: Y } }, 3, "1L_3"), - /* @__PURE__ */ Nr("h3", null, null, \`Parametri\`, 1, null), - /* @__PURE__ */ Nr("p", null, null, \`Ciao! Sono Qwik Speak\`, 1, null), - /* @__PURE__ */ Nr("h3", null, null, \`Tag Html\`, 1, null), - /* @__PURE__ */ Nr("p", null, { - dangerouslySetInnerHTML: Ot((p0) => \`<em>Libreria di internazionalizzazione (i18n) per tradurre testi, date e numeri nelle app Qwik</em>\`, [ - t - ]) - }, null, 3, null), - /* @__PURE__ */ Nr("h3", null, null, \`Plurale\`, 1, null), - /* @__PURE__ */ Nr("p", null, { + /* @__PURE__ */ Sr("h3", null, null, \`Parametri\`, 1, null), + /* @__PURE__ */ Sr("p", null, null, \`Ciao! Sono Qwik Speak\`, 1, null), + /* @__PURE__ */ Sr("h3", null, null, \`Tag Html\`, 1, null), + /* @__PURE__ */ Sr("p", { + dangerouslySetInnerHTML: text + }, null, null, 3, null), + /* @__PURE__ */ Sr("h3", null, null, \`Plurale\`, 1, null), + /* @__PURE__ */ Sr("p", null, { class: "counter" }, (new Intl.PluralRules(\`it-IT\`).select(+count.value) === \`other\` && \`\${count.value} sviluppatori software\` || \`\${count.value} sviluppatore software\`), 1, null), - /* @__PURE__ */ Nr("button", null, { + /* @__PURE__ */ Sr("button", null, { class: "btn-counter", - onClick$: /* @__PURE__ */ z(() => __vitePreload(() => Promise.resolve().then(() => entry_Home), true ? void 0 : void 0), "s_UVYDAmatcag", [ + onClick$: /* @__PURE__ */ hs(() => __vitePreload(() => Promise.resolve().then(() => entry_Home), true ? void 0 : void 0), "s_UVYDAmatcag", [ count ]) }, \`Incrementa\`, 1, null), - /* @__PURE__ */ Nr("h3", null, null, \`Date e tempo relativo\`, 1, null), - /* @__PURE__ */ Nr("p", null, null, fd(Date.now(), { + /* @__PURE__ */ Sr("h3", null, null, \`Date e tempo relativo\`, 1, null), + /* @__PURE__ */ Sr("p", null, null, fd(Date.now(), { dateStyle: "full", timeStyle: "short" }), 1, null), - /* @__PURE__ */ Nr("p", null, null, rt(-1, "second"), 1, null), - /* @__PURE__ */ Nr("h3", null, null, \`Numeri e valute\`, 1, null), - /* @__PURE__ */ Nr("p", null, null, fn(1e6), 1, null), - /* @__PURE__ */ Nr("p", null, null, fn(1e6, { + /* @__PURE__ */ Sr("p", null, null, rt(-1, "second"), 1, null), + /* @__PURE__ */ Sr("h3", null, null, \`Numeri e valute\`, 1, null), + /* @__PURE__ */ Sr("p", null, null, fn(1e6), 1, null), + /* @__PURE__ */ Sr("p", null, null, fn(1e6, { style: "currency" }), 1, null), - /* @__PURE__ */ Nr("p", null, null, fn(1, { + /* @__PURE__ */ Sr("p", null, null, fn(1, { style: "unit", unit: locale.units["length"] }), 1, null) diff --git a/src/routes/[...lang]/index.tsx b/src/routes/[...lang]/index.tsx index 8d59a44..8cfb39b 100644 --- a/src/routes/[...lang]/index.tsx +++ b/src/routes/[...lang]/index.tsx @@ -38,8 +38,8 @@ export const Home = component$(() => { const count = useSignal(0); - // Prefer translating inside components rather than on props - const title = t('app.title'); + // Translate inside components rather than on props + const [title, text] = t(['app.title', 'home.text']); return ( <div class="content"> @@ -51,7 +51,7 @@ export const Home = component$(() => { <p>{t('home.greeting', { name: 'Qwik Speak' })}</p> <h3>{t('home.tags')}</h3> - <p dangerouslySetInnerHTML={t('home.text')}></p> + <p dangerouslySetInnerHTML={text}></p> <h3>{t('home.plural')}</h3> <p class="counter">{p(count.value, 'home.devs')}</p> diff --git a/src/routes/[...lang]/page/[slug]/index.tsx b/src/routes/[...lang]/page/[slug]/index.tsx deleted file mode 100644 index 510d247..0000000 --- a/src/routes/[...lang]/page/[slug]/index.tsx +++ /dev/null @@ -1,23 +0,0 @@ -import { component$ } from '@builder.io/qwik'; -import type { DocumentHead } from '@builder.io/qwik-city'; -import { useLocation } from "@builder.io/qwik-city"; -import { useTranslate } from 'qwik-speak'; - -export default component$(() => { - const t = useTranslate(); - const loc = useLocation() - - return ( - <div class="content"> - <h1>{t('app.title')}</h1> - <h2>{t('app.subtitle')}</h2> - - <p>{loc.params.slug}</p> - </div> - ); -}); - -export const head: DocumentHead = { - title: 'runtime.head.page.title', - meta: [{ name: 'description', content: 'runtime.head.page.description' }] -}; From 65776208ccfd91894f95ebeb49a937fb0a8750eb Mon Sep 17 00:00:00 2001 From: Roberto Simonetti <robisim74@gmail.com> Date: Tue, 14 Nov 2023 23:11:48 +0100 Subject: [PATCH 09/21] Feat: InlineTranslate no context --- packages/qwik-speak/src/context.ts | 2 + packages/qwik-speak/src/inline-translate.ts | 16 +-- .../qwik-speak/src/qwik-speak-component.tsx | 14 +- packages/qwik-speak/src/use-translate.ts | 10 +- .../tests/inline-translate.test.tsx | 14 +- packages/qwik-speak/tools/inline/plugin.ts | 85 +++++++----- .../qwik-speak/tools/tests/inline.test.ts | 6 +- packages/qwik-speak/tools/tests/mock.ts | 131 ++++++++---------- src/routes/[...lang]/index.tsx | 18 +-- 9 files changed, 141 insertions(+), 155 deletions(-) diff --git a/packages/qwik-speak/src/context.ts b/packages/qwik-speak/src/context.ts index 9959023..06cc9db 100644 --- a/packages/qwik-speak/src/context.ts +++ b/packages/qwik-speak/src/context.ts @@ -3,3 +3,5 @@ import { createContextId } from '@builder.io/qwik'; import type { SpeakState } from './types'; export const SpeakContext = createContextId<SpeakState>('qwikspeak'); + +export const serverSpeakContext: Partial<SpeakState> = {}; diff --git a/packages/qwik-speak/src/inline-translate.ts b/packages/qwik-speak/src/inline-translate.ts index 0d30cab..2fafa99 100644 --- a/packages/qwik-speak/src/inline-translate.ts +++ b/packages/qwik-speak/src/inline-translate.ts @@ -1,37 +1,35 @@ import type { SpeakState } from './types'; import { getValue } from './core'; +import { serverSpeakContext } from './context'; export type InlineTranslateFn = { /** - * Translate a key outside the component$. + * Translate a key. * The syntax of the string is 'key@@[default value]' * @param key The key to translate - * @param ctx The Speak context * @param params Optional parameters contained in the value * @param lang Optional language if different from the current one * @returns The translation or the key if not found */ - (key: string, ctx: SpeakState, params?: Record<string, any>, lang?: string): string; - <T>(key: string, ctx: SpeakState, params?: Record<string, any>, lang?: string): T; + <T = string>(key: string, params?: Record<string, any>, lang?: string): T; /** - * Translate an array of keys outside the component$. + * Translate an array of keys. * The syntax of the strings is 'key@@[default value]' * @param keys The array of keys to translate - * @param ctx The Speak context * @param params Optional parameters contained in the values * @param lang Optional language if different from the current one * @returns The translations or the keys if not found */ - (keys: string[], ctx: SpeakState, params?: Record<string, any>, lang?: string): string[]; - <T>(keys: string[], ctx: SpeakState, params?: Record<string, any>, lang?: string): T[]; + <T = string>(keys: string[], params?: Record<string, any>, lang?: string): T[]; }; export const inlineTranslate: InlineTranslateFn = ( keys: string | string[], - ctx: SpeakState, params?: Record<string, any>, lang?: string ) => { + const ctx = serverSpeakContext as SpeakState; + const { locale, translation, config } = ctx; lang ??= locale.lang; diff --git a/packages/qwik-speak/src/qwik-speak-component.tsx b/packages/qwik-speak/src/qwik-speak-component.tsx index 81fca66..9d9adbb 100644 --- a/packages/qwik-speak/src/qwik-speak-component.tsx +++ b/packages/qwik-speak/src/qwik-speak-component.tsx @@ -1,8 +1,8 @@ import { $, component$, Slot, useContextProvider, useServerData, useTask$ } from '@builder.io/qwik'; -import { isDev } from '@builder.io/qwik/build'; +import { isDev, isServer } from '@builder.io/qwik/build'; import type { SpeakConfig, SpeakLocale, SpeakState, TranslationFn } from './types'; -import { SpeakContext } from './context'; +import { serverSpeakContext, SpeakContext } from './context'; import { loadTranslations } from './core'; import { logDebug, logWarn } from './log'; @@ -62,7 +62,15 @@ export const QwikSpeakProvider = component$((props: QwikSpeakProps) => { }, translationFn: resolvedTranslationFn }; - const { config } = state; + + const { locale, translation, config } = state; + + // Create server context + if (isServer) { + serverSpeakContext.locale = locale; + serverSpeakContext.translation = translation; + serverSpeakContext.config = config; + } // Create context useContextProvider(SpeakContext, state); diff --git a/packages/qwik-speak/src/use-translate.ts b/packages/qwik-speak/src/use-translate.ts index 5cbd81f..ff2b57b 100644 --- a/packages/qwik-speak/src/use-translate.ts +++ b/packages/qwik-speak/src/use-translate.ts @@ -3,25 +3,23 @@ import { getValue } from './core'; export type TranslateFn = { /** - * Translate a key. + * Translate a key inside a component$. * The syntax of the string is 'key@@[default value]' * @param key The key to translate * @param params Optional parameters contained in the value * @param lang Optional language if different from the current one * @returns The translation or the key if not found */ - (key: string, params?: Record<string, any>, lang?: string): string; - <T>(key: string, params?: Record<string, any>, lang?: string): T; + <T = string>(key: string, params?: Record<string, any>, lang?: string): T; /** - * Translate an array of keys. + * Translate an array of keys inside a component$. * The syntax of the strings is 'key@@[default value]' * @param keys The array of keys to translate * @param params Optional parameters contained in the values * @param lang Optional language if different from the current one * @returns The translations or the keys if not found */ - (keys: string[], params?: Record<string, any>, lang?: string): string[]; - <T>(keys: string[], params?: Record<string, any>, lang?: string): T[]; + <T = string>(keys: string[], params?: Record<string, any>, lang?: string): T[]; }; export const useTranslate = (): TranslateFn => { diff --git a/packages/qwik-speak/tests/inline-translate.test.tsx b/packages/qwik-speak/tests/inline-translate.test.tsx index 7143e30..fcd2239 100644 --- a/packages/qwik-speak/tests/inline-translate.test.tsx +++ b/packages/qwik-speak/tests/inline-translate.test.tsx @@ -2,23 +2,19 @@ import { createDOM } from '@builder.io/qwik/testing'; import { component$, useSignal, useTask$, $ } from '@builder.io/qwik'; import { test, describe, expect } from 'vitest'; -import { inlineTranslate } from '../src/inline-translate'; -import { useSpeakContext } from '../src/use-speak'; +import { inlineTranslate as _ } from '../src/inline-translate'; import { QwikSpeakProvider } from '../src/qwik-speak-component'; import { config, translationFnStub } from './config'; -import type { SpeakState } from '../src/types'; -const MyComponent = (props: { ctx: SpeakState }) => { - return <div id="B">{inlineTranslate('test', props.ctx)}</div>; +const MyComponent = () => { + return <div id="B">{_('test')}</div>; }; const TestComponent = component$(() => { - const ctx = useSpeakContext(); - const s = useSignal(''); const test$ = $(() => { - return inlineTranslate('test', ctx); + return _('test'); }); useTask$(async () => { @@ -28,7 +24,7 @@ const TestComponent = component$(() => { return ( <div> <div id="A">{s.value}</div> - <MyComponent ctx={ctx} /> + <MyComponent /> </div> ); }); diff --git a/packages/qwik-speak/tools/inline/plugin.ts b/packages/qwik-speak/tools/inline/plugin.ts index 908f40b..230b5f2 100644 --- a/packages/qwik-speak/tools/inline/plugin.ts +++ b/packages/qwik-speak/tools/inline/plugin.ts @@ -145,7 +145,7 @@ export function qwikSpeakInline(options: QwikSpeakInlineOptions): Plugin { } // Filter code: inlineTranslate if (/inlineTranslate/.test(code)) { - code = transformInline(code); + code = transformInline(code, id); } // Inline in dev mode @@ -173,7 +173,7 @@ export function qwikSpeakInline(options: QwikSpeakInlineOptions): Plugin { transform(code, id); } if (/inlineTranslate/.test(code)) { - transformInline(code); + transformInline(code, id); } } } @@ -346,7 +346,7 @@ export function transform(code: string, id: string): string { } // Props - code = validateProps(id, code, alias); + validateProps(id, code, alias); } return code; @@ -355,7 +355,7 @@ export function transform(code: string, id: string): string { /** * Transform inlineTranslate to placeholder */ -export function transformInline(code: string): string { +export function transformInline(code: string, id: string): string { const alias = getInlineTranslateAlias(code); // Parse sequence @@ -370,7 +370,8 @@ export function transformInline(code: string): string { const args = expr.arguments; if (args?.length > 0) { - if (checkDynamicInline(args, originalFn)) continue; + // Dynamic + validateInline(args, originalFn, id); // Transpile with placeholder const transpiled = originalFn.replace(new RegExp(`${alias}\\(`), `${inlineTranslatePlaceholder}(`); @@ -411,7 +412,7 @@ export function transformPlural(code: string, id: string): string { } // Props - code = validateProps(id, code, alias); + validateProps(id, code, alias); } return code; @@ -420,7 +421,7 @@ export function transformPlural(code: string, id: string): string { export function validateProps(id: string, code: string, alias: string) { const sequence = parseSequenceExpressions(code, signalAlias); - if (sequence.length === 0) return code; + if (sequence.length === 0) return; for (const expr of sequence) { // Arguments @@ -453,7 +454,7 @@ export function validateProps(id: string, code: string, alias: string) { if (mode === 'dev') { console.log( '\n\x1b[31mQwik Speak Inline error\x1b[0m\n%s', - `${transformedFn} is used as a prop or attribute in the following file:\n${id}\nSee https://robisim74.gitbook.io/qwik-speak/library/translate#component-props-and-jsx-attributes` + `${transformedFn} is used as a prop or attribute\nFile: ${id}\nSee https://robisim74.gitbook.io/qwik-speak/library/translate#component-props-and-jsx-attributes` ); } else { console.log( @@ -470,8 +471,44 @@ export function validateProps(id: string, code: string, alias: string) { } } } +} - return code; +export function validateInline(args: Argument[], originalFn: string, id: string) { + let dynamic = false; + + if (args?.[0]?.value) { + // Dynamic key + if (args[0].type === 'Identifier') { + dynamic = true; + } + if (args[0].type === 'Literal') { + if (/\${.*}/.test(args[0].value)) { + dynamic = true; + } + } + + // Dynamic argument (params, lang) + if (args[1]?.type === 'Identifier' || args[1]?.type === 'CallExpression' || + args[2]?.type === 'Identifier' || args[2]?.type === 'CallExpression') { + dynamic = true; + } + } + + if (dynamic) { + originalFn = trimFn(originalFn); + if (mode === 'dev') { + console.log( + '\n\x1b[31mQwik Speak Inline error\x1b[0m\n%s', + `InlineTranslate ${originalFn} contains a dynamic key or param\nFile: ${id}\nSee https://robisim74.gitbook.io/qwik-speak/library/translate#inlinetranslate` + ); + } else { + console.log( + '\n\x1b[31mQwik Speak Inline error\x1b[0m\n%s', + `${originalFn} contains a dynamic key or param\nSee https://robisim74.gitbook.io/qwik-speak/library/translate#inlinetranslate` + ); + } + process.exit(1); + } } export function inlineAll( @@ -511,7 +548,7 @@ export function inline( const args = expr.arguments; if (args?.length > 0) { - const resolvedLang = withLang(lang, placeholder === inlinePlaceholder ? args[2] : args[3], opts); + const resolvedLang = withLang(lang, args[2], opts); let resolvedValue: string | Translation = quoteValue(''); @@ -523,7 +560,7 @@ export function inline( const value = getValue( key, translation[resolvedLang], - placeholder === inlinePlaceholder ? args[1] : args[2], + args[1], opts.keySeparator, opts.keyValueSeparator, resolvedLang @@ -537,7 +574,7 @@ export function inline( const value = getValue( key, translation[resolvedLang], - placeholder === inlinePlaceholder ? args[1] : args[2], + args[1], opts.keySeparator, opts.keyValueSeparator, resolvedLang @@ -676,30 +713,6 @@ export function checkDynamic(args: Argument[], originalFn: string): boolean { return false; } -export function checkDynamicInline(args: Argument[], originalFn: string): boolean { - if (args?.[0]?.value) { - // Dynamic key - if (args[0].type === 'Identifier') { - logDynamic(originalFn, 'key'); - return true; - } - if (args[0].type === 'Literal') { - if (/\${.*}/.test(args[0].value)) { - logDynamic(originalFn, 'key'); - return true; - } - } - - // Dynamic argument (params, lang) - if (args[2]?.type === 'Identifier' || args[2]?.type === 'CallExpression' || - args[3]?.type === 'Identifier' || args[3]?.type === 'CallExpression') { - logDynamic(originalFn, 'params'); - return true; - } - } - return false; -} - export function checkDynamicPlural(args: Argument[], originalFn: string): boolean { if (args?.[0]?.value) { // Dynamic argument (key, params, options, lang) diff --git a/packages/qwik-speak/tools/tests/inline.test.ts b/packages/qwik-speak/tools/tests/inline.test.ts index 76b3bde..399a5c7 100644 --- a/packages/qwik-speak/tools/tests/inline.test.ts +++ b/packages/qwik-speak/tools/tests/inline.test.ts @@ -205,8 +205,8 @@ describe('inline', () => { expect(inlined).toBe('import { useTranslate } from "qwik-speak";const t = useTranslate();const value = `Traduci le tue app Qwik in qualsiasi lingua`'); }); test('transform & inlineTranslate', async () => { - const code = `import { inlineTranslate as t } from "qwik-speak";const value = t('app.subtitle', ctx)`; - const transformed = transformInline(code); + const code = `import { inlineTranslate as _ } from "qwik-speak";const value = _('app.subtitle')`; + const transformed = transformInline(code, ''); const inlined = inline(transformed, { 'en-US': { @@ -226,7 +226,7 @@ describe('inline', () => { assetsPath: 'i18n', outDir: 'dist' }); - expect(inlined).toBe('import { inlineTranslate as t } from "qwik-speak";const value = `Translate your Qwik apps into any language`'); + expect(inlined).toBe('import { inlineTranslate as _ } from "qwik-speak";const value = `Translate your Qwik apps into any language`'); }); test('writeChunks', async () => { const plugin = qwikSpeakInline({ diff --git a/packages/qwik-speak/tools/tests/mock.ts b/packages/qwik-speak/tools/tests/mock.ts index 10318ef..b35a156 100644 --- a/packages/qwik-speak/tools/tests/mock.ts +++ b/packages/qwik-speak/tools/tests/mock.ts @@ -3,16 +3,15 @@ export const mockSource = `import { component$, useSignal } from '@builder.io/qw import type { DocumentHead } from '@builder.io/qwik-city'; import { Speak, - inlineTranslate, + inlineTranslate as _, useFormatDate, useFormatNumber, usePlural, useRelativeTime, - useSpeakContext, useSpeakLocale, useTranslate } from 'qwik-speak'; -import type { SpeakState, Translation } from 'qwik-speak'; +import type { Translation } from 'qwik-speak'; interface TitleProps { name: string; @@ -22,8 +21,8 @@ export const Title = component$((props: TitleProps) => { return (<h1>{props.name}</h1>) }); -export const SubTitle = (props: { ctx: SpeakState }) => { - return <h2>{inlineTranslate('app.subtitle', props.ctx)}</h2>; +export const SubTitle = () => { + return <h2>{_('app.subtitle')}</h2>; }; export const Home = component$(() => { @@ -33,7 +32,6 @@ export const Home = component$(() => { const rt = useRelativeTime(); const fn = useFormatNumber(); - const ctx = useSpeakContext(); const locale = useSpeakLocale(); const units = locale.units!; @@ -50,19 +48,17 @@ export const Home = component$(() => { Object.values(tObject).map((x) => console.log(x)); tArrayObjects.map((x) => console.log(x['num'])); - const [title, text] = t(['app.title', 'home.text']); - return ( <div class="content"> - <Title name={title} /> + <Title name={_('app.title')} /> - <SubTitle ctx={ctx} /> + <SubTitle /> <h3>{t('home.params')}</h3> <p>{t('home.greeting', { name: 'Qwik Speak' })}</p> <h3>{t('home.tags')}</h3> - <p dangerouslySetInnerHTML={text}></p> + <p dangerouslySetInnerHTML={_('home.text')}></p> <h3>{t('home.plural')}</h3> <p class="counter">{p(count.value, 'home.devs')}</p> @@ -98,6 +94,7 @@ export const head: DocumentHead = { export const mockCode = `import { SubTitle } from "./routes/[...lang]/index.tsx"; import { Title } from "./routes/[...lang]/index.tsx"; +import { inlineTranslate as _ } from "qwik-speak"; import { _IMMUTABLE } from "@builder.io/qwik"; import { _fnSignal } from "@builder.io/qwik"; import { _jsxC } from "@builder.io/qwik"; @@ -108,7 +105,6 @@ import { useFormatNumber } from "qwik-speak"; import { usePlural } from "qwik-speak"; import { useRelativeTime } from "qwik-speak"; import { useSignal } from "@builder.io/qwik"; -import { useSpeakContext } from "qwik-speak"; import { useSpeakLocale } from "qwik-speak"; import { useTranslate } from "qwik-speak"; export const s_dYGb4b0cyCA = ()=>{ @@ -117,7 +113,6 @@ export const s_dYGb4b0cyCA = ()=>{ const fd = useFormatDate(); const rt = useRelativeTime(); const fn = useFormatNumber(); - const ctx = useSpeakContext(); const locale = useSpeakLocale(); const count = useSignal(0); const tParam = t('home.greeting', { @@ -132,30 +127,26 @@ export const s_dYGb4b0cyCA = ()=>{ console.log(item); Object.values(tObject).map((x)=>console.log(x)); tArrayObjects.map((x)=>console.log(x['num'])); - const [title, text] = t([ - 'app.title', - 'home.text' - ]); return /*#__PURE__*/ _jsxQ("div", null, { class: "content" }, [ /*#__PURE__*/ _jsxC(Title, { - name: title - }, 3, "1L_2"), - /*#__PURE__*/ _jsxC(SubTitle, { - ctx: ctx, + get name () { + return _('app.title'); + }, [_IMMUTABLE]: { - ctx: _IMMUTABLE + name: _IMMUTABLE } - }, 3, "1L_3"), + }, 3, "1L_2"), + /*#__PURE__*/ _jsxC(SubTitle, null, 3, "1L_3"), /*#__PURE__*/ _jsxQ("h3", null, null, t('home.params'), 1, null), /*#__PURE__*/ _jsxQ("p", null, null, t('home.greeting', { name: 'Qwik Speak' }), 1, null), /*#__PURE__*/ _jsxQ("h3", null, null, t('home.tags'), 1, null), - /*#__PURE__*/ _jsxQ("p", { - dangerouslySetInnerHTML: text - }, null, null, 3, null), + /*#__PURE__*/ _jsxQ("p", null, { + dangerouslySetInnerHTML: _('home.text') + }, null, 3, null), /*#__PURE__*/ _jsxQ("h3", null, null, t('home.plural'), 1, null), /*#__PURE__*/ _jsxQ("p", null, { class: "counter" @@ -186,6 +177,7 @@ export const s_dYGb4b0cyCA = ()=>{ export const mockTransformedCode = `import { SubTitle } from "./routes/[...lang]/index.tsx"; import { Title } from "./routes/[...lang]/index.tsx"; +import { inlineTranslate as _ } from "qwik-speak"; import { _IMMUTABLE } from "@builder.io/qwik"; import { _fnSignal } from "@builder.io/qwik"; import { _jsxC } from "@builder.io/qwik"; @@ -196,7 +188,6 @@ import { useFormatNumber } from "qwik-speak"; import { usePlural } from "qwik-speak"; import { useRelativeTime } from "qwik-speak"; import { useSignal } from "@builder.io/qwik"; -import { useSpeakContext } from "qwik-speak"; import { useSpeakLocale } from "qwik-speak"; import { useTranslate } from "qwik-speak"; export const s_dYGb4b0cyCA = ()=>{ @@ -205,7 +196,6 @@ export const s_dYGb4b0cyCA = ()=>{ const fd = useFormatDate(); const rt = useRelativeTime(); const fn = useFormatNumber(); - const ctx = useSpeakContext(); const locale = useSpeakLocale(); const count = useSignal(0); const tParam = __qsInline('home.greeting', { @@ -220,30 +210,26 @@ export const s_dYGb4b0cyCA = ()=>{ console.log(item); Object.values(tObject).map((x)=>console.log(x)); tArrayObjects.map((x)=>console.log(x['num'])); - const [title, text] = __qsInline([ - 'app.title', - 'home.text' - ]); return /*#__PURE__*/ _jsxQ("div", null, { class: "content" }, [ /*#__PURE__*/ _jsxC(Title, { - name: title - }, 3, "1L_2"), - /*#__PURE__*/ _jsxC(SubTitle, { - ctx: ctx, + get name () { + return __qsInlineTranslate('app.title'); + }, [_IMMUTABLE]: { - ctx: _IMMUTABLE + name: _IMMUTABLE } - }, 3, "1L_3"), + }, 3, "1L_2"), + /*#__PURE__*/ _jsxC(SubTitle, null, 3, "1L_3"), /*#__PURE__*/ _jsxQ("h3", null, null, __qsInline('home.params'), 1, null), /*#__PURE__*/ _jsxQ("p", null, null, __qsInline('home.greeting', { name: 'Qwik Speak' }), 1, null), /*#__PURE__*/ _jsxQ("h3", null, null, __qsInline('home.tags'), 1, null), - /*#__PURE__*/ _jsxQ("p", { - dangerouslySetInnerHTML: text - }, null, null, 3, null), + /*#__PURE__*/ _jsxQ("p", null, { + dangerouslySetInnerHTML: __qsInlineTranslate('home.text') + }, null, 3, null), /*#__PURE__*/ _jsxQ("h3", null, null, __qsInline('home.plural'), 1, null), /*#__PURE__*/ _jsxQ("p", null, { class: "counter" @@ -278,7 +264,6 @@ export const mockChunkCode = `const s_dYGb4b0cyCA = () => { const fd = useFormatDate(); const rt = useRelativeTime(); const fn = useFormatNumber(); - const ctx = useSpeakContext(); const locale = useSpeakLocale(); const count = Wc(0); const tParam = __qsInline("home.greeting", { @@ -293,30 +278,26 @@ export const mockChunkCode = `const s_dYGb4b0cyCA = () => { console.log(item); Object.values(tObject).map((x) => console.log(x)); tArrayObjects.map((x) => console.log(x["num"])); - const [title, text] = __qsInline([ - 'app.title', - 'home.text' - ]); return /* @__PURE__ */ Sr("div", null, { class: "content" }, [ /* @__PURE__ */ jr(Title, { - name: title - }, 3, "1L_2"), - /* @__PURE__ */ jr(SubTitle, { - ctx, + get name() { + return __qsInlineTranslate("app.title"); + }, [Y]: { - ctx: Y + name: Y } - }, 3, "1L_3"), + }, 3, "1L_2"), + /* @__PURE__ */ jr(SubTitle, null, 3, "1L_3"), /* @__PURE__ */ Sr("h3", null, null, __qsInline("home.params"), 1, null), /* @__PURE__ */ Sr("p", null, null, __qsInline("home.greeting", { name: "Qwik Speak" }), 1, null), /* @__PURE__ */ Sr("h3", null, null, __qsInline("home.tags"), 1, null), - /* @__PURE__ */ Sr("p", { - dangerouslySetInnerHTML: text - }, null, null, 3, null), + /* @__PURE__ */ Sr("p", null, { + dangerouslySetInnerHTML: __qsInlineTranslate("home.text") + }, null, 3, null), /* @__PURE__ */ Sr("h3", null, null, __qsInline("home.plural"), 1, null), /* @__PURE__ */ Sr("p", null, { class: "counter" @@ -351,7 +332,6 @@ export const mockInlinedCode = `const s_dYGb4b0cyCA = () => { const fd = useFormatDate(); const rt = useRelativeTime(); const fn = useFormatNumber(); - const ctx = useSpeakContext(); const locale = useSpeakLocale(); const count = Wc(0); const tParam = \`Hi! I am \${\`\`}\`; @@ -364,25 +344,24 @@ export const mockInlinedCode = `const s_dYGb4b0cyCA = () => { console.log(item); Object.values(tObject).map((x) => console.log(x)); tArrayObjects.map((x) => console.log(x["num"])); - const [title, text] = [\`\`,\`<em>Internationalization (i18n) library to translate texts, dates and numbers in Qwik apps</em>\`]; return /* @__PURE__ */ Sr("div", null, { class: "content" }, [ /* @__PURE__ */ jr(Title, { - name: title - }, 3, "1L_2"), - /* @__PURE__ */ jr(SubTitle, { - ctx, + get name() { + return \`\`; + }, [Y]: { - ctx: Y + name: Y } - }, 3, "1L_3"), + }, 3, "1L_2"), + /* @__PURE__ */ jr(SubTitle, null, 3, "1L_3"), /* @__PURE__ */ Sr("h3", null, null, \`Parameters\`, 1, null), /* @__PURE__ */ Sr("p", null, null, \`Hi! I am Qwik Speak\`, 1, null), /* @__PURE__ */ Sr("h3", null, null, \`Html tags\`, 1, null), - /* @__PURE__ */ Sr("p", { - dangerouslySetInnerHTML: text - }, null, null, 3, null), + /* @__PURE__ */ Sr("p", null, { + dangerouslySetInnerHTML: \`<em>Internationalization (i18n) library to translate texts, dates and numbers in Qwik apps</em>\` + }, null, 3, null), /* @__PURE__ */ Sr("h3", null, null, \`Plural\`, 1, null), /* @__PURE__ */ Sr("p", null, { class: "counter" @@ -417,7 +396,6 @@ export const mockInlinedCodeByLang = `const s_dYGb4b0cyCA = () => { const fd = useFormatDate(); const rt = useRelativeTime(); const fn = useFormatNumber(); - const ctx = useSpeakContext(); const locale = useSpeakLocale(); const count = Wc(0); const tParam = \`Ciao! Sono \${\`\`}\`; @@ -430,25 +408,24 @@ export const mockInlinedCodeByLang = `const s_dYGb4b0cyCA = () => { console.log(item); Object.values(tObject).map((x) => console.log(x)); tArrayObjects.map((x) => console.log(x["num"])); - const [title, text] = [\`\`,\`<em>Libreria di internazionalizzazione (i18n) per tradurre testi, date e numeri nelle app Qwik</em>\`]; return /* @__PURE__ */ Sr("div", null, { class: "content" }, [ /* @__PURE__ */ jr(Title, { - name: title - }, 3, "1L_2"), - /* @__PURE__ */ jr(SubTitle, { - ctx, + get name() { + return \`\`; + }, [Y]: { - ctx: Y + name: Y } - }, 3, "1L_3"), + }, 3, "1L_2"), + /* @__PURE__ */ jr(SubTitle, null, 3, "1L_3"), /* @__PURE__ */ Sr("h3", null, null, \`Parametri\`, 1, null), /* @__PURE__ */ Sr("p", null, null, \`Ciao! Sono Qwik Speak\`, 1, null), /* @__PURE__ */ Sr("h3", null, null, \`Tag Html\`, 1, null), - /* @__PURE__ */ Sr("p", { - dangerouslySetInnerHTML: text - }, null, null, 3, null), + /* @__PURE__ */ Sr("p", null, { + dangerouslySetInnerHTML: \`<em>Libreria di internazionalizzazione (i18n) per tradurre testi, date e numeri nelle app Qwik</em>\` + }, null, 3, null), /* @__PURE__ */ Sr("h3", null, null, \`Plurale\`, 1, null), /* @__PURE__ */ Sr("p", null, { class: "counter" diff --git a/src/routes/[...lang]/index.tsx b/src/routes/[...lang]/index.tsx index 8cfb39b..adc1b52 100644 --- a/src/routes/[...lang]/index.tsx +++ b/src/routes/[...lang]/index.tsx @@ -2,16 +2,14 @@ import { component$, useSignal } from '@builder.io/qwik'; import type { DocumentHead } from '@builder.io/qwik-city'; import { Speak, - inlineTranslate, + inlineTranslate as _, useFormatDate, useFormatNumber, usePlural, useRelativeTime, - useSpeakContext, useSpeakLocale, useTranslate } from 'qwik-speak'; -import type { SpeakState } from 'qwik-speak'; interface TitleProps { name: string; @@ -21,8 +19,8 @@ export const Title = component$<TitleProps>(props => { return (<h1>{props.name}</h1>) }); -export const SubTitle = (props: { ctx: SpeakState }) => { - return <h2>{inlineTranslate('app.subtitle', props.ctx)}</h2>; +export const SubTitle = () => { + return <h2>{_('app.subtitle')}</h2>; }; export const Home = component$(() => { @@ -32,26 +30,22 @@ export const Home = component$(() => { const rt = useRelativeTime(); const fn = useFormatNumber(); - const ctx = useSpeakContext(); const locale = useSpeakLocale(); const units = locale.units!; const count = useSignal(0); - // Translate inside components rather than on props - const [title, text] = t(['app.title', 'home.text']); - return ( <div class="content"> - <Title name={title} /> + <Title name={_('app.title')} /> - <SubTitle ctx={ctx} /> + <SubTitle /> <h3>{t('home.params')}</h3> <p>{t('home.greeting', { name: 'Qwik Speak' })}</p> <h3>{t('home.tags')}</h3> - <p dangerouslySetInnerHTML={text}></p> + <p dangerouslySetInnerHTML={_('home.text')}></p> <h3>{t('home.plural')}</h3> <p class="counter">{p(count.value, 'home.devs')}</p> From 46c9e0cd7a5f7ecfbc1a22684ae13f426dfaee2e Mon Sep 17 00:00:00 2001 From: Roberto Simonetti <robisim74@gmail.com> Date: Thu, 16 Nov 2023 12:43:00 +0100 Subject: [PATCH 10/21] Feat: start no context --- packages/qwik-speak/src/context.ts | 4 +- packages/qwik-speak/src/core.ts | 6 +- packages/qwik-speak/src/index.ts | 19 +- packages/qwik-speak/src/inline-plural.ts | 46 ++++ packages/qwik-speak/src/inline-translate.ts | 14 +- .../qwik-speak/src/qwik-speak-component.tsx | 12 +- .../src/qwik-speak-inline-component.tsx | 41 ++++ packages/qwik-speak/src/speak-component.tsx | 28 +-- packages/qwik-speak/src/use-display-name.ts | 2 +- packages/qwik-speak/src/use-format-date.ts | 2 +- packages/qwik-speak/src/use-format-number.ts | 2 +- .../src/{use-speak.ts => use-functions.ts} | 0 packages/qwik-speak/src/use-inline.ts | 19 -- packages/qwik-speak/src/use-plural.ts | 46 ---- packages/qwik-speak/src/use-relative-time.ts | 2 +- packages/qwik-speak/src/use-translate-path.ts | 2 +- packages/qwik-speak/src/use-translate.ts | 2 +- .../tests/inline-translate.test.tsx | 6 +- .../tests/use-format-number.test.tsx | 2 +- packages/qwik-speak/tests/use-plural.test.tsx | 4 +- packages/qwik-speak/tools/core/parser.ts | 25 +-- packages/qwik-speak/tools/extract/index.ts | 27 +-- packages/qwik-speak/tools/inline/plugin.ts | 212 +++--------------- .../qwik-speak/tools/tests/inline.test.ts | 18 +- .../qwik-speak/tools/tests/parser.test.ts | 4 +- .../change-locale/change-locale.tsx | 3 +- src/components/header/header.tsx | 8 +- src/components/router-head/router-head.tsx | 4 +- src/root.tsx | 3 +- src/routes/[...lang]/index.tsx | 17 +- src/routes/[...lang]/page/index.tsx | 4 +- 31 files changed, 218 insertions(+), 366 deletions(-) create mode 100644 packages/qwik-speak/src/inline-plural.ts create mode 100644 packages/qwik-speak/src/qwik-speak-inline-component.tsx rename packages/qwik-speak/src/{use-speak.ts => use-functions.ts} (100%) delete mode 100644 packages/qwik-speak/src/use-inline.ts delete mode 100644 packages/qwik-speak/src/use-plural.ts diff --git a/packages/qwik-speak/src/context.ts b/packages/qwik-speak/src/context.ts index 06cc9db..99bd1ac 100644 --- a/packages/qwik-speak/src/context.ts +++ b/packages/qwik-speak/src/context.ts @@ -2,6 +2,6 @@ import { createContextId } from '@builder.io/qwik'; import type { SpeakState } from './types'; -export const SpeakContext = createContextId<SpeakState>('qwikspeak'); +export const SpeakContext = createContextId<SpeakState>('qwik-speak'); -export const serverSpeakContext: Partial<SpeakState> = {}; +export const _speakContext: Partial<SpeakState> = {}; diff --git a/packages/qwik-speak/src/core.ts b/packages/qwik-speak/src/core.ts index 66cba2d..3b34422 100644 --- a/packages/qwik-speak/src/core.ts +++ b/packages/qwik-speak/src/core.ts @@ -1,5 +1,5 @@ import { noSerialize } from '@builder.io/qwik'; -import { isDev, isServer } from '@builder.io/qwik/build'; +import { isBrowser, isDev, isServer } from '@builder.io/qwik/build'; import type { Translation, SpeakState, LoadTranslationFn } from './types'; import { logWarn } from './log'; @@ -57,8 +57,8 @@ export const loadTranslations = async ( for (const lang of resolvedLangs) { let tasks: Promise<any>[]; - // Cache requests in prod mode - if (!isDev) { + // Cache requests on client in prod mode + if (!isDev && isBrowser) { const memoized = memoize(translationFn.loadTranslation$); tasks = resolvedAssets.map(asset => memoized(lang, asset)); } else { diff --git a/packages/qwik-speak/src/index.ts b/packages/qwik-speak/src/index.ts index 186627b..1d1f419 100644 --- a/packages/qwik-speak/src/index.ts +++ b/packages/qwik-speak/src/index.ts @@ -12,7 +12,7 @@ export type { QwikSpeakProps } from './qwik-speak-component'; export type { SpeakProps } from './speak-component'; export type { TranslateFn } from './use-translate'; export type { TranslatePathFn } from './use-translate-path'; -export type { PluralFn } from './use-plural'; +export type { InlinePluralFn as PluralFn } from './inline-plural'; export type { InlineTranslateFn } from './inline-translate'; export type { FormatDateFn } from './use-format-date'; export type { FormatNumberFn } from './use-format-number'; @@ -20,21 +20,22 @@ export type { RelativeTimeFn } from './use-relative-time'; export type { DisplayNameFn } from './use-display-name'; // Components export { QwikSpeakProvider } from './qwik-speak-component'; -export { Speak, useSpeak } from './speak-component'; -// Functions -export { inlineTranslate } from './inline-translate'; -// Use functions +export { QwikSpeakInline } from './qwik-speak-inline-component'; +export { Speak } from './speak-component'; +// Inline functions +export { t } from './inline-translate'; +export { p } from './inline-plural'; + export { useTranslate } from './use-translate'; export { useTranslatePath } from './use-translate-path'; -export { usePlural } from './use-plural'; export { useFormatNumber } from './use-format-number'; export { useFormatDate } from './use-format-date'; export { useRelativeTime } from './use-relative-time'; export { useDisplayName } from './use-display-name'; + +// Use functions export { useSpeakContext, useSpeakLocale, useSpeakConfig, -} from './use-speak'; -// Internals -export { useInline } from './use-inline'; +} from './use-functions'; diff --git a/packages/qwik-speak/src/inline-plural.ts b/packages/qwik-speak/src/inline-plural.ts new file mode 100644 index 0000000..fe55209 --- /dev/null +++ b/packages/qwik-speak/src/inline-plural.ts @@ -0,0 +1,46 @@ +import type { SpeakState } from './types'; +import { getValue } from './core'; +import { _speakContext } from './context'; + +export type InlinePluralFn = { + /** + * Get the plural by a number. + * The value is passed as a parameter to the translate function + * @param value A number or a string + * @param key Optional key + * @param params Optional parameters contained in the values + * @param options Intl PluralRulesOptions object + * @param lang Optional language if different from the current one + * @returns The translation for the plural + */ + ( + value: number | string, + key?: string, + params?: Record<string, any>, + options?: Intl.PluralRulesOptions, + lang?: string + ): string; +}; + +const inlinePlural: InlinePluralFn = ( + value: number | string, + key?: string, + params?: Record<string, any>, + options?: Intl.PluralRulesOptions, + lang?: string +) => { + const ctx = _speakContext as SpeakState; + + const { locale, translation, config } = ctx; + + lang ??= locale.lang; + + value = +value; + + const rule = new Intl.PluralRules(lang, options).select(value); + key = key ? `${key}${config.keySeparator}${rule}` : rule; + + return getValue(key, translation[lang], { value, ...params }, config.keySeparator, config.keyValueSeparator); +}; + +export { inlinePlural as p }; diff --git a/packages/qwik-speak/src/inline-translate.ts b/packages/qwik-speak/src/inline-translate.ts index 2fafa99..1a76798 100644 --- a/packages/qwik-speak/src/inline-translate.ts +++ b/packages/qwik-speak/src/inline-translate.ts @@ -1,6 +1,6 @@ import type { SpeakState } from './types'; import { getValue } from './core'; -import { serverSpeakContext } from './context'; +import { _speakContext } from './context'; export type InlineTranslateFn = { /** @@ -11,7 +11,8 @@ export type InlineTranslateFn = { * @param lang Optional language if different from the current one * @returns The translation or the key if not found */ - <T = string>(key: string, params?: Record<string, any>, lang?: string): T; + (key: string, params?: Record<string, any>, lang?: string): string; + <T>(key: string, params?: Record<string, any>, lang?: string): T; /** * Translate an array of keys. * The syntax of the strings is 'key@@[default value]' @@ -20,15 +21,16 @@ export type InlineTranslateFn = { * @param lang Optional language if different from the current one * @returns The translations or the keys if not found */ - <T = string>(keys: string[], params?: Record<string, any>, lang?: string): T[]; + (keys: string[], params?: Record<string, any>, lang?: string): string[]; + <T>(keys: string[], params?: Record<string, any>, lang?: string): T[]; }; -export const inlineTranslate: InlineTranslateFn = ( +const inlineTranslate: InlineTranslateFn = ( keys: string | string[], params?: Record<string, any>, lang?: string ) => { - const ctx = serverSpeakContext as SpeakState; + const ctx = _speakContext as SpeakState; const { locale, translation, config } = ctx; @@ -40,3 +42,5 @@ export const inlineTranslate: InlineTranslateFn = ( return getValue(keys, translation[lang], params, config.keySeparator, config.keyValueSeparator); }; + +export { inlineTranslate as t }; diff --git a/packages/qwik-speak/src/qwik-speak-component.tsx b/packages/qwik-speak/src/qwik-speak-component.tsx index 9d9adbb..cb5f2dd 100644 --- a/packages/qwik-speak/src/qwik-speak-component.tsx +++ b/packages/qwik-speak/src/qwik-speak-component.tsx @@ -1,8 +1,8 @@ import { $, component$, Slot, useContextProvider, useServerData, useTask$ } from '@builder.io/qwik'; -import { isDev, isServer } from '@builder.io/qwik/build'; +import { isDev } from '@builder.io/qwik/build'; import type { SpeakConfig, SpeakLocale, SpeakState, TranslationFn } from './types'; -import { serverSpeakContext, SpeakContext } from './context'; +import { _speakContext, SpeakContext } from './context'; import { loadTranslations } from './core'; import { logDebug, logWarn } from './log'; @@ -66,11 +66,9 @@ export const QwikSpeakProvider = component$((props: QwikSpeakProps) => { const { locale, translation, config } = state; // Create server context - if (isServer) { - serverSpeakContext.locale = locale; - serverSpeakContext.translation = translation; - serverSpeakContext.config = config; - } + _speakContext.locale = locale; + _speakContext.translation = translation; + _speakContext.config = config; // Create context useContextProvider(SpeakContext, state); diff --git a/packages/qwik-speak/src/qwik-speak-inline-component.tsx b/packages/qwik-speak/src/qwik-speak-inline-component.tsx new file mode 100644 index 0000000..61f9892 --- /dev/null +++ b/packages/qwik-speak/src/qwik-speak-inline-component.tsx @@ -0,0 +1,41 @@ +import { component$, Slot, useVisibleTask$ } from '@builder.io/qwik'; +import { isDev } from '@builder.io/qwik/build'; + +import { useSpeakContext } from './use-functions'; +import { _speakContext, } from './context'; + +export const QwikSpeakInline = component$(() => { + const ctx = useSpeakContext(); + + useVisibleTask$(() => { + const { locale, translation, config } = ctx; + + // In dev mode, send lang from client to the server + if (isDev) { + console.debug( + '%cQwik Speak Inline', + 'background: #0c75d2; color: white; padding: 2px 3px; border-radius: 2px; font-size: 0.8em;', + 'Ready' + ); + if (import.meta.hot) { + import.meta.hot.send('qwik-speak:lang', { msg: locale.lang }); + } + } + + // Create client context + _speakContext.locale = locale; + _speakContext.translation = translation; + _speakContext.config = config; + + if (isDev) { + console.debug( + '%cQwik Speak Inline', + 'background: #0c75d2; color: white; padding: 2px 3px; border-radius: 2px; font-size: 0.8em;', + 'Client context', + _speakContext + ); + } + }, { strategy: 'document-ready' }); + + return <Slot />; +}); diff --git a/packages/qwik-speak/src/speak-component.tsx b/packages/qwik-speak/src/speak-component.tsx index 49ea506..cc23058 100644 --- a/packages/qwik-speak/src/speak-component.tsx +++ b/packages/qwik-speak/src/speak-component.tsx @@ -1,8 +1,9 @@ import { component$, Slot, useTask$ } from '@builder.io/qwik'; -import { isDev } from '@builder.io/qwik/build'; +import { isBrowser, isDev } from '@builder.io/qwik/build'; -import { useSpeakContext } from './use-speak'; +import { useSpeakContext } from './use-functions'; import { loadTranslations } from './core'; +import { _speakContext } from './context'; import { logWarn } from './log'; export interface SpeakProps { @@ -25,16 +26,6 @@ export interface SpeakProps { * Translations will only be available in child components */ export const Speak = component$((props: SpeakProps) => { - useSpeak(props); - - return <Slot />; -}); - -/** - * Add scoped translation data to the context. - * Translations will only be available in child components - */ -export const useSpeak = (props: SpeakProps) => { const ctx = useSpeakContext(); const { config } = ctx; @@ -54,5 +45,16 @@ export const useSpeak = (props: SpeakProps) => { } await loadTranslations(ctx, props.assets, props.runtimeAssets, props.langs); + + if (isDev && isBrowser) { + console.debug( + '%cQwik Speak Inline', + 'background: #0c75d2; color: white; padding: 2px 3px; border-radius: 2px; font-size: 0.8em;', + 'Client context', + _speakContext + ); + } }); -}; + + return <Slot />; +}); diff --git a/packages/qwik-speak/src/use-display-name.ts b/packages/qwik-speak/src/use-display-name.ts index bfdf2f4..b3e05c8 100644 --- a/packages/qwik-speak/src/use-display-name.ts +++ b/packages/qwik-speak/src/use-display-name.ts @@ -1,4 +1,4 @@ -import { useSpeakLocale } from './use-speak'; +import { useSpeakLocale } from './use-functions'; export type DisplayNameFn = { /** diff --git a/packages/qwik-speak/src/use-format-date.ts b/packages/qwik-speak/src/use-format-date.ts index 9fb8591..ff4297c 100644 --- a/packages/qwik-speak/src/use-format-date.ts +++ b/packages/qwik-speak/src/use-format-date.ts @@ -1,4 +1,4 @@ -import { useSpeakLocale } from './use-speak'; +import { useSpeakLocale } from './use-functions'; export type FormatDateFn = { /** diff --git a/packages/qwik-speak/src/use-format-number.ts b/packages/qwik-speak/src/use-format-number.ts index 6b7f97b..143c3bc 100644 --- a/packages/qwik-speak/src/use-format-number.ts +++ b/packages/qwik-speak/src/use-format-number.ts @@ -1,4 +1,4 @@ -import { useSpeakLocale } from './use-speak'; +import { useSpeakLocale } from './use-functions'; export type FormatNumberFn = { /** diff --git a/packages/qwik-speak/src/use-speak.ts b/packages/qwik-speak/src/use-functions.ts similarity index 100% rename from packages/qwik-speak/src/use-speak.ts rename to packages/qwik-speak/src/use-functions.ts diff --git a/packages/qwik-speak/src/use-inline.ts b/packages/qwik-speak/src/use-inline.ts deleted file mode 100644 index 67c2073..0000000 --- a/packages/qwik-speak/src/use-inline.ts +++ /dev/null @@ -1,19 +0,0 @@ -import { useVisibleTask$ } from '@builder.io/qwik'; - -import { useSpeakLocale } from './use-speak'; - -export const useInline = () => { - const locale = useSpeakLocale(); - - // In dev mode, send lang from client to the server - useVisibleTask$(() => { - console.debug( - '%cQwik Speak Inline', - 'background: #0c75d2; color: white; padding: 2px 3px; border-radius: 2px; font-size: 0.8em;', - 'ready' - ); - if (import.meta.hot) { - import.meta.hot.send('qwik-speak:lang', { msg: locale.lang }); - } - }, { strategy: 'document-ready' }); -}; diff --git a/packages/qwik-speak/src/use-plural.ts b/packages/qwik-speak/src/use-plural.ts deleted file mode 100644 index 85eb15e..0000000 --- a/packages/qwik-speak/src/use-plural.ts +++ /dev/null @@ -1,46 +0,0 @@ -import { useSpeakContext } from './use-speak'; -import { getValue } from './core'; - -export type PluralFn = { - /** - * Get the plural by a number. - * The value is passed as a parameter to the translate function - * @param value A number or a string - * @param key Optional key - * @param params Optional parameters contained in the values - * @param options Intl PluralRulesOptions object - * @param lang Optional language if different from the current one - * @returns The translation for the plural - */ - ( - value: number | string, - key?: string, - params?: Record<string, any>, - options?: Intl.PluralRulesOptions, - lang?: string - ): string; -}; - -export const usePlural = (): PluralFn => { - const ctx = useSpeakContext(); - - const plural = ( - value: number | string, - key?: string, - params?: Record<string, any>, - options?: Intl.PluralRulesOptions, - lang?: string - ) => { - const { locale, translation, config } = ctx; - lang ??= locale.lang; - - value = +value; - - const rule = new Intl.PluralRules(lang, options).select(value); - key = key ? `${key}${config.keySeparator}${rule}` : rule; - - return getValue(key, translation[lang], { value, ...params }, config.keySeparator, config.keyValueSeparator); - }; - - return plural as PluralFn; -}; diff --git a/packages/qwik-speak/src/use-relative-time.ts b/packages/qwik-speak/src/use-relative-time.ts index f9de108..7080c2c 100644 --- a/packages/qwik-speak/src/use-relative-time.ts +++ b/packages/qwik-speak/src/use-relative-time.ts @@ -1,4 +1,4 @@ -import { useSpeakLocale } from './use-speak'; +import { useSpeakLocale } from './use-functions'; export type RelativeTimeFn = { /** diff --git a/packages/qwik-speak/src/use-translate-path.ts b/packages/qwik-speak/src/use-translate-path.ts index 9c69d0c..a512222 100644 --- a/packages/qwik-speak/src/use-translate-path.ts +++ b/packages/qwik-speak/src/use-translate-path.ts @@ -1,5 +1,5 @@ import { isDev } from '@builder.io/qwik/build'; -import { useSpeakContext } from './use-speak'; +import { useSpeakContext } from './use-functions'; import { logWarn } from './log'; export type TranslatePathFn = { diff --git a/packages/qwik-speak/src/use-translate.ts b/packages/qwik-speak/src/use-translate.ts index ff2b57b..b28edd2 100644 --- a/packages/qwik-speak/src/use-translate.ts +++ b/packages/qwik-speak/src/use-translate.ts @@ -1,4 +1,4 @@ -import { useSpeakContext } from './use-speak'; +import { useSpeakContext } from './use-functions'; import { getValue } from './core'; export type TranslateFn = { diff --git a/packages/qwik-speak/tests/inline-translate.test.tsx b/packages/qwik-speak/tests/inline-translate.test.tsx index fcd2239..08223b8 100644 --- a/packages/qwik-speak/tests/inline-translate.test.tsx +++ b/packages/qwik-speak/tests/inline-translate.test.tsx @@ -2,19 +2,19 @@ import { createDOM } from '@builder.io/qwik/testing'; import { component$, useSignal, useTask$, $ } from '@builder.io/qwik'; import { test, describe, expect } from 'vitest'; -import { inlineTranslate as _ } from '../src/inline-translate'; +import { t } from '../src/inline-translate'; import { QwikSpeakProvider } from '../src/qwik-speak-component'; import { config, translationFnStub } from './config'; const MyComponent = () => { - return <div id="B">{_('test')}</div>; + return <div id="B">{t('test')}</div>; }; const TestComponent = component$(() => { const s = useSignal(''); const test$ = $(() => { - return _('test'); + return t('test'); }); useTask$(async () => { diff --git a/packages/qwik-speak/tests/use-format-number.test.tsx b/packages/qwik-speak/tests/use-format-number.test.tsx index 0f40703..6436571 100644 --- a/packages/qwik-speak/tests/use-format-number.test.tsx +++ b/packages/qwik-speak/tests/use-format-number.test.tsx @@ -5,7 +5,7 @@ import { test, describe, expect } from 'vitest'; import { useFormatNumber } from '../src/use-format-number'; import { QwikSpeakProvider } from '../src/qwik-speak-component'; import { config } from './config'; -import { useSpeakLocale } from '../src/use-speak'; +import { useSpeakLocale } from '../src/use-functions'; const TestComponent = component$(() => { const fn = useFormatNumber(); diff --git a/packages/qwik-speak/tests/use-plural.test.tsx b/packages/qwik-speak/tests/use-plural.test.tsx index 1301a5e..a6d6dc5 100644 --- a/packages/qwik-speak/tests/use-plural.test.tsx +++ b/packages/qwik-speak/tests/use-plural.test.tsx @@ -2,12 +2,12 @@ import { createDOM } from '@builder.io/qwik/testing'; import { component$ } from '@builder.io/qwik'; import { test, describe, expect } from 'vitest'; -import { usePlural } from '../src/use-plural'; +import { inlinePlural } from '../src/inline-plural'; import { QwikSpeakProvider } from '../src/qwik-speak-component'; import { config, translationFnStub } from './config'; const TestComponent = component$(() => { - const p = usePlural(); + const p = inlinePlural(); return ( <div> diff --git a/packages/qwik-speak/tools/core/parser.ts b/packages/qwik-speak/tools/core/parser.ts index 09634d1..a3961dd 100644 --- a/packages/qwik-speak/tools/core/parser.ts +++ b/packages/qwik-speak/tools/core/parser.ts @@ -404,24 +404,19 @@ export function parseSequenceExpressions(code: string, alias: string): CallExpre return sequenceExpressions; } -/** - * Get useTranslate alias - */ -export function getUseTranslateAlias(code: string): string | null { - let translateAlias = code.match(/(?<=\bconst\s).*?(?=\s?=\s?useTranslate\(\);?)/)?.[0]?.trim(); - if (!translateAlias) return null; - // Assert position at a word boundary - if (!translateAlias.startsWith('$')) translateAlias = `\\b${translateAlias}`; - // Escape special characters - translateAlias = translateAlias.replace(/\$/g, '\\$'); - return translateAlias; +export function matchInlineTranslate(code: string): boolean { + return /{.*\bt\b.*}\s+from\s+.qwik-speak.;?/s.test(code); +} + +export function matchInlinePlural(code: string): boolean { + return /{.*\bp\b.*}\s+from\s+.qwik-speak.;?/s.test(code); } /** * Get inlineTranslate alias */ export function getInlineTranslateAlias(code: string): string { - let translateAlias = code.match(/(?<=inlineTranslate\s+as).*?(?=,|\})/s)?.[0]?.trim() || 'inlineTranslate'; + let translateAlias = code.match(/(?<=\bt\s+as).*?(?=,|\})/s)?.[0]?.trim() || 't'; // Assert position at a word boundary if (!translateAlias.startsWith('$')) translateAlias = `\\b${translateAlias}`; // Escape special characters @@ -430,10 +425,10 @@ export function getInlineTranslateAlias(code: string): string { } /** - * Get usePlural alias + * Get inlinePlural alias */ -export function getUsePluralAlias(code: string): string | null { - let pluralAlias = code.match(/(?<=\bconst\s).*?(?=\s?=\s?usePlural\(\);?)/)?.[0]?.trim(); +export function getInlinePluralAlias(code: string): string | null { + let pluralAlias = code.match(/(?<=\bp\s+as).*?(?=,|\})/s)?.[0]?.trim() || 'p'; if (!pluralAlias) return null; // Assert position at a word boundary if (!pluralAlias.startsWith('$')) pluralAlias = `\\b${pluralAlias}`; diff --git a/packages/qwik-speak/tools/extract/index.ts b/packages/qwik-speak/tools/extract/index.ts index 711f023..e587d58 100644 --- a/packages/qwik-speak/tools/extract/index.ts +++ b/packages/qwik-speak/tools/extract/index.ts @@ -4,7 +4,14 @@ import { extname, join, normalize } from 'path'; import type { QwikSpeakExtractOptions, Translation } from '../core/types'; import type { Argument, CallExpression, Element } from '../core/parser'; -import { getUseTranslateAlias, getInlineTranslateAlias, getUsePluralAlias, parseJson, parseSequenceExpressions } from '../core/parser'; +import { + getInlineTranslateAlias, + getInlinePluralAlias, + parseJson, + parseSequenceExpressions, + matchInlineTranslate, + matchInlinePlural +} from '../core/parser'; import { deepClone, deepMerge, deepSet, merge } from '../core/merge'; import { minDepth, sortTarget, toJsonString } from '../core/format'; import { getOptions, getRules } from '../core/intl-parser'; @@ -109,20 +116,8 @@ export async function qwikSpeakExtract(options: QwikSpeakExtractOptions) { } } - // useTranslate - if (/useTranslate/.test(code)) { - const alias = getUseTranslateAlias(code); - if (alias) { - // Clear types - clearTypes(alias); - // Parse sequence - const sequence = parseSequenceExpressions(code, alias); - parseSequence(sequence); - } - } - // inlineTranslate - if (/inlineTranslate/.test(code)) { + if (matchInlineTranslate(code)) { const alias = getInlineTranslateAlias(code); // Clear types clearTypes(alias); @@ -132,8 +127,8 @@ export async function qwikSpeakExtract(options: QwikSpeakExtractOptions) { } // usePlural - if (/usePlural/.test(code)) { - const alias = getUsePluralAlias(code); + if (matchInlinePlural(code)) { + const alias = getInlinePluralAlias(code); if (alias) { // Parse sequence diff --git a/packages/qwik-speak/tools/inline/plugin.ts b/packages/qwik-speak/tools/inline/plugin.ts index 230b5f2..c1ccbe5 100644 --- a/packages/qwik-speak/tools/inline/plugin.ts +++ b/packages/qwik-speak/tools/inline/plugin.ts @@ -6,17 +6,14 @@ import { extname, normalize } from 'path'; import type { QwikSpeakInlineOptions, Translation } from '../core/types'; import type { Argument, Property } from '../core/parser'; -import { getUseTranslateAlias, getInlineTranslateAlias, getUsePluralAlias, parseJson, parse, tokenize } from '../core/parser'; +import { getInlineTranslateAlias, getInlinePluralAlias, parseJson, matchInlinePlural, matchInlineTranslate } from '../core/parser'; import { parseSequenceExpressions } from '../core/parser'; import { getOptions, getRules } from '../core/intl-parser'; import { merge } from '../core/merge'; -const inlinePlaceholder = '__qsInline'; const inlineTranslatePlaceholder = '__qsInlineTranslate'; const inlinePluralPlaceholder = '__qsInlinePlural'; -const signalAlias = '\\b_fnSignal'; - // Logs const missingValues: string[] = []; const dynamics: string[] = []; @@ -135,23 +132,18 @@ export function qwikSpeakInline(options: QwikSpeakInlineOptions): Plugin { if (target === 'client' || (target === 'ssr' && options?.ssr === false)) { // Filter id if (/\/src\//.test(id) && /\.(js|cjs|mjs|jsx|ts|tsx)$/.test(id)) { - // Filter code: usePlural - if (/usePlural/.test(code)) { + // Filter code: inlinePlural + if (matchInlinePlural(code)) { code = transformPlural(code, id); } - // Filter code: useTranslate - if (/useTranslate/.test(code)) { - code = transform(code, id); - } // Filter code: inlineTranslate - if (/inlineTranslate/.test(code)) { - code = transformInline(code, id); + if (matchInlineTranslate(code)) { + code = transformTranslate(code, id); } // Inline in dev mode if (mode === 'dev') { - if (code.includes(inlinePlaceholder) || - code.includes(inlineTranslatePlaceholder) || + if (code.includes(inlineTranslatePlaceholder) || code.includes(inlinePluralPlaceholder)) { code = inlineAll(code, devLang, resolvedOptions, translation); @@ -163,38 +155,24 @@ export function qwikSpeakInline(options: QwikSpeakInlineOptions): Plugin { } } - // Validate server code - if (target === 'ssr' && mode === 'dev') { - if (/\/src\//.test(id) && /\.(js|cjs|mjs|jsx|ts|tsx)$/.test(id)) { - if (/usePlural/.test(code)) { - transformPlural(code, id); - } - if (/useTranslate/.test(code)) { - transform(code, id); - } - if (/inlineTranslate/.test(code)) { - transformInline(code, id); + // Check QwikSpeakInline component + if (target === 'ssr') { + if (id.endsWith('root.tsx' || id.endsWith('root.jsx'))) { + if (!/QwikSpeakInline/.test(code)) { + console.log( + '\n\x1b[31mQwik Speak Inline error\x1b[0m\n%s', + "Missing 'QwikSpeakInline' component in 'root.tsx' file: see https://robisim74.gitbook.io/qwik-speak/tools/setup" + ); + process.exit(1) } } - } - - // Add useInline in dev mode - if (target === 'ssr' && mode === 'dev') { - if (id.endsWith('router-head.tsx') || id.endsWith('router-head.jsx')) { - code = code.replace(/^/, `import { useInline } from 'qwik-speak';\n`); - code = code.replace('return /*#__PURE__*/ _jsxC', `useInline();\nreturn /*#__PURE__*/ _jsxC`); - - return code; - } - } - // Check base url in prod mode - if (target === 'ssr' && mode === 'prod') { + // Check base url if (id.endsWith('entry.ssr.tsx') || id.endsWith('entry.ssr.jsx')) { if (!/(?<!\/\/\s*)base:\s*extractBase/.test(code)) { console.log( '\n\x1b[31mQwik Speak Inline error\x1b[0m\n%s', - "Missing 'base' option in 'entry.ssr.tsx' file: see https://robisim74.gitbook.io/qwik-speak/tools/inline#usage" + "Missing 'base' option in 'entry.ssr.tsx' file: see https://robisim74.gitbook.io/qwik-speak/tools/setup" ); process.exit(1); } @@ -317,45 +295,10 @@ export async function writeChunks( } } -/** - * Transform useTranslate to placeholder - */ -export function transform(code: string, id: string): string { - const alias = getUseTranslateAlias(code); - - if (alias) { - // Parse sequence - const sequence = parseSequenceExpressions(code, alias); - - if (sequence.length === 0) return code; - - for (const expr of sequence) { - // Original function - const originalFn = expr.value; - // Arguments - const args = expr.arguments; - - if (args?.length > 0) { - if (checkDynamic(args, originalFn)) continue; - - // Transpile with placeholder - const transpiled = originalFn.replace(new RegExp(`${alias}\\(`), `${inlinePlaceholder}(`); - // Replace - code = code.replace(originalFn, transpiled); - } - } - - // Props - validateProps(id, code, alias); - } - - return code; -} - /** * Transform inlineTranslate to placeholder */ -export function transformInline(code: string, id: string): string { +export function transformTranslate(code: string, id: string): string { const alias = getInlineTranslateAlias(code); // Parse sequence @@ -371,7 +314,7 @@ export function transformInline(code: string, id: string): string { if (args?.length > 0) { // Dynamic - validateInline(args, originalFn, id); + if (checkDynamicTranslate(args, originalFn)) continue; // Transpile with placeholder const transpiled = originalFn.replace(new RegExp(`${alias}\\(`), `${inlineTranslatePlaceholder}(`); @@ -384,10 +327,10 @@ export function transformInline(code: string, id: string): string { } /** - * Transform usePlural to placeholder + * Transform inlinePlural to placeholder */ export function transformPlural(code: string, id: string): string { - const alias = getUsePluralAlias(code); + const alias = getInlinePluralAlias(code); if (alias) { // Parse sequence @@ -410,107 +353,11 @@ export function transformPlural(code: string, id: string): string { code = code.replace(originalFn, transpiled); } } - - // Props - validateProps(id, code, alias); } return code; } -export function validateProps(id: string, code: string, alias: string) { - const sequence = parseSequenceExpressions(code, signalAlias); - - if (sequence.length === 0) return; - - for (const expr of sequence) { - // Arguments - const args = expr.arguments; - - // Check identifier - if (args?.length > 2) { - const arrayIndex = args.findIndex(arg => arg.type === 'ArrayExpression'); - const callIndex = args.findIndex(arg => arg.type === 'CallExpression') - if (arrayIndex !== -1 && callIndex !== -1) { - const elements = args[arrayIndex].elements; - if (elements) { - const index = elements.findIndex(element => new RegExp(`^${alias}$`).test(element.value)); - if (index !== -1 && args[index].type === 'Identifier') { - // Transformed function - let transformedFn = args[callIndex].value; - if (transformedFn && args[index].value) { - const transformedAlias = `\\b${args[index].value}`; - const tokens = tokenize(transformedFn); - const transformedExpr = parse(tokens, transformedFn, transformedAlias); - - if (transformedExpr) { - // Arguments - const transformedArgs = transformedExpr.arguments; - - if (transformedArgs?.length > 0) { - // Invalid props or attributes - transformedFn = trimFn(transformedFn); - transformedFn = transformedFn.replace(new RegExp(`^${args[index].value}`), elements[index].value); - if (mode === 'dev') { - console.log( - '\n\x1b[31mQwik Speak Inline error\x1b[0m\n%s', - `${transformedFn} is used as a prop or attribute\nFile: ${id}\nSee https://robisim74.gitbook.io/qwik-speak/library/translate#component-props-and-jsx-attributes` - ); - } else { - console.log( - '\n\x1b[31mQwik Speak Inline error\x1b[0m\n%s', - `${transformedFn} is used as a prop or attribute\nSee https://robisim74.gitbook.io/qwik-speak/library/translate#component-props-and-jsx-attributes` - ); - } - process.exit(1); - } - } - } - } - } - } - } - } -} - -export function validateInline(args: Argument[], originalFn: string, id: string) { - let dynamic = false; - - if (args?.[0]?.value) { - // Dynamic key - if (args[0].type === 'Identifier') { - dynamic = true; - } - if (args[0].type === 'Literal') { - if (/\${.*}/.test(args[0].value)) { - dynamic = true; - } - } - - // Dynamic argument (params, lang) - if (args[1]?.type === 'Identifier' || args[1]?.type === 'CallExpression' || - args[2]?.type === 'Identifier' || args[2]?.type === 'CallExpression') { - dynamic = true; - } - } - - if (dynamic) { - originalFn = trimFn(originalFn); - if (mode === 'dev') { - console.log( - '\n\x1b[31mQwik Speak Inline error\x1b[0m\n%s', - `InlineTranslate ${originalFn} contains a dynamic key or param\nFile: ${id}\nSee https://robisim74.gitbook.io/qwik-speak/library/translate#inlinetranslate` - ); - } else { - console.log( - '\n\x1b[31mQwik Speak Inline error\x1b[0m\n%s', - `${originalFn} contains a dynamic key or param\nSee https://robisim74.gitbook.io/qwik-speak/library/translate#inlinetranslate` - ); - } - process.exit(1); - } -} - export function inlineAll( code: string, lang: string, @@ -518,18 +365,15 @@ export function inlineAll( translation: Translation ) { if (code.includes(inlinePluralPlaceholder)) { - code = inlinePlural(code, inlinePluralPlaceholder, inlinePlaceholder, lang, opts); - } - if (code.includes(inlinePlaceholder)) { - code = inline(code, translation, inlinePlaceholder, lang, opts); + code = inlinePlural(code, inlinePluralPlaceholder, inlineTranslatePlaceholder, lang, opts); } if (code.includes(inlineTranslatePlaceholder)) { - code = inline(code, translation, inlineTranslatePlaceholder, lang, opts); + code = inlineTranslate(code, translation, inlineTranslatePlaceholder, lang, opts); } return code; } -export function inline( +export function inlineTranslate( code: string, translation: Translation, placeholder: string, @@ -584,7 +428,7 @@ export function inline( } // Transpile - const transpiled = transpileFn(resolvedValue); + const transpiled = transpileTranslateFn(resolvedValue); // Replace code = code.replace(originalFn, transpiled); @@ -631,9 +475,9 @@ export function inlinePlural( } /** - * Transpile the function + * Transpile the translate function */ -export function transpileFn(value: string | Translation): string { +export function transpileTranslateFn(value: string | Translation): string { if (typeof value === 'object') { return `${stringifyObject(value)}`; } else { @@ -689,7 +533,7 @@ export function transpilePluralFn( return translation; } -export function checkDynamic(args: Argument[], originalFn: string): boolean { +export function checkDynamicTranslate(args: Argument[], originalFn: string): boolean { if (args?.[0]?.value) { // Dynamic key if (args[0].type === 'Identifier') { diff --git a/packages/qwik-speak/tools/tests/inline.test.ts b/packages/qwik-speak/tools/tests/inline.test.ts index 399a5c7..1890185 100644 --- a/packages/qwik-speak/tools/tests/inline.test.ts +++ b/packages/qwik-speak/tools/tests/inline.test.ts @@ -4,7 +4,7 @@ import { writeFile } from 'fs/promises'; import { normalize } from 'path'; import { getRules } from '../core/intl-parser'; -import { getValue, inline, qwikSpeakInline, transform, transformInline, transpileFn, transpilePluralFn } from '../inline/plugin'; +import { getValue, inlineTranslate, qwikSpeakInline, transform, transformTranslate, transpileTranslateFn, transpilePluralFn } from '../inline/plugin'; import { mockChunkCode, mockCode, mockInlinedCode, mockInlinedCodeByLang, mockTransformedCode, mockTranslatedAsset, mockTranslatedAssetByLang } from './mock'; // Mock part of 'fs' module @@ -100,20 +100,20 @@ describe('inline', () => { }); test('transpileFn', () => { const value = '`Value`'; - const line = transpileFn(value); + const line = transpileTranslateFn(value); expect(line).toBe('`Value`'); }); test('transpileFn with array', () => { let value = ['`Value1`', '`Value2`']; - let line = transpileFn(value); + let line = transpileTranslateFn(value); expect(line).toBe('[`Value1`,`Value2`]'); value = ['Value1', 'Value2']; - line = transpileFn(value); + line = transpileTranslateFn(value); expect(line).toBe('["Value1","Value2"]'); }); test('transpileFn with objects', () => { const value = { value1: 'Value1' }; - const line = transpileFn(value); + const line = transpileTranslateFn(value); expect(line).toBe('{"value1":"Value1"}'); }); test('transpilePluralFn', () => { @@ -153,7 +153,7 @@ describe('inline', () => { expect(line).toBe('(new Intl.PluralRules(`en-US`, {type: `cardinal`}).select(+count.value) === `other` && __qsInline(`home.devs.other`, {value: count.value, role: `software`}, `en-US`) || __qsInline(`home.devs.one`, {value: count.value, role: `software`}, `en-US`))'); }); test('inline arrays', async () => { - const inlined = inline(`const values = __qsInline(['app.title', 'app.subtitle'])`, + const inlined = inlineTranslate(`const values = __qsInline(['app.title', 'app.subtitle'])`, { 'en-US': { 'app': { @@ -178,7 +178,7 @@ describe('inline', () => { test('transform & inline multilingual', async () => { const code = `import { useTranslate } from "qwik-speak";const t = useTranslate();const value = t('app.subtitle', undefined, 'it-IT')`; const transformed = transform(code, ''); - const inlined = inline(transformed, + const inlined = inlineTranslate(transformed, { 'en-US': { 'app': { @@ -206,8 +206,8 @@ describe('inline', () => { }); test('transform & inlineTranslate', async () => { const code = `import { inlineTranslate as _ } from "qwik-speak";const value = _('app.subtitle')`; - const transformed = transformInline(code, ''); - const inlined = inline(transformed, + const transformed = transformTranslate(code, ''); + const inlined = inlineTranslate(transformed, { 'en-US': { 'app': { diff --git a/packages/qwik-speak/tools/tests/parser.test.ts b/packages/qwik-speak/tools/tests/parser.test.ts index 9d93a9d..7b1aa81 100644 --- a/packages/qwik-speak/tools/tests/parser.test.ts +++ b/packages/qwik-speak/tools/tests/parser.test.ts @@ -1,6 +1,6 @@ import { test, describe, expect } from 'vitest'; -import { getInlineTranslateAlias, getUsePluralAlias, getUseTranslateAlias, parse, parseSequenceExpressions, tokenize } from '../core/parser'; +import { getInlineTranslateAlias, getInlinePluralAlias, getUseTranslateAlias, parse, parseSequenceExpressions, tokenize } from '../core/parser'; describe('parser: tokenize', () => { test('tokenize', () => { @@ -648,7 +648,7 @@ describe('aliases', () => { expect(alias).toBe('\\binlineTranslate'); }); test('getUsePluralAlias', () => { - const alias = getUsePluralAlias('const p = usePlural();'); + const alias = getInlinePluralAlias('const p = usePlural();'); expect(alias).toBe('\\bp'); }); }); diff --git a/src/components/change-locale/change-locale.tsx b/src/components/change-locale/change-locale.tsx index 253e8c6..a79e446 100644 --- a/src/components/change-locale/change-locale.tsx +++ b/src/components/change-locale/change-locale.tsx @@ -1,7 +1,7 @@ import { $, component$, useStyles$ } from '@builder.io/qwik'; import { useLocation } from '@builder.io/qwik-city'; import type { SpeakLocale } from 'qwik-speak'; -import { useSpeakLocale, useSpeakConfig, useDisplayName, useTranslate } from 'qwik-speak'; +import { useSpeakLocale, useSpeakConfig, useDisplayName, t } from 'qwik-speak'; // import { useTranslatePath } from 'qwik-speak'; import styles from './change-locale.css?inline'; @@ -9,7 +9,6 @@ import styles from './change-locale.css?inline'; export const ChangeLocale = component$(() => { useStyles$(styles); - const t = useTranslate(); const dn = useDisplayName(); /** Uncomment this line to use url rewriting to translate paths */ diff --git a/src/components/header/header.tsx b/src/components/header/header.tsx index 7c1cb02..1f4a24a 100644 --- a/src/components/header/header.tsx +++ b/src/components/header/header.tsx @@ -1,6 +1,6 @@ import { component$, useStyles$ } from '@builder.io/qwik'; import { Link, useLocation } from '@builder.io/qwik-city'; -import { useSpeakConfig, useSpeakLocale, useTranslate } from 'qwik-speak'; +import { useSpeakConfig, useSpeakLocale, t } from 'qwik-speak'; // import { useTranslatePath } from 'qwik-speak'; import { ChangeLocale } from '../change-locale/change-locale'; @@ -11,8 +11,6 @@ import styles from './header.css?inline'; export const Header = component$(() => { useStyles$(styles); - const t = useTranslate(); - const pathname = useLocation().url.pathname; const lang = useSpeakLocale().lang; const config = useSpeakConfig(); @@ -31,8 +29,8 @@ export const Header = component$(() => { <header class="header"> <div class="logo"> {/** Uncomment this line to use url rewriting to translate paths */} - {/* <Link href={homePath}> */} - <Link href={getHref('/')}> + {/* <Link href={homePath} title={t('app.title')> */} + <Link href={getHref('/')} title={t('app.title')}> <SpeakLogo /> </Link> </div> diff --git a/src/components/router-head/router-head.tsx b/src/components/router-head/router-head.tsx index 40261f7..8b88aec 100644 --- a/src/components/router-head/router-head.tsx +++ b/src/components/router-head/router-head.tsx @@ -1,13 +1,11 @@ import { component$ } from '@builder.io/qwik'; import { useDocumentHead, useLocation } from '@builder.io/qwik-city'; -import { useTranslate } from 'qwik-speak'; +import { t } from 'qwik-speak'; /** * The RouterHead component is placed inside of the document `<head>` element. */ export const RouterHead = component$(() => { - const t = useTranslate(); - const head = useDocumentHead(); const loc = useLocation(); diff --git a/src/root.tsx b/src/root.tsx index e1e69dd..e21ba4f 100644 --- a/src/root.tsx +++ b/src/root.tsx @@ -1,6 +1,6 @@ import { component$ } from '@builder.io/qwik'; import { QwikCityProvider, RouterOutlet, ServiceWorkerRegister } from '@builder.io/qwik-city'; -import { QwikSpeakProvider } from 'qwik-speak'; +import { QwikSpeakInline, QwikSpeakProvider } from 'qwik-speak'; import { RouterHead } from './components/router-head/router-head'; import { config } from './speak-config'; @@ -20,6 +20,7 @@ export default component$(() => { <link rel="manifest" href="/manifest.json" /> <RouterHead /> <ServiceWorkerRegister /> + <QwikSpeakInline /> {/* Register Qwik Speak Inline */} </head> <body> <RouterOutlet /> diff --git a/src/routes/[...lang]/index.tsx b/src/routes/[...lang]/index.tsx index adc1b52..274ebd3 100644 --- a/src/routes/[...lang]/index.tsx +++ b/src/routes/[...lang]/index.tsx @@ -1,14 +1,13 @@ import { component$, useSignal } from '@builder.io/qwik'; -import type { DocumentHead } from '@builder.io/qwik-city'; +import { type DocumentHead } from '@builder.io/qwik-city'; import { Speak, - inlineTranslate as _, + t, + p, useFormatDate, useFormatNumber, - usePlural, useRelativeTime, - useSpeakLocale, - useTranslate + useSpeakLocale } from 'qwik-speak'; interface TitleProps { @@ -20,12 +19,10 @@ export const Title = component$<TitleProps>(props => { }); export const SubTitle = () => { - return <h2>{_('app.subtitle')}</h2>; + return <h2>{t('app.subtitle')}</h2>; }; export const Home = component$(() => { - const t = useTranslate(); - const p = usePlural(); const fd = useFormatDate(); const rt = useRelativeTime(); const fn = useFormatNumber(); @@ -37,7 +34,7 @@ export const Home = component$(() => { return ( <div class="content"> - <Title name={_('app.title')} /> + <Title name={t('app.title')} /> <SubTitle /> @@ -45,7 +42,7 @@ export const Home = component$(() => { <p>{t('home.greeting', { name: 'Qwik Speak' })}</p> <h3>{t('home.tags')}</h3> - <p dangerouslySetInnerHTML={_('home.text')}></p> + <p dangerouslySetInnerHTML={t('home.text')}></p> <h3>{t('home.plural')}</h3> <p class="counter">{p(count.value, 'home.devs')}</p> diff --git a/src/routes/[...lang]/page/index.tsx b/src/routes/[...lang]/page/index.tsx index 98f7ffc..6af5738 100644 --- a/src/routes/[...lang]/page/index.tsx +++ b/src/routes/[...lang]/page/index.tsx @@ -1,10 +1,8 @@ import { component$ } from '@builder.io/qwik'; import type { DocumentHead } from '@builder.io/qwik-city'; -import { Speak, useTranslate } from 'qwik-speak'; +import { Speak, t } from 'qwik-speak'; export const Page = component$(() => { - const t = useTranslate(); - const key = 'dynamic'; return ( From a82308e86fe30cbbd172764a419af1ca1341b391 Mon Sep 17 00:00:00 2001 From: Roberto Simonetti <robisim74@gmail.com> Date: Fri, 17 Nov 2023 22:19:25 +0100 Subject: [PATCH 11/21] Feat: no context --- packages/qwik-speak/src/context.ts | 11 +++++ packages/qwik-speak/src/core.ts | 31 ++++++++------ packages/qwik-speak/src/index.ts | 14 +++---- packages/qwik-speak/src/inline-plural.ts | 36 ++++++++-------- packages/qwik-speak/src/inline-translate.ts | 32 +++++++------- .../qwik-speak/src/qwik-speak-component.tsx | 11 ++--- .../src/qwik-speak-inline-component.tsx | 5 ++- packages/qwik-speak/src/use-translate.ts | 41 ------------------ packages/qwik-speak/tools/core/parser.ts | 11 ++--- packages/qwik-speak/tools/extract/index.ts | 13 +++--- packages/qwik-speak/tools/inline/plugin.ts | 42 ++++++++++--------- .../change-locale/change-locale.tsx | 4 +- src/components/header/header.tsx | 4 +- src/components/router-head/router-head.tsx | 4 +- src/routes/[...lang]/index.tsx | 8 +++- src/routes/[...lang]/page/index.tsx | 4 +- src/speak-config.ts | 2 +- 17 files changed, 134 insertions(+), 139 deletions(-) delete mode 100644 packages/qwik-speak/src/use-translate.ts diff --git a/packages/qwik-speak/src/context.ts b/packages/qwik-speak/src/context.ts index 99bd1ac..bf6273d 100644 --- a/packages/qwik-speak/src/context.ts +++ b/packages/qwik-speak/src/context.ts @@ -4,4 +4,15 @@ import type { SpeakState } from './types'; export const SpeakContext = createContextId<SpeakState>('qwik-speak'); +/** + * Shared server/client context: + * - config + * - translation + */ export const _speakContext: Partial<SpeakState> = {}; + +export let getLang = (): string => ''; + +export const setGetLangFn = (fn: () => string) => { + getLang = () => fn(); +}; diff --git a/packages/qwik-speak/src/core.ts b/packages/qwik-speak/src/core.ts index 3b34422..2d23612 100644 --- a/packages/qwik-speak/src/core.ts +++ b/packages/qwik-speak/src/core.ts @@ -1,7 +1,7 @@ -import { noSerialize } from '@builder.io/qwik'; import { isBrowser, isDev, isServer } from '@builder.io/qwik/build'; import type { Translation, SpeakState, LoadTranslationFn } from './types'; +import { _speakContext } from './context'; import { logWarn } from './log'; const cache: Record<string, Promise<any>> = {}; @@ -33,6 +33,8 @@ export const loadTranslations = async ( ): Promise<void> => { if (isServer || runtimeAssets) { const { locale, translation, translationFn, config } = ctx; + // Shared server/client context + const { translation: _translation } = _speakContext as SpeakState; if (isDev) { const conflictingAsset = assets?.find(asset => runtimeAssets?.includes(asset)) || @@ -73,18 +75,21 @@ export const loadTranslations = async ( for (const data of assetSources) { if (data?.source) { - if (isServer && assets?.includes(data.asset)) { - // Assets are not serialized - for (let [key, value] of Object.entries<Translation>(data.source)) { - // Depth 0: convert string to String object - if (typeof value === 'string') { - value = new String(value); - } - translation[lang][key] = noSerialize(value); + if (isServer) { + // On server: + // - assets & runtimeAssets in shared context + // - runtimeAssets in context (must be serialized to be passed to the client) + if (assets?.includes(data.asset)) { + Object.assign(_translation[lang], data.source); + } else { + Object.assign(_translation[lang], data.source); + // Serialize whether runtimeAssets + Object.assign(translation[lang], data.source); } } else { - // Serialize whether runtime assets - Object.assign(translation[lang], data.source); + // On client: + // - assets & runtimeAssets in shared context + Object.assign(_translation[lang], data.source); } } } @@ -112,8 +117,8 @@ export const getValue = ( undefined, data); if (value) { - if (typeof value === 'string' || value instanceof String) - return params ? transpileParams(value.toString(), params) : value.toString(); + if (typeof value === 'string') + return params ? transpileParams(value, params) : value; if (typeof value === 'object') return params ? JSON.parse(transpileParams(JSON.stringify(value), params)) : value; } diff --git a/packages/qwik-speak/src/index.ts b/packages/qwik-speak/src/index.ts index 1d1f419..d850957 100644 --- a/packages/qwik-speak/src/index.ts +++ b/packages/qwik-speak/src/index.ts @@ -10,9 +10,8 @@ export type { } from './types'; export type { QwikSpeakProps } from './qwik-speak-component'; export type { SpeakProps } from './speak-component'; -export type { TranslateFn } from './use-translate'; export type { TranslatePathFn } from './use-translate-path'; -export type { InlinePluralFn as PluralFn } from './inline-plural'; +export type { InlinePluralFn } from './inline-plural'; export type { InlineTranslateFn } from './inline-translate'; export type { FormatDateFn } from './use-format-date'; export type { FormatNumberFn } from './use-format-number'; @@ -23,19 +22,16 @@ export { QwikSpeakProvider } from './qwik-speak-component'; export { QwikSpeakInline } from './qwik-speak-inline-component'; export { Speak } from './speak-component'; // Inline functions -export { t } from './inline-translate'; -export { p } from './inline-plural'; - -export { useTranslate } from './use-translate'; -export { useTranslatePath } from './use-translate-path'; +export { inlineTranslate } from './inline-translate'; +export { inlinePlural } from './inline-plural'; +// Use functions export { useFormatNumber } from './use-format-number'; export { useFormatDate } from './use-format-date'; export { useRelativeTime } from './use-relative-time'; export { useDisplayName } from './use-display-name'; - -// Use functions export { useSpeakContext, useSpeakLocale, useSpeakConfig, } from './use-functions'; +export { useTranslatePath } from './use-translate-path'; diff --git a/packages/qwik-speak/src/inline-plural.ts b/packages/qwik-speak/src/inline-plural.ts index fe55209..fd39e64 100644 --- a/packages/qwik-speak/src/inline-plural.ts +++ b/packages/qwik-speak/src/inline-plural.ts @@ -1,6 +1,6 @@ import type { SpeakState } from './types'; import { getValue } from './core'; -import { _speakContext } from './context'; +import { _speakContext, getLang } from './context'; export type InlinePluralFn = { /** @@ -22,25 +22,27 @@ export type InlinePluralFn = { ): string; }; -const inlinePlural: InlinePluralFn = ( - value: number | string, - key?: string, - params?: Record<string, any>, - options?: Intl.PluralRulesOptions, - lang?: string -) => { - const ctx = _speakContext as SpeakState; +export const inlinePlural = (): InlinePluralFn => { + const currentLang = getLang(); - const { locale, translation, config } = ctx; + const plural = ( + value: number | string, + key?: string, + params?: Record<string, any>, + options?: Intl.PluralRulesOptions, + lang?: string + ) => { + const { translation, config } = _speakContext as SpeakState; - lang ??= locale.lang; + lang ??= currentLang; - value = +value; + value = +value; - const rule = new Intl.PluralRules(lang, options).select(value); - key = key ? `${key}${config.keySeparator}${rule}` : rule; + const rule = new Intl.PluralRules(lang, options).select(value); + key = key ? `${key}${config.keySeparator}${rule}` : rule; - return getValue(key, translation[lang], { value, ...params }, config.keySeparator, config.keyValueSeparator); -}; + return getValue(key, translation[lang], { value, ...params }, config.keySeparator, config.keyValueSeparator); + }; -export { inlinePlural as p }; + return plural as InlinePluralFn; +}; diff --git a/packages/qwik-speak/src/inline-translate.ts b/packages/qwik-speak/src/inline-translate.ts index 1a76798..b99bb6c 100644 --- a/packages/qwik-speak/src/inline-translate.ts +++ b/packages/qwik-speak/src/inline-translate.ts @@ -1,6 +1,6 @@ import type { SpeakState } from './types'; import { getValue } from './core'; -import { _speakContext } from './context'; +import { _speakContext, getLang } from './context'; export type InlineTranslateFn = { /** @@ -25,22 +25,24 @@ export type InlineTranslateFn = { <T>(keys: string[], params?: Record<string, any>, lang?: string): T[]; }; -const inlineTranslate: InlineTranslateFn = ( - keys: string | string[], - params?: Record<string, any>, - lang?: string -) => { - const ctx = _speakContext as SpeakState; +export const inlineTranslate = (): InlineTranslateFn => { + const currentLang = getLang(); - const { locale, translation, config } = ctx; + const translate: InlineTranslateFn = ( + keys: string | string[], + params?: Record<string, any>, + lang?: string + ) => { + const { translation, config } = _speakContext as SpeakState; - lang ??= locale.lang; + lang ??= currentLang; - if (Array.isArray(keys)) { - return keys.map(k => getValue(k, translation[lang!], params, config.keySeparator, config.keyValueSeparator)); - } + if (Array.isArray(keys)) { + return keys.map(k => getValue(k, translation[lang!], params, config.keySeparator, config.keyValueSeparator)); + } - return getValue(keys, translation[lang], params, config.keySeparator, config.keyValueSeparator); -}; + return getValue(keys, translation[lang], params, config.keySeparator, config.keyValueSeparator); + }; -export { inlineTranslate as t }; + return translate as InlineTranslateFn; +}; diff --git a/packages/qwik-speak/src/qwik-speak-component.tsx b/packages/qwik-speak/src/qwik-speak-component.tsx index cb5f2dd..3de27db 100644 --- a/packages/qwik-speak/src/qwik-speak-component.tsx +++ b/packages/qwik-speak/src/qwik-speak-component.tsx @@ -1,8 +1,8 @@ -import { $, component$, Slot, useContextProvider, useServerData, useTask$ } from '@builder.io/qwik'; +import { $, component$, getLocale, Slot, useContextProvider, useServerData, useTask$ } from '@builder.io/qwik'; import { isDev } from '@builder.io/qwik/build'; import type { SpeakConfig, SpeakLocale, SpeakState, TranslationFn } from './types'; -import { _speakContext, SpeakContext } from './context'; +import { _speakContext, setGetLangFn, SpeakContext } from './context'; import { loadTranslations } from './core'; import { logDebug, logWarn } from './log'; @@ -63,12 +63,13 @@ export const QwikSpeakProvider = component$((props: QwikSpeakProps) => { translationFn: resolvedTranslationFn }; - const { locale, translation, config } = state; + const { config } = state; // Create server context - _speakContext.locale = locale; - _speakContext.translation = translation; + _speakContext.translation = Object.fromEntries(props.config.supportedLocales.map(value => [value.lang, {}])); _speakContext.config = config; + // Set the getLang function to use Qwik locale + setGetLangFn(() => getLocale(config.defaultLocale.lang)); // Create context useContextProvider(SpeakContext, state); diff --git a/packages/qwik-speak/src/qwik-speak-inline-component.tsx b/packages/qwik-speak/src/qwik-speak-inline-component.tsx index 61f9892..05ab198 100644 --- a/packages/qwik-speak/src/qwik-speak-inline-component.tsx +++ b/packages/qwik-speak/src/qwik-speak-inline-component.tsx @@ -2,7 +2,7 @@ import { component$, Slot, useVisibleTask$ } from '@builder.io/qwik'; import { isDev } from '@builder.io/qwik/build'; import { useSpeakContext } from './use-functions'; -import { _speakContext, } from './context'; +import { _speakContext, setGetLangFn, } from './context'; export const QwikSpeakInline = component$(() => { const ctx = useSpeakContext(); @@ -23,9 +23,10 @@ export const QwikSpeakInline = component$(() => { } // Create client context - _speakContext.locale = locale; _speakContext.translation = translation; _speakContext.config = config; + // Set the getLang function to use the current lang + setGetLangFn(() => locale.lang); if (isDev) { console.debug( diff --git a/packages/qwik-speak/src/use-translate.ts b/packages/qwik-speak/src/use-translate.ts deleted file mode 100644 index b28edd2..0000000 --- a/packages/qwik-speak/src/use-translate.ts +++ /dev/null @@ -1,41 +0,0 @@ -import { useSpeakContext } from './use-functions'; -import { getValue } from './core'; - -export type TranslateFn = { - /** - * Translate a key inside a component$. - * The syntax of the string is 'key@@[default value]' - * @param key The key to translate - * @param params Optional parameters contained in the value - * @param lang Optional language if different from the current one - * @returns The translation or the key if not found - */ - <T = string>(key: string, params?: Record<string, any>, lang?: string): T; - /** - * Translate an array of keys inside a component$. - * The syntax of the strings is 'key@@[default value]' - * @param keys The array of keys to translate - * @param params Optional parameters contained in the values - * @param lang Optional language if different from the current one - * @returns The translations or the keys if not found - */ - <T = string>(keys: string[], params?: Record<string, any>, lang?: string): T[]; -}; - -export const useTranslate = (): TranslateFn => { - const ctx = useSpeakContext(); - - const translate = (keys: string | string[], params?: Record<string, any>, lang?: string) => { - const { locale, translation, config } = ctx; - - lang ??= locale.lang; - - if (Array.isArray(keys)) { - return keys.map(k => getValue(k, translation[lang!], params, config.keySeparator, config.keyValueSeparator)); - } - - return getValue(keys, translation[lang], params, config.keySeparator, config.keyValueSeparator); - }; - - return translate as TranslateFn; -}; diff --git a/packages/qwik-speak/tools/core/parser.ts b/packages/qwik-speak/tools/core/parser.ts index a3961dd..e7128f0 100644 --- a/packages/qwik-speak/tools/core/parser.ts +++ b/packages/qwik-speak/tools/core/parser.ts @@ -405,18 +405,19 @@ export function parseSequenceExpressions(code: string, alias: string): CallExpre } export function matchInlineTranslate(code: string): boolean { - return /{.*\bt\b.*}\s+from\s+.qwik-speak.;?/s.test(code); + return /inlineTranslate/.test(code); } export function matchInlinePlural(code: string): boolean { - return /{.*\bp\b.*}\s+from\s+.qwik-speak.;?/s.test(code); + return /inlinePlural/.test(code); } /** * Get inlineTranslate alias */ -export function getInlineTranslateAlias(code: string): string { - let translateAlias = code.match(/(?<=\bt\s+as).*?(?=,|\})/s)?.[0]?.trim() || 't'; +export function getInlineTranslateAlias(code: string): string | null { + let translateAlias = code.match(/(?<=\bconst\s).*?(?=\s?=\s?inlineTranslate\(\);?)/)?.[0]?.trim(); + if (!translateAlias) return null; // Assert position at a word boundary if (!translateAlias.startsWith('$')) translateAlias = `\\b${translateAlias}`; // Escape special characters @@ -428,7 +429,7 @@ export function getInlineTranslateAlias(code: string): string { * Get inlinePlural alias */ export function getInlinePluralAlias(code: string): string | null { - let pluralAlias = code.match(/(?<=\bp\s+as).*?(?=,|\})/s)?.[0]?.trim() || 'p'; + let pluralAlias = code.match(/(?<=\bconst\s).*?(?=\s?=\s?inlinePlural\(\);?)/)?.[0]?.trim(); if (!pluralAlias) return null; // Assert position at a word boundary if (!pluralAlias.startsWith('$')) pluralAlias = `\\b${pluralAlias}`; diff --git a/packages/qwik-speak/tools/extract/index.ts b/packages/qwik-speak/tools/extract/index.ts index e587d58..9aafe40 100644 --- a/packages/qwik-speak/tools/extract/index.ts +++ b/packages/qwik-speak/tools/extract/index.ts @@ -119,11 +119,14 @@ export async function qwikSpeakExtract(options: QwikSpeakExtractOptions) { // inlineTranslate if (matchInlineTranslate(code)) { const alias = getInlineTranslateAlias(code); - // Clear types - clearTypes(alias); - // Parse sequence - const sequence = parseSequenceExpressions(code, alias); - parseSequence(sequence); + + if (alias) { + // Clear types + clearTypes(alias); + // Parse sequence + const sequence = parseSequenceExpressions(code, alias); + parseSequence(sequence); + } } // usePlural diff --git a/packages/qwik-speak/tools/inline/plugin.ts b/packages/qwik-speak/tools/inline/plugin.ts index c1ccbe5..a2c5804 100644 --- a/packages/qwik-speak/tools/inline/plugin.ts +++ b/packages/qwik-speak/tools/inline/plugin.ts @@ -134,11 +134,11 @@ export function qwikSpeakInline(options: QwikSpeakInlineOptions): Plugin { if (/\/src\//.test(id) && /\.(js|cjs|mjs|jsx|ts|tsx)$/.test(id)) { // Filter code: inlinePlural if (matchInlinePlural(code)) { - code = transformPlural(code, id); + code = transformPlural(code); } // Filter code: inlineTranslate if (matchInlineTranslate(code)) { - code = transformTranslate(code, id); + code = transformTranslate(code); } // Inline in dev mode @@ -298,9 +298,11 @@ export async function writeChunks( /** * Transform inlineTranslate to placeholder */ -export function transformTranslate(code: string, id: string): string { +export function transformTranslate(code: string): string { const alias = getInlineTranslateAlias(code); + if (!alias) return code; + // Parse sequence const sequence = parseSequenceExpressions(code, alias); @@ -329,29 +331,29 @@ export function transformTranslate(code: string, id: string): string { /** * Transform inlinePlural to placeholder */ -export function transformPlural(code: string, id: string): string { +export function transformPlural(code: string): string { const alias = getInlinePluralAlias(code); - if (alias) { - // Parse sequence - const sequence = parseSequenceExpressions(code, alias); + if (!alias) return code; - if (sequence.length === 0) return code; + // Parse sequence + const sequence = parseSequenceExpressions(code, alias); - for (const expr of sequence) { - // Original function - const originalFn = expr.value; - // Arguments - const args = expr.arguments; + if (sequence.length === 0) return code; - if (args?.length > 0) { - if (checkDynamicPlural(args, originalFn)) continue; + for (const expr of sequence) { + // Original function + const originalFn = expr.value; + // Arguments + const args = expr.arguments; - // Transpile with placeholder - const transpiled = originalFn.replace(new RegExp(`${alias}\\(`), `${inlinePluralPlaceholder}(`); - // Replace - code = code.replace(originalFn, transpiled); - } + if (args?.length > 0) { + if (checkDynamicPlural(args, originalFn)) continue; + + // Transpile with placeholder + const transpiled = originalFn.replace(new RegExp(`${alias}\\(`), `${inlinePluralPlaceholder}(`); + // Replace + code = code.replace(originalFn, transpiled); } } diff --git a/src/components/change-locale/change-locale.tsx b/src/components/change-locale/change-locale.tsx index a79e446..2094c8b 100644 --- a/src/components/change-locale/change-locale.tsx +++ b/src/components/change-locale/change-locale.tsx @@ -1,7 +1,7 @@ import { $, component$, useStyles$ } from '@builder.io/qwik'; import { useLocation } from '@builder.io/qwik-city'; import type { SpeakLocale } from 'qwik-speak'; -import { useSpeakLocale, useSpeakConfig, useDisplayName, t } from 'qwik-speak'; +import { useSpeakLocale, useSpeakConfig, useDisplayName, inlineTranslate } from 'qwik-speak'; // import { useTranslatePath } from 'qwik-speak'; import styles from './change-locale.css?inline'; @@ -9,6 +9,8 @@ import styles from './change-locale.css?inline'; export const ChangeLocale = component$(() => { useStyles$(styles); + const t = inlineTranslate(); + const dn = useDisplayName(); /** Uncomment this line to use url rewriting to translate paths */ diff --git a/src/components/header/header.tsx b/src/components/header/header.tsx index 1f4a24a..d371e3a 100644 --- a/src/components/header/header.tsx +++ b/src/components/header/header.tsx @@ -1,6 +1,6 @@ import { component$, useStyles$ } from '@builder.io/qwik'; import { Link, useLocation } from '@builder.io/qwik-city'; -import { useSpeakConfig, useSpeakLocale, t } from 'qwik-speak'; +import { useSpeakConfig, useSpeakLocale, inlineTranslate } from 'qwik-speak'; // import { useTranslatePath } from 'qwik-speak'; import { ChangeLocale } from '../change-locale/change-locale'; @@ -11,6 +11,8 @@ import styles from './header.css?inline'; export const Header = component$(() => { useStyles$(styles); + const t = inlineTranslate(); + const pathname = useLocation().url.pathname; const lang = useSpeakLocale().lang; const config = useSpeakConfig(); diff --git a/src/components/router-head/router-head.tsx b/src/components/router-head/router-head.tsx index 8b88aec..c22b12c 100644 --- a/src/components/router-head/router-head.tsx +++ b/src/components/router-head/router-head.tsx @@ -1,11 +1,13 @@ import { component$ } from '@builder.io/qwik'; import { useDocumentHead, useLocation } from '@builder.io/qwik-city'; -import { t } from 'qwik-speak'; +import { inlineTranslate } from 'qwik-speak'; /** * The RouterHead component is placed inside of the document `<head>` element. */ export const RouterHead = component$(() => { + const t = inlineTranslate(); + const head = useDocumentHead(); const loc = useLocation(); diff --git a/src/routes/[...lang]/index.tsx b/src/routes/[...lang]/index.tsx index 274ebd3..241b3d2 100644 --- a/src/routes/[...lang]/index.tsx +++ b/src/routes/[...lang]/index.tsx @@ -2,8 +2,8 @@ import { component$, useSignal } from '@builder.io/qwik'; import { type DocumentHead } from '@builder.io/qwik-city'; import { Speak, - t, - p, + inlineTranslate, + inlinePlural, useFormatDate, useFormatNumber, useRelativeTime, @@ -19,10 +19,14 @@ export const Title = component$<TitleProps>(props => { }); export const SubTitle = () => { + const t = inlineTranslate(); return <h2>{t('app.subtitle')}</h2>; }; export const Home = component$(() => { + const t = inlineTranslate() + const p = inlinePlural(); + const fd = useFormatDate(); const rt = useRelativeTime(); const fn = useFormatNumber(); diff --git a/src/routes/[...lang]/page/index.tsx b/src/routes/[...lang]/page/index.tsx index 6af5738..6702478 100644 --- a/src/routes/[...lang]/page/index.tsx +++ b/src/routes/[...lang]/page/index.tsx @@ -1,8 +1,10 @@ import { component$ } from '@builder.io/qwik'; import type { DocumentHead } from '@builder.io/qwik-city'; -import { Speak, t } from 'qwik-speak'; +import { Speak, inlineTranslate } from 'qwik-speak'; export const Page = component$(() => { + const t = inlineTranslate(); + const key = 'dynamic'; return ( diff --git a/src/speak-config.ts b/src/speak-config.ts index 89cf5ff..9fbd1f4 100644 --- a/src/speak-config.ts +++ b/src/speak-config.ts @@ -15,7 +15,7 @@ export const config: SpeakConfig = { { lang: 'de-DE', currency: 'EUR', timeZone: 'Europe/Rome', units: { 'length': 'kilometer' }, dir: 'ltr' } ], assets: [ - 'app' // Translations shared by the pages + 'app' ], runtimeAssets: [ 'runtime' // Translations with dynamic keys or parameters From 4ea0c56c16ff97b1e71441658c074cc3e76d6739 Mon Sep 17 00:00:00 2001 From: Roberto Simonetti <robisim74@gmail.com> Date: Sat, 18 Nov 2023 18:15:49 +0100 Subject: [PATCH 12/21] Fix: remove components & update app --- i18n/.metadata/translated-langs.json | 4 + i18n/.metadata/translated.json | 24 + i18n/de-DE/app.json | 22 +- i18n/de-DE/home.json | 16 - i18n/de-DE/page.json | 5 - i18n/de-DE/runtime.json | 2 +- i18n/en-US/app.json | 16 +- i18n/en-US/home.json | 16 - i18n/en-US/page.json | 6 - i18n/it-IT/app.json | 16 +- i18n/it-IT/home.json | 16 - i18n/it-IT/page.json | 5 - i18n/it-IT/runtime.json | 2 +- i18n/it-IT/runtimePage.json | 2 +- package-lock.json | 141 +- package.json | 8 +- packages/qwik-speak/package-lock.json | 4291 ++++++++++------- packages/qwik-speak/package.json | 30 +- packages/qwik-speak/src/core.ts | 14 +- packages/qwik-speak/src/index.ts | 15 +- .../src/qwik-speak-inline-component.tsx | 42 - ...se-translate-path.ts => translate-path.ts} | 22 +- ...speak-component.tsx => use-qwik-speak.tsx} | 58 +- .../src/{speak-component.tsx => use-speak.ts} | 16 +- .../tests/inline-translate.test.tsx | 6 +- .../tests/use-display-name.test.tsx | 6 +- .../qwik-speak/tests/use-format-date.test.tsx | 6 +- .../tests/use-format-number.test.tsx | 6 +- packages/qwik-speak/tests/use-plural.test.tsx | 6 +- .../tests/use-relative-time.test.tsx | 6 +- .../tests/use-translate-path.test.tsx | 10 +- .../qwik-speak/tests/use-translate.test.tsx | 6 +- packages/qwik-speak/tools/extract/index.ts | 19 +- packages/qwik-speak/tools/inline/plugin.ts | 21 +- .../change-locale/change-locale.tsx | 4 +- src/components/header/header.tsx | 5 +- src/e2e/home.spec.ts | 4 +- src/e2e/page.spec.ts | 8 +- src/root.tsx | 35 +- src/routes/[...lang]/index.test.tsx | 6 +- src/routes/[...lang]/index.tsx | 32 +- src/routes/[...lang]/page/index.tsx | 20 +- src/speak-config.ts | 1 + 43 files changed, 3009 insertions(+), 1987 deletions(-) create mode 100644 i18n/.metadata/translated-langs.json create mode 100644 i18n/.metadata/translated.json delete mode 100644 i18n/de-DE/home.json delete mode 100644 i18n/de-DE/page.json delete mode 100644 i18n/en-US/home.json delete mode 100644 i18n/en-US/page.json delete mode 100644 i18n/it-IT/home.json delete mode 100644 i18n/it-IT/page.json delete mode 100644 packages/qwik-speak/src/qwik-speak-inline-component.tsx rename packages/qwik-speak/src/{use-translate-path.ts => translate-path.ts} (87%) rename packages/qwik-speak/src/{qwik-speak-component.tsx => use-qwik-speak.tsx} (59%) rename packages/qwik-speak/src/{speak-component.tsx => use-speak.ts} (74%) diff --git a/i18n/.metadata/translated-langs.json b/i18n/.metadata/translated-langs.json new file mode 100644 index 0000000..c4e4706 --- /dev/null +++ b/i18n/.metadata/translated-langs.json @@ -0,0 +1,4 @@ +[ + "it-IT", + "de-DE" +] \ No newline at end of file diff --git a/i18n/.metadata/translated.json b/i18n/.metadata/translated.json new file mode 100644 index 0000000..1c5e150 --- /dev/null +++ b/i18n/.metadata/translated.json @@ -0,0 +1,24 @@ +[ + "app.changeLocale", + "app.nav.home", + "app.nav.page", + "app.subtitle", + "app.title", + "anotherPage", + "dates", + "defaultValue", + "description", + "devs.one", + "devs.other", + "greeting", + "increment", + "numbers", + "params", + "plural", + "tags", + "runtime.head.home.description", + "runtime.head.home.title", + "runtime.head.page.description", + "runtime.head.page.title", + "runtimePage.dynamic" +] \ No newline at end of file diff --git a/i18n/de-DE/app.json b/i18n/de-DE/app.json index 3e9dee5..dc825db 100644 --- a/i18n/de-DE/app.json +++ b/i18n/de-DE/app.json @@ -1,11 +1,25 @@ { "app": { - "changeLocale": "Den Ort wechseln", + "changeLocale": "Ändern Sie die Gebietsschema", "nav": { - "home": "Heim", + "home": "Startseite", "page": "Seite" }, - "subtitle": "Übersetzen Sie Ihre Qwik-Apps in eine beliebige Sprache", + "subtitle": "Übersetzen Sie Ihre Qwik-Apps in jede Sprache", "title": "Qwik Speak" - } + }, + "anotherPage": "Ich bin eine andere Seite", + "dates": "Daten & relative Zeit", + "defaultValue": "Ich bin ein Standardwert", + "description": "<em>Internationalisierung (i18n) Bibliothek zum Übersetzen von Texten, Daten und Zahlen in Qwik-Apps</em>", + "devs": { + "one": "{{ value }} Softwareentwickler", + "other": "{{ value }} Softwareentwickler" + }, + "greeting": "Hallo! Ich bin {{name}}", + "increment": "Erhöhen", + "numbers": "Zahlen & Währungen", + "params": "Parameter", + "plural": "Mehrzahl", + "tags": "Html-Tags" } \ No newline at end of file diff --git a/i18n/de-DE/home.json b/i18n/de-DE/home.json deleted file mode 100644 index f57f618..0000000 --- a/i18n/de-DE/home.json +++ /dev/null @@ -1,16 +0,0 @@ -{ - "home": { - "dates": "Daten und relative Zeit", - "devs": { - "one": "{{ value }} Softwareentwickler", - "other": "{{ value }} Softwareentwickler" - }, - "greeting": "Hallo! Im {{name}}", - "increment": "Zunahme", - "numbers": "Zahlen und Währungen", - "params": "Parameter", - "plural": "Plural", - "tags": "HTML-Tags", - "text": "<em>Internationalisierungsbibliothek (i18n) zum Übersetzen von Texten, Datumsangaben und Zahlen in Qwik-Apps</em>" - } -} \ No newline at end of file diff --git a/i18n/de-DE/page.json b/i18n/de-DE/page.json deleted file mode 100644 index 1845d61..0000000 --- a/i18n/de-DE/page.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "page": { - "text": "Ich bin eine andere Seite" - } -} \ No newline at end of file diff --git a/i18n/de-DE/runtime.json b/i18n/de-DE/runtime.json index a334fac..fd6fcb6 100644 --- a/i18n/de-DE/runtime.json +++ b/i18n/de-DE/runtime.json @@ -2,7 +2,7 @@ "runtime": { "head": { "home": { - "description": "Internationalisierungsbibliothek (i18n) zum Übersetzen von Texten, Datumsangaben und Zahlen in Qwik-Apps", + "description": "Internationalisierung (i18n) Bibliothek zur Übersetzung von Texten, Daten und Zahlen in Qwik Apps", "title": "{{name}}" }, "page": { diff --git a/i18n/en-US/app.json b/i18n/en-US/app.json index 05a534e..e99ff9c 100644 --- a/i18n/en-US/app.json +++ b/i18n/en-US/app.json @@ -7,5 +7,19 @@ }, "subtitle": "Translate your Qwik apps into any language", "title": "Qwik Speak" - } + }, + "anotherPage": "I'm another page", + "dates": "Dates & relative time", + "defaultValue": "I'm a default value", + "description": "<em>Internationalization (i18n) library to translate texts, dates and numbers in Qwik apps</em>", + "devs": { + "one": "{{ value }} software developer", + "other": "{{ value }} software developers" + }, + "greeting": "Hi! I am {{name}}", + "increment": "Increment", + "numbers": "Numbers & currencies", + "params": "Parameters", + "plural": "Plural", + "tags": "Html tags" } \ No newline at end of file diff --git a/i18n/en-US/home.json b/i18n/en-US/home.json deleted file mode 100644 index f2e5ca4..0000000 --- a/i18n/en-US/home.json +++ /dev/null @@ -1,16 +0,0 @@ -{ - "home": { - "dates": "Dates & relative time", - "devs": { - "one": "{{ value }} software developer", - "other": "{{ value }} software developers" - }, - "greeting": "Hi! I am {{name}}", - "increment": "Increment", - "numbers": "Numbers & currencies", - "params": "Parameters", - "plural": "Plural", - "tags": "Html tags", - "text": "<em>Internationalization (i18n) library to translate texts, dates and numbers in Qwik apps</em>" - } -} \ No newline at end of file diff --git a/i18n/en-US/page.json b/i18n/en-US/page.json deleted file mode 100644 index cb2e3e3..0000000 --- a/i18n/en-US/page.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "page": { - "default": "I'm a default value", - "text": "I'm another page" - } -} \ No newline at end of file diff --git a/i18n/it-IT/app.json b/i18n/it-IT/app.json index d609ead..0c9a576 100644 --- a/i18n/it-IT/app.json +++ b/i18n/it-IT/app.json @@ -7,5 +7,19 @@ }, "subtitle": "Traduci le tue app Qwik in qualsiasi lingua", "title": "Qwik Speak" - } + }, + "anotherPage": "Sono un'altra pagina", + "dates": "Date e tempo relativo", + "defaultValue": "Sono un valore predefinito", + "description": "<em>Libreria di internazionalizzazione (i18n) per tradurre testi, date e numeri nelle app Qwik</em>", + "devs": { + "one": "{{ value }} sviluppatore di software", + "other": "{{ value }} sviluppatori di software" + }, + "greeting": "Ciao! Sono {{name}}", + "increment": "Incremento", + "numbers": "Numeri e valute", + "params": "Parametri", + "plural": "Plurale", + "tags": "Tag Html" } \ No newline at end of file diff --git a/i18n/it-IT/home.json b/i18n/it-IT/home.json deleted file mode 100644 index af27064..0000000 --- a/i18n/it-IT/home.json +++ /dev/null @@ -1,16 +0,0 @@ -{ - "home": { - "dates": "Date e tempo relativo", - "devs": { - "one": "{{ value }} sviluppatore software", - "other": "{{ value }} sviluppatori software" - }, - "greeting": "Ciao! Sono {{name}}", - "increment": "Incrementa", - "numbers": "Numeri e valute", - "params": "Parametri", - "plural": "Plurale", - "tags": "Tag Html", - "text": "<em>Libreria di internazionalizzazione (i18n) per tradurre testi, date e numeri nelle app Qwik</em>" - } -} \ No newline at end of file diff --git a/i18n/it-IT/page.json b/i18n/it-IT/page.json deleted file mode 100644 index 9d3cc41..0000000 --- a/i18n/it-IT/page.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "page": { - "text": "Io sono un'altra pagina" - } -} \ No newline at end of file diff --git a/i18n/it-IT/runtime.json b/i18n/it-IT/runtime.json index 13804d2..2d51f3f 100644 --- a/i18n/it-IT/runtime.json +++ b/i18n/it-IT/runtime.json @@ -6,7 +6,7 @@ "title": "{{name}}" }, "page": { - "description": "Io sono un'altra pagina", + "description": "Sono un'altra pagina", "title": "Pagina - {{name}}" } } diff --git a/i18n/it-IT/runtimePage.json b/i18n/it-IT/runtimePage.json index a65133e..104529b 100644 --- a/i18n/it-IT/runtimePage.json +++ b/i18n/it-IT/runtimePage.json @@ -1,5 +1,5 @@ { "runtimePage": { - "dynamic": "Io sono un valore dinamico" + "dynamic": "Sono un valore dinamico" } } \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index fc0af3b..856ad07 100644 --- a/package-lock.json +++ b/package-lock.json @@ -6,15 +6,16 @@ "": { "name": "qwik-speak", "devDependencies": { - "@builder.io/qwik": "1.2.17", - "@builder.io/qwik-city": "1.2.17", + "@builder.io/qwik": "1.2.18", + "@builder.io/qwik-city": "1.2.18", "@playwright/test": "1.38.0", "@types/eslint": "8.44.6", "@types/node": "^20.8.9", "@typescript-eslint/eslint-plugin": "6.9.0", "@typescript-eslint/parser": "6.9.0", "eslint": "8.52.0", - "eslint-plugin-qwik": "1.2.17", + "eslint-plugin-qwik": "1.2.18", + "gpt-translate-json": "^0.1.0", "typescript": "5.2.2", "undici": "5.27.0", "vite": "4.5.0", @@ -35,9 +36,9 @@ } }, "node_modules/@builder.io/qwik": { - "version": "1.2.17", - "resolved": "https://registry.npmjs.org/@builder.io/qwik/-/qwik-1.2.17.tgz", - "integrity": "sha512-66fGmPHIy/brJqtrUapxckw9EVAh6U3pcjRr1WiqGGjq1cvgJdP88jayMw85XvckEjrF4ymNLOYCmjdlQNzU+Q==", + "version": "1.2.18", + "resolved": "https://registry.npmjs.org/@builder.io/qwik/-/qwik-1.2.18.tgz", + "integrity": "sha512-YfkpRI8IoJ6OkIlMoJ4jdjbotZSmBfMtf9ibxbf9vZLgKVLkSbSb4hgpA+NMWwQb/ojgn/6YJ49MwaZP81sm4Q==", "dev": true, "dependencies": { "csstype": "^3.1.2", @@ -54,9 +55,9 @@ } }, "node_modules/@builder.io/qwik-city": { - "version": "1.2.17", - "resolved": "https://registry.npmjs.org/@builder.io/qwik-city/-/qwik-city-1.2.17.tgz", - "integrity": "sha512-aCAjNQZr/DMgdcjqkyPVh1Uqly9urJx3mRu2iteLLy62VB6QYhaHtOHA6WlcwBT8LQGOOVilHEdDNB6hbrOwvQ==", + "version": "1.2.18", + "resolved": "https://registry.npmjs.org/@builder.io/qwik-city/-/qwik-city-1.2.18.tgz", + "integrity": "sha512-MfWzMLi0MaoPpI11g4qXVF5mX6hqfjsIS3G+1FUU+x4FIJFcbTu9cMnbaw5YdMOgNdJRS5wf2M++KXVy5oyJ/w==", "dev": true, "dependencies": { "@mdx-js/mdx": "2.3.0", @@ -1319,6 +1320,12 @@ "astring": "bin/astring" } }, + "node_modules/asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", + "dev": true + }, "node_modules/available-typed-arrays": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.5.tgz", @@ -1331,6 +1338,15 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/axios": { + "version": "0.26.1", + "resolved": "https://registry.npmjs.org/axios/-/axios-0.26.1.tgz", + "integrity": "sha512-fPwcX4EvnSHuInCMItEhAGnaSEXRBjtzh9fOtsE6E1G6p7vl7edEeZe11QHf18+6+9gR5PbKV/sGKNaD8YaMeA==", + "dev": true, + "dependencies": { + "follow-redirects": "^1.14.8" + } + }, "node_modules/b4a": { "version": "1.6.4", "resolved": "https://registry.npmjs.org/b4a/-/b4a-1.6.4.tgz", @@ -1610,6 +1626,18 @@ "simple-swizzle": "^0.2.2" } }, + "node_modules/combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "dev": true, + "dependencies": { + "delayed-stream": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, "node_modules/comma-separated-tokens": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/comma-separated-tokens/-/comma-separated-tokens-2.0.3.tgz", @@ -1817,6 +1845,15 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", + "dev": true, + "engines": { + "node": ">=0.4.0" + } + }, "node_modules/dequal": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz", @@ -2151,9 +2188,9 @@ } }, "node_modules/eslint-plugin-qwik": { - "version": "1.2.17", - "resolved": "https://registry.npmjs.org/eslint-plugin-qwik/-/eslint-plugin-qwik-1.2.17.tgz", - "integrity": "sha512-Q6gwahd8np5s7/SwWrgvhzAgymqHmiuXdgUvIgnDt0N0fZvdEJQUYQtTOpwMpRj8+E53oKLVk3vogeRki6QbFw==", + "version": "1.2.18", + "resolved": "https://registry.npmjs.org/eslint-plugin-qwik/-/eslint-plugin-qwik-1.2.18.tgz", + "integrity": "sha512-B4c8bo0x6QyhCgGLkSn3/fqLreCnHeLv6UsVlZfW41VpWc4nPiYQqURp+ChY3TiPy2SXJEKtn4F4bBrn3sn4Fw==", "dev": true, "dependencies": { "jsx-ast-utils": "^3.3.5" @@ -2481,6 +2518,26 @@ "integrity": "sha512-5nqDSxl8nn5BSNxyR3n4I6eDmbolI6WT+QqR547RwxQapgjQBmtktdP+HTBb/a/zLsbzERTONyUB5pefh5TtjQ==", "dev": true }, + "node_modules/follow-redirects": { + "version": "1.15.3", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.3.tgz", + "integrity": "sha512-1VzOtuEM8pC9SFU1E+8KfTjZyMztRsgEfwQl44z8A25uy13jSzTj6dyK2Df52iV0vgHCfBwLhDWevLn95w5v6Q==", + "dev": true, + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/RubenVerborgh" + } + ], + "engines": { + "node": ">=4.0" + }, + "peerDependenciesMeta": { + "debug": { + "optional": true + } + } + }, "node_modules/for-each": { "version": "0.3.3", "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.3.tgz", @@ -2490,6 +2547,20 @@ "is-callable": "^1.1.3" } }, + "node_modules/form-data": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz", + "integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==", + "dev": true, + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 6" + } + }, "node_modules/fs-constants": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/fs-constants/-/fs-constants-1.0.0.tgz", @@ -2695,6 +2766,21 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/gpt-translate-json": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/gpt-translate-json/-/gpt-translate-json-0.1.0.tgz", + "integrity": "sha512-wbG6mzff1o4DAVN9tBDOA+qmfPsUYu4z3RW7j44Z8KpiZ9MIjkvq5hhLthLDenBALA25vb1A/mmwPohlUfgRmg==", + "dev": true, + "dependencies": { + "openai": "^3.3.0" + }, + "bin": { + "gpt-translate-json": "lib/cli.js" + }, + "engines": { + "node": ">=16.0.0" + } + }, "node_modules/graphemer": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz", @@ -4210,6 +4296,27 @@ "node": ">=8.6" } }, + "node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "dev": true, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "dev": true, + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, "node_modules/mimic-response": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-3.1.0.tgz", @@ -4398,6 +4505,16 @@ "wrappy": "1" } }, + "node_modules/openai": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/openai/-/openai-3.3.0.tgz", + "integrity": "sha512-uqxI/Au+aPRnsaQRe8CojU0eCR7I0mBiKjD3sNMzY6DaC1ZVrc85u98mtJW6voDug8fgGN+DIZmTDxTthxb7dQ==", + "dev": true, + "dependencies": { + "axios": "^0.26.0", + "form-data": "^4.0.0" + } + }, "node_modules/optionator": { "version": "0.9.3", "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.3.tgz", diff --git a/package.json b/package.json index 8059545..7b9ecbf 100644 --- a/package.json +++ b/package.json @@ -15,21 +15,23 @@ "lint": "eslint \"src/**/*.ts*\"", "preview": "qwik build preview && vite preview --open", "qwik-speak-extract": "node ./packages/qwik-speak/extract/cli.js --supportedLangs=en-US,it-IT,de-DE --assetsPath=i18n", + "gpt-translate-json": "gpt-translate-json --apiKey=openai_api_key --model=gpt-4 --maxTokens=3000 --langs=en-US,it-IT,de-DE --originalLang=en-US", "start": "vite --open --mode ssr", "test": "vitest test --run", "test.e2e": "playwright test", "qwik": "qwik" }, "devDependencies": { - "@builder.io/qwik": "1.2.17", - "@builder.io/qwik-city": "1.2.17", + "@builder.io/qwik": "1.2.18", + "@builder.io/qwik-city": "1.2.18", "@playwright/test": "1.38.0", "@types/eslint": "8.44.6", "@types/node": "^20.8.9", "@typescript-eslint/eslint-plugin": "6.9.0", "@typescript-eslint/parser": "6.9.0", "eslint": "8.52.0", - "eslint-plugin-qwik": "1.2.17", + "eslint-plugin-qwik": "1.2.18", + "gpt-translate-json": "^0.1.0", "typescript": "5.2.2", "undici": "5.27.0", "vite": "4.5.0", diff --git a/packages/qwik-speak/package-lock.json b/packages/qwik-speak/package-lock.json index 7ba136d..2a0080b 100644 --- a/packages/qwik-speak/package-lock.json +++ b/packages/qwik-speak/package-lock.json @@ -12,27 +12,27 @@ "qwik-speak-extract": "extract/cli.js" }, "devDependencies": { - "@builder.io/qwik": "1.1.4", - "@microsoft/api-documenter": "^7.22.3", - "@microsoft/api-extractor": "^7.36.1", - "@types/eslint": "8.37.0", - "@types/node": "^18.16.1", - "@typescript-eslint/eslint-plugin": "5.59.1", - "@typescript-eslint/parser": "5.59.1", - "eslint": "8.39.0", - "eslint-plugin-qwik": "1.1.4", - "np": "^7.7.0", + "@builder.io/qwik": "1.2.18", + "@microsoft/api-documenter": "^7.23.12", + "@microsoft/api-extractor": "^7.38.3", + "@types/eslint": "8.44.4", + "@types/node": "^20.8.4", + "@typescript-eslint/eslint-plugin": "6.7.5", + "@typescript-eslint/parser": "6.7.5", + "eslint": "8.51.0", + "eslint-plugin-qwik": "1.2.18", + "np": "^8.0.4", "rollup-plugin-add-shebang": "^0.3.1", - "typescript": "5.0.4", - "undici": "5.22.0", - "vite": "4.3.3", - "vitest": "^0.30.1" + "typescript": "5.2.2", + "undici": "5.26.0", + "vite": "4.4.11", + "vitest": "^0.34.6" }, "engines": { "node": ">=16.0.0" }, "peerDependencies": { - "@builder.io/qwik": ">=1.1.4" + "@builder.io/qwik": ">=1.2.18" } }, "node_modules/@aashutoshrathi/word-wrap": { @@ -45,34 +45,106 @@ } }, "node_modules/@babel/code-frame": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.22.5.tgz", - "integrity": "sha512-Xmwn266vad+6DAqEB2A6V/CcZVp62BbwVmcOJc2RPuwih1kw02TjQvWVWlcKGbBPd+8/0V5DEkOcizRGYsspYQ==", + "version": "7.22.13", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.22.13.tgz", + "integrity": "sha512-XktuhWlJ5g+3TJXc5upd9Ks1HutSArik6jf2eAjYFyIOf4ej3RN+184cZbzDvbPnuTJIUhPKKJE3cIsYTiAT3w==", "dev": true, "dependencies": { - "@babel/highlight": "^7.22.5" + "@babel/highlight": "^7.22.13", + "chalk": "^2.4.2" }, "engines": { "node": ">=6.9.0" } }, + "node_modules/@babel/code-frame/node_modules/ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, + "dependencies": { + "color-convert": "^1.9.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/@babel/code-frame/node_modules/chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dev": true, + "dependencies": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/@babel/code-frame/node_modules/color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "dev": true, + "dependencies": { + "color-name": "1.1.3" + } + }, + "node_modules/@babel/code-frame/node_modules/color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", + "dev": true + }, + "node_modules/@babel/code-frame/node_modules/escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", + "dev": true, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/@babel/code-frame/node_modules/has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/@babel/code-frame/node_modules/supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "dependencies": { + "has-flag": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, "node_modules/@babel/helper-validator-identifier": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.22.5.tgz", - "integrity": "sha512-aJXu+6lErq8ltp+JhkJUfk1MTGyuA4v7f3pA+BJ5HLfNC6nAQ0Cpi9uOquUj8Hehg0aUiHzWQbOVJGao6ztBAQ==", + "version": "7.22.20", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.22.20.tgz", + "integrity": "sha512-Y4OZ+ytlatR8AI+8KZfKuL5urKp7qey08ha31L8b3BwewJAoJamTzyvxPR/5D+KkdJCGPq/+8TukHBlY10FX9A==", "dev": true, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/highlight": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.22.5.tgz", - "integrity": "sha512-BSKlD1hgnedS5XRnGOljZawtag7H1yPfQp0tdNJCHoH6AZ+Pcm9VvkrK59/Yy593Ypg0zMxH2BxD1VPYUQ7UIw==", + "version": "7.22.20", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.22.20.tgz", + "integrity": "sha512-dkdMCN3py0+ksCgYmGG8jKeGA/8Tk+gJwSYYlFGxG5lmhfKNoAy004YpLxpS1W2J8m/EK2Ew+yOs9pVRwO89mg==", "dev": true, "dependencies": { - "@babel/helper-validator-identifier": "^7.22.5", - "chalk": "^2.0.0", + "@babel/helper-validator-identifier": "^7.22.20", + "chalk": "^2.4.2", "js-tokens": "^4.0.0" }, "engines": { @@ -150,11 +222,26 @@ "node": ">=4" } }, + "node_modules/@bconnorwhite/module": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/@bconnorwhite/module/-/module-2.0.2.tgz", + "integrity": "sha512-ck1me5WMgZKp06gnJrVKEkytpehTTQbvsAMbF1nGPeHri/AZNhj87++PSE2LOxmZqM0EtGMaqeLdx7Lw7SUnTA==", + "dev": true, + "dependencies": { + "find-up": "^5.0.0", + "read-json-safe": "^1.0.5", + "types-pkg-json": "^1.1.0" + } + }, "node_modules/@builder.io/qwik": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/@builder.io/qwik/-/qwik-1.1.4.tgz", - "integrity": "sha512-FSbe2GcUBptAXMAHfN+oGUpnb3nZXzup2EYlza61bAfgWSx9SryOwNULyROis3mu7Ywdua3w6VKvCWOFdovz2w==", + "version": "1.2.18", + "resolved": "https://registry.npmjs.org/@builder.io/qwik/-/qwik-1.2.18.tgz", + "integrity": "sha512-YfkpRI8IoJ6OkIlMoJ4jdjbotZSmBfMtf9ibxbf9vZLgKVLkSbSb4hgpA+NMWwQb/ojgn/6YJ49MwaZP81sm4Q==", "dev": true, + "dependencies": { + "csstype": "^3.1.2", + "vite": "^4.4.11" + }, "bin": { "qwik": "qwik.cjs" }, @@ -166,9 +253,9 @@ } }, "node_modules/@esbuild/android-arm": { - "version": "0.17.19", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.17.19.tgz", - "integrity": "sha512-rIKddzqhmav7MSmoFCmDIb6e2W57geRsM94gV2l38fzhXMwq7hZoClug9USI2pFRGL06f4IOPHHpFNOkWieR8A==", + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.18.20.tgz", + "integrity": "sha512-fyi7TDI/ijKKNZTUJAQqiG5T7YjJXgnzkURqmGj13C6dCqckZBLdl4h7bkhHt/t0WP+zO9/zwroDvANaOqO5Sw==", "cpu": [ "arm" ], @@ -182,9 +269,9 @@ } }, "node_modules/@esbuild/android-arm64": { - "version": "0.17.19", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.17.19.tgz", - "integrity": "sha512-KBMWvEZooR7+kzY0BtbTQn0OAYY7CsiydT63pVEaPtVYF0hXbUaOyZog37DKxK7NF3XacBJOpYT4adIJh+avxA==", + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.18.20.tgz", + "integrity": "sha512-Nz4rJcchGDtENV0eMKUNa6L12zz2zBDXuhj/Vjh18zGqB44Bi7MBMSXjgunJgjRhCmKOjnPuZp4Mb6OKqtMHLQ==", "cpu": [ "arm64" ], @@ -198,9 +285,9 @@ } }, "node_modules/@esbuild/android-x64": { - "version": "0.17.19", - "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.17.19.tgz", - "integrity": "sha512-uUTTc4xGNDT7YSArp/zbtmbhO0uEEK9/ETW29Wk1thYUJBz3IVnvgEiEwEa9IeLyvnpKrWK64Utw2bgUmDveww==", + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.18.20.tgz", + "integrity": "sha512-8GDdlePJA8D6zlZYJV/jnrRAi6rOiNaCC/JclcXpB+KIuvfBN4owLtgzY2bsxnx666XjJx2kDPUmnTtR8qKQUg==", "cpu": [ "x64" ], @@ -214,9 +301,9 @@ } }, "node_modules/@esbuild/darwin-arm64": { - "version": "0.17.19", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.17.19.tgz", - "integrity": "sha512-80wEoCfF/hFKM6WE1FyBHc9SfUblloAWx6FJkFWTWiCoht9Mc0ARGEM47e67W9rI09YoUxJL68WHfDRYEAvOhg==", + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.18.20.tgz", + "integrity": "sha512-bxRHW5kHU38zS2lPTPOyuyTm+S+eobPUnTNkdJEfAddYgEcll4xkT8DB9d2008DtTbl7uJag2HuE5NZAZgnNEA==", "cpu": [ "arm64" ], @@ -230,9 +317,9 @@ } }, "node_modules/@esbuild/darwin-x64": { - "version": "0.17.19", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.17.19.tgz", - "integrity": "sha512-IJM4JJsLhRYr9xdtLytPLSH9k/oxR3boaUIYiHkAawtwNOXKE8KoU8tMvryogdcT8AU+Bflmh81Xn6Q0vTZbQw==", + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.18.20.tgz", + "integrity": "sha512-pc5gxlMDxzm513qPGbCbDukOdsGtKhfxD1zJKXjCCcU7ju50O7MeAZ8c4krSJcOIJGFR+qx21yMMVYwiQvyTyQ==", "cpu": [ "x64" ], @@ -246,9 +333,9 @@ } }, "node_modules/@esbuild/freebsd-arm64": { - "version": "0.17.19", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.17.19.tgz", - "integrity": "sha512-pBwbc7DufluUeGdjSU5Si+P3SoMF5DQ/F/UmTSb8HXO80ZEAJmrykPyzo1IfNbAoaqw48YRpv8shwd1NoI0jcQ==", + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.18.20.tgz", + "integrity": "sha512-yqDQHy4QHevpMAaxhhIwYPMv1NECwOvIpGCZkECn8w2WFHXjEwrBn3CeNIYsibZ/iZEUemj++M26W3cNR5h+Tw==", "cpu": [ "arm64" ], @@ -262,9 +349,9 @@ } }, "node_modules/@esbuild/freebsd-x64": { - "version": "0.17.19", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.17.19.tgz", - "integrity": "sha512-4lu+n8Wk0XlajEhbEffdy2xy53dpR06SlzvhGByyg36qJw6Kpfk7cp45DR/62aPH9mtJRmIyrXAS5UWBrJT6TQ==", + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.18.20.tgz", + "integrity": "sha512-tgWRPPuQsd3RmBZwarGVHZQvtzfEBOreNuxEMKFcd5DaDn2PbBxfwLcj4+aenoh7ctXcbXmOQIn8HI6mCSw5MQ==", "cpu": [ "x64" ], @@ -278,9 +365,9 @@ } }, "node_modules/@esbuild/linux-arm": { - "version": "0.17.19", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.17.19.tgz", - "integrity": "sha512-cdmT3KxjlOQ/gZ2cjfrQOtmhG4HJs6hhvm3mWSRDPtZ/lP5oe8FWceS10JaSJC13GBd4eH/haHnqf7hhGNLerA==", + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.18.20.tgz", + "integrity": "sha512-/5bHkMWnq1EgKr1V+Ybz3s1hWXok7mDFUMQ4cG10AfW3wL02PSZi5kFpYKrptDsgb2WAJIvRcDm+qIvXf/apvg==", "cpu": [ "arm" ], @@ -294,9 +381,9 @@ } }, "node_modules/@esbuild/linux-arm64": { - "version": "0.17.19", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.17.19.tgz", - "integrity": "sha512-ct1Tg3WGwd3P+oZYqic+YZF4snNl2bsnMKRkb3ozHmnM0dGWuxcPTTntAF6bOP0Sp4x0PjSF+4uHQ1xvxfRKqg==", + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.18.20.tgz", + "integrity": "sha512-2YbscF+UL7SQAVIpnWvYwM+3LskyDmPhe31pE7/aoTMFKKzIc9lLbyGUpmmb8a8AixOL61sQ/mFh3jEjHYFvdA==", "cpu": [ "arm64" ], @@ -310,9 +397,9 @@ } }, "node_modules/@esbuild/linux-ia32": { - "version": "0.17.19", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.17.19.tgz", - "integrity": "sha512-w4IRhSy1VbsNxHRQpeGCHEmibqdTUx61Vc38APcsRbuVgK0OPEnQ0YD39Brymn96mOx48Y2laBQGqgZ0j9w6SQ==", + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.18.20.tgz", + "integrity": "sha512-P4etWwq6IsReT0E1KHU40bOnzMHoH73aXp96Fs8TIT6z9Hu8G6+0SHSw9i2isWrD2nbx2qo5yUqACgdfVGx7TA==", "cpu": [ "ia32" ], @@ -326,9 +413,9 @@ } }, "node_modules/@esbuild/linux-loong64": { - "version": "0.17.19", - "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.17.19.tgz", - "integrity": "sha512-2iAngUbBPMq439a+z//gE+9WBldoMp1s5GWsUSgqHLzLJ9WoZLZhpwWuym0u0u/4XmZ3gpHmzV84PonE+9IIdQ==", + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.18.20.tgz", + "integrity": "sha512-nXW8nqBTrOpDLPgPY9uV+/1DjxoQ7DoB2N8eocyq8I9XuqJ7BiAMDMf9n1xZM9TgW0J8zrquIb/A7s3BJv7rjg==", "cpu": [ "loong64" ], @@ -342,9 +429,9 @@ } }, "node_modules/@esbuild/linux-mips64el": { - "version": "0.17.19", - "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.17.19.tgz", - "integrity": "sha512-LKJltc4LVdMKHsrFe4MGNPp0hqDFA1Wpt3jE1gEyM3nKUvOiO//9PheZZHfYRfYl6AwdTH4aTcXSqBerX0ml4A==", + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.18.20.tgz", + "integrity": "sha512-d5NeaXZcHp8PzYy5VnXV3VSd2D328Zb+9dEq5HE6bw6+N86JVPExrA6O68OPwobntbNJ0pzCpUFZTo3w0GyetQ==", "cpu": [ "mips64el" ], @@ -358,9 +445,9 @@ } }, "node_modules/@esbuild/linux-ppc64": { - "version": "0.17.19", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.17.19.tgz", - "integrity": "sha512-/c/DGybs95WXNS8y3Ti/ytqETiW7EU44MEKuCAcpPto3YjQbyK3IQVKfF6nbghD7EcLUGl0NbiL5Rt5DMhn5tg==", + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.18.20.tgz", + "integrity": "sha512-WHPyeScRNcmANnLQkq6AfyXRFr5D6N2sKgkFo2FqguP44Nw2eyDlbTdZwd9GYk98DZG9QItIiTlFLHJHjxP3FA==", "cpu": [ "ppc64" ], @@ -374,9 +461,9 @@ } }, "node_modules/@esbuild/linux-riscv64": { - "version": "0.17.19", - "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.17.19.tgz", - "integrity": "sha512-FC3nUAWhvFoutlhAkgHf8f5HwFWUL6bYdvLc/TTuxKlvLi3+pPzdZiFKSWz/PF30TB1K19SuCxDTI5KcqASJqA==", + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.18.20.tgz", + "integrity": "sha512-WSxo6h5ecI5XH34KC7w5veNnKkju3zBRLEQNY7mv5mtBmrP/MjNBCAlsM2u5hDBlS3NGcTQpoBvRzqBcRtpq1A==", "cpu": [ "riscv64" ], @@ -390,9 +477,9 @@ } }, "node_modules/@esbuild/linux-s390x": { - "version": "0.17.19", - "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.17.19.tgz", - "integrity": "sha512-IbFsFbxMWLuKEbH+7sTkKzL6NJmG2vRyy6K7JJo55w+8xDk7RElYn6xvXtDW8HCfoKBFK69f3pgBJSUSQPr+4Q==", + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.18.20.tgz", + "integrity": "sha512-+8231GMs3mAEth6Ja1iK0a1sQ3ohfcpzpRLH8uuc5/KVDFneH6jtAJLFGafpzpMRO6DzJ6AvXKze9LfFMrIHVQ==", "cpu": [ "s390x" ], @@ -406,9 +493,9 @@ } }, "node_modules/@esbuild/linux-x64": { - "version": "0.17.19", - "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.17.19.tgz", - "integrity": "sha512-68ngA9lg2H6zkZcyp22tsVt38mlhWde8l3eJLWkyLrp4HwMUr3c1s/M2t7+kHIhvMjglIBrFpncX1SzMckomGw==", + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.18.20.tgz", + "integrity": "sha512-UYqiqemphJcNsFEskc73jQ7B9jgwjWrSayxawS6UVFZGWrAAtkzjxSqnoclCXxWtfwLdzU+vTpcNYhpn43uP1w==", "cpu": [ "x64" ], @@ -422,9 +509,9 @@ } }, "node_modules/@esbuild/netbsd-x64": { - "version": "0.17.19", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.17.19.tgz", - "integrity": "sha512-CwFq42rXCR8TYIjIfpXCbRX0rp1jo6cPIUPSaWwzbVI4aOfX96OXY8M6KNmtPcg7QjYeDmN+DD0Wp3LaBOLf4Q==", + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.18.20.tgz", + "integrity": "sha512-iO1c++VP6xUBUmltHZoMtCUdPlnPGdBom6IrO4gyKPFFVBKioIImVooR5I83nTew5UOYrk3gIJhbZh8X44y06A==", "cpu": [ "x64" ], @@ -438,9 +525,9 @@ } }, "node_modules/@esbuild/openbsd-x64": { - "version": "0.17.19", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.17.19.tgz", - "integrity": "sha512-cnq5brJYrSZ2CF6c35eCmviIN3k3RczmHz8eYaVlNasVqsNY+JKohZU5MKmaOI+KkllCdzOKKdPs762VCPC20g==", + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.18.20.tgz", + "integrity": "sha512-e5e4YSsuQfX4cxcygw/UCPIEP6wbIL+se3sxPdCiMbFLBWu0eiZOJ7WoD+ptCLrmjZBK1Wk7I6D/I3NglUGOxg==", "cpu": [ "x64" ], @@ -454,9 +541,9 @@ } }, "node_modules/@esbuild/sunos-x64": { - "version": "0.17.19", - "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.17.19.tgz", - "integrity": "sha512-vCRT7yP3zX+bKWFeP/zdS6SqdWB8OIpaRq/mbXQxTGHnIxspRtigpkUcDMlSCOejlHowLqII7K2JKevwyRP2rg==", + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.18.20.tgz", + "integrity": "sha512-kDbFRFp0YpTQVVrqUd5FTYmWo45zGaXe0X8E1G/LKFC0v8x0vWrhOWSLITcCn63lmZIxfOMXtCfti/RxN/0wnQ==", "cpu": [ "x64" ], @@ -470,9 +557,9 @@ } }, "node_modules/@esbuild/win32-arm64": { - "version": "0.17.19", - "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.17.19.tgz", - "integrity": "sha512-yYx+8jwowUstVdorcMdNlzklLYhPxjniHWFKgRqH7IFlUEa0Umu3KuYplf1HUZZ422e3NU9F4LGb+4O0Kdcaag==", + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.18.20.tgz", + "integrity": "sha512-ddYFR6ItYgoaq4v4JmQQaAI5s7npztfV4Ag6NrhiaW0RrnOXqBkgwZLofVTlq1daVTQNhtI5oieTvkRPfZrePg==", "cpu": [ "arm64" ], @@ -486,9 +573,9 @@ } }, "node_modules/@esbuild/win32-ia32": { - "version": "0.17.19", - "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.17.19.tgz", - "integrity": "sha512-eggDKanJszUtCdlVs0RB+h35wNlb5v4TWEkq4vZcmVt5u/HiDZrTXe2bWFQUez3RgNHwx/x4sk5++4NSSicKkw==", + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.18.20.tgz", + "integrity": "sha512-Wv7QBi3ID/rROT08SABTS7eV4hX26sVduqDOTe1MvGMjNd3EjOz4b7zeexIR62GTIEKrfJXKL9LFxTYgkyeu7g==", "cpu": [ "ia32" ], @@ -502,9 +589,9 @@ } }, "node_modules/@esbuild/win32-x64": { - "version": "0.17.19", - "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.17.19.tgz", - "integrity": "sha512-lAhycmKnVOuRYNtRtatQR1LPQf2oYCkRGkSFnseDAKPl8lu5SOsK/e1sXe5a0Pc5kHIHe6P2I/ilntNv2xf3cA==", + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.18.20.tgz", + "integrity": "sha512-kTdfRcSiDfQca/y9QIkng02avJ+NCaQvrMejlsB3RRv5sE9rRoeBPISaZpKxHELzRxZyLvNts1P27W3wV+8geQ==", "cpu": [ "x64" ], @@ -542,9 +629,9 @@ } }, "node_modules/@eslint/eslintrc": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.1.1.tgz", - "integrity": "sha512-9t7ZA7NGGK8ckelF0PQCfcxIUzs1Md5rrO6U/c+FIQNanea5UZC0wqKXH4vHBccmu4ZJgZ2idtPeW7+Q2npOEA==", + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.1.3.tgz", + "integrity": "sha512-yZzuIG+jnVu6hNSzFEN07e8BxF3uAzYtQb6uDkaYZLo6oYZDCq454c5kB8zxnzfCYyP4MIuyBn10L0DqwujTmA==", "dev": true, "dependencies": { "ajv": "^6.12.4", @@ -583,21 +670,30 @@ } }, "node_modules/@eslint/js": { - "version": "8.39.0", - "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.39.0.tgz", - "integrity": "sha512-kf9RB0Fg7NZfap83B3QOqOGg9QmD9yBudqQXzzOtn3i4y7ZUXe5ONeW34Gwi+TxhH4mvj72R1Zc300KUMa9Bng==", + "version": "8.51.0", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.51.0.tgz", + "integrity": "sha512-HxjQ8Qn+4SI3/AFv6sOrDB+g6PpUTDwSJiQqOrnneEk8L71161srI9gjzzZvYVbzHiVg/BvcH95+cK/zfIt4pg==", "dev": true, "engines": { "node": "^12.22.0 || ^14.17.0 || >=16.0.0" } }, + "node_modules/@fastify/busboy": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@fastify/busboy/-/busboy-2.1.0.tgz", + "integrity": "sha512-+KpH+QxZU7O4675t3mnkQKcZZg56u+K/Ct2K+N2AZYNVK8kyeo/bI18tI8aPm3tvNNRyTWfj6s5tnGNlcbQRsA==", + "dev": true, + "engines": { + "node": ">=14" + } + }, "node_modules/@humanwhocodes/config-array": { - "version": "0.11.10", - "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.10.tgz", - "integrity": "sha512-KVVjQmNUepDVGXNuoRRdmmEjruj0KfiGSbS8LVc12LMsWDQzRXJ0qdhN8L8uUigKpfEHRhlaQFY0ib1tnUbNeQ==", + "version": "0.11.13", + "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.13.tgz", + "integrity": "sha512-JSBDMiDKSzQVngfRjOdFXgFfklaXI4K9nLF49Auh21lmBWRLIK3+xTErTWD4KU54pb6coM6ESE7Awz/FNU3zgQ==", "dev": true, "dependencies": { - "@humanwhocodes/object-schema": "^1.2.1", + "@humanwhocodes/object-schema": "^2.0.1", "debug": "^4.1.1", "minimatch": "^3.0.5" }, @@ -619,27 +715,51 @@ } }, "node_modules/@humanwhocodes/object-schema": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-1.2.1.tgz", - "integrity": "sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-2.0.1.tgz", + "integrity": "sha512-dvuCeX5fC9dXgJn9t+X5atfmgQAzUOWqS1254Gh0m6i8wKd10ebXkfNKiRK+1GWi/yTvvLDHpoxLr0xxxeslWw==", "dev": true }, + "node_modules/@jest/schemas": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-29.6.3.tgz", + "integrity": "sha512-mo5j5X+jIZmJQveBKeS/clAueipV7KgiX1vMgCxam1RNYiqE1w62n0/tJJnHtjW8ZHcQco5gY85jA3mi0L+nSA==", + "dev": true, + "dependencies": { + "@sinclair/typebox": "^0.27.8" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, "node_modules/@jridgewell/sourcemap-codec": { "version": "1.4.15", "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz", "integrity": "sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==", "dev": true }, + "node_modules/@ljharb/through": { + "version": "2.3.11", + "resolved": "https://registry.npmjs.org/@ljharb/through/-/through-2.3.11.tgz", + "integrity": "sha512-ccfcIDlogiXNq5KcbAwbaO7lMh3Tm1i3khMPYpxlK8hH/W53zN81KM9coerRLOnTGu3nfXIniAmQbRI9OxbC0w==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/@microsoft/api-documenter": { - "version": "7.22.32", - "resolved": "https://registry.npmjs.org/@microsoft/api-documenter/-/api-documenter-7.22.32.tgz", - "integrity": "sha512-JePSgTg3qz5SiqcINQO52EKNh15DTbnvUpnx1qcrBlpR9gX0TmCw5g5uGPegIHJzIeif4uYe15nEUynrYDT2nw==", + "version": "7.23.12", + "resolved": "https://registry.npmjs.org/@microsoft/api-documenter/-/api-documenter-7.23.12.tgz", + "integrity": "sha512-ZFQGHNs8fSe3KoSCNa+jt/HLTN8IdTRGd0TZqmSeHpz2cSvUYHJeyQKhv8s7yi2flr1LezBq5/ig65ITZPSSqw==", "dev": true, "dependencies": { - "@microsoft/api-extractor-model": "7.27.5", + "@microsoft/api-extractor-model": "7.28.2", "@microsoft/tsdoc": "0.14.2", - "@rushstack/node-core-library": "3.59.6", - "@rushstack/ts-command-line": "4.15.1", + "@rushstack/node-core-library": "3.61.0", + "@rushstack/ts-command-line": "4.17.1", "colors": "~1.2.1", "js-yaml": "~3.13.1", "resolve": "~1.22.1" @@ -649,17 +769,17 @@ } }, "node_modules/@microsoft/api-extractor": { - "version": "7.36.3", - "resolved": "https://registry.npmjs.org/@microsoft/api-extractor/-/api-extractor-7.36.3.tgz", - "integrity": "sha512-u0H6362AQq+r55X8drHx4npgkrCfJnMzRRHfQo8PMNKB8TcBnrTLfXhXWi+xnTM6CzlU/netEN8c4bq581Rnrg==", + "version": "7.38.3", + "resolved": "https://registry.npmjs.org/@microsoft/api-extractor/-/api-extractor-7.38.3.tgz", + "integrity": "sha512-xt9iYyC5f39281j77JTA9C3ISJpW1XWkCcnw+2vM78CPnro6KhPfwQdPDfwS5JCPNuq0grm8cMdPUOPvrchDWw==", "dev": true, "dependencies": { - "@microsoft/api-extractor-model": "7.27.5", + "@microsoft/api-extractor-model": "7.28.2", "@microsoft/tsdoc": "0.14.2", "@microsoft/tsdoc-config": "~0.16.1", - "@rushstack/node-core-library": "3.59.6", - "@rushstack/rig-package": "0.4.0", - "@rushstack/ts-command-line": "4.15.1", + "@rushstack/node-core-library": "3.61.0", + "@rushstack/rig-package": "0.5.1", + "@rushstack/ts-command-line": "4.17.1", "colors": "~1.2.1", "lodash": "~4.17.15", "resolve": "~1.22.1", @@ -672,14 +792,27 @@ } }, "node_modules/@microsoft/api-extractor-model": { - "version": "7.27.5", - "resolved": "https://registry.npmjs.org/@microsoft/api-extractor-model/-/api-extractor-model-7.27.5.tgz", - "integrity": "sha512-9/tBzYMJitR+o+zkPr1lQh2+e8ClcaTF6eZo7vZGDqRt2O5XmXWPbYJZmxyM3wb5at6lfJNEeGZrQXLjsQ0Nbw==", + "version": "7.28.2", + "resolved": "https://registry.npmjs.org/@microsoft/api-extractor-model/-/api-extractor-model-7.28.2.tgz", + "integrity": "sha512-vkojrM2fo3q4n4oPh4uUZdjJ2DxQ2+RnDQL/xhTWSRUNPF6P4QyrvY357HBxbnltKcYu+nNNolVqc6TIGQ73Ig==", "dev": true, "dependencies": { "@microsoft/tsdoc": "0.14.2", "@microsoft/tsdoc-config": "~0.16.1", - "@rushstack/node-core-library": "3.59.6" + "@rushstack/node-core-library": "3.61.0" + } + }, + "node_modules/@microsoft/api-extractor/node_modules/typescript": { + "version": "5.0.4", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.0.4.tgz", + "integrity": "sha512-cW9T5W9xY37cc+jfEnaUvX91foxtHkza3Nw3wkoF4sSlKn0MONdkdEndig/qPBWXNkmplh3NzayQzCiHM4/hqw==", + "dev": true, + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=12.20" } }, "node_modules/@microsoft/tsdoc": { @@ -748,10 +881,51 @@ "node": ">= 8" } }, + "node_modules/@pnpm/config.env-replace": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@pnpm/config.env-replace/-/config.env-replace-1.1.0.tgz", + "integrity": "sha512-htyl8TWnKL7K/ESFa1oW2UB5lVDxuF5DpM7tBi6Hu2LNL3mWkIzNLG6N4zoCUP1lCKNxWy/3iu8mS8MvToGd6w==", + "dev": true, + "engines": { + "node": ">=12.22.0" + } + }, + "node_modules/@pnpm/network.ca-file": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@pnpm/network.ca-file/-/network.ca-file-1.0.2.tgz", + "integrity": "sha512-YcPQ8a0jwYU9bTdJDpXjMi7Brhkr1mXsXrUJvjqM2mQDgkRiz8jFaQGOdaLxgjtUfQgZhKy/O3cG/YwmgKaxLA==", + "dev": true, + "dependencies": { + "graceful-fs": "4.2.10" + }, + "engines": { + "node": ">=12.22.0" + } + }, + "node_modules/@pnpm/network.ca-file/node_modules/graceful-fs": { + "version": "4.2.10", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.10.tgz", + "integrity": "sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA==", + "dev": true + }, + "node_modules/@pnpm/npm-conf": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/@pnpm/npm-conf/-/npm-conf-2.2.2.tgz", + "integrity": "sha512-UA91GwWPhFExt3IizW6bOeY/pQ0BkuNwKjk9iQW9KqxluGCrg4VenZ0/L+2Y0+ZOtme72EVvg6v0zo3AMQRCeA==", + "dev": true, + "dependencies": { + "@pnpm/config.env-replace": "^1.1.0", + "@pnpm/network.ca-file": "^1.0.1", + "config-chain": "^1.1.11" + }, + "engines": { + "node": ">=12" + } + }, "node_modules/@rushstack/node-core-library": { - "version": "3.59.6", - "resolved": "https://registry.npmjs.org/@rushstack/node-core-library/-/node-core-library-3.59.6.tgz", - "integrity": "sha512-bMYJwNFfWXRNUuHnsE9wMlW/mOB4jIwSUkRKtu02CwZhQdmzMsUbxE0s1xOLwTpNIwlzfW/YT7OnOHgDffLgYg==", + "version": "3.61.0", + "resolved": "https://registry.npmjs.org/@rushstack/node-core-library/-/node-core-library-3.61.0.tgz", + "integrity": "sha512-tdOjdErme+/YOu4gPed3sFS72GhtWCgNV9oDsHDnoLY5oDfwjKUc9Z+JOZZ37uAxcm/OCahDHfuu2ugqrfWAVQ==", "dev": true, "dependencies": { "colors": "~1.2.1", @@ -772,9 +946,9 @@ } }, "node_modules/@rushstack/rig-package": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/@rushstack/rig-package/-/rig-package-0.4.0.tgz", - "integrity": "sha512-FnM1TQLJYwSiurP6aYSnansprK5l8WUK8VG38CmAaZs29ZeL1msjK0AP1VS4ejD33G0kE/2cpsPsS9jDenBMxw==", + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/@rushstack/rig-package/-/rig-package-0.5.1.tgz", + "integrity": "sha512-pXRYSe29TjRw7rqxD4WS3HN/sRSbfr+tJs4a9uuaSIBAITbUggygdhuG0VrO0EO+QqH91GhYMN4S6KRtOEmGVA==", "dev": true, "dependencies": { "resolve": "~1.22.1", @@ -782,9 +956,9 @@ } }, "node_modules/@rushstack/ts-command-line": { - "version": "4.15.1", - "resolved": "https://registry.npmjs.org/@rushstack/ts-command-line/-/ts-command-line-4.15.1.tgz", - "integrity": "sha512-EL4jxZe5fhb1uVL/P/wQO+Z8Rc8FMiWJ1G7VgnPDvdIt5GVjRfK7vwzder1CZQiX3x0PY6uxENYLNGTFd1InRQ==", + "version": "4.17.1", + "resolved": "https://registry.npmjs.org/@rushstack/ts-command-line/-/ts-command-line-4.17.1.tgz", + "integrity": "sha512-2jweO1O57BYP5qdBGl6apJLB+aRIn5ccIRTPDyULh0KMwVzFqWtw6IZWt1qtUoZD/pD2RNkIOosH6Cq45rIYeg==", "dev": true, "dependencies": { "@types/argparse": "1.0.38", @@ -822,10 +996,16 @@ "node": ">=6" } }, + "node_modules/@sinclair/typebox": { + "version": "0.27.8", + "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.27.8.tgz", + "integrity": "sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA==", + "dev": true + }, "node_modules/@sindresorhus/is": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-2.1.1.tgz", - "integrity": "sha512-/aPsuoj/1Dw/kzhkgz+ES6TxG0zfTMGLwuK2ZG00k/iJzYHTLCE8mVU8EPqEOp/lmxPoq1C1C9RYToRKb2KEfg==", + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-4.6.0.tgz", + "integrity": "sha512-t09vSN3MdfsyCHoFcTRCH/iUtG7OJ0CsjzB8cjAmKc/va/kIgeDI/TxsigdncE/4be734m0cvIYwNaV4i2XqAw==", "dev": true, "engines": { "node": ">=10" @@ -880,9 +1060,9 @@ } }, "node_modules/@types/eslint": { - "version": "8.37.0", - "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-8.37.0.tgz", - "integrity": "sha512-Piet7dG2JBuDIfohBngQ3rCt7MgO9xCO4xIMKxBThCq5PNRB91IjlJ10eJVwfoNtvTErmxLzwBZ7rHZtbOMmFQ==", + "version": "8.44.4", + "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-8.44.4.tgz", + "integrity": "sha512-lOzjyfY/D9QR4hY9oblZ76B90MYTB3RrQ4z2vBIJKj9ROCRqdkYl2gSUx1x1a4IWPjKJZLL4Aw1Zfay7eMnmnA==", "dev": true, "dependencies": { "@types/estree": "*", @@ -896,9 +1076,9 @@ "dev": true }, "node_modules/@types/http-cache-semantics": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/@types/http-cache-semantics/-/http-cache-semantics-4.0.1.tgz", - "integrity": "sha512-SZs7ekbP8CN0txVG2xVRH6EgKmEm31BOxA07vkFaETzZz1xh+cbt8BcI0slpymvwhx5dlFnQG2rTlPVQn+iRPQ==", + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/@types/http-cache-semantics/-/http-cache-semantics-4.0.4.tgz", + "integrity": "sha512-1m0bIFVc7eJWyve9S0RnuRgcQqF/Xd5QsUZAZeQFr1Q3/p9JWoQQEqmVy+DPTNpGXwhgIetAoYF8JSc33q29QA==", "dev": true }, "node_modules/@types/json-schema": { @@ -916,72 +1096,64 @@ "@types/node": "*" } }, - "node_modules/@types/minimist": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/@types/minimist/-/minimist-1.2.2.tgz", - "integrity": "sha512-jhuKLIRrhvCPLqwPcx6INqmKeiA5EWrsCOPhrlFSrbrmU4ZMPjj5Ul/oLCMDO98XRUIwVm78xICz4EPCektzeQ==", - "dev": true - }, "node_modules/@types/node": { - "version": "18.17.3", - "resolved": "https://registry.npmjs.org/@types/node/-/node-18.17.3.tgz", - "integrity": "sha512-2x8HWtFk0S99zqVQABU9wTpr8wPoaDHZUcAkoTKH+nL7kPv3WUI9cRi/Kk5Mz4xdqXSqTkKP7IWNoQQYCnDsTA==", - "dev": true + "version": "20.9.1", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.9.1.tgz", + "integrity": "sha512-HhmzZh5LSJNS5O8jQKpJ/3ZcrrlG6L70hpGqMIAoM9YVD0YBRNWYsfwcXq8VnSjlNpCpgLzMXdiPo+dxcvSmiA==", + "dev": true, + "dependencies": { + "undici-types": "~5.26.4" + } }, "node_modules/@types/normalize-package-data": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/@types/normalize-package-data/-/normalize-package-data-2.4.1.tgz", - "integrity": "sha512-Gj7cI7z+98M282Tqmp2K5EIsoouUEzbBJhQQzDE3jSIRk6r9gsz0oUokqIUR4u1R3dMHo0pDHM7sNOHyhulypw==", - "dev": true - }, - "node_modules/@types/parse-json": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/@types/parse-json/-/parse-json-4.0.0.tgz", - "integrity": "sha512-//oorEZjL6sbPcKUaCdIGlIUeH26mgzimjBB77G6XRgnDl/L5wOnpyBGRe/Mmf5CVW3PwEBE1NjiMZ/ssFh4wA==", + "version": "2.4.4", + "resolved": "https://registry.npmjs.org/@types/normalize-package-data/-/normalize-package-data-2.4.4.tgz", + "integrity": "sha512-37i+OaWTh9qeK4LSHPsyRC7NahnGotNuZvjLSgcPzblpHB3rrCJxAOgI5gCdKm7coonsaX1Of0ILiTcnZjbfxA==", "dev": true }, "node_modules/@types/responselike": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/@types/responselike/-/responselike-1.0.0.tgz", - "integrity": "sha512-85Y2BjiufFzaMIlvJDvTTB8Fxl2xfLo4HgmHzVBz08w4wDePCTjYw66PdrolO0kzli3yam/YCgRufyo1DdQVTA==", + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@types/responselike/-/responselike-1.0.3.tgz", + "integrity": "sha512-H/+L+UkTV33uf49PH5pCAUBVPNj2nDBXTN+qS1dOwyyg24l3CcicicCA7ca+HMvJBZcFgl5r8e+RR6elsb4Lyw==", "dev": true, "dependencies": { "@types/node": "*" } }, "node_modules/@types/semver": { - "version": "7.5.0", - "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.5.0.tgz", - "integrity": "sha512-G8hZ6XJiHnuhQKR7ZmysCeJWE08o8T0AXtk5darsCaTVsYZhhgUrq53jizaR2FvsoeCwJhlmwTjkXBY5Pn/ZHw==", + "version": "7.5.5", + "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.5.5.tgz", + "integrity": "sha512-+d+WYC1BxJ6yVOgUgzK8gWvp5qF8ssV5r4nsDcZWKRWcDQLQ619tvWAxJQYGgBrO1MnLJC7a5GtiYsAoQ47dJg==", "dev": true }, "node_modules/@typescript-eslint/eslint-plugin": { - "version": "5.59.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.59.1.tgz", - "integrity": "sha512-AVi0uazY5quFB9hlp2Xv+ogpfpk77xzsgsIEWyVS7uK/c7MZ5tw7ZPbapa0SbfkqE0fsAMkz5UwtgMLVk2BQAg==", + "version": "6.7.5", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-6.7.5.tgz", + "integrity": "sha512-JhtAwTRhOUcP96D0Y6KYnwig/MRQbOoLGXTON2+LlyB/N35SP9j1boai2zzwXb7ypKELXMx3DVk9UTaEq1vHEw==", "dev": true, "dependencies": { - "@eslint-community/regexpp": "^4.4.0", - "@typescript-eslint/scope-manager": "5.59.1", - "@typescript-eslint/type-utils": "5.59.1", - "@typescript-eslint/utils": "5.59.1", + "@eslint-community/regexpp": "^4.5.1", + "@typescript-eslint/scope-manager": "6.7.5", + "@typescript-eslint/type-utils": "6.7.5", + "@typescript-eslint/utils": "6.7.5", + "@typescript-eslint/visitor-keys": "6.7.5", "debug": "^4.3.4", - "grapheme-splitter": "^1.0.4", - "ignore": "^5.2.0", - "natural-compare-lite": "^1.4.0", - "semver": "^7.3.7", - "tsutils": "^3.21.0" + "graphemer": "^1.4.0", + "ignore": "^5.2.4", + "natural-compare": "^1.4.0", + "semver": "^7.5.4", + "ts-api-utils": "^1.0.1" }, "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + "node": "^16.0.0 || >=18.0.0" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { - "@typescript-eslint/parser": "^5.0.0", - "eslint": "^6.0.0 || ^7.0.0 || ^8.0.0" + "@typescript-eslint/parser": "^6.0.0 || ^6.0.0-alpha", + "eslint": "^7.0.0 || ^8.0.0" }, "peerDependenciesMeta": { "typescript": { @@ -990,25 +1162,26 @@ } }, "node_modules/@typescript-eslint/parser": { - "version": "5.59.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-5.59.1.tgz", - "integrity": "sha512-nzjFAN8WEu6yPRDizIFyzAfgK7nybPodMNFGNH0M9tei2gYnYszRDqVA0xlnRjkl7Hkx2vYrEdb6fP2a21cG1g==", + "version": "6.7.5", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-6.7.5.tgz", + "integrity": "sha512-bIZVSGx2UME/lmhLcjdVc7ePBwn7CLqKarUBL4me1C5feOd663liTGjMBGVcGr+BhnSLeP4SgwdvNnnkbIdkCw==", "dev": true, "dependencies": { - "@typescript-eslint/scope-manager": "5.59.1", - "@typescript-eslint/types": "5.59.1", - "@typescript-eslint/typescript-estree": "5.59.1", + "@typescript-eslint/scope-manager": "6.7.5", + "@typescript-eslint/types": "6.7.5", + "@typescript-eslint/typescript-estree": "6.7.5", + "@typescript-eslint/visitor-keys": "6.7.5", "debug": "^4.3.4" }, "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + "node": "^16.0.0 || >=18.0.0" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { - "eslint": "^6.0.0 || ^7.0.0 || ^8.0.0" + "eslint": "^7.0.0 || ^8.0.0" }, "peerDependenciesMeta": { "typescript": { @@ -1017,16 +1190,16 @@ } }, "node_modules/@typescript-eslint/scope-manager": { - "version": "5.59.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-5.59.1.tgz", - "integrity": "sha512-mau0waO5frJctPuAzcxiNWqJR5Z8V0190FTSqRw1Q4Euop6+zTwHAf8YIXNwDOT29tyUDrQ65jSg9aTU/H0omA==", + "version": "6.7.5", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-6.7.5.tgz", + "integrity": "sha512-GAlk3eQIwWOJeb9F7MKQ6Jbah/vx1zETSDw8likab/eFcqkjSD7BI75SDAeC5N2L0MmConMoPvTsmkrg71+B1A==", "dev": true, "dependencies": { - "@typescript-eslint/types": "5.59.1", - "@typescript-eslint/visitor-keys": "5.59.1" + "@typescript-eslint/types": "6.7.5", + "@typescript-eslint/visitor-keys": "6.7.5" }, "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + "node": "^16.0.0 || >=18.0.0" }, "funding": { "type": "opencollective", @@ -1034,25 +1207,25 @@ } }, "node_modules/@typescript-eslint/type-utils": { - "version": "5.59.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-5.59.1.tgz", - "integrity": "sha512-ZMWQ+Oh82jWqWzvM3xU+9y5U7MEMVv6GLioM3R5NJk6uvP47kZ7YvlgSHJ7ERD6bOY7Q4uxWm25c76HKEwIjZw==", + "version": "6.7.5", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-6.7.5.tgz", + "integrity": "sha512-Gs0qos5wqxnQrvpYv+pf3XfcRXW6jiAn9zE/K+DlmYf6FcpxeNYN0AIETaPR7rHO4K2UY+D0CIbDP9Ut0U4m1g==", "dev": true, "dependencies": { - "@typescript-eslint/typescript-estree": "5.59.1", - "@typescript-eslint/utils": "5.59.1", + "@typescript-eslint/typescript-estree": "6.7.5", + "@typescript-eslint/utils": "6.7.5", "debug": "^4.3.4", - "tsutils": "^3.21.0" + "ts-api-utils": "^1.0.1" }, "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + "node": "^16.0.0 || >=18.0.0" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { - "eslint": "*" + "eslint": "^7.0.0 || ^8.0.0" }, "peerDependenciesMeta": { "typescript": { @@ -1061,12 +1234,12 @@ } }, "node_modules/@typescript-eslint/types": { - "version": "5.59.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.59.1.tgz", - "integrity": "sha512-dg0ICB+RZwHlysIy/Dh1SP+gnXNzwd/KS0JprD3Lmgmdq+dJAJnUPe1gNG34p0U19HvRlGX733d/KqscrGC1Pg==", + "version": "6.7.5", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-6.7.5.tgz", + "integrity": "sha512-WboQBlOXtdj1tDFPyIthpKrUb+kZf2VroLZhxKa/VlwLlLyqv/PwUNgL30BlTVZV1Wu4Asu2mMYPqarSO4L5ZQ==", "dev": true, "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + "node": "^16.0.0 || >=18.0.0" }, "funding": { "type": "opencollective", @@ -1074,21 +1247,21 @@ } }, "node_modules/@typescript-eslint/typescript-estree": { - "version": "5.59.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-5.59.1.tgz", - "integrity": "sha512-lYLBBOCsFltFy7XVqzX0Ju+Lh3WPIAWxYpmH/Q7ZoqzbscLiCW00LeYCdsUnnfnj29/s1WovXKh2gwCoinHNGA==", + "version": "6.7.5", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-6.7.5.tgz", + "integrity": "sha512-NhJiJ4KdtwBIxrKl0BqG1Ur+uw7FiOnOThcYx9DpOGJ/Abc9z2xNzLeirCG02Ig3vkvrc2qFLmYSSsaITbKjlg==", "dev": true, "dependencies": { - "@typescript-eslint/types": "5.59.1", - "@typescript-eslint/visitor-keys": "5.59.1", + "@typescript-eslint/types": "6.7.5", + "@typescript-eslint/visitor-keys": "6.7.5", "debug": "^4.3.4", "globby": "^11.1.0", "is-glob": "^4.0.3", - "semver": "^7.3.7", - "tsutils": "^3.21.0" + "semver": "^7.5.4", + "ts-api-utils": "^1.0.1" }, "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + "node": "^16.0.0 || >=18.0.0" }, "funding": { "type": "opencollective", @@ -1101,42 +1274,41 @@ } }, "node_modules/@typescript-eslint/utils": { - "version": "5.59.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-5.59.1.tgz", - "integrity": "sha512-MkTe7FE+K1/GxZkP5gRj3rCztg45bEhsd8HYjczBuYm+qFHP5vtZmjx3B0yUCDotceQ4sHgTyz60Ycl225njmA==", + "version": "6.7.5", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-6.7.5.tgz", + "integrity": "sha512-pfRRrH20thJbzPPlPc4j0UNGvH1PjPlhlCMq4Yx7EGjV7lvEeGX0U6MJYe8+SyFutWgSHsdbJ3BXzZccYggezA==", "dev": true, "dependencies": { - "@eslint-community/eslint-utils": "^4.2.0", - "@types/json-schema": "^7.0.9", - "@types/semver": "^7.3.12", - "@typescript-eslint/scope-manager": "5.59.1", - "@typescript-eslint/types": "5.59.1", - "@typescript-eslint/typescript-estree": "5.59.1", - "eslint-scope": "^5.1.1", - "semver": "^7.3.7" + "@eslint-community/eslint-utils": "^4.4.0", + "@types/json-schema": "^7.0.12", + "@types/semver": "^7.5.0", + "@typescript-eslint/scope-manager": "6.7.5", + "@typescript-eslint/types": "6.7.5", + "@typescript-eslint/typescript-estree": "6.7.5", + "semver": "^7.5.4" }, "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + "node": "^16.0.0 || >=18.0.0" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { - "eslint": "^6.0.0 || ^7.0.0 || ^8.0.0" + "eslint": "^7.0.0 || ^8.0.0" } }, "node_modules/@typescript-eslint/visitor-keys": { - "version": "5.59.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.59.1.tgz", - "integrity": "sha512-6waEYwBTCWryx0VJmP7JaM4FpipLsFl9CvYf2foAE8Qh/Y0s+bxWysciwOs0LTBED4JCaNxTZ5rGadB14M6dwA==", + "version": "6.7.5", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-6.7.5.tgz", + "integrity": "sha512-3MaWdDZtLlsexZzDSdQWsFQ9l9nL8B80Z4fImSpyllFC/KLqWQRdEcB+gGGO+N3Q2uL40EsG66wZLsohPxNXvg==", "dev": true, "dependencies": { - "@typescript-eslint/types": "5.59.1", - "eslint-visitor-keys": "^3.3.0" + "@typescript-eslint/types": "6.7.5", + "eslint-visitor-keys": "^3.4.1" }, "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + "node": "^16.0.0 || >=18.0.0" }, "funding": { "type": "opencollective", @@ -1144,26 +1316,31 @@ } }, "node_modules/@vitest/expect": { - "version": "0.30.1", - "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-0.30.1.tgz", - "integrity": "sha512-c3kbEtN8XXJSeN81iDGq29bUzSjQhjES2WR3aColsS4lPGbivwLtas4DNUe0jD9gg/FYGIteqOenfU95EFituw==", + "version": "0.34.6", + "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-0.34.6.tgz", + "integrity": "sha512-QUzKpUQRc1qC7qdGo7rMK3AkETI7w18gTCUrsNnyjjJKYiuUB9+TQK3QnR1unhCnWRC0AbKv2omLGQDF/mIjOw==", "dev": true, "dependencies": { - "@vitest/spy": "0.30.1", - "@vitest/utils": "0.30.1", - "chai": "^4.3.7" + "@vitest/spy": "0.34.6", + "@vitest/utils": "0.34.6", + "chai": "^4.3.10" + }, + "funding": { + "url": "https://opencollective.com/vitest" } }, "node_modules/@vitest/runner": { - "version": "0.30.1", - "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-0.30.1.tgz", - "integrity": "sha512-W62kT/8i0TF1UBCNMRtRMOBWJKRnNyv9RrjIgdUryEe0wNpGZvvwPDLuzYdxvgSckzjp54DSpv1xUbv4BQ0qVA==", + "version": "0.34.6", + "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-0.34.6.tgz", + "integrity": "sha512-1CUQgtJSLF47NnhN+F9X2ycxUP0kLHQ/JWvNHbeBfwW8CzEGgeskzNnHDyv1ieKTltuR6sdIHV+nmR6kPxQqzQ==", "dev": true, "dependencies": { - "@vitest/utils": "0.30.1", - "concordance": "^5.0.4", + "@vitest/utils": "0.34.6", "p-limit": "^4.0.0", - "pathe": "^1.1.0" + "pathe": "^1.1.1" + }, + "funding": { + "url": "https://opencollective.com/vitest" } }, "node_modules/@vitest/runner/node_modules/p-limit": { @@ -1194,20 +1371,23 @@ } }, "node_modules/@vitest/snapshot": { - "version": "0.30.1", - "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-0.30.1.tgz", - "integrity": "sha512-fJZqKrE99zo27uoZA/azgWyWbFvM1rw2APS05yB0JaLwUIg9aUtvvnBf4q7JWhEcAHmSwbrxKFgyBUga6tq9Tw==", + "version": "0.34.6", + "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-0.34.6.tgz", + "integrity": "sha512-B3OZqYn6k4VaN011D+ve+AA4whM4QkcwcrwaKwAbyyvS/NB1hCWjFIBQxAQQSQir9/RtyAAGuq+4RJmbn2dH4w==", "dev": true, "dependencies": { - "magic-string": "^0.30.0", - "pathe": "^1.1.0", - "pretty-format": "^27.5.1" + "magic-string": "^0.30.1", + "pathe": "^1.1.1", + "pretty-format": "^29.5.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" } }, "node_modules/@vitest/snapshot/node_modules/magic-string": { - "version": "0.30.2", - "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.2.tgz", - "integrity": "sha512-lNZdu7pewtq/ZvWUp9Wpf/x7WzMTsR26TWV03BRZrXFsv+BI6dy8RAiKgm1uM/kyR0rCfUcqvOlXKG66KhIGug==", + "version": "0.30.5", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.5.tgz", + "integrity": "sha512-7xlpfBaQaP/T6Vh8MO/EqXSW5En6INHEvEXQiuff7Gku0PWjU3uf6w/j9o7O+SpB5fOAkrI5HeoNgwjEO0pFsA==", "dev": true, "dependencies": { "@jridgewell/sourcemap-codec": "^1.4.15" @@ -1217,23 +1397,29 @@ } }, "node_modules/@vitest/spy": { - "version": "0.30.1", - "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-0.30.1.tgz", - "integrity": "sha512-YfJeIf37GvTZe04ZKxzJfnNNuNSmTEGnla2OdL60C8od16f3zOfv9q9K0nNii0NfjDJRt/CVN/POuY5/zTS+BA==", + "version": "0.34.6", + "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-0.34.6.tgz", + "integrity": "sha512-xaCvneSaeBw/cz8ySmF7ZwGvL0lBjfvqc1LpQ/vcdHEvpLn3Ff1vAvjw+CoGn0802l++5L/pxb7whwcWAw+DUQ==", "dev": true, "dependencies": { - "tinyspy": "^2.1.0" + "tinyspy": "^2.1.1" + }, + "funding": { + "url": "https://opencollective.com/vitest" } }, "node_modules/@vitest/utils": { - "version": "0.30.1", - "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-0.30.1.tgz", - "integrity": "sha512-/c8Xv2zUVc+rnNt84QF0Y0zkfxnaGhp87K2dYJMLtLOIckPzuxLVzAtFCicGFdB4NeBHNzTRr1tNn7rCtQcWFA==", + "version": "0.34.6", + "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-0.34.6.tgz", + "integrity": "sha512-IG5aDD8S6zlvloDsnzHw0Ut5xczlF+kv2BOTo+iXfPr54Yhi5qbVOgGB1hZaVq4iJ4C/MZ2J0y15IlsV/ZcI0A==", "dev": true, "dependencies": { - "concordance": "^5.0.4", + "diff-sequences": "^29.4.3", "loupe": "^2.3.6", - "pretty-format": "^27.5.1" + "pretty-format": "^29.5.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" } }, "node_modules/acorn": { @@ -1267,25 +1453,31 @@ } }, "node_modules/aggregate-error": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/aggregate-error/-/aggregate-error-3.1.0.tgz", - "integrity": "sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA==", + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/aggregate-error/-/aggregate-error-4.0.1.tgz", + "integrity": "sha512-0poP0T7el6Vq3rstR8Mn4V/IQrpBLO6POkUSrN7RhyY+GF/InCFShQzsQ39T25gkHhLgSLByyAz+Kjb+c2L98w==", "dev": true, "dependencies": { - "clean-stack": "^2.0.0", - "indent-string": "^4.0.0" + "clean-stack": "^4.0.0", + "indent-string": "^5.0.0" }, "engines": { - "node": ">=8" + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/aggregate-error/node_modules/indent-string": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz", - "integrity": "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==", + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-5.0.0.tgz", + "integrity": "sha512-m6FAo/spmsW2Ab2fU35JTYwtOKa2yAwXSwgjSv1TJzh4Mh7mC3lzAOVLBprb72XsTrgkEIsl7YrFNAiDiRhIGg==", "dev": true, "engines": { - "node": ">=8" + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/ajv": { @@ -1304,6 +1496,22 @@ "url": "https://github.com/sponsors/epoberezkin" } }, + "node_modules/all-package-names": { + "version": "2.0.780", + "resolved": "https://registry.npmjs.org/all-package-names/-/all-package-names-2.0.780.tgz", + "integrity": "sha512-gVk6lhl+7opXJQHNI+JO9RHxx+3JhoAb8wxhtqK12S1v+SBVjRG65+56JrMGWnjPvIS/olTmlk/Wcadsf/lD3A==", + "dev": true, + "dependencies": { + "commander-version": "^1.1.0", + "p-lock": "^2.0.0", + "parse-json-object": "^2.0.1", + "progress": "^2.0.3", + "types-json": "^1.2.2" + }, + "bin": { + "all-package-names": "build/bin/index.js" + } + }, "node_modules/ansi-align": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/ansi-align/-/ansi-align-3.0.1.tgz", @@ -1364,26 +1572,6 @@ "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, - "node_modules/any-observable": { - "version": "0.5.1", - "resolved": "https://registry.npmjs.org/any-observable/-/any-observable-0.5.1.tgz", - "integrity": "sha512-8zv01bgDOp9PTmRTNCAHTw64TFP2rvlX4LvtNJLachaXY+AjmIvLT47fABNPCiIe89hKiSCo2n5zmPqI9CElPA==", - "dev": true, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - }, - "peerDependenciesMeta": { - "rxjs": { - "optional": true - }, - "zen-observable": { - "optional": true - } - } - }, "node_modules/argparse": { "version": "1.0.10", "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", @@ -1472,15 +1660,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/arrify": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/arrify/-/arrify-1.0.1.tgz", - "integrity": "sha512-3CYzex9M9FGQjCGMGyi6/31c8GJbgb0qGyrx5HWxPd0aCwh4cB2YjMb2Xf9UuoogrMrlO9cTqnB5rI5GHZTcUA==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/assertion-error": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-1.1.0.tgz", @@ -1490,15 +1669,6 @@ "node": "*" } }, - "node_modules/async-exit-hook": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/async-exit-hook/-/async-exit-hook-2.0.1.tgz", - "integrity": "sha512-NW2cX8m1Q7KPA7a5M2ULQeZ2wR5qI5PAbw5L0UOMxdioVk9PMZ0h1TmyZEkPYrCvYjDlFICusOu1dlEKAAeXBw==", - "dev": true, - "engines": { - "node": ">=0.12.0" - } - }, "node_modules/available-typed-arrays": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.5.tgz", @@ -1517,63 +1687,183 @@ "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", "dev": true }, - "node_modules/blueimp-md5": { - "version": "2.19.0", - "resolved": "https://registry.npmjs.org/blueimp-md5/-/blueimp-md5-2.19.0.tgz", - "integrity": "sha512-DRQrD6gJyy8FbiE4s+bDoXS9hiW3Vbx5uCdwvcCf3zLHL+Iv7LtGHLpr+GZV8rHG8tK766FGYBwRbu8pELTt+w==", - "dev": true + "node_modules/base64-js": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", + "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] }, - "node_modules/boxen": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/boxen/-/boxen-5.1.2.tgz", - "integrity": "sha512-9gYgQKXx+1nP8mP7CzFyaUARhg7D3n1dF/FnErWmu9l6JvGpNUN278h0aSb+QjoiKSWG+iZ3uHrcqk0qrY9RQQ==", + "node_modules/big-integer": { + "version": "1.6.51", + "resolved": "https://registry.npmjs.org/big-integer/-/big-integer-1.6.51.tgz", + "integrity": "sha512-GPEid2Y9QU1Exl1rpO9B2IPJGHPSupF5GnVIP0blYvNOMer2bTvSWs1jGOUg04hTmu67nmLsQ9TBo1puaotBHg==", "dev": true, - "dependencies": { - "ansi-align": "^3.0.0", - "camelcase": "^6.2.0", - "chalk": "^4.1.0", - "cli-boxes": "^2.2.1", - "string-width": "^4.2.2", - "type-fest": "^0.20.2", - "widest-line": "^3.1.0", - "wrap-ansi": "^7.0.0" - }, "engines": { - "node": ">=10" + "node": ">=0.6" + } + }, + "node_modules/bl": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/bl/-/bl-4.1.0.tgz", + "integrity": "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==", + "dev": true, + "dependencies": { + "buffer": "^5.5.0", + "inherits": "^2.0.4", + "readable-stream": "^3.4.0" + } + }, + "node_modules/boxen": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/boxen/-/boxen-7.1.1.tgz", + "integrity": "sha512-2hCgjEmP8YLWQ130n2FerGv7rYpfBmnmp9Uy2Le1vge6X3gZIfSmEzP5QTDElFxcvVcXlEn8Aq6MU/PZygIOog==", + "dev": true, + "dependencies": { + "ansi-align": "^3.0.1", + "camelcase": "^7.0.1", + "chalk": "^5.2.0", + "cli-boxes": "^3.0.0", + "string-width": "^5.1.2", + "type-fest": "^2.13.0", + "widest-line": "^4.0.1", + "wrap-ansi": "^8.1.0" + }, + "engines": { + "node": ">=14.16" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/boxen/node_modules/camelcase": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz", - "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==", + "node_modules/boxen/node_modules/ansi-regex": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.0.1.tgz", + "integrity": "sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==", "dev": true, "engines": { - "node": ">=10" + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/boxen/node_modules/ansi-styles": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz", + "integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/boxen/node_modules/chalk": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.3.0.tgz", + "integrity": "sha512-dLitG79d+GV1Nb/VYcCDFivJeK1hiukt9QjRNVOsUtTy1rR1YJsmpGGTZ3qJos+uw7WmWF4wUwBd9jxjocFC2w==", + "dev": true, + "engines": { + "node": "^12.17.0 || ^14.13 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/boxen/node_modules/emoji-regex": { + "version": "9.2.2", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", + "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", + "dev": true + }, + "node_modules/boxen/node_modules/string-width": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", + "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", + "dev": true, + "dependencies": { + "eastasianwidth": "^0.2.0", + "emoji-regex": "^9.2.2", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/boxen/node_modules/strip-ansi": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", + "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", + "dev": true, + "dependencies": { + "ansi-regex": "^6.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, + "node_modules/boxen/node_modules/type-fest": { + "version": "2.19.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-2.19.0.tgz", + "integrity": "sha512-RAH822pAdBgcNMAfWnCBU3CFZcfZ/i1eZjwFU/dsLKumyuuP3niueg2UAukXYF0E2AAoc82ZSSf9J0WQBinzHA==", + "dev": true, + "engines": { + "node": ">=12.20" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/boxen/node_modules/wrap-ansi": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", - "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz", + "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==", "dev": true, "dependencies": { - "ansi-styles": "^4.0.0", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0" + "ansi-styles": "^6.1.0", + "string-width": "^5.0.1", + "strip-ansi": "^7.0.1" }, "engines": { - "node": ">=10" + "node": ">=12" }, "funding": { "url": "https://github.com/chalk/wrap-ansi?sponsor=1" } }, + "node_modules/bplist-parser": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/bplist-parser/-/bplist-parser-0.2.0.tgz", + "integrity": "sha512-z0M+byMThzQmD9NILRniCUXYsYpjwnlO8N5uCFaCqIOpqRsJCrQL9NK3JsD67CN5a08nF5oIL2bD6loTdHOuKw==", + "dev": true, + "dependencies": { + "big-integer": "^1.6.44" + }, + "engines": { + "node": ">= 5.10.0" + } + }, "node_modules/brace-expansion": { "version": "1.1.11", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", @@ -1596,22 +1886,49 @@ "node": ">=8" } }, + "node_modules/buffer": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", + "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "dependencies": { + "base64-js": "^1.3.1", + "ieee754": "^1.1.13" + } + }, "node_modules/builtins": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/builtins/-/builtins-1.0.3.tgz", "integrity": "sha512-uYBjakWipfaO/bXI7E8rq6kpwHRZK5cNYrUv2OzZSI/FvmdMyXJ2tG9dKcjEC5YHmHpUAwsargWIZNWdxb/bnQ==", "dev": true }, - "node_modules/busboy": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/busboy/-/busboy-1.6.0.tgz", - "integrity": "sha512-8SFQbg/0hQ9xy3UNTB0YEnsNBbWfhf7RtnzpL7TkBiTBRfrQ9Fxcnz7VJsleJpyp6rVLvXiuORqjlHi5q+PYuA==", + "node_modules/bundle-name": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/bundle-name/-/bundle-name-3.0.0.tgz", + "integrity": "sha512-PKA4BeSvBpQKQ8iPOGCSiell+N8P+Tf1DlwqmYhpe2gAhKPHn8EYOxVT+ShuGmhg8lN8XiSlS80yiExKXrURlw==", "dev": true, "dependencies": { - "streamsearch": "^1.1.0" + "run-applescript": "^5.0.0" }, "engines": { - "node": ">=10.16.0" + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/cac": { @@ -1624,16 +1941,12 @@ } }, "node_modules/cacheable-lookup": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/cacheable-lookup/-/cacheable-lookup-2.0.1.tgz", - "integrity": "sha512-EMMbsiOTcdngM/K6gV/OxF2x0t07+vMOWxZNSCRQMjO2MY2nhZQ6OYhOOpyQrbhqsgtvKGI7hcq6xjnA92USjg==", + "version": "5.0.4", + "resolved": "https://registry.npmjs.org/cacheable-lookup/-/cacheable-lookup-5.0.4.tgz", + "integrity": "sha512-2/kNscPhpcxrOigMZzbiWF7dz8ilhb/nIHU3EyZiXWXpeq/au8qJ8VhdftMkty3n7Gj6HIGalQG8oiBNB3AJgA==", "dev": true, - "dependencies": { - "@types/keyv": "^3.1.1", - "keyv": "^4.0.0" - }, "engines": { - "node": ">=10" + "node": ">=10.6.0" } }, "node_modules/cacheable-request": { @@ -1683,53 +1996,42 @@ } }, "node_modules/callsites": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", - "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-4.1.0.tgz", + "integrity": "sha512-aBMbD1Xxay75ViYezwT40aQONfr+pSXTHwNKvIXhXD6+LY3F1dLIcceoC5OZKBVHbXcysz1hL9D2w0JJIMXpUw==", "dev": true, "engines": { - "node": ">=6" + "node": ">=12.20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/camelcase": { - "version": "5.3.1", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", - "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", - "dev": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/camelcase-keys": { - "version": "6.2.2", - "resolved": "https://registry.npmjs.org/camelcase-keys/-/camelcase-keys-6.2.2.tgz", - "integrity": "sha512-YrwaA0vEKazPBkn0ipTiMpSajYDSe+KjQfrjhcBMxJt/znbvlHd8Pw/Vamaz5EB4Wfhs3SUR3Z9mwRu/P3s3Yg==", + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-7.0.1.tgz", + "integrity": "sha512-xlx1yCK2Oc1APsPXDL2LdlNP6+uu8OCDdhOBSVT279M/S+y75O30C2VuD8T2ogdePBBl7PfPF4504tnLgX3zfw==", "dev": true, - "dependencies": { - "camelcase": "^5.3.1", - "map-obj": "^4.0.0", - "quick-lru": "^4.0.1" - }, "engines": { - "node": ">=8" + "node": ">=14.16" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/chai": { - "version": "4.3.7", - "resolved": "https://registry.npmjs.org/chai/-/chai-4.3.7.tgz", - "integrity": "sha512-HLnAzZ2iupm25PlN0xFreAlBA5zaBSv3og0DdeGA4Ar6h6rJ3A0rolRUKJhSF2V10GZKDgWF/VmAEsNWjCRB+A==", + "version": "4.3.10", + "resolved": "https://registry.npmjs.org/chai/-/chai-4.3.10.tgz", + "integrity": "sha512-0UXG04VuVbruMUYbJ6JctvH0YnC/4q3/AkT18q4NaITo91CUm0liMS9VqzT9vZhVQ/1eqPanMWjBM+Juhfb/9g==", "dev": true, "dependencies": { "assertion-error": "^1.1.0", - "check-error": "^1.0.2", - "deep-eql": "^4.1.2", - "get-func-name": "^2.0.0", - "loupe": "^2.3.1", + "check-error": "^1.0.3", + "deep-eql": "^4.1.3", + "get-func-name": "^2.0.2", + "loupe": "^2.3.6", "pathval": "^1.1.1", - "type-detect": "^4.0.5" + "type-detect": "^4.0.8" }, "engines": { "node": ">=4" @@ -1758,36 +2060,66 @@ "dev": true }, "node_modules/check-error": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/check-error/-/check-error-1.0.2.tgz", - "integrity": "sha512-BrgHpW9NURQgzoNyjfq0Wu6VFO6D7IZEmJNdtgNqpzGG8RuNFHt2jQxWlAs4HMe119chBnv+34syEZtc6IhLtA==", + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/check-error/-/check-error-1.0.3.tgz", + "integrity": "sha512-iKEoDYaRmd1mxM90a2OEfWhjsjPpYPuQ+lMYsoxB126+t8fw7ySEO48nmDg5COTjxDI65/Y2OWpeEHk3ZOe8zg==", "dev": true, + "dependencies": { + "get-func-name": "^2.0.2" + }, "engines": { "node": "*" } }, "node_modules/ci-info": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-2.0.0.tgz", - "integrity": "sha512-5tK7EtrZ0N+OLFMthtqOj4fI2Jeb88C4CAZPu25LDVUgXJ0A3Js4PMGqrn0JU1W0Mh1/Z8wZzYPxqUrXeBboCQ==", - "dev": true + "version": "3.9.0", + "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-3.9.0.tgz", + "integrity": "sha512-NIxF55hv4nSqQswkAeiOi1r83xy8JldOFDTWiug55KBu9Jnblncd2U6ViHmYgHf01TPZS77NJBhBMKdWj9HQMQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/sibiraj-s" + } + ], + "engines": { + "node": ">=8" + } }, "node_modules/clean-stack": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/clean-stack/-/clean-stack-2.2.0.tgz", - "integrity": "sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A==", + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/clean-stack/-/clean-stack-4.2.0.tgz", + "integrity": "sha512-LYv6XPxoyODi36Dp976riBtSY27VmFo+MKqEU9QCCWyTrdEPDog+RWA7xQWHi6Vbp61j5c4cdzzX1NidnwtUWg==", "dev": true, + "dependencies": { + "escape-string-regexp": "5.0.0" + }, "engines": { - "node": ">=6" + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/clean-stack/node_modules/escape-string-regexp": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-5.0.0.tgz", + "integrity": "sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/cli-boxes": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/cli-boxes/-/cli-boxes-2.2.1.tgz", - "integrity": "sha512-y4coMcylgSCdVinjiDBuR8PCC2bLjyGTwEmPb9NHR/QaNU6EUOXcTY/s6VjGMD6ENSEaeQYHCY0GNGS5jfMwPw==", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/cli-boxes/-/cli-boxes-3.0.0.tgz", + "integrity": "sha512-/lzGpEWL/8PfI0BmBOPRwp0c/wFNX1RdUML3jK/RcSBA9T8mZDdQpqYBKtCFTOfQbwPqWEOpjqW+Fnayc0969g==", "dev": true, "engines": { - "node": ">=6" + "node": ">=10" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" @@ -1805,6 +2137,18 @@ "node": ">=8" } }, + "node_modules/cli-spinners": { + "version": "2.9.1", + "resolved": "https://registry.npmjs.org/cli-spinners/-/cli-spinners-2.9.1.tgz", + "integrity": "sha512-jHgecW0pxkonBJdrKsqxgRX9AcG+u/5k0Q7WPDfi8AogLAdwxEkyYYNWwZ5GvVFoFx2uiY1eNcSK00fh+1+FyQ==", + "dev": true, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/cli-truncate": { "version": "0.2.1", "resolved": "https://registry.npmjs.org/cli-truncate/-/cli-truncate-0.2.1.tgz", @@ -1874,6 +2218,15 @@ "node": ">= 10" } }, + "node_modules/clone": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/clone/-/clone-1.0.4.tgz", + "integrity": "sha512-JQHZ2QMW6l3aH/j6xCqQThY/9OH4D/9ls34cgkUBiEeocRTU04tHfKPBsUK1PqZCUQM7GiA0IIXJSuXHI64Kbg==", + "dev": true, + "engines": { + "node": ">=0.8" + } + }, "node_modules/clone-response": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/clone-response/-/clone-response-1.0.3.tgz", @@ -1886,15 +2239,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/clone-response/node_modules/mimic-response": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-1.0.1.tgz", - "integrity": "sha512-j5EctnkH7amfV/q5Hgmoal1g2QHFJRraOtmx0JpIqkxhBhI/lJSl1nMpQ45hVarwNETOoWEimndZ4QK0RHxuxQ==", - "dev": true, - "engines": { - "node": ">=4" - } - }, "node_modules/code-point-at": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/code-point-at/-/code-point-at-1.1.0.tgz", @@ -1932,13 +2276,22 @@ } }, "node_modules/commander": { - "version": "9.5.0", - "resolved": "https://registry.npmjs.org/commander/-/commander-9.5.0.tgz", - "integrity": "sha512-KRs7WVDKg86PWiuAqhDrAQnTXZKraVcCc6vFdL14qrZ/DcWwuRo7VoiYXalXO7S5GKpqYiVEwCbgFDfxNHKJBQ==", + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/commander/-/commander-6.2.1.tgz", + "integrity": "sha512-U7VdrJFnJgo4xjrHpTzu0yrHPGImdsmD95ZlgYSEajAn2JKzDhDTPG9kBTefmObL2w/ngeZnilk+OV9CG3d7UA==", "dev": true, - "optional": true, "engines": { - "node": "^12.20.0 || >=14" + "node": ">= 6" + } + }, + "node_modules/commander-version": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/commander-version/-/commander-version-1.1.0.tgz", + "integrity": "sha512-9aNW4N6q6EPDUszLRH6k9IwO6OoGYh3HRgUF/fA7Zs+Mz1v1x5akSqT7QGB8JsGY7AG7qMA7oRRB/4yyn33FYA==", + "dev": true, + "dependencies": { + "@bconnorwhite/module": "^2.0.2", + "commander": "^6.1.0" } }, "node_modules/concat-map": { @@ -1947,68 +2300,98 @@ "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", "dev": true }, - "node_modules/concordance": { - "version": "5.0.4", - "resolved": "https://registry.npmjs.org/concordance/-/concordance-5.0.4.tgz", - "integrity": "sha512-OAcsnTEYu1ARJqWVGwf4zh4JDfHZEaSNlNccFmt8YjB2l/n19/PF2viLINHc57vO4FKIAFl2FWASIGZZWZ2Kxw==", + "node_modules/config-chain": { + "version": "1.1.13", + "resolved": "https://registry.npmjs.org/config-chain/-/config-chain-1.1.13.tgz", + "integrity": "sha512-qj+f8APARXHrM0hraqXYb2/bOVSV4PvJQlNZ/DVj0QrmNM2q2euizkeuVckQ57J+W0mRH6Hvi+k50M4Jul2VRQ==", "dev": true, "dependencies": { - "date-time": "^3.1.0", - "esutils": "^2.0.3", - "fast-diff": "^1.2.0", - "js-string-escape": "^1.0.1", - "lodash": "^4.17.15", - "md5-hex": "^3.0.1", - "semver": "^7.3.2", - "well-known-symbols": "^2.0.0" - }, - "engines": { - "node": ">=10.18.0 <11 || >=12.14.0 <13 || >=14" + "ini": "^1.3.4", + "proto-list": "~1.2.1" } }, + "node_modules/config-chain/node_modules/ini": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz", + "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==", + "dev": true + }, "node_modules/configstore": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/configstore/-/configstore-5.0.1.tgz", - "integrity": "sha512-aMKprgk5YhBNyH25hj8wGt2+D52Sw1DRRIzqBwLp2Ya9mFmY8KPvvtvmna8SxVR9JMZ4kzMD68N22vlaRpkeFA==", + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/configstore/-/configstore-6.0.0.tgz", + "integrity": "sha512-cD31W1v3GqUlQvbBCGcXmd2Nj9SvLDOP1oQ0YFuLETufzSPaKp11rYBsSOm7rCsW3OnIRAFM3OxRhceaXNYHkA==", "dev": true, "dependencies": { - "dot-prop": "^5.2.0", - "graceful-fs": "^4.1.2", - "make-dir": "^3.0.0", - "unique-string": "^2.0.0", - "write-file-atomic": "^3.0.0", - "xdg-basedir": "^4.0.0" + "dot-prop": "^6.0.1", + "graceful-fs": "^4.2.6", + "unique-string": "^3.0.0", + "write-file-atomic": "^3.0.3", + "xdg-basedir": "^5.0.1" }, "engines": { - "node": ">=8" + "node": ">=12" + }, + "funding": { + "url": "https://github.com/yeoman/configstore?sponsor=1" } }, "node_modules/configstore/node_modules/dot-prop": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/dot-prop/-/dot-prop-5.3.0.tgz", - "integrity": "sha512-QM8q3zDe58hqUqjraQOmzZ1LIH9SWQJTlEKCH4kJ2oQvLZk7RbQXvtDM2XEq3fwkV9CCvvH4LA0AV+ogFsBM2Q==", + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/dot-prop/-/dot-prop-6.0.1.tgz", + "integrity": "sha512-tE7ztYzXHIeyvc7N+hR3oi7FIbf/NIjVP9hmAt3yMXzrQ072/fpjGLx2GxNxGxUl5V73MEqYzioOMoVhGMJ5cA==", "dev": true, "dependencies": { "is-obj": "^2.0.0" }, "engines": { - "node": ">=8" + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/cosmiconfig": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-7.1.0.tgz", - "integrity": "sha512-AdmX6xUzdNASswsFtmwSt7Vj8po9IuqXm0UXz7QKPuEUmPB4XyjGfaAr2PSuELMwkRMVH1EpIkX5bTZGRB3eCA==", + "version": "8.3.6", + "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-8.3.6.tgz", + "integrity": "sha512-kcZ6+W5QzcJ3P1Mt+83OUv/oHFqZHIx8DuxG6eZ5RGMERoLqp4BuGjhHLYGK+Kf5XVkQvqBSmAy/nGWN3qDgEA==", "dev": true, "dependencies": { - "@types/parse-json": "^4.0.0", - "import-fresh": "^3.2.1", - "parse-json": "^5.0.0", - "path-type": "^4.0.0", - "yaml": "^1.10.0" + "import-fresh": "^3.3.0", + "js-yaml": "^4.1.0", + "parse-json": "^5.2.0", + "path-type": "^4.0.0" }, "engines": { - "node": ">=10" + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/d-fischer" + }, + "peerDependencies": { + "typescript": ">=4.9.5" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/cosmiconfig/node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true + }, + "node_modules/cosmiconfig/node_modules/js-yaml": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", + "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "dev": true, + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" } }, "node_modules/cross-spawn": { @@ -2026,32 +2409,44 @@ } }, "node_modules/crypto-random-string": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/crypto-random-string/-/crypto-random-string-2.0.0.tgz", - "integrity": "sha512-v1plID3y9r/lPhviJ1wrXpLeyUIGAZ2SHNYTEapm7/8A9nLPoyvVp3RK/EPFqn5kEznyWgYZNsRtYYIWbuG8KA==", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/crypto-random-string/-/crypto-random-string-4.0.0.tgz", + "integrity": "sha512-x8dy3RnvYdlUcPOjkEHqozhiwzKNSq7GcPuXFbnyMOCHxX8V3OgIg/pYuabl2sbUPfIJaeAQB7PMOK8DFIdoRA==", "dev": true, + "dependencies": { + "type-fest": "^1.0.1" + }, "engines": { - "node": ">=8" + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/date-fns": { + "node_modules/crypto-random-string/node_modules/type-fest": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-1.4.0.tgz", + "integrity": "sha512-yGSza74xk0UG8k+pLh5oeoYirvIiWo5t0/o3zHHAO2tRDiZcxWP7fywNlXhqb6/r6sWvwi+RsyQMWhVLe4BVuA==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/csstype": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.2.tgz", + "integrity": "sha512-I7K1Uu0MBPzaFKg4nI5Q7Vs2t+3gWWW648spaF+Rg7pI9ds18Ugn+lvg4SHczUdKlHI5LWBXyqfS8+DufyBsgQ==", + "dev": true + }, + "node_modules/date-fns": { "version": "1.30.1", "resolved": "https://registry.npmjs.org/date-fns/-/date-fns-1.30.1.tgz", "integrity": "sha512-hBSVCvSmWC+QypYObzwGOd9wqdDpOt+0wl0KbU+R+uuZBS1jN8VsD1ss3irQDknRj5NvxiTF6oj/nDRnN/UQNw==", "dev": true }, - "node_modules/date-time": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/date-time/-/date-time-3.1.0.tgz", - "integrity": "sha512-uqCUKXE5q1PNBXjPqvwhwJf9SwMoAHBgWJ6DcrnS5o+W2JOiIILl0JEdVD8SGujrNS02GGxgwAg2PN2zONgtjg==", - "dev": true, - "dependencies": { - "time-zone": "^1.0.0" - }, - "engines": { - "node": ">=6" - } - }, "node_modules/debug": { "version": "4.3.4", "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", @@ -2069,50 +2464,31 @@ } } }, - "node_modules/decamelize": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", - "integrity": "sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/decamelize-keys": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/decamelize-keys/-/decamelize-keys-1.1.1.tgz", - "integrity": "sha512-WiPxgEirIV0/eIOMcnFBA3/IJZAZqKnwAwWyvvdi4lsr1WCN22nhdf/3db3DoZcUjTV2SqfzIwNyp6y2xs3nmg==", + "node_modules/decompress-response": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-6.0.0.tgz", + "integrity": "sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==", "dev": true, "dependencies": { - "decamelize": "^1.1.0", - "map-obj": "^1.0.0" + "mimic-response": "^3.1.0" }, "engines": { - "node": ">=0.10.0" + "node": ">=10" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/decamelize-keys/node_modules/map-obj": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/map-obj/-/map-obj-1.0.1.tgz", - "integrity": "sha512-7N/q3lyZ+LVCp7PzuxrJr4KMbBE2hW7BT7YNia330OFxIf4d3r5zVpicP2650l7CPN6RM9zOJRl3NGpqSiw3Eg==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/decompress-response": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-5.0.0.tgz", - "integrity": "sha512-TLZWWybuxWgoW7Lykv+gq9xvzOsUjQ9tF09Tj6NSTYGMTCHNXzrPnD6Hi+TgZq19PyTAGH4Ll/NIM/eTGglnMw==", + "node_modules/decompress-response/node_modules/mimic-response": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-3.1.0.tgz", + "integrity": "sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==", "dev": true, - "dependencies": { - "mimic-response": "^2.0.0" - }, "engines": { "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/deep-eql": { @@ -2142,6 +2518,52 @@ "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", "dev": true }, + "node_modules/default-browser": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/default-browser/-/default-browser-4.0.0.tgz", + "integrity": "sha512-wX5pXO1+BrhMkSbROFsyxUm0i/cJEScyNhA4PPxc41ICuv05ZZB/MX28s8aZx6xjmatvebIapF6hLEKEcpneUA==", + "dev": true, + "dependencies": { + "bundle-name": "^3.0.0", + "default-browser-id": "^3.0.0", + "execa": "^7.1.1", + "titleize": "^3.0.0" + }, + "engines": { + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/default-browser-id": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/default-browser-id/-/default-browser-id-3.0.0.tgz", + "integrity": "sha512-OZ1y3y0SqSICtE8DE4S8YOE9UZOJ8wO16fKWVP5J1Qz42kV9jcnMVFrEE/noXb/ss3Q4pZIH79kxofzyNNtUNA==", + "dev": true, + "dependencies": { + "bplist-parser": "^0.2.0", + "untildify": "^4.0.0" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/defaults": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/defaults/-/defaults-1.0.4.tgz", + "integrity": "sha512-eFuaLoy/Rxalv2kr+lqMlUnrDWV+3j4pljOIJgLIhI058IQfWJ7vXhyEIHu+HtC738klGALYxOKDO0bQP3tg8A==", + "dev": true, + "dependencies": { + "clone": "^1.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/defer-to-connect": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/defer-to-connect/-/defer-to-connect-2.0.1.tgz", @@ -2151,6 +2573,18 @@ "node": ">=10" } }, + "node_modules/define-lazy-prop": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/define-lazy-prop/-/define-lazy-prop-3.0.0.tgz", + "integrity": "sha512-N+MeXYoqr3pOgn8xfyRPREN7gHakLYjhsHhWGT3fWAiL4IkAt0iDw14QiiEm2bE30c5XX5q0FtAA3CK5f9/BUg==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/define-properties": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.2.0.tgz", @@ -2168,27 +2602,79 @@ } }, "node_modules/del": { - "version": "6.1.1", - "resolved": "https://registry.npmjs.org/del/-/del-6.1.1.tgz", - "integrity": "sha512-ua8BhapfP0JUJKC/zV9yHHDW/rDoDxP4Zhn3AkA6/xT6gY7jYXJiaeyBZznYVujhZZET+UgcbZiQ7sN3WqcImg==", + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/del/-/del-7.1.0.tgz", + "integrity": "sha512-v2KyNk7efxhlyHpjEvfyxaAihKKK0nWCuf6ZtqZcFFpQRG0bJ12Qsr0RpvsICMjAAZ8DOVCxrlqpxISlMHC4Kg==", "dev": true, "dependencies": { - "globby": "^11.0.1", - "graceful-fs": "^4.2.4", - "is-glob": "^4.0.1", - "is-path-cwd": "^2.2.0", - "is-path-inside": "^3.0.2", - "p-map": "^4.0.0", + "globby": "^13.1.2", + "graceful-fs": "^4.2.10", + "is-glob": "^4.0.3", + "is-path-cwd": "^3.0.0", + "is-path-inside": "^4.0.0", + "p-map": "^5.5.0", "rimraf": "^3.0.2", - "slash": "^3.0.0" + "slash": "^4.0.0" }, "engines": { - "node": ">=10" + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/del/node_modules/globby": { + "version": "13.2.2", + "resolved": "https://registry.npmjs.org/globby/-/globby-13.2.2.tgz", + "integrity": "sha512-Y1zNGV+pzQdh7H39l9zgB4PJqjRNqydvdYCDG4HFXM4XuvSaQQlEc91IU1yALL8gUTDomgBAfz3XJdmUS+oo0w==", + "dev": true, + "dependencies": { + "dir-glob": "^3.0.1", + "fast-glob": "^3.3.0", + "ignore": "^5.2.4", + "merge2": "^1.4.1", + "slash": "^4.0.0" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/del/node_modules/is-path-inside": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-4.0.0.tgz", + "integrity": "sha512-lJJV/5dYS+RcL8uQdBDW9c9uWFLLBNRyFhnAKXw5tVqLlKZ4RMGZKv+YQ/IA3OhD+RpbJa1LLFM1FQPGyIXvOA==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/del/node_modules/slash": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-4.0.0.tgz", + "integrity": "sha512-3dOsAHXXUkQTpOYcoAxLIorMTp4gIQr5IW3iVb7A7lFIp0VHhnynm9izx6TssdrIcVIESAlVjtnO2K8bg+Coew==", + "dev": true, + "engines": { + "node": ">=12" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/diff-sequences": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-29.6.3.tgz", + "integrity": "sha512-EjePK1srD3P08o2j4f0ExnylqRs5B9tJjcp9t1krH2qRi8CCdsYfwe9JgSLurFBWwq4uOlipzfk5fHNvwFKr8Q==", + "dev": true, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, "node_modules/dir-glob": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz", @@ -2214,24 +2700,36 @@ } }, "node_modules/dot-prop": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/dot-prop/-/dot-prop-6.0.1.tgz", - "integrity": "sha512-tE7ztYzXHIeyvc7N+hR3oi7FIbf/NIjVP9hmAt3yMXzrQ072/fpjGLx2GxNxGxUl5V73MEqYzioOMoVhGMJ5cA==", + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/dot-prop/-/dot-prop-7.2.0.tgz", + "integrity": "sha512-Ol/IPXUARn9CSbkrdV4VJo7uCy1I3VuSiWCaFSg+8BdUOzF9n3jefIpcgAydvUZbTdEBZs2vEiTiS9m61ssiDA==", "dev": true, "dependencies": { - "is-obj": "^2.0.0" + "type-fest": "^2.11.2" }, "engines": { - "node": ">=10" + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/dot-prop/node_modules/type-fest": { + "version": "2.19.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-2.19.0.tgz", + "integrity": "sha512-RAH822pAdBgcNMAfWnCBU3CFZcfZ/i1eZjwFU/dsLKumyuuP3niueg2UAukXYF0E2AAoc82ZSSf9J0WQBinzHA==", + "dev": true, + "engines": { + "node": ">=12.20" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/duplexer3": { - "version": "0.1.5", - "resolved": "https://registry.npmjs.org/duplexer3/-/duplexer3-0.1.5.tgz", - "integrity": "sha512-1A8za6ws41LQgv9HrE/66jyC5yuSjQ3L/KOpFtoBilsAK2iA2wuS5rTt1OCzIvtS2V7nVmedsUU+DGRcjBmOYA==", + "node_modules/eastasianwidth": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", + "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==", "dev": true }, "node_modules/elegant-spinner": { @@ -2361,9 +2859,9 @@ } }, "node_modules/esbuild": { - "version": "0.17.19", - "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.17.19.tgz", - "integrity": "sha512-XQ0jAPFkK/u3LcVRcvVHQcTIqD6E2H1fvZMA5dQPSOWb3suUbWbfbRf94pjc0bNzRYLfIrDRQXr7X+LHIm5oHw==", + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.18.20.tgz", + "integrity": "sha512-ceqxoedUrcayh7Y7ZX6NdbbDzGROiyVBgC4PriJThBKSVPWnnFHZAkfI1lJT8QFkOwH4qOS2SJkS4wvpGl8BpA==", "dev": true, "hasInstallScript": true, "bin": { @@ -2373,37 +2871,37 @@ "node": ">=12" }, "optionalDependencies": { - "@esbuild/android-arm": "0.17.19", - "@esbuild/android-arm64": "0.17.19", - "@esbuild/android-x64": "0.17.19", - "@esbuild/darwin-arm64": "0.17.19", - "@esbuild/darwin-x64": "0.17.19", - "@esbuild/freebsd-arm64": "0.17.19", - "@esbuild/freebsd-x64": "0.17.19", - "@esbuild/linux-arm": "0.17.19", - "@esbuild/linux-arm64": "0.17.19", - "@esbuild/linux-ia32": "0.17.19", - "@esbuild/linux-loong64": "0.17.19", - "@esbuild/linux-mips64el": "0.17.19", - "@esbuild/linux-ppc64": "0.17.19", - "@esbuild/linux-riscv64": "0.17.19", - "@esbuild/linux-s390x": "0.17.19", - "@esbuild/linux-x64": "0.17.19", - "@esbuild/netbsd-x64": "0.17.19", - "@esbuild/openbsd-x64": "0.17.19", - "@esbuild/sunos-x64": "0.17.19", - "@esbuild/win32-arm64": "0.17.19", - "@esbuild/win32-ia32": "0.17.19", - "@esbuild/win32-x64": "0.17.19" + "@esbuild/android-arm": "0.18.20", + "@esbuild/android-arm64": "0.18.20", + "@esbuild/android-x64": "0.18.20", + "@esbuild/darwin-arm64": "0.18.20", + "@esbuild/darwin-x64": "0.18.20", + "@esbuild/freebsd-arm64": "0.18.20", + "@esbuild/freebsd-x64": "0.18.20", + "@esbuild/linux-arm": "0.18.20", + "@esbuild/linux-arm64": "0.18.20", + "@esbuild/linux-ia32": "0.18.20", + "@esbuild/linux-loong64": "0.18.20", + "@esbuild/linux-mips64el": "0.18.20", + "@esbuild/linux-ppc64": "0.18.20", + "@esbuild/linux-riscv64": "0.18.20", + "@esbuild/linux-s390x": "0.18.20", + "@esbuild/linux-x64": "0.18.20", + "@esbuild/netbsd-x64": "0.18.20", + "@esbuild/openbsd-x64": "0.18.20", + "@esbuild/sunos-x64": "0.18.20", + "@esbuild/win32-arm64": "0.18.20", + "@esbuild/win32-ia32": "0.18.20", + "@esbuild/win32-x64": "0.18.20" } }, "node_modules/escape-goat": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/escape-goat/-/escape-goat-3.0.0.tgz", - "integrity": "sha512-w3PwNZJwRxlp47QGzhuEBldEqVHHhh8/tIPcl6ecf2Bou99cdAt0knihBV0Ecc7CGxYduXVBDheH1K2oADRlvw==", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-goat/-/escape-goat-4.0.0.tgz", + "integrity": "sha512-2Sd4ShcWxbx6OY1IHyla/CVNwvg7XwZVoXZHcSu9w9SReNP1EzzD5T8NWKIR38fIqEns9kDWKUQTXXAmlDrdPg==", "dev": true, "engines": { - "node": ">=10" + "node": ">=12" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" @@ -2422,27 +2920,27 @@ } }, "node_modules/eslint": { - "version": "8.39.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.39.0.tgz", - "integrity": "sha512-mwiok6cy7KTW7rBpo05k6+p4YVZByLNjAZ/ACB9DRCu4YDRwjXI01tWHp6KAUWelsBetTxKK/2sHB0vdS8Z2Og==", + "version": "8.51.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.51.0.tgz", + "integrity": "sha512-2WuxRZBrlwnXi+/vFSJyjMqrNjtJqiasMzehF0shoLaW7DzS3/9Yvrmq5JiT66+pNjiX4UBnLDiKHcWAr/OInA==", "dev": true, "dependencies": { "@eslint-community/eslint-utils": "^4.2.0", - "@eslint-community/regexpp": "^4.4.0", - "@eslint/eslintrc": "^2.0.2", - "@eslint/js": "8.39.0", - "@humanwhocodes/config-array": "^0.11.8", + "@eslint-community/regexpp": "^4.6.1", + "@eslint/eslintrc": "^2.1.2", + "@eslint/js": "8.51.0", + "@humanwhocodes/config-array": "^0.11.11", "@humanwhocodes/module-importer": "^1.0.1", "@nodelib/fs.walk": "^1.2.8", - "ajv": "^6.10.0", + "ajv": "^6.12.4", "chalk": "^4.0.0", "cross-spawn": "^7.0.2", "debug": "^4.3.2", "doctrine": "^3.0.0", "escape-string-regexp": "^4.0.0", - "eslint-scope": "^7.2.0", - "eslint-visitor-keys": "^3.4.0", - "espree": "^9.5.1", + "eslint-scope": "^7.2.2", + "eslint-visitor-keys": "^3.4.3", + "espree": "^9.6.1", "esquery": "^1.4.2", "esutils": "^2.0.2", "fast-deep-equal": "^3.1.3", @@ -2450,22 +2948,19 @@ "find-up": "^5.0.0", "glob-parent": "^6.0.2", "globals": "^13.19.0", - "grapheme-splitter": "^1.0.4", + "graphemer": "^1.4.0", "ignore": "^5.2.0", - "import-fresh": "^3.0.0", "imurmurhash": "^0.1.4", "is-glob": "^4.0.0", "is-path-inside": "^3.0.3", - "js-sdsl": "^4.1.4", "js-yaml": "^4.1.0", "json-stable-stringify-without-jsonify": "^1.0.1", "levn": "^0.4.1", "lodash.merge": "^4.6.2", "minimatch": "^3.1.2", "natural-compare": "^1.4.0", - "optionator": "^0.9.1", + "optionator": "^0.9.3", "strip-ansi": "^6.0.1", - "strip-json-comments": "^3.1.0", "text-table": "^0.2.0" }, "bin": { @@ -2479,37 +2974,24 @@ } }, "node_modules/eslint-plugin-qwik": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/eslint-plugin-qwik/-/eslint-plugin-qwik-1.1.4.tgz", - "integrity": "sha512-RHeH/OwYu6iLxVxyuHshCRiIetmrsEo585yGC8ZRTuIa25zCUbD54w/KumXU6u4W5t00NxxrDwt3+ZH206hQEA==", + "version": "1.2.18", + "resolved": "https://registry.npmjs.org/eslint-plugin-qwik/-/eslint-plugin-qwik-1.2.18.tgz", + "integrity": "sha512-B4c8bo0x6QyhCgGLkSn3/fqLreCnHeLv6UsVlZfW41VpWc4nPiYQqURp+ChY3TiPy2SXJEKtn4F4bBrn3sn4Fw==", "dev": true, "dependencies": { - "jsx-ast-utils": "^3.3.3" + "jsx-ast-utils": "^3.3.5" }, "engines": { - "node": ">=16" + "node": ">=16.8.0 <18.0.0 || >=18.11" }, "peerDependencies": { - "eslint": ">= 8" - } - }, - "node_modules/eslint-scope": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz", - "integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==", - "dev": true, - "dependencies": { - "esrecurse": "^4.3.0", - "estraverse": "^4.1.1" - }, - "engines": { - "node": ">=8.0.0" + "eslint": "^8.45.0" } }, "node_modules/eslint-visitor-keys": { - "version": "3.4.2", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.2.tgz", - "integrity": "sha512-8drBzUEyZ2llkpCA67iYrgEssKDUu68V8ChqqOfFupIaG/LCVPUT+CoGJpT77zJprs4T/W7p07LP7zAIMuweVw==", + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", + "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", "dev": true, "engines": { "node": "^12.22.0 || ^14.17.0 || >=16.0.0" @@ -2633,15 +3115,6 @@ "node": ">=4.0" } }, - "node_modules/estraverse": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", - "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", - "dev": true, - "engines": { - "node": ">=4.0" - } - }, "node_modules/estree-walker": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-0.6.1.tgz", @@ -2658,28 +3131,67 @@ } }, "node_modules/execa": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz", - "integrity": "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==", + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/execa/-/execa-7.2.0.tgz", + "integrity": "sha512-UduyVP7TLB5IcAQl+OzLyLcS/l32W/GLg+AhHJ+ow40FOk2U3SAllPwR44v4vmdFwIWqpdwxxpQbF1n5ta9seA==", "dev": true, "dependencies": { "cross-spawn": "^7.0.3", - "get-stream": "^6.0.0", - "human-signals": "^2.1.0", - "is-stream": "^2.0.0", + "get-stream": "^6.0.1", + "human-signals": "^4.3.0", + "is-stream": "^3.0.0", "merge-stream": "^2.0.0", - "npm-run-path": "^4.0.1", - "onetime": "^5.1.2", - "signal-exit": "^3.0.3", - "strip-final-newline": "^2.0.0" + "npm-run-path": "^5.1.0", + "onetime": "^6.0.0", + "signal-exit": "^3.0.7", + "strip-final-newline": "^3.0.0" }, "engines": { - "node": ">=10" + "node": "^14.18.0 || ^16.14.0 || >=18.0.0" }, "funding": { "url": "https://github.com/sindresorhus/execa?sponsor=1" } }, + "node_modules/execa/node_modules/mimic-fn": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-4.0.0.tgz", + "integrity": "sha512-vqiC06CuhBTUdZH+RYl8sFrL096vA45Ok5ISO6sE/Mr1jRbGH4Csnhi8f3wKVl7x8mO4Au7Ir9D3Oyv1VYMFJw==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/execa/node_modules/onetime": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-6.0.0.tgz", + "integrity": "sha512-1FlR+gjXK7X+AsAHso35MnyN5KqGwJRi/31ft6x0M194ht7S+rWAvd7PHss9xSKMzE0asv1pyIHaJYq+BbacAQ==", + "dev": true, + "dependencies": { + "mimic-fn": "^4.0.0" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/exit-hook": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/exit-hook/-/exit-hook-3.2.0.tgz", + "integrity": "sha512-aIQN7Q04HGAV/I5BszisuHTZHXNoC23WtLkxdCLuYZMdWviRD0TMIt2bnUBi9MrHaF/hH8b3gwG9iaAUHKnJGA==", + "dev": true, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/external-editor": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/external-editor/-/external-editor-3.1.0.tgz", @@ -2700,16 +3212,10 @@ "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", "dev": true }, - "node_modules/fast-diff": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/fast-diff/-/fast-diff-1.3.0.tgz", - "integrity": "sha512-VxPP4NqbUjj6MaAOafWeUn2cXWLcCtljklUtZf0Ind4XQ+QPtmA0b18zZy0jIQx+ExRVCR/ZQpBmik5lXshNsw==", - "dev": true - }, "node_modules/fast-glob": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.1.tgz", - "integrity": "sha512-kNFPyjhh5cKjrUltxs+wFx+ZkbRaxxmZ+X0ZU31SOsxCEtP9VPgtq2teZw1DebupL5GmDaNQ6yKMMVcM41iqDg==", + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.2.tgz", + "integrity": "sha512-oX2ruAFQwf/Orj8m737Y5adxDQO0LAB7/S5MnxCdTNDd4p6BsyIVsv9JQsATbTSq8KHRpLwIHbVlUNatxd+1Ow==", "dev": true, "dependencies": { "@nodelib/fs.stat": "^2.0.2", @@ -2847,6 +3353,15 @@ "is-callable": "^1.1.3" } }, + "node_modules/form-data-encoder": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/form-data-encoder/-/form-data-encoder-2.1.4.tgz", + "integrity": "sha512-yDYSgNMraqvnxiEXO4hi88+YZxaHC6QKzb5N84iRCTDeRO7ZALpir/lVmf/uXUhnwUr2O4HU8s/n6x+yNjQkHw==", + "dev": true, + "engines": { + "node": ">= 14.17" + } + }, "node_modules/fs-extra": { "version": "7.0.1", "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-7.0.1.tgz", @@ -2915,9 +3430,9 @@ } }, "node_modules/get-func-name": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/get-func-name/-/get-func-name-2.0.0.tgz", - "integrity": "sha512-Hm0ixYtaSZ/V7C8FJrtZIuBBI+iSgL+1Aq82zSu8VQNB4S3Gk8e7Qs3VwBDJAhmRZcFqkl3tQu36g/Foh5I5ig==", + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/get-func-name/-/get-func-name-2.0.2.tgz", + "integrity": "sha512-8vXOvuE167CtIc3OyItco7N/dpRtBbYOsPsXCz7X/PMnlGjYjSGuZJgM1Y7mmew7BKf9BqvLX2tnOVy1BBUsxQ==", "dev": true, "engines": { "node": "*" @@ -3005,24 +3520,24 @@ } }, "node_modules/global-dirs": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/global-dirs/-/global-dirs-2.1.0.tgz", - "integrity": "sha512-MG6kdOUh/xBnyo9cJFeIKkLEc1AyFq42QTU4XiX51i2NEdxLxLWXIjEjmqKeSuKR7pAZjTqUVoT2b2huxVLgYQ==", + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/global-dirs/-/global-dirs-3.0.1.tgz", + "integrity": "sha512-NBcGGFbBA9s1VzD41QXDG+3++t9Mn5t1FpLdhESY6oKY4gYTFpX4wO3sqGUa0Srjtbfj3szX0RnemmrVRUdULA==", "dev": true, "dependencies": { - "ini": "1.3.7" + "ini": "2.0.0" }, "engines": { - "node": ">=8" + "node": ">=10" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/globals": { - "version": "13.20.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-13.20.0.tgz", - "integrity": "sha512-Qg5QtVkCy/kv3FUSlu4ukeZDVf9ee0iXLAUYX13gbR17bnejFTzr4iS9bY7kwCf1NztRNm1t91fjOiyx4CSwPQ==", + "version": "13.23.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-13.23.0.tgz", + "integrity": "sha512-XAmF0RjlrjY23MA51q3HltdlGxUpXPvg0GioKiD9X6HD28iMjo2dKC8Vqwm7lne4GNr78+RHTfliktR6ZH09wA==", "dev": true, "dependencies": { "type-fest": "^0.20.2" @@ -3082,82 +3597,42 @@ } }, "node_modules/got": { - "version": "10.7.0", - "resolved": "https://registry.npmjs.org/got/-/got-10.7.0.tgz", - "integrity": "sha512-aWTDeNw9g+XqEZNcTjMMZSy7B7yE9toWOFYip7ofFTLleJhvZwUxxTxkTpKvF+p1SAA4VHmuEy7PiHTHyq8tJg==", + "version": "11.8.6", + "resolved": "https://registry.npmjs.org/got/-/got-11.8.6.tgz", + "integrity": "sha512-6tfZ91bOr7bOXnK7PRDCGBLa1H4U080YHNaAQ2KsMGlLEzRbk44nsZF2E1IeRc3vtJHPVbKCYgdFbaGO2ljd8g==", "dev": true, "dependencies": { - "@sindresorhus/is": "^2.0.0", - "@szmarczak/http-timer": "^4.0.0", + "@sindresorhus/is": "^4.0.0", + "@szmarczak/http-timer": "^4.0.5", "@types/cacheable-request": "^6.0.1", - "cacheable-lookup": "^2.0.0", - "cacheable-request": "^7.0.1", - "decompress-response": "^5.0.0", - "duplexer3": "^0.1.4", - "get-stream": "^5.0.0", + "@types/responselike": "^1.0.0", + "cacheable-lookup": "^5.0.3", + "cacheable-request": "^7.0.2", + "decompress-response": "^6.0.0", + "http2-wrapper": "^1.0.0-beta.5.2", "lowercase-keys": "^2.0.0", - "mimic-response": "^2.1.0", "p-cancelable": "^2.0.0", - "p-event": "^4.0.0", - "responselike": "^2.0.0", - "to-readable-stream": "^2.0.0", - "type-fest": "^0.10.0" + "responselike": "^2.0.0" }, "engines": { - "node": ">=10" + "node": ">=10.19.0" }, "funding": { "url": "https://github.com/sindresorhus/got?sponsor=1" } }, - "node_modules/got/node_modules/get-stream": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.2.0.tgz", - "integrity": "sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==", - "dev": true, - "dependencies": { - "pump": "^3.0.0" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/got/node_modules/type-fest": { - "version": "0.10.0", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.10.0.tgz", - "integrity": "sha512-EUV9jo4sffrwlg8s0zDhP0T2WD3pru5Xi0+HTE3zTUmBaZNhfkite9PdSJwdXLwPVW0jnAHT56pZHIOYckPEiw==", - "dev": true, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/graceful-fs": { "version": "4.2.11", "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", "dev": true }, - "node_modules/grapheme-splitter": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/grapheme-splitter/-/grapheme-splitter-1.0.4.tgz", - "integrity": "sha512-bzh50DW9kTPM00T8y4o8vQg89Di9oLJVLW/KaOGIXJWP/iqCN6WKYkbNOF04vFLJhwcpYUh9ydh/+5vpOqV4YQ==", + "node_modules/graphemer": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz", + "integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==", "dev": true }, - "node_modules/hard-rejection": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/hard-rejection/-/hard-rejection-2.1.0.tgz", - "integrity": "sha512-VIZB+ibDhx7ObhAe7OVtoEbuP4h/MuOTHJ+J8h/eBXotJYl0fBgR72xDFCKgIh22OJZIOVNxBMWuhAr10r8HdA==", - "dev": true, - "engines": { - "node": ">=6" - } - }, "node_modules/has": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", @@ -3261,24 +3736,36 @@ } }, "node_modules/has-yarn": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/has-yarn/-/has-yarn-2.1.0.tgz", - "integrity": "sha512-UqBRqi4ju7T+TqGNdqAO0PaSVGsDGJUBQvk9eUWNGRY1CFGDzYhLWoM7JQEemnlvVcv/YEmc2wNW8BC24EnUsw==", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-yarn/-/has-yarn-3.0.0.tgz", + "integrity": "sha512-IrsVwUHhEULx3R8f/aA8AHuEzAorplsab/v8HBzEiIukwq5i/EC+xmOW+HfP1OaDP+2JkgT1yILHN2O3UFIbcA==", "dev": true, "engines": { - "node": ">=8" - } + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } }, "node_modules/hosted-git-info": { - "version": "3.0.8", - "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-3.0.8.tgz", - "integrity": "sha512-aXpmwoOhRBrw6X3j0h5RloK4x1OzsxMPyxqIHyNfSe2pypkVTZFpEiRoSipPEPlMrh0HW/XsjkJ5WgnCirpNUw==", + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-6.1.1.tgz", + "integrity": "sha512-r0EI+HBMcXadMrugk0GCQ+6BQV39PiWAZVfq7oIckeGiN7sjRGyQxPdft3nQekFTCQbYxLBH+/axZMeH8UX6+w==", "dev": true, "dependencies": { - "lru-cache": "^6.0.0" + "lru-cache": "^7.5.1" }, "engines": { - "node": ">=10" + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/hosted-git-info/node_modules/lru-cache": { + "version": "7.18.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-7.18.3.tgz", + "integrity": "sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA==", + "dev": true, + "engines": { + "node": ">=12" } }, "node_modules/http-cache-semantics": { @@ -3287,13 +3774,26 @@ "integrity": "sha512-er295DKPVsV82j5kw1Gjt+ADA/XYHsajl82cGNQG2eyoPkvgUhX+nDIyelzhIWbbsXP39EHcI6l5tYs2FYqYXQ==", "dev": true }, + "node_modules/http2-wrapper": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/http2-wrapper/-/http2-wrapper-1.0.3.tgz", + "integrity": "sha512-V+23sDMr12Wnz7iTcDeJr3O6AIxlnvT/bmaAAAP/Xda35C90p9599p0F1eHR/N1KILWSoWVAiOMFjBBXaXSMxg==", + "dev": true, + "dependencies": { + "quick-lru": "^5.1.1", + "resolve-alpn": "^1.0.0" + }, + "engines": { + "node": ">=10.19.0" + } + }, "node_modules/human-signals": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz", - "integrity": "sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==", + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-4.3.1.tgz", + "integrity": "sha512-nZXjEF2nbo7lIw3mgYjItAfgQXog3OjJogSbKa2CQIIvSGWcKgeJnQlNXip6NglNzYH45nSRiEVimMvYL8DDqQ==", "dev": true, "engines": { - "node": ">=10.17.0" + "node": ">=14.18.0" } }, "node_modules/iconv-lite": { @@ -3308,6 +3808,26 @@ "node": ">=0.10.0" } }, + "node_modules/ieee754": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", + "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, "node_modules/ignore": { "version": "5.2.4", "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.2.4.tgz", @@ -3318,12 +3838,39 @@ } }, "node_modules/ignore-walk": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/ignore-walk/-/ignore-walk-3.0.4.tgz", - "integrity": "sha512-PY6Ii8o1jMRA1z4F2hRkH/xN59ox43DavKvD3oDpfurRlOJyAHpifIwpbdv1n4jt4ov0jSpw3kQ4GhJnpBL6WQ==", + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/ignore-walk/-/ignore-walk-6.0.3.tgz", + "integrity": "sha512-C7FfFoTA+bI10qfeydT8aZbvr91vAEU+2W5BZUlzPec47oNb07SsOfwYrtxuvOYdUApPP/Qlh4DtAO51Ekk2QA==", + "dev": true, + "dependencies": { + "minimatch": "^9.0.0" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/ignore-walk/node_modules/brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/ignore-walk/node_modules/minimatch": { + "version": "9.0.3", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.3.tgz", + "integrity": "sha512-RHiac9mvaRw0x3AYRgDC1CxAP7HTcNrrECeA8YYJeWnpo+2Q5CegtZjaotWTWxDG3UeGA1coE05iH1mPjT/2mg==", "dev": true, "dependencies": { - "minimatch": "^3.0.4" + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" } }, "node_modules/import-fresh": { @@ -3469,10 +4016,13 @@ "dev": true }, "node_modules/ini": { - "version": "1.3.7", - "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.7.tgz", - "integrity": "sha512-iKpRpXP+CrP2jyrxvg1kMUpXDyRUFDWurxbnVT1vQPx+Wz9uCYsMIqYuSBLV+PAaZG/d7kRLKRFc9oDMsH+mFQ==", - "dev": true + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ini/-/ini-2.0.0.tgz", + "integrity": "sha512-7PnF4oN3CvZF23ADhA5wRaYEQpJ8qygSkbtTXWBeXWXmEVRXK+1ITciHWwHhsjv1TmW0MgacIv6hEi5pX5NQdA==", + "dev": true, + "engines": { + "node": ">=10" + } }, "node_modules/inquirer": { "version": "7.3.3", @@ -3822,12 +4372,12 @@ } }, "node_modules/is-ci": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/is-ci/-/is-ci-2.0.0.tgz", - "integrity": "sha512-YfJT7rkpQB0updsdHLGWrvhBJfcfzNNawYDNIyQXJz0IViGf75O8EBPKSdvw2rF+LGCsX4FZ8tcr3b19LcZq4w==", + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/is-ci/-/is-ci-3.0.1.tgz", + "integrity": "sha512-ZYvCgrefwqoQ6yTyYUbQu64HsITZ3NfKX1lzaEYdkTDcfKzzCI/wthRRYKkdjHKFVgNiXKAKm65Zo1pk2as/QQ==", "dev": true, "dependencies": { - "ci-info": "^2.0.0" + "ci-info": "^3.2.0" }, "bin": { "is-ci": "bin.js" @@ -3861,15 +4411,15 @@ } }, "node_modules/is-docker": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-2.2.1.tgz", - "integrity": "sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ==", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-3.0.0.tgz", + "integrity": "sha512-eljcgEDlEns/7AXFosB5K/2nCM4P7FQPkGc/DWLy5rmFEWvZayGrik1d9/QIY5nJ4f9YsVvBkA6kJpHn9rISdQ==", "dev": true, "bin": { "is-docker": "cli.js" }, "engines": { - "node": ">=8" + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" @@ -3905,29 +4455,61 @@ "node": ">=0.10.0" } }, + "node_modules/is-inside-container": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-inside-container/-/is-inside-container-1.0.0.tgz", + "integrity": "sha512-KIYLCCJghfHZxqjYBE7rEy0OBuTd5xCHS7tHVgvCLkx7StIoaxwNW3hCALgEUjFfeRk+MG/Qxmp/vtETEF3tRA==", + "dev": true, + "dependencies": { + "is-docker": "^3.0.0" + }, + "bin": { + "is-inside-container": "cli.js" + }, + "engines": { + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/is-installed-globally": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/is-installed-globally/-/is-installed-globally-0.3.2.tgz", - "integrity": "sha512-wZ8x1js7Ia0kecP/CHM/3ABkAmujX7WPvQk6uu3Fly/Mk44pySulQpnHG46OMjHGXApINnV4QhY3SWnECO2z5g==", + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/is-installed-globally/-/is-installed-globally-0.4.0.tgz", + "integrity": "sha512-iwGqO3J21aaSkC7jWnHP/difazwS7SFeIqxv6wEtLU8Y5KlzFTjyqcSIT0d8s4+dDhKytsk9PJZ2BkS5eZwQRQ==", "dev": true, "dependencies": { - "global-dirs": "^2.0.1", - "is-path-inside": "^3.0.1" + "global-dirs": "^3.0.0", + "is-path-inside": "^3.0.2" }, "engines": { - "node": ">=8" + "node": ">=10" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/is-interactive": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-interactive/-/is-interactive-1.0.0.tgz", - "integrity": "sha512-2HvIEKRoqS62guEC+qBjpvRubdX910WCMuJTZ+I9yvqKU2/12eSL549HMwtabb4oupdj2sMP50k+XJfB/8JE6w==", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-interactive/-/is-interactive-2.0.0.tgz", + "integrity": "sha512-qP1vozQRI+BMOPcjFzrjXuQvdak2pHNUMZoeG2eRbiSqyvbEf/wQtEOTOX1guk6E3t36RkaqiSt8A/6YElNxLQ==", "dev": true, "engines": { - "node": ">=8" + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-name-taken": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-name-taken/-/is-name-taken-2.0.0.tgz", + "integrity": "sha512-W+FUWF5g7ONVJTx3rldZeVizmPzrMMUdscpSQ96vyYerx+4b2NcqaujLJJDWruGzE0FjzGZO9RFIipOGxx/WIw==", + "dev": true, + "dependencies": { + "all-package-names": "^2.0.2", + "package-name-conflict": "^1.0.3", + "validate-npm-package-name": "^3.0.0" } }, "node_modules/is-negative-zero": { @@ -3943,12 +4525,12 @@ } }, "node_modules/is-npm": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/is-npm/-/is-npm-5.0.0.tgz", - "integrity": "sha512-WW/rQLOazUq+ST/bCAVBp/2oMERWLsR7OrKyt052dNDk4DHcDE0/7QSXITlmi+VBcV13DfIbysG3tZJm5RfdBA==", + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/is-npm/-/is-npm-6.0.0.tgz", + "integrity": "sha512-JEjxbSmtPSt1c8XTkVrlujcXdKV1/tvuQ7GwKcAlyiVLeYFQ2VHat8xfrDJsIkhCdF/tZ7CiIR3sy141c6+gPQ==", "dev": true, "engines": { - "node": ">=10" + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" @@ -4009,12 +4591,15 @@ } }, "node_modules/is-path-cwd": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/is-path-cwd/-/is-path-cwd-2.2.0.tgz", - "integrity": "sha512-w942bTcih8fdJPJmQHFzkS76NEP8Kzzvmw92cXsazb8intwLqPibPPdXf4ANdKV3rYMuuQYGIWtvz9JilB3NFQ==", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-path-cwd/-/is-path-cwd-3.0.0.tgz", + "integrity": "sha512-kyiNFFLU0Ampr6SDZitD/DwUo4Zs1nSdnygUBqsu3LooL00Qvb5j+UnvApUn/TTj1J3OuE6BTdQ5rudKmU2ZaA==", "dev": true, "engines": { - "node": ">=6" + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/is-path-inside": { @@ -4026,15 +4611,6 @@ "node": ">=8" } }, - "node_modules/is-plain-obj": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-1.1.0.tgz", - "integrity": "sha512-yvkRyxmFKEOQ4pNXCmJG5AEQNlXJS5LaONXo5/cLdTZdWvsZ1ioJEonLGAosKlMWE8lwUy/bJzMjcw8az73+Fg==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/is-promise": { "version": "2.2.2", "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-2.2.2.tgz", @@ -4058,15 +4634,18 @@ } }, "node_modules/is-scoped": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/is-scoped/-/is-scoped-2.1.0.tgz", - "integrity": "sha512-Cv4OpPTHAK9kHYzkzCrof3VJh7H/PrG2MBUMvvJebaaUMbqhm0YAtXnvh0I3Hnj2tMZWwrRROWLSgfJrKqWmlQ==", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-scoped/-/is-scoped-3.0.0.tgz", + "integrity": "sha512-ezxLUq30kiTvP0w/5n9tj4qTOKlrA07Oty1hwTQ+lcqw11x6uc8sp7VRb2OVGRzKfCHZ2A22T5Zsau/Q2Akb0g==", "dev": true, "dependencies": { - "scoped-regex": "^2.0.0" + "scoped-regex": "^3.0.0" }, "engines": { - "node": ">=8" + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/is-shared-array-buffer": { @@ -4082,12 +4661,12 @@ } }, "node_modules/is-stream": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", - "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-3.0.0.tgz", + "integrity": "sha512-LnQR4bZ9IADDRSkvpqMGvt/tEJWclzklNgSw48V5EAaAeDd6qGvN8ei6k5p0tvxSR171VmGyHuTiAOfxAbr8kA==", "dev": true, "engines": { - "node": ">=8" + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" @@ -4145,24 +4724,24 @@ "dev": true }, "node_modules/is-unicode-supported": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz", - "integrity": "sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==", + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-1.3.0.tgz", + "integrity": "sha512-43r2mRvz+8JRIKnWJ+3j8JtjRKZ6GmjzfaE/qiBJnikNnYv/6bagRJ1kUhNk8R5EX/GkobD+r+sfxCPJsiKBLQ==", "dev": true, "engines": { - "node": ">=10" + "node": ">=12" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/is-url-superb": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/is-url-superb/-/is-url-superb-4.0.0.tgz", - "integrity": "sha512-GI+WjezhPPcbM+tqE9LnmsY5qqjwHzTvjJ36wxYX5ujNXefSUJ/T17r5bqDV8yLhcgB59KTPNOc9O9cmHTPWsA==", + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/is-url-superb/-/is-url-superb-6.1.0.tgz", + "integrity": "sha512-LXdhGlYqUPdvEyIhWPEEwYYK3yrUiPcBjmFGlZNv1u5GtIL5qQRf7ddDyPNAvsMFqdzS923FROpTQU97tLe3JQ==", "dev": true, "engines": { - "node": ">=10" + "node": ">=12" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" @@ -4192,11 +4771,29 @@ "node": ">=8" } }, + "node_modules/is-wsl/node_modules/is-docker": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-2.2.1.tgz", + "integrity": "sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ==", + "dev": true, + "bin": { + "is-docker": "cli.js" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/is-yarn-global": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/is-yarn-global/-/is-yarn-global-0.3.0.tgz", - "integrity": "sha512-VjSeb/lHmkoyd8ryPVIKvOCn4D1koMqY+vqyjjUfc3xyKtP4dYOxM44sZrnqQSzSds3xyOrUTLTC9LVCVgLngw==", - "dev": true + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/is-yarn-global/-/is-yarn-global-0.4.1.tgz", + "integrity": "sha512-/kppl+R+LO5VmhYSEWARUFjodS25D68gvj8W7z0I7OWhUla5xWu8KL6CtB2V0R6yqhnRgbcaREMr4EEM6htLPQ==", + "dev": true, + "engines": { + "node": ">=12" + } }, "node_modules/isarray": { "version": "2.0.5", @@ -4211,12 +4808,15 @@ "dev": true }, "node_modules/issue-regex": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/issue-regex/-/issue-regex-3.1.0.tgz", - "integrity": "sha512-0RHjbtw9QXeSYnIEY5Yrp2QZrdtz21xBDV9C/GIlY2POmgoS6a7qjkYS5siRKXScnuAj5/SPv1C3YForNCHTJA==", + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/issue-regex/-/issue-regex-4.1.0.tgz", + "integrity": "sha512-X3HBmm7+Th+l4/kMtqwcHHgELD0Lfl0Ina6S3+grr+mKmTxsrM84NAO1UuRPIxIbGLIl3TCEu45S1kdu21HYbQ==", "dev": true, "engines": { - "node": ">=10" + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/jju": { @@ -4225,25 +4825,6 @@ "integrity": "sha512-8wb9Yw966OSxApiCt0K3yNJL8pnNeIv+OEq2YMidz4FKP6nonSRoOXc80iXY4JaN2FC11B9qsNmDsm+ZOfMROA==", "dev": true }, - "node_modules/js-sdsl": { - "version": "4.4.2", - "resolved": "https://registry.npmjs.org/js-sdsl/-/js-sdsl-4.4.2.tgz", - "integrity": "sha512-dwXFwByc/ajSV6m5bcKAPwe4yDDF6D614pxmIi5odytzxRlwqF6nwoiCek80Ixc7Cvma5awClxrzFtxCQvcM8w==", - "dev": true, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/js-sdsl" - } - }, - "node_modules/js-string-escape": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/js-string-escape/-/js-string-escape-1.0.1.tgz", - "integrity": "sha512-Smw4xcfIQ5LVjAOuJCvN/zIodzA/BBSsluuoSykP+lUvScIi4U6RJLfwHet5cxFnCswUjISV8oAXaqaJDY3chg==", - "dev": true, - "engines": { - "node": ">= 0.8" - } - }, "node_modules/js-tokens": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", @@ -4318,33 +4899,27 @@ } }, "node_modules/keyv": { - "version": "4.5.3", - "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.3.tgz", - "integrity": "sha512-QCiSav9WaX1PgETJ+SpNnx2PRRapJ/oRSXM4VO5OGYGSjrxbKPVFVhB3l2OCbLCk329N8qyAtsJjSjvVBWzEug==", + "version": "4.5.4", + "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", + "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", "dev": true, "dependencies": { "json-buffer": "3.0.1" } }, - "node_modules/kind-of": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", - "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/latest-version": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/latest-version/-/latest-version-5.1.0.tgz", - "integrity": "sha512-weT+r0kTkRQdCdYCNtkMwWXQTMEswKrFBkm4ckQOMVhhqhIMI1UT2hMj+1iigIhgSZm5gTmrRXBNoGUgaTY1xA==", + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/latest-version/-/latest-version-7.0.0.tgz", + "integrity": "sha512-KvNT4XqAMzdcL6ka6Tl3i2lYeFDgXNCuIX+xNx6ZMVR1dFq+idXd9FLKNMOIx0t9mJ9/HudyX4oZWXZQ0UJHeg==", "dev": true, "dependencies": { - "package-json": "^6.3.0" + "package-json": "^8.1.0" }, "engines": { - "node": ">=8" + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/levn": { @@ -4741,21 +5316,33 @@ "dev": true }, "node_modules/log-symbols": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-4.1.0.tgz", - "integrity": "sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg==", + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-5.1.0.tgz", + "integrity": "sha512-l0x2DvrW294C9uDCoQe1VSU4gf529FkSZ6leBl4TiqZH/e+0R7hSfHQBNut2mNygDgHwvYHfFLn6Oxb3VWj2rA==", "dev": true, "dependencies": { - "chalk": "^4.1.0", - "is-unicode-supported": "^0.1.0" + "chalk": "^5.0.0", + "is-unicode-supported": "^1.1.0" }, "engines": { - "node": ">=10" + "node": ">=12" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/log-symbols/node_modules/chalk": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.3.0.tgz", + "integrity": "sha512-dLitG79d+GV1Nb/VYcCDFivJeK1hiukt9QjRNVOsUtTy1rR1YJsmpGGTZ3qJos+uw7WmWF4wUwBd9jxjocFC2w==", + "dev": true, + "engines": { + "node": "^12.17.0 || ^14.13 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, "node_modules/log-update": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/log-update/-/log-update-2.3.0.tgz", @@ -4826,12 +5413,12 @@ } }, "node_modules/loupe": { - "version": "2.3.6", - "resolved": "https://registry.npmjs.org/loupe/-/loupe-2.3.6.tgz", - "integrity": "sha512-RaPMZKiMy8/JruncMU5Bt6na1eftNoo++R4Y+N2FrxkDVTrGvcyzFTsaGif4QTeKESheMGegbhw6iUAq+5A8zA==", + "version": "2.3.7", + "resolved": "https://registry.npmjs.org/loupe/-/loupe-2.3.7.tgz", + "integrity": "sha512-zSMINGVYkdpYSOBmLi0D1Uo7JU9nVdQKrHxC8eYlV+9YKK9WePqAlL7lSlorG/U2Fw1w0hTBmaa/jrQ3UbPHtA==", "dev": true, "dependencies": { - "get-func-name": "^2.0.0" + "get-func-name": "^2.0.1" } }, "node_modules/lowercase-keys": { @@ -4864,156 +5451,59 @@ "sourcemap-codec": "^1.4.8" } }, - "node_modules/make-dir": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz", - "integrity": "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==", + "node_modules/meow": { + "version": "12.1.1", + "resolved": "https://registry.npmjs.org/meow/-/meow-12.1.1.tgz", + "integrity": "sha512-BhXM0Au22RwUneMPwSCnyhTOizdWoIEPU9sp0Aqa1PnDMR5Wv2FGXYDjuzJEIX+Eo2Rb8xuYe5jrnm5QowQFkw==", "dev": true, - "dependencies": { - "semver": "^6.0.0" - }, "engines": { - "node": ">=8" + "node": ">=16.10" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/make-dir/node_modules/semver": { - "version": "6.3.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", - "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "node_modules/merge-stream": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", + "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==", + "dev": true + }, + "node_modules/merge2": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", + "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", "dev": true, - "bin": { - "semver": "bin/semver.js" + "engines": { + "node": ">= 8" } }, - "node_modules/map-age-cleaner": { - "version": "0.1.3", - "resolved": "https://registry.npmjs.org/map-age-cleaner/-/map-age-cleaner-0.1.3.tgz", - "integrity": "sha512-bJzx6nMoP6PDLPBFmg7+xRKeFZvFboMrGlxmNj9ClvX53KrmvM5bXFXEWjbz4cz1AFn+jWJ9z/DJSz7hrs0w3w==", + "node_modules/micromatch": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.5.tgz", + "integrity": "sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==", "dev": true, "dependencies": { - "p-defer": "^1.0.0" + "braces": "^3.0.2", + "picomatch": "^2.3.1" }, "engines": { - "node": ">=6" + "node": ">=8.6" } }, - "node_modules/map-obj": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/map-obj/-/map-obj-4.3.0.tgz", - "integrity": "sha512-hdN1wVrZbb29eBGiGjJbeP8JbKjq1urkHJ/LIP/NY48MZ1QVXUsQBV1G1zvYFHn1XE06cwjBsOI2K3Ulnj1YXQ==", + "node_modules/mimic-fn": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", + "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", "dev": true, "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/md5-hex": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/md5-hex/-/md5-hex-3.0.1.tgz", - "integrity": "sha512-BUiRtTtV39LIJwinWBjqVsU9xhdnz7/i889V859IBFpuqGAj6LuOvHv5XLbgZ2R7ptJoJaEcxkv88/h25T7Ciw==", - "dev": true, - "dependencies": { - "blueimp-md5": "^2.10.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/meow": { - "version": "8.1.2", - "resolved": "https://registry.npmjs.org/meow/-/meow-8.1.2.tgz", - "integrity": "sha512-r85E3NdZ+mpYk1C6RjPFEMSE+s1iZMuHtsHAqY0DT3jZczl0diWUZ8g6oU7h0M9cD2EL+PzaYghhCLzR0ZNn5Q==", - "dev": true, - "dependencies": { - "@types/minimist": "^1.2.0", - "camelcase-keys": "^6.2.2", - "decamelize-keys": "^1.1.0", - "hard-rejection": "^2.1.0", - "minimist-options": "4.1.0", - "normalize-package-data": "^3.0.0", - "read-pkg-up": "^7.0.1", - "redent": "^3.0.0", - "trim-newlines": "^3.0.0", - "type-fest": "^0.18.0", - "yargs-parser": "^20.2.3" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/meow/node_modules/type-fest": { - "version": "0.18.1", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.18.1.tgz", - "integrity": "sha512-OIAYXk8+ISY+qTOwkHtKqzAuxchoMiD9Udx+FSGQDuiRR+PJKJHc2NJAXlbhkGwTt/4/nKZxELY1w3ReWOL8mw==", - "dev": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/merge-stream": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", - "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==", - "dev": true - }, - "node_modules/merge2": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", - "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", - "dev": true, - "engines": { - "node": ">= 8" - } - }, - "node_modules/micromatch": { - "version": "4.0.5", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.5.tgz", - "integrity": "sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==", - "dev": true, - "dependencies": { - "braces": "^3.0.2", - "picomatch": "^2.3.1" - }, - "engines": { - "node": ">=8.6" - } - }, - "node_modules/mimic-fn": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", - "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", - "dev": true, - "engines": { - "node": ">=6" + "node": ">=6" } }, "node_modules/mimic-response": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-2.1.0.tgz", - "integrity": "sha512-wXqjST+SLt7R009ySCglWBCFpjUygmCIfD790/kVbiGmUgfYGuB14PiTd5DwVxSV4NcYHjzMkoj5LjQZwTQLEA==", - "dev": true, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/min-indent": { "version": "1.0.1", - "resolved": "https://registry.npmjs.org/min-indent/-/min-indent-1.0.1.tgz", - "integrity": "sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg==", + "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-1.0.1.tgz", + "integrity": "sha512-j5EctnkH7amfV/q5Hgmoal1g2QHFJRraOtmx0JpIqkxhBhI/lJSl1nMpQ45hVarwNETOoWEimndZ4QK0RHxuxQ==", "dev": true, "engines": { "node": ">=4" @@ -5040,30 +5530,16 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/minimist-options": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/minimist-options/-/minimist-options-4.1.0.tgz", - "integrity": "sha512-Q4r8ghd80yhO/0j1O3B2BjweX3fiHg9cdOwjJd2J76Q135c+NDxGCqdYKQ1SKBuFfgWbAUzBfvYjPUEeNgqN1A==", - "dev": true, - "dependencies": { - "arrify": "^1.0.1", - "is-plain-obj": "^1.1.0", - "kind-of": "^6.0.3" - }, - "engines": { - "node": ">= 6" - } - }, "node_modules/mlly": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/mlly/-/mlly-1.4.0.tgz", - "integrity": "sha512-ua8PAThnTwpprIaU47EPeZ/bPUVp2QYBbWMphUQpVdBI3Lgqzm5KZQ45Agm3YJedHXaIHl6pBGabaLSUPPSptg==", + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/mlly/-/mlly-1.4.2.tgz", + "integrity": "sha512-i/Ykufi2t1EZ6NaPLdfnZk2AX8cs0d+mTzVKuPfqPKPatxLApaBoxJQ9x1/uckXtrS/U5oisPMDkNs0yQTaBRg==", "dev": true, "dependencies": { - "acorn": "^8.9.0", + "acorn": "^8.10.0", "pathe": "^1.1.1", "pkg-types": "^1.0.3", - "ufo": "^1.1.2" + "ufo": "^1.3.0" } }, "node_modules/ms": { @@ -5102,31 +5578,31 @@ "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", "dev": true }, - "node_modules/natural-compare-lite": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/natural-compare-lite/-/natural-compare-lite-1.4.0.tgz", - "integrity": "sha512-Tj+HTDSJJKaZnfiuw+iaF9skdPpTo2GtEly5JHnWV/hfv2Qj/9RKsGISQtLh2ox3l5EAGw487hnBee0sIJ6v2g==", - "dev": true - }, "node_modules/new-github-release-url": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/new-github-release-url/-/new-github-release-url-1.0.0.tgz", - "integrity": "sha512-dle7yf655IMjyFUqn6Nxkb18r4AOAkzRcgcZv6WZ0IqrOH4QCEZ8Sm6I7XX21zvHdBeeMeTkhR9qT2Z0EJDx6A==", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/new-github-release-url/-/new-github-release-url-2.0.0.tgz", + "integrity": "sha512-NHDDGYudnvRutt/VhKFlX26IotXe1w0cmkDm6JGquh5bz/bDTw0LufSmH/GxTjEdpHEO+bVKFTwdrcGa/9XlKQ==", "dev": true, "dependencies": { - "type-fest": "^0.4.1" + "type-fest": "^2.5.1" }, "engines": { - "node": ">=10" + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/new-github-release-url/node_modules/type-fest": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.4.1.tgz", - "integrity": "sha512-IwzA/LSfD2vC1/YDYMv/zHP4rDF1usCwllsDpbolT3D4fUepIO7f9K70jjmUewU/LmGUKJcwcVtDCpnKk4BPMw==", + "version": "2.19.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-2.19.0.tgz", + "integrity": "sha512-RAH822pAdBgcNMAfWnCBU3CFZcfZ/i1eZjwFU/dsLKumyuuP3niueg2UAukXYF0E2AAoc82ZSSf9J0WQBinzHA==", "dev": true, "engines": { - "node": ">=6" + "node": ">=12.20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/normalize-package-data": { @@ -5169,109 +5645,268 @@ } }, "node_modules/np": { - "version": "7.7.0", - "resolved": "https://registry.npmjs.org/np/-/np-7.7.0.tgz", - "integrity": "sha512-G4HfO6JUl7iKOX1qfYHM/kG5ApqqZ4ma8YjtVAJoyS5VdKkGE/OdSG3cOE9Lwr71klNz9n6KIZmPRnh0L7qM1Q==", - "dev": true, - "dependencies": { - "@samverschueren/stream-to-observable": "^0.3.1", - "any-observable": "^0.5.1", - "async-exit-hook": "^2.0.1", - "chalk": "^4.1.0", - "cosmiconfig": "^7.0.0", - "del": "^6.0.0", - "escape-goat": "^3.0.0", - "escape-string-regexp": "^4.0.0", - "execa": "^5.0.0", + "version": "8.0.4", + "resolved": "https://registry.npmjs.org/np/-/np-8.0.4.tgz", + "integrity": "sha512-a4s1yESHcIwsrk/oaTekfbhb1R/2z2yyfVLX6Atl54w/9+QR01qeYyK3vMWgJ0UY+kYsGzQXausgvUX0pkmIMg==", + "dev": true, + "dependencies": { + "chalk": "^5.2.0", + "cosmiconfig": "^8.1.3", + "del": "^7.0.0", + "escape-goat": "^4.0.0", + "escape-string-regexp": "^5.0.0", + "execa": "^7.1.1", + "exit-hook": "^3.2.0", "github-url-from-git": "^1.5.0", - "has-yarn": "^2.1.0", - "hosted-git-info": "^3.0.7", - "ignore-walk": "^3.0.3", - "import-local": "^3.0.2", - "inquirer": "^7.3.3", - "is-installed-globally": "^0.3.2", - "is-interactive": "^1.0.0", - "is-scoped": "^2.1.0", - "issue-regex": "^3.1.0", + "has-yarn": "^3.0.0", + "hosted-git-info": "^6.1.1", + "ignore-walk": "^6.0.3", + "import-local": "^3.1.0", + "inquirer": "^9.2.6", + "is-installed-globally": "^0.4.0", + "is-interactive": "^2.0.0", + "is-scoped": "^3.0.0", + "issue-regex": "^4.1.0", "listr": "^0.14.3", "listr-input": "^0.2.1", - "log-symbols": "^4.0.0", - "meow": "^8.1.0", - "minimatch": "^3.0.4", - "new-github-release-url": "^1.0.0", - "npm-name": "^6.0.1", - "onetime": "^5.1.2", - "open": "^7.3.0", - "ow": "^0.21.0", - "p-memoize": "^4.0.1", - "p-timeout": "^4.1.0", - "pkg-dir": "^5.0.0", - "read-pkg-up": "^7.0.1", - "rxjs": "^6.6.3", - "semver": "^7.3.4", - "split": "^1.0.1", - "symbol-observable": "^3.0.0", - "terminal-link": "^2.1.1", - "update-notifier": "^5.0.1" + "log-symbols": "^5.1.0", + "meow": "^12.0.1", + "new-github-release-url": "^2.0.0", + "npm-name": "^7.1.0", + "onetime": "^6.0.0", + "open": "^9.1.0", + "ow": "^1.1.1", + "p-memoize": "^7.1.1", + "p-timeout": "^6.1.1", + "path-exists": "^5.0.0", + "pkg-dir": "^7.0.0", + "read-pkg-up": "^9.1.0", + "rxjs": "^7.8.1", + "semver": "^7.5.1", + "symbol-observable": "^4.0.0", + "terminal-link": "^3.0.0", + "update-notifier": "^6.0.2" }, "bin": { "np": "source/cli.js" }, "engines": { "git": ">=2.11.0", - "node": ">=10", - "npm": ">=6.8.0", + "node": ">=16.6.0", + "npm": ">=7.19.0", "yarn": ">=1.7.0" }, "funding": { "url": "https://github.com/sindresorhus/np?sponsor=1" } }, - "node_modules/npm-name": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/npm-name/-/npm-name-6.0.1.tgz", - "integrity": "sha512-fhKRvUAxaYzMEUZim4mXWyfFbVS+M1CbrCLdAo3txWzrctxKka/h+KaBW0O9Cz5uOM00Nldn2JLWhuwnyW3SUw==", + "node_modules/np/node_modules/chalk": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.3.0.tgz", + "integrity": "sha512-dLitG79d+GV1Nb/VYcCDFivJeK1hiukt9QjRNVOsUtTy1rR1YJsmpGGTZ3qJos+uw7WmWF4wUwBd9jxjocFC2w==", + "dev": true, + "engines": { + "node": "^12.17.0 || ^14.13 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/np/node_modules/cli-width": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/cli-width/-/cli-width-4.1.0.tgz", + "integrity": "sha512-ouuZd4/dm2Sw5Gmqy6bGyNNNe1qt9RpmxveLSO7KcgsTnU7RXfsw+/bukWGo1abgBiMAic068rclZsO4IWmmxQ==", + "dev": true, + "engines": { + "node": ">= 12" + } + }, + "node_modules/np/node_modules/escape-string-regexp": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-5.0.0.tgz", + "integrity": "sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/np/node_modules/figures": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/figures/-/figures-5.0.0.tgz", + "integrity": "sha512-ej8ksPF4x6e5wvK9yevct0UCXh8TTFlWGVLlgjZuoBH1HwjIfKE/IdL5mq89sFA7zELi1VhKpmtDnrs7zWyeyg==", "dev": true, "dependencies": { - "got": "^10.6.0", - "is-scoped": "^2.1.0", - "is-url-superb": "^4.0.0", - "lodash.zip": "^4.2.0", - "org-regex": "^1.0.0", - "p-map": "^3.0.0", - "registry-auth-token": "^4.0.0", - "registry-url": "^5.1.0", - "validate-npm-package-name": "^3.0.0" + "escape-string-regexp": "^5.0.0", + "is-unicode-supported": "^1.2.0" }, "engines": { - "node": ">=10" + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/np/node_modules/inquirer": { + "version": "9.2.12", + "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-9.2.12.tgz", + "integrity": "sha512-mg3Fh9g2zfuVWJn6lhST0O7x4n03k7G8Tx5nvikJkbq8/CK47WDVm+UznF0G6s5Zi0KcyUisr6DU8T67N5U+1Q==", + "dev": true, + "dependencies": { + "@ljharb/through": "^2.3.11", + "ansi-escapes": "^4.3.2", + "chalk": "^5.3.0", + "cli-cursor": "^3.1.0", + "cli-width": "^4.1.0", + "external-editor": "^3.1.0", + "figures": "^5.0.0", + "lodash": "^4.17.21", + "mute-stream": "1.0.0", + "ora": "^5.4.1", + "run-async": "^3.0.0", + "rxjs": "^7.8.1", + "string-width": "^4.2.3", + "strip-ansi": "^6.0.1", + "wrap-ansi": "^6.2.0" + }, + "engines": { + "node": ">=14.18.0" + } + }, + "node_modules/np/node_modules/mimic-fn": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-4.0.0.tgz", + "integrity": "sha512-vqiC06CuhBTUdZH+RYl8sFrL096vA45Ok5ISO6sE/Mr1jRbGH4Csnhi8f3wKVl7x8mO4Au7Ir9D3Oyv1VYMFJw==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/np/node_modules/mute-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-1.0.0.tgz", + "integrity": "sha512-avsJQhyd+680gKXyG/sQc0nXaC6rBkPOfyHYcFb9+hdkqQkR9bdnkJ0AMZhke0oesPqIO+mFFJ+IdBc7mst4IA==", + "dev": true, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/np/node_modules/onetime": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-6.0.0.tgz", + "integrity": "sha512-1FlR+gjXK7X+AsAHso35MnyN5KqGwJRi/31ft6x0M194ht7S+rWAvd7PHss9xSKMzE0asv1pyIHaJYq+BbacAQ==", + "dev": true, + "dependencies": { + "mimic-fn": "^4.0.0" + }, + "engines": { + "node": ">=12" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/npm-name/node_modules/p-map": { + "node_modules/np/node_modules/path-exists": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-5.0.0.tgz", + "integrity": "sha512-RjhtfwJOxzcFmNOi6ltcbcu4Iu+FL3zEj83dk4kAS+fVpTxXLO1b38RvJgT/0QwvV/L3aY9TAnyv0EOqW4GoMQ==", + "dev": true, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + } + }, + "node_modules/np/node_modules/run-async": { "version": "3.0.0", - "resolved": "https://registry.npmjs.org/p-map/-/p-map-3.0.0.tgz", - "integrity": "sha512-d3qXVTF/s+W+CdJ5A29wywV2n8CQQYahlgz2bFiA+4eVNJbHJodPZ+/gXwPGh0bOqA+j8S+6+ckmvLGPk1QpxQ==", + "resolved": "https://registry.npmjs.org/run-async/-/run-async-3.0.0.tgz", + "integrity": "sha512-540WwVDOMxA6dN6We19EcT9sc3hkXPw5mzRNGM3FkdN/vtE9NFvj5lFAPNwUDmJjXidm3v7TC1cTE7t17Ulm1Q==", + "dev": true, + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/np/node_modules/rxjs": { + "version": "7.8.1", + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.1.tgz", + "integrity": "sha512-AA3TVj+0A2iuIoQkWEK/tqFjBq2j+6PO6Y0zJcvzLAFhEFIO3HL0vls9hWLncZbAAbK0mar7oZ4V079I/qPMxg==", "dev": true, "dependencies": { - "aggregate-error": "^3.0.0" + "tslib": "^2.1.0" + } + }, + "node_modules/np/node_modules/tslib": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", + "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==", + "dev": true + }, + "node_modules/np/node_modules/wrap-ansi": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz", + "integrity": "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" }, "engines": { "node": ">=8" } }, + "node_modules/npm-name": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/npm-name/-/npm-name-7.1.1.tgz", + "integrity": "sha512-lyOwsFndLoozriMEsaqJ5lXvhCATYOEhDvxlom8TNvB9a/htDXuLgpVhMUOBd9zCewUXCyBXAPxrGr2TK2adgQ==", + "dev": true, + "dependencies": { + "got": "^11.8.5", + "is-name-taken": "^2.0.0", + "is-scoped": "^3.0.0", + "is-url-superb": "^6.1.0", + "lodash.zip": "^4.2.0", + "org-regex": "^1.0.0", + "p-map": "^5.5.0", + "registry-auth-token": "^4.2.2", + "registry-url": "^6.0.1", + "validate-npm-package-name": "^3.0.0" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/npm-run-path": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", - "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==", + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-5.1.0.tgz", + "integrity": "sha512-sJOdmRGrY2sjNTRMbSvluQqg+8X7ZK61yvzBEIDhz4f8z1TZFYABsqjjCBd/0PUNE9M6QDgHJXQkGUEm7Q+l9Q==", "dev": true, "dependencies": { - "path-key": "^3.0.0" + "path-key": "^4.0.0" }, "engines": { - "node": ">=8" + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/npm-run-path/node_modules/path-key": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-4.0.0.tgz", + "integrity": "sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/number-is-nan": { @@ -5370,16 +6005,18 @@ } }, "node_modules/open": { - "version": "7.4.2", - "resolved": "https://registry.npmjs.org/open/-/open-7.4.2.tgz", - "integrity": "sha512-MVHddDVweXZF3awtlAS+6pgKLlm/JgxZ90+/NBurBoQctVOOB/zDdVjcyPzQ+0laDGbsWgrRkflI65sQeOgT9Q==", + "version": "9.1.0", + "resolved": "https://registry.npmjs.org/open/-/open-9.1.0.tgz", + "integrity": "sha512-OS+QTnw1/4vrf+9hh1jc1jnYjzSG4ttTBB8UxOwAnInG3Uo4ssetzC1ihqaIHjLJnA5GGlRl6QlZXOTQhRBUvg==", "dev": true, "dependencies": { - "is-docker": "^2.0.0", - "is-wsl": "^2.1.1" + "default-browser": "^4.0.0", + "define-lazy-prop": "^3.0.0", + "is-inside-container": "^1.0.0", + "is-wsl": "^2.2.0" }, "engines": { - "node": ">=8" + "node": ">=14.16" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" @@ -5402,37 +6039,43 @@ "node": ">= 0.8.0" } }, - "node_modules/org-regex": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/org-regex/-/org-regex-1.0.0.tgz", - "integrity": "sha512-7bqkxkEJwzJQUAlyYniqEZ3Ilzjh0yoa62c7gL6Ijxj5bEpPL+8IE1Z0PFj0ywjjXQcdrwR51g9MIcLezR0hKQ==", + "node_modules/ora": { + "version": "5.4.1", + "resolved": "https://registry.npmjs.org/ora/-/ora-5.4.1.tgz", + "integrity": "sha512-5b6Y85tPxZZ7QytO+BQzysW31HJku27cRIlkbAXaNx+BdcVi+LlRFmVXzeF6a7JCwJpyw5c4b+YSVImQIrBpuQ==", "dev": true, + "dependencies": { + "bl": "^4.1.0", + "chalk": "^4.1.0", + "cli-cursor": "^3.1.0", + "cli-spinners": "^2.5.0", + "is-interactive": "^1.0.0", + "is-unicode-supported": "^0.1.0", + "log-symbols": "^4.1.0", + "strip-ansi": "^6.0.0", + "wcwidth": "^1.0.1" + }, "engines": { - "node": ">=8" + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/os-tmpdir": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz", - "integrity": "sha512-D2FR03Vir7FIu45XBY20mTb+/ZSWB00sjU9jdQXt83gDrI4Ztz5Fs7/yy74g2N5SVQY4xY1qDr4rNddwYRVX0g==", + "node_modules/ora/node_modules/is-interactive": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-interactive/-/is-interactive-1.0.0.tgz", + "integrity": "sha512-2HvIEKRoqS62guEC+qBjpvRubdX910WCMuJTZ+I9yvqKU2/12eSL549HMwtabb4oupdj2sMP50k+XJfB/8JE6w==", "dev": true, "engines": { - "node": ">=0.10.0" + "node": ">=8" } }, - "node_modules/ow": { - "version": "0.21.0", - "resolved": "https://registry.npmjs.org/ow/-/ow-0.21.0.tgz", - "integrity": "sha512-dlsoDe39g7mhdsdrC1R/YwjT7yjVqE3svWwOlMGvN690waBkgEZBmKBdkmKvSt5/wZ6E0Jn/nIesPqMZOpPKqw==", + "node_modules/ora/node_modules/is-unicode-supported": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz", + "integrity": "sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==", "dev": true, - "dependencies": { - "@sindresorhus/is": "^4.0.0", - "callsites": "^3.1.0", - "dot-prop": "^6.0.1", - "lodash.isequal": "^4.5.0", - "type-fest": "^0.20.2", - "vali-date": "^1.0.0" - }, "engines": { "node": ">=10" }, @@ -5440,70 +6083,78 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/ow/node_modules/@sindresorhus/is": { - "version": "4.6.0", - "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-4.6.0.tgz", - "integrity": "sha512-t09vSN3MdfsyCHoFcTRCH/iUtG7OJ0CsjzB8cjAmKc/va/kIgeDI/TxsigdncE/4be734m0cvIYwNaV4i2XqAw==", + "node_modules/ora/node_modules/log-symbols": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-4.1.0.tgz", + "integrity": "sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg==", "dev": true, + "dependencies": { + "chalk": "^4.1.0", + "is-unicode-supported": "^0.1.0" + }, "engines": { "node": ">=10" }, "funding": { - "url": "https://github.com/sindresorhus/is?sponsor=1" + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/p-cancelable": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/p-cancelable/-/p-cancelable-2.1.1.tgz", - "integrity": "sha512-BZOr3nRQHOntUjTrH8+Lh54smKHoHyur8We1V8DSMVrl5A2malOOwuJRnKRDjSnkoeBh4at6BwEnb5I7Jl31wg==", + "node_modules/org-regex": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/org-regex/-/org-regex-1.0.0.tgz", + "integrity": "sha512-7bqkxkEJwzJQUAlyYniqEZ3Ilzjh0yoa62c7gL6Ijxj5bEpPL+8IE1Z0PFj0ywjjXQcdrwR51g9MIcLezR0hKQ==", "dev": true, "engines": { "node": ">=8" } }, - "node_modules/p-defer": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/p-defer/-/p-defer-1.0.0.tgz", - "integrity": "sha512-wB3wfAxZpk2AzOfUMJNL+d36xothRSyj8EXOa4f6GMqYDN9BJaaSISbsk+wS9abmnebVw95C2Kb5t85UmpCxuw==", + "node_modules/os-tmpdir": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz", + "integrity": "sha512-D2FR03Vir7FIu45XBY20mTb+/ZSWB00sjU9jdQXt83gDrI4Ztz5Fs7/yy74g2N5SVQY4xY1qDr4rNddwYRVX0g==", "dev": true, "engines": { - "node": ">=4" + "node": ">=0.10.0" } }, - "node_modules/p-event": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/p-event/-/p-event-4.2.0.tgz", - "integrity": "sha512-KXatOjCRXXkSePPb1Nbi0p0m+gQAwdlbhi4wQKJPI1HsMQS9g+Sqp2o+QHziPr7eYJyOZet836KoHEVM1mwOrQ==", + "node_modules/ow": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/ow/-/ow-1.1.1.tgz", + "integrity": "sha512-sJBRCbS5vh1Jp9EOgwp1Ws3c16lJrUkJYlvWTYC03oyiYVwS/ns7lKRWow4w4XjDyTrA2pplQv4B2naWSR6yDA==", "dev": true, "dependencies": { - "p-timeout": "^3.1.0" + "@sindresorhus/is": "^5.3.0", + "callsites": "^4.0.0", + "dot-prop": "^7.2.0", + "lodash.isequal": "^4.5.0", + "vali-date": "^1.0.0" }, "engines": { - "node": ">=8" + "node": ">=14.16" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/p-event/node_modules/p-timeout": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/p-timeout/-/p-timeout-3.2.0.tgz", - "integrity": "sha512-rhIwUycgwwKcP9yTOOFK/AKsAopjjCakVqLHePO3CC6Mir1Z99xT+R63jZxAT5lFZLa2inS5h+ZS2GvR99/FBg==", + "node_modules/ow/node_modules/@sindresorhus/is": { + "version": "5.6.0", + "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-5.6.0.tgz", + "integrity": "sha512-TV7t8GKYaJWsn00tFDqBw8+Uqmr8A0fRU1tvTQhyZzGv0sJCGRQL3JGMI3ucuKo3XIZdUP+Lx7/gh2t3lewy7g==", "dev": true, - "dependencies": { - "p-finally": "^1.0.0" - }, "engines": { - "node": ">=8" + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sindresorhus/is?sponsor=1" } }, - "node_modules/p-finally": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/p-finally/-/p-finally-1.0.0.tgz", - "integrity": "sha512-LICb2p9CB7FS+0eR1oqWnHhp0FljGLZCWBE9aix0Uye9W8LTQPwMTYVGWQWIw9RdQiDg4+epXQODwIYJtSJaow==", + "node_modules/p-cancelable": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/p-cancelable/-/p-cancelable-2.1.1.tgz", + "integrity": "sha512-BZOr3nRQHOntUjTrH8+Lh54smKHoHyur8We1V8DSMVrl5A2malOOwuJRnKRDjSnkoeBh4at6BwEnb5I7Jl31wg==", "dev": true, "engines": { - "node": ">=4" + "node": ">=8" } }, "node_modules/p-limit": { @@ -5536,94 +6187,77 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/p-lock": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/p-lock/-/p-lock-2.1.0.tgz", + "integrity": "sha512-pi2yT8gNhVrV4LgsUvJWQy58TXH1HG2+NXDby9+UrsS/9fXb0FJH9aCxbdHJ0EAQ6XC7ggSP6GAzuR5puDArUQ==", + "dev": true + }, "node_modules/p-map": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/p-map/-/p-map-4.0.0.tgz", - "integrity": "sha512-/bjOqmgETBYB5BoEeGVea8dmvHb2m9GLy1E9W43yeyfP6QQCZGFNa+XRceJEuDB6zqr+gKpIAmlLebMpykw/MQ==", + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/p-map/-/p-map-5.5.0.tgz", + "integrity": "sha512-VFqfGDHlx87K66yZrNdI4YGtD70IRyd+zSvgks6mzHPRNkoKy+9EKP4SFC77/vTTQYmRmti7dvqC+m5jBrBAcg==", "dev": true, "dependencies": { - "aggregate-error": "^3.0.0" + "aggregate-error": "^4.0.0" }, "engines": { - "node": ">=10" + "node": ">=12" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/p-memoize": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/p-memoize/-/p-memoize-4.0.4.tgz", - "integrity": "sha512-ijdh0DP4Mk6J4FXlOM6vPPoCjPytcEseW8p/k5SDTSSfGV3E9bpt9Yzfifvzp6iohIieoLTkXRb32OWV0fB2Lw==", + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/p-memoize/-/p-memoize-7.1.1.tgz", + "integrity": "sha512-DZ/bONJILHkQ721hSr/E9wMz5Am/OTJ9P6LhLFo2Tu+jL8044tgc9LwHO8g4PiaYePnlVVRAJcKmgy8J9MVFrA==", "dev": true, "dependencies": { - "map-age-cleaner": "^0.1.3", - "mimic-fn": "^3.0.0", - "p-settle": "^4.1.1" + "mimic-fn": "^4.0.0", + "type-fest": "^3.0.0" }, "engines": { - "node": ">=10" + "node": ">=14.16" }, "funding": { "url": "https://github.com/sindresorhus/p-memoize?sponsor=1" } }, "node_modules/p-memoize/node_modules/mimic-fn": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-3.1.0.tgz", - "integrity": "sha512-Ysbi9uYW9hFyfrThdDEQuykN4Ey6BuwPD2kpI5ES/nFTDn/98yxYNLZJcgUAKPT/mcrLLKaGzJR9YVxJrIdASQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/p-reflect": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/p-reflect/-/p-reflect-2.1.0.tgz", - "integrity": "sha512-paHV8NUz8zDHu5lhr/ngGWQiW067DK/+IbJ+RfZ4k+s8y4EKyYCz8pGYWjxCg35eHztpJAt+NUgvN4L+GCbPlg==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/p-settle": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/p-settle/-/p-settle-4.1.1.tgz", - "integrity": "sha512-6THGh13mt3gypcNMm0ADqVNCcYa3BK6DWsuJWFCuEKP1rpY+OKGp7gaZwVmLspmic01+fsg/fN57MfvDzZ/PuQ==", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-4.0.0.tgz", + "integrity": "sha512-vqiC06CuhBTUdZH+RYl8sFrL096vA45Ok5ISO6sE/Mr1jRbGH4Csnhi8f3wKVl7x8mO4Au7Ir9D3Oyv1VYMFJw==", "dev": true, - "dependencies": { - "p-limit": "^2.2.2", - "p-reflect": "^2.1.0" - }, "engines": { - "node": ">=10" + "node": ">=12" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/p-settle/node_modules/p-limit": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", - "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "node_modules/p-memoize/node_modules/type-fest": { + "version": "3.13.1", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-3.13.1.tgz", + "integrity": "sha512-tLq3bSNx+xSpwvAJnzrK0Ep5CLNWjvFTOp71URMaAEWBfRb9nnJiBoUe0tF8bI4ZFO3omgBR6NvnbzVUT3Ly4g==", "dev": true, - "dependencies": { - "p-try": "^2.0.0" - }, "engines": { - "node": ">=6" + "node": ">=14.16" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/p-timeout": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/p-timeout/-/p-timeout-4.1.0.tgz", - "integrity": "sha512-+/wmHtzJuWii1sXn3HCuH/FTwGhrp4tmJTxSKJbfS+vkipci6osxXM5mY0jUiRzWKMTgUT8l7HFbeSwZAynqHw==", + "version": "6.1.2", + "resolved": "https://registry.npmjs.org/p-timeout/-/p-timeout-6.1.2.tgz", + "integrity": "sha512-UbD77BuZ9Bc9aABo74gfXhNvzC9Tx7SxtHSh1fxvx3jTLLYvmVhiQZZrJzqqU0jKbN32kb5VOKiLEQI/3bIjgQ==", "dev": true, "engines": { - "node": ">=10" + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/p-try": { @@ -5636,213 +6270,190 @@ } }, "node_modules/package-json": { - "version": "6.5.0", - "resolved": "https://registry.npmjs.org/package-json/-/package-json-6.5.0.tgz", - "integrity": "sha512-k3bdm2n25tkyxcjSKzB5x8kfVxlMdgsbPr0GkZcwHsLpba6cBjqCt1KlcChKEvxHIcTB1FVMuwoijZ26xex5MQ==", + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/package-json/-/package-json-8.1.1.tgz", + "integrity": "sha512-cbH9IAIJHNj9uXi196JVsRlt7cHKak6u/e6AkL/bkRelZ7rlL3X1YKxsZwa36xipOEKAsdtmaG6aAJoM1fx2zA==", "dev": true, "dependencies": { - "got": "^9.6.0", - "registry-auth-token": "^4.0.0", - "registry-url": "^5.0.0", - "semver": "^6.2.0" + "got": "^12.1.0", + "registry-auth-token": "^5.0.1", + "registry-url": "^6.0.0", + "semver": "^7.3.7" }, "engines": { - "node": ">=8" + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/package-json/node_modules/@sindresorhus/is": { - "version": "0.14.0", - "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-0.14.0.tgz", - "integrity": "sha512-9NET910DNaIPngYnLLPeg+Ogzqsi9uM4mSboU5y6p8S5DzMTVEsJZrawi+BoDNUVBa2DhJqQYUFvMDfgU062LQ==", + "version": "5.6.0", + "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-5.6.0.tgz", + "integrity": "sha512-TV7t8GKYaJWsn00tFDqBw8+Uqmr8A0fRU1tvTQhyZzGv0sJCGRQL3JGMI3ucuKo3XIZdUP+Lx7/gh2t3lewy7g==", "dev": true, "engines": { - "node": ">=6" + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sindresorhus/is?sponsor=1" } }, "node_modules/package-json/node_modules/@szmarczak/http-timer": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@szmarczak/http-timer/-/http-timer-1.1.2.tgz", - "integrity": "sha512-XIB2XbzHTN6ieIjfIMV9hlVcfPU26s2vafYWQcZHWXHOxiaRZYEDKEwdl129Zyg50+foYV2jCgtrqSA6qNuNSA==", + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/@szmarczak/http-timer/-/http-timer-5.0.1.tgz", + "integrity": "sha512-+PmQX0PiAYPMeVYe237LJAYvOMYW1j2rH5YROyS3b4CTVJum34HfRvKvAzozHAQG0TnHNdUfY9nCeUyRAs//cw==", "dev": true, "dependencies": { - "defer-to-connect": "^1.0.1" + "defer-to-connect": "^2.0.1" }, "engines": { - "node": ">=6" + "node": ">=14.16" } }, - "node_modules/package-json/node_modules/cacheable-request": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/cacheable-request/-/cacheable-request-6.1.0.tgz", - "integrity": "sha512-Oj3cAGPCqOZX7Rz64Uny2GYAZNliQSqfbePrgAQ1wKAihYmCUnraBtJtKcGR4xz7wF+LoJC+ssFZvv5BgF9Igg==", + "node_modules/package-json/node_modules/cacheable-lookup": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/cacheable-lookup/-/cacheable-lookup-7.0.0.tgz", + "integrity": "sha512-+qJyx4xiKra8mZrcwhjMRMUhD5NR1R8esPkzIYxX96JiecFoxAXFuz/GpR3+ev4PE1WamHip78wV0vcmPQtp8w==", "dev": true, - "dependencies": { - "clone-response": "^1.0.2", - "get-stream": "^5.1.0", - "http-cache-semantics": "^4.0.0", - "keyv": "^3.0.0", - "lowercase-keys": "^2.0.0", - "normalize-url": "^4.1.0", - "responselike": "^1.0.2" - }, "engines": { - "node": ">=8" + "node": ">=14.16" } }, - "node_modules/package-json/node_modules/cacheable-request/node_modules/get-stream": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.2.0.tgz", - "integrity": "sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==", + "node_modules/package-json/node_modules/cacheable-request": { + "version": "10.2.14", + "resolved": "https://registry.npmjs.org/cacheable-request/-/cacheable-request-10.2.14.tgz", + "integrity": "sha512-zkDT5WAF4hSSoUgyfg5tFIxz8XQK+25W/TLVojJTMKBaxevLBBtLxgqguAuVQB8PVW79FVjHcU+GJ9tVbDZ9mQ==", "dev": true, "dependencies": { - "pump": "^3.0.0" + "@types/http-cache-semantics": "^4.0.2", + "get-stream": "^6.0.1", + "http-cache-semantics": "^4.1.1", + "keyv": "^4.5.3", + "mimic-response": "^4.0.0", + "normalize-url": "^8.0.0", + "responselike": "^3.0.0" }, "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "node": ">=14.16" } }, - "node_modules/package-json/node_modules/decompress-response": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-3.3.0.tgz", - "integrity": "sha512-BzRPQuY1ip+qDonAOz42gRm/pg9F768C+npV/4JOsxRC2sq+Rlk+Q4ZCAsOhnIaMrgarILY+RMUIvMmmX1qAEA==", + "node_modules/package-json/node_modules/got": { + "version": "12.6.1", + "resolved": "https://registry.npmjs.org/got/-/got-12.6.1.tgz", + "integrity": "sha512-mThBblvlAF1d4O5oqyvN+ZxLAYwIJK7bpMxgYqPD9okW0C3qm5FFn7k811QrcuEBwaogR3ngOFoCfs6mRv7teQ==", "dev": true, "dependencies": { - "mimic-response": "^1.0.0" + "@sindresorhus/is": "^5.2.0", + "@szmarczak/http-timer": "^5.0.1", + "cacheable-lookup": "^7.0.0", + "cacheable-request": "^10.2.8", + "decompress-response": "^6.0.0", + "form-data-encoder": "^2.1.2", + "get-stream": "^6.0.1", + "http2-wrapper": "^2.1.10", + "lowercase-keys": "^3.0.0", + "p-cancelable": "^3.0.0", + "responselike": "^3.0.0" }, "engines": { - "node": ">=4" - } - }, - "node_modules/package-json/node_modules/defer-to-connect": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/defer-to-connect/-/defer-to-connect-1.1.3.tgz", - "integrity": "sha512-0ISdNousHvZT2EiFlZeZAHBUvSxmKswVCEf8hW7KWgG4a8MVEu/3Vb6uWYozkjylyCxe0JBIiRB1jV45S70WVQ==", - "dev": true - }, - "node_modules/package-json/node_modules/get-stream": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-4.1.0.tgz", - "integrity": "sha512-GMat4EJ5161kIy2HevLlr4luNjBgvmj413KaQA7jt4V8B4RDsfpHk7WQ9GVqfYyyx8OS/L66Kox+rJRNklLK7w==", - "dev": true, - "dependencies": { - "pump": "^3.0.0" + "node": ">=14.16" }, - "engines": { - "node": ">=6" + "funding": { + "url": "https://github.com/sindresorhus/got?sponsor=1" } }, - "node_modules/package-json/node_modules/got": { - "version": "9.6.0", - "resolved": "https://registry.npmjs.org/got/-/got-9.6.0.tgz", - "integrity": "sha512-R7eWptXuGYxwijs0eV+v3o6+XH1IqVK8dJOEecQfTmkncw9AV4dcw/Dhxi8MdlqPthxxpZyizMzyg8RTmEsG+Q==", + "node_modules/package-json/node_modules/http2-wrapper": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/http2-wrapper/-/http2-wrapper-2.2.1.tgz", + "integrity": "sha512-V5nVw1PAOgfI3Lmeaj2Exmeg7fenjhRUgz1lPSezy1CuhPYbgQtbQj4jZfEAEMlaL+vupsvhjqCyjzob0yxsmQ==", "dev": true, "dependencies": { - "@sindresorhus/is": "^0.14.0", - "@szmarczak/http-timer": "^1.1.2", - "cacheable-request": "^6.0.0", - "decompress-response": "^3.3.0", - "duplexer3": "^0.1.4", - "get-stream": "^4.1.0", - "lowercase-keys": "^1.0.1", - "mimic-response": "^1.0.1", - "p-cancelable": "^1.0.0", - "to-readable-stream": "^1.0.0", - "url-parse-lax": "^3.0.0" + "quick-lru": "^5.1.1", + "resolve-alpn": "^1.2.0" }, "engines": { - "node": ">=8.6" - } - }, - "node_modules/package-json/node_modules/got/node_modules/lowercase-keys": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-1.0.1.tgz", - "integrity": "sha512-G2Lj61tXDnVFFOi8VZds+SoQjtQC3dgokKdDG2mTm1tx4m50NUHBOZSBwQQHyy0V12A0JTG4icfZQH+xPyh8VA==", - "dev": true, - "engines": { - "node": ">=0.10.0" + "node": ">=10.19.0" } }, - "node_modules/package-json/node_modules/json-buffer": { + "node_modules/package-json/node_modules/lowercase-keys": { "version": "3.0.0", - "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.0.tgz", - "integrity": "sha512-CuUqjv0FUZIdXkHPI8MezCnFCdaTAacej1TZYulLoAg1h/PhwkdXFN4V/gzY4g+fMBCOV2xF+rp7t2XD2ns/NQ==", - "dev": true - }, - "node_modules/package-json/node_modules/keyv": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/keyv/-/keyv-3.1.0.tgz", - "integrity": "sha512-9ykJ/46SN/9KPM/sichzQ7OvXyGDYKGTaDlKMGCAlg2UK8KRy4jb0d8sFc+0Tt0YYnThq8X2RZgCg74RPxgcVA==", + "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-3.0.0.tgz", + "integrity": "sha512-ozCC6gdQ+glXOQsveKD0YsDy8DSQFjDTz4zyzEHNV5+JP5D62LmfDZ6o1cycFx9ouG940M5dE8C8CTewdj2YWQ==", "dev": true, - "dependencies": { - "json-buffer": "3.0.0" + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/package-json/node_modules/mimic-response": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-1.0.1.tgz", - "integrity": "sha512-j5EctnkH7amfV/q5Hgmoal1g2QHFJRraOtmx0JpIqkxhBhI/lJSl1nMpQ45hVarwNETOoWEimndZ4QK0RHxuxQ==", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-4.0.0.tgz", + "integrity": "sha512-e5ISH9xMYU0DzrT+jl8q2ze9D6eWBto+I8CNpe+VI+K2J/F/k3PdkdTdz4wvGVH4NTpo+NRYTVIuMQEMMcsLqg==", "dev": true, "engines": { - "node": ">=4" + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/package-json/node_modules/normalize-url": { - "version": "4.5.1", - "resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-4.5.1.tgz", - "integrity": "sha512-9UZCFRHQdNrfTpGg8+1INIg93B6zE0aXMVFkw1WFwvO4SlZywU6aLg5Of0Ap/PgcbSw4LNxvMWXMeugwMCX0AA==", + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-8.0.0.tgz", + "integrity": "sha512-uVFpKhj5MheNBJRTiMZ9pE/7hD1QTeEvugSJW/OmLzAp78PB5O6adfMNTvmfKhXBkvCzC+rqifWcVYpGFwTjnw==", "dev": true, "engines": { - "node": ">=8" + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/package-json/node_modules/p-cancelable": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/p-cancelable/-/p-cancelable-1.1.0.tgz", - "integrity": "sha512-s73XxOZ4zpt1edZYZzvhqFa6uvQc1vwUa0K0BdtIZgQMAJj9IbebH+JkgKZc9h+B05PKHLOTl4ajG1BmNrVZlw==", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/p-cancelable/-/p-cancelable-3.0.0.tgz", + "integrity": "sha512-mlVgR3PGuzlo0MmTdk4cXqXWlwQDLnONTAg6sm62XkMJEiRxN3GL3SffkYvqwonbkJBcrI7Uvv5Zh9yjvn2iUw==", "dev": true, "engines": { - "node": ">=6" + "node": ">=12.20" } }, - "node_modules/package-json/node_modules/responselike": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/responselike/-/responselike-1.0.2.tgz", - "integrity": "sha512-/Fpe5guzJk1gPqdJLJR5u7eG/gNY4nImjbRDaVWVMRhne55TCmj2i9Q+54PBRfatRC8v/rIiv9BN0pMd9OV5EQ==", + "node_modules/package-json/node_modules/registry-auth-token": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/registry-auth-token/-/registry-auth-token-5.0.2.tgz", + "integrity": "sha512-o/3ikDxtXaA59BmZuZrJZDJv8NMDGSj+6j6XaeBmHw8eY1i1qd9+6H+LjVvQXx3HN6aRCGa1cUdJ9RaJZUugnQ==", "dev": true, "dependencies": { - "lowercase-keys": "^1.0.0" - } - }, - "node_modules/package-json/node_modules/responselike/node_modules/lowercase-keys": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-1.0.1.tgz", - "integrity": "sha512-G2Lj61tXDnVFFOi8VZds+SoQjtQC3dgokKdDG2mTm1tx4m50NUHBOZSBwQQHyy0V12A0JTG4icfZQH+xPyh8VA==", - "dev": true, + "@pnpm/npm-conf": "^2.1.0" + }, "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/package-json/node_modules/semver": { - "version": "6.3.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", - "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", - "dev": true, - "bin": { - "semver": "bin/semver.js" + "node": ">=14" } }, - "node_modules/package-json/node_modules/to-readable-stream": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/to-readable-stream/-/to-readable-stream-1.0.0.tgz", - "integrity": "sha512-Iq25XBt6zD5npPhlLVXGFN3/gyR2/qODcKNNyTMd4vbm39HUaOiAM4PMq0eMVC/Tkxz+Zjdsc55g9yyz+Yq00Q==", + "node_modules/package-json/node_modules/responselike": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/responselike/-/responselike-3.0.0.tgz", + "integrity": "sha512-40yHxbNcl2+rzXvZuVkrYohathsSJlMTXKryG5y8uciHv1+xDLHQpgjG64JUO9nrEq2jGLH6IZ8BcZyw3wrweg==", "dev": true, + "dependencies": { + "lowercase-keys": "^3.0.0" + }, "engines": { - "node": ">=6" + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/package-name-conflict": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/package-name-conflict/-/package-name-conflict-1.0.3.tgz", + "integrity": "sha512-DPBNWSUWC0wPofXeNThao0uP4a93J7r90UyhagmJS0QcacTTkorZwXYsOop70phn1hKdcf/2e9lJIhazS8bx5A==", + "dev": true + }, "node_modules/parent-module": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", @@ -5855,6 +6466,15 @@ "node": ">=6" } }, + "node_modules/parent-module/node_modules/callsites": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "dev": true, + "engines": { + "node": ">=6" + } + }, "node_modules/parse-json": { "version": "5.2.0", "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", @@ -5873,6 +6493,15 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/parse-json-object": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/parse-json-object/-/parse-json-object-2.0.1.tgz", + "integrity": "sha512-/oF7PUUBjCqHmMEE6xIQeX5ZokQ9+miudACzPt4KBU2qi6CxZYPdisPXx4ad7wpZJYi2ZpcW2PacLTU3De3ebw==", + "dev": true, + "dependencies": { + "types-json": "^1.2.0" + } + }, "node_modules/path-exists": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", @@ -5949,15 +6578,100 @@ } }, "node_modules/pkg-dir": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-5.0.0.tgz", - "integrity": "sha512-NPE8TDbzl/3YQYY7CSS228s3g2ollTFnc+Qi3tqmqJp9Vg2ovUpixcJEo2HJScN2Ez+kEaal6y70c0ehqJBJeA==", + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-7.0.0.tgz", + "integrity": "sha512-Ie9z/WINcxxLp27BKOCHGde4ITq9UklYKDzVo1nhk5sqGEXU3FpkwP5GM2voTGJkGd9B3Otl+Q4uwSOeSUtOBA==", "dev": true, "dependencies": { - "find-up": "^5.0.0" + "find-up": "^6.3.0" }, "engines": { - "node": ">=10" + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/pkg-dir/node_modules/find-up": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-6.3.0.tgz", + "integrity": "sha512-v2ZsoEuVHYy8ZIlYqwPe/39Cy+cFDzp4dXPaxNvkEuouymu+2Jbz0PxpKarJHYJTmv2HWT3O382qY8l4jMWthw==", + "dev": true, + "dependencies": { + "locate-path": "^7.1.0", + "path-exists": "^5.0.0" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/pkg-dir/node_modules/locate-path": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-7.2.0.tgz", + "integrity": "sha512-gvVijfZvn7R+2qyPX8mAuKcFGDf6Nc61GdvGafQsHL0sBIxfKzA+usWn4GFC/bk+QdwPUD4kWFJLhElipq+0VA==", + "dev": true, + "dependencies": { + "p-locate": "^6.0.0" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/pkg-dir/node_modules/p-limit": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-4.0.0.tgz", + "integrity": "sha512-5b0R4txpzjPWVw/cXXUResoD4hb6U/x9BH08L7nw+GN1sezDzPdxeRvpc9c433fZhBan/wusjbCsqwqm4EIBIQ==", + "dev": true, + "dependencies": { + "yocto-queue": "^1.0.0" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/pkg-dir/node_modules/p-locate": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-6.0.0.tgz", + "integrity": "sha512-wPrq66Llhl7/4AGC6I+cqxT07LhXvWL08LNXz1fENOw0Ap4sRZZ/gZpTTJ5jpurzzzfS2W/Ge9BY3LgLjCShcw==", + "dev": true, + "dependencies": { + "p-limit": "^4.0.0" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/pkg-dir/node_modules/path-exists": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-5.0.0.tgz", + "integrity": "sha512-RjhtfwJOxzcFmNOi6ltcbcu4Iu+FL3zEj83dk4kAS+fVpTxXLO1b38RvJgT/0QwvV/L3aY9TAnyv0EOqW4GoMQ==", + "dev": true, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + } + }, + "node_modules/pkg-dir/node_modules/yocto-queue": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-1.0.0.tgz", + "integrity": "sha512-9bnSc/HEW2uRy67wc+T8UwauLuPJVn28jb+GtJY16iiKWyvmYJRXVT4UamsAEGQfPohgr2q4Tq0sQbQlxTfi1g==", + "dev": true, + "engines": { + "node": ">=12.20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/pkg-types": { @@ -6008,27 +6722,18 @@ "node": ">= 0.8.0" } }, - "node_modules/prepend-http": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/prepend-http/-/prepend-http-2.0.0.tgz", - "integrity": "sha512-ravE6m9Atw9Z/jjttRUZ+clIXogdghyZAuWJ3qEzjT+jI/dL1ifAqhZeC5VHzQp1MSt1+jxKkFNemj/iO7tVUA==", - "dev": true, - "engines": { - "node": ">=4" - } - }, "node_modules/pretty-format": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-27.5.1.tgz", - "integrity": "sha512-Qb1gy5OrP5+zDf2Bvnzdl3jsTf1qXVMazbvCoKhtKqVs4/YK4ozX4gKQJJVyNe+cajNPn0KoC0MC3FUmaHWEmQ==", + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.7.0.tgz", + "integrity": "sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==", "dev": true, "dependencies": { - "ansi-regex": "^5.0.1", + "@jest/schemas": "^29.6.3", "ansi-styles": "^5.0.0", - "react-is": "^17.0.1" + "react-is": "^18.0.0" }, "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, "node_modules/pretty-format/node_modules/ansi-styles": { @@ -6043,6 +6748,21 @@ "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, + "node_modules/progress": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/progress/-/progress-2.0.3.tgz", + "integrity": "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==", + "dev": true, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/proto-list": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/proto-list/-/proto-list-1.2.4.tgz", + "integrity": "sha512-vtK/94akxsTMhe0/cbfpR+syPuszcuwhqVjJq26CuNDgFGj682oRBXOP5MJpv2r7JtE8MsiepGIqvvOTBwn2vA==", + "dev": true + }, "node_modules/pump": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", @@ -6054,33 +6774,27 @@ } }, "node_modules/punycode": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.0.tgz", - "integrity": "sha512-rRV+zQD8tVFys26lAGR9WUuS4iUAngJScM+ZRSKtvl5tKeZ2t5bvdNFdNHBW9FWR4guGHlgmsZ1G7BSm2wTbuA==", - "dev": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/pupa": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/pupa/-/pupa-2.1.1.tgz", - "integrity": "sha512-l1jNAspIBSFqbT+y+5FosojNpVpF94nlI+wDUpqP9enwOTfHx9f0gh5nB96vl+6yTpsJsypeNrwfzPrKuHB41A==", + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", + "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", "dev": true, - "dependencies": { - "escape-goat": "^2.0.0" - }, "engines": { - "node": ">=8" + "node": ">=6" } }, - "node_modules/pupa/node_modules/escape-goat": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/escape-goat/-/escape-goat-2.1.1.tgz", - "integrity": "sha512-8/uIhbG12Csjy2JEW7D9pHbreaVaS/OpN3ycnyvElTdwM5n6GY6W6e2IPemfvGZeUMqZ9A/3GqIZMgKnBhAw/Q==", + "node_modules/pupa": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/pupa/-/pupa-3.1.0.tgz", + "integrity": "sha512-FLpr4flz5xZTSJxSeaheeMKN/EDzMdK7b8PTOC6a5PYFKTucWbdqjgqaEyH0shFiSJrVB1+Qqi4Tk19ccU6Aug==", "dev": true, + "dependencies": { + "escape-goat": "^4.0.0" + }, "engines": { - "node": ">=8" + "node": ">=12.20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/queue-microtask": { @@ -6104,12 +6818,15 @@ ] }, "node_modules/quick-lru": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/quick-lru/-/quick-lru-4.0.1.tgz", - "integrity": "sha512-ARhCpm70fzdcvNQfPoy49IaanKkTlRWF2JMzqhcJbhSFRZv7nPTvZJdcY7301IPmvW+/p0RgIWnQDLJxifsQ7g==", + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/quick-lru/-/quick-lru-5.1.1.tgz", + "integrity": "sha512-WuyALRjWPDGtt/wzJiadO5AXY+8hZ80hVpe6MyivgraREW751X3SbhRvG3eLKOYN+8VEvqLcf3wdnt44Z4S4SA==", "dev": true, "engines": { - "node": ">=8" + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/rc": { @@ -6127,6 +6844,12 @@ "rc": "cli.js" } }, + "node_modules/rc/node_modules/ini": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz", + "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==", + "dev": true + }, "node_modules/rc/node_modules/strip-json-comments": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", @@ -6137,160 +6860,189 @@ } }, "node_modules/react-is": { - "version": "17.0.2", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz", - "integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==", + "version": "18.2.0", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz", + "integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==", "dev": true }, + "node_modules/read-file-safe": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/read-file-safe/-/read-file-safe-1.0.10.tgz", + "integrity": "sha512-qW25fd2uMX3dV6Ui/R0jYK1MhTpjx8FO/VHaHTXzwWsGnkNwLRcqYfCXd9qDM+NZ273DPUvP2RaimYuLSu1K/g==", + "dev": true + }, + "node_modules/read-json-safe": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/read-json-safe/-/read-json-safe-1.0.5.tgz", + "integrity": "sha512-SJyNY/U9+vW35FPus22Qvv1oilnR7PCfN2E70uKQEGaJS313A5/cz9Yhv7ZtWzZ+XIwrtEPxXf10BOyYemHehA==", + "dev": true, + "dependencies": { + "parse-json-object": "^1.0.5", + "read-file-safe": "^1.0.5" + } + }, + "node_modules/read-json-safe/node_modules/parse-json-object": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/parse-json-object/-/parse-json-object-1.1.0.tgz", + "integrity": "sha512-4w5s6uJY1tW9REY8UwUOyaZKSKsrbQrMEzlV/Le/g5t4iMWuuyK83pZZ0OZimSOL9iyv2ORvRSgz71Ekd7iD3g==", + "dev": true, + "dependencies": { + "types-json": "^1.0.6" + } + }, "node_modules/read-pkg": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-5.2.0.tgz", - "integrity": "sha512-Ug69mNOpfvKDAc2Q8DRpMjjzdtrnv9HcSMX+4VsZxD1aZ6ZzrIE7rlzXBtWTyhULSMKg076AW6WR5iZpD0JiOg==", + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-7.1.0.tgz", + "integrity": "sha512-5iOehe+WF75IccPc30bWTbpdDQLOCc3Uu8bi3Dte3Eueij81yx1Mrufk8qBx/YAbR4uL1FdUr+7BKXDwEtisXg==", "dev": true, "dependencies": { - "@types/normalize-package-data": "^2.4.0", - "normalize-package-data": "^2.5.0", - "parse-json": "^5.0.0", - "type-fest": "^0.6.0" + "@types/normalize-package-data": "^2.4.1", + "normalize-package-data": "^3.0.2", + "parse-json": "^5.2.0", + "type-fest": "^2.0.0" }, "engines": { - "node": ">=8" + "node": ">=12.20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/read-pkg-up": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-7.0.1.tgz", - "integrity": "sha512-zK0TB7Xd6JpCLmlLmufqykGE+/TlOePD6qKClNW7hHDKFh/J7/7gCWGR7joEQEW1bKq3a3yUZSObOoWLFQ4ohg==", + "version": "9.1.0", + "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-9.1.0.tgz", + "integrity": "sha512-vaMRR1AC1nrd5CQM0PhlRsO5oc2AAigqr7cCrZ/MW/Rsaflz4RlgzkpL4qoU/z1F6wrbd85iFv1OQj/y5RdGvg==", "dev": true, "dependencies": { - "find-up": "^4.1.0", - "read-pkg": "^5.2.0", - "type-fest": "^0.8.1" + "find-up": "^6.3.0", + "read-pkg": "^7.1.0", + "type-fest": "^2.5.0" }, "engines": { - "node": ">=8" + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/read-pkg-up/node_modules/find-up": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", - "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-6.3.0.tgz", + "integrity": "sha512-v2ZsoEuVHYy8ZIlYqwPe/39Cy+cFDzp4dXPaxNvkEuouymu+2Jbz0PxpKarJHYJTmv2HWT3O382qY8l4jMWthw==", "dev": true, "dependencies": { - "locate-path": "^5.0.0", - "path-exists": "^4.0.0" + "locate-path": "^7.1.0", + "path-exists": "^5.0.0" }, "engines": { - "node": ">=8" + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/read-pkg-up/node_modules/locate-path": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", - "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-7.2.0.tgz", + "integrity": "sha512-gvVijfZvn7R+2qyPX8mAuKcFGDf6Nc61GdvGafQsHL0sBIxfKzA+usWn4GFC/bk+QdwPUD4kWFJLhElipq+0VA==", "dev": true, "dependencies": { - "p-locate": "^4.1.0" + "p-locate": "^6.0.0" }, "engines": { - "node": ">=8" + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/read-pkg-up/node_modules/p-limit": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", - "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-4.0.0.tgz", + "integrity": "sha512-5b0R4txpzjPWVw/cXXUResoD4hb6U/x9BH08L7nw+GN1sezDzPdxeRvpc9c433fZhBan/wusjbCsqwqm4EIBIQ==", "dev": true, "dependencies": { - "p-try": "^2.0.0" + "yocto-queue": "^1.0.0" }, "engines": { - "node": ">=6" + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/read-pkg-up/node_modules/p-locate": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", - "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-6.0.0.tgz", + "integrity": "sha512-wPrq66Llhl7/4AGC6I+cqxT07LhXvWL08LNXz1fENOw0Ap4sRZZ/gZpTTJ5jpurzzzfS2W/Ge9BY3LgLjCShcw==", "dev": true, "dependencies": { - "p-limit": "^2.2.0" + "p-limit": "^4.0.0" }, "engines": { - "node": ">=8" + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/read-pkg-up/node_modules/type-fest": { - "version": "0.8.1", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.8.1.tgz", - "integrity": "sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA==", + "node_modules/read-pkg-up/node_modules/path-exists": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-5.0.0.tgz", + "integrity": "sha512-RjhtfwJOxzcFmNOi6ltcbcu4Iu+FL3zEj83dk4kAS+fVpTxXLO1b38RvJgT/0QwvV/L3aY9TAnyv0EOqW4GoMQ==", "dev": true, "engines": { - "node": ">=8" + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" } }, - "node_modules/read-pkg/node_modules/hosted-git-info": { - "version": "2.8.9", - "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.8.9.tgz", - "integrity": "sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw==", - "dev": true - }, - "node_modules/read-pkg/node_modules/normalize-package-data": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.5.0.tgz", - "integrity": "sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA==", + "node_modules/read-pkg-up/node_modules/type-fest": { + "version": "2.19.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-2.19.0.tgz", + "integrity": "sha512-RAH822pAdBgcNMAfWnCBU3CFZcfZ/i1eZjwFU/dsLKumyuuP3niueg2UAukXYF0E2AAoc82ZSSf9J0WQBinzHA==", "dev": true, - "dependencies": { - "hosted-git-info": "^2.1.4", - "resolve": "^1.10.0", - "semver": "2 || 3 || 4 || 5", - "validate-npm-package-license": "^3.0.1" + "engines": { + "node": ">=12.20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/read-pkg/node_modules/semver": { - "version": "5.7.2", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz", - "integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==", + "node_modules/read-pkg-up/node_modules/yocto-queue": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-1.0.0.tgz", + "integrity": "sha512-9bnSc/HEW2uRy67wc+T8UwauLuPJVn28jb+GtJY16iiKWyvmYJRXVT4UamsAEGQfPohgr2q4Tq0sQbQlxTfi1g==", "dev": true, - "bin": { - "semver": "bin/semver" + "engines": { + "node": ">=12.20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/read-pkg/node_modules/type-fest": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.6.0.tgz", - "integrity": "sha512-q+MB8nYR1KDLrgr4G5yemftpMC7/QLqVndBmEEdqzmNj5dcFOO4Oo8qlwZE3ULT3+Zim1F8Kq4cBnikNhlCMlg==", + "version": "2.19.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-2.19.0.tgz", + "integrity": "sha512-RAH822pAdBgcNMAfWnCBU3CFZcfZ/i1eZjwFU/dsLKumyuuP3niueg2UAukXYF0E2AAoc82ZSSf9J0WQBinzHA==", "dev": true, "engines": { - "node": ">=8" + "node": ">=12.20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/redent": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/redent/-/redent-3.0.0.tgz", - "integrity": "sha512-6tDA8g98We0zd0GvVeMT9arEOnTw9qM03L9cJXaCjrip1OO764RDBLBfrB4cwzNGDj5OA5ioymC9GkizgWJDUg==", + "node_modules/readable-stream": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", "dev": true, "dependencies": { - "indent-string": "^4.0.0", - "strip-indent": "^3.0.0" + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" }, "engines": { - "node": ">=8" - } - }, - "node_modules/redent/node_modules/indent-string": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz", - "integrity": "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==", - "dev": true, - "engines": { - "node": ">=8" + "node": ">= 6" } }, "node_modules/regexp.prototype.flags": { @@ -6323,15 +7075,18 @@ } }, "node_modules/registry-url": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/registry-url/-/registry-url-5.1.0.tgz", - "integrity": "sha512-8acYXXTI0AkQv6RAOjE3vOaIXZkT9wo4LOFbBKYQEEnnMNBpKqdUrI6S4NT0KPIo/WVvJ5tE/X5LF/TQUf0ekw==", + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/registry-url/-/registry-url-6.0.1.tgz", + "integrity": "sha512-+crtS5QjFRqFCoQmvGduwYWEBng99ZvmFvF+cUJkGYF1L1BfU8C6Zp9T7f5vPAwyLkUExpvK+ANVZmGU49qi4Q==", "dev": true, "dependencies": { - "rc": "^1.2.8" + "rc": "1.2.8" }, "engines": { - "node": ">=8" + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/resolve": { @@ -6351,6 +7106,12 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/resolve-alpn": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/resolve-alpn/-/resolve-alpn-1.2.1.tgz", + "integrity": "sha512-0a1F4l73/ZFZOakJnQ3FvkJ2+gSTQWz/r2KE5OdDY0TxPm5h4GkqkWWfM47T7HsbnOtcJVEF4epCVy6u7Q3K+g==", + "dev": true + }, "node_modules/resolve-cwd": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/resolve-cwd/-/resolve-cwd-3.0.0.tgz", @@ -6466,6 +7227,86 @@ "estree-walker": "^0.6.1" } }, + "node_modules/run-applescript": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/run-applescript/-/run-applescript-5.0.0.tgz", + "integrity": "sha512-XcT5rBksx1QdIhlFOCtgZkB99ZEouFZ1E2Kc2LHqNW13U3/74YGdkQRmThTwxy4QIyookibDKYZOPqX//6BlAg==", + "dev": true, + "dependencies": { + "execa": "^5.0.0" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/run-applescript/node_modules/execa": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz", + "integrity": "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==", + "dev": true, + "dependencies": { + "cross-spawn": "^7.0.3", + "get-stream": "^6.0.0", + "human-signals": "^2.1.0", + "is-stream": "^2.0.0", + "merge-stream": "^2.0.0", + "npm-run-path": "^4.0.1", + "onetime": "^5.1.2", + "signal-exit": "^3.0.3", + "strip-final-newline": "^2.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sindresorhus/execa?sponsor=1" + } + }, + "node_modules/run-applescript/node_modules/human-signals": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz", + "integrity": "sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==", + "dev": true, + "engines": { + "node": ">=10.17.0" + } + }, + "node_modules/run-applescript/node_modules/is-stream": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", + "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", + "dev": true, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/run-applescript/node_modules/npm-run-path": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", + "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==", + "dev": true, + "dependencies": { + "path-key": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/run-applescript/node_modules/strip-final-newline": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz", + "integrity": "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==", + "dev": true, + "engines": { + "node": ">=6" + } + }, "node_modules/run-async": { "version": "2.4.1", "resolved": "https://registry.npmjs.org/run-async/-/run-async-2.4.1.tgz", @@ -6528,6 +7369,26 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, "node_modules/safe-regex-test": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/safe-regex-test/-/safe-regex-test-1.0.0.tgz", @@ -6549,12 +7410,15 @@ "dev": true }, "node_modules/scoped-regex": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/scoped-regex/-/scoped-regex-2.1.0.tgz", - "integrity": "sha512-g3WxHrqSWCZHGHlSrF51VXFdjImhwvH8ZO/pryFH56Qi0cDsZfylQa/t0jCzVQFNbNvM00HfHjkDPEuarKDSWQ==", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/scoped-regex/-/scoped-regex-3.0.0.tgz", + "integrity": "sha512-yEsN6TuxZhZ1Tl9iB81frTNS292m0I/IG7+w8lTvfcJQP2x3vnpOoevjBoE3Np5A6KnZM2+RtVenihj9t6NiYg==", "dev": true, "engines": { - "node": ">=8" + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/semver": { @@ -6573,24 +7437,18 @@ } }, "node_modules/semver-diff": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/semver-diff/-/semver-diff-3.1.1.tgz", - "integrity": "sha512-GX0Ix/CJcHyB8c4ykpHGIAvLyOwOobtM/8d+TQkAd81/bEjgPHrfba41Vpesr7jX/t8Uh+R3EX9eAS5be+jQYg==", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/semver-diff/-/semver-diff-4.0.0.tgz", + "integrity": "sha512-0Ju4+6A8iOnpL/Thra7dZsSlOHYAHIeMxfhWQRI1/VLcT3WDBZKKtQt/QkBOsiIN9ZpuvHE6cGZ0x4glCMmfiA==", "dev": true, "dependencies": { - "semver": "^6.3.0" + "semver": "^7.3.5" }, "engines": { - "node": ">=8" - } - }, - "node_modules/semver-diff/node_modules/semver": { - "version": "6.3.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", - "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", - "dev": true, - "bin": { - "semver": "bin/semver.js" + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/shebang-command": { @@ -6704,29 +7562,17 @@ "resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-3.0.1.tgz", "integrity": "sha512-cbqHunsQWnJNE6KhVSMsMeH5H/L9EpymbzqTQ3uLwNCLZ1Q481oWaofqH7nO6V07xlXwY6PhQdQ2IedWx/ZK4Q==", "dev": true, - "dependencies": { - "spdx-exceptions": "^2.1.0", - "spdx-license-ids": "^3.0.0" - } - }, - "node_modules/spdx-license-ids": { - "version": "3.0.13", - "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.13.tgz", - "integrity": "sha512-XkD+zwiqXHikFZm4AX/7JSCXA98U5Db4AFd5XUg/+9UNtnH75+Z9KxtpYiJZx36mUDVOwH83pl7yvCer6ewM3w==", - "dev": true - }, - "node_modules/split": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/split/-/split-1.0.1.tgz", - "integrity": "sha512-mTyOoPbrivtXnwnIxZRFYRrPNtEFKlpB2fvjSnCQUiAA6qAZzqwna5envK4uk6OIeP17CsdF3rSBGYVBsU0Tkg==", - "dev": true, - "dependencies": { - "through": "2" - }, - "engines": { - "node": "*" + "dependencies": { + "spdx-exceptions": "^2.1.0", + "spdx-license-ids": "^3.0.0" } }, + "node_modules/spdx-license-ids": { + "version": "3.0.16", + "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.16.tgz", + "integrity": "sha512-eWN+LnM3GR6gPu35WxNgbGl8rmY1AEmoMDvL/QD6zYmPWgywxWqJWNdLGT+ke8dKNWrcYgYjPpG5gbTfghP8rw==", + "dev": true + }, "node_modules/sprintf-js": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", @@ -6745,13 +7591,13 @@ "integrity": "sha512-Rz6yejtVyWnVjC1RFvNmYL10kgjC49EOghxWn0RFqlCHGFpQx+Xe7yW3I4ceK1SGrWIGMjD5Kbue8W/udkbMJg==", "dev": true }, - "node_modules/streamsearch": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/streamsearch/-/streamsearch-1.1.0.tgz", - "integrity": "sha512-Mcc5wHehp9aXz1ax6bZUyY5afg9u2rv5cqQI3mRrYkGC8rW2hM02jWuwjtL++LS5qinSyhj2QfLyNsuc+VsExg==", + "node_modules/string_decoder": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", + "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", "dev": true, - "engines": { - "node": ">=10.0.0" + "dependencies": { + "safe-buffer": "~5.2.0" } }, "node_modules/string-argv": { @@ -6835,24 +7681,15 @@ } }, "node_modules/strip-final-newline": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz", - "integrity": "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==", - "dev": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/strip-indent": { "version": "3.0.0", - "resolved": "https://registry.npmjs.org/strip-indent/-/strip-indent-3.0.0.tgz", - "integrity": "sha512-laJTa3Jb+VQpaC6DseHhF7dXVqHTfJPCRDaEbid/drOhgitgYku/letMUqOXFoWV0zIIUbjpdH2t+tYj4bQMRQ==", + "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-3.0.0.tgz", + "integrity": "sha512-dOESqjYr96iWYylGObzd39EuNTa5VJxyvVAEm5Jnh7KGo75V43Hk1odPQkNDyXNmUR6k+gEiDVXnjB8HJ3crXw==", "dev": true, - "dependencies": { - "min-indent": "^1.0.0" - }, "engines": { - "node": ">=8" + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/strip-json-comments": { @@ -6917,25 +7754,52 @@ } }, "node_modules/symbol-observable": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/symbol-observable/-/symbol-observable-3.0.0.tgz", - "integrity": "sha512-6tDOXSHiVjuCaasQSWTmHUWn4PuG7qa3+1WT031yTc/swT7+rLiw3GOrFxaH1E3lLP09dH3bVuVDf2gK5rxG3Q==", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/symbol-observable/-/symbol-observable-4.0.0.tgz", + "integrity": "sha512-b19dMThMV4HVFynSAM1++gBHAbk2Tc/osgLIBZMKsyqh34jb2e8Os7T6ZW/Bt3pJFdBTd2JwAnAAEQV7rSNvcQ==", "dev": true, "engines": { "node": ">=0.10" } }, "node_modules/terminal-link": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/terminal-link/-/terminal-link-2.1.1.tgz", - "integrity": "sha512-un0FmiRUQNr5PJqy9kP7c40F5BOfpGlYTrxonDChEZB7pzZxRNp/bt+ymiy9/npwXya9KH99nJ/GXFIiUkYGFQ==", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/terminal-link/-/terminal-link-3.0.0.tgz", + "integrity": "sha512-flFL3m4wuixmf6IfhFJd1YPiLiMuxEc8uHRM1buzIeZPm22Au2pDqBJQgdo7n1WfPU1ONFGv7YDwpFBmHGF6lg==", "dev": true, "dependencies": { - "ansi-escapes": "^4.2.1", - "supports-hyperlinks": "^2.0.0" + "ansi-escapes": "^5.0.0", + "supports-hyperlinks": "^2.2.0" }, "engines": { - "node": ">=8" + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/terminal-link/node_modules/ansi-escapes": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-5.0.0.tgz", + "integrity": "sha512-5GFMVX8HqE/TB+FuBJGuO5XG0WrsA6ptUqoODaT/n9mmUaZFkqnBueB4leqGBCmrUHnCnC4PCZTCd0E7QQ83bA==", + "dev": true, + "dependencies": { + "type-fest": "^1.0.2" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/terminal-link/node_modules/type-fest": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-1.4.0.tgz", + "integrity": "sha512-yGSza74xk0UG8k+pLh5oeoYirvIiWo5t0/o3zHHAO2tRDiZcxWP7fywNlXhqb6/r6sWvwi+RsyQMWhVLe4BVuA==", + "dev": true, + "engines": { + "node": ">=10" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" @@ -6953,15 +7817,6 @@ "integrity": "sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg==", "dev": true }, - "node_modules/time-zone": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/time-zone/-/time-zone-1.0.0.tgz", - "integrity": "sha512-TIsDdtKo6+XrPtiTm1ssmMngN1sAhyKnTO2kunQWqNPWIVvCm15Wmw4SWInwTVgJ5u/Tr04+8Ei9TNcw4x4ONA==", - "dev": true, - "engines": { - "node": ">=4" - } - }, "node_modules/tinybench": { "version": "2.5.0", "resolved": "https://registry.npmjs.org/tinybench/-/tinybench-2.5.0.tgz", @@ -6969,23 +7824,35 @@ "dev": true }, "node_modules/tinypool": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/tinypool/-/tinypool-0.4.0.tgz", - "integrity": "sha512-2ksntHOKf893wSAH4z/+JbPpi92esw8Gn9N2deXX+B0EO92hexAVI9GIZZPx7P5aYo5KULfeOSt3kMOmSOy6uA==", + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/tinypool/-/tinypool-0.7.0.tgz", + "integrity": "sha512-zSYNUlYSMhJ6Zdou4cJwo/p7w5nmAH17GRfU/ui3ctvjXFErXXkruT4MWW6poDeXgCaIBlGLrfU6TbTXxyGMww==", "dev": true, "engines": { "node": ">=14.0.0" } }, "node_modules/tinyspy": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/tinyspy/-/tinyspy-2.1.1.tgz", - "integrity": "sha512-XPJL2uSzcOyBMky6OFrusqWlzfFrXtE0hPuMgW8A2HmaqrPo4ZQHRN/V0QXN3FSjKxpsbRrFc5LI7KOwBsT1/w==", + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/tinyspy/-/tinyspy-2.2.0.tgz", + "integrity": "sha512-d2eda04AN/cPOR89F7Xv5bK/jrQEhmcLFe6HFldoeO9AJtps+fqEnh486vnT/8y4bw38pSyxDcTCAq+Ks2aJTg==", "dev": true, "engines": { "node": ">=14.0.0" } }, + "node_modules/titleize": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/titleize/-/titleize-3.0.0.tgz", + "integrity": "sha512-KxVu8EYHDPBdUYdKZdKtU2aj2XfEx9AfjXxE/Aj0vT06w2icA09Vus1rh6eSu1y01akYg6BjIK/hxyLJINoMLQ==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/tmp": { "version": "0.0.33", "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.33.tgz", @@ -6998,15 +7865,6 @@ "node": ">=0.6.0" } }, - "node_modules/to-readable-stream": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/to-readable-stream/-/to-readable-stream-2.1.0.tgz", - "integrity": "sha512-o3Qa6DGg1CEXshSdvWNX2sN4QHqg03SPq7U6jPXRahlQdl5dK8oXjkU/2/sGrnOZKeGV1zLSO8qPwyKklPPE7w==", - "dev": true, - "engines": { - "node": ">=8" - } - }, "node_modules/to-regex-range": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", @@ -7019,13 +7877,16 @@ "node": ">=8.0" } }, - "node_modules/trim-newlines": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/trim-newlines/-/trim-newlines-3.0.1.tgz", - "integrity": "sha512-c1PTsA3tYrIsLGkJkzHF+w9F2EyxfXGo4UyJc4pFL++FMjnq0HJS69T3M7d//gKrFKwy429bouPescbjecU+Zw==", + "node_modules/ts-api-utils": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-1.0.3.tgz", + "integrity": "sha512-wNMeqtMz5NtwpT/UZGY5alT+VoKdSsOOP/kqHFcUW1P/VRhH2wJ48+DN2WwUliNbQ976ETwDL0Ifd2VVvgonvg==", "dev": true, "engines": { - "node": ">=8" + "node": ">=16.13.0" + }, + "peerDependencies": { + "typescript": ">=4.2.0" } }, "node_modules/tslib": { @@ -7034,21 +7895,6 @@ "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", "dev": true }, - "node_modules/tsutils": { - "version": "3.21.0", - "resolved": "https://registry.npmjs.org/tsutils/-/tsutils-3.21.0.tgz", - "integrity": "sha512-mHKK3iUXL+3UF6xL5k0PEhKRUBKPBCv/+RkEOpjRWxxx27KKRBmmA60A9pgOUvMi8GKhRMPEmjBRPzs2W7O1OA==", - "dev": true, - "dependencies": { - "tslib": "^1.8.1" - }, - "engines": { - "node": ">= 6" - }, - "peerDependencies": { - "typescript": ">=2.8.0 || >= 3.2.0-dev || >= 3.3.0-dev || >= 3.4.0-dev || >= 3.5.0-dev || >= 3.6.0-dev || >= 3.6.0-beta || >= 3.7.0-dev || >= 3.7.0-beta" - } - }, "node_modules/type-check": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", @@ -7156,23 +8002,48 @@ "is-typedarray": "^1.0.0" } }, + "node_modules/types-eslintrc": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/types-eslintrc/-/types-eslintrc-1.0.3.tgz", + "integrity": "sha512-zKTR6aKHEudQpl+JoZjS3qh0B5IzSpQK/BCpYBECujcnKtqL87DJJ1sJKe5B8k/y8/UJ5sukq42QDvlaJyCO2w==", + "dev": true, + "dependencies": { + "types-json": "^1.2.2" + } + }, + "node_modules/types-json": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/types-json/-/types-json-1.2.2.tgz", + "integrity": "sha512-VfVLISHypS7ayIHvhacOESOTib4Sm4mAhnsgR8fzQdGp89YoBwMqvGmqENjtYehUQzgclT+7NafpEXkK/MHKwA==", + "dev": true + }, + "node_modules/types-pkg-json": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/types-pkg-json/-/types-pkg-json-1.2.1.tgz", + "integrity": "sha512-Wj75lCkPwfj1BhmaJxMPpTQj9YGpihjs3WICigt1IjTAswr7zPXP0iJYPZjU0Rw/IriODhMJjAImkCIxt9KeuQ==", + "dev": true, + "dependencies": { + "types-eslintrc": "^1.0.3", + "types-json": "^1.2.2" + } + }, "node_modules/typescript": { - "version": "5.0.4", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.0.4.tgz", - "integrity": "sha512-cW9T5W9xY37cc+jfEnaUvX91foxtHkza3Nw3wkoF4sSlKn0MONdkdEndig/qPBWXNkmplh3NzayQzCiHM4/hqw==", + "version": "5.2.2", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.2.2.tgz", + "integrity": "sha512-mI4WrpHsbCIcwT9cF4FZvr80QUeKvsUsUvKDoR+X/7XHQH98xYD8YHZg7ANtz2GtZt/CBq2QJ0thkGJMHfqc1w==", "dev": true, "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" }, "engines": { - "node": ">=12.20" + "node": ">=14.17" } }, "node_modules/ufo": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/ufo/-/ufo-1.2.0.tgz", - "integrity": "sha512-RsPyTbqORDNDxqAdQPQBpgqhWle1VcTSou/FraClYlHf6TZnQcGslpLcAphNR+sQW4q5lLWLbOsRlh9j24baQg==", + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/ufo/-/ufo-1.3.2.tgz", + "integrity": "sha512-o+ORpgGwaYQXgqGDwd+hkS4PuZ3QnmqMMxRuajK/a38L6fTpcE5GPIfrf+L/KemFzfUpeUQc1rRS1iDBozvnFA==", "dev": true }, "node_modules/unbox-primitive": { @@ -7191,27 +8062,36 @@ } }, "node_modules/undici": { - "version": "5.22.0", - "resolved": "https://registry.npmjs.org/undici/-/undici-5.22.0.tgz", - "integrity": "sha512-fR9RXCc+6Dxav4P9VV/sp5w3eFiSdOjJYsbtWfd4s5L5C4ogyuVpdKIVHeW0vV1MloM65/f7W45nR9ZxwVdyiA==", + "version": "5.26.0", + "resolved": "https://registry.npmjs.org/undici/-/undici-5.26.0.tgz", + "integrity": "sha512-MLqGMyaJk2ubSl7FrmWuV7ZOsYWmdF7gcBHDRxm4AR8NoodQhgy3vO/D1god79HoetxR0uAeVNB65yj2lNRQnQ==", "dev": true, "dependencies": { - "busboy": "^1.6.0" + "@fastify/busboy": "^2.0.0" }, "engines": { "node": ">=14.0" } }, + "node_modules/undici-types": { + "version": "5.26.5", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz", + "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==", + "dev": true + }, "node_modules/unique-string": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/unique-string/-/unique-string-2.0.0.tgz", - "integrity": "sha512-uNaeirEPvpZWSgzwsPGtU2zVSTrn/8L5q/IexZmH0eH6SA73CmAA5U4GwORTxQAZs95TAXLNqeLoPPNO5gZfWg==", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/unique-string/-/unique-string-3.0.0.tgz", + "integrity": "sha512-VGXBUVwxKMBUznyffQweQABPRRW1vHZAbadFZud4pLFAqRGvv/96vafgjWFqzourzr8YonlQiPgH0YCJfawoGQ==", "dev": true, "dependencies": { - "crypto-random-string": "^2.0.0" + "crypto-random-string": "^4.0.0" }, "engines": { - "node": ">=8" + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/universalify": { @@ -7223,81 +8103,53 @@ "node": ">= 4.0.0" } }, - "node_modules/update-notifier": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/update-notifier/-/update-notifier-5.1.0.tgz", - "integrity": "sha512-ItnICHbeMh9GqUy31hFPrD1kcuZ3rpxDZbf4KUDavXwS0bW5m7SLbDQpGX3UYr072cbrF5hFUs3r5tUsPwjfHw==", + "node_modules/untildify": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/untildify/-/untildify-4.0.0.tgz", + "integrity": "sha512-KK8xQ1mkzZeg9inewmFVDNkg3l5LUhoq9kN6iWYB/CC9YMG8HA+c1Q8HwDe6dEX7kErrEVNVBO3fWsVq5iDgtw==", "dev": true, - "dependencies": { - "boxen": "^5.0.0", - "chalk": "^4.1.0", - "configstore": "^5.0.1", - "has-yarn": "^2.1.0", - "import-lazy": "^2.1.0", - "is-ci": "^2.0.0", - "is-installed-globally": "^0.4.0", - "is-npm": "^5.0.0", - "is-yarn-global": "^0.3.0", - "latest-version": "^5.1.0", - "pupa": "^2.1.1", - "semver": "^7.3.4", - "semver-diff": "^3.1.1", - "xdg-basedir": "^4.0.0" - }, "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/yeoman/update-notifier?sponsor=1" + "node": ">=8" } }, - "node_modules/update-notifier/node_modules/global-dirs": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/global-dirs/-/global-dirs-3.0.1.tgz", - "integrity": "sha512-NBcGGFbBA9s1VzD41QXDG+3++t9Mn5t1FpLdhESY6oKY4gYTFpX4wO3sqGUa0Srjtbfj3szX0RnemmrVRUdULA==", + "node_modules/update-notifier": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/update-notifier/-/update-notifier-6.0.2.tgz", + "integrity": "sha512-EDxhTEVPZZRLWYcJ4ZXjGFN0oP7qYvbXWzEgRm/Yql4dHX5wDbvh89YHP6PK1lzZJYrMtXUuZZz8XGK+U6U1og==", "dev": true, "dependencies": { - "ini": "2.0.0" + "boxen": "^7.0.0", + "chalk": "^5.0.1", + "configstore": "^6.0.0", + "has-yarn": "^3.0.0", + "import-lazy": "^4.0.0", + "is-ci": "^3.0.1", + "is-installed-globally": "^0.4.0", + "is-npm": "^6.0.0", + "is-yarn-global": "^0.4.0", + "latest-version": "^7.0.0", + "pupa": "^3.1.0", + "semver": "^7.3.7", + "semver-diff": "^4.0.0", + "xdg-basedir": "^5.1.0" }, "engines": { - "node": ">=10" + "node": ">=14.16" }, "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/update-notifier/node_modules/import-lazy": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/import-lazy/-/import-lazy-2.1.0.tgz", - "integrity": "sha512-m7ZEHgtw69qOGw+jwxXkHlrlIPdTGkyh66zXZ1ajZbxkDBNjSY/LGbmjc7h0s2ELsUDTAhFr55TrPSSqJGPG0A==", - "dev": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/update-notifier/node_modules/ini": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ini/-/ini-2.0.0.tgz", - "integrity": "sha512-7PnF4oN3CvZF23ADhA5wRaYEQpJ8qygSkbtTXWBeXWXmEVRXK+1ITciHWwHhsjv1TmW0MgacIv6hEi5pX5NQdA==", - "dev": true, - "engines": { - "node": ">=10" + "url": "https://github.com/yeoman/update-notifier?sponsor=1" } }, - "node_modules/update-notifier/node_modules/is-installed-globally": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/is-installed-globally/-/is-installed-globally-0.4.0.tgz", - "integrity": "sha512-iwGqO3J21aaSkC7jWnHP/difazwS7SFeIqxv6wEtLU8Y5KlzFTjyqcSIT0d8s4+dDhKytsk9PJZ2BkS5eZwQRQ==", + "node_modules/update-notifier/node_modules/chalk": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.3.0.tgz", + "integrity": "sha512-dLitG79d+GV1Nb/VYcCDFivJeK1hiukt9QjRNVOsUtTy1rR1YJsmpGGTZ3qJos+uw7WmWF4wUwBd9jxjocFC2w==", "dev": true, - "dependencies": { - "global-dirs": "^3.0.0", - "is-path-inside": "^3.0.2" - }, "engines": { - "node": ">=10" + "node": "^12.17.0 || ^14.13 || >=16.0.0" }, "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "url": "https://github.com/chalk/chalk?sponsor=1" } }, "node_modules/uri-js": { @@ -7309,17 +8161,11 @@ "punycode": "^2.1.0" } }, - "node_modules/url-parse-lax": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/url-parse-lax/-/url-parse-lax-3.0.0.tgz", - "integrity": "sha512-NjFKA0DidqPa5ciFcSrXnAltTtzz84ogy+NebPvfEgAck0+TNg4UJ4IN+fB7zRZfbgUf0syOo9MDxFkDSMuFaQ==", - "dev": true, - "dependencies": { - "prepend-http": "^2.0.0" - }, - "engines": { - "node": ">=4" - } + "node_modules/util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", + "dev": true }, "node_modules/vali-date": { "version": "1.0.0", @@ -7359,14 +8205,14 @@ } }, "node_modules/vite": { - "version": "4.3.3", - "resolved": "https://registry.npmjs.org/vite/-/vite-4.3.3.tgz", - "integrity": "sha512-MwFlLBO4udZXd+VBcezo3u8mC77YQk+ik+fbc0GZWGgzfbPP+8Kf0fldhARqvSYmtIWoAJ5BXPClUbMTlqFxrA==", + "version": "4.4.11", + "resolved": "https://registry.npmjs.org/vite/-/vite-4.4.11.tgz", + "integrity": "sha512-ksNZJlkcU9b0lBwAGZGGaZHCMqHsc8OpgtoYhsQ4/I2v5cnpmmmqe5pM4nv/4Hn6G/2GhTdj0DhZh2e+Er1q5A==", "dev": true, "dependencies": { - "esbuild": "^0.17.5", - "postcss": "^8.4.23", - "rollup": "^3.21.0" + "esbuild": "^0.18.10", + "postcss": "^8.4.27", + "rollup": "^3.27.1" }, "bin": { "vite": "bin/vite.js" @@ -7374,12 +8220,16 @@ "engines": { "node": "^14.18.0 || >=16.0.0" }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, "optionalDependencies": { "fsevents": "~2.3.2" }, "peerDependencies": { "@types/node": ">= 14", "less": "*", + "lightningcss": "^1.21.0", "sass": "*", "stylus": "*", "sugarss": "*", @@ -7392,6 +8242,9 @@ "less": { "optional": true }, + "lightningcss": { + "optional": true + }, "sass": { "optional": true }, @@ -7407,17 +8260,17 @@ } }, "node_modules/vite-node": { - "version": "0.30.1", - "resolved": "https://registry.npmjs.org/vite-node/-/vite-node-0.30.1.tgz", - "integrity": "sha512-vTikpU/J7e6LU/8iM3dzBo8ZhEiKZEKRznEMm+mJh95XhWaPrJQraT/QsT2NWmuEf+zgAoMe64PKT7hfZ1Njmg==", + "version": "0.34.6", + "resolved": "https://registry.npmjs.org/vite-node/-/vite-node-0.34.6.tgz", + "integrity": "sha512-nlBMJ9x6n7/Amaz6F3zJ97EBwR2FkzhBRxF5e+jE6LA3yi6Wtc2lyTij1OnDMIr34v5g/tVQtsVAzhT0jc5ygA==", "dev": true, "dependencies": { "cac": "^6.7.14", "debug": "^4.3.4", - "mlly": "^1.2.0", - "pathe": "^1.1.0", + "mlly": "^1.4.0", + "pathe": "^1.1.1", "picocolors": "^1.0.0", - "vite": "^3.0.0 || ^4.0.0" + "vite": "^3.0.0 || ^4.0.0 || ^5.0.0-0" }, "bin": { "vite-node": "vite-node.mjs" @@ -7426,40 +8279,38 @@ "node": ">=v14.18.0" }, "funding": { - "url": "https://github.com/sponsors/antfu" + "url": "https://opencollective.com/vitest" } }, "node_modules/vitest": { - "version": "0.30.1", - "resolved": "https://registry.npmjs.org/vitest/-/vitest-0.30.1.tgz", - "integrity": "sha512-y35WTrSTlTxfMLttgQk4rHcaDkbHQwDP++SNwPb+7H8yb13Q3cu2EixrtHzF27iZ8v0XCciSsLg00RkPAzB/aA==", + "version": "0.34.6", + "resolved": "https://registry.npmjs.org/vitest/-/vitest-0.34.6.tgz", + "integrity": "sha512-+5CALsOvbNKnS+ZHMXtuUC7nL8/7F1F2DnHGjSsszX8zCjWSSviphCb/NuS9Nzf4Q03KyyDRBAXhF/8lffME4Q==", "dev": true, "dependencies": { - "@types/chai": "^4.3.4", + "@types/chai": "^4.3.5", "@types/chai-subset": "^1.3.3", "@types/node": "*", - "@vitest/expect": "0.30.1", - "@vitest/runner": "0.30.1", - "@vitest/snapshot": "0.30.1", - "@vitest/spy": "0.30.1", - "@vitest/utils": "0.30.1", - "acorn": "^8.8.2", + "@vitest/expect": "0.34.6", + "@vitest/runner": "0.34.6", + "@vitest/snapshot": "0.34.6", + "@vitest/spy": "0.34.6", + "@vitest/utils": "0.34.6", + "acorn": "^8.9.0", "acorn-walk": "^8.2.0", "cac": "^6.7.14", - "chai": "^4.3.7", - "concordance": "^5.0.4", + "chai": "^4.3.10", "debug": "^4.3.4", "local-pkg": "^0.4.3", - "magic-string": "^0.30.0", - "pathe": "^1.1.0", + "magic-string": "^0.30.1", + "pathe": "^1.1.1", "picocolors": "^1.0.0", - "source-map": "^0.6.1", - "std-env": "^3.3.2", + "std-env": "^3.3.3", "strip-literal": "^1.0.1", - "tinybench": "^2.4.0", - "tinypool": "^0.4.0", - "vite": "^3.0.0 || ^4.0.0", - "vite-node": "0.30.1", + "tinybench": "^2.5.0", + "tinypool": "^0.7.0", + "vite": "^3.1.0 || ^4.0.0 || ^5.0.0-0", + "vite-node": "0.34.6", "why-is-node-running": "^2.2.2" }, "bin": { @@ -7469,7 +8320,7 @@ "node": ">=v14.18.0" }, "funding": { - "url": "https://github.com/sponsors/antfu" + "url": "https://opencollective.com/vitest" }, "peerDependencies": { "@edge-runtime/vm": "*", @@ -7520,13 +8371,13 @@ "node": ">=12" } }, - "node_modules/well-known-symbols": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/well-known-symbols/-/well-known-symbols-2.0.0.tgz", - "integrity": "sha512-ZMjC3ho+KXo0BfJb7JgtQ5IBuvnShdlACNkKkdsqBmYw3bPAaJfPeYUo6tLUaT5tG/Gkh7xkpBhKRQ9e7pyg9Q==", + "node_modules/wcwidth": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/wcwidth/-/wcwidth-1.0.1.tgz", + "integrity": "sha512-XHPEwS0q6TaxcvG85+8EYkbiCux2XtWG2mkc47Ng2A77BQu9+DqIOJldST4HgPkuea7dvKSj5VgX3P1d4rW8Tg==", "dev": true, - "engines": { - "node": ">=6" + "dependencies": { + "defaults": "^1.0.3" } }, "node_modules/which": { @@ -7596,15 +8447,68 @@ } }, "node_modules/widest-line": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/widest-line/-/widest-line-3.1.0.tgz", - "integrity": "sha512-NsmoXalsWVDMGupxZ5R08ka9flZjjiLvHVAWYOKtiKM8ujtZWr9cRffak+uSE48+Ob8ObalXpwyeUiyDD6QFgg==", + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/widest-line/-/widest-line-4.0.1.tgz", + "integrity": "sha512-o0cyEG0e8GPzT4iGHphIOh0cJOV8fivsXxddQasHPHfoZf1ZexrfeA21w2NaEN1RHE+fXlfISmOE8R9N3u3Qig==", "dev": true, "dependencies": { - "string-width": "^4.0.0" + "string-width": "^5.0.1" }, "engines": { - "node": ">=8" + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/widest-line/node_modules/ansi-regex": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.0.1.tgz", + "integrity": "sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/widest-line/node_modules/emoji-regex": { + "version": "9.2.2", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", + "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", + "dev": true + }, + "node_modules/widest-line/node_modules/string-width": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", + "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", + "dev": true, + "dependencies": { + "eastasianwidth": "^0.2.0", + "emoji-regex": "^9.2.2", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/widest-line/node_modules/strip-ansi": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", + "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", + "dev": true, + "dependencies": { + "ansi-regex": "^6.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" } }, "node_modules/wrap-ansi": { @@ -7682,12 +8586,15 @@ } }, "node_modules/xdg-basedir": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/xdg-basedir/-/xdg-basedir-4.0.0.tgz", - "integrity": "sha512-PSNhEJDejZYV7h50BohL09Er9VaIefr2LMAf3OEmpCkjOi34eYyQYAXUTjEQtZJTKcF0E2UKTh+osDLsgNim9Q==", + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/xdg-basedir/-/xdg-basedir-5.1.0.tgz", + "integrity": "sha512-GCPAHLvrIH13+c0SuacwvRYj2SxJXQ4kaVTT5xgL3kPrz56XxkF21IGhjSE1+W0aw7gpBWRGXLCPnPby6lSpmQ==", "dev": true, "engines": { - "node": ">=8" + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/yallist": { @@ -7696,24 +8603,6 @@ "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", "dev": true }, - "node_modules/yaml": { - "version": "1.10.2", - "resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.2.tgz", - "integrity": "sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==", - "dev": true, - "engines": { - "node": ">= 6" - } - }, - "node_modules/yargs-parser": { - "version": "20.2.9", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.9.tgz", - "integrity": "sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==", - "dev": true, - "engines": { - "node": ">=10" - } - }, "node_modules/yocto-queue": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", @@ -7745,6 +8634,16 @@ "optionalDependencies": { "commander": "^9.4.1" } + }, + "node_modules/z-schema/node_modules/commander": { + "version": "9.5.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-9.5.0.tgz", + "integrity": "sha512-KRs7WVDKg86PWiuAqhDrAQnTXZKraVcCc6vFdL14qrZ/DcWwuRo7VoiYXalXO7S5GKpqYiVEwCbgFDfxNHKJBQ==", + "dev": true, + "optional": true, + "engines": { + "node": "^12.20.0 || >=14" + } } } } diff --git a/packages/qwik-speak/package.json b/packages/qwik-speak/package.json index 8a233c4..b42f92c 100644 --- a/packages/qwik-speak/package.json +++ b/packages/qwik-speak/package.json @@ -17,24 +17,24 @@ "qwik-speak-extract": "./extract/cli.js" }, "peerDependencies": { - "@builder.io/qwik": ">=1.1.4" + "@builder.io/qwik": ">=1.2.18" }, "devDependencies": { - "@builder.io/qwik": "1.1.4", - "@microsoft/api-documenter": "^7.22.3", - "@microsoft/api-extractor": "^7.36.1", - "@types/eslint": "8.37.0", - "@types/node": "^18.16.1", - "@typescript-eslint/eslint-plugin": "5.59.1", - "@typescript-eslint/parser": "5.59.1", - "eslint": "8.39.0", - "eslint-plugin-qwik": "1.1.4", - "np": "^7.7.0", + "@builder.io/qwik": "1.2.18", + "@microsoft/api-documenter": "^7.23.12", + "@microsoft/api-extractor": "^7.38.3", + "@types/eslint": "8.44.4", + "@types/node": "^20.8.4", + "@typescript-eslint/eslint-plugin": "6.7.5", + "@typescript-eslint/parser": "6.7.5", + "eslint": "8.51.0", + "eslint-plugin-qwik": "1.2.18", + "np": "^8.0.4", "rollup-plugin-add-shebang": "^0.3.1", - "typescript": "5.0.4", - "undici": "5.22.0", - "vite": "4.3.3", - "vitest": "^0.30.1" + "typescript": "5.2.2", + "undici": "5.26.0", + "vite": "4.4.11", + "vitest": "^0.34.6" }, "main": "./lib/index.qwik.cjs", "module": "./lib/index.qwik.mjs", diff --git a/packages/qwik-speak/src/core.ts b/packages/qwik-speak/src/core.ts index 2d23612..05ac5f9 100644 --- a/packages/qwik-speak/src/core.ts +++ b/packages/qwik-speak/src/core.ts @@ -1,4 +1,4 @@ -import { isBrowser, isDev, isServer } from '@builder.io/qwik/build'; +import { isDev, isServer } from '@builder.io/qwik/build'; import type { Translation, SpeakState, LoadTranslationFn } from './types'; import { _speakContext } from './context'; @@ -7,7 +7,7 @@ import { logWarn } from './log'; const cache: Record<string, Promise<any>> = {}; /** - * In SPA mode, cache the results + * Cache the requests on server and on client in SPA mode */ export const memoize = (fn: LoadTranslationFn) => { return (...args: [string, string]) => { @@ -23,7 +23,7 @@ export const memoize = (fn: LoadTranslationFn) => { * Load translations when: * - on server * - or runtime assets - * Assets are not serialized + * runtimeAssets are serialized */ export const loadTranslations = async ( ctx: SpeakState, @@ -59,8 +59,8 @@ export const loadTranslations = async ( for (const lang of resolvedLangs) { let tasks: Promise<any>[]; - // Cache requests on client in prod mode - if (!isDev && isBrowser) { + // Cache requests prod mode + if (!isDev) { const memoized = memoize(translationFn.loadTranslation$); tasks = resolvedAssets.map(asset => memoized(lang, asset)); } else { @@ -78,12 +78,12 @@ export const loadTranslations = async ( if (isServer) { // On server: // - assets & runtimeAssets in shared context - // - runtimeAssets in context (must be serialized to be passed to the client) + // - runtimeAssets in context as well (must be serialized to be passed to the client) if (assets?.includes(data.asset)) { Object.assign(_translation[lang], data.source); } else { Object.assign(_translation[lang], data.source); - // Serialize whether runtimeAssets + // Serialize runtimeAssets Object.assign(translation[lang], data.source); } } else { diff --git a/packages/qwik-speak/src/index.ts b/packages/qwik-speak/src/index.ts index d850957..546acf4 100644 --- a/packages/qwik-speak/src/index.ts +++ b/packages/qwik-speak/src/index.ts @@ -8,9 +8,9 @@ export type { LoadTranslationFn, RewriteRouteOption } from './types'; -export type { QwikSpeakProps } from './qwik-speak-component'; -export type { SpeakProps } from './speak-component'; -export type { TranslatePathFn } from './use-translate-path'; +export type { QwikSpeakProps } from './use-qwik-speak'; +export type { SpeakProps } from './use-speak'; +export type { TranslatePathFn } from './translate-path'; export type { InlinePluralFn } from './inline-plural'; export type { InlineTranslateFn } from './inline-translate'; export type { FormatDateFn } from './use-format-date'; @@ -18,13 +18,15 @@ export type { FormatNumberFn } from './use-format-number'; export type { RelativeTimeFn } from './use-relative-time'; export type { DisplayNameFn } from './use-display-name'; // Components -export { QwikSpeakProvider } from './qwik-speak-component'; -export { QwikSpeakInline } from './qwik-speak-inline-component'; -export { Speak } from './speak-component'; +export { QwikSpeakMockProvider } from './use-qwik-speak'; // Inline functions export { inlineTranslate } from './inline-translate'; export { inlinePlural } from './inline-plural'; +// Functions +export { translatePath } from './translate-path'; // Use functions +export { useQwikSpeak } from './use-qwik-speak'; +export { useSpeak } from './use-speak'; export { useFormatNumber } from './use-format-number'; export { useFormatDate } from './use-format-date'; export { useRelativeTime } from './use-relative-time'; @@ -34,4 +36,3 @@ export { useSpeakLocale, useSpeakConfig, } from './use-functions'; -export { useTranslatePath } from './use-translate-path'; diff --git a/packages/qwik-speak/src/qwik-speak-inline-component.tsx b/packages/qwik-speak/src/qwik-speak-inline-component.tsx deleted file mode 100644 index 05ab198..0000000 --- a/packages/qwik-speak/src/qwik-speak-inline-component.tsx +++ /dev/null @@ -1,42 +0,0 @@ -import { component$, Slot, useVisibleTask$ } from '@builder.io/qwik'; -import { isDev } from '@builder.io/qwik/build'; - -import { useSpeakContext } from './use-functions'; -import { _speakContext, setGetLangFn, } from './context'; - -export const QwikSpeakInline = component$(() => { - const ctx = useSpeakContext(); - - useVisibleTask$(() => { - const { locale, translation, config } = ctx; - - // In dev mode, send lang from client to the server - if (isDev) { - console.debug( - '%cQwik Speak Inline', - 'background: #0c75d2; color: white; padding: 2px 3px; border-radius: 2px; font-size: 0.8em;', - 'Ready' - ); - if (import.meta.hot) { - import.meta.hot.send('qwik-speak:lang', { msg: locale.lang }); - } - } - - // Create client context - _speakContext.translation = translation; - _speakContext.config = config; - // Set the getLang function to use the current lang - setGetLangFn(() => locale.lang); - - if (isDev) { - console.debug( - '%cQwik Speak Inline', - 'background: #0c75d2; color: white; padding: 2px 3px; border-radius: 2px; font-size: 0.8em;', - 'Client context', - _speakContext - ); - } - }, { strategy: 'document-ready' }); - - return <Slot />; -}); diff --git a/packages/qwik-speak/src/use-translate-path.ts b/packages/qwik-speak/src/translate-path.ts similarity index 87% rename from packages/qwik-speak/src/use-translate-path.ts rename to packages/qwik-speak/src/translate-path.ts index a512222..39cb892 100644 --- a/packages/qwik-speak/src/use-translate-path.ts +++ b/packages/qwik-speak/src/translate-path.ts @@ -1,5 +1,7 @@ import { isDev } from '@builder.io/qwik/build'; -import { useSpeakContext } from './use-functions'; + +import { _speakContext, getLang } from './context'; +import { type SpeakState } from './types'; import { logWarn } from './log'; export type TranslatePathFn = { @@ -19,11 +21,11 @@ export type TranslatePathFn = { (pathname: string[], lang?: string): string[]; }; -export const useTranslatePath = (): TranslatePathFn => { - const ctx = useSpeakContext(); +export const translatePath = (): TranslatePathFn => { + const currentLang = getLang(); const normalizePath = (pathname: string) => { - const { config } = ctx; + const { config } = _speakContext as SpeakState; const source = config.rewriteRoutes?.find(rewrite => ( pathname === `/${rewrite.prefix}` || @@ -50,7 +52,7 @@ export const useTranslatePath = (): TranslatePathFn => { } const rewritePath = (pathname: string, prefix?: string) => { - const { config } = ctx; + const { config } = _speakContext as SpeakState; let splitted = pathname.split('/'); const destination = config.rewriteRoutes?.find( @@ -84,9 +86,7 @@ export const useTranslatePath = (): TranslatePathFn => { } const translateOne = (pathname: string, lang?: string) => { - const { locale } = ctx; - - lang ??= locale.lang; + lang ??= currentLang; const normalized = normalizePath(pathname); const rewrote = rewritePath(normalized, lang); @@ -94,14 +94,14 @@ export const useTranslatePath = (): TranslatePathFn => { }; const translate = (pathname: string | string[], lang?: string) => { - const { locale, config } = ctx; + const { config } = _speakContext as SpeakState; if (!config.rewriteRoutes) { - if (isDev) logWarn(`SpeakConfig: rewriteRoutes not found`); + if (isDev) logWarn(`translatePath: rewriteRoutes not found`); return pathname; } - lang ??= locale.lang; + lang ??= currentLang; if (Array.isArray(pathname)) { return pathname.map(path => translateOne(path, lang)); diff --git a/packages/qwik-speak/src/qwik-speak-component.tsx b/packages/qwik-speak/src/use-qwik-speak.tsx similarity index 59% rename from packages/qwik-speak/src/qwik-speak-component.tsx rename to packages/qwik-speak/src/use-qwik-speak.tsx index 3de27db..43edad0 100644 --- a/packages/qwik-speak/src/qwik-speak-component.tsx +++ b/packages/qwik-speak/src/use-qwik-speak.tsx @@ -1,5 +1,5 @@ -import { $, component$, getLocale, Slot, useContextProvider, useServerData, useTask$ } from '@builder.io/qwik'; -import { isDev } from '@builder.io/qwik/build'; +import { $, component$, getLocale, Slot, useContextProvider, useOnDocument, useServerData, useTask$ } from '@builder.io/qwik'; +import { isDev, isServer } from '@builder.io/qwik/build'; import type { SpeakConfig, SpeakLocale, SpeakState, TranslationFn } from './types'; import { _speakContext, setGetLangFn, SpeakContext } from './context'; @@ -26,9 +26,10 @@ export interface QwikSpeakProps { } /** - * Create and provide the Speak context + * Create and provide the Speak context. + * Translations will be available in the whole app */ -export const QwikSpeakProvider = component$((props: QwikSpeakProps) => { +export const useQwikSpeak = (props: QwikSpeakProps) => { // Get Qwik locale const lang = useServerData<string>('locale'); @@ -74,10 +75,55 @@ export const QwikSpeakProvider = component$((props: QwikSpeakProps) => { // Create context useContextProvider(SpeakContext, state); - // Called the first time when the component mounts + // Load shared translations useTask$(async () => { - await loadTranslations(state, config.assets, config.runtimeAssets, props.langs); + // Drop code on client + if (isServer) { + await loadTranslations(state, config.assets, config.runtimeAssets, props.langs); + } }); + // Resume shared context on client + const resumeContext$ = $(() => { + const { locale, translation, config } = state; + + // Create client context + _speakContext.translation = translation; + _speakContext.config = config; + _speakContext.locale = locale; + // Set the getLang function to use the current lang + setGetLangFn(() => locale.lang); + + if (isDev) { + console.debug( + '%cQwik Speak Inline', + 'background: #0c75d2; color: white; padding: 2px 3px; border-radius: 2px; font-size: 0.8em;', + 'Client context', + _speakContext + ); + } + + // In dev mode, send lang from client to the server + if (isDev) { + console.debug( + '%cQwik Speak Inline', + 'background: #0c75d2; color: white; padding: 2px 3px; border-radius: 2px; font-size: 0.8em;', + 'Ready' + ); + if (import.meta.hot) { + import.meta.hot.send('qwik-speak:lang', { msg: locale.lang }); + } + } + }); + + useOnDocument('qinit', resumeContext$); +}; + +/** + * Create and provide the Speak context to test enviroments + */ +export const QwikSpeakMockProvider = component$<QwikSpeakProps>(props => { + useQwikSpeak(props); + return <Slot />; }); diff --git a/packages/qwik-speak/src/speak-component.tsx b/packages/qwik-speak/src/use-speak.ts similarity index 74% rename from packages/qwik-speak/src/speak-component.tsx rename to packages/qwik-speak/src/use-speak.ts index cc23058..ae6084d 100644 --- a/packages/qwik-speak/src/speak-component.tsx +++ b/packages/qwik-speak/src/use-speak.ts @@ -1,4 +1,4 @@ -import { component$, Slot, useTask$ } from '@builder.io/qwik'; +import { useTask$ } from '@builder.io/qwik'; import { isBrowser, isDev } from '@builder.io/qwik/build'; import { useSpeakContext } from './use-functions'; @@ -25,22 +25,22 @@ export interface SpeakProps { * Add scoped translation data to the context. * Translations will only be available in child components */ -export const Speak = component$((props: SpeakProps) => { +export const useSpeak = (props: SpeakProps) => { const ctx = useSpeakContext(); const { config } = ctx; - // Called the first time when the component mounts + // Load scoped translations useTask$(async () => { if (isDev) { - if (!props.assets && !props.runtimeAssets) logWarn('Speak component: no assets provided'); + if (!props.assets && !props.runtimeAssets) logWarn('useSpeak: no assets provided'); const duplicateAsset = config.assets?.find(asset => props.assets?.includes(asset)); if (duplicateAsset) { - logWarn(`Speak component: duplicate assets '${duplicateAsset}'`); + logWarn(`useSpeak: duplicate assets '${duplicateAsset}'`); } const duplicateRuntimeAsset = config.runtimeAssets?.find(asset => props.runtimeAssets?.includes(asset)); if (duplicateRuntimeAsset) { - logWarn(`Speak component: duplicate runtimeAssets '${duplicateRuntimeAsset}'`); + logWarn(`useSpeak: duplicate runtimeAssets '${duplicateRuntimeAsset}'`); } } @@ -55,6 +55,4 @@ export const Speak = component$((props: SpeakProps) => { ); } }); - - return <Slot />; -}); +}; diff --git a/packages/qwik-speak/tests/inline-translate.test.tsx b/packages/qwik-speak/tests/inline-translate.test.tsx index 08223b8..d3b7a1d 100644 --- a/packages/qwik-speak/tests/inline-translate.test.tsx +++ b/packages/qwik-speak/tests/inline-translate.test.tsx @@ -3,7 +3,7 @@ import { component$, useSignal, useTask$, $ } from '@builder.io/qwik'; import { test, describe, expect } from 'vitest'; import { t } from '../src/inline-translate'; -import { QwikSpeakProvider } from '../src/qwik-speak-component'; +import { QwikSpeakMockProvider } from '../src/use-qwik-speak'; import { config, translationFnStub } from './config'; const MyComponent = () => { @@ -33,9 +33,9 @@ describe('inlineTranslate function', async () => { const { screen, render } = await createDOM(); await render( - <QwikSpeakProvider config={config} translationFn={translationFnStub} locale={config.defaultLocale}> + <QwikSpeakMockProvider config={config} translationFn={translationFnStub} locale={config.defaultLocale}> <TestComponent /> - </QwikSpeakProvider> + </QwikSpeakMockProvider> ); test('translate', () => { diff --git a/packages/qwik-speak/tests/use-display-name.test.tsx b/packages/qwik-speak/tests/use-display-name.test.tsx index 03140b1..4043771 100644 --- a/packages/qwik-speak/tests/use-display-name.test.tsx +++ b/packages/qwik-speak/tests/use-display-name.test.tsx @@ -3,7 +3,7 @@ import { component$ } from '@builder.io/qwik'; import { test, describe, expect } from 'vitest'; import { useDisplayName } from '../src/use-display-name'; -import { QwikSpeakProvider } from '../src/qwik-speak-component'; +import { QwikSpeakMockProvider } from '../src/use-qwik-speak'; import { config } from './config'; const TestComponent = component$(() => { @@ -21,9 +21,9 @@ describe('useDisplayName function', async () => { const { screen, render } = await createDOM(); await render( - <QwikSpeakProvider config={config} locale={config.defaultLocale}> + <QwikSpeakMockProvider config={config} locale={config.defaultLocale}> <TestComponent /> - </QwikSpeakProvider> + </QwikSpeakMockProvider> ); test('display', () => { diff --git a/packages/qwik-speak/tests/use-format-date.test.tsx b/packages/qwik-speak/tests/use-format-date.test.tsx index 586494b..6a8fef6 100644 --- a/packages/qwik-speak/tests/use-format-date.test.tsx +++ b/packages/qwik-speak/tests/use-format-date.test.tsx @@ -3,7 +3,7 @@ import { component$ } from '@builder.io/qwik'; import { test, describe, expect } from 'vitest'; import { useFormatDate } from '../src/use-format-date'; -import { QwikSpeakProvider } from '../src/qwik-speak-component'; +import { QwikSpeakMockProvider } from '../src/use-qwik-speak'; import { config } from './config'; const TestComponent = component$(() => { @@ -22,9 +22,9 @@ describe('useFormatDate function', async () => { const { screen, render } = await createDOM(); await render( - <QwikSpeakProvider config={config} locale={config.defaultLocale}> + <QwikSpeakMockProvider config={config} locale={config.defaultLocale}> <TestComponent /> - </QwikSpeakProvider> + </QwikSpeakMockProvider> ); test('format', () => { diff --git a/packages/qwik-speak/tests/use-format-number.test.tsx b/packages/qwik-speak/tests/use-format-number.test.tsx index 6436571..99e9fe6 100644 --- a/packages/qwik-speak/tests/use-format-number.test.tsx +++ b/packages/qwik-speak/tests/use-format-number.test.tsx @@ -3,7 +3,7 @@ import { component$ } from '@builder.io/qwik'; import { test, describe, expect } from 'vitest'; import { useFormatNumber } from '../src/use-format-number'; -import { QwikSpeakProvider } from '../src/qwik-speak-component'; +import { QwikSpeakMockProvider } from '../src/use-qwik-speak'; import { config } from './config'; import { useSpeakLocale } from '../src/use-functions'; @@ -27,9 +27,9 @@ describe('useFormatNumber function', async () => { const { screen, render } = await createDOM(); await render( - <QwikSpeakProvider config={config} locale={config.defaultLocale}> + <QwikSpeakMockProvider config={config} locale={config.defaultLocale}> <TestComponent /> - </QwikSpeakProvider> + </QwikSpeakMockProvider> ); test('format', () => { diff --git a/packages/qwik-speak/tests/use-plural.test.tsx b/packages/qwik-speak/tests/use-plural.test.tsx index a6d6dc5..38a52aa 100644 --- a/packages/qwik-speak/tests/use-plural.test.tsx +++ b/packages/qwik-speak/tests/use-plural.test.tsx @@ -3,7 +3,7 @@ import { component$ } from '@builder.io/qwik'; import { test, describe, expect } from 'vitest'; import { inlinePlural } from '../src/inline-plural'; -import { QwikSpeakProvider } from '../src/qwik-speak-component'; +import { QwikSpeakMockProvider } from '../src/use-qwik-speak'; import { config, translationFnStub } from './config'; const TestComponent = component$(() => { @@ -21,9 +21,9 @@ describe('usePlural function', async () => { const { screen, render } = await createDOM(); await render( - <QwikSpeakProvider config={config} translationFn={translationFnStub} locale={config.defaultLocale}> + <QwikSpeakMockProvider config={config} translationFn={translationFnStub} locale={config.defaultLocale}> <TestComponent /> - </QwikSpeakProvider> + </QwikSpeakMockProvider> ); test('plural', () => { diff --git a/packages/qwik-speak/tests/use-relative-time.test.tsx b/packages/qwik-speak/tests/use-relative-time.test.tsx index b720782..1e65e7e 100644 --- a/packages/qwik-speak/tests/use-relative-time.test.tsx +++ b/packages/qwik-speak/tests/use-relative-time.test.tsx @@ -3,7 +3,7 @@ import { component$ } from '@builder.io/qwik'; import { test, describe, expect } from 'vitest'; import { useRelativeTime } from '../src/use-relative-time'; -import { QwikSpeakProvider } from '../src/qwik-speak-component'; +import { QwikSpeakMockProvider } from '../src/use-qwik-speak'; import { config } from './config'; const TestComponent = component$(() => { @@ -21,9 +21,9 @@ describe('useRelativeTime function', async () => { const { screen, render } = await createDOM(); await render( - <QwikSpeakProvider config={config} locale={config.defaultLocale}> + <QwikSpeakMockProvider config={config} locale={config.defaultLocale}> <TestComponent /> - </QwikSpeakProvider> + </QwikSpeakMockProvider> ); test('format', () => { diff --git a/packages/qwik-speak/tests/use-translate-path.test.tsx b/packages/qwik-speak/tests/use-translate-path.test.tsx index bbb78ad..5127772 100644 --- a/packages/qwik-speak/tests/use-translate-path.test.tsx +++ b/packages/qwik-speak/tests/use-translate-path.test.tsx @@ -2,12 +2,12 @@ import { createDOM } from '@builder.io/qwik/testing'; import { component$ } from '@builder.io/qwik'; import { test, describe, expect } from 'vitest'; -import { useTranslatePath } from '../src/use-translate-path'; -import { QwikSpeakProvider } from '../src/qwik-speak-component'; +import { translatePath } from '../src/translate-path'; +import { QwikSpeakMockProvider } from '../src/use-qwik-speak'; import { config, translationFnStub } from './config'; const TestComponent = component$(() => { - const tp = useTranslatePath(); + const tp = translatePath(); return ( <div> @@ -95,9 +95,9 @@ describe('useTranslatePath function', async () => { const { screen, render } = await createDOM(); await render( - <QwikSpeakProvider config={config} translationFn={translationFnStub} locale={config.defaultLocale}> + <QwikSpeakMockProvider config={config} translationFn={translationFnStub} locale={config.defaultLocale}> <TestComponent /> - </QwikSpeakProvider> + </QwikSpeakMockProvider> ); test('translate without prefix', () => { diff --git a/packages/qwik-speak/tests/use-translate.test.tsx b/packages/qwik-speak/tests/use-translate.test.tsx index 0c0cd49..428663c 100644 --- a/packages/qwik-speak/tests/use-translate.test.tsx +++ b/packages/qwik-speak/tests/use-translate.test.tsx @@ -4,7 +4,7 @@ import { test, describe, expect } from 'vitest'; import type { Translation } from '../src/types'; import { useTranslate } from '../src/use-translate'; -import { QwikSpeakProvider } from '../src/qwik-speak-component'; +import { QwikSpeakMockProvider } from '../src/use-qwik-speak'; import { config, translationFnStub } from './config'; interface ChildComponentProps { @@ -60,9 +60,9 @@ describe('useTranslate function', async () => { const { screen, render } = await createDOM(); await render( - <QwikSpeakProvider config={config} translationFn={translationFnStub} locale={config.defaultLocale}> + <QwikSpeakMockProvider config={config} translationFn={translationFnStub} locale={config.defaultLocale}> <TestComponent /> - </QwikSpeakProvider> + </QwikSpeakMockProvider> ); test('translate', () => { diff --git a/packages/qwik-speak/tools/extract/index.ts b/packages/qwik-speak/tools/extract/index.ts index 9aafe40..1d318b9 100644 --- a/packages/qwik-speak/tools/extract/index.ts +++ b/packages/qwik-speak/tools/extract/index.ts @@ -43,6 +43,8 @@ export async function qwikSpeakExtract(options: QwikSpeakExtractOptions) { const sourceFiles: string[] = []; // Translation data const translation: Translation = Object.fromEntries(resolvedOptions.supportedLangs.map(value => [value, {}])); + // Plurals + const pluralKeys: string[] = []; /** * Read source files recursively @@ -160,7 +162,12 @@ export async function qwikSpeakExtract(options: QwikSpeakExtractOptions) { for (const rule of rules) { let key = args?.[1]?.value; - key = key ? `${key}${resolvedOptions.keySeparator}${rule}` : rule; + if (key) { + pluralKeys.push(key); + key = `${key}${resolvedOptions.keySeparator}${rule}`; + } else { + key = rule; + } keys.push(key); } } @@ -228,8 +235,14 @@ export async function qwikSpeakExtract(options: QwikSpeakExtractOptions) { mkdirSync(baseAssets, { recursive: true }); } - const topLevelKeys = Object.keys(translation[lang]).filter(key => minDepth(translation[lang][key]) > 0); - const bottomLevelKeys = Object.keys(translation[lang]).filter(key => minDepth(translation[lang][key]) === 0); + const topLevelKeys = Object.keys(translation[lang]) + .filter(key => pluralKeys.includes(key) ? + minDepth(translation[lang][key]) > 1 : + minDepth(translation[lang][key]) > 0); + const bottomLevelKeys = Object.keys(translation[lang]) + .filter(key => pluralKeys.includes(key) ? + minDepth(translation[lang][key]) === 1 : + minDepth(translation[lang][key]) === 0); const bottomTranslation: Translation = {}; if (translation[lang][resolvedOptions.filename]) { diff --git a/packages/qwik-speak/tools/inline/plugin.ts b/packages/qwik-speak/tools/inline/plugin.ts index a2c5804..e81f315 100644 --- a/packages/qwik-speak/tools/inline/plugin.ts +++ b/packages/qwik-speak/tools/inline/plugin.ts @@ -6,7 +6,13 @@ import { extname, normalize } from 'path'; import type { QwikSpeakInlineOptions, Translation } from '../core/types'; import type { Argument, Property } from '../core/parser'; -import { getInlineTranslateAlias, getInlinePluralAlias, parseJson, matchInlinePlural, matchInlineTranslate } from '../core/parser'; +import { + getInlineTranslateAlias, + getInlinePluralAlias, + parseJson, + matchInlinePlural, + matchInlineTranslate +} from '../core/parser'; import { parseSequenceExpressions } from '../core/parser'; import { getOptions, getRules } from '../core/intl-parser'; import { merge } from '../core/merge'; @@ -155,19 +161,8 @@ export function qwikSpeakInline(options: QwikSpeakInlineOptions): Plugin { } } - // Check QwikSpeakInline component + // Check base url if (target === 'ssr') { - if (id.endsWith('root.tsx' || id.endsWith('root.jsx'))) { - if (!/QwikSpeakInline/.test(code)) { - console.log( - '\n\x1b[31mQwik Speak Inline error\x1b[0m\n%s', - "Missing 'QwikSpeakInline' component in 'root.tsx' file: see https://robisim74.gitbook.io/qwik-speak/tools/setup" - ); - process.exit(1) - } - } - - // Check base url if (id.endsWith('entry.ssr.tsx') || id.endsWith('entry.ssr.jsx')) { if (!/(?<!\/\/\s*)base:\s*extractBase/.test(code)) { console.log( diff --git a/src/components/change-locale/change-locale.tsx b/src/components/change-locale/change-locale.tsx index 2094c8b..00e84a9 100644 --- a/src/components/change-locale/change-locale.tsx +++ b/src/components/change-locale/change-locale.tsx @@ -2,7 +2,7 @@ import { $, component$, useStyles$ } from '@builder.io/qwik'; import { useLocation } from '@builder.io/qwik-city'; import type { SpeakLocale } from 'qwik-speak'; import { useSpeakLocale, useSpeakConfig, useDisplayName, inlineTranslate } from 'qwik-speak'; -// import { useTranslatePath } from 'qwik-speak'; +// import { translatePath } from 'qwik-speak'; import styles from './change-locale.css?inline'; @@ -14,7 +14,7 @@ export const ChangeLocale = component$(() => { const dn = useDisplayName(); /** Uncomment this line to use url rewriting to translate paths */ - // const tp = useTranslatePath(); + // const tp = translatePath(); const loc = useLocation(); diff --git a/src/components/header/header.tsx b/src/components/header/header.tsx index d371e3a..4d4358f 100644 --- a/src/components/header/header.tsx +++ b/src/components/header/header.tsx @@ -1,7 +1,7 @@ import { component$, useStyles$ } from '@builder.io/qwik'; import { Link, useLocation } from '@builder.io/qwik-city'; import { useSpeakConfig, useSpeakLocale, inlineTranslate } from 'qwik-speak'; -// import { useTranslatePath } from 'qwik-speak'; +// import { translatePath } from 'qwik-speak'; import { ChangeLocale } from '../change-locale/change-locale'; import { SpeakLogo } from '../icons/speak'; @@ -22,7 +22,7 @@ export const Header = component$(() => { }; /** Uncomment this lines to use url rewriting to translate paths */ - // const tp = useTranslatePath(); + // const tp = translatePath(); // const { url } = useLocation(); // const [homePath, pagePath] = tp(['/', '/page/']) @@ -55,6 +55,7 @@ export const Header = component$(() => { </li> </ul> </header> + <ChangeLocale /> </> ); diff --git a/src/e2e/home.spec.ts b/src/e2e/home.spec.ts index 66b8bbe..4f46fcf 100644 --- a/src/e2e/home.spec.ts +++ b/src/e2e/home.spec.ts @@ -34,7 +34,7 @@ test.describe('Home', () => { await page.locator('text=Pagina').click(); - await expect(page.locator('main')).toContainText("Io sono un'altra pagina"); - await expect(page.locator('main')).toContainText("Io sono un valore dinamico"); + await expect(page.locator('main')).toContainText("Sono un'altra pagina"); + await expect(page.locator('main')).toContainText("Sono un valore dinamico"); }); }); diff --git a/src/e2e/page.spec.ts b/src/e2e/page.spec.ts index b003ed6..3e0c2c2 100644 --- a/src/e2e/page.spec.ts +++ b/src/e2e/page.spec.ts @@ -21,11 +21,11 @@ test.describe('Page', () => { await expect(page.locator('main')).toContainText('Qwik Speak'); await expect(page.locator('main')).toContainText('Traduci le tue app Qwik in qualsiasi lingua'); - await expect(page.locator('main')).toContainText("Io sono un'altra pagina"); - await expect(page.locator('main')).toContainText("I'm a default value"); - await expect(page.locator('main')).toContainText("Io sono un valore dinamico"); + await expect(page.locator('main')).toContainText("Sono un'altra pagina"); + await expect(page.locator('main')).toContainText("Sono un valore predefinito"); + await expect(page.locator('main')).toContainText("Sono un valore dinamico"); await expect(page).toHaveTitle('Pagina - Qwik Speak'); - await expect(page.locator('meta[name="description"]')).toHaveAttribute('content', "Io sono un'altra pagina"); + await expect(page.locator('meta[name="description"]')).toHaveAttribute('content', "Sono un'altra pagina"); }); }); diff --git a/src/root.tsx b/src/root.tsx index e21ba4f..64d9306 100644 --- a/src/root.tsx +++ b/src/root.tsx @@ -1,6 +1,6 @@ import { component$ } from '@builder.io/qwik'; import { QwikCityProvider, RouterOutlet, ServiceWorkerRegister } from '@builder.io/qwik-city'; -import { QwikSpeakInline, QwikSpeakProvider } from 'qwik-speak'; +import { useQwikSpeak } from 'qwik-speak'; import { RouterHead } from './components/router-head/router-head'; import { config } from './speak-config'; @@ -9,23 +9,22 @@ import { translationFn } from './speak-functions'; import './global.css'; export default component$(() => { + /** + * Init Qwik Speak + */ + useQwikSpeak({ config, translationFn }); + return ( - /** - * Init Qwik Speak (only available in child components) - */ - <QwikSpeakProvider config={config} translationFn={translationFn}> - <QwikCityProvider> - <head> - <meta charSet="utf-8" /> - <link rel="manifest" href="/manifest.json" /> - <RouterHead /> - <ServiceWorkerRegister /> - <QwikSpeakInline /> {/* Register Qwik Speak Inline */} - </head> - <body> - <RouterOutlet /> - </body> - </QwikCityProvider> - </QwikSpeakProvider> + <QwikCityProvider> + <head> + <meta charSet="utf-8" /> + <link rel="manifest" href="/manifest.json" /> + <RouterHead /> + <ServiceWorkerRegister /> + </head> + <body> + <RouterOutlet /> + </body> + </QwikCityProvider> ); }); diff --git a/src/routes/[...lang]/index.test.tsx b/src/routes/[...lang]/index.test.tsx index 1b50393..315f711 100644 --- a/src/routes/[...lang]/index.test.tsx +++ b/src/routes/[...lang]/index.test.tsx @@ -1,6 +1,6 @@ import { createDOM } from '@builder.io/qwik/testing'; import { test, expect } from 'vitest'; -import { QwikSpeakProvider } from 'qwik-speak'; +import { QwikSpeakMockProvider } from 'qwik-speak'; import Home from './index'; import { config } from '../../speak-config'; @@ -10,9 +10,9 @@ test(`[Home Component]: Should render translated texts`, async () => { const { screen, render, userEvent } = await createDOM(); await render( - <QwikSpeakProvider config={config} translationFn={translationFn} locale={config.defaultLocale}> + <QwikSpeakMockProvider config={config} translationFn={translationFn} locale={config.defaultLocale}> <Home /> - </QwikSpeakProvider> + </QwikSpeakMockProvider> ); expect(screen.outerHTML).toContain('Translate your Qwik apps into any language'); diff --git a/src/routes/[...lang]/index.tsx b/src/routes/[...lang]/index.tsx index 241b3d2..468fa44 100644 --- a/src/routes/[...lang]/index.tsx +++ b/src/routes/[...lang]/index.tsx @@ -1,7 +1,6 @@ import { component$, useSignal } from '@builder.io/qwik'; import { type DocumentHead } from '@builder.io/qwik-city'; import { - Speak, inlineTranslate, inlinePlural, useFormatDate, @@ -23,7 +22,7 @@ export const SubTitle = () => { return <h2>{t('app.subtitle')}</h2>; }; -export const Home = component$(() => { +export default component$(() => { const t = inlineTranslate() const p = inlinePlural(); @@ -42,21 +41,21 @@ export const Home = component$(() => { <SubTitle /> - <h3>{t('home.params')}</h3> - <p>{t('home.greeting', { name: 'Qwik Speak' })}</p> + <h3>{t('params')}</h3> + <p>{t('greeting', { name: 'Qwik Speak' })}</p> - <h3>{t('home.tags')}</h3> - <p dangerouslySetInnerHTML={t('home.text')}></p> + <h3>{t('tags')}</h3> + <p dangerouslySetInnerHTML={t('description')}></p> - <h3>{t('home.plural')}</h3> - <p class="counter">{p(count.value, 'home.devs')}</p> - <button class="btn-counter" onClick$={() => count.value++}>{t('home.increment')}</button> + <h3>{t('plural')}</h3> + <p class="counter">{p(count.value, 'devs')}</p> + <button class="btn-counter" onClick$={() => count.value++}>{t('increment')}</button> - <h3>{t('home.dates')}</h3> + <h3>{t('dates')}</h3> <p>{fd(Date.now(), { dateStyle: 'full', timeStyle: 'short' })}</p> <p>{rt(-1, 'second')}</p> - <h3>{t('home.numbers')}</h3> + <h3>{t('numbers')}</h3> <p>{fn(1000000)}</p> <p>{fn(1000000, { style: 'currency' })}</p> <p>{fn(1, { style: 'unit', unit: units['length'] })}</p> @@ -64,17 +63,6 @@ export const Home = component$(() => { ); }); -export default component$(() => { - return ( - /** - * Add Home translations (only available in child components) - */ - <Speak assets={['home']}> - <Home /> - </Speak> - ); -}); - export const head: DocumentHead = { title: 'runtime.head.home.title', meta: [{ name: 'description', content: 'runtime.head.home.description' }] diff --git a/src/routes/[...lang]/page/index.tsx b/src/routes/[...lang]/page/index.tsx index 6702478..8c407bb 100644 --- a/src/routes/[...lang]/page/index.tsx +++ b/src/routes/[...lang]/page/index.tsx @@ -1,6 +1,6 @@ import { component$ } from '@builder.io/qwik'; import type { DocumentHead } from '@builder.io/qwik-city'; -import { Speak, inlineTranslate } from 'qwik-speak'; +import { inlineTranslate, useSpeak } from 'qwik-speak'; export const Page = component$(() => { const t = inlineTranslate(); @@ -12,22 +12,20 @@ export const Page = component$(() => { <h1>{t('app.title')}</h1> <h2>{t('app.subtitle')}</h2> - <p>{t('page.text')}</p> - <p>{t('page.default@@I\'m a default value')}</p> + <p>{t('anotherPage')}</p> + <p>{t('defaultValue@@I\'m a default value')}</p> <p>{t(`runtimePage.${key}`)}</p> </div> ); }); export default component$(() => { - return ( - /** - * Add Page translations (only available in child components) - */ - <Speak assets={['page']} runtimeAssets={['runtimePage']}> - <Page /> - </Speak> - ); + /** + * Add scoped translations (only available in child components) + */ + useSpeak({ runtimeAssets: ['runtimePage'] }); + + return <Page /> }); export const head: DocumentHead = { diff --git a/src/speak-config.ts b/src/speak-config.ts index 9fbd1f4..a98beba 100644 --- a/src/speak-config.ts +++ b/src/speak-config.ts @@ -14,6 +14,7 @@ export const config: SpeakConfig = { { lang: 'it-IT', currency: 'EUR', timeZone: 'Europe/Rome', units: { 'length': 'kilometer' }, dir: 'ltr' }, { lang: 'de-DE', currency: 'EUR', timeZone: 'Europe/Rome', units: { 'length': 'kilometer' }, dir: 'ltr' } ], + // Translations available in the whole app assets: [ 'app' ], From 1f8c5b33c17114edf87caf7c8ff3f72194acef7a Mon Sep 17 00:00:00 2001 From: Roberto Simonetti <robisim74@gmail.com> Date: Sun, 19 Nov 2023 15:54:43 +0100 Subject: [PATCH 13/21] Feat: head & optimizations --- i18n/.metadata/translated-langs.json | 4 --- i18n/.metadata/translated.json | 24 ----------------- i18n/de-DE/app.json | 10 ++++++++ i18n/de-DE/runtime.json | 11 +------- i18n/de-DE/runtimePage.json | 5 ---- i18n/en-US/app.json | 10 ++++++++ i18n/en-US/runtime.json | 11 +------- i18n/en-US/runtimePage.json | 5 ---- i18n/it-IT/app.json | 10 ++++++++ i18n/it-IT/runtime.json | 11 +------- i18n/it-IT/runtimePage.json | 5 ---- packages/qwik-speak/src/use-qwik-speak.tsx | 3 +-- packages/qwik-speak/tools/inline/plugin.ts | 30 ++++++++++++++++++++-- src/components/router-head/router-head.tsx | 7 ++--- src/routes/[...lang]/index.tsx | 17 +++++++++--- src/routes/[...lang]/page/index.tsx | 28 ++++++++++---------- src/speak-config.ts | 3 ++- 17 files changed, 93 insertions(+), 101 deletions(-) delete mode 100644 i18n/.metadata/translated-langs.json delete mode 100644 i18n/.metadata/translated.json delete mode 100644 i18n/de-DE/runtimePage.json delete mode 100644 i18n/en-US/runtimePage.json delete mode 100644 i18n/it-IT/runtimePage.json diff --git a/i18n/.metadata/translated-langs.json b/i18n/.metadata/translated-langs.json deleted file mode 100644 index c4e4706..0000000 --- a/i18n/.metadata/translated-langs.json +++ /dev/null @@ -1,4 +0,0 @@ -[ - "it-IT", - "de-DE" -] \ No newline at end of file diff --git a/i18n/.metadata/translated.json b/i18n/.metadata/translated.json deleted file mode 100644 index 1c5e150..0000000 --- a/i18n/.metadata/translated.json +++ /dev/null @@ -1,24 +0,0 @@ -[ - "app.changeLocale", - "app.nav.home", - "app.nav.page", - "app.subtitle", - "app.title", - "anotherPage", - "dates", - "defaultValue", - "description", - "devs.one", - "devs.other", - "greeting", - "increment", - "numbers", - "params", - "plural", - "tags", - "runtime.head.home.description", - "runtime.head.home.title", - "runtime.head.page.description", - "runtime.head.page.title", - "runtimePage.dynamic" -] \ No newline at end of file diff --git a/i18n/de-DE/app.json b/i18n/de-DE/app.json index dc825db..b2ddf43 100644 --- a/i18n/de-DE/app.json +++ b/i18n/de-DE/app.json @@ -1,6 +1,16 @@ { "app": { "changeLocale": "Ändern Sie die Gebietsschema", + "head": { + "home": { + "description": "Internationalisierung (i18n) Bibliothek zur Übersetzung von Texten, Daten und Zahlen in Qwik Apps", + "title": "{{name}}" + }, + "page": { + "description": "Ich bin eine andere Seite", + "title": "Seite - {{name}}" + } + }, "nav": { "home": "Startseite", "page": "Seite" diff --git a/i18n/de-DE/runtime.json b/i18n/de-DE/runtime.json index fd6fcb6..082b2d1 100644 --- a/i18n/de-DE/runtime.json +++ b/i18n/de-DE/runtime.json @@ -1,14 +1,5 @@ { "runtime": { - "head": { - "home": { - "description": "Internationalisierung (i18n) Bibliothek zur Übersetzung von Texten, Daten und Zahlen in Qwik Apps", - "title": "{{name}}" - }, - "page": { - "description": "Ich bin eine andere Seite", - "title": "Seite - {{name}}" - } - } + "dynamic": "Ich bin ein dynamischer Wert" } } \ No newline at end of file diff --git a/i18n/de-DE/runtimePage.json b/i18n/de-DE/runtimePage.json deleted file mode 100644 index b00ea57..0000000 --- a/i18n/de-DE/runtimePage.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "runtimePage": { - "dynamic": "Ich bin ein dynamischer Wert" - } -} \ No newline at end of file diff --git a/i18n/en-US/app.json b/i18n/en-US/app.json index e99ff9c..f3cd379 100644 --- a/i18n/en-US/app.json +++ b/i18n/en-US/app.json @@ -1,6 +1,16 @@ { "app": { "changeLocale": "Change locale", + "head": { + "home": { + "description": "Internationalization (i18n) library to translate texts, dates and numbers in Qwik apps", + "title": "{{name}}" + }, + "page": { + "description": "I'm another page", + "title": "Page - {{name}}" + } + }, "nav": { "home": "Home", "page": "Page" diff --git a/i18n/en-US/runtime.json b/i18n/en-US/runtime.json index 410e16a..e8642fa 100644 --- a/i18n/en-US/runtime.json +++ b/i18n/en-US/runtime.json @@ -1,14 +1,5 @@ { "runtime": { - "head": { - "home": { - "description": "Internationalization (i18n) library to translate texts, dates and numbers in Qwik apps", - "title": "{{name}}" - }, - "page": { - "description": "I'm another page", - "title": "Page - {{name}}" - } - } + "dynamic": "I'm a dynamic value" } } \ No newline at end of file diff --git a/i18n/en-US/runtimePage.json b/i18n/en-US/runtimePage.json deleted file mode 100644 index fe289f7..0000000 --- a/i18n/en-US/runtimePage.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "runtimePage": { - "dynamic": "I'm a dynamic value" - } -} \ No newline at end of file diff --git a/i18n/it-IT/app.json b/i18n/it-IT/app.json index 0c9a576..0597d3b 100644 --- a/i18n/it-IT/app.json +++ b/i18n/it-IT/app.json @@ -1,6 +1,16 @@ { "app": { "changeLocale": "Cambia località", + "head": { + "home": { + "description": "Libreria di internazionalizzazione (i18n) per tradurre testi, date e numeri nelle app Qwik", + "title": "{{name}}" + }, + "page": { + "description": "Sono un'altra pagina", + "title": "Pagina - {{name}}" + } + }, "nav": { "home": "Home", "page": "Pagina" diff --git a/i18n/it-IT/runtime.json b/i18n/it-IT/runtime.json index 2d51f3f..9bfca36 100644 --- a/i18n/it-IT/runtime.json +++ b/i18n/it-IT/runtime.json @@ -1,14 +1,5 @@ { "runtime": { - "head": { - "home": { - "description": "Libreria di internazionalizzazione (i18n) per tradurre testi, date e numeri nelle app Qwik", - "title": "{{name}}" - }, - "page": { - "description": "Sono un'altra pagina", - "title": "Pagina - {{name}}" - } - } + "dynamic": "Sono un valore dinamico" } } \ No newline at end of file diff --git a/i18n/it-IT/runtimePage.json b/i18n/it-IT/runtimePage.json deleted file mode 100644 index 104529b..0000000 --- a/i18n/it-IT/runtimePage.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "runtimePage": { - "dynamic": "Sono un valore dinamico" - } -} \ No newline at end of file diff --git a/packages/qwik-speak/src/use-qwik-speak.tsx b/packages/qwik-speak/src/use-qwik-speak.tsx index 43edad0..b754a1b 100644 --- a/packages/qwik-speak/src/use-qwik-speak.tsx +++ b/packages/qwik-speak/src/use-qwik-speak.tsx @@ -77,7 +77,6 @@ export const useQwikSpeak = (props: QwikSpeakProps) => { // Load shared translations useTask$(async () => { - // Drop code on client if (isServer) { await loadTranslations(state, config.assets, config.runtimeAssets, props.langs); } @@ -116,7 +115,7 @@ export const useQwikSpeak = (props: QwikSpeakProps) => { } }); - useOnDocument('qinit', resumeContext$); + useOnDocument('DOMContentLoaded', resumeContext$); }; /** diff --git a/packages/qwik-speak/tools/inline/plugin.ts b/packages/qwik-speak/tools/inline/plugin.ts index e81f315..a290763 100644 --- a/packages/qwik-speak/tools/inline/plugin.ts +++ b/packages/qwik-speak/tools/inline/plugin.ts @@ -298,6 +298,7 @@ export function transformTranslate(code: string): string { if (!alias) return code; + let dynamic = false; // Parse sequence const sequence = parseSequenceExpressions(code, alias); @@ -311,7 +312,10 @@ export function transformTranslate(code: string): string { if (args?.length > 0) { // Dynamic - if (checkDynamicTranslate(args, originalFn)) continue; + if (checkDynamicTranslate(args, originalFn)) { + dynamic = true; + continue; + } // Transpile with placeholder const transpiled = originalFn.replace(new RegExp(`${alias}\\(`), `${inlineTranslatePlaceholder}(`); @@ -320,6 +324,11 @@ export function transformTranslate(code: string): string { } } + // Remove invocation + if (!dynamic) { + code = removeInlineTranslate(code, alias); + } + return code; } @@ -331,6 +340,7 @@ export function transformPlural(code: string): string { if (!alias) return code; + let dynamic = false; // Parse sequence const sequence = parseSequenceExpressions(code, alias); @@ -343,7 +353,10 @@ export function transformPlural(code: string): string { const args = expr.arguments; if (args?.length > 0) { - if (checkDynamicPlural(args, originalFn)) continue; + if (checkDynamicPlural(args, originalFn)) { + dynamic = true; + continue; + } // Transpile with placeholder const transpiled = originalFn.replace(new RegExp(`${alias}\\(`), `${inlinePluralPlaceholder}(`); @@ -352,6 +365,11 @@ export function transformPlural(code: string): string { } } + // Remove invocation + if (!dynamic) { + code = removeInlinePlural(code, alias); + } + return code; } @@ -702,6 +720,14 @@ export function trimFn(fn: string): string { return fn.replace(/\s+/g, ' ').trim(); } +export function removeInlineTranslate(code: string, alias: string): string { + return code.replace(new RegExp(`\\bconst\\s${alias}\\s=\\sinlineTranslate\\(\\);?`, 'g'), ''); +} + +export function removeInlinePlural(code: string, alias: string): string { + return code.replace(new RegExp(`\\bconst\\s${alias}\\s=\\sinlinePlural\\(\\);?`, 'g'), ''); +} + /** * Replace quoted values with a placeholder */ diff --git a/src/components/router-head/router-head.tsx b/src/components/router-head/router-head.tsx index c22b12c..17ad631 100644 --- a/src/components/router-head/router-head.tsx +++ b/src/components/router-head/router-head.tsx @@ -1,26 +1,23 @@ import { component$ } from '@builder.io/qwik'; import { useDocumentHead, useLocation } from '@builder.io/qwik-city'; -import { inlineTranslate } from 'qwik-speak'; /** * The RouterHead component is placed inside of the document `<head>` element. */ export const RouterHead = component$(() => { - const t = inlineTranslate(); - const head = useDocumentHead(); const loc = useLocation(); return ( <> - <title>{t(head.title, { name: 'Qwik Speak' })} + {head.title} {head.meta.map((m) => ( - + ))} {head.links.map((l) => ( diff --git a/src/routes/[...lang]/index.tsx b/src/routes/[...lang]/index.tsx index 468fa44..8c8ba88 100644 --- a/src/routes/[...lang]/index.tsx +++ b/src/routes/[...lang]/index.tsx @@ -23,7 +23,7 @@ export const SubTitle = () => { }; export default component$(() => { - const t = inlineTranslate() + const t = inlineTranslate(); const p = inlinePlural(); const fd = useFormatDate(); @@ -63,7 +63,16 @@ export default component$(() => { ); }); -export const head: DocumentHead = { - title: 'runtime.head.home.title', - meta: [{ name: 'description', content: 'runtime.head.home.description' }] +export const head: DocumentHead = () => { + const t = inlineTranslate(); + + return { + title: t('app.head.home.title', { name: 'Qwik Speak' }), + meta: [ + { + name: 'description', + content: t('app.head.home.description') + } + ], + }; }; diff --git a/src/routes/[...lang]/page/index.tsx b/src/routes/[...lang]/page/index.tsx index 8c407bb..22bc4ec 100644 --- a/src/routes/[...lang]/page/index.tsx +++ b/src/routes/[...lang]/page/index.tsx @@ -1,8 +1,8 @@ import { component$ } from '@builder.io/qwik'; import type { DocumentHead } from '@builder.io/qwik-city'; -import { inlineTranslate, useSpeak } from 'qwik-speak'; +import { inlineTranslate } from 'qwik-speak'; -export const Page = component$(() => { +export default component$(() => { const t = inlineTranslate(); const key = 'dynamic'; @@ -14,21 +14,21 @@ export const Page = component$(() => {

{t('anotherPage')}

{t('defaultValue@@I\'m a default value')}

-

{t(`runtimePage.${key}`)}

+

{t(`runtime.${key}`)}

); }); -export default component$(() => { - /** - * Add scoped translations (only available in child components) - */ - useSpeak({ runtimeAssets: ['runtimePage'] }); - - return -}); +export const head: DocumentHead = () => { + const t = inlineTranslate(); -export const head: DocumentHead = { - title: 'runtime.head.page.title', - meta: [{ name: 'description', content: 'runtime.head.page.description' }] + return { + title: t('app.head.page.title', { name: 'Qwik Speak' }), + meta: [ + { + name: 'description', + content: t('app.head.page.description') + } + ], + }; }; diff --git a/src/speak-config.ts b/src/speak-config.ts index a98beba..52abdca 100644 --- a/src/speak-config.ts +++ b/src/speak-config.ts @@ -18,7 +18,8 @@ export const config: SpeakConfig = { assets: [ 'app' ], + // Translations with dynamic keys available in the whole app runtimeAssets: [ - 'runtime' // Translations with dynamic keys or parameters + 'runtime' ] }; From 469cdbd3a160bd465318ced52c5f1f57b331ce7b Mon Sep 17 00:00:00 2001 From: Roberto Simonetti Date: Sun, 19 Nov 2023 22:58:25 +0100 Subject: [PATCH 14/21] Update tests --- packages/qwik-speak/src/index.ts | 2 +- packages/qwik-speak/src/use-qwik-speak.tsx | 74 ++++++++--- packages/qwik-speak/tests/config.ts | 57 ++++++--- packages/qwik-speak/tests/core.test.ts | 4 - ...plural.test.tsx => inline-plural.test.tsx} | 2 +- .../tests/inline-translate.test.tsx | 90 ++++++++++--- ...-path.test.tsx => translate-path.test.tsx} | 2 +- .../qwik-speak/tests/use-translate.test.tsx | 119 ------------------ .../qwik-speak/tools/tests/parser.test.ts | 15 +-- src/speak-routes.ts | 2 +- 10 files changed, 182 insertions(+), 185 deletions(-) rename packages/qwik-speak/tests/{use-plural.test.tsx => inline-plural.test.tsx} (95%) rename packages/qwik-speak/tests/{use-translate-path.test.tsx => translate-path.test.tsx} (99%) delete mode 100644 packages/qwik-speak/tests/use-translate.test.tsx diff --git a/packages/qwik-speak/src/index.ts b/packages/qwik-speak/src/index.ts index 546acf4..ca244d5 100644 --- a/packages/qwik-speak/src/index.ts +++ b/packages/qwik-speak/src/index.ts @@ -8,7 +8,7 @@ export type { LoadTranslationFn, RewriteRouteOption } from './types'; -export type { QwikSpeakProps } from './use-qwik-speak'; +export type { QwikSpeakProps, QwikSpeakMockProps } from './use-qwik-speak'; export type { SpeakProps } from './use-speak'; export type { TranslatePathFn } from './translate-path'; export type { InlinePluralFn } from './inline-plural'; diff --git a/packages/qwik-speak/src/use-qwik-speak.tsx b/packages/qwik-speak/src/use-qwik-speak.tsx index b754a1b..09cbabd 100644 --- a/packages/qwik-speak/src/use-qwik-speak.tsx +++ b/packages/qwik-speak/src/use-qwik-speak.tsx @@ -1,4 +1,4 @@ -import { $, component$, getLocale, Slot, useContextProvider, useOnDocument, useServerData, useTask$ } from '@builder.io/qwik'; +import { $, component$, getLocale, Slot, useContextProvider, useOnDocument, useTask$ } from '@builder.io/qwik'; import { isDev, isServer } from '@builder.io/qwik/build'; import type { SpeakConfig, SpeakLocale, SpeakState, TranslationFn } from './types'; @@ -15,31 +15,29 @@ export interface QwikSpeakProps { * Optional functions to use */ translationFn?: TranslationFn; - /** - * Optional locale to use - */ - locale?: SpeakLocale; /** * Optional additional languages to preload data for (multilingual) */ langs?: string[]; } +export interface QwikSpeakMockProps extends QwikSpeakProps { + /** + * Optional locale to use + */ + locale?: SpeakLocale; +} + /** * Create and provide the Speak context. * Translations will be available in the whole app */ export const useQwikSpeak = (props: QwikSpeakProps) => { // Get Qwik locale - const lang = useServerData('locale'); - - // Resolve functions - const resolvedTranslationFn: TranslationFn = { - loadTranslation$: props.translationFn?.loadTranslation$ ?? $(() => null) - }; + const lang = getLocale(''); // Resolve locale - let resolvedLocale = props.locale ?? props.config.supportedLocales.find(value => value.lang === lang); + let resolvedLocale = props.config.supportedLocales.find(value => value.lang === lang); if (!resolvedLocale) { resolvedLocale = props.config.defaultLocale; @@ -48,6 +46,11 @@ export const useQwikSpeak = (props: QwikSpeakProps) => { logDebug(`Resolved locale: ${resolvedLocale.lang}`); } + // Resolve functions + const resolvedTranslationFn: TranslationFn = { + loadTranslation$: props.translationFn?.loadTranslation$ ?? $(() => null) + }; + // Set initial state as object (no reactive) const state: SpeakState = { locale: Object.assign({}, resolvedLocale), @@ -121,8 +124,51 @@ export const useQwikSpeak = (props: QwikSpeakProps) => { /** * Create and provide the Speak context to test enviroments */ -export const QwikSpeakMockProvider = component$(props => { - useQwikSpeak(props); +export const QwikSpeakMockProvider = component$(props => { + const lang = props.locale?.lang; + + // Resolve locale + let resolvedLocale = props.config.supportedLocales.find(value => value.lang === lang); + if (!resolvedLocale) { + resolvedLocale = props.config.defaultLocale; + } + + // Resolve functions + const resolvedTranslationFn: TranslationFn = { + loadTranslation$: props.translationFn?.loadTranslation$ ?? $(() => null) + }; + + // Set initial state as object (no reactive) + const state: SpeakState = { + locale: Object.assign({}, resolvedLocale), + translation: Object.fromEntries(props.config.supportedLocales.map(value => [value.lang, {}])), + config: { + rewriteRoutes: props.config.rewriteRoutes, + defaultLocale: props.config.defaultLocale, + supportedLocales: props.config.supportedLocales, + assets: props.config.assets, + runtimeAssets: props.config.runtimeAssets, + keySeparator: props.config.keySeparator || '.', + keyValueSeparator: props.config.keyValueSeparator || '@@' + }, + translationFn: resolvedTranslationFn + }; + + const { config } = state; + + // Create server context + _speakContext.translation = Object.fromEntries(props.config.supportedLocales.map(value => [value.lang, {}])); + _speakContext.config = config; + // Set the getLang function to use the provided lang + setGetLangFn(() => resolvedLocale!.lang); + + // Create context + useContextProvider(SpeakContext, state); + + // Load shared translations + useTask$(async () => { + await loadTranslations(state, config.assets, config.runtimeAssets, props.langs); + }); return ; }); diff --git a/packages/qwik-speak/tests/config.ts b/packages/qwik-speak/tests/config.ts index 55ce5c1..55b01e9 100644 --- a/packages/qwik-speak/tests/config.ts +++ b/packages/qwik-speak/tests/config.ts @@ -1,6 +1,19 @@ import { $ } from '@builder.io/qwik'; -import type { SpeakConfig, LoadTranslationFn, TranslationFn } from '../src/types'; -import { rewriteRoutes } from '../../../src/speak-routes'; +import type { SpeakConfig, LoadTranslationFn, TranslationFn, RewriteRouteOption } from '../src/types'; + +const rewriteRoutes: RewriteRouteOption[] = [ + { + prefix: 'it-IT', + paths: { + 'page': 'pagina' + } + }, { + prefix: 'de-DE', + paths: { + 'page': 'seite' + } + } +]; export const config: SpeakConfig = { rewriteRoutes, @@ -16,22 +29,38 @@ export const config: SpeakConfig = { }; export const mockJson = { - test: 'Test', - testParams: 'Test {{param}}', - nested: { + 'en-US': { test: 'Test', - array: ['Test1 {{ param }}', 'Test2 {{ param }}'] + testParams: 'Test {{param}}', + nested: { + test: 'Test', + array: ['Test1 {{ param }}', 'Test2 {{ param }}'] + }, + one: 'One {{ role }} developer', + other: '{{value}} {{ role }} developers', + arrayObjects: [ + { num: 'One {{ param }}' }, + { num: 'Two {{ param }}' } + ] + }, + 'it-IT': { + test: 'Prova', + testParams: 'Prova {{param}}', + nested: { + test: 'Prova', + array: ['Prova1 {{ param }}', 'Prova2 {{ param }}'] + }, + one: 'Un {{ role }} developer', + other: '{{value}} {{ role }} developers', + arrayObjects: [ + { num: 'Uno {{ param }}' }, + { num: 'Due {{ param }}' } + ] }, - one: 'One {{ role }} developer', - other: '{{value}} {{ role }} developers', - arrayObjects: [ - { num: 'One {{ param }}' }, - { num: 'Two {{ param }}' } - ] }; -export const loadTranslationStub$: LoadTranslationFn = $(() => { - return mockJson; +export const loadTranslationStub$: LoadTranslationFn = $((lang: string) => { + return (mockJson as any)[lang]; }); export const translationFnStub: TranslationFn = { diff --git a/packages/qwik-speak/tests/core.test.ts b/packages/qwik-speak/tests/core.test.ts index 2c75d11..727ea74 100644 --- a/packages/qwik-speak/tests/core.test.ts +++ b/packages/qwik-speak/tests/core.test.ts @@ -13,10 +13,6 @@ describe('core', () => { value = getValue('SUBKEY1.BB', { KEY1: 'key1', SUBKEY1: { AA: 'aa' } }); expect(value).toBe('SUBKEY1.BB'); }); - test('getValue when String', () => { - const value = getValue('KEY1', { KEY1: new String('key1') }); - expect(value).toBe('key1'); - }); test('transpileParams', () => { let value = transpileParams('Test {{param}}', { param: 'params' }); expect(value).toBe('Test params'); diff --git a/packages/qwik-speak/tests/use-plural.test.tsx b/packages/qwik-speak/tests/inline-plural.test.tsx similarity index 95% rename from packages/qwik-speak/tests/use-plural.test.tsx rename to packages/qwik-speak/tests/inline-plural.test.tsx index 38a52aa..7a0d4eb 100644 --- a/packages/qwik-speak/tests/use-plural.test.tsx +++ b/packages/qwik-speak/tests/inline-plural.test.tsx @@ -17,7 +17,7 @@ const TestComponent = component$(() => { ); }); -describe('usePlural function', async () => { +describe('inlinePlural function', async () => { const { screen, render } = await createDOM(); await render( diff --git a/packages/qwik-speak/tests/inline-translate.test.tsx b/packages/qwik-speak/tests/inline-translate.test.tsx index d3b7a1d..a12e4cd 100644 --- a/packages/qwik-speak/tests/inline-translate.test.tsx +++ b/packages/qwik-speak/tests/inline-translate.test.tsx @@ -1,30 +1,33 @@ import { createDOM } from '@builder.io/qwik/testing'; -import { component$, useSignal, useTask$, $ } from '@builder.io/qwik'; +import { component$, useSignal, $, useTask$ } from '@builder.io/qwik'; import { test, describe, expect } from 'vitest'; -import { t } from '../src/inline-translate'; +import type { Translation } from '../src/types'; +import { inlineTranslate } from '../src/inline-translate'; import { QwikSpeakMockProvider } from '../src/use-qwik-speak'; import { config, translationFnStub } from './config'; -const MyComponent = () => { - return
{t('test')}
; -}; - const TestComponent = component$(() => { - const s = useSignal(''); - - const test$ = $(() => { - return t('test'); - }); - - useTask$(async () => { - s.value = await test$(); - }); + const t = inlineTranslate(); return (
-
{s.value}
- +
{t('test')}
+
{t('test@@Default')}
+
{t('testParams', { param: 'params' })}
+
{t(['test', 'testParams'], { param: 'params' }).map((v, i) => (
{v}
))}
+
{t('test1')}
+
{t('nested.test')}
+
{t('test1@@Test 1')}
+
{t('test1@@Test {{param}}', { param: 'params' })}
+
{t('nested.array', { param: 'params' }).map((v, i) => + (
{v}
))} +
+
{t('nested.array.1', { param: 'params' })}
+
{t('nested')['test']}
+
{t('arrayObjects', { param: 'params' }).map((o, i) => + (
{o['num']}
))} +
); }); @@ -40,6 +43,57 @@ describe('inlineTranslate function', async () => { test('translate', () => { expect((screen.querySelector('#A') as HTMLDivElement).innerHTML).toContain('Test'); - expect((screen.querySelector('#B') as HTMLDivElement).innerHTML).toContain('Test'); + }); + test('translate with default value', () => { + expect((screen.querySelector('#A1') as HTMLDivElement).innerHTML).toContain('Test'); + }); + test('translate with params', () => { + expect((screen.querySelector('#A2') as HTMLDivElement).innerHTML).toContain('Test params'); + }); + test('translate with array of keys', () => { + expect((screen.querySelector('#A3') as HTMLDivElement).innerHTML).toContain('Test'); + expect((screen.querySelector('#A3') as HTMLDivElement).innerHTML).toContain('Test params'); + }); + test('missing value', () => { + expect((screen.querySelector('#A4') as HTMLDivElement).innerHTML).toContain('test1'); + }); + test('key separator', () => { + expect((screen.querySelector('#A5') as HTMLDivElement).innerHTML).toContain('Test'); + }); + test('key-value separator', () => { + expect((screen.querySelector('#A6') as HTMLDivElement).innerHTML).toContain('Test 1'); + }); + test('key-value separator with params', () => { + expect((screen.querySelector('#A7') as HTMLDivElement).innerHTML).toContain('Test params'); + }); + test('array', () => { + expect((screen.querySelector('#A8') as HTMLDivElement).innerHTML).toContain('Test1 params'); + expect((screen.querySelector('#A8') as HTMLDivElement).innerHTML).toContain('Test2 params'); + }); + test('array with dot notation', () => { + expect((screen.querySelector('#A9') as HTMLDivElement).innerHTML).toContain('Test2 params'); + }); + test('object', () => { + expect((screen.querySelector('#A10') as HTMLDivElement).innerHTML).toContain('Test'); + }); + test('array of objects', () => { + expect((screen.querySelector('#A11') as HTMLDivElement).innerHTML).toContain('One params'); + expect((screen.querySelector('#A11') as HTMLDivElement).innerHTML).toContain('Two params'); + }); +}); + +describe('inlineTranslate function with different lang', async () => { + const { screen, render } = await createDOM(); + + const locale = { lang: 'it-IT' }; + + await render( + + + + ); + + test('translate', () => { + expect((screen.querySelector('#A') as HTMLDivElement).innerHTML).toContain('Prova'); }); }); diff --git a/packages/qwik-speak/tests/use-translate-path.test.tsx b/packages/qwik-speak/tests/translate-path.test.tsx similarity index 99% rename from packages/qwik-speak/tests/use-translate-path.test.tsx rename to packages/qwik-speak/tests/translate-path.test.tsx index 5127772..dc25de3 100644 --- a/packages/qwik-speak/tests/use-translate-path.test.tsx +++ b/packages/qwik-speak/tests/translate-path.test.tsx @@ -91,7 +91,7 @@ const TestComponent = component$(() => { ); }); -describe('useTranslatePath function', async () => { +describe('translatePath function', async () => { const { screen, render } = await createDOM(); await render( diff --git a/packages/qwik-speak/tests/use-translate.test.tsx b/packages/qwik-speak/tests/use-translate.test.tsx deleted file mode 100644 index 428663c..0000000 --- a/packages/qwik-speak/tests/use-translate.test.tsx +++ /dev/null @@ -1,119 +0,0 @@ -import { createDOM } from '@builder.io/qwik/testing'; -import { component$ } from '@builder.io/qwik'; -import { test, describe, expect } from 'vitest'; - -import type { Translation } from '../src/types'; -import { useTranslate } from '../src/use-translate'; -import { QwikSpeakMockProvider } from '../src/use-qwik-speak'; -import { config, translationFnStub } from './config'; - -interface ChildComponentProps { - value: string; -} - -const ChildComponent = component$((props: ChildComponentProps) => { - return ( -
-
{props.value}
-
- ); -}); - -const TestComponent = component$(() => { - const t = useTranslate(); - - const value = t('test'); - - return ( -
-
{t('test')}
-
{t('test@@Default')}
-
{t('testParams', { param: 'params' })}
-
{t(['test', 'testParams'], { param: 'params' }).map((v, i) => (
{v}
))}
-
{t('test1')}
-
{t('nested.test')}
-
{t('test1@@Test 1')}
-
{t('test1@@Test {{param}}', { param: 'params' })}
-
{t('nested.array', { param: 'params' }).map((v, i) => - (
{v}
))} -
-
{t('nested.array.1', { param: 'params' })}
-
{t('nested')['test']}
-
{t('arrayObjects', { param: 'params' }).map((o, i) => - (
{o['num']}
))} -
-
- {true && -

- {t('test')} -

- } -
-
{true && t('test')}
-
- -
- ); -}); - -describe('useTranslate function', async () => { - const { screen, render } = await createDOM(); - - await render( - - - - ); - - test('translate', () => { - expect((screen.querySelector('#A') as HTMLDivElement).innerHTML).toContain('Test'); - }); - test('translate with default value', () => { - expect((screen.querySelector('#A1') as HTMLDivElement).innerHTML).toContain('Test'); - }); - test('translate with params', () => { - expect((screen.querySelector('#A2') as HTMLDivElement).innerHTML).toContain('Test params'); - }); - test('translate with array of keys', () => { - expect((screen.querySelector('#A3') as HTMLDivElement).innerHTML).toContain('Test'); - expect((screen.querySelector('#A3') as HTMLDivElement).innerHTML).toContain('Test params'); - }); - test('missing value', () => { - expect((screen.querySelector('#A4') as HTMLDivElement).innerHTML).toContain('test1'); - }); - test('key separator', () => { - expect((screen.querySelector('#A5') as HTMLDivElement).innerHTML).toContain('Test'); - }); - test('key-value separator', () => { - expect((screen.querySelector('#A6') as HTMLDivElement).innerHTML).toContain('Test 1'); - }); - test('key-value separator with params', () => { - expect((screen.querySelector('#A7') as HTMLDivElement).innerHTML).toContain('Test params'); - }); - test('array', () => { - expect((screen.querySelector('#A8') as HTMLDivElement).innerHTML).toContain('Test1 params'); - expect((screen.querySelector('#A8') as HTMLDivElement).innerHTML).toContain('Test2 params'); - }); - test('array with dot notation', () => { - expect((screen.querySelector('#A9') as HTMLDivElement).innerHTML).toContain('Test2 params'); - }); - test('object', () => { - expect((screen.querySelector('#A10') as HTMLDivElement).innerHTML).toContain('Test'); - }); - test('array of objects', () => { - expect((screen.querySelector('#A11') as HTMLDivElement).innerHTML).toContain('One params'); - expect((screen.querySelector('#A11') as HTMLDivElement).innerHTML).toContain('Two params'); - }); - test('conditional rendering', () => { - expect((screen.querySelector('#A12') as HTMLDivElement).innerHTML).toContain('Test'); - }); - test('inline conditional rendering', () => { - expect((screen.querySelector('#A13') as HTMLDivElement).innerHTML).toContain('Test'); - }); - test('jsx attributes', () => { - expect((screen.querySelector('#A14') as HTMLDivElement).getAttribute('title')).toContain('Test'); - }); - test('component props', () => { - expect((screen.querySelector('#B') as HTMLDivElement).innerHTML).toContain('Test'); - }) -}); diff --git a/packages/qwik-speak/tools/tests/parser.test.ts b/packages/qwik-speak/tools/tests/parser.test.ts index 7b1aa81..09683f0 100644 --- a/packages/qwik-speak/tools/tests/parser.test.ts +++ b/packages/qwik-speak/tools/tests/parser.test.ts @@ -1,6 +1,6 @@ import { test, describe, expect } from 'vitest'; -import { getInlineTranslateAlias, getInlinePluralAlias, getUseTranslateAlias, parse, parseSequenceExpressions, tokenize } from '../core/parser'; +import { getInlineTranslateAlias, getInlinePluralAlias, parse, parseSequenceExpressions, tokenize } from '../core/parser'; describe('parser: tokenize', () => { test('tokenize', () => { @@ -634,20 +634,11 @@ describe('parser: parseSequenceExpressions', () => { }); describe('aliases', () => { - test('getUseTranslateAlias', () => { - const alias = getUseTranslateAlias(`const t = useTranslate();`); - expect(alias).toBe('\\bt'); - }); test('getInlineTranslateAlias', () => { - let alias = getInlineTranslateAlias(`import { - inlineTranslate as t, - useSpeakLocale - } from 'qwik-speak';`); + const alias = getInlineTranslateAlias(`const t = inlineTranslate();`); expect(alias).toBe('\\bt'); - alias = getInlineTranslateAlias("import { inlineTranslate } from 'qwik-speak';"); - expect(alias).toBe('\\binlineTranslate'); }); - test('getUsePluralAlias', () => { + test('getInlinePluralAlias', () => { const alias = getInlinePluralAlias('const p = usePlural();'); expect(alias).toBe('\\bp'); }); diff --git a/src/speak-routes.ts b/src/speak-routes.ts index 2c70771..ebb91ae 100644 --- a/src/speak-routes.ts +++ b/src/speak-routes.ts @@ -21,4 +21,4 @@ export const rewriteRoutes: RewriteRouteOption[] = [ 'page': 'seite' } } -] +]; From 29cc7383ef6df65acbf395323153bc25b5534d9a Mon Sep 17 00:00:00 2001 From: Roberto Simonetti Date: Mon, 20 Nov 2023 14:58:03 +0100 Subject: [PATCH 15/21] Feat: localizePath --- packages/qwik-speak/src/index.ts | 2 + packages/qwik-speak/src/inline-translate.ts | 6 +- packages/qwik-speak/src/localize-path.ts | 67 ++++++++++++++++ packages/qwik-speak/src/translate-path.ts | 31 ++++--- .../tests/inline-translate.test.tsx | 58 ++++++++++++++ .../qwik-speak/tests/localize-path.test.tsx | 80 +++++++++++++++++++ .../change-locale/change-locale.tsx | 44 ++-------- src/components/header/header.tsx | 28 ++----- 8 files changed, 244 insertions(+), 72 deletions(-) create mode 100644 packages/qwik-speak/src/localize-path.ts create mode 100644 packages/qwik-speak/tests/localize-path.test.tsx diff --git a/packages/qwik-speak/src/index.ts b/packages/qwik-speak/src/index.ts index ca244d5..2a5a4ae 100644 --- a/packages/qwik-speak/src/index.ts +++ b/packages/qwik-speak/src/index.ts @@ -10,6 +10,7 @@ export type { } from './types'; export type { QwikSpeakProps, QwikSpeakMockProps } from './use-qwik-speak'; export type { SpeakProps } from './use-speak'; +export type { LocalizePathFn } from './localize-path'; export type { TranslatePathFn } from './translate-path'; export type { InlinePluralFn } from './inline-plural'; export type { InlineTranslateFn } from './inline-translate'; @@ -23,6 +24,7 @@ export { QwikSpeakMockProvider } from './use-qwik-speak'; export { inlineTranslate } from './inline-translate'; export { inlinePlural } from './inline-plural'; // Functions +export { localizePath } from './localize-path'; export { translatePath } from './translate-path'; // Use functions export { useQwikSpeak } from './use-qwik-speak'; diff --git a/packages/qwik-speak/src/inline-translate.ts b/packages/qwik-speak/src/inline-translate.ts index b99bb6c..6582658 100644 --- a/packages/qwik-speak/src/inline-translate.ts +++ b/packages/qwik-speak/src/inline-translate.ts @@ -28,11 +28,7 @@ export type InlineTranslateFn = { export const inlineTranslate = (): InlineTranslateFn => { const currentLang = getLang(); - const translate: InlineTranslateFn = ( - keys: string | string[], - params?: Record, - lang?: string - ) => { + const translate = (keys: string | string[], params?: Record, lang?: string) => { const { translation, config } = _speakContext as SpeakState; lang ??= currentLang; diff --git a/packages/qwik-speak/src/localize-path.ts b/packages/qwik-speak/src/localize-path.ts new file mode 100644 index 0000000..64d9140 --- /dev/null +++ b/packages/qwik-speak/src/localize-path.ts @@ -0,0 +1,67 @@ +import { type SpeakState } from './types'; +import { _speakContext, getLang } from './context'; + +export type LocalizePathFn = { + /** + * Localize a path with the language + * @param pathname The path to localize + * @param lang Optional language if different from the default one + * @returns The localized path + */ + (pathname: string, lang?: string): string; + /** + * Localize a url with the language + * @param url The url to localize + * @param lang Optional language if different from the default one + * @returns The localized url + */ + (url: URL, lang?: string): string; + /** + * Localize an array of paths with the language + * @param pathnames The array of paths to localize + * @param lang Optional language if different from the default one + * @returns The localized paths + */ + (pathnames: string[], lang?: string): string[]; +}; + +export const localizePath = (): LocalizePathFn => { + const currentLang = getLang(); + + const getRegEpx = (lang: string) => new RegExp(`(/${lang}/)|(/${lang}$)|(/(${lang})(?=\\?))`); + + const replace = (pathname: string, lang?: string) => { + const { config } = _speakContext as SpeakState; + + lang ??= currentLang; + + const langParam = config.supportedLocales.find(locale => getRegEpx(locale.lang)?.test(pathname))?.lang; + if (langParam) { + if (lang !== config.defaultLocale.lang) { + pathname = pathname.replace(langParam, lang); + } else { + pathname = pathname.replace(getRegEpx(langParam), '/'); + } + } else if (lang !== config.defaultLocale.lang) { + pathname = `/${lang}${pathname}`; + } + + return pathname; + }; + + const localize = (route: (string | URL) | string[], lang?: string) => { + if (Array.isArray(route)) { + return route.map(path => replace(path, lang)); + } + + if (typeof route === 'string') { + return replace(route, lang); + } + + route.pathname = replace(route.pathname, lang); + + return route.toString().replace(/\/\?/, '?'); + }; + + return localize as LocalizePathFn; +}; diff --git a/packages/qwik-speak/src/translate-path.ts b/packages/qwik-speak/src/translate-path.ts index 39cb892..dab5a27 100644 --- a/packages/qwik-speak/src/translate-path.ts +++ b/packages/qwik-speak/src/translate-path.ts @@ -1,24 +1,31 @@ import { isDev } from '@builder.io/qwik/build'; -import { _speakContext, getLang } from './context'; import { type SpeakState } from './types'; +import { _speakContext, getLang } from './context'; import { logWarn } from './log'; export type TranslatePathFn = { /** - * Translate a path. + * Translate a path * @param pathname The path to translate * @param lang Optional language if different from the default one * @returns The translation or the path if not found */ (pathname: string, lang?: string): string; /** - * Translate an array of paths. + * Translate a url + * @param url The url to translate + * @param lang Optional language if different from the default one + * @returns The translation or the url if not found + */ + (url: URL, lang?: string): string; + /** + * Translate an array of paths * @param pathname The array of paths to translate * @param lang Optional language if different from the default one * @returns The translations or the paths if not found */ - (pathname: string[], lang?: string): string[]; + (pathnames: string[], lang?: string): string[]; }; export const translatePath = (): TranslatePathFn => { @@ -93,21 +100,25 @@ export const translatePath = (): TranslatePathFn => { return slashPath(pathname, rewrote); }; - const translate = (pathname: string | string[], lang?: string) => { + const translate = (route: (string | URL) | string[], lang?: string) => { const { config } = _speakContext as SpeakState; if (!config.rewriteRoutes) { if (isDev) logWarn(`translatePath: rewriteRoutes not found`); - return pathname; + return route; } - lang ??= currentLang; + if (Array.isArray(route)) { + return route.map(path => translateOne(path, lang)); + } - if (Array.isArray(pathname)) { - return pathname.map(path => translateOne(path, lang)); + if (typeof route === 'string') { + return translateOne(route, lang); } - return translateOne(pathname, lang); + route.pathname = translateOne(route.pathname, lang); + + return route.toString(); }; return translate as TranslatePathFn; diff --git a/packages/qwik-speak/tests/inline-translate.test.tsx b/packages/qwik-speak/tests/inline-translate.test.tsx index a12e4cd..ad583d6 100644 --- a/packages/qwik-speak/tests/inline-translate.test.tsx +++ b/packages/qwik-speak/tests/inline-translate.test.tsx @@ -7,9 +7,36 @@ import { inlineTranslate } from '../src/inline-translate'; import { QwikSpeakMockProvider } from '../src/use-qwik-speak'; import { config, translationFnStub } from './config'; +const ChildComponent = component$((props: { value: string, value1: string }) => { + return ( +
+
{props.value}
+
{props.value1}
+
+ ); +}); + +const InlineComponent = () => { + const t = inlineTranslate(); + return
{t('test')}
; +}; + +const test$ = $(() => { + const t = inlineTranslate(); + return t('test'); +}); + const TestComponent = component$(() => { const t = inlineTranslate(); + const s = useSignal(''); + const s1 = useSignal(false); + + useTask$(async () => { + s.value = await test$(); + s1.value = true; + }); + return (
{t('test')}
@@ -28,6 +55,18 @@ const TestComponent = component$(() => {
{t('arrayObjects', { param: 'params' }).map((o, i) => (
{o['num']}
))}
+
+ {s1.value && +

+ {t('test')} +

+ } +
+
{s1.value && t('test')}
+
+
{s.value}
+ +
); }); @@ -80,6 +119,25 @@ describe('inlineTranslate function', async () => { expect((screen.querySelector('#A11') as HTMLDivElement).innerHTML).toContain('One params'); expect((screen.querySelector('#A11') as HTMLDivElement).innerHTML).toContain('Two params'); }); + test('conditional rendering', () => { + expect((screen.querySelector('#A12') as HTMLDivElement).innerHTML).toContain('Test'); + }); + test('inline conditional rendering', () => { + expect((screen.querySelector('#A13') as HTMLDivElement).innerHTML).toContain('Test'); + }); + test('jsx attributes', () => { + expect((screen.querySelector('#A14') as HTMLDivElement).getAttribute('title')).toContain('Test'); + }); + test('signal', () => { + expect((screen.querySelector('#A15') as HTMLDivElement).innerHTML).toContain('Test'); + }); + test('component props', () => { + expect((screen.querySelector('#B') as HTMLDivElement).innerHTML).toContain('Test'); + expect((screen.querySelector('#B1') as HTMLDivElement).innerHTML).toContain('Test'); + }); + test('inline component', () => { + expect((screen.querySelector('#C') as HTMLDivElement).innerHTML).toContain('Test'); + }); }); describe('inlineTranslate function with different lang', async () => { diff --git a/packages/qwik-speak/tests/localize-path.test.tsx b/packages/qwik-speak/tests/localize-path.test.tsx new file mode 100644 index 0000000..b2e3797 --- /dev/null +++ b/packages/qwik-speak/tests/localize-path.test.tsx @@ -0,0 +1,80 @@ +import { createDOM } from '@builder.io/qwik/testing'; +import { component$ } from '@builder.io/qwik'; +import { test, describe, expect } from 'vitest'; + +import { localizePath } from '../src'; +import { QwikSpeakMockProvider } from '../src/use-qwik-speak'; +import { config, translationFnStub } from './config'; + +const TestComponent = component$(() => { + const lp = localizePath(); + + return ( +
+ + + + + + + + + + + + + + +
+ ); +}); + +describe('localizePath function', async () => { + const { screen, render } = await createDOM(); + + await render( + + + + ); + + test('add language', () => { + expect((screen.querySelector('#A1') as HTMLDivElement).getAttribute('href')).toBe('http://localhost/it-IT/'); + expect((screen.querySelector('#A2') as HTMLDivElement).getAttribute('href')).toBe('http://localhost/it-IT/page?id=1'); + expect((screen.querySelector('#A3') as HTMLDivElement).getAttribute('href')).toBe('http://localhost/it-IT?id=1'); + }); + + test('remove language', () => { + expect((screen.querySelector('#B1') as HTMLDivElement).getAttribute('href')).toBe('http://localhost/'); + expect((screen.querySelector('#B2') as HTMLDivElement).getAttribute('href')).toBe('http://localhost/page?id=1'); + expect((screen.querySelector('#B4') as HTMLDivElement).getAttribute('href')).toBe('http://localhost?id=1'); + }); + + test('update language', () => { + expect((screen.querySelector('#C1') as HTMLDivElement).getAttribute('href')).toBe('http://localhost/de-DE/'); + expect((screen.querySelector('#C2') as HTMLDivElement).getAttribute('href')).toBe('http://localhost/de-DE/page?id=1'); + expect((screen.querySelector('#C4') as HTMLDivElement).getAttribute('href')).toBe('http://localhost/de-DE?id=1'); + }); + + test('default language', () => { + expect((screen.querySelector('#D1') as HTMLDivElement).getAttribute('href')).toBe('/'); + expect((screen.querySelector('#D2') as HTMLDivElement).getAttribute('href')).toBe('/page'); + }); +}); + +describe('localizePath function with different lang', async () => { + const { screen, render } = await createDOM(); + + const locale = { lang: 'it-IT' }; + + await render( + + + + ); + + test('default language', () => { + expect((screen.querySelector('#D1') as HTMLDivElement).getAttribute('href')).toBe('/it-IT/'); + expect((screen.querySelector('#D2') as HTMLDivElement).getAttribute('href')).toBe('/it-IT/page'); + }); +}); diff --git a/src/components/change-locale/change-locale.tsx b/src/components/change-locale/change-locale.tsx index 00e84a9..944a759 100644 --- a/src/components/change-locale/change-locale.tsx +++ b/src/components/change-locale/change-locale.tsx @@ -1,7 +1,6 @@ -import { $, component$, useStyles$ } from '@builder.io/qwik'; +import { component$, useStyles$ } from '@builder.io/qwik'; import { useLocation } from '@builder.io/qwik-city'; -import type { SpeakLocale } from 'qwik-speak'; -import { useSpeakLocale, useSpeakConfig, useDisplayName, inlineTranslate } from 'qwik-speak'; +import { useSpeakLocale, useSpeakConfig, useDisplayName, inlineTranslate, localizePath } from 'qwik-speak'; // import { translatePath } from 'qwik-speak'; import styles from './change-locale.css?inline'; @@ -11,52 +10,25 @@ export const ChangeLocale = component$(() => { const t = inlineTranslate(); + const url = useLocation().url; + const dn = useDisplayName(); /** Uncomment this line to use url rewriting to translate paths */ - // const tp = translatePath(); - - const loc = useLocation(); + // const getPath = translatePath(); + const getPath = localizePath(); const locale = useSpeakLocale(); const config = useSpeakConfig(); - /** Uncomment this lines to use url rewriting to translate paths */ - // const getLocalePath = (newLocale: SpeakLocale) => { - // const url = new URL(loc.url) - // url.pathname = tp(url.pathname, newLocale.lang) - // return url.toString(); - // }; - - // Replace the locale and navigate to the new URL - const navigateByLocale$ = $((newLocale: SpeakLocale) => { - const url = new URL(location.href); - if (loc.params.lang) { - if (newLocale.lang !== config.defaultLocale.lang) { - url.pathname = url.pathname.replace(loc.params.lang, newLocale.lang); - } else { - url.pathname = url.pathname.replace(new RegExp(`(/${loc.params.lang}/)|(/${loc.params.lang}$)`), '/'); - } - } else if (newLocale.lang !== config.defaultLocale.lang) { - url.pathname = `/${newLocale.lang}${url.pathname}`; - } - - location.href = url.toString(); - }); - return ( diff --git a/src/components/header/header.tsx b/src/components/header/header.tsx index 4d4358f..5a6e7cf 100644 --- a/src/components/header/header.tsx +++ b/src/components/header/header.tsx @@ -1,6 +1,6 @@ import { component$, useStyles$ } from '@builder.io/qwik'; import { Link, useLocation } from '@builder.io/qwik-city'; -import { useSpeakConfig, useSpeakLocale, inlineTranslate } from 'qwik-speak'; +import { inlineTranslate, localizePath } from 'qwik-speak'; // import { translatePath } from 'qwik-speak'; import { ChangeLocale } from '../change-locale/change-locale'; @@ -14,42 +14,28 @@ export const Header = component$(() => { const t = inlineTranslate(); const pathname = useLocation().url.pathname; - const lang = useSpeakLocale().lang; - const config = useSpeakConfig(); - - const getHref = (name: string) => { - return lang === config.defaultLocale.lang ? name : `/${lang}${name}`; - }; /** Uncomment this lines to use url rewriting to translate paths */ - // const tp = translatePath(); - // const { url } = useLocation(); - // const [homePath, pagePath] = tp(['/', '/page/']) + // const getPath = translatePath(); + const getPath = localizePath(); + const [homePath, pagePath] = getPath(['/', '/page/']); return ( <>
  • - {/** Uncomment this line to use url rewriting to translate paths */} - {/* */} - pathname.endsWith(`${x.lang}/`)) }}> + {t('app.nav.home')}
  • - {/** Uncomment this line to use url rewriting to translate paths */} - {/* */} - + {t('app.nav.page')}
  • From 368bedc80538c173bf8c1080b449cc8d8ff87e49 Mon Sep 17 00:00:00 2001 From: Roberto Simonetti Date: Mon, 20 Nov 2023 17:02:23 +0100 Subject: [PATCH 16/21] Tools: update tests --- packages/qwik-speak/tools/core/format.ts | 6 - packages/qwik-speak/tools/extract/index.ts | 37 +- .../qwik-speak/tools/tests/extract.test.ts | 12 +- .../qwik-speak/tools/tests/format.test.ts | 13 +- .../qwik-speak/tools/tests/inline.test.ts | 44 +- packages/qwik-speak/tools/tests/mock.ts | 762 +++++++++++------- .../qwik-speak/tools/tests/parser.test.ts | 2 +- src/global.css | 2 +- 8 files changed, 483 insertions(+), 395 deletions(-) diff --git a/packages/qwik-speak/tools/core/format.ts b/packages/qwik-speak/tools/core/format.ts index a03776c..4b41ad1 100644 --- a/packages/qwik-speak/tools/core/format.ts +++ b/packages/qwik-speak/tools/core/format.ts @@ -4,12 +4,6 @@ export function toJsonString(target: Translation): string { return JSON.stringify(target, replacer, 2); } -export function minDepth(target: Translation): number { - return typeof target === 'object' && Object.keys(target).length > 0 ? - 1 + Math.min(1, ...Object.values(target).map(o => minDepth(o))) - : 0 -} - export function sortTarget(target: Translation) { return Object.keys(target).sort().reduce( (out: any, key: string) => { diff --git a/packages/qwik-speak/tools/extract/index.ts b/packages/qwik-speak/tools/extract/index.ts index 1d318b9..f2655c4 100644 --- a/packages/qwik-speak/tools/extract/index.ts +++ b/packages/qwik-speak/tools/extract/index.ts @@ -13,7 +13,7 @@ import { matchInlinePlural } from '../core/parser'; import { deepClone, deepMerge, deepSet, merge } from '../core/merge'; -import { minDepth, sortTarget, toJsonString } from '../core/format'; +import { sortTarget, toJsonString } from '../core/format'; import { getOptions, getRules } from '../core/intl-parser'; /** @@ -43,8 +43,6 @@ export async function qwikSpeakExtract(options: QwikSpeakExtractOptions) { const sourceFiles: string[] = []; // Translation data const translation: Translation = Object.fromEntries(resolvedOptions.supportedLangs.map(value => [value, {}])); - // Plurals - const pluralKeys: string[] = []; /** * Read source files recursively @@ -160,15 +158,17 @@ export async function qwikSpeakExtract(options: QwikSpeakExtractOptions) { } } - for (const rule of rules) { - let key = args?.[1]?.value; - if (key) { - pluralKeys.push(key); - key = `${key}${resolvedOptions.keySeparator}${rule}`; - } else { - key = rule; + const key = args?.[1]?.value; + if (key) { + const valueObj: any = {}; + for (const rule of rules) { + valueObj[rule] = ''; + } + keys.push(`${key}${resolvedOptions.keyValueSeparator}${JSON.stringify(valueObj)}`); + } else { + for (const rule of rules) { + keys.push(rule); } - keys.push(key); } } } @@ -227,7 +227,7 @@ export async function qwikSpeakExtract(options: QwikSpeakExtractOptions) { * min depth > 0: filenames = each top-level property name * min depth = 0: filename = 'app' */ - const writeAssets = async () => { + const writeAssets = async (prefixes: string[]) => { for (const lang of resolvedOptions.supportedLangs) { const baseAssets = normalize(`${resolvedOptions.basePath}/${resolvedOptions.assetsPath}/${lang}`); @@ -236,13 +236,9 @@ export async function qwikSpeakExtract(options: QwikSpeakExtractOptions) { } const topLevelKeys = Object.keys(translation[lang]) - .filter(key => pluralKeys.includes(key) ? - minDepth(translation[lang][key]) > 1 : - minDepth(translation[lang][key]) > 0); + .filter(key => !prefixes.includes(key)); const bottomLevelKeys = Object.keys(translation[lang]) - .filter(key => pluralKeys.includes(key) ? - minDepth(translation[lang][key]) === 1 : - minDepth(translation[lang][key]) === 0); + .filter(key => prefixes.includes(key)); const bottomTranslation: Translation = {}; if (translation[lang][resolvedOptions.filename]) { @@ -296,6 +292,7 @@ export async function qwikSpeakExtract(options: QwikSpeakExtractOptions) { keys = [...new Set(keys)]; stats.set('unique keys', (stats.get('unique keys') ?? 0) + keys.length); + const prefixes: string[] = []; /* Deep set in translation data */ for (let key of keys) { let defaultValue: string | Translation | undefined = undefined; @@ -310,6 +307,8 @@ export async function qwikSpeakExtract(options: QwikSpeakExtractOptions) { for (const lang of resolvedOptions.supportedLangs) { deepSet(translation[lang], key.split(resolvedOptions.keySeparator), deepClone(defaultValue || '')); } + + prefixes.push(key); } /* Read assets */ @@ -328,7 +327,7 @@ export async function qwikSpeakExtract(options: QwikSpeakExtractOptions) { } /* Write translation data */ - await writeAssets(); + await writeAssets(prefixes); /* Log */ for (const [key, value] of stats) { diff --git a/packages/qwik-speak/tools/tests/extract.test.ts b/packages/qwik-speak/tools/tests/extract.test.ts index 0f91f44..8a7c0e3 100644 --- a/packages/qwik-speak/tools/tests/extract.test.ts +++ b/packages/qwik-speak/tools/tests/extract.test.ts @@ -14,7 +14,7 @@ vi.mock('fs/promises', async () => { ...mod, readdir: vi.fn() .mockImplementationOnce(() => [{ name: 'home.tsx', isDirectory: () => false }]) - .mockImplementationOnce(() => ['home.json']), + .mockImplementationOnce(() => ['app.json']), readFile: vi.fn() .mockImplementationOnce(() => mockSource) .mockImplementationOnce(() => mockAsset), @@ -32,13 +32,7 @@ describe('extract', () => { expect(readdir).toHaveBeenCalledTimes(2); expect(readFile).toHaveBeenCalledTimes(2); - expect(writeFile).toHaveBeenCalledTimes(2); - expect(writeFile).toHaveBeenNthCalledWith(1, normalize('../../i18n/en-US/app.json'), `{ - "app": { - "subtitle": "", - "title": "" - } -}`); - expect(writeFile).toHaveBeenNthCalledWith(2, normalize('../../i18n/en-US/home.json'), mockExtractedAsset); + expect(writeFile).toHaveBeenCalledTimes(1); + expect(writeFile).toHaveBeenNthCalledWith(1, normalize('../../i18n/en-US/app.json'), mockExtractedAsset); }); }); diff --git a/packages/qwik-speak/tools/tests/format.test.ts b/packages/qwik-speak/tools/tests/format.test.ts index 9a87815..1ddab39 100644 --- a/packages/qwik-speak/tools/tests/format.test.ts +++ b/packages/qwik-speak/tools/tests/format.test.ts @@ -1,19 +1,8 @@ import { test, describe, expect } from 'vitest'; -import { minDepth, sortTarget } from '../core/format'; +import { sortTarget } from '../core/format'; describe('format', () => { - test('minDepth', () => { - let target = {}; - let depth = minDepth(target); - expect(depth).toBe(0); - target = { key1: { subkey1: 'Subkey1' }, key2: 'Key2' }; - depth = minDepth(target); - expect(depth).toBe(1); - target = { key1: { subkey1: 'Subkey1' }, key2: { subkey2: 'Subkey2' } }; - depth = minDepth(target); - expect(depth).toBe(2); - }); test('sortTarget', () => { let target = { b: { b: 'B', a: 'A' }, a: 'A' }; target = sortTarget(target); diff --git a/packages/qwik-speak/tools/tests/inline.test.ts b/packages/qwik-speak/tools/tests/inline.test.ts index 1890185..9e5f8cb 100644 --- a/packages/qwik-speak/tools/tests/inline.test.ts +++ b/packages/qwik-speak/tools/tests/inline.test.ts @@ -4,7 +4,7 @@ import { writeFile } from 'fs/promises'; import { normalize } from 'path'; import { getRules } from '../core/intl-parser'; -import { getValue, inlineTranslate, qwikSpeakInline, transform, transformTranslate, transpileTranslateFn, transpilePluralFn } from '../inline/plugin'; +import { getValue, inlineTranslate, qwikSpeakInline, transformTranslate, transpileTranslateFn, transpilePluralFn } from '../inline/plugin'; import { mockChunkCode, mockCode, mockInlinedCode, mockInlinedCodeByLang, mockTransformedCode, mockTranslatedAsset, mockTranslatedAssetByLang } from './mock'; // Mock part of 'fs' module @@ -118,18 +118,18 @@ describe('inline', () => { }); test('transpilePluralFn', () => { const rules = getRules('en-US'); - const line = transpilePluralFn(rules, 'en-US', '__qsInline', + const line = transpilePluralFn(rules, 'en-US', '__qsInlineTranslate', [ { type: 'Identifier', value: 'count.value' }, { type: 'Literal', value: 'home.devs' } ], { keySeparator: '.' } as any ); - expect(line).toBe('(new Intl.PluralRules(`en-US`).select(+count.value) === `other` && __qsInline(`home.devs.other`, {value: count.value}, `en-US`) || __qsInline(`home.devs.one`, {value: count.value}, `en-US`))'); + expect(line).toBe('(new Intl.PluralRules(`en-US`).select(+count.value) === `other` && __qsInlineTranslate(`home.devs.other`, {value: count.value}, `en-US`) || __qsInlineTranslate(`home.devs.one`, {value: count.value}, `en-US`))'); }); test('transpilePluralFn with params and options', () => { const rules = getRules('en-US'); - const line = transpilePluralFn(rules, 'en-US', '__qsInline', + const line = transpilePluralFn(rules, 'en-US', '__qsInlineTranslate', [ { type: 'Identifier', value: 'count.value' }, { type: 'Literal', value: 'home.devs' }, @@ -150,10 +150,10 @@ describe('inline', () => { ], { keySeparator: '.' } as any ); - expect(line).toBe('(new Intl.PluralRules(`en-US`, {type: `cardinal`}).select(+count.value) === `other` && __qsInline(`home.devs.other`, {value: count.value, role: `software`}, `en-US`) || __qsInline(`home.devs.one`, {value: count.value, role: `software`}, `en-US`))'); + expect(line).toBe('(new Intl.PluralRules(`en-US`, {type: `cardinal`}).select(+count.value) === `other` && __qsInlineTranslate(`home.devs.other`, {value: count.value, role: `software`}, `en-US`) || __qsInlineTranslate(`home.devs.one`, {value: count.value, role: `software`}, `en-US`))'); }); test('inline arrays', async () => { - const inlined = inlineTranslate(`const values = __qsInline(['app.title', 'app.subtitle'])`, + const inlined = inlineTranslate(`const values = __qsInlineTranslate(['app.title', 'app.subtitle'])`, { 'en-US': { 'app': { @@ -162,7 +162,7 @@ describe('inline', () => { } } }, - '__qsInline', + '__qsInlineTranslate', 'en-US', { supportedLangs: ['en-US', 'it-IT'], @@ -176,8 +176,8 @@ describe('inline', () => { expect(inlined).toBe('const values = [`Qwik Speak`,`Translate your Qwik apps into any language`]'); }); test('transform & inline multilingual', async () => { - const code = `import { useTranslate } from "qwik-speak";const t = useTranslate();const value = t('app.subtitle', undefined, 'it-IT')`; - const transformed = transform(code, ''); + const code = `import { inlineTranslate } from "qwik-speak";const t = inlineTranslate();const value = t('app.subtitle', undefined, 'it-IT')`; + const transformed = transformTranslate(code); const inlined = inlineTranslate(transformed, { 'en-US': { @@ -191,30 +191,6 @@ describe('inline', () => { } } }, - '__qsInline', - 'en-US', - { - supportedLangs: ['en-US', 'it-IT'], - defaultLang: 'en-US', - keySeparator: '.', - keyValueSeparator: '@@', - basePath: './', - assetsPath: 'i18n', - outDir: 'dist' - }); - expect(inlined).toBe('import { useTranslate } from "qwik-speak";const t = useTranslate();const value = `Traduci le tue app Qwik in qualsiasi lingua`'); - }); - test('transform & inlineTranslate', async () => { - const code = `import { inlineTranslate as _ } from "qwik-speak";const value = _('app.subtitle')`; - const transformed = transformTranslate(code, ''); - const inlined = inlineTranslate(transformed, - { - 'en-US': { - 'app': { - 'subtitle': 'Translate your Qwik apps into any language', - } - } - }, '__qsInlineTranslate', 'en-US', { @@ -226,7 +202,7 @@ describe('inline', () => { assetsPath: 'i18n', outDir: 'dist' }); - expect(inlined).toBe('import { inlineTranslate as _ } from "qwik-speak";const value = `Translate your Qwik apps into any language`'); + expect(inlined).toBe('import { inlineTranslate } from "qwik-speak";const value = `Traduci le tue app Qwik in qualsiasi lingua`'); }); test('writeChunks', async () => { const plugin = qwikSpeakInline({ diff --git a/packages/qwik-speak/tools/tests/mock.ts b/packages/qwik-speak/tools/tests/mock.ts index b35a156..2217b67 100644 --- a/packages/qwik-speak/tools/tests/mock.ts +++ b/packages/qwik-speak/tools/tests/mock.ts @@ -1,33 +1,33 @@ /* eslint-disable */ export const mockSource = `import { component$, useSignal } from '@builder.io/qwik'; -import type { DocumentHead } from '@builder.io/qwik-city'; +import { type DocumentHead } from '@builder.io/qwik-city'; import { - Speak, - inlineTranslate as _, + inlineTranslate, + inlinePlural, useFormatDate, useFormatNumber, - usePlural, useRelativeTime, useSpeakLocale, - useTranslate + type Translation } from 'qwik-speak'; -import type { Translation } from 'qwik-speak'; interface TitleProps { name: string; } -export const Title = component$((props: TitleProps) => { +export const Title = component$(props => { return (

    {props.name}

    ) }); export const SubTitle = () => { - return

    {_('app.subtitle')}

    ; + const t = inlineTranslate(); + return

    {t('app.subtitle')}

    ; }; -export const Home = component$(() => { - const t = useTranslate(); - const p = usePlural(); +export default component$(() => { + const t = inlineTranslate(); + const p = inlinePlural(); + const fd = useFormatDate(); const rt = useRelativeTime(); const fn = useFormatNumber(); @@ -37,11 +37,11 @@ export const Home = component$(() => { const count = useSignal(0); - const tParam = t('home.greeting', { name: t('app.title') }); - const tArray = t('home.array@@["{{ name }} one", "{{ name }} two"]', { name: 'n.' }); - const item = t('home.array.2@@{{ name }} three', { name: 'n.' }); - const tObject = t('home.obj@@{"one": "{{ name }} one", "two": "{{ name }} two"}', { name: 'n.' }); - const tArrayObjects = t('home.arrayObjects@@[{"num": "one"}, {"num": "two"}]'); + const tParam = t('greeting', { name: t('app.title') }); + const tArray = t('array@@["{{ name }} one", "{{ name }} two"]', { name: 'n.' }); + const item = t('array.2@@{{ name }} three', { name: 'n.' }); + const tObject = t('obj@@{"one": "{{ name }} one", "two": "{{ name }} two"}', { name: 'n.' }); + const tArrayObjects = t('arrayObjects@@[{"num": "one"}, {"num": "two"}]'); console.log(tParam); tArray.map((x) => console.log(x)); console.log(item); @@ -50,25 +50,25 @@ export const Home = component$(() => { return (
    - + <Title name={t('app.title')} /> <SubTitle /> - <h3>{t('home.params')}</h3> - <p>{t('home.greeting', { name: 'Qwik Speak' })}</p> + <h3>{t('params')}</h3> + <p>{t('greeting', { name: 'Qwik Speak' })}</p> - <h3>{t('home.tags')}</h3> - <p dangerouslySetInnerHTML={_('home.text')}></p> + <h3>{t('tags')}</h3> + <p dangerouslySetInnerHTML={t('description')}></p> - <h3>{t('home.plural')}</h3> - <p class="counter">{p(count.value, 'home.devs')}</p> - <button class="btn-counter" onClick$={() => count.value++}>{t('home.increment')}</button> + <h3>{t('plural')}</h3> + <p class="counter">{p(count.value, 'devs')}</p> + <button class="btn-counter" onClick$={() => count.value++}>{t('increment')}</button> - <h3>{t('home.dates')}</h3> + <h3>{t('dates')}</h3> <p>{fd(Date.now(), { dateStyle: 'full', timeStyle: 'short' })}</p> <p>{rt(-1, 'second')}</p> - <h3>{t('home.numbers')}</h3> + <h3>{t('numbers')}</h3> <p>{fn(1000000)}</p> <p>{fn(1000000, { style: 'currency' })}</p> <p>{fn(1, { style: 'unit', unit: units['length'] })}</p> @@ -76,52 +76,53 @@ export const Home = component$(() => { ); }); -export default component$(() => { - return ( - /** - * Add Home translations (only available in child components) - */ - <Speak assets={['home']}> - <Home /> - </Speak> - ); -}); +export const head: DocumentHead = () => { + const t = inlineTranslate(); -export const head: DocumentHead = { - title: 'runtime.head.home.title', - meta: [{ name: 'description', content: 'runtime.head.home.description' }] + return { + title: t('app.head.home.title', { name: 'Qwik Speak' }), + meta: [ + { + name: 'description', + content: t('app.head.home.description') + } + ], + }; };`; export const mockCode = `import { SubTitle } from "./routes/[...lang]/index.tsx"; import { Title } from "./routes/[...lang]/index.tsx"; -import { inlineTranslate as _ } from "qwik-speak"; -import { _IMMUTABLE } from "@builder.io/qwik"; -import { _fnSignal } from "@builder.io/qwik"; import { _jsxC } from "@builder.io/qwik"; import { _jsxQ } from "@builder.io/qwik"; -import { qrl } from "@builder.io/qwik"; +import { inlinePlural } from "qwik-speak"; +import { inlineTranslate } from "qwik-speak"; +import { qrlDEV } from "@builder.io/qwik"; import { useFormatDate } from "qwik-speak"; import { useFormatNumber } from "qwik-speak"; -import { usePlural } from "qwik-speak"; import { useRelativeTime } from "qwik-speak"; import { useSignal } from "@builder.io/qwik"; import { useSpeakLocale } from "qwik-speak"; -import { useTranslate } from "qwik-speak"; -export const s_dYGb4b0cyCA = ()=>{ - const t = useTranslate(); - const p = usePlural(); +export const ____lang__component_eTU0cN78ZUc = ()=>{ + const t = inlineTranslate(); + const p = inlinePlural(); const fd = useFormatDate(); const rt = useRelativeTime(); const fn = useFormatNumber(); const locale = useSpeakLocale(); const count = useSignal(0); - const tParam = t('home.greeting', { + const tParam = t('greeting', { name: t('app.title') }); - const tArray = t('home.array@@["{{ name }} one", "{{ name }} two"]', { name: 'n.' }); - const item = t('home.array.2@@{{ name }} three', { name: 'n.' }); - const tObject = t('home.obj@@{"one": "{{ name }} one", "two": "{{ name }} two"}', { name: 'n.' }); - const tArrayObjects = t('home.arrayObjects@@[{"num": "one"}, {"num": "two"}]'); + const tArray = t('array@@["{{ name }} one", "{{ name }} two"]', { + name: 'n.' + }); + const item = t('array.2@@{{ name }} three', { + name: 'n.' + }); + const tObject = t('obj@@{"one": "{{ name }} one", "two": "{{ name }} two"}', { + name: 'n.' + }); + const tArrayObjects = t('arrayObjects@@[{"num": "one"}, {"num": "two"}]'); console.log(tParam); tArray.map((x)=>console.log(x)); console.log(item); @@ -131,80 +132,151 @@ export const s_dYGb4b0cyCA = ()=>{ class: "content" }, [ /*#__PURE__*/ _jsxC(Title, { - get name () { - return _('app.title'); - }, - [_IMMUTABLE]: { - name: _IMMUTABLE - } - }, 3, "1L_2"), - /*#__PURE__*/ _jsxC(SubTitle, null, 3, "1L_3"), - /*#__PURE__*/ _jsxQ("h3", null, null, t('home.params'), 1, null), - /*#__PURE__*/ _jsxQ("p", null, null, t('home.greeting', { + name: t('app.title') + }, 3, "1L_2", { + fileName: "routes/[...lang]/index.tsx", + lineNumber: 52, + columnNumber: 7 + }), + /*#__PURE__*/ _jsxC(SubTitle, null, 3, "1L_3", { + fileName: "routes/[...lang]/index.tsx", + lineNumber: 54, + columnNumber: 7 + }), + /*#__PURE__*/ _jsxQ("h3", null, null, t('params'), 1, null, { + fileName: "routes/[...lang]/index.tsx", + lineNumber: 56, + columnNumber: 7 + }), + /*#__PURE__*/ _jsxQ("p", null, null, t('greeting', { name: 'Qwik Speak' - }), 1, null), - /*#__PURE__*/ _jsxQ("h3", null, null, t('home.tags'), 1, null), - /*#__PURE__*/ _jsxQ("p", null, { - dangerouslySetInnerHTML: _('home.text') - }, null, 3, null), - /*#__PURE__*/ _jsxQ("h3", null, null, t('home.plural'), 1, null), + }), 1, null, { + fileName: "routes/[...lang]/index.tsx", + lineNumber: 57, + columnNumber: 7 + }), + /*#__PURE__*/ _jsxQ("h3", null, null, t('tags'), 1, null, { + fileName: "routes/[...lang]/index.tsx", + lineNumber: 59, + columnNumber: 7 + }), + /*#__PURE__*/ _jsxQ("p", { + dangerouslySetInnerHTML: t('description') + }, null, null, 3, null, { + fileName: "routes/[...lang]/index.tsx", + lineNumber: 60, + columnNumber: 7 + }), + /*#__PURE__*/ _jsxQ("h3", null, null, t('plural'), 1, null, { + fileName: "routes/[...lang]/index.tsx", + lineNumber: 62, + columnNumber: 7 + }), /*#__PURE__*/ _jsxQ("p", null, { class: "counter" - }, __qsInlinePlural(count.value, 'home.devs'), 1, null), + }, p(count.value, 'devs'), 1, null, { + fileName: "routes/[...lang]/index.tsx", + lineNumber: 63, + columnNumber: 7 + }), /*#__PURE__*/ _jsxQ("button", null, { class: "btn-counter", - onClick$: /*#__PURE__*/ qrl(()=>import("./entry_Home.js"), "s_UVYDAmatcag", [ + onClick$: /*#__PURE__*/ qrlDEV(()=>import("./____lang__component_div_button_onclick_xgivgs2jpeg.js"), "____lang__component_div_button_onClick_XGiVgs2jpeg", { + file: "/home/robisim74/documents/github/qwik-speak/src/routes/[...lang]/index.tsx", + lo: 1796, + hi: 1815, + displayName: "____lang__component_div_button_onClick" + }, [ count ]) - }, t('home.increment'), 1, null), - /*#__PURE__*/ _jsxQ("h3", null, null, t('home.dates'), 1, null), + }, t('increment'), 1, null, { + fileName: "routes/[...lang]/index.tsx", + lineNumber: 64, + columnNumber: 7 + }), + /*#__PURE__*/ _jsxQ("h3", null, null, t('dates'), 1, null, { + fileName: "routes/[...lang]/index.tsx", + lineNumber: 66, + columnNumber: 7 + }), /*#__PURE__*/ _jsxQ("p", null, null, fd(Date.now(), { dateStyle: 'full', timeStyle: 'short' - }), 1, null), - /*#__PURE__*/ _jsxQ("p", null, null, rt(-1, 'second'), 1, null), - /*#__PURE__*/ _jsxQ("h3", null, null, t('home.numbers'), 1, null), - /*#__PURE__*/ _jsxQ("p", null, null, fn(1000000), 1, null), + }), 1, null, { + fileName: "routes/[...lang]/index.tsx", + lineNumber: 67, + columnNumber: 7 + }), + /*#__PURE__*/ _jsxQ("p", null, null, rt(-1, 'second'), 1, null, { + fileName: "routes/[...lang]/index.tsx", + lineNumber: 68, + columnNumber: 7 + }), + /*#__PURE__*/ _jsxQ("h3", null, null, t('numbers'), 1, null, { + fileName: "routes/[...lang]/index.tsx", + lineNumber: 70, + columnNumber: 7 + }), + /*#__PURE__*/ _jsxQ("p", null, null, fn(1000000), 1, null, { + fileName: "routes/[...lang]/index.tsx", + lineNumber: 71, + columnNumber: 7 + }), /*#__PURE__*/ _jsxQ("p", null, null, fn(1000000, { style: 'currency' - }), 1, null), + }), 1, null, { + fileName: "routes/[...lang]/index.tsx", + lineNumber: 72, + columnNumber: 7 + }), /*#__PURE__*/ _jsxQ("p", null, null, fn(1, { style: 'unit', unit: locale.units['length'] - }), 1, null) - ], 1, "1L_4"); + }), 1, null, { + fileName: "routes/[...lang]/index.tsx", + lineNumber: 73, + columnNumber: 7 + }) + ], 1, "1L_4", { + fileName: "routes/[...lang]/index.tsx", + lineNumber: 51, + columnNumber: 5 + }); };`; export const mockTransformedCode = `import { SubTitle } from "./routes/[...lang]/index.tsx"; import { Title } from "./routes/[...lang]/index.tsx"; -import { inlineTranslate as _ } from "qwik-speak"; -import { _IMMUTABLE } from "@builder.io/qwik"; -import { _fnSignal } from "@builder.io/qwik"; import { _jsxC } from "@builder.io/qwik"; import { _jsxQ } from "@builder.io/qwik"; -import { qrl } from "@builder.io/qwik"; +import { inlinePlural } from "qwik-speak"; +import { inlineTranslate } from "qwik-speak"; +import { qrlDEV } from "@builder.io/qwik"; import { useFormatDate } from "qwik-speak"; import { useFormatNumber } from "qwik-speak"; -import { usePlural } from "qwik-speak"; import { useRelativeTime } from "qwik-speak"; import { useSignal } from "@builder.io/qwik"; import { useSpeakLocale } from "qwik-speak"; -import { useTranslate } from "qwik-speak"; -export const s_dYGb4b0cyCA = ()=>{ - const t = useTranslate(); - const p = usePlural(); +export const ____lang__component_eTU0cN78ZUc = ()=>{ + + const fd = useFormatDate(); const rt = useRelativeTime(); const fn = useFormatNumber(); const locale = useSpeakLocale(); const count = useSignal(0); - const tParam = __qsInline('home.greeting', { - name: __qsInline('app.title') + const tParam = __qsInlineTranslate('greeting', { + name: __qsInlineTranslate('app.title') + }); + const tArray = __qsInlineTranslate('array@@["{{ name }} one", "{{ name }} two"]', { + name: 'n.' + }); + const item = __qsInlineTranslate('array.2@@{{ name }} three', { + name: 'n.' }); - const tArray = __qsInline('home.array@@["{{ name }} one", "{{ name }} two"]', { name: 'n.' }); - const item = __qsInline('home.array.2@@{{ name }} three', { name: 'n.' }); - const tObject = __qsInline('home.obj@@{"one": "{{ name }} one", "two": "{{ name }} two"}', { name: 'n.' }); - const tArrayObjects = __qsInline('home.arrayObjects@@[{"num": "one"}, {"num": "two"}]'); + const tObject = __qsInlineTranslate('obj@@{"one": "{{ name }} one", "two": "{{ name }} two"}', { + name: 'n.' + }); + const tArrayObjects = __qsInlineTranslate('arrayObjects@@[{"num": "one"}, {"num": "two"}]'); console.log(tParam); tArray.map((x)=>console.log(x)); console.log(item); @@ -214,127 +286,192 @@ export const s_dYGb4b0cyCA = ()=>{ class: "content" }, [ /*#__PURE__*/ _jsxC(Title, { - get name () { - return __qsInlineTranslate('app.title'); - }, - [_IMMUTABLE]: { - name: _IMMUTABLE - } - }, 3, "1L_2"), - /*#__PURE__*/ _jsxC(SubTitle, null, 3, "1L_3"), - /*#__PURE__*/ _jsxQ("h3", null, null, __qsInline('home.params'), 1, null), - /*#__PURE__*/ _jsxQ("p", null, null, __qsInline('home.greeting', { + name: __qsInlineTranslate('app.title') + }, 3, "1L_2", { + fileName: "routes/[...lang]/index.tsx", + lineNumber: 52, + columnNumber: 7 + }), + /*#__PURE__*/ _jsxC(SubTitle, null, 3, "1L_3", { + fileName: "routes/[...lang]/index.tsx", + lineNumber: 54, + columnNumber: 7 + }), + /*#__PURE__*/ _jsxQ("h3", null, null, __qsInlineTranslate('params'), 1, null, { + fileName: "routes/[...lang]/index.tsx", + lineNumber: 56, + columnNumber: 7 + }), + /*#__PURE__*/ _jsxQ("p", null, null, __qsInlineTranslate('greeting', { name: 'Qwik Speak' - }), 1, null), - /*#__PURE__*/ _jsxQ("h3", null, null, __qsInline('home.tags'), 1, null), - /*#__PURE__*/ _jsxQ("p", null, { - dangerouslySetInnerHTML: __qsInlineTranslate('home.text') - }, null, 3, null), - /*#__PURE__*/ _jsxQ("h3", null, null, __qsInline('home.plural'), 1, null), + }), 1, null, { + fileName: "routes/[...lang]/index.tsx", + lineNumber: 57, + columnNumber: 7 + }), + /*#__PURE__*/ _jsxQ("h3", null, null, __qsInlineTranslate('tags'), 1, null, { + fileName: "routes/[...lang]/index.tsx", + lineNumber: 59, + columnNumber: 7 + }), + /*#__PURE__*/ _jsxQ("p", { + dangerouslySetInnerHTML: __qsInlineTranslate('description') + }, null, null, 3, null, { + fileName: "routes/[...lang]/index.tsx", + lineNumber: 60, + columnNumber: 7 + }), + /*#__PURE__*/ _jsxQ("h3", null, null, __qsInlineTranslate('plural'), 1, null, { + fileName: "routes/[...lang]/index.tsx", + lineNumber: 62, + columnNumber: 7 + }), /*#__PURE__*/ _jsxQ("p", null, { class: "counter" - }, __qsInlinePlural(count.value, 'home.devs'), 1, null), + }, __qsInlinePlural(count.value, 'devs'), 1, null, { + fileName: "routes/[...lang]/index.tsx", + lineNumber: 63, + columnNumber: 7 + }), /*#__PURE__*/ _jsxQ("button", null, { class: "btn-counter", - onClick$: /*#__PURE__*/ qrl(()=>import("./entry_Home.js"), "s_UVYDAmatcag", [ + onClick$: /*#__PURE__*/ qrlDEV(()=>import("./____lang__component_div_button_onclick_xgivgs2jpeg.js"), "____lang__component_div_button_onClick_XGiVgs2jpeg", { + file: "/home/robisim74/documents/github/qwik-speak/src/routes/[...lang]/index.tsx", + lo: 1796, + hi: 1815, + displayName: "____lang__component_div_button_onClick" + }, [ count ]) - }, __qsInline('home.increment'), 1, null), - /*#__PURE__*/ _jsxQ("h3", null, null, __qsInline('home.dates'), 1, null), + }, __qsInlineTranslate('increment'), 1, null, { + fileName: "routes/[...lang]/index.tsx", + lineNumber: 64, + columnNumber: 7 + }), + /*#__PURE__*/ _jsxQ("h3", null, null, __qsInlineTranslate('dates'), 1, null, { + fileName: "routes/[...lang]/index.tsx", + lineNumber: 66, + columnNumber: 7 + }), /*#__PURE__*/ _jsxQ("p", null, null, fd(Date.now(), { dateStyle: 'full', timeStyle: 'short' - }), 1, null), - /*#__PURE__*/ _jsxQ("p", null, null, rt(-1, 'second'), 1, null), - /*#__PURE__*/ _jsxQ("h3", null, null, __qsInline('home.numbers'), 1, null), - /*#__PURE__*/ _jsxQ("p", null, null, fn(1000000), 1, null), + }), 1, null, { + fileName: "routes/[...lang]/index.tsx", + lineNumber: 67, + columnNumber: 7 + }), + /*#__PURE__*/ _jsxQ("p", null, null, rt(-1, 'second'), 1, null, { + fileName: "routes/[...lang]/index.tsx", + lineNumber: 68, + columnNumber: 7 + }), + /*#__PURE__*/ _jsxQ("h3", null, null, __qsInlineTranslate('numbers'), 1, null, { + fileName: "routes/[...lang]/index.tsx", + lineNumber: 70, + columnNumber: 7 + }), + /*#__PURE__*/ _jsxQ("p", null, null, fn(1000000), 1, null, { + fileName: "routes/[...lang]/index.tsx", + lineNumber: 71, + columnNumber: 7 + }), /*#__PURE__*/ _jsxQ("p", null, null, fn(1000000, { style: 'currency' - }), 1, null), + }), 1, null, { + fileName: "routes/[...lang]/index.tsx", + lineNumber: 72, + columnNumber: 7 + }), /*#__PURE__*/ _jsxQ("p", null, null, fn(1, { style: 'unit', unit: locale.units['length'] - }), 1, null) - ], 1, "1L_4"); + }), 1, null, { + fileName: "routes/[...lang]/index.tsx", + lineNumber: 73, + columnNumber: 7 + }) + ], 1, "1L_4", { + fileName: "routes/[...lang]/index.tsx", + lineNumber: 51, + columnNumber: 5 + }); };`; -export const mockChunkCode = `const s_dYGb4b0cyCA = () => { - useTranslate(); - usePlural(); +export const mockChunkCode = `const s_eTU0cN78ZUc = () => { const fd = useFormatDate(); const rt = useRelativeTime(); const fn = useFormatNumber(); const locale = useSpeakLocale(); - const count = Wc(0); - const tParam = __qsInline("home.greeting", { - name: __qsInline("app.title") + const count = Qc(0); + const tParam = __qsInlineTranslate("greeting", { + name: __qsInlineTranslate("app.title") + }); + const tArray = __qsInlineTranslate('array@@["{{ name }} one", "{{ name }} two"]', { + name: "n." + }); + const item = __qsInlineTranslate("array.2@@{{ name }} three", { + name: "n." }); - const tArray = __qsInline('home.array@@["{{ name }} one", "{{ name }} two"]', { name: 'n.' }); - const item = __qsInline('home.array.2@@{{ name }} three', { name: 'n.' }); - const tObject = __qsInline('home.obj@@{"one": "{{ name }} one", "two": "{{ name }} two"}', { name: 'n.' }); - const tArrayObjects = __qsInline('home.arrayObjects@@[{"num": "one"}, {"num": "two"}]'); + const tObject = __qsInlineTranslate('obj@@{"one": "{{ name }} one", "two": "{{ name }} two"}', { + name: "n." + }); + const tArrayObjects = __qsInlineTranslate('arrayObjects@@[{"num": "one"}, {"num": "two"}]'); console.log(tParam); tArray.map((x) => console.log(x)); console.log(item); Object.values(tObject).map((x) => console.log(x)); tArrayObjects.map((x) => console.log(x["num"])); - return /* @__PURE__ */ Sr("div", null, { + return /* @__PURE__ */ Cr("div", null, { class: "content" }, [ /* @__PURE__ */ jr(Title, { - get name() { - return __qsInlineTranslate("app.title"); - }, - [Y]: { - name: Y - } + name: __qsInlineTranslate("app.title") }, 3, "1L_2"), /* @__PURE__ */ jr(SubTitle, null, 3, "1L_3"), - /* @__PURE__ */ Sr("h3", null, null, __qsInline("home.params"), 1, null), - /* @__PURE__ */ Sr("p", null, null, __qsInline("home.greeting", { + /* @__PURE__ */ Cr("h3", null, null, __qsInlineTranslate("params"), 1, null), + /* @__PURE__ */ Cr("p", null, null, __qsInlineTranslate("greeting", { name: "Qwik Speak" }), 1, null), - /* @__PURE__ */ Sr("h3", null, null, __qsInline("home.tags"), 1, null), - /* @__PURE__ */ Sr("p", null, { - dangerouslySetInnerHTML: __qsInlineTranslate("home.text") - }, null, 3, null), - /* @__PURE__ */ Sr("h3", null, null, __qsInline("home.plural"), 1, null), - /* @__PURE__ */ Sr("p", null, { + /* @__PURE__ */ Cr("h3", null, null, __qsInlineTranslate("tags"), 1, null), + /* @__PURE__ */ Cr("p", { + dangerouslySetInnerHTML: __qsInlineTranslate("description") + }, null, null, 3, null), + /* @__PURE__ */ Cr("h3", null, null, __qsInlineTranslate("plural"), 1, null), + /* @__PURE__ */ Cr("p", null, { class: "counter" - }, __qsInlinePlural(count.value, "home.devs"), 1, null), - /* @__PURE__ */ Sr("button", null, { + }, __qsInlinePlural(count.value, "devs"), 1, null), + /* @__PURE__ */ Cr("button", null, { class: "btn-counter", - onClick$: /* @__PURE__ */ hs(() => __vitePreload(() => Promise.resolve().then(() => entry_Home), true ? void 0 : void 0), "s_UVYDAmatcag", [ + onClick$: /* @__PURE__ */ vs(() => __vitePreload(() => Promise.resolve().then(() => entry_____lang_), true ? void 0 : void 0), "s_XGiVgs2jpeg", [ count ]) - }, __qsInline("home.increment"), 1, null), - /* @__PURE__ */ Sr("h3", null, null, __qsInline("home.dates"), 1, null), - /* @__PURE__ */ Sr("p", null, null, fd(Date.now(), { + }, __qsInlineTranslate("increment"), 1, null), + /* @__PURE__ */ Cr("h3", null, null, __qsInlineTranslate("dates"), 1, null), + /* @__PURE__ */ Cr("p", null, null, fd(Date.now(), { dateStyle: "full", timeStyle: "short" }), 1, null), - /* @__PURE__ */ Sr("p", null, null, rt(-1, "second"), 1, null), - /* @__PURE__ */ Sr("h3", null, null, __qsInline("home.numbers"), 1, null), - /* @__PURE__ */ Sr("p", null, null, fn(1e6), 1, null), - /* @__PURE__ */ Sr("p", null, null, fn(1e6, { + /* @__PURE__ */ Cr("p", null, null, rt(-1, "second"), 1, null), + /* @__PURE__ */ Cr("h3", null, null, __qsInlineTranslate("numbers"), 1, null), + /* @__PURE__ */ Cr("p", null, null, fn(1e6), 1, null), + /* @__PURE__ */ Cr("p", null, null, fn(1e6, { style: "currency" }), 1, null), - /* @__PURE__ */ Sr("p", null, null, fn(1, { + /* @__PURE__ */ Cr("p", null, null, fn(1, { style: "unit", unit: locale.units["length"] }), 1, null) ], 1, "1L_4"); };`; -export const mockInlinedCode = `const s_dYGb4b0cyCA = () => { - useTranslate(); - usePlural(); +export const mockInlinedCode = `const s_eTU0cN78ZUc = () => { const fd = useFormatDate(); const rt = useRelativeTime(); const fn = useFormatNumber(); const locale = useSpeakLocale(); - const count = Wc(0); - const tParam = \`Hi! I am \${\`\`}\`; + const count = Qc(0); + const tParam = \`Hi! I am \${\`Qwik Speak\`}\`; const tArray = [\`n. one\`,\`n. two\`,\`n. three\`]; const item = \`n. three\`; const tObject = {"one":\`n. one\`,"two":\`n. two\`}; @@ -344,61 +481,54 @@ export const mockInlinedCode = `const s_dYGb4b0cyCA = () => { console.log(item); Object.values(tObject).map((x) => console.log(x)); tArrayObjects.map((x) => console.log(x["num"])); - return /* @__PURE__ */ Sr("div", null, { + return /* @__PURE__ */ Cr("div", null, { class: "content" }, [ /* @__PURE__ */ jr(Title, { - get name() { - return \`\`; - }, - [Y]: { - name: Y - } + name: \`Qwik Speak\` }, 3, "1L_2"), /* @__PURE__ */ jr(SubTitle, null, 3, "1L_3"), - /* @__PURE__ */ Sr("h3", null, null, \`Parameters\`, 1, null), - /* @__PURE__ */ Sr("p", null, null, \`Hi! I am Qwik Speak\`, 1, null), - /* @__PURE__ */ Sr("h3", null, null, \`Html tags\`, 1, null), - /* @__PURE__ */ Sr("p", null, { + /* @__PURE__ */ Cr("h3", null, null, \`Parameters\`, 1, null), + /* @__PURE__ */ Cr("p", null, null, \`Hi! I am Qwik Speak\`, 1, null), + /* @__PURE__ */ Cr("h3", null, null, \`Html tags\`, 1, null), + /* @__PURE__ */ Cr("p", { dangerouslySetInnerHTML: \`<em>Internationalization (i18n) library to translate texts, dates and numbers in Qwik apps</em>\` - }, null, 3, null), - /* @__PURE__ */ Sr("h3", null, null, \`Plural\`, 1, null), - /* @__PURE__ */ Sr("p", null, { + }, null, null, 3, null), + /* @__PURE__ */ Cr("h3", null, null, \`Plural\`, 1, null), + /* @__PURE__ */ Cr("p", null, { class: "counter" }, (new Intl.PluralRules(\`en-US\`).select(+count.value) === \`other\` && \`\${count.value} software developers\` || \`\${count.value} software developer\`), 1, null), - /* @__PURE__ */ Sr("button", null, { + /* @__PURE__ */ Cr("button", null, { class: "btn-counter", - onClick$: /* @__PURE__ */ hs(() => __vitePreload(() => Promise.resolve().then(() => entry_Home), true ? void 0 : void 0), "s_UVYDAmatcag", [ + onClick$: /* @__PURE__ */ vs(() => __vitePreload(() => Promise.resolve().then(() => entry_____lang_), true ? void 0 : void 0), "s_XGiVgs2jpeg", [ count ]) }, \`Increment\`, 1, null), - /* @__PURE__ */ Sr("h3", null, null, \`Dates & relative time\`, 1, null), - /* @__PURE__ */ Sr("p", null, null, fd(Date.now(), { + /* @__PURE__ */ Cr("h3", null, null, \`Dates & relative time\`, 1, null), + /* @__PURE__ */ Cr("p", null, null, fd(Date.now(), { dateStyle: "full", timeStyle: "short" }), 1, null), - /* @__PURE__ */ Sr("p", null, null, rt(-1, "second"), 1, null), - /* @__PURE__ */ Sr("h3", null, null, \`Numbers & currencies\`, 1, null), - /* @__PURE__ */ Sr("p", null, null, fn(1e6), 1, null), - /* @__PURE__ */ Sr("p", null, null, fn(1e6, { + /* @__PURE__ */ Cr("p", null, null, rt(-1, "second"), 1, null), + /* @__PURE__ */ Cr("h3", null, null, \`Numbers & currencies\`, 1, null), + /* @__PURE__ */ Cr("p", null, null, fn(1e6), 1, null), + /* @__PURE__ */ Cr("p", null, null, fn(1e6, { style: "currency" }), 1, null), - /* @__PURE__ */ Sr("p", null, null, fn(1, { + /* @__PURE__ */ Cr("p", null, null, fn(1, { style: "unit", unit: locale.units["length"] }), 1, null) ], 1, "1L_4"); };`; -export const mockInlinedCodeByLang = `const s_dYGb4b0cyCA = () => { - useTranslate(); - usePlural(); +export const mockInlinedCodeByLang = `const s_eTU0cN78ZUc = () => { const fd = useFormatDate(); const rt = useRelativeTime(); const fn = useFormatNumber(); const locale = useSpeakLocale(); - const count = Wc(0); - const tParam = \`Ciao! Sono \${\`\`}\`; + const count = Qc(0); + const tParam = \`Ciao! Sono \${\`Qwik Speak\`}\`; const tArray = [\`n. uno\`,\`n. due\`,\`n. tre\`]; const item = \`n. tre\`; const tObject = {"one":\`n. uno\`,"two":\`n. due\`}; @@ -408,46 +538,41 @@ export const mockInlinedCodeByLang = `const s_dYGb4b0cyCA = () => { console.log(item); Object.values(tObject).map((x) => console.log(x)); tArrayObjects.map((x) => console.log(x["num"])); - return /* @__PURE__ */ Sr("div", null, { + return /* @__PURE__ */ Cr("div", null, { class: "content" }, [ /* @__PURE__ */ jr(Title, { - get name() { - return \`\`; - }, - [Y]: { - name: Y - } + name: \`Qwik Speak\` }, 3, "1L_2"), /* @__PURE__ */ jr(SubTitle, null, 3, "1L_3"), - /* @__PURE__ */ Sr("h3", null, null, \`Parametri\`, 1, null), - /* @__PURE__ */ Sr("p", null, null, \`Ciao! Sono Qwik Speak\`, 1, null), - /* @__PURE__ */ Sr("h3", null, null, \`Tag Html\`, 1, null), - /* @__PURE__ */ Sr("p", null, { + /* @__PURE__ */ Cr("h3", null, null, \`Parametri\`, 1, null), + /* @__PURE__ */ Cr("p", null, null, \`Ciao! Sono Qwik Speak\`, 1, null), + /* @__PURE__ */ Cr("h3", null, null, \`Tag Html\`, 1, null), + /* @__PURE__ */ Cr("p", { dangerouslySetInnerHTML: \`<em>Libreria di internazionalizzazione (i18n) per tradurre testi, date e numeri nelle app Qwik</em>\` - }, null, 3, null), - /* @__PURE__ */ Sr("h3", null, null, \`Plurale\`, 1, null), - /* @__PURE__ */ Sr("p", null, { + }, null, null, 3, null), + /* @__PURE__ */ Cr("h3", null, null, \`Plurale\`, 1, null), + /* @__PURE__ */ Cr("p", null, { class: "counter" - }, (new Intl.PluralRules(\`it-IT\`).select(+count.value) === \`other\` && \`\${count.value} sviluppatori software\` || \`\${count.value} sviluppatore software\`), 1, null), - /* @__PURE__ */ Sr("button", null, { + }, (new Intl.PluralRules(\`it-IT\`).select(+count.value) === \`other\` && \`\${count.value} sviluppatori di software\` || \`\${count.value} sviluppatore di software\`), 1, null), + /* @__PURE__ */ Cr("button", null, { class: "btn-counter", - onClick$: /* @__PURE__ */ hs(() => __vitePreload(() => Promise.resolve().then(() => entry_Home), true ? void 0 : void 0), "s_UVYDAmatcag", [ + onClick$: /* @__PURE__ */ vs(() => __vitePreload(() => Promise.resolve().then(() => entry_____lang_), true ? void 0 : void 0), "s_XGiVgs2jpeg", [ count ]) - }, \`Incrementa\`, 1, null), - /* @__PURE__ */ Sr("h3", null, null, \`Date e tempo relativo\`, 1, null), - /* @__PURE__ */ Sr("p", null, null, fd(Date.now(), { + }, \`Incremento\`, 1, null), + /* @__PURE__ */ Cr("h3", null, null, \`Date e tempo relativo\`, 1, null), + /* @__PURE__ */ Cr("p", null, null, fd(Date.now(), { dateStyle: "full", timeStyle: "short" }), 1, null), - /* @__PURE__ */ Sr("p", null, null, rt(-1, "second"), 1, null), - /* @__PURE__ */ Sr("h3", null, null, \`Numeri e valute\`, 1, null), - /* @__PURE__ */ Sr("p", null, null, fn(1e6), 1, null), - /* @__PURE__ */ Sr("p", null, null, fn(1e6, { + /* @__PURE__ */ Cr("p", null, null, rt(-1, "second"), 1, null), + /* @__PURE__ */ Cr("h3", null, null, \`Numeri e valute\`, 1, null), + /* @__PURE__ */ Cr("p", null, null, fn(1e6), 1, null), + /* @__PURE__ */ Cr("p", null, null, fn(1e6, { style: "currency" }), 1, null), - /* @__PURE__ */ Sr("p", null, null, fn(1, { + /* @__PURE__ */ Cr("p", null, null, fn(1, { style: "unit", unit: locale.units["length"] }), 1, null) @@ -455,115 +580,126 @@ export const mockInlinedCodeByLang = `const s_dYGb4b0cyCA = () => { };`; export const mockAsset = JSON.stringify({ - "home": { - "dates": "Dates & relative time", - "greeting": "Hi! I am {{name}}", - "increment": "Increment", - "numbers": "Numbers & currencies", - "plural": "Plural", - "tags": "Html tags", - "text": "<em>Internationalization (i18n) library to translate texts, dates and numbers in Qwik apps</em>" - } + "dates": "Dates & relative time", + "greeting": "Hi! I am {{name}}", + "increment": "Increment", + "numbers": "Numbers & currencies", + "params": "Parameters", + "plural": "Plural", + "tags": "Html tags", + "description": "<em>Internationalization (i18n) library to translate texts, dates and numbers in Qwik apps</em>" }, null, 2); export const mockExtractedAsset = JSON.stringify({ - "home": { - "array": [ - "{{ name }} one", - "{{ name }} two", - "{{ name }} three" - ], - "arrayObjects": [ - { - "num": "one" - }, - { - "num": "two" + "app": { + "head": { + "home": { + "description": "", + "title": "" } - ], - "dates": "Dates & relative time", - "devs": { - "one": "", - "other": "" }, - "greeting": "Hi! I am {{name}}", - "increment": "Increment", - "numbers": "Numbers & currencies", - "obj": { - "one": "{{ name }} one", - "two": "{{ name }} two" + "subtitle": "", + "title": "" + }, + "array": [ + "{{ name }} one", + "{{ name }} two", + "{{ name }} three" + ], + "arrayObjects": [ + { + "num": "one" }, - "params": "", - "plural": "Plural", - "tags": "Html tags", - "text": "<em>Internationalization (i18n) library to translate texts, dates and numbers in Qwik apps</em>" - } + { + "num": "two" + } + ], + "dates": "Dates & relative time", + "description": "<em>Internationalization (i18n) library to translate texts, dates and numbers in Qwik apps</em>", + "devs": { + "one": "", + "other": "" + }, + "greeting": "Hi! I am {{name}}", + "increment": "Increment", + "numbers": "Numbers & currencies", + "obj": { + "one": "{{ name }} one", + "two": "{{ name }} two" + }, + "params": "Parameters", + "plural": "Plural", + "tags": "Html tags" }, null, 2); export const mockTranslatedAsset = JSON.stringify({ - "home": { - "array": [ - "{{ name }} one", - "{{ name }} two", - "{{ name }} three" - ], - "arrayObjects": [ - { - "num": "one" - }, - { - "num": "two" - } - ], - "dates": "Dates & relative time", - "devs": { - "one": "{{ value }} software developer", - "other": "{{ value }} software developers" + "app": { + "subtitle": "Translate your Qwik apps into any language", + "title": "Qwik Speak" + }, + "array": [ + "{{ name }} one", + "{{ name }} two", + "{{ name }} three" + ], + "arrayObjects": [ + { + "num": "one" }, - "greeting": "Hi! I am {{name}}", - "increment": "Increment", - "numbers": "Numbers & currencies", - "obj": { - "one": "{{ name }} one", - "two": "{{ name }} two" - }, - "params": "Parameters", - "plural": "Plural", - "tags": "Html tags", - "text": "<em>Internationalization (i18n) library to translate texts, dates and numbers in Qwik apps</em>" - } + { + "num": "two" + } + ], + "dates": "Dates & relative time", + "description": "<em>Internationalization (i18n) library to translate texts, dates and numbers in Qwik apps</em>", + "devs": { + "one": "{{ value }} software developer", + "other": "{{ value }} software developers" + }, + "greeting": "Hi! I am {{name}}", + "increment": "Increment", + "numbers": "Numbers & currencies", + "obj": { + "one": "{{ name }} one", + "two": "{{ name }} two" + }, + "params": "Parameters", + "plural": "Plural", + "tags": "Html tags" }, null, 2); export const mockTranslatedAssetByLang = JSON.stringify({ - "home": { - "array": [ - "{{ name }} uno", - "{{ name }} due", - "{{ name }} tre" - ], - "arrayObjects": [ - { - "num": "uno" - }, - { - "num": "due" - } - ], - "dates": "Date e tempo relativo", - "devs": { - "one": "{{ value }} sviluppatore software", - "other": "{{ value }} sviluppatori software" - }, - "greeting": "Ciao! Sono {{name}}", - "increment": "Incrementa", - "numbers": "Numeri e valute", - "obj": { - "one": "{{ name }} uno", - "two": "{{ name }} due" + "app": { + "subtitle": "Traduci le tue app Qwik in qualsiasi lingua", + "title": "Qwik Speak" + }, + "array": [ + "{{ name }} uno", + "{{ name }} due", + "{{ name }} tre" + ], + "arrayObjects": [ + { + "num": "uno" }, - "params": "Parametri", - "plural": "Plurale", - "tags": "Tag Html", - "text": "<em>Libreria di internazionalizzazione (i18n) per tradurre testi, date e numeri nelle app Qwik</em>", - } + { + "num": "due" + } + ], + "dates": "Date e tempo relativo", + "description": "<em>Libreria di internazionalizzazione (i18n) per tradurre testi, date e numeri nelle app Qwik</em>", + "devs": { + "one": "{{ value }} sviluppatore di software", + "other": "{{ value }} sviluppatori di software" + }, + "greeting": "Ciao! Sono {{name}}", + "increment": "Incremento", + "numbers": "Numeri e valute", + "obj": { + "one": "{{ name }} uno", + "two": "{{ name }} due" + }, + "params": "Parametri", + "plural": "Plurale", + "tags": "Tag Html" }, null, 2); diff --git a/packages/qwik-speak/tools/tests/parser.test.ts b/packages/qwik-speak/tools/tests/parser.test.ts index 09683f0..a769076 100644 --- a/packages/qwik-speak/tools/tests/parser.test.ts +++ b/packages/qwik-speak/tools/tests/parser.test.ts @@ -639,7 +639,7 @@ describe('aliases', () => { expect(alias).toBe('\\bt'); }); test('getInlinePluralAlias', () => { - const alias = getInlinePluralAlias('const p = usePlural();'); + const alias = getInlinePluralAlias('const p = inlinePlural();'); expect(alias).toBe('\\bp'); }); }); diff --git a/src/global.css b/src/global.css index 7a83755..87062a6 100644 --- a/src/global.css +++ b/src/global.css @@ -29,6 +29,7 @@ button, .button { background-color: #fff; font-size: 14px; text-decoration: none; + color: inherit; } button:hover, .button:hover { @@ -54,6 +55,5 @@ button:hover, .button:hover { display: inline-block; padding: 10px 20px; font-size: 16px; - text-decoration: none; } } \ No newline at end of file From dfae617aaab8150b8a6a597f7903ca19e861c936 Mon Sep 17 00:00:00 2001 From: Roberto Simonetti <robisim74@gmail.com> Date: Tue, 21 Nov 2023 21:10:05 +0100 Subject: [PATCH 17/21] Fixes --- i18n/.metadata/translated-langs.json | 4 +++ i18n/.metadata/translated.json | 24 ++++++++++++++++++ packages/qwik-speak/src/context.ts | 9 ++++++- packages/qwik-speak/src/core.ts | 2 +- packages/qwik-speak/src/index.ts | 4 +-- packages/qwik-speak/src/localize-path.ts | 2 +- packages/qwik-speak/src/translate-path.ts | 2 +- packages/qwik-speak/src/use-qwik-speak.tsx | 25 +++++++++++-------- packages/qwik-speak/tools/extract/index.ts | 3 +++ .../change-locale/change-locale.tsx | 5 ++-- src/routes/plugin.ts | 19 +++++++------- 11 files changed, 70 insertions(+), 29 deletions(-) create mode 100644 i18n/.metadata/translated-langs.json create mode 100644 i18n/.metadata/translated.json diff --git a/i18n/.metadata/translated-langs.json b/i18n/.metadata/translated-langs.json new file mode 100644 index 0000000..c4e4706 --- /dev/null +++ b/i18n/.metadata/translated-langs.json @@ -0,0 +1,4 @@ +[ + "it-IT", + "de-DE" +] \ No newline at end of file diff --git a/i18n/.metadata/translated.json b/i18n/.metadata/translated.json new file mode 100644 index 0000000..920ff16 --- /dev/null +++ b/i18n/.metadata/translated.json @@ -0,0 +1,24 @@ +[ + "app.changeLocale", + "app.head.home.description", + "app.head.home.title", + "app.head.page.description", + "app.head.page.title", + "app.nav.home", + "app.nav.page", + "app.subtitle", + "app.title", + "anotherPage", + "dates", + "defaultValue", + "description", + "devs.one", + "devs.other", + "greeting", + "increment", + "numbers", + "params", + "plural", + "tags", + "runtime.dynamic" +] \ No newline at end of file diff --git a/packages/qwik-speak/src/context.ts b/packages/qwik-speak/src/context.ts index bf6273d..0fc8a5c 100644 --- a/packages/qwik-speak/src/context.ts +++ b/packages/qwik-speak/src/context.ts @@ -2,6 +2,10 @@ import { createContextId } from '@builder.io/qwik'; import type { SpeakState } from './types'; +type DeepPartial<T> = T extends object ? { + [P in keyof T]?: DeepPartial<T[P]>; +} : T; + export const SpeakContext = createContextId<SpeakState>('qwik-speak'); /** @@ -9,7 +13,10 @@ export const SpeakContext = createContextId<SpeakState>('qwik-speak'); * - config * - translation */ -export const _speakContext: Partial<SpeakState> = {}; +export const _speakContext: DeepPartial<SpeakState> = { + translation: {}, + config: {} +}; export let getLang = (): string => ''; diff --git a/packages/qwik-speak/src/core.ts b/packages/qwik-speak/src/core.ts index 05ac5f9..84a7038 100644 --- a/packages/qwik-speak/src/core.ts +++ b/packages/qwik-speak/src/core.ts @@ -59,7 +59,7 @@ export const loadTranslations = async ( for (const lang of resolvedLangs) { let tasks: Promise<any>[]; - // Cache requests prod mode + // Cache requests in prod mode if (!isDev) { const memoized = memoize(translationFn.loadTranslation$); tasks = resolvedAssets.map(asset => memoized(lang, asset)); diff --git a/packages/qwik-speak/src/index.ts b/packages/qwik-speak/src/index.ts index 2a5a4ae..88e42d7 100644 --- a/packages/qwik-speak/src/index.ts +++ b/packages/qwik-speak/src/index.ts @@ -18,8 +18,6 @@ export type { FormatDateFn } from './use-format-date'; export type { FormatNumberFn } from './use-format-number'; export type { RelativeTimeFn } from './use-relative-time'; export type { DisplayNameFn } from './use-display-name'; -// Components -export { QwikSpeakMockProvider } from './use-qwik-speak'; // Inline functions export { inlineTranslate } from './inline-translate'; export { inlinePlural } from './inline-plural'; @@ -38,3 +36,5 @@ export { useSpeakLocale, useSpeakConfig, } from './use-functions'; +// Testing +export { QwikSpeakMockProvider } from './use-qwik-speak'; diff --git a/packages/qwik-speak/src/localize-path.ts b/packages/qwik-speak/src/localize-path.ts index 64d9140..7272ed1 100644 --- a/packages/qwik-speak/src/localize-path.ts +++ b/packages/qwik-speak/src/localize-path.ts @@ -10,7 +10,7 @@ export type LocalizePathFn = { */ (pathname: string, lang?: string): string; /** - * Localize a url with the language + * Localize an url with the language * @param url The url to localize * @param lang Optional language if different from the default one * @returns The localized url diff --git a/packages/qwik-speak/src/translate-path.ts b/packages/qwik-speak/src/translate-path.ts index dab5a27..cead0cf 100644 --- a/packages/qwik-speak/src/translate-path.ts +++ b/packages/qwik-speak/src/translate-path.ts @@ -13,7 +13,7 @@ export type TranslatePathFn = { */ (pathname: string, lang?: string): string; /** - * Translate a url + * Translate an url * @param url The url to translate * @param lang Optional language if different from the default one * @returns The translation or the url if not found diff --git a/packages/qwik-speak/src/use-qwik-speak.tsx b/packages/qwik-speak/src/use-qwik-speak.tsx index 09cbabd..14216ec 100644 --- a/packages/qwik-speak/src/use-qwik-speak.tsx +++ b/packages/qwik-speak/src/use-qwik-speak.tsx @@ -51,27 +51,30 @@ export const useQwikSpeak = (props: QwikSpeakProps) => { loadTranslation$: props.translationFn?.loadTranslation$ ?? $(() => null) }; + // Resolve config + const resolvedConfig: SpeakConfig = { + rewriteRoutes: props.config.rewriteRoutes, + defaultLocale: props.config.defaultLocale, + supportedLocales: props.config.supportedLocales, + assets: props.config.assets, + runtimeAssets: props.config.runtimeAssets, + keySeparator: props.config.keySeparator || '.', + keyValueSeparator: props.config.keyValueSeparator || '@@' + }; + // Set initial state as object (no reactive) const state: SpeakState = { locale: Object.assign({}, resolvedLocale), translation: Object.fromEntries(props.config.supportedLocales.map(value => [value.lang, {}])), - config: { - rewriteRoutes: props.config.rewriteRoutes, - defaultLocale: props.config.defaultLocale, - supportedLocales: props.config.supportedLocales, - assets: props.config.assets, - runtimeAssets: props.config.runtimeAssets, - keySeparator: props.config.keySeparator || '.', - keyValueSeparator: props.config.keyValueSeparator || '@@' - }, + config: Object.assign({}, resolvedConfig), translationFn: resolvedTranslationFn }; const { config } = state; - // Create server context + // Init server context _speakContext.translation = Object.fromEntries(props.config.supportedLocales.map(value => [value.lang, {}])); - _speakContext.config = config; + _speakContext.config = Object.assign({}, resolvedConfig); // Set the getLang function to use Qwik locale setGetLangFn(() => getLocale(config.defaultLocale.lang)); diff --git a/packages/qwik-speak/tools/extract/index.ts b/packages/qwik-speak/tools/extract/index.ts index f2655c4..e16f976 100644 --- a/packages/qwik-speak/tools/extract/index.ts +++ b/packages/qwik-speak/tools/extract/index.ts @@ -288,6 +288,9 @@ export async function qwikSpeakExtract(options: QwikSpeakExtractOptions) { keys = keys.concat(source); } + /* Sort keys (keys with the default value will overwrite the empty ones) */ + keys.sort(); + /* Unique keys */ keys = [...new Set<string>(keys)]; stats.set('unique keys', (stats.get('unique keys') ?? 0) + keys.length); diff --git a/src/components/change-locale/change-locale.tsx b/src/components/change-locale/change-locale.tsx index 944a759..34c0f69 100644 --- a/src/components/change-locale/change-locale.tsx +++ b/src/components/change-locale/change-locale.tsx @@ -12,15 +12,14 @@ export const ChangeLocale = component$(() => { const url = useLocation().url; + const locale = useSpeakLocale(); + const config = useSpeakConfig(); const dn = useDisplayName(); /** Uncomment this line to use url rewriting to translate paths */ // const getPath = translatePath(); const getPath = localizePath(); - const locale = useSpeakLocale(); - const config = useSpeakConfig(); - return ( <div class="change-locale"> <h2>{t('app.changeLocale')}</h2> diff --git a/src/routes/plugin.ts b/src/routes/plugin.ts index 328bbc1..3cd4684 100644 --- a/src/routes/plugin.ts +++ b/src/routes/plugin.ts @@ -4,15 +4,16 @@ import { config } from '../speak-config'; // import { rewriteRoutes } from '../speak-routes'; export const onRequest: RequestHandler = ({ params, locale, error }) => { - // Check supported locales - const supportedLocale = config.supportedLocales.find(value => value.lang === params.lang) - - // Check for 404 error page - const lang = supportedLocale - ? supportedLocale.lang - : !params.lang && config.defaultLocale.lang - - if (!lang) throw error(404, 'Page not found'); + let lang: string | undefined = undefined; + + if (params.lang) { + // Check supported locales + lang = config.supportedLocales.find(value => value.lang === params.lang)?.lang; + // 404 error page + if (!lang) throw error(404, 'Page not found'); + } else { + lang = config.defaultLocale.lang; + } // Set Qwik locale locale(lang); From bb515f538c3b56a7f43f2a23504c712a66c3cc06 Mon Sep 17 00:00:00 2001 From: Roberto Simonetti <robisim74@gmail.com> Date: Thu, 23 Nov 2023 16:51:41 +0100 Subject: [PATCH 18/21] Improve Qwik Speak context --- packages/qwik-speak/src/context.ts | 37 ++++++++++++-- packages/qwik-speak/src/core.ts | 17 ++++--- packages/qwik-speak/src/inline-plural.ts | 5 +- packages/qwik-speak/src/inline-translate.ts | 5 +- packages/qwik-speak/src/localize-path.ts | 5 +- packages/qwik-speak/src/translate-path.ts | 9 ++-- packages/qwik-speak/src/use-qwik-speak.tsx | 53 ++++++++++++--------- packages/qwik-speak/src/use-speak.ts | 3 +- packages/qwik-speak/tools/core/merge.ts | 5 +- packages/qwik-speak/tools/extract/index.ts | 3 -- 10 files changed, 90 insertions(+), 52 deletions(-) diff --git a/packages/qwik-speak/src/context.ts b/packages/qwik-speak/src/context.ts index 0fc8a5c..c620ca2 100644 --- a/packages/qwik-speak/src/context.ts +++ b/packages/qwik-speak/src/context.ts @@ -1,4 +1,5 @@ import { createContextId } from '@builder.io/qwik'; +import { isServer } from '@builder.io/qwik/build'; import type { SpeakState } from './types'; @@ -6,20 +7,48 @@ type DeepPartial<T> = T extends object ? { [P in keyof T]?: DeepPartial<T[P]>; } : T; +/** + * Qwik context (per user) + */ export const SpeakContext = createContextId<SpeakState>('qwik-speak'); /** - * Shared server/client context: - * - config - * - translation + * Qwik Speak server context (shared) */ -export const _speakContext: DeepPartial<SpeakState> = { +const _speakServerContext: DeepPartial<SpeakState> = { translation: {}, config: {} }; +/** + * Qwik Speak client context (per user) + */ +const _speakClientContext: DeepPartial<SpeakState> = { + translation: {}, + config: {}, + locale: {} +}; + +/** + * Return Qwik Speak server or client context + */ +export const getSpeakContext = (): SpeakState => { + if (isServer) { + return _speakServerContext as SpeakState; + } else { + return _speakClientContext as SpeakState; + } +} + +/** + * Qwik Speak function to get language + */ export let getLang = (): string => ''; +/** + * Set getLang function + * @param fn + */ export const setGetLangFn = (fn: () => string) => { getLang = () => fn(); }; diff --git a/packages/qwik-speak/src/core.ts b/packages/qwik-speak/src/core.ts index 84a7038..cf321be 100644 --- a/packages/qwik-speak/src/core.ts +++ b/packages/qwik-speak/src/core.ts @@ -1,7 +1,7 @@ import { isDev, isServer } from '@builder.io/qwik/build'; import type { Translation, SpeakState, LoadTranslationFn } from './types'; -import { _speakContext } from './context'; +import { getSpeakContext } from './context'; import { logWarn } from './log'; const cache: Record<string, Promise<any>> = {}; @@ -33,8 +33,8 @@ export const loadTranslations = async ( ): Promise<void> => { if (isServer || runtimeAssets) { const { locale, translation, translationFn, config } = ctx; - // Shared server/client context - const { translation: _translation } = _speakContext as SpeakState; + // Qwik Speak server/client context + const { translation: _translation } = getSpeakContext(); if (isDev) { const conflictingAsset = assets?.find(asset => runtimeAssets?.includes(asset)) || @@ -73,12 +73,17 @@ export const loadTranslations = async ( source: source })); + // Set Qwik Speak server/client context + if (!(lang in _translation)) { + Object.assign(_translation, { [lang]: {} }); + } + for (const data of assetSources) { if (data?.source) { if (isServer) { // On server: - // - assets & runtimeAssets in shared context - // - runtimeAssets in context as well (must be serialized to be passed to the client) + // - assets & runtimeAssets in Qwik Speak server context + // - runtimeAssets in Qwik context (must be serialized to be passed to the client) if (assets?.includes(data.asset)) { Object.assign(_translation[lang], data.source); } else { @@ -88,7 +93,7 @@ export const loadTranslations = async ( } } else { // On client: - // - assets & runtimeAssets in shared context + // - assets & runtimeAssets in Qwik Speak client context Object.assign(_translation[lang], data.source); } } diff --git a/packages/qwik-speak/src/inline-plural.ts b/packages/qwik-speak/src/inline-plural.ts index fd39e64..f0e3019 100644 --- a/packages/qwik-speak/src/inline-plural.ts +++ b/packages/qwik-speak/src/inline-plural.ts @@ -1,6 +1,5 @@ -import type { SpeakState } from './types'; import { getValue } from './core'; -import { _speakContext, getLang } from './context'; +import { getLang, getSpeakContext } from './context'; export type InlinePluralFn = { /** @@ -32,7 +31,7 @@ export const inlinePlural = (): InlinePluralFn => { options?: Intl.PluralRulesOptions, lang?: string ) => { - const { translation, config } = _speakContext as SpeakState; + const { translation, config } = getSpeakContext(); lang ??= currentLang; diff --git a/packages/qwik-speak/src/inline-translate.ts b/packages/qwik-speak/src/inline-translate.ts index 6582658..ef79db1 100644 --- a/packages/qwik-speak/src/inline-translate.ts +++ b/packages/qwik-speak/src/inline-translate.ts @@ -1,6 +1,5 @@ -import type { SpeakState } from './types'; import { getValue } from './core'; -import { _speakContext, getLang } from './context'; +import { getLang, getSpeakContext } from './context'; export type InlineTranslateFn = { /** @@ -29,7 +28,7 @@ export const inlineTranslate = (): InlineTranslateFn => { const currentLang = getLang(); const translate = (keys: string | string[], params?: Record<string, any>, lang?: string) => { - const { translation, config } = _speakContext as SpeakState; + const { translation, config } = getSpeakContext(); lang ??= currentLang; diff --git a/packages/qwik-speak/src/localize-path.ts b/packages/qwik-speak/src/localize-path.ts index 7272ed1..6d481c5 100644 --- a/packages/qwik-speak/src/localize-path.ts +++ b/packages/qwik-speak/src/localize-path.ts @@ -1,5 +1,4 @@ -import { type SpeakState } from './types'; -import { _speakContext, getLang } from './context'; +import { getLang, getSpeakContext } from './context'; export type LocalizePathFn = { /** @@ -31,7 +30,7 @@ export const localizePath = (): LocalizePathFn => { const getRegEpx = (lang: string) => new RegExp(`(/${lang}/)|(/${lang}$)|(/(${lang})(?=\\?))`); const replace = (pathname: string, lang?: string) => { - const { config } = _speakContext as SpeakState; + const { config } = getSpeakContext(); lang ??= currentLang; diff --git a/packages/qwik-speak/src/translate-path.ts b/packages/qwik-speak/src/translate-path.ts index cead0cf..3e85cdc 100644 --- a/packages/qwik-speak/src/translate-path.ts +++ b/packages/qwik-speak/src/translate-path.ts @@ -1,7 +1,6 @@ import { isDev } from '@builder.io/qwik/build'; -import { type SpeakState } from './types'; -import { _speakContext, getLang } from './context'; +import { getLang, getSpeakContext } from './context'; import { logWarn } from './log'; export type TranslatePathFn = { @@ -32,7 +31,7 @@ export const translatePath = (): TranslatePathFn => { const currentLang = getLang(); const normalizePath = (pathname: string) => { - const { config } = _speakContext as SpeakState; + const { config } = getSpeakContext(); const source = config.rewriteRoutes?.find(rewrite => ( pathname === `/${rewrite.prefix}` || @@ -59,7 +58,7 @@ export const translatePath = (): TranslatePathFn => { } const rewritePath = (pathname: string, prefix?: string) => { - const { config } = _speakContext as SpeakState; + const { config } = getSpeakContext(); let splitted = pathname.split('/'); const destination = config.rewriteRoutes?.find( @@ -101,7 +100,7 @@ export const translatePath = (): TranslatePathFn => { }; const translate = (route: (string | URL) | string[], lang?: string) => { - const { config } = _speakContext as SpeakState; + const { config } = getSpeakContext(); if (!config.rewriteRoutes) { if (isDev) logWarn(`translatePath: rewriteRoutes not found`); diff --git a/packages/qwik-speak/src/use-qwik-speak.tsx b/packages/qwik-speak/src/use-qwik-speak.tsx index 14216ec..a37dc7b 100644 --- a/packages/qwik-speak/src/use-qwik-speak.tsx +++ b/packages/qwik-speak/src/use-qwik-speak.tsx @@ -2,7 +2,7 @@ import { $, component$, getLocale, Slot, useContextProvider, useOnDocument, useT import { isDev, isServer } from '@builder.io/qwik/build'; import type { SpeakConfig, SpeakLocale, SpeakState, TranslationFn } from './types'; -import { _speakContext, setGetLangFn, SpeakContext } from './context'; +import { getSpeakContext, setGetLangFn, SpeakContext } from './context'; import { loadTranslations } from './core'; import { logDebug, logWarn } from './log'; @@ -72,16 +72,17 @@ export const useQwikSpeak = (props: QwikSpeakProps) => { const { config } = state; - // Init server context - _speakContext.translation = Object.fromEntries(props.config.supportedLocales.map(value => [value.lang, {}])); - _speakContext.config = Object.assign({}, resolvedConfig); + // Set Qwik Speak server context + const { config: _config } = getSpeakContext(); + Object.assign(_config, resolvedConfig); + // Set the getLang function to use Qwik locale setGetLangFn(() => getLocale(config.defaultLocale.lang)); - // Create context + // Create Qwik context useContextProvider(SpeakContext, state); - // Load shared translations + // Load translations useTask$(async () => { if (isServer) { await loadTranslations(state, config.assets, config.runtimeAssets, props.langs); @@ -91,11 +92,13 @@ export const useQwikSpeak = (props: QwikSpeakProps) => { // Resume shared context on client const resumeContext$ = $(() => { const { locale, translation, config } = state; + // Set Qwik Speak client context + const _speakContext = getSpeakContext(); + const { locale: _locale, translation: _translation, config: _config } = _speakContext; + Object.assign(_locale, locale); + Object.assign(_translation, translation); + Object.assign(_config, config); - // Create client context - _speakContext.translation = translation; - _speakContext.config = config; - _speakContext.locale = locale; // Set the getLang function to use the current lang setGetLangFn(() => locale.lang); @@ -141,34 +144,38 @@ export const QwikSpeakMockProvider = component$<QwikSpeakMockProps>(props => { loadTranslation$: props.translationFn?.loadTranslation$ ?? $(() => null) }; + // Resolve config + const resolvedConfig: SpeakConfig = { + rewriteRoutes: props.config.rewriteRoutes, + defaultLocale: props.config.defaultLocale, + supportedLocales: props.config.supportedLocales, + assets: props.config.assets, + runtimeAssets: props.config.runtimeAssets, + keySeparator: props.config.keySeparator || '.', + keyValueSeparator: props.config.keyValueSeparator || '@@' + }; + // Set initial state as object (no reactive) const state: SpeakState = { locale: Object.assign({}, resolvedLocale), translation: Object.fromEntries(props.config.supportedLocales.map(value => [value.lang, {}])), - config: { - rewriteRoutes: props.config.rewriteRoutes, - defaultLocale: props.config.defaultLocale, - supportedLocales: props.config.supportedLocales, - assets: props.config.assets, - runtimeAssets: props.config.runtimeAssets, - keySeparator: props.config.keySeparator || '.', - keyValueSeparator: props.config.keyValueSeparator || '@@' - }, + config: Object.assign({}, resolvedConfig), translationFn: resolvedTranslationFn }; const { config } = state; - // Create server context - _speakContext.translation = Object.fromEntries(props.config.supportedLocales.map(value => [value.lang, {}])); - _speakContext.config = config; + // Set Qwik Speak server context + const { config: _config } = getSpeakContext(); + Object.assign(_config, resolvedConfig); + // Set the getLang function to use the provided lang setGetLangFn(() => resolvedLocale!.lang); // Create context useContextProvider(SpeakContext, state); - // Load shared translations + // Load translations useTask$(async () => { await loadTranslations(state, config.assets, config.runtimeAssets, props.langs); }); diff --git a/packages/qwik-speak/src/use-speak.ts b/packages/qwik-speak/src/use-speak.ts index ae6084d..0d55d0f 100644 --- a/packages/qwik-speak/src/use-speak.ts +++ b/packages/qwik-speak/src/use-speak.ts @@ -3,7 +3,7 @@ import { isBrowser, isDev } from '@builder.io/qwik/build'; import { useSpeakContext } from './use-functions'; import { loadTranslations } from './core'; -import { _speakContext } from './context'; +import { getSpeakContext } from './context'; import { logWarn } from './log'; export interface SpeakProps { @@ -47,6 +47,7 @@ export const useSpeak = (props: SpeakProps) => { await loadTranslations(ctx, props.assets, props.runtimeAssets, props.langs); if (isDev && isBrowser) { + const _speakContext = getSpeakContext(); console.debug( '%cQwik Speak Inline', 'background: #0c75d2; color: white; padding: 2px 3px; border-radius: 2px; font-size: 0.8em;', diff --git a/packages/qwik-speak/tools/core/merge.ts b/packages/qwik-speak/tools/core/merge.ts index 1775b4d..73c5b4d 100644 --- a/packages/qwik-speak/tools/core/merge.ts +++ b/packages/qwik-speak/tools/core/merge.ts @@ -5,7 +5,10 @@ export function deepSet(target: Translation, keys: string[], val: string | Trans const len = keys.length; while (i < len) { const key = keys[i++]; - target[key] = (i === len) ? val : typeof target[key] === 'object' ? target[key] : {}; + target[key] = target[key] && !val ? + target[key] : (i === len) ? + val : typeof target[key] === 'object' ? + target[key] : {}; target = target[key]; } } diff --git a/packages/qwik-speak/tools/extract/index.ts b/packages/qwik-speak/tools/extract/index.ts index e16f976..f2655c4 100644 --- a/packages/qwik-speak/tools/extract/index.ts +++ b/packages/qwik-speak/tools/extract/index.ts @@ -288,9 +288,6 @@ export async function qwikSpeakExtract(options: QwikSpeakExtractOptions) { keys = keys.concat(source); } - /* Sort keys (keys with the default value will overwrite the empty ones) */ - keys.sort(); - /* Unique keys */ keys = [...new Set<string>(keys)]; stats.set('unique keys', (stats.get('unique keys') ?? 0) + keys.length); From 63f19fe63ea221c1e79d6da77c0448bc7ba2f812 Mon Sep 17 00:00:00 2001 From: Roberto Simonetti <robisim74@gmail.com> Date: Thu, 23 Nov 2023 17:13:11 +0100 Subject: [PATCH 19/21] Feat: add validateLocale --- packages/qwik-speak/src/index.ts | 1 + packages/qwik-speak/src/validate-locale.ts | 9 +++++++++ packages/qwik-speak/tests/validate-locale.test.ts | 12 ++++++++++++ src/routes/plugin.ts | 3 ++- 4 files changed, 24 insertions(+), 1 deletion(-) create mode 100644 packages/qwik-speak/src/validate-locale.ts create mode 100644 packages/qwik-speak/tests/validate-locale.test.ts diff --git a/packages/qwik-speak/src/index.ts b/packages/qwik-speak/src/index.ts index 88e42d7..f38518a 100644 --- a/packages/qwik-speak/src/index.ts +++ b/packages/qwik-speak/src/index.ts @@ -24,6 +24,7 @@ export { inlinePlural } from './inline-plural'; // Functions export { localizePath } from './localize-path'; export { translatePath } from './translate-path'; +export { validateLocale } from './validate-locale'; // Use functions export { useQwikSpeak } from './use-qwik-speak'; export { useSpeak } from './use-speak'; diff --git a/packages/qwik-speak/src/validate-locale.ts b/packages/qwik-speak/src/validate-locale.ts new file mode 100644 index 0000000..90031f5 --- /dev/null +++ b/packages/qwik-speak/src/validate-locale.ts @@ -0,0 +1,9 @@ +/** + * Validate language[-script][-region] + * - `language` ISO 639 two-letter or three-letter code + * - `script` ISO 15924 four-letter script code + * - `region` ISO 3166 two-letter, uppercase code + */ +export const validateLocale = (lang: string): boolean => { + return /^([a-z]{2,3})(-[A-Z][a-z]{3})?(-[A-Z]{2})?$/.test(lang); +}; diff --git a/packages/qwik-speak/tests/validate-locale.test.ts b/packages/qwik-speak/tests/validate-locale.test.ts new file mode 100644 index 0000000..9e6a62d --- /dev/null +++ b/packages/qwik-speak/tests/validate-locale.test.ts @@ -0,0 +1,12 @@ +import { test, describe, expect } from 'vitest'; + +import { validateLocale } from '../src'; + +describe('validateLocale', () => { + test('langs', () => { + expect(validateLocale('en')).toBe(true); + expect(validateLocale('en-US')).toBe(true); + expect(validateLocale('en-Zzzz-US')).toBe(true); + expect(validateLocale('en-us')).toBe(false); + }); +}); diff --git a/src/routes/plugin.ts b/src/routes/plugin.ts index 3cd4684..4177ccf 100644 --- a/src/routes/plugin.ts +++ b/src/routes/plugin.ts @@ -1,4 +1,5 @@ import type { RequestHandler } from "@builder.io/qwik-city"; +import { validateLocale } from 'qwik-speak'; import { config } from '../speak-config'; // import { rewriteRoutes } from '../speak-routes'; @@ -6,7 +7,7 @@ import { config } from '../speak-config'; export const onRequest: RequestHandler = ({ params, locale, error }) => { let lang: string | undefined = undefined; - if (params.lang) { + if (params.lang && validateLocale(params.lang)) { // Check supported locales lang = config.supportedLocales.find(value => value.lang === params.lang)?.lang; // 404 error page From 4a946b79d538867cf6dec336b66d81cd40b391af Mon Sep 17 00:00:00 2001 From: Roberto Simonetti <robisim74@gmail.com> Date: Fri, 24 Nov 2023 14:22:31 +0100 Subject: [PATCH 20/21] Update docs --- README.md | 76 ++--- SUMMARY.md | 5 +- docs/adapters.md | 2 +- docs/extract.md | 6 +- docs/inline.md | 45 +-- docs/lazy-loading.md | 43 +++ docs/quick-start.md | 361 ++++------------------- docs/testing.md | 35 +-- docs/translate.md | 205 ++++++------- docs/tutorial-routing-rewrite.md | 418 +++++++-------------------- docs/tutorial-routing.md | 395 ++++++------------------- packages/qwik-speak/src/use-speak.ts | 4 +- 12 files changed, 451 insertions(+), 1144 deletions(-) create mode 100644 docs/lazy-loading.md diff --git a/README.md b/README.md index 00a4854..13ed945 100644 --- a/README.md +++ b/README.md @@ -9,10 +9,11 @@ npm install qwik-speak --save-dev ## Getting Started - [Quick Start](./docs/quick-start.md) -- [Tutorial: localized routing with prefix only](./docs/tutorial-routing.md) -- [Tutorial: localized routing with url rewriting](./docs/tutorial-routing-rewrite.md) +- [Tutorial: localized routing with the language](./docs/tutorial-routing.md) +- [Tutorial: translated routing with url rewriting](./docs/tutorial-routing-rewrite.md) - [Translate](./docs/translate.md) -- [Translation functions](./docs/translation-functions.md) +- [Translation functions](./docs/translation-functions.md) +- [Lazy loading translation](./docs/lazy-loading.md) - [Qwik Speak and Adapters](./docs/adapters.md) - [Testing](./docs/testing.md) @@ -21,15 +22,15 @@ Live example on [Cloudflare pages](https://qwik-speak.pages.dev/) and playground ## Overview ### Getting the translation ```tsx -import { useTranslate } from 'qwik-speak'; +import { inlineTranslate } from 'qwik-speak'; export default component$(() => { - const t = useTranslate(); + const t = inlineTranslate(); return ( <> - <h1>{t('app.title@@Qwik Speak')}</h1> {/* Qwik Speak */} - <p>{t('home.greeting@@Hi! I am {{name}}', { name: 'Qwik Speak' })}</p> {/* Hi! I am Qwik Speak */} + <h1>{t('title@@Qwik Speak')}</h1> {/* Qwik Speak */} + <p>{t('greeting@@Hi! I am {{name}}', { name: 'Qwik Speak' })}</p> {/* Hi! I am Qwik Speak */} </> ); }); @@ -53,6 +54,11 @@ export default component$(() => { }); ``` +## Static translations +Translation are loaded and inlined in chunks sent to the browser during the build. + +See [Qwik Speak Inline Vite plugin](./docs/inline.md) for more information on how it works and how to use it. + ## Extraction of translations To extract translations directly from the components, a command is available that automatically generates the files with the keys and default values. @@ -63,11 +69,6 @@ To automatically translate files, an external command is available that uses Ope See [GPT Translate JSON](./docs/gpt-translate-json.md) for more information on how to use it. -## Production -In production, translations are loaded and inlined during the build. - -See [Qwik Speak Inline Vite plugin](./docs/inline.md) for more information on how it works and how to use it. - ## Speak context ```mermaid stateDiagram-v2 @@ -93,16 +94,11 @@ stateDiagram-v2 - loadTranslation$ end note note right of State5 - key-value pairs - of translation data + runtime assets end note ``` > `SpeakState` is immutable: it cannot be updated after it is created and is not reactive -- `useSpeakContext()` Returns the Speak state -- `useSpeakConfig()` Returns the configuration in Speak context -- `useSpeakLocale()` Returns the locale in Speak context - ### Speak config - `defaultLocale` The default locale to use as fallback - `supportedLocales` List of locales supported by the app @@ -110,7 +106,7 @@ stateDiagram-v2 - `runtimeAssets` Assets available at runtime - `keySeparator` Separator of nested keys. Default is `.` - `keyValueSeparator` Key-value separator. Default is `@@` -- `rewriteRoutes` Rewrite routes as specified in Vite config for qwikCity +- `rewriteRoutes` Rewrite routes as specified in Vite config for `qwikCity` plugin ### SpeakLocale The `SpeakLocale` object contains the `lang`, in the format `language[-script][-region]`, where: @@ -129,36 +125,34 @@ and optionally contains: `TranslationFn` interface can be implemented to change the behavior of the library: - `loadTranslation$` QRL function to load translation data +### Translation +`Translation` contains only the key value pairs of the translation data provided with the `runtimeAssets` + ## APIs -### Components -#### QwikSpeakProvider component -`QwikSpeakProvider` component provides the Speak context to the app. `Props`: +### Providers +`useQwikSpeak(props: QwikSpeakProps)` provides the Speak context to the app. `QwikSpeakProps`: - `config` Speak config - `translationFn` Optional functions to use - - `locale` Optional locale to use - `langs` Optional additional languages to preload data for (multilingual) -#### Speak component (scoped translations) -`Speak` component can be used for scoped translations. `Props`: +`useSpeak(props: SpeakProps) ` can be used for lazy loading translation. `SpeakProps`: - `assets` Assets to load - `runtimeAssets` Assets to load available at runtime - `langs` Optional additional languages to preload data for (multilingual) -### Functions -#### Translate -- `useTranslate: () => (keys: string | string[], params?: Record<string, any>, lang?: string)` -Translates a key or an array of keys. The syntax of the string is `key@@[default value]` +### Context +- `useSpeakContext()` Returns the Speak state +- `useSpeakConfig()` Returns the configuration in Speak context +- `useSpeakLocale()` Returns the locale in Speak context -- `inlineTranslate(keys: string | string[], ctx: SpeakState, params?: Record<string, any>, lang?: string)` -Translates a key or an array of keys outside the `component$`. The syntax of the string is `key@@[default value]` +### Translate +- `inlineTranslate: () => (keys: string | string[], params?: Record<string, any>, lang?: string)` +Translates a key or an array of keys. The syntax of the string is `key@@[default value]` -- `usePlural: () => (value: number | string, key?: string, params?: Record<string, any>, options?: Intl.PluralRulesOptions, lang?: string)` +- `inlinePlural: () => (value: number | string, key?: string, params?: Record<string, any>, options?: Intl.PluralRulesOptions, lang?: string)` Gets the plural by a number using [Intl.PluralRules](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/PluralRules) API -- `useTranslatePath: () => (paths: string | string[], lang?: string)` -Translates a path or an array of paths. The translating string can be in any language. If not specified the target lang is the current one - -#### Localize +### Localize - `useFormatDate: () => (value: Date | number | string, options?: Intl.DateTimeFormatOptions, lang?: string, timeZone?: string)` Formats a date using [Intl.DateTimeFormat](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/DateTimeFormat) API @@ -171,6 +165,16 @@ Formats a number using [Intl.NumberFormat](https://developer.mozilla.org/en-US/d - `useDisplayName: () => (code: string, options: Intl.DisplayNamesOptions, lang?: string)` Returns the translation of language, region, script or currency display names using [Intl.DisplayNames](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/DisplayNames) API +### Routing +- `localizePath: () => (route: (string | URL) | string[], lang?: string)` +Localize a path, an URL or an array of paths with the language + +- `translatePath: () => (route: (string | URL) | string[], lang?: string)` +Translates a path, an URL or an array of paths. The translating string can be in any language. If not specified the target lang is the current one + +### Testing +- `QwikSpeakMockProvider` component provides the Speak context to test enviroments + ## Development Builds ### Library & tools #### Build diff --git a/SUMMARY.md b/SUMMARY.md index cbc89de..14eef82 100644 --- a/SUMMARY.md +++ b/SUMMARY.md @@ -3,10 +3,11 @@ ## Library * [Quick Start](docs/quick-start.md) -* [Tutorial: localized routing with prefix only](docs/tutorial-routing.md) -* [Tutorial: localized routing with url rewriting](docs/tutorial-routing-rewrite.md) +* [Tutorial: localized routing with the language](docs/tutorial-routing.md) +* [Tutorial: translated routing with url rewriting](docs/tutorial-routing-rewrite.md) * [Translate](docs/translate.md) * [Translation functions](docs/translation-functions.md) +* [Lazy loading translation](docs/lazy-loading.md) * [Qwik Speak and Adapters](docs/adapters.md) * [Testing](docs/testing.md) diff --git a/docs/adapters.md b/docs/adapters.md index 289e405..00a9fe5 100644 --- a/docs/adapters.md +++ b/docs/adapters.md @@ -7,7 +7,7 @@ If your production environment doesn't support _dynamic import_, you can import /** * Translation files are imported directly as string */ -const translationData = import.meta.glob('/i18n/**/*.json', { as: 'raw', eager: true }); +const translationData = import.meta.glob<Translation>('/i18n/**/*.json', { as: 'raw', eager: true }); const loadTranslation$: LoadTranslationFn = server$((lang: string, asset: string) => JSON.parse(translationData[`/i18n/${lang}/${asset}.json`]) diff --git a/docs/extract.md b/docs/extract.md index 9b028e1..73e35f1 100644 --- a/docs/extract.md +++ b/docs/extract.md @@ -7,13 +7,13 @@ #### Get the code ready Optionally, you can use a default value for the keys. The syntax is `key@@[default value]`: ```html -<p>{t('app.title@@Qwik Speak'}</p> -<p>{t('home.greeting@@Hi! I am {{name}}', { name: 'Qwik Speak' })}</p> +<p>{t('title@@Qwik Speak'}</p> +<p>{t('greeting@@Hi! I am {{name}}', { name: 'Qwik Speak' })}</p> ``` When you use a default value, it will be used as initial value for the key in every translation. -> Note. A key will not be extracted when it is an identifier or contains an indentifier (dynamic) +> Note that it is not necessary to provide the default value of a key every time: it is sufficient and not mandatory to provide it once in the app #### Naming conventions If you use scoped translations, the first property will be used as filename: diff --git a/docs/inline.md b/docs/inline.md index d9ae365..b28cae5 100644 --- a/docs/inline.md +++ b/docs/inline.md @@ -1,18 +1,19 @@ # Qwik Speak Inline Vite plugin -> Inline Qwik Speak `useTranslate`, `inlineTranslate` and `usePlural` functions at compile time +> Inline Qwik Speak `inlineTranslate` and `inlinePlural` functions at compile time ## How it works -In development mode, translation happens _at runtime_: `assets` are loaded during SSR or on client, and the lookup also happens at runtime. +O the server, translation happens _at runtime_: `assets` are loaded during SSR and the lookup also happens at runtime. -In production mode, `assets` are loaded only during SSR, and to get the translations on the client as well you have to use _Qwik Speak Inline_ Vite plugin. +On the client, translation happens _at compile-time_: `assets` are loaded and inlined in chunks sent to the browser during the build, reducing resource usage at runtime. -Using the _Qwik Speak Inline_, translation happens _at compile-time_: `assets` are loaded and inlined in chunks sent to the browser during the build, reducing resource usage at runtime: +`runtimeAssets` are always loaded at runtime, both on the server or on the client, allowing dynamic translations. ```mermaid sequenceDiagram participant Server participant assets + participant runtimeAssets participant Client Server->>assets: loadTranslation$ activate assets @@ -20,6 +21,12 @@ sequenceDiagram deactivate assets Server->>Client: SSR: no serialize data Note over Client: inlined data + Server->>runtimeAssets: loadTranslation$ + activate runtimeAssets + runtimeAssets-->>Server: data + deactivate runtimeAssets + Server->>Client: SSR: serialize data + Note over Client: runtime data ``` ## Usage @@ -104,32 +111,4 @@ _dist/build/it-IT/q-*.js_ At the end of the build, in root folder a `qwik-speak-inline.log` file is generated which contains: - Missing values -- Translations with dynamic keys -- Translations with dynamic params - -## Qwik Speak Inline Vite plugin & runtime -When there are translations with dynamic keys or params, you have to use separate files, and add them to `runtimeAssets`: - -```typescript -export const config: SpeakConfig = { - /* ... */ - assets: [ - 'app' // Translations shared by the pages - ], - runtimeAssets: [ - 'runtime' // Translations with dynamic keys or parameters - ] -}; -``` -Likewise, you can also create scoped runtime files for different pages and pass them to `Speak` components: -```tsx -export default component$(() => { - return ( - <Speak assets={['home']} runtimeAssets={['runtimeHome']}> - <Page /> - </Speak> - ); -}); -``` -> `runtimeAssets` are serialized and sent to the client, and loaded when required - +- Translations with dynamic keys or params diff --git a/docs/lazy-loading.md b/docs/lazy-loading.md new file mode 100644 index 0000000..9500c98 --- /dev/null +++ b/docs/lazy-loading.md @@ -0,0 +1,43 @@ +# Lazy loading translation + +If you are developing a large app, you can consider using lazy loading translation: translations that are lazy loaded only when requested (when the user navigates to a specific section or page of the app): + +```mermaid +C4Container + Container_Boundary(a, "App") { + Component(a0, "root", "useQwikSpeak", "Translations available in the whole app") + Container_Boundary(b1, "Site") { + Component(b10, "Page", "useSpeak", "Translations available in Page component") + + } + Container_Boundary(b2, "Admin") { + Component(b20, "layout", "useSpeak", "Translations available in child components") + } + } +``` + +For lazy loading of files in a specific section, you need to add `useSpeak` to the layout: +```tsx +import { useSpeak } from 'qwik-speak'; + +export default component$(() => { + useSpeak({assets:['admin'], runtimeAssets: ['runtimeAdmin']}); + + return ( + <> + <main> + <Slot /> + </main> + </> + ); +}); +``` +or in a specific page: +```tsx +export default component$(() => { + useSpeak({runtimeAssets: ['runtimePage']}); + + return <Page />; +}); +``` +> Note that you must create a component for the page, because Qwik renders components in isolation, and translations are only available in child components \ No newline at end of file diff --git a/docs/quick-start.md b/docs/quick-start.md index 2beb043..5f50107 100644 --- a/docs/quick-start.md +++ b/docs/quick-start.md @@ -1,14 +1,34 @@ # Quick Start -> Step by step, let's build a sample app with Qwik Speak +> Setup an app with Qwik Speak -```shell -npm create qwik@latest +```shell npm install qwik-speak --save-dev ``` +## Vite plugin +Add [Qwik Speak Inline Vite plugin](./inline.md) in `vite.config.ts`: +```typescript +import { qwikSpeakInline } from 'qwik-speak/inline'; + +export default defineConfig(() => { + return { + plugins: [ + qwikCity(), + qwikVite(), + qwikSpeakInline({ + supportedLangs: ['en-US', 'it-IT'], + defaultLang: 'en-US', + assetsPath: 'i18n' + }), + tsconfigPaths(), + ], + }; +}); +``` + ## Configuration -Let's create `speak-config.ts` and `speak-functions.ts` files in `src`: +Let's create `speak-config.ts` and `speak-functions.ts` files in `src` folder: _src/speak-config.ts_ ```typescript @@ -20,8 +40,13 @@ export const config: SpeakConfig = { { lang: 'it-IT', currency: 'EUR', timeZone: 'Europe/Rome' }, { lang: 'en-US', currency: 'USD', timeZone: 'America/Los_Angeles' } ], + // Translations available in the whole app assets: [ - 'app' // Translations shared by the pages + 'app' + ], + // Translations with dynamic keys available in the whole app + runtimeAssets: [ + 'runtime' ] }; ``` @@ -47,168 +72,47 @@ export const translationFn: TranslationFn = { loadTranslation$: loadTranslation$ }; ``` -We have added the Speak config and the implementation of the `loadTranslation$` function to load translation files. - > `loadTranslation$` is a customizable QRL function: you can load the translation files in the way you prefer -## Adding Qwik Speak -Just wrap Qwik City provider with `QwikSpeakProvider` component in `root.tsx` and pass it the configuration and the translation functions: + +Add `useQwikSpeak` provider in `root.tsx` and pass it the configuration and the translation functions: _src/root.tsx_ ```tsx -import { QwikSpeakProvider } from 'qwik-speak'; - -import { config } from './speak-config'; -import { translationFn } from './speak-functions'; +import { useQwikSpeak } from 'qwik-speak'; +import { config } from "./speak-config"; +import { translationFn } from "./speak-functions"; export default component$(() => { - return ( - <QwikSpeakProvider config={config} translationFn={translationFn}> - <QwikCityProvider> - <head> - <meta charSet="utf-8" /> - <link rel="manifest" href="/manifest.json" /> - <RouterHead /> - <ServiceWorkerRegister /> - </head> - <body lang="en"> - <RouterOutlet /> - </body> - </QwikCityProvider> - </QwikSpeakProvider> - ); -}); -``` - -Finally we add an `index.tsx` with some translation, providing optional default values for each translation: `key@@[default value]`: - -_src/routes/index.tsx_ -```tsx -import { - useTranslate, - useFormatDate, - useFormatNumber, - Speak, -} from 'qwik-speak'; - -interface TitleProps { - name: string; -} - -export const Title = component$<TitleProps>(props => { - return (<h1>{props.name}</h1>) -}); - -export const Home = component$(() => { - const t = useTranslate(); - const fd = useFormatDate(); - const fn = useFormatNumber(); - - // Translate inside components rather than on props - const title = t('app.title@@{{name}} demo', { name: 'Qwik Speak' }); + /** + * Init Qwik Speak + */ + useQwikSpeak({ config, translationFn }); return ( - <> - <Title name={title} /> - - <h3>{t('home.dates@@Dates')}</h3> - <p>{fd(Date.now(), { dateStyle: 'full', timeStyle: 'short' })}</p> - - <h3>{t('home.numbers@@Numbers')}</h3> - <p>{fn(1000000, { style: 'currency' })}</p> - </> - ); -}); - -export default component$(() => { - return ( - /** - * Add Home translations (only available in child components) - */ - <Speak assets={['home']}> - <Home /> - </Speak> - ); -}); - -export const head: DocumentHead = { - title: 'home.head.title@@Qwik Speak', - meta: [{ name: 'description', content: 'home.head.description@@Quick start' }] -}; -``` - -## Scoped translation -We have used the `Speak` component to add scoped translations to the `Home` component: -- `Home` component will use the `home` asset, in addition to the `app` asset that comes with the configuration -- Using the asset name `home` as the root property in each key is the best practice to avoid keys in different files being overwritten - -> `Speak` component is a `Slot` component: because Qwik renders `Slot` components and direct children in isolation, translations are not immediately available in direct children, and we need to use a component for the `Home` page. It is not necessary to use more than one `Speak` component per page - -## Head metas -You may have noticed, that in `index.tsx` we have provided the meta title and description with only the keys. Since the Qwik City `DocumentHead` is out of context, we need to do the translations directly in `router-head.tsx`: - -_src/components/router-head/router-head.tsx_ -```tsx -export const RouterHead = component$(() => { - const t = useTranslate(); - - const head = useDocumentHead(); - - return ( - <> - <title>{t(head.title, { name: 'Qwik Speak' })} - - {head.meta.map((m) => ( - - ))} - + + {/* ... */} + ); }); ``` -We can also pass the `lang` attribute in the html tag: - -_src/entry.ssr.tsx_ -```typescript -import { config } from './speak-config'; - -export default function (opts: RenderToStreamOptions) { - return renderToStream(, { - manifest, - ...opts, - // Use container attributes to set attributes on the html tag - containerAttributes: { - lang: opts.serverData?.locale || config.defaultLocale.lang, - ...opts.containerAttributes, - }, - }); -} -``` - ## Resolve locale -We can resolve the locale to use in two ways: passing the `locale` parameter to the `QwikSpeakProvider` component, or assigning it to the `locale` handled by Qwik. Create `plugin.ts` in the root of the `src/routes` directory: +Create `plugin.ts` in the root of the `src/routes` directory: _src/routes/plugin.ts_ ```typescript +import type { RequestHandler } from '@builder.io/qwik-city'; import { config } from '../speak-config'; export const onRequest: RequestHandler = ({ request, locale }) => { - const cookie = request.headers?.get('cookie'); const acceptLanguage = request.headers?.get('accept-language'); let lang: string | null = null; - // Try whether the language is stored in a cookie - if (cookie) { - const result = new RegExp('(?:^|; )' + encodeURIComponent('locale') + '=([^;]*)').exec(cookie); - if (result) { - lang = JSON.parse(result[1])['lang']; - } - } + // Try to use user language - if (!lang) { - if (acceptLanguage) { - lang = acceptLanguage.split(';')[0]?.split(',')[0]; - } + if (acceptLanguage) { + lang = acceptLanguage.split(';')[0]?.split(',')[0]; } // Check supported locales @@ -218,155 +122,13 @@ export const onRequest: RequestHandler = ({ request, locale }) => { locale(lang); }; ``` -Internally, Qwik Speak will try to use the Qwik `locale`, before falling back to default locale if it is not in `supportedLocales`. +> We're on the server here, and you can get the language from `acceptLanguage`, a cookie, or a URL parameter, as you like. But is mandatory to set the Qwik locale -## Change locale -Now we want to change locale. Let's create a `ChangeLocale` component: - -_src/components/change-locale.tsx_ -```tsx -import type { SpeakLocale } from 'qwik-speak'; -import { useSpeakConfig, useTranslate } from 'qwik-speak'; - -export const ChangeLocale = component$(() => { - const t = useTranslate(); - - const config = useSpeakConfig(); - - const changeLocale$ = $((newLocale: SpeakLocale) => { - // Store locale in a cookie - document.cookie = `locale=${JSON.stringify(newLocale)};max-age=86400;path=/`; - - location.reload(); - }); - - return ( -
    -

    {t('app.changeLocale@@Change locale')}

    - {config.supportedLocales.map(value => ( - - ))} -
    - ); -}); -``` -and add the component in `header.tsx`: -```tsx -export default component$(() => { - return ( -
    - -
    - ); -}); -``` -In `changeLocale$` we set the locale in a cookie, before reloading the page. - -## Extraction -We can now extract the translations and generate the `assets` as json. In `package.json` add the following command to the scripts: -```json -"qwik-speak-extract": "qwik-speak-extract --supportedLangs=en-US,it-IT --assetsPath=i18n" -``` - -```shell -npm run qwik-speak-extract -``` - -The following files are generated: -``` -i18n/en-US/app.json -i18n/en-US/home.json -i18n/it-IT/app.json -i18n/it-IT/home.json -translations skipped due to dynamic keys: 2 -extracted keys: 4 -``` -`app` asset and `home` asset for each language, initialized with the default values we provided. - -_translations skipped due to dynamic keys_ are meta title and description keys, because those keys are passed as dynamic parameters. We have to add them manually in a new file that we will call `runtime`: - -_i18n/[lang]/runtime.json_ -```json -{ - "runtime": { - "home": { - "head": { - "title": "Qwik Speak", - "description": "Quick start" - } - } - } -} -``` -Update the keys in `DocumentHead` of `index.tsx`: -```tsx -export const head: DocumentHead = { - title: 'runtime.home.head.title@@Qwik Speak', - meta: [{ name: 'description', content: 'runtime.home.head.description@@Quick start' }] -}; -``` -and add `runtime` asset in Speak config: -```typescript -assets: [ - 'app' // Translations shared by the pages -], -runtimeAssets: [ - 'runtime' // Translations with dynamic keys or parameters -] -``` - -See [Qwik Speak Extract](./extract.md) for more details. - -## Translation -We can translate the `it-IT` files. - -If you have an OpenAI API key, you could use `gpt-translate-json` package: -```shell -npm install gpt-translate-json --save-dev -``` -In `package.json` add the following command to the scripts: -```json -"gpt-translate-json": "gpt-translate-json --apiKey=openai_api_key --model=gpt-3.5-turbo --maxTokens=3000 --langs=en-US,it-IT --originalLang=en-US" -``` - -```shell -npm gpt-translate-json -``` - -Run the app: -```shell -npm start -``` - -See [GPT Translate JSON](./gpt-translate-json.md) for more details. - -## Production -In production mode, `assets` are loaded only during SSR, and to get the translations on the client as well it is required to inline the translations in chucks sent to the browser. - -Add `qwikSpeakInline` Vite plugin in `vite.config.ts`: -```typescript -import { qwikSpeakInline } from 'qwik-speak/inline'; - -export default defineConfig(() => { - return { - plugins: [ - qwikCity(), - qwikVite(), - qwikSpeakInline({ - supportedLangs: ['en-US', 'it-IT'], - defaultLang: 'en-US', - assetsPath: 'i18n' - }), - tsconfigPaths(), - ], - }; -}); -``` Set the base URL for loading the chunks in the browser in `entry.ssr.tsx` file: ```typescript import { isDev } from '@builder.io/qwik/build'; +import type { RenderOptions } from "@builder.io/qwik/server"; +import { config } from './speak-config'; /** * Determine the base URL to use for loading the chunks in the browser. @@ -387,22 +149,15 @@ export default function (opts: RenderToStreamOptions) { ...opts, // Determine the base URL for the client code base: extractBase, + // Use container attributes to set attributes on the html tag + containerAttributes: { + lang: opts.serverData?.locale || config.defaultLocale.lang, + ...opts.containerAttributes, + }, }); } ``` -Build the production app in preview mode: -```shell -npm run preview -``` -Inspect the `qwik-speak-inline.log` file in root folder: - -``` -client: root.tsx -dynamic key: t(head.title) - Make sure the keys are in 'runtimeAssets' -dynamic key: t(m.content) - Make sure the keys are in 'runtimeAssets' -``` -It contains the non-inlined dynamic keys that we added in the `runtime.json` file. - -> The app will have the same behavior as you saw in dev mode, but now the translations are inlined as you can verify by inspecting the production files, reducing resource usage at runtime -See [Qwik Speak Inline Vite plugin](./inline.md) for more details. +## Tutorials +- [Tutorial: localized routing with the language](./tutorial-routing.md) +- [Tutorial: translated routing with url rewriting](./tutorial-routing-rewrite.md) diff --git a/docs/testing.md b/docs/testing.md index ae97f72..99380aa 100644 --- a/docs/testing.md +++ b/docs/testing.md @@ -2,44 +2,23 @@ > Unit test a Qwik component using Qwik Speak -To unit test a component which uses `qwik-speak`, you need to wrap it with `QwikSpeakProvider` component, so that it can pass the `SpeakContext` to the test component and its children. +To unit test a component which uses `qwik-speak`, you need to wrap it with `QwikSpeakMockProvider` component, so that it can pass the `SpeakContext` to the test component and its children. -Given the `config` object and a component to test like in [Quick Start](./quick-start.md): +Given the `config` object and a component to test like: _src/routes/index.tsx_ ```tsx -import { - useTranslate, - useFormatDate, - useFormatNumber, - Speak, -} from 'qwik-speak'; +import { inlineTranslate, useFormatDate, useFormatNumber } from 'qwik-speak'; -export const Home = component$(() => { - const t = useTranslate(); - const fd = useFormatDate(); - const fn = useFormatNumber(); +export default component$(() => { + const t = inlineTranslate(); return ( <>

    {t('app.title@@{{name}} demo', { name: 'Qwik Speak' })}

    - -

    {t('home.dates@@Dates')}

    -

    {fd(Date.now(), { dateStyle: 'full', timeStyle: 'short' })}

    - -

    {t('home.numbers@@Numbers')}

    -

    {fn(1000000, { style: 'currency' })}

    ); }); - -export default component$(() => { - return ( - - - - ); -}); ``` We'll have the following unit test (using _Vitest_): @@ -52,9 +31,9 @@ test(`[Home Component]: Should render the component`, async () => { const { screen, render } = await createDOM(); await render( - + - + ); expect(screen.outerHTML).toContain('Qwik Speak demo'); diff --git a/docs/translate.md b/docs/translate.md index a4e6852..44ebbc0 100644 --- a/docs/translate.md +++ b/docs/translate.md @@ -1,12 +1,12 @@ # Translate -> The return functions of `useTranslate`, `inlineTranslate` and `usePlural` are parsed and replaced with translated texts at compile time. For this reason, they expect _values_ or _identifiers_ as parameters, and no JavaScript _operators_ +> The return functions of `inlineTranslate` and `inlinePlural` are parsed and replaced with translated texts in chunks sent to the browser at compile time -## useTranslate -`useTranslate` returns a functions to get the translation using key-value pairs: +## inlineTranslate +`inlineTranslate` returns a functions to get the translation using key-value pairs: ```tsx -const t = useTranslate(); +const t = inlineTranslate(); -t('home.title@@Qwik Speak') +t('title@@Qwik Speak') ``` Value after `@@` is the optional default value: ```tsx @@ -16,7 +16,7 @@ Value after `@@` is the optional default value: ### Params interpolation `t` function accept params as well: ```tsx -t('home.greeting@@Hi! I am {{name}}', { name: 'Qwik Speak' }) +t('greeting@@Hi! I am {{name}}', { name: 'Qwik Speak' }) ``` `name` param is replaced at runtime or during the inlining: ```text @@ -37,16 +37,14 @@ and returns an array of translated values: `t` function can get arrays and objects directly from files: ```json { - "home": { - "array": [ - "one", - "two", - "three" - ], - "obj": { - "one": "one", - "two": "two" - } + "array": [ + "one", + "two", + "three" + ], + "obj": { + "one": "one", + "two": "two" } } ``` @@ -54,133 +52,54 @@ just pass to the function the type parameter: ```tsx import type { Translation } from 'qwik-speak'; -t('home.array') -t('home.obj') +t('array') +t('obj') ``` You can also access by array position: ```tsx -t('home.array.2@@three') +t('array.2@@three') ``` Finally, it is possible to set arrays and objects passing a _valid stringified_ default value: ```tsx -t('home.array@@["one","two","three"]') -t('home.obj@@{"one":"one","two":"two"}') +t('array@@["one","two","three"]') +t('obj@@{"one":"one","two":"two"}') ``` ### Html in translations You can have Html in translations, like: ```json { - "home": { - "text": "Internationalization (i18n) library to translate texts, dates and numbers in Qwik apps" - } + "description": "Internationalization (i18n) library to translate texts, dates and numbers in Qwik apps" } ``` but you have to use `dangerouslySetInnerHTML`: ```tsx -export const Home = component$(() => { - const t = useTranslate(); - - const text = t('home.text'); - return ( -

    - ); -}); +

    ``` > On the client the text is _inlined_ during build, so there are no XSS risks -## Component props and jsx attributes -Translate inside components rather than on props: - -```tsx -export const Title = component$((props) => { - return (

    {props.name}

    ) -}); - -export const Home = component$(() => { - const t = useTranslate(); - - const name = t('app.title'); - return ( - - ); -}); -``` -or -```tsx -export const Title = component$<TitleProps>((props) => { - const t = useTranslate(); - - return (<h1>{t(props.name)}</h1>) -}); - -export const Home = component$(() => { - return ( - <Title name='app.title' /> - ); -}); -``` -In the latter case, `app.title` will have to be placed in the `runtimeAssets`, as a dynamic key is passed to the `t` function. - -Translate the attributes into the components as well: - -```tsx -export const Home = component$(() => { - const t = useTranslate(); - - const text = t('home.text'); - return ( - <p dangerouslySetInnerHTML={text}></p> - ); -}); -``` - -## inlineTranslate -`inlineTranslate` function has the same behavior as the function returned by `useTranslate`, but can be used outside the `component$`, for example in _Inline components_, passing the Speak context as second argument: -```tsx -import { inlineTranslate, useSpeakContext } from 'qwik-speak'; - -export const TitleComponent = (props: { ctx: SpeakState }) => { - return <h1>{inlineTranslate('home.title@@Qwik Speak', props.ctx)}</h1>; -}; - -export const Home = component$(() => { - const ctx = useSpeakContext(); - - return ( - <div> - <TitleComponent ctx={ctx} /> - </div> - ); -}); -``` - -## usePlural -`usePlural` returns a functions that uses [Intl.PluralRules](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/PluralRules) API: +## inlinePlural +`inlinePlural` returns a functions that uses [Intl.PluralRules](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/PluralRules) API: ```tsx -const p = usePlural(); +const p = inlinePlural(); -p(1, 'home.devs') +p(1, 'devs') ``` -When you run the extraction tool, it creates a translation file with the Intl API plural rules for each language: +When you run the extraction tool, it creates the Intl API plural rules for each language: ```json { - "home": { - "devs": { - "one": "", - "other": "" - } + "devs": { + "one": "", + "other": "" } } ``` -There is no default value for `usePlural` function, so you must add the translation in each language, keeping in mind that the counter is optionally interpolated with the `value` parameter: +There is no default value for `inlinePlural` function, so you must add the translation in each language, keeping in mind that the counter is optionally interpolated with the `value` parameter: ```json { - "home": { - "devs": { - "one": "{{ value }} software developer", - "other": "{{ value }} software developers" - } + "devs": { + "one": "{{ value }} software developer", + "other": "{{ value }} software developers" } } ``` @@ -189,6 +108,54 @@ It is rendered as: 1 software developer ``` +## Runtime translation +When you use a translation like this: +```tsx +const key = 'dynamic'; + +t(key) +``` +you are using a dynamic translation. It means that it is not possible to evaluate the translation at compile time but only at runtime based on the value that the key takes on. + +To instruct Qwik Speak to use dynamic translations, create a file with the values that these translations can take: + +_i18n/[lang]/runtime.json_ +```json +{ + "dynamic": "I'm a dynamic value" +} +``` +and add the `runtime` file to `runtimeAssets` in configuration or `useSpeak` provider. + +## Server translation +`inlineTranslate` and `inlinePlural` work in `component$`, _Inline components_, QRL and functions if called by the components, but they might not work in functions invoked on the server, such as `routeLoader$` and _endpoints_. + +Functions like `routeLoader$` live on the server, which knows nothing about the context of the app, and depending on the case they can be invoked before the app runs. To translate on the server you need: +- make sure translations are available +- let the server know the current language of the user + +`server$` function can satisfy both conditions, since the function is executed only when invoked, and accepts parameters: + +```tsx +export const serverFn = server$(function (lang: string) { + const t = inlineTranslate(); + + return t('title', { name: 'Qwik Speak' }, lang); +}); + +export default component$(() => { + const locale = useSpeakLocale(); + const s = useSignal(''); + + useTask$(async () => { + s.value = await serverFn(locale.lang) + }); + + return (<p>{s.value}</p>); +}); +``` +You can also extract the language directly into the function, through the request (cookies, params), instead of passing it as a parameter. + # Localize ## useFormatDate `useFormatDate` returns a functions that uses [Intl.DateTimeFormat](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/DateTimeFormat) API to format dates: @@ -269,13 +236,11 @@ American English # Multilingual Each of the translation and localization functions accepts a different language other than the current one as its last argument: ```tsx -const t = useTranslate(); +const t = inlineTranslate(); -t('home.title@@Qwik Speak', undefined, 'it-IT') +t('title@@Qwik Speak', undefined, 'it-IT') ``` -For the translation to occur in the language passed as an argument, you need to pass the additional language to `QwikSpeakProvider` or `Speak` components: +For the translation to occur in the language passed as an argument, you need to set the additional language to `useQwikSpeak` or `useSpeak` providers: ```tsx -<Speak assets={['home']} langs={['it-IT']}> - <Home /> -</Speak> +useQwikSpeak({ config, translationFn, langs: ['it-IT'] }); ``` diff --git a/docs/tutorial-routing-rewrite.md b/docs/tutorial-routing-rewrite.md index d656462..0576f30 100644 --- a/docs/tutorial-routing-rewrite.md +++ b/docs/tutorial-routing-rewrite.md @@ -1,12 +1,11 @@ -# Tutorial: localized routing with url rewriting +# Tutorial: translated routing with url rewriting > Step by step, let's build a sample app with Qwik Speak and translated paths using Qwik City features -```shell -npm create qwik@latest -npm install qwik-speak --save-dev -``` +## Setup +See [Quick Start](./quick-start.md) +## Routing Let's assume that we want to create a navigation of this type: - default language (en-US): routes not localized `http://127.0.0.1:4173/` - other languages (it-IT): localized routes `http://127.0.0.1:4173/it-IT/` @@ -18,8 +17,7 @@ Or: But we DON'T want to have this url instead: - other languages (it-IT): localized routes `http://127.0.0.1:4173/it-IT/page` -## Configuration -Let's create `speak-routes.ts` file in `src`: +Now let's handle it. Create `speak-routes.ts` file in `src`: _src/speak-routes.ts_ ```typescript @@ -29,32 +27,43 @@ import type { RewriteRouteOption } from 'qwik-speak'; * Translation paths */ export const rewriteRoutes: RewriteRouteOption[] = [ + // No prefix for default locale + // { + // paths: { + // 'page': 'page' + // } + // }, { prefix: 'it-IT', paths: { - 'page': 'pagina' + 'page': 'pagina' } } -] +]; ``` -and update `qwikCity` Vite plugin in `vite.config.ts`: +Add `rewriteRoutes` to `qwikCity` Vite plugin in `vite.config.ts`: + ```typescript +import { qwikSpeakInline } from 'qwik-speak/inline'; + import { rewriteRoutes } from './src/speak-routes'; export default defineConfig(() => { return { plugins: [ - qwikCity( - { rewriteRoutes } - ), + qwikCity({ rewriteRoutes }), qwikVite(), + qwikSpeakInline({ + supportedLangs: ['en-US', 'it-IT'], + defaultLang: 'en-US', + assetsPath: 'i18n' + }), tsconfigPaths(), ], }; }); ``` - -Now create `speak-config.ts` and `speak-functions.ts` files in `src`: +Add `rewriteRoutes` to `speak-config.ts` in `src`: _src/speak-config.ts_ ```typescript @@ -69,39 +78,17 @@ export const config: SpeakConfig = { { lang: 'it-IT', currency: 'EUR', timeZone: 'Europe/Rome' }, { lang: 'en-US', currency: 'USD', timeZone: 'America/Los_Angeles' } ], + // Translations available in the whole app assets: [ - 'app' // Translations shared by the pages + 'app' + ], + // Translations with dynamic keys available in the whole app + runtimeAssets: [ + 'runtime' ] }; ``` -_src/speak-functions.ts_ -```typescript -import { server$ } from '@builder.io/qwik-city'; -import type { LoadTranslationFn, Translation, TranslationFn } from 'qwik-speak'; - -/** - * Translation files are lazy-loaded via dynamic import and will be split into separate chunks during build. - * Keys must be valid variable names - */ -const translationData = import.meta.glob<Translation>('/i18n/**/*.json'); - -/** - * Using server$, translation data is always accessed on the server - */ -const loadTranslation$: LoadTranslationFn = server$(async (lang: string, asset: string) => - await translationData[`/i18n/${lang}/${asset}.json`]?.() -); - -export const translationFn: TranslationFn = { - loadTranslation$: loadTranslation$ -}; -``` -We have added the Speak config and the implementation of the `loadTranslation$` function to load translation files. - -> `loadTranslation$` is a customizable QRL function: you can load the translation files in the way you prefer - -## Routing -Now let's handle the routing. Create `plugin.ts` in the root of the `src/routes` directory: +Update `plugin.ts` in the root of the `src/routes` directory: _src/routes/plugin.ts_ ```typescript @@ -122,230 +109,133 @@ export const onRequest: RequestHandler = ({ url, locale }) => { locale(lang || config.defaultLocale.lang); }; ``` -We assign the value of the `lang` parameter (from the url prefix) to Qwik `locale`. This way it will be immediately available to the library. -## Adding Qwik Speak -Just put `QwikSpeakProvider` inside Qwik City provider component in `root.tsx` and pass it the configuration and the translation functions: +## Usage +Add `index.tsx` with some translation, providing optional default values for each translation: `key@@[default value]`: -_src/root.tsx_ +_src/routes//index.tsx_ ```tsx -import { QwikSpeakProvider } from 'qwik-speak'; - -import { config } from './speak-config'; -import { translationFn } from './speak-functions'; +import { inlineTranslate, useFormatDate, useFormatNumber } from 'qwik-speak'; export default component$(() => { - return ( - <QwikCityProvider> - <QwikSpeakProvider config={config} translationFn={translationFn}> - <head> - <meta charSet="utf-8" /> - <link rel="manifest" href="/manifest.json" /> - <RouterHead /> - <ServiceWorkerRegister /> - </head> - <body lang="en"> - <RouterOutlet /> - </body> - </QwikSpeakProvider> - </QwikCityProvider> - ); -}); -``` + const t = inlineTranslate(); -Now we add an `index.tsx` with some translation, providing optional default values for each translation: `key@@[default value]`: - -_src/routes/index.tsx_ -```tsx -import { - useTranslate, - useFormatDate, - useFormatNumber, - Speak, -} from 'qwik-speak'; - -interface TitleProps { - name: string; -} - -export const Title = component$<TitleProps>(props => { - return (<h1>{props.name}</h1>) -}); - -export const Home = component$(() => { - const t = useTranslate(); const fd = useFormatDate(); const fn = useFormatNumber(); - // Translate inside components rather than on props - const title = t('app.title@@{{name}} demo', { name: 'Qwik Speak' }); - return ( <> - <Title name={title} /> + <h1>{t('app.title@@{{name}} demo', { name: 'Qwik Speak' })}</h1> - <h3>{t('home.dates@@Dates')}</h3> + <h3>{t('dates@@Dates')}</h3> <p>{fd(Date.now(), { dateStyle: 'full', timeStyle: 'short' })}</p> - <h3>{t('home.numbers@@Numbers')}</h3> + <h3>{t('numbers@@Numbers')}</h3> <p>{fn(1000000, { style: 'currency' })}</p> </> ); }); -export default component$(() => { - return ( - /** - * Add Home translations (only available in child components) - */ - <Speak assets={['home']}> - <Home /> - </Speak> - ); -}); +export const head: DocumentHead = () => { + const t = inlineTranslate(); -export const head: DocumentHead = { - title: 'home.head.title@@Qwik Speak', - meta: [{ name: 'description', content: 'home.head.description@@Qwik Speak with localized routing' }] + return { + title: t('app.head.home.title@@{{name}}', { name: 'Qwik Speak' }), + meta: [{ name: 'description', content: t('app.head.home.description@@Localized routing') }], + }; }; ``` +Add a `page/index.tsx` to try the router: -Finally we add a `page/index.tsx` to try the router: - -_src/routes/page/index.tsx_ +_src/routes//page/index.tsx_ ```tsx -import { component$ } from '@builder.io/qwik'; -import { useTranslate } from 'qwik-speak'; +import { inlineTranslate } from 'qwik-speak'; export default component$(() => { - const t = useTranslate(); + const t = inlineTranslate(); - return ( - <> - <h1>{t('app.title')}</h1> - <h2>{t('app.subtitle')}</h2> - </> - ); -}); -``` - -## Scoped translation -We have used the `Speak` component to add scoped translations to the `Home` component: -- `Home` component will use the `home` asset, in addition to the `app` asset that comes with the configuration -- Using the asset name `home` as the root property in each key is the best practice to avoid keys in different files being overwritten - -> `Speak` component is a `Slot` component: because Qwik renders `Slot` components and direct children in isolation, translations are not immediately available in direct children, and we need to use a component for the `Home` page. It is not necessary to use more than one `Speak` component per page - -## Head metas -You may have noticed, that in `index.tsx` we have provided the meta title and description with only the keys. Since the Qwik City `DocumentHead` is out of context, we need to do the translations directly in `router-head.tsx`: - -_src/components/router-head/router-head.tsx_ -```tsx -export const RouterHead = component$(() => { - const t = useTranslate(); - - const head = useDocumentHead(); + const key = 'dynamic'; return ( <> - <title>{t(head.title, { name: 'Qwik Speak' })} +

    {t('app.title', { name: 'Qwik Speak' })}

    - {head.meta.map((m) => ( - - ))} +

    {t(`runtime.${key}`)}

    ); }); ``` +> Note that it is not necessary to provide the default value in the key once again: it is sufficient and not mandatory to provide it once in the app -We can also pass the `lang` attribute in the html tag: - -_src/entry.ssr.tsx_ -```typescript -import { config } from './speak-config'; - -export default function (opts: RenderToStreamOptions) { - return renderToStream(, { - manifest, - ...opts, - // Use container attributes to set attributes on the html tag - containerAttributes: { - lang: opts.serverData?.locale || config.defaultLocale.lang, - ...opts.containerAttributes, - }, - }); -} -``` +> Note the use of a dynamic key (which will therefore only be available at runtime), which we assign to the `runtime` scope ## Change locale Now we want to change locale. Let's create a `ChangeLocale` component: -_src/components/change-locale.tsx_ +_src/components/change-locale/change-locale.tsx_ ```tsx -import type { SpeakLocale } from 'qwik-speak'; -import { useSpeakLocale, useSpeakConfig, useDisplayName, useTranslate, useTranslatePath } from 'qwik-speak'; +import { useLocation } from '@builder.io/qwik-city'; +import { useSpeakLocale, useSpeakConfig, useDisplayName, inlineTranslate, translatePath } from 'qwik-speak'; export const ChangeLocale = component$(() => { - const t = useTranslate(); - const tp = useTranslatePath(); - const dn = useDisplayName(); + const t = inlineTranslate(); + + const url = useLocation().url; - const loc = useLocation() const locale = useSpeakLocale(); const config = useSpeakConfig(); + const dn = useDisplayName(); - // Replace the locale and navigate to the new URL - const getLocalePath = (newLocale: SpeakLocale) => { - const url = new URL(loc.url) - url.pathname = tp(url.pathname, newLocale.lang) - return url.toString(); - }; + const getPath = translatePath(); return ( -
    + <>

    {t('app.changeLocale@@Change locale')}

    {config.supportedLocales.map(value => ( - + {dn(value.lang, { type: 'language' })} ))} -
    + ); }); ``` -and add the component in `header.tsx`: +> We use the `` tag tag because it is mandatory to reload the page when changing the language + +Add the `ChangeLocale` component in `header.tsx` along with localized navigation links: ```tsx import { Link, useLocation } from '@builder.io/qwik-city'; -import { useTranslate, useTranslatePath } from 'qwik-speak'; -import { ChangeLocale } from '../change-locale/change-locale'; +import { inlineTranslate, translatePath } from 'qwik-speak'; + +import { ChangeLocale } from '../../change-locale/change-locale'; export default component$(() => { - const t = useTranslate(); - const tp = useTranslatePath(); + const t = inlineTranslate(); - const { url } = useLocation(); + const pathname = useLocation().url.pathname; + + const getPath = translatePath(); + const [homePath, pagePath] = getPath(['/', '/page/']); - const [ - homePath, - pagePath, - ] = tp(['/', '/page/']) - return ( -
    -
      -
    • - - {t('app.nav.home@@Home')} - -
    • -
    • - - {t('app.nav.page@@Page')} - -
    • -
    + <> +
    +
      +
    • + + {t('app.nav.home@@Home')} + +
    • +
    • + + {t('app.nav.page@@Page')} + +
    • +
    +
    + -
    + ); }); ``` @@ -363,141 +253,35 @@ npm run qwik-speak-extract The following files are generated: ``` i18n/en-US/app.json -i18n/en-US/home.json i18n/it-IT/app.json -i18n/it-IT/home.json -translations skipped due to dynamic keys: 2 -extracted keys: 4 +translations skipped due to dynamic keys: 1 +extracted keys: 9 ``` -`app` asset and `home` asset for each language, initialized with the default values we provided. +`app` asset for each language, initialized with the default values we provided. -_translations skipped due to dynamic keys_ are meta title and description keys, because those keys are passed as dynamic parameters. We have to add them manually in a new file that we will call `runtime`: +_translations skipped due to dynamic keys_ is `runtime.${key}`. During configuration, we provided in `runtimeAssets` a `runtime` file, which we can now create and populate with dynamic keys: _i18n/[lang]/runtime.json_ ```json { "runtime": { - "home": { - "head": { - "title": "Qwik Speak", - "description": "Qwik Speak with localized routing" - } - } + "dynamic": "I'm a dynamic value" } } ``` -Update the keys in `DocumentHead` of `index.tsx`: -```tsx -export const head: DocumentHead = { - title: 'runtime.home.head.title@@Qwik Speak', - meta: [{ name: 'description', content: 'runtime.home.head.description@@Qwik Speak with localized routing' }] -}; -``` -and add `runtime` asset in Speak config: -```typescript -assets: [ - 'app' // Translations shared by the pages -], -runtimeAssets: [ - 'runtime' // Translations with dynamic keys or parameters -] -``` - See [Qwik Speak Extract](./extract.md) for more details. -## Translation -We can translate the `it-IT` files. - -If you have an OpenAI API key, you could use `gpt-translate-json` package: -```shell -npm install gpt-translate-json --save-dev -``` -In `package.json` add the following command to the scripts: -```json -"gpt-translate-json": "gpt-translate-json --apiKey=openai_api_key --model=gpt-3.5-turbo --maxTokens=3000 --langs=en-US,it-IT --originalLang=en-US" -``` +## Development +We can translate the `it-IT` files and start the app: -```shell -npm gpt-translate-json -``` - -Run the app: ```shell npm start ``` -See [GPT Translate JSON](./gpt-translate-json.md) for more details. - ## Production -In production mode, `assets` are loaded only during SSR, and to get the translations on the client as well it is required to inline the translations in chucks sent to the browser. - -Add `qwikSpeakInline` Vite plugin in `vite.config.ts`: -```typescript -import { qwikSpeakInline } from 'qwik-speak/inline'; - -import { rewriteRoutes } from './src/speak-routes'; - -export default defineConfig(() => { - return { - plugins: [ - qwikCity( - { rewriteRoutes } - ), - qwikVite(), - qwikSpeakInline({ - supportedLangs: ['en-US', 'it-IT'], - defaultLang: 'en-US', - assetsPath: 'i18n' - }), - tsconfigPaths(), - ], - }; -}); -``` -Set the base URL for loading the chunks in the browser in `entry.ssr.tsx` file: -```typescript -import { isDev } from '@builder.io/qwik/build'; - -/** - * Determine the base URL to use for loading the chunks in the browser. - * The value set through Qwik 'locale()' in 'plugin.ts' is saved by Qwik in 'serverData.locale' directly. - * Make sure the locale is among the 'supportedLocales' - */ -export function extractBase({ serverData }: RenderOptions): string { - if (!isDev && serverData?.locale) { - return '/build/' + serverData.locale; - } else { - return '/build'; - } -} - -export default function (opts: RenderToStreamOptions) { - return renderToStream(, { - manifest, - ...opts, - // Determine the base URL for the client code - base: extractBase, - // Use container attributes to set attributes on the html tag - containerAttributes: { - lang: opts.serverData?.locale || config.defaultLocale.lang, - ...opts.containerAttributes, - }, - }); -} -``` Build the production app in preview mode: ```shell npm run preview ``` -Inspect the `qwik-speak-inline.log` file in root folder: - -``` -client: root.tsx -dynamic key: t(head.title) - Make sure the keys are in 'runtimeAssets' -dynamic key: t(m.content) - Make sure the keys are in 'runtimeAssets' -``` -It contains the non-inlined dynamic keys that we added in the `runtime.json` file. - -> The app will have the same behavior as you saw in dev mode, but now the translations are inlined as you can verify by inspecting the production files, reducing resource usage at runtime -See [Qwik Speak Inline Vite plugin](./inline.md) for more details. +and inspect the `qwik-speak-inline.log` file in root folder to see warnings for missing values or dynamic keys. diff --git a/docs/tutorial-routing.md b/docs/tutorial-routing.md index 4b5951b..857375c 100644 --- a/docs/tutorial-routing.md +++ b/docs/tutorial-routing.md @@ -1,55 +1,9 @@ -# Tutorial: localized routing with prefix only +# Tutorial: localized routing with the language > Step by step, let's build a sample app with Qwik Speak and a localized router using Qwik City features -```shell -npm create qwik@latest -npm install qwik-speak --save-dev -``` - -## Configuration -Let's create `speak-config.ts` and `speak-functions.ts` files in `src`: - -_src/speak-config.ts_ -```typescript -import type { SpeakConfig } from 'qwik-speak'; - -export const config: SpeakConfig = { - defaultLocale: { lang: 'en-US', currency: 'USD', timeZone: 'America/Los_Angeles' }, - supportedLocales: [ - { lang: 'it-IT', currency: 'EUR', timeZone: 'Europe/Rome' }, - { lang: 'en-US', currency: 'USD', timeZone: 'America/Los_Angeles' } - ], - assets: [ - 'app' // Translations shared by the pages - ] -}; -``` -_src/speak-functions.ts_ -```typescript -import { server$ } from '@builder.io/qwik-city'; -import type { LoadTranslationFn, Translation, TranslationFn } from 'qwik-speak'; - -/** - * Translation files are lazy-loaded via dynamic import and will be split into separate chunks during build. - * Keys must be valid variable names - */ -const translationData = import.meta.glob('/i18n/**/*.json'); - -/** - * Using server$, translation data is always accessed on the server - */ -const loadTranslation$: LoadTranslationFn = server$(async (lang: string, asset: string) => - await translationData[`/i18n/${lang}/${asset}.json`]?.() -); - -export const translationFn: TranslationFn = { - loadTranslation$: loadTranslation$ -}; -``` -We have added the Speak config and the implementation of the `loadTranslation$` function to load translation files. - -> `loadTranslation$` is a customizable QRL function: you can load the translation files in the way you prefer +## Setup +See [Quick Start](./quick-start.md) ## Routing Let's assume that we want to create a navigation of this type: @@ -65,216 +19,161 @@ src/routes/ layout.tsx ``` -Now let's handle it. Create `plugin.ts` in the root of the `src/routes` directory: +Now let's handle it. Update `plugin.ts` in the root of the `src/routes` directory: _src/routes/plugin.ts_ ```typescript -import { config } from '../speak-config'; +import type { RequestHandler } from '@builder.io/qwik-city'; +import { validateLocale } from 'qwik-speak'; -export const onRequest: RequestHandler = ({ params, locale }) => { - // Check supported locales - const supportedLocale = config.supportedLocales.find(value => value.lang === params.lang) +import { config } from '../speak-config'; - // Check for 404 error page - const lang = supportedLocale - ? supportedLocale.lang - : !params.lang && config.defaultLocale.lang +export const onRequest: RequestHandler = ({ params, locale, error }) => { + let lang: string | undefined = undefined; - if(!lang) throw error(404, 'Page not found'); + if (params.lang && validateLocale(params.lang)) { + // Check supported locales + lang = config.supportedLocales.find(value => value.lang === params.lang)?.lang; + // 404 error page + if (!lang) throw error(404, 'Page not found'); + } else { + lang = config.defaultLocale.lang; + } // Set Qwik locale locale(lang); }; ``` -We assign the value of the `lang` parameter to Qwik `locale`. This way it will be immediately available to the library. - -## Adding Qwik Speak -Just wrap Qwik City provider with `QwikSpeakProvider` component in `root.tsx` and pass it the configuration and the translation functions: - -_src/root.tsx_ -```tsx -import { QwikSpeakProvider } from 'qwik-speak'; -import { config } from './speak-config'; -import { translationFn } from './speak-functions'; - -export default component$(() => { - return ( - - - - - - - - - - - - - - ); -}); -``` - -Finally we add an `index.tsx` with some translation, providing optional default values for each translation: `key@@[default value]`: +## Usage +Add `index.tsx` with some translation, providing optional default values for each translation: `key@@[default value]`: _src/routes/[...lang]/index.tsx_ ```tsx -import { - useTranslate, - useFormatDate, - useFormatNumber, - Speak, -} from 'qwik-speak'; - -interface TitleProps { - name: string; -} +import { inlineTranslate, useFormatDate, useFormatNumber } from 'qwik-speak'; -export const Title = component$(props => { - return (

    {props.name}

    ) -}); +export default component$(() => { + const t = inlineTranslate(); -export const Home = component$(() => { - const t = useTranslate(); const fd = useFormatDate(); const fn = useFormatNumber(); - // Translate inside components rather than on props - const title = t('app.title@@{{name}} demo', { name: 'Qwik Speak' }); - return ( <> - + <h1>{t('app.title@@{{name}} demo', { name: 'Qwik Speak' })}</h1> - <h3>{t('home.dates@@Dates')}</h3> + <h3>{t('dates@@Dates')}</h3> <p>{fd(Date.now(), { dateStyle: 'full', timeStyle: 'short' })}</p> - <h3>{t('home.numbers@@Numbers')}</h3> + <h3>{t('numbers@@Numbers')}</h3> <p>{fn(1000000, { style: 'currency' })}</p> </> ); }); -export default component$(() => { - return ( - /** - * Add Home translations (only available in child components) - */ - <Speak assets={['home']}> - <Home /> - </Speak> - ); -}); +export const head: DocumentHead = () => { + const t = inlineTranslate(); -export const head: DocumentHead = { - title: 'home.head.title@@Qwik Speak', - meta: [{ name: 'description', content: 'home.head.description@@Qwik Speak with localized routing' }] + return { + title: t('app.head.home.title@@{{name}}', { name: 'Qwik Speak' }), + meta: [{ name: 'description', content: t('app.head.home.description@@Localized routing') }], + }; }; ``` +Add a `page/index.tsx` to try the router: -## Scoped translation -We have used the `Speak` component to add scoped translations to the `Home` component: -- `Home` component will use the `home` asset, in addition to the `app` asset that comes with the configuration -- Using the asset name `home` as the root property in each key is the best practice to avoid keys in different files being overwritten - -> `Speak` component is a `Slot` component: because Qwik renders `Slot` components and direct children in isolation, translations are not immediately available in direct children, and we need to use a component for the `Home` page. It is not necessary to use more than one `Speak` component per page - -## Head metas -You may have noticed, that in `index.tsx` we have provided the meta title and description with only the keys. Since the Qwik City `DocumentHead` is out of context, we need to do the translations directly in `router-head.tsx`: - -_src/components/router-head/router-head.tsx_ +_src/routes/[...lang]/page/index.tsx_ ```tsx -export const RouterHead = component$(() => { - const t = useTranslate(); +import { inlineTranslate } from 'qwik-speak'; + +export default component$(() => { + const t = inlineTranslate(); - const head = useDocumentHead(); + const key = 'dynamic'; return ( <> - <title>{t(head.title, { name: 'Qwik Speak' })} +

    {t('app.title', { name: 'Qwik Speak' })}

    - {head.meta.map((m) => ( - - ))} +

    {t(`runtime.${key}`)}

    ); }); ``` +> Note that it is not necessary to provide the default value in the key once again: it is sufficient and not mandatory to provide it once in the app -We can also pass the `lang` attribute in the html tag: - -_src/entry.ssr.tsx_ -```typescript -import { config } from './speak-config'; - -export default function (opts: RenderToStreamOptions) { - return renderToStream(, { - manifest, - ...opts, - // Use container attributes to set attributes on the html tag - containerAttributes: { - lang: opts.serverData?.locale || config.defaultLocale.lang, - ...opts.containerAttributes, - }, - }); -} -``` +> Note the use of a dynamic key (which will therefore only be available at runtime), which we assign to the `runtime` scope ## Change locale Now we want to change locale. Let's create a `ChangeLocale` component: -_src/components/change-locale.tsx_ +_src/components/change-locale/change-locale.tsx_ ```tsx -import type { SpeakLocale } from 'qwik-speak'; -import { useSpeakConfig, useTranslate } from 'qwik-speak'; +import { useLocation } from '@builder.io/qwik-city'; +import { useSpeakLocale, useSpeakConfig, useDisplayName, inlineTranslate, localizePath } from 'qwik-speak'; export const ChangeLocale = component$(() => { - const t = useTranslate(); + const t = inlineTranslate(); - const loc = useLocation(); + const url = useLocation().url; + + const locale = useSpeakLocale(); const config = useSpeakConfig(); + const dn = useDisplayName(); - // Replace the locale and navigate to the new URL - const navigateByLocale$ = $((newLocale: SpeakLocale) => { - const url = new URL(location.href); - if (loc.params.lang) { - if (newLocale.lang !== config.defaultLocale.lang) { - url.pathname = url.pathname.replace(loc.params.lang, newLocale.lang); - } else { - url.pathname = url.pathname.replace(new RegExp(`(/${loc.params.lang}/)|(/${loc.params.lang}$)`), '/'); - } - } else if (newLocale.lang !== config.defaultLocale.lang) { - url.pathname = `/${newLocale.lang}${url.pathname}`; - } - - location.href = url.toString(); - }); + const getPath = localizePath(); return ( -
    + ); }); ``` -and add the component in `header.tsx`: +> We use the `` tag tag because it is mandatory to reload the page when changing the language + +Add the `ChangeLocale` component in `header.tsx` along with localized navigation links: ```tsx +import { Link, useLocation } from '@builder.io/qwik-city'; +import { inlineTranslate, localizePath } from 'qwik-speak'; + +import { ChangeLocale } from '../../change-locale/change-locale'; + export default component$(() => { + const t = inlineTranslate(); + + const pathname = useLocation().url.pathname; + + const getPath = localizePath(); + const [homePath, pagePath] = getPath(['/', '/page/']); + return ( -
    + <> +
    +
      +
    • + + {t('app.nav.home@@Home')} + +
    • +
    • + + {t('app.nav.page@@Page')} + +
    • +
    +
    + -
    + ); }); ``` -In `navigateByLocale$` we replace the language in the URL, before navigating to the new localized URL. ## Extraction We can now extract the translations and generate the `assets` as json. In `package.json` add the following command to the scripts: @@ -289,137 +188,35 @@ npm run qwik-speak-extract The following files are generated: ``` i18n/en-US/app.json -i18n/en-US/home.json i18n/it-IT/app.json -i18n/it-IT/home.json -translations skipped due to dynamic keys: 2 -extracted keys: 4 +translations skipped due to dynamic keys: 1 +extracted keys: 9 ``` -`app` asset and `home` asset for each language, initialized with the default values we provided. +`app` asset for each language, initialized with the default values we provided. -_translations skipped due to dynamic keys_ are meta title and description keys, because those keys are passed as dynamic parameters. We have to add them manually in a new file that we will call `runtime`: +_translations skipped due to dynamic keys_ is `runtime.${key}`. During configuration, we provided in `runtimeAssets` a `runtime` file, which we can now create and populate with dynamic keys: _i18n/[lang]/runtime.json_ ```json { "runtime": { - "home": { - "head": { - "title": "Qwik Speak", - "description": "Qwik Speak with localized routing" - } - } + "dynamic": "I'm a dynamic value" } } ``` -Update the keys in `DocumentHead` of `index.tsx`: -```tsx -export const head: DocumentHead = { - title: 'runtime.home.head.title@@Qwik Speak', - meta: [{ name: 'description', content: 'runtime.home.head.description@@Qwik Speak with localized routing' }] -}; -``` -and add `runtime` asset in Speak config: -```typescript -assets: [ - 'app' // Translations shared by the pages -], -runtimeAssets: [ - 'runtime' // Translations with dynamic keys or parameters -] -``` - See [Qwik Speak Extract](./extract.md) for more details. -## Translation -We can translate the `it-IT` files. - -If you have an OpenAI API key, you could use `gpt-translate-json` package: -```shell -npm install gpt-translate-json --save-dev -``` -In `package.json` add the following command to the scripts: -```json -"gpt-translate-json": "gpt-translate-json --apiKey=openai_api_key --model=gpt-3.5-turbo --maxTokens=3000 --langs=en-US,it-IT --originalLang=en-US" -``` +## Development +We can translate the `it-IT` files and start the app: -```shell -npm gpt-translate-json -``` - -Run the app: ```shell npm start ``` -See [GPT Translate JSON](./gpt-translate-json.md) for more details. - ## Production -In production mode, `assets` are loaded only during SSR, and to get the translations on the client as well it is required to inline the translations in chucks sent to the browser. - -Add `qwikSpeakInline` Vite plugin in `vite.config.ts`: -```typescript -import { qwikSpeakInline } from 'qwik-speak/inline'; - -export default defineConfig(() => { - return { - plugins: [ - qwikCity(), - qwikVite(), - qwikSpeakInline({ - supportedLangs: ['en-US', 'it-IT'], - defaultLang: 'en-US', - assetsPath: 'i18n' - }), - tsconfigPaths(), - ], - }; -}); -``` -Set the base URL for loading the chunks in the browser in `entry.ssr.tsx` file: -```typescript -import { isDev } from '@builder.io/qwik/build'; - -/** - * Determine the base URL to use for loading the chunks in the browser. - * The value set through Qwik 'locale()' in 'plugin.ts' is saved by Qwik in 'serverData.locale' directly. - * Make sure the locale is among the 'supportedLocales' - */ -export function extractBase({ serverData }: RenderOptions): string { - if (!isDev && serverData?.locale) { - return '/build/' + serverData.locale; - } else { - return '/build'; - } -} - -export default function (opts: RenderToStreamOptions) { - return renderToStream(, { - manifest, - ...opts, - // Determine the base URL for the client code - base: extractBase, - // Use container attributes to set attributes on the html tag - containerAttributes: { - lang: opts.serverData?.locale || config.defaultLocale.lang, - ...opts.containerAttributes, - }, - }); -} -``` Build the production app in preview mode: ```shell npm run preview ``` -Inspect the `qwik-speak-inline.log` file in root folder: - -``` -client: root.tsx -dynamic key: t(head.title) - Make sure the keys are in 'runtimeAssets' -dynamic key: t(m.content) - Make sure the keys are in 'runtimeAssets' -``` -It contains the non-inlined dynamic keys that we added in the `runtime.json` file. - -> The app will have the same behavior as you saw in dev mode, but now the translations are inlined as you can verify by inspecting the production files, reducing resource usage at runtime -See [Qwik Speak Inline Vite plugin](./inline.md) for more details. +and inspect the `qwik-speak-inline.log` file in root folder to see warnings for missing values or dynamic keys. diff --git a/packages/qwik-speak/src/use-speak.ts b/packages/qwik-speak/src/use-speak.ts index 0d55d0f..03b0b05 100644 --- a/packages/qwik-speak/src/use-speak.ts +++ b/packages/qwik-speak/src/use-speak.ts @@ -22,7 +22,7 @@ export interface SpeakProps { } /** - * Add scoped translation data to the context. + * Add translation data to the context. * Translations will only be available in child components */ export const useSpeak = (props: SpeakProps) => { @@ -30,7 +30,7 @@ export const useSpeak = (props: SpeakProps) => { const { config } = ctx; - // Load scoped translations + // Load translations useTask$(async () => { if (isDev) { if (!props.assets && !props.runtimeAssets) logWarn('useSpeak: no assets provided'); From 818bec3b50fdb0f8c61f83a80b6f9eb4d92056da Mon Sep 17 00:00:00 2001 From: Roberto Simonetti Date: Sat, 25 Nov 2023 15:05:59 +0100 Subject: [PATCH 21/21] Update use-qwik-speak.tsx --- packages/qwik-speak/src/use-qwik-speak.tsx | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/packages/qwik-speak/src/use-qwik-speak.tsx b/packages/qwik-speak/src/use-qwik-speak.tsx index a37dc7b..1ff5410 100644 --- a/packages/qwik-speak/src/use-qwik-speak.tsx +++ b/packages/qwik-speak/src/use-qwik-speak.tsx @@ -1,4 +1,4 @@ -import { $, component$, getLocale, Slot, useContextProvider, useOnDocument, useTask$ } from '@builder.io/qwik'; +import { $, component$, getLocale, Slot, useContextProvider, useOnWindow, useTask$ } from '@builder.io/qwik'; import { isDev, isServer } from '@builder.io/qwik/build'; import type { SpeakConfig, SpeakLocale, SpeakState, TranslationFn } from './types'; @@ -89,7 +89,7 @@ export const useQwikSpeak = (props: QwikSpeakProps) => { } }); - // Resume shared context on client + // Resume Qwik Speak context on client const resumeContext$ = $(() => { const { locale, translation, config } = state; // Set Qwik Speak client context @@ -124,7 +124,8 @@ export const useQwikSpeak = (props: QwikSpeakProps) => { } }); - useOnDocument('DOMContentLoaded', resumeContext$); + // The load event is fired when the whole page has loaded + useOnWindow('load', resumeContext$); }; /**