@oxy.so/core 1.1.2 → 1.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -76,7 +76,7 @@ export type { OxyConfig, PrivacySettings, NotificationPreferences, UserPreferenc
76
76
  export { SECURITY_EVENT_SEVERITY_MAP } from './models/interfaces';
77
77
  export { TopicType, TopicSource } from './models/Topic';
78
78
  export type { TopicData, TopicTranslation, TopicListResult } from './models/Topic';
79
- export { SUPPORTED_LANGUAGES, FALLBACK_LOCALE, getBaseLanguage, normalizeLocale, isSupportedLocale, getLanguageMetadata, getLanguageName, getNativeLanguageName, isRTLLocale, getUserLanguages, getPrimaryLanguage, } from './utils/languageUtils';
79
+ export { SUPPORTED_LANGUAGES, FALLBACK_LOCALE, getBaseLanguage, normalizeLocale, isSupportedLocale, getLanguageMetadata, getLanguageName, getNativeLanguageName, isRTLLocale, getUserLanguages, getPrimaryLanguage, coerceToSupportedLocale, } from './utils/languageUtils';
80
80
  export type { SupportedLanguage } from './utils/languageUtils';
81
81
  export { getPlatformOS, setPlatformOS, isWeb, isNative, isIOS, isAndroid, isWebBrowser, } from './utils/platform';
82
82
  export type { PlatformOS } from './utils/platform';
@@ -98,3 +98,16 @@ export declare function getUserLanguages(user: Pick<User, 'languages'> | null |
98
98
  * locale.
99
99
  */
100
100
  export declare function getPrimaryLanguage(user: Pick<User, 'languages'> | null | undefined): string | undefined;
101
+ /**
102
+ * Coerce a locale down to one from a CALLER-provided catalog: an exact
103
+ * canonical match wins, else the closest entry sharing the same base
104
+ * language (an `es-MX` account reads a host app's `es-ES` catalog), else
105
+ * `fallback`.
106
+ *
107
+ * A host app's own shipped translation catalog is rarely {@link SUPPORTED_LANGUAGES}
108
+ * itself — Oxy's account-locale catalog is broader than any one app's
109
+ * translated strings — so this is the one place that gap is bridged. Every Oxy
110
+ * app was independently re-deriving this exact algorithm against its own
111
+ * catalog before it moved here.
112
+ */
113
+ export declare function coerceToSupportedLocale(locale: string | null | undefined, supportedLocales: readonly string[], fallback: string): string;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@oxy.so/core",
3
- "version": "1.1.2",
3
+ "version": "1.2.1",
4
4
  "description": "OxyHQ SDK Foundation — API client, authentication, cryptographic identity, and shared utilities",
5
5
  "main": "dist/cjs/index.js",
6
6
  "module": "dist/esm/index.js",
package/src/index.ts CHANGED
@@ -428,6 +428,7 @@ export {
428
428
  isRTLLocale,
429
429
  getUserLanguages,
430
430
  getPrimaryLanguage,
431
+ coerceToSupportedLocale,
431
432
  } from './utils/languageUtils';
432
433
  export type { SupportedLanguage } from './utils/languageUtils';
433
434
 
@@ -9,6 +9,7 @@ import {
9
9
  isRTLLocale,
10
10
  getUserLanguages,
11
11
  getPrimaryLanguage,
12
+ coerceToSupportedLocale,
12
13
  } from '../languageUtils';
13
14
 
14
15
  describe('getBaseLanguage', () => {
@@ -187,6 +188,30 @@ describe('getPrimaryLanguage', () => {
187
188
  });
188
189
  });
189
190
 
191
+ describe('coerceToSupportedLocale', () => {
192
+ const hostCatalog = ['en-US', 'es-ES', 'fr-FR'] as const;
193
+
194
+ it('returns an exact canonical match from the host catalog', () => {
195
+ expect(coerceToSupportedLocale('es-es', hostCatalog, 'en-US')).toBe('es-ES');
196
+ });
197
+
198
+ it('falls back to the closest entry sharing the same base language', () => {
199
+ // `es-MX` is a real Oxy locale, but this host never shipped a Mexican
200
+ // Spanish catalog — its Spanish (Spain) one is the closest match.
201
+ expect(coerceToSupportedLocale('es-MX', hostCatalog, 'en-US')).toBe('es-ES');
202
+ });
203
+
204
+ it('falls back to the caller default when no base language matches', () => {
205
+ expect(coerceToSupportedLocale('ja-JP', hostCatalog, 'en-US')).toBe('en-US');
206
+ });
207
+
208
+ it('falls back to the caller default for empty or unresolved input', () => {
209
+ expect(coerceToSupportedLocale(undefined, hostCatalog, 'en-US')).toBe('en-US');
210
+ expect(coerceToSupportedLocale(null, hostCatalog, 'en-US')).toBe('en-US');
211
+ expect(coerceToSupportedLocale('', hostCatalog, 'en-US')).toBe('en-US');
212
+ });
213
+ });
214
+
190
215
  describe('SUPPORTED_LANGUAGES catalog integrity', () => {
191
216
  it('is non-empty', () => {
192
217
  expect(SUPPORTED_LANGUAGES.length).toBeGreaterThan(0);
@@ -254,3 +254,32 @@ export function getUserLanguages(user: Pick<User, 'languages'> | null | undefine
254
254
  export function getPrimaryLanguage(user: Pick<User, 'languages'> | null | undefined): string | undefined {
255
255
  return getUserLanguages(user)[0];
256
256
  }
257
+
258
+ /**
259
+ * Coerce a locale down to one from a CALLER-provided catalog: an exact
260
+ * canonical match wins, else the closest entry sharing the same base
261
+ * language (an `es-MX` account reads a host app's `es-ES` catalog), else
262
+ * `fallback`.
263
+ *
264
+ * A host app's own shipped translation catalog is rarely {@link SUPPORTED_LANGUAGES}
265
+ * itself — Oxy's account-locale catalog is broader than any one app's
266
+ * translated strings — so this is the one place that gap is bridged. Every Oxy
267
+ * app was independently re-deriving this exact algorithm against its own
268
+ * catalog before it moved here.
269
+ */
270
+ export function coerceToSupportedLocale(
271
+ locale: string | null | undefined,
272
+ supportedLocales: readonly string[],
273
+ fallback: string,
274
+ ): string {
275
+ if (locale) {
276
+ const canonical = normalizeLocale(locale);
277
+ if (canonical && supportedLocales.includes(canonical)) {
278
+ return canonical;
279
+ }
280
+ const base = getBaseLanguage(locale);
281
+ const byBase = supportedLocales.find((entry) => getBaseLanguage(entry) === base);
282
+ if (byBase) return byBase;
283
+ }
284
+ return fallback;
285
+ }