diff --git a/projects/ngx-translate/src/lib/translate.service.ts b/projects/ngx-translate/src/lib/translate.service.ts index e3d2e036..1c3c4976 100644 --- a/projects/ngx-translate/src/lib/translate.service.ts +++ b/projects/ngx-translate/src/lib/translate.service.ts @@ -6,7 +6,7 @@ import { TranslateCompiler } from "./translate.compiler"; import { TranslateLoader } from "./translate.loader"; import { InterpolateFunction, TranslateParser } from "./translate.parser"; import { TranslateStore } from "./translate.store"; -import { insertValue, isArray, isDefinedAndNotNull, isDict, isString } from "./util"; +import { insertValue, isArray, isDefinedAndNotNull, isDict, isString, getValue } from "./util"; /** * Configuration object for the translation service. @@ -98,6 +98,7 @@ export abstract class ITranslateService { public abstract reloadLang(lang: Language): Observable; public abstract resetLang(lang: Language): void; + public abstract hasTranslation(translationKey: string, language?: string): boolean; public abstract instant( key: string | string[], interpolateParams?: InterpolationParameters, @@ -573,6 +574,35 @@ export class TranslateService implements ITranslateService { ); } + /** + * Checks if a translation key exists in the specified language, or in the current/fallback languages when not specified. + * @param key The translation key to check + * @param language Optional language to check instead of the current language + * @returns true if the key exists, false otherwise + */ + public hasTranslation(key: string, language?: string): boolean { + if (!isDefinedAndNotNull(key) || key.length === 0) { + return false; + } + + if (isDefinedAndNotNull(language)) { + const translationsForLanguage = this.store.getTranslations(language); + if (!isDefinedAndNotNull(translationsForLanguage)) { + return false; + } + const value = getValue(translationsForLanguage, key); + return value !== undefined; + } + + const currentLang = this.getCurrentLang(); + const translationsForCurrentLang = this.store.getTranslations(currentLang); + if (!isDefinedAndNotNull(translationsForCurrentLang)) { + return false; + } + const value = getValue(translationsForCurrentLang, key); + return value !== undefined; + } + /** * Returns a translation instantly from the internal state of loaded translation. * All rules regarding the current language, the preferred language of even fallback languages diff --git a/projects/ngx-translate/src/tests/translate.service.spec.ts b/projects/ngx-translate/src/tests/translate.service.spec.ts index 4d5ed0e5..cca8fd16 100644 --- a/projects/ngx-translate/src/tests/translate.service.spec.ts +++ b/projects/ngx-translate/src/tests/translate.service.spec.ts @@ -140,6 +140,69 @@ describe("TranslateService", () => { }); }); + it("should be able to check if translation keys exist", () => { + translations = { + user: { + profile: { + name: "User Name", + email: "user@example.com", + }, + settings: { + language: "English", + }, + }, + common: { + buttons: { + save: "Save", + cancel: "Cancel", + }, + }, + }; + translate.use("en"); + + // Check existing nested keys + expect(translate.hasTranslation("user.profile.name")).toBe(true); + expect(translate.hasTranslation("user.profile.email")).toBe(true); + expect(translate.hasTranslation("user.settings.language")).toBe(true); + expect(translate.hasTranslation("common.buttons.save")).toBe(true); + expect(translate.hasTranslation("common.buttons.cancel")).toBe(true); + + // Check non-existing nested keys + expect(translate.hasTranslation("user.profile.age")).toBe(false); + expect(translate.hasTranslation("user.non.existent")).toBe(false); + expect(translate.hasTranslation("common.buttons.non.existent")).toBe(false); + + // Check with fallback language + translate.setDefaultLang("fr"); + translate.setTranslation("fr", { + user: { + profile: { + name: "Nom d'utilisateur", + }, + }, + }); + + // Change current language to trigger fallback + translate.use("fr"); + + expect(translate.hasTranslation("user.profile.name")).toBe(true); + expect(translate.hasTranslation("user.profile.email")).toBe(false); + }); + + it("should check translation existence for a specific language when provided", () => { + translate.setTranslation("en", { a: { b: "x" } }); + translate.setTranslation("de", { a: { c: "y" } }); + translate.setDefaultLang("de"); + translate.use("en"); + + expect(translate.hasTranslation("a.b", "en")).toBe(true); + expect(translate.hasTranslation("a.c", "en")).toBe(false); + expect(translate.hasTranslation("a.c", "de")).toBe(true); + + expect(translate.hasTranslation("a.b")).toBe(true); + expect(translate.hasTranslation("a.c")).toBe(false); + }); + it("translates text using 'default' translation Id", () => { translations = { default: "Default text" }; translate.use("en");