dotstrings 2.0.0__tar.gz → 3.1.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,23 +1,25 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: dotstrings
3
- Version: 2.0.0
3
+ Version: 3.1.0
4
4
  Summary: Tools for dealing with the .strings files for iOS and macOS
5
5
  Home-page: https://github.com/Microsoft/dotstrings
6
6
  License: MIT
7
7
  Keywords: localization,iOS,macOS,strings
8
8
  Author: Dale Myers
9
- Author-email: dalemy@microsoft.com
10
- Requires-Python: >=3.8,<4.0
9
+ Author-email: dalemyers@microsoft.com
10
+ Requires-Python: >=3.10,<4.0
11
11
  Classifier: Development Status :: 5 - Production/Stable
12
12
  Classifier: Environment :: Console
13
13
  Classifier: Environment :: MacOS X
14
14
  Classifier: Intended Audience :: Developers
15
15
  Classifier: License :: OSI Approved :: MIT License
16
16
  Classifier: Programming Language :: Python :: 3
17
- Classifier: Programming Language :: Python :: 3.8
18
- Classifier: Programming Language :: Python :: 3.9
19
17
  Classifier: Programming Language :: Python :: 3.10
20
18
  Classifier: Programming Language :: Python :: 3.11
19
+ Classifier: Programming Language :: Python :: 3.12
20
+ Classifier: Programming Language :: Python :: 3.13
21
+ Classifier: Programming Language :: Python :: 3.8
22
+ Classifier: Programming Language :: Python :: 3.9
21
23
  Classifier: Topic :: Software Development
22
24
  Classifier: Topic :: Utilities
23
25
  Project-URL: Repository, https://github.com/Microsoft/dotstrings
@@ -1,11 +1,11 @@
1
1
  """Utilities for dealing with .strings files"""
2
2
 
3
3
  import os
4
- from typing import Dict, List, Optional, Set
5
4
 
6
5
  from dotstrings.parser import load, loads, load_dict, loads_dict
7
6
  from dotstrings.dot_strings_entry import DotStringsEntry
8
7
  from dotstrings.dot_stringsdict_entry import DotStringsDictEntry, Variable
8
+ from dotstrings.exceptions import DotStringsException
9
9
  from dotstrings.localized_bundle import LocalizedBundle
10
10
  from dotstrings.localized_string import LocalizedString
11
11
 
@@ -50,7 +50,7 @@ def stringsdict_file_path(stringsdict_folder: str, language: str, table_name: st
50
50
  )
51
51
 
52
52
 
53
- def languages_in_folder(strings_folder: str) -> Set[str]:
53
+ def languages_in_folder(strings_folder: str) -> set[str]:
54
54
  """Find all the languages in a folder
55
55
 
56
56
  This looks for *.lproj folders.
@@ -65,7 +65,7 @@ def languages_in_folder(strings_folder: str) -> Set[str]:
65
65
  return {language.replace(".lproj", "") for language in languages}
66
66
 
67
67
 
68
- def load_table(strings_folder: str, language: str, table_name: str) -> List[LocalizedString]:
68
+ def load_table(strings_folder: str, language: str, table_name: str) -> list[LocalizedString]:
69
69
  """Load the specified .strings table
70
70
 
71
71
  :param strings_folder: The location of the strings folder (which contains
@@ -83,7 +83,7 @@ def load_table(strings_folder: str, language: str, table_name: str) -> List[Loca
83
83
  )
84
84
 
85
85
 
86
- def load_language_tables(strings_folder: str, language: str) -> Dict[str, List[LocalizedString]]:
86
+ def load_language_tables(strings_folder: str, language: str) -> dict[str, list[LocalizedString]]:
87
87
  """Load the .strings tables for a given language
88
88
 
89
89
  :param strings_folder: The location of the strings folder (which contains
@@ -129,7 +129,7 @@ def load_all_strings(strings_folder: str) -> LocalizedBundle:
129
129
 
130
130
  def normalize(
131
131
  strings_path: str,
132
- output_path: Optional[str] = None,
132
+ output_path: str | None = None,
133
133
  remove_duplicates: bool = True,
134
134
  sort_comments: bool = True,
135
135
  ) -> None:
@@ -165,7 +165,7 @@ def normalize(
165
165
  # If we have duplicate keys but the values don't match, that's an
166
166
  # exception, whether or not we are removing duplicates
167
167
  if deduped_entries[-1].value != entry.value or not remove_duplicates:
168
- raise Exception(f"Found duplicate strings with key: {entry.key}")
168
+ raise DotStringsException(f"Found duplicate strings with key: {entry.key}")
169
169
 
170
170
  deduped_entries[-1].comments.extend(entry.comments)
171
171
 
@@ -1,7 +1,5 @@
1
1
  """Base types for the dotstrings library."""
2
2
 
3
- from typing import List
4
-
5
3
 
6
4
  class DotStringsEntry:
7
5
  """Represents a .strings entry.
@@ -13,9 +11,9 @@ class DotStringsEntry:
13
11
 
14
12
  key: str
15
13
  value: str
16
- comments: List[str]
14
+ comments: list[str]
17
15
 
18
- def __init__(self, key: str, value: str, comments: List[str]) -> None:
16
+ def __init__(self, key: str, value: str, comments: list[str]) -> None:
19
17
  self.key = key
20
18
  self.value = value
21
19
  self.comments = comments
@@ -1,6 +1,6 @@
1
1
  """Base types for the dotstrings library."""
2
2
 
3
- from typing import Optional
3
+ from dotstrings.exceptions import DotStringsException
4
4
 
5
5
  VARIABLE_VALUE_TYPE_KEY = "NSStringFormatValueTypeKey"
6
6
  VARIABLE_VALUE_SPEC_KEY = "NSStringFormatSpecTypeKey"
@@ -21,13 +21,13 @@ class Variable:
21
21
  :param other_value: Value for other
22
22
  """
23
23
 
24
- value_type: Optional[str]
25
- zero_value: Optional[str]
26
- one_value: Optional[str]
27
- two_value: Optional[str]
28
- few_value: Optional[str]
29
- many_value: Optional[str]
30
- other_value: Optional[str]
24
+ value_type: str | None
25
+ zero_value: str | None
26
+ one_value: str | None
27
+ two_value: str | None
28
+ few_value: str | None
29
+ many_value: str | None
30
+ other_value: str | None
31
31
 
32
32
  def __init__(self) -> None:
33
33
  self.value_type = None
@@ -50,14 +50,16 @@ class Variable:
50
50
 
51
51
  variable = Variable()
52
52
  if VARIABLE_VALUE_SPEC_KEY not in contents:
53
- raise Exception("NSStringFormatSpecTypeKey missing in entry")
53
+ raise DotStringsException("NSStringFormatSpecTypeKey missing in entry")
54
54
 
55
55
  if contents[VARIABLE_VALUE_SPEC_KEY] != VARIABLE_VALUE_SPEC_PLURAL:
56
- raise Exception("Value of NSStringFormatSpecTypeKey is not NSStringPluralRuleType")
56
+ raise DotStringsException(
57
+ "Value of NSStringFormatSpecTypeKey is not NSStringPluralRuleType"
58
+ )
57
59
 
58
60
  # When initializing from a dict (parsing), be sure NSStringFormatValueTypeKey exists
59
61
  if VARIABLE_VALUE_TYPE_KEY not in contents:
60
- raise Exception("NSStringFormatValueTypeKey missing in entry")
62
+ raise DotStringsException("NSStringFormatValueTypeKey missing in entry")
61
63
 
62
64
  variable.value_type = contents[VARIABLE_VALUE_TYPE_KEY]
63
65
 
@@ -116,7 +118,7 @@ class DotStringsDictEntry:
116
118
 
117
119
  :returns: The dict representation of this entry
118
120
  """
119
- result = {}
121
+ result: dict[str, str | dict[str, str]] = {}
120
122
  result[FORMAT_KEY] = self.value
121
123
  for variable_name, variable in self.variables.items():
122
124
  variable_dict = {}
@@ -165,7 +167,7 @@ class DotStringsDictEntry:
165
167
  :returns: The parsed stringsdict entry
166
168
  """
167
169
  if FORMAT_KEY not in contents:
168
- raise Exception("NSStringLocalizedFormatKey missing in entry")
170
+ raise DotStringsException("NSStringLocalizedFormatKey missing in entry")
169
171
 
170
172
  entry_format = contents[FORMAT_KEY]
171
173
 
@@ -0,0 +1,5 @@
1
+ """Exception types."""
2
+
3
+
4
+ class DotStringsException(Exception):
5
+ """Base class used for dotstrings errors."""
@@ -4,7 +4,8 @@ import os
4
4
  import shutil
5
5
  import subprocess
6
6
  import tempfile
7
- from typing import List
7
+
8
+ from dotstrings.exceptions import DotStringsException
8
9
 
9
10
 
10
11
  def _convert_to_utf8(file_path: str) -> None:
@@ -22,13 +23,13 @@ def _convert_to_utf8(file_path: str) -> None:
22
23
  iconv_command = f'iconv -f UTF-16 -t UTF-8 "{file_path}" > "{temp_file_path}"'
23
24
 
24
25
  if subprocess.run(iconv_command, shell=True, check=False).returncode != 0:
25
- raise Exception("Unable to convert from UTF-16 to UTF-8!")
26
+ raise DotStringsException("Unable to convert from UTF-16 to UTF-8!")
26
27
 
27
28
  shutil.move(temp_file_path, file_path)
28
29
 
29
30
 
30
31
  def generate_strings(
31
- *, output_directory: str, file_paths: List[str], clear_existing: bool = True
32
+ *, output_directory: str, file_paths: list[str], clear_existing: bool = True
32
33
  ) -> None:
33
34
  """Run the genstrings command over the files passed in.
34
35
 
@@ -53,7 +54,7 @@ def generate_strings(
53
54
 
54
55
  :param str output_directory: The location to place the output files (this
55
56
  folder will contain an en.lproj folder after)
56
- :param List[str] file_paths: The paths to the files that should be scanned
57
+ :param list[str] file_paths: The paths to the files that should be scanned
57
58
  :param bool clear_existing: Set to True when the existing files in the
58
59
  output directory should be wiped before
59
60
  generating the new strings. Defaults to True.
@@ -110,9 +111,9 @@ def generate_strings(
110
111
  output = output.strip()
111
112
 
112
113
  if len(output) > 0:
113
- raise Exception(f"Encountered an error generating strings: {output}")
114
+ raise DotStringsException(f"Encountered an error generating strings: {output}")
114
115
  except subprocess.CalledProcessError as ex:
115
- raise Exception(f"Unable generate .strings files! {ex}") from ex
116
+ raise DotStringsException(f"Unable generate .strings files! {ex}") from ex
116
117
 
117
118
  # Convert all .strings files to UTF-8
118
119
  for file_name in os.listdir(english_strings_directory):
@@ -1,7 +1,8 @@
1
1
  """Representation of a localized bundle."""
2
2
 
3
- from typing import cast, Dict, List, Set
3
+ from typing import cast
4
4
 
5
+ from dotstrings.exceptions import DotStringsException
5
6
  from dotstrings.localized_string import LocalizedString
6
7
 
7
8
 
@@ -11,17 +12,17 @@ class LocalizedBundle:
11
12
  :param raw_entries: The raw dictionary entries that were parsed from disk
12
13
  """
13
14
 
14
- def __init__(self, raw_entries: Dict[str, Dict[str, List[LocalizedString]]]) -> None:
15
+ def __init__(self, raw_entries: dict[str, dict[str, list[LocalizedString]]]) -> None:
15
16
  self.raw_entries = raw_entries
16
17
 
17
- def languages(self) -> List[str]:
18
+ def languages(self) -> list[str]:
18
19
  """Return the languages supported in the bundle
19
20
 
20
21
  :returns: A list of language codes
21
22
  """
22
23
  return list(self.raw_entries.keys())
23
24
 
24
- def table_names(self, validate_identical: bool = False) -> List[str]:
25
+ def table_names(self, validate_identical: bool = False) -> list[str]:
25
26
  """Return the tables in the bundle.
26
27
 
27
28
  :param validate_identical: Set to True to confirm all languages have the
@@ -34,7 +35,7 @@ class LocalizedBundle:
34
35
 
35
36
  # Build up a map of languages to table names
36
37
 
37
- found_tables: Dict[str, Set[str]] = {}
38
+ found_tables: dict[str, set[str]] = {}
38
39
  for language, table_map in self.raw_entries.items():
39
40
  # table_map is a dictionary of names to lists of strings
40
41
  found_tables[language] = set(table_map.keys())
@@ -50,13 +51,13 @@ class LocalizedBundle:
50
51
  missing_tables = base_language_tables - table_names
51
52
 
52
53
  if len(extra_tables) > 0:
53
- raise Exception(
54
+ raise DotStringsException(
54
55
  f"The following table names were in {language}"
55
56
  + f" but not in {base_language}: {extra_tables}"
56
57
  )
57
58
 
58
59
  if len(missing_tables) > 0:
59
- raise Exception(
60
+ raise DotStringsException(
60
61
  f"The following table names were in {base_language}"
61
62
  + f" but not in {language}: {missing_tables}"
62
63
  )
@@ -71,7 +72,7 @@ class LocalizedBundle:
71
72
 
72
73
  return list(all_table_names)
73
74
 
74
- def tables_for_language(self, language: str) -> Dict[str, List[LocalizedString]]:
75
+ def tables_for_language(self, language: str) -> dict[str, list[LocalizedString]]:
75
76
  """Return the tables for a language.
76
77
 
77
78
  :param language: The language to get the tables for
@@ -82,13 +83,13 @@ class LocalizedBundle:
82
83
  result = self.raw_entries.get(language, sentinel)
83
84
 
84
85
  if result is sentinel:
85
- raise Exception(f"There were no entries for language: {language}")
86
+ raise DotStringsException(f"There were no entries for language: {language}")
86
87
 
87
- return cast(Dict[str, List[LocalizedString]], result)
88
+ return cast(dict[str, list[LocalizedString]], result)
88
89
 
89
90
  def table_for_languages(
90
91
  self, table: str, *, allow_missing: bool = False
91
- ) -> Dict[str, List[LocalizedString]]:
92
+ ) -> dict[str, list[LocalizedString]]:
92
93
  """Return a dictionary of languages to strings for a given table.
93
94
 
94
95
  :param table: The table to load the data for
@@ -106,15 +107,15 @@ class LocalizedBundle:
106
107
  continue
107
108
 
108
109
  if table_data is sentinel and not allow_missing:
109
- raise Exception(f"Could not find table {table} for language {language}")
110
+ raise DotStringsException(f"Could not find table {table} for language {language}")
110
111
 
111
- results[language] = cast(List[LocalizedString], table_data)
112
+ results[language] = cast(list[LocalizedString], table_data)
112
113
 
113
114
  return results
114
115
 
115
116
  def tables(
116
117
  self, *, validate_missing: bool = True
117
- ) -> Dict[str, Dict[str, List[LocalizedString]]]:
118
+ ) -> dict[str, dict[str, list[LocalizedString]]]:
118
119
  """Return the entries in the bundle, first keyed by table, then by language.
119
120
 
120
121
  :param bool validate_missing: Set to False to disable the check that a table exists for every language
@@ -2,9 +2,10 @@
2
2
 
3
3
  import hashlib
4
4
  import re
5
- from typing import ClassVar, List, Optional, Pattern
5
+ from typing import ClassVar, Pattern
6
6
 
7
7
  from dotstrings.dot_strings_entry import DotStringsEntry
8
+ from dotstrings.exceptions import DotStringsException
8
9
 
9
10
 
10
11
  class LocalizedString:
@@ -20,41 +21,42 @@ class LocalizedString:
20
21
  you may wish to automatically generate keys to avoid having to manually
21
22
  deal with deduplication.
22
23
 
23
- :param Optional[str] key: The key for the string. If this is None, they key
24
+ :param str | None key: The key for the string. If this is None, they key
24
25
  will be automatically derived from the value and
25
26
  the key extension.
26
27
  :param str value: The value of the string
27
28
  :param str language: The language code of the string
28
29
  :param str table: The string table to use
29
- :param Optional[str] comment: The comment for the string
30
- :param Optional[str] key_extension: The key extension to differentiate
30
+ :param str | None comment: The comment for the string
31
+ :param str | None key_extension: The key extension to differentiate
31
32
  between identical strings with different
32
33
  meanings
33
34
  :param str bundle: The bundle the string can be found in
34
35
  """
35
36
 
36
- _TOKEN_REGEX: ClassVar[
37
- str
38
- ] = r"(%(?:[0-9]+\$)?[0-9]*\.?[0-9]*[a-zA-Z]{0,2}[dDuUxXoOfFeEgGcCsSaAp@])"
37
+ _TOKEN_REGEX: ClassVar[str] = (
38
+ r"(%(?:[0-9]+\$)?[0-9]*\.?[0-9]*[a-zA-Z]{0,2}[dDuUxXoOfFeEgGcCsSaAp@])"
39
+ )
39
40
  _TOKEN_PATTERN: ClassVar[Pattern] = re.compile(_TOKEN_REGEX, flags=re.DOTALL)
40
41
 
41
42
  key: str
42
43
  value: str
43
44
  language: str
44
45
  table: str
45
- comment: Optional[str]
46
- key_extension: Optional[str]
46
+ comment: str | None
47
+ key_extension: str | None
47
48
  bundle: str
48
49
 
50
+ # pylint: disable=too-many-arguments
49
51
  def __init__(
50
52
  self,
51
53
  *,
52
- key: Optional[str],
54
+ key: str | None,
53
55
  value: str,
54
56
  language: str,
55
57
  table: str,
56
- comment: Optional[str] = None,
57
- key_extension: Optional[str] = None,
58
+ comment: str | None = None,
59
+ key_extension: str | None = None,
58
60
  bundle: str = "",
59
61
  ) -> None:
60
62
  self.value = value
@@ -69,8 +71,10 @@ class LocalizedString:
69
71
  else:
70
72
  self.key = LocalizedString._calculate_key(value=value, key_extension=key_extension)
71
73
 
74
+ # pylint: enable=too-many-arguments
75
+
72
76
  @staticmethod
73
- def _calculate_key(*, value: str, key_extension: Optional[str]) -> str:
77
+ def _calculate_key(*, value: str, key_extension: str | None) -> str:
74
78
  """Calculate the unique key to use for this string.
75
79
 
76
80
  :param value: The value of the localized string
@@ -93,7 +97,7 @@ class LocalizedString:
93
97
 
94
98
  return key
95
99
 
96
- def tokens(self) -> List[str]:
100
+ def tokens(self) -> list[str]:
97
101
  """Find and return the tokens in the string.
98
102
 
99
103
  :returns: The list of tokens in the string
@@ -108,7 +112,7 @@ class LocalizedString:
108
112
  :raises Exception: If the language is not English
109
113
  """
110
114
  if self.language != "en":
111
- raise Exception(f"This should only be called for English strings: {self}")
115
+ raise DotStringsException(f"This should only be called for English strings: {self}")
112
116
  return (
113
117
  "NSLocalizedStringWithDefaultValue("
114
118
  + f'@"{self.key}", @"{self.table}", @"{self.bundle}", @"{self.value}", @"{self.comment}");'
@@ -186,11 +190,11 @@ class LocalizedString:
186
190
 
187
191
  @staticmethod
188
192
  def from_dotstring_entries(
189
- *, entries: List[DotStringsEntry], language: str, table: str
190
- ) -> List["LocalizedString"]:
193
+ *, entries: list[DotStringsEntry], language: str, table: str
194
+ ) -> list["LocalizedString"]:
191
195
  """Convert a list of DotStringsEntry's into a list of LocalizedString's
192
196
 
193
- :param List[DotStringsEntry] entries: The DotStringsEntry's to convert
197
+ :param list[DotStringsEntry] entries: The DotStringsEntry's to convert
194
198
  :param str language: The language the DotStringsEntry's are in
195
199
  :param str table: The table the DotStringsEntry's are from
196
200
 
@@ -2,8 +2,9 @@
2
2
 
3
3
  import plistlib
4
4
  import re
5
- from typing import BinaryIO, List, Optional, Pattern, TextIO, Union
5
+ from typing import BinaryIO, Pattern, TextIO
6
6
 
7
+ from dotstrings.exceptions import DotStringsException
7
8
  from dotstrings.dot_strings_entry import DotStringsEntry
8
9
  from dotstrings.dot_stringsdict_entry import DotStringsDictEntry
9
10
 
@@ -13,7 +14,8 @@ class Patterns:
13
14
 
14
15
  comment = re.compile(r"(\'(?:[^\'\\]|\\[\s\S])*\')|//.*|/\*(?:[^*]|\*(?!/))*\*/", re.MULTILINE)
15
16
  whitespace = re.compile(r"\s*", re.MULTILINE)
16
- entry = re.compile(r'"(.*)" = "(.*)";')
17
+ entry = re.compile(r'"(.*)"\s*=\s*"(.*)";')
18
+ quoteless_key_entry = re.compile(r'(.*?)\s*=\s*"(.*)";')
17
19
 
18
20
 
19
21
  class Scanner:
@@ -33,7 +35,7 @@ class Scanner:
33
35
  """
34
36
  return self.offset < len(self.string)
35
37
 
36
- def scan(self, pattern: Union[str, Pattern], flags: int = 0) -> Optional[str]:
38
+ def scan(self, pattern: str | Pattern, flags: int = 0) -> str | None:
37
39
  """Scan a string for a pattern and return the string if found.
38
40
 
39
41
  :param pattern: The pattern to scan for
@@ -56,7 +58,7 @@ class Scanner:
56
58
  return None
57
59
 
58
60
 
59
- def load(file_details: Union[TextIO, str], encoding: Optional[str] = None) -> List[DotStringsEntry]:
61
+ def load(file_details: TextIO | str, encoding: str | None = None) -> list[DotStringsEntry]:
60
62
  """Parse the contents of a .strings file from a file pointer.
61
63
 
62
64
  :param file_details: The file pointer or a file path
@@ -83,10 +85,10 @@ def load(file_details: Union[TextIO, str], encoding: Optional[str] = None) -> Li
83
85
  except UnicodeDecodeError:
84
86
  pass
85
87
 
86
- raise Exception(f"Could not determine encoding for file at path: {file_details}")
88
+ raise DotStringsException(f"Could not determine encoding for file at path: {file_details}")
87
89
 
88
90
 
89
- def loads(contents: str) -> List[DotStringsEntry]:
91
+ def loads(contents: str) -> list[DotStringsEntry]:
90
92
  """Parse the contents of a .strings file.
91
93
 
92
94
  Note: CRLF is not supported in strings.
@@ -98,7 +100,7 @@ def loads(contents: str) -> List[DotStringsEntry]:
98
100
  # Sometimes we have CRLF. It's easier to just replace now. This could, in
99
101
  # theory, cause issues, but we just don't support it for now.
100
102
  if "\r\n" in contents:
101
- raise Exception("Strings contain CRLF")
103
+ raise DotStringsException("Strings contain CRLF")
102
104
  contents = contents.replace("\r\n", "\n")
103
105
 
104
106
  scanner = Scanner(contents)
@@ -136,18 +138,32 @@ def loads(contents: str) -> List[DotStringsEntry]:
136
138
  # Pull out any whitespace
137
139
  _ = scanner.scan(Patterns.whitespace)
138
140
 
139
- # Get the entry line
141
+ # Get the entry line. Always try with quotes first to avoid matching
142
+ # the "quoteless" style and then including the quotes in the key.
140
143
  entry = scanner.scan(Patterns.entry)
144
+ regular_entry = True
141
145
 
142
146
  if entry is None:
143
- if scanner.has_more():
144
- raise Exception(f"Expected an entry at offset {scanner.offset}")
145
- break
147
+ entry = scanner.scan(Patterns.quoteless_key_entry)
148
+ regular_entry = False
149
+
150
+ if entry is None:
151
+ if scanner.has_more():
152
+ raise DotStringsException(
153
+ f"Expected an entry at offset {scanner.offset}"
154
+ )
155
+ break
146
156
 
147
157
  # Now extract the key and value
148
- entry_matches = Patterns.entry.search(entry)
158
+ if regular_entry:
159
+ entry_matches = Patterns.entry.search(entry)
160
+ else:
161
+ entry_matches = Patterns.quoteless_key_entry.search(entry)
162
+
149
163
  if not entry_matches:
150
- raise Exception(f"Failed to parse entry at offset {scanner.offset}")
164
+ raise DotStringsException(
165
+ f"Failed to parse entry at offset {scanner.offset}"
166
+ )
151
167
 
152
168
  key = entry_matches.group(1)
153
169
  value = entry_matches.group(2)
@@ -157,7 +173,7 @@ def loads(contents: str) -> List[DotStringsEntry]:
157
173
  return strings
158
174
 
159
175
 
160
- def load_dict(file_details: Union[BinaryIO, str]) -> List[DotStringsDictEntry]:
176
+ def load_dict(file_details: BinaryIO | str) -> list[DotStringsDictEntry]:
161
177
  """Parse the contents of a .stringsdict file from a file pointer.
162
178
 
163
179
  :param file_details: The file pointer or a file path
@@ -174,7 +190,7 @@ def load_dict(file_details: Union[BinaryIO, str]) -> List[DotStringsDictEntry]:
174
190
  return load_dict(stringsdict_file)
175
191
 
176
192
 
177
- def loads_dict(contents: bytes) -> List[DotStringsDictEntry]:
193
+ def loads_dict(contents: bytes) -> list[DotStringsDictEntry]:
178
194
  """Parse the contents of a .stringsdict file from binary data.
179
195
 
180
196
  :param contents: The binary data of a .stringsdict file
@@ -185,12 +201,12 @@ def loads_dict(contents: bytes) -> List[DotStringsDictEntry]:
185
201
  strings_dict = plistlib.loads(contents)
186
202
 
187
203
  if not isinstance(strings_dict, dict):
188
- raise Exception("stringsdict format is incorrect")
204
+ raise DotStringsException("stringsdict format is incorrect")
189
205
 
190
206
  entries = []
191
207
  for key, entry in strings_dict.items():
192
208
  if not isinstance(entry, dict):
193
- raise Exception("stringsdict entry format is incorrect")
209
+ raise DotStringsException("stringsdict entry format is incorrect")
194
210
 
195
211
  entries.append(DotStringsDictEntry.parse(key, entry))
196
212
 
@@ -1,12 +1,12 @@
1
1
  [tool.poetry]
2
2
  name = "dotstrings"
3
- version = "2.0.0"
3
+ version = "3.1.0"
4
4
  description = "Tools for dealing with the .strings files for iOS and macOS"
5
5
 
6
6
  license = "MIT"
7
7
 
8
8
  authors = [
9
- "Dale Myers <dalemy@microsoft.com>"
9
+ "Dale Myers <dalemyers@microsoft.com>"
10
10
  ]
11
11
 
12
12
  readme = 'README.md'
@@ -31,14 +31,14 @@ classifiers = [
31
31
  ]
32
32
 
33
33
  [tool.poetry.dependencies]
34
- python = "^3.8"
34
+ python = "^3.10"
35
35
 
36
36
  [tool.poetry.dev-dependencies]
37
- black = "^22.10.0"
38
- mypy = "^0.991"
39
- pylint = "^2.15.6"
40
- pytest = "^7.2.0"
41
- pytest-cov = "^4.0.0"
37
+ black = "24.4.0"
38
+ mypy = "1.9.0"
39
+ pylint = "3.1.0"
40
+ pytest = "^8.1.0"
41
+ pytest-cov = "^5.0.0"
42
42
 
43
43
 
44
44
  [build-system]
File without changes
File without changes