Translations

Translations

ElmapiCMS supports linking content entries across different locales as translations of each other. This feature allows you to build multilingual websites where content in different languages is connected, making it easy to implement language switchers and maintain content relationships.

Overview

When you link entries as translations:

  • Entries share a translation_group_id that connects them
  • Each entry maintains its own locale, content, and status
  • You can retrieve a translation of any entry via the API

Linking Translations in the Admin Panel

To link entries as translations:

  1. Open any content entry in edit mode
  2. Click the Translations button (visible when your project has multiple locales)
  3. For each locale:
    • If no translation exists: Click Select to choose an existing entry
    • If a translation exists: Click the entry number link to navigate to it, or Unlink to remove the connection

Getting Translations via API

Endpoint

[GET]

/{collection_slug}/{entry_uuid}?translation_locale={locale}

Query Parameters

NameTypeRequiredDescription
translation_localestringNoGet the translation of this entry in the specified locale. Returns the linked translation entry instead of the original.

How It Works

  1. The API finds the entry by UUID
  2. Checks if the entry has a translation_group_id
  3. Searches for a linked entry with the requested locale
  4. Returns the translation entry if found

Example Requests

import { createClient } from '@elmapicms/js-sdk';
 
const client = createClient(
  'https://your-domain.com/api',
  'YOUR_API_TOKEN',
  'YOUR_PROJECT_UUID'
);
 
// Get French translation of the entry
const translation = await client.getEntry('blog-posts', 'e3a4b1c2-d5e6-f7g8-h9i0-j1k2l3m4n5o6', {
  translation_locale: 'fr'
});
const collectionSlug = 'blog-posts';
const entryUuid = 'e3a4b1c2-d5e6-f7g8-h9i0-j1k2l3m4n5o6';
const targetLocale = 'fr';
 
// Get French translation of the entry
axios.get(`https://your-domain.com/api/${collectionSlug}/${entryUuid}`, {
    params: {
        translation_locale: targetLocale
    },
    headers: {
        'Accept': 'application/json',
        'Authorization': 'Bearer YOUR_API_TOKEN', // Required for private APIs
        'project-id': 'YOUR_PROJECT_UUID'
    }
});
use Illuminate\Support\Facades\Http;
 
$collectionSlug = 'blog-posts';
$entryUuid = 'e3a4b1c2-d5e6-f7g8-h9i0-j1k2l3m4n5o6';
$targetLocale = 'fr';
 
$response = Http::withHeaders([
    'Accept' => 'application/json',
    'Authorization' => 'Bearer YOUR_API_TOKEN',
    'project-id' => 'YOUR_PROJECT_UUID'
])->get("https://your-domain.com/api/{$collectionSlug}/{$entryUuid}", [
    'translation_locale' => $targetLocale
]);
import React, { useState } from 'react';
 
function LanguageSwitcher({ currentEntryUuid, availableLocales, currentLocale }) {
  const [loading, setLoading] = useState(false);
 
  const switchLanguage = async (targetLocale) => {
    if (targetLocale === currentLocale) return;
    
    setLoading(true);
    try {
      const response = await fetch(
        `https://your-domain.com/api/blog-posts/${currentEntryUuid}?translation_locale=${targetLocale}`,
        {
          method: 'GET',
          headers: {
            'Accept': 'application/json',
            'Authorization': 'Bearer YOUR_API_TOKEN',
            'project-id': 'YOUR_PROJECT_UUID'
          }
        }
      );
 
      if (!response.ok) {
        throw new Error(`HTTP error! status: ${response.status}`);
      }
 
      const data = await response.json();
      // Navigate to the translated entry
      window.location.href = `/posts/${data.data.uuid}`;
    } catch (error) {
      console.error('Error fetching translation:', error);
      alert('Translation not available for this language');
    } finally {
      setLoading(false);
    }
  };
 
  return (
    <div className="language-switcher">
      {availableLocales.map(locale => (
        <button
          key={locale}
          onClick={() => switchLanguage(locale)}
          disabled={loading || locale === currentLocale}
          className={locale === currentLocale ? 'active' : ''}
        >
          {locale.toUpperCase()}
        </button>
      ))}
    </div>
  );
}
<template>
  <div class="language-switcher">
    <button
      v-for="locale in availableLocales"
      :key="locale"
      @click="switchLanguage(locale)"
      :disabled="loading || locale === currentLocale"
      :class="{ active: locale === currentLocale }"
    >
      {{ locale.toUpperCase() }}
    </button>
  </div>
</template>
 
<script setup>
import { ref } from 'vue';
 
const props = defineProps({
  currentEntryUuid: { type: String, required: true },
  availableLocales: { type: Array, required: true },
  currentLocale: { type: String, required: true }
});
 
const loading = ref(false);
 
const switchLanguage = async (targetLocale) => {
  if (targetLocale === props.currentLocale) return;
  
  loading.value = true;
  try {
    const response = await fetch(
      `https://your-domain.com/api/blog-posts/${props.currentEntryUuid}?translation_locale=${targetLocale}`,
      {
        method: 'GET',
        headers: {
          'Accept': 'application/json',
          'Authorization': 'Bearer YOUR_API_TOKEN',
          'project-id': 'YOUR_PROJECT_UUID'
        }
      }
    );
 
    if (!response.ok) {
      throw new Error(`HTTP error! status: ${response.status}`);
    }
 
    const data = await response.json();
    // Navigate to the translated entry
    window.location.href = `/posts/${data.data.uuid}`;
  } catch (error) {
    console.error('Error fetching translation:', error);
    alert('Translation not available for this language');
  } finally {
    loading.value = false;
  }
};
</script>
curl -X GET "https://your-domain.com/api/blog-posts/e3a4b1c2-d5e6-f7g8-h9i0-j1k2l3m4n5o6?translation_locale=fr" \
     -H "Accept: application/json" \
     -H "Authorization: Bearer YOUR_API_TOKEN" \
     -H "project-id: YOUR_PROJECT_UUID"

Responses

200: Success

Returns the translation entry in the requested locale.

{
    "data": {
        "uuid": "f4b5c2d3-e6f7-g8h9-i0j1-k2l3m4n5o6p7",
        "locale": "fr",
        "published_at": "2023-10-28T12:00:00Z",
        "fields": {
            "title": "Mon Premier Article de Blog",
            "slug": "mon-premier-article-de-blog",
            "content": "<p>Ceci est le contenu de l'article.</p>",
            "author": "Jean Dupont"
        }
    }
}

404: Translation Not Found

Returned if:

  • The entry has no translation group
  • No translation exists for the requested locale
{
    "message": "Translation not found for locale 'fr'."
}
{
    "message": "This entry has no translations linked."
}

Important Notes

  • State Filtering: The translation respects the same state filter (published/draft) as the original entry request
  • Same Structure: Translation entries return the same data structure as regular entries
  • UUID Navigation: Use the returned entry's UUID for routing/navigation in your frontend
  • Fallback Handling: Always handle 404 responses gracefully when translations are not available

Use Cases

Language Switcher

Implement a language switcher on your detail pages:

import { createClient } from '@elmapicms/js-sdk';
 
const client = createClient(
  'https://your-domain.com/api',
  'YOUR_API_TOKEN',
  'YOUR_PROJECT_UUID'
);
 
// When user clicks a language flag/button
async function switchLanguage(currentEntryUuid, targetLocale) {
  try {
    const translation = await client.getEntry('blog-posts', currentEntryUuid, {
      translation_locale: targetLocale
    });
    // Update your page with the translated content
    // Or navigate to: /posts/${translation.data.uuid}
    return translation;
  } catch (error) {
    // Handle gracefully - maybe show a message or keep current page
    console.log('Translation not available');
    return null;
  }
}
// When user clicks a language flag/button
async function switchLanguage(currentEntryUuid, targetLocale) {
  const response = await fetch(
    `https://your-domain.com/api/blog-posts/${currentEntryUuid}?translation_locale=${targetLocale}`,
    {
      headers: {
        'Accept': 'application/json',
        'Authorization': 'Bearer YOUR_API_TOKEN',
        'project-id': 'YOUR_PROJECT_UUID'
      }
    }
  );
  
  if (response.ok) {
    const translation = await response.json();
    // Update your page with the translated content
    // Or navigate to: /posts/${translation.data.uuid}
    return translation;
  } else {
    // Handle gracefully - maybe show a message or keep current page
    console.log('Translation not available');
    return null;
  }
}

Multilingual Navigation

Build navigation menus that show content in the user's selected language:

import { createClient } from '@elmapicms/js-sdk';
 
const client = createClient(
  'https://your-domain.com/api',
  'YOUR_API_TOKEN',
  'YOUR_PROJECT_UUID'
);
 
// Get all blog posts in French
const posts = await client.getEntries('blog-posts', {
  locale: 'fr'
});
 
// When user clicks a post, get its English translation if needed
const englishPost = await client.getEntry('blog-posts', post.uuid, {
  translation_locale: 'en'
});
// Get all blog posts in French
const posts = await fetch('https://your-domain.com/api/blog-posts?locale=fr', {
  headers: {
    'Accept': 'application/json',
    'Authorization': 'Bearer YOUR_API_TOKEN',
    'project-id': 'YOUR_PROJECT_UUID'
  }
});
 
// When user clicks a post, get its English translation if needed
const englishPost = await fetch(
  `https://your-domain.com/api/blog-posts/${post.uuid}?translation_locale=en`,
  {
    headers: {
      'Accept': 'application/json',
      'Authorization': 'Bearer YOUR_API_TOKEN',
      'project-id': 'YOUR_PROJECT_UUID'
    }
  }
);

Best Practices

  1. Always Check for Translations: Before showing a language switcher, verify that translations exist
  2. Handle Missing Translations: Provide a fallback (e.g., show original language or a message)
  3. Cache Translation UUIDs: Store translation UUIDs in your frontend state to avoid unnecessary API calls
  4. Use Consistent Locale Codes: Ensure your project locales match your frontend language codes

Search documentation

Find guides and reference pages