44
55from typing import Dict , Optional , TextIO
66from xml .etree import ElementTree as ET
7+ from xml .etree .ElementTree import Element
78
89from twine .formatters .apple import AppleFormatter
910from twine .placeholders import convert_placeholders_from_android_to_twine
@@ -128,94 +129,27 @@ def read(self, io: TextIO, lang: str):
128129 comment_text = None
129130 for j in range (i - 1 , - 1 , - 1 ):
130131 prev = children [j ]
131- if isinstance (prev , ET .Comment ):
132+ # Handle comments (they have a callable tag function)
133+ if callable (prev .tag ):
132134 comment_text = prev .text .strip () if prev .text else None
133135 break
134136 elif prev .tag is not None : # Hit another element
135137 break
136138
137139 # Extract plural hash
138- plural_hash = {}
139-
140- # Find <key>value</key><dict> inside value_container
141- value_dict = None
142- value_children = list (value_container )
143-
144- for j , inner_key in enumerate (value_children ):
145- if inner_key .tag == "key" and inner_key .text == "value" :
146- if j + 1 < len (value_children ):
147- value_dict = value_children [j + 1 ]
148- break
149-
150- if value_dict is not None and value_dict .tag == "dict" :
151- # Extract plural entries
152- plural_children = list (value_dict )
153- j = 0
154-
155- while j < len (plural_children ):
156- pkey_elem = plural_children [j ]
157-
158- if pkey_elem .tag == "key" :
159- pkey = pkey_elem .text
160-
161- if pkey in TwineDefinition .PLURAL_KEYS :
162- if j + 1 < len (plural_children ):
163- string_elem = plural_children [j + 1 ]
164-
165- if string_elem .tag == "string" :
166- pvalue = string_elem .text or ""
167- plural_hash [pkey ] = pvalue
168-
169- j += 1
140+ plural_hash = self .extract_plural_dict (value_container )
170141
171142 if not plural_hash :
172143 i += 2
173144 continue
174145
175146 # Get or create definition
176- definition = self .twine_file .definitions_by_key .get (key_name )
177-
178- if not definition :
179- if self .options .get ("consume_all" ):
180- print (
181- f"Adding new plural definition '{ key_name } ' to twine file." ,
182- file = twine .stdout ,
183- )
184-
185- # Find or create Uncategorized section
186- current_section = next (
187- (
188- s
189- for s in self .twine_file .sections
190- if s .name == "Uncategorized"
191- ),
192- None ,
193- )
194-
195- if not current_section :
196- current_section = TwineSection ("Uncategorized" )
197- self .twine_file .sections .insert (0 , current_section )
198-
199- definition = TwineDefinition (key_name )
200- current_section .definitions .append (definition )
201- self .twine_file .definitions_by_key [key_name ] = definition
202- else :
203- print (
204- f"WARNING: '{ key_name } ' not found in twine file (plural)." ,
205- file = twine .stdout ,
206- )
207- i += 2
208- continue
209-
210- # Merge plural translations
211- if lang not in definition .plural_translations :
212- definition .plural_translations [lang ] = {}
213-
214- definition .plural_translations [lang ].update (plural_hash )
215-
216- # Set base translation to 'other' if present
217- if "other" in plural_hash :
218- self .set_translation_for_key (key_name , lang , plural_hash ["other" ])
147+ if not self .match_default_lang_translation (key_name , lang , plural_hash ):
148+ self .set_translation_for_key_plural (key_name , lang , plural_hash , section_name = None )
149+
150+ # Set base translation to 'other' if present
151+ if "other" in plural_hash :
152+ self .set_translation_for_key (key_name , lang , plural_hash ["other" ], section_name = None )
219153
220154 # Set comment if requested
221155 if comment_text and self .options .get ("consume_comments" ):
@@ -227,9 +161,75 @@ def read(self, io: TextIO, lang: str):
227161
228162 i += 2
229163
164+ def extract_plural_dict (self , value_element : Element ) -> dict :
165+ """ Parse next XML structure to extract key-value pairs:
166+ <dict>
167+ <key>NSStringLocalizedFormatKey</key>
168+ <string>%#@value@</string>
169+ <key>value</key>
170+ <dict>
171+ <key>NSStringFormatSpecTypeKey</key>
172+ <string>NSStringPluralRuleType</string>
173+ <key>NSStringFormatValueTypeKey</key>
174+ <string>d</string>
175+ <key>one</key>
176+ <string>%d bookmark</string>
177+ <key>other</key>
178+ <string>%d bookmarks</string>
179+ </dict>
180+ </dict>
181+ """
182+ plural_dict = {}
183+
184+ # Find <key>value</key><dict> inside value_element
185+ value_dict = None
186+ value_children = list (value_element )
187+
188+ for j , inner_key in enumerate (value_children ):
189+ if inner_key .tag == "key" and inner_key .text == "value" :
190+ if j + 1 < len (value_children ):
191+ value_dict = value_children [j + 1 ]
192+ break
193+
194+ if value_dict is not None and value_dict .tag == "dict" :
195+ # Extract plural entries
196+ plural_children = list (value_dict )
197+ j = 0
198+
199+ while j < len (plural_children ):
200+ pkey_elem = plural_children [j ]
201+
202+ if pkey_elem .tag == "key" :
203+ pkey = pkey_elem .text
204+
205+ if pkey in TwineDefinition .PLURAL_KEYS :
206+ if j + 1 < len (plural_children ):
207+ string_elem = plural_children [j + 1 ]
208+
209+ if string_elem .tag == "string" :
210+ pvalue = string_elem .text or ""
211+ plural_dict [pkey ] = pvalue
212+
213+ j += 1
214+ return plural_dict
215+
230216 def should_include_definition (self , definition , lang : str ) -> bool :
231217 """Only include plural definitions."""
232218 return (
233219 definition .is_plural ()
234220 and definition .plural_translation_for_lang (lang ) is not None
235221 )
222+
223+ def match_default_lang_translation (self , key :str , lang :str , value :dict ) -> bool :
224+ """ Apple strings file for non-default language (es, de, fr, etc) contains
225+ default value for not translated keys. That's why in Slovenian .strings
226+ file you can find english words.
227+ If `value` matches translation from default language, it means that
228+ this string is not translated.
229+ """
230+ default_lang = self .twine_file .get_developer_language_code ()
231+ if default_lang is None :
232+ return False
233+ if default_lang == lang :
234+ return False
235+ return self .twine_file .definitions_by_key [key ].plural_translations [default_lang ] == value
0 commit comments