Skip to content

Conversation

@macarooni-man
Copy link

@macarooni-man macarooni-man commented Oct 24, 2025

What does this PR do?

This PR adds a new plugin: define, which provides inline dictionary definitions to search results, powered by Wiktionary. I use this feature a lot with search engines, and added this feature to my own instance for that reason. I also thought it would be beneficial to share with the community!

image



When a user searches for a query beginning with

define <term>

the plugin fetches definitions via the official Wiktionary REST API and renders them as a answer card using a modified Translations type.

The plugin:

  • Displays definitions grouped by part of speech (noun, verb, adjective, etc.)
  • Includes a few example usages from Wiktionary
  • Rewrites internal /wiki/... links to equivalent local searches (e.g. /search?q=define+term)
  • Returns results only on the first page of a search
  • Adds a button in the botton right corner to view the Wiktionary entry directly
  • Falls back gracefully with a short "No definitions were found." message

Implementation notes:

  • Fully type-annotated and follows the required code style
  • At least from what I can tell, safe HTML rewriting and de-duplication
  • Uses SearXNG’s native HTTP client (searx.network.get) for requests
  • An extensible provider framework to add new sources in the future with DefinitionProvider
  • Adds a Jinja template for rendering embedded HTML from the Wiktionary REST API answer/define.html

Why is this change important?

This plugin gives users quick, inline dictionary definitions without opening another tab or engine result. This is similar to what major search engines provide.
It enhances the SearXNG user experience by:

  • Adding native dictionary-style answers directly at the top of search results when triggered
  • Leveraging Wiktionary, a free and open data source
  • Integrating seamlessly with SearXNG’s existing answerer and translations infrastructure
  • Recursive searches with hyperlinks to browse multiple definitions easily
  • Providing multilingual results when available (unfortunately it's not in my wheelhouse and still needs work)

This makes "define" queries faster, privacy-friendly, and self-contained in the UI.


How to test this PR locally?

  1. Make sure the plugin is enabled in your SearXNG configuration (settings.yml):

Note: I've enabled it by default in this PR

plugins:
  - searx.plugins.define.SXNGPlugin
  1. Rebuild or reload your dev environment:

    make run
  2. Open SearXNG in your browser and verify that "Define" is enabled in Special Queries > Plugins:

image
  1. From the home page, try queries such as:

    • define effervescence
    • define network
    • define chicken tender
  2. You should see a compact definition card rendered at the top of the results:

image



To run automated tests:

make test

Expected:

  • pyright: no errors
  • pylint: 10.00/10
  • pytest: all tests pass

Note that I've tested this in GitHub Codespaces, and on my personal server from a manual installation in Ubuntu Server (minimal) 24.02

view my results
(.venv) @macarooni-man ➜ /workspaces/searxng-define (master) $ make test
TEST      [yamllint] .github/ISSUE_TEMPLATE/config.yml
TEST      [black] --exclude
All done! ✨ 🍰 ✨
366 files would be left unchanged.
TEST      [basedpyright] static type check of local modified files
0 errors, 0 warnings, 0 notes
TEST      [pylint] ./searx/engines

--------------------------------------------------------------------
Your code has been rated at 10.00/10 (previous run: 10.00/10, +0.00)

TEST      [pylint] ./searx ./searxng_extra ./tests

--------------------------------------------------------------------
Your code has been rated at 10.00/10 (previous run: 10.00/10, +0.00)

TEST      tests/unit
...................................................................................................................................................................................................................................................................................................
----------------------------------------------------------------------
Ran 291 tests in 50.368s

OK
TEST      robot
INSTALL   gecko.driver
INSTALL   geckodriver already installed
2025-10-24 04:16:49,725 WARNING:searx.botdetection.config: missing config file: /workspaces/searxng-define/tests/robot/limiter.toml

Running 7 tests
/workspaces/searxng-define/local/py3/bin/geckodriver
/usr/bin/firefox
TEST      [reST markup] README.rst
TEST      test.shell OK
TEST      [shfmt] ./manage ./container ./utils
INSTALL   [pkg.go.dev] ./go.mod: developer and CI tools
go: downloading mvdan.cc/sh/v3 v3.12.0
go: downloading github.com/google/renameio/v2 v2.0.0
go: downloading github.com/rogpeppe/go-internal v1.14.1
go: downloading golang.org/x/term v0.32.0
go: downloading mvdan.cc/editorconfig v0.3.0
go: downloading golang.org/x/sys v0.33.0
go: downloading github.com/go-quicktest/qt v1.101.0
go: downloading github.com/google/go-cmp v0.7.0
go: downloading golang.org/x/tools v0.31.0
go: downloading github.com/kr/pretty v0.3.1
go: downloading github.com/kr/text v0.2.0

Author's checklist

  • Fully typed, tests are clean
  • pylint score: 10.00/10
  • All unit tests pass
  • Verified rendering of definitions and examples
  • Confirmed proper escaping and link rewriting

Future ideas:

  • Add support for multiple dictionary providers
  • Add support for more locales
  • Add local result caching

I'm open to any feedback, and I also can't wait to see how else this project evolves. I've been really happy with this tool in my network!

@macarooni-man macarooni-man changed the title [feat] Add "define <term>" Plugin to Show Definitions in Searches [feat] plugins: Add "define <term>" Plugin to Show Definitions in Searches Oct 24, 2025
Comment on lines +51 to +53
@dataclass(eq=True, frozen=False)
class Definition:
"""
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unfortunately, the concept of result types has not yet been fully implemented and is therefore somewhat difficult to recognize --> In the model of result types, all answers are of the type BaseAnswer.

In terms of data structure, this class here is very similar to the type Translations ..

class Translations(BaseAnswer, kw_only=True):
"""Answer type with a list of translations.

which has a list of examples ..

translations: "list[Translations.Item]"
"""List of translations."""

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hi @return42, thanks for taking a peek!

I wanted to clarify as it may not have been clear through my implementation & PR description.

The Definition class here isn’t an answer type, it's only an internal data model used to normalize and store entries fetched from a provider (currently Wiktionary). The intended purpose is API abstraction to let new providers easily plug in later by returning the same structure.

The BaseAnswer emitted by the plugin is a standard Translations card, and the Definition is converted here in DefineHandler with the custom template:

def _make_definitions_answer(self, definitions: list[Translations.Item], url: str | None) -> Translations:
if not self.provider.name:
raise NotImplementedError("no real provider was specified")
return Translations(
translations=definitions,
url=url,
engine=self.provider.name,
template="answer/define.html",
)
def _build_translations_answer(self, definitions: list[Definition]) -> Translations | None:
"""
Turn a list[Definition] into a single Translations answer
- One item per part-of-speech
- Each item carries multiple definitions & a few examples
"""
if not definitions:
return None
by_pos: dict[str, list[Definition]] = {}
for d in definitions:
by_pos.setdefault(d.normalized_pos, []).append(d)
url = next((d.source_url for d in definitions if d.source_url), None)
items: list[Translations.Item] = []
# Dedupe identical senses
for pos, entries in by_pos.items():
seen: set[tuple[str, str]] = set()
definitions_text: list[str] = []
examples: list[str] = []
for d in entries:
if d.dedupe_key in seen:
continue
seen.add(d.dedupe_key)
if len(definitions_text) < self.max_definitions:
definitions_text.append(d.definition)
for ex in d.examples:
if len(examples) >= self.max_examples:
break
if ex not in examples:
examples.append(ex)
# Title of the card uses the first definition's word
word = entries[0].word if entries else ""
pos = entries[0].normalized_pos if entries else "unknown"
items.append(
Translations.Item(
text=word, # now just "liquid"
transliteration=f"({pos})" if pos != "unknown" else "",
definitions=definitions_text,
examples=examples,
synonyms=[],
)
)
return self._make_definitions_answer(definitions=items, url=url)

Copy link
Member

@Bnyro Bnyro left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Personally I feel like this is so similar to normal engine implementations, that this should be a normal engine instead of a plugin.

Apart from that we already have implemented something very similar by using !define tree, you can get definitions from all definition search engines we already have.

So in my opinion it would make sense to only take the implemented wiktionary API from this PR to create a wiktionary engine for the define category returning definitions similar to https://github.com/searxng/searxng/blob/master/searx/engines/wordnik.py.

@macarooni-man
Copy link
Author

macarooni-man commented Oct 24, 2025

@Bnyro hello, thanks for the follow-up!

I didn't know that !define was a feature, I just tried it and that's awesome!

I would prefer to integrate with an existing feature rather than creating a plugin after learning about what you suggested, however, I do believe there is more for your project to benefit from my design than simply a provider.

The existing implementation has potential to improve from a usability & UX standpoint:

  • If you're explicitly searching for a definition, it's extra work to expand all the drop-downs to view the content, which is a bit anti-user

  • There is no point-of-speech delineation, which can be confusing to map definitions to their semantic context

  • There are no examples or linked words which I find to be less accessible, but I also understand the design choice of filtering them for safety

  • An entry limit is helpful for visibility as some of the definitions are redundant, which without it requires too much scrolling to view other semantics

  • This is a personal preference, but I like the ability to have the card and search results at the same time with a dictionary, and that wasn't something I was able to get working from an answerer alone. I also understand the design choice to limit overhead of concurrent requests

  • Maybe it's just a "me" issue, but I find the !define implementation more challenging to read and decipher. Line spacing and separation of categories for establishing a clear visual hierarchy a goes a long way with that:

image image

@Bnyro
Copy link
Member

Bnyro commented Oct 24, 2025

I agree, your layout definitely has some aesthetic advantages over the current one.

Perhaps it'd make sense to adjust the existing translations template with your suggestions, so that we don't have two maintain two separate templates? This way translations could also benefit from the better UI design :)

@macarooni-man
Copy link
Author

I agree, your layout definitely has some aesthetic advantages over the current one.

Perhaps it'd make sense to adjust the existing translations template with your suggestions, so that we don't have two maintain two separate templates? This way translations could also benefit from the better UI design :)

Absolutely! I also completely agree that it would be much easier to converge from a maintainability, and both a backend and UX design perspective. I think it would be great to improve more than just the definitions.

If I'm being honest, I learned about SearXNG a few days ago and set up a server for personal use, but I started looking through the codebase yesterday. I don't have enough experience with it to be comfortable modifying a core feature without understanding the implications. It's for that reason specifically I chose the plugin approach, as it doesn't break anything 😊

With that being said, I'd be happy to help however I can within my "green" understanding of the project, and you’re more than welcome to use any part of my code in whatever way fits best. I just wanted to share my implementation for personal use with the hope that it could be beneficial in some way.

@Bnyro
Copy link
Member

Bnyro commented Oct 24, 2025

To change the style of the existing translations answer template, you can simply:

  1. Create a new file translations.less here: https://github.com/searxng/searxng/tree/master/client/simple/src/less/result_types and insert your CSS code
  2. Add an import to import your style here:
    @import "result_types/keyvalue.less";
    @import "result_types/code.less";
    @import "result_types/paper.less";
    @import "result_types/file.less";
    , similar to the existing ones
  3. Commit your changes and run make static.build.commit afterwards

To convert your wiktionary code into an engine, you can just create a new file here: https://github.com/searxng/searxng/tree/master/searx/engines and read through the developer documentation: https://docs.searxng.org/dev/index.html. Perhaps this should be fairly simple given the fact that you already implemented this plugin.

If you have specific questions, please don't hesitate to ask. :)

@macarooni-man
Copy link
Author

macarooni-man commented Oct 24, 2025

Great @Bnyro, thanks for the suggestions!

I've been digging further into the source and noticed there are already a few dictionary-style providers. I honestly hadn't realized that earlier, very interesting! Just to confirm, are these currently the only engine modules using the Translations framework? I want to be certain of the proverbial blast radius with core changes before considering a design:

  • wordnik
  • libretranslate
  • dictzone
  • translated

Additionally, from an architectural standpoint, I'd like to understand your stance on generated HTML. Since you mentioned improving the translation/definition framework as a whole, I'd prefer that any refactor toward an engine should also include the opportunity to bring the existing modules in line with a consistent structure. For example, adopting some of the normalization and layout ideas from my plugin.

To expand a bit on the HTML standardization, my current implementation preserves formatting like bold, italics, and internal links (rewritten as local SearXNG searches). Since most of the dictionary engines already simplify structured HTML, this could be implemented fairly cleanly. If I restrict the rendered HTML in the template for only a handful of tags, does that approach align with your expectations around sanitization?

Finally, I'd like to clarify configuration possibilities. Ideally, it would be great to expose a few small options:

  • a toggle to load searches concurrently with any cards (if feasible within the answerer framework, and disabled by default)
  • a configurable limit on definition/example entries, defaulting to 5/3 respectively seems comfortable based on my testing

I know scope creep is a common concern and I want to be respectful of that, but at the same time, there are some features like the HTML parsing which would require modifying how the template is rendered, and also, how data is passed into it. So I just want to be clear about where the line is for you on that.

I mainly want to make sure these goals fit within your long-term direction before I start moving pieces around. Let me know your thoughts!

@return42
Copy link
Member

return42 commented Oct 25, 2025

For example, adopting some of the normalization and layout ideas from my plugin.

I have to repeat, the concept behind are the Result Types ..

template: str = "answer/translations.html"
"""The template in :origin:`answer/translations.html
<searx/templates/simple/answer/translations.html>`"""

.. in this concept, a result type like Translations is bound to a html template in the simple theme simple/answer/translations.html and its not intended to bound Translations results to a template define.html

Translations(
    ...
    template="answer/define.html"
) 

On Client side, we have the CSS (aka LESS) https://github.com/searxng/searxng/tree/master/client/simple/src/less

See #5366 (comment)

For example, the WeatherAnswer type ..

class WeatherAnswer(BaseAnswer, kw_only=True):
"""Answer type for weather data."""
template: str = "answer/weather.html"
"""The template is located at :origin:`answer/weather.html
<searx/templates/simple/answer/weather.html>`"""

HTML template is in

and the CSS layout of the client is in

To expand a bit on the HTML standardization, my current implementation preserves formatting like bold, italics,

Don't do it, the engines, plugins and answerers .. the code that produce the BaseAnswers does not decide about rendering, this code returns a result type .. noting more .. you can re-use existing Translations types if the rendering for your results do not need any special UI ..

If you want a UI different from Translations you have to implement a result (answer) type Definitions which is ..

In terms of data structure, this class here is very similar to the type Translations

see my comment above: #5366 (comment)

I hope my explanations have been able to show “where” we want to go... As already mentioned above, this concept of result-types is currently still under development and therefore somewhat difficult to understand .. If I had more time, I would rebuild your PR... However, that would take longer, as I currently have many other unfinished tasks to deal with.

Perhaps you would like to try to restructure the code in this PR yourself so that it fits better into our concept of result types? On the other hand, if you have some time and patience with me, we can also do this together.

@macarooni-man
Copy link
Author

macarooni-man commented Oct 25, 2025

@return42 as Bnyro and I were discussing before, we were considering a restructure of Translations as a whole to align with the design motif of my plugin, not to use a custom template. That way, all the Translations type engines could support those features and improve the entire system.

However, your "don’t do it" framing and the way my questions were brushed aside comes across as fairly dismissive, and if your intent is to collaborate as you suggested, I don't feel like my perspective is being heard. My goal was to clarify architectural intent and find a collaborative path forward, and it seems as though you're firmly against the idea of restructuring the Translations template, rendering, or functionality.

I respect that, and that's why I asked about where your scope lies. If that is in fact your stance, I don't have an interest in continuing to work on this. Though, you're more than welcome to use my code.

@return42
Copy link
Member

However, your "don’t do it" framing and the way my questions were brushed aside comes across as fairly dismissive, and if your intent is to collaborate as you suggested,

My english isn't the best .. in the core I offered you my support.

we were considering a restructure of Translations as a whole to align with the design motif of my plugin, not to use a custom template. That way, all the Translations type engines could support those features and improve the entire system.

If you prefer to restructure existing code .. then yes, please consider a restructure of Translations 👍

@macarooni-man
Copy link
Author

If you prefer to restructure existing code .. then yes, please consider a restructure of Translations 👍

Okay, great. With this perspective, could you both take another look through my previous comment? I want to ensure that we're on the same page, and that I understand the scope of these changes before proceeding with a design.

@Bnyro
Copy link
Member

Bnyro commented Oct 26, 2025

Additionally, from an architectural standpoint, I'd like to understand your stance on generated HTML. Since you mentioned improving the translation/definition framework as a whole, I'd prefer that any refactor toward an engine should also include the opportunity to bring the existing modules in line with a consistent structure. For example, adopting some of the normalization and layout ideas from my plugin.

To expand a bit on the HTML standardization, my current implementation preserves formatting like bold, italics, and internal links (rewritten as local SearXNG searches). Since most of the dictionary engines already simplify structured HTML, this could be implemented fairly cleanly. If I restrict the rendered HTML in the template for only a handful of tags, does that approach align with your expectations around sanitization?

Sure, please do so 👍

Finally, I'd like to clarify configuration possibilities. Ideally, it would be great to expose a few small options:

a toggle to load searches concurrently with any cards (if feasible within the answerer framework, and disabled by default)
a configurable limit on definition/example entries, defaulting to 5/3 respectively seems comfortable based on my testing

I think you can do that in a different follow-up PR.

@return42
Copy link
Member

If I restrict the rendered HTML in the template for only a handful of tags, does that approach align with your expectations around sanitization?

Sure, please do so 👍

I don't have a final meaning about the HTML markup. In order to separate data from its representation, we have recently made considerable efforts to remove all HTML tags from the result items of the engines and have begun to build up the result types.

The requirement for an inline markup for paragraph-like types has been on my mind for quite some time:

We could (as suggested above) use a subset of HTML tags for this purpose, but then we would have to agree on which tags are included in this set, how they can be combined, and much more. In my opinion, this approach is wrong because it would mean we would have to start developing our own markup. Another disadvantage would be the presence of a few HTML tags in the strings of the JSON output.

What we need are basic inline markups such as those found here in the reST markup.

For this, we can implement a class CommonMark for str objects with some basic inline markups, similar to the DateTime type to wrap datetime.datetime objects.

Something like (untested) ...

import functools
import msgspec
from markdown_it import MarkdownIt

class CommonMark(msgspec.Struct):
    """String type with some `basic inline markup`_ (CommonMark_)

    .. _basic inline markup: https://docs.searxng.org/dev/reST.html#basic-inline-markup
    .. _CommonMark: https://commonmark.org/
    """

    p: str

    def __post_init__(self):
        # normalize whitespaces to one space
        self.p = " ".join(self.p.split())

    def __str__(self):
        return self.p

    @functools.cached_property
    def html(self):
        """Generates a string with HTML markup from CommonMark (uses markdown-it-py_).

        .. _markdown-it-py: https://github.com/executablebooks/markdown-it-py
        """
        return (
            MarkdownIt("commonmark", {"typographer": True}).enable(["replacements", "smartquotes"]).render(self.p)
        )

In line 137 we can replace the str by the CommonMark from above.

class Item(msgspec.Struct, kw_only=True):
"""A single element of the translations / a translation. A translation
consists of at least a mandatory ``text`` property (the translation) ,
optional properties such as *definitions*, *synonyms* and *examples* are
possible."""
text: str
"""Translated text."""

And in the template we replace item.text by item.text.html:

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants