Project Structure

Understanding the .lingo/ directory and its contents.

Directory Overview

When you first run the compiler, it creates a .lingo/ directory in your project root:

your-project/
├── .lingo/
│   ├── metadata.json
│   ├── locale-resolver.server.ts (optional)
│   └── locale-resolver.client.ts (optional)
├── src/
├── package.json
└── ...

metadata.json

The core file containing all translation data.

Structure

{
  "version": "1",
  "sourceLocale": "en",
  "targetLocales": ["es", "de", "fr"],
  "translations": {
    "abc123def456": {
      "source": "Welcome to our app",
      "context": {
        "file": "app/page.tsx",
        "line": 12,
        "component": "HomePage"
      },
      "locales": {
        "es": "Bienvenido a nuestra aplicación",
        "de": "Willkommen in unserer App",
        "fr": "Bienvenue dans notre application"
      },
      "metadata": {
        "createdAt": "2024-01-15T10:30:00Z",
        "updatedAt": "2024-01-15T10:30:00Z"
      }
    }
  }
}

Key Fields

Version: Metadata format version. Current: "1"

Source/Target Locales: Your configured locales

Translations: Hash-based mapping of all translatable strings:

  • Hash (abc123def456): Stable identifier based on source text + context
  • source: Original English text
  • context: Where this text appears (file, line, component)
  • locales: Translations for each target locale
  • metadata: When translation was created/updated

Hash Generation

Hashes are deterministic:

  • Based on source text + component context
  • Same text in different locations = different hashes
  • Enables context-aware translations

Example:

// app/home/page.tsx
<button>Submit</button> // Hash: abc123

// app/checkout/page.tsx
<button>Submit</button> // Hash: def456 (different context)

Custom Locale Resolvers

Optional files for customizing locale detection and persistence.

locale-resolver.server.ts

Server-side locale detection (Next.js only):

// .lingo/locale-resolver.server.ts
export async function getServerLocale(): Promise<string> {
  // Your custom logic
  return "en";
}

locale-resolver.client.ts

Client-side locale detection and persistence:

// .lingo/locale-resolver.client.ts
export function getClientLocale(): string {
  // Detect locale
  return "en";
}

export function persistLocale(locale: string): void {
  // Save locale preference
}

If these files don't exist, the compiler uses default cookie-based behavior.

See Custom Locale Resolvers for details.

Version Control

Should you commit .lingo/?

Yes. The .lingo/ directory should be version-controlled:

Commit:

  • metadata.json — Contains all translations
  • Custom locale resolvers (if created)

Don't commit:

  • Nothing—all files in .lingo/ should be committed

Why Commit Translations?

  1. Version control — Track translation changes alongside code
  2. Team collaboration — Share translations across team
  3. CI/CD — Production builds use committed translations
  4. Audit trail — See when translations changed and why

Git Integration

Add .lingo/ to your repo:

git add .lingo/
git commit -m "chore: update translations"
git push

The compiler automatically updates .lingo/metadata.json when:

  • New translatable text is added
  • Existing text is modified
  • Translations are generated

Commit these updates regularly.

File Size

metadata.json grows with your app:

  • Small app (50 strings): ~10 KB
  • Medium app (500 strings): ~100 KB
  • Large app (5000 strings): ~1 MB

This is normal and acceptable for version control.

Cleaning Up

Remove Unused Translations

Over time, you may accumulate unused translations (from deleted components).

Manual cleanup:

  1. Search for hash in your codebase
  2. If not found, delete from metadata.json

Automated cleanup (coming soon):

npx @lingo.dev/compiler clean

This will remove unused translations automatically.

Reset Translations

To regenerate all translations from scratch:

# Backup current translations
cp .lingo/metadata.json .lingo/metadata.backup.json

# Delete metadata
rm .lingo/metadata.json

# Regenerate
npm run dev # or npm run build

Migration

From Old Compiler

The old compiler used different file structure:

Old:

lingo/
├── dictionary.js
├── meta.json
└── [locale]/
    └── *.json

New:

.lingo/
└── metadata.json

Migration is not automated. See Migration Guide for details.

Inspecting Translations

View in Editor

Open .lingo/metadata.json in your editor:

{
  "translations": {
    "abc123": {
      "source": "Welcome",
      "locales": {
        "es": "Bienvenido"
      }
    }
  }
}

Search for Translation

Find translation by source text:

grep -r "Welcome" .lingo/metadata.json

Find by Hash

grep -r "abc123" .lingo/metadata.json

Pretty Print

cat .lingo/metadata.json | jq '.'

Common Questions

Can I edit metadata.json manually? Yes, but not recommended. Use data-lingo-override instead—it's safer and version-controlled in source code.

What if I delete metadata.json? The compiler regenerates it on next build. All translations will be generated fresh (costs API credits).

Can I move .lingo/ to a different directory? Yes. Configure via lingoDir option:

{
  lingoDir: "translations"
}

Does metadata.json contain sensitive data? No. It only contains source text and translations—no API keys or secrets.

Can I merge metadata.json from multiple branches? Yes. Git handles merges automatically. Conflicts are rare (hashes are unique).

What if two branches add the same translation? Git merges them automatically. If hashes differ (different context), both are kept.

Next Steps