Stringhe condivise

Come internazionalizzare stringhe utilizzate in più componenti e file

Le stringhe condivise sono valori di testo utilizzati in più punti dell’applicazione, come etichette di navigazione, messaggi dei moduli o dati di configurazione. Invece di duplicare la logica di traduzione ovunque, usa msg per contrassegnare le stringhe da tradurre e useMessages per decodificarle.

Il problema dei contenuti condivisi

Considera questa configurazione di navigazione utilizzata in tutta l’app:

// navData.ts
export const navData = [
  {
    label: 'Home',
    description: 'La home page',
    href: '/'
  },
  {
    label: 'About', 
    description: 'Informazioni sull\'azienda',
    href: '/about'
  }
];

Per internazionalizzare questo, in genere dovresti:

  1. Convertirlo in una funzione che accetti una funzione di traduzione
  2. Aggiornare ogni punto d’uso per chiamare la funzione con t
  3. Gestire la complessità in tutto il codebase

Questo comporta oneri di manutenzione e rende il codice più difficile da leggere. La funzione msg risolve il problema permettendoti di contrassegnare le stringhe per la traduzione inline e poi decodificarle quando necessario.

Guida rapida

Usa msg per contrassegnare le stringhe e useMessages per decodificarle:

// navData.ts - Contrassegna le stringhe per la traduzione
import { msg } from 'gt-next';

export const navData = [
  {
    label: msg('Home'),
    description: msg('La pagina iniziale'), 
    href: '/'
  },
  {
    label: msg('Chi siamo'),
    description: msg('Informazioni sull\'azienda'),
    href: '/about'
  }
];
// Utilizzo del componente - Decodifica stringhe marcate
import { useMessages } from 'gt-next';
import { navData } from './navData';

function Navigation() {
  const m = useMessages();
  
  return (
    <nav>
      {navData.map((item) => (
        <a key={item.href} href={item.href} title={m(item.description)}>
          {m(item.label)}
        </a>
      ))}
    </nav>
  );
}

Come funzionano le stringhe condivise

Il sistema delle stringhe condivise opera in due fasi:

  1. Fase di marcatura: msg contrassegna le stringhe con metadati di traduzione
  2. Fase di decodifica: useMessages o getMessages decodificano e traducono le stringhe
// msg() codifica la stringa con i metadati
const encoded = msg('Hello, world!');
console.log(encoded); // "Hello, world!:eyIkX2hhc2giOiJkMjA3MDliZGExNjNlZmM2In0="

// useMessages() decodifica e traduce
const m = useMessages();
const translated = m(encoded); // "Hello, world!" nella lingua dell'utente

Le stringhe codificate da msg non possono essere utilizzate direttamente: devono essere decodificate con useMessages o getMessages.

Uso lato client vs lato server

Componenti client

Usa l’hook useMessages:

import { useMessages } from 'gt-next';

const encodedString = msg('Ciao, mondo!');

function MyComponent() {
  const m = useMessages();
  return <div>{m(encodedString)}</div>;
}

Server Components

Usa la funzione getMessages:

import { getMessages } from 'gt-next/server';

const encodedString = msg('Ciao, mondo!');

async function MyServerComponent() {
  const m = await getMessages();
  return <div>{m(encodedString)}</div>;
}

Recuperare le stringhe originali con decodeMsg

A volte è necessario accedere alla stringa originale senza traduzione, ad esempio per il logging, il debug o i confronti. Usa decodeMsg per estrarre il testo originale:

import { decodeMsg } from 'gt-next';

const encoded = msg('Hello, world!');
const original = decodeMsg(encoded); // "Hello, world!" (originale)
const translated = m(encoded); // "Hello, world!" (nella lingua dell'utente)

// Utile per il logging o il debugging
console.log('Stringa originale:', decodeMsg(encoded));
console.log('Stringa tradotta:', m(encoded));

Casi d’uso di decodeMsg

  • Sviluppo e debugging: registra le stringhe originali per il troubleshooting
  • Gestione dei fallback: usa il testo originale quando la traduzione non è disponibile
  • Confronto delle stringhe: confronta con valori originali noti
  • Analytics: monitora l’utilizzo delle stringhe originali
// Esempio: Gestione del fallback
function getDisplayText(encodedStr) {
  const m = useMessages();
  try {
    return m(encodedStr);
  } catch (error) {
    console.warn('Traduzione non riuscita, uso l\'originale:', decodeMsg(encodedStr));
    return decodeMsg(encodedStr);
  }
}

Uso delle variabili

Per le stringhe con contenuto dinamico, usa segnaposto e passa le variabili:

// Contrassegna stringa con variabili
const items = 100;
export const pricing = [
  {
    name: 'Basic',
    price: 100,
    description: msg('Il piano Basic include {items} elementi', { items })
  }
];
// Uso nel componente
function PricingCard() {
  const m = useMessages();
  
  return (
    <div>
      <h3>{pricing[0].name}</h3>
      <p>{m(pricing[0].description)}</p>
    </div>
  );
}

Formato dei messaggi ICU

Per formattazioni avanzate, usa la sintassi ICU:

const count = 10;
const message = msg('Nel carrello {count, plural, =0 {non ci sono articoli} =1 {c\'è un articolo} other {ci sono {count} articoli}}', { count });

Scopri di più sull’ICU Message Format nella documentazione di Unicode.

Esempi

Configurazione della navigazione

// config/navigation.ts
import { msg } from 'gt-next';

export const mainNav = [
  {
    label: msg('Home'),
    href: '/',
    icon: 'home'
  },
  {
    label: msg('Prodotti'),
    href: '/products', 
    icon: 'package'
  },
  {
    label: msg('Chi siamo'),
    href: '/about',
    icon: 'info'
  }
];

export const footerLinks = [
  {
    title: msg('Azienda'),
    links: [
      { label: msg('Chi siamo'), href: '/about' },
      { label: msg('Lavora con noi'), href: '/careers' },
      { label: msg('Contatti'), href: '/contact' }
    ]
  },
  {
    title: msg('Assistenza'), 
    links: [
      { label: msg('Centro assistenza'), href: '/help' },
      { label: msg('Documentazione'), href: '/docs' },
      { label: msg('Riferimenti API'), href: '/api' }
    ]
  }
];
// components/Navigation.tsx
import { useMessages } from 'gt-next';
import { mainNav } from '../config/navigation';

function Navigation() {
  const m = useMessages();
  
  return (
    <nav>
      {mainNav.map((item) => (
        <a key={item.href} href={item.href}>
          <Icon name={item.icon} />
          {m(item.label)}
        </a>
      ))}
    </nav>
  );
}

Configurazione del modulo

// config/forms.ts
import { msg } from 'gt-next';

export const formMessages = {
  placeholders: {
    email: msg('Inserisci il tuo indirizzo email'),
    password: msg('Inserisci la tua password'),
    message: msg('Scrivi qui il tuo messaggio...')
  },
  actions: {
    send: msg('Invia messaggio'),
    save: msg('Salva le modifiche'),
    cancel: msg('Annulla')
  },
  validation: {
    required: msg('Questo campo è obbligatorio'),
    email: msg('Inserisci un indirizzo email valido'),
    minLength: msg('Deve contenere almeno {min} caratteri', { min: 8 }),
    maxLength: msg('Non può superare {max} caratteri', { max: 100 })
  },
  success: {
    saved: msg('Modifiche salvate correttamente'),
    sent: msg('Messaggio inviato correttamente'),
    updated: msg('Profilo aggiornato')
  },
  errors: {
    network: msg('Errore di rete - riprova'),
    server: msg('Errore del server - contatta l\'assistenza'),
    timeout: msg('Tempo di richiesta scaduto - riprova')
  }
};
// components/ContactForm.tsx
import { useMessages } from 'gt-next';
import { formMessages } from '../config/forms';

function ContactForm() {
  const m = useMessages();
  const [errors, setErrors] = useState({});
  
  return (
    <form>
      <input 
        type="email"
        placeholder={m(formMessages.placeholders.email)}
        required
      />
      {errors.email && <span>{m(formMessages.validation.email)}</span>}
      
      <button type="submit">
        {m(formMessages.actions.send)}
      </button>
    </form>
  );
}

Generazione di contenuto dinamico

// utils/productData.ts
import { msg } from 'gt-next';

function mockProducts() {
  return [
    { name: 'iPhone 15', company: 'Apple', category: 'Elettronica' },
    { name: 'Galaxy S24', company: 'Samsung', category: 'Elettronica' }
  ];
}

export function getProductData() {
  const products = mockProducts();
  
  return products.map(product => ({
    ...product,
    description: msg('{name} è un prodotto di {category} di {company}', {
      name: product.name,
      category: product.category,
      company: product.company
    })
  }));
}
// components/ProductList.tsx
import { useMessages } from 'gt-next';
import { getProductData } from '../utils/productData';

function ProductList() {
  const m = useMessages();
  const products = getProductData();
  
  return (
    <div>
      {products.map(product => (
        <div key={product.name}>
          <h3>{product.name}</h3>
          <p>{m(product.description)}</p>
        </div>
      ))}
    </div>
  );
}

Problemi comuni

Utilizzo diretto di stringhe codificate

Non usare mai direttamente l’output di msg:

// ❌ Errato - stringa codificata utilizzata direttamente
const encoded = msg('Hello, world!');
return <div>{encoded}</div>; // Mostra la stringa codificata, non la traduzione

// ✅ Corretto - decodifica prima la stringa  
const encoded = msg('Hello, world!');
const m = useMessages();
return <div>{m(encoded)}</div>; // Mostra la traduzione appropriata

Contenuto dinamico in msg()

Le stringhe devono essere note in fase di build:

// ❌ Errato - template literal dinamico
const name = 'John';
const message = msg(`Hello, ${name}`); // Errore in fase di build

// ✅ Corretto - utilizza variabili  
const name = 'John';
const message = msg('Hello, {name}', { name });

Dimenticare di decodificare

Ogni stringa msg deve essere decodificata:

// ❌ Decodifica mancante
const config = {
  title: msg('Dashboard'),
  subtitle: msg('Bentornato')
};

// Più avanti nel componente: decodifica dimenticata
return <h1>{config.title}</h1>; // Mostra la stringa codificata

// ✅ Corretto: decodifica all'uso
const m = useMessages();
return <h1>{m(config.title)}</h1>; // Mostra il titolo tradotto

Prossimi passaggi

Come valuti questa guida?

Stringhe condivise