fastlane-plugin-translate_gpt_release_notes 0.3.1 → 0.3.2
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 +8 -7
- data/lib/fastlane/plugin/translate_gpt_release_notes/helper/providers/anthropic_provider.rb +6 -5
- data/lib/fastlane/plugin/translate_gpt_release_notes/helper/providers/base_provider.rb +28 -12
- data/lib/fastlane/plugin/translate_gpt_release_notes/helper/providers/deepl_provider.rb +1 -9
- data/lib/fastlane/plugin/translate_gpt_release_notes/helper/providers/gemini_provider.rb +6 -5
- data/lib/fastlane/plugin/translate_gpt_release_notes/helper/providers/openai_provider.rb +6 -7
- data/lib/fastlane/plugin/translate_gpt_release_notes/version.rb +1 -1
- metadata +2 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 34d44bece5c1bdb823c405512f9cd7123c61584c7839d832eeae0f3b8c730d3f
|
|
4
|
+
data.tar.gz: '0851f02c45a3fcce10a4313fbb6a55f4334f770fc0aa78fdf4a98b0d2fc25fb8'
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 3572a763d2202c00cb26e826f44b141e1d4042ea33214fd6182c3f2dcb6d672b0d36625c42f8ccc454a406460d49f04efeff391bde9e8c12affe73f33676c606
|
|
7
|
+
data.tar.gz: 52a36475a11bb478ba51f8823916649272a4bd7e3fb42113a5462b2f5dd58051df34dee849460de3c40859a97e0712f8f30f47e579cf30017cfe7a3903075871
|
data/README.md
CHANGED
|
@@ -182,7 +182,8 @@ The glossary feature ensures consistent translation of app-specific terms (scree
|
|
|
182
182
|
1. The plugin loads glossary terms from a JSON file and/or a directory of localization files
|
|
183
183
|
2. Before each translation, it fuzzy-matches terms from the glossary against the source text
|
|
184
184
|
3. Only relevant terms are included in the translation prompt (keeping it concise)
|
|
185
|
-
4.
|
|
185
|
+
4. The AI is instructed to use glossary terms as **reference translations** and apply appropriate grammatical forms (declension, conjugation, agreement) as needed — so translations sound natural in inflected languages like Russian, German, or Finnish rather than copying terms verbatim
|
|
186
|
+
5. Each provider uses the glossary differently:
|
|
186
187
|
- **OpenAI**: Glossary terms are sent as a system message for strong instruction following
|
|
187
188
|
- **Anthropic / Gemini**: Glossary terms are included in the translation prompt
|
|
188
189
|
- **DeepL**: Glossary terms are passed via the `context` parameter
|
|
@@ -406,10 +407,10 @@ translate_gpt_release_notes(
|
|
|
406
407
|
|
|
407
408
|
### Android 500 Character Limit
|
|
408
409
|
|
|
409
|
-
Android has a limit of 500 characters for changelogs. The plugin
|
|
410
|
+
Android has a hard limit of 500 characters for changelogs. The plugin enforces this in two layers:
|
|
410
411
|
|
|
411
|
-
1. **
|
|
412
|
-
2. **
|
|
412
|
+
1. **Prompt constraint**: For AI providers, the limit is included near the top of the prompt (alongside core instructions) with explicit wording that the model must count carefully and shorten or summarize if needed. This positions it as a hard constraint rather than an afterthought.
|
|
413
|
+
2. **Safety-net truncation**: All providers (including DeepL) truncate the result and log a warning if the model still exceeds 500 characters despite the prompt.
|
|
413
414
|
|
|
414
415
|
If you frequently hit the limit, consider shortening your master locale changelog.
|
|
415
416
|
|
|
@@ -465,9 +466,9 @@ export OPENAI_API_KEY='your-key-here'
|
|
|
465
466
|
**Cause**: The translated text is longer than 500 characters.
|
|
466
467
|
|
|
467
468
|
**Solutions**:
|
|
468
|
-
1. Shorten your source changelog
|
|
469
|
-
2.
|
|
470
|
-
3. For AI providers, the prompt
|
|
469
|
+
1. Shorten your source changelog — the most reliable fix
|
|
470
|
+
2. All providers will truncate automatically if the limit is exceeded (with a warning), but this may cut mid-sentence
|
|
471
|
+
3. For AI providers, the prompt strongly instructs the model to stay within the limit and shorten if needed
|
|
471
472
|
|
|
472
473
|
### API Timeout Errors
|
|
473
474
|
|
|
@@ -83,10 +83,11 @@ module Fastlane
|
|
|
83
83
|
# @return [String, nil] Translated text or nil on error
|
|
84
84
|
def translate(text, source_locale, target_locale, glossary_terms: {})
|
|
85
85
|
# Build prompt using inherited method (includes instructions, glossary, and text)
|
|
86
|
-
prompt = build_prompt(
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
86
|
+
prompt = build_prompt(
|
|
87
|
+
text, source_locale, target_locale,
|
|
88
|
+
glossary_terms: glossary_terms,
|
|
89
|
+
platform: @params[:platform]
|
|
90
|
+
)
|
|
90
91
|
|
|
91
92
|
# Make API call using ruby-anthropic gem API
|
|
92
93
|
response = @client.complete(
|
|
@@ -97,7 +98,7 @@ module Fastlane
|
|
|
97
98
|
)
|
|
98
99
|
|
|
99
100
|
# Extract text from response
|
|
100
|
-
extract_text_from_response(response)
|
|
101
|
+
enforce_android_limit(extract_text_from_response(response))
|
|
101
102
|
rescue StandardError => e
|
|
102
103
|
UI.error "Anthropic provider error: #{e.message}"
|
|
103
104
|
nil
|
|
@@ -92,13 +92,19 @@ module Fastlane
|
|
|
92
92
|
# @param target_locale [String] Target language code
|
|
93
93
|
# @param glossary_terms [Hash] Optional glossary { source_term => target_translation }
|
|
94
94
|
# @return [String] The formatted prompt
|
|
95
|
-
def build_prompt(text, source_locale, target_locale, glossary_terms: {})
|
|
95
|
+
def build_prompt(text, source_locale, target_locale, glossary_terms: {}, platform: nil)
|
|
96
96
|
prompt_parts = []
|
|
97
97
|
|
|
98
98
|
# Instructions first: role, task, and output format
|
|
99
99
|
prompt_parts << "Translate the following release notes from #{source_locale} to #{target_locale}."
|
|
100
100
|
prompt_parts << "Respond with ONLY the translated text. Preserve the original formatting, line breaks, and bullet points."
|
|
101
101
|
|
|
102
|
+
# Android character limit is a hard constraint — include it with core instructions
|
|
103
|
+
if platform == 'android'
|
|
104
|
+
prompt_parts << ""
|
|
105
|
+
prompt_parts << android_limitation_instruction
|
|
106
|
+
end
|
|
107
|
+
|
|
102
108
|
# Add context if provided
|
|
103
109
|
if @params[:context]
|
|
104
110
|
prompt_parts << ""
|
|
@@ -108,7 +114,7 @@ module Fastlane
|
|
|
108
114
|
# Add glossary terms before the text so the model reads them first
|
|
109
115
|
unless glossary_terms.nil? || glossary_terms.empty?
|
|
110
116
|
prompt_parts << ""
|
|
111
|
-
prompt_parts << "Use the following glossary for consistent terminology.
|
|
117
|
+
prompt_parts << "Use the following glossary for consistent terminology. These are reference translations — apply appropriate grammatical forms (declension, conjugation, agreement) as needed for natural-sounding language in the target language. Do not copy verbatim if grammar requires a different form:"
|
|
112
118
|
glossary_terms.each do |source_term, target_term|
|
|
113
119
|
prompt_parts << "- \"#{source_term}\" -> \"#{target_term}\""
|
|
114
120
|
end
|
|
@@ -130,12 +136,18 @@ module Fastlane
|
|
|
130
136
|
# @param target_locale [String] Target language code
|
|
131
137
|
# @param glossary_terms [Hash] Optional glossary { source_term => target_translation }
|
|
132
138
|
# @return [String] The system instruction
|
|
133
|
-
def build_system_instruction(source_locale, target_locale, glossary_terms: {})
|
|
139
|
+
def build_system_instruction(source_locale, target_locale, glossary_terms: {}, platform: nil)
|
|
134
140
|
parts = []
|
|
135
141
|
|
|
136
142
|
parts << "Translate the following release notes from #{source_locale} to #{target_locale}."
|
|
137
143
|
parts << "Respond with ONLY the translated text. Preserve the original formatting, line breaks, and bullet points."
|
|
138
144
|
|
|
145
|
+
# Android character limit is a hard constraint — include it with core instructions
|
|
146
|
+
if platform == 'android'
|
|
147
|
+
parts << ""
|
|
148
|
+
parts << android_limitation_instruction
|
|
149
|
+
end
|
|
150
|
+
|
|
139
151
|
if @params[:context]
|
|
140
152
|
parts << ""
|
|
141
153
|
parts << "Context: #{@params[:context]}"
|
|
@@ -143,7 +155,7 @@ module Fastlane
|
|
|
143
155
|
|
|
144
156
|
unless glossary_terms.nil? || glossary_terms.empty?
|
|
145
157
|
parts << ""
|
|
146
|
-
parts << "Use the following glossary for consistent terminology.
|
|
158
|
+
parts << "Use the following glossary for consistent terminology. These are reference translations — apply appropriate grammatical forms (declension, conjugation, agreement) as needed for natural-sounding language in the target language. Do not copy verbatim if grammar requires a different form:"
|
|
147
159
|
glossary_terms.each do |source_term, target_term|
|
|
148
160
|
parts << "- \"#{source_term}\" -> \"#{target_term}\""
|
|
149
161
|
end
|
|
@@ -157,17 +169,21 @@ module Fastlane
|
|
|
157
169
|
#
|
|
158
170
|
# @return [String] The Android limitation instruction
|
|
159
171
|
def android_limitation_instruction
|
|
160
|
-
"
|
|
161
|
-
"
|
|
172
|
+
"CRITICAL: Google Play Store enforces a hard #{ANDROID_CHAR_LIMIT}-character limit for release notes. " \
|
|
173
|
+
"Your translation MUST be #{ANDROID_CHAR_LIMIT} characters or fewer. " \
|
|
174
|
+
"Count carefully and shorten or summarize if needed to stay within this limit."
|
|
162
175
|
end
|
|
163
176
|
|
|
164
|
-
#
|
|
165
|
-
#
|
|
177
|
+
# Truncates the translated text to the Android character limit if exceeded.
|
|
178
|
+
# Logs a warning when truncation occurs.
|
|
166
179
|
#
|
|
167
|
-
# @param
|
|
168
|
-
# @return [String] The
|
|
169
|
-
def
|
|
170
|
-
|
|
180
|
+
# @param text [String, nil] The translated text
|
|
181
|
+
# @return [String, nil] The text, truncated to ANDROID_CHAR_LIMIT if necessary
|
|
182
|
+
def enforce_android_limit(text)
|
|
183
|
+
return text unless @params[:platform] == 'android' && text && text.length > ANDROID_CHAR_LIMIT
|
|
184
|
+
|
|
185
|
+
UI.warning("Translation exceeds #{ANDROID_CHAR_LIMIT} characters (#{text.length}), truncating...")
|
|
186
|
+
text[0...ANDROID_CHAR_LIMIT]
|
|
171
187
|
end
|
|
172
188
|
|
|
173
189
|
# Adds a configuration error to the errors list.
|
|
@@ -118,15 +118,7 @@ module Fastlane
|
|
|
118
118
|
# Make API call
|
|
119
119
|
result = DeepL.translate(text, source_lang, target_lang, options)
|
|
120
120
|
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
# Handle Android 500 character limit
|
|
124
|
-
if @params[:platform] == 'android' && translated.length > 500
|
|
125
|
-
UI.warning "DeepL translation exceeds 500 characters (#{translated.length}), truncating..."
|
|
126
|
-
translated = translated[0...500]
|
|
127
|
-
end
|
|
128
|
-
|
|
129
|
-
translated
|
|
121
|
+
enforce_android_limit(result.text)
|
|
130
122
|
rescue DeepL::Exceptions::RequestError => e
|
|
131
123
|
UI.error "DeepL API error: #{e.message}"
|
|
132
124
|
nil
|
|
@@ -80,16 +80,17 @@ module Fastlane
|
|
|
80
80
|
# @return [String, nil] Translated text or nil on error
|
|
81
81
|
def translate(text, source_locale, target_locale, glossary_terms: {})
|
|
82
82
|
# Build prompt using inherited method (includes instructions, glossary, and text)
|
|
83
|
-
prompt = build_prompt(
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
83
|
+
prompt = build_prompt(
|
|
84
|
+
text, source_locale, target_locale,
|
|
85
|
+
glossary_terms: glossary_terms,
|
|
86
|
+
platform: @params[:platform]
|
|
87
|
+
)
|
|
87
88
|
|
|
88
89
|
# Make API call
|
|
89
90
|
result = make_api_request(prompt)
|
|
90
91
|
|
|
91
92
|
# Extract text from response
|
|
92
|
-
extract_text_from_response(result)
|
|
93
|
+
enforce_android_limit(extract_text_from_response(result))
|
|
93
94
|
rescue StandardError => e
|
|
94
95
|
UI.error "Gemini provider error: #{e.message}"
|
|
95
96
|
nil
|
|
@@ -82,14 +82,13 @@ module Fastlane
|
|
|
82
82
|
# @return [String, nil] Translated text or nil on error
|
|
83
83
|
def translate(text, source_locale, target_locale, glossary_terms: {})
|
|
84
84
|
# Build system instruction and user content separately for better results
|
|
85
|
-
system_instruction = build_system_instruction(
|
|
85
|
+
system_instruction = build_system_instruction(
|
|
86
|
+
source_locale, target_locale,
|
|
87
|
+
glossary_terms: glossary_terms,
|
|
88
|
+
platform: @params[:platform]
|
|
89
|
+
)
|
|
86
90
|
user_content = text
|
|
87
91
|
|
|
88
|
-
# Add Android limitations to system instruction if needed
|
|
89
|
-
if @params[:platform] == 'android'
|
|
90
|
-
system_instruction += "\n\n" + android_limitation_instruction
|
|
91
|
-
end
|
|
92
|
-
|
|
93
92
|
# Build parameters hash with separate system and user messages
|
|
94
93
|
parameters = {
|
|
95
94
|
model: @params[:model_name] || DEFAULT_MODEL,
|
|
@@ -112,7 +111,7 @@ module Fastlane
|
|
|
112
111
|
UI.error "OpenAI translation error: #{error}"
|
|
113
112
|
nil
|
|
114
113
|
else
|
|
115
|
-
response.dig('choices', 0, 'message', 'content')&.strip
|
|
114
|
+
enforce_android_limit(response.dig('choices', 0, 'message', 'content')&.strip)
|
|
116
115
|
end
|
|
117
116
|
rescue StandardError => e
|
|
118
117
|
UI.error "OpenAI provider error: #{e.message}"
|
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: fastlane-plugin-translate_gpt_release_notes
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.3.
|
|
4
|
+
version: 0.3.2
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Anton Karliner
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: bin
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2026-
|
|
11
|
+
date: 2026-04-01 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: ruby-openai
|