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( 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
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-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)

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-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

View file

@ -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.

View file

@ -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?
} }

View file

@ -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.
} }

View file

@ -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.
} }

View file

@ -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.
} }

View file

@ -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
} }

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-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
} }

View file

@ -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"

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-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}

View file

@ -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...

View file

@ -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.
} }

View file

@ -6,3 +6,4 @@ search-note-modified = Note Modified
search-card-modified = Card 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-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.
} }

View file

@ -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
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-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 }

View file

@ -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?
} }

View file

@ -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.

View file

@ -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

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-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
}

View file

@ -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
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: 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

View file

@ -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)

View file

@ -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):

View file

@ -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 += (

View file

@ -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

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 ./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
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 # 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(

View file

@ -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"))
}) })

View file

@ -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*"));
}
} }

View file

@ -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")],
)