Skip to content

[#707] Add localization-ready structure by default#713

Open
ducbm051291 wants to merge 1 commit into
developfrom
feature/707-localization-ready-structure
Open

[#707] Add localization-ready structure by default#713
ducbm051291 wants to merge 1 commit into
developfrom
feature/707-localization-ready-structure

Conversation

@ducbm051291
Copy link
Copy Markdown
Contributor

What happened 👀

Added localization-ready structure to the SwiftUI template.

  • Added Localizable.xcstrings with default English starter strings.
  • Replaced hardcoded starter UI copy with localization keys.
  • Added localized copy for the session restore loading state.
  • Fixed CheckForceUpdateUseCase.defaultCurrentVersion() visibility because generated builds failed when using it as a public initializer default value.

Insight 📝

  • N/A

Proof Of Work 📹

Simulator.Screen.Recording.-.iPhone.17.-.2026-05-04.at.16.41.20.mov

@ducbm051291 ducbm051291 added this to the 4.13.0 milestone May 4, 2026
@ducbm051291 ducbm051291 self-assigned this May 4, 2026
@ducbm051291 ducbm051291 added the feature New feature or enhancement label May 4, 2026
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 4, 2026

Important

Review skipped

Auto reviews are disabled on base/target branches other than the default branch.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: ae696cb2-c085-4b6e-bd65-5f6fed43d9cd

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feature/707-localization-ready-structure

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@ducbm051291 ducbm051291 changed the base branch from develop to feature/710-native-navigationstack-routing-foundation May 4, 2026 09:51
@ducbm051291 ducbm051291 marked this pull request as ready for review May 4, 2026 09:51
@ducbm051291 ducbm051291 force-pushed the feature/707-localization-ready-structure branch from eb173ba to 00ad0e5 Compare May 5, 2026 02:34
@ducbm051291 ducbm051291 force-pushed the feature/710-native-navigationstack-routing-foundation branch from c3dc801 to 791b326 Compare May 5, 2026 03:39
@ducbm051291 ducbm051291 force-pushed the feature/707-localization-ready-structure branch from 00ad0e5 to 4693378 Compare May 5, 2026 03:39
Copy link
Copy Markdown
Contributor

@markgravity markgravity left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How about using SwiftGen to generate the localization key?

extension LocalizedStringKey {
  enum forceUpdate {
    /// Coming Soon
    static let title = LocalizedString(table: "Localizable", lookupKey: "forceUpdate.title").key
...

 Text("force_update.title") ->  Text(.forceUpdate.title)

stencil file

// Generated using SwiftGen — https://github.com/SwiftGen/SwiftGen

{% if tables.count > 0 %}
import SwiftUI

// MARK: - Strings

{% macro parametersBlock types %}{% filter removeNewlines:"leading" %}
  {% for type in types %}
    {% if type == "String" %}
    _ p{{forloop.counter}}: Any
    {% else %}
    _ p{{forloop.counter}}: {{type}}
    {% endif %}
    {{ ", " if not forloop.last }}
  {% endfor %}
{% endfilter %}{% endmacro %}
{% macro argumentsBlock types %}{% filter removeNewlines:"leading" %}
  {% for type in types %}
    {% if type == "String" %}
    String(describing: p{{forloop.counter}})
    {% elif type == "UnsafeRawPointer" %}
    Int(bitPattern: p{{forloop.counter}})
    {% else %}
    p{{forloop.counter}}
    {% endif %}
    {{ ", " if not forloop.last }}
  {% endfor %}
{% endfilter %}{% endmacro %}
{% macro recursiveBlock table item %}
  {% for string in item.strings %}
  {% if not param.noComments %}
  /// {{string.translation}}
  {% endif %}
  {% if string.types %}
  static func {{string.name|swiftIdentifier:"pretty"|lowerFirstWord|escapeReservedKeywords}}({% call parametersBlock string.types %}) -> LocalizedStringKey {
    return LocalizedString(table: "{{table}}", lookupKey: "{{string.key}}", args: {% call argumentsBlock string.types %}).key
  }
  {% else %}
  static let {{string.name|swiftIdentifier:"pretty"|lowerFirstWord|escapeReservedKeywords}} = LocalizedString(table: "{{table}}", lookupKey: "{{string.key}}").key
  {% endif %}
  {% endfor %}
  {% for child in item.children %}
  enum {{child.name|swiftIdentifier:"pretty"|lowerFirstWord|escapeReservedKeywords}} {
    {% filter indent:2 %}{% call recursiveBlock table child %}{% endfilter %}
  }
  {% endfor %}
{% endmacro %}
{% set enumName %}{{param.enumName|default:"L10n"}}{% endset %}

public extension LocalizedStringKey {
  {% for table in tables %}
  {% if table.name != "InfoPlist" %}
  {% call recursiveBlock table.name table.levels %}
  {% endif %}
  {% endfor %}
}

// MARK: - Implementation Details
{% if not param.withoutSupporter %}
fileprivate func tr(_ table: String, _ key: String, _ locale: Locale = Locale.current, _ args: CVarArg...) -> String {
  let path = Bundle.main.path(forResource: locale.identifier, ofType: "lproj") ?? ""
  let format: String
  if let bundle = Bundle(path: path) {
    format = NSLocalizedString(key, tableName: table, bundle: bundle, comment: "")
  } else {
    format = BundleToken.bundle.localizedString(forKey: key, value: nil, table: table)
  }
  return String(format: format, locale: locale, arguments: args)
}

public struct LocalizedString: Hashable {
  let table: String
  let lookupKey: String
  fileprivate let args: [CVarArg]

  init(table: String, lookupKey: String, args: CVarArg...) {
    self.table = table
    self.lookupKey = lookupKey
    self.args = args
  }

  var key: LocalizedStringKey {
    var string = LocalizedStringKey.StringInterpolation(literalCapacity: 1, interpolationCount: 1)
    string.appendLiteral(lookupKey.replacingOccurrences(of: "%@", with: ""))
    args.forEach {
      string.appendInterpolation("\($0)")
    }

    return LocalizedStringKey(stringInterpolation: string)
  }

  var text: String {
    tr(table, lookupKey, .current, args)
  }

  func text(withLocale locale: Locale) -> String {
    tr(table, lookupKey, locale, args)
  }

  public func hash(into hasher: inout Hasher) {
    hasher.combine(lookupKey)
    hasher.combine(table)
  }

  public static func == (lhs: LocalizedString, rhs: LocalizedString) -> Bool {
    lhs.table == rhs.table && lhs.lookupKey == rhs.lookupKey
  }
}
public extension LocalizedStringKey {
    func translate(args: CVarArg...) -> String {
        tr("Localizable", stringKey, .current, args)
    }

    private var stringKey: String {
        //use reflection
        let mirror = Mirror(reflecting: self)

        //try to find 'key' attribute value
        let attributeLabelAndValue = mirror.children.first { (arg0) -> Bool in
            let (label, _) = arg0
            if(label == "key"){
                return true;
            }
            return false;
        }

        if(attributeLabelAndValue != nil) {
            //ask for localization of found key via NSLocalizedString
            return String.localizedStringWithFormat(NSLocalizedString(attributeLabelAndValue!.value as! String, comment: ""));
        }
        else {
            return "Swift LocalizedStringKey signature must have changed. @see Apple documentation."
        }
    }
}

{% endif %}
{% if not param.bundle %}

private final class BundleToken {
  static let bundle: Bundle = {
    #if SWIFT_PACKAGE
      return Bundle.module
    #else
      return Bundle(for: BundleToken.self)
    #endif
  }()
}
{% endif %}
{% else %}
// No string found
{% endif %}

@ducbm051291 ducbm051291 force-pushed the feature/707-localization-ready-structure branch from 4693378 to de8525f Compare May 5, 2026 06:18
Base automatically changed from feature/710-native-navigationstack-routing-foundation to develop May 7, 2026 02:15
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

feature New feature or enhancement

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Feature] Add localization-ready structure by default

3 participants