NestboltNestbolt

@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-emitter

Register 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-set

Payload: TranslationHasBeenSetEvent

The event payload is an instance of TranslationHasBeenSetEvent with the following properties:

PropertyTypeDescription
entityanyThe entity instance on which the translation was set.
keystringThe translatable field name (e.g., "name", "description").
localestringThe locale that was set (e.g., "en", "ar").
oldValuestring | nullThe previous translation value, or null if there was no previous translation for this locale.
newValuestring | nullThe 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 call repo.save().
  • If you set multiple translations (via setTranslations or replaceTranslations), 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.