Merge branch 'master' into init-lang

This commit is contained in:
Damien Elmes 2020-11-22 16:10:49 +10:00 committed by GitHub
commit d85d0b88a1
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
42 changed files with 440 additions and 275 deletions

View file

@ -1,3 +1,5 @@
load("@py_deps//:requirements.bzl", "requirement")
filegroup(
name = "ftl",
srcs = [
@ -7,6 +9,44 @@ filegroup(
visibility = ["//rslib:__subpackages__"],
)
py_binary(
name = "format",
srcs = ["format.py"],
deps = [requirement("fluent-syntax")],
)
py_test(
name = "format_check",
srcs = [
"format.py",
"format_check.py",
],
# so we can locate data files
args = ["$(location BUILD.bazel)"],
data = glob(["**/*.ftl"]) + ["BUILD.bazel"],
deps = [requirement("fluent-syntax")],
)
py_binary(
name = "sync",
srcs = ["sync.py"],
tags = ["manual"],
)
py_binary(
name = "extract-strings",
srcs = ["extract-strings.py"],
tags = ["manual"],
deps = [requirement("fluent-syntax")],
)
py_binary(
name = "transform-string",
srcs = ["transform-string.py"],
tags = ["manual"],
deps = [requirement("fluent-syntax")],
)
# export this file as a way of locating the top level folder in $(location ...)
exports_files(
["BUILD.bazel"],

3
ftl/README.md Normal file
View file

@ -0,0 +1,3 @@
Files related to Anki's translations.
Please see https://translating.ankiweb.net/#/anki/developers

View file

@ -43,7 +43,7 @@ browsing-first-card = First Card
browsing-flag = Flag
browsing-font = <b>Font</b>:
browsing-font-size = <b>Font Size</b>:
browsing-found-as-across-bs = Found %(a)s across %(b)s.
browsing-found-as-across-bs = Found { $part } across { $whole }.
browsing-home = Home
browsing-ignore-case = Ignore case
browsing-in = <b>In</b>:
@ -54,7 +54,7 @@ browsing-line-size = <b>Line Size</b>:
browsing-manage-note-types = Manage Note Types...
browsing-move-cards = Move Cards
browsing-move-cards-to-deck = Move cards to deck:
browsing-nd-names = %(n)d: %(name)s
browsing-nd-names = { $num }: { $name }
browsing-new = (new)
browsing-new-note-type = New note type:
browsing-no-flag = No Flag
@ -102,17 +102,19 @@ browsing-treat-input-as-regular-expression = Treat input as regular expression
browsing-type-here-to-search = <type here to search; hit enter to show current deck>
browsing-whole-collection = Whole Collection
browsing-you-must-have-at-least-one = You must have at least one column.
browsing-group = { $count ->
browsing-group =
{ $count ->
[one] { $count } group
*[other] { $count } groups
}
browsing-note-count = { $count ->
browsing-note-count =
{ $count ->
[one] { $count } note
*[other] { $count } notes
}
browsing-note-deleted = { $count ->
browsing-note-deleted =
{ $count ->
[one] { $count } note deleted.
*[other] { $count } notes deleted.
}
browsing-window-title = Browse ({ $selected } of { $total } cards selected)

View file

@ -22,4 +22,3 @@ card-stats-review-log-type-review = Review
card-stats-review-log-type-relearn = Relearn
card-stats-review-log-type-filtered = Filtered
card-stats-review-log-type-manual = Manual

View file

@ -4,39 +4,27 @@
# Label of link users can click on
card-template-rendering-more-info = More information
card-template-rendering-front-side-problem = Front template has a problem:
card-template-rendering-back-side-problem = Back template has a problem:
# when the user forgot to close a field reference,
# eg, Missing '}}' in '{{Field'
card-template-rendering-no-closing-brackets =
Missing '{$missing}' in '{$tag}'
card-template-rendering-no-closing-brackets = Missing '{ $missing }' in '{ $tag }'
# when the user opened a conditional, but forgot to close it
# eg, Missing '{{/Conditional}}'
card-template-rendering-conditional-not-closed =
Missing '{$missing}'
card-template-rendering-conditional-not-closed = Missing '{ $missing }'
# when the user closed the wrong conditional
# eg, Found '{{/Something}}', but expected '{{/SomethingElse}}'
card-template-rendering-wrong-conditional-closed =
Found '{$found}', but expected '{$expected}'
card-template-rendering-wrong-conditional-closed = Found '{ $found }', but expected '{ $expected }'
# when the user closed a conditional that wasn't open
# eg, Found '{{/Something}}', but missing '{{#Something}}' or '{{^Something}}'
card-template-rendering-conditional-not-open =
Found '{$found}', but missing '{$missing1}' or '{$missing2}'
card-template-rendering-conditional-not-open = Found '{ $found }', but missing '{ $missing1 }' or '{ $missing2 }'
# when the user referenced a field that doesn't exist
# eg, Found '{{Field}}', but there is not field called 'Field'
card-template-rendering-no-such-field =
Found '{$found}', but there is no field called '{$field}'
card-template-rendering-no-such-field = Found '{ $found }', but there is no field called '{ $field }'
# This message is shown when the front side of the card is blank,
# either due to a badly-designed template, or because required fields
# are missing.
card-template-rendering-empty-front = The front of this card is blank.
card-template-rendering-missing-cloze = No cloze { $number } found on card.
card-template-rendering-missing-cloze =
No cloze { $number } found on card.
Please either add a cloze deletion, or use the Empty Cards tool.

View file

@ -30,7 +30,7 @@ card-templates-card-types = Card Types
card-templates-card-types-for = Card Types for { $val }
card-templates-cloze = Cloze { $val }
card-templates-deck-override = Deck Override...
card-templates-delete-the-as-card-type-and = Delete the '%(a)s' card type, and its %(b)s?
card-templates-delete-the-as-card-type-and = Delete the '{ $template }' card type, and its { $cards }?
card-templates-enter-deck-to-place-new = Enter deck to place new { $val } cards in, or leave blank:
card-templates-enter-new-card-position-1 = Enter new card position (1...{ $val }):
card-templates-flip = Flip
@ -40,11 +40,13 @@ card-templates-on = (on)
card-templates-remove-card-type = Remove Card Type...
card-templates-rename-card-type = Rename Card Type...
card-templates-reposition-card-type = Reposition Card Type...
card-templates-card-count = { $count ->
card-templates-card-count =
{ $count ->
[one] { $count } card
*[other] { $count } cards
}
card-templates-this-will-create-card-proceed = { $count ->
card-templates-this-will-create-card-proceed =
{ $count ->
[one] This will create { $count } card. Proceed?
*[other] This will create { $count } cards. Proceed?
}

View file

@ -31,7 +31,8 @@ decks-reschedule-cards-based-on-my-answers = Reschedule cards based on my answer
decks-study = Study
decks-study-deck = Study Deck
decks-the-provided-search-did-not-match = The provided search did not match any cards. Would you like to revise it?
decks-it-has-card = { $count ->
decks-it-has-card =
{ $count ->
[one] It has { $count } card.
*[other] It has { $count } cards.
}

View file

@ -1,11 +1,11 @@
empty-cards-for-note-type = Empty cards for { $notetype }:
empty-cards-count-line =
{ $empty_count } of { $existing_count } cards empty ({ $template_names }).
empty-cards-count-line = { $empty_count } of { $existing_count } cards empty ({ $template_names }).
empty-cards-window-title = Empty Cards
empty-cards-preserve-notes-checkbox = Keep notes with no valid cards
empty-cards-delete-button = Delete
empty-cards-not-found = No empty cards.
empty-cards-deleted-count = Deleted { $cards ->
empty-cards-deleted-count =
Deleted { $cards ->
[one] { $cards } card.
*[other] { $cards } cards.
}

View file

@ -15,15 +15,18 @@ exporting-include-scheduling-information = Include scheduling information
exporting-include-tags = Include tags
exporting-notes-in-plain-text = Notes in Plain Text
exporting-selected-notes = Selected Notes
exporting-card-exported = { $count ->
exporting-card-exported =
{ $count ->
[one] { $count } card exported.
*[other] { $count } cards exported.
}
exporting-exported-media-file = { $count ->
exporting-exported-media-file =
{ $count ->
[one] Exported { $count } media file
*[other] Exported { $count } media files
}
exporting-note-exported = { $count ->
exporting-note-exported =
{ $count ->
[one] { $count } note exported.
*[other] { $count } notes exported.
}

View file

@ -1,4 +1,5 @@
findreplace-notes-updated = { $total ->
findreplace-notes-updated =
{ $total ->
[one] { $changed } of { $total } note updated
*[other] { $changed } of { $total } notes updated
}

View file

@ -36,7 +36,7 @@ importing-notes-that-could-not-be-imported = Notes that could not be imported as
importing-notes-updated-as-file-had-newer = Notes updated, as file had newer version: { $val }
importing-packaged-anki-deckcollection-apkg-colpkg-zip = Packaged Anki Deck/Collection (*.apkg *.colpkg *.zip)
importing-pauker-18-lesson-paugz = Pauker 1.8 Lesson (*.pau.gz)
importing-rows-had-num1d-fields-expected-num2d = '%(row)s' had %(num1)d fields, expected %(num2)d
importing-rows-had-num1d-fields-expected-num2d = '{ $row }' had { $found } fields, expected { $expected }
importing-selected-file-was-not-in-utf8 = Selected file was not in UTF-8 format. Please see the importing section of the manual.
importing-semicolon = Semicolon
importing-skipped = Skipped
@ -52,23 +52,28 @@ importing-unable-to-import-from-a-readonly = Unable to import from a read-only f
importing-unknown-file-format = Unknown file format.
importing-update-existing-notes-when-first-field = Update existing notes when first field matches
importing-updated = Updated
importing-note-added = { $count ->
importing-note-added =
{ $count ->
[one] { $count } note added
*[other] { $count } notes added
}
importing-note-imported = { $count ->
importing-note-imported =
{ $count ->
[one] { $count } note imported.
*[other] { $count } notes imported.
}
importing-note-unchanged = { $count ->
importing-note-unchanged =
{ $count ->
[one] { $count } note unchanged
*[other] { $count } notes unchanged
}
importing-note-updated = { $count ->
importing-note-updated =
{ $count ->
[one] { $count } note updated
*[other] { $count } notes updated
}
importing-processed-media-file = { $count ->
importing-processed-media-file =
{ $count ->
[one] Processed { $count } media file
*[other] Processed { $count } media files
}

View file

@ -1,7 +1,6 @@
## Shown at the top of the media check screen
media-check-window-title = Check Media
# the number of files, and the total space used by files
# that have been moved to the trash folder. eg,
# "Trash folder: 3 files, 3.47MB"

View file

@ -2,5 +2,4 @@ network-offline = Please check your internet connection.
network-timeout = Connection timed out. Please try again. If you see frequent timeouts, please try a different network connection.
network-proxy-auth = Your proxy requires authentication.
network-other = A network error occurred.
network-details = Error details: { $details }

View file

@ -18,7 +18,6 @@ notetypes-cloze-name = Cloze
notetypes-card-1-name = Card 1
notetypes-card-2-name = Card 2
notetypes-add = Add: { $val }
notetypes-add-note-type = Add Note Type
notetypes-cards = Cards...

View file

@ -138,7 +138,8 @@ scheduling-steps-must-be-numbers = Steps must be numbers.
scheduling-tag-only = Tag Only
scheduling-the-default-configuration-cant-be-removed = The default configuration can't be removed.
scheduling-your-changes-will-affect-multiple-decks = Your changes will affect multiple decks. If you wish to change only the current deck, please add a new options group first.
scheduling-deck-updated = { $count ->
scheduling-deck-updated =
{ $count ->
[one] { $count } deck updated.
*[other] { $count } decks updated.
}

View file

@ -6,3 +6,4 @@ search-note-modified = Note Modified
search-card-modified = Card Modified
##

View file

@ -42,15 +42,18 @@ studying-type-answer-unknown-field = Type answer: unknown field { $val }
studying-unbury = Unbury
studying-what-would-you-like-to-unbury = What would you like to unbury?
studying-you-havent-recorded-your-voice-yet = You haven't recorded your voice yet.
studying-card-studied-in = { $count ->
studying-card-studied-in =
{ $count ->
[one] { $count } card studied in
*[other] { $count } cards studied in
}
studying-minute = { $count ->
studying-minute =
{ $count ->
[one] { $count } minute.
*[other] { $count } minutes.
}
studying-note-and-its-card-deleted = { $count ->
studying-note-and-its-card-deleted =
{ $count ->
[one] Note and its { $count } card deleted.
*[other] Note and its { $count } cards deleted.
}

View file

@ -1,5 +1,6 @@
### Messages shown when synchronizing with AnkiWeb.
## Media synchronization
sync-media-added-count = Added: { $up }↑ { $down }↓

62
ftl/format.py Normal file
View file

@ -0,0 +1,62 @@
#!/usr/bin/env python3
# -*- coding: UTF-8 -*-
"""
Parse and re-serialize ftl files to get them in a consistent form.
"""
import os
import json
import glob
import sys
from typing import List
from fluent.syntax import parse, serialize
from fluent.syntax.ast import Junk
def check_file(path: str, fix: bool) -> bool:
"True if file is ok."
orig_text = open(path).read()
obj = parse(orig_text, with_spans=False)
# make sure there's no junk
for ent in obj.body:
if isinstance(ent, Junk):
raise Exception(f"file had junk! {path} {ent}")
# serialize
new_text = serialize(obj)
# make sure serializing did not introduce new junk
obj = parse(new_text, with_spans=False)
for ent in obj.body:
if isinstance(ent, Junk):
raise Exception(f"file introduced junk! {path} {ent}")
if new_text == orig_text:
return True
if fix:
print(f"Fixing {path}")
open(path, "w", newline="\n", encoding="utf8").write(new_text)
return True
else:
print(f"Bad formatting in {path}")
return False
def check_files(files: List[str], fix: bool) -> bool:
"True if files ok."
found_bad = False
for path in files:
ok = check_file(path, fix)
if not ok:
found_bad = True
return True
if __name__ == "__main__":
template_root = os.environ["BUILD_WORKSPACE_DIRECTORY"]
template_files = glob.glob(
os.path.join(template_root, "ftl", "*", "*.ftl"), recursive=True
)
check_files(template_files, fix=True)

10
ftl/format_check.py Normal file
View file

@ -0,0 +1,10 @@
import os
import format
import sys
import glob
template_root = os.path.dirname(sys.argv[1])
template_files = glob.glob(os.path.join(template_root, "*", "*.ftl"), recursive=True)
if not format.check_files(template_files, fix=False):
sys.exit(1)

View file

@ -7,4 +7,4 @@ about-copy-debug-info = Copy Debug Info
about-if-you-have-contributed-and-are = If you have contributed and are not on this list, please get in touch.
about-version = Version { $val }
about-visit-website = <a href='{ $val }'>Visit website</a>
about-written-by-damien-elmes-with-patches = Written by Damien Elmes, with patches, translation, testing and design from:<p>%(cont)s
about-written-by-damien-elmes-with-patches = Written by Damien Elmes, with patches, translation, testing and design from:<p>{ $cont }

View file

@ -22,10 +22,10 @@ addons-corrupt-addon-file = Corrupt add-on file.
addons-disabled = (disabled)
addons-disabled2 = (disabled)
addons-download-complete-please-restart-anki-to = Download complete. Please restart Anki to apply changes.
addons-downloaded-fnames = Downloaded %(fname)s
addons-downloading-adbd-kb02fkb = Downloading %(a)d/%(b)d (%(kb)0.2fKB)...
addons-error-downloading-ids-errors = Error downloading <i>%(id)s</i>: %(error)s
addons-error-installing-bases-errors = Error installing <i>%(base)s</i>: %(error)s
addons-downloaded-fnames = Downloaded { $fname }
addons-downloading-adbd-kb02fkb = Downloading { $part }/{ $total } ({ $kilobytes }KB)...
addons-error-downloading-ids-errors = Error downloading <i>{ $id }</i>: { $error }
addons-error-installing-bases-errors = Error installing <i>{ $base }</i>: { $error }
addons-get-addons = Get Add-ons...
addons-important-as-addons-are-programs-downloaded = <b>Important</b>: As add-ons are programs downloaded from the internet, they are potentially malicious.<b>You should only install add-ons you trust.</b><br><br>Are you sure you want to proceed with the installation of the following Anki add-on(s)?<br><br>%(names)s
addons-install-addon = Install Add-on
@ -33,7 +33,7 @@ addons-install-addons = Install Add-on(s)
addons-install-anki-addon = Install Anki add-on
addons-install-from-file = Install from file...
addons-installation-complete = Installation complete
addons-installed-names = Installed %(name)s
addons-installed-names = Installed { $name }
addons-installed-successfully = Installed successfully.
addons-invalid-addon-manifest = Invalid add-on manifest.
addons-invalid-code = Invalid code.
@ -49,7 +49,7 @@ addons-please-restart-anki-to-complete-the = <b>Please restart Anki to complete
addons-please-select-a-single-addon-first = Please select a single add-on first.
addons-requires = (requires { $val })
addons-restored-defaults = Restored defaults
addons-the-following-addons-are-incompatible-with = The following add-ons are incompatible with %(name)s and have been disabled: %(found)s
addons-the-following-addons-are-incompatible-with = The following add-ons are incompatible with { $name } and have been disabled: { $found }
addons-the-following-addons-have-updates-available = The following add-ons have updates available. Install them now?
addons-the-following-conflicting-addons-were-disabled = The following conflicting add-ons were disabled:
addons-this-addon-is-not-compatible-with = This add-on is not compatible with your version of Anki.
@ -59,7 +59,8 @@ addons-unable-to-update-or-delete-addon = Unable to update or delete add-on. Ple
addons-unknown-error = Unknown error: { $val }
addons-view-addon-page = View Add-on Page
addons-view-files = View Files
addons-delete-the-numd-selected-addon = { $count ->
addons-delete-the-numd-selected-addon =
{ $count ->
[one] Delete the { $count } selected add-on?
*[other] Delete the { $count } selected add-ons?
}

View file

@ -1,7 +1,6 @@
# shown instead of the 'night mode' option when night mode is forced on because
# macOS is in dark mode
preferences-dark-mode-active = macOS is in dark mode
preferences-dark-mode-disable =
To show Anki in light mode while macOS is in dark mode, please
see the Night Mode section of the manual.

View file

@ -4,6 +4,5 @@ profiles-folder-readme =
please see:
{ $link }
# will appear as 'Downgrade & Quit'
profiles-downgrade-and-quit = Downgrade && Quit

View file

@ -61,13 +61,14 @@ qt-misc-would-you-like-to-download-it = Would you like to download it now?
qt-misc-your-collection-file-appears-to-be = 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.
qt-misc-your-computers-storage-may-be-full = Your computer's storage may be full. Please delete some unneeded files, then try again.
qt-misc-your-firewall-or-antivirus-program-is = Your firewall or antivirus program is preventing Anki from creating a connection to itself. Please add an exception for Anki.
qt-misc-second = { $count ->
[one] { $count } second
*[other] { $count } seconds
}
qt-misc-error = Error
qt-misc-no-temp-folder = No usable temporary folder found. Make sure C:\\temp exists or TEMP in your environment points to a valid, writable folder.
qt-misc-incompatible-video-driver = Your video driver is incompatible. Please start Anki again, and Anki will switch to a slower, more compatible mode.
qt-misc-error-loading-graphics-driver = Error loading '{ $mode }' graphics driver. Please start Anki again to try next driver. { $context }
qt-misc-anki-is-running = Anki Already Running
qt-misc-if-instance-is-not-responding = If the existing instance of Anki is not responding, please close it using your task manager, or restart your computer.
qt-misc-second =
{ $count ->
[one] { $count } second
*[other] { $count } seconds
}

View file

@ -10,11 +10,9 @@ import os
import sys
from typing import Optional, Tuple
repos_bzl = "repos.bzl"
working_folder = "../anki-i18n"
if not os.path.exists(repos_bzl):
raise Exception("run from workspace root")
root = os.environ["BUILD_WORKSPACE_DIRECTORY"]
repos_bzl = os.path.join(root, "repos.bzl")
working_folder = os.path.join(root, "..", "anki-i18n")
if not os.path.exists(working_folder):
os.mkdir(working_folder)
@ -35,12 +33,12 @@ modules = [
Module(
name="core",
repo="git@github.com:ankitects/anki-core-i18n",
ftl=("ftl/core", "core/templates"),
ftl=(os.path.join(root, "ftl", "core"), "core/templates"),
),
Module(
name="qtftl",
repo="git@github.com:ankitects/anki-desktop-ftl",
ftl=("ftl/qt", "desktop/templates"),
ftl=(os.path.join(root, "ftl", "qt"), "desktop/templates"),
),
]
@ -103,7 +101,7 @@ def update_repos_bzl():
out.append(line)
open(path, "w").writelines(out)
commit_if_changed(".")
commit_if_changed(root)
def commit_if_changed(folder: str):

71
ftl/transform-string.py Normal file
View file

@ -0,0 +1,71 @@
#!/usr/bin/env python3
# -*- coding: UTF-8 -*-
"""
Tool to apply transform to an ftl string and its translations.
"""
import os
import json
import glob
from fluent.syntax import parse, serialize
from fluent.syntax.ast import Junk
template_root = os.environ["BUILD_WORKSPACE_DIRECTORY"]
template_files = glob.glob(
os.path.join(template_root, "ftl", "*", "*.ftl"), recursive=True
)
translation_root = os.path.join(template_root, "..", "anki-i18n")
translation_files = glob.glob(
os.path.join(translation_root, "*", "*", "*", "*.ftl"), recursive=True
)
target_repls = [
["addons-downloaded-fnames", "%(fname)s", "{ $fname }"],
["addons-downloading-adbd-kb02fkb", "%(a)d", "{ $part }"],
["addons-downloading-adbd-kb02fkb", "%(b)d", "{ $total }"],
["addons-downloading-adbd-kb02fkb", "%(kb)0.2f", "{ $kilobytes }"],
["addons-error-downloading-ids-errors", "%(id)s", "{ $id }"],
["addons-error-downloading-ids-errors", "%(error)s", "{ $error }"],
["addons-error-installing-bases-errors", "%(base)s", "{ $base }"],
["addons-error-installing-bases-errors", "%(error)s", "{ $error }"],
["addons-important-as-addons-are-programs-downloaded", "%(name)s", "{ $name }"],
["addons-installed-names", "%(name)s", "{ $name }"],
["addons-the-following-addons-are-incompatible-with", "%(name)s", "{ $name }"],
["addons-the-following-addons-are-incompatible-with", "%(found)s", "{ $found }"],
["about-written-by-damien-elmes-with-patches", "%(cont)s", "{ $cont }"],
["importing-rows-had-num1d-fields-expected-num2d", "%(row)s", "{ $row }"],
["importing-rows-had-num1d-fields-expected-num2d", "%(num1)d", "{ $found }"],
["importing-rows-had-num1d-fields-expected-num2d", "%(num2)d", "{ $expected }"],
["card-templates-delete-the-as-card-type-and", "%(a)s", "{ $template }"],
["card-templates-delete-the-as-card-type-and", "%(b)s", "{ $cards }"],
["browsing-found-as-across-bs", "%(a)s", "{ $part }"],
["browsing-found-as-across-bs", "%(b)s", "{ $whole }"],
["browsing-nd-names", "%(n)d", "{ $num }"],
["browsing-nd-names", "%(name)s", "{ $name }"],
]
def transform_string_in_file(path):
obj = parse(open(path).read(), with_spans=False)
changed = False
for ent in obj.body:
if isinstance(ent, Junk):
raise Exception(f"file had junk! {path} {ent}")
if getattr(ent, "id", None):
key = ent.id.name
for (target_key, src, dst) in target_repls:
if key == target_key:
for elem in ent.value.elements:
newval = elem.value.replace(src, dst)
if newval != elem.value:
elem.value = newval
changed = True
if changed:
open(path, "w", encoding="utf8").write(serialize(obj))
print("updated", path)
for path in template_files + translation_files:
transform_string_in_file(path)

View file

@ -42,13 +42,11 @@ class TextImporter(NoteImporter):
if row:
log.append(
self.col.tr(
TR.IMPORTING_ROWS_HAD_NUM1D_FIELDS_EXPECTED_NUM2D
TR.IMPORTING_ROWS_HAD_NUM1D_FIELDS_EXPECTED_NUM2D,
row=" ".join(row),
found=len(row),
expected=self.numFields,
)
% {
"row": " ".join(row),
"num1": len(row),
"num2": self.numFields,
}
)
ignored += 1
continue

View file

@ -202,9 +202,9 @@ def show(mw):
)
)
abouttext += "<p>" + tr(TR.ABOUT_WRITTEN_BY_DAMIEN_ELMES_WITH_PATCHES) % {
"cont": ", ".join(allusers)
}
abouttext += "<p>" + tr(
TR.ABOUT_WRITTEN_BY_DAMIEN_ELMES_WITH_PATCHES, cont=", ".join(allusers)
)
abouttext += "<p>" + tr(TR.ABOUT_IF_YOU_HAVE_CONTRIBUTED_AND_ARE)
abouttext += "<p>" + tr(TR.ABOUT_A_BIG_THANKS_TO_ALL_THE)
abt.label.setMinimumWidth(800)

View file

@ -275,8 +275,11 @@ class AddonManager:
if conflicting:
addons = ", ".join(self.addonName(f) for f in conflicting)
showInfo(
tr(TR.ADDONS_THE_FOLLOWING_ADDONS_ARE_INCOMPATIBLE_WITH)
% dict(name=addon.human_name(), found=addons),
tr(
TR.ADDONS_THE_FOLLOWING_ADDONS_ARE_INCOMPATIBLE_WITH,
name=addon.human_name(),
found=addons,
),
textFormat="plain",
)
@ -306,7 +309,7 @@ class AddonManager:
meta = self.addon_meta(dir)
name = meta.human_name()
if not meta.enabled:
name += tr(TR.ADDONS_DISABLED)
name += " " + tr(TR.ADDONS_DISABLED)
return name
# Conflict resolution
@ -469,26 +472,24 @@ class AddonManager:
result.errmsg, tr(TR.ADDONS_UNKNOWN_ERROR, val=result.errmsg)
)
if mode == "download": # preserve old format strings for i18n
template = tr(TR.ADDONS_ERROR_DOWNLOADING_IDS_ERRORS)
if mode == "download":
template = tr(TR.ADDONS_ERROR_DOWNLOADING_IDS_ERRORS, id=base, error=msg)
else:
template = tr(TR.ADDONS_ERROR_INSTALLING_BASES_ERRORS)
template = tr(TR.ADDONS_ERROR_INSTALLING_BASES_ERRORS, base=base, error=msg)
name = base
return [template % dict(base=name, id=name, error=msg)]
return [template]
def _installationSuccessReport(
self, result: InstallOk, base: str, mode: str = "download"
) -> List[str]:
if mode == "download": # preserve old format strings for i18n
template = tr(TR.ADDONS_DOWNLOADED_FNAMES)
else:
template = tr(TR.ADDONS_INSTALLED_NAMES)
name = result.name or base
strings = [template % dict(name=name, fname=name)]
if mode == "download":
template = tr(TR.ADDONS_DOWNLOADED_FNAMES, fname=name)
else:
template = tr(TR.ADDONS_INSTALLED_NAMES, name=name)
strings = [template]
if result.conflicts:
strings.append(
@ -1074,13 +1075,12 @@ class DownloaderInstaller(QObject):
def _progress_callback(self, up: int, down: int) -> None:
self.dl_bytes += down
self.mgr.mw.progress.update(
# T: "%(a)d" is the index of the element currently
# downloaded. "%(b)d" is the number of element to download,
# and "%(kb)0.2f" is the number of downloaded
# kilobytes. This lead for example to "Downloading 3/5
# (27KB)"
label=tr(TR.ADDONS_DOWNLOADING_ADBD_KB02FKB)
% dict(a=len(self.log) + 1, b=len(self.ids), kb=self.dl_bytes / 1024)
label=tr(
TR.ADDONS_DOWNLOADING_ADBD_KB02FKB,
part=len(self.log) + 1,
total=len(self.ids),
kilobytes=self.dl_bytes / 1024,
)
)
def _download_all(self) -> None:
@ -1361,7 +1361,7 @@ class ConfigEditor(QDialog):
showInfo(msg)
return
except Exception as e:
showInfo(tr(TR.ADDONS_INVALID_CONFIGURATION) + repr(e))
showInfo(tr(TR.ADDONS_INVALID_CONFIGURATION) + " " + repr(e))
return
if not isinstance(new_conf, dict):

View file

@ -1352,8 +1352,10 @@ QTableView {{ gridline-color: {grid} }}
for c, tmpl in enumerate(nt["tmpls"]):
# T: name is a card type name. n it's order in the list of card type.
# T: this is shown in browser's filter, when seeing the list of card type of a note type.
name = tr(TR.BROWSING_ND_NAMES) % dict(
n=c + 1, name=self._escapeMenuItem(tmpl["name"])
name = tr(
TR.BROWSING_ND_NAMES,
num=c + 1,
name=self._escapeMenuItem(tmpl["name"]),
)
subm.addItem(
name, self._filterFunc("note", nt["name"], "card", str(c + 1))
@ -1995,7 +1997,7 @@ where id in %s"""
notes = sum(len(r[1]) for r in res)
part1 = tr(TR.BROWSING_GROUP, count=groups)
part2 = tr(TR.BROWSING_NOTE_COUNT, count=notes)
t += tr(TR.BROWSING_FOUND_AS_ACROSS_BS) % dict(a=part1, b=part2)
t += tr(TR.BROWSING_FOUND_AS_ACROSS_BS, part=part1, whole=part2)
t += "<p><ol>"
for val, nids in res:
t += (

View file

@ -564,8 +564,10 @@ class CardLayout(QDialog):
template = self.current_template()
cards = tr(TR.CARD_TEMPLATES_CARD_COUNT, count=card_cnt)
msg = tr(TR.CARD_TEMPLATES_DELETE_THE_AS_CARD_TYPE_AND) % dict(
a=template["name"], b=cards
msg = tr(
TR.CARD_TEMPLATES_DELETE_THE_AS_CARD_TYPE_AND,
template=template["name"],
cards=cards,
)
if not askUser(msg):
return

View file

@ -1,40 +0,0 @@
PREFIX=/usr/local
all:
@echo "You can run Anki with ./bin/Anki"
@echo "If you wish to install it system wide, type 'sudo make install',"
@echo "then run with 'anki'."
@echo "Uninstall with 'sudo make uninstall'"
install:
rm -rf ${PREFIX}/share/anki
mkdir -p ${PREFIX}/share/anki
cp -av * ${PREFIX}/share/anki/
mkdir -p ${PREFIX}/bin
ln -sf ${PREFIX}/share/anki/bin/Anki ${PREFIX}/bin/anki
# fix a previous packaging issue where we created this as a file
(test -f ${PREFIX}/share/applications && rm ${PREFIX}/share/applications)||true
mkdir -p ${PREFIX}/share/pixmaps
mkdir -p ${PREFIX}/share/applications
mkdir -p ${PREFIX}/share/man/man1
cd ${PREFIX}/share/anki && (\
mv anki.xpm anki.png ${PREFIX}/share/pixmaps/;\
mv anki.desktop ${PREFIX}/share/applications/;\
mv anki.1 ${PREFIX}/share/man/man1/)
xdg-mime install anki.xml --novendor
xdg-mime default anki.desktop application/x-colpkg
xdg-mime default anki.desktop application/x-apkg
xdg-mime default anki.desktop application/x-ankiaddon
@echo
@echo "Install complete. Type 'anki' to run."
uninstall:
-xdg-mime uninstall ${PREFIX}/share/anki/anki.xml
rm -rf ${PREFIX}/share/anki
rm -rf ${PREFIX}/bin/anki
rm -rf ${PREFIX}/share/pixmaps/anki.xpm
rm -rf ${PREFIX}/share/pixmaps/anki.png
rm -rf ${PREFIX}/share/applications/anki.desktop
rm -rf ${PREFIX}/share/man/man1/anki.1
@echo
@echo "Uninstall complete."

View file

@ -2,11 +2,8 @@ To run, change to this folder in a terminal and run the following command:
./bin/Anki
- To install system wide, ensure you have make installed
(eg in Ubuntu, run 'sudo apt install make' in a terminal)
- Run 'sudo make install'
- Start Anki with 'anki'
- To remove in the future, run 'sudo make uninstall' from the same folder.
- To install system wide, run 'sudo ./install.sh'
- To remove in the future, run 'sudo ./uninstall.sh' from the same folder.
To play and record audio, mpv and lame must be installed. If mpv is not
installed or too old, Anki will try to fall back on using mplayer.

29
qt/linux/install.sh Executable file
View file

@ -0,0 +1,29 @@
#!/bin/bash
set -e
if [ "$PREFIX" = "" ]; then
PREFIX=/usr/local
fi
rm -rf ${PREFIX}/share/anki
mkdir -p ${PREFIX}/share/anki
cp -av * ${PREFIX}/share/anki/
mkdir -p ${PREFIX}/bin
ln -sf ${PREFIX}/share/anki/bin/Anki ${PREFIX}/bin/anki
# fix a previous packaging issue where we created this as a file
(test -f ${PREFIX}/share/applications && rm ${PREFIX}/share/applications)||true
mkdir -p ${PREFIX}/share/pixmaps
mkdir -p ${PREFIX}/share/applications
mkdir -p ${PREFIX}/share/man/man1
cd ${PREFIX}/share/anki && (\
mv anki.xpm anki.png ${PREFIX}/share/pixmaps/;\
mv anki.desktop ${PREFIX}/share/applications/;\
mv anki.1 ${PREFIX}/share/man/man1/)
xdg-mime install anki.xml --novendor
xdg-mime default anki.desktop application/x-colpkg
xdg-mime default anki.desktop application/x-apkg
xdg-mime default anki.desktop application/x-ankiaddon
echo "Install complete. Type 'anki' to run."

18
qt/linux/uninstall.sh Executable file
View file

@ -0,0 +1,18 @@
#!/bin/bash
set -e
if [ "$PREFIX" = "" ]; then
PREFIX=/usr/local
fi
xdg-mime uninstall ${PREFIX}/share/anki/anki.xml || true
rm -rf ${PREFIX}/share/anki
rm -rf ${PREFIX}/bin/anki
rm -rf ${PREFIX}/share/pixmaps/anki.xpm
rm -rf ${PREFIX}/share/pixmaps/anki.png
rm -rf ${PREFIX}/share/applications/anki.desktop
rm -rf ${PREFIX}/share/man/man1/anki.1
echo "Uninstall complete."

View file

@ -120,11 +120,11 @@ def register_repos():
# translations
################
core_i18n_commit = "1599ff1c4cb60b98fe0e80e1b45da47e9de9eeb1"
core_i18n_shallow_since = "1605671186 +1000"
core_i18n_commit = "82530e22eb374bb745af9d7ae9af879441f5d909"
core_i18n_shallow_since = "1606023535 +1000"
qtftl_i18n_commit = "9909cfa4386288e686b2336b3b1048b7ee1bb194"
qtftl_i18n_shallow_since = "1605664969 +1000"
qtftl_i18n_commit = "72e91ac6c5e00855728fefacfe1366189055fb47"
qtftl_i18n_shallow_since = "1606023535 +1000"
i18n_build_content = """
filegroup(

View file

@ -5,7 +5,7 @@ use crate::{
collection::Collection,
err::{AnkiError, Result},
notes::{NoteID, TransformNoteOutput},
text::text_to_re,
text::to_re,
{text::normalize_to_nfc, types::Usn},
};
use regex::{NoExpand, Regex, Replacer};
@ -134,7 +134,7 @@ impl Collection {
// generate regexps
let tags = split_tags(tags)
.map(|tag| {
let tag = if regex { tag.into() } else { text_to_re(tag) };
let tag = if regex { tag.into() } else { to_re(tag) };
Regex::new(&format!("(?i)^{}$", tag))
.map_err(|_| AnkiError::invalid_input("invalid regex"))
})

View file

@ -258,38 +258,6 @@ pub(crate) fn without_combining(s: &str) -> Cow<str> {
.into()
}
/// Escape text, converting glob characters to regex syntax, then return.
pub(crate) fn text_to_re(glob: &str) -> String {
lazy_static! {
static ref ESCAPED: Regex = Regex::new(r"(\\\\)?\\\*").unwrap();
static ref GLOB: Regex = Regex::new(r"(\\\\)?[_%]").unwrap();
}
let escaped = regex::escape(glob);
let text = ESCAPED.replace_all(&escaped, |caps: &Captures| {
if caps.get(0).unwrap().as_str().len() == 2 {
".*"
} else {
r"\*"
}
});
let text2 = GLOB.replace_all(&text, |caps: &Captures| {
match caps.get(0).unwrap().as_str() {
"_" => ".",
"%" => ".*",
other => {
// strip off the escaping char
&other[2..]
}
}
.to_string()
});
text2.into()
}
/// Check if string contains an unescaped wildcard.
pub(crate) fn is_glob(txt: &str) -> bool {
// even number of \s followed by a wildcard
@ -375,10 +343,7 @@ pub(crate) fn matches_glob(text: &str, search: &str) -> bool {
#[cfg(test)]
mod test {
use crate::text::without_combining;
use crate::text::{
extract_av_tags, strip_av_tags, strip_html, strip_html_preserving_media_filenames, AVTag,
};
use super::*;
use std::borrow::Cow;
#[test]
@ -427,4 +392,16 @@ mod test {
assert!(matches!(without_combining("test"), Cow::Borrowed(_)));
assert!(matches!(without_combining("Über"), Cow::Owned(_)));
}
#[test]
fn conversion() {
assert_eq!(&to_re(r"[te\*st]"), r"\[te\*st\]");
assert_eq!(&to_custom_re("f_o*", r"\d"), r"f\do\d*");
assert_eq!(&to_sql("%f_o*"), r"\%f_o%");
assert_eq!(&to_text(r"\*\_*_"), "*_*_");
assert_eq!(&escape_sql(r"1\2%3_"), r"1\\2\%3\_");
assert!(is_glob(r"\\\\_"));
assert!(!is_glob(r"\\\_"));
assert!(matches_glob("foo*bar123", r"foo\*bar*"));
}
}

View file

@ -7,9 +7,3 @@ py_binary(
stamp = 1,
visibility = ["//visibility:public"],
)
py_binary(
name = "extract-strings",
srcs = ["extract-strings.py"],
deps = [requirement("fluent-syntax")],
)