Skip to content

feat: update localization handling and improve UI responsiveness#378

Open
serhii-londar wants to merge 3 commits into#367from
example-app-adjustments
Open

feat: update localization handling and improve UI responsiveness#378
serhii-londar wants to merge 3 commits into#367from
example-app-adjustments

Conversation

@serhii-londar
Copy link
Collaborator

@serhii-londar serhii-londar commented Feb 22, 2026

Note

Medium Risk
Touches localization selection and app root controller rebuilding; mistakes could cause incorrect language state, UI flicker, or regressions in startup/search behavior.

Overview
Improves localization switching to be async and state-safe: SettingsVC now blocks repeated taps, shows a loading spinner, applies localization via CrowdinSDK.setCurrentLocalization(...completion:), and refreshes the whole UI on success (with error alerting on failure).

Refactors app startup/localization refresh plumbing: SceneDelegate centralizes window/root setup in setupMainInterface and adds reloadLocalizedUI() to rebuild the root controller with a cross-dissolve, while UI tests and launch-arg handling migrate from direct currentLocalization assignment to the async setter.

Tightens main screen layout/search behavior: MainVC pins the footer to the safe area, dynamically adjusts table insets to avoid content underlapping the footer, fixes header sizing in viewDidLayoutSubviews, ensures the footer stays on top, and delays setupSearch() until after the diffable datasource is configured (with updated search presentation settings).

Written by Cursor Bugbot for commit 6d79232. This will update automatically on new commits. Configure here.

Copilot AI review requested due to automatic review settings February 22, 2026 16:23
Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This pull request refactors localization handling in the Crowdin iOS SDK and improves UI responsiveness in the example app. The main changes include deprecating the currentLocalization property setter in favor of setCurrentLocalization(_:completion:), adding async completion handlers for localization changes, and enhancing the example app's UI to provide loading feedback during localization updates.

Changes:

  • Deprecated the currentLocalization property setter and introduced setCurrentLocalization(_:completion:) as the preferred API for changing localization with async completion support
  • Refactored localization lookup to use Localization.currentLocalization directly instead of accessing through CrowdinSDK.currentLocalization
  • Enhanced the example app's Settings UI with loading indicators, disabled state during localization changes, and animated UI reloading
  • Improved MainVC layout with dynamic footer inset calculation and search bar repositioned as table header view

Reviewed changes

Copilot reviewed 8 out of 8 changed files in this pull request and generated 10 comments.

Show a summary per file
File Description
Sources/CrowdinSDK/CrowdinSDK/CrowdinSDK.swift Deprecated currentLocalization setter, added setCurrentLocalization(_:completion:) and deprecated setLocalization(_:completion:) alias
Sources/CrowdinSDK/Providers/Crowdin/Extensions/CrowdinSDK+CrowdinProvider.swift Updated to use Localization.currentLocalization instead of CrowdinSDK.currentLocalization
Sources/CrowdinSDK/Providers/Crowdin/CrowdinRemoteLocalizationStorage.swift Modified localization detection logic with fallback chain
Sources/CrowdinSDK/Features/RealtimeUpdateFeature/RealtimeUpdateFeature.swift Updated localization property with extended fallback chain
Example/AppleRemindersUITests/AppleRemindersUITestsCrowdinScreenhsotTests.swift Updated to use new setCurrentLocalization API
Example/AppleReminders/SceneDelegate.swift Refactored UI setup into reusable method with animation support and added reloadLocalizedUI()
Example/AppleReminders/Controllers/SettingsVC.swift Added loading indicator, error handling, and async completion support for localization changes
Example/AppleReminders/Controllers/MainVC.swift Changed search bar to table header view, added dynamic footer inset calculation, and improved layout

/// - Parameters:
/// - localization: Localization code to use. If `nil`, localization will be auto-detected.
/// - completion: Completion handler called when localization refresh finishes.
@available(*, deprecated, message: "Please use setCurrentLocalization(_:completion:) instead.")
Copy link

Copilot AI Feb 22, 2026

Choose a reason for hiding this comment

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

The new setLocalization method is immediately deprecated with a message pointing to setCurrentLocalization(_:completion:). Since this is a new addition, consider not adding it at all or removing the deprecation marker. Adding a method as deprecated doesn't provide value to users.

Suggested change
@available(*, deprecated, message: "Please use setCurrentLocalization(_:completion:) instead.")

Copilot uses AI. Check for mistakes.
Comment on lines +118 to +138
private func setupMainInterface(for windowScene: UIWindowScene, animated: Bool) {
let navController = UINavigationController(rootViewController: MainVC())

if window == nil {
// source: https://www.youtube.com/watch?v=Htn4h51BQsk
window = UIWindow(frame: windowScene.coordinateSpace.bounds)
window?.windowScene = windowScene
}

guard let window = window else { return }

if animated {
UIView.transition(with: window, duration: 0.2, options: [.transitionCrossDissolve, .allowAnimatedContent], animations: {
window.rootViewController = navController
})
} else {
window.rootViewController = navController
}

window.makeKeyAndVisible()
}
Copy link

Copilot AI Feb 22, 2026

Choose a reason for hiding this comment

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

The setupMainInterface method recreates the navigation controller and root view controller every time it's called. When called with animated: true from reloadLocalizedUI(), this will lose the current navigation state (e.g., if the user is deep in a navigation stack, they'll be pushed back to the root). Consider preserving the navigation state or only reloading the localized strings in the existing view hierarchy instead of recreating the entire interface.

Copilot uses AI. Check for mistakes.
Comment on lines +108 to +112
/// Method for changing SDK localization and getting notified when localization refresh completes.
///
/// - Parameters:
/// - localization: Localization code to use. If `nil`, localization will be auto-detected.
/// - completion: Completion handler called when localization refresh finishes.
Copy link

Copilot AI Feb 22, 2026

Choose a reason for hiding this comment

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

This method has identical documentation to setCurrentLocalization (lines 99-103). Since this is a deprecated alias that delegates to setCurrentLocalization, the documentation is redundant. Consider simplifying the documentation to just reference the preferred method, or remove this method entirely since it's being added as already deprecated.

Suggested change
/// Method for changing SDK localization and getting notified when localization refresh completes.
///
/// - Parameters:
/// - localization: Localization code to use. If `nil`, localization will be auto-detected.
/// - completion: Completion handler called when localization refresh finishes.
/// Deprecated alias for `setCurrentLocalization(_:completion:)`.
///
/// - Note: Use ``setCurrentLocalization(_:completion:)`` instead.

Copilot uses AI. Check for mistakes.
Comment on lines 35 to 36
var localization: String {
let localizations = Localization.current.provider.remoteStorage.localizations
Copy link

Copilot AI Feb 22, 2026

Choose a reason for hiding this comment

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

The code accesses Localization.current.provider.remoteStorage.localizations without checking if Localization.current is nil. Since this is marked as an implicitly unwrapped optional in the Localization class, it could crash if accessed before SDK initialization. While this appears to be in the localization property which is computed, it should be safe if only called after initialization, but consider adding a safety check or documenting the initialization requirement.

Copilot uses AI. Check for mistakes.
// Only update localization if it wasn't explicitly set and if CrowdinSDK has a current localization
// or if the current localization is not in the available localizations
if let currentLocalization = CrowdinSDK.currentLocalization,
if let currentLocalization = Localization.currentLocalization ?? Localization.current?.provider.localization,
Copy link

Copilot AI Feb 22, 2026

Choose a reason for hiding this comment

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

This line has a logical issue. The original code checked if CrowdinSDK.currentLocalization was not nil, but the new code changes the condition to always try to get a value via the nil-coalescing operator. This changes the behavior: if Localization.currentLocalization is nil, it now falls back to Localization.current?.provider.localization, meaning the condition will always be true if the provider is initialized, even when no explicit localization was set. The original intent was to only update the localization if it was explicitly set by the user. Consider reverting to check only Localization.currentLocalization without the fallback.

Copilot uses AI. Check for mistakes.
guard localization != CrowdinSDK.currentLocalization else {
self.tableView.reloadData()
return
}
Copy link

Choose a reason for hiding this comment

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

Guard never skips redundant "Auto" localization change

Medium Severity

The guard localization != CrowdinSDK.currentLocalization is meant to skip redundant localization changes, but it never triggers for the "Auto" option. localizationCode(for:) returns nil for row 0, while CrowdinSDK.currentLocalization always returns a non-nil effective language string (because its getter falls back to Localization.current?.provider.localization). So nil != "en" is always true, causing an unnecessary async refresh, loading spinner, and full root view controller replacement when the user taps "Auto" while already in auto mode.

Fix in Cursor Fix in Web

@codecov
Copy link

codecov bot commented Feb 22, 2026

Codecov Report

❌ Patch coverage is 60.00000% with 2 lines in your changes missing coverage. Please review.
✅ Project coverage is 56.19%. Comparing base (8d78687) to head (6d79232).

Files with missing lines Patch % Lines
Sources/CrowdinSDK/CrowdinSDK/CrowdinSDK.swift 33.34% 2 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             #367     #378      +/-   ##
==========================================
- Coverage   56.22%   56.19%   -0.03%     
==========================================
  Files         136      136              
  Lines        6377     6379       +2     
==========================================
- Hits         3585     3584       -1     
- Misses       2792     2795       +3     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

Copy link

@cursor cursor bot left a comment

Choose a reason for hiding this comment

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

Cursor Bugbot has reviewed your changes and found 1 potential issue.

Bugbot Autofix is OFF. To automatically fix reported issues with Cloud Agents, enable Autofix in the Cursor dashboard.

var localization: String {
let localizations = Localization.current.provider.remoteStorage.localizations
return CrowdinSDK.currentLocalization ?? Bundle.main.preferredLanguage(with: localizations)
return Localization.currentLocalization ?? Bundle.main.preferredLanguage(with: localizations)
Copy link

Choose a reason for hiding this comment

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

Missing localization fallback in RealtimeUpdateFeature

Medium Severity

The replacement of CrowdinSDK.currentLocalization with Localization.currentLocalization drops the Localization.current?.provider.localization fallback. The old expression expanded to a three-step chain (Localization.currentLocalization ?? Localization.current?.provider.localization ?? Bundle.main.preferredLanguage(with:)), but the new code only has two steps. In "Auto" mode, the provider's resolved localization is skipped, and Bundle.main.preferredLanguage(with:) is called with only the remote localizations (not remote + bundle), potentially resolving to a different language than the rest of the SDK.

Fix in Cursor Fix in Web

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants