Shared Strings

How to internationalize strings used across multiple components and files

Shared strings are text values used in multiple places throughout your application - like navigation labels, form messages, or configuration data. Instead of duplicating translation logic everywhere, use msg to mark strings for translation and useMessages to decode them.

The Problem with Shared Content

Consider this navigation configuration used across your app:

// navData.ts
export const navData = [
  {
    label: 'Home',
    description: 'The home page',
    href: '/'
  },
  {
    label: 'About', 
    description: 'Information about the company',
    href: '/about'
  }
];

To internationalize this, you'd typically need to:

  1. Convert it to a function that accepts a translation function
  2. Update every usage to call the function with t
  3. Manage the complexity across your codebase

This creates maintenance overhead and makes your code harder to read. The msg function solves this by letting you mark strings for translation in-place, then decode them when needed.

Quick Start

Use msg to mark strings and useMessages to decode them:

// navData.ts - Mark strings for translation
import { msg } from 'gt-react';

export const navData = [
  {
    label: msg('Home'),
    description: msg('The home page'), 
    href: '/'
  },
  {
    label: msg('About'),
    description: msg('Information about the company'),
    href: '/about'
  }
];
// Component usage - Decode marked strings
import { useMessages } from 'gt-react';
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>
  );
}

How Shared Strings Work

The shared string system works in two phases:

  1. Mark Phase: msg encodes strings with translation metadata
  2. Decode Phase: useMessages decodes and translates the strings
// msg() encodes the string with metadata
const encoded = msg('Hello, world!');
console.log(encoded); // "Hello, world!:eyIkX2hhc2giOiJkMjA3MDliZGExNjNlZmM2In0="

// useMessages() decodes and translates
const m = useMessages();
const translated = m(encoded); // "Hello, world!" in user's language

Encoded strings from msg cannot be used directly - they must be decoded with useMessages.

Components

Use useMessages hook:

import { useMessages } from 'gt-react';

const encodedString = msg('Hello, world!');

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

Getting Original Strings with decodeMsg

Sometimes you need to access the original string without translation, such as for logging, debugging, or comparisons. Use decodeMsg to extract the original text:

import { decodeMsg } from 'gt-react';

const encoded = msg('Hello, world!');
const original = decodeMsg(encoded); // "Hello, world!" (original)
const translated = m(encoded); // "Hello, world!" (in user's language)

// Useful for logging or debugging
console.log('Original string:', decodeMsg(encoded));
console.log('Translated string:', m(encoded));

Use Cases for decodeMsg

  • Development & Debugging: Log original strings for troubleshooting
  • Fallback Handling: Use original text when translations fail
  • String Comparisons: Compare against known original values
  • Analytics: Track original string usage
// Example: Fallback handling
function getDisplayText(encodedStr) {
  const m = useMessages();
  try {
    return m(encodedStr);
  } catch (error) {
    console.warn('Translation failed, using original:', decodeMsg(encodedStr));
    return decodeMsg(encodedStr);
  }
}

Using Variables

For strings with dynamic content, use placeholders and pass variables:

// Mark string with variables
const items = 100;
export const pricing = [
  {
    name: 'Basic',
    price: 100,
    description: msg('The basic plan includes {items} items', { items })
  }
];
// Use in component
function PricingCard() {
  const m = useMessages();
  
  return (
    <div>
      <h3>{pricing[0].name}</h3>
      <p>{m(pricing[0].description)}</p>
    </div>
  );
}

ICU Message Format

For advanced formatting, use ICU syntax:

const count = 10;
const message = msg('There are {count, plural, =0 {no items} =1 {one item} other {{count} items}} in the cart', { count });

Learn more about ICU Message Format in the Unicode documentation.

Examples

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

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

export const footerLinks = [
  {
    title: msg('Company'),
    links: [
      { label: msg('About'), href: '/about' },
      { label: msg('Careers'), href: '/careers' },
      { label: msg('Contact'), href: '/contact' }
    ]
  },
  {
    title: msg('Support'), 
    links: [
      { label: msg('Help Center'), href: '/help' },
      { label: msg('Documentation'), href: '/docs' },
      { label: msg('API Reference'), href: '/api' }
    ]
  }
];
// components/Navigation.tsx
import { useMessages } from 'gt-react';
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>
  );
}

Form Configuration

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

export const formMessages = {
  placeholders: {
    email: msg('Enter your email address'),
    password: msg('Enter your password'),
    message: msg('Type your message here...')
  },
  actions: {
    send: msg('Send Message'),
    save: msg('Save Changes'),
    cancel: msg('Cancel')
  },
  validation: {
    required: msg('This field is required'),
    email: msg('Please enter a valid email address'),
    minLength: msg('Must be at least {min} characters', { min: 8 }),
    maxLength: msg('Cannot exceed {max} characters', { max: 100 })
  },
  success: {
    saved: msg('Changes saved successfully'),
    sent: msg('Message sent successfully'),
    updated: msg('Profile updated')
  },
  errors: {
    network: msg('Network error - please try again'),
    server: msg('Server error - please contact support'),
    timeout: msg('Request timed out - please try again')
  }
};
// components/ContactForm.tsx
import { useMessages } from 'gt-react';
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>
  );
}

Dynamic Content Generation

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

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

export function getProductData() {
  const products = mockProducts();
  
  return products.map(product => ({
    ...product,
    description: msg('{name} is a {category} product by {company}', {
      name: product.name,
      category: product.category,
      company: product.company
    })
  }));
}
// components/ProductList.tsx
import { useMessages } from 'gt-react';
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>
  );
}

Common Issues

Using Encoded Strings Directly

Never use the output of msg directly:

// ❌ Wrong - encoded string used directly
const encoded = msg('Hello, world!');
return <div>{encoded}</div>; // Shows encoded string, not translation

// ✅ Correct - decode the string first  
const encoded = msg('Hello, world!');
const m = useMessages();
return <div>{m(encoded)}</div>; // Shows proper translation

Dynamic Content in msg()

Strings must be known at build time:

// ❌ Wrong - dynamic template literal
const name = 'John';
const message = msg(`Hello, ${name}`); // Build time error

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

Forgetting to Decode

Every msg string needs to be decoded:

// ❌ Missing decoding
const config = {
  title: msg('Dashboard'),
  subtitle: msg('Welcome back')
};

// Later in component - forgot to decode
return <h1>{config.title}</h1>; // Shows encoded string

// ✅ Correct - decode when using
const m = useMessages();
return <h1>{m(config.title)}</h1>; // Shows translated title

Next Steps

How is this guide?

Shared Strings