Translation classes, utils, custom repositories and decorators for i18n in TypeORM.
NPM
npm install typeorm-translatable
Yarn
yarn add typeorm-translatable
This library is not supposed to be used for adding translation columns in the same table. Instead, a separate table is used for translation. For each source/target entity (table), a translation entity (table) must be created. The source entity has One-To-Many relation with the created translation entity. (e.g Post
has many PostTranslation
.)
import { TranslatableEntity, Translation } from 'typeorm-translatable';
import { PostTranslation } from './post-translation.entity';
import { Column, Entity, OneToMany, PrimaryGeneratedColumn } from 'typeorm';
@Entity()
export class Post extends TranslatableEntity<PostTranslation> {
@PrimaryGeneratedColumn({ type: 'bigint', unsigned: true })
id?: number;
@Column('varchar')
title?: string;
@Column('text')
body?: string;
@OneToMany(
() => PostTranslation,
postTranslation => postTranslation.source
)
translations?: Translation<PostTranslation>[] | undefined;
static translatableFields = new Set(['title', 'body']);
}
import { Post } from './post.entity';
import { TranslationEntity } from 'typeorm-translatable';
import { Column, Entity, ManyToOne } from 'typeorm';
@Entity()
export class PostTranslation extends TranslationEntity<Post> {
@Column('varchar')
title?: string;
@Column('text')
body?: string;
@ManyToOne(
() => Post,
post => post.translations
)
source?: Post | undefined;
}
For the target or source entity, extend TranslatableEntity
class, add translations
as one-to-many relation and add translatableFields
static property. translatableFields
must include fields that can be translated when using entity.translate
method or translateEntity
util function.
For the translation entity (table that has translations), extend TranslationEntity
class, define translatable columns and add source
as many-to-one relation.
Then when you retrieve data, left join translation entity.
let posts = await postRepository
.createQueryBuilder('post')
.leftJoinAndSelect(
'post.translations',
'translation',
`translation.locale = :locale`,
{
locale,
}
)
.getMany();
Output:
[
{
"id": "1",
"title": "This is a title",
"body": "This is a post",
"translations": [
{
"id": "1",
"locale": "my",
"title": "ခေါင်းစဉ်",
"body": "အကြောင်းအရာ"
}
]
}
]
For the above output, You may use entity.translate
method to replace title
and body
with translated values.
posts = posts.map(post => post.translate());
Output:
[
{
"id": "1",
"title": "ခေါင်းစဉ်",
"body": "အကြောင်းအရာ"
}
]
Use TranslationConfig
to configure default options. You can use TranslationConfig.use
many times (except for entitySuffix
).
await appDataSource.initialize();
TranslationConfig.use({
getLocale: () => 'my', // default locale that is used when translating, typically from req.locale or als.getStore().locale
getEntityManager: () => appDataSource.manager, // only needed for using with decorators
entitySuffix: 'Translation', // only needed for using with decorators
shouldDelete: true, // whether or not translations should be deleted after translation
shouldMutate: false, // whether or not given entity should be mutated after translation
});
This is rather overriding the repository instead of extending it. TranslatableRepository
will override the entityManager
of repository to left join translation entity automatically when doing select operations. *Important since entityManager
's createQueryBuilder
will be overridden, you need to create new entityManager
. You can reuse this entityManager
for all repositories with translation. But not the repositories without translation. Not doing so will result in issues like this .
const postManager = appDataSource.createEntityManager();
let postRepository = postManager.getRepository(Post);
postRepository = postRepository.extend(
TranslatableRepository(postManager)
);
Then when you query entities, just use normal repository methods.
let posts = await postRepository.find();
This will output the same result as above. Also need to use entity.translate
method for translation.
TranslatableRepository.query
method will not left join. It will just execute the given query and return the result.
You still need to extends the above mentioned classes.
Classes decorated with Translatable
wil generate a translation entity automatically. Class properties decorated with TranslatableColumn
wil generate a column in the generated translation entity. Class property decorated with Translations
will have one-to-many relation with the generated translation entity.
import {
Translatable,
TranslatableColumn,
TranslatableEntity,
Translation,
TranslationEntity,
Translations,
} from 'typeorm-translatable';
import { Column, Entity, PrimaryGeneratedColumn, ObjectLiteral } from 'typeorm';
@Translatable()
@Entity()
export class PostWithDecorators extends TranslatableEntity<
TranslationEntity<ObjectLiteral>
> {
@PrimaryGeneratedColumn({ type: 'bigint', unsigned: true })
id?: number;
@TranslatableColumn()
@Column('varchar')
title?: string;
@TranslatableColumn()
@Column('text')
body?: string;
@Translations()
translations?: Translation<TranslationEntity<ObjectLiteral>>[] | undefined;
static translatableFields = new Set(['title', 'body']);
}
Then translation entities need to be generated and need to be added into TypeORM dataSourceOptions.entities
. Use TranslationConfig.generate
method to generate and get translation entities.
entities: [
PostWithDecorators,
...TranslationConfig.generate(),
],
You can access the generated translation entity by using getTranslationEntity
method.
const PostTranslation = TranslationConfig.getTranslationEntity(
PostWithDecorators
);
See more examples here.
Coming soon..., please read the source code for now :)
- Inspired by framework Vendure