Qwik Speak

repository·main·Indexed 41 days ago

https://github.com/robisim74/qwik-speak

An internationalization (i18n) library for Qwik applications to translate text, dates, and numbers. It provides hooks like useFormatDate, useRelativeTime, and useFormatNumber using standard Intl APIs, along with a CLI tool (qwik-speak-extract) for extracting translation keys from components into JSON files.

Tokens
27.2K
Snippets
85
Records
101
Agent score
46%

What's inside Qwik Speak

  1. Understand SpeakLocale structure

    main

    A SpeakLocale object represents a specific language setting. It follows the format language[-script][-region].

    Core Fields:

    • language: ISO 639 two-letter or three-letter code.
    • script: ISO 15924 four-letter script code.
    • region: ISO 3166 two-letter, uppercase code.

    Optional Fields:

    • extension: Language with Intl extensions (e.g., en-US-u-ca-gregory-nu-latn).
    • currency: ISO 4217 three-letter code.
    • timeZone: IANA time zone database name.
    • units: Key-value pairs of unit identifiers.
    • dir: Text direction ('ltr' | 'rtl' | 'auto').
    • domain: Default domain for the locale in domain-based routing.
    • withDomain: Alternative domain for the locale in domain-based routing.
  2. How the Qwik Speak Inline Vite plugin works

    main

    The qwik-speak/inline plugin optimizes translation performance by shifting the translation work from runtime to compile-time for the client.

    • On the Server (SSR): Translations happen at runtime. Assets are loaded during SSR, and lookups occur dynamically.
    • On the Client: Translations happen at compile-time. Assets are loaded and inlined directly into the JavaScript chunks sent to the browser during the build process. This reduces runtime resource usage.
    • Runtime Assets: Assets designated as runtimeAssets are always loaded at runtime on both the server and the client, enabling dynamic translations that cannot be inlined at build time.
  3. Configure the Qwik Speak Inline Vite plugin

    main

    Add the qwikSpeakInline plugin to your vite.config.ts to enable inline translation support. You must specify supportedLangs, a defaultLang, and the assetsPath where your translation files are located.

    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(),
        ],
      };
    });
  4. Test translations in different languages using QwikSpeakProvider

    main

    If your test requirements involve verifying specific translations for different locales, use the standard QwikSpeakProvider instead of the mock provider.

    To make this work in a test environment, you must:

    1. Provide a translationFn (via the translationFn prop) to ensure translations are loaded.
    2. Provide a locale object specifying the language, currency, and time zone.

    This allows you to assert that the rendered output matches the expected string for that specific locale.

    test(`[Home Component]: Should render translated texts in Italian`, async () => {
      const { screen, render } = await createDOM();
    
      await render(
        <QwikSpeakProvider 
          config={config} 
          translationFn={translationFn} 
          locale={{ lang: 'it-IT', currency: 'EUR', timeZone: 'Europe/Rome' }}
        >
          <Home />
        </QwikSpeakProvider>
      );
    
      expect(screen.outerHTML).toContain('Qwik Speak dimostrazione');
    });
  5. Extract translations to JSON assets

    main

    You can extract all used translation keys into JSON files for production. This process generates files in your specified assetsPath (e.g., i18n/en-US/app.json).

    Note on Dynamic Keys: Keys that are constructed dynamically at runtime (e.g., `runtime.${key}`) will be skipped by the extractor. These must be manually added to your runtimeAssets files (e.g., i18n/[lang]/runtime.json).

    Command: Add a script to your package.json to run the extraction:

    # Example script entry
    "qwik-speak-extract": "qwik-speak-extract --supportedLangs=en-US,it-IT --assetsPath=i18n"

    Then run:

    npm run qwik-speak-extract
  6. Configure the Qwik Speak Inline Vite plugin

    main

    To use inline translations, add the qwikSpeakInline plugin to your vite.config.ts. This plugin inlines inlineTranslate and inlinePlural functions at compile time. Currently, only .json files are supported as the translation format.

    Configuration Options:

    • supportedLangs (Required): Array of supported language codes (e.g., ['en-US', 'it-IT']).
    • defaultLang (Required): The default language code.
    • basePath: The base path. Defaults to './'.
    • assetsPath: Path to translation files following the pattern [basePath]/[assetsPath]/[lang]/*.json. Defaults to 'i18n'.
    • outDir: The build output directory. Defaults to 'dist'.
    • loadAssets: An optional function to load assets by language (see Load assets from external sources).
    • autoKeys: Automatically handle keys for each string. Defaults to false.
    • keySeparator: Separator for nested keys. Defaults to '.'.
    • keyValueSeparator: Key-value separator. Defaults to '@@'.
    import { qwikSpeakInline } from 'qwik-speak/inline';
    import { defineConfig } from 'vite';
    
    export default defineConfig(() => {
      return {
        plugins: [
          qwikSpeakInline({
            basePath: './',
            assetsPath: 'i18n',
            supportedLangs: ['en-US', 'it-IT'],
            defaultLang: 'en-US'
          }),
        ],
      };
    });
  7. Resolve the Locale via Middleware

    main

    To automatically detect and set the locale on every request, implement an onRequest handler in a plugin.ts file within your src/routes directory. You must call the locale() function provided by Qwik City to set the active language.

    Common strategies include:

    • Parsing the accept-language header.
    • Reading from a cookie.
    • Reading from a URL parameter.
    import type { RequestHandler } from '@builder.io/qwik-city';
    import { config } from '../speak-config';
    
    export const onRequest: RequestHandler = ({ request, locale }) => {
      const acceptLanguage = request.headers?.get('accept-language');
    
      let lang: string | null = null;
    
      if (acceptLanguage) {
        lang = acceptLanguage.split(';')[0]?.split(',')[0];
      }
    
      lang = config.supportedLocales.find(value => value.lang === lang)?.lang || config.defaultLocale.lang;
    
      locale(lang);
    };
  8. Translate on the server with `server$`

    main

    Functions like routeLoader$ or endpoints run on the server and may not have access to the application's translation context. To translate in these environments, use the server$ function. You must ensure the server knows the current language, typically by passing the locale as a parameter from the client or extracting it from the request (cookies/params).

    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>);
    });
  9. Implement localized routing with Qwik City

    main

    To implement localized routing (e.g., / for default and /it-IT/ for other languages), use a catch-all route directory [...lang] in your src/routes folder.

    Use a plugin.ts file at the root of src/routes to handle the locale detection via onRequest. This middleware should validate the locale, set the Speak context, and set the Qwik locale. Avoid performing redirects or throwing errors inside this middleware; instead, handle those in layouts or pages to ensure the middleware remains performant as it runs on every request.

    import type { RequestHandler } from '@builder.io/qwik-city';
    import { setSpeakContext, validateLocale } from 'qwik-speak';
    import { config } from '../speak-config';
    
    export const onRequest: RequestHandler = ({ params, locale }) => {
      let lang: string | undefined = undefined;
    
      if (params.lang && validateLocale(params.lang)) {
        lang = config.supportedLocales.find(value => value.lang === params.lang)?.lang;
      } else {
        lang = config.defaultLocale.lang;
      }
    
      setSpeakContext(config);
      locale(lang);
    };
  10. Handle dynamic translations at runtime

    main

    If a translation key is determined at runtime (e.g., t(dynamicKey)), it cannot be evaluated at compile time. To support this, you must:

    1. Create a runtime.json file in your language directory (e.g., i18n/[lang]/runtime.json).
    2. Add the path to this file in the runtimeAssets configuration of your useSpeak provider.
    // i18n/[lang]/runtime.json
    {
      "dynamic": "I'm a dynamic value"
    }
  11. Automatically remove unused translation keys

    main

    To clean up your translation files by removing keys that are no longer used in your components, enable the --unusedKeys option. You must also provide a comma-separated list of --runtimeAssets to ensure that files used at runtime are not purged.

    Example CLI command:

    npm run qwik-speak-extract -- --supportedLangs=en-US,it-IT --unusedKeys=true --runtimeAssets=runtime