diff --git a/solara/lib/core/dashboard/handler/edit_section_handler.rb b/solara/lib/core/dashboard/handler/edit_section_handler.rb index 8d1bc8f..96a5960 100644 --- a/solara/lib/core/dashboard/handler/edit_section_handler.rb +++ b/solara/lib/core/dashboard/handler/edit_section_handler.rb @@ -31,9 +31,10 @@ def update_section(key, data, brand_key) template = BrandConfigurationsManager.new(brand_key).template_with_key(key) path = template[:path] + if File.exist?(path) if key === 'InfoPlist.xcstrings' - InfoPListStringCatalogManager.new.update(data, path) + InfoPListStringCatalogManager.new.update(data, brand_key) else File.write(path, JSON.pretty_generate(data)) end diff --git a/solara/lib/core/doctor/brand_doctor.rb b/solara/lib/core/doctor/brand_doctor.rb index 7c6c180..6273815 100644 --- a/solara/lib/core/doctor/brand_doctor.rb +++ b/solara/lib/core/doctor/brand_doctor.rb @@ -9,6 +9,7 @@ def initialize def visit(brand_keys = [], print_logs: true) keys = brand_keys.empty? ? BrandsManager.instance.brands_list.map { |brand| brand['key'] } : brand_keys issues = [] + has_template_errors = false keys.each do |brand_key| validator = TemplateValidator.new(FilePath.brand(brand_key), FilePath.template_validation_config) @@ -17,15 +18,16 @@ def visit(brand_keys = [], print_logs: true) errors.each { |error| issues << Issue.error(error) } + has_template_errors = true unless issues.select { |issue| issue.type == Issue::ERROR }.empty? # Validate InfoPlist.xcstrings only if all validations so far are passed to avoid files issues - if issues.select { |issue| issue.type == Issue::ERROR }.empty? + unless has_template_errors issues += InfoPListStringCatalogManager.new.validate_required_keys(brand_key) end end # Validate settings only if all validations so far are passed to avoid files issues - if issues.select { |issue| issue.type == Issue::ERROR }.empty? + unless has_template_errors issues += BrandSettingsValidatorManager.new.validate end diff --git a/solara/lib/core/scripts/platform/ios/infoplist_string_catalog_manager.rb b/solara/lib/core/scripts/platform/ios/infoplist_string_catalog_manager.rb index 668092a..8ac82b8 100644 --- a/solara/lib/core/scripts/platform/ios/infoplist_string_catalog_manager.rb +++ b/solara/lib/core/scripts/platform/ios/infoplist_string_catalog_manager.rb @@ -1,105 +1,107 @@ class InfoPListStringCatalogManager - def update(result, path) - localizations = JSON.parse(File.read(path)) - - result.each do |base_key, languages| - # Ensure the base key exists in the localizations - if localizations['strings'].key?(base_key) - # Update each language value - languages.each do |lang_code, value| - # Initialize the localizations for the language if it doesn't exist - localizations['strings'][base_key]['localizations'][lang_code] ||= { - "stringUnit" => { - "state" => "new", - "value" => "" - } - } - # Update the value in the JSON structure - state = value === '' ? 'new' : 'translated' - localizations['strings'][base_key]['localizations'][lang_code]['stringUnit']['state'] = state - - localizations['strings'][base_key]['localizations'][lang_code]['stringUnit']['value'] = value - end - end - end - - # Remove any localization keys that are no longer in the result - localizations['strings'].each do |base_key, data| - data['localizations'].each_key do |lang_code| - unless result.key?(base_key) && result[base_key].key?(lang_code) - data['localizations'].delete(lang_code) - end - end - end - - File.write(path, JSON.pretty_generate(localizations)) - end + def update(result, brand_key) + data = load_string_catalog(brand_key) - def get_default_state_of(key, brand_key) - get_default_value(key, 'state', brand_key) - end + update_localizations(result, data) + remove_unused_localizations(result, data) - def get_default_value_of(key, brand_key) - get_default_value(key, 'value', brand_key) - end + File.write(FilePath.brand_infoplist_string_catalog(brand_key), JSON.pretty_generate(data)) + end - def get_default_value(key, target, brand_key) - path = FilePath.brand_infoplist_string_catalog(brand_key) + def get_state_of(key, brand_key, language:) + get_value(key, 'state', brand_key, language: language) + end - data = JSON.parse(File.read(path)) + def get_value_of(key, brand_key, language:) + get_value(key, 'value', brand_key, language: language) + end - # Get the source language - source_language = data['sourceLanguage'] - - # Retrieve the CFBundleDisplayName localizations value - data['strings'][key]['localizations'][source_language]['stringUnit'][target] - end - - def validate_required_keys(brand_key) - errors = [] - bundle_name = validate(InfoPListKey::BUNDLE_NAME, brand_key) - errors << Issue.error(bundle_name) if bundle_name - - bundle_display_name = validate(InfoPListKey::BUNDLE_DISPLAY_NAME, brand_key) - errors << Issue.error(bundle_display_name) if bundle_display_name - - errors - end + def validate_required_keys(brand_key) + data = load_string_catalog(brand_key) + source_language = source_language(brand_key) - private + issues = [] - def validate(key, brand_key) - unless has_value(key, brand_key) - message = "The value for '#{key}' is not translated in #{FilePath.brand_infoplist_string_catalog(brand_key)}. To resolve this issue, please open the dashboard and update the entry for '#{key}.#{source_language(brand_key)}'. If you prefer, you can manually add a value and mark its state as 'translated'." - return message + data['strings'].keys.each do |key| + languages = data['strings'][key]['localizations'].keys + languages.each do |language| + puts "language = #{language}" + validation_error = validate(key, brand_key, language: language) + # If the value of source language is missed, raise an error. Else, raise a warning. + issues << (language === source_language ? Issue.error(validation_error) : Issue.warning(validation_error)) if validation_error end - nil end - def has_bundle_name(brand_key) - get_default_state_of(InfoPListKey::BUNDLE_NAME, brand_key) != 'new' - end - - def has_bundle_display_name(brand_key) - get_default_state_of(InfoPListKey::BUNDLE_DISPLAY_NAME, brand_key) != 'new' - end + issues + end - def has_value(key, brand_key) - state = get_default_state_of(key, brand_key) - value = get_default_value_of(key, brand_key) - state != 'new' && value != '' - end + private - def source_language(brand_key) - path = FilePath.brand_infoplist_string_catalog(brand_key) + def load_string_catalog(brand_key) + JSON.parse(File.read(FilePath.brand_infoplist_string_catalog(brand_key))) + end - data = JSON.parse(File.read(path)) + def update_localizations(result, data) + result.each do |base_key, languages| + next unless data['strings'].key?(base_key) - # Get the source language - data['sourceLanguage'] + languages.each do |lang_code, value| + initialize_localization(data, base_key, lang_code) + update_string_unit(data, base_key, lang_code, value) + end end - + end + + def initialize_localization(data, base_key, lang_code) + data['strings'][base_key]['localizations'][lang_code] ||= { + "stringUnit" => { + "state" => "new", + "value" => "" + } + } + end + + def update_string_unit(data, base_key, lang_code, value) + state = value === '' ? 'new' : 'translated' + data['strings'][base_key]['localizations'][lang_code]['stringUnit'].merge!({ + 'state' => state, + 'value' => value + }) + end + + def remove_unused_localizations(result, data) + data['strings'].each do |base_key, data| + data['localizations'].each_key do |lang_code| + data['localizations'].delete(lang_code) unless result.dig(base_key, lang_code) + end + end + end + + def get_value(key, target, brand_key, language:) + data = load_string_catalog(brand_key) + lang = language || data['sourceLanguage'] + data['strings'][key]['localizations'][lang]['stringUnit'][target] + end + + def validate(key, brand_key, language:) + return nil if has_value(key, brand_key, language: language) + + "The value for '#{key}' is not translated in #{FilePath.brand_infoplist_string_catalog(brand_key)}. " \ + "To resolve this issue, please open the dashboard and update the entry for '#{key}.#{source_language(brand_key)}' in section 'iOS InfoPlist.xcstrings Configuration'. " \ + "If you prefer, you can manually add a value and mark its state as 'translated'." + end + + def has_value(key, brand_key, language:) + state = get_state_of(key, brand_key, language: language) + value = get_value_of(key, brand_key, language: language) + state != 'new' && value != '' + end + + def source_language(brand_key) + data = load_string_catalog(brand_key) + data['sourceLanguage'] + end end module InfoPListKey diff --git a/solara/lib/core/scripts/platform/ios/xcconfig_generator.rb b/solara/lib/core/scripts/platform/ios/xcconfig_generator.rb index 25defbc..4a9e8b2 100644 --- a/solara/lib/core/scripts/platform/ios/xcconfig_generator.rb +++ b/solara/lib/core/scripts/platform/ios/xcconfig_generator.rb @@ -41,7 +41,7 @@ def generate_xcconfig_content end def get_value_of(key) - InfoPListStringCatalogManager.new.get_default_value_of(key, @brand_key) + InfoPListStringCatalogManager.new.get_value_of(key, @brand_key, language: nil) end def load_config diff --git a/solara/lib/solara.rb b/solara/lib/solara.rb index 6aae48f..45a2d1c 100644 --- a/solara/lib/solara.rb +++ b/solara/lib/solara.rb @@ -143,6 +143,7 @@ def switch begin SolaraManager.new.switch(brand_key) rescue StandardError => e + Solara.logger.fatal("Switching to #{brand_key} failed. #{e}") Solara.logger.fatal("Switching to #{brand_key} failed.") exit 1 end