jekyll-l10n 1.5.0 → 1.6.0
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 +4 -4
- data/lib/jekyll-l10n/instrumentation.rb +306 -0
- data/lib/jekyll-l10n/po_file/reader.rb +2 -0
- data/lib/jekyll-l10n.rb +3 -0
- metadata +17 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: a3b778612621ba8532ed88057a833cebfb615d4e7dfeb09befb9f58249896a5b
|
|
4
|
+
data.tar.gz: 399997d1222baaeeea243b1cc61638b169e797d72da8cc543c3949f8b63b5f4b
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 122a6388d40c31898704cf3f91b96ef226d30fd9a8b706e7a68d0639bab07042d2707809ecd5076b2d84c79384824cca7964582bd4f931f4a26c95cedd4c567f
|
|
7
|
+
data.tar.gz: 6de61435ff7b179e62e1c7b3dbf705f6a8059a8b5b3f7158f9e2542145ddf3f391493b0a3e2dcff729c142d500658a0ce98815f514449aa27edfbcde4fe73566
|
|
@@ -0,0 +1,306 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Jekyll
|
|
4
|
+
module L10n
|
|
5
|
+
# OpenTelemetry instrumentation facade for jekyll-l10n.
|
|
6
|
+
#
|
|
7
|
+
# All tracing is configured centrally in TRACED_METHODS — no span code lives
|
|
8
|
+
# in business logic classes. To add a span: append one entry. To rename a
|
|
9
|
+
# method: update the one entry. When a method is removed from its class the
|
|
10
|
+
# stale entry raises NoMethodError in tests, signalling the entry to delete.
|
|
11
|
+
#
|
|
12
|
+
# Requires opentelemetry-api at runtime; falls back to a no-op if absent.
|
|
13
|
+
# Users opt in to real tracing by adding opentelemetry-sdk and
|
|
14
|
+
# opentelemetry-exporter-otlp to their site Gemfile and exporting:
|
|
15
|
+
#
|
|
16
|
+
# OTEL_SERVICE_NAME=jekyll-l10n
|
|
17
|
+
# OTEL_EXPORTER_OTLP_ENDPOINT=http://localhost:4318
|
|
18
|
+
module Instrumentation
|
|
19
|
+
TRACER_NAME = 'jekyll-l10n'
|
|
20
|
+
|
|
21
|
+
# Central instrumentation registry.
|
|
22
|
+
#
|
|
23
|
+
# Each row: [class_name, method_type, method_name, span_name, attribute_proc]
|
|
24
|
+
#
|
|
25
|
+
# method_type:
|
|
26
|
+
# :instance — public instance method
|
|
27
|
+
# :private — private instance method (visibility is preserved on the wrapper)
|
|
28
|
+
# :class — class / module method (prepended on the singleton class)
|
|
29
|
+
#
|
|
30
|
+
# attribute_proc: ->(span, this, args, result) or nil
|
|
31
|
+
# span — OTel span (or NoopSpan); call span.set_attribute after super returns
|
|
32
|
+
# this — receiver object (instance or nil for class methods)
|
|
33
|
+
# args — positional args array as passed to the method
|
|
34
|
+
# result — return value of the original method
|
|
35
|
+
# :nocov: — attribute procs are configuration data; coverage comes from integration tests
|
|
36
|
+
|
|
37
|
+
# Helpers for common attribute patterns used inside TRACED_METHODS procs.
|
|
38
|
+
def self.hash_val(hash, key)
|
|
39
|
+
hash.is_a?(Hash) ? hash[key].to_i : 0
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def self.hash_size(hash)
|
|
43
|
+
hash.is_a?(Hash) ? hash.size : 0
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def self.array_size(val)
|
|
47
|
+
val.is_a?(Array) ? val.size : 0
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
TRACED_METHODS = [
|
|
51
|
+
# ── Jekyll integration ────────────────────────────────────────────────
|
|
52
|
+
['Jekyll::L10n::Generator', :instance, :generate, 'l10n.generate',
|
|
53
|
+
lambda { |span, _this, args, _result|
|
|
54
|
+
span.set_attribute('l10n.site_page_count', args[0]&.pages&.size || 0)
|
|
55
|
+
}],
|
|
56
|
+
|
|
57
|
+
['Jekyll::L10n::PostWriteProcessor', :instance, :process_localizations,
|
|
58
|
+
'l10n.post_write', nil],
|
|
59
|
+
|
|
60
|
+
# translate is the post_render hook entry point — one span per localized page
|
|
61
|
+
['Jekyll::L10n::Translator', :instance, :translate, 'l10n.translate_render',
|
|
62
|
+
lambda { |span, this, _args, _result|
|
|
63
|
+
span.set_attribute('l10n.locale', this.page.data['locale'].to_s)
|
|
64
|
+
span.set_attribute('l10n.page_url', this.page.url.to_s)
|
|
65
|
+
}],
|
|
66
|
+
|
|
67
|
+
# ── Extraction pipeline ───────────────────────────────────────────────
|
|
68
|
+
['Jekyll::L10n::Extractor', :instance, :extract_site, 'l10n.extract_site',
|
|
69
|
+
lambda { |span, _this, _args, result|
|
|
70
|
+
span.set_attribute('l10n.file_count', Instrumentation.hash_val(result, :files_processed))
|
|
71
|
+
}],
|
|
72
|
+
|
|
73
|
+
# process_file is the per-page body called from the html_files loop
|
|
74
|
+
['Jekyll::L10n::Extractor', :private, :process_file, 'l10n.extract_page',
|
|
75
|
+
lambda { |span, _this, args, result|
|
|
76
|
+
span.set_attribute('l10n.page_path', args[0].to_s)
|
|
77
|
+
span.set_attribute('l10n.strings_extracted',
|
|
78
|
+
Instrumentation.hash_val(result, :strings_extracted))
|
|
79
|
+
}],
|
|
80
|
+
|
|
81
|
+
['Jekyll::L10n::HtmlStringExtractor', :instance, :extract, 'l10n.html_extract',
|
|
82
|
+
lambda { |span, _this, args, result|
|
|
83
|
+
span.set_attribute('l10n.html_size_bytes', args[0].bytesize)
|
|
84
|
+
span.set_attribute('l10n.extracted_count', Instrumentation.array_size(result))
|
|
85
|
+
}],
|
|
86
|
+
|
|
87
|
+
['Jekyll::L10n::ExtractionResultSaver', :instance, :save_results, 'l10n.po_file_write',
|
|
88
|
+
lambda { |span, _this, args, result|
|
|
89
|
+
span.set_attribute('l10n.page_path', args[2].to_s)
|
|
90
|
+
span.set_attribute('l10n.entry_count', Instrumentation.array_size(args[1]))
|
|
91
|
+
span.set_attribute('l10n.po_files_created',
|
|
92
|
+
Instrumentation.hash_val(result, :po_files_created))
|
|
93
|
+
}],
|
|
94
|
+
|
|
95
|
+
['Jekyll::L10n::CompendiumMerger', :instance, :merge_compendia,
|
|
96
|
+
'l10n.compendium_merge', nil],
|
|
97
|
+
|
|
98
|
+
['Jekyll::L10n::CompendiumTranslator', :instance, :translate_compendia,
|
|
99
|
+
'l10n.translate_compendia',
|
|
100
|
+
lambda { |span, _this, args, _result|
|
|
101
|
+
config = args[0]
|
|
102
|
+
span.set_attribute('l10n.locale_count', config.locales.size) if config.respond_to?(:locales)
|
|
103
|
+
}],
|
|
104
|
+
|
|
105
|
+
# ── Translation pipeline ──────────────────────────────────────────────
|
|
106
|
+
['Jekyll::L10n::PostWriteHtmlReprocessor', :instance, :reprocess_localized_pages,
|
|
107
|
+
'l10n.reprocess_localized_pages', nil],
|
|
108
|
+
|
|
109
|
+
# translate_html_file is the per-page body called from the localized_files loop
|
|
110
|
+
['Jekyll::L10n::PostWriteHtmlReprocessor', :private, :translate_html_file,
|
|
111
|
+
'l10n.translate_page',
|
|
112
|
+
lambda { |span, _this, args, _result|
|
|
113
|
+
span.set_attribute('l10n.page_path', args[0].to_s)
|
|
114
|
+
span.set_attribute('l10n.locale', args[1].to_s)
|
|
115
|
+
}],
|
|
116
|
+
|
|
117
|
+
['Jekyll::L10n::PageTranslationLoader', :class, :load, 'l10n.translation_load',
|
|
118
|
+
lambda { |span, _this, args, result|
|
|
119
|
+
span.set_attribute('l10n.locale', args[1].to_s)
|
|
120
|
+
span.set_attribute('l10n.page_path', args[2].to_s)
|
|
121
|
+
span.set_attribute('l10n.entry_count', Instrumentation.hash_size(result))
|
|
122
|
+
}],
|
|
123
|
+
|
|
124
|
+
['Jekyll::L10n::HtmlTranslator', :instance, :translate, 'l10n.dom_translate',
|
|
125
|
+
lambda { |span, this, args, _result|
|
|
126
|
+
span.set_attribute('l10n.locale', (args[2] || 'en').to_s)
|
|
127
|
+
span.set_attribute('l10n.fallback_mode', this.fallback_mode.to_s)
|
|
128
|
+
}],
|
|
129
|
+
|
|
130
|
+
['Jekyll::L10n::LibreTranslator', :private, :make_api_request,
|
|
131
|
+
'l10n.libretranslate_batch',
|
|
132
|
+
lambda { |span, _this, args, _result|
|
|
133
|
+
span.set_attribute('l10n.locale', args[1].to_s)
|
|
134
|
+
span.set_attribute('l10n.batch_size', args[0].is_a?(Array) ? args[0].size : 1)
|
|
135
|
+
}],
|
|
136
|
+
|
|
137
|
+
# ── Utilities ─────────────────────────────────────────────────────────
|
|
138
|
+
['Jekyll::L10n::HtmlParser', :class, :parse_document, 'l10n.html_parse',
|
|
139
|
+
lambda { |span, _this, args, _result|
|
|
140
|
+
span.set_attribute('l10n.html_size_bytes', args[0].bytesize)
|
|
141
|
+
}],
|
|
142
|
+
|
|
143
|
+
['Jekyll::L10n::UrlTransformer', :class, :transform_document, 'l10n.url_transform',
|
|
144
|
+
lambda { |span, _this, args, _result|
|
|
145
|
+
span.set_attribute('l10n.locale', args[1].to_s)
|
|
146
|
+
doc = args[0]
|
|
147
|
+
span.set_attribute('l10n.href_count',
|
|
148
|
+
doc.respond_to?(:css) ? doc.css('a[href]').size : 0)
|
|
149
|
+
}],
|
|
150
|
+
|
|
151
|
+
['Jekyll::L10n::ExternalLinkIconPreserver', :class, :preserve,
|
|
152
|
+
'l10n.icon_preserve', nil],
|
|
153
|
+
|
|
154
|
+
# ── PO file operations ────────────────────────────────────────────────
|
|
155
|
+
['Jekyll::L10n::PoFileReader', :instance, :parse_for_translation,
|
|
156
|
+
'l10n.po_file_read',
|
|
157
|
+
lambda { |span, this, _args, result|
|
|
158
|
+
span.set_attribute('l10n.file_path', this.po_path.to_s)
|
|
159
|
+
span.set_attribute('l10n.entry_count', Instrumentation.hash_size(result))
|
|
160
|
+
}],
|
|
161
|
+
|
|
162
|
+
['Jekyll::L10n::PoFileMerger', :class, :merge_for_locale, 'l10n.po_merge',
|
|
163
|
+
lambda { |span, _this, args, result|
|
|
164
|
+
span.set_attribute('l10n.locale', args[2].to_s)
|
|
165
|
+
span.set_attribute('l10n.merged_count', Instrumentation.hash_size(result))
|
|
166
|
+
}]
|
|
167
|
+
].freeze
|
|
168
|
+
# :nocov:
|
|
169
|
+
|
|
170
|
+
# Returns the active OTel tracer, or a no-op tracer if opentelemetry-api is absent.
|
|
171
|
+
def self.tracer
|
|
172
|
+
@tracer ||=
|
|
173
|
+
if defined?(OpenTelemetry)
|
|
174
|
+
# :nocov:
|
|
175
|
+
OpenTelemetry.tracer_provider.tracer(TRACER_NAME, Jekyll::L10n::VERSION)
|
|
176
|
+
# :nocov:
|
|
177
|
+
else
|
|
178
|
+
NoopTracer.new
|
|
179
|
+
end
|
|
180
|
+
end
|
|
181
|
+
|
|
182
|
+
# Wraps a block in an OTel span.
|
|
183
|
+
#
|
|
184
|
+
# @param span_name [String] Dot-separated span name (e.g. 'l10n.extract_page')
|
|
185
|
+
# @param attributes [Hash] Initial span attributes
|
|
186
|
+
# @yieldparam span [OpenTelemetry::Trace::Span, NoopSpan] Active span
|
|
187
|
+
# @return [Object] The return value of the block
|
|
188
|
+
def self.instrument(span_name, attributes: {}, &block)
|
|
189
|
+
tracer.in_span(span_name, attributes: attributes, &block)
|
|
190
|
+
end
|
|
191
|
+
|
|
192
|
+
# Resets the cached tracer and installation flag. Call in tests after changing OTel configuration.
|
|
193
|
+
def self.reset!
|
|
194
|
+
@tracer = nil
|
|
195
|
+
@installed = false
|
|
196
|
+
end
|
|
197
|
+
|
|
198
|
+
# Returns true when OTel is requested via standard environment variables.
|
|
199
|
+
#
|
|
200
|
+
# install! guards on this so the prepend wrappers are only applied when
|
|
201
|
+
# a real exporter is configured. In CI and local tests (no OTel env vars)
|
|
202
|
+
# business logic classes are untouched, keeping allow_any_instance_of stubs
|
|
203
|
+
# and other RSpec mechanics fully functional.
|
|
204
|
+
def self.enabled?
|
|
205
|
+
ENV.key?('OTEL_EXPORTER_OTLP_ENDPOINT') || ENV.key?('OTEL_SERVICE_NAME')
|
|
206
|
+
end
|
|
207
|
+
|
|
208
|
+
# Installs wrappers on all classes listed in TRACED_METHODS using Module#prepend.
|
|
209
|
+
#
|
|
210
|
+
# Called once at plugin load time (end of jekyll-l10n.rb, after all requires),
|
|
211
|
+
# but only when enabled? returns true. Use OTEL_EXPORTER_OTLP_ENDPOINT or
|
|
212
|
+
# OTEL_SERVICE_NAME to activate tracing.
|
|
213
|
+
def self.install!
|
|
214
|
+
return if @installed
|
|
215
|
+
|
|
216
|
+
@installed = true
|
|
217
|
+
setup_sdk!
|
|
218
|
+
TRACED_METHODS.group_by { |e| e[0] }.each do |class_name, entries|
|
|
219
|
+
prepend_wrappers(resolve_class(class_name), entries)
|
|
220
|
+
end
|
|
221
|
+
end
|
|
222
|
+
|
|
223
|
+
# Configures the OTel SDK from standard env vars if the SDK gem is available.
|
|
224
|
+
#
|
|
225
|
+
# Called from install! so that spans are exported to the configured backend
|
|
226
|
+
# without requiring sites to add a separate initializer. Silently no-ops when
|
|
227
|
+
# opentelemetry-sdk or opentelemetry-exporter-otlp is not installed.
|
|
228
|
+
#
|
|
229
|
+
# SimpleSpanProcessor is used instead of the SDK default (BatchSpanProcessor)
|
|
230
|
+
# because Jekyll is a short-lived CLI process. BatchSpanProcessor exports on a
|
|
231
|
+
# 5-second schedule; outer spans that close last (post_write, generate) are
|
|
232
|
+
# silently dropped when the process exits before the next flush. Simple exports
|
|
233
|
+
# each span synchronously the moment it closes, guaranteeing no span loss.
|
|
234
|
+
def self.setup_sdk!
|
|
235
|
+
# :nocov:
|
|
236
|
+
require 'opentelemetry/sdk'
|
|
237
|
+
require 'opentelemetry/exporter/otlp'
|
|
238
|
+
exporter = OpenTelemetry::Exporter::OTLP::Exporter.new
|
|
239
|
+
processor = OpenTelemetry::SDK::Trace::Export::SimpleSpanProcessor.new(exporter)
|
|
240
|
+
OpenTelemetry::SDK.configure do |c|
|
|
241
|
+
c.add_span_processor(processor)
|
|
242
|
+
end
|
|
243
|
+
rescue LoadError
|
|
244
|
+
nil
|
|
245
|
+
# :nocov:
|
|
246
|
+
end
|
|
247
|
+
|
|
248
|
+
# Resolves a dot-separated class name to a constant; returns nil on NameError.
|
|
249
|
+
def self.resolve_class(name)
|
|
250
|
+
name.split('::').reduce(Object) { |m, c| m.const_get(c) }
|
|
251
|
+
rescue NameError
|
|
252
|
+
nil
|
|
253
|
+
end
|
|
254
|
+
|
|
255
|
+
# Applies instance and class-method wrappers to klass; skips if klass is nil.
|
|
256
|
+
def self.prepend_wrappers(klass, entries)
|
|
257
|
+
return unless klass
|
|
258
|
+
|
|
259
|
+
class_entries, instance_entries = entries.partition { |e| e[1] == :class }
|
|
260
|
+
klass.prepend(build_wrapper_module(instance_entries)) unless instance_entries.empty?
|
|
261
|
+
klass.singleton_class.prepend(build_wrapper_module(class_entries)) unless class_entries.empty?
|
|
262
|
+
end
|
|
263
|
+
|
|
264
|
+
# Builds an anonymous module that wraps each listed method in a span.
|
|
265
|
+
def self.build_wrapper_module(entries)
|
|
266
|
+
Module.new do
|
|
267
|
+
entries.each do |_class_name, method_type, method_name, span_name, attr_proc|
|
|
268
|
+
define_method(method_name) do |*args, **kwargs, &blk|
|
|
269
|
+
Instrumentation.instrument(span_name) do |span|
|
|
270
|
+
result = super(*args, **kwargs, &blk)
|
|
271
|
+
attr_proc&.call(span, self, args, result)
|
|
272
|
+
result
|
|
273
|
+
rescue StandardError => e
|
|
274
|
+
span.record_exception(e)
|
|
275
|
+
raise
|
|
276
|
+
end
|
|
277
|
+
end
|
|
278
|
+
private method_name if method_type == :private
|
|
279
|
+
end
|
|
280
|
+
end
|
|
281
|
+
end
|
|
282
|
+
|
|
283
|
+
private_class_method :build_wrapper_module, :prepend_wrappers, :setup_sdk!
|
|
284
|
+
|
|
285
|
+
# No-op tracer used when opentelemetry-api is not loaded.
|
|
286
|
+
class NoopTracer
|
|
287
|
+
def in_span(_name, **_opts)
|
|
288
|
+
yield NoopSpan.new
|
|
289
|
+
end
|
|
290
|
+
end
|
|
291
|
+
|
|
292
|
+
# No-op span that silently accepts all attribute and event calls.
|
|
293
|
+
class NoopSpan
|
|
294
|
+
def set_attribute(*)
|
|
295
|
+
self
|
|
296
|
+
end
|
|
297
|
+
|
|
298
|
+
def record_exception(*)
|
|
299
|
+
self
|
|
300
|
+
end
|
|
301
|
+
|
|
302
|
+
def status=(*); end
|
|
303
|
+
end
|
|
304
|
+
end
|
|
305
|
+
end
|
|
306
|
+
end
|
|
@@ -36,6 +36,8 @@ module Jekyll
|
|
|
36
36
|
MSGSTR_PATTERN = /^msgstr ['"](.*)['"] *$/.freeze unless const_defined?(:MSGSTR_PATTERN)
|
|
37
37
|
NO_REFERENCE = nil unless const_defined?(:NO_REFERENCE)
|
|
38
38
|
|
|
39
|
+
attr_reader :po_path
|
|
40
|
+
|
|
39
41
|
# Initialize a new PoFileReader.
|
|
40
42
|
#
|
|
41
43
|
# Accepts either a file path (if file exists) or inline PO content. Determines
|
data/lib/jekyll-l10n.rb
CHANGED
|
@@ -170,6 +170,7 @@ require 'gettext/po'
|
|
|
170
170
|
# @see Jekyll::L10n::UrlFilter for liquid filters
|
|
171
171
|
|
|
172
172
|
require_relative 'jekyll-l10n/version'
|
|
173
|
+
require_relative 'jekyll-l10n/instrumentation'
|
|
173
174
|
require_relative 'jekyll-l10n/constants'
|
|
174
175
|
require_relative 'jekyll-l10n/errors'
|
|
175
176
|
require_relative 'jekyll-l10n/po_file/reader'
|
|
@@ -271,3 +272,5 @@ Jekyll::Hooks.register :site, :post_write, priority: :high do |site|
|
|
|
271
272
|
end
|
|
272
273
|
|
|
273
274
|
Liquid::Template.register_filter(Jekyll::L10n::UrlFilter)
|
|
275
|
+
|
|
276
|
+
Jekyll::L10n::Instrumentation.install! if Jekyll::L10n::Instrumentation.enabled?
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: jekyll-l10n
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 1.
|
|
4
|
+
version: 1.6.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Alain Reguera Delgado
|
|
@@ -69,6 +69,20 @@ dependencies:
|
|
|
69
69
|
- - "<"
|
|
70
70
|
- !ruby/object:Gem::Version
|
|
71
71
|
version: '2.0'
|
|
72
|
+
- !ruby/object:Gem::Dependency
|
|
73
|
+
name: opentelemetry-api
|
|
74
|
+
requirement: !ruby/object:Gem::Requirement
|
|
75
|
+
requirements:
|
|
76
|
+
- - "~>"
|
|
77
|
+
- !ruby/object:Gem::Version
|
|
78
|
+
version: '1.4'
|
|
79
|
+
type: :runtime
|
|
80
|
+
prerelease: false
|
|
81
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
82
|
+
requirements:
|
|
83
|
+
- - "~>"
|
|
84
|
+
- !ruby/object:Gem::Version
|
|
85
|
+
version: '1.4'
|
|
72
86
|
- !ruby/object:Gem::Dependency
|
|
73
87
|
name: bundler
|
|
74
88
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -230,6 +244,7 @@ files:
|
|
|
230
244
|
- lib/jekyll-l10n/extraction/html_string_extractor.rb
|
|
231
245
|
- lib/jekyll-l10n/extraction/logger.rb
|
|
232
246
|
- lib/jekyll-l10n/extraction/result_saver.rb
|
|
247
|
+
- lib/jekyll-l10n/instrumentation.rb
|
|
233
248
|
- lib/jekyll-l10n/jekyll/generator.rb
|
|
234
249
|
- lib/jekyll-l10n/jekyll/localized_page.rb
|
|
235
250
|
- lib/jekyll-l10n/jekyll/post_write_html_reprocessor.rb
|
|
@@ -289,7 +304,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
289
304
|
- !ruby/object:Gem::Version
|
|
290
305
|
version: '0'
|
|
291
306
|
requirements: []
|
|
292
|
-
rubygems_version:
|
|
307
|
+
rubygems_version: 4.0.10
|
|
293
308
|
specification_version: 4
|
|
294
309
|
summary: Jekyll plugin for jekyll site localization.
|
|
295
310
|
test_files: []
|