i18n-context-generator 0.5.0 → 0.5.1
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.
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 2109020f4ff761dcf5fdd9817499b551619dc8ee17cef9e88acc881ac5ad647f
|
|
4
|
+
data.tar.gz: 26c9e86a187bbe4f2a0523754a7ee78912d96d9e532e261143ce905106cfd3da
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: ed14b199d897b8ee27dbd793c372df01b72efc4a9b6a1122cd9fa724abb76030b93e1c967f82c02c5fafb4e104fba3b8abe10b9b1d672d95521b9b9f04d6b555
|
|
7
|
+
data.tar.gz: e13a1a8d4c17823597727717afb4f5950d0790109c35a124262989504123165a2111482fe9d27bf9baff3de930c9bc8fb6159edb1dee1ed9a8ad863beb6c346c
|
|
@@ -38,8 +38,18 @@ module I18nContextGenerator
|
|
|
38
38
|
required: %w[description ui_element tone max_length confidence ambiguity_reason],
|
|
39
39
|
properties: {
|
|
40
40
|
description: { type: 'string' },
|
|
41
|
-
ui_element: {
|
|
42
|
-
|
|
41
|
+
ui_element: {
|
|
42
|
+
anyOf: [
|
|
43
|
+
{ type: 'string', enum: UI_ELEMENTS },
|
|
44
|
+
{ type: 'null' }
|
|
45
|
+
]
|
|
46
|
+
},
|
|
47
|
+
tone: {
|
|
48
|
+
anyOf: [
|
|
49
|
+
{ type: 'string', enum: TONES },
|
|
50
|
+
{ type: 'null' }
|
|
51
|
+
]
|
|
52
|
+
},
|
|
43
53
|
max_length: { type: %w[integer null] },
|
|
44
54
|
confidence: { type: 'string', enum: CONFIDENCE_LEVELS },
|
|
45
55
|
ambiguity_reason: {
|
|
@@ -9,6 +9,25 @@ module I18nContextGenerator
|
|
|
9
9
|
# lets diff handling map revision-specific line numbers without reformatting.
|
|
10
10
|
class XcstringsDocument
|
|
11
11
|
Member = Data.define(:key, :key_start, :key_end, :value_start, :value_end, :separator)
|
|
12
|
+
BYTE = {
|
|
13
|
+
backslash: '\\'.ord,
|
|
14
|
+
close_array: ']'.ord,
|
|
15
|
+
close_object: '}'.ord,
|
|
16
|
+
colon: ':'.ord,
|
|
17
|
+
comma: ','.ord,
|
|
18
|
+
newline: "\n".ord,
|
|
19
|
+
open_array: '['.ord,
|
|
20
|
+
open_object: '{'.ord,
|
|
21
|
+
quote: '"'.ord
|
|
22
|
+
}.freeze
|
|
23
|
+
JSON_WHITESPACE_BYTES = ["\t".ord, "\n".ord, "\r".ord, ' '.ord].freeze
|
|
24
|
+
PRIMITIVE_TERMINATOR_BYTES = [
|
|
25
|
+
BYTE[:comma],
|
|
26
|
+
BYTE[:close_object],
|
|
27
|
+
BYTE[:close_array],
|
|
28
|
+
*JSON_WHITESPACE_BYTES
|
|
29
|
+
].freeze
|
|
30
|
+
private_constant :BYTE, :JSON_WHITESPACE_BYTES, :PRIMITIVE_TERMINATOR_BYTES
|
|
12
31
|
|
|
13
32
|
attr_reader :catalog
|
|
14
33
|
|
|
@@ -17,10 +36,11 @@ module I18nContextGenerator
|
|
|
17
36
|
@path = path
|
|
18
37
|
@catalog = Oj.load(content, mode: :strict)
|
|
19
38
|
@line_starts = [0]
|
|
20
|
-
content.
|
|
39
|
+
content.each_byte.with_index { |byte, index| @line_starts << (index + 1) if byte == BYTE[:newline] }
|
|
21
40
|
@root_members = object_members(skip_whitespace(0))
|
|
22
41
|
strings_member = @root_members.find { |member| member.key == 'strings' }
|
|
23
|
-
|
|
42
|
+
strings_object = strings_member && byte_at(strings_member.value_start) == BYTE[:open_object]
|
|
43
|
+
raise TypeError, 'strings must be a mapping' unless strings_object
|
|
24
44
|
|
|
25
45
|
@strings_member = strings_member
|
|
26
46
|
@entry_members = object_members(strings_member.value_start)
|
|
@@ -40,18 +60,19 @@ module I18nContextGenerator
|
|
|
40
60
|
def with_comments(comments_by_key)
|
|
41
61
|
edits = comments_by_key.filter_map do |key, comment|
|
|
42
62
|
entry_member = @entries_by_key[key]
|
|
43
|
-
next unless entry_member &&
|
|
63
|
+
next unless entry_member && byte_at(entry_member.value_start) == BYTE[:open_object]
|
|
44
64
|
next if @catalog.dig('strings', key, 'comment') == comment
|
|
45
65
|
|
|
46
66
|
comment_edit(entry_member, comment)
|
|
47
67
|
end
|
|
48
68
|
return @content if edits.empty?
|
|
49
69
|
|
|
50
|
-
|
|
70
|
+
original_encoding = @content.encoding
|
|
71
|
+
rendered = @content.b
|
|
51
72
|
edits.sort_by(&:first).reverse_each do |start_offset, end_offset, replacement|
|
|
52
|
-
rendered[start_offset...end_offset] = replacement
|
|
73
|
+
rendered[start_offset...end_offset] = replacement.b
|
|
53
74
|
end
|
|
54
|
-
rendered
|
|
75
|
+
rendered.force_encoding(original_encoding)
|
|
55
76
|
end
|
|
56
77
|
|
|
57
78
|
private
|
|
@@ -109,8 +130,9 @@ module I18nContextGenerator
|
|
|
109
130
|
end
|
|
110
131
|
|
|
111
132
|
def line_indent(offset)
|
|
112
|
-
|
|
113
|
-
|
|
133
|
+
following_line = @line_starts.bsearch_index { |line_start| line_start > offset }
|
|
134
|
+
line_start = following_line ? @line_starts.fetch(following_line - 1) : @line_starts.last
|
|
135
|
+
byte_slice(line_start, offset)
|
|
114
136
|
end
|
|
115
137
|
|
|
116
138
|
def line_number(offset)
|
|
@@ -118,18 +140,18 @@ module I18nContextGenerator
|
|
|
118
140
|
end
|
|
119
141
|
|
|
120
142
|
def object_members(object_start)
|
|
121
|
-
raise TypeError, "expected object at byte #{object_start}" unless
|
|
143
|
+
raise TypeError, "expected object at byte #{object_start}" unless byte_at(object_start) == BYTE[:open_object]
|
|
122
144
|
|
|
123
145
|
members = []
|
|
124
146
|
cursor = skip_whitespace(object_start + 1)
|
|
125
|
-
return members if
|
|
147
|
+
return members if byte_at(cursor) == BYTE[:close_object]
|
|
126
148
|
|
|
127
149
|
loop do
|
|
128
150
|
key_start = cursor
|
|
129
151
|
key_end = string_end(key_start)
|
|
130
|
-
key = JSON.parse(
|
|
152
|
+
key = JSON.parse(byte_slice(key_start, key_end))
|
|
131
153
|
cursor = skip_whitespace(key_end)
|
|
132
|
-
raise TypeError, "expected ':' at byte #{cursor}" unless
|
|
154
|
+
raise TypeError, "expected ':' at byte #{cursor}" unless byte_at(cursor) == BYTE[:colon]
|
|
133
155
|
|
|
134
156
|
cursor = skip_whitespace(cursor + 1)
|
|
135
157
|
value_start = cursor
|
|
@@ -140,12 +162,12 @@ module I18nContextGenerator
|
|
|
140
162
|
key_end: key_end,
|
|
141
163
|
value_start: value_start,
|
|
142
164
|
value_end: value_end,
|
|
143
|
-
separator:
|
|
165
|
+
separator: byte_slice(key_end, value_start)
|
|
144
166
|
)
|
|
145
167
|
cursor = skip_whitespace(value_end)
|
|
146
|
-
break if
|
|
168
|
+
break if byte_at(cursor) == BYTE[:close_object]
|
|
147
169
|
|
|
148
|
-
raise TypeError, "expected ',' at byte #{cursor}" unless
|
|
170
|
+
raise TypeError, "expected ',' at byte #{cursor}" unless byte_at(cursor) == BYTE[:comma]
|
|
149
171
|
|
|
150
172
|
cursor = skip_whitespace(cursor + 1)
|
|
151
173
|
end
|
|
@@ -154,13 +176,13 @@ module I18nContextGenerator
|
|
|
154
176
|
end
|
|
155
177
|
|
|
156
178
|
def value_end(start_offset)
|
|
157
|
-
case
|
|
158
|
-
when
|
|
179
|
+
case byte_at(start_offset)
|
|
180
|
+
when BYTE[:quote]
|
|
159
181
|
string_end(start_offset)
|
|
160
|
-
when
|
|
161
|
-
collection_end(start_offset,
|
|
162
|
-
when
|
|
163
|
-
collection_end(start_offset,
|
|
182
|
+
when BYTE[:open_object]
|
|
183
|
+
collection_end(start_offset, BYTE[:open_object], BYTE[:close_object])
|
|
184
|
+
when BYTE[:open_array]
|
|
185
|
+
collection_end(start_offset, BYTE[:open_array], BYTE[:close_array])
|
|
164
186
|
else
|
|
165
187
|
primitive_end(start_offset)
|
|
166
188
|
end
|
|
@@ -169,13 +191,13 @@ module I18nContextGenerator
|
|
|
169
191
|
def string_end(start_offset)
|
|
170
192
|
cursor = start_offset + 1
|
|
171
193
|
escaped = false
|
|
172
|
-
while cursor < @content.
|
|
173
|
-
|
|
194
|
+
while cursor < @content.bytesize
|
|
195
|
+
byte = byte_at(cursor)
|
|
174
196
|
if escaped
|
|
175
197
|
escaped = false
|
|
176
|
-
elsif
|
|
198
|
+
elsif byte == BYTE[:backslash]
|
|
177
199
|
escaped = true
|
|
178
|
-
elsif
|
|
200
|
+
elsif byte == BYTE[:quote]
|
|
179
201
|
return cursor + 1
|
|
180
202
|
end
|
|
181
203
|
cursor += 1
|
|
@@ -186,10 +208,10 @@ module I18nContextGenerator
|
|
|
186
208
|
def collection_end(start_offset, opening, closing)
|
|
187
209
|
depth = 0
|
|
188
210
|
cursor = start_offset
|
|
189
|
-
while cursor < @content.
|
|
190
|
-
|
|
191
|
-
case
|
|
192
|
-
when
|
|
211
|
+
while cursor < @content.bytesize
|
|
212
|
+
byte = byte_at(cursor)
|
|
213
|
+
case byte
|
|
214
|
+
when BYTE[:quote]
|
|
193
215
|
cursor = string_end(cursor)
|
|
194
216
|
next
|
|
195
217
|
when opening
|
|
@@ -205,14 +227,22 @@ module I18nContextGenerator
|
|
|
205
227
|
|
|
206
228
|
def primitive_end(start_offset)
|
|
207
229
|
cursor = start_offset
|
|
208
|
-
cursor += 1 while cursor < @content.
|
|
230
|
+
cursor += 1 while cursor < @content.bytesize && !PRIMITIVE_TERMINATOR_BYTES.include?(byte_at(cursor))
|
|
209
231
|
cursor
|
|
210
232
|
end
|
|
211
233
|
|
|
212
234
|
def skip_whitespace(offset)
|
|
213
235
|
cursor = offset
|
|
214
|
-
cursor += 1 while cursor < @content.
|
|
236
|
+
cursor += 1 while cursor < @content.bytesize && JSON_WHITESPACE_BYTES.include?(byte_at(cursor))
|
|
215
237
|
cursor
|
|
216
238
|
end
|
|
239
|
+
|
|
240
|
+
def byte_at(offset)
|
|
241
|
+
@content.getbyte(offset)
|
|
242
|
+
end
|
|
243
|
+
|
|
244
|
+
def byte_slice(start_offset, end_offset)
|
|
245
|
+
@content.byteslice(start_offset...end_offset)
|
|
246
|
+
end
|
|
217
247
|
end
|
|
218
248
|
end
|
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: i18n-context-generator
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.5.
|
|
4
|
+
version: 0.5.1
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Automattic
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: exe
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2026-07-
|
|
11
|
+
date: 2026-07-24 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: concurrent-ruby
|