Files
msime-windows/server/tests/src/test_candidate_translation_policy.cpp
T
Lu Fan(SonnyCalcr) 5e03628a7e feat(installer): build cn-en translations into the local dictionary (#784)
* fix(server): drop part-of-speech labels from Ctrl+Enter translation senses

The machine-translated Chinese-to-English glosses prefix each sense with a
label ("n. silver medal", "proper n. Wanshan District"). The candidate
window keeps showing the whole gloss, but splitting it for Ctrl+Enter now
strips the label so it never reaches the committed text.

* feat(installer): build cn-en translations into the local dictionary

build-dictionary.py gains a cn-en-translations stage that loads
sources/cn-en/machine-translations.txt from msime-dictionary into
zh_en_glosses verbatim, keeping every sense instead of the two the ECDICT
derivation keeps. It runs after english-glosses and before
custom-translations, so it overrides ECDICT and custom/translations.txt
still overrides it.

Local full test packages now always build the dictionary from the sibling
msime-dictionary checkout instead of downloading the product-lock release:
Invoke-LocalTest.ps1 and prepare-package-inputs.ps1 call
build-dictionary.py --if-stale, which skips only when out/ was built from
the current clean source commit by the current builder. CI and releases
still take the dictionary from product-lock.json.
2026-10-10 21:17:03 +08:00

77 lines
3.7 KiB
C++
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#include "ipc/candidate_translation_policy.h"
#include "tests/includes/test_framework.h"
TEST_CASE(ctrl_enter_alone_commits_the_secondary_candidate)
{
using FanyImeIpc::IsTranslationCommitKey;
REQUIRE(IsTranslationCommitKey(0x0D, FanyImeIpc::kModifierControl));
// The UI-less flag lives outside the modifier mask and must not disarm the shortcut.
REQUIRE(IsTranslationCommitKey(0x0D, FanyImeIpc::kModifierControl | FanyImeIpc::kModifierUiLess));
REQUIRE(!IsTranslationCommitKey(0x0D, 0));
REQUIRE(!IsTranslationCommitKey(0x0D, FanyImeIpc::kModifierControl | FanyImeIpc::kModifierShift));
REQUIRE(!IsTranslationCommitKey(0x0D, FanyImeIpc::kModifierControl | FanyImeIpc::kModifierAlt));
REQUIRE(!IsTranslationCommitKey(0x20, FanyImeIpc::kModifierControl));
REQUIRE(!IsTranslationCommitKey('J', FanyImeIpc::kModifierControl));
}
TEST_CASE(translation_gloss_splits_on_both_semicolon_widths)
{
using FanyImeIpc::SplitTranslationGloss;
// en->zh glosses join senses with the fullwidth semicolon.
const auto apple = SplitTranslationGloss("苹果;家伙;[计]苹果公司");
REQUIRE_EQ(apple.size(), static_cast<size_t>(3));
REQUIRE_EQ(apple[0], std::string("苹果"));
REQUIRE_EQ(apple[1], std::string("家伙"));
REQUIRE_EQ(apple[2], std::string("[计]苹果公司"));
// zh->en glosses use "; " and the trailing space must not survive.
const auto chuan = SplitTranslationGloss("string; trail; chuan");
REQUIRE_EQ(chuan.size(), static_cast<size_t>(3));
REQUIRE_EQ(chuan[0], std::string("string"));
REQUIRE_EQ(chuan[1], std::string("trail"));
REQUIRE_EQ(chuan[2], std::string("chuan"));
}
TEST_CASE(translation_gloss_drops_part_of_speech_labels)
{
using FanyImeIpc::SplitTranslationGloss;
// Machine-translated zh->en glosses label each sense; every sense is kept.
const auto laugh = SplitTranslationGloss("adj. spreading; n. sound of laughter; v. giggle; idiom. burst out");
REQUIRE_EQ(laugh.size(), static_cast<size_t>(4));
REQUIRE_EQ(laugh[0], std::string("spreading"));
REQUIRE_EQ(laugh[1], std::string("sound of laughter"));
REQUIRE_EQ(laugh[2], std::string("giggle"));
REQUIRE_EQ(laugh[3], std::string("burst out"));
const auto place = SplitTranslationGloss("proper n. Wanshan District");
REQUIRE_EQ(place.size(), static_cast<size_t>(1));
REQUIRE_EQ(place[0], std::string("Wanshan District"));
// Unlabelled senses, and words that merely start like a label, are untouched.
const auto stoop = SplitTranslationGloss("v. stoop; crouch");
REQUIRE_EQ(stoop[1], std::string("crouch"));
const auto peculiar = SplitTranslationGloss("very peculiar");
REQUIRE_EQ(peculiar[0], std::string("very peculiar"));
const auto bare = SplitTranslationGloss("n.");
REQUIRE_EQ(bare[0], std::string("n."));
}
TEST_CASE(translation_gloss_keeps_a_single_sense_whole)
{
using FanyImeIpc::SplitTranslationGloss;
// A cloud gloss is one sentence: commas inside it are not sense separators.
const auto cloud = SplitTranslationGloss("今天天气不错,我们出去走走");
REQUIRE_EQ(cloud.size(), static_cast<size_t>(1));
REQUIRE_EQ(cloud[0], std::string("今天天气不错,我们出去走走"));
REQUIRE(SplitTranslationGloss("").empty());
REQUIRE(SplitTranslationGloss(" ").empty());
// Empty senses are dropped rather than becoming blank candidates.
REQUIRE_EQ(SplitTranslationGloss(";;").size(), static_cast<size_t>(0));
const auto ragged = SplitTranslationGloss(";apple; ;pear;");
REQUIRE_EQ(ragged.size(), static_cast<size_t>(2));
REQUIRE_EQ(ragged[0], std::string("apple"));
REQUIRE_EQ(ragged[1], std::string("pear"));
}