CP6.13: craft-libs#495 review — tr: Ignore words in UPPERCASE reaches Turkish's own check; a comma after de/da, ne or ya is fine; repeated numbers are no repeated words; table suggestions keep the word's case

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FypgCX8LsYYE8fsj4byBm2
This commit is contained in:
Martin BallandClaude Opus 5.5 committed 2026-11-02 00:27:37 +00:00
1 parent c5daa8d16c
commit cb5d7310ec
3 files changed
+41 -13

No files matched your search

+21 -4
View File
@@ -128,7 +128,7 @@ const ABBREVIATIONS: &[&str] = &[
const CONJUNCTIONS_NO_PRE_COMMA: &[&str] = &["ve", "veya", "yahut", "velev", "veyahut", "de", "da"];
/// Conjunctions that must not be followed by a comma.
const CONJUNCTIONS_NO_POST_COMMA: &[&str] = &["ve", "veya", "yahut", "veyahut", "hem", "ne", "ya", "gerek", "ister", "de", "da"];
const CONJUNCTIONS_NO_POST_COMMA: &[&str] = &["ve", "veya", "yahut", "veyahut", "hem", "gerek", "ister"];
/// Check grammar and punctuation in a Turkish text block.
pub fn check(text: &str) -> Vec<Issue> {
@@ -154,7 +154,7 @@ pub fn check_with(text: &str, capital_first: bool) -> Vec<Issue> {
let only_spaces = gap.chars().all(|c| c == ' ' || c == '\t');
let lower1 = w1.to_lowercase();
let lower2 = w2.to_lowercase();
if only_spaces && lower1 == lower2 && !ALLOWED_REPEATS.contains(&lower1.as_str()) {
if only_spaces && w1.chars().all(char::is_alphabetic) && lower1 == lower2 && !ALLOWED_REPEATS.contains(&lower1.as_str()) {
v.push(Issue {
start: pair[1].0,
end: pair[1].1,
@@ -395,10 +395,27 @@ mod tests {
assert_eq!(issues.len(), 1);
assert!(issues[0].message.contains("virgül konulmaz"));
let text2 = "Bana kalırsa bu mesele de, haklı olan taraf bizdik.";
let text2 = "Elma ve, armut.";
let issues2 = check(text2);
assert_eq!(issues2.len(), 1);
assert!(issues2[0].message.contains("'de' bağlacından sonra virgül konulmaz"));
assert!(issues2[0].message.contains("'ve' bağlacından sonra virgül konulmaz"));
}
/// `de`/`da`, `ne` and `ya` often take a comma after them in correct Turkish, so no mark
/// (craft-libs#488 review).
#[test]
fn a_comma_after_de_ne_or_ya_is_fine() {
for text in ["Ali de, Veli de geldi.", "Ne, sen mi geldin?", "Ya, bilmiyorum.", "Bu mesele de, haklı olan bizdik."] {
assert!(check(text).is_empty(), "{text}: {:?}", check(text));
}
}
/// Repeated numbers are not repeated words (craft-libs#488 review).
#[test]
fn repeated_numbers_are_not_flagged() {
for text in ["Skor 1 1 bitti.", "Toplam 100 100 değil.", "Oda A1 A1 değil."] {
assert!(check(text).is_empty(), "{text}: {:?}", check(text));
}
}
#[test]
+13 -6
View File
@@ -78,12 +78,8 @@ fn check(d: &TrDict, word: &str, opts: &crate::Options) -> bool {
}
let w = word.trim_matches(|c: char| !c.is_alphanumeric() && c != '\'' && c != '’');
let w = w.trim_matches(['\'', '’']);
let n = w.chars().count();
if n <= 1 || (opts.ignore_numbers && w.chars().any(|c| c.is_ascii_digit())) || !w.chars().any(char::is_alphabetic) {
return true;
}
// Check user dictionary first
if crate::in_user_dictionary(&suggest::turkish_to_lower(w)) || crate::in_user_dictionary(&w.to_lowercase()) {
// One letter, no letters, digits, capitals (Ignore words in UPPERCASE) or the user dictionary.
if crate::accepted_by_options(w, opts) || crate::in_user_dictionary(&suggest::turkish_to_lower(w)) {
return true;
}
@@ -161,4 +157,15 @@ mod tests {
assert!(crate::is_correct_in_with("2026", lang, &check_digits), "numbers alone stay correct");
assert!(is_correct("kitapp2"), "direct checks keep the defaults");
}
/// Ignore words in UPPERCASE reaches Turkish's own entry points too, so `NATO` and `TBMM` are
/// right by default and checked when the option is off (craft-libs#495 review).
#[test]
fn turkish_ignores_capitals_unless_told_otherwise() {
test_dict::install();
let check_caps = crate::Options { ignore_uppercase: false, ..crate::Options::default() };
assert!(is_correct("TBMMX") && is_correct_with_options("TBMMX", &crate::Options::default()));
assert!(!is_correct_with_options("TBMMX", &check_caps));
assert!(is_correct_with_options("KİTAP", &check_caps), "a word in Turkish capitals is still looked up");
}
}
+7 -3
View File
@@ -70,7 +70,7 @@ pub fn suggest_with(d: &TrDict, word: &str, max: usize) -> Vec<String> {
// 1. Check direct common replacement table
for &(wrong, right) in COMMON_REPLACEMENTS {
if lower == wrong && seen.insert(right.to_string()) {
suggestions.push(right.to_string());
suggestions.push(match_casing(word, right));
if suggestions.len() >= max {
return suggestions;
}
@@ -270,7 +270,8 @@ pub fn turkish_to_upper(s: &str) -> String {
/// Match original casing on suggestion.
fn match_casing(orig: &str, cand: &str) -> String {
if orig.chars().all(char::is_uppercase) {
let mut letters = orig.chars().filter(|c| c.is_alphabetic()).peekable();
if letters.peek().is_some() && letters.all(char::is_uppercase) {
turkish_to_upper(cand)
} else if orig.chars().next().is_some_and(char::is_uppercase) {
super::grammar::turkish_capitalize(cand)
@@ -329,7 +330,10 @@ mod tests {
fn two_words_get_only_the_tables_replacement() {
let d = TrDict::from_hunspell("SET UTF-8\n", "2\nbirkaç\nsonra\n").unwrap();
assert_eq!(suggest_with(&d, "bir kaç", 3), vec!["birkaç".to_string()]);
assert_eq!(suggest_with(&d, "Bir kaç", 3), vec!["birkaç".to_string()]);
// craft-libs#487 review: the table keeps the word's case.
assert_eq!(suggest_with(&d, "Bir kaç", 3), vec!["Birkaç".to_string()]);
assert_eq!(suggest_with(&d, "BİR KAÇ", 3), vec!["BİRKAÇ".to_string()]);
assert_eq!(suggest_with(&d, "Herşey", 3), vec!["Her şey".to_string()]);
assert!(suggest_with(&d, "sorna sorna", 3).is_empty());
}