i18n-context-generator 0.3.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 +7 -0
- data/LICENSE +373 -0
- data/README.md +282 -0
- data/exe/i18n-context-generator +7 -0
- data/lib/i18n_context_generator/cache.rb +49 -0
- data/lib/i18n_context_generator/cli.rb +223 -0
- data/lib/i18n_context_generator/config.rb +211 -0
- data/lib/i18n_context_generator/context_extractor.rb +381 -0
- data/lib/i18n_context_generator/git_diff.rb +159 -0
- data/lib/i18n_context_generator/llm/anthropic.rb +91 -0
- data/lib/i18n_context_generator/llm/client.rb +260 -0
- data/lib/i18n_context_generator/llm/openai.rb +112 -0
- data/lib/i18n_context_generator/parsers/android_xml_parser.rb +110 -0
- data/lib/i18n_context_generator/parsers/base.rb +60 -0
- data/lib/i18n_context_generator/parsers/json_parser.rb +21 -0
- data/lib/i18n_context_generator/parsers/strings_parser.rb +28 -0
- data/lib/i18n_context_generator/parsers/yaml_parser.rb +30 -0
- data/lib/i18n_context_generator/platform_validator.rb +92 -0
- data/lib/i18n_context_generator/searcher.rb +447 -0
- data/lib/i18n_context_generator/version.rb +5 -0
- data/lib/i18n_context_generator/writers/android_xml_writer.rb +117 -0
- data/lib/i18n_context_generator/writers/csv_writer.rb +39 -0
- data/lib/i18n_context_generator/writers/helpers.rb +58 -0
- data/lib/i18n_context_generator/writers/json_writer.rb +34 -0
- data/lib/i18n_context_generator/writers/strings_writer.rb +67 -0
- data/lib/i18n_context_generator/writers/swift_writer.rb +160 -0
- data/lib/i18n_context_generator.rb +36 -0
- metadata +196 -0
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module I18nContextGenerator
|
|
4
|
+
module Writers
|
|
5
|
+
# Shared utilities for writer classes (description filtering, file discovery).
|
|
6
|
+
module Helpers
|
|
7
|
+
def skip_description?(description)
|
|
8
|
+
description.include?('No usage found') || description.include?('Processing failed')
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def writable_result?(result)
|
|
12
|
+
return false unless result&.description
|
|
13
|
+
return false unless result.error.nil?
|
|
14
|
+
return false if result.description.strip.empty?
|
|
15
|
+
|
|
16
|
+
!skip_description?(result.description)
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def result_matches_source_path?(result, source_path)
|
|
20
|
+
return true unless result.respond_to?(:source_file) && result.source_file
|
|
21
|
+
|
|
22
|
+
File.expand_path(result.source_file) == File.expand_path(source_path)
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def find_swift_files(path, ignore_patterns: [])
|
|
26
|
+
files = if File.file?(path) && path.end_with?('.swift')
|
|
27
|
+
[path]
|
|
28
|
+
elsif File.directory?(path)
|
|
29
|
+
Dir.glob(File.join(path, '**', '*.swift'))
|
|
30
|
+
else
|
|
31
|
+
[]
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
filter_ignored_paths(files, ignore_patterns)
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
private
|
|
38
|
+
|
|
39
|
+
def filter_ignored_paths(paths, ignore_patterns)
|
|
40
|
+
compiled_patterns = ignore_patterns.map { |pattern| glob_to_regex(pattern) }
|
|
41
|
+
paths.reject { |path| ignored_path?(path, compiled_patterns) }.sort
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def ignored_path?(path, compiled_patterns)
|
|
45
|
+
compiled_patterns.any? { |pattern| pattern.match?(path) }
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def glob_to_regex(glob_pattern)
|
|
49
|
+
regex_str = Regexp.escape(glob_pattern)
|
|
50
|
+
.gsub('\*\*/', '(.*/)?')
|
|
51
|
+
.gsub('\*\*', '.*')
|
|
52
|
+
.gsub('\*', '[^/]*')
|
|
53
|
+
.gsub('\?', '.')
|
|
54
|
+
Regexp.new("(?:^|/)#{regex_str}(?:$|/)")
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
end
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'time'
|
|
4
|
+
|
|
5
|
+
module I18nContextGenerator
|
|
6
|
+
module Writers
|
|
7
|
+
# Writes extraction results to a JSON file.
|
|
8
|
+
class JsonWriter
|
|
9
|
+
def write(results, path)
|
|
10
|
+
output = {
|
|
11
|
+
generated_at: Time.now.iso8601,
|
|
12
|
+
version: I18nContextGenerator::VERSION,
|
|
13
|
+
total: results.size,
|
|
14
|
+
entries: results.sort_by(&:key).map do |result|
|
|
15
|
+
{
|
|
16
|
+
key: result.key,
|
|
17
|
+
text: result.text,
|
|
18
|
+
context: {
|
|
19
|
+
description: result.description,
|
|
20
|
+
ui_element: result.ui_element,
|
|
21
|
+
tone: result.tone,
|
|
22
|
+
max_length: result.max_length
|
|
23
|
+
},
|
|
24
|
+
locations: result.locations,
|
|
25
|
+
error: result.error
|
|
26
|
+
}
|
|
27
|
+
end
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
File.write(path, Oj.dump(output, indent: 2, mode: :compat))
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
end
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module I18nContextGenerator
|
|
4
|
+
module Writers
|
|
5
|
+
# Writer that updates iOS .strings files with context comments
|
|
6
|
+
# Uses the dotstrings gem for proper parsing and generation
|
|
7
|
+
class StringsWriter
|
|
8
|
+
include Helpers
|
|
9
|
+
|
|
10
|
+
def initialize(context_prefix: 'Context: ', context_mode: 'replace')
|
|
11
|
+
@context_prefix = context_prefix
|
|
12
|
+
@context_mode = context_mode
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def write(results, source_path)
|
|
16
|
+
return unless File.exist?(source_path)
|
|
17
|
+
|
|
18
|
+
# Parse the existing file
|
|
19
|
+
original_file = DotStrings.parse_file(source_path, strict: false)
|
|
20
|
+
results_by_key = results.each_with_object({}) do |result, lookup|
|
|
21
|
+
next unless result_matches_source_path?(result, source_path)
|
|
22
|
+
|
|
23
|
+
lookup[result.key] = result
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
# Build new file with updated comments (DotStrings::Item is immutable)
|
|
27
|
+
new_file = DotStrings::File.new
|
|
28
|
+
|
|
29
|
+
original_file.items.each do |item|
|
|
30
|
+
result = results_by_key[item.key]
|
|
31
|
+
|
|
32
|
+
new_comment = if writable_result?(result)
|
|
33
|
+
build_comment(item.comment, result.description)
|
|
34
|
+
else
|
|
35
|
+
item.comment
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
new_item = DotStrings::Item.new(
|
|
39
|
+
key: item.key,
|
|
40
|
+
value: item.value,
|
|
41
|
+
comment: new_comment
|
|
42
|
+
)
|
|
43
|
+
new_file << new_item
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
# Write back to file
|
|
47
|
+
File.write(source_path, new_file.to_s)
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
private
|
|
51
|
+
|
|
52
|
+
def build_comment(existing_comment, context_description)
|
|
53
|
+
context_line = "#{@context_prefix}#{context_description}"
|
|
54
|
+
|
|
55
|
+
if existing_comment.nil? || existing_comment.empty? || @context_mode == 'replace'
|
|
56
|
+
context_line
|
|
57
|
+
elsif !@context_prefix.empty? && existing_comment.include?(@context_prefix)
|
|
58
|
+
# Replace existing context line (idempotent update)
|
|
59
|
+
existing_comment.gsub(/#{Regexp.escape(@context_prefix)}[^\n]*/, context_line)
|
|
60
|
+
else
|
|
61
|
+
# Append context to existing comment
|
|
62
|
+
"#{existing_comment}\n#{context_line}"
|
|
63
|
+
end
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
end
|
|
67
|
+
end
|
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module I18nContextGenerator
|
|
4
|
+
module Writers
|
|
5
|
+
# Writer that updates comment: parameters in Swift localization calls
|
|
6
|
+
# Supports NSLocalizedString, String(localized:), Text(), and custom functions
|
|
7
|
+
class SwiftWriter
|
|
8
|
+
include Helpers
|
|
9
|
+
|
|
10
|
+
SWIFT_STRING_PATTERN = '"(?:\\\\.|[^"\\\\])*"'
|
|
11
|
+
COMMENT_ARGUMENT_PATTERN = /comment:\s*"((?:\\.|[^"\\])*)"/
|
|
12
|
+
private_constant :SWIFT_STRING_PATTERN, :COMMENT_ARGUMENT_PATTERN
|
|
13
|
+
|
|
14
|
+
# Default patterns for Swift localization functions
|
|
15
|
+
# Each pattern should capture: (prefix)(key)(middle)(comment_value)(suffix)
|
|
16
|
+
DEFAULT_FUNCTIONS = %w[
|
|
17
|
+
NSLocalizedString
|
|
18
|
+
String(localized:
|
|
19
|
+
Text(
|
|
20
|
+
].freeze
|
|
21
|
+
|
|
22
|
+
def initialize(functions: nil, context_prefix: 'Context: ', context_mode: 'replace')
|
|
23
|
+
@functions = functions || DEFAULT_FUNCTIONS
|
|
24
|
+
@context_prefix = context_prefix
|
|
25
|
+
@context_mode = context_mode
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
# Write context back to all Swift files that contain the keys
|
|
29
|
+
# @param results [Array] extraction results with key and description
|
|
30
|
+
# @param source_paths [Array<String>] paths to search for Swift files
|
|
31
|
+
def write_to_source_files(results, source_paths, ignore_patterns: [])
|
|
32
|
+
results_by_key = results.to_h { |r| [r.key, r] }
|
|
33
|
+
|
|
34
|
+
source_paths.each do |source_path|
|
|
35
|
+
swift_files = find_swift_files(source_path, ignore_patterns: ignore_patterns)
|
|
36
|
+
|
|
37
|
+
swift_files.each do |swift_file|
|
|
38
|
+
update_file(swift_file, results_by_key)
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
# Update a single Swift file with context comments
|
|
44
|
+
# @param path [String] path to Swift file
|
|
45
|
+
# @param results_by_key [Hash] results keyed by translation key
|
|
46
|
+
def update_file(path, results_by_key)
|
|
47
|
+
return unless File.exist?(path)
|
|
48
|
+
|
|
49
|
+
content = File.read(path)
|
|
50
|
+
original_content = content.dup
|
|
51
|
+
updated = false
|
|
52
|
+
|
|
53
|
+
results_by_key.each do |key, result|
|
|
54
|
+
next unless writable_result?(result)
|
|
55
|
+
|
|
56
|
+
new_content = update_comment_for_key(content, key, result.description)
|
|
57
|
+
if new_content != content
|
|
58
|
+
content = new_content
|
|
59
|
+
updated = true
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
if updated && content != original_content
|
|
64
|
+
File.write(path, content)
|
|
65
|
+
true
|
|
66
|
+
else
|
|
67
|
+
false
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
private
|
|
72
|
+
|
|
73
|
+
# Update comment for a specific key in the content
|
|
74
|
+
def update_comment_for_key(content, key, description)
|
|
75
|
+
escaped_key = Regexp.escape(key)
|
|
76
|
+
|
|
77
|
+
@functions.each do |func|
|
|
78
|
+
# Build pattern based on function type
|
|
79
|
+
pattern = build_pattern_for_function(func, escaped_key)
|
|
80
|
+
next unless pattern
|
|
81
|
+
|
|
82
|
+
# Try to match and replace
|
|
83
|
+
content = content.gsub(pattern) do |match|
|
|
84
|
+
update_match(match, func, key, description)
|
|
85
|
+
end
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
content
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
def build_pattern_for_function(func, escaped_key)
|
|
92
|
+
comment_pattern = "comment:\\s*#{SWIFT_STRING_PATTERN}"
|
|
93
|
+
|
|
94
|
+
case func
|
|
95
|
+
when 'NSLocalizedString'
|
|
96
|
+
# NSLocalizedString("key", comment: "...")
|
|
97
|
+
# NSLocalizedString("key", value: "...", comment: "...")
|
|
98
|
+
# NSLocalizedString("key", tableName: "...", comment: "...")
|
|
99
|
+
Regexp.new("NSLocalizedString\\(\\s*\"#{escaped_key}\"[^)]*#{comment_pattern}[^)]*\\)", Regexp::MULTILINE)
|
|
100
|
+
when 'String(localized:'
|
|
101
|
+
# String(localized: "key", comment: "...")
|
|
102
|
+
Regexp.new("String\\(\\s*localized:\\s*\"#{escaped_key}\"[^)]*#{comment_pattern}[^)]*\\)", Regexp::MULTILINE)
|
|
103
|
+
when 'Text('
|
|
104
|
+
# Text("key", comment: "...")
|
|
105
|
+
# Text(LocalizedStringKey("key"), comment: "...")
|
|
106
|
+
Regexp.new("Text\\([^)]*\"#{escaped_key}\"[^)]*#{comment_pattern}[^)]*\\)", Regexp::MULTILINE)
|
|
107
|
+
else
|
|
108
|
+
# Custom function - assume pattern like: func("key", ..., comment: "...")
|
|
109
|
+
escaped_func = Regexp.escape(func)
|
|
110
|
+
Regexp.new("#{escaped_func}\\([^)]*\"#{escaped_key}\"[^)]*#{comment_pattern}[^)]*\\)", Regexp::MULTILINE)
|
|
111
|
+
end
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
def update_match(match, _func, _key, new_comment)
|
|
115
|
+
# Replace the comment value while preserving the rest of the call
|
|
116
|
+
match.gsub(COMMENT_ARGUMENT_PATTERN) do |_comment_match|
|
|
117
|
+
existing_comment = unescape_swift_string(Regexp.last_match(1))
|
|
118
|
+
final_comment = build_final_comment(existing_comment, new_comment)
|
|
119
|
+
"comment: \"#{escape_swift_string(final_comment)}\""
|
|
120
|
+
end
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
def build_final_comment(existing_comment, new_context)
|
|
124
|
+
context_line = "#{@context_prefix}#{new_context}"
|
|
125
|
+
|
|
126
|
+
if existing_comment.nil? || existing_comment.empty? || @context_mode == 'replace'
|
|
127
|
+
context_line
|
|
128
|
+
elsif !@context_prefix.empty? && existing_comment.include?(@context_prefix)
|
|
129
|
+
# Update existing context line (idempotent)
|
|
130
|
+
existing_comment.gsub(/#{Regexp.escape(@context_prefix)}[^\n]*/, context_line)
|
|
131
|
+
else
|
|
132
|
+
# Append context to existing comment
|
|
133
|
+
"#{existing_comment} #{context_line}"
|
|
134
|
+
end
|
|
135
|
+
end
|
|
136
|
+
|
|
137
|
+
def escape_swift_string(str)
|
|
138
|
+
str.gsub('\\', '\\\\')
|
|
139
|
+
.gsub('"', '\\"')
|
|
140
|
+
.gsub("\r", '\\r')
|
|
141
|
+
.gsub("\n", '\\n')
|
|
142
|
+
.gsub("\t", '\\t')
|
|
143
|
+
end
|
|
144
|
+
|
|
145
|
+
def unescape_swift_string(str)
|
|
146
|
+
escape_map = {
|
|
147
|
+
'"' => '"',
|
|
148
|
+
'\\' => '\\',
|
|
149
|
+
'n' => "\n",
|
|
150
|
+
'r' => "\r",
|
|
151
|
+
't' => "\t"
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
str.gsub(/\\(["\\nrt])/) do
|
|
155
|
+
escape_map.fetch(Regexp.last_match(1))
|
|
156
|
+
end
|
|
157
|
+
end
|
|
158
|
+
end
|
|
159
|
+
end
|
|
160
|
+
end
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'yaml'
|
|
4
|
+
require 'csv'
|
|
5
|
+
require 'digest'
|
|
6
|
+
require 'fileutils'
|
|
7
|
+
require 'oj'
|
|
8
|
+
require 'concurrent'
|
|
9
|
+
require 'tty-progressbar'
|
|
10
|
+
require 'dotstrings'
|
|
11
|
+
|
|
12
|
+
require_relative 'i18n_context_generator/version'
|
|
13
|
+
require_relative 'i18n_context_generator/config'
|
|
14
|
+
require_relative 'i18n_context_generator/parsers/base'
|
|
15
|
+
require_relative 'i18n_context_generator/parsers/json_parser'
|
|
16
|
+
require_relative 'i18n_context_generator/parsers/yaml_parser'
|
|
17
|
+
require_relative 'i18n_context_generator/parsers/strings_parser'
|
|
18
|
+
require_relative 'i18n_context_generator/parsers/android_xml_parser'
|
|
19
|
+
require_relative 'i18n_context_generator/searcher'
|
|
20
|
+
require_relative 'i18n_context_generator/llm/client'
|
|
21
|
+
require_relative 'i18n_context_generator/llm/anthropic'
|
|
22
|
+
require_relative 'i18n_context_generator/llm/openai'
|
|
23
|
+
require_relative 'i18n_context_generator/writers/helpers'
|
|
24
|
+
require_relative 'i18n_context_generator/writers/csv_writer'
|
|
25
|
+
require_relative 'i18n_context_generator/writers/json_writer'
|
|
26
|
+
require_relative 'i18n_context_generator/writers/strings_writer'
|
|
27
|
+
require_relative 'i18n_context_generator/writers/android_xml_writer'
|
|
28
|
+
require_relative 'i18n_context_generator/writers/swift_writer'
|
|
29
|
+
require_relative 'i18n_context_generator/cache'
|
|
30
|
+
require_relative 'i18n_context_generator/git_diff'
|
|
31
|
+
require_relative 'i18n_context_generator/platform_validator'
|
|
32
|
+
require_relative 'i18n_context_generator/context_extractor'
|
|
33
|
+
|
|
34
|
+
module I18nContextGenerator
|
|
35
|
+
class Error < StandardError; end
|
|
36
|
+
end
|
metadata
ADDED
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: i18n-context-generator
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 0.3.0
|
|
5
|
+
platform: ruby
|
|
6
|
+
authors:
|
|
7
|
+
- Automattic
|
|
8
|
+
autorequire:
|
|
9
|
+
bindir: exe
|
|
10
|
+
cert_chain: []
|
|
11
|
+
date: 2026-03-23 00:00:00.000000000 Z
|
|
12
|
+
dependencies:
|
|
13
|
+
- !ruby/object:Gem::Dependency
|
|
14
|
+
name: concurrent-ruby
|
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
|
16
|
+
requirements:
|
|
17
|
+
- - "~>"
|
|
18
|
+
- !ruby/object:Gem::Version
|
|
19
|
+
version: '1.2'
|
|
20
|
+
type: :runtime
|
|
21
|
+
prerelease: false
|
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
23
|
+
requirements:
|
|
24
|
+
- - "~>"
|
|
25
|
+
- !ruby/object:Gem::Version
|
|
26
|
+
version: '1.2'
|
|
27
|
+
- !ruby/object:Gem::Dependency
|
|
28
|
+
name: dotstrings
|
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
|
30
|
+
requirements:
|
|
31
|
+
- - "~>"
|
|
32
|
+
- !ruby/object:Gem::Version
|
|
33
|
+
version: '0.6'
|
|
34
|
+
type: :runtime
|
|
35
|
+
prerelease: false
|
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
37
|
+
requirements:
|
|
38
|
+
- - "~>"
|
|
39
|
+
- !ruby/object:Gem::Version
|
|
40
|
+
version: '0.6'
|
|
41
|
+
- !ruby/object:Gem::Dependency
|
|
42
|
+
name: oj
|
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
|
44
|
+
requirements:
|
|
45
|
+
- - "~>"
|
|
46
|
+
- !ruby/object:Gem::Version
|
|
47
|
+
version: '3.16'
|
|
48
|
+
type: :runtime
|
|
49
|
+
prerelease: false
|
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
51
|
+
requirements:
|
|
52
|
+
- - "~>"
|
|
53
|
+
- !ruby/object:Gem::Version
|
|
54
|
+
version: '3.16'
|
|
55
|
+
- !ruby/object:Gem::Dependency
|
|
56
|
+
name: rexml
|
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
|
58
|
+
requirements:
|
|
59
|
+
- - "~>"
|
|
60
|
+
- !ruby/object:Gem::Version
|
|
61
|
+
version: '3.2'
|
|
62
|
+
type: :runtime
|
|
63
|
+
prerelease: false
|
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
65
|
+
requirements:
|
|
66
|
+
- - "~>"
|
|
67
|
+
- !ruby/object:Gem::Version
|
|
68
|
+
version: '3.2'
|
|
69
|
+
- !ruby/object:Gem::Dependency
|
|
70
|
+
name: thor
|
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
|
72
|
+
requirements:
|
|
73
|
+
- - "~>"
|
|
74
|
+
- !ruby/object:Gem::Version
|
|
75
|
+
version: '1.3'
|
|
76
|
+
type: :runtime
|
|
77
|
+
prerelease: false
|
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
79
|
+
requirements:
|
|
80
|
+
- - "~>"
|
|
81
|
+
- !ruby/object:Gem::Version
|
|
82
|
+
version: '1.3'
|
|
83
|
+
- !ruby/object:Gem::Dependency
|
|
84
|
+
name: tty-progressbar
|
|
85
|
+
requirement: !ruby/object:Gem::Requirement
|
|
86
|
+
requirements:
|
|
87
|
+
- - "~>"
|
|
88
|
+
- !ruby/object:Gem::Version
|
|
89
|
+
version: '0.18'
|
|
90
|
+
type: :runtime
|
|
91
|
+
prerelease: false
|
|
92
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
93
|
+
requirements:
|
|
94
|
+
- - "~>"
|
|
95
|
+
- !ruby/object:Gem::Version
|
|
96
|
+
version: '0.18'
|
|
97
|
+
- !ruby/object:Gem::Dependency
|
|
98
|
+
name: rspec
|
|
99
|
+
requirement: !ruby/object:Gem::Requirement
|
|
100
|
+
requirements:
|
|
101
|
+
- - "~>"
|
|
102
|
+
- !ruby/object:Gem::Version
|
|
103
|
+
version: '3.13'
|
|
104
|
+
type: :development
|
|
105
|
+
prerelease: false
|
|
106
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
107
|
+
requirements:
|
|
108
|
+
- - "~>"
|
|
109
|
+
- !ruby/object:Gem::Version
|
|
110
|
+
version: '3.13'
|
|
111
|
+
- !ruby/object:Gem::Dependency
|
|
112
|
+
name: rubocop-rake
|
|
113
|
+
requirement: !ruby/object:Gem::Requirement
|
|
114
|
+
requirements:
|
|
115
|
+
- - ">="
|
|
116
|
+
- !ruby/object:Gem::Version
|
|
117
|
+
version: '0'
|
|
118
|
+
type: :development
|
|
119
|
+
prerelease: false
|
|
120
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
121
|
+
requirements:
|
|
122
|
+
- - ">="
|
|
123
|
+
- !ruby/object:Gem::Version
|
|
124
|
+
version: '0'
|
|
125
|
+
- !ruby/object:Gem::Dependency
|
|
126
|
+
name: rubocop-rspec
|
|
127
|
+
requirement: !ruby/object:Gem::Requirement
|
|
128
|
+
requirements:
|
|
129
|
+
- - ">="
|
|
130
|
+
- !ruby/object:Gem::Version
|
|
131
|
+
version: '0'
|
|
132
|
+
type: :development
|
|
133
|
+
prerelease: false
|
|
134
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
135
|
+
requirements:
|
|
136
|
+
- - ">="
|
|
137
|
+
- !ruby/object:Gem::Version
|
|
138
|
+
version: '0'
|
|
139
|
+
description: A CLI tool that analyzes source code to extract contextual information
|
|
140
|
+
for translation keys, improving translation quality with AI-powered analysis.
|
|
141
|
+
email: mobile@automattic.com
|
|
142
|
+
executables:
|
|
143
|
+
- i18n-context-generator
|
|
144
|
+
extensions: []
|
|
145
|
+
extra_rdoc_files: []
|
|
146
|
+
files:
|
|
147
|
+
- LICENSE
|
|
148
|
+
- README.md
|
|
149
|
+
- exe/i18n-context-generator
|
|
150
|
+
- lib/i18n_context_generator.rb
|
|
151
|
+
- lib/i18n_context_generator/cache.rb
|
|
152
|
+
- lib/i18n_context_generator/cli.rb
|
|
153
|
+
- lib/i18n_context_generator/config.rb
|
|
154
|
+
- lib/i18n_context_generator/context_extractor.rb
|
|
155
|
+
- lib/i18n_context_generator/git_diff.rb
|
|
156
|
+
- lib/i18n_context_generator/llm/anthropic.rb
|
|
157
|
+
- lib/i18n_context_generator/llm/client.rb
|
|
158
|
+
- lib/i18n_context_generator/llm/openai.rb
|
|
159
|
+
- lib/i18n_context_generator/parsers/android_xml_parser.rb
|
|
160
|
+
- lib/i18n_context_generator/parsers/base.rb
|
|
161
|
+
- lib/i18n_context_generator/parsers/json_parser.rb
|
|
162
|
+
- lib/i18n_context_generator/parsers/strings_parser.rb
|
|
163
|
+
- lib/i18n_context_generator/parsers/yaml_parser.rb
|
|
164
|
+
- lib/i18n_context_generator/platform_validator.rb
|
|
165
|
+
- lib/i18n_context_generator/searcher.rb
|
|
166
|
+
- lib/i18n_context_generator/version.rb
|
|
167
|
+
- lib/i18n_context_generator/writers/android_xml_writer.rb
|
|
168
|
+
- lib/i18n_context_generator/writers/csv_writer.rb
|
|
169
|
+
- lib/i18n_context_generator/writers/helpers.rb
|
|
170
|
+
- lib/i18n_context_generator/writers/json_writer.rb
|
|
171
|
+
- lib/i18n_context_generator/writers/strings_writer.rb
|
|
172
|
+
- lib/i18n_context_generator/writers/swift_writer.rb
|
|
173
|
+
homepage: https://github.com/Automattic/i18n-context-generator
|
|
174
|
+
licenses:
|
|
175
|
+
- MPL-2.0
|
|
176
|
+
metadata: {}
|
|
177
|
+
post_install_message:
|
|
178
|
+
rdoc_options: []
|
|
179
|
+
require_paths:
|
|
180
|
+
- lib
|
|
181
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
182
|
+
requirements:
|
|
183
|
+
- - ">="
|
|
184
|
+
- !ruby/object:Gem::Version
|
|
185
|
+
version: 3.2.0
|
|
186
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
187
|
+
requirements:
|
|
188
|
+
- - ">="
|
|
189
|
+
- !ruby/object:Gem::Version
|
|
190
|
+
version: '0'
|
|
191
|
+
requirements: []
|
|
192
|
+
rubygems_version: 3.4.10
|
|
193
|
+
signing_key:
|
|
194
|
+
specification_version: 4
|
|
195
|
+
summary: Extract translation context from source code using AI
|
|
196
|
+
test_files: []
|