@happyvertical/smrt-languages 0.37.4 → 0.37.5

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.
package/dist/index.js CHANGED
@@ -3,7 +3,7 @@ import { ObjectRegistry } from "@happyvertical/smrt-core";
3
3
  import { getPackageConfig } from "@happyvertical/smrt-config";
4
4
  import { getTenantId } from "@happyvertical/smrt-tenancy";
5
5
  //#region src/__smrt-register__.ts
6
- ObjectRegistry.registerPackageManifest(new URL("./manifest.json", "" + import.meta.url));
6
+ ObjectRegistry.registerPackageManifest(JSON.parse("{\"version\":\"1.0.0\",\"timestamp\":1782953446280,\"packageName\":\"@happyvertical/smrt-languages\",\"packageVersion\":\"0.37.5\",\"objects\":{\"@happyvertical/smrt-languages:LanguageTranslationTask\":{\"name\":\"languagetranslationtask\",\"className\":\"LanguageTranslationTask\",\"qualifiedName\":\"@happyvertical/smrt-languages:LanguageTranslationTask\",\"collection\":\"languagetranslationtasks\",\"filePath\":\"/home/runner/_work/smrt/smrt/packages/languages/src/translation-job.ts\",\"packageName\":\"@happyvertical/smrt-languages\",\"fields\":{},\"methods\":{\"execute\":{\"name\":\"execute\",\"async\":true,\"parameters\":[{\"name\":\"args\",\"type\":\"TranslationJobPayload\",\"optional\":false}],\"returnType\":\"Promise<object>\",\"isStatic\":false,\"isPublic\":true}},\"decoratorConfig\":{\"api\":{\"include\":[]},\"cli\":{\"include\":[]},\"mcp\":{\"include\":[]}},\"extends\":\"SmrtObject\",\"exportName\":\"LanguageTranslationTask\",\"collectionExportName\":\"LanguageTranslationTaskCollection\",\"schema\":{\"tableName\":\"language_translation_tasks\",\"ddl\":\"CREATE TABLE IF NOT EXISTS \\\"language_translation_tasks\\\" (\\n \\\"id\\\" UUID PRIMARY KEY NOT NULL,\\n \\\"slug\\\" TEXT NOT NULL,\\n \\\"context\\\" TEXT NOT NULL DEFAULT '',\\n \\\"created_at\\\" TIMESTAMP NOT NULL DEFAULT current_timestamp,\\n \\\"updated_at\\\" TIMESTAMP NOT NULL DEFAULT current_timestamp\\n);\",\"columns\":{\"id\":{\"type\":\"UUID\",\"primaryKey\":true,\"referenceKind\":\"id\",\"notNull\":true},\"slug\":{\"type\":\"TEXT\",\"notNull\":true},\"context\":{\"type\":\"TEXT\",\"notNull\":true,\"default\":\"\"},\"created_at\":{\"type\":\"TIMESTAMP\",\"notNull\":true,\"default\":\"current_timestamp\"},\"updated_at\":{\"type\":\"TIMESTAMP\",\"notNull\":true,\"default\":\"current_timestamp\"}},\"indexes\":[{\"name\":\"language_translation_tasks_id_idx\",\"columns\":[\"id\"]},{\"name\":\"language_translation_tasks_slug_context_idx\",\"columns\":[\"slug\",\"context\"],\"unique\":true}],\"version\":\"2cfc8812\"}},\"@happyvertical/smrt-languages:LanguageOverrideCollection\":{\"name\":\"languageoverridecollection\",\"className\":\"LanguageOverrideCollection\",\"qualifiedName\":\"@happyvertical/smrt-languages:LanguageOverrideCollection\",\"collection\":\"languageoverrides\",\"filePath\":\"/home/runner/_work/smrt/smrt/packages/languages/src/collections/LanguageOverrideCollection.ts\",\"packageName\":\"@happyvertical/smrt-languages\",\"fields\":{},\"methods\":{\"getAppOverride\":{\"name\":\"getAppOverride\",\"async\":true,\"parameters\":[{\"name\":\"key\",\"type\":\"string\",\"optional\":false},{\"name\":\"locale\",\"type\":\"string\",\"optional\":false}],\"returnType\":\"Promise<LanguageOverride | null>\",\"isStatic\":false,\"isPublic\":true},\"getTenantOverride\":{\"name\":\"getTenantOverride\",\"async\":true,\"parameters\":[{\"name\":\"key\",\"type\":\"string\",\"optional\":false},{\"name\":\"locale\",\"type\":\"string\",\"optional\":false},{\"name\":\"tenantId\",\"type\":\"string\",\"optional\":false}],\"returnType\":\"Promise<LanguageOverride | null>\",\"isStatic\":false,\"isPublic\":true},\"getResolutionLayers\":{\"name\":\"getResolutionLayers\",\"async\":true,\"parameters\":[{\"name\":\"key\",\"type\":\"string\",\"optional\":false},{\"name\":\"locale\",\"type\":\"string\",\"optional\":false},{\"name\":\"tenantId\",\"type\":\"string | null\",\"optional\":true}],\"returnType\":\"Promise<object>\",\"isStatic\":false,\"isPublic\":true},\"listTenantOverrides\":{\"name\":\"listTenantOverrides\",\"async\":true,\"parameters\":[{\"name\":\"tenantId\",\"type\":\"string\",\"optional\":false}],\"returnType\":\"Promise<LanguageOverride[]>\",\"isStatic\":false,\"isPublic\":true},\"listUnreviewedAutoTranslations\":{\"name\":\"listUnreviewedAutoTranslations\",\"async\":true,\"parameters\":[{\"name\":\"options\",\"type\":\"object\",\"optional\":true}],\"returnType\":\"Promise<LanguageOverride[]>\",\"isStatic\":false,\"isPublic\":true}},\"decoratorConfig\":{\"tableName\":\"_smrt_language_overrides\"},\"extends\":\"SmrtCollection\",\"extendsTypeArg\":\"LanguageOverride\",\"exportName\":\"LanguageOverrideCollection\",\"collectionExportName\":\"LanguageOverrideCollectionCollection\",\"schema\":{\"tableName\":\"_smrt_language_overrides\",\"ddl\":\"CREATE TABLE IF NOT EXISTS \\\"_smrt_language_overrides\\\" (\\n \\\"id\\\" UUID PRIMARY KEY NOT NULL,\\n \\\"slug\\\" TEXT NOT NULL,\\n \\\"context\\\" TEXT NOT NULL DEFAULT '',\\n \\\"created_at\\\" TIMESTAMP NOT NULL DEFAULT current_timestamp,\\n \\\"updated_at\\\" TIMESTAMP NOT NULL DEFAULT current_timestamp\\n);\",\"columns\":{\"id\":{\"type\":\"UUID\",\"primaryKey\":true,\"referenceKind\":\"id\",\"notNull\":true},\"slug\":{\"type\":\"TEXT\",\"notNull\":true},\"context\":{\"type\":\"TEXT\",\"notNull\":true,\"default\":\"\"},\"created_at\":{\"type\":\"TIMESTAMP\",\"notNull\":true,\"default\":\"current_timestamp\"},\"updated_at\":{\"type\":\"TIMESTAMP\",\"notNull\":true,\"default\":\"current_timestamp\"}},\"indexes\":[{\"name\":\"_smrt_language_overrides_id_idx\",\"columns\":[\"id\"]},{\"name\":\"_smrt_language_overrides_slug_context_idx\",\"columns\":[\"slug\",\"context\"],\"unique\":true}],\"version\":\"743ae4eb\"}},\"@happyvertical/smrt-languages:LanguageOverride\":{\"name\":\"languageoverride\",\"className\":\"LanguageOverride\",\"qualifiedName\":\"@happyvertical/smrt-languages:LanguageOverride\",\"collection\":\"languageoverrides\",\"filePath\":\"/home/runner/_work/smrt/smrt/packages/languages/src/models/LanguageOverride.ts\",\"packageName\":\"@happyvertical/smrt-languages\",\"fields\":{\"key\":{\"type\":\"text\",\"required\":true,\"default\":\"\",\"_meta\":{\"required\":true}},\"locale\":{\"type\":\"text\",\"required\":true,\"default\":\"\",\"_meta\":{\"required\":true}},\"tenantId\":{\"type\":\"text\",\"required\":false,\"_meta\":{\"nullable\":true}},\"template\":{\"type\":\"text\",\"required\":true,\"_meta\":{\"required\":true}},\"auto_generated\":{\"type\":\"boolean\",\"required\":true,\"default\":false,\"_meta\":{\"required\":true,\"default\":false}},\"source_hash\":{\"type\":\"text\",\"required\":false,\"_meta\":{\"nullable\":true}},\"ai_model\":{\"type\":\"text\",\"required\":false,\"_meta\":{\"nullable\":true}},\"reviewed_at\":{\"type\":\"text\",\"required\":false,\"_meta\":{\"nullable\":true}},\"reviewed_by\":{\"type\":\"text\",\"required\":false,\"_meta\":{\"nullable\":true}}},\"methods\":{\"save\":{\"name\":\"save\",\"async\":true,\"parameters\":[],\"returnType\":\"Promise\",\"isStatic\":false,\"isPublic\":true},\"delete\":{\"name\":\"delete\",\"async\":true,\"parameters\":[],\"returnType\":\"Promise<void>\",\"isStatic\":false,\"isPublic\":true},\"approve\":{\"name\":\"approve\",\"async\":true,\"parameters\":[{\"name\":\"reviewerId\",\"type\":\"string\",\"optional\":false}],\"returnType\":\"Promise\",\"isStatic\":false,\"isPublic\":true}},\"decoratorConfig\":{\"tableName\":\"_smrt_language_overrides\",\"conflictColumns\":[\"key\",\"locale\",\"context\"],\"api\":{\"include\":[\"list\",\"get\",\"create\",\"update\",\"delete\"]},\"cli\":{\"include\":[\"list\",\"get\",\"create\",\"update\",\"delete\",\"approve\"],\"skipApiCheck\":true},\"mcp\":{\"include\":[]}},\"extends\":\"SmrtObject\",\"exportName\":\"LanguageOverride\",\"collectionExportName\":\"LanguageOverrideCollection\",\"validationRules\":[{\"field\":\"key\",\"rule\":\"required\",\"fieldType\":\"text\"},{\"field\":\"locale\",\"rule\":\"required\",\"fieldType\":\"text\"},{\"field\":\"template\",\"rule\":\"required\",\"fieldType\":\"text\"},{\"field\":\"auto_generated\",\"rule\":\"required\",\"fieldType\":\"boolean\"}],\"schema\":{\"tableName\":\"_smrt_language_overrides\",\"ddl\":\"CREATE TABLE IF NOT EXISTS \\\"_smrt_language_overrides\\\" (\\n \\\"id\\\" UUID PRIMARY KEY NOT NULL,\\n \\\"slug\\\" TEXT NOT NULL,\\n \\\"context\\\" TEXT NOT NULL DEFAULT '',\\n \\\"created_at\\\" TIMESTAMP NOT NULL DEFAULT current_timestamp,\\n \\\"updated_at\\\" TIMESTAMP NOT NULL DEFAULT current_timestamp,\\n \\\"key\\\" TEXT NOT NULL DEFAULT '',\\n \\\"locale\\\" TEXT NOT NULL DEFAULT '',\\n \\\"tenant_id\\\" TEXT,\\n \\\"template\\\" TEXT NOT NULL,\\n \\\"auto_generated\\\" BOOLEAN NOT NULL DEFAULT FALSE,\\n \\\"source_hash\\\" TEXT,\\n \\\"ai_model\\\" TEXT,\\n \\\"reviewed_at\\\" TEXT,\\n \\\"reviewed_by\\\" TEXT\\n);\",\"columns\":{\"id\":{\"type\":\"UUID\",\"primaryKey\":true,\"referenceKind\":\"id\",\"notNull\":true},\"slug\":{\"type\":\"TEXT\",\"notNull\":true},\"context\":{\"type\":\"TEXT\",\"notNull\":true,\"default\":\"\"},\"created_at\":{\"type\":\"TIMESTAMP\",\"notNull\":true,\"default\":\"current_timestamp\"},\"updated_at\":{\"type\":\"TIMESTAMP\",\"notNull\":true,\"default\":\"current_timestamp\"},\"key\":{\"type\":\"TEXT\",\"notNull\":true,\"unique\":false,\"default\":\"\"},\"locale\":{\"type\":\"TEXT\",\"notNull\":true,\"unique\":false,\"default\":\"\"},\"tenant_id\":{\"type\":\"TEXT\",\"notNull\":false,\"unique\":false},\"template\":{\"type\":\"TEXT\",\"notNull\":true,\"unique\":false},\"auto_generated\":{\"type\":\"BOOLEAN\",\"notNull\":true,\"unique\":false,\"default\":false},\"source_hash\":{\"type\":\"TEXT\",\"notNull\":false,\"unique\":false},\"ai_model\":{\"type\":\"TEXT\",\"notNull\":false,\"unique\":false},\"reviewed_at\":{\"type\":\"TEXT\",\"notNull\":false,\"unique\":false},\"reviewed_by\":{\"type\":\"TEXT\",\"notNull\":false,\"unique\":false}},\"indexes\":[{\"name\":\"_smrt_language_overrides_id_idx\",\"columns\":[\"id\"]},{\"name\":\"_smrt_language_overrides_key_locale_idx\",\"columns\":[\"key\",\"locale\",\"context\"],\"unique\":true}],\"version\":\"4bef8abc\"}}},\"moduleType\":\"smrt\",\"smrtDependencies\":[\"@happyvertical/smrt-core\",\"@happyvertical/smrt-features\",\"@happyvertical/smrt-jobs\",\"@happyvertical/smrt-prompts\",\"@happyvertical/smrt-tenancy\"]}"));
7
7
  //#endregion
8
8
  //#region src/language-resolver.ts
9
9
  var DEFAULT_LOCALE = "en";
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","names":[],"sources":["../src/__smrt-register__.ts","../src/language-resolver.ts","../src/index.ts"],"sourcesContent":["/**\n * Self-registers this package's build-time manifest before any @smrt()\n * decorator in the package fires. Same pattern as smrt-prompts — see\n * packages/prompts/src/__smrt-register__.ts and issue #1132 for context.\n */\nimport { ObjectRegistry } from '@happyvertical/smrt-core';\n\nObjectRegistry.registerPackageManifest(\n new URL('./manifest.json', import.meta.url),\n);\n","import { getPackageConfig } from '@happyvertical/smrt-config';\nimport { getTenantId } from '@happyvertical/smrt-tenancy';\nimport {\n getCachedLanguage,\n invalidateLanguageCache,\n setCachedLanguage,\n} from './cache.js';\nimport { LanguageOverrideCollection } from './collections/LanguageOverrideCollection.js';\nimport { LanguageRegistry } from './language-registry.js';\nimport type {\n LanguageCacheValue,\n LanguagesPackageConfig,\n ResolvedLanguageString,\n ResolveLanguageStringOptions,\n} from './types.js';\nimport {\n buildLocaleFallbackChain,\n normalizeLocale,\n renderTemplate,\n} from './utils.js';\n\nconst DEFAULT_LOCALE = 'en';\n\nfunction getLanguagesConfig(): LanguagesPackageConfig {\n return getPackageConfig<LanguagesPackageConfig>('languages', {\n defaultLocale: DEFAULT_LOCALE,\n overrides: {},\n });\n}\n\ninterface ResolutionAttempt {\n template: string | null;\n source: ResolvedLanguageString['source'];\n resolvedFromLocale: string;\n}\n\nasync function resolveBaseForLocale(\n key: string,\n locale: string,\n options: ResolveLanguageStringOptions,\n config: LanguagesPackageConfig,\n): Promise<ResolutionAttempt> {\n const tenantId =\n options.tenantId !== undefined ? options.tenantId : (getTenantId() ?? null);\n\n let collection: LanguageOverrideCollection | null = null;\n if (options.db) {\n collection = await LanguageOverrideCollection.create({\n db: options.db,\n });\n }\n\n const cacheDb = collection?.db ?? options.db;\n const cached = getCachedLanguage(key, locale, tenantId, cacheDb);\n if (cached) {\n return {\n template: cached.template,\n source: cached.source,\n resolvedFromLocale: cached.resolvedFromLocale,\n };\n }\n\n // 1. Tenant override (highest precedence among stored layers).\n if (collection && tenantId) {\n const tenantOverride = await collection.getTenantOverride(\n key,\n locale,\n tenantId,\n );\n if (tenantOverride) {\n return finalize({\n key,\n locale,\n tenantId,\n db: cacheDb,\n attempt: {\n template: tenantOverride.template,\n source: 'tenant',\n resolvedFromLocale: locale,\n },\n });\n }\n }\n\n // 2. App-level override (incl. AI auto-translations).\n if (collection) {\n const appOverride = await collection.getAppOverride(key, locale);\n if (appOverride) {\n return finalize({\n key,\n locale,\n tenantId,\n db: cacheDb,\n attempt: {\n template: appOverride.template,\n source: 'app',\n resolvedFromLocale: locale,\n },\n });\n }\n }\n\n // 3. File-config override. Locale keys in user-authored config files\n // commonly come in mixed case (`fr-ca`, `Fr-CA`, etc.); normalize lookup so\n // config matches the same way the DB and code-default layers do.\n const configForKey = config.overrides?.[key];\n const configTemplate = lookupNormalizedLocale(configForKey, locale);\n if (typeof configTemplate === 'string') {\n return finalize({\n key,\n locale,\n tenantId,\n db: cacheDb,\n attempt: {\n template: configTemplate,\n source: 'config',\n resolvedFromLocale: locale,\n },\n });\n }\n\n // 4. Code default.\n const definition = LanguageRegistry.get(key, locale);\n if (definition) {\n return finalize({\n key,\n locale,\n tenantId,\n db: cacheDb,\n attempt: {\n template: definition.template,\n source: 'code',\n resolvedFromLocale: locale,\n },\n });\n }\n\n return {\n template: null,\n source: 'missing',\n resolvedFromLocale: locale,\n };\n}\n\nfunction lookupNormalizedLocale(\n bag: Record<string, string> | undefined,\n locale: string,\n): string | undefined {\n if (!bag) return undefined;\n // Fast path: exact match (most file configs use the canonical form).\n if (typeof bag[locale] === 'string') return bag[locale];\n for (const [candidate, template] of Object.entries(bag)) {\n if (normalizeLocale(candidate) === locale) {\n return template;\n }\n }\n return undefined;\n}\n\nfunction finalize(args: {\n key: string;\n locale: string;\n tenantId: string | null;\n db: unknown;\n attempt: ResolutionAttempt;\n}): ResolutionAttempt {\n if (args.attempt.template !== null) {\n const value: LanguageCacheValue = {\n key: args.key,\n locale: args.locale,\n template: args.attempt.template,\n source: args.attempt.source,\n resolvedFromLocale: args.attempt.resolvedFromLocale,\n };\n setCachedLanguage(args.key, args.locale, args.tenantId, args.db, value);\n }\n return args.attempt;\n}\n\nexport async function resolveLanguageString(\n key: string,\n options: ResolveLanguageStringOptions = {},\n): Promise<ResolvedLanguageString> {\n if (!key || key.trim() === '') {\n throw new Error('resolveLanguageString requires a non-empty key');\n }\n\n const config = getLanguagesConfig();\n const defaultLocale = normalizeLocale(config.defaultLocale ?? DEFAULT_LOCALE);\n const requested = normalizeLocale(options.locale ?? defaultLocale);\n\n // Runtime override always wins, regardless of locale chain.\n if (typeof options.overrides?.template === 'string') {\n return {\n key,\n locale: requested,\n template: options.overrides.template,\n text: renderTemplate(options.overrides.template, options.vars),\n resolvedFromLocale: requested,\n source: 'runtime',\n };\n }\n\n const chain = buildLocaleFallbackChain(requested, defaultLocale);\n\n let firstHit: ResolutionAttempt | null = null;\n let firstLocale = requested;\n for (const locale of chain) {\n const attempt = await resolveBaseForLocale(key, locale, options, config);\n if (attempt.template !== null) {\n firstHit = attempt;\n firstLocale = locale;\n break;\n }\n }\n\n if (!firstHit) {\n if (options.strict) {\n throw new Error(\n `Language string \"${key}\" has no resolution for locale \"${requested}\"`,\n );\n }\n // Soft-miss: enqueue translation if the requested locale differs from the\n // default and a default-locale source exists. Failure of the enqueue path\n // must never break resolution — we still return the key as the rendered\n // text so the UI degrades gracefully.\n await tryEnqueueTranslation({\n key,\n requestedLocale: requested,\n defaultLocale,\n options,\n });\n return {\n key,\n locale: requested,\n template: key,\n text: key,\n resolvedFromLocale: requested,\n source: 'missing',\n };\n }\n\n // Hit at a fallback locale that isn't the requested one — fire-and-forget a\n // translation job for the requested target.\n if (firstLocale !== requested) {\n await tryEnqueueTranslation({\n key,\n requestedLocale: requested,\n defaultLocale,\n options,\n });\n return {\n key,\n locale: requested,\n template: firstHit.template ?? key,\n text: renderTemplate(firstHit.template ?? key, options.vars),\n resolvedFromLocale: firstLocale,\n source: 'fallback',\n };\n }\n\n return {\n key,\n locale: requested,\n template: firstHit.template ?? key,\n text: renderTemplate(firstHit.template ?? key, options.vars),\n resolvedFromLocale: firstLocale,\n source: firstHit.source,\n };\n}\n\n/**\n * Best-effort enqueue of an AI translation job. Wrapped so resolution never\n * fails because of an enqueue error, missing optional dependency, etc.\n *\n * The actual job machinery lives in `./translation-job.ts` and is loaded\n * lazily so the resolver does not pull `@happyvertical/ai` into the import\n * graph for synchronous resolves that never miss.\n */\nasync function tryEnqueueTranslation(args: {\n key: string;\n requestedLocale: string;\n defaultLocale: string;\n options: ResolveLanguageStringOptions;\n}): Promise<void> {\n if (!args.options.db) {\n // Without a DB we cannot persist a job; skip silently — resolver still\n // returns a fallback to the caller.\n return;\n }\n if (normalizeLocale(args.requestedLocale) === args.defaultLocale) {\n return;\n }\n\n try {\n const { enqueueTranslationJob } = await import('./translation-job.js');\n await enqueueTranslationJob({\n key: args.key,\n targetLocale: args.requestedLocale,\n sourceLocale: args.defaultLocale,\n tenantId: args.options.tenantId ?? getTenantId() ?? null,\n db: args.options.db,\n });\n } catch {\n // Enqueueing must never break resolution.\n }\n}\n\nexport function invalidateResolvedLanguageCache(\n key: string,\n locale: string,\n tenantId: string | null | undefined,\n db: unknown,\n): void {\n invalidateLanguageCache(key, locale, tenantId, db);\n}\n","/**\n * @happyvertical/smrt-languages\n *\n * Code-first language strings with config + tenant overrides and AI-driven\n * auto-translation for SMRT applications. Mirrors the architecture of\n * `@happyvertical/smrt-prompts` plus a smrt-jobs handler that fills gaps in\n * non-default locales the first time a string is requested.\n *\n * The package root intentionally exposes only the read path\n * (`defineLanguageString`, `resolveLanguageString`, `LanguageOverride`, the\n * cache helpers, glossary helpers, and shared types/utilities). The\n * translation-worker stack — which depends on `@happyvertical/ai`,\n * smrt-jobs, smrt-features, and smrt-prompts — lives on the\n * `@happyvertical/smrt-languages/jobs` subpath, and the admin CLI helpers\n * live on `@happyvertical/smrt-languages/cli`. Resolver consumers (the vast\n * majority of call sites) never load the worker dependency tree.\n *\n * @packageDocumentation\n */\n\nimport './__smrt-register__.js';\n\nexport {\n clearLanguageCache,\n getLanguageCacheTtlMs,\n invalidateLanguageCache,\n} from './cache.js';\nexport { LanguageOverrideCollection } from './collections/LanguageOverrideCollection.js';\nexport { buildTenantGlossary } from './glossary.js';\n\nexport {\n defineLanguageString,\n LanguageRegistry,\n} from './language-registry.js';\n\nexport {\n invalidateResolvedLanguageCache,\n resolveLanguageString,\n} from './language-resolver.js';\n\nexport {\n LanguageOverride,\n type LanguageOverrideCtorOptions,\n} from './models/LanguageOverride.js';\n\nexport type {\n LanguageCacheValue,\n LanguageOverrideOptions,\n LanguageStringDefinition,\n LanguageStringDefinitionInput,\n LanguagesPackageConfig,\n LanguageVariables,\n ResolvedLanguageString,\n ResolveLanguageStringOptions,\n} from './types.js';\n\nexport {\n buildLocaleFallbackChain,\n buildTranslationJobId,\n computeSourceHash,\n normalizeLocale,\n renderTemplate,\n} from './utils.js';\n\n/** @internal */\nexport const PACKAGE_VERSION_INITIALIZED = true;\n"],"mappings":"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"}
1
+ {"version":3,"file":"index.js","names":[],"sources":["../src/__smrt-register__.ts","../src/language-resolver.ts","../src/index.ts"],"sourcesContent":["/**\n * Self-registers this package's build-time manifest before any @smrt()\n * decorator in the package fires. Same pattern as smrt-prompts — see\n * packages/prompts/src/__smrt-register__.ts and issue #1132 for context.\n */\nimport { ObjectRegistry } from '@happyvertical/smrt-core';\n\nObjectRegistry.registerPackageManifest(\n new URL('./manifest.json', import.meta.url),\n);\n","import { getPackageConfig } from '@happyvertical/smrt-config';\nimport { getTenantId } from '@happyvertical/smrt-tenancy';\nimport {\n getCachedLanguage,\n invalidateLanguageCache,\n setCachedLanguage,\n} from './cache.js';\nimport { LanguageOverrideCollection } from './collections/LanguageOverrideCollection.js';\nimport { LanguageRegistry } from './language-registry.js';\nimport type {\n LanguageCacheValue,\n LanguagesPackageConfig,\n ResolvedLanguageString,\n ResolveLanguageStringOptions,\n} from './types.js';\nimport {\n buildLocaleFallbackChain,\n normalizeLocale,\n renderTemplate,\n} from './utils.js';\n\nconst DEFAULT_LOCALE = 'en';\n\nfunction getLanguagesConfig(): LanguagesPackageConfig {\n return getPackageConfig<LanguagesPackageConfig>('languages', {\n defaultLocale: DEFAULT_LOCALE,\n overrides: {},\n });\n}\n\ninterface ResolutionAttempt {\n template: string | null;\n source: ResolvedLanguageString['source'];\n resolvedFromLocale: string;\n}\n\nasync function resolveBaseForLocale(\n key: string,\n locale: string,\n options: ResolveLanguageStringOptions,\n config: LanguagesPackageConfig,\n): Promise<ResolutionAttempt> {\n const tenantId =\n options.tenantId !== undefined ? options.tenantId : (getTenantId() ?? null);\n\n let collection: LanguageOverrideCollection | null = null;\n if (options.db) {\n collection = await LanguageOverrideCollection.create({\n db: options.db,\n });\n }\n\n const cacheDb = collection?.db ?? options.db;\n const cached = getCachedLanguage(key, locale, tenantId, cacheDb);\n if (cached) {\n return {\n template: cached.template,\n source: cached.source,\n resolvedFromLocale: cached.resolvedFromLocale,\n };\n }\n\n // 1. Tenant override (highest precedence among stored layers).\n if (collection && tenantId) {\n const tenantOverride = await collection.getTenantOverride(\n key,\n locale,\n tenantId,\n );\n if (tenantOverride) {\n return finalize({\n key,\n locale,\n tenantId,\n db: cacheDb,\n attempt: {\n template: tenantOverride.template,\n source: 'tenant',\n resolvedFromLocale: locale,\n },\n });\n }\n }\n\n // 2. App-level override (incl. AI auto-translations).\n if (collection) {\n const appOverride = await collection.getAppOverride(key, locale);\n if (appOverride) {\n return finalize({\n key,\n locale,\n tenantId,\n db: cacheDb,\n attempt: {\n template: appOverride.template,\n source: 'app',\n resolvedFromLocale: locale,\n },\n });\n }\n }\n\n // 3. File-config override. Locale keys in user-authored config files\n // commonly come in mixed case (`fr-ca`, `Fr-CA`, etc.); normalize lookup so\n // config matches the same way the DB and code-default layers do.\n const configForKey = config.overrides?.[key];\n const configTemplate = lookupNormalizedLocale(configForKey, locale);\n if (typeof configTemplate === 'string') {\n return finalize({\n key,\n locale,\n tenantId,\n db: cacheDb,\n attempt: {\n template: configTemplate,\n source: 'config',\n resolvedFromLocale: locale,\n },\n });\n }\n\n // 4. Code default.\n const definition = LanguageRegistry.get(key, locale);\n if (definition) {\n return finalize({\n key,\n locale,\n tenantId,\n db: cacheDb,\n attempt: {\n template: definition.template,\n source: 'code',\n resolvedFromLocale: locale,\n },\n });\n }\n\n return {\n template: null,\n source: 'missing',\n resolvedFromLocale: locale,\n };\n}\n\nfunction lookupNormalizedLocale(\n bag: Record<string, string> | undefined,\n locale: string,\n): string | undefined {\n if (!bag) return undefined;\n // Fast path: exact match (most file configs use the canonical form).\n if (typeof bag[locale] === 'string') return bag[locale];\n for (const [candidate, template] of Object.entries(bag)) {\n if (normalizeLocale(candidate) === locale) {\n return template;\n }\n }\n return undefined;\n}\n\nfunction finalize(args: {\n key: string;\n locale: string;\n tenantId: string | null;\n db: unknown;\n attempt: ResolutionAttempt;\n}): ResolutionAttempt {\n if (args.attempt.template !== null) {\n const value: LanguageCacheValue = {\n key: args.key,\n locale: args.locale,\n template: args.attempt.template,\n source: args.attempt.source,\n resolvedFromLocale: args.attempt.resolvedFromLocale,\n };\n setCachedLanguage(args.key, args.locale, args.tenantId, args.db, value);\n }\n return args.attempt;\n}\n\nexport async function resolveLanguageString(\n key: string,\n options: ResolveLanguageStringOptions = {},\n): Promise<ResolvedLanguageString> {\n if (!key || key.trim() === '') {\n throw new Error('resolveLanguageString requires a non-empty key');\n }\n\n const config = getLanguagesConfig();\n const defaultLocale = normalizeLocale(config.defaultLocale ?? DEFAULT_LOCALE);\n const requested = normalizeLocale(options.locale ?? defaultLocale);\n\n // Runtime override always wins, regardless of locale chain.\n if (typeof options.overrides?.template === 'string') {\n return {\n key,\n locale: requested,\n template: options.overrides.template,\n text: renderTemplate(options.overrides.template, options.vars),\n resolvedFromLocale: requested,\n source: 'runtime',\n };\n }\n\n const chain = buildLocaleFallbackChain(requested, defaultLocale);\n\n let firstHit: ResolutionAttempt | null = null;\n let firstLocale = requested;\n for (const locale of chain) {\n const attempt = await resolveBaseForLocale(key, locale, options, config);\n if (attempt.template !== null) {\n firstHit = attempt;\n firstLocale = locale;\n break;\n }\n }\n\n if (!firstHit) {\n if (options.strict) {\n throw new Error(\n `Language string \"${key}\" has no resolution for locale \"${requested}\"`,\n );\n }\n // Soft-miss: enqueue translation if the requested locale differs from the\n // default and a default-locale source exists. Failure of the enqueue path\n // must never break resolution — we still return the key as the rendered\n // text so the UI degrades gracefully.\n await tryEnqueueTranslation({\n key,\n requestedLocale: requested,\n defaultLocale,\n options,\n });\n return {\n key,\n locale: requested,\n template: key,\n text: key,\n resolvedFromLocale: requested,\n source: 'missing',\n };\n }\n\n // Hit at a fallback locale that isn't the requested one — fire-and-forget a\n // translation job for the requested target.\n if (firstLocale !== requested) {\n await tryEnqueueTranslation({\n key,\n requestedLocale: requested,\n defaultLocale,\n options,\n });\n return {\n key,\n locale: requested,\n template: firstHit.template ?? key,\n text: renderTemplate(firstHit.template ?? key, options.vars),\n resolvedFromLocale: firstLocale,\n source: 'fallback',\n };\n }\n\n return {\n key,\n locale: requested,\n template: firstHit.template ?? key,\n text: renderTemplate(firstHit.template ?? key, options.vars),\n resolvedFromLocale: firstLocale,\n source: firstHit.source,\n };\n}\n\n/**\n * Best-effort enqueue of an AI translation job. Wrapped so resolution never\n * fails because of an enqueue error, missing optional dependency, etc.\n *\n * The actual job machinery lives in `./translation-job.ts` and is loaded\n * lazily so the resolver does not pull `@happyvertical/ai` into the import\n * graph for synchronous resolves that never miss.\n */\nasync function tryEnqueueTranslation(args: {\n key: string;\n requestedLocale: string;\n defaultLocale: string;\n options: ResolveLanguageStringOptions;\n}): Promise<void> {\n if (!args.options.db) {\n // Without a DB we cannot persist a job; skip silently — resolver still\n // returns a fallback to the caller.\n return;\n }\n if (normalizeLocale(args.requestedLocale) === args.defaultLocale) {\n return;\n }\n\n try {\n const { enqueueTranslationJob } = await import('./translation-job.js');\n await enqueueTranslationJob({\n key: args.key,\n targetLocale: args.requestedLocale,\n sourceLocale: args.defaultLocale,\n tenantId: args.options.tenantId ?? getTenantId() ?? null,\n db: args.options.db,\n });\n } catch {\n // Enqueueing must never break resolution.\n }\n}\n\nexport function invalidateResolvedLanguageCache(\n key: string,\n locale: string,\n tenantId: string | null | undefined,\n db: unknown,\n): void {\n invalidateLanguageCache(key, locale, tenantId, db);\n}\n","/**\n * @happyvertical/smrt-languages\n *\n * Code-first language strings with config + tenant overrides and AI-driven\n * auto-translation for SMRT applications. Mirrors the architecture of\n * `@happyvertical/smrt-prompts` plus a smrt-jobs handler that fills gaps in\n * non-default locales the first time a string is requested.\n *\n * The package root intentionally exposes only the read path\n * (`defineLanguageString`, `resolveLanguageString`, `LanguageOverride`, the\n * cache helpers, glossary helpers, and shared types/utilities). The\n * translation-worker stack — which depends on `@happyvertical/ai`,\n * smrt-jobs, smrt-features, and smrt-prompts — lives on the\n * `@happyvertical/smrt-languages/jobs` subpath, and the admin CLI helpers\n * live on `@happyvertical/smrt-languages/cli`. Resolver consumers (the vast\n * majority of call sites) never load the worker dependency tree.\n *\n * @packageDocumentation\n */\n\nimport './__smrt-register__.js';\n\nexport {\n clearLanguageCache,\n getLanguageCacheTtlMs,\n invalidateLanguageCache,\n} from './cache.js';\nexport { LanguageOverrideCollection } from './collections/LanguageOverrideCollection.js';\nexport { buildTenantGlossary } from './glossary.js';\n\nexport {\n defineLanguageString,\n LanguageRegistry,\n} from './language-registry.js';\n\nexport {\n invalidateResolvedLanguageCache,\n resolveLanguageString,\n} from './language-resolver.js';\n\nexport {\n LanguageOverride,\n type LanguageOverrideCtorOptions,\n} from './models/LanguageOverride.js';\n\nexport type {\n LanguageCacheValue,\n LanguageOverrideOptions,\n LanguageStringDefinition,\n LanguageStringDefinitionInput,\n LanguagesPackageConfig,\n LanguageVariables,\n ResolvedLanguageString,\n ResolveLanguageStringOptions,\n} from './types.js';\n\nexport {\n buildLocaleFallbackChain,\n buildTranslationJobId,\n computeSourceHash,\n normalizeLocale,\n renderTemplate,\n} from './utils.js';\n\n/** @internal */\nexport const PACKAGE_VERSION_INITIALIZED = true;\n"],"mappings":"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"}
@@ -1,8 +1,8 @@
1
1
  {
2
2
  "version": "1.0.0",
3
- "timestamp": 1782943649265,
3
+ "timestamp": 1782953446280,
4
4
  "packageName": "@happyvertical/smrt-languages",
5
- "packageVersion": "0.37.4",
5
+ "packageVersion": "0.37.5",
6
6
  "objects": {
7
7
  "@happyvertical/smrt-languages:LanguageTranslationTask": {
8
8
  "name": "languagetranslationtask",
@@ -91,6 +91,167 @@
91
91
  "version": "2cfc8812"
92
92
  }
93
93
  },
94
+ "@happyvertical/smrt-languages:LanguageOverrideCollection": {
95
+ "name": "languageoverridecollection",
96
+ "className": "LanguageOverrideCollection",
97
+ "qualifiedName": "@happyvertical/smrt-languages:LanguageOverrideCollection",
98
+ "collection": "languageoverrides",
99
+ "filePath": "/home/runner/_work/smrt/smrt/packages/languages/src/collections/LanguageOverrideCollection.ts",
100
+ "packageName": "@happyvertical/smrt-languages",
101
+ "fields": {},
102
+ "methods": {
103
+ "getAppOverride": {
104
+ "name": "getAppOverride",
105
+ "async": true,
106
+ "parameters": [
107
+ {
108
+ "name": "key",
109
+ "type": "string",
110
+ "optional": false
111
+ },
112
+ {
113
+ "name": "locale",
114
+ "type": "string",
115
+ "optional": false
116
+ }
117
+ ],
118
+ "returnType": "Promise<LanguageOverride | null>",
119
+ "isStatic": false,
120
+ "isPublic": true
121
+ },
122
+ "getTenantOverride": {
123
+ "name": "getTenantOverride",
124
+ "async": true,
125
+ "parameters": [
126
+ {
127
+ "name": "key",
128
+ "type": "string",
129
+ "optional": false
130
+ },
131
+ {
132
+ "name": "locale",
133
+ "type": "string",
134
+ "optional": false
135
+ },
136
+ {
137
+ "name": "tenantId",
138
+ "type": "string",
139
+ "optional": false
140
+ }
141
+ ],
142
+ "returnType": "Promise<LanguageOverride | null>",
143
+ "isStatic": false,
144
+ "isPublic": true
145
+ },
146
+ "getResolutionLayers": {
147
+ "name": "getResolutionLayers",
148
+ "async": true,
149
+ "parameters": [
150
+ {
151
+ "name": "key",
152
+ "type": "string",
153
+ "optional": false
154
+ },
155
+ {
156
+ "name": "locale",
157
+ "type": "string",
158
+ "optional": false
159
+ },
160
+ {
161
+ "name": "tenantId",
162
+ "type": "string | null",
163
+ "optional": true
164
+ }
165
+ ],
166
+ "returnType": "Promise<object>",
167
+ "isStatic": false,
168
+ "isPublic": true
169
+ },
170
+ "listTenantOverrides": {
171
+ "name": "listTenantOverrides",
172
+ "async": true,
173
+ "parameters": [
174
+ {
175
+ "name": "tenantId",
176
+ "type": "string",
177
+ "optional": false
178
+ }
179
+ ],
180
+ "returnType": "Promise<LanguageOverride[]>",
181
+ "isStatic": false,
182
+ "isPublic": true
183
+ },
184
+ "listUnreviewedAutoTranslations": {
185
+ "name": "listUnreviewedAutoTranslations",
186
+ "async": true,
187
+ "parameters": [
188
+ {
189
+ "name": "options",
190
+ "type": "object",
191
+ "optional": true
192
+ }
193
+ ],
194
+ "returnType": "Promise<LanguageOverride[]>",
195
+ "isStatic": false,
196
+ "isPublic": true
197
+ }
198
+ },
199
+ "decoratorConfig": {
200
+ "tableName": "_smrt_language_overrides"
201
+ },
202
+ "extends": "SmrtCollection",
203
+ "extendsTypeArg": "LanguageOverride",
204
+ "exportName": "LanguageOverrideCollection",
205
+ "collectionExportName": "LanguageOverrideCollectionCollection",
206
+ "schema": {
207
+ "tableName": "_smrt_language_overrides",
208
+ "ddl": "CREATE TABLE IF NOT EXISTS \"_smrt_language_overrides\" (\n \"id\" UUID PRIMARY KEY NOT NULL,\n \"slug\" TEXT NOT NULL,\n \"context\" TEXT NOT NULL DEFAULT '',\n \"created_at\" TIMESTAMP NOT NULL DEFAULT current_timestamp,\n \"updated_at\" TIMESTAMP NOT NULL DEFAULT current_timestamp\n);",
209
+ "columns": {
210
+ "id": {
211
+ "type": "UUID",
212
+ "primaryKey": true,
213
+ "referenceKind": "id",
214
+ "notNull": true
215
+ },
216
+ "slug": {
217
+ "type": "TEXT",
218
+ "notNull": true
219
+ },
220
+ "context": {
221
+ "type": "TEXT",
222
+ "notNull": true,
223
+ "default": ""
224
+ },
225
+ "created_at": {
226
+ "type": "TIMESTAMP",
227
+ "notNull": true,
228
+ "default": "current_timestamp"
229
+ },
230
+ "updated_at": {
231
+ "type": "TIMESTAMP",
232
+ "notNull": true,
233
+ "default": "current_timestamp"
234
+ }
235
+ },
236
+ "indexes": [
237
+ {
238
+ "name": "_smrt_language_overrides_id_idx",
239
+ "columns": [
240
+ "id"
241
+ ]
242
+ },
243
+ {
244
+ "name": "_smrt_language_overrides_slug_context_idx",
245
+ "columns": [
246
+ "slug",
247
+ "context"
248
+ ],
249
+ "unique": true
250
+ }
251
+ ],
252
+ "version": "743ae4eb"
253
+ }
254
+ },
94
255
  "@happyvertical/smrt-languages:LanguageOverride": {
95
256
  "name": "languageoverride",
96
257
  "className": "LanguageOverride",
@@ -352,167 +513,6 @@
352
513
  ],
353
514
  "version": "4bef8abc"
354
515
  }
355
- },
356
- "@happyvertical/smrt-languages:LanguageOverrideCollection": {
357
- "name": "languageoverridecollection",
358
- "className": "LanguageOverrideCollection",
359
- "qualifiedName": "@happyvertical/smrt-languages:LanguageOverrideCollection",
360
- "collection": "languageoverrides",
361
- "filePath": "/home/runner/_work/smrt/smrt/packages/languages/src/collections/LanguageOverrideCollection.ts",
362
- "packageName": "@happyvertical/smrt-languages",
363
- "fields": {},
364
- "methods": {
365
- "getAppOverride": {
366
- "name": "getAppOverride",
367
- "async": true,
368
- "parameters": [
369
- {
370
- "name": "key",
371
- "type": "string",
372
- "optional": false
373
- },
374
- {
375
- "name": "locale",
376
- "type": "string",
377
- "optional": false
378
- }
379
- ],
380
- "returnType": "Promise<LanguageOverride | null>",
381
- "isStatic": false,
382
- "isPublic": true
383
- },
384
- "getTenantOverride": {
385
- "name": "getTenantOverride",
386
- "async": true,
387
- "parameters": [
388
- {
389
- "name": "key",
390
- "type": "string",
391
- "optional": false
392
- },
393
- {
394
- "name": "locale",
395
- "type": "string",
396
- "optional": false
397
- },
398
- {
399
- "name": "tenantId",
400
- "type": "string",
401
- "optional": false
402
- }
403
- ],
404
- "returnType": "Promise<LanguageOverride | null>",
405
- "isStatic": false,
406
- "isPublic": true
407
- },
408
- "getResolutionLayers": {
409
- "name": "getResolutionLayers",
410
- "async": true,
411
- "parameters": [
412
- {
413
- "name": "key",
414
- "type": "string",
415
- "optional": false
416
- },
417
- {
418
- "name": "locale",
419
- "type": "string",
420
- "optional": false
421
- },
422
- {
423
- "name": "tenantId",
424
- "type": "string | null",
425
- "optional": true
426
- }
427
- ],
428
- "returnType": "Promise<object>",
429
- "isStatic": false,
430
- "isPublic": true
431
- },
432
- "listTenantOverrides": {
433
- "name": "listTenantOverrides",
434
- "async": true,
435
- "parameters": [
436
- {
437
- "name": "tenantId",
438
- "type": "string",
439
- "optional": false
440
- }
441
- ],
442
- "returnType": "Promise<LanguageOverride[]>",
443
- "isStatic": false,
444
- "isPublic": true
445
- },
446
- "listUnreviewedAutoTranslations": {
447
- "name": "listUnreviewedAutoTranslations",
448
- "async": true,
449
- "parameters": [
450
- {
451
- "name": "options",
452
- "type": "object",
453
- "optional": true
454
- }
455
- ],
456
- "returnType": "Promise<LanguageOverride[]>",
457
- "isStatic": false,
458
- "isPublic": true
459
- }
460
- },
461
- "decoratorConfig": {
462
- "tableName": "_smrt_language_overrides"
463
- },
464
- "extends": "SmrtCollection",
465
- "extendsTypeArg": "LanguageOverride",
466
- "exportName": "LanguageOverrideCollection",
467
- "collectionExportName": "LanguageOverrideCollectionCollection",
468
- "schema": {
469
- "tableName": "_smrt_language_overrides",
470
- "ddl": "CREATE TABLE IF NOT EXISTS \"_smrt_language_overrides\" (\n \"id\" UUID PRIMARY KEY NOT NULL,\n \"slug\" TEXT NOT NULL,\n \"context\" TEXT NOT NULL DEFAULT '',\n \"created_at\" TIMESTAMP NOT NULL DEFAULT current_timestamp,\n \"updated_at\" TIMESTAMP NOT NULL DEFAULT current_timestamp\n);",
471
- "columns": {
472
- "id": {
473
- "type": "UUID",
474
- "primaryKey": true,
475
- "referenceKind": "id",
476
- "notNull": true
477
- },
478
- "slug": {
479
- "type": "TEXT",
480
- "notNull": true
481
- },
482
- "context": {
483
- "type": "TEXT",
484
- "notNull": true,
485
- "default": ""
486
- },
487
- "created_at": {
488
- "type": "TIMESTAMP",
489
- "notNull": true,
490
- "default": "current_timestamp"
491
- },
492
- "updated_at": {
493
- "type": "TIMESTAMP",
494
- "notNull": true,
495
- "default": "current_timestamp"
496
- }
497
- },
498
- "indexes": [
499
- {
500
- "name": "_smrt_language_overrides_id_idx",
501
- "columns": [
502
- "id"
503
- ]
504
- },
505
- {
506
- "name": "_smrt_language_overrides_slug_context_idx",
507
- "columns": [
508
- "slug",
509
- "context"
510
- ],
511
- "unique": true
512
- }
513
- ],
514
- "version": "743ae4eb"
515
- }
516
516
  }
517
517
  },
518
518
  "moduleType": "smrt",
@@ -1,13 +1,13 @@
1
1
  {
2
2
  "schemaVersion": 1,
3
- "generatedAt": "2026-07-01T22:07:36.914Z",
3
+ "generatedAt": "2026-07-02T00:50:55.843Z",
4
4
  "packageName": "@happyvertical/smrt-languages",
5
- "packageVersion": "0.37.4",
5
+ "packageVersion": "0.37.5",
6
6
  "sourceManifestPath": "dist/manifest.json",
7
7
  "agentDocPath": "AGENTS.md",
8
8
  "sourceHashes": {
9
- "manifest": "aa81c3db0fccedb591ed37016de5ad5ac79a90ba4e8fd30846f0882a93d64231",
10
- "packageJson": "a2039db1f3ecef4d5b7c84983c6b621980c91446ef6b3e4cb1f8bab5fcf1f0c0",
9
+ "manifest": "136084faa4aaa7440167f5c37ea54aff4c2d0b15b863b0840cbe4a5be250ad13",
10
+ "packageJson": "507f6b87246adba6b6bb5c5d11afcb6d54b55165a743055a2374359050072305",
11
11
  "agents": "6dc270bd9bb9c51d48f526396544ab2a54a071c42f60869f4806520818f7e864"
12
12
  },
13
13
  "exports": [
@@ -71,6 +71,29 @@
71
71
  "tags": [],
72
72
  "risks": []
73
73
  },
74
+ {
75
+ "name": "LanguageOverrideCollection",
76
+ "qualifiedName": "@happyvertical/smrt-languages:LanguageOverrideCollection",
77
+ "collection": "languageoverrides",
78
+ "tableName": "_smrt_language_overrides",
79
+ "packageName": "@happyvertical/smrt-languages",
80
+ "extends": "SmrtCollection",
81
+ "fields": [],
82
+ "relationships": [],
83
+ "methods": [
84
+ "getAppOverride",
85
+ "getResolutionLayers",
86
+ "getTenantOverride",
87
+ "listTenantOverrides",
88
+ "listUnreviewedAutoTranslations"
89
+ ],
90
+ "surfaces": [],
91
+ "relationshipFeatures": [
92
+ "uuidColumns"
93
+ ],
94
+ "tags": [],
95
+ "risks": []
96
+ },
74
97
  {
75
98
  "name": "LanguageOverride",
76
99
  "qualifiedName": "@happyvertical/smrt-languages:LanguageOverride",
@@ -223,29 +246,6 @@
223
246
  ],
224
247
  "tags": [],
225
248
  "risks": []
226
- },
227
- {
228
- "name": "LanguageOverrideCollection",
229
- "qualifiedName": "@happyvertical/smrt-languages:LanguageOverrideCollection",
230
- "collection": "languageoverrides",
231
- "tableName": "_smrt_language_overrides",
232
- "packageName": "@happyvertical/smrt-languages",
233
- "extends": "SmrtCollection",
234
- "fields": [],
235
- "relationships": [],
236
- "methods": [
237
- "getAppOverride",
238
- "getResolutionLayers",
239
- "getTenantOverride",
240
- "listTenantOverrides",
241
- "listUnreviewedAutoTranslations"
242
- ],
243
- "surfaces": [],
244
- "relationshipFeatures": [
245
- "uuidColumns"
246
- ],
247
- "tags": [],
248
- "risks": []
249
249
  }
250
250
  ],
251
251
  "surfaces": [
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@happyvertical/smrt-languages",
3
- "version": "0.37.4",
3
+ "version": "0.37.5",
4
4
  "description": "Code-first language strings with config + tenant overrides and AI auto-translation for SMRT",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -30,20 +30,20 @@
30
30
  "@happyvertical/ai": "^0.74.11",
31
31
  "@happyvertical/logger": "^0.74.11",
32
32
  "@happyvertical/sql": "^0.74.11",
33
- "@happyvertical/smrt-config": "0.37.4",
34
- "@happyvertical/smrt-core": "0.37.4",
35
- "@happyvertical/smrt-features": "0.37.4",
36
- "@happyvertical/smrt-jobs": "0.37.4",
37
- "@happyvertical/smrt-prompts": "0.37.4",
38
- "@happyvertical/smrt-tenancy": "0.37.4"
33
+ "@happyvertical/smrt-config": "0.37.5",
34
+ "@happyvertical/smrt-jobs": "0.37.5",
35
+ "@happyvertical/smrt-core": "0.37.5",
36
+ "@happyvertical/smrt-features": "0.37.5",
37
+ "@happyvertical/smrt-prompts": "0.37.5",
38
+ "@happyvertical/smrt-tenancy": "0.37.5"
39
39
  },
40
40
  "devDependencies": {
41
41
  "@types/node": "24.13.2",
42
42
  "typescript": "^5.9.3",
43
43
  "vite": "^8.1.2",
44
44
  "vitest": "^4.1.9",
45
- "@happyvertical/smrt-cli": "0.37.4",
46
- "@happyvertical/smrt-vitest": "0.37.4"
45
+ "@happyvertical/smrt-vitest": "0.37.5",
46
+ "@happyvertical/smrt-cli": "0.37.5"
47
47
  },
48
48
  "keywords": [
49
49
  "smrt",