Hibernate I18n
Typed, locale-bound entity fields backed by relational translations.
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.
What it does.
Ordinary entity fields
Mark String fields or converted values with @Translation; the runtime mapping stores their values per locale.
Locale-bound queries
Load, filter, order and page by translated values with a fixed Session locale and field-level fallback.
Translation editor
Read and write exact inactive locales through a typed handle, with tenant isolation and optimistic row versions.
Installation
libraryDependencies += "com.anjunar" % "hibernate-i18n" % "1.0.0"
Package: com.anjunar:hibernate-i18n
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.
import com.anjunar.hibernatei18n.annotation.{Localized, Translation} import jakarta.persistence.{Entity, Id, Table} import java.util.UUID import scala.compiletime.uninitialized (name = "page") (defaultLocale = "en", fallbackLocale = "en") class Page: var id: UUID = uninitialized 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.
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()
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.