├── .python-version ├── Procfile ├── mypy.ini ├── .gitignore ├── Makefile ├── config.yaml.example ├── templates ├── advanced_partial_forms_hint.html ├── no_duplicate.html ├── bulk-not-allowed.html ├── error-api.html ├── no-such-template.html ├── template_li.html ├── ambiguous-template.html ├── index.html ├── error-oauth-callback.html ├── bulk-result.html ├── wikifunctions.html ├── duplicates.html ├── settings.html ├── bulk.html └── base.html ├── .mailmap ├── CODE_OF_CONDUCT.md ├── static ├── tool.css ├── edit.css ├── menu.js ├── preventDoubleSubmit.js ├── template.css ├── alertPartialForms.js ├── edit.js └── wikifunctions.js ├── dev-requirements.in ├── service.template ├── i18n ├── .dir-locals.el ├── mrh.json ├── th.json ├── cy.json ├── skr-arab.json ├── anp.json ├── vec.json ├── lv.json ├── ro.json ├── ig.json ├── ko-kp.json ├── kai.json ├── et.json ├── ast.json ├── lt.json ├── nn.json ├── la.json ├── kaa.json ├── rki.json ├── xmf.json ├── ta.json ├── ban.json ├── ko.json ├── ku.json ├── ml.json ├── ja.json ├── sh-latn.json ├── yo.json ├── af.json ├── vi.json ├── ba.json ├── ms-arab.json ├── hy.json ├── da.json ├── lb.json ├── eo.json ├── fi.json ├── kn.json ├── sa.json ├── hu.json ├── el.json ├── br.json ├── te.json ├── yue-hant.json ├── pl.json ├── zh-hant.json ├── fa.json ├── zh-hans.json ├── tg.json ├── hno.json ├── pnb.json ├── bn.json ├── tt-cyrl.json ├── hr.json ├── qu.json ├── roa-tara.json ├── or.json ├── ur.json ├── eu.json ├── sk.json ├── ht.json ├── hi.json ├── aig.json ├── he.json ├── sv.json ├── io.json ├── uk.json ├── pa.json └── ar.json ├── entity_ids ├── __init__.py ├── property_ids.py ├── lexical_category_item_ids.py ├── __main__.py └── language_item_ids.py ├── flask_utils.py ├── stubs └── toolforge.pyi ├── requirements.in ├── .flake8 ├── uwsgi.ini ├── test_language_info.py ├── .gitlab-ci.yml ├── LICENSE ├── test_translations.py ├── purge-all-lexemes.py ├── .github └── workflows │ └── test.yaml ├── mwapi_utils.py ├── job-purge-all-lexemes.yaml ├── language_info.py ├── dev-requirements.txt ├── tool_translations_config.py ├── test_flask_utils.py ├── wikibase_types.py ├── requirements.txt ├── parse_tpsv.py └── test_parse_tpsv.py /.python-version: -------------------------------------------------------------------------------- 1 | 3.13 2 | -------------------------------------------------------------------------------- /Procfile: -------------------------------------------------------------------------------- 1 | web: gunicorn --workers=4 app:app 2 | -------------------------------------------------------------------------------- /mypy.ini: -------------------------------------------------------------------------------- 1 | [mypy] 2 | mypy_path = stubs/ 3 | files = . 4 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /config.yaml 2 | __pycache__/ 3 | /.pytest_cache/ 4 | /venv/ 5 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | .PHONY: check 2 | 3 | check: 4 | flake8 5 | mypy 6 | pytest 7 | -------------------------------------------------------------------------------- /config.yaml.example: -------------------------------------------------------------------------------- 1 | SECRET_KEY: ... 2 | OAUTH: 3 | CONSUMER_KEY: ... 4 | CONSUMER_SECRET: ... 5 | 6 | -------------------------------------------------------------------------------- /templates/advanced_partial_forms_hint.html: -------------------------------------------------------------------------------- 1 |
{{ message( 'advanced-partial-forms-hint' ) }}
2 | -------------------------------------------------------------------------------- /.mailmap: -------------------------------------------------------------------------------- 1 | Lucas Werkmeister 2 | Nikki 3 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | The development of this software is covered by a [Code of Conduct](https://www.mediawiki.org/wiki/Code_of_Conduct). 2 | -------------------------------------------------------------------------------- /static/tool.css: -------------------------------------------------------------------------------- 1 | #collapsible-menu.nojs { 2 | display: flex; 3 | flex-grow: 1; 4 | } 5 | 6 | .template-li a:not(:hover) { 7 | text-decoration-line: none; 8 | } 9 | -------------------------------------------------------------------------------- /templates/no_duplicate.html: -------------------------------------------------------------------------------- 1 | 5 | -------------------------------------------------------------------------------- /dev-requirements.in: -------------------------------------------------------------------------------- 1 | -c requirements.txt 2 | flake8 3 | mypy >= 0.930 4 | pytest 5 | types-babel 6 | types-beautifulsoup4 7 | types-decorator 8 | types-PyYAML 9 | types-requests 10 | -------------------------------------------------------------------------------- /service.template: -------------------------------------------------------------------------------- 1 | # Toolforge webservice template 2 | # Provide default arguments for `webservice start` commands for this tool. 3 | type: buildservice 4 | health-check-path: /healthz 5 | mount: none 6 | -------------------------------------------------------------------------------- /i18n/.dir-locals.el: -------------------------------------------------------------------------------- 1 | ;;; Directory Local Variables 2 | ;;; For more information see (info "(emacs) Directory Variables") 3 | 4 | ((js-mode . ((indent-tabs-mode . t) 5 | (tab-width . 4)))) 6 | -------------------------------------------------------------------------------- /static/edit.css: -------------------------------------------------------------------------------- 1 | :root.dragging input[name=form_representation] { 2 | outline: 2px dashed red; /* should be border but see #74 on GitHub */ 3 | } 4 | 5 | :root.draggable .grabbable { 6 | cursor: grab; 7 | } 8 | -------------------------------------------------------------------------------- /templates/bulk-not-allowed.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | {% block main %} 3 |
4 | {{ message('bulk-not-allowed', user_name=user_name) }} 5 |
6 | {% endblock main %} 7 | -------------------------------------------------------------------------------- /entity_ids/__init__.py: -------------------------------------------------------------------------------- 1 | from .property_ids import * # noqa: F401, F403 2 | from .language_item_ids import * # noqa: F401, F403 3 | from .lexical_category_item_ids import * # noqa: F401, F403 4 | from .other_item_ids import * # noqa: F401, F403 5 | -------------------------------------------------------------------------------- /flask_utils.py: -------------------------------------------------------------------------------- 1 | import flask.json 2 | 3 | class SetJSONProvider(flask.json.provider.DefaultJSONProvider): 4 | @staticmethod 5 | def default(o): 6 | if isinstance(o, set): 7 | return sorted(o) 8 | return super().default(o) 9 | -------------------------------------------------------------------------------- /stubs/toolforge.pyi: -------------------------------------------------------------------------------- 1 | from typing import Any, Optional 2 | 3 | 4 | def set_user_agent(tool: str, url: Optional[str] = None, email: Optional[str] = None) -> str: ... 5 | 6 | 7 | # catch-all for any unstubbed attributes 8 | def __getattr__(name) -> Any: ... 9 | -------------------------------------------------------------------------------- /requirements.in: -------------------------------------------------------------------------------- 1 | babel 2 | decorator 3 | flask >= 2.2.0 4 | gunicorn 5 | jinja2 >= 3.0.0 6 | MarkupSafe 7 | mwapi >= 0.6.0 8 | mwoauth 9 | PyYAML 10 | requests 11 | requests_oauthlib 12 | toolforge >= 6.1 13 | toolforge_i18n[Flask] >= 0.1.0 14 | werkzeug >= 2.0.0 15 | -------------------------------------------------------------------------------- /templates/error-api.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | {% block main %} 3 | 11 | {% endblock main %} 12 | -------------------------------------------------------------------------------- /templates/no-such-template.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | {% block title %}Unknown template – {{ super() }}{% endblock title %} 3 | {% block main %} 4 | 8 | {% endblock main %} 9 | -------------------------------------------------------------------------------- /.flake8: -------------------------------------------------------------------------------- 1 | # -*- mode: conf; -*- 2 | [flake8] 3 | exclude = 4 | venv 5 | ignore = 6 | # expected 2 blank lines... 7 | # ...between functions and classes 8 | E302 9 | # ...after functions and classes 10 | E305 11 | # line too long 12 | E501 13 | # "'name' may be undefined, or defined from star imports" 14 | # (spoiler: it’s star imports) 15 | F405 16 | # line break after binary operator 17 | W504 18 | -------------------------------------------------------------------------------- /i18n/mrh.json: -------------------------------------------------------------------------------- 1 | { 2 | "@metadata": { 3 | "authors": [ 4 | "Kiathy" 5 | ] 6 | }, 7 | "create": "Tao", 8 | "form-optional": " (na khoh khiah)", 9 | "advanced": "Riasâhpa", 10 | "advanced-general": "\"Riasâhpa\" mode lia na y.", 11 | "lexeme-id": "Lexeme ID", 12 | "edit-link": "taopathi", 13 | "title-create": "$1 (tao)", 14 | "title-advanced": "$1 (riasâhpa)", 15 | "title-bulk": "$1 (apô)", 16 | "title-edit": "$1 (taopathi)" 17 | } 18 | -------------------------------------------------------------------------------- /static/menu.js: -------------------------------------------------------------------------------- 1 | document.addEventListener('DOMContentLoaded', function() { 2 | 'use strict'; 3 | 4 | const menu = document.getElementById('collapsible-menu'); 5 | menu.classList.remove('nojs'); 6 | menu.classList.add('collapse', 'navbar-collapse'); 7 | 8 | const button = document.querySelector('nav .navbar-toggler'); 9 | button.hidden = false; 10 | button.addEventListener('click', () => { menu.classList.toggle('show'); }); 11 | }); 12 | -------------------------------------------------------------------------------- /static/preventDoubleSubmit.js: -------------------------------------------------------------------------------- 1 | document.addEventListener('DOMContentLoaded', function() { 2 | 'use strict'; 3 | 4 | for (const form of document.forms) { 5 | let lastSubmit = null; 6 | form.addEventListener('submit', (e) => { 7 | if (lastSubmit && (Date.now() - lastSubmit) < 5000) { 8 | e.preventDefault(); 9 | } else { 10 | lastSubmit = Date.now(); 11 | } 12 | }); 13 | } 14 | }); 15 | -------------------------------------------------------------------------------- /uwsgi.ini: -------------------------------------------------------------------------------- 1 | [uwsgi] 2 | # default buffer size for all request headers is just 4096 bytes, 3 | # while the browser limit for all cookies is some 4093 bytes – 4 | # to fully utilize the cookie limit, we need to raise the request limit a bit 5 | buffer-size = 8192 6 | # rotate uwsgi.log after 100 MiB 7 | log-maxsize = 104857600 8 | # don’t log the /healthz health-check endpoint to ~/uwsgi.log 9 | # (it’s included in Kubernetes startup and liveness probes so it gets lots of requests) 10 | route = ^/healthz donotlog: 11 | -------------------------------------------------------------------------------- /i18n/th.json: -------------------------------------------------------------------------------- 1 | { 2 | "@metadata": { 3 | "authors": [ 4 | "Ekminarin", 5 | "Patsagorn Y." 6 | ] 7 | }, 8 | "create": "สร้าง", 9 | "form-optional": " (ไม่บังคับ)", 10 | "advanced": "ขั้นสูง", 11 | "advanced-general": "คุณอยู่ในโหมด \"ขั้นสูง\"", 12 | "edit-link": "แก้ไข", 13 | "title-create": "$1 (สร้าง)", 14 | "title-advanced": "$1 (ขั้นสูง)", 15 | "title-edit": "$1 (แก้ไข)", 16 | "settings-save": "บันทึก", 17 | "login": "เข้าสู่ระบบ", 18 | "settings-link": "การตั้งค่า", 19 | "settings-heading": "การตั้งค่า" 20 | } 21 | -------------------------------------------------------------------------------- /i18n/cy.json: -------------------------------------------------------------------------------- 1 | { 2 | "@metadata": { 3 | "authors": [ 4 | "Afalau", 5 | "Ceirios" 6 | ] 7 | }, 8 | "create": "Creu", 9 | "form-optional": " (dewisol)", 10 | "advanced": "Uwch", 11 | "lexeme-id": "ID y Geirem", 12 | "bulk-format-help": "cymorth fformatio", 13 | "edit-link": "golygu", 14 | "title-create": "$1 (creu)", 15 | "title-advanced": "$1 (uwch)", 16 | "title-edit": "$1 (golygu)", 17 | "settings-save": "Cadw", 18 | "login": "Mewngofnodi", 19 | "settings-link": "Gosodiadau", 20 | "settings-heading": "Gosodiadau" 21 | } 22 | -------------------------------------------------------------------------------- /templates/template_li.html: -------------------------------------------------------------------------------- 1 | {% macro template_li(template) %} 2 | {% set template_name = template['@template_name'] %} 3 |
  • 4 | 5 | {{ template.label }} 6 | {% if can_use_bulk_mode %} 7 | ({{ message('bulk-link') }}) 8 | {% endif %} 9 | 10 |
  • 11 | {% endmacro %} 12 | -------------------------------------------------------------------------------- /i18n/skr-arab.json: -------------------------------------------------------------------------------- 1 | { 2 | "@metadata": { 3 | "authors": [ 4 | "Bgo eiu", 5 | "Saraiki" 6 | ] 7 | }, 8 | "create": "بݨاؤ", 9 | "advanced": "ودھایا", 10 | "lexeme-id": "لفظی آئی ڈی", 11 | "bulk-link": "تھوک موڈ", 12 | "edit-link": "تبدیلی کرو", 13 | "title-create": "$1 (لفظ دیݨ)", 14 | "title-advanced": "$1 (ودھایا)", 15 | "title-edit": "$1 (تبدیلی کرݨ)", 16 | "settings-save": "بچاؤ", 17 | "documentation": "دستاویز کاری", 18 | "toolforge": "وکی میڈیا ٹولفورج", 19 | "source-code": "سورس کوڈ", 20 | "login": "لاگ ان تھیوو", 21 | "settings-link": "ترتیباں", 22 | "settings-heading": "ترتیباں" 23 | } 24 | -------------------------------------------------------------------------------- /test_language_info.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | from typing import Optional 3 | 4 | from language_info import label 5 | 6 | @pytest.mark.parametrize('code, expected', [ 7 | ('bn-x-Q6747180', 'ঝাড়খণ্ডী উপভাষা'), 8 | ('de-x-Q188', 'Deutsch'), 9 | ('en-x-Q188', 'German'), 10 | ('de-x-Q27860798', 'Protein structure comparison by alignment of distance matrices'), 11 | ('de-x-Q18775580', None), 12 | ('qqx-x-Q42', None), 13 | ('en-x-Q0', None), 14 | ('-x-Q1', None), 15 | ('en-x-y-z', None), 16 | ('en', None), 17 | ]) 18 | def test_label(code: str, expected: Optional[str]): 19 | assert label(code) == expected 20 | -------------------------------------------------------------------------------- /i18n/anp.json: -------------------------------------------------------------------------------- 1 | { 2 | "@metadata": { 3 | "authors": [ 4 | "Angpradesh", 5 | "Proabscorp!" 6 | ] 7 | }, 8 | "create": "बनाबौ", 9 | "csrf-warning": "क्षमा करौ, हम्में ई अनुरोध क प्रोसेस नै करअ पारलिऐ (CSRF सुरक्षा असफल रहलै)। कृपा करी क फार्म क दोबारा जमा करी क चेष्टा करिऐ।", 10 | "advanced": "उन्नत स्तर", 11 | "lexeme-id": "शब्दिम ID", 12 | "bulk-link": "थोक बला मोड", 13 | "bulk-format-help": "प्रारूपन मँ सहायता", 14 | "edit-link": "संपादन करौ", 15 | "title-create": "$1 (बनाबौ)", 16 | "title-advanced": "$1 (उन्नत)", 17 | "title-bulk": "$1 (थोक)", 18 | "title-edit": "$1 (सम्पादन)" 19 | } 20 | -------------------------------------------------------------------------------- /.gitlab-ci.yml: -------------------------------------------------------------------------------- 1 | default: 2 | image: python:3.13 3 | cache: 4 | - key: pip-python-3.13 5 | paths: 6 | - .cache/pip 7 | 8 | stages: 9 | - test 10 | 11 | variables: 12 | PYTHONDONTWRITEBYTECODE: "1" 13 | PIP_CACHE_DIR: "$CI_PROJECT_DIR/.cache/pip" 14 | 15 | test-job: 16 | stage: test 17 | script: 18 | - python3 -m pip install -r requirements.txt -r dev-requirements.txt 19 | - make check 20 | 21 | test-prod-requirements-job: 22 | stage: test 23 | script: 24 | # only install prod requirements 25 | - python3 -m pip install -r requirements.txt 26 | # check that app.py runs without crashing on a missing import 27 | - python3 app.py 28 | -------------------------------------------------------------------------------- /templates/ambiguous-template.html: -------------------------------------------------------------------------------- 1 | {% from 'template_li.html' import template_li %} 2 | {% extends "base.html" %} 3 | {% block title %}Ambiguous template {{ template_name }} – {{ super() }}{% endblock title %} 4 | {% block main %} 5 |