1
1
# frozen_string_literal: true
2
2
3
+ require 'open-uri'
4
+ require 'fluent_tools'
5
+
3
6
PROJECT_ROOT = File . expand_path ( '..' , __dir__ )
4
7
5
8
LANE_VALUE_VERSION = 'WP_VERSION'
@@ -11,6 +14,72 @@ LANE_VALUE_XCFRAMEWORK_CHECKSUM_PATH = 'WP_XCFRAMEWORK_CHECKSUM_PATH'
11
14
GITHUB_REPO = 'automattic/wordpress-rs'
12
15
GIT_REMOTE_NAME = 'origin'
13
16
17
+ # Localization constants
18
+ LOCALIZATION_FLUENT_FILES_DIR = File . join ( PROJECT_ROOT , 'wp_localization' , 'localization' )
19
+ LOCALIZATION_PO_SOURCE_FILE = File . join ( PROJECT_ROOT , 'wp_localization' , 'glotpress' , 'en-US.pot' )
20
+ MAIN_FLUENT_FILE_NAME = 'main.ftl'
21
+ PROJECT_NAME = 'wordpress-rs'
22
+
23
+ # GlotPress configuration
24
+ GLOTPRESS_PROJECT_BASE_URL = 'https://translate.wordpress.com/projects/mobile/wordpress-rs'
25
+
26
+ # Supported locales mapping between GlotPress and project locale codes
27
+ # This list combines locales supported in the iOS and Android apps
28
+ SUPPORTED_LOCALES = [
29
+ { glotpress : 'ar' , project : 'ar' } ,
30
+ { glotpress : 'az' , project : 'az' } ,
31
+ { glotpress : 'bg' , project : 'bg' } ,
32
+ { glotpress : 'cs' , project : 'cs' } ,
33
+ { glotpress : 'cy' , project : 'cy' } ,
34
+ { glotpress : 'da' , project : 'da' } ,
35
+ { glotpress : 'de' , project : 'de' } ,
36
+ { glotpress : 'el' , project : 'el' } ,
37
+ { glotpress : 'en-au' , project : 'en-AU' } ,
38
+ { glotpress : 'en-ca' , project : 'en-CA' } ,
39
+ { glotpress : 'en-gb' , project : 'en-GB' } ,
40
+ { glotpress : 'es' , project : 'es' } ,
41
+ { glotpress : 'es-cl' , project : 'es-CL' } ,
42
+ { glotpress : 'es-co' , project : 'es-CO' } ,
43
+ { glotpress : 'es-mx' , project : 'es-MX' } ,
44
+ { glotpress : 'es-ve' , project : 'es-VE' } ,
45
+ { glotpress : 'eu' , project : 'eu' } ,
46
+ { glotpress : 'fr' , project : 'fr' } ,
47
+ { glotpress : 'fr-ca' , project : 'fr-CA' } ,
48
+ { glotpress : 'gd' , project : 'gd' } ,
49
+ { glotpress : 'gl' , project : 'gl' } ,
50
+ { glotpress : 'he' , project : 'he' } ,
51
+ { glotpress : 'hi' , project : 'hi' } ,
52
+ { glotpress : 'hr' , project : 'hr' } ,
53
+ { glotpress : 'hu' , project : 'hu' } ,
54
+ { glotpress : 'id' , project : 'id' } ,
55
+ { glotpress : 'is' , project : 'is' } ,
56
+ { glotpress : 'it' , project : 'it' } ,
57
+ { glotpress : 'ja' , project : 'ja' } ,
58
+ { glotpress : 'kmr' , project : 'kmr' } ,
59
+ { glotpress : 'ko' , project : 'ko' } ,
60
+ { glotpress : 'lv' , project : 'lv' } ,
61
+ { glotpress : 'mk' , project : 'mk' } ,
62
+ { glotpress : 'ms' , project : 'ms' } ,
63
+ { glotpress : 'nb' , project : 'nb' } ,
64
+ { glotpress : 'nl' , project : 'nl' } ,
65
+ { glotpress : 'pl' , project : 'pl' } ,
66
+ { glotpress : 'pt' , project : 'pt' } ,
67
+ { glotpress : 'pt-br' , project : 'pt-BR' } ,
68
+ { glotpress : 'ro' , project : 'ro' } ,
69
+ { glotpress : 'ru' , project : 'ru' } ,
70
+ { glotpress : 'sk' , project : 'sk' } ,
71
+ { glotpress : 'sq' , project : 'sq' } ,
72
+ { glotpress : 'sr' , project : 'sr' } ,
73
+ { glotpress : 'sv' , project : 'sv' } ,
74
+ { glotpress : 'th' , project : 'th' } ,
75
+ { glotpress : 'tr' , project : 'tr-TR' } ,
76
+ { glotpress : 'uz' , project : 'uz' } ,
77
+ { glotpress : 'vi' , project : 'vi' } ,
78
+ { glotpress : 'zh-cn' , project : 'zh-CN' } ,
79
+ { glotpress : 'zh-hk' , project : 'zh-HK' } ,
80
+ { glotpress : 'zh-tw' , project : 'zh-TW' }
81
+ ] . freeze
82
+
14
83
lane :release do |options |
15
84
version = options [ :version ] || UI . user_error! ( 'version is required' )
16
85
lane_context [ LANE_VALUE_VERSION ] = version
@@ -105,6 +174,154 @@ lane :publish_to_s3 do
105
174
)
106
175
end
107
176
177
+ # Converts the English Fluent localization file to PO format for translation
178
+ #
179
+ # The resulting PO file is saved as the source file (.pot) for translations and is synced to GlotPress.
180
+ #
181
+ # @param commit_and_push_changes [Boolean] Whether to commit and push the generated PO file (default: false)
182
+ #
183
+ lane :generate_source_po_file do |commit_and_push_changes : false |
184
+ UI . header ( '🔄 Converting English Fluent file to PO format' )
185
+
186
+ FileUtils . mkdir_p ( File . dirname ( LOCALIZATION_PO_SOURCE_FILE ) )
187
+
188
+ fluent_file = File . join ( LOCALIZATION_FLUENT_FILES_DIR , 'en-US' , MAIN_FLUENT_FILE_NAME )
189
+
190
+ UI . user_error! ( "❌ English Fluent file not found: #{ fluent_file } " ) unless File . exist? ( fluent_file )
191
+
192
+ begin
193
+ FluentTools . fluent_to_po (
194
+ fluent_file ,
195
+ LOCALIZATION_PO_SOURCE_FILE ,
196
+ locale : 'en-US'
197
+ )
198
+
199
+ UI . success ( "✅ #{ File . basename ( fluent_file ) } → #{ File . basename ( LOCALIZATION_PO_SOURCE_FILE ) } " )
200
+ rescue StandardError => e
201
+ UI . error ( "❌ Failed to convert English Fluent file: #{ e . message } " )
202
+ end
203
+
204
+ if commit_and_push_changes
205
+ commit_changed_files (
206
+ files : LOCALIZATION_PO_SOURCE_FILE ,
207
+ message : 'Update source PO file (en-US.pot) to be synced to GlotPress'
208
+ )
209
+ end
210
+ end
211
+
212
+ # Downloads the latest translations from GlotPress and updates Fluent files
213
+ #
214
+ # This lane fetches translated PO files from GlotPress for all supported locales,
215
+ # converts them back to Fluent format, and optionally commits and pushes the changes.
216
+ #
217
+ # @param commit_and_push_changes [Boolean] Whether to commit and push the updated Fluent files (default: false)
218
+ #
219
+ lane :download_translations do |commit_and_push_changes : false |
220
+ UI . header ( '🌐 Downloading translations from GlotPress' )
221
+
222
+ Dir . mktmpdir do |temp_download_dir |
223
+ downloaded_files = download_po_files_from_glotpress ( download_dir : temp_download_dir )
224
+ updated_fluent_files = [ ]
225
+
226
+ if downloaded_files . empty?
227
+ UI . message ( "No .po files were downloaded from GlotPress" )
228
+ next
229
+ end
230
+
231
+ UI . header ( '🔄 Converting PO files to Fluent format' )
232
+
233
+ downloaded_files . each do |file_path |
234
+ fluent_file_path = generate_fluent_file_from_po ( file_path : file_path )
235
+ updated_fluent_files << fluent_file_path if fluent_file_path
236
+ rescue StandardError => e
237
+ UI . error ( "❌ Failed to convert #{ File . basename ( file_path ) } : #{ e . message } " )
238
+ end
239
+
240
+ UI . success ( "✅ Updated Fluent files: #{ updated_fluent_files . length } locales" )
241
+
242
+ if commit_and_push_changes && updated_fluent_files . any?
243
+ commit_changed_files (
244
+ files : updated_fluent_files ,
245
+ message : 'Update generated Fluent files based on latest GlotPress translations'
246
+ )
247
+ end
248
+ end
249
+ end
250
+
251
+ # Downloads PO files from GlotPress for existing project locales
252
+ #
253
+ # This lane fetches translated PO files from GlotPress for all supported locales
254
+ # that exist in the project. It only downloads files for locales that have
255
+ # directories in the source localization folder.
256
+ #
257
+ # @return [Array<String>] List of successfully downloaded files
258
+ #
259
+ lane :download_po_files_from_glotpress do |download_dir :|
260
+ UI . header ( '🌐 Downloading PO files from GlotPress' )
261
+
262
+ UI . user_error! ( "Download directory does not exist: #{ download_dir } " ) unless Dir . exist? ( download_dir )
263
+
264
+ downloaded_files = [ ]
265
+
266
+ SUPPORTED_LOCALES . each do |locale_mapping |
267
+ glotpress_locale = locale_mapping [ :glotpress ]
268
+ project_locale = locale_mapping [ :project ]
269
+
270
+ po_file_path = File . join ( download_dir , "#{ project_locale } .po" )
271
+ download_url = "#{ GLOTPRESS_PROJECT_BASE_URL } /#{ glotpress_locale } /default/export-translations/?format=po"
272
+
273
+ UI . message ( "⬇️ Downloading PO file for #{ project_locale } ..." )
274
+
275
+ begin
276
+ # Download PO file using Ruby's URI.open
277
+ File . write ( po_file_path , URI . open ( download_url ) . read )
278
+
279
+ if File . exist? ( po_file_path ) && !File . empty? ( po_file_path )
280
+ downloaded_files << po_file_path
281
+ else
282
+ UI . error ( "❌ Failed to download #{ project_locale } : empty response" )
283
+ end
284
+ rescue StandardError => e
285
+ UI . error ( "❌ Failed to download #{ project_locale } : #{ e . message } " )
286
+ end
287
+ end
288
+
289
+ UI . success ( "✅ Downloaded: #{ downloaded_files . length } PO files" ) if downloaded_files . any?
290
+
291
+ downloaded_files
292
+ end
293
+
294
+ # Converts a PO file for a given locale back to Fluent format
295
+ #
296
+ # This lane takes a PO file and converts it to the corresponding Fluent format file.
297
+ # The locale is extracted from the PO filename (e.g., 'fr-FR.po' becomes 'fr-FR').
298
+ # The resulting Fluent file is saved in the appropriate locale directory.
299
+ #
300
+ # @param file_path [String] The PO file path to convert (e.g., 'path/to/fr-FR.po')
301
+ # @return [String] The path to the generated Fluent file
302
+ #
303
+ lane :generate_fluent_file_from_po do |file_path :|
304
+ UI . user_error! ( "PO file not found: #{ file_path } " ) unless File . exist? ( file_path )
305
+
306
+ locale = File . basename ( file_path , '.po' )
307
+ fluent_file_path = File . join ( LOCALIZATION_FLUENT_FILES_DIR , locale , MAIN_FLUENT_FILE_NAME )
308
+
309
+ FileUtils . mkdir_p ( File . dirname ( fluent_file_path ) )
310
+
311
+ FluentTools . po_to_fluent (
312
+ file_path ,
313
+ fluent_file_path
314
+ )
315
+
316
+ next if !File . exist? ( fluent_file_path ) || File . empty? ( fluent_file_path )
317
+
318
+ UI . message ( "✅ #{ File . basename ( file_path ) } → #{ fluent_file_path } " )
319
+
320
+ fluent_file_path
321
+ end
322
+
323
+ # Utils
324
+
108
325
def xcframework_checksum
109
326
File . read ( xcframework_checksum_file_path ) . strip!
110
327
end
126
343
def remove_lane_context_values ( names )
127
344
names . each { |name | lane_context . delete ( name ) }
128
345
end
346
+
347
+ def commit_changed_files ( files :, message :, push : true )
348
+ git_add ( path : files )
349
+ result = git_commit (
350
+ path : files ,
351
+ message : message ,
352
+ allow_nothing_to_commit : true
353
+ )
354
+
355
+ if result . nil?
356
+ UI . important ( '⚠️ No changed files' )
357
+ elsif push
358
+ push_to_git_remote ( set_upstream : true , tags : false )
359
+ end
360
+ end
0 commit comments