Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Reduce amount of requests to download localization #307

Open
wants to merge 22 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
db80c22
Add saving donwloaded files timestamps.
serhii-londar Aug 31, 2024
f256ed6
Add minimum update interval for downloading manifest.
serhii-londar Sep 1, 2024
51e3936
Update ManifestManagerTests.swift
serhii-londar Sep 1, 2024
50b9006
Move saving last manifest update timestamp to FileTimestampStorage.
serhii-londar Sep 1, 2024
1dc24d3
Merge branch 'master' into #310-1
serhii-londar Dec 8, 2024
8fbd954
Fix example app build error.
serhii-londar Dec 8, 2024
05e68dc
Fix downloading mapping files twice when screenshots and realtime upd…
serhii-londar Dec 8, 2024
8ccec53
Update ManifestManagerTests.swift
serhii-londar Dec 8, 2024
8158ce2
Merge branch 'master' into #310-1
serhii-londar Dec 9, 2024
1ced5a5
Update ManifestManager.swift
serhii-londar Feb 2, 2025
2b8aefb
Fix issue with saving e-tags for xcstrings file.
serhii-londar Feb 2, 2025
925d204
Merge branch 'master' into #310-1
serhii-londar Feb 2, 2025
36b9f1b
Fix saving timestamps for xcstrings files to prevent downloading them…
serhii-londar Feb 2, 2025
bfa951b
Merge branch '#322' into #310-1
serhii-londar Feb 2, 2025
7d0a750
Merge branch 'master' into #310-1
serhii-londar Feb 8, 2025
6c58936
Use latest stable xcode to try to fix crashes during tests launch.
serhii-londar Feb 8, 2025
8ae46a2
Use macos-15 and same simulator name.
serhii-londar Feb 11, 2025
892f77c
Use xcode-15
serhii-londar Feb 12, 2025
82249c6
Use iPhone 16 as destination name.
serhii-londar Feb 12, 2025
b3426aa
Set test job as dependency for pod-lint
serhii-londar Feb 12, 2025
2d3fa05
Run pod lint on pull request.
serhii-londar Feb 12, 2025
cfccfcc
Run swiftlint and pod lint only for pull requests.
serhii-londar Feb 12, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 15 additions & 12 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ on:
jobs:
danger-swiftlint:
name: Danger SwiftLint
runs-on: macos-latest
runs-on: macos-15
continue-on-error: true
if: github.event_name == 'pull_request'
env:
Expand All @@ -36,7 +36,9 @@ jobs:

pod-lint:
name: PodLint
runs-on: macos-latest
needs: test
runs-on: macos-15
if: github.event_name == 'pull_request'
steps:
- uses: actions/checkout@v4

Expand All @@ -45,17 +47,17 @@ jobs:
run: pod install

- name: Lint pod library
run: pod lib lint --skip-tests --platforms=macos,ios,tvos
run: pod lib lint --skip-tests --platforms=macos,ios,tvos --allow-warnings

spm-build:
name: Build Swift Package
runs-on: macos-latest
runs-on: macos-15
strategy:
matrix:
platform: [iOS, macOS, tvOS, watchOS]
include:
- platform: iOS
destination: 'platform=iOS Simulator,name=iPhone 15'
destination: 'platform=iOS Simulator,name=iPhone 16'
- platform: macOS
destination: 'platform=macOS'
- platform: tvOS
Expand All @@ -67,7 +69,7 @@ jobs:

- uses: maxim-lobanov/setup-xcode@v1
with:
xcode-version: 15.4.0
xcode-version: 16.0

- name: Install xcbeautify
run: brew install xcbeautify
Expand All @@ -78,7 +80,8 @@ jobs:

build:
name: Build
runs-on: macos-latest
needs: spm-build
runs-on: macos-15
steps:
- uses: actions/checkout@v4

Expand All @@ -91,7 +94,7 @@ jobs:

- uses: maxim-lobanov/setup-xcode@v1
with:
xcode-version: 15.4.0
xcode-version: 16.0

- name: Build
working-directory: Example
Expand All @@ -100,14 +103,14 @@ jobs:
-workspace ./AppleReminders.xcworkspace \
-scheme AppleReminders \
-configuration Debug \
-destination 'platform=iOS Simulator,name=iPhone SE (3rd generation)' | xcbeautify && exit ${PIPESTATUS[0]}
-destination 'platform=iOS Simulator,name=iPhone 16' | xcbeautify && exit ${PIPESTATUS[0]}

# TODO: ObjCExample

test:
name: Test
needs: build
runs-on: macos-latest
runs-on: macos-15
steps:
- uses: actions/checkout@v4

Expand All @@ -120,7 +123,7 @@ jobs:

- uses: maxim-lobanov/setup-xcode@v1
with:
xcode-version: 15.4.0
xcode-version: 16.0

- name: Tests
working-directory: Tests
Expand All @@ -130,7 +133,7 @@ jobs:
-workspace ./Tests.xcworkspace \
-scheme Tests \
-configuration Debug \
-destination 'platform=iOS Simulator,name=iPhone SE (3rd generation)' \
-destination 'platform=iOS Simulator,name=iPhone 16' \
-enableCodeCoverage YES | xcbeautify && exit ${PIPESTATUS[0]}

- name: Upload coverage to Codecov
Expand Down
2 changes: 2 additions & 0 deletions Example/AppleReminders.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -837,6 +837,7 @@
CODE_SIGN_STYLE = Automatic;
DEVELOPMENT_TEAM = C66Y3DM74C;
INFOPLIST_FILE = AppleReminders/Info.plist;
IPHONEOS_DEPLOYMENT_TARGET = 18.0;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
Expand All @@ -856,6 +857,7 @@
CODE_SIGN_STYLE = Automatic;
DEVELOPMENT_TEAM = C66Y3DM74C;
INFOPLIST_FILE = AppleReminders/Info.plist;
IPHONEOS_DEPLOYMENT_TARGET = 18.0;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
Expand Down
7 changes: 6 additions & 1 deletion Example/AppleReminders/AppDelegate.swift
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,17 @@
//

import UIKit
import DebugSwift

@UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
// Override point for customization after application launch.
true

DebugSwift.setup()
DebugSwift.show()

return true
}

// MARK: UISceneSession Lifecycle
Expand Down
7 changes: 4 additions & 3 deletions Example/AppleReminders/SceneDelegate.swift
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,8 @@
//

import UIKit
import DebugSwift
import CrowdinSDK
import netfox

class SceneDelegate: UIResponder, UIWindowSceneDelegate {
var window: UIWindow?
Expand All @@ -25,8 +25,6 @@ class SceneDelegate: UIResponder, UIWindowSceneDelegate {
func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
// Check for launch arguments

NFX.sharedInstance().start()

let arguments = ProcessInfo.processInfo.arguments

let isTesting = arguments.contains("UI_TESTING")
Expand Down Expand Up @@ -60,6 +58,9 @@ class SceneDelegate: UIResponder, UIWindowSceneDelegate {
return

} else {
DebugSwift.setup()
DebugSwift.show()

let loginConfig = try! CrowdinLoginConfig(clientId: Self.clientId,
clientSecret: Self.clientSecret,
scope: "project")
Expand Down
2 changes: 1 addition & 1 deletion Example/Podfile
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ target 'AppleReminders' do
pod 'RealmSwift'
pod 'SwiftDate'
pod 'SwiftLint'
pod 'netfox', :configurations => ['Debug']
pod 'DebugSwift', :configurations => ['Debug']

pod 'CrowdinSDK', :path => '../'
pod 'CrowdinSDK/Settings', :path => '../'
Expand Down
14 changes: 7 additions & 7 deletions Example/Podfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -51,29 +51,29 @@ PODS:
- CrowdinSDK/RealtimeUpdate
- CrowdinSDK/RefreshLocalization
- CrowdinSDK/Screenshots
- netfox (1.21.0)
- DebugSwift (0.3.6)
- Realm (10.42.0):
- Realm/Headers (= 10.42.0)
- Realm/Headers (10.42.0)
- RealmSwift (10.42.0):
- Realm (= 10.42.0)
- Starscream (4.0.8)
- Starscream (4.0.6)
- SwiftDate (7.0.0)
- SwiftLint (0.52.4)

DEPENDENCIES:
- CrowdinSDK (from `../`)
- CrowdinSDK/CrowdinXCTestScreenshots (from `../`)
- CrowdinSDK/Settings (from `../`)
- netfox
- DebugSwift
- RealmSwift
- SwiftDate
- SwiftLint

SPEC REPOS:
trunk:
- BaseAPI
- netfox
- DebugSwift
- Realm
- RealmSwift
- Starscream
Expand All @@ -87,13 +87,13 @@ EXTERNAL SOURCES:
SPEC CHECKSUMS:
BaseAPI: 7d1c79778a5c85f8e05f5e5c9d7f9be6474a53eb
CrowdinSDK: 7b2b2449c735eb24b94082c92b61e1503abdaeac
netfox: 9d5cc727fe7576c4c7688a2504618a156b7d44b7
DebugSwift: f766d934affddea9ffe36b0cf2631cd28311481f
Realm: 490aad28f1360e58fc22256d5d686d3a36525346
RealmSwift: f6a9b56d747bbdd7931de1835896c5f024b6898a
Starscream: 19b5533ddb925208db698f0ac508a100b884a1b9
Starscream: fb2c4510bebf908c62bd383bcf05e673720e91fd
SwiftDate: bbc26e26fc8c0c33fbee8c140c5e8a68293a148a
SwiftLint: 1cc5cd61ba9bacb2194e340aeb47a2a37fda00b3

PODFILE CHECKSUM: 74f10a9ccf1bc87b1bf0889ac0fedbdb04ae9ef0
PODFILE CHECKSUM: 42867966e84bd43e7bb55bce756fdd985f958362

COCOAPODS: 1.15.2
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ extension CrowdinSDK {
guard let config = CrowdinSDK.config else { return }
let crowdinProviderConfig = config.crowdinProviderConfig ?? CrowdinProviderConfig()
if config.realtimeUpdatesEnabled {
RealtimeUpdateFeature.shared = RealtimeUpdateFeature(hash: crowdinProviderConfig.hashString, sourceLanguage: crowdinProviderConfig.sourceLanguage, organizationName: config.crowdinProviderConfig?.organizationName, loginFeature: CrowdinSDK.loginFeature)
RealtimeUpdateFeature.shared = RealtimeUpdateFeature(hash: crowdinProviderConfig.hashString, sourceLanguage: crowdinProviderConfig.sourceLanguage, organizationName: config.crowdinProviderConfig?.organizationName, minimumManifestUpdateInterval: crowdinProviderConfig.minimumManifestUpdateInterval, loginFeature: CrowdinSDK.loginFeature)
swizzleControlMethods()
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,11 +19,11 @@ class RURemoteLocalizationStorage: RemoteLocalizationStorageProtocol {
let hash: String
let fileDownloader: RUFilesDownloader
let manifestManager: ManifestManager

init(localization: String, sourceLanguage: String, hash: String, projectId: String, organizationName: String?) {
init(localization: String, sourceLanguage: String, hash: String, projectId: String, organizationName: String?, minimumManifestUpdateInterval: TimeInterval) {
self.localization = localization
self.hash = hash
manifestManager = ManifestManager.manifest(for: hash, sourceLanguage: sourceLanguage, organizationName: organizationName)
manifestManager = ManifestManager.manifest(for: hash, sourceLanguage: sourceLanguage, organizationName: organizationName, minimumManifestUpdateInterval: minimumManifestUpdateInterval)
self.fileDownloader = RUFilesDownloader(projectId: projectId, organizationName: organizationName, manifestManager: manifestManager, loginFeature: CrowdinSDK.loginFeature)
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,14 +14,14 @@ import Foundation

protocol RealtimeUpdateFeatureProtocol {
static var shared: RealtimeUpdateFeatureProtocol? { get set }

var success: (() -> Void)? { get set }
var error: ((Error) -> Void)? { set get }
var disconnect: (() -> Void)? { get set }
var enabled: Bool { get set }

init(hash: String, sourceLanguage: String, organizationName: String?, loginFeature: AnyLoginFeature?)

init(hash: String, sourceLanguage: String, organizationName: String?, minimumManifestUpdateInterval: TimeInterval, loginFeature: AnyLoginFeature?)
func start()
func stop()
func subscribe(control: Refreshable)
Expand All @@ -39,10 +39,10 @@ class RealtimeUpdateFeature: RealtimeUpdateFeatureProtocol {
let localizations = Localization.current.provider.remoteStorage.localizations
return CrowdinSDK.currentLocalization ?? Bundle.main.preferredLanguage(with: localizations)
}
var hashString: String
let hashString: String
let sourceLanguage: String
let organizationName: String?

let minimumManifestUpdateInterval: TimeInterval
var distributionResponse: DistributionsResponse? = nil

var active: Bool { return socketManger?.active ?? false }
Expand All @@ -59,13 +59,14 @@ class RealtimeUpdateFeature: RealtimeUpdateFeatureProtocol {
private var socketManger: CrowdinSocketManagerProtocol?
private var mappingManager: CrowdinMappingManagerProtocol
private let loginFeature: AnyLoginFeature?

required init(hash: String, sourceLanguage: String, organizationName: String?, loginFeature: AnyLoginFeature?) {
required init(hash: String, sourceLanguage: String, organizationName: String?, minimumManifestUpdateInterval: TimeInterval, loginFeature: AnyLoginFeature?) {
self.hashString = hash
self.sourceLanguage = sourceLanguage
self.organizationName = organizationName
self.loginFeature = loginFeature
self.mappingManager = CrowdinMappingManager(hash: hash, sourceLanguage: sourceLanguage, organizationName: organizationName)
self.minimumManifestUpdateInterval = minimumManifestUpdateInterval
self.mappingManager = CrowdinMappingManager.shared(hash: hash, sourceLanguage: sourceLanguage, organizationName: organizationName, minimumManifestUpdateInterval: minimumManifestUpdateInterval)
}

func downloadDistribution(with successHandler: (() -> Void)? = nil, errorHandler: ((Error) -> Void)? = nil) {
Expand Down Expand Up @@ -124,7 +125,7 @@ class RealtimeUpdateFeature: RealtimeUpdateFeatureProtocol {
}
setupRealtimeUpdatesLocalizationProvider(with: projectId) { [weak self] in
guard let self = self else { return }
self.setupSocketManager(with: projectId, projectWsHash: projectWsHash, userId: userId, wsUrl: wsUrl)
self.setupSocketManager(with: projectId, projectWsHash: projectWsHash, userId: userId, wsUrl: wsUrl, minimumManifestUpdateInterval: minimumManifestUpdateInterval)
}
}

Expand All @@ -140,8 +141,7 @@ class RealtimeUpdateFeature: RealtimeUpdateFeatureProtocol {

func setupRealtimeUpdatesLocalizationProvider(with projectId: String, completion: @escaping () -> Void) {
oldProvider = Localization.current.provider
Localization.current.provider = LocalizationProvider(localization: self.localization, localStorage: RULocalLocalizationStorage(localization: self.localization), remoteStorage: RURemoteLocalizationStorage(localization: self.localization, sourceLanguage: sourceLanguage, hash: self.hashString, projectId: projectId, organizationName: self.organizationName))

Localization.current.provider = LocalizationProvider(localization: self.localization, localStorage: RULocalLocalizationStorage(localization: self.localization), remoteStorage: RURemoteLocalizationStorage(localization: self.localization, sourceLanguage: sourceLanguage, hash: self.hashString, projectId: projectId, organizationName: self.organizationName, minimumManifestUpdateInterval: self.minimumManifestUpdateInterval))
Localization.current.provider.refreshLocalization { [weak self] error in
guard let self = self else { return }
if let error = error {
Expand All @@ -166,14 +166,14 @@ class RealtimeUpdateFeature: RealtimeUpdateFeatureProtocol {
self.refreshAllControls()
}
}

func setupSocketManager(with projectId: String, projectWsHash: String, userId: String, wsUrl: String) {
func setupSocketManager(with projectId: String, projectWsHash: String, userId: String, wsUrl: String, minimumManifestUpdateInterval: TimeInterval) {
// Download manifest if it is not initialized.
let manifestManager = ManifestManager.manifest(for: hashString, sourceLanguage: sourceLanguage, organizationName: organizationName)
guard manifestManager.downloaded else {
let manifestManager = ManifestManager.manifest(for: hashString, sourceLanguage: sourceLanguage, organizationName: organizationName, minimumManifestUpdateInterval: minimumManifestUpdateInterval)
guard manifestManager.available else {
manifestManager.download { [weak self] in
guard let self = self else { return }
self.setupSocketManager(with: projectId, projectWsHash: projectWsHash, userId: userId, wsUrl: wsUrl)
self.setupSocketManager(with: projectId, projectWsHash: projectWsHash, userId: userId, wsUrl: wsUrl, minimumManifestUpdateInterval: minimumManifestUpdateInterval)
}
return
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ extension CrowdinSDK {
guard let config = CrowdinSDK.config else { return }
if config.screenshotsEnabled {
let crowdinProviderConfig = config.crowdinProviderConfig ?? CrowdinProviderConfig()
let screenshotUploader = CrowdinScreenshotUploader(organizationName: config.crowdinProviderConfig?.organizationName, hash: crowdinProviderConfig.hashString, sourceLanguage: crowdinProviderConfig.sourceLanguage, loginFeature: CrowdinSDK.loginFeature)
let screenshotUploader = CrowdinScreenshotUploader(organizationName: config.crowdinProviderConfig?.organizationName, hash: crowdinProviderConfig.hashString, sourceLanguage: crowdinProviderConfig.sourceLanguage, minimumManifestUpdateInterval: crowdinProviderConfig.minimumManifestUpdateInterval, loginFeature: CrowdinSDK.loginFeature)
ScreenshotFeature.shared = ScreenshotFeature(screenshotUploader: screenshotUploader, screenshotProcessor: CrowdinScreenshotProcessor())
swizzleControlMethods()
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,12 +40,12 @@ class CrowdinScreenshotUploader: ScreenshotUploader {
case unknownError = "Unknown error."
case noLocalizedStringsDetected = "There are no localized strings detected on current screen."
}

init(organizationName: String?, hash: String, sourceLanguage: String, loginFeature: AnyLoginFeature?) {
init(organizationName: String?, hash: String, sourceLanguage: String, minimumManifestUpdateInterval: TimeInterval, loginFeature: AnyLoginFeature?) {
self.organizationName = organizationName
self.hash = hash
self.sourceLanguage = sourceLanguage
self.mappingManager = CrowdinMappingManager(hash: hash, sourceLanguage: sourceLanguage, organizationName: organizationName)
self.mappingManager = CrowdinMappingManager.shared(hash: hash, sourceLanguage: sourceLanguage, organizationName: organizationName, minimumManifestUpdateInterval: minimumManifestUpdateInterval)
self.loginFeature = loginFeature
self.storageAPI = StorageAPI(organizationName: organizationName, auth: loginFeature)
}
Expand Down
Loading
Loading