]> git.frustrated-labs.net Git - larousseapi/commitdiff
feat: add support for multilang definitions
authorAlexander Goussas <[email protected]>
Tue, 2 Jun 2026 03:32:19 +0000 (22:32 -0500)
committerAlexander Goussas <[email protected]>
Tue, 2 Jun 2026 03:32:19 +0000 (22:32 -0500)
collections/larousse API/Define a word.yml
collections/larousse API/Translate a word.yml
src/main/java/net/frustratedfunctor/core/Lang.java [new file with mode: 0644]
src/main/java/net/frustratedfunctor/definitions/FrenchWordDefinitionRepository.java [new file with mode: 0644]
src/main/java/net/frustratedfunctor/definitions/JsoupWordDefinitionRepository.java [deleted file]
src/main/java/net/frustratedfunctor/definitions/LanguageAwareDefinitionsRepository.java [new file with mode: 0644]
src/main/java/net/frustratedfunctor/definitions/WordDefinitionResource.java
src/main/java/net/frustratedfunctor/definitions/WordDefinitionService.java
src/main/java/net/frustratedfunctor/ui/UiResource.java
src/main/resources/templates/pub/contributing.html
src/main/resources/templates/pub/index.html

index 96fcef16aa2812ac2a0cf7f02ae1bdea879fc849..905fda1851ce457e62843126d436d6a852b60ac3 100644 (file)
@@ -5,8 +5,11 @@ info:
 
 http:
   method: GET
-  url: "{{api.base_url}}/words/:word/definition"
+  url: "{{api.base_url}}/words/:word/definition?lang=de"
   params:
+    - name: lang
+      value: de
+      type: query
     - name: word
       value: voiture
       type: path
index 9f08930b082d935152d244bc29bb23906178e914..9e3151855da80dc50df6808df54f6242347e0e78 100644 (file)
@@ -5,8 +5,11 @@ info:
 
 http:
   method: GET
-  url: "{{api.base_url}}/words/:word/translation"
+  url: "{{api.base_url}}/words/:word/translation?lang=de"
   params:
+    - name: lang
+      value: de
+      type: query
     - name: word
       value: voiture
       type: path
diff --git a/src/main/java/net/frustratedfunctor/core/Lang.java b/src/main/java/net/frustratedfunctor/core/Lang.java
new file mode 100644 (file)
index 0000000..a58a4ae
--- /dev/null
@@ -0,0 +1,28 @@
+package net.frustratedfunctor.core;
+
+import java.util.Optional;
+
+public enum Lang {
+    French("fr"),
+    German("de");
+
+    public final String code;
+
+    public final static Lang DEFAULT = French;
+
+    Lang(String code) {
+        this.code = code;
+    }
+
+    public static Optional<Lang> parse(String s) {
+        return Optional.ofNullable(switch (s.toLowerCase()) {
+            case "fr" -> Lang.French;
+            case "de" -> Lang.German;
+            default -> null;
+        });
+    }
+
+    public static Lang parseOrDefault(String s) {
+        return parse(s).orElse(DEFAULT);
+    }
+}
diff --git a/src/main/java/net/frustratedfunctor/definitions/FrenchWordDefinitionRepository.java b/src/main/java/net/frustratedfunctor/definitions/FrenchWordDefinitionRepository.java
new file mode 100644 (file)
index 0000000..0b26252
--- /dev/null
@@ -0,0 +1,29 @@
+package net.frustratedfunctor.definitions;
+
+import io.quarkus.logging.Log;
+import jakarta.enterprise.context.ApplicationScoped;
+import org.jsoup.Jsoup;
+import org.jsoup.nodes.Element;
+
+import java.net.URI;
+import java.util.ArrayList;
+import java.util.List;
+
+@ApplicationScoped
+public class FrenchWordDefinitionRepository implements WordDefinitionRepository {
+    @Override
+    public List<String> definitionsForWord(String word) {
+        try {
+            final var larousseUrl = URI.create("https://www.larousse.fr/dictionnaires/francais/" + word).toURL();
+            final var doc = Jsoup.parse(larousseUrl, 10_000);
+            return doc
+                    .select("li.DivisionDefinition")
+                    .stream()
+                    .map(Element::ownText)
+                    .toList();
+        } catch (Exception e) {
+            Log.error("Error while fetching word translations", e);
+            return new ArrayList<>();
+        }
+    }
+}
diff --git a/src/main/java/net/frustratedfunctor/definitions/JsoupWordDefinitionRepository.java b/src/main/java/net/frustratedfunctor/definitions/JsoupWordDefinitionRepository.java
deleted file mode 100644 (file)
index 96e7d78..0000000
+++ /dev/null
@@ -1,29 +0,0 @@
-package net.frustratedfunctor.definitions;
-
-import io.quarkus.logging.Log;
-import jakarta.enterprise.context.ApplicationScoped;
-import org.jsoup.Jsoup;
-import org.jsoup.nodes.Element;
-
-import java.net.URI;
-import java.util.ArrayList;
-import java.util.List;
-
-@ApplicationScoped
-public class JsoupWordDefinitionRepository implements WordDefinitionRepository {
-    @Override
-    public List<String> definitionsForWord(String word) {
-        try {
-            final var larousseUrl = URI.create("https://www.larousse.fr/dictionnaires/francais/" + word).toURL();
-            final var doc = Jsoup.parse(larousseUrl, 10_000);
-            return doc
-                    .select("li.DivisionDefinition")
-                    .stream()
-                    .map(Element::ownText)
-                    .toList();
-        } catch (Exception e) {
-            Log.error("Error while fetching word translations", e);
-            return new ArrayList<>();
-        }
-    }
-}
diff --git a/src/main/java/net/frustratedfunctor/definitions/LanguageAwareDefinitionsRepository.java b/src/main/java/net/frustratedfunctor/definitions/LanguageAwareDefinitionsRepository.java
new file mode 100644 (file)
index 0000000..fd43308
--- /dev/null
@@ -0,0 +1,29 @@
+package net.frustratedfunctor.definitions;
+
+import jakarta.annotation.Priority;
+import jakarta.enterprise.context.ApplicationScoped;
+import net.frustratedfunctor.core.Lang;
+
+import java.util.List;
+
+@Priority(1)
+@ApplicationScoped
+public class LanguageAwareDefinitionsRepository implements WordDefinitionRepository {
+    private final FrenchWordDefinitionRepository french;
+
+    public LanguageAwareDefinitionsRepository(FrenchWordDefinitionRepository french) {
+        this.french = french;
+    }
+
+    @Override
+    public List<String> definitionsForWord(String word) {
+        return french.definitionsForWord(word);
+    }
+
+    public List<String> definitionsForWord(String word, String lang) {
+        return switch (Lang.parseOrDefault(lang)) {
+            case French -> french.definitionsForWord(word);
+            case German -> List.of("OOPS! NOT IMPLEMENTED");
+        };
+    }
+}
index 6f3b78deebe1254aaede217a6c5c4e6ee7bc3582..7e9e9b1004b3de5542a958da40bf88f8baa0a76c 100644 (file)
@@ -1,9 +1,6 @@
 package net.frustratedfunctor.definitions;
 
-import jakarta.ws.rs.GET;
-import jakarta.ws.rs.Path;
-import jakarta.ws.rs.PathParam;
-import jakarta.ws.rs.Produces;
+import jakarta.ws.rs.*;
 import jakarta.ws.rs.core.MediaType;
 
 @Path("/words")
@@ -18,8 +15,11 @@ public class WordDefinitionResource {
     @Path("/{word}/definition")
     @GET
     @Produces(MediaType.APPLICATION_JSON)
-    public WordDefinition definition(@PathParam("word") String word) {
-        return definitions.define(word);
+    public WordDefinition definition(
+            @PathParam("word") String word,
+            @QueryParam("lang") String lang
+    ) {
+        return definitions.define(word, lang);
     }
 
 }
index 44696e252798a8b62b2f1c5330c03c083e0f38da..564aafa801123e2434b39b8fd75f07ee19c31c52 100644 (file)
@@ -5,17 +5,15 @@ import jakarta.enterprise.context.ApplicationScoped;
 @ApplicationScoped
 public class WordDefinitionService {
 
-    private final WordDefinitionRepository definitions;
+    private final LanguageAwareDefinitionsRepository definitions;
 
-    public WordDefinitionService(WordDefinitionRepository definitions) {
+    public WordDefinitionService(LanguageAwareDefinitionsRepository definitions) {
         this.definitions = definitions;
     }
 
-    public WordDefinition define(String word) {
-        return new WordDefinition(
-                word,
-                definitions.definitionsForWord(word)
-        );
+    public WordDefinition define(String word, String lang) {
+        final var defs = definitions.definitionsForWord(word, lang);
+        return new WordDefinition(word, defs);
     }
 
 }
index 0fe5dc28ad0e5c5222d3e1ef851a4f3cbd5946de..00e688daa42952db4576bacef8a412eed60eef19 100644 (file)
@@ -28,10 +28,13 @@ public class UiResource {
     @Path("getTranslationsDefinitions")
     @POST
     @Produces(MediaType.TEXT_HTML)
-    public TemplateInstance getTranslationsDefinitions(@FormParam("word") String word) {
+    public TemplateInstance getTranslationsDefinitions(
+            @FormParam("word") String word,
+            @FormParam("lang") String lang
+    ) {
         final var data = new WordTranslationsDefinitions(
                 translations.translate(word).translations(),
-                definitions.define(word).definitions()
+                definitions.define(word, lang).definitions()
         );
         return index.data("translationsDefinitions", data);
     }
index 78b98b32e5fd412e3cab0ac8ddcd44cce716728f..80d10791c14b1823063f1091b172462675349fe2 100644 (file)
@@ -56,8 +56,7 @@
         You can send me a patch implementing support for your language. Take a look at the <a
             href="https://git.frustrated-labs.net/?p=larousseapi;a=summary">codebase</a>.
         I think everything is pretty self-explanatory. You should create a new implementation of the
-        <code>WordDefinitionRepository</code> and <code>WordTranslationRepository</code> interfaces and add it to the
-        corresponding composite classes.
+        <code>WordDefinitionRepository</code> and <code>WordTranslationRepository</code> interfaces.
     </p>
     <p>
         The patches should be sent over email with the subject <code>[larousseapi] PATCH: description</code>
index 34bcd6f00573924879562c8991d609cba9c82f5c..69ad8cb1a62576aaecb77f271558b8344a80b94a 100644 (file)
         </small>
         <form method="POST" action="/ui/getTranslationsDefinitions">
             <fieldset>
+                <label for="lang">Language</label>
+                <select id="lang" name="lang" style="margin-bottom: 5px;">
+                    <option value="fr">French</option>
+                    <option value="de">German</option>
+                </select>
                 <label class="stack"><input name="word" type="text" placeholder="Jambon..."></label>
                 <button class="stack">Let's Go!</button>
             </fieldset>