Tsuji 5.0.0
Migrating
Taihei Tsuji 5.0.0 is the successor of dev.taihei::taihei-i18n 4.x under a new name, and a major release. Moving to it is a change of coordinates and imports first, and then a move to typed message families, one family at a time.
What changes
- The dependency is
dev.taihei::taihei-tsuji(anddev.taihei::taihei-tsuji-tools), where it wasdev.taihei::taihei-i18n. - The package is
dev.taihei.tsuji, where it wasdev.taihei.i18n. - A library ships its catalogue files under
META-INF/taihei-tsuji/. - The 4.x
Message, which carried every translation, is replaced by typed message families. The 4.x type stays, unchanged and deprecated, asdev.taihei.tsuji.legacy.Messageuntil 6.0.0.
The JSON of a message does not change, and neither do the keys of CoreMessages, so a taihei-i18n 4.x peer and a Taihei Tsuji 5 peer still exchange messages.
There is no taihei-i18n 5.0.0 and no relocation from the old coordinates. The two lines are different artifacts in different packages, so they can share a classpath: a library built against taihei-i18n 4.x keeps working beside Taihei Tsuji 5 with its own taihei-i18n jar. Its dev.taihei.i18n.Language and the dev.taihei.tsuji.Language of 5.x are different types, and cannot be passed for each other.
The 4.x line keeps its name and receives security fixes for 12 months after the release of Taihei Tsuji 5.0.0: see taihei-i18n 4.x.
Step 1: coordinates and imports
Change the dependency to dev.taihei::taihei-tsuji:5.0.0, then replace the package in every Scala file:
# every import of dev.taihei.i18n becomes dev.taihei.tsuji
grep -rlZ 'dev\.taihei\.i18n' --include='*.scala' . | xargs -0 perl -pi -e 's/dev\.taihei\.i18n/dev.taihei.tsuji/g'
Where 4.x messages are built, import the legacy Message explicitly. An explicit import wins over the wildcard's new Message. A 4.x file:
import dev.taihei.i18n.*
import dev.taihei.i18n.Language.*
object Account:
val saved = Message.with0Args(English -> "Saved", Dutch -> "Opgeslagen")
def hello(name: String): Message =
Message.withNamedArgs(English -> "Hello {$name}", Dutch -> "Hallo {$name}")("name" -> name)
becomes, after the line above and one added import:
import dev.taihei.tsuji.*
import dev.taihei.tsuji.Language.*
import dev.taihei.tsuji.legacy.Message // an explicit import wins over the wildcard
object Account:
val saved = Message.with0Args(English -> "Saved", Dutch -> "Opgeslagen")
def hello(name: String): Message =
Message.withNamedArgs(English -> "Hello {$name}", Dutch -> "Hallo {$name}")("name" -> name)
Code that only builds and translates 4.x messages then compiles, with deprecation warnings, and works as before. Legacy messages and new ones are both Displayable, so they are shown side by side. To silence the warnings while you migrate, add this compiler option:
-Wconf:cat=deprecation&origin=dev\.taihei\.tsuji\.legacy\..*:s
Code that uses Message.of, Language.label, CoreMessages, the MessageArg union or the MessageFormat 2.0 internals needs the changes in the table below already in this step.
Step 2: families, one at a time
Then move the messages to families. The same messages as a family:
import dev.taihei.tsuji.*
import dev.taihei.tsuji.Language.*
object Account extends MessageFamily("account"):
val saved = message("Saved", Dutch -> "Opgeslagen")
val hello = message[(name: String)]("Hello {$name}", Dutch -> "Hallo {$name}")
given Catalogues = Catalogues(Seq(Account))
Account.saved.localize(Dutch) // Opgeslagen
Account.hello(name = "Ada").localize(Dutch) // Hallo Ada
This is where the compiler starts to help: a template that names an argument it does not have, or formats a date as a number, no longer compiles. Do it family by family.
| 4.x | 5.x |
|---|---|
Message.with0Args(English -> "Saved", Dutch -> "Opgeslagen") | val saved = message("Saved", Dutch -> "Opgeslagen") in a MessageFamily |
Message.withNamedArgs(English -> "Hello {$name}")("name" -> n) | val hello = message[(name: String)]("Hello {$name}"), then hello(name = n) |
Message.with1Arg to with10Args and {$0} | named arguments: message[(count: Int, query: String)]("{$count} results for {$query}") |
message.translate(language), message.render(language) | message.localize(language), with a given Catalogues |
message.toString (English) | message.toString (the family's source language) |
Message.of(throwable) | Text.of(throwable) |
Message.of(string) | Text(string, Language.English) |
the union type MessageArg | the type class MessageArg[A]; the union is legacy.MessageArg |
MessageFormat2.Parser, its AST, Context | internal now: use MessageFormat2.format and MessageFormat2.validate |
PlatformNumberFormatter, PlatformDateTimeFormatter, PlatformLocales, PlatformLanguageNames | internal now: format through a template |
Language.label, a legacy message | Language.label: Message, shown in any reader's language |
CoreMessages.X.member(args) | the same names, with named, typed arguments |
CoreMessages.Exceptions.revisionMismatch(expected, actual) | CoreMessages.Exceptions.revisionMismatchBetween(expected, actual) |
CoreMessages.Validation.formatErrors(Map[String, List[Message]]) | formatErrors(Map[String, List[Displayable]]); each field error is shown in the reader's language |
| CoreMessages in Dutch and French on Scala.js, inline in 4.x | catalogue files now: export them (--scan dev.taihei.tsuji) and load them with CatalogueLoader, or a browser shows English |
the case class Message(args, namedArgs, translationTemplates), copy, its fields | only on legacy.Message |
message.nonEmpty | message.localize(language).nonEmpty |
given JsonCodec[Message], needing nothing | JsonEncoder[Message] as before; JsonDecoder and JsonCodec[Message] need a given Catalogues where they are summoned or derived |
!!unknown message!! | gone: the source language always has the key |
Language has the API of 4.x apart from label, which now returns the new Message.
JSON during a rollout
Stored or exchanged 4.x messages stay readable in 5.x, as legacy messages with the key legacy, within the bounds of taihei-i18n 4.0.2: at most 1000 templates and 1000 arguments, none of them, nor an argument name, longer than 65536 characters.
A message that a 5.x writer produces is not readable by a 4.x reader. During a rollout, upgrade the readers (the clients) first, or keep writing legacy messages until they have.
Coming from edo-i18n or genius-i18n
taihei-i18n 4.x has the API of com.edoframework::edo-i18n:3.0.0 under its new name, with the package dev.taihei.i18n where it was com.edoframework.i18n. Coming from fish.genius::genius-i18n:2.0.0, the package was genius.i18n. Move to the newest taihei-i18n 4.0.x first, then to Taihei Tsuji 5.0.0 as above. Since 3.0.0 the library needs Scala 3.9 or later, uses zio-json 1.1 instead of 0.9, and is licensed under the Apache License 2.0; releases up to 2.0.0 stay under the MIT License.
Do not stay on 2.0.0, 3.0.0 or 4.0.0: parsing a long language tag can overflow the stack in them (advisory GHSA-45w8-x6c8-w4hg). Do not stay on 4.0.1 either: decoding or formatting a Message read from untrusted JSON can use unbounded memory and CPU in it and every earlier release (advisory GHSA-6h29-v54g-8337). taihei-i18n 4.0.2 has both fixes.