Cadenas compartidas

Cómo internacionalizar cadenas utilizadas en varios componentes y archivos

Las cadenas compartidas son valores de texto que se usan en varios lugares de tu aplicación, como etiquetas de navegación, mensajes de formularios o datos de configuración. En lugar de duplicar la lógica de traducción en todas partes, usa msg para marcar las cadenas para traducción y useMessages para decodificarlas.

El problema del contenido compartido

Considera esta configuración de navegación utilizada en toda tu aplicación:

// navData.ts
export const navData = [
  {
    label: 'Inicio',
    description: 'La página de inicio',
    href: '/'
  },
  {
    label: 'Acerca de', 
    description: 'Información sobre la empresa',
    href: '/about'
  }
];

Para internacionalizar esto, normalmente tendrías que:

  1. Convertirlo en una función que reciba una función de traducción
  2. Actualizar cada uso para llamar a la función con t
  3. Gestionar la complejidad en todo el código

Esto genera sobrecarga de mantenimiento y hace que tu código sea más difícil de leer. La función msg lo soluciona al permitirte marcar las cadenas para traducción in situ y decodificarlas cuando sea necesario.

Inicio rápido

Usa msg para marcar las cadenas y useMessages para decodificarlas:

// navData.ts - Marcar cadenas para traducción
import { msg } from 'gt-next';

export const navData = [
  {
    label: msg('Inicio'),
    description: msg('La página principal'), 
    href: '/'
  },
  {
    label: msg('Acerca de'),
    description: msg('Información sobre la empresa'),
    href: '/about'
  }
];
// Uso del componente - Decodificar cadenas marcadas
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>
  );
}

Cómo funcionan las cadenas compartidas

El sistema de cadenas compartidas funciona en dos fases:

  1. Fase de marcado: msg codifica las cadenas con metadatos de traducción
  2. Fase de decodificación: useMessages o getMessages decodifican y traducen las cadenas
// msg() codifica la cadena con metadatos
const encoded = msg('Hello, world!');
console.log(encoded); // "Hello, world!:eyIkX2hhc2giOiJkMjA3MDliZGExNjNlZmM2In0="

// useMessages() decodifica y traduce
const m = useMessages();
const translated = m(encoded); // "Hello, world!" en el idioma del usuario

Las cadenas codificadas de msg no pueden usarse directamente; deben decodificarse con useMessages o getMessages.

Uso en el cliente vs. en el servidor

Componentes de cliente

Usa el hook useMessages:

import { useMessages } from 'gt-next';

const encodedString = msg('¡Hola, mundo!');

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

Componentes del servidor

Usa la función getMessages:

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

const encodedString = msg('¡Hola, mundo!');

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

Obtener cadenas originales con decodeMsg

A veces necesitas acceder a la cadena original sin traducir, por ejemplo, para registro, depuración o comparaciones. Usa decodeMsg para extraer el texto original:

import { decodeMsg } from 'gt-next';

const encoded = msg('¡Hola, mundo!');
const original = decodeMsg(encoded); // "¡Hola, mundo!" (original)
const translated = m(encoded); // "¡Hola, mundo!" (en el idioma del usuario)

// Útil para registro o depuración
console.log('Cadena original:', decodeMsg(encoded));
console.log('Cadena traducida:', m(encoded));

Casos de uso de decodeMsg

  • Desarrollo y depuración: Registrar cadenas originales para solucionar problemas
  • Manejo de contenido de respaldo predeterminado: Usar el texto original cuando fallen las traducciones
  • Comparación de cadenas: Comparar con valores originales conocidos
  • Analítica: Rastrear el uso de cadenas originales
// Ejemplo: Manejo de contenido de respaldo predeterminado
function getDisplayText(encodedStr) {
  const m = useMessages();
  try {
    return m(encodedStr);
  } catch (error) {
    console.warn('Traducción falló, usando original:', decodeMsg(encodedStr));
    return decodeMsg(encodedStr);
  }
}

Uso de variables

Para cadenas con contenido dinámico, utiliza marcadores de posición y pasa variables:

// Marcar cadena con variables
const items = 100;
export const pricing = [
  {
    name: 'Basic',
    price: 100,
    description: msg('El plan básico incluye {items} elementos', { items })
  }
];
// Usar en componente
function PricingCard() {
  const m = useMessages();
  
  return (
    <div>
      <h3>{pricing[0].name}</h3>
      <p>{m(pricing[0].description)}</p>
    </div>
  );
}

ICU message format

Para formatos avanzados, usa la sintaxis de ICU:

const count = 10;
const message = msg('Hay {count, plural, =0 {ningún artículo} =1 {un artículo} other {{count} artículos}} en el carrito', { count });

Obtén más información sobre ICU message format en la documentación de Unicode.

Ejemplos

Configuración de la navegación

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

export const mainNav = [
  {
    label: msg('Inicio'),
    href: '/',
    icon: 'home'
  },
  {
    label: msg('Productos'),
    href: '/products', 
    icon: 'package'
  },
  {
    label: msg('Acerca de nosotros'),
    href: '/about',
    icon: 'info'
  }
];

export const footerLinks = [
  {
    title: msg('Empresa'),
    links: [
      { label: msg('Acerca de'), href: '/about' },
      { label: msg('Empleos'), href: '/careers' },
      { label: msg('Contacto'), href: '/contact' }
    ]
  },
  {
    title: msg('Soporte'), 
    links: [
      { label: msg('Centro de ayuda'), href: '/help' },
      { label: msg('Documentación'), href: '/docs' },
      { label: msg('Referencia de 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>
  );
}

Configuración del formulario

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

export const formMessages = {
  placeholders: {
    email: msg('Ingresa tu dirección de correo electrónico'),
    password: msg('Ingresa tu contraseña'),
    message: msg('Escribe tu mensaje aquí...')
  },
  actions: {
    send: msg('Enviar mensaje'),
    save: msg('Guardar cambios'),
    cancel: msg('Cancelar')
  },
  validation: {
    required: msg('Este campo es obligatorio'),
    email: msg('Por favor, ingresa una dirección de correo electrónico válida'),
    minLength: msg('Debe tener al menos {min} caracteres', { min: 8 }),
    maxLength: msg('No puede exceder {max} caracteres', { max: 100 })
  },
  success: {
    saved: msg('Cambios guardados correctamente'),
    sent: msg('Mensaje enviado correctamente'),
    updated: msg('Perfil actualizado')
  },
  errors: {
    network: msg('Error de red: por favor, inténtalo de nuevo'),
    server: msg('Error del servidor: por favor, contacta con soporte'),
    timeout: msg('La solicitud ha caducado: por favor, inténtalo de nuevo')
  }
};
// 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>
  );
}

Generación de contenido dinámico

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

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

export function getProductData() {
  const products = mockProducts();
  
  return products.map(product => ({
    ...product,
    description: msg('{name} es un producto {category} de {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>
  );
}

Problemas habituales

Uso directo de cadenas codificadas

Nunca utilices directamente la salida de msg:

// ❌ Incorrecto - cadena codificada usada directamente
const encoded = msg('Hello, world!');
return <div>{encoded}</div>; // Muestra cadena codificada, no traducción

// ✅ Correcto - decodifica la cadena primero  
const encoded = msg('Hello, world!');
const m = useMessages();
return <div>{m(encoded)}</div>; // Muestra la traducción correcta

Contenido dinámico en msg()

Las cadenas deben conocerse en tiempo de compilación:

// ❌ Incorrecto - literal de plantilla dinámico
const name = 'John';
const message = msg(`Hello, ${name}`); // Error en tiempo de compilación

// ✅ Correcto - usar variables  
const name = 'John';
const message = msg('Hello, {name}', { name });

Olvidarse de decodificar

Cada cadena msg debe decodificarse:

// ❌ Falta la decodificación
const config = {
  title: msg('Dashboard'),
  subtitle: msg('Bienvenido de nuevo')
};

// Más tarde en el componente - se olvidó decodificar
return <h1>{config.title}</h1>; // Muestra cadena codificada

// ✅ Correcto - decodificar al usar
const m = useMessages();
return <h1>{m(config.title)}</h1>; // Muestra título traducido

Próximos pasos

¿Qué te ha parecido esta guía?

Cadenas compartidas