Repository: fniessink/toisto (language: python)

Issue:
Support abbreviations
For example:

```json
{
    "llc": {
        "full form": {
            "en": "limited liability company",
            "fi": "osakeyhtiö",
            "nl": "naamloze vennootschap"
        },
        "abbreviation": {
            "en": "LLC",
            "fi": "oy",
            "nl": "NV"
        }
    }
}
```


Discussion / hints:

Fix patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 4d39d716..9ca1c889 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]
+
+### Added
+
+- Support abbreviations. Closes [#498](https://github.com/fniessink/toisto/issues/498).
+
 ## 0.14.1 - 2023-12-09
 
 ### Fixed
diff --git a/README.md b/README.md
index 8ee9598d..c0bbc0ff 100644
--- a/README.md
+++ b/README.md
@@ -130,7 +130,7 @@ oranje
 
 Toisto quizzes you repeatably on words and phrases in the language you want to practice, your target language. For each quiz, Toisto keeps track of how long you answer it correctly. When you answer a quiz correctly multiple times, Toisto will silence the quiz for a while. The longer the time you have answered the quiz correctly, the longer a quiz is silenced. This starts at a few minutes, but then increases rapidly when you keep answering correctly.
 
-At the moment, Toisto has the following types of quizzes:
+Toisto supports quiz types such as:
 
 - **Translate** a word or phrase from your target language to your source language or the other way around. For example, if your native language is English and you're practicing Dutch, Toisto can ask you to give the English version of "maandag" (which is, you guessed it, "Monday") or ask you to give the Dutch version of "Friday" (which is "vrijdag").
 - **Listen** to a word or phrase from your target language and type what you hear either in your target language or your source language. For example, if your target language is Finnish, Toisto may say "Tänään on maanantai" and then you have to type ether "Tänään on maanantai" or "Today is Monday".
@@ -143,6 +143,8 @@ At the moment, Toisto has the following types of quizzes:
 - Change the **sentence type** of declarative sentences into interrogative sentences and vice versa. For example, what is the interrogative form of "The car is black"? The answer would be "Is the car black?".
 - **Answer a question**. For example, a question in Finnish could be "Pidätko sinä jäätelöstä?" (meaning "Do you like ice cream?") and correct answers would be "Pidän" (meaning "Yes, I do") and "En" (meaning "No, I don't").
 
+See [the documentation](docs/software.md#quizzes) for the complete list of quiz types.
+
 When you stop the program (hit Ctrl-C or Ctrl-D), progress is saved in a file named `.toisto-progress-{target language}.json` in your home folder, for example `.toisto-progress-fi.json`.
 
 ## Further documentation
diff --git a/docs/software.md b/docs/software.md
index 9a257a8c..635241b3 100644
--- a/docs/software.md
+++ b/docs/software.md
@@ -526,6 +526,27 @@ Cardinal and ordinal numbers can be specified as follows:
 }
 ```
 
+### Abbreviations
+
+Abbreviations can be specified as follows:
+
+```json
+{
+    "llc": {
+        "full form": {
+            "en": "limited liability company",
+            "fi": "osakeyhtiö",
+            "nl": "de naamloze vennootschap"
+        },
+        "abbreviation": {
+            "en": "LLC",
+            "fi": "oy",
+            "nl": "de NV"
+        }
+    }
+}
+```
+
 ### Concept relationships
 
 #### Compound concepts
@@ -727,6 +748,7 @@ Toisto uses the concepts to generate quizzes. Currently, the following types of
 13. Quizzes to change cardinal numbers into ordinal numbers and vice versa.
 14. Quizzes to give the antonym.
 15. Quizzes to answer a question.
+16. Quizzes to abbreviate a concept or give the full-form of the abbreviation.
 
 Except when asking the user to translate from the source language to the target language, quizzes only use the user's target language.
 
diff --git a/src/concepts/work.json b/src/concepts/work.json
index 4672c826..0fba1670 100644
--- a/src/concepts/work.json
+++ b/src/concepts/work.json
@@ -458,5 +458,17 @@
         "en": "packed lunch",
         "fi": "eväät",
         "nl": "het lunchpakket"
+    },
+    "llc": {
+        "full form": {
+            "en": "limited liability company",
+            "fi": "osakeyhtiö",
+            "nl": "de naamloze vennootschap"
+        },
+        "abbreviation": {
+            "en": "LLC",
+            "fi": "oy",
+            "nl": "de NV"
+        }
     }
 }
\ No newline at end of file
diff --git a/src/toisto/model/language/grammar.py b/src/toisto/model/language/grammar.py
index 12536229..2e64235d 100644
--- a/src/toisto/model/language/grammar.py
+++ b/src/toisto/model/language/grammar.py
@@ -11,6 +11,7 @@
 SentenceForm = Literal["declarative", "interrogative"]
 GrammaticalPolarity = Literal["affirmative", "negative"]
 Number = Literal["cardinal", "ordinal"]
+Abbreviation = Literal["abbreviation", "full form"]
 GrammaticalCategory = Literal[
     GrammaticalGender,
     GrammaticalNumber,
@@ -21,4 +22,5 @@
     SentenceForm,
     GrammaticalPolarity,
     Number,
+    Abbreviation,
 ]
diff --git a/src/toisto/model/quiz/quiz.py b/src/toisto/model/quiz/quiz.py
index ea2f85e8..120f07c2 100644
--- a/src/toisto/model/quiz/quiz.py
+++ b/src/toisto/model/quiz/quiz.py
@@ -40,6 +40,8 @@
     "negate",
     "make cardinal",
     "make ordinal",
+    "abbreviate",
+    "give full form",
 ]
 QuizType = Literal[TranslationQuizType, ListenQuizType, SemanticQuizType, GrammaticalQuizType]
 GRAMMATICAL_QUIZ_TYPES: Final[dict[GrammaticalCategory, GrammaticalQuizType]] = {
@@ -64,6 +66,8 @@
     "negative": "negate",
     "cardinal": "make cardinal",
     "ordinal": "make ordinal",
+    "abbreviation": "abbreviate",
+    "full form": "give full form",
 }
 QUIZ_TYPE_GRAMMATICAL_CATEGORIES: Final = {value: key for key, value in GRAMMATICAL_QUIZ_TYPES.items()}
 INSTRUCTIONS: Final[dict[Literal[TranslationQuizType, ListenQuizType, SemanticQuizType], str]] = {


Test patch:
diff --git a/tests/toisto/model/quiz/test_quiz.py b/tests/toisto/model/quiz/test_quiz.py
index 3d56081e..2175a6e9 100644
--- a/tests/toisto/model/quiz/test_quiz.py
+++ b/tests/toisto/model/quiz/test_quiz.py
@@ -114,6 +114,8 @@ def test_instructions(self):
             "Give the [underline]negative[/underline] in",
             "Give the [underline]cardinal[/underline] in",
             "Give the [underline]ordinal[/underline] in",
+            "Give the [underline]abbreviation[/underline] in",
+            "Give the [underline]full form[/underline] in",
         ]
         for expected_instruction, quiz_type in zip(expected_instructions, get_args(QuizType), strict=True):
             quiz = self.create_quiz(self.concept, "fi", "fi", "Hei", ["Hei hei"], (quiz_type,))
diff --git a/tests/toisto/model/quiz/test_quiz_factory.py b/tests/toisto/model/quiz/test_quiz_factory.py
index 9b09afcb..922cc5ee 100644
--- a/tests/toisto/model/quiz/test_quiz_factory.py
+++ b/tests/toisto/model/quiz/test_quiz_factory.py
@@ -1138,6 +1138,24 @@ def test_numbers_and_translations(self):
         )
 
 
+class AbbreviationTest(ToistoTestCase):
+    """Unit tests for abbreviations."""
+
+    def test_abbreviations(self):
+        """Test that quizzes can be generated for abbreviations."""
+        concept = create_concept("llc", {"full form": dict(nl="naamloze vennootschap"), "abbreviation": dict(nl="NV")})
+        full_form, abbreviation = concept.constituents
+        self.assertSetEqual(
+            {
+                self.create_quiz(full_form, "nl", "nl", "naamloze vennootschap", ["naamloze vennootschap"], "dictate"),
+                self.create_quiz(abbreviation, "nl", "nl", "NV", ["NV"], "dictate"),
+                self.create_quiz(concept, "nl", "nl", "naamloze vennootschap", ["NV"], "abbreviate"),
+                self.create_quiz(concept, "nl", "nl", "NV", ["naamloze vennootschap"], "give full form"),
+            },
+            create_quizzes("nl", "en", concept),
+        )
+
+
 class QuizNoteTest(ToistoTestCase):
     """Unit tests for the quiz notes."""