示例代码🔗https://github.com/gewill/BlogCodes/tree/main/Localizable%20in%20SwiftPM

在处理SwiftPM中本地化时,尝试了几种方案。先说结论Rswift preferredLanguage方案最佳。

方案一:Local

在SwiftUI中使用local可行,但是在SwiftPM会被宿主应用中覆写。不过也是小问题,只要命名规范,按照模块页面功能前缀来的话,一般也不会出现key重复的问题。

这里也是用到了Rswift自动生成的key,避免复制粘贴字符串类型的key

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// SwiftUI view			
Section {
Text("Change locale").font(.title)
Text("Will be overwrite by host app!").foregroundColor(.pink)
Button(action: {
viewModel.locale = Locale(identifier: Language.en.rawValue)
}, label: {
Text("Change locale english")
})
Button(action: {
viewModel.locale = Locale(identifier: Language.zh_Hans.rawValue)
}, label: {
Text("Change locale chinese simplified")
})
Text(LocalizedStringKey(R.string.localizable.hello_world.key.description))
} header: {
Text("Change locale")
}
.environment(\.locale, viewModel.locale)

方案二:Rswift preferredLanguage

目前是比较完善的方案。配合 AppLocale 可以全局切换语言。

利用Rswift可处理key和bundle的问题,还优化了SwiftUI.Text的使用体验,直接使用init即可。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
class AppLocale {
let preferredLanguage = CurrentValueSubject<Language, Never>(.en)
var preferredString: _R.string {
R.string(preferredLanguages: [preferredLanguage.value.rawValue])
}

static var shared = AppLocale()
private init() {}
}

// SwiftUI view
Section {
Text("Preferred Languages \(viewModel.preferredLanguage.displayTitle)")
Picker("Preferred Languages", selection: $viewModel.preferredLanguage) {
ForEach(Language.allCases) {
Text($0.displayTitle)
}
}
.pickerStyle(.segmented)
Text(AppLocale.shared.preferredString.localizable.hello_world)
} header: {
Text("Change R.string Preferred Languages")
}

最轻量级集成方式在ViewModel订阅AppLocale.shared.preferredLanguage,更新self.objectWillChange.send(),即可响应语言切换。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
class HomeViewModel: ObservableObject {
init() {
AppLocale.shared.preferredLanguage
.removeDuplicates()
.sink(receiveValue: { _ in
guard let self else { return }
self.objectWillChange.send()
})
.store(in: &cancelables)
}
}

struc HomeView: View {
@StateObject var viewModel = HomeViewModel()

var body: some View {
Text(AppLocale.shared.preferredString.localizable.hello_world)
}
}

方案三:liamnichols / xcstrings-tool

可用,但是仅支持 iOS16+。有个小坑SwiftPM集成时,官方教程的有错误,正确的git地址为:

1
2
3
4
// 1. Add the xcstrings-tool Package dependency
.package(url: "https://github.com/liamnichols/xcstrings-tool.git", from: "0.1.0")
// 2. Or use the repo is essentially a mirror of the main repository however the xcstrings-tool command line interface is a binary dependency that significantly simplifies your build graph and improves compile times.
.package(url: "https://github.com/liamnichols/xcstrings-tool-plugin.git", from: "0.1.0")

具体参考官方的示例:https://github.com/liamnichols/xcstrings-tool-demo

serverTrustEvaluationFailed 错误

1
2
3
4
5
6
7
8
Printing description of error:
▿ AFError
▿ serverTrustEvaluationFailed : 1 element
▿ reason : ServerTrustFailureReason
▿ noRequiredEvaluator : 1 element
- host : "***"
(lldb) po error.debugDescription
"Server trust evaluation failed due to reason: A ServerTrustEvaluating value is required for host *** but none was found."

原因是配置了白名单

1
2
3
4
5
6
7
8
9
10
11
var evaluators: [String: ServerTrustEvaluating] = [:]

let evaluators: [String: ServerTrustEvaluating] = [
"*.yourdomain.com": PinnedCertificatesTrustEvaluator()
]
// 白名单 allHostsMustBeEvaluated: true
let serverTrust = ServerTrustManager(allHostsMustBeEvaluated: true,
evaluators: evaluators)

session = Alamofire.Session(configuration: configuration,
serverTrustManager: serverTrust)

可把 allHostsMustBeEvaluated 改为 false,只对指定的host开启。

原理 TLS Server Trust

https://github.com/Alamofire/Alamofire/blob/master/Documentation/AdvancedUsage.md#evaluating-server-trusts-with-servertrustmanager-and-servertrustevaluating

在与服务器和 Web 服务通信时使用安全的 HTTPS 连接是保护敏感数据的重要步骤。默认情况下,Alamofire 会收到与 URLSession 相同的自动 TLS 证书和证书链验证。虽然这保证了证书链的有效性,但它并不能防止中间人 (MITM) 攻击或其他潜在漏洞。为了减轻中间人攻击,处理敏感客户数据或财务信息的应用程序应使用 Alamofire ServerTrustEvaluating 协议提供的证书或公钥固定。

使用 ServerTrustManager 和 ServerTrustEvaluating 评估服务器信任

该协议 ServerTrustEvaluating 提供了一种执行任何类型的服务器信任评估的方法。它只有一个要求:

1
func evaluate(_ trust: SecTrust, forHost host: String) throws

此方法提供从基础 URLSession 接收 SecTrust 的值和主机 String ,并提供执行各种评估的机会。

包括许多不同类型的信任评估器,提供对评估过程的可组合控制:

  1. DefaultTrustEvaluator :使用默认服务器信任评估,同时允许您控制是否验证质询提供的主机。
  2. RevocationTrustEvaluator :检查收到的证书的状态,以确保其未被吊销。由于它需要网络请求开销,因此通常不会对每个请求执行此操作。
  3. PinnedCertificatesTrustEvaluator :使用提供的证书来验证服务器信任。如果其中一个固定的证书与其中一个服务器证书匹配,则认为服务器信任有效。此赋值器还可以接受自签名证书。
  4. PublicKeysTrustEvaluator :使用提供的公钥来验证服务器信任。如果其中一个固定的公钥与其中一个服务器证书公钥匹配,则认为服务器信任有效。
  5. CompositeTrustEvaluator :计算值数组,仅当所有 ServerTrustEvaluating 值都成功时才成功。例如,此类型可用于组合 RevocationTrustEvaluator 和 PinnedCertificatesTrustEvaluator 。
  6. DisabledTrustEvaluator :此评估程序应仅在调试方案中使用,因为它会禁用所有评估,而这些评估将始终将任何服务器信任视为有效。此评估器绝不应在生产环境中使用!

ServerTrustManager

负责 ServerTrustManager 存储值到特定主机的 ServerTrustEvaluating 内部映射。这允许 Alamofire 使用不同的评估器评估每个主机。

1
2
3
4
5
6
7
8
let evaluators: [String: ServerTrustEvaluating] = [
// 默认情况下,应用程序捆绑包中包含的证书会自动固定。
"cert.example.com": PinnedCertificatesTrustEvaluator(),
// 默认情况下,会自动使用应用程序包中包含的证书中的公钥。
"keys.example.com": PublicKeysTrustEvaluator(),
]

let manager = ServerTrustManager(evaluators: evaluators)

这将 ServerTrustManager 具有以下行为:

  1. cert.example.com 将始终使用启用默认和主机验证的证书固定,因此需要满足以下条件才能使 TLS 握手成功:
    1. 证书链必须有效。
    2. 证书链必须包含其中一个固定的证书。
    3. 质询主机必须与证书链的叶证书中的主机匹配。
  2. keys.example.com 将始终使用启用默认和主机验证的公钥固定,因此需要满足以下条件才能使 TLS 握手成功:
    1. 证书链必须有效。
    2. 证书链必须包含其中一个固定的公钥。
    3. 质询主机必须与证书链的叶证书中的主机匹配。
  3. 对其他主机的请求将产生错误,因为 ServerTrustManager 默认情况下需要评估所有主机。

测试用例

Alamofire 项目中测试用例:Tests/TLSEvaluationTests.swift

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
private enum TestCertificates {
static let rootCA = TestCertificates.certificate(filename: "expired.badssl.com-root-ca")
static let intermediateCA1 = TestCertificates.certificate(filename: "expired.badssl.com-intermediate-ca-1")
static let intermediateCA2 = TestCertificates.certificate(filename: "expired.badssl.com-intermediate-ca-2")
static let leaf = TestCertificates.certificate(filename: "expired.badssl.com-leaf")

// 从给定的文件名创建证书:SecCertificate对象。
static func certificate(filename: String) -> SecCertificate {
let filePath = Bundle.test.path(forResource: filename, ofType: "cer")!
let data = try! Data(contentsOf: URL(fileURLWithPath: filePath))
let certificate = SecCertificateCreateWithData(nil, data as CFData)!

return certificate
}
}

func testThatExpiredCertificateRequestFailsWhenPinningLeafPublicKeyWithCertificateChainValidation() {
// Given
// 这里直接从证书提取公钥
let keys = [TestCertificates.leaf].af.publicKeys
let evaluators = [expiredHost: PublicKeysTrustEvaluator(keys: keys)]

let manager = Session(configuration: configuration,
serverTrustManager: ServerTrustManager(evaluators: evaluators))

let expectation = expectation(description: "\(expiredURLString)")
var error: AFError?

// When
manager.request(expiredURLString)
.response { resp in
error = resp.error
expectation.fulfill()
}

waitForExpectations(timeout: timeout)

// Then
XCTAssertNotNil(error, "error should not be nil")
XCTAssertEqual(error?.isServerTrustEvaluationError, true)

if case let .serverTrustEvaluationFailed(reason)? = error {
if #available(iOS 12, macOS 10.14, tvOS 12, watchOS 5, *) {
XCTAssertTrue(reason.isTrustEvaluationFailed, "should be .trustEvaluationFailed")
} else {
XCTAssertTrue(reason.isDefaultEvaluationFailed, "should be .defaultEvaluationFailed")
}
} else {
XCTFail("error should be .serverTrustEvaluationFailed")
}
}

引子

onevcat on Twitter / X

大家的 iOS 项目都是怎么做 CI 的?Xcode Cloud 不够用啊(太贵了)

看你的提交频率了…我自己的话低配就够了。另外就是找一款合适的虚拟机,尽量别自己折腾环境和升级啥的 挺烦的…我是tart+GitHub Action 还挺满意

Xcode 的 Test 结果可以直接显示在 GitHub 里吗?如果再能把截图都同步过去就完美了。

https://github.com/kishikawakatsumi/xcresulttool

Fastlane

不知道有没有帮助,可以参考下,

如何用极狐GitLab 为 iOS App 创建自动化CI/CD?详细教程来了

💡 GitLab Actions + Fastlane,GitLab Actions仅为触发器。

Tart

https://github.com/cirruslabs/tart

直接下载

下载带Xcode的完整镜像

1
2
3
brew install cirruslabs/cli/tart
tart clone ghcr.io/cirruslabs/macos-sonoma-xcode:latest sonoma-xcode
tart run sonoma-xcode

从头开始创建 macOS 虚拟机映像

Tart 可以从 *.ipsw 文件创建虚拟机。你可以在这里下载特定的 *.ipsw 文件,也可以使用 latest 代替 *.ipsw 的路径来下载最新的可用版本:

1
2
tart create --from-ipsw=latest sonoma-vanilla
tart run sonoma-vanilla

自托管 CI

创建 .cirrus.yml 文件

1
2
3
4
5
6
7
8
9
10
11
task:
name: hello
macos_instance:
# can be a remote or a local virtual machine
# image: ghcr.io/cirruslabs/macos-sonoma-base:latest
image: sonoma-xcode
hello_script:
- echo "Hello from within a Tart VM!"
- echo "Here is my CPU info:"
- sysctl -n machdep.cpu.brand_string
- sleep 15

运行该task

将上述 .cirrus.yml 文件放入存储库的根目录中,并使用以下命令运行它:

1
2
brew install cirruslabs/cli/cirrus
cirrus run

从 Tart VM 中检索工件

在许多情况下,需要从 Tart 虚拟机中检索特定文件或文件夹。例如,以下 .cirrus.yml 配置定义了一个任务,该任务构建二进制 tart 文件并通过 artifacts 指令公开它:

1
2
3
4
5
6
7
task:
name: Build
macos_instance:
image: ghcr.io/cirruslabs/macos-sonoma-xcode:latest
build_script: swift build --product tart
binary_artifacts:
path: .build/debug/tart

运行 Cirrus CLI 时,将 --artifacts-dir 定义 artifacts 写入主机上提供的本地目录:

1
cirrus run --artifacts-dir artifacts

请注意,所有检索到的工件都将以关联的任务名称和 artifacts 指令名称为前缀。对于上面的示例, tart 二进制文件将保存到 $PWD/artifacts/Build/binary/.build/debug/tart .

整合方案

第一步:GitHub Actions

GitHub Actions 触发 cirrus run

1
2
3
4
5
6
7
8
9
10
11
12
name: learn-github-actions
run-name: ${{ github.actor }} is learning GitHub Actions
on: [push]
jobs:
check-bats-version:
runs-on: self-hosted
steps:
- run: cd ~/git/ci
- run: git stash
- run: git checkout main
- run: git pull
- run: cirrus run --artifacts-dir artifacts

第二步:cirrus

这一步使用tart来管理并隔离运行环境。

cirrustart虚拟机中执行脚本: xcodebuild 打包或者测试。fastlane最好。

1
2
3
4
5
6
7
8
9
10
11
12
13
task:
name: xcode test
macos_instance:
# can be a remote or a local virtual machine
# image: ghcr.io/cirruslabs/macos-sonoma-base:latest
image: sonoma-xcode
hello_script:
- echo "Hello from within a Tart VM!"
- echo "Here is my CPU info:"
- sysctl -n machdep.cpu.brand_string
build_script: xcodebuild -scheme DemoApp -destination 'platform=iOS Simulator,name=iPhone 15,OS=17.2' clean test
binary_artifacts:
path: .build/debug/tart

GitHub Actions 部分log

1
2
3
4
5
6
7
8
9
10
Run cirrus run
'xcode test' task
pull virtual machine
clone virtual machine
boot virtual machine
syncing working directory
'hello' script
'build' script
'binary' artifacts
'xcode test' task succeeded in 03:17!

第三步:Fastlane

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
default_platform(:ios)

ipa_dir = "fastlane_build/"
ipa_name = "AppName" + Time.new.strftime("%Y-%m-%d_%H:%M:%S")

commit = last_git_commit
message = commit[:message]
short_hash = commit[:abbreviated_commit_hash]

# 更新内容
changelog = %(by Will
git branch: #{git_branch}
git short_hash: #{short_hash}
git message: #{message}
)

before_all do
app_store_connect_api_key(
key_id: "",
issuer_id: "",
key_filepath: "",
duration: 1200,
in_house: false,
)
end

platform :ios do
desc "Push a new beta build to TestFlight"
lane :beta do
increment_build_number(
build_number: latest_testflight_build_number + 1,
)
build_app(
scheme: "AppName",
# Debug、Release
configuration: "Release",
clean: true,
# 导出方式 app-store、ad-hoc、enterprise、development
export_method: "app-store",
export_xcargs: "-allowProvisioningUpdates", # enable automatic signing
# ipa的存放目录
output_directory: ipa_dir,
# 输出ipa的文件名为当前的build号
output_name: ipa_name,
)
upload_to_testflight(
skip_waiting_for_build_processing: true,
changelog: changelog,
)
end
end

总结

总的流程就是

  1. GitHub Actions触发
  2. cirrus管理Tart
  3. Fastlane执行打包或者测试

相比Jenkins,全局都是配置文件,更干净简洁,没有繁琐的UI。当然也稍微增加了门槛,上手就要求熟悉配置文件。

Introducing

Open Chinese Convert (OpenCC, 開放中文轉換) is an opensource project for conversions between Traditional Chinese, Simplified Chinese and Japanese Kanji (Shinjitai). It supports character-level and phrase-level conversion, character variant conversion and regional idioms among Mainland China, Taiwan and Hong Kong. This is not translation tool between Mandarin and Cantonese, etc.

Features

Strictly differentiate between 「one simplified to many traditionals」 and 「one simplified to many variants」.
Completely compatible with different variants and can realize dynamic substitution.
Strictly scrutinize one-simplified-to-multiple-traditional entries, and the principle is 「if it can be divided, then it will be divided」.
Support Mainland China, Taiwan, Hong Kong, different variants and regional customary word conversion, such as 「裏」「裡」、「鼠標」「滑鼠」.

If you have any questions or suggestions, you can contact them through Email.

Download OpenCCman on the App Store

Privacy policy

This App does not collect or upload any private information.

介绍

中文简繁转换开源项目,支持词汇级别的转换、异体字转换和地区习惯用词转换(中国大陆、台湾、香港、日本新字体)。不提供普通话与粤语的转换。

特点

严格区分「一简对多繁」和「一简对多异」。
完全兼容异体字,可以实现动态替换。
严格审校一简对多繁词条,原则为「能分则不合」。
支持中国大陆、台湾、香港异体字和地区习惯用词转换,如「裏」「裡」、「鼠標」「滑鼠」。

应用使用有任何问题或建议,欢迎邮件联系:531sunlight@gmail.com

App Store下载 OpenCCman

隐私政策

本App不进行任何隐私信息收集或上传。

介紹

中文簡繁轉換開源項目,支持詞彙級別的轉換、異體字轉換和地區習慣用詞轉換(中國大陸、臺灣、香港、日本新字體)。不提供普通話與粵語的轉換。

特點

嚴格區分「一簡對多繁」和「一簡對多異」。
完全兼容異體字,可以實現動態替換。
嚴格審校一簡對多繁詞條,原則爲「能分則不合」。
支持中國大陸、臺灣、香港異體字和地區習慣用詞轉換,如「裏」「裡」、「鼠標」「滑鼠」。

應用使用有任何問題或建議,歡迎郵件聯繫:531sunlight@gmail.com

App Store下載 OpenCCman

隱私政策

本App不進行任何隱私信息收集或上傳。

介绍

Preview Chinese light

ConnectUI 是专为应用程序开发人员和发布者构建的强大销售分析和报告平台。

借助 ConnectUI,您可以轻松跟踪和分析您的应用销售、收入、下载和更新。

应用使用有任何问题或建议,欢迎邮件联系:531sunlight@gmail.com

App Store下载 ConnectUI

隐私政策

本App不进行任何隐私信息收集或上传。

Introducing

Preview English light

ConnectUI is a powerful sales analytics and reporting platform built specifically for app developers and publishers.

With ConnectUI, you can easily track and analyze your app sales, revenues, downloads, and updates.

For any questions or suggestions, please feel free to contact us via email at 531sunlight@gmail.com.

Download ConnectUI on the App Store

Privacy policy

This App does not collect or upload any private information.

先说结论:Pass

1Password

1Password-screenshot官网:https://1password.com/

客户端:支持 Mac、iOS、Windows、Android、Chrome OS 和 Linux

技术栈:Electron

代码开源:小部分组件开源 https://github.com/1Password

存储:官方存储

价格:订阅制,每年$36

优缺点:最出名的,价格偏贵,数据同步省心

Elpass

Elpass-screenshot官网:https://elpass.app/

客户端:iOS和macOS,Chrome拓展

技术栈:Objective-C 原生开发

代码开源:核心开源 Elpass-Core on GitHub

存储:官方无存储,支持网盘同步:iCloud和Dropbox

价格:订阅制,每年$20

优缺点:苹果系统下体验不错,一旦到Windows、Linux只有Chrome拓展勉强可用,数据同步依赖三方网盘

KeePassXC

KeePassXC_autotype_entrylevel

官网:https://keepassxc.org/

客户端:支持 Mac、Windows、和 Linux

技术栈:C++、Qt

代码开源:GPL-2 https://github.com/keepassxreboot/keepassxc

存储:本地存储

价格:免费

优缺点:开源免费、缺失良好同步机制。

LessPass

LessPass

官网:https://www.lesspass.com/

客户端:支持 Mac、iOS、Windows、Android、Chrome OS 和 Linux

技术栈:JS、TS、Vue、Python、React Native

代码开源:GPL-3 https://github.com/lesspass/lesspass

存储:无存储,计算型密码生成器

价格:免费

优缺点:计算型无泄露风险、但是无法批量导入历史密码

Pass

pass-cli

Pass-iOS

官网:https://www.passwordstore.org/

客户端:支持 Mac、Windows、和 Linux

技术栈:C++、Qt

代码开源:GPL-2 https://git.zx2c4.com/password-store

存储:本地存储

价格:免费

优缺点:开源免费、gpg加密文件,git同步数据、命令行和GUI客户端都有。个人认为最佳。

原理解释

基于对称加密算法和口令加密算法。

1
secret = encrypt(key, plain); // 加密
1
plain = decrypt(key, secret); // 解密
1
key = Hash(password, salt); // 密钥长度不够,加密来凑

参考

  1. 密码管理器的进化史(上)
  2. 对称加密算法
  3. 2021年,用更现代的方法使用PGP(上)

Introducing

Preview English light

Preview English dark

Clickman is a small and practical auxiliary tool software.

It can automatically click the left mouse button at the set time interval to achieve the effect of automatic consecutive clicks.

Up to about 110 clicks per second.

The main application scenarios are as follows:

  1. Automatically repeat clicking skills in online games. Especially for clicking-type air combat or tower defense games, repeat clicking attacks to achieve higher damage output.

  2. Webpage auto-refresh or scroll. By setting a proper click interval and location, achieve the effect of the mouse automatically scrolling up and down the webpage, equivalent to manually dragging the mouse scroll wheel.

For any questions or suggestions, please feel free to contact us via email at 531sunlight@gmail.com.

Download Clickman on the App Store

Privacy policy

This App does not collect or upload any private information.

0%