@nestbolt/translatable
Events
Listen for translation change events with @nestjs/event-emitter -- audit logs, cache invalidation, and search index updates.
When @nestjs/event-emitter is installed and configured, @nestbolt/translatable emits events whenever a translation value is set on an entity. This enables you to build audit trails, invalidate caches, update search indexes, or trigger any other side effect when translations change.
Prerequisites
Install @nestjs/event-emitter as an optional peer dependency:
npm install @nestjs/event-emitterRegister the EventEmitterModule in your application:
import { Module } from "@nestjs/common";
import { EventEmitterModule } from "@nestjs/event-emitter";
import { TranslatableModule } from "@nestbolt/translatable";
@Module({
imports: [
EventEmitterModule.forRoot(),
TranslatableModule.forRoot({
defaultLocale: "en",
fallbackLocales: ["en"],
}),
],
})
export class AppModule {}The TranslatableService automatically detects the EventEmitter2 provider and uses it to emit events. If @nestjs/event-emitter is not installed, no events are emitted and there is no runtime cost.
translatable.translation-set
This event is emitted every time setTranslation is called on a translatable entity. Since setTranslations and replaceTranslations call setTranslation internally, they also trigger this event for each individual translation.
Event Name
translatable.translation-setPayload: TranslationHasBeenSetEvent
The event payload is an instance of TranslationHasBeenSetEvent with the following properties:
| Property | Type | Description |
|---|---|---|
entity | any | The entity instance on which the translation was set. |
key | string | The translatable field name (e.g., "name", "description"). |
locale | string | The locale that was set (e.g., "en", "ar"). |
oldValue | string | null | The previous translation value, or null if there was no previous translation for this locale. |
newValue | string | null | The new translation value, or null if the translation was removed. |
Event Class
import { TranslationHasBeenSetEvent } from "@nestbolt/translatable";The class definition:
class TranslationHasBeenSetEvent {
constructor(
public readonly entity: any,
public readonly key: string,
public readonly locale: string,
public readonly oldValue: string | null,
public readonly newValue: string | null,
) {}
}Listening for Events
Use the @OnEvent() decorator from @nestjs/event-emitter to listen for translation events:
import { Injectable } from "@nestjs/common";
import { OnEvent } from "@nestjs/event-emitter";
import { TranslationHasBeenSetEvent } from "@nestbolt/translatable";
@Injectable()
export class TranslationListener {
@OnEvent("translatable.translation-set")
handleTranslationSet(event: TranslationHasBeenSetEvent) {
console.log(
`Translation changed: ${event.key}[${event.locale}]: "${event.oldValue}" -> "${event.newValue}"`,
);
}
}Make sure the listener class is registered as a provider in a module:
@Module({
providers: [TranslationListener],
})
export class AppModule {}Use Cases
Audit Logging
Track all translation changes for compliance or debugging:
@Injectable()
export class TranslationAuditListener {
constructor(
@InjectRepository(AuditLog)
private readonly auditRepo: Repository<AuditLog>,
) {}
@OnEvent("translatable.translation-set")
async logTranslationChange(event: TranslationHasBeenSetEvent) {
const log = new AuditLog();
log.action = "translation.updated";
log.entityType = event.entity.constructor.name;
log.entityId = event.entity.id;
log.field = event.key;
log.locale = event.locale;
log.oldValue = event.oldValue;
log.newValue = event.newValue;
await this.auditRepo.save(log);
}
}Cache Invalidation
Invalidate cached translations when they change:
@Injectable()
export class TranslationCacheListener {
constructor(private readonly cacheManager: Cache) {}
@OnEvent("translatable.translation-set")
async invalidateCache(event: TranslationHasBeenSetEvent) {
const entityType = event.entity.constructor.name.toLowerCase();
const entityId = event.entity.id;
// Invalidate locale-specific cache
await this.cacheManager.del(`${entityType}:${entityId}:${event.locale}`);
// Invalidate the general entity cache
await this.cacheManager.del(`${entityType}:${entityId}`);
}
}Search Index Updates
Update a search index when translations change:
@Injectable()
export class TranslationSearchListener {
constructor(private readonly searchService: SearchService) {}
@OnEvent("translatable.translation-set")
async updateSearchIndex(event: TranslationHasBeenSetEvent) {
if (event.newValue === null) {
// Translation was removed
await this.searchService.removeLocaleEntry(
event.entity.constructor.name,
event.entity.id,
event.key,
event.locale,
);
} else {
// Translation was added or updated
await this.searchService.indexTranslation(
event.entity.constructor.name,
event.entity.id,
event.key,
event.locale,
event.newValue,
);
}
}
}Event Timing
Events are emitted synchronously during the setTranslation call, before the entity is persisted to the database. This means:
- The event fires when you call
setTranslation(), not when you callrepo.save(). - If you set multiple translations (via
setTranslationsorreplaceTranslations), one event is emitted per locale. - If the save operation later fails, the events will have already been emitted.
For example, calling setTranslations with three locales emits three separate events:
product.setTranslations("name", {
en: "Laptop",
ar: "حاسوب محمول",
fr: "Ordinateur portable",
});
// Emits 3 events: one for "en", one for "ar", one for "fr"Without Event Emitter
If @nestjs/event-emitter is not installed, the TranslatableService simply skips event emission. There is no error, no warning, and no performance overhead. The setTranslation method works identically in both cases -- the only difference is whether events are emitted.