Merge pull request #108862 from timothyqiu/plural-rules

Move context and plural support to `Translation`
This commit is contained in:
Thaddeus Crews 2025-10-15 16:31:07 -05:00
commit 49219de402
No known key found for this signature in database
GPG key ID: 8C6E5FEB5FC03CCC
15 changed files with 678 additions and 478 deletions

View file

@ -31,8 +31,8 @@
#pragma once
#include "core/string/optimized_translation.h"
#include "core/string/plural_rules.h"
#include "core/string/translation.h"
#include "core/string/translation_po.h"
#include "core/string/translation_server.h"
#ifdef TOOLS_ENABLED
@ -45,7 +45,8 @@
namespace TestTranslation {
TEST_CASE("[Translation] Messages") {
Ref<Translation> translation = memnew(Translation);
Ref<Translation> translation;
translation.instantiate();
translation->set_locale("fr");
translation->add_message("Hello", "Bonjour");
CHECK(translation->get_message("Hello") == "Bonjour");
@ -70,8 +71,9 @@ TEST_CASE("[Translation] Messages") {
CHECK(messages.find("Hello3"));
}
TEST_CASE("[TranslationPO] Messages with context") {
Ref<TranslationPO> translation = memnew(TranslationPO);
TEST_CASE("[Translation] Messages with context") {
Ref<Translation> translation;
translation.instantiate();
translation->set_locale("fr");
translation->add_message("Hello", "Bonjour");
translation->add_message("Hello", "Salut", "friendly");
@ -89,11 +91,8 @@ TEST_CASE("[TranslationPO] Messages with context") {
List<StringName> messages;
translation->get_message_list(&messages);
// `get_message_count()` takes all contexts into account.
CHECK(translation->get_message_count() == 1);
// Only the default context is taken into account.
// Since "Hello" is now only present in a non-default context, it is not counted in the list of messages.
CHECK(messages.size() == 0);
CHECK(messages.size() == 1);
translation->add_message("Hello2", "Bonjour2");
translation->add_message("Hello2", "Salut2", "friendly");
@ -101,32 +100,96 @@ TEST_CASE("[TranslationPO] Messages with context") {
messages.clear();
translation->get_message_list(&messages);
// `get_message_count()` takes all contexts into account.
CHECK(translation->get_message_count() == 4);
// Only the default context is taken into account.
CHECK(messages.size() == 2);
CHECK(messages.size() == 4);
// Messages are stored in a Map, don't assume ordering.
CHECK(messages.find("Hello2"));
CHECK(messages.find("Hello3"));
// Context and untranslated string are separated by EOT.
CHECK(messages.find("friendly\x04Hello2"));
}
TEST_CASE("[TranslationPO] Plural messages") {
Ref<TranslationPO> translation = memnew(TranslationPO);
translation->set_locale("fr");
translation->set_plural_rule("Plural-Forms: nplurals=2; plural=(n >= 2);");
CHECK(translation->get_plural_forms() == 2);
TEST_CASE("[Translation] Plural messages") {
{
Ref<Translation> translation;
translation.instantiate();
translation->set_locale("fr");
CHECK(translation->get_nplurals() == 3);
}
PackedStringArray plurals;
plurals.push_back("Il y a %d pomme");
plurals.push_back("Il y a %d pommes");
translation->add_plural_message("There are %d apples", plurals);
{
Ref<Translation> translation;
translation.instantiate();
translation->set_locale("invalid");
CHECK(translation->get_nplurals() == 2);
}
{
Ref<Translation> translation;
translation.instantiate();
translation->set_plural_rules_override("Plural-Forms: nplurals=2; plural=(n >= 2);");
CHECK(translation->get_nplurals() == 2);
PackedStringArray plurals;
plurals.push_back("Il y a %d pomme");
plurals.push_back("Il y a %d pommes");
translation->add_plural_message("There are %d apples", plurals);
ERR_PRINT_OFF;
// This is invalid, as the number passed to `get_plural_message()` may not be negative.
CHECK(vformat(translation->get_plural_message("There are %d apples", "", -1), -1) == "");
ERR_PRINT_ON;
CHECK(vformat(translation->get_plural_message("There are %d apples", "", 0), 0) == "Il y a 0 pomme");
CHECK(vformat(translation->get_plural_message("There are %d apples", "", 1), 1) == "Il y a 1 pomme");
CHECK(vformat(translation->get_plural_message("There are %d apples", "", 2), 2) == "Il y a 2 pommes");
}
}
TEST_CASE("[Translation] Plural rules parsing") {
ERR_PRINT_OFF;
// This is invalid, as the number passed to `get_plural_message()` may not be negative.
CHECK(vformat(translation->get_plural_message("There are %d apples", "", -1), -1) == "");
{
CHECK(PluralRules::parse("") == nullptr);
CHECK(PluralRules::parse("plurals=(n != 1);") == nullptr);
CHECK(PluralRules::parse("nplurals; plurals=(n != 1);") == nullptr);
CHECK(PluralRules::parse("nplurals=; plurals=(n != 1);") == nullptr);
CHECK(PluralRules::parse("nplurals=0; plurals=(n != 1);") == nullptr);
CHECK(PluralRules::parse("nplurals=-1; plurals=(n != 1);") == nullptr);
CHECK(PluralRules::parse("nplurals=2;") == nullptr);
CHECK(PluralRules::parse("nplurals=2; plurals;") == nullptr);
CHECK(PluralRules::parse("nplurals=2; plurals=;") == nullptr);
}
ERR_PRINT_ON;
CHECK(vformat(translation->get_plural_message("There are %d apples", "", 0), 0) == "Il y a 0 pomme");
CHECK(vformat(translation->get_plural_message("There are %d apples", "", 1), 1) == "Il y a 1 pomme");
CHECK(vformat(translation->get_plural_message("There are %d apples", "", 2), 2) == "Il y a 2 pommes");
{
PluralRules *pr = PluralRules::parse("nplurals=3; plural=(n==0 ? 0 : n==1 ? 1 : 2);");
REQUIRE(pr != nullptr);
CHECK(pr->get_nplurals() == 3);
CHECK(pr->get_plural() == "(n==0 ? 0 : n==1 ? 1 : 2)");
CHECK(pr->evaluate(0) == 0);
CHECK(pr->evaluate(1) == 1);
CHECK(pr->evaluate(2) == 2);
CHECK(pr->evaluate(3) == 2);
memdelete(pr);
}
{
PluralRules *pr = PluralRules::parse("nplurals=1; plural=0;");
REQUIRE(pr != nullptr);
CHECK(pr->get_nplurals() == 1);
CHECK(pr->get_plural() == "0");
CHECK(pr->evaluate(0) == 0);
CHECK(pr->evaluate(1) == 0);
CHECK(pr->evaluate(2) == 0);
CHECK(pr->evaluate(3) == 0);
memdelete(pr);
}
}
#ifdef TOOLS_ENABLED