Skip to main content
Check out our example project at https://example.honyaku.dev This example project is a multilingual recipe discovery app called World Kitchen, built with Next.js and next-intl. It uses honyaku.dev to automatically translate the app into 100+ languages via a GitHub Actions workflow. You can find the full source code on GitHub.

Project Structure

honyaku-example/
├── .github/workflows/
│   └── translate.yml          # GitHub Actions workflow for auto-translation
├── app/
│   └── [locale]/
│       ├── layout.tsx         # Root layout with locale support
│       └── page.tsx           # Home page
├── components/
│   ├── Header.tsx             # Header with locale switcher
│   ├── LocaleSwitcher.tsx     # Language selector dropdown
│   └── RecipeCard.tsx         # Recipe display card
├── i18n/
│   ├── navigation.ts          # Locale-aware navigation utilities
│   ├── request.ts             # Server-side i18n config
│   └── routing.ts             # Locale routing definition
├── lib/
│   ├── honyaku.ts             # Honyaku message format converter
│   └── recipes.ts             # Recipe data
├── messages/
│   ├── en.json                # English source translations
│   └── generated/
│       └── *.json             # Auto-generated translations (100+ languages)
├── next.config.ts             # Next.js config with next-intl plugin
└── proxy.ts                   # next-intl middleware

Translation Source File

The source translation file uses the honyaku.dev format with text and description fields. The description provides context for the AI translator to produce more accurate translations.
messages/en.json
{
  "header_title": {
    "text": "World Kitchen",
    "description": "App name shown in the header. A recipe discovery app for world cuisines."
  },
  "header_subtitle": {
    "text": "Discover recipes from around the world",
    "description": "Tagline shown below the app name in the header."
  },
  "home_page_hero_title": {
    "text": "Explore Global Flavors",
    "description": "Main heading on the home page hero section."
  },
  "recipe_card_cooking_time": {
    "text": "{minutes} min",
    "description": "Cooking time label on a recipe card. {minutes} is replaced with the number of minutes."
  },
  ...
}

Honyaku Message Converter

Since next-intl expects simple string values, we need to convert the honyaku.dev format (with text and description fields) into plain strings. loadMessages extracts the text from each entry, and loadLocale handles loading the right file for each locale.
lib/honyaku.ts
import enMessages from "@/messages/en.json"

export function loadMessages(messages: Record<string, { text: string }>): Record<string, string> {
  return Object.fromEntries(Object.entries(messages).map(([key, value]) => [key, value.text]))
}

export async function loadLocale(locale: string) {
  if (locale === "en") {
    return loadMessages(enMessages)
  } else {
    return loadMessages((await import(`../messages/generated/${locale}.json`)).default)
  }
}

i18n Setup

Routing

Defines the supported locales from @honyaku-dev/locales (which provides metadata for 100+ languages) and sets English as the default.
i18n/routing.ts
import { defineRouting } from "next-intl/routing";
import locales from "@honyaku-dev/locales";

export const routing = defineRouting({
  locales: locales.map((locale) => locale.id),
  defaultLocale: "en",
});

Request Configuration

Loads the appropriate translation file for each request. English messages are always spread as a fallback, so any missing translations in other languages will gracefully fall back to English.
i18n/request.ts
import { getRequestConfig } from "next-intl/server";
import { hasLocale } from "next-intl";
import { routing } from "./routing";
import { loadLocale, loadMessages } from "@/lib/honyaku";
import en from "@/messages/en.json"

const enMessages = loadMessages(en);

export default getRequestConfig(async ({ requestLocale }) => {
  const requested = await requestLocale;
  const locale = hasLocale(routing.locales, requested)
    ? requested
    : routing.defaultLocale;

  const messages = await loadLocale(locale);
  return {
    locale,
    messages: {
      ...enMessages,
      ...messages,
    }
  };
});

GitHub Actions Workflow

This workflow automatically translates the source file into all supported languages whenever messages/en.json is updated on the main branch. The targets: "all:{id}.json" pattern generates a separate file for each language.
.github/workflows/translate.yml
name: Translate

on:
  workflow_dispatch:
  push:
    branches: [main]
    paths:
      - "messages/en.json"

permissions:
  contents: write

jobs:
  translate:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: honyaku-dev/honyaku-action@v1
        with:
          source-file: "messages/en.json"
          output-dir: "messages/generated"
          targets: "all:{id}.json"
          api-key: ${{ secrets.HONYAKU_API_KEY }}