mirror of
https://github.com/ankitects/anki.git
synced 2025-09-19 06:22:22 -04:00
Merge branch 'master' into init-lang
This commit is contained in:
commit
d85d0b88a1
42 changed files with 440 additions and 275 deletions
|
@ -1,3 +1,5 @@
|
||||||
|
load("@py_deps//:requirements.bzl", "requirement")
|
||||||
|
|
||||||
filegroup(
|
filegroup(
|
||||||
name = "ftl",
|
name = "ftl",
|
||||||
srcs = [
|
srcs = [
|
||||||
|
@ -7,6 +9,44 @@ filegroup(
|
||||||
visibility = ["//rslib:__subpackages__"],
|
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 ...)
|
# export this file as a way of locating the top level folder in $(location ...)
|
||||||
exports_files(
|
exports_files(
|
||||||
["BUILD.bazel"],
|
["BUILD.bazel"],
|
||||||
|
|
3
ftl/README.md
Normal file
3
ftl/README.md
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
Files related to Anki's translations.
|
||||||
|
|
||||||
|
Please see https://translating.ankiweb.net/#/anki/developers
|
|
@ -43,7 +43,7 @@ browsing-first-card = First Card
|
||||||
browsing-flag = Flag
|
browsing-flag = Flag
|
||||||
browsing-font = <b>Font</b>:
|
browsing-font = <b>Font</b>:
|
||||||
browsing-font-size = <b>Font Size</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-home = Home
|
||||||
browsing-ignore-case = Ignore case
|
browsing-ignore-case = Ignore case
|
||||||
browsing-in = <b>In</b>:
|
browsing-in = <b>In</b>:
|
||||||
|
@ -54,7 +54,7 @@ browsing-line-size = <b>Line Size</b>:
|
||||||
browsing-manage-note-types = Manage Note Types...
|
browsing-manage-note-types = Manage Note Types...
|
||||||
browsing-move-cards = Move Cards
|
browsing-move-cards = Move Cards
|
||||||
browsing-move-cards-to-deck = Move cards to deck:
|
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 = (new)
|
||||||
browsing-new-note-type = New note type:
|
browsing-new-note-type = New note type:
|
||||||
browsing-no-flag = No Flag
|
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-type-here-to-search = <type here to search; hit enter to show current deck>
|
||||||
browsing-whole-collection = Whole Collection
|
browsing-whole-collection = Whole Collection
|
||||||
browsing-you-must-have-at-least-one = You must have at least one column.
|
browsing-you-must-have-at-least-one = You must have at least one column.
|
||||||
browsing-group = { $count ->
|
browsing-group =
|
||||||
|
{ $count ->
|
||||||
[one] { $count } group
|
[one] { $count } group
|
||||||
*[other] { $count } groups
|
*[other] { $count } groups
|
||||||
}
|
}
|
||||||
browsing-note-count = { $count ->
|
browsing-note-count =
|
||||||
|
{ $count ->
|
||||||
[one] { $count } note
|
[one] { $count } note
|
||||||
*[other] { $count } notes
|
*[other] { $count } notes
|
||||||
}
|
}
|
||||||
browsing-note-deleted = { $count ->
|
browsing-note-deleted =
|
||||||
|
{ $count ->
|
||||||
[one] { $count } note deleted.
|
[one] { $count } note deleted.
|
||||||
*[other] { $count } notes deleted.
|
*[other] { $count } notes deleted.
|
||||||
}
|
}
|
||||||
browsing-window-title = Browse ({ $selected } of { $total } cards selected)
|
browsing-window-title = Browse ({ $selected } of { $total } cards selected)
|
||||||
|
|
||||||
|
|
|
@ -22,4 +22,3 @@ card-stats-review-log-type-review = Review
|
||||||
card-stats-review-log-type-relearn = Relearn
|
card-stats-review-log-type-relearn = Relearn
|
||||||
card-stats-review-log-type-filtered = Filtered
|
card-stats-review-log-type-filtered = Filtered
|
||||||
card-stats-review-log-type-manual = Manual
|
card-stats-review-log-type-manual = Manual
|
||||||
|
|
||||||
|
|
|
@ -4,39 +4,27 @@
|
||||||
|
|
||||||
# Label of link users can click on
|
# Label of link users can click on
|
||||||
card-template-rendering-more-info = More information
|
card-template-rendering-more-info = More information
|
||||||
|
|
||||||
card-template-rendering-front-side-problem = Front template has a problem:
|
card-template-rendering-front-side-problem = Front template has a problem:
|
||||||
card-template-rendering-back-side-problem = Back template has a problem:
|
card-template-rendering-back-side-problem = Back template has a problem:
|
||||||
|
|
||||||
# when the user forgot to close a field reference,
|
# when the user forgot to close a field reference,
|
||||||
# eg, Missing '}}' in '{{Field'
|
# eg, Missing '}}' in '{{Field'
|
||||||
card-template-rendering-no-closing-brackets =
|
card-template-rendering-no-closing-brackets = Missing '{ $missing }' in '{ $tag }'
|
||||||
Missing '{$missing}' in '{$tag}'
|
|
||||||
|
|
||||||
# when the user opened a conditional, but forgot to close it
|
# when the user opened a conditional, but forgot to close it
|
||||||
# eg, Missing '{{/Conditional}}'
|
# eg, Missing '{{/Conditional}}'
|
||||||
card-template-rendering-conditional-not-closed =
|
card-template-rendering-conditional-not-closed = Missing '{ $missing }'
|
||||||
Missing '{$missing}'
|
|
||||||
|
|
||||||
# when the user closed the wrong conditional
|
# when the user closed the wrong conditional
|
||||||
# eg, Found '{{/Something}}', but expected '{{/SomethingElse}}'
|
# eg, Found '{{/Something}}', but expected '{{/SomethingElse}}'
|
||||||
card-template-rendering-wrong-conditional-closed =
|
card-template-rendering-wrong-conditional-closed = Found '{ $found }', but expected '{ $expected }'
|
||||||
Found '{$found}', but expected '{$expected}'
|
|
||||||
|
|
||||||
# when the user closed a conditional that wasn't open
|
# when the user closed a conditional that wasn't open
|
||||||
# eg, Found '{{/Something}}', but missing '{{#Something}}' or '{{^Something}}'
|
# eg, Found '{{/Something}}', but missing '{{#Something}}' or '{{^Something}}'
|
||||||
card-template-rendering-conditional-not-open =
|
card-template-rendering-conditional-not-open = Found '{ $found }', but missing '{ $missing1 }' or '{ $missing2 }'
|
||||||
Found '{$found}', but missing '{$missing1}' or '{$missing2}'
|
|
||||||
|
|
||||||
# when the user referenced a field that doesn't exist
|
# when the user referenced a field that doesn't exist
|
||||||
# eg, Found '{{Field}}', but there is not field called 'Field'
|
# eg, Found '{{Field}}', but there is not field called 'Field'
|
||||||
card-template-rendering-no-such-field =
|
card-template-rendering-no-such-field = Found '{ $found }', but there is no field called '{ $field }'
|
||||||
Found '{$found}', but there is no field called '{$field}'
|
|
||||||
|
|
||||||
# This message is shown when the front side of the card is blank,
|
# This message is shown when the front side of the card is blank,
|
||||||
# either due to a badly-designed template, or because required fields
|
# either due to a badly-designed template, or because required fields
|
||||||
# are missing.
|
# are missing.
|
||||||
card-template-rendering-empty-front = The front of this card is blank.
|
card-template-rendering-empty-front = The front of this card is blank.
|
||||||
|
card-template-rendering-missing-cloze =
|
||||||
card-template-rendering-missing-cloze = No cloze { $number } found on card.
|
No cloze { $number } found on card.
|
||||||
Please either add a cloze deletion, or use the Empty Cards tool.
|
Please either add a cloze deletion, or use the Empty Cards tool.
|
||||||
|
|
|
@ -11,7 +11,7 @@ card-templates-front-preview = Front Preview
|
||||||
card-templates-back-preview = Back Preview
|
card-templates-back-preview = Back Preview
|
||||||
card-templates-preview-box = Preview
|
card-templates-preview-box = Preview
|
||||||
card-templates-template-box = Template
|
card-templates-template-box = Template
|
||||||
card-templates-sample-cloze = This is a {"{{c1::"}sample{"}}"} cloze deletion.
|
card-templates-sample-cloze = This is a { "{{c1::" }sample{ "}}" } cloze deletion.
|
||||||
card-templates-fill-empty = Fill Empty Fields
|
card-templates-fill-empty = Fill Empty Fields
|
||||||
card-templates-night-mode = Night Mode
|
card-templates-night-mode = Night Mode
|
||||||
# Add "mobile" class to card preview, so the card appears like it would
|
# Add "mobile" class to card preview, so the card appears like it would
|
||||||
|
@ -30,7 +30,7 @@ card-templates-card-types = Card Types
|
||||||
card-templates-card-types-for = Card Types for { $val }
|
card-templates-card-types-for = Card Types for { $val }
|
||||||
card-templates-cloze = Cloze { $val }
|
card-templates-cloze = Cloze { $val }
|
||||||
card-templates-deck-override = Deck Override...
|
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-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-enter-new-card-position-1 = Enter new card position (1...{ $val }):
|
||||||
card-templates-flip = Flip
|
card-templates-flip = Flip
|
||||||
|
@ -40,11 +40,13 @@ card-templates-on = (on)
|
||||||
card-templates-remove-card-type = Remove Card Type...
|
card-templates-remove-card-type = Remove Card Type...
|
||||||
card-templates-rename-card-type = Rename Card Type...
|
card-templates-rename-card-type = Rename Card Type...
|
||||||
card-templates-reposition-card-type = Reposition Card Type...
|
card-templates-reposition-card-type = Reposition Card Type...
|
||||||
card-templates-card-count = { $count ->
|
card-templates-card-count =
|
||||||
|
{ $count ->
|
||||||
[one] { $count } card
|
[one] { $count } card
|
||||||
*[other] { $count } cards
|
*[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?
|
[one] This will create { $count } card. Proceed?
|
||||||
*[other] This will create { $count } cards. Proceed?
|
*[other] This will create { $count } cards. Proceed?
|
||||||
}
|
}
|
||||||
|
|
|
@ -31,7 +31,8 @@ decks-reschedule-cards-based-on-my-answers = Reschedule cards based on my answer
|
||||||
decks-study = Study
|
decks-study = Study
|
||||||
decks-study-deck = Study Deck
|
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-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.
|
[one] It has { $count } card.
|
||||||
*[other] It has { $count } cards.
|
*[other] It has { $count } cards.
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,11 +1,11 @@
|
||||||
empty-cards-for-note-type = Empty cards for { $notetype }:
|
empty-cards-for-note-type = Empty cards for { $notetype }:
|
||||||
empty-cards-count-line =
|
empty-cards-count-line = { $empty_count } of { $existing_count } cards empty ({ $template_names }).
|
||||||
{ $empty_count } of { $existing_count } cards empty ({ $template_names }).
|
|
||||||
empty-cards-window-title = Empty Cards
|
empty-cards-window-title = Empty Cards
|
||||||
empty-cards-preserve-notes-checkbox = Keep notes with no valid cards
|
empty-cards-preserve-notes-checkbox = Keep notes with no valid cards
|
||||||
empty-cards-delete-button = Delete
|
empty-cards-delete-button = Delete
|
||||||
empty-cards-not-found = No empty cards.
|
empty-cards-not-found = No empty cards.
|
||||||
empty-cards-deleted-count = Deleted { $cards ->
|
empty-cards-deleted-count =
|
||||||
|
Deleted { $cards ->
|
||||||
[one] { $cards } card.
|
[one] { $cards } card.
|
||||||
*[other] { $cards } cards.
|
*[other] { $cards } cards.
|
||||||
}
|
}
|
||||||
|
|
|
@ -15,15 +15,18 @@ exporting-include-scheduling-information = Include scheduling information
|
||||||
exporting-include-tags = Include tags
|
exporting-include-tags = Include tags
|
||||||
exporting-notes-in-plain-text = Notes in Plain Text
|
exporting-notes-in-plain-text = Notes in Plain Text
|
||||||
exporting-selected-notes = Selected Notes
|
exporting-selected-notes = Selected Notes
|
||||||
exporting-card-exported = { $count ->
|
exporting-card-exported =
|
||||||
|
{ $count ->
|
||||||
[one] { $count } card exported.
|
[one] { $count } card exported.
|
||||||
*[other] { $count } cards exported.
|
*[other] { $count } cards exported.
|
||||||
}
|
}
|
||||||
exporting-exported-media-file = { $count ->
|
exporting-exported-media-file =
|
||||||
|
{ $count ->
|
||||||
[one] Exported { $count } media file
|
[one] Exported { $count } media file
|
||||||
*[other] Exported { $count } media files
|
*[other] Exported { $count } media files
|
||||||
}
|
}
|
||||||
exporting-note-exported = { $count ->
|
exporting-note-exported =
|
||||||
|
{ $count ->
|
||||||
[one] { $count } note exported.
|
[one] { $count } note exported.
|
||||||
*[other] { $count } notes exported.
|
*[other] { $count } notes exported.
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
findreplace-notes-updated = { $total ->
|
findreplace-notes-updated =
|
||||||
[one] {$changed} of {$total} note updated
|
{ $total ->
|
||||||
*[other] {$changed} of {$total} notes updated
|
[one] { $changed } of { $total } note updated
|
||||||
|
*[other] { $changed } of { $total } notes updated
|
||||||
}
|
}
|
||||||
|
|
|
@ -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-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-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-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-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-semicolon = Semicolon
|
||||||
importing-skipped = Skipped
|
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-unknown-file-format = Unknown file format.
|
||||||
importing-update-existing-notes-when-first-field = Update existing notes when first field matches
|
importing-update-existing-notes-when-first-field = Update existing notes when first field matches
|
||||||
importing-updated = Updated
|
importing-updated = Updated
|
||||||
importing-note-added = { $count ->
|
importing-note-added =
|
||||||
|
{ $count ->
|
||||||
[one] { $count } note added
|
[one] { $count } note added
|
||||||
*[other] { $count } notes added
|
*[other] { $count } notes added
|
||||||
}
|
}
|
||||||
importing-note-imported = { $count ->
|
importing-note-imported =
|
||||||
|
{ $count ->
|
||||||
[one] { $count } note imported.
|
[one] { $count } note imported.
|
||||||
*[other] { $count } notes imported.
|
*[other] { $count } notes imported.
|
||||||
}
|
}
|
||||||
importing-note-unchanged = { $count ->
|
importing-note-unchanged =
|
||||||
|
{ $count ->
|
||||||
[one] { $count } note unchanged
|
[one] { $count } note unchanged
|
||||||
*[other] { $count } notes unchanged
|
*[other] { $count } notes unchanged
|
||||||
}
|
}
|
||||||
importing-note-updated = { $count ->
|
importing-note-updated =
|
||||||
|
{ $count ->
|
||||||
[one] { $count } note updated
|
[one] { $count } note updated
|
||||||
*[other] { $count } notes updated
|
*[other] { $count } notes updated
|
||||||
}
|
}
|
||||||
importing-processed-media-file = { $count ->
|
importing-processed-media-file =
|
||||||
|
{ $count ->
|
||||||
[one] Processed { $count } media file
|
[one] Processed { $count } media file
|
||||||
*[other] Processed { $count } media files
|
*[other] Processed { $count } media files
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
## Shown at the top of the media check screen
|
## Shown at the top of the media check screen
|
||||||
|
|
||||||
media-check-window-title = Check Media
|
media-check-window-title = Check Media
|
||||||
|
|
||||||
# the number of files, and the total space used by files
|
# the number of files, and the total space used by files
|
||||||
# that have been moved to the trash folder. eg,
|
# that have been moved to the trash folder. eg,
|
||||||
# "Trash folder: 3 files, 3.47MB"
|
# "Trash folder: 3 files, 3.47MB"
|
||||||
|
|
|
@ -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-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-proxy-auth = Your proxy requires authentication.
|
||||||
network-other = A network error occurred.
|
network-other = A network error occurred.
|
||||||
|
network-details = Error details: { $details }
|
||||||
network-details = Error details: {$details}
|
|
||||||
|
|
|
@ -18,7 +18,6 @@ notetypes-cloze-name = Cloze
|
||||||
|
|
||||||
notetypes-card-1-name = Card 1
|
notetypes-card-1-name = Card 1
|
||||||
notetypes-card-2-name = Card 2
|
notetypes-card-2-name = Card 2
|
||||||
|
|
||||||
notetypes-add = Add: { $val }
|
notetypes-add = Add: { $val }
|
||||||
notetypes-add-note-type = Add Note Type
|
notetypes-add-note-type = Add Note Type
|
||||||
notetypes-cards = Cards...
|
notetypes-cards = Cards...
|
||||||
|
|
|
@ -138,7 +138,8 @@ scheduling-steps-must-be-numbers = Steps must be numbers.
|
||||||
scheduling-tag-only = Tag Only
|
scheduling-tag-only = Tag Only
|
||||||
scheduling-the-default-configuration-cant-be-removed = The default configuration can't be removed.
|
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-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.
|
[one] { $count } deck updated.
|
||||||
*[other] { $count } decks updated.
|
*[other] { $count } decks updated.
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,3 +6,4 @@ search-note-modified = Note Modified
|
||||||
search-card-modified = Card Modified
|
search-card-modified = Card Modified
|
||||||
|
|
||||||
##
|
##
|
||||||
|
|
||||||
|
|
|
@ -42,15 +42,18 @@ studying-type-answer-unknown-field = Type answer: unknown field { $val }
|
||||||
studying-unbury = Unbury
|
studying-unbury = Unbury
|
||||||
studying-what-would-you-like-to-unbury = What would you like to 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-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
|
[one] { $count } card studied in
|
||||||
*[other] { $count } cards studied in
|
*[other] { $count } cards studied in
|
||||||
}
|
}
|
||||||
studying-minute = { $count ->
|
studying-minute =
|
||||||
|
{ $count ->
|
||||||
[one] { $count } minute.
|
[one] { $count } minute.
|
||||||
*[other] { $count } minutes.
|
*[other] { $count } minutes.
|
||||||
}
|
}
|
||||||
studying-note-and-its-card-deleted = { $count ->
|
studying-note-and-its-card-deleted =
|
||||||
|
{ $count ->
|
||||||
[one] Note and its { $count } card deleted.
|
[one] Note and its { $count } card deleted.
|
||||||
*[other] Note and its { $count } cards deleted.
|
*[other] Note and its { $count } cards deleted.
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
### Messages shown when synchronizing with AnkiWeb.
|
### Messages shown when synchronizing with AnkiWeb.
|
||||||
|
|
||||||
|
|
||||||
## Media synchronization
|
## Media synchronization
|
||||||
|
|
||||||
sync-media-added-count = Added: { $up }↑ { $down }↓
|
sync-media-added-count = Added: { $up }↑ { $down }↓
|
||||||
|
|
62
ftl/format.py
Normal file
62
ftl/format.py
Normal 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
10
ftl/format_check.py
Normal 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)
|
|
@ -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-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-version = Version { $val }
|
||||||
about-visit-website = <a href='{ $val }'>Visit website</a>
|
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 }
|
||||||
|
|
|
@ -22,10 +22,10 @@ addons-corrupt-addon-file = Corrupt add-on file.
|
||||||
addons-disabled = (disabled)
|
addons-disabled = (disabled)
|
||||||
addons-disabled2 = (disabled)
|
addons-disabled2 = (disabled)
|
||||||
addons-download-complete-please-restart-anki-to = Download complete. Please restart Anki to apply changes.
|
addons-download-complete-please-restart-anki-to = Download complete. Please restart Anki to apply changes.
|
||||||
addons-downloaded-fnames = Downloaded %(fname)s
|
addons-downloaded-fnames = Downloaded { $fname }
|
||||||
addons-downloading-adbd-kb02fkb = Downloading %(a)d/%(b)d (%(kb)0.2fKB)...
|
addons-downloading-adbd-kb02fkb = Downloading { $part }/{ $total } ({ $kilobytes }KB)...
|
||||||
addons-error-downloading-ids-errors = Error downloading <i>%(id)s</i>: %(error)s
|
addons-error-downloading-ids-errors = Error downloading <i>{ $id }</i>: { $error }
|
||||||
addons-error-installing-bases-errors = Error installing <i>%(base)s</i>: %(error)s
|
addons-error-installing-bases-errors = Error installing <i>{ $base }</i>: { $error }
|
||||||
addons-get-addons = Get Add-ons...
|
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-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
|
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-anki-addon = Install Anki add-on
|
||||||
addons-install-from-file = Install from file...
|
addons-install-from-file = Install from file...
|
||||||
addons-installation-complete = Installation complete
|
addons-installation-complete = Installation complete
|
||||||
addons-installed-names = Installed %(name)s
|
addons-installed-names = Installed { $name }
|
||||||
addons-installed-successfully = Installed successfully.
|
addons-installed-successfully = Installed successfully.
|
||||||
addons-invalid-addon-manifest = Invalid add-on manifest.
|
addons-invalid-addon-manifest = Invalid add-on manifest.
|
||||||
addons-invalid-code = Invalid code.
|
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-please-select-a-single-addon-first = Please select a single add-on first.
|
||||||
addons-requires = (requires { $val })
|
addons-requires = (requires { $val })
|
||||||
addons-restored-defaults = Restored defaults
|
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-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-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.
|
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-unknown-error = Unknown error: { $val }
|
||||||
addons-view-addon-page = View Add-on Page
|
addons-view-addon-page = View Add-on Page
|
||||||
addons-view-files = View Files
|
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?
|
[one] Delete the { $count } selected add-on?
|
||||||
*[other] Delete the { $count } selected add-ons?
|
*[other] Delete the { $count } selected add-ons?
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
# shown instead of the 'night mode' option when night mode is forced on because
|
# shown instead of the 'night mode' option when night mode is forced on because
|
||||||
# macOS is in dark mode
|
# macOS is in dark mode
|
||||||
preferences-dark-mode-active = macOS is in dark mode
|
preferences-dark-mode-active = macOS is in dark mode
|
||||||
|
|
||||||
preferences-dark-mode-disable =
|
preferences-dark-mode-disable =
|
||||||
To show Anki in light mode while macOS is in dark mode, please
|
To show Anki in light mode while macOS is in dark mode, please
|
||||||
see the Night Mode section of the manual.
|
see the Night Mode section of the manual.
|
||||||
|
|
|
@ -3,7 +3,6 @@ profiles-folder-readme =
|
||||||
to make backups easy. To tell Anki to use a different location,
|
to make backups easy. To tell Anki to use a different location,
|
||||||
please see:
|
please see:
|
||||||
|
|
||||||
{$link}
|
{ $link }
|
||||||
|
|
||||||
# will appear as 'Downgrade & Quit'
|
# will appear as 'Downgrade & Quit'
|
||||||
profiles-downgrade-and-quit = Downgrade && Quit
|
profiles-downgrade-and-quit = Downgrade && Quit
|
||||||
|
|
|
@ -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-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-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-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-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-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-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-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-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-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
|
||||||
|
}
|
||||||
|
|
|
@ -10,11 +10,9 @@ import os
|
||||||
import sys
|
import sys
|
||||||
from typing import Optional, Tuple
|
from typing import Optional, Tuple
|
||||||
|
|
||||||
repos_bzl = "repos.bzl"
|
root = os.environ["BUILD_WORKSPACE_DIRECTORY"]
|
||||||
working_folder = "../anki-i18n"
|
repos_bzl = os.path.join(root, "repos.bzl")
|
||||||
|
working_folder = os.path.join(root, "..", "anki-i18n")
|
||||||
if not os.path.exists(repos_bzl):
|
|
||||||
raise Exception("run from workspace root")
|
|
||||||
|
|
||||||
if not os.path.exists(working_folder):
|
if not os.path.exists(working_folder):
|
||||||
os.mkdir(working_folder)
|
os.mkdir(working_folder)
|
||||||
|
@ -35,12 +33,12 @@ modules = [
|
||||||
Module(
|
Module(
|
||||||
name="core",
|
name="core",
|
||||||
repo="git@github.com:ankitects/anki-core-i18n",
|
repo="git@github.com:ankitects/anki-core-i18n",
|
||||||
ftl=("ftl/core", "core/templates"),
|
ftl=(os.path.join(root, "ftl", "core"), "core/templates"),
|
||||||
),
|
),
|
||||||
Module(
|
Module(
|
||||||
name="qtftl",
|
name="qtftl",
|
||||||
repo="git@github.com:ankitects/anki-desktop-ftl",
|
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)
|
out.append(line)
|
||||||
open(path, "w").writelines(out)
|
open(path, "w").writelines(out)
|
||||||
|
|
||||||
commit_if_changed(".")
|
commit_if_changed(root)
|
||||||
|
|
||||||
|
|
||||||
def commit_if_changed(folder: str):
|
def commit_if_changed(folder: str):
|
71
ftl/transform-string.py
Normal file
71
ftl/transform-string.py
Normal 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)
|
|
@ -42,13 +42,11 @@ class TextImporter(NoteImporter):
|
||||||
if row:
|
if row:
|
||||||
log.append(
|
log.append(
|
||||||
self.col.tr(
|
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
|
ignored += 1
|
||||||
continue
|
continue
|
||||||
|
|
|
@ -202,9 +202,9 @@ def show(mw):
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
abouttext += "<p>" + tr(TR.ABOUT_WRITTEN_BY_DAMIEN_ELMES_WITH_PATCHES) % {
|
abouttext += "<p>" + tr(
|
||||||
"cont": ", ".join(allusers)
|
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_IF_YOU_HAVE_CONTRIBUTED_AND_ARE)
|
||||||
abouttext += "<p>" + tr(TR.ABOUT_A_BIG_THANKS_TO_ALL_THE)
|
abouttext += "<p>" + tr(TR.ABOUT_A_BIG_THANKS_TO_ALL_THE)
|
||||||
abt.label.setMinimumWidth(800)
|
abt.label.setMinimumWidth(800)
|
||||||
|
|
|
@ -275,8 +275,11 @@ class AddonManager:
|
||||||
if conflicting:
|
if conflicting:
|
||||||
addons = ", ".join(self.addonName(f) for f in conflicting)
|
addons = ", ".join(self.addonName(f) for f in conflicting)
|
||||||
showInfo(
|
showInfo(
|
||||||
tr(TR.ADDONS_THE_FOLLOWING_ADDONS_ARE_INCOMPATIBLE_WITH)
|
tr(
|
||||||
% dict(name=addon.human_name(), found=addons),
|
TR.ADDONS_THE_FOLLOWING_ADDONS_ARE_INCOMPATIBLE_WITH,
|
||||||
|
name=addon.human_name(),
|
||||||
|
found=addons,
|
||||||
|
),
|
||||||
textFormat="plain",
|
textFormat="plain",
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -306,7 +309,7 @@ class AddonManager:
|
||||||
meta = self.addon_meta(dir)
|
meta = self.addon_meta(dir)
|
||||||
name = meta.human_name()
|
name = meta.human_name()
|
||||||
if not meta.enabled:
|
if not meta.enabled:
|
||||||
name += tr(TR.ADDONS_DISABLED)
|
name += " " + tr(TR.ADDONS_DISABLED)
|
||||||
return name
|
return name
|
||||||
|
|
||||||
# Conflict resolution
|
# Conflict resolution
|
||||||
|
@ -469,26 +472,24 @@ class AddonManager:
|
||||||
result.errmsg, tr(TR.ADDONS_UNKNOWN_ERROR, val=result.errmsg)
|
result.errmsg, tr(TR.ADDONS_UNKNOWN_ERROR, val=result.errmsg)
|
||||||
)
|
)
|
||||||
|
|
||||||
if mode == "download": # preserve old format strings for i18n
|
if mode == "download":
|
||||||
template = tr(TR.ADDONS_ERROR_DOWNLOADING_IDS_ERRORS)
|
template = tr(TR.ADDONS_ERROR_DOWNLOADING_IDS_ERRORS, id=base, error=msg)
|
||||||
else:
|
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]
|
||||||
|
|
||||||
return [template % dict(base=name, id=name, error=msg)]
|
|
||||||
|
|
||||||
def _installationSuccessReport(
|
def _installationSuccessReport(
|
||||||
self, result: InstallOk, base: str, mode: str = "download"
|
self, result: InstallOk, base: str, mode: str = "download"
|
||||||
) -> List[str]:
|
) -> 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
|
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:
|
if result.conflicts:
|
||||||
strings.append(
|
strings.append(
|
||||||
|
@ -1074,13 +1075,12 @@ class DownloaderInstaller(QObject):
|
||||||
def _progress_callback(self, up: int, down: int) -> None:
|
def _progress_callback(self, up: int, down: int) -> None:
|
||||||
self.dl_bytes += down
|
self.dl_bytes += down
|
||||||
self.mgr.mw.progress.update(
|
self.mgr.mw.progress.update(
|
||||||
# T: "%(a)d" is the index of the element currently
|
label=tr(
|
||||||
# downloaded. "%(b)d" is the number of element to download,
|
TR.ADDONS_DOWNLOADING_ADBD_KB02FKB,
|
||||||
# and "%(kb)0.2f" is the number of downloaded
|
part=len(self.log) + 1,
|
||||||
# kilobytes. This lead for example to "Downloading 3/5
|
total=len(self.ids),
|
||||||
# (27KB)"
|
kilobytes=self.dl_bytes / 1024,
|
||||||
label=tr(TR.ADDONS_DOWNLOADING_ADBD_KB02FKB)
|
)
|
||||||
% dict(a=len(self.log) + 1, b=len(self.ids), kb=self.dl_bytes / 1024)
|
|
||||||
)
|
)
|
||||||
|
|
||||||
def _download_all(self) -> None:
|
def _download_all(self) -> None:
|
||||||
|
@ -1361,7 +1361,7 @@ class ConfigEditor(QDialog):
|
||||||
showInfo(msg)
|
showInfo(msg)
|
||||||
return
|
return
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
showInfo(tr(TR.ADDONS_INVALID_CONFIGURATION) + repr(e))
|
showInfo(tr(TR.ADDONS_INVALID_CONFIGURATION) + " " + repr(e))
|
||||||
return
|
return
|
||||||
|
|
||||||
if not isinstance(new_conf, dict):
|
if not isinstance(new_conf, dict):
|
||||||
|
|
|
@ -1352,8 +1352,10 @@ QTableView {{ gridline-color: {grid} }}
|
||||||
for c, tmpl in enumerate(nt["tmpls"]):
|
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: 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.
|
# 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(
|
name = tr(
|
||||||
n=c + 1, name=self._escapeMenuItem(tmpl["name"])
|
TR.BROWSING_ND_NAMES,
|
||||||
|
num=c + 1,
|
||||||
|
name=self._escapeMenuItem(tmpl["name"]),
|
||||||
)
|
)
|
||||||
subm.addItem(
|
subm.addItem(
|
||||||
name, self._filterFunc("note", nt["name"], "card", str(c + 1))
|
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)
|
notes = sum(len(r[1]) for r in res)
|
||||||
part1 = tr(TR.BROWSING_GROUP, count=groups)
|
part1 = tr(TR.BROWSING_GROUP, count=groups)
|
||||||
part2 = tr(TR.BROWSING_NOTE_COUNT, count=notes)
|
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>"
|
t += "<p><ol>"
|
||||||
for val, nids in res:
|
for val, nids in res:
|
||||||
t += (
|
t += (
|
||||||
|
|
|
@ -564,8 +564,10 @@ class CardLayout(QDialog):
|
||||||
|
|
||||||
template = self.current_template()
|
template = self.current_template()
|
||||||
cards = tr(TR.CARD_TEMPLATES_CARD_COUNT, count=card_cnt)
|
cards = tr(TR.CARD_TEMPLATES_CARD_COUNT, count=card_cnt)
|
||||||
msg = tr(TR.CARD_TEMPLATES_DELETE_THE_AS_CARD_TYPE_AND) % dict(
|
msg = tr(
|
||||||
a=template["name"], b=cards
|
TR.CARD_TEMPLATES_DELETE_THE_AS_CARD_TYPE_AND,
|
||||||
|
template=template["name"],
|
||||||
|
cards=cards,
|
||||||
)
|
)
|
||||||
if not askUser(msg):
|
if not askUser(msg):
|
||||||
return
|
return
|
||||||
|
|
|
@ -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."
|
|
|
@ -2,11 +2,8 @@ To run, change to this folder in a terminal and run the following command:
|
||||||
|
|
||||||
./bin/Anki
|
./bin/Anki
|
||||||
|
|
||||||
- To install system wide, ensure you have make installed
|
- To install system wide, run 'sudo ./install.sh'
|
||||||
(eg in Ubuntu, run 'sudo apt install make' in a terminal)
|
- To remove in the future, run 'sudo ./uninstall.sh' from the same folder.
|
||||||
- Run 'sudo make install'
|
|
||||||
- Start Anki with 'anki'
|
|
||||||
- To remove in the future, run 'sudo make uninstall' from the same folder.
|
|
||||||
|
|
||||||
To play and record audio, mpv and lame must be installed. If mpv is not
|
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.
|
installed or too old, Anki will try to fall back on using mplayer.
|
||||||
|
|
29
qt/linux/install.sh
Executable file
29
qt/linux/install.sh
Executable 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
18
qt/linux/uninstall.sh
Executable 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."
|
|
@ -120,11 +120,11 @@ def register_repos():
|
||||||
# translations
|
# translations
|
||||||
################
|
################
|
||||||
|
|
||||||
core_i18n_commit = "1599ff1c4cb60b98fe0e80e1b45da47e9de9eeb1"
|
core_i18n_commit = "82530e22eb374bb745af9d7ae9af879441f5d909"
|
||||||
core_i18n_shallow_since = "1605671186 +1000"
|
core_i18n_shallow_since = "1606023535 +1000"
|
||||||
|
|
||||||
qtftl_i18n_commit = "9909cfa4386288e686b2336b3b1048b7ee1bb194"
|
qtftl_i18n_commit = "72e91ac6c5e00855728fefacfe1366189055fb47"
|
||||||
qtftl_i18n_shallow_since = "1605664969 +1000"
|
qtftl_i18n_shallow_since = "1606023535 +1000"
|
||||||
|
|
||||||
i18n_build_content = """
|
i18n_build_content = """
|
||||||
filegroup(
|
filegroup(
|
||||||
|
|
|
@ -5,7 +5,7 @@ use crate::{
|
||||||
collection::Collection,
|
collection::Collection,
|
||||||
err::{AnkiError, Result},
|
err::{AnkiError, Result},
|
||||||
notes::{NoteID, TransformNoteOutput},
|
notes::{NoteID, TransformNoteOutput},
|
||||||
text::text_to_re,
|
text::to_re,
|
||||||
{text::normalize_to_nfc, types::Usn},
|
{text::normalize_to_nfc, types::Usn},
|
||||||
};
|
};
|
||||||
use regex::{NoExpand, Regex, Replacer};
|
use regex::{NoExpand, Regex, Replacer};
|
||||||
|
@ -134,7 +134,7 @@ impl Collection {
|
||||||
// generate regexps
|
// generate regexps
|
||||||
let tags = split_tags(tags)
|
let tags = split_tags(tags)
|
||||||
.map(|tag| {
|
.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))
|
Regex::new(&format!("(?i)^{}$", tag))
|
||||||
.map_err(|_| AnkiError::invalid_input("invalid regex"))
|
.map_err(|_| AnkiError::invalid_input("invalid regex"))
|
||||||
})
|
})
|
||||||
|
|
|
@ -258,38 +258,6 @@ pub(crate) fn without_combining(s: &str) -> Cow<str> {
|
||||||
.into()
|
.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.
|
/// Check if string contains an unescaped wildcard.
|
||||||
pub(crate) fn is_glob(txt: &str) -> bool {
|
pub(crate) fn is_glob(txt: &str) -> bool {
|
||||||
// even number of \s followed by a wildcard
|
// even number of \s followed by a wildcard
|
||||||
|
@ -375,10 +343,7 @@ pub(crate) fn matches_glob(text: &str, search: &str) -> bool {
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod test {
|
mod test {
|
||||||
use crate::text::without_combining;
|
use super::*;
|
||||||
use crate::text::{
|
|
||||||
extract_av_tags, strip_av_tags, strip_html, strip_html_preserving_media_filenames, AVTag,
|
|
||||||
};
|
|
||||||
use std::borrow::Cow;
|
use std::borrow::Cow;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
@ -427,4 +392,16 @@ mod test {
|
||||||
assert!(matches!(without_combining("test"), Cow::Borrowed(_)));
|
assert!(matches!(without_combining("test"), Cow::Borrowed(_)));
|
||||||
assert!(matches!(without_combining("Über"), Cow::Owned(_)));
|
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*"));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,9 +7,3 @@ py_binary(
|
||||||
stamp = 1,
|
stamp = 1,
|
||||||
visibility = ["//visibility:public"],
|
visibility = ["//visibility:public"],
|
||||||
)
|
)
|
||||||
|
|
||||||
py_binary(
|
|
||||||
name = "extract-strings",
|
|
||||||
srcs = ["extract-strings.py"],
|
|
||||||
deps = [requirement("fluent-syntax")],
|
|
||||||
)
|
|
||||||
|
|
Loading…
Reference in a new issue