Autore:
    Creazione:2026-08-12Ultimo aggiornamento:2026-08-13

    Plugin ESLint x OXLint

    eslint-plugin-intlayer rileva i tipi di errori i18n che TypeScript non può individuare:

    1. Testo hardcoded che non è mai stato inserito in un dizionario.
    2. Chiamate dinamiche che superano il controllo dei tipi e vengono eseguite, ma che il compilatore Intlayer non può ottimizzare.
    3. Contenuto inutilizzato (dead content) — dizionari e campi che nessun elemento nel progetto legge (attivazione opzionale).

    Le chiavi di dizionario sconosciute, i percorsi di campo sconosciuti e le impostazioni internazionali mancanti sono già errori di compilazione, quindi il plugin non li ripete.

    Installazione

    bash
    npm install --save-dev eslint-plugin-intlayer
    

    Richiede ESLint 9 o versione successiva (flat config). ESLint 10 è supportato.

    Utilizzo

    Il plugin funziona sia in ESLint che in oxlint — stesse regole, stesse opzioni.

    eslint.config.mjs
    import intlayer from "eslint-plugin-intlayer";
    
    export default [...intlayer.configs.recommended];
    

    Oppure espandi una configurazione e imposta tu stesso le severità:

    eslint.config.mjs
    import intlayer from "eslint-plugin-intlayer";
    
    export default [
      ...intlayer.configs.recommended,
      {
        rules: {
          "intlayer/no-raw-text": "warn",
          "intlayer/static-dictionary-key": "error",
          "intlayer/no-dynamic-field-access": "error",
          "intlayer/enforce-adapter-import": "warn",
          "intlayer/no-unused-content": "warn",
        },
      },
    ];
    
    .oxlintrc.json
    {
      "jsPlugins": ["eslint-plugin-intlayer"],
      "rules": {
        "intlayer/no-raw-text": "warn",
        "intlayer/static-dictionary-key": "error",
        "intlayer/no-dynamic-field-access": "error",
        "intlayer/enforce-adapter-import": "warn"
      }
    }
    

    Due precisazioni: il supporto ai plugin JS in oxlint è ancora in versione alfa e oxlint non supporta parser personalizzati — quindi i file .vue, .svelte, .astro e i template Angular non vengono analizzati lì. Esegui oxlint sui tuoi file JS/TS/JSX e mantieni ESLint per il resto.

    no-unused-content è intenzionalmente esclusa sopra: necessita della directory di lavoro e del percorso del file analizzato dal contesto della regola, cosa che il bridge alfa del plugin JS non garantisce. Eseguila sotto ESLint.

    Configurazioni

    Configurazione no-raw-text static-dictionary-key no-dynamic-field-access enforce-adapter-import no-unused-content
    recommended warn error error off off
    strict error (+ letterali esterni a JSX) error error error off
    contract-only off error error off off

    recommended mantiene deliberatamente no-raw-text su warn: applicarla a una base di codice esistente fa emergere tutte le stringhe non tradotte contemporaneamente, il che non dovrebbe interrompere la build dal primo giorno.

    enforce-adapter-import è disabilitata per impostazione predefinita — attivala esplicitamente se lo desideri.

    no-unused-content è disattivata in ogni configurazione, inclusa strict. È l'unica regola che legge la configurazione di Intlayer ed esamina i file sorgente dal disco, pertanto la sua attivazione dovrebbe essere una scelta deliberata anziché un'impostazione predefinita.

    Regole

    no-raw-text

    Segnala il testo rivolto all'utente che non è dichiarato in un dizionario. Utilizza lo stesso rilevamento di intlayer extract, pertanto i nomi di brand, le classi CSS e gli identificatori tecnici vengono ignorati.

    jsx
    // ✗ Segnalato
    <h1>Welcome to our documentation</h1>
    <input placeholder="Enter your email address" />
    
    // ✓ Corretto
    const { title } = useIntlayer("home");
    <h1>{title}</h1>
    

    I file di dichiarazione del contenuto (*.content.ts, …) vengono ignorati.

    Per correggere un intero file in una volta, esegui npx intlayer extract e lascia che il compilatore sposti le stringhe in un dizionario al posto tuo.

    Opzioni

    eslint.config.mjs
    {
      "intlayer/no-raw-text": [
        "warn",
        {
          // Attributi il cui valore è testo rivolto all'utente.
          // Predefinito: title, placeholder, alt, aria-label, label
          attributes: ["title", "placeholder", "alt", "aria-label", "label"],
    
          // Elementi il cui contenuto non è mai testo rivolto all'utente.
          // Predefinito: code, pre, script, style
          ignoreElements: ["code", "pre", "script", "style"],
    
          // Espressioni regolari per il testo da non segnalare mai.
          ignorePatterns: ["^Powered by"],
    
          // Segnala anche i letterali di stringa fuori dal markup. Predefinito: false
          includeStringLiterals: false,
        },
      ],
    }
    

    static-dictionary-key

    Richiede che la chiave del dizionario sia un valore letterale stringa.

    Il compilatore può precaricare un dizionario solo quando può leggere la chiave direttamente nel punto di chiamata. Con una chiave calcolata, salta silenziosamente l'ottimizzazione e include invece tutti i dizionari nel bundle.

    typescript
    // ✗ Segnalato
    useIntlayer(dictionaryKey);
    useIntlayer(`home-${suffix}`);
    getTranslations({ namespace: page });
    
    // ✗ Una variabile non è un letterale
    const key = "home";
    useIntlayer(key);
    
    // ✓ Corretto
    useIntlayer("home");
    getTranslations({ namespace: "home" });
    

    Questo vale per useIntlayer, getIntlayer e tutti gli adattatori di compatibilità (useTranslation, useTranslations, formatMessage, <FormattedMessage id>, <Trans i18nKey>, …).

    no-dynamic-field-access

    Richiede che il campo letto da un dizionario sia noto staticamente.

    Il compilatore rimuove i campi che non vede utilizzati. Un accesso dinamico è invisibile per esso, quindi la lettura potrebbe restituire undefined a runtime.

    typescript
    // ✗ Segnalato
    const content = useIntlayer("home");
    content[fieldName];
    
    const t = useTranslations("home");
    t(messageKey);
    
    // ✓ Corretto
    content.title;
    content["title"];
    content.items[0];
    t("hero.title");
    

    enforce-adapter-import

    Preferisce l'adattatore di compatibilità @intlayer/* rispetto al pacchetto originale. Il pacchetto originale si risolve in Intlayer solo quando è configurato l'alias del bundler; l'adattatore lo fa sempre. Corregibile automaticamente con --fix.

    typescript
    // ✗ Segnalato
    import { useTranslation } from "react-i18next";
    import { getTranslations } from "next-intl/server";
    
    // ✓ Corretto
    import { useTranslation } from "@intlayer/react-i18next";
    import { getTranslations } from "@intlayer/next-intl/server";
    

    no-unused-content

    Disattivata per impostazione predefinita. Segnala i contenuti che nessun elemento nel progetto legge, oltre alle chiavi di dizionario dichiarate in più punti.

    src/home.content.ts
    export default {
      key: "home", // ✗ Segnalato se nessun chiamante nel progetto richiede "home"
      content: {
        title: t({ it: "Titolo", en: "Title" }),
    
        // ✗ Segnalato se nulla legge `hero`
        hero: {
          subtitle: t({ it: "Sottotitolo", en: "Subtitle" }),
        },
      },
    };
    

    A differenza delle altre regole, questa non può rispondere solo dal file analizzato: un campo è inutilizzato solo rispetto all'intero progetto. Alla prima dichiarazione di contenuto di un'esecuzione di lint, carica la configurazione di Intlayer, analizza i file sorgente dichiarati da tale configurazione (build.traversePattern, compiler.transformPattern) ed esegue lo stesso analizzatore di utilizzo che alimenta @intlayer/lsp e il testo barrato "inutilizzato" nell'estensione VS Code. Il risultato viene memorizzato nella cache per cacheTtl millisecondi, pertanto la scansione avviene una volta per esecuzione anziché una volta per file.

    Opzioni

    eslint.config.mjs
    {
      "intlayer/no-unused-content": [
        "warn",
        {
          // Segnala le chiavi di dizionario a cui nulla fa riferimento. Predefinito: true
          reportUnusedDictionaries: true,
    
          // Segnala i campi di contenuto che nulla legge. Predefinito: true
          reportUnusedFields: true,
    
          // Segnala le chiavi dichiarate in più posizioni. Predefinito: true
          reportDuplicateKeys: true,
    
          // Espressioni regolari per i percorsi di campo da non segnalare mai.
          ignoreFields: ["^meta"],
    
          // Directory radice del progetto da cui parte la scansione. Predefinito: directory di lavoro di ESLint
          baseDir: process.cwd(),
    
          // Durata del riutilizzo di una scansione del progetto, in ms. Predefinito: 30000
          cacheTtl: 30000,
        },
      ],
    }
    

    Riduci cacheTtl quando esegui il lint da un server dell'editor a lunga durata e desideri che le modifiche vengano riflesse prima; imposta baseDir quando una singola esecuzione di lint comprende diversi progetti Intlayer in un monorepo.

    Predilige il silenzio. Un falso positivo in questo caso eliminerebbe una traduzione, pertanto non viene segnalato nulla quando il dizionario viene utilizzato in un modo che l'analisi non può tracciare: l'oggetto contenuto passato nel suo insieme, una funzione di traduzione associata ad esso (const t = useTranslations("home")), una dichiarazione raggiunta tramite un'importazione diretta (useDictionary(myDictionary)), un nest() da un altro dizionario o un elenco di campi reso non esaustivo da uno spread. I componenti a file singolo (.vue, .svelte, .astro) contano come utilizzatori di ogni campo dei dizionari che menzionano, poiché i loro blocchi di script non vengono analizzati qui.

    reportDuplicateKeys legge i dizionari non uniti che la build scrive sotto .intlayer/, quindi rimane inattiva finché il progetto non è stato compilato almeno una volta. Due dichiarazioni che condividono una chiave vengono unite, il che è un modello valido: la segnalazione esiste perché un campo definito su entrambi i lati mantiene silenziosamente solo uno dei due valori.

    L'analizzatore viene caricato da @intlayer/lsp, distribuito come modulo ESM. La regola necessita pertanto di una versione di Node in grado di eseguire require() su un modulo ES — Node 20.19+ o 22.12+. Con versioni precedenti, non segnala nulla anziché interrompere l'esecuzione del lint.

    Frameworks

    Tutte le regole funzionano su tutte le integrazioni Intlayer, compresi i template Vue, Svelte e Angular. Devi solo indicare a ESLint quale parser legge ciascun tipo di file.

    Framework File Parser
    React, Preact, Solid, Lit .jsx .tsx typescript-eslint
    Next.js .jsx .tsx typescript-eslint
    Vue, Nuxt .vue vue-eslint-parser
    Svelte, SvelteKit .svelte svelte-eslint-parser
    Angular .ts typescript-eslint
    Template Angular .component.html @angular-eslint/template-parser
    Astro .astro astro-eslint-parser
    eslint.config.mjs
    import intlayer from "eslint-plugin-intlayer";
    import tseslint from "typescript-eslint";
    import vueParser from "vue-eslint-parser";
    import svelteParser from "svelte-eslint-parser";
    import angularTemplateParser from "@angular-eslint/template-parser";
    
    export default [
      ...intlayer.configs.recommended,
    
      {
        files: ["**/*.{ts,tsx,jsx}"],
        languageOptions: { parser: tseslint.parser },
      },
      {
        files: ["**/*.vue"],
        languageOptions: {
          parser: vueParser,
          parserOptions: { parser: tseslint.parser },
        },
      },
      {
        files: ["**/*.svelte"],
        languageOptions: {
          parser: svelteParser,
          parserOptions: { parser: tseslint.parser },
        },
      },
      {
        files: ["**/*.component.html"],
        languageOptions: { parser: angularTemplateParser },
      },
    ];
    

    Installa solo i parser di cui il tuo progetto ha bisogno.

    Limitazione nota. Nei template Vue e Angular, un'espressione come {{ content[key] }} non viene verificata da no-dynamic-field-access. Le letture dinamiche scritte nel blocco script vengono invece rilevate normalmente.