robisim74/qwik-speak

Issue with Folder Nesting in vite-plugin-qwik-speak-inline Plugin

Closed this issue · 3 comments

Dear qwik-speak developer,

Allow me to start by thanking you for the effort and dedication put into creating this exceptional plugin, whose contribution has been immense.

I am reaching out to you because I have experienced a problem when trying to execute the "build" or "preview" functions of QwikJS. The issue lies in an error that arises with vite-plugin-qwik-speak-inline. Specifically, the error message reflects: "ENOENT: no such file or directory, scandir '/src/translations/en'".

This error does not appear in the "dev" development mode of QwikJS, which has led me to carry out a more detailed diagnosis. I found that the root of the problem is that the qwik-speak's vite-plugin-qwik-speak-inline plugin does not recognize ".json" files located in subfolders. An example of this is "src/translates/en/contact/contact.json". Despite it working correctly in the "dev" mode of QwikJS.

In other words, the vite-plugin-qwik-speak-inline plugin does not support nesting of folders, which, from my perspective, would be a substantial improvement. This capability would allow for a more efficient organization of the folder and file structure. Without it, we are forced to host all JSON files within a single folder, which can become confusing, especially in medium to large sized applications.

I would greatly appreciate it if you could provide me with some guidance on how to tackle this issue. Attached below is a code snippet that reflects the current configuration of qwik-speak in my application.

Once again, thank you for your commitment to creating this useful plugin. I am looking forward to your response.

import { server$ } from '@builder.io/qwik-city';

import { configuration } from '~/configuration';

import type { SpeakConfig, Translation, TranslationFn } from 'qwik-speak';

const translationFiles = import.meta.glob<Translation>(
  '/src/translations/**/*.json'
);

function getAssets(): string[] {
  const assets: string[] = [];

  for (const path of Object.keys(translationFiles)) {
    let finalPath = path;

    for (const language of configuration.i18n.language.available) {
      finalPath = finalPath.replace(`/src/translations/${language}`, '');
    }

    if (!finalPath.startsWith('/')) {
      finalPath = `/${finalPath}`;
    }

    if (!assets.includes(finalPath) && !finalPath.endsWith('runtime.json')) {
      assets.push(finalPath);
    }
  }

  return assets;
}

export const speakConfig: SpeakConfig = {
  assets: getAssets(),
  defaultLocale: {
    lang: configuration.i18n.language.default,
  },
  runtimeAssets: ['/runtime/runtime.json'],
  supportedLocales: configuration.i18n.language.available.map((language) => ({
    lang: language,
  })),
};

export const translationFunction: TranslationFn = {
  loadTranslation$: server$(async (language: string, asset: string) => {
    return translationFiles[`/src/translations/${language}${asset}`]();
  }),
};

Hi @devcaeg,

assets and runtimeAssets contain only names, not paths, as indicated in the documentation and examples of this project.

In dev mode paths as assets work because import.meta.glob is recursive.

It would not be a problem to modify the Inline plugin to read the files recursively as well: but this would create an inconsistency with the Extract command, which uses the depth 0 property as filename when generating.

From the code you posted, I notice that all the files are passed to the initial configuration: different files should be passed to different pages, using the Speak component for better performance.

I suggest to use unique names (and in camelCase) for files, and pass them to individual pages: so you can use both the Inline plugin and the Extract command, and get better performance.

I deeply appreciate your prompt response.

Regarding my practice of making all translations available on each page, I want to point out that I adopt this strategy because it does not cause performance issues during the development phase. In the production stage, I plan to use the "inline" plugin, which will embed the texts directly into the code, optimizing its operation.

As for the "extract" command, I understand that its function is to store all ".json" files in a specific directory. However, it is not clear to me why there would be a problem if the "inline" plugin supported the nesting folder structure. From my technical point of view, if it doesn't find any more directories, it should simply get the files from the last one found. Although I'm sure there must be a conflict reason, given that you, as the creator of the system, have a deeper understanding of its internal workings.

Lastly, I was wondering if there could be an option in the "inline" plugin that allows for the nesting of folders. This could provide an alternative solution to the situation.

I can't say in the documentation: you can use a path as asset name, and then make only the Inline plugin work, and not the Extract command.

So for now the rule remains the same: assets and runtimeAssets must contain the filename, not the path, as indicated in the documentation and examples.