i18n-context-generator 0.4.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 +4 -4
- data/README.md +163 -26
- data/lib/i18n_context_generator/android_resource.rb +209 -0
- data/lib/i18n_context_generator/apple_string_literal.rb +28 -0
- data/lib/i18n_context_generator/cache.rb +40 -8
- data/lib/i18n_context_generator/changed_location.rb +55 -0
- data/lib/i18n_context_generator/cli.rb +163 -76
- data/lib/i18n_context_generator/config/cli_values.rb +33 -0
- data/lib/i18n_context_generator/config/defaults.rb +35 -0
- data/lib/i18n_context_generator/config/schema.rb +225 -0
- data/lib/i18n_context_generator/config/serialization.rb +64 -0
- data/lib/i18n_context_generator/config/validation.rb +249 -0
- data/lib/i18n_context_generator/config.rb +293 -102
- data/lib/i18n_context_generator/context_extractor/cache_identity.rb +65 -0
- data/lib/i18n_context_generator/context_extractor/extraction_result.rb +46 -0
- data/lib/i18n_context_generator/context_extractor/run_logging.rb +45 -6
- data/lib/i18n_context_generator/context_extractor/source_entries.rb +103 -0
- data/lib/i18n_context_generator/context_extractor/source_filters.rb +51 -5
- data/lib/i18n_context_generator/context_extractor/translation_filters.rb +91 -0
- data/lib/i18n_context_generator/context_extractor/workflow.rb +118 -0
- data/lib/i18n_context_generator/context_extractor.rb +207 -216
- data/lib/i18n_context_generator/file_classifier.rb +72 -0
- data/lib/i18n_context_generator/generated_comment.rb +32 -0
- data/lib/i18n_context_generator/git_diff/xml_changes.rb +235 -0
- data/lib/i18n_context_generator/git_diff.rb +224 -95
- data/lib/i18n_context_generator/llm/anthropic.rb +65 -38
- data/lib/i18n_context_generator/llm/client.rb +304 -92
- data/lib/i18n_context_generator/llm/openai.rb +92 -51
- data/lib/i18n_context_generator/llm/openai_compatible.rb +20 -0
- data/lib/i18n_context_generator/llm/prompt_evidence.rb +47 -0
- data/lib/i18n_context_generator/llm/request_policy.rb +147 -0
- data/lib/i18n_context_generator/localization_syntax.rb +246 -0
- data/lib/i18n_context_generator/parsers/android_xml_parser.rb +47 -8
- data/lib/i18n_context_generator/parsers/base.rb +7 -4
- data/lib/i18n_context_generator/parsers/json_parser.rb +4 -0
- data/lib/i18n_context_generator/parsers/xcstrings_parser.rb +79 -0
- data/lib/i18n_context_generator/parsers/yaml_parser.rb +20 -7
- data/lib/i18n_context_generator/path_policy.rb +107 -0
- data/lib/i18n_context_generator/platform_validator.rb +24 -50
- data/lib/i18n_context_generator/run_metrics.rb +47 -0
- data/lib/i18n_context_generator/searcher/comment_masking.rb +115 -0
- data/lib/i18n_context_generator/searcher/match_filtering.rb +52 -0
- data/lib/i18n_context_generator/searcher/source_discovery.rb +109 -64
- data/lib/i18n_context_generator/searcher.rb +61 -205
- data/lib/i18n_context_generator/supplemental_context.rb +77 -0
- data/lib/i18n_context_generator/translation_comment_index.rb +121 -0
- data/lib/i18n_context_generator/version.rb +1 -1
- data/lib/i18n_context_generator/writers/android_xml_writer.rb +48 -18
- data/lib/i18n_context_generator/writers/atomic_file.rb +37 -0
- data/lib/i18n_context_generator/writers/csv_writer.rb +43 -18
- data/lib/i18n_context_generator/writers/helpers.rb +7 -29
- data/lib/i18n_context_generator/writers/json_writer.rb +31 -6
- data/lib/i18n_context_generator/writers/preview.rb +80 -0
- data/lib/i18n_context_generator/writers/result_serialization.rb +30 -0
- data/lib/i18n_context_generator/writers/strings_writer.rb +23 -12
- data/lib/i18n_context_generator/writers/swift_writer.rb +16 -54
- data/lib/i18n_context_generator/writers/xcstrings_writer.rb +57 -0
- data/lib/i18n_context_generator/xcstrings_document.rb +248 -0
- data/lib/i18n_context_generator/xml_scanner.rb +38 -0
- data/lib/i18n_context_generator.rb +20 -4
- metadata +59 -12
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module I18nContextGenerator
|
|
4
|
+
# A line in a Git diff, including the revision side needed by review clients.
|
|
5
|
+
#
|
|
6
|
+
# `fallback_line` identifies a corresponding line in the head revision. It is
|
|
7
|
+
# useful when a removed translator comment needs a right-side suggestion anchor.
|
|
8
|
+
ChangedLocation = Data.define(:file, :line, :side, :fallback_line) do
|
|
9
|
+
def initialize(file:, line:, side: :right, fallback_line: nil)
|
|
10
|
+
normalized_side = side.to_s.downcase.to_sym
|
|
11
|
+
raise ArgumentError, "Unsupported diff side: #{side}" unless %i[left right].include?(normalized_side)
|
|
12
|
+
|
|
13
|
+
super(
|
|
14
|
+
file: file.to_s,
|
|
15
|
+
line: Integer(line),
|
|
16
|
+
side: normalized_side,
|
|
17
|
+
fallback_line: fallback_line && Integer(fallback_line)
|
|
18
|
+
)
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def self.parse(value)
|
|
22
|
+
return value if value.is_a?(self)
|
|
23
|
+
|
|
24
|
+
match = /\A(.+):(\d+)\z/.match(value.to_s)
|
|
25
|
+
raise ArgumentError, "Invalid changed location: #{value.inspect}" unless match
|
|
26
|
+
|
|
27
|
+
new(file: match[1], line: match[2], side: :right)
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def left?
|
|
31
|
+
side == :left
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def right?
|
|
35
|
+
side == :right
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def review_side
|
|
39
|
+
side.to_s.upcase
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def to_s
|
|
43
|
+
"#{file}:#{line}"
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def to_h
|
|
47
|
+
{
|
|
48
|
+
file: file,
|
|
49
|
+
line: line,
|
|
50
|
+
side: side,
|
|
51
|
+
fallback_line: fallback_line
|
|
52
|
+
}
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
end
|
|
@@ -1,10 +1,39 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
require 'thor'
|
|
4
|
+
require_relative 'config'
|
|
4
5
|
|
|
5
6
|
module I18nContextGenerator
|
|
7
|
+
# Configuration inspection commands kept separate from extraction options.
|
|
8
|
+
class ConfigCommand < Thor
|
|
9
|
+
def self.exit_on_failure?
|
|
10
|
+
true
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
desc 'validate [PATH]', 'Validate a client configuration file'
|
|
14
|
+
def validate(path = '.i18n-context-generator.yml')
|
|
15
|
+
Config.from_file(path).validate!
|
|
16
|
+
say "Configuration is valid (schema version #{Config::Schema::VERSION}): #{path}"
|
|
17
|
+
rescue I18nContextGenerator::Error => e
|
|
18
|
+
warn "Error: #{e.message}"
|
|
19
|
+
exit 1
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
|
|
6
23
|
# Thor-based CLI entry point for the i18n-context-generator command.
|
|
7
24
|
class CLI < Thor
|
|
25
|
+
register ConfigCommand, 'config', 'config SUBCOMMAND', 'Inspect or validate configuration'
|
|
26
|
+
|
|
27
|
+
def self.extraction_options
|
|
28
|
+
Config::Schema.cli_definitions.each do |definition|
|
|
29
|
+
option definition.cli_name, **definition.thor_options
|
|
30
|
+
end
|
|
31
|
+
option :translations, type: :string, hide: true,
|
|
32
|
+
desc: 'Deprecated: comma-separated translation files'
|
|
33
|
+
option :keys, type: :string, hide: true,
|
|
34
|
+
desc: 'Deprecated: comma-separated key filters'
|
|
35
|
+
end
|
|
36
|
+
|
|
8
37
|
def self.exit_on_failure?
|
|
9
38
|
true
|
|
10
39
|
end
|
|
@@ -28,51 +57,35 @@ module I18nContextGenerator
|
|
|
28
57
|
# Use config file
|
|
29
58
|
i18n-context-generator extract --config .i18n-context-generator.yml
|
|
30
59
|
DESC
|
|
31
|
-
|
|
32
|
-
option :translations, aliases: '-t', desc: 'Translation file(s), comma-separated'
|
|
33
|
-
option :source, aliases: '-s', desc: 'Source directory(ies) to search, comma-separated'
|
|
34
|
-
option :output, aliases: '-o', desc: 'Output file path (CSV written only if specified)'
|
|
35
|
-
option :format, aliases: '-f', enum: %w[csv json], desc: 'Output format (default: csv)'
|
|
36
|
-
option :provider, aliases: '-p', enum: %w[anthropic openai], desc: 'LLM provider (default: anthropic)'
|
|
37
|
-
option :model, aliases: '-m', desc: 'LLM model to use'
|
|
38
|
-
option :keys, aliases: '-k', desc: 'Filter keys (comma-separated patterns, supports * wildcard)'
|
|
39
|
-
option :discovery_mode, type: :string, enum: %w[auto translations source],
|
|
40
|
-
desc: 'How to discover entries: auto, translations, or source (default: auto)'
|
|
41
|
-
option :concurrency, type: :numeric, desc: 'Number of concurrent requests (default: 5)'
|
|
42
|
-
option :dry_run, type: :boolean, desc: 'Show what would be processed without calling LLM'
|
|
43
|
-
option :cache, type: :boolean, desc: 'Enable caching of LLM results'
|
|
44
|
-
option :write_back, type: :boolean,
|
|
45
|
-
desc: 'Write context back to source translation files (.strings, strings.xml)'
|
|
46
|
-
option :write_back_to_code, type: :boolean,
|
|
47
|
-
desc: 'Write context back to Swift source code comment: parameters'
|
|
48
|
-
option :diff_base, type: :string, desc: 'Only process keys changed since this git ref (e.g., main, origin/main)'
|
|
49
|
-
option :context_prefix, type: :string,
|
|
50
|
-
desc: 'Prefix for context comments (default: "Context: ", use empty string for none)'
|
|
51
|
-
option :context_mode, type: :string, enum: %w[replace append],
|
|
52
|
-
desc: 'How to handle existing comments: replace or append (default: replace)'
|
|
53
|
-
option :start_key, type: :string, desc: 'Start processing from this key (inclusive)'
|
|
54
|
-
option :end_key, type: :string, desc: 'Stop processing at this key (inclusive)'
|
|
55
|
-
option :include_file_paths, type: :boolean,
|
|
56
|
-
desc: 'Include full source file paths in LLM prompts (default: false)'
|
|
57
|
-
option :include_translation_comments, type: :boolean,
|
|
58
|
-
desc: 'Include translation file comments in LLM prompts (default: true)'
|
|
59
|
-
option :redact_prompts, type: :boolean,
|
|
60
|
-
desc: 'Redact likely secrets and PII from LLM prompts (default: true)'
|
|
60
|
+
extraction_options
|
|
61
61
|
|
|
62
62
|
def extract
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
63
|
+
run_workflow(nil)
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
desc 'check', 'Validate and check source usage without calling the LLM'
|
|
67
|
+
extraction_options
|
|
68
|
+
def check
|
|
69
|
+
run_workflow('check')
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
desc 'plan', 'Show selected keys and destinations without calling the LLM'
|
|
73
|
+
extraction_options
|
|
74
|
+
def plan
|
|
75
|
+
run_workflow('plan')
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
map 'preview-diff' => :preview_diff
|
|
79
|
+
desc 'preview-diff', 'Generate and preview write-back patches without modifying files'
|
|
80
|
+
extraction_options
|
|
81
|
+
def preview_diff
|
|
82
|
+
run_workflow('preview_diff')
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
desc 'apply', 'Generate context and apply the configured destinations'
|
|
86
|
+
extraction_options
|
|
87
|
+
def apply
|
|
88
|
+
run_workflow('apply')
|
|
76
89
|
end
|
|
77
90
|
|
|
78
91
|
desc 'init', 'Create a sample config file'
|
|
@@ -99,20 +112,52 @@ module I18nContextGenerator
|
|
|
99
112
|
|
|
100
113
|
private
|
|
101
114
|
|
|
115
|
+
def run_workflow(stage)
|
|
116
|
+
validate_options!
|
|
117
|
+
warn_deprecated_list_options!
|
|
118
|
+
config = Config.load(options)
|
|
119
|
+
config.merge_cli(workflow_stage: stage) if stage
|
|
120
|
+
config.validate!
|
|
121
|
+
if config.print_config
|
|
122
|
+
puts YAML.dump(config.to_h)
|
|
123
|
+
return
|
|
124
|
+
end
|
|
125
|
+
validate_destination!(config)
|
|
126
|
+
validate_api_key!(provider: config.provider, dry_run: config.dry_run) unless %w[check plan].include?(config.workflow_stage)
|
|
127
|
+
validate_diff_range!(base_ref: config.diff_base, head_ref: config.diff_head) if config.diff_base
|
|
128
|
+
extractor = ContextExtractor.new(config)
|
|
129
|
+
extractor.run
|
|
130
|
+
fail_if_extraction_errors!(extractor)
|
|
131
|
+
rescue I18nContextGenerator::Error => e
|
|
132
|
+
say_error "Error: #{e.message}"
|
|
133
|
+
exit 1
|
|
134
|
+
rescue Interrupt
|
|
135
|
+
say "\nInterrupted"
|
|
136
|
+
exit 130
|
|
137
|
+
end
|
|
138
|
+
|
|
102
139
|
def validate_options!
|
|
103
|
-
return if options[:
|
|
140
|
+
return if options[:print_config]
|
|
141
|
+
return if options[:config]
|
|
104
142
|
|
|
105
|
-
return if options[:translations]
|
|
143
|
+
return if options[:translation] || options[:translations]
|
|
106
144
|
return if options[:discovery_mode] == 'source' && options[:source]
|
|
107
145
|
|
|
108
|
-
say_error 'Error: --
|
|
146
|
+
say_error 'Error: --translation (-t) is required unless using a config file or --discovery-mode source with --source'
|
|
109
147
|
exit 1
|
|
110
148
|
end
|
|
111
149
|
|
|
150
|
+
def warn_deprecated_list_options!
|
|
151
|
+
say_error 'Warning: --translations is deprecated; repeat --translation instead.' if options[:translations]
|
|
152
|
+
say_error 'Warning: --keys is deprecated; repeat --key instead.' if options[:keys]
|
|
153
|
+
end
|
|
154
|
+
|
|
112
155
|
def validate_api_key!(provider: nil, dry_run: nil)
|
|
113
156
|
return if dry_run.nil? ? options[:dry_run] : dry_run
|
|
114
157
|
|
|
115
158
|
provider ||= options[:provider] || 'anthropic'
|
|
159
|
+
return if provider == 'openai_compatible'
|
|
160
|
+
|
|
116
161
|
env_var = case provider
|
|
117
162
|
when 'anthropic' then 'ANTHROPIC_API_KEY'
|
|
118
163
|
when 'openai' then 'OPENAI_API_KEY'
|
|
@@ -126,17 +171,30 @@ module I18nContextGenerator
|
|
|
126
171
|
exit 1
|
|
127
172
|
end
|
|
128
173
|
|
|
129
|
-
def
|
|
174
|
+
def validate_destination!(config)
|
|
175
|
+
return if %w[check plan].include?(config.workflow_stage)
|
|
176
|
+
return if config.dry_run
|
|
177
|
+
return if config.output_path || config.write_back || config.write_back_to_code
|
|
178
|
+
|
|
179
|
+
raise Error, 'A non-dry extraction requires --output, --write-back, or --write-back-to-code'
|
|
180
|
+
end
|
|
181
|
+
|
|
182
|
+
def validate_diff_range!(base_ref: options[:diff_base], head_ref: options[:diff_head] || 'HEAD')
|
|
130
183
|
unless GitDiff.available?
|
|
131
184
|
say_error 'Error: --diff-base requires a git repository'
|
|
132
185
|
exit 1
|
|
133
186
|
end
|
|
134
187
|
|
|
135
|
-
git_diff = GitDiff.new(base_ref: base_ref)
|
|
136
|
-
|
|
188
|
+
git_diff = GitDiff.new(base_ref: base_ref, head_ref: head_ref)
|
|
189
|
+
unless git_diff.base_ref_exists?
|
|
190
|
+
say_error "Error: git ref '#{base_ref}' not found"
|
|
191
|
+
say_error 'Try: origin/main, main, or a specific commit SHA'
|
|
192
|
+
exit 1
|
|
193
|
+
end
|
|
194
|
+
return if git_diff.head_ref_exists?
|
|
137
195
|
|
|
138
|
-
say_error "Error: git ref '#{
|
|
139
|
-
say_error 'Try:
|
|
196
|
+
say_error "Error: git ref '#{head_ref}' not found"
|
|
197
|
+
say_error 'Try: HEAD, a branch name, or a specific commit SHA'
|
|
140
198
|
exit 1
|
|
141
199
|
end
|
|
142
200
|
|
|
@@ -152,12 +210,16 @@ module I18nContextGenerator
|
|
|
152
210
|
end
|
|
153
211
|
|
|
154
212
|
def sample_config
|
|
213
|
+
schema = Config::Schema
|
|
214
|
+
swift_functions = schema.default(:swift_functions).map { |function| " - #{function.inspect}" }.join("\n")
|
|
215
|
+
|
|
155
216
|
<<~YAML
|
|
156
217
|
# i18n-context-generator configuration
|
|
157
218
|
# Extract translation context from mobile app source code
|
|
219
|
+
schema_version: #{schema.default(:schema_version)}
|
|
158
220
|
|
|
159
221
|
# Translation files to process
|
|
160
|
-
# Supported formats: .strings (iOS), strings.xml (Android), .json, .yml
|
|
222
|
+
# Supported formats: .strings/.xcstrings (iOS), strings.xml (Android), .json, .yml
|
|
161
223
|
translations:
|
|
162
224
|
# iOS example
|
|
163
225
|
- path: ios/MyApp/Resources/Localizable.strings
|
|
@@ -165,63 +227,88 @@ module I18nContextGenerator
|
|
|
165
227
|
# Android example
|
|
166
228
|
# - path: android/app/src/main/res/values/strings.xml
|
|
167
229
|
|
|
230
|
+
# YAML locale roots are stripped only when configured explicitly
|
|
231
|
+
# - path: config/translations.yml
|
|
232
|
+
# locale: en
|
|
233
|
+
|
|
168
234
|
# Source code directories to search
|
|
169
235
|
source:
|
|
170
236
|
paths:
|
|
171
237
|
- ios/MyApp/
|
|
172
238
|
# - android/app/src/main/java/
|
|
239
|
+
# These entries extend the built-in dependency, build, and test ignores.
|
|
173
240
|
ignore:
|
|
174
|
-
- "**/Pods/**"
|
|
175
|
-
- "**/build/**"
|
|
176
241
|
- "**/*.generated.*"
|
|
177
|
-
|
|
242
|
+
|
|
243
|
+
# Optional free-form reference material included in every LLM request.
|
|
244
|
+
context:
|
|
245
|
+
files: []
|
|
246
|
+
# - GLOSSARY.md
|
|
247
|
+
# - docs/localization-style.md
|
|
178
248
|
|
|
179
249
|
# LLM configuration
|
|
180
250
|
llm:
|
|
181
|
-
provider:
|
|
182
|
-
model:
|
|
251
|
+
provider: #{schema.default(:provider)}
|
|
252
|
+
# model: provider-specific default
|
|
253
|
+
# For provider: openai_compatible, set an explicit model and endpoint.
|
|
254
|
+
# endpoint: http://127.0.0.1:11434/v1/responses
|
|
183
255
|
# API key is read from the matching provider env var
|
|
184
256
|
# (ANTHROPIC_API_KEY or OPENAI_API_KEY)
|
|
185
257
|
|
|
186
258
|
# Processing options
|
|
187
259
|
processing:
|
|
260
|
+
# Optional explicit platform override: ios or android
|
|
261
|
+
# platform: ios
|
|
188
262
|
# Discovery mode: auto, translations, or source
|
|
189
|
-
discovery_mode:
|
|
190
|
-
concurrency:
|
|
191
|
-
context_lines:
|
|
192
|
-
max_matches_per_key:
|
|
263
|
+
discovery_mode: #{schema.default(:discovery_mode)}
|
|
264
|
+
concurrency: #{schema.default(:concurrency)}
|
|
265
|
+
context_lines: #{schema.default(:context_lines)}
|
|
266
|
+
max_matches_per_key: #{schema.default(:max_matches_per_key)}
|
|
267
|
+
# Hard character limit for each prompt; oversized context is truncated
|
|
268
|
+
max_prompt_chars: #{schema.default(:max_prompt_chars)}
|
|
269
|
+
|
|
270
|
+
# Optional local cache. Only successful results are cached.
|
|
271
|
+
cache:
|
|
272
|
+
enabled: #{schema.default(:cache_enabled)}
|
|
273
|
+
directory: #{schema.default(:cache_dir)}
|
|
274
|
+
|
|
275
|
+
# Default workflow for programmatic/config-file runs.
|
|
276
|
+
workflow:
|
|
277
|
+
stage: #{schema.default(:workflow_stage)}
|
|
193
278
|
|
|
194
279
|
# Output configuration
|
|
195
280
|
output:
|
|
196
|
-
format:
|
|
281
|
+
format: #{schema.default(:output_format)}
|
|
197
282
|
path: translation-context.csv
|
|
198
|
-
#
|
|
199
|
-
|
|
283
|
+
# Write the selected CSV/JSON format to stdout instead of a file.
|
|
284
|
+
stdout: #{schema.default(:output_stdout)}
|
|
285
|
+
# Set to true to write context comments back to translation files
|
|
286
|
+
# (.strings, .xcstrings, strings.xml)
|
|
287
|
+
write_back: #{schema.default(:write_back)}
|
|
200
288
|
# Set to true to write context back to Swift source code comment: parameters
|
|
201
|
-
write_back_to_code:
|
|
289
|
+
write_back_to_code: #{schema.default(:write_back_to_code)}
|
|
202
290
|
# Prefix for context comments (use empty string for no prefix)
|
|
203
|
-
# context_prefix:
|
|
291
|
+
# context_prefix: #{schema.default(:context_prefix).inspect}
|
|
204
292
|
# How to handle existing comments: "replace" or "append"
|
|
205
|
-
# context_mode:
|
|
293
|
+
# context_mode: #{schema.default(:context_mode)}
|
|
206
294
|
|
|
207
|
-
# Swift-specific
|
|
295
|
+
# Swift-specific extraction and write-back configuration
|
|
208
296
|
swift:
|
|
209
|
-
#
|
|
297
|
+
# Custom entries extend the built-in localization functions (defaults shown)
|
|
210
298
|
functions:
|
|
211
|
-
|
|
212
|
-
- "String(localized:"
|
|
213
|
-
- "Text("
|
|
299
|
+
#{swift_functions}
|
|
214
300
|
# Add custom functions like:
|
|
215
301
|
# - "MyLocalizedString("
|
|
216
302
|
|
|
217
303
|
# Prompt privacy controls
|
|
218
304
|
privacy:
|
|
219
305
|
# Include full source paths in prompts sent to the LLM (default: false)
|
|
220
|
-
include_file_paths:
|
|
306
|
+
include_file_paths: #{schema.default(:include_file_paths)}
|
|
221
307
|
# Include translation file comments in prompts (default: true)
|
|
222
|
-
include_translation_comments:
|
|
223
|
-
#
|
|
224
|
-
|
|
308
|
+
include_translation_comments: #{schema.default(:include_translation_comments)}
|
|
309
|
+
# Best-effort redact likely secrets, URLs, and emails before sending prompts.
|
|
310
|
+
# Source snippets still leave the machine when using a remote provider.
|
|
311
|
+
redact_prompts: #{schema.default(:redact_prompts)}
|
|
225
312
|
YAML
|
|
226
313
|
end
|
|
227
314
|
end
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module I18nContextGenerator
|
|
4
|
+
class Config
|
|
5
|
+
# Normalizes repeatable singular CLI values while retaining explicit legacy
|
|
6
|
+
# comma-list support.
|
|
7
|
+
module CliValues
|
|
8
|
+
def cli_path_list(values, legacy: nil)
|
|
9
|
+
repeatable = Array(values).filter_map do |value|
|
|
10
|
+
normalized = value.to_s.strip
|
|
11
|
+
normalized unless normalized.empty?
|
|
12
|
+
end
|
|
13
|
+
(repeatable + split_legacy_list(legacy)).uniq
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def cli_key_filters(values, legacy: nil)
|
|
17
|
+
repeatable = Array(values).filter_map do |value|
|
|
18
|
+
normalized = value.to_s.strip
|
|
19
|
+
normalized unless normalized.empty?
|
|
20
|
+
end
|
|
21
|
+
repeatable + split_legacy_list(legacy)
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
private
|
|
25
|
+
|
|
26
|
+
def split_legacy_list(value)
|
|
27
|
+
Array(value).flat_map { |item| item.to_s.split(',') }
|
|
28
|
+
.map(&:strip)
|
|
29
|
+
.reject(&:empty?)
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
end
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module I18nContextGenerator
|
|
4
|
+
class Config
|
|
5
|
+
# Built-in source exclusions and their additive merge policy.
|
|
6
|
+
module Defaults
|
|
7
|
+
def default_ignore_patterns
|
|
8
|
+
[
|
|
9
|
+
'**/node_modules/**',
|
|
10
|
+
'**/vendor/**',
|
|
11
|
+
'**/.git/**',
|
|
12
|
+
'**/build/**',
|
|
13
|
+
'**/dist/**',
|
|
14
|
+
'**/*.min.js',
|
|
15
|
+
'**/*.test.*',
|
|
16
|
+
'**/*.spec.*',
|
|
17
|
+
'**/Pods/**',
|
|
18
|
+
'**/Carthage/**',
|
|
19
|
+
'**/.build/**',
|
|
20
|
+
'**/DerivedData/**',
|
|
21
|
+
'**/*Tests.swift',
|
|
22
|
+
'**/*Tests.kt',
|
|
23
|
+
'**/*Test.java',
|
|
24
|
+
'**/*Test.kt'
|
|
25
|
+
]
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def merge_ignore_patterns(patterns)
|
|
29
|
+
return patterns unless patterns.is_a?(Array)
|
|
30
|
+
|
|
31
|
+
(default_ignore_patterns + patterns).compact.uniq
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
end
|