RocketChat/Rocket.Chat · warning · Meteor.Error

Moment locale not found: ${locale}

Error message

Moment locale not found: ${locale}

What it means

The `loadLocale` Meteor method returns the Moment.js locale source so clients can format dates. It calls `getMomentLocale(locale)`, which tries the server assets `moment-locales/<locale>.js`, then the primary subtag (`zh-cn` -> `zh`), then a small alias map (`ug`->`ug-cn`, `zh`->`zh-cn`); if none of those asset files exist it throws, and the method re-wraps it as `Moment locale not found: ${locale}` (code `moment-locale-not-found`). Moment locale filenames are lowercase and hyphenated, so codes like `zh_CN` (underscore) or unknown variants fail all three lookups.

Source

Thrown at apps/meteor/server/meteor-methods/platform/loadLocale.ts:21

import { Meteor } from 'meteor/meteor';

import { getMomentLocale } from '../../lib/getMomentLocale';

declare module '@rocket.chat/ddp-client' {
	// eslint-disable-next-line @typescript-eslint/naming-convention
	interface ServerMethods {
		loadLocale(locale: string): string | undefined;
	}
}

Meteor.methods<ServerMethods>({
	loadLocale(locale) {
		check(locale, String);

		try {
			return getMomentLocale(locale);
		} catch (error: any) {
			throw new Meteor.Error(error.message, `Moment locale not found: ${locale}`);
		}
	},
});

View on GitHub (pinned to b2c16d5842)

Solutions

  1. Normalize the code before calling: lowercase it and replace underscores with hyphens (`'zh_CN'.toLowerCase().replace('_', '-')` -> `zh-cn`)
  2. Trim to the primary subtag when the full tag is unavailable (`pt-br` -> `pt`), mirroring the server's own fallback order
  3. Map special cases the server maps (`zh` -> `zh-cn`, `ug` -> `ug-cn`) and check the locale exists client-side via `moment.locales()` before requesting
  4. On failure, retry once with the workspace default locale (e.g. `en`) so date formatting still works

Example fix

// before
Meteor.call('loadLocale', user.language, (err, src) => { ... });
// after
const normalize = (l) => l.toLowerCase().replace('_', '-');
const primary = (l) => l.split('-').shift();
Meteor.call('loadLocale', normalize(user.language), (err, src) => {
  if (err) return Meteor.call('loadLocale', primary(normalize(user.language)) || 'en', cb);
  useLocaleSource(src);
});
Defensive patterns

Strategy: validation

Validate before calling

const KNOWN = new Set(['en', 'pt', 'pt-br', 'zh', 'zh-cn', 'ug', 'ug-cn' /* ... */]);
const normalize = (l) => l.toLowerCase().replace('_', '-');
const resolveLocale = (l) => {
  const n = normalize(l);
  if (KNOWN.has(n)) return n;
  const primary = n.split('-').shift();
  if (KNOWN.has(primary)) return primary;
  return 'en';
};
Meteor.call('loadLocale', resolveLocale(user.language), cb);

Type guard

const isKnownMomentLocale = (locale: string): boolean =>
  typeof moment !== 'undefined' &&
  moment.locales().includes(locale.toLowerCase().replace('_', '-'));

Try / catch

Meteor.call('loadLocale', locale, (err, src) => {
  if (err) {
    // degrade gracefully: retry once with the default locale
    return Meteor.call('loadLocale', 'en', fallbackCb);
  }
  useLocaleSource(src);
});

Prevention

When it happens

Trigger: Calling `Meteor.call('loadLocale', locale)` with a code that has no matching moment-locales asset: `zh_CN` instead of `zh-cn`, a regional variant that is not bundled (`en-US` has no asset; only the fallback to `en` saves it if `en.js` exists), or a typo like `ptb`. Clients usually forward the user's saved `language` setting or `navigator.language` verbatim.

Common situations: Browser BCP47 tags (en-US, zh-Hant) passed unnormalized; a user language saved by an older Rocket.Chat whose moment bundle differed from the current server's `moment-locales` assets; locale strings with underscores or uppercase letters.

Related errors


AI-assisted analysis of RocketChat/Rocket.Chat@b2c16d5842 (2026-08-18). Data as JSON: /api/errors/d00e6c0b6117c66f. Report an issue: GitHub.