All frameworks
ANJUNAR / OPEN SOURCE

Hibernate I18n

Typed, locale-bound entity fields backed by relational translations.

01

The idea

Hibernate I18n keeps translated content in separate rows while entity fields retain their ordinary types. The 1.0.0 runtime fixes a content locale per Session and supports translated queries, fallback and typed editor access for Hibernate 7.4.10.Final and PostgreSQL.

02

What it does.

01

Ordinary entity fields

Mark String fields or converted values with @Translation; the runtime mapping stores their values per locale.

02

Locale-bound queries

Load, filter, order and page by translated values with a fixed Session locale and field-level fallback.

03

Translation editor

Read and write exact inactive locales through a typed handle, with tenant isolation and optimistic row versions.

03

Installation

Add to your project
libraryDependencies += "com.anjunar" % "hibernate-i18n" % "1.0.0"

Package: com.anjunar:hibernate-i18n

04

Start building.

Add hibernate-i18n 1.0.0 from Maven Central, bootstrap with HibernateI18n.registryBuilder(), map a @Localized entity and install every @Translation field bridge before opening a localized Session. The guide covers schema startup and editor access.

Typed fields, translated rows

The entity keeps a String title. The runtime maps its values into locale-specific rows rather than a title column on the parent.

Source code
import com.anjunar.hibernatei18n.annotation.{Localized, Translation}
import jakarta.persistence.{Entity, Id, Table}
import java.util.UUID
import scala.compiletime.uninitialized

@Entity
@Table(name = "page")
@Localized(defaultLocale = "en", fallbackLocale = "en")
class Page:
  @Id var id: UUID = uninitialized
  @Translation var title: String = uninitialized

An exact-locale editor

After runtime bootstrap, install the title bridge once. Edit English through the handle while the managed entity retains its German Session value.

Source code
import com.anjunar.hibernatei18n.runtime.{TranslationField, HibernateI18n}

val titleField = TranslationField.string[Page]("title", _.title)
val translations = HibernateI18n.install(
  factory, classOf[Page], _.id, Seq(titleField))

val session = HibernateI18n.openSession(factory, "de")
try
  val tx = session.beginTransaction()
  try
    val page = session.find(classOf[Page], pageId)
    val english = translations.get(session, page, titleField, "en")
    translations.set(session, page, titleField, "en", "Hello")
    tx.commit()
  catch
    case error: Throwable =>
      if tx.isActive then tx.rollback()
      throw error
finally session.close()
05

By design.

One Session owns one content locale. Domain properties apply exact-locale, language, configured fallback and default-locale lookup per field; the editor reads exact overrides. The runtime rejects detached merge, replicate and query caching. Its bootstrap guard refuses incomplete localized mappings.

Your next step

Make it part of your stack.

Built carefully with Scala JS UI.Imprint