npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@0x30-ch/localized-field

v1.0.0

Published

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

Readme

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:

| Option | Values | Default | Description | |--------|--------|---------|-------------| | Field type | Short text, Long text | Short text | Single-line input or textarea | | Default locale | Any locale code | (auto-detected) | Override which locale tab opens first and is used for fallback | | Fallback strategy | Default locale, First available, None | Default locale | How 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

| Strategy | Behavior | |----------|----------| | Default locale | Falls back to the configured default locale (or Strapi's i18n default) | | First available | Returns the first non-empty translation found | | None | Returns 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