Catalog npm
PACKAGE EXPLORERView in registry

@0x30-ch/localized-field

npm

Strapi custom field for per-locale translations without full i18n localization

fieldi18nlocalelocalizationpluginstrapitranslation
Versions collected1
Direct dependencies12From release 1.0.0
Last observed20 Oct, 22:34 UTC

README

Current package documentation

Strapi Localized Field Plugin

A Strapi 5 custom field that stores per-locale translations as JSON — without requiring full i18n content localization. Perfect for fields like SEO titles, button labels, or any text that needs translations while the entry itself stays in a single locale.

Strapi License TypeScript Version

Features

  • Per-Locale Translations: Store translations for all locales in a single JSON field on a single entry
  • No Full i18n Required: Works alongside Strapi's i18n plugin without duplicating entire entries per locale
  • Compact Locale Tabs: Inline tab UI with status dots showing filled/empty state and a fill counter badge
  • Short & Long Text: Choose between single-line input or textarea per field
  • Configurable Fallback: Three strategies when a locale is missing — default locale, first available, or none
  • Automatic API Resolution: Middleware resolves translations in content API responses based on ?locale= query param
  • Populate All Locales: Request all translations via ?plugins[localized-field][populateLocalization]=true
  • Component & Dynamic Zone Support: Works inside components, repeatable components, and dynamic zones
  • Full i18n: All admin UI strings use react-intl for internationalization

Installation

npm install @0x30-ch/localized-field
# or
yarn add @0x30-ch/localized-field
# or
pnpm add @0x30-ch/localized-field

Configuration

1. Enable the Plugin

// config/plugins.ts
export default () => ({
  'localized-field': {
    enabled: true,
    resolve: './node_modules/@0x30-ch/localized-field',
  },
});

2. Add the Custom Field

In the Strapi Content-Type Builder, add the Localized string custom field to your content type. Configure:

OptionValuesDefaultDescription
Field typeShort text, Long textShort textSingle-line input or textarea
Default localeAny locale code(auto-detected)Override which locale tab opens first and is used for fallback
Fallback strategyDefault locale, First available, NoneDefault localeHow to resolve when the requested locale has no translation

3. Restart Your Server

After configuration, restart your Strapi server to apply the schema changes.

Usage

Admin Interface

  1. Open any entry with a localized field
  2. Switch locale by clicking the compact tab chips — each shows a status dot (filled = has content)
  3. Type your translation in the input field
  4. Track progress with the badge showing filled count (e.g., 2/4)
  5. Save the entry — all translations are stored as a single JSON value

Content API

The plugin registers a global Koa middleware that automatically resolves localized fields in content API responses.

Resolve a Single Locale

Pass ?locale= to get the resolved string value instead of the raw JSON object:

# Returns resolved string for French
GET /api/tags?locale=fr

Response:

{
  "data": [
    {
      "id": 1,
      "label": "Bonjour"
    }
  ]
}

Get All Translations

Add the populateLocalization plugin param to receive all locale values:

GET /api/tags?locale=fr&plugins[localized-field][populateLocalization]=true

Response:

{
  "data": [
    {
      "id": 1,
      "label": "Bonjour",
      "label_localizations": {
        "en": "Hello",
        "fr": "Bonjour",
        "de": "Hallo"
      }
    }
  ]
}

Without Locale Param

When no ?locale= is provided, the raw JSON object is returned as-is:

{
  "data": [
    {
      "id": 1,
      "label": {
        "en": "Hello",
        "fr": "Bonjour",
        "de": "Hallo"
      }
    }
  ]
}

Fallback Strategies

StrategyBehavior
Default localeFalls back to the configured default locale (or Strapi's i18n default)
First availableReturns the first non-empty translation found
NoneReturns null if the requested locale has no translation

Data Structure

The field stores a JSON object mapping locale codes to translation strings:

{
  "en": "Hello world",
  "fr": "Bonjour le monde",
  "de": "Hallo Welt"
}

This is stored in a single json column in the database — no additional tables or relations needed.

Development

Prerequisites

  • Node.js >= 18.0.0
  • Strapi 5.x
  • i18n plugin enabled (for locale detection)

Setup

git clone https://github.com/0x30-ch/strapi-plugins.git
cd strapi-plugins
pnpm install

# Start development
pnpm dev

# Build plugin
cd packages/localized-field
npm run build

# Type checking
npm run test:ts:front  # Frontend types
npm run test:ts:back   # Backend types

# Verify plugin structure
npm run verify

Project Structure

packages/localized-field/
├── admin/                        # Frontend (React + Strapi Design System v2)
│   ├── src/
│   │   ├── components/
│   │   │   ├── LocalizedInput.tsx       # Main input with locale tabs
│   │   │   └── LocalizedFieldIcon.tsx   # Field icon for content-type builder
│   │   ├── utils/
│   │   │   └── getTranslation.ts        # i18n helper
│   │   ├── translations/
│   │   │   └── en.json                  # English translations
│   │   ├── pluginId.ts                  # Plugin identifier
│   │   └── index.ts                     # Plugin registration + custom field config
│   └── tsconfig.json
├── server/                       # Backend logic
│   ├── src/
│   │   ├── register.ts                  # Custom field server registration
│   │   ├── bootstrap.ts                 # Global middleware for API resolution
│   │   ├── utils/
│   │   │   └── localized.ts             # Core logic (collect, resolve, fallback)
│   │   ├── config/                      # Plugin configuration
│   │   ├── controllers/                 # (empty — no admin routes needed)
│   │   ├── services/                    # (empty — logic in middleware)
│   │   └── routes/                      # (empty — no custom routes)
│   └── tsconfig.json
└── package.json

Architecture

How It Works

  1. Registration: The plugin registers a localizedString custom field of type json
  2. Admin UI: The LocalizedInput component fetches available locales from /i18n/locales and renders compact tab chips with an input per locale
  3. Storage: All translations are stored as a single JSON object in one database column
  4. API Resolution: A global Koa middleware intercepts content API responses, detects localized fields in the schema, and resolves them based on the ?locale= query parameter
  5. Deep Resolution: The middleware recursively walks components, repeatable components, and dynamic zones to resolve nested localized fields

Why Not Full i18n?

Strapi's built-in i18n duplicates the entire entry per locale. This plugin is for cases where:

  • You need translations for specific fields only (SEO, labels, slugs)
  • Your content structure is locale-independent (same images, same relations)
  • You want to manage all translations in one place, on one entry
  • You need a lightweight alternative without entry duplication

Contributing

  1. Fork the repository
  2. Create a feature branch (git checkout -b feature/amazing-feature)
  3. Commit your changes (git commit -m 'Add amazing feature')
  4. Push to the branch (git push origin feature/amazing-feature)
  5. Open a Pull Request

License

MIT License - see LICENSE file for details.

Acknowledgments

  • Strapi - Headless CMS framework
  • 0x30 - Plugin author

Support

Downloads & popularity

Awaiting observations

No metric observations yet

Download counts and popularity will appear here as the collector receives them.

Versions 1

Collected releases
VersionPublishedDirect dependenciesOpen version
1.0.0Registry latest9 Feb 202612

Package advisories

0
No matching advisories collected

This describes our collected data; it is not a security assessment.

Repository

PENDING
0x30-ch/strapi-plugins

Shared repository data · GitHub

License detected in repository
Not collected

Metadata history

Meaningful changes
Current collected state Current

Observed since 20 Oct, 22:34 UTC

No superseded metadata states collected. Unchanged observations do not create new revisions.

Record identity

package:babd385248ec0ea5f0e1f596ced2f2c1fb542c027623307066ecac1460881a95