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

metalsmith-multilingual

v0.1.1

Published

A Metalsmith plugin that adds internationalization metadata to files based on directory structure and frontmatter alternate links

Readme

metalsmith-multilingual

A Metalsmith plugin that adds internationalization metadata to files based on directory structure and frontmatter alternate links

metalsmith:plugin npm: version license: MIT coverage ESM Known Vulnerabilities AI-assisted development

This Metalsmith plugin is under active development. The API is stable, but breaking changes may occur before reaching 1.0.0.

Features

  • Directory-based locale detection - Detects each file's locale from its path (e.g. files under de/ are German)
  • hreflang generation - Builds <link rel="alternate" hreflang="..."> data from frontmatter cross-references
  • Global multilingual metadata - Exposes locale configuration via metalsmith.metadata() for templates
  • Configurable path patterns - Supports custom directory structures for locale detection
  • Configurable alternate key - Reads cross-language links from any frontmatter path via dot notation
  • Translated URL slugs - Works with fully translated paths (e.g. works/ vs werke/), not just locale prefixes
  • ESM module: import multilingual from 'metalsmith-multilingual'

Installation

npm install metalsmith-multilingual

Usage

Pass metalsmith-multilingual to metalsmith.use:

Basic Usage

import Metalsmith from 'metalsmith';
import multilingual from 'metalsmith-multilingual';

Metalsmith(import.meta.dirname)
  .use(multilingual())
  .build((err) => {
    if (err) throw err;
  });

With Custom Options

import Metalsmith from 'metalsmith';
import multilingual from 'metalsmith-multilingual';

Metalsmith(import.meta.dirname)
  .use(
    multilingual({
      defaultLocale: 'en',
      locales: ['en', 'de'],
      pathPattern: '{locale}/**',
      alternateKey: 'seo.alternate',
      localeLabels: { en: 'English', de: 'Deutsch' }
    })
  )
  .build((err) => {
    if (err) throw err;
  });

Options

| Option | Description | Type | Default | | --- | --- | --- | --- | | defaultLocale | Fallback locale for files not matching any locale path | String | 'en' | | locales | Array of supported locale codes | String[] | ['en', 'de'] | | pathPattern | Pattern for detecting locale from file path. Must contain {locale} | String | '{locale}/**' | | alternateKey | Dot-path to alternate links in frontmatter | String | 'seo.alternate' | | localeLabels | Map of locale codes to human-readable labels | Object | { en: 'English', de: 'Deutsch' } |

How It Works

The plugin detects each file's locale from its path and enriches it with multilingual metadata. It expects your content to be organized with locale-specific directories and frontmatter cross-references between language versions.

Content Structure

src/
  index.md                      # English (default locale)
  works/2026.03.002.md          # English artwork page
  studio-notes/first-piece.md   # English studio note
  de/
    index.md                    # German homepage
    werke/2026.03.002.md        # German artwork page
    studio-notizen/erstes-stueck.md  # German studio note

Files under de/ get locale: 'de'. All other files get the default locale 'en'. The plugin supports fully translated URL slugs — it does not require matching directory names across languages.

Frontmatter Cross-References

Each page links to its alternate language version via frontmatter:

# English page (src/works/2026.03.002.md)
seo:
  title: 'Wall sculpture with laminated cardboard'
  alternate:
    de: /de/werke/2026.03.002/

# German page (src/de/werke/2026.03.002.md)
seo:
  title: 'Wandskulptur mit laminierten Kartonstreifen'
  alternate:
    en: /works/2026.03.002/

The plugin reads these cross-references (via the configurable alternateKey) but does not create them — you maintain the links in your content.

Per-File Metadata

After the plugin runs, each file has:

| Property | Type | Description | | --- | --- | --- | | locale | String | The detected locale code (e.g. 'en' or 'de') | | isDefaultLocale | Boolean | true when the file's locale matches defaultLocale | | hreflang | Array | Array of { lang, url } objects for all language versions |

The hreflang array includes a self-referencing entry, entries for each alternate language from frontmatter, and an x-default entry pointing to the default locale URL. This data can be used directly in templates to render <link rel="alternate" hreflang="..."> tags.

Example hreflang output for the English homepage:

[
  { "lang": "en", "url": "/" },
  { "lang": "de", "url": "/de/" },
  { "lang": "x-default", "url": "/" }
]

Global Metadata

The plugin adds a multilingual object to metalsmith.metadata():

{
  "defaultLocale": "en",
  "locales": [
    { "code": "en", "label": "English", "isDefault": true },
    { "code": "de", "label": "Deutsch", "isDefault": false }
  ],
  "localeLabels": { "en": "English", "de": "Deutsch" }
}

This is available in templates for rendering language switchers, navigation filtering, and other locale-aware UI.

Examples

Rendering hreflang tags in a layout

Using the per-file hreflang array to emit <link rel="alternate"> tags in an HTML <head> (Nunjucks):

{% for entry in hreflang %}
  <link rel="alternate" hreflang="{{ entry.lang }}" href="https://example.com{{ entry.url }}">
{% endfor %}

Building a language switcher

Using the global multilingual metadata together with the per-file hreflang data (Nunjucks):

<nav aria-label="Language">
  <ul>
    {% for locale in multilingual.locales %}
      {% set target = hreflang | selectattr('lang', 'equalto', locale.code) | first %}
      <li>
        <a href="{{ target.url }}" {% if locale.code == page.locale %}aria-current="true"{% endif %}>
          {{ locale.label }}
        </a>
      </li>
    {% endfor %}
  </ul>
</nav>

Filtering a collection by locale

Combine with @metalsmith/collections to render per-locale index pages by filtering on the locale property added by this plugin:

import Metalsmith from 'metalsmith';
import collections from '@metalsmith/collections';
import multilingual from 'metalsmith-multilingual';

Metalsmith(import.meta.dirname)
  .use(multilingual({ locales: ['en', 'de'] }))
  .use(
    collections({
      worksEn: { pattern: 'works/*.md', refer: false },
      worksDe: { pattern: 'de/werke/*.md', refer: false }
    })
  )
  .build((err) => {
    if (err) throw err;
  });

Non-default locale prefix structure

If your English content lives under en/ instead of at the root, set defaultLocale accordingly — the plugin still detects each file from its path:

multilingual({
  defaultLocale: 'en',
  locales: ['en', 'de', 'fr'],
  pathPattern: '{locale}/**',
  localeLabels: { en: 'English', de: 'Deutsch', fr: 'Français' }
});

Test Coverage

This plugin is tested using Node's built-in test runner (node:test) with built-in V8 coverage.

Debug

To enable debug logs, set the DEBUG environment variable to metalsmith-multilingual*:

metalsmith.env('DEBUG', 'metalsmith-multilingual*');

Alternatively, you can set DEBUG to metalsmith:* to debug all Metalsmith plugins.

CLI Usage

To use this plugin with the Metalsmith CLI, add metalsmith-multilingual to the plugins key in your metalsmith.json file:

{
  "plugins": [
    {
      "metalsmith-multilingual": {
        "defaultLocale": "en",
        "locales": ["en", "de"],
        "alternateKey": "seo.alternate",
        "localeLabels": {
          "en": "English",
          "de": "Deutsch"
        }
      }
    }
  ]
}

License

MIT

Development transparency

Portions of this project were developed with the assistance of AI tools including Claude and Claude Code. These tools were used to:

  • Generate or refactor code
  • Assist with documentation
  • Troubleshoot bugs and explore alternative approaches

All AI-assisted code has been reviewed and tested to ensure it meets project standards. See the included CLAUDE.md for more details.