Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
*.gem
.idea/
*.iml
*.lock
.ruby-version
.DS_Store
Expand Down
13 changes: 13 additions & 0 deletions python_twine/tests/fixtures/formatter_android_plurals.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<!-- SECTION: Section 1 -->
<!-- comment Plural bookmarks -->
<plurals name="bookmarks_places">
<item quantity="one">%d bookmark</item>
<item quantity="other">%d bookmarks</item>
</plurals>
<plurals name="tracks">
<item quantity="one">%d track</item>
<item quantity="other">%d tracks</item>
</plurals>
</resources>
43 changes: 43 additions & 0 deletions python_twine/tests/fixtures/formatter_apple_plurals.stringsdict
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>

<!-- ********** Strings for downloading map from search **********/ -->

<key>bookmarks_places</key>
<dict>
<key>NSStringLocalizedFormatKey</key>
<string>%#@value@</string>
<key>value</key>
<dict>
<key>NSStringFormatSpecTypeKey</key>
<string>NSStringPluralRuleType</string>
<key>NSStringFormatValueTypeKey</key>
<string>d</string>
<key>one</key>
<string>%d bookmark</string>
<key>other</key>
<string>%d bookmarks</string>
</dict>
</dict>

<key>tracks</key>
<dict>
<key>NSStringLocalizedFormatKey</key>
<string>%#@value@</string>
<key>value</key>
<dict>
<key>NSStringFormatSpecTypeKey</key>
<string>NSStringPluralRuleType</string>
<key>NSStringFormatValueTypeKey</key>
<string>d</string>
<key>one</key>
<string>%d track</string>
<key>other</key>
<string>%d tracks</string>
</dict>
</dict>

</dict>
</plist>
24 changes: 4 additions & 20 deletions python_twine/tests/test_formatters.py
Original file line number Diff line number Diff line change
Expand Up @@ -157,35 +157,19 @@ def test_double_quotes_not_modified(self, formatter):

def test_escape_ampersand(self, formatter):
"""Test ampersand escaping."""
formatter.set_translation_for_key("key1", "en", "this &amp; that", "Section A")
assert (
formatter.twine_file.definitions_by_key["key1"].translations["en"]
== "this & that"
)
assert AndroidFormatter.unescape_value("this &amp; that") == "this & that"

def test_escape_less_than(self, formatter):
"""Test less-than escaping."""
formatter.set_translation_for_key("key1", "en", "this &lt; that", "Section B")
assert (
formatter.twine_file.definitions_by_key["key1"].translations["en"]
== "this < that"
)
assert AndroidFormatter.unescape_value("this &lt; that") == "this < that"

def test_escape_apostrophe(self, formatter):
"""Test apostrophe escaping."""
formatter.set_translation_for_key("key1", "en", "it\\'s complicated", "Section C")
assert (
formatter.twine_file.definitions_by_key["key1"].translations["en"]
== "it's complicated"
)
assert AndroidFormatter.unescape_value("it\\'s complicated") == "it's complicated"

def test_placeholder_conversion(self, formatter):
"""Test placeholder conversion from %s to %@."""
formatter.set_translation_for_key("key1", "en", "value %s", "Section D")
assert (
formatter.twine_file.definitions_by_key["key1"].translations["en"]
== "value %@"
)
assert AndroidFormatter.unescape_value("value %s") == "value %@"

def test_writer_escape_ampersand(self, formatter):
"""Test ampersand escaping."""
Expand Down
209 changes: 209 additions & 0 deletions python_twine/tests/test_formatters_plural.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,97 @@

import pytest

from twine.formatters.apple_plural import ApplePluralFormatter
from twine.twine_file import TwineFile, TwineDefinition, TwineSection
from twine.formatters.android import AndroidFormatter

class TestAndroidPluralFormatter:
"""Test Android XML formatter with <plural/> tags."""

@pytest.fixture
def formatter(self):
"""Create formatter with empty TwineFile."""
twine_file = TwineFile()
formatter = AndroidFormatter()
formatter.twine_file = twine_file
formatter.options = {"consume_all": True, "consume_comments": True}
return formatter

@pytest.fixture
def fixtures_dir(self):
"""Get fixtures directory path."""
return Path(__file__).parent / "fixtures"

@pytest.fixture
def twine_file(self):
# Prepare TwineFile data
twine_file = TwineFile()
num_edits_def = TwineDefinition("num_edits")

twine_file.definitions_by_key["num_edits"] = num_edits_def
twine_file.language_codes = ["en", "de"]
twine_file.sections = [TwineSection("OSM")]
twine_file.sections[0].definitions.append(num_edits_def)

# Put plural translation
num_edits_def.plural_translations["en"] = {
"one": "%d edit",
"other": "%d edits"
}
num_edits_def.translations["en"] = "%d edits"

num_edits_def.plural_translations["de"] = {
"zero": "%d Bearbeitungen",
"one": "%d Bearbeitung",
"other": "%d Bearbeitungen"
}
num_edits_def.translations["de"] = "%d Bearbeitungen"

return twine_file

def test_read_format(self, formatter, fixtures_dir):
"""Test reading Android XML format with <plural/> tags."""
fixture_path = fixtures_dir / "formatter_android_plurals.xml"
with open(fixture_path, "r", encoding="utf-8") as f:
formatter.read(f, "en")

twine_file = formatter.twine_file

assert "bookmarks_places" in twine_file.definitions_by_key
translations1 = twine_file.definitions_by_key["bookmarks_places"].plural_translations
assert translations1 == {"en": {"one": "%d bookmark", "other": "%d bookmarks"}}

assert "tracks" in twine_file.definitions_by_key
translations2 = twine_file.definitions_by_key["tracks"].plural_translations
assert translations2 == {"en": {"one": "%d track", "other": "%d tracks"}}

def test_write_format(self, formatter, twine_file):
formatter.twine_file = twine_file

en_plural_content = formatter.format_file("en")
assert en_plural_content == """<?xml version="1.0" encoding="utf-8"?>
<resources>

<!-- SECTION: OSM -->
<plurals name="num_edits">
<item quantity="one">%d edit</item>
<item quantity="other">%d edits</item>
</plurals>
</resources>
"""

de_plural_content = formatter.format_file("de")
assert de_plural_content == """<?xml version="1.0" encoding="utf-8"?>
<resources>

<!-- SECTION: OSM -->
<plurals name="num_edits">
<item quantity="zero">%d Bearbeitungen</item>
<item quantity="one">%d Bearbeitung</item>
<item quantity="other">%d Bearbeitungen</item>
</plurals>
</resources>
"""


class TestTwineFilePlural:
Expand Down Expand Up @@ -71,3 +161,122 @@ def test_read_plurals(self, fixtures_dir):
"many": "%d меток",
"other": "%d меток"
}


class TestApplePluralFormatter:
@pytest.fixture
def formatter(self) -> ApplePluralFormatter:
"""Create formatter with empty TwineFile."""
formatter = ApplePluralFormatter()
formatter.options = {"consume_all": True, "consume_comments": True}
return formatter

@pytest.fixture
def fixtures_dir(self) -> Path:
"""Get fixtures directory path."""
return Path(__file__).parent / "fixtures"

@pytest.fixture
def twine_file(self) -> TwineFile:
# Prepare TwineFile data
twine_file = TwineFile()
num_edits_def = TwineDefinition("num_edits")

twine_file.definitions_by_key["num_edits"] = num_edits_def
twine_file.language_codes = ["en", "de"]
twine_file.sections = [TwineSection("OSM")]
twine_file.sections[0].definitions.append(num_edits_def)

# Put plural translation
num_edits_def.plural_translations["en"] = {
"one": "%d edit",
"other": "%d edits"
}
num_edits_def.translations["en"] = "%d edits"

num_edits_def.plural_translations["de"] = {
"zero": "%d Bearbeitungen",
"one": "%d Bearbeitung",
"other": "%d Bearbeitungen"
}
num_edits_def.translations["de"] = "%d Bearbeitungen"

return twine_file

def test_read_stringsdict(self, formatter:ApplePluralFormatter, fixtures_dir):
"""Test reading Android XML format with <plural/> tags."""
fixture_path = fixtures_dir / "formatter_apple_plurals.stringsdict"
with open(fixture_path, "r", encoding="utf-8") as f:
formatter.read(f, "en")

twine_file = formatter.twine_file

assert "bookmarks_places" in twine_file.definitions_by_key
translations1 = twine_file.definitions_by_key["bookmarks_places"].plural_translations
assert translations1 == {"en": {"one": "%d bookmark", "other": "%d bookmarks"}}

assert "tracks" in twine_file.definitions_by_key
translations2 = twine_file.definitions_by_key["tracks"].plural_translations
assert translations2 == {"en": {"one": "%d track", "other": "%d tracks"}}


def test_write_plural_format(self, formatter, twine_file):
formatter.twine_file = twine_file

en_plural_content = formatter.format_file("en")
assert en_plural_content == """<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>

<!-- ********** OSM **********/ -->

\t<key>num_edits</key>
\t<dict>
\t\t<key>NSStringLocalizedFormatKey</key>
\t\t<string>%#@value@</string>
\t\t<key>value</key>
\t\t<dict>
\t\t\t<key>NSStringFormatSpecTypeKey</key>
\t\t\t<string>NSStringPluralRuleType</string>
\t\t\t<key>NSStringFormatValueTypeKey</key>
\t\t\t<string>d</string>
\t\t\t<key>one</key>
\t\t\t<string>%d edit</string>
\t\t\t<key>other</key>
\t\t\t<string>%d edits</string>
\t\t</dict>
\t</dict>

</dict>
</plist>"""

de_plural_content = formatter.format_file("de")
assert de_plural_content == """<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>

<!-- ********** OSM **********/ -->

\t<key>num_edits</key>
\t<dict>
\t\t<key>NSStringLocalizedFormatKey</key>
\t\t<string>%#@value@</string>
\t\t<key>value</key>
\t\t<dict>
\t\t\t<key>NSStringFormatSpecTypeKey</key>
\t\t\t<string>NSStringPluralRuleType</string>
\t\t\t<key>NSStringFormatValueTypeKey</key>
\t\t\t<string>d</string>
\t\t\t<key>zero</key>
\t\t\t<string>%d Bearbeitungen</string>
\t\t\t<key>one</key>
\t\t\t<string>%d Bearbeitung</string>
\t\t\t<key>other</key>
\t\t\t<string>%d Bearbeitungen</string>
\t\t</dict>
\t</dict>

</dict>
</plist>"""
3 changes: 3 additions & 0 deletions python_twine/twine/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,9 @@ def create_parser() -> argparse.ArgumentParser:
)
consume_all.add_argument("twine_file", help="Path to Twine data file")
consume_all.add_argument("input_path", help="Input directory path")
consume_all.add_argument(
"-n", "--file-name", help="Input file name (default: format-specific)"
)
CLI._add_common_arguments(consume_all)
CLI._add_consume_arguments(consume_all)

Expand Down
Loading