Repository: fniessink/toisto (language: python)

Issue:
Support nouns where the plural and singular are the same
For example, "means of transportation", "sheep", "jeans"


Discussion / hints:

Fix patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index f9810193..39859c33 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -4,6 +4,12 @@ All notable changes to Toisto will be documented in this file.
 
 The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/) and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
 
+## [Unreleased]
+
+### Fixed
+
+- Some quizzes would not be created if nouns are invariant in one language, but not in another (like "sheep" or "means of transportation"). Fixes [#438](https://github.com/fniessink/toisto/issues/438).
+
 ## 0.22.0 - 2024-07-29
 
 ### Fixed


Test patch:
diff --git a/tests/base.py b/tests/base.py
index 51f953b7..e07e5268 100644
--- a/tests/base.py
+++ b/tests/base.py
@@ -90,3 +90,32 @@ def question_language(language_pair: LanguagePair, quiz_types: tuple[QuizType, .
     def answer_language(language_pair: LanguagePair, quiz_types: tuple[QuizType, ...]) -> Language:
         """Return the answer language for the quiz types, given the target and source languages."""
         return language_pair.source if {"interpret", "read"} & set(quiz_types) else language_pair.target
+
+    def create_noun_invariant_in_english(self) -> Concept:
+        """Return a concept that is composite in Dutch and not composite in English."""
+        return self.create_concept(
+            "means of transportation",
+            dict(
+                en="means of transportation",
+                singular=dict(nl="het vervoersmiddel"),
+                plural=dict(nl="de vervoersmiddelen"),
+            ),
+        )
+
+    def create_verb_with_grammatical_number_and_person(self) -> Concept:
+        """Create a verb with grammatical number nested with grammatical person."""
+        return self.create_concept(
+            "to have",
+            dict(
+                singular={
+                    "first person": dict(en="I have", fi="minulla on", nl="ik heb"),
+                    "second person": dict(en="you have", fi="sinulla on", nl="jij hebt"),
+                    "third person": dict(en="she has", fi="hänellä on", nl="zij heeft"),
+                },
+                plural={
+                    "first person": dict(en="we have", fi="meillä on", nl="wij hebben"),
+                    "second person": dict(en="you have", fi="teillä on", nl="jullie hebben"),
+                    "third person": dict(en="they have", fi="heillä on", nl="zij hebben"),
+                },
+            ),
+        )
diff --git a/tests/toisto/model/language/test_concept.py b/tests/toisto/model/language/test_concept.py
index 6d05419e..3638a33a 100644
--- a/tests/toisto/model/language/test_concept.py
+++ b/tests/toisto/model/language/test_concept.py
@@ -33,14 +33,7 @@ def test_instance_registry(self):
 
     def test_is_composite(self):
         """Test that a composite concept is composite, and a leaf concept is not."""
-        concept = self.create_concept(
-            "means of transportation",
-            dict(
-                en="means of transportation",
-                singular=dict(nl="het vervoersmiddel"),
-                plural=dict(nl="de vervoersmiddelen"),
-            ),
-        )
+        concept = self.create_noun_invariant_in_english()
         self.assertTrue(concept.is_composite(NL))
         self.assertFalse(concept.is_composite(EN))
         for constituent in concept.constituents:
@@ -76,24 +69,26 @@ def test_meaning_mixed_concept(self):
 
     def test_labels(self):
         """Test that the labels are returned, recursively."""
-        concept = self.create_concept(
-            "to have",
-            dict(
-                singular={
-                    "first person": dict(en="I have"),
-                    "second person": dict(en="you have"),
-                    "third person": dict(en="she has"),
-                },
-                plural={
-                    "first person": dict(en="we have"),
-                    "second person": dict(en="you have"),
-                    "third person": dict(en="they have"),
-                },
-            ),
-        )
+        concept = self.create_verb_with_grammatical_number_and_person()
         expected_labels = ("I have", "you have", "she has", "we have", "you have", "they have")
         self.assertEqual(Labels(Label(EN, label) for label in expected_labels), concept.labels(EN))
 
+    def test_labels_when_not_all_languages_have_the_same_grammatical_categories(self):
+        """Test that the labels are returned, recursively."""
+        concept = self.create_noun_invariant_in_english()
+        singular, plural = concept.constituents
+        for each_concept in concept, singular, plural:
+            self.assertEqual((Label(EN, "means of transportation"),), each_concept.labels(EN))
+        self.assertEqual((Label(NL, "het vervoersmiddel"),), singular.labels(NL))
+        self.assertEqual((Label(NL, "de vervoersmiddelen"),), plural.labels(NL))
+        self.assertEqual(
+            (
+                Label(NL, "het vervoersmiddel"),
+                Label(NL, "de vervoersmiddelen"),
+            ),
+            concept.labels(NL),
+        )
+
     def test_homographs(self):
         """Test that notes are ignored when determining homographs."""
         bank = self.create_concept("bank", {"nl": "de bank;;some note"})
diff --git a/tests/toisto/model/quiz/test_quiz.py b/tests/toisto/model/quiz/test_quiz.py
index dd7b6b04..4ebe86b7 100644
--- a/tests/toisto/model/quiz/test_quiz.py
+++ b/tests/toisto/model/quiz/test_quiz.py
@@ -251,17 +251,7 @@ def test_homographs_get_an_automatic_note_based_on_only_the_first_hypernym(self)
     def test_homographs_get_an_automatic_note_based_on_the_common_base_concept(self):
         """Test that homographs get an automatic note based on the common base concept."""
         self.language_pair = EN_NL
-        concept = self.create_concept(
-            "to have",
-            dict(
-                singular={
-                    "second person": dict(en="you have", nl="jij hebt"),
-                },
-                plural={
-                    "second person": dict(en="you have", nl="jullie hebben"),
-                },
-            ),
-        )
+        concept = self.create_verb_with_grammatical_number_and_person()
         second_person_singular = next(
             leaf_concept
             for leaf_concept in concept.leaf_concepts(EN)
diff --git a/tests/toisto/model/quiz/test_quiz_factory.py b/tests/toisto/model/quiz/test_quiz_factory.py
index 6d55e942..56b878a1 100644
--- a/tests/toisto/model/quiz/test_quiz_factory.py
+++ b/tests/toisto/model/quiz/test_quiz_factory.py
@@ -44,24 +44,6 @@ def create_verb_with_tense_and_person(self, *tense: Tense) -> Concept:
             }
         return self.create_concept("to eat", concept_dict)
 
-    def create_verb_with_number_and_person(self) -> Concept:
-        """Create a verb with grammatical number nested with grammatical person."""
-        return self.create_concept(
-            "to have",
-            dict(
-                singular={
-                    "first person": dict(fi="minulla on", nl="ik heb"),
-                    "second person": dict(fi="sinulla on", nl="jij hebt"),
-                    "third person": dict(fi="hänellä on", nl="zij heeft"),
-                },
-                plural={
-                    "first person": dict(fi="meillä on", nl="wij hebben"),
-                    "second person": dict(fi="teillä on", nl="jullie hebben"),
-                    "third person": dict(fi="heillä on", nl="zij hebben"),
-                },
-            ),
-        )
-
     def create_verb_with_infinitive_and_person(self) -> Concept:
         """Create a verb with infinitive and grammatical person."""
         return self.create_concept(
@@ -242,14 +224,7 @@ def test_grammatical_number_without_plural(self):
     def test_grammatical_number_in_target_language_not_in_source_language(self):
         """Test that quizzes can be generated even if one language has no grammatical number for the concept."""
         self.language_pair = NL_EN
-        concept = self.create_concept(
-            "means of transportation",
-            dict(
-                en="means of transportation",
-                singular=dict(nl="het vervoersmiddel"),
-                plural=dict(nl="de vervoersmiddelen"),
-            ),
-        )
+        concept = self.create_noun_invariant_in_english()
         singular, plural = concept.leaf_concepts(NL)
         quizzes = create_quizzes(NL_EN, concept)
         self.assertSetEqual(
@@ -273,14 +248,7 @@ def test_grammatical_number_in_target_language_not_in_source_language(self):
     def test_grammatical_number_in_source_language_not_in_target_language(self):
         """Test that quizzes can be generated even if one language has no grammatical number for the concept."""
         self.language_pair = EN_NL
-        concept = self.create_concept(
-            "means of transportation",
-            dict(
-                en="means of transportation",
-                singular=dict(nl="het vervoersmiddel"),
-                plural=dict(nl="de vervoersmiddelen"),
-            ),
-        )
+        concept = self.create_noun_invariant_in_english()
         quizzes = create_quizzes(EN_NL, concept)
         self.assertSetEqual(
             {
@@ -639,7 +607,7 @@ def test_grammatical_person_nested_with_grammatical_gender_in_one_language_but_n
     def test_grammatical_number_nested_with_grammatical_person(self):
         """Test that quizzes can be generated for grammatical number, nested with grammatical person."""
         self.language_pair = NL_FI
-        concept = self.create_verb_with_number_and_person()
+        concept = self.create_verb_with_grammatical_number_and_person()
         singular, plural = concept.constituents
         first_person_singular, second_person_singular, third_person_singular = singular.constituents
         first_person_plural, second_person_plural, third_person_plural = plural.constituents
@@ -1584,7 +1552,7 @@ def test_verb_with_infinitive_and_person(self):
 
     def test_verb_with_person_and_number(self):
         """Test the grammatical quiz types for a verb with grammatical person and number."""
-        verb = self.create_verb_with_number_and_person()
+        verb = self.create_verb_with_grammatical_number_and_person()
         (
             first_singular,
             second_singular,