Skip to content

feat(docs): Automatically generate format features docmentation#18106

Open
gersona wants to merge 19 commits intoWeblateOrg:mainfrom
gersona:9077_format_features_tables
Open

feat(docs): Automatically generate format features docmentation#18106
gersona wants to merge 19 commits intoWeblateOrg:mainfrom
gersona:9077_format_features_tables

Conversation

@gersona
Copy link
Contributor

@gersona gersona commented Feb 19, 2026

@gersona
Copy link
Contributor Author

gersona commented Feb 19, 2026

@nijel could you briefly have a look at the implementation suggestion before I move on to implement this for the other formats ?

@codecov
Copy link

codecov bot commented Feb 19, 2026

❌ 4 Tests Failed:

Tests completed Failed Passed Skipped
5854 4 5850 703
View the top 3 failed test(s) by shortest run time
weblate.formats.tests.test_formats.AutoLoadTest::test_yaml
Stack Traces | 0.002s run time
self = <weblate.formats.tests.test_formats.AutoLoadTest testMethod=test_yaml>

    def test_yaml(self) -> None:
>       self.single_test(TEST_YAML, YAMLFormat)

.../formats/tests/test_formats.py:178: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
.../formats/tests/test_formats.py:139: in single_test
    store = try_load(
weblate/formats/auto.py:123: in try_load
    raise failure
weblate/formats/auto.py:100: in try_load
    result = file_format(
weblate/formats/base.py:439: in __init__
    self.store = self.load(storefile, template_store)
                 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
weblate/formats/ttkit.py:310: in load
    store = self.parse_store(storefile)
            ^^^^^^^^^^^^^^^^^^^^^^^^^^^
weblate/formats/auto.py:137: in parse_store
    return factory.getobject(storefile)  # type: ignore[return-value]
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
.venv/lib/python3.14.../translate/storage/factory.py:227: in getobject
    storeclass = getclass(
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

storefile = <weblate.formats.helpers.NamedBytesIO object at 0x13edd7470>
localfiletype = None, ignore = None, classes = None
classes_str = {'_trados_txt_tm': ('trados', 'TradosTxtTmFile'), '_wftm': ('wordfast', 'WordfastTMFile'), 'catkeys': ('catkeys', 'CatkeysFile'), 'csv': ('csvl10n', 'csvfile'), ...}
hiddenclasses = {'txt': <function _examine_txt at 0x10cb2fa00>}

    def getclass(
        storefile,
        localfiletype=None,
        ignore=None,
        classes=None,
        classes_str=None,
        hiddenclasses=None,
    ):
        """
        Factory that returns the applicable class for the type of file
        presented.  Specify ignore to ignore some part at the back of the name
        (like .gz).
        """
        storefilename = _get_name(storefile)
        if classes_str is None:
            classes_str = _classes_str
        if hiddenclasses is None:
            hiddenclasses = _hiddenclasses
        if ignore and storefilename.endswith(ignore):
            storefilename = storefilename[: -len(ignore)]
        ext = localfiletype
        if ext is None:
            root, ext = os.path.splitext(storefilename)
            ext = ext[len(os.path.extsep) :].lower()
            decomp = None
            if ext in decompressclass:
                decomp = ext
                root, ext = os.path.splitext(root)
                ext = ext[len(os.path.extsep) :].lower()
            if ext in hiddenclasses:
                guesserfn = hiddenclasses[ext]
                if decomp:
                    file = import_class(*decompressclass[decomp])
                    ext = guesserfn(file(storefile))
                else:
                    ext = guesserfn(storefile)
        try:
            # we prefer classes (if given) since that is the older API that Pootle uses
            if classes:
                storeclass = classes[ext]
            else:
                storeclass = import_class(*classes_str[ext], "translate.storage")
        except KeyError:
>           raise ValueError(f"Unknown filetype ({storefilename})") from None
E           ValueError: Unknown filetype (.../tests/data/cs.pyml)

.venv/lib/python3.14.../translate/storage/factory.py:195: ValueError
weblate.trans.tests.test_component.ComponentTest::test_create_json
Stack Traces | 0.453s run time
self = <weblate.trans.tests.test_component.ComponentTest testMethod=test_create_json>

    def test_create_json(self) -> None:
        component = self.create_json()
>       self.verify_component(component, 2, "cs", 4)

.../trans/tests/test_component.py:220: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
.../trans/tests/test_component.py:43: in verify_component
    component.full_clean()
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <Component: Test/Test>, exclude = {'template'}, validate_unique = True
validate_constraints = True

    def full_clean(self, exclude=None, validate_unique=True, validate_constraints=True):
        """
        Call clean_fields(), clean(), validate_unique(), and
        validate_constraints() on the model. Raise a ValidationError for any
        errors that occur.
        """
        errors = {}
        if exclude is None:
            exclude = set()
        else:
            exclude = set(exclude)
    
        try:
            self.clean_fields(exclude=exclude)
        except ValidationError as e:
            errors = e.update_error_dict(errors)
    
        # Form.clean() is run even if other validation fails, so do the
        # same with Model.clean() for consistency.
        try:
            self.clean()
        except ValidationError as e:
            errors = e.update_error_dict(errors)
    
        # Run unique checks, but only for fields that passed validation.
        if validate_unique:
            for name in errors:
                if name != NON_FIELD_ERRORS and name not in exclude:
                    exclude.add(name)
            try:
                self.validate_unique(exclude=exclude)
            except ValidationError as e:
                errors = e.update_error_dict(errors)
    
        # Run constraints checks, but only for fields that passed validation.
        if validate_constraints:
            for name in errors:
                if name != NON_FIELD_ERRORS and name not in exclude:
                    exclude.add(name)
            try:
                self.validate_constraints(exclude=exclude)
            except ValidationError as e:
                errors = e.update_error_dict(errors)
    
        if errors:
>           raise ValidationError(errors)
E           django.core.exceptions.ValidationError: {'template': ['You can not use a monolingual translation without a base file.']}

.venv/lib/python3.14.../db/models/base.py:1712: ValidationError
weblate.trans.tests.test_files.XlsxImportTest::test_import
Stack Traces | 0.8s run time
self = <weblate.trans.tests.test_files.XlsxImportTest testMethod=test_import>

    def test_import(self) -> None:
        translation = self.get_translation()
        self.assertEqual(translation.stats.translated, 0)
        self.assertEqual(translation.stats.fuzzy, 0)
        with open(self.test_file, "rb") as handle:
            self.client.post(
                reverse("upload", kwargs=self.kw_translation),
                {
                    "file": handle,
                    "method": "translate",
                    "author_name": self.user.full_name,
                    "author_email": self.user.email,
                },
            )
        # Verify stats
        translation = self.get_translation()
>       self.assertEqual(translation.stats.translated, 1)
E       AssertionError: 0 != 1

.../trans/tests/test_files.py:540: AssertionError
weblate.trans.tests.test_component.ComponentValidationTest::test_fileformat
Stack Traces | 1.06s run time
django.core.exceptions.ValidationError: {'template': ['You can not use a monolingual translation without a base file.']}

During handling of the above exception, another exception occurred:

self = <weblate.trans.tests.test_component.ComponentValidationTest testMethod=test_fileformat>

    def test_fileformat(self) -> None:
        """Unknown file format."""
        self.component.file_format = "i18next"
        self.component.filemask = "invalid/*.invalid"
>       with self.assertRaisesMessage(
            ValidationError, "Could not parse 2 matched files."
        ):

.../trans/tests/test_component.py:816: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
.../homebrew/Cellar/python@3.14/3.14.3_1/Frameworks/Python.framework/Versions/3.14/lib/python3.14/contextlib.py:162: in __exit__
    self.gen.throw(value)
E   AssertionError: 'Could not parse 2 matched files.' not found in "{'template': ['You can not use a monolingual translation without a base file.']}"

To view more test analytics, go to the Test Analytics Dashboard
📋 Got 3 mins? Take this short survey to help us improve Test Analytics.

Copy link
Member

@nijel nijel left a comment

Choose a reason for hiding this comment

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

Many of the features can already be detected from the file format class (monolingual, supports_explanation, supports_plural) so it might be better to define all such flags there without the need for the additional layer? I'm worried that with an additional layer, things will easily get out of sync.

The actual documentation generation then could live in the command.

@gersona
Copy link
Contributor Author

gersona commented Feb 20, 2026

That's a fair point, many flags can definitely be read directly from the format class.
My concern is with format-specific features that don't map to any existing class attribute (and they can get pretty long if we keep them in the format class), like
https://github.com/gersona/weblate/blob/c3cdbee736cdea21861c917a24d3372a0cb1ed43/weblate/formats/docs.py#L67-L72
The intent was to split concern and keep this kind of doc-only metadata out of the format class and avoid adding attributes to it purely for documentation purposes.

@github-actions
Copy link

This pull request has been automatically marked as stale because there wasn’t any recent activity.

It will be closed soon if no further action occurs.

Thank you for your contributions!

@github-actions github-actions bot added the wontfix Will be closed when no feedback arrives. label Mar 23, 2026
@nijel
Copy link
Member

nijel commented Mar 23, 2026

To make this easier to deal with, let's start with the features we already have flags for. Some are obvious, like supports_explanation, and some are a bit hidden, like SUPPORTS_FUZZY and will need conversion.

@gersona
Copy link
Contributor Author

gersona commented Mar 23, 2026

I actually already got something quite ready for review, with a different approach than what I initially proposed..
I can push it later today.

@gersona gersona force-pushed the 9077_format_features_tables branch from c3cdbee to 7b4f024 Compare March 23, 2026 15:12
@gersona gersona changed the title WIP: Automatically generate format features docmentation Automatically generate format features docmentation Mar 23, 2026
@nijel
Copy link
Member

nijel commented Mar 23, 2026

I think this is a good direction. Can we get rid of existing SUPPORTS_FUZZY and use the new flags instead?

@gersona gersona added enhancement Adding or requesting a new feature. documentation Improvements or additions to the documentation. and removed wontfix Will be closed when no feedback arrives. labels Mar 23, 2026
@gersona
Copy link
Contributor Author

gersona commented Mar 23, 2026

one of the comments in #9077 mentions a doc page with an overview of features and the file formats that support them. Should it also be in the scope of this PR ?

@gersona gersona changed the title Automatically generate format features docmentation feat(docs): Automatically generate format features docmentation Mar 24, 2026
@gersona gersona marked this pull request as ready for review March 24, 2026 08:13
@gersona gersona requested a review from AliceVisek as a code owner March 24, 2026 08:13
@nijel
Copy link
Member

nijel commented Mar 24, 2026

If all info from the “Translation types capabilities” is now covered directly in the formats doc, it should be removed.

@nijel nijel added this to the 5.17 milestone Mar 24, 2026
@nijel nijel requested a review from Copilot March 25, 2026 11:29
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Adds an auto-generated “supported format features” documentation system by introducing a new list_format_features management command and wiring its output into the Sphinx docs (fixes #9077).

Changes:

  • Added list_format_features command to generate per-format RST snippet tables of supported capabilities.
  • Extended format classes with explicit supports_* capability flags and additional_states metadata.
  • Updated documentation to include the generated snippets and adjusted docs build config/Makefile to generate/exclude snippet sources.

Reviewed changes

Copilot reviewed 110 out of 112 changed files in this pull request and generated 1 comment.

Show a summary per file
File Description
weblate/utils/management/base.py Adds output_required to support commands that require an output destination.
weblate/formats/base.py Introduces shared capability flags (supports_*) and additional_states on TranslationFormat.
weblate/formats/ttkit.py Aligns fuzzy-state handling with additional_states and sets per-format capability flags.
weblate/formats/convert.py Marks Windows RC format as supporting descriptions.
weblate/formats/management/commands/list_format_features.py New generator command producing per-format “features” snippet files.
weblate/formats/tests/test_commands.py Tests list_format_features basic argument validation and file generation.
docs/Makefile Generates format-feature snippets during update-docs.
docs/conf.py Excludes snippet files from being treated as standalone Sphinx documents.
docs/admin/management.rst Documents the new list_format_features management command and its --output option.
docs/changes.rst Adds changelog entries referencing the new command and doc improvements.
docs/formats.rst Replaces a static capabilities table with guidance to refer to per-format pages.
docs/formats/android.rst Includes generated format-features snippet on the Android format page.
docs/formats/apple.rst Includes generated format-features snippet on the Apple Strings format page.
docs/formats/appstore.rst Includes generated format-features snippet on the AppStore format page.
docs/formats/arb.rst Includes generated format-features snippet on the ARB format page.
docs/formats/asciidoc.rst Includes generated format-features snippet on the AsciiDoc format page.
docs/formats/catkeys.rst Includes generated format-features snippet on the Catkeys format page.
docs/formats/compose-multiplatform-resources.rst Includes generated format-features snippet for Compose Multiplatform resources.
docs/formats/csv.rst Includes generated format-features snippet on the CSV format page.
docs/formats/dtd.rst Includes generated format-features snippet on the DTD format page.
docs/formats/excel.rst Includes generated format-features snippet on the Excel/XLSX format page.
docs/formats/flatxml.rst Includes generated format-features snippet on the FlatXML format page.
docs/formats/fluent.rst Includes generated format-features snippet on the Fluent format page.
docs/formats/formatjs.rst Includes generated format-features snippet on the FormatJS format page.
docs/formats/gettext.rst Includes generated format-features snippets for gettext (mono + bilingual).
docs/formats/go-i18n.rst Includes generated format-features snippets for go-i18n variants.
docs/formats/gotext.rst Includes generated format-features snippet on the GoText format page.
docs/formats/gwt.rst Includes generated format-features snippet on the GWT format page.
docs/formats/html.rst Includes generated format-features snippet on the HTML format page.
docs/formats/i18next.rst Includes generated format-features snippets for i18next variants.
docs/formats/idml.rst Includes generated format-features snippet on the IDML format page.
docs/formats/ini.rst Includes generated format-features snippet on the INI format page.
docs/formats/innosetup.rst Includes generated format-features snippet (ISLU) on the Inno Setup format page.
docs/formats/java.rst Includes generated format-features snippet on the Java properties format page.
docs/formats/joomla.rst Includes generated format-features snippet on the Joomla format page.
docs/formats/json.rst Includes generated format-features snippet on the JSON format page.
docs/formats/laravel.rst Includes generated format-features snippet on the Laravel format page.
docs/formats/markdown.rst Includes generated format-features snippet on the Markdown format page.
docs/formats/mi18n.rst Includes generated format-features snippet on the mi18n format page.
docs/formats/moko-resources.rst Includes generated format-features snippet on the Moko resources format page.
docs/formats/nextcloud-json.rst Includes generated format-features snippet on the Nextcloud JSON format page.
docs/formats/odf.rst Includes generated format-features snippet on the ODF format page.
docs/formats/php.rst Includes generated format-features snippet on the PHP format page.
docs/formats/qt.rst Includes generated format-features snippet on the Qt/TS format page.
docs/formats/resjson.rst Includes generated format-features snippet on the resjson format page.
docs/formats/resourcedict.rst Includes generated format-features snippet on the resource dictionary format page.
docs/formats/resx.rst Includes generated format-features snippet on the RESX format page.
docs/formats/ruby.rst Includes generated format-features snippet on the Ruby YAML format page.
docs/formats/stringsdict.rst Includes generated format-features snippet on the StringsDict format page.
docs/formats/subtitles.rst Includes generated format-features snippet (SRT) on the subtitles format page.
docs/formats/tbx.rst Replaces inline “features” content with an included snippet.
docs/formats/toml.rst Includes generated format-features snippet on the TOML format page.
docs/formats/txt.rst Includes generated format-features snippet on the TXT format page.
docs/formats/webextension.rst Includes generated format-features snippet on the WebExtension format page.
docs/formats/winrc.rst Includes generated format-features snippet on the Windows RC format page.
docs/formats/wxl.rst Includes generated format-features snippet on the WXL format page.
docs/formats/xliff.rst Includes generated format-features snippet on the XLIFF format page.
docs/formats/xliff2.rst Includes generated format-features snippet on the XLIFF2 format page.
docs/formats/yaml.rst Includes generated format-features snippet on the YAML format page.
docs/snippets/format-features/appstore-features.rst New generated per-format snippet used by docs.
docs/snippets/format-features/arb-features.rst New generated per-format snippet used by docs.
docs/snippets/format-features/aresource-features.rst New generated per-format snippet used by docs.
docs/snippets/format-features/asciidoc-features.rst New generated per-format snippet used by docs.
docs/snippets/format-features/catkeys-features.rst New generated per-format snippet used by docs.
docs/snippets/format-features/cmp-resource-features.rst New generated per-format snippet used by docs.
docs/snippets/format-features/csv-features.rst New generated per-format snippet used by docs.
docs/snippets/format-features/dtd-features.rst New generated per-format snippet used by docs.
docs/snippets/format-features/flatxml-features.rst New generated per-format snippet used by docs.
docs/snippets/format-features/fluent-features.rst New generated per-format snippet used by docs.
docs/snippets/format-features/formatjs-features.rst New generated per-format snippet used by docs.
docs/snippets/format-features/go-i18n-json-features.rst New generated per-format snippet used by docs.
docs/snippets/format-features/go-i18n-toml-features.rst New generated per-format snippet used by docs.
docs/snippets/format-features/gotext-features.rst New generated per-format snippet used by docs.
docs/snippets/format-features/gwt-features.rst New generated per-format snippet used by docs.
docs/snippets/format-features/html-features.rst New generated per-format snippet used by docs.
docs/snippets/format-features/i18next-features.rst New generated per-format snippet used by docs.
docs/snippets/format-features/i18nextv4-features.rst New generated per-format snippet used by docs.
docs/snippets/format-features/idml-features.rst New generated per-format snippet used by docs.
docs/snippets/format-features/ini-features.rst New generated per-format snippet used by docs.
docs/snippets/format-features/islu-features.rst New generated per-format snippet used by docs.
docs/snippets/format-features/joomla-features.rst New generated per-format snippet used by docs.
docs/snippets/format-features/json-features.rst New generated per-format snippet used by docs.
docs/snippets/format-features/laravel-features.rst New generated per-format snippet used by docs.
docs/snippets/format-features/markdown-features.rst New generated per-format snippet used by docs.
docs/snippets/format-features/mi18n-lang-features.rst New generated per-format snippet used by docs.
docs/snippets/format-features/moko-resource-features.rst New generated per-format snippet used by docs.
docs/snippets/format-features/nextcloud-json-features.rst New generated per-format snippet used by docs.
docs/snippets/format-features/odf-features.rst New generated per-format snippet used by docs.
docs/snippets/format-features/php-features.rst New generated per-format snippet used by docs.
docs/snippets/format-features/po-features.rst New generated per-format snippet used by docs.
docs/snippets/format-features/po-mono-features.rst New generated per-format snippet used by docs.
docs/snippets/format-features/properties-features.rst New generated per-format snippet used by docs.
docs/snippets/format-features/rc-features.rst New generated per-format snippet used by docs.
docs/snippets/format-features/resjson-features.rst New generated per-format snippet used by docs.
docs/snippets/format-features/resourcedictionary-features.rst New generated per-format snippet used by docs.
docs/snippets/format-features/resx-features.rst New generated per-format snippet used by docs.
docs/snippets/format-features/ruby-yaml-features.rst New generated per-format snippet used by docs.
docs/snippets/format-features/srt-features.rst New generated per-format snippet used by docs.
docs/snippets/format-features/strings-features.rst New generated per-format snippet used by docs.
docs/snippets/format-features/stringsdict-features.rst New generated per-format snippet used by docs.
docs/snippets/format-features/tbx-features.rst New generated per-format snippet used by docs; retains some format-specific notes.
docs/snippets/format-features/toml-features.rst New generated per-format snippet used by docs.
docs/snippets/format-features/ts-features.rst New generated per-format snippet used by docs.
docs/snippets/format-features/txt-features.rst New generated per-format snippet used by docs.
docs/snippets/format-features/webextension-features.rst New generated per-format snippet used by docs.
docs/snippets/format-features/wxl-features.rst New generated per-format snippet used by docs.
docs/snippets/format-features/xliff-features.rst New generated per-format snippet used by docs.
docs/snippets/format-features/xliff2-features.rst New generated per-format snippet used by docs.
docs/snippets/format-features/xlsx-features.rst New generated per-format snippet used by docs.
docs/snippets/format-features/yaml-features.rst New generated per-format snippet used by docs.
Comments suppressed due to low confidence (5)

weblate/formats/management/commands/list_format_features.py:1

  • common_extensions is taken directly from file_format.get_class().Extensions and then mutated with .append() / .extend(). If Extensions is a class-level list (or otherwise shared), this will leak extensions across formats and produce incorrect docs (e.g., unrelated extensions showing up for other formats). Fix by copying before mutation (e.g., list(...)) and avoid mutating any class attribute returned by the toolkit.
    weblate/utils/management/base.py:1
  • The help text always says "Optional output path", but required=self.output_required makes it mandatory for commands like list_format_features. Consider making the help string conditional (required vs optional) so CLI help matches actual behavior.
    weblate/formats/ttkit.py:1
  • supports_plurals is introduced alongside the existing supports_plural flag and is inconsistent with the capability naming pattern used elsewhere in this PR (supports_plural, supports_descriptions, etc.). If this is meant to indicate plural support, it likely won’t be picked up by the generator (which reads supports_plural) and should be removed or renamed to the canonical supports_plural (or added to the shared base API if truly distinct).
    weblate/formats/tests/test_commands.py:1
  • The test validates argument handling and that at least one output file exists, but it doesn’t assert any generated content. Adding a content assertion for a known format (e.g., that php-features.rst contains the expected markers and that its extension list doesn’t include unrelated extensions) would catch regressions like shared/mutated extension lists.
    docs/snippets/format-features/tbx-features.rst:1
  • These lines end with trailing commas, which is invalid/awkward RST punctuation and may render incorrectly. Remove the trailing commas (and consider converting these into a consistent list/table format) to keep the included snippet clean and Sphinx-friendly.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

@gersona gersona force-pushed the 9077_format_features_tables branch from 029a8c9 to 8995259 Compare March 25, 2026 17:51
@argos-ci
Copy link

argos-ci bot commented Mar 25, 2026

The latest updates on your projects. Learn more about Argos notifications ↗︎

Build Status Details Updated (UTC)
default (Inspect) ⚠️ Changes detected (Review) 22 changed Mar 25, 2026, 6:53 PM

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

Labels

documentation Improvements or additions to the documentation. enhancement Adding or requesting a new feature.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Move file format features from table to format pages

3 participants