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

@servicenow/eslint-plugin-aiux-i18n

v0.2.0

Published

ESLint rules for i18n extraction coverage in AIUX apps. Pairs with `aiux dev --pseudo` (runtime visual) to catch translation gaps at build time.

Downloads

226

Readme

@servicenow/eslint-plugin-aiux-i18n

ESLint rules for catching i18n extraction gaps at build time in AIUX apps. Pairs with aiux dev --pseudo (the runtime visual signal): same gaps, two different surfaces.

Why

The AST extractor in core/sdk/src/utils/swc-decorators.js only captures string-literal arguments to i18n.getMessage(). Calls with variable arguments (getMessage(item.label)) and strings hardcoded outside getMessage() will ship as English in every locale — they never reach sys_ui_message, so non-English builds fall back to the source key.

These rules catch those patterns at lint time.

Install

pnpm add -D @servicenow/eslint-plugin-aiux-i18n

Usage

Flat config:

import aiuxI18n from '@servicenow/eslint-plugin-aiux-i18n';

export default [
  ...aiuxI18n.flatConfigs.recommended,
  // or individual rules:
  {
    plugins: {'@servicenow/aiux-i18n': aiuxI18n},
    rules: {
      '@servicenow/aiux-i18n/getmessage-no-variable-arg': 'error',
      '@servicenow/aiux-i18n/no-hardcoded-html-text': 'warn',
      '@servicenow/aiux-i18n/no-hardcoded-translatable-attrs': 'warn'
    }
  }
];

Rules

getmessage-no-variable-arg

Flags i18n.getMessage(<non-literal>) and i18n.mark(<non-literal>) calls. The AST extractor only sees the literal string at the call site; variable args mean the literal value is never captured and never ships to translation.

// ✗ extractor can't see 'Manage Pods'
const label = i18n.getMessage(item.label);

// ✓ extractor captures the literal
const label = i18n.getMessage('Manage Pods');

// ✓ mark in module-scope config + getMessage in render
const NAV = [{label: i18n.mark('Manage Pods'), path: '/manage-pods'}];
// later in render:
const label = i18n.getMessage(item.label); // still flagged — see "Convention" below

Convention to make config-driven UIs translatable

If your config arrays put labels in object literals and your render code does getMessage(item.label), the extractor never sees the literal. Three ways out:

  1. i18n.mark in the config + i18n.getMessage in render (recommended): the AST extractor recognises both, and mark is a no-op at runtime — render-time getMessage(item.label) does the actual lookup with the current locale.
  2. Getter functions: {label: () => i18n.getMessage('Manage Pods')}, then item.label() in render.
  3. Inline templates: skip the data-driven config and write ${i18n.getMessage('Manage Pods')} directly.

The lint rule flags the variable-arg case for both getMessage and mark. To allow getMessage(item.label), use the mark pattern: the extractor catches the literal at module load via mark, and the variable-arg getMessage in render does the actual lookup. Both happen — the lint rule flags getMessage(item.label) to prompt that conversation; opt out per-line with // eslint-disable-next-line when the mark pattern is wired up.

no-hardcoded-html-text

Flags user-visible text directly inside Lit html`...` templates.

// ✗
return html`<button>Save changes</button>`;

// ✓
return html`<button>${i18n.getMessage('Save changes')}</button>`;

// ✓ explicit opt-out (intentional static text)
return html`<span data-i18n-skip>ServiceNow</span>`;

Skipped by default: text inside <script>, <style>, <code>, <pre>, <noscript>, <svg> and its children; pure numeric / punctuation text; single characters.

no-hardcoded-translatable-attrs

Flags string-literal values for aria-label, aria-description, placeholder, title, alt, label, and other screen-reader-visible attributes.

// ✗
return html`<button aria-label="Close dialog">×</button>`;

// ✓
return html`<button aria-label=${i18n.getMessage('Close dialog')}>×</button>`;

// ✓ opt-out
return html`<input placeholder="ServiceNow" data-i18n-skip />`;

Configurable extra attributes:

{
  '@servicenow/aiux-i18n/no-hardcoded-translatable-attrs': [
    'warn',
    {additionalAttributes: ['data-label', 'data-tooltip']}
  ];
}

Pairing with aiux dev --pseudo

This plugin catches issues at lint time; aiux dev --pseudo shows them at runtime — strings the extractor caught render in accented Latin (「Šàṽé____」), strings it missed render in plain ASCII. The two surfaces complement each other: use the linter to enforce the contract, use pseudo-loc to spot-check coverage in the browser.