Class: Deliver::UploadMetadata

Inherits:
Object
  • Object
show all
Defined in:
deliver/lib/deliver/upload_metadata.rb

Overview

upload description, rating, etc. rubocop:disable Metrics/ClassLength

Constant Summary collapse

LOCALISED_VERSION_VALUES =

All the localised values attached to the version

{
  description: "description",
  keywords: "keywords",
  release_notes: "whats_new",
  support_url: "support_url",
  marketing_url: "marketing_url",
  promotional_text: "promotional_text"
}
NON_LOCALISED_VERSION_VALUES =

Everything attached to the version but not being localised

{
  copyright: "copyright"
}
LOCALISED_APP_VALUES =

Localised app details values

{
  name: "name",
  subtitle: "subtitle",
  privacy_url: "privacy_policy_url",
  apple_tv_privacy_policy: "privacy_policy_text"
}
NON_LOCALISED_APP_VALUES =

Non localized app details values

{
  primary_category: :primary_category,
  secondary_category: :secondary_category,
  primary_first_sub_category: :primary_subcategory_one,
  primary_second_sub_category: :primary_subcategory_two,
  secondary_first_sub_category: :secondary_subcategory_one,
  secondary_second_sub_category: :secondary_subcategory_two
}
REVIEW_INFORMATION_VALUES_LEGACY =

Review information values

{
  review_first_name: :first_name,
  review_last_name: :last_name,
  review_phone_number: :phone_number,
  review_email: :email_address,
  review_demo_user: :demo_user,
  review_demo_password: :demo_password,
  review_notes: :notes
}
REVIEW_INFORMATION_VALUES =
{
  first_name: "contact_first_name",
  last_name: "contact_last_name",
  phone_number: "contact_phone",
  email_address: "contact_email",
  demo_user: "demo_account_name",
  demo_password: "demo_account_password",
  notes: "notes"
}
LOCALISED_LIVE_VALUES =

Localized app details values, that are editable in live state

[:description, :release_notes, :support_url, :marketing_url, :promotional_text, :privacy_url]
NON_LOCALISED_LIVE_VALUES =

Non localized app details values, that are editable in live state

[:copyright]
TRADE_REPRESENTATIVE_CONTACT_INFORMATION_DIR =

Directory name it contains trade representative contact information

"trade_representative_contact_information"
REVIEW_INFORMATION_DIR =

Directory name it contains review information

"review_information"
ALL_META_SUB_DIRS =
[TRADE_REPRESENTATIVE_CONTACT_INFORMATION_DIR, REVIEW_INFORMATION_DIR]

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(options) ⇒ UploadMetadata

Returns a new instance of UploadMetadata.



83
84
85
# File 'deliver/lib/deliver/upload_metadata.rb', line 83

def initialize(options)
  self.options = options
end

Instance Attribute Details

#optionsObject

Returns the value of attribute options.



81
82
83
# File 'deliver/lib/deliver/upload_metadata.rb', line 81

def options
  @options
end

Instance Method Details

#assign_defaultsObject

If the user is using the ‘default’ language, then assign values where they are needed



369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
# File 'deliver/lib/deliver/upload_metadata.rb', line 369

def assign_defaults
  # Normalizes languages keys from symbols to strings
  normalize_language_keys

  # Build a complete list of the required languages
  enabled_languages = detect_languages

  # Get all languages used in existing settings
  (LOCALISED_VERSION_VALUES.keys + LOCALISED_APP_VALUES.keys).each do |key|
    current = options[key]
    next unless current && current.kind_of?(Hash)
    current.each do |language, value|
      enabled_languages << language unless enabled_languages.include?(language)
    end
  end

  # Check folder list (an empty folder signifies a language is required)
  ignore_validation = options[:ignore_language_directory_validation]
  Loader.language_folders(options[:metadata_path], ignore_validation).each do |lang_folder|
    enabled_languages << lang_folder.basename unless enabled_languages.include?(lang_folder.basename)
  end

  return unless enabled_languages.include?("default")
  UI.message("Detected languages: " + enabled_languages.to_s)

  (LOCALISED_VERSION_VALUES.keys + LOCALISED_APP_VALUES.keys).each do |key|
    current = options[key]
    next unless current && current.kind_of?(Hash)

    default = current["default"]
    next if default.nil?

    enabled_languages.each do |language|
      value = current[language]
      next unless value.nil?

      current[language] = default
    end
    current.delete("default")
  end
end

#convert_ms_to_iso8601(time_in_ms) ⇒ Object

rubocop:enable Metrics/PerceivedComplexity



358
359
360
361
362
363
364
365
366
# File 'deliver/lib/deliver/upload_metadata.rb', line 358

def convert_ms_to_iso8601(time_in_ms)
  time_in_s = time_in_ms / 1000

  # Remove minutes and seconds (whole hour)
  seconds_in_hour = 60 * 60
  time_in_s_to_hour = (time_in_s / seconds_in_hour).to_i * seconds_in_hour

  return Time.at(time_in_s_to_hour).utc.strftime("%Y-%m-%dT%H:%M:%S%:z")
end

#detect_languagesObject



411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
# File 'deliver/lib/deliver/upload_metadata.rb', line 411

def detect_languages
  # Build a complete list of the required languages
  enabled_languages = options[:languages] || []

  # Get all languages used in existing settings
  (LOCALISED_VERSION_VALUES.keys + LOCALISED_APP_VALUES.keys).each do |key|
    current = options[key]
    next unless current && current.kind_of?(Hash)
    current.each do |language, value|
      enabled_languages << language unless enabled_languages.include?(language)
    end
  end

  # Check folder list (an empty folder signifies a language is required)
  ignore_validation = options[:ignore_language_directory_validation]
  Loader.language_folders(options[:metadata_path], ignore_validation).each do |lang_folder|
    enabled_languages << lang_folder.basename unless enabled_languages.include?(lang_folder.basename)
  end

  # Mapping to strings because :default symbol can be passed in
  enabled_languages
    .map(&:to_s)
    .uniq
end

#fetch_edit_app_info(app) ⇒ Object



442
443
444
445
446
# File 'deliver/lib/deliver/upload_metadata.rb', line 442

def fetch_edit_app_info(app)
  retry_if_nil("Cannot find edit app info") do
    app.fetch_edit_app_info
  end
end

#fetch_edit_app_store_version(app, platform) ⇒ Object



436
437
438
439
440
# File 'deliver/lib/deliver/upload_metadata.rb', line 436

def fetch_edit_app_store_version(app, platform)
  retry_if_nil("Cannot find edit app store version") do
    app.get_edit_app_store_version(platform: platform)
  end
end

#fetch_live_app_info(app) ⇒ Object



448
449
450
451
452
# File 'deliver/lib/deliver/upload_metadata.rb', line 448

def fetch_live_app_info(app)
  retry_if_nil("Cannot find live app info") do
    app.fetch_live_app_info
  end
end

#load_from_filesystemObject

Loads the metadata files and stores them into the options object



584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
# File 'deliver/lib/deliver/upload_metadata.rb', line 584

def load_from_filesystem
  return if options[:skip_metadata]

  # Load localised data
  ignore_validation = options[:ignore_language_directory_validation]
  Loader.language_folders(options[:metadata_path], ignore_validation).each do |lang_folder|
    (LOCALISED_VERSION_VALUES.keys + LOCALISED_APP_VALUES.keys).each do |key|
      path = File.join(lang_folder.path, "#{key}.txt")
      next unless File.exist?(path)

      UI.message("Loading '#{path}'...")
      options[key] ||= {}
      options[key][lang_folder.basename] ||= File.read(path)
    end
  end

  # Load non localised data
  (NON_LOCALISED_VERSION_VALUES.keys + NON_LOCALISED_APP_VALUES.keys).each do |key|
    path = File.join(options[:metadata_path], "#{key}.txt")
    next unless File.exist?(path)

    UI.message("Loading '#{path}'...")
    options[key] ||= File.read(path)
  end

  # Load review information
  # This is used to find the file path for both new and legacy review information filenames
  resolve_review_info_path = lambda do |option_name|
    path = File.join(options[:metadata_path], REVIEW_INFORMATION_DIR, "#{option_name}.txt")
    return nil unless File.exist?(path)
    return nil if options[:app_review_information][option_name].to_s.length > 0

    UI.message("Loading '#{path}'...")
    return path
  end

  # First try and load review information from legacy filenames
  options[:app_review_information] ||= {}
  REVIEW_INFORMATION_VALUES_LEGACY.each do |legacy_option_name, option_name|
    path = resolve_review_info_path.call(legacy_option_name)
    next if path.nil?
    options[:app_review_information][option_name] ||= File.read(path)

    UI.deprecated("Review rating option '#{legacy_option_name}' from iTunesConnect has been deprecated. Please replace with '#{option_name}'")
  end

  # Then load review information from new App Store Connect filenames
  REVIEW_INFORMATION_VALUES.keys.each do |option_name|
    path = resolve_review_info_path.call(option_name)
    next if path.nil?
    options[:app_review_information][option_name] ||= File.read(path)
  end
end

#retry_if_nil(message) ⇒ Object

Retries a block of code if the return value is nil, with an exponential backoff.



455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
# File 'deliver/lib/deliver/upload_metadata.rb', line 455

def retry_if_nil(message)
  tries = options[:version_check_wait_retry_limit]
  wait_time = 10
  loop do
    tries -= 1

    value = yield
    return value if value

    # Calculate sleep time to be the lesser of the exponential backoff or 5 minutes.
    # This prevents problems with CI's console output timeouts (of usually 10 minutes), and also
    # speeds up the retry time for the user, as waiting longer than 5 minutes is a too long wait for a retry.
    sleep_time = [wait_time * 2, 5 * 60].min
    UI.message("#{message}... Retrying after #{sleep_time} seconds (remaining: #{tries})")
    Kernel.sleep(sleep_time)

    return nil if tries.zero?

    wait_time *= 2 # Double the wait time for the next iteration
  end
end

#updating_localized_app_info?(app, app_info) ⇒ Boolean

Checking if the metadata to update includes localised App Info

Returns:



478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
# File 'deliver/lib/deliver/upload_metadata.rb', line 478

def updating_localized_app_info?(app, app_info)
  app_info ||= fetch_live_app_info(app)
  unless app_info
    UI.important("Can't find edit or live App info. Skipping upload.")
    return false
  end
  localizations = app_info.get_app_info_localizations

  LOCALISED_APP_VALUES.each do |key, localized_key|
    current = options[key]
    next unless current

    unless current.kind_of?(Hash)
      UI.error("Error with provided '#{key}'. Must be a hash, the key being the language.")
      next
    end

    current.each do |language, value|
      strip_value = value.to_s.strip
      next if strip_value.empty?

      app_info_locale = localizations.find { |l| l.locale == language }
      next if app_info_locale.nil?

      begin
        current_value = app_info_locale.public_send(localized_key.to_sym)
      rescue NoMethodError
        next
      end

      return true if current_value != strip_value
    end
  end

  UI.message('No changes to localized App Info detected. Skipping upload.')
  return false
end

#uploadObject

Make sure to call ‘load_from_filesystem` before calling upload



88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
# File 'deliver/lib/deliver/upload_metadata.rb', line 88

def upload
  return if options[:skip_metadata]

  app = Deliver.cache[:app]

  platform = Spaceship::ConnectAPI::Platform.map(options[:platform])

  enabled_languages = detect_languages

  app_store_version_localizations = verify_available_version_languages!(app, enabled_languages) unless options[:edit_live]
  app_info = fetch_edit_app_info(app)
  app_info_localizations = verify_available_info_languages!(app, app_info, enabled_languages) unless options[:edit_live] || !updating_localized_app_info?(app, app_info)

  if options[:edit_live]
    # not all values are editable when using live_version
    version = app.get_live_app_store_version(platform: platform)
    localised_options = LOCALISED_LIVE_VALUES
    non_localised_options = NON_LOCALISED_LIVE_VALUES

    if version.nil?
      UI.message("Couldn't find live version, editing the current version on App Store Connect instead")
      version = fetch_edit_app_store_version(app, platform)
      # we don't want to update the localised_options and non_localised_options
      # as we also check for `options[:edit_live]` at other areas in the code
      # by not touching those 2 variables, deliver is more consistent with what the option says
      # in the documentation
    else
      UI.message("Found live version")
    end
  else
    version = fetch_edit_app_store_version(app, platform)
    localised_options = (LOCALISED_VERSION_VALUES.keys + LOCALISED_APP_VALUES.keys)
    non_localised_options = NON_LOCALISED_VERSION_VALUES.keys
  end

  # Needed for to filter out release notes from being sent up
  number_of_versions = Spaceship::ConnectAPI.get_app_store_versions(
    app_id: app.id,
    filter: { platform: platform },
    limit: 2
  ).count
  is_first_version = number_of_versions == 1
  UI.verbose("Version '#{version.version_string}' is the first version on App Store Connect") if is_first_version

  UI.important("Will begin uploading metadata for '#{version.version_string}' on App Store Connect")

  localized_version_attributes_by_locale = {}
  localized_info_attributes_by_locale = {}

  localised_options.each do |key|
    current = options[key]
    next unless current

    unless current.kind_of?(Hash)
      UI.error("Error with provided '#{key}'. Must be a hash, the key being the language.")
      next
    end

    if key == :release_notes && is_first_version
      UI.error("Skipping 'release_notes'... this is the first version of the app")
      next
    end

    current.each do |language, value|
      next unless value.to_s.length > 0
      strip_value = value.to_s.strip

      if LOCALISED_VERSION_VALUES.include?(key) && !strip_value.empty?
        attribute_name = LOCALISED_VERSION_VALUES[key]

        localized_version_attributes_by_locale[language] ||= {}
        localized_version_attributes_by_locale[language][attribute_name] = strip_value
      end

      next unless LOCALISED_APP_VALUES.include?(key) && !strip_value.empty?
      attribute_name = LOCALISED_APP_VALUES[key]

      localized_info_attributes_by_locale[language] ||= {}
      localized_info_attributes_by_locale[language][attribute_name] = strip_value
    end
  end

  non_localized_version_attributes = {}
  non_localised_options.each do |key|
    strip_value = options[key].to_s.strip
    next unless strip_value.to_s.length > 0

    if NON_LOCALISED_VERSION_VALUES.include?(key) && !strip_value.empty?
      attribute_name = NON_LOCALISED_VERSION_VALUES[key]
      non_localized_version_attributes[attribute_name] = strip_value
    end
  end

  release_type = if options[:auto_release_date]
                   # Convert time format to 2020-06-17T12:00:00-07:00
                   time_in_ms = options[:auto_release_date]
                   date = convert_ms_to_iso8601(time_in_ms)

                   non_localized_version_attributes['earliestReleaseDate'] = date
                   Spaceship::ConnectAPI::AppStoreVersion::ReleaseType::SCHEDULED
                 elsif options[:automatic_release] == true
                   Spaceship::ConnectAPI::AppStoreVersion::ReleaseType::AFTER_APPROVAL
                 elsif options[:automatic_release] == false
                   Spaceship::ConnectAPI::AppStoreVersion::ReleaseType::MANUAL
                 end
  if release_type.nil?
    UI.important("Release type will not be set because neither `automatic_release` nor `auto_release_date` were provided. Please explicitly set one of these options if you need a release type set")
  else
    non_localized_version_attributes['releaseType'] = release_type
  end

  # Update app store version
  # This needs to happen before updating localizations (https://openradar.appspot.com/radar?id=4925914991296512)
  #
  # Adding some sleeps because the API will sometimes be in a state where releaseType can't be modified
  #   https://github.com/fastlane/fastlane/issues/16911
  UI.message("Uploading metadata to App Store Connect for version")
  sleep(2)
  version.update(attributes: non_localized_version_attributes)
  sleep(1)

  # Update app store version localizations
  store_version_worker = FastlaneCore::QueueWorker.new do |app_store_version_localization|
    attributes = localized_version_attributes_by_locale[app_store_version_localization.locale]
    if attributes
      UI.message("Uploading metadata to App Store Connect for localized version '#{app_store_version_localization.locale}'")
      app_store_version_localization.update(attributes: attributes)
    end
  end
  store_version_worker.batch_enqueue(app_store_version_localizations)
  store_version_worker.start

  # Update app info localizations
  if app_info_localizations
    app_info_worker = FastlaneCore::QueueWorker.new do |app_info_localization|
      attributes = localized_info_attributes_by_locale[app_info_localization.locale]
      if attributes
        UI.message("Uploading metadata to App Store Connect for localized info '#{app_info_localization.locale}'")
        app_info_localization.update(attributes: attributes)
      end
    end
    app_info_worker.batch_enqueue(app_info_localizations)
    app_info_worker.start
  end

  # Update categories
  if app_info
    category_id_map = {}

    primary_category = options[:primary_category].to_s.strip
    secondary_category = options[:secondary_category].to_s.strip
    primary_first_sub_category = options[:primary_first_sub_category].to_s.strip
    primary_second_sub_category = options[:primary_second_sub_category].to_s.strip
    secondary_first_sub_category = options[:secondary_first_sub_category].to_s.strip
    secondary_second_sub_category = options[:secondary_second_sub_category].to_s.strip

    mapped_values = {}

    # Only update primary and secondary category if explicitly set
    unless primary_category.empty?
      mapped = Spaceship::ConnectAPI::AppCategory.map_category_from_itc(
        primary_category
      )

      mapped_values[primary_category] = mapped
      category_id_map[:primary_category_id] = mapped
    end
    unless secondary_category.empty?
      mapped = Spaceship::ConnectAPI::AppCategory.map_category_from_itc(
        secondary_category
      )

      mapped_values[secondary_category] = mapped
      category_id_map[:secondary_category_id] = mapped
    end

    # Only set if primary category is going to be set
    unless primary_category.empty?
      mapped = Spaceship::ConnectAPI::AppCategory.map_subcategory_from_itc(
        primary_first_sub_category
      )

      mapped_values[primary_first_sub_category] = mapped
      category_id_map[:primary_subcategory_one_id] = mapped
    end
    unless primary_category.empty?
      mapped = Spaceship::ConnectAPI::AppCategory.map_subcategory_from_itc(
        primary_second_sub_category
      )

      mapped_values[primary_second_sub_category] = mapped
      category_id_map[:primary_subcategory_two_id] = mapped
    end

    # Only set if secondary category is going to be set
    unless secondary_category.empty?
      mapped = Spaceship::ConnectAPI::AppCategory.map_subcategory_from_itc(
        secondary_first_sub_category
      )

      mapped_values[secondary_first_sub_category] = mapped
      category_id_map[:secondary_subcategory_one_id] = mapped
    end
    unless secondary_category.empty?
      mapped = Spaceship::ConnectAPI::AppCategory.map_subcategory_from_itc(
        secondary_second_sub_category
      )

      mapped_values[secondary_second_sub_category] = mapped
      category_id_map[:secondary_subcategory_two_id] = mapped
    end

    # Print deprecation warnings if category was mapped
    has_mapped_values = false
    mapped_values.each do |k, v|
      next if k.nil? || v.nil?
      next if k == v
      has_mapped_values = true
      UI.deprecated("Category '#{k}' from iTunesConnect has been deprecated. Please replace with '#{v}'")
    end
    UI.deprecated("You can find more info at https://docs.fastlane.tools/actions/deliver/#reference") if has_mapped_values

    app_info.update_categories(category_id_map: category_id_map)
  end

  # Update phased release
  unless options[:phased_release].nil?
    phased_release = begin
                       version.fetch_app_store_version_phased_release
                     rescue
                       nil
                     end # returns no data error so need to rescue
    if !!options[:phased_release]
      unless phased_release
        UI.message("Creating phased release on App Store Connect")
        version.create_app_store_version_phased_release(attributes: {
          phasedReleaseState: Spaceship::ConnectAPI::AppStoreVersionPhasedRelease::PhasedReleaseState::INACTIVE
        })
      end
    elsif phased_release
      UI.message("Removing phased release on App Store Connect")
      phased_release.delete!
    end
  end

  # Update rating reset
  unless options[:reset_ratings].nil?
    reset_rating_request = begin
                             version.fetch_reset_ratings_request
                           rescue
                             nil
                           end # returns no data error so need to rescue
    if !!options[:reset_ratings]
      unless reset_rating_request
        UI.message("Creating reset ratings request on App Store Connect")
        version.create_reset_ratings_request
      end
    elsif reset_rating_request
      UI.message("Removing reset ratings request on App Store Connect")
      reset_rating_request.delete!
    end
  end

  review_information(version)
  review_attachment_file(version)
  app_rating(app_info)
end

#verify_available_info_languages!(app, app_info, languages) ⇒ Object

Finding languages to enable



517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
# File 'deliver/lib/deliver/upload_metadata.rb', line 517

def verify_available_info_languages!(app, app_info, languages)
  unless app_info
    UI.user_error!("Cannot update languages - could not find an editable 'App Info'. Verify that your app is in one of the editable states in App Store Connect")
    return
  end

  localizations = app_info.get_app_info_localizations

  languages = (languages || []).reject { |lang| lang == "default" }
  locales_to_enable = languages - localizations.map(&:locale)

  if locales_to_enable.count > 0
    lng_text = "language"
    lng_text += "s" if locales_to_enable.count != 1
    Helper.show_loading_indicator("Activating info #{lng_text} #{locales_to_enable.join(', ')}...")

    locales_to_enable.each do |locale|
      app_info.create_app_info_localization(attributes: {
        locale: locale
      })
    end

    Helper.hide_loading_indicator

    # Refresh version localizations
    localizations = app_info.get_app_info_localizations
  end

  return localizations
end

#verify_available_version_languages!(app, languages) ⇒ Object

Finding languages to enable



549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
# File 'deliver/lib/deliver/upload_metadata.rb', line 549

def verify_available_version_languages!(app, languages)
  platform = Spaceship::ConnectAPI::Platform.map(options[:platform])
  version = fetch_edit_app_store_version(app, platform)

  unless version
    UI.user_error!("Cannot update languages - could not find an editable version for '#{platform}'")
    return
  end

  localizations = version.get_app_store_version_localizations

  languages = (languages || []).reject { |lang| lang == "default" }
  locales_to_enable = languages - localizations.map(&:locale)

  if locales_to_enable.count > 0
    lng_text = "language"
    lng_text += "s" if locales_to_enable.count != 1
    Helper.show_loading_indicator("Activating version #{lng_text} #{locales_to_enable.join(', ')}...")

    locales_to_enable.each do |locale|
      version.create_app_store_version_localization(attributes: {
        locale: locale
      })
    end

    Helper.hide_loading_indicator

    # Refresh version localizations
    localizations = version.get_app_store_version_localizations
  end

  return localizations
end