diff --git a/qt/aqt/about.py b/qt/aqt/about.py index 79fee400e..f2470ccc4 100644 --- a/qt/aqt/about.py +++ b/qt/aqt/about.py @@ -93,14 +93,8 @@ def show(mw): # WebView contents ###################################################################### abouttext = "
" - abouttext += "

" + _( - "Anki is a friendly, intelligent spaced learning \ -system. It's free and open source." - ) - abouttext += "

" + _( - "Anki is licensed under the AGPL3 license. Please see " - "the license file in the source distribution for more information." - ) + abouttext += "

" + tr(TR.ABOUT_ANKI_IS_A_FRIENDLY_INTELLIGENT_SPACED) + abouttext += "

" + tr(TR.ABOUT_ANKI_IS_LICENSED_UNDER_THE_AGPL3) abouttext += "

" + tr(TR.ABOUT_VERSION, val=versionWithBuild()) + "
" abouttext += ("Python %s Qt %s PyQt %s
") % ( platform.python_version(), @@ -209,22 +203,11 @@ system. It's free and open source." ) ) - abouttext += ( - "

" - + _( - "Written by Damien Elmes, with patches, translation,\ - testing and design from:

%(cont)s" - ) - % {"cont": ", ".join(allusers)} - ) - abouttext += "

" + _( - "If you have contributed and are not on this list, \ -please get in touch." - ) - abouttext += "

" + _( - "A big thanks to all the people who have provided \ -suggestions, bug reports and donations." - ) + abouttext += "

" + tr(TR.ABOUT_WRITTEN_BY_DAMIEN_ELMES_WITH_PATCHES) % { + "cont": ", ".join(allusers) + } + abouttext += "

" + tr(TR.ABOUT_IF_YOU_HAVE_CONTRIBUTED_AND_ARE) + abouttext += "

" + tr(TR.ABOUT_A_BIG_THANKS_TO_ALL_THE) abt.label.setMinimumWidth(800) abt.label.setMinimumHeight(600) dialog.show() diff --git a/qt/aqt/addcards.py b/qt/aqt/addcards.py index 757c08f8a..bd2492757 100644 --- a/qt/aqt/addcards.py +++ b/qt/aqt/addcards.py @@ -175,12 +175,7 @@ class AddCards(QDialog): return None if note.model()["type"] == MODEL_CLOZE: if not note.cloze_numbers_in_fields(): - if not askUser( - _( - "You have a cloze deletion note type " - "but have not made any cloze deletions. Proceed?" - ) - ): + if not askUser(tr(TR.ADDING_YOU_HAVE_A_CLOZE_DELETION_NOTE)): return None self.mw.col.add_note(note, self.deckChooser.selectedId()) self.mw.col.clearUndo() diff --git a/qt/aqt/addons.py b/qt/aqt/addons.py index c4ea1392e..8053278be 100644 --- a/qt/aqt/addons.py +++ b/qt/aqt/addons.py @@ -836,12 +836,7 @@ class AddonsDialog(QDialog): if not selected: return if not askUser( - ngettext( - "Delete the %(num)d selected add-on?", - "Delete the %(num)d selected add-ons?", - len(selected), - ) - % dict(num=len(selected)) + tr(TR.ADDONS_DELETE_THE_NUMD_SELECTED_ADDON, count=len(selected)) ): return for dir in selected: diff --git a/qt/aqt/browser.py b/qt/aqt/browser.py index 1ab4b6347..e4bec4192 100644 --- a/qt/aqt/browser.py +++ b/qt/aqt/browser.py @@ -17,7 +17,6 @@ import aqt.forms from anki.cards import Card from anki.collection import Collection from anki.consts import * -from anki.lang import _, ngettext from anki.models import NoteType from anki.notes import Note from anki.rsbackend import TR, DeckTreeNode, InvalidInput @@ -821,16 +820,10 @@ class Browser(QMainWindow): def updateTitle(self): selected = len(self.form.tableView.selectionModel().selectedRows()) cur = len(self.model.cards) + print("fixme: browser updateTitle()") self.setWindowTitle( - ngettext( - "Browse (%(cur)d card shown; %(sel)s)", - "Browse (%(cur)d cards shown; %(sel)s)", - cur, - ) - % { - "cur": cur, - "sel": ngettext("%d selected", "%d selected", selected) % selected, - } + "Browse (%(cur)d cards shown; %(sel)s)" + % {"cur": cur, "sel": self.col.tr(TR.BROWSING_SELECTED, count=selected)} ) return selected @@ -2266,14 +2259,7 @@ class ChangeModel(QDialog): fmap = self.getFieldMap() cmap = self.getTemplateMap() if any(True for c in list(cmap.values()) if c is None): - if not askUser( - _( - """\ -Any cards mapped to nothing will be deleted. \ -If a note has no remaining cards, it will be lost. \ -Are you sure you want to continue?""" - ) - ): + if not askUser(tr(TR.BROWSING_ANY_CARDS_MAPPED_TO_NOTHING_WILL)): return self.browser.mw.checkpoint(tr(TR.BROWSING_CHANGE_NOTE_TYPE)) b = self.browser diff --git a/qt/aqt/clayout.py b/qt/aqt/clayout.py index 55a26d7ed..9bd1c467c 100644 --- a/qt/aqt/clayout.py +++ b/qt/aqt/clayout.py @@ -9,7 +9,6 @@ from typing import Any, Dict, List, Optional import aqt from anki.cards import Card from anki.consts import * -from anki.lang import _, ngettext from anki.notes import Note from anki.rsbackend import TemplateError from anki.template import TemplateRenderContext @@ -656,13 +655,7 @@ class CardLayout(QDialog): def _flipQA(self, src, dst): m = re.match("(?s)(.+)


(.+)", src["afmt"]) if not m: - showInfo( - _( - """\ -Anki couldn't find the line between the question and answer. Please \ -adjust the template manually to switch the question and answer.""" - ) - ) + showInfo(tr(TR.CARD_TEMPLATES_ANKI_COULDNT_FIND_THE_LINE_BETWEEN)) return self.change_tracker.mark_basic() dst["afmt"] = "{{FrontSide}}\n\n
\n\n%s" % src["qfmt"] @@ -736,10 +729,7 @@ adjust the template manually to switch the question and answer.""" d.setMinimumWidth(400) l = QVBoxLayout() lab = QLabel( - _( - """\ -Enter deck to place new %s cards in, or leave blank:""" - ) + tr(TR.CARD_TEMPLATES_ENTER_DECK_TO_PLACE_NEW, val="%s") % self.current_template()["name"] ) lab.setWordWrap(True) diff --git a/qt/aqt/deckbrowser.py b/qt/aqt/deckbrowser.py index eaf899ff6..00f63f2e0 100644 --- a/qt/aqt/deckbrowser.py +++ b/qt/aqt/deckbrowser.py @@ -8,7 +8,6 @@ from dataclasses import dataclass import aqt from anki.errors import DeckRenameError -from anki.lang import _, ngettext from anki.rsbackend import TR, DeckTreeNode from anki.utils import ids2str from aqt import AnkiQt, gui_hooks diff --git a/qt/aqt/deckconf.py b/qt/aqt/deckconf.py index 8c3af16f6..24fa1dfe2 100644 --- a/qt/aqt/deckconf.py +++ b/qt/aqt/deckconf.py @@ -8,7 +8,6 @@ from PyQt5.QtWidgets import QLineEdit import aqt from anki.consts import NEW_CARDS_RANDOM -from anki.lang import _, ngettext from aqt import gui_hooks from aqt.qt import * from aqt.utils import ( @@ -110,10 +109,7 @@ class DeckConf(QDialog): self.loadConf() cnt = len(self.mw.col.decks.didsForConf(conf)) if cnt > 1: - txt = _( - "Your changes will affect multiple decks. If you wish to " - "change only the current deck, please add a new options group first." - ) + txt = tr(TR.SCHEDULING_YOUR_CHANGES_WILL_AFFECT_MULTIPLE_DECKS) else: txt = "" self.form.count.setText(txt) diff --git a/qt/aqt/dyndeckconf.py b/qt/aqt/dyndeckconf.py index 43b134be9..3389ebb84 100644 --- a/qt/aqt/dyndeckconf.py +++ b/qt/aqt/dyndeckconf.py @@ -123,13 +123,7 @@ class DeckConf(QDialog): if not self.saveConf(): return if not self.mw.col.sched.rebuild_filtered_deck(self.deck["id"]): - if askUser( - _( - """\ -The provided search did not match any cards. Would you like to revise \ -it?""" - ) - ): + if askUser(tr(TR.DECKS_THE_PROVIDED_SEARCH_DID_NOT_MATCH)): return self.mw.reset() QDialog.accept(self) diff --git a/qt/aqt/editor.py b/qt/aqt/editor.py index 4b221c1bd..e55f5f2a1 100644 --- a/qt/aqt/editor.py +++ b/qt/aqt/editor.py @@ -658,20 +658,9 @@ class Editor: # check that the model is set up for cloze deletion if not re.search("{{(.*:)*cloze:", self.note.model()["tmpls"][0]["qfmt"]): if self.addMode: - tooltip( - _( - "Warning, cloze deletions will not work until " - "you switch the type at the top to Cloze." - ) - ) + tooltip(tr(TR.EDITING_WARNING_CLOZE_DELETIONS_WILL_NOT_WORK)) else: - showInfo( - _( - """\ -To make a cloze deletion on an existing note, you need to change it \ -to a cloze type first, via 'Notes>Change Note Type'""" - ) - ) + showInfo(tr(TR.EDITING_TO_MAKE_A_CLOZE_DELETION_ON)) return # find the highest existing cloze highest = 0 diff --git a/qt/aqt/errors.py b/qt/aqt/errors.py index 94b606e34..c78f527cb 100644 --- a/qt/aqt/errors.py +++ b/qt/aqt/errors.py @@ -65,10 +65,7 @@ class ErrorHandler(QObject): self.timer.start() def tempFolderMsg(self): - return _( - """Unable to access Anki media folder. The permissions on \ -your system's temporary folder may be incorrect.""" - ) + return tr(TR.QT_MISC_UNABLE_TO_ACCESS_ANKI_MEDIA_FOLDER) def onTimeout(self): error = html.escape(self.pool) @@ -79,12 +76,7 @@ your system's temporary folder may be incorrect.""" if "10013" in error: return showWarning(tr(TR.QT_MISC_YOUR_FIREWALL_OR_ANTIVIRUS_PROGRAM_IS)) if "no default input" in error.lower(): - return showWarning( - _( - "Please connect a microphone, and ensure " - "other programs are not using the audio device." - ) - ) + return showWarning(tr(TR.QT_MISC_PLEASE_CONNECT_A_MICROPHONE_AND_ENSURE)) if "invalidTempFolder" in error: return showWarning(self.tempFolderMsg()) if "Beautiful Soup is not an HTTP client" in error: diff --git a/qt/aqt/exporting.py b/qt/aqt/exporting.py index a4c0d0b3c..2d9b5c909 100644 --- a/qt/aqt/exporting.py +++ b/qt/aqt/exporting.py @@ -12,7 +12,6 @@ from typing import List, Optional import aqt from anki import hooks from anki.exporting import Exporter, exporters -from anki.lang import _, ngettext from aqt.qt import * from aqt.utils import TR, checkInvalidFilename, getSaveFile, showWarning, tooltip, tr diff --git a/qt/aqt/fields.py b/qt/aqt/fields.py index 173a7edb4..f326228bc 100644 --- a/qt/aqt/fields.py +++ b/qt/aqt/fields.py @@ -3,7 +3,6 @@ import aqt from anki.consts import * -from anki.lang import _, ngettext from anki.models import NoteType from anki.rsbackend import TemplateError from aqt import AnkiQt, gui_hooks diff --git a/qt/aqt/importing.py b/qt/aqt/importing.py index 176a9e78e..4116673e5 100644 --- a/qt/aqt/importing.py +++ b/qt/aqt/importing.py @@ -15,7 +15,6 @@ import anki.importing as importing import aqt.deckchooser import aqt.forms import aqt.modelchooser -from anki.lang import _, ngettext from aqt import AnkiQt, gui_hooks from aqt.qt import * from aqt.utils import ( @@ -118,12 +117,7 @@ class ImportDialog(QDialog): def onDelimiter(self): str = ( getOnlyText( - _( - """\ -By default, Anki will detect the character between fields, such as -a tab, comma, and so on. If Anki is detecting the character incorrectly, -you can enter it here. Use \\t to represent tab.""" - ), + tr(TR.IMPORTING_BY_DEFAULT_ANKI_WILL_DETECT_THE), self, help="importing", ) @@ -132,10 +126,7 @@ you can enter it here. Use \\t to represent tab.""" str = str.replace("\\t", "\t") if len(str) > 1: showWarning( - _( - "Multi-character separators are not supported. " - "Please enter one character only." - ) + tr(TR.IMPORTING_MULTICHARACTER_SEPARATORS_ARE_NOT_SUPPORTED_PLEASE) ) return self.hideMapping() @@ -297,12 +288,7 @@ you can enter it here. Use \\t to represent tab.""" def showUnicodeWarning(): """Shorthand to show a standard warning.""" - showWarning( - _( - "Selected file was not in UTF-8 format. Please see the " - "importing section of the manual." - ) - ) + showWarning(tr(TR.IMPORTING_SELECTED_FILE_WAS_NOT_IN_UTF8)) def onImport(mw): @@ -390,20 +376,12 @@ def importFile(mw, file): except Exception as e: err = repr(str(e)) if "invalidFile" in err: - msg = _( - """\ -Invalid file. Please restore from backup.""" - ) + msg = tr(TR.IMPORTING_INVALID_FILE_PLEASE_RESTORE_FROM_BACKUP) showWarning(msg) elif "invalidTempFolder" in err: showWarning(mw.errorHandler.tempFolderMsg()) elif "readonly" in err: - showWarning( - _( - """\ -Unable to import from a read-only file.""" - ) - ) + showWarning(tr(TR.IMPORTING_UNABLE_TO_IMPORT_FROM_A_READONLY)) else: msg = tr(TR.IMPORTING_FAILED_DEBUG_INFO) + "\n" msg += str(traceback.format_exc()) @@ -421,13 +399,7 @@ Unable to import from a read-only file.""" def invalidZipMsg(): - return _( - """\ -This file does not appear to be a valid .apkg file. If you're getting this \ -error from a file downloaded from AnkiWeb, chances are that your download \ -failed. Please try again, and if the problem persists, please try again \ -with a different browser.""" - ) + return tr(TR.IMPORTING_THIS_FILE_DOES_NOT_APPEAR_TO) def setupApkgImport(mw, importer): @@ -441,11 +413,7 @@ def setupApkgImport(mw, importer): # adding return True if not mw.restoringBackup and not askUser( - _( - """\ -This will delete your existing collection and replace it with the data in \ -the file you're importing. Are you sure?""" - ), + tr(TR.IMPORTING_THIS_WILL_DELETE_YOUR_EXISTING_COLLECTION), msgfunc=QMessageBox.warning, defaultno=True, ): diff --git a/qt/aqt/main.py b/qt/aqt/main.py index a15a8b617..670c7687a 100644 --- a/qt/aqt/main.py +++ b/qt/aqt/main.py @@ -29,7 +29,6 @@ from anki import hooks from anki.collection import Collection from anki.decks import Deck from anki.hooks import runHook -from anki.lang import _, ngettext from anki.rsbackend import RustBackend from anki.sound import AVTag, SoundOrVideoTag from anki.utils import devMode, ids2str, intTime, isMac, isWin, splitFields @@ -123,12 +122,7 @@ class AnkiQt(QMainWindow): sys.exit(1) # must call this after ui set up if self.safeMode: - tooltip( - _( - "Shift key was held down. Skipping automatic " - "syncing and add-on loading." - ) - ) + tooltip(tr(TR.QT_MISC_SHIFT_KEY_WAS_HELD_DOWN_SKIPPING)) # were we given a file to import? if args and args[0] and not self._isAddon(args[0]): self.onAppMsg(args[0]) @@ -319,11 +313,7 @@ class AnkiQt(QMainWindow): return showWarning(tr(TR.QT_MISC_THERE_MUST_BE_AT_LEAST_ONE)) # sure? if not askUser( - _( - """\ -All cards, notes, and media for this profile will be deleted. \ -Are you sure?""" - ), + tr(TR.QT_MISC_ALL_CARDS_NOTES_AND_MEDIA_FOR), msgfunc=QMessageBox.warning, defaultno=True, ): @@ -333,10 +323,7 @@ Are you sure?""" def onOpenBackup(self): if not askUser( - _( - """\ -Replace your collection with an earlier backup?""" - ), + tr(TR.QT_MISC_REPLACE_YOUR_COLLECTION_WITH_AN_EARLIER), msgfunc=QMessageBox.warning, defaultno=True, ): @@ -364,13 +351,7 @@ Replace your collection with an earlier backup?""" self.pendingImport = path self.restoringBackup = True - showInfo( - _( - """\ -Automatic syncing and backups have been disabled while restoring. To enable them again, \ -close the profile or restart Anki.""" - ) - ) + showInfo(tr(TR.QT_MISC_AUTOMATIC_SYNCING_AND_BACKUPS_HAVE_BEEN)) self.onOpenProfile() @@ -568,15 +549,7 @@ close the profile or restart Anki.""" self.col = None self.progress.finish() if corrupt: - showWarning( - _( - "Your collection file appears to be corrupt. \ -This can happen when the file is copied or moved while Anki is open, or \ -when the collection is stored on a network or cloud drive. If problems \ -persist after restarting your computer, please open an automatic backup \ -from the profile screen." - ) - ) + showWarning(tr(TR.QT_MISC_YOUR_COLLECTION_FILE_APPEARS_TO_BE)) if not corrupt and not self.restoringBackup: self.backup() @@ -1218,26 +1191,7 @@ title="%s" %s>%s""" % ( print("clock is off; ignoring") return diffText = tr(TR.QT_MISC_SECOND, count=diff) - warn = ( - _( - """\ -In order to ensure your collection works correctly when moved between \ -devices, Anki requires your computer's internal clock to be set correctly. \ -The internal clock can be wrong even if your system is showing the correct \ -local time. - -Please go to the time settings on your computer and check the following: - -- AM/PM -- Clock drift -- Day, month and year -- Timezone -- Daylight savings - -Difference to correct time: %s.""" - ) - % diffText - ) + warn = tr(TR.QT_MISC_IN_ORDER_TO_ENSURE_YOUR_COLLECTION, val="%s") % diffText showWarning(warn) self.app.closeAllWindows() @@ -1281,13 +1235,7 @@ Difference to correct time: %s.""" self._activeWindowOnPlay: Optional[QWidget] = None def onOdueInvalid(self): - showWarning( - _( - """\ -Invalid property found on card. Please use Tools>Check Database, \ -and if the problem comes up again, please ask on the support site.""" - ) - ) + showWarning(tr(TR.QT_MISC_INVALID_PROPERTY_FOUND_ON_CARD_PLEASE)) def _isVideo(self, tag: AVTag) -> bool: if isinstance(tag, SoundOrVideoTag): @@ -1336,15 +1284,7 @@ and if the problem comes up again, please ask on the support site.""" progress_shown = self.progress.busy() if progress_shown: self.progress.finish() - ret = askUser( - _( - """\ -The requested change will require a full upload of the database when \ -you next synchronize your collection. If you have reviews or other changes \ -waiting on another device that haven't been synchronized here yet, they \ -will be lost. Continue?""" - ) - ) + ret = askUser(tr(TR.QT_MISC_THE_REQUESTED_CHANGE_WILL_REQUIRE_A)) if progress_shown: self.progress.start() return ret @@ -1355,15 +1295,7 @@ will be lost. Continue?""" True if confirmed or already modified.""" if self.col.schemaChanged(): return True - return askUser( - _( - """\ -The requested change will require a full upload of the database when \ -you next synchronize your collection. If you have reviews or other changes \ -waiting on another device that haven't been synchronized here yet, they \ -will be lost. Continue?""" - ) - ) + return askUser(tr(TR.QT_MISC_THE_REQUESTED_CHANGE_WILL_REQUIRE_A)) # Advanced features ########################################################################## @@ -1603,10 +1535,7 @@ will be lost. Continue?""" # we can't raise the main window while in profile dialog, syncing, etc if buf != "raise": showInfo( - _( - """\ -Please ensure a profile is open and Anki is not busy, then try again.""" - ), + tr(TR.QT_MISC_PLEASE_ENSURE_A_PROFILE_IS_OPEN), parent=None, ) return None diff --git a/qt/aqt/models.py b/qt/aqt/models.py index 12d7f05f4..9c07f876c 100644 --- a/qt/aqt/models.py +++ b/qt/aqt/models.py @@ -7,7 +7,6 @@ from typing import Any, List, Optional, Sequence import aqt.clayout from anki import stdmodels from anki.backend_pb2 import NoteTypeNameIDUseCount -from anki.lang import _, ngettext from anki.models import NoteType from anki.notes import Note from anki.rsbackend import pb diff --git a/qt/aqt/overview.py b/qt/aqt/overview.py index b11cddf53..6c635087c 100644 --- a/qt/aqt/overview.py +++ b/qt/aqt/overview.py @@ -183,20 +183,9 @@ class Overview: def _desc(self, deck): if deck["dyn"]: - desc = _( - """\ -This is a special deck for studying outside of the normal schedule.""" - ) - desc += " " + _( - """\ -Cards will be automatically returned to their original decks after you review \ -them.""" - ) - desc += " " + _( - """\ -Deleting this deck from the deck list will return all remaining cards \ -to their original deck.""" - ) + desc = tr(TR.STUDYING_THIS_IS_A_SPECIAL_DECK_FOR) + desc += " " + tr(TR.STUDYING_CARDS_WILL_BE_AUTOMATICALLY_RETURNED_TO) + desc += " " + tr(TR.STUDYING_DELETING_THIS_DECK_FROM_THE_DECK) else: desc = deck.get("desc", "") if not desc: diff --git a/qt/aqt/preferences.py b/qt/aqt/preferences.py index fb49246db..a1e2f2090 100644 --- a/qt/aqt/preferences.py +++ b/qt/aqt/preferences.py @@ -181,11 +181,7 @@ class Preferences(QDialog): self.form.syncDeauth.setVisible(False) self.form.syncUser.setText("") self.form.syncLabel.setText( - _( - """\ -Synchronization
-Not currently enabled; click the sync button in the main window to enable.""" - ) + tr(TR.PREFERENCES_SYNCHRONIZATIONNOT_CURRENTLY_ENABLED_CLICK_THE_SYNC) ) def onSyncDeauth(self) -> None: diff --git a/qt/aqt/profiles.py b/qt/aqt/profiles.py index e9e87d152..8b8e2f31d 100644 --- a/qt/aqt/profiles.py +++ b/qt/aqt/profiles.py @@ -253,11 +253,7 @@ class ProfileManager: QMessageBox.warning( None, tr(TR.PROFILES_PROFILE_CORRUPT), - _( - """\ -Anki could not read your profile data. Window sizes and your sync login \ -details have been forgotten.""" - ), + tr(TR.PROFILES_ANKI_COULD_NOT_READ_YOUR_PROFILE), ) traceback.print_stack() print("resetting corrupt profile") @@ -322,14 +318,7 @@ details have been forgotten.""" except Exception as e: self.db.rollback() if "WinError 5" in str(e): - showWarning( - _( - """\ -Anki could not rename your profile because it could not rename the profile \ -folder on disk. Please ensure you have permission to write to Documents/Anki \ -and no other programs are accessing your profile folders, then try again.""" - ) - ) + showWarning(tr(TR.PROFILES_ANKI_COULD_NOT_RENAME_YOUR_PROFILE)) else: raise except: diff --git a/qt/aqt/reviewer.py b/qt/aqt/reviewer.py index 7b81e92ed..87d64d9a6 100644 --- a/qt/aqt/reviewer.py +++ b/qt/aqt/reviewer.py @@ -14,7 +14,6 @@ from PyQt5.QtCore import Qt from anki import hooks from anki.cards import Card -from anki.lang import _, ngettext from anki.utils import stripHTML from aqt import AnkiQt, gui_hooks from aqt.qt import * @@ -388,10 +387,7 @@ class Reviewer: if not self.typeCorrect: if self.typeCorrect is None: if clozeIdx: - warn = _( - """\ -Please run Tools>Empty Cards""" - ) + warn = tr(TR.STUDYING_PLEASE_RUN_TOOLSEMPTY_CARDS) else: warn = tr(TR.STUDYING_TYPE_ANSWER_UNKNOWN_FIELD, val=fld) return re.sub(self.typeAnsPat, warn, buf) diff --git a/qt/po/scripts/rewrite-refs.py b/qt/po/scripts/rewrite-refs.py index 5bcfce179..e8a6c06f7 100644 --- a/qt/po/scripts/rewrite-refs.py +++ b/qt/po/scripts/rewrite-refs.py @@ -3,13 +3,10 @@ import glob, re, json, stringcase files = ( - # glob.glob("../../pylib/**/*.py", recursive=True) + # glob.glob("../../pylib/**/*.py", recursive=True) + glob.glob("../../qt/**/*.py", recursive=True) - # glob.glob("../../qt/**/forms/*.ui", recursive=True) -) -string_re = re.compile( - r'ngettext\(\s*"(.+?)",\s+".+?",\s+(.+?)\s*,?\s*\)\s+%\s+\2', re.DOTALL ) +string_re = re.compile(r'_\(\s*(".*?")\s*\)', re.DOTALL) map = json.load(open("keys_by_text.json")) @@ -18,67 +15,29 @@ blacklist = { "Label1", "After pressing OK, you can choose which tags to include.", "Filter/Cram", - "Show %s", - "~", - "about:blank", - "%d card imported.", - # need to update manually - "Browse (%(cur)d card shown; %(sel)s)", # previewer.py needs updating to fix these "Shortcut key: R", "Shortcut key: B", } -from html.entities import name2codepoint - -reEnts = re.compile(r"&#?\w+;") - - -def decode_ents(html): - def fixup(m): - text = m.group(0) - if text[:2] == "&#": - # character reference - try: - if text[:3] == "&#x": - return chr(int(text[3:-1], 16)) - else: - return chr(int(text[2:-1])) - except ValueError: - pass - else: - # named entity - try: - text = chr(name2codepoint[text[1:-1]]) - except KeyError: - pass - return text # leave as is - - return reEnts.sub(fixup, html) - - -def munge_key(key): - if key == "browsing-note": - return "browsing-note-count" - if key == "card-templates-card": - return "card-templates-card-count" - return key - def repl(m): - print(m.group(0)) - text = decode_ents(m.group(1)) + # the argument may consistent of multiple strings that need merging together + text = eval("(" + m.group(1) + ")") + print(f"text is `{text}`") + if text in blacklist: return m.group(0) (module, key) = map[text] - key = munge_key(key) - screaming = stringcase.constcase(key) + print(screaming) - ret = f"tr(TR.{screaming}, count={m.group(2)})" - print(ret) - return ret + if "%d" in text or "%s" in text: + # replace { $val } with %s for compat with old code + return f'tr(TR.{screaming}, val="%s")' + + return f"tr(TR.{screaming})" for file in files: