chore: adopt source-available license and strengthen privacy disclosures
Replace MIT with a restrictive source-available license and align README/docs copy. Add cloud polish acknowledgment, privacy manifest updates, voice history disclosure, export compliance metadata, and support links for App Store readiness.
This commit is contained in:
@@ -1,21 +1,220 @@
|
|||||||
MIT License
|
OSGKeyboard Source Available License
|
||||||
|
Version 1.0, June 2026
|
||||||
|
|
||||||
Copyright (c) 2026 OSGKeyboard Contributors
|
Copyright (c) 2026 OSGKeyboard Contributors. All rights reserved.
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
This is NOT an open-source license. The software is made available for
|
||||||
of this software and associated documentation files (the "Software"), to deal
|
transparency and personal, non-commercial use only. Commercial rights are
|
||||||
in the Software without restriction, including without limitation the rights
|
reserved exclusively by the copyright holder unless a separate written
|
||||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
agreement is granted.
|
||||||
copies of the Software, and to permit persons to whom the Software is
|
|
||||||
furnished to do so, subject to the following conditions:
|
|
||||||
|
|
||||||
The above copyright notice and this permission notice shall be included in all
|
Contact for commercial licensing: rocky.hk@gmail.com
|
||||||
copies or substantial portions of the Software.
|
|
||||||
|
|
||||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
||||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
1. Definitions
|
||||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
||||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
"Software" means the OSGKeyboard source code, documentation, and
|
||||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
associated files in this repository.
|
||||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
||||||
SOFTWARE.
|
"You" means any individual or legal entity exercising permissions under
|
||||||
|
this License.
|
||||||
|
|
||||||
|
"Commercial Use" means any use of the Software that is intended for or
|
||||||
|
directed toward commercial advantage or monetary compensation, including
|
||||||
|
but not limited to: selling, licensing, or distributing the Software or
|
||||||
|
derivatives; offering a product or service that incorporates or is
|
||||||
|
derived from the Software; using the Software in production for a
|
||||||
|
business; or using the Software to develop a competing product.
|
||||||
|
|
||||||
|
|
||||||
|
2. Permitted Uses (Non-Commercial, Personal)
|
||||||
|
|
||||||
|
You may, without a separate commercial license:
|
||||||
|
|
||||||
|
(a) Clone, download, and view the Software for personal learning and
|
||||||
|
reference.
|
||||||
|
|
||||||
|
(b) Build and run the Software locally on devices you own or control,
|
||||||
|
for personal, non-commercial evaluation and debugging.
|
||||||
|
|
||||||
|
(c) Modify the Software for your own personal, non-commercial use on
|
||||||
|
devices you own or control, provided you do not distribute,
|
||||||
|
publish, or make available any modified version to any third party.
|
||||||
|
|
||||||
|
|
||||||
|
3. Prohibited Uses
|
||||||
|
|
||||||
|
Without prior written permission from the copyright holder, You may NOT:
|
||||||
|
|
||||||
|
(a) Use the Software for any Commercial Use.
|
||||||
|
|
||||||
|
(b) Redistribute, publish, sublicense, sell, or otherwise make the
|
||||||
|
Software or any portion thereof available to any third party,
|
||||||
|
whether in original or modified form.
|
||||||
|
|
||||||
|
(c) Create, maintain, or distribute forked or derivative works based on
|
||||||
|
the Software, including repositories, packages, or products intended
|
||||||
|
for public or third-party use.
|
||||||
|
|
||||||
|
(d) Remove or alter copyright notices or this License.
|
||||||
|
|
||||||
|
(e) Use the Software, or any derivative thereof, as the basis for a
|
||||||
|
competing keyboard, voice-input, or substantially similar product.
|
||||||
|
|
||||||
|
|
||||||
|
4. Commercial Licensing
|
||||||
|
|
||||||
|
Exclusive commercial rights are retained by the copyright holder.
|
||||||
|
Commercial Use requires a separate written license. To inquire, contact:
|
||||||
|
|
||||||
|
rocky.hk@gmail.com
|
||||||
|
|
||||||
|
|
||||||
|
5. Contributions
|
||||||
|
|
||||||
|
Contributions (e.g., pull requests, patches, issues with code) are
|
||||||
|
welcome. By submitting a contribution, You represent that You have the
|
||||||
|
right to submit it and You grant the copyright holder a perpetual,
|
||||||
|
worldwide, non-exclusive, royalty-free license to use, reproduce,
|
||||||
|
modify, and sublicense Your contribution as part of the Software under
|
||||||
|
this License. You retain ownership of Your contribution; no copyright
|
||||||
|
transfer is required.
|
||||||
|
|
||||||
|
|
||||||
|
6. App Store Distribution
|
||||||
|
|
||||||
|
Distribution of compiled application binaries through the Apple App Store
|
||||||
|
(or other channels) by the copyright holder or its authorized licensees
|
||||||
|
does not grant You any rights to the source code beyond those stated in
|
||||||
|
this License.
|
||||||
|
|
||||||
|
|
||||||
|
7. Third-Party Components
|
||||||
|
|
||||||
|
The Software may include or depend on third-party components governed by
|
||||||
|
their own licenses. This License applies only to OSGKeyboard's own
|
||||||
|
original work and does not override third-party license terms.
|
||||||
|
|
||||||
|
|
||||||
|
8. Disclaimer of Warranty
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
|
||||||
|
OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||||
|
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, AND NONINFRINGEMENT.
|
||||||
|
|
||||||
|
|
||||||
|
9. Limitation of Liability
|
||||||
|
|
||||||
|
IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY
|
||||||
|
CLAIM, DAMAGES, OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
||||||
|
TORT, OR OTHERWISE, ARISING FROM, OUT OF, OR IN CONNECTION WITH THE
|
||||||
|
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
|
|
||||||
|
|
||||||
|
10. Termination
|
||||||
|
|
||||||
|
Your rights under this License terminate automatically if You breach
|
||||||
|
any term. Sections 3–9 survive termination.
|
||||||
|
|
||||||
|
|
||||||
|
11. Governing Language
|
||||||
|
|
||||||
|
This English text is the authoritative version. A Chinese translation
|
||||||
|
follows for reference only.
|
||||||
|
|
||||||
|
|
||||||
|
================================================================================
|
||||||
|
OSGKeyboard 源码可见许可协议
|
||||||
|
版本 1.0,2026 年 6 月
|
||||||
|
|
||||||
|
版权所有 (c) 2026 OSGKeyboard Contributors。保留所有权利。
|
||||||
|
|
||||||
|
本协议并非开源许可。公开源码仅为透明与个人非商用目的。除另行书面授权外,
|
||||||
|
商业权利由版权持有人独占保留。
|
||||||
|
|
||||||
|
商业授权联系:rocky.hk@gmail.com
|
||||||
|
|
||||||
|
|
||||||
|
1. 定义
|
||||||
|
|
||||||
|
「软件」指本仓库中的 OSGKeyboard 源代码、文档及相关文件。
|
||||||
|
|
||||||
|
「您」指在本协议下行使权利的任何个人或法人。
|
||||||
|
|
||||||
|
「商业使用」指以商业利益或金钱报酬为目的的使用,包括但不限于:销售、
|
||||||
|
许可或分发软件或其衍生作品;提供包含或源于本软件的产品或服务;在
|
||||||
|
商业生产环境中使用;或用于开发竞争产品。
|
||||||
|
|
||||||
|
|
||||||
|
2. 允许的使用(非商用、个人)
|
||||||
|
|
||||||
|
无需另行商业授权,您可:
|
||||||
|
|
||||||
|
(a) 克隆、下载、阅读软件,用于个人学习与研究;
|
||||||
|
|
||||||
|
(b) 在自有或控制的设备上构建并运行软件,用于个人非商用评估与调试;
|
||||||
|
|
||||||
|
(c) 为个人非商用目的修改软件并在自有设备上使用,但不得向任何第三方
|
||||||
|
分发、发布或提供任何修改版本。
|
||||||
|
|
||||||
|
|
||||||
|
3. 禁止的使用
|
||||||
|
|
||||||
|
未经版权持有人事先书面许可,您不得:
|
||||||
|
|
||||||
|
(a) 将软件用于任何商业使用;
|
||||||
|
|
||||||
|
(b) 以任何形式向第三方再分发、发布、转许可、销售或提供软件或其部分,
|
||||||
|
无论是否修改;
|
||||||
|
|
||||||
|
(c) 创建、维护或分发基于本软件的分叉或衍生作品,包括面向公众或第三
|
||||||
|
方的仓库、包或产品;
|
||||||
|
|
||||||
|
(d) 移除或篡改版权声明或本协议;
|
||||||
|
|
||||||
|
(e) 将软件或其衍生作品作为竞争性的键盘、语音输入或实质相似产品的
|
||||||
|
基础。
|
||||||
|
|
||||||
|
|
||||||
|
4. 商业授权
|
||||||
|
|
||||||
|
商业权利由版权持有人独占保留。商业使用须取得单独书面授权。联系:
|
||||||
|
|
||||||
|
rocky.hk@gmail.com
|
||||||
|
|
||||||
|
|
||||||
|
5. 贡献
|
||||||
|
|
||||||
|
欢迎提交贡献(如 Pull Request、补丁等)。提交即表示您有权提交该内容,
|
||||||
|
并授予版权持有人一项永久的、全球性的、非独占的、免版税许可,以便在
|
||||||
|
本协议下将您的贡献作为软件的一部分使用、复制、修改和再许可。您保留
|
||||||
|
对贡献的所有权;不要求转让版权。
|
||||||
|
|
||||||
|
|
||||||
|
6. App Store 分发
|
||||||
|
|
||||||
|
版权持有人或其授权方通过 Apple App Store(或其他渠道)分发编译后的
|
||||||
|
应用,不授予您超出本协议所述范围的源码权利。
|
||||||
|
|
||||||
|
|
||||||
|
7. 第三方组件
|
||||||
|
|
||||||
|
软件可能包含或依赖受各自许可约束的第三方组件。本协议仅适用于
|
||||||
|
OSGKeyboard 原创作品,不覆盖第三方许可条款。
|
||||||
|
|
||||||
|
|
||||||
|
8. 免责声明
|
||||||
|
|
||||||
|
软件按「原样」提供,不提供任何明示或暗示的保证,包括但不限于适销性、
|
||||||
|
特定用途适用性和不侵权保证。
|
||||||
|
|
||||||
|
|
||||||
|
9. 责任限制
|
||||||
|
|
||||||
|
在任何情况下,版权持有人或贡献者均不对因软件或使用软件而产生的任何
|
||||||
|
索赔、损害或其他责任承担责任。
|
||||||
|
|
||||||
|
|
||||||
|
10. 终止
|
||||||
|
|
||||||
|
若您违反本协议任何条款,您的权利自动终止。第 3–9 条在终止后继续有效。
|
||||||
|
|||||||
@@ -36,6 +36,8 @@
|
|||||||
</array>
|
</array>
|
||||||
<key>CFBundleVersion</key>
|
<key>CFBundleVersion</key>
|
||||||
<string>$(CURRENT_PROJECT_VERSION)</string>
|
<string>$(CURRENT_PROJECT_VERSION)</string>
|
||||||
|
<key>ITSAppUsesNonExemptEncryption</key>
|
||||||
|
<false/>
|
||||||
<key>LSApplicationCategoryType</key>
|
<key>LSApplicationCategoryType</key>
|
||||||
<string>public.app-category.utilities</string>
|
<string>public.app-category.utilities</string>
|
||||||
<key>NSAppTransportSecurity</key>
|
<key>NSAppTransportSecurity</key>
|
||||||
|
|||||||
@@ -7,7 +7,20 @@
|
|||||||
<key>NSPrivacyTrackingDomains</key>
|
<key>NSPrivacyTrackingDomains</key>
|
||||||
<array/>
|
<array/>
|
||||||
<key>NSPrivacyCollectedDataTypes</key>
|
<key>NSPrivacyCollectedDataTypes</key>
|
||||||
<array/>
|
<array>
|
||||||
|
<dict>
|
||||||
|
<key>NSPrivacyCollectedDataType</key>
|
||||||
|
<string>NSPrivacyCollectedDataTypeOtherUserContent</string>
|
||||||
|
<key>NSPrivacyCollectedDataTypeLinked</key>
|
||||||
|
<false/>
|
||||||
|
<key>NSPrivacyCollectedDataTypeTracking</key>
|
||||||
|
<false/>
|
||||||
|
<key>NSPrivacyCollectedDataTypePurposes</key>
|
||||||
|
<array>
|
||||||
|
<string>NSPrivacyCollectedDataTypePurposeAppFunctionality</string>
|
||||||
|
</array>
|
||||||
|
</dict>
|
||||||
|
</array>
|
||||||
<key>NSPrivacyAccessedAPITypes</key>
|
<key>NSPrivacyAccessedAPITypes</key>
|
||||||
<array>
|
<array>
|
||||||
<dict>
|
<dict>
|
||||||
|
|||||||
@@ -8,4 +8,9 @@ enum LegalLinks {
|
|||||||
static var privacyPolicyURL: URL? {
|
static var privacyPolicyURL: URL? {
|
||||||
URL(string: "https://hkgood.github.io/OSGKeyboard/privacy/")
|
URL(string: "https://hkgood.github.io/OSGKeyboard/privacy/")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Support / feedback (GitHub Issues).
|
||||||
|
static var supportURL: URL? {
|
||||||
|
URL(string: "https://github.com/hkgood/OSGKeyboard/issues")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,93 @@
|
|||||||
|
// PrivacyDisclosureViews.swift
|
||||||
|
// OSGKeyboard · Main App
|
||||||
|
//
|
||||||
|
// Reusable privacy / data-flow disclosure blocks for Settings and Onboarding.
|
||||||
|
|
||||||
|
import SwiftUI
|
||||||
|
import OSGKeyboardShared
|
||||||
|
|
||||||
|
/// Card-style block for Full Access and similar permission explanations.
|
||||||
|
struct PrivacyInfoCard: View {
|
||||||
|
@Environment(\.themePalette) private var palette: ThemePalette
|
||||||
|
|
||||||
|
let title: LocalizedStringKey
|
||||||
|
let bodyText: LocalizedStringKey
|
||||||
|
|
||||||
|
var body: some View {
|
||||||
|
VStack(alignment: .leading, spacing: Spacing.xs) {
|
||||||
|
Text(title)
|
||||||
|
.font(TypeStyle.body)
|
||||||
|
.foregroundStyle(palette.textPrimary)
|
||||||
|
Text(bodyText)
|
||||||
|
.font(TypeStyle.caption2)
|
||||||
|
.foregroundStyle(palette.textSecondary)
|
||||||
|
.fixedSize(horizontal: false, vertical: true)
|
||||||
|
}
|
||||||
|
.padding(Spacing.md)
|
||||||
|
.frame(maxWidth: .infinity, alignment: .leading)
|
||||||
|
.background(palette.surface, in: RoundedRectangle(cornerRadius: Radius.large, style: .continuous))
|
||||||
|
.overlay(
|
||||||
|
RoundedRectangle(cornerRadius: Radius.large, style: .continuous)
|
||||||
|
.stroke(palette.divider, lineWidth: 0.5)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Footnote when Cloud polish is active — text goes to the user's configured API.
|
||||||
|
struct CloudPolishDisclosureBanner: View {
|
||||||
|
@Environment(\.themePalette) private var palette: ThemePalette
|
||||||
|
|
||||||
|
var body: some View {
|
||||||
|
HStack(alignment: .top, spacing: Spacing.sm) {
|
||||||
|
Image(systemName: "info.circle")
|
||||||
|
.font(.system(size: 14, weight: .medium))
|
||||||
|
.foregroundStyle(palette.accent)
|
||||||
|
Text("settings.privacy.cloud.body")
|
||||||
|
.font(TypeStyle.caption2)
|
||||||
|
.foregroundStyle(palette.textSecondary)
|
||||||
|
.fixedSize(horizontal: false, vertical: true)
|
||||||
|
}
|
||||||
|
.padding(Spacing.md)
|
||||||
|
.frame(maxWidth: .infinity, alignment: .leading)
|
||||||
|
.background(palette.accentMuted.opacity(0.35), in: RoundedRectangle(cornerRadius: Radius.medium, style: .continuous))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// First-time confirmation before enabling Cloud polish (user-configured third-party API).
|
||||||
|
struct CloudSharingAcknowledgmentModifier: ViewModifier {
|
||||||
|
@ObservedObject var config: ProviderConfig
|
||||||
|
@Binding var isPresented: Bool
|
||||||
|
let onConfirm: () -> Void
|
||||||
|
let onCancel: () -> Void
|
||||||
|
|
||||||
|
func body(content: Content) -> some View {
|
||||||
|
content
|
||||||
|
.alert("settings.privacy.cloud.alert.title", isPresented: $isPresented) {
|
||||||
|
Button("common.continue") {
|
||||||
|
config.hasAcknowledgedCloudSharing = true
|
||||||
|
onConfirm()
|
||||||
|
}
|
||||||
|
Button("common.cancel", role: .cancel) {
|
||||||
|
onCancel()
|
||||||
|
}
|
||||||
|
} message: {
|
||||||
|
Text("settings.privacy.cloud.alert.message")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extension View {
|
||||||
|
func cloudSharingAcknowledgment(
|
||||||
|
config: ProviderConfig,
|
||||||
|
isPresented: Binding<Bool>,
|
||||||
|
onConfirm: @escaping () -> Void,
|
||||||
|
onCancel: @escaping () -> Void
|
||||||
|
) -> some View {
|
||||||
|
modifier(CloudSharingAcknowledgmentModifier(
|
||||||
|
config: config,
|
||||||
|
isPresented: isPresented,
|
||||||
|
onConfirm: onConfirm,
|
||||||
|
onCancel: onCancel
|
||||||
|
))
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -22,6 +22,8 @@ struct EnginePickerSection: View {
|
|||||||
@Environment(\.themePalette) private var palette: ThemePalette
|
@Environment(\.themePalette) private var palette: ThemePalette
|
||||||
|
|
||||||
@ObservedObject var config: ProviderConfig
|
@ObservedObject var config: ProviderConfig
|
||||||
|
@State private var showCloudAcknowledgment = false
|
||||||
|
@State private var pendingEngineSelection: String?
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
VStack(alignment: .leading, spacing: SettingsListMetrics.sectionLabelSpacing) {
|
VStack(alignment: .leading, spacing: SettingsListMetrics.sectionLabelSpacing) {
|
||||||
@@ -46,8 +48,18 @@ struct EnginePickerSection: View {
|
|||||||
RoundedRectangle(cornerRadius: Radius.large, style: .continuous)
|
RoundedRectangle(cornerRadius: Radius.large, style: .continuous)
|
||||||
.stroke(palette.divider, lineWidth: 0.5)
|
.stroke(palette.divider, lineWidth: 0.5)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
if config.engineMode == "cloud" {
|
||||||
|
CloudPolishDisclosureBanner()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
.cloudSharingAcknowledgment(
|
||||||
|
config: config,
|
||||||
|
isPresented: $showCloudAcknowledgment,
|
||||||
|
onConfirm: { applyPendingEngineSelection() },
|
||||||
|
onCancel: { pendingEngineSelection = nil }
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
private var localSubtitle: String {
|
private var localSubtitle: String {
|
||||||
// iOS 26's `SpeechAnalyzer` is always fully on-device, so the
|
// iOS 26's `SpeechAnalyzer` is always fully on-device, so the
|
||||||
@@ -64,10 +76,13 @@ struct EnginePickerSection: View {
|
|||||||
) -> some View {
|
) -> some View {
|
||||||
let isSelected = config.engineMode == id
|
let isSelected = config.engineMode == id
|
||||||
return Button {
|
return Button {
|
||||||
withAnimation(.easeInOut(duration: 0.2)) {
|
guard config.engineMode != id else { return }
|
||||||
config.engineMode = id
|
if id == "cloud", !config.hasAcknowledgedCloudSharing {
|
||||||
if id == "local" { config.modeId = "transcribe" }
|
pendingEngineSelection = id
|
||||||
|
showCloudAcknowledgment = true
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
selectEngine(id)
|
||||||
} label: {
|
} label: {
|
||||||
HStack(spacing: Spacing.sm) {
|
HStack(spacing: Spacing.sm) {
|
||||||
engineMark(
|
engineMark(
|
||||||
@@ -97,6 +112,19 @@ struct EnginePickerSection: View {
|
|||||||
.buttonStyle(.plain)
|
.buttonStyle(.plain)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private func selectEngine(_ id: String) {
|
||||||
|
withAnimation(.easeInOut(duration: 0.2)) {
|
||||||
|
config.engineMode = id
|
||||||
|
if id == "local" { config.modeId = "transcribe" }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private func applyPendingEngineSelection() {
|
||||||
|
guard let id = pendingEngineSelection else { return }
|
||||||
|
pendingEngineSelection = nil
|
||||||
|
selectEngine(id)
|
||||||
|
}
|
||||||
|
|
||||||
@ViewBuilder
|
@ViewBuilder
|
||||||
private func engineMark(assetName: String?, systemIcon: String?, isSelected: Bool) -> some View {
|
private func engineMark(assetName: String?, systemIcon: String?, isSelected: Bool) -> some View {
|
||||||
ZStack {
|
ZStack {
|
||||||
|
|||||||
@@ -39,6 +39,13 @@ struct HistoryView: View {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Text("history.subtitle")
|
||||||
|
.font(TypeStyle.caption2)
|
||||||
|
.foregroundStyle(palette.textSecondary)
|
||||||
|
.frame(maxWidth: .infinity, alignment: .leading)
|
||||||
|
.padding(.horizontal, Spacing.md)
|
||||||
|
.padding(.bottom, Spacing.sm)
|
||||||
|
|
||||||
ZStack {
|
ZStack {
|
||||||
palette.background.ignoresSafeArea()
|
palette.background.ignoresSafeArea()
|
||||||
|
|
||||||
|
|||||||
@@ -617,6 +617,13 @@ private struct EnableKeyboardPage: View {
|
|||||||
.padding(.horizontal, Spacing.xl)
|
.padding(.horizontal, Spacing.xl)
|
||||||
.padding(.top, OnboardingLayoutMetrics.heroTextGap)
|
.padding(.top, OnboardingLayoutMetrics.heroTextGap)
|
||||||
|
|
||||||
|
PrivacyInfoCard(
|
||||||
|
title: "settings.privacy.fullAccess.title",
|
||||||
|
bodyText: "settings.privacy.fullAccess.body"
|
||||||
|
)
|
||||||
|
.padding(.horizontal, Spacing.lg)
|
||||||
|
.padding(.top, Spacing.lg)
|
||||||
|
|
||||||
Button {
|
Button {
|
||||||
AppPermissions.openSystemSettings()
|
AppPermissions.openSystemSettings()
|
||||||
} label: {
|
} label: {
|
||||||
|
|||||||
@@ -42,6 +42,7 @@ struct SettingsView: View {
|
|||||||
confirmActionTitle: "common.reset"
|
confirmActionTitle: "common.reset"
|
||||||
) {
|
) {
|
||||||
config.reset()
|
config.reset()
|
||||||
|
SpeechHistoryStore.shared.clearAll()
|
||||||
}
|
}
|
||||||
if presentation == .sheet {
|
if presentation == .sheet {
|
||||||
Button("common.done") { dismiss() }
|
Button("common.done") { dismiss() }
|
||||||
@@ -301,6 +302,10 @@ struct SettingsView: View {
|
|||||||
footerLinkRow(title: "settings.privacy.policy", url: url)
|
footerLinkRow(title: "settings.privacy.policy", url: url)
|
||||||
Divider().background(palette.divider)
|
Divider().background(palette.divider)
|
||||||
}
|
}
|
||||||
|
if let url = LegalLinks.supportURL {
|
||||||
|
footerLinkRow(title: "settings.link.support", url: url)
|
||||||
|
Divider().background(palette.divider)
|
||||||
|
}
|
||||||
footerLinkRow(
|
footerLinkRow(
|
||||||
title: "settings.link.github",
|
title: "settings.link.github",
|
||||||
url: URL(string: "https://github.com/hkgood/OSGKeyboard")!
|
url: URL(string: "https://github.com/hkgood/OSGKeyboard")!
|
||||||
|
|||||||
@@ -0,0 +1,4 @@
|
|||||||
|
/* Permission prompts — English */
|
||||||
|
|
||||||
|
"NSMicrophoneUsageDescription" = "OSGKeyboard uses the microphone for voice dictation and keeps a background audio session active while a voice session is running.";
|
||||||
|
"NSSpeechRecognitionUsageDescription" = "OSGKeyboard uses on-device speech recognition to transcribe your voice. Audio is processed on your device and is not uploaded for transcription.";
|
||||||
@@ -84,7 +84,7 @@
|
|||||||
/* Settings */
|
/* Settings */
|
||||||
"settings.title" = "Settings";
|
"settings.title" = "Settings";
|
||||||
"settings.reset.title" = "Reset all settings?";
|
"settings.reset.title" = "Reset all settings?";
|
||||||
"settings.reset.message" = "API key, model, and base URL will be cleared.";
|
"settings.reset.message" = "API key, model, base URL, voice history, and cloud consent will be cleared.";
|
||||||
"settings.reset.confirm" = "Reset all settings";
|
"settings.reset.confirm" = "Reset all settings";
|
||||||
"settings.engine.title" = "Engine";
|
"settings.engine.title" = "Engine";
|
||||||
"settings.engine.subtitle" = "Pick the recognition engine. Local engine does transcription only, no API key needed.";
|
"settings.engine.subtitle" = "Pick the recognition engine. Local engine does transcription only, no API key needed.";
|
||||||
@@ -92,7 +92,7 @@
|
|||||||
"settings.engine.local.ios26" = "Always on-device, no network.";
|
"settings.engine.local.ios26" = "Always on-device, no network.";
|
||||||
"settings.engine.local.legacy" = "On-device ASR. Transcription only, no polish.";
|
"settings.engine.local.legacy" = "On-device ASR. Transcription only, no polish.";
|
||||||
"settings.engine.cloud.title" = "Cloud polish";
|
"settings.engine.cloud.title" = "Cloud polish";
|
||||||
"settings.engine.cloud.subtitle" = "ASR + LLM polish. API key required.";
|
"settings.engine.cloud.subtitle" = "On-device ASR + polish via your API. Text is sent to the endpoint you configure.";
|
||||||
"settings.provider.title" = "Provider";
|
"settings.provider.title" = "Provider";
|
||||||
"settings.provider.subtitle" = "Pick the LLM that polishes your dictation.";
|
"settings.provider.subtitle" = "Pick the LLM that polishes your dictation.";
|
||||||
"provider.openai" = "OpenAI";
|
"provider.openai" = "OpenAI";
|
||||||
@@ -123,6 +123,10 @@
|
|||||||
"settings.privacy.policy" = "Privacy Policy";
|
"settings.privacy.policy" = "Privacy Policy";
|
||||||
"settings.privacy.fullAccess.title" = "About Full Access";
|
"settings.privacy.fullAccess.title" = "About Full Access";
|
||||||
"settings.privacy.fullAccess.body" = "Full Access is required for the microphone and to read your API key. OSGKeyboard does not record or upload what you type with the keyboard.";
|
"settings.privacy.fullAccess.body" = "Full Access is required for the microphone and to read your API key. OSGKeyboard does not record or upload what you type with the keyboard.";
|
||||||
|
"settings.privacy.cloud.body" = "In Cloud polish mode, transcribed text is sent to the API endpoint you configure (e.g. OpenAI or your own server). OSGKeyboard does not operate servers and does not store transcripts in the cloud.";
|
||||||
|
"settings.privacy.cloud.alert.title" = "Third-party API";
|
||||||
|
"settings.privacy.cloud.alert.message" = "Cloud polish sends transcribed text to the third-party API you configure. OSGKeyboard never stores data on our servers. Continue?";
|
||||||
|
"settings.link.support" = "Help & Feedback";
|
||||||
"settings.link.github" = "GitHub";
|
"settings.link.github" = "GitHub";
|
||||||
|
|
||||||
/* App group error */
|
/* App group error */
|
||||||
|
|||||||
@@ -0,0 +1,4 @@
|
|||||||
|
/* 权限说明 — 简体中文 */
|
||||||
|
|
||||||
|
"NSMicrophoneUsageDescription" = "OSGKeyboard 使用麦克风进行语音听写,并在语音会话运行期间保持后台音频会话。";
|
||||||
|
"NSSpeechRecognitionUsageDescription" = "OSGKeyboard 使用设备端语音识别将你的语音转为文字。音频仅在设备上处理,不会上传用于转写。";
|
||||||
@@ -84,7 +84,7 @@
|
|||||||
/* Settings */
|
/* Settings */
|
||||||
"settings.title" = "设置";
|
"settings.title" = "设置";
|
||||||
"settings.reset.title" = "重置所有设置?";
|
"settings.reset.title" = "重置所有设置?";
|
||||||
"settings.reset.message" = "API key、model 和 base URL 都会被清空。";
|
"settings.reset.message" = "将清空 API Key、模型、接口地址、语音历史及云端确认状态。";
|
||||||
"settings.reset.confirm" = "重置所有设置";
|
"settings.reset.confirm" = "重置所有设置";
|
||||||
"settings.engine.title" = "引擎";
|
"settings.engine.title" = "引擎";
|
||||||
"settings.engine.subtitle" = "本地不用 Key,只转文字;云端会润色,要配 Key。";
|
"settings.engine.subtitle" = "本地不用 Key,只转文字;云端会润色,要配 Key。";
|
||||||
@@ -92,7 +92,7 @@
|
|||||||
"settings.engine.local.ios26" = "全程在手机本地,不用联网";
|
"settings.engine.local.ios26" = "全程在手机本地,不用联网";
|
||||||
"settings.engine.local.legacy" = "端侧 ASR,仅转录,无润色。";
|
"settings.engine.local.legacy" = "端侧 ASR,仅转录,无润色。";
|
||||||
"settings.engine.cloud.title" = "云端润色";
|
"settings.engine.cloud.title" = "云端润色";
|
||||||
"settings.engine.cloud.subtitle" = "先转文字,再用 AI 润色,要 API Key";
|
"settings.engine.cloud.subtitle" = "本地转写 + 你配置的 API 润色,文字发往该第三方服务";
|
||||||
"settings.provider.title" = "提供商";
|
"settings.provider.title" = "提供商";
|
||||||
"settings.provider.subtitle" = "选择 LLM 提供商。";
|
"settings.provider.subtitle" = "选择 LLM 提供商。";
|
||||||
"provider.openai" = "OpenAI";
|
"provider.openai" = "OpenAI";
|
||||||
@@ -123,6 +123,10 @@
|
|||||||
"settings.privacy.policy" = "隐私政策";
|
"settings.privacy.policy" = "隐私政策";
|
||||||
"settings.privacy.fullAccess.title" = "关于完全访问";
|
"settings.privacy.fullAccess.title" = "关于完全访问";
|
||||||
"settings.privacy.fullAccess.body" = "用来调用麦克风和读取 API Key;不会读取或上传你的输入内容。";
|
"settings.privacy.fullAccess.body" = "用来调用麦克风和读取 API Key;不会读取或上传你的输入内容。";
|
||||||
|
"settings.privacy.cloud.body" = "云端润色模式下,转写文字会发送到你配置的 API(如 OpenAI 或自建服务)。OSGKeyboard 不运营服务器,也不会把转写内容存到云端。";
|
||||||
|
"settings.privacy.cloud.alert.title" = "第三方 API";
|
||||||
|
"settings.privacy.cloud.alert.message" = "云端润色会把转写文字发到你配置的第三方 API。OSGKeyboard 不会在自有服务器上存储数据。是否继续?";
|
||||||
|
"settings.link.support" = "帮助与反馈";
|
||||||
"settings.link.github" = "GitHub";
|
"settings.link.github" = "GitHub";
|
||||||
|
|
||||||
/* App group error */
|
/* App group error */
|
||||||
|
|||||||
@@ -1,10 +1,9 @@
|
|||||||
// HostAppLauncher.swift
|
// HostAppLauncher.swift
|
||||||
// OSGKeyboard · Keyboard Extension
|
// OSGKeyboard · Keyboard Extension
|
||||||
//
|
//
|
||||||
// Opens the host app via URL using every extension-safe strategy:
|
// Opens the host app via URL using extension-safe strategies:
|
||||||
// 1. `extensionContext.open` (official)
|
// 1. `extensionContext.open` (official)
|
||||||
// 2. Responder-chain `UIApplication.open` (TypeWhisper pattern)
|
// 2. Responder-chain `UIApplication.open` (TypeWhisper pattern)
|
||||||
// 3. `sharedApplication` KVC fallback (common in full-access keyboards)
|
|
||||||
|
|
||||||
import UIKit
|
import UIKit
|
||||||
|
|
||||||
@@ -22,23 +21,12 @@ enum HostAppLauncher {
|
|||||||
completion(true)
|
completion(true)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
completion(openViaFallback(url, from: controller))
|
completion(openViaResponderChain(url, from: controller))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
completion(openViaFallback(url, from: controller))
|
completion(openViaResponderChain(url, from: controller))
|
||||||
}
|
|
||||||
|
|
||||||
@MainActor
|
|
||||||
private static func openViaFallback(
|
|
||||||
_ url: URL,
|
|
||||||
from controller: KeyboardViewController
|
|
||||||
) -> Bool {
|
|
||||||
if openViaResponderChain(url, from: controller) {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
return openViaSharedApplication(url)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@MainActor
|
@MainActor
|
||||||
@@ -56,15 +44,4 @@ enum HostAppLauncher {
|
|||||||
}
|
}
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
@MainActor
|
|
||||||
private static func openViaSharedApplication(_ url: URL) -> Bool {
|
|
||||||
guard
|
|
||||||
let application = UIApplication.value(forKeyPath: "sharedApplication") as? UIApplication
|
|
||||||
else {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
application.open(url, options: [:]) { _ in }
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -29,6 +29,7 @@ public final class ProviderConfig: ObservableObject, @unchecked Sendable {
|
|||||||
static let engineMode = "config.engineMode"
|
static let engineMode = "config.engineMode"
|
||||||
static let hasCompletedOnboarding = "config.hasCompletedOnboarding"
|
static let hasCompletedOnboarding = "config.hasCompletedOnboarding"
|
||||||
static let onboardingPage = "config.onboardingPage"
|
static let onboardingPage = "config.onboardingPage"
|
||||||
|
static let hasAcknowledgedCloudSharing = "config.hasAcknowledgedCloudSharing"
|
||||||
}
|
}
|
||||||
|
|
||||||
@Published public var providerId: String {
|
@Published public var providerId: String {
|
||||||
@@ -80,6 +81,10 @@ public final class ProviderConfig: ObservableObject, @unchecked Sendable {
|
|||||||
@Published public var onboardingPage: Int {
|
@Published public var onboardingPage: Int {
|
||||||
didSet { defaults.set(onboardingPage, forKey: Key.onboardingPage) }
|
didSet { defaults.set(onboardingPage, forKey: Key.onboardingPage) }
|
||||||
}
|
}
|
||||||
|
/// User confirmed that Cloud polish sends transcripts to their configured third-party API.
|
||||||
|
@Published public var hasAcknowledgedCloudSharing: Bool {
|
||||||
|
didSet { defaults.set(hasAcknowledgedCloudSharing, forKey: Key.hasAcknowledgedCloudSharing) }
|
||||||
|
}
|
||||||
|
|
||||||
public var isConfigured: Bool {
|
public var isConfigured: Bool {
|
||||||
// Local engine (on-device ASR only) doesn't need an API key,
|
// Local engine (on-device ASR only) doesn't need an API key,
|
||||||
@@ -125,6 +130,7 @@ public final class ProviderConfig: ObservableObject, @unchecked Sendable {
|
|||||||
self.hasCompletedOnboarding = resolvedDefaults.bool(forKey: Key.hasCompletedOnboarding)
|
self.hasCompletedOnboarding = resolvedDefaults.bool(forKey: Key.hasCompletedOnboarding)
|
||||||
let savedPage = resolvedDefaults.integer(forKey: Key.onboardingPage)
|
let savedPage = resolvedDefaults.integer(forKey: Key.onboardingPage)
|
||||||
self.onboardingPage = savedPage > 0 ? savedPage : 0
|
self.onboardingPage = savedPage > 0 ? savedPage : 0
|
||||||
|
self.hasAcknowledgedCloudSharing = resolvedDefaults.bool(forKey: Key.hasAcknowledgedCloudSharing)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Read the API key from the Keychain, falling back to a one-time
|
/// Read the API key from the Keychain, falling back to a one-time
|
||||||
@@ -170,5 +176,6 @@ public final class ProviderConfig: ObservableObject, @unchecked Sendable {
|
|||||||
apiKey = ""
|
apiKey = ""
|
||||||
model = preset.defaultModel
|
model = preset.defaultModel
|
||||||
systemPrompt = AppGroupStore.defaultSystemPrompt(for: "openai")
|
systemPrompt = AppGroupStore.defaultSystemPrompt(for: "openai")
|
||||||
|
hasAcknowledgedCloudSharing = false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,11 +1,11 @@
|
|||||||
# OSGKeyboard
|
# OSGKeyboard
|
||||||
|
|
||||||
> Hold a key, speak, release — AI-polished text appears at your cursor in any app.
|
> Hold a key, speak, release — AI-polished text appears at your cursor in any app.
|
||||||
> An open-source, custom-keyboard-based voice input tool for iOS 26+, inspired by [Typeless](https://typeless.com) and [OpenLess](https://github.com/Open-Less/openless).
|
> A source-available, custom-keyboard-based voice input tool for iOS 26+, inspired by [Typeless](https://typeless.com) and [OpenLess](https://github.com/Open-Less/openless).
|
||||||
|
|
||||||

|

|
||||||

|

|
||||||

|

|
||||||

|

|
||||||
|
|
||||||
[中文 README](./README.zh.md)
|
[中文 README](./README.zh.md)
|
||||||
@@ -14,7 +14,7 @@
|
|||||||
|
|
||||||
## What is it?
|
## What is it?
|
||||||
|
|
||||||
OSGKeyboard is a free, open alternative to commercial voice-input tools. It runs as a **Custom Keyboard Extension** on iOS, so you can use it in **any app** — Messages, Notes, Mail, ChatGPT, Claude, Cursor, you name it.
|
OSGKeyboard is a free, source-available alternative to commercial voice-input tools. It runs as a **Custom Keyboard Extension** on iOS, so you can use it in **any app** — Messages, Notes, Mail, ChatGPT, Claude, Cursor, you name it.
|
||||||
|
|
||||||
1. Press and hold the mic key
|
1. Press and hold the mic key
|
||||||
2. Speak naturally
|
2. Speak naturally
|
||||||
@@ -138,7 +138,7 @@ That's it. No other code changes required.
|
|||||||
|
|
||||||
## License
|
## License
|
||||||
|
|
||||||
[MIT](./LICENSE) — use it, fork it, ship it. No warranty.
|
[OSGKeyboard Source Available License](./LICENSE) — personal learning and non-commercial local use only. No commercial use, redistribution, or public forks without permission. Commercial licensing: [rocky.hk@gmail.com](mailto:rocky.hk@gmail.com).
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|||||||
+4
-4
@@ -1,11 +1,11 @@
|
|||||||
# OSGKeyboard
|
# OSGKeyboard
|
||||||
|
|
||||||
> 按住说话,松开即得 AI 润色文字,插入任意 App 的光标处。
|
> 按住说话,松开即得 AI 润色文字,插入任意 App 的光标处。
|
||||||
> 一款开源的 iOS 自定义键盘语音输入工具,灵感来自 [Typeless](https://typeless.com) 和 [OpenLess](https://github.com/Open-Less/openless)。
|
> 一款源码可见的 iOS 自定义键盘语音输入工具,灵感来自 [Typeless](https://typeless.com) 和 [OpenLess](https://github.com/Open-Less/openless)。
|
||||||
|
|
||||||

|

|
||||||

|

|
||||||

|

|
||||||
|
|
||||||
[English README](./README.md)
|
[English README](./README.md)
|
||||||
|
|
||||||
@@ -13,7 +13,7 @@
|
|||||||
|
|
||||||
## 这是什么?
|
## 这是什么?
|
||||||
|
|
||||||
OSGKeyboard 是商业语音输入工具的免费开源替代。它以 **iOS 自定义键盘扩展** 的形式运行,所以你可以在 **任何 App** 里使用 —— 微信、备忘录、邮件、ChatGPT、Claude、Cursor,无所不能。
|
OSGKeyboard 是商业语音输入工具的免费、源码可见替代方案。它以 **iOS 自定义键盘扩展** 的形式运行,所以你可以在 **任何 App** 里使用 —— 微信、备忘录、邮件、ChatGPT、Claude、Cursor,无所不能。
|
||||||
|
|
||||||
1. 长按麦克风键
|
1. 长按麦克风键
|
||||||
2. 自由说话
|
2. 自由说话
|
||||||
@@ -137,7 +137,7 @@ LLMProvider(
|
|||||||
|
|
||||||
## 许可
|
## 许可
|
||||||
|
|
||||||
[MIT](./LICENSE) —— 使用、修改、商用均可。无任何担保。
|
[OSGKeyboard 源码可见许可协议](./LICENSE) —— 仅限个人学习与非商用本地使用;禁止商用、再分发及公开 fork。商业授权请联系 [rocky.hk@gmail.com](mailto:rocky.hk@gmail.com)。
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|||||||
+5
-5
@@ -719,7 +719,7 @@
|
|||||||
|
|
||||||
<footer class="footer">
|
<footer class="footer">
|
||||||
<div class="footer-inner">
|
<div class="footer-inner">
|
||||||
<span data-i18n="footer.copy">© OSGKeyboard — open source under MIT</span>
|
<span data-i18n="footer.copy">© OSGKeyboard — source available</span>
|
||||||
<span>
|
<span>
|
||||||
<a href="https://github.com/hkgood/OSGKeyboard" data-i18n="footer.source">Source code</a>
|
<a href="https://github.com/hkgood/OSGKeyboard" data-i18n="footer.source">Source code</a>
|
||||||
·
|
·
|
||||||
@@ -732,7 +732,7 @@
|
|||||||
const copy = {
|
const copy = {
|
||||||
en: {
|
en: {
|
||||||
"nav.github": "GitHub",
|
"nav.github": "GitHub",
|
||||||
"hero.eyebrow": "Open source · iOS 26+",
|
"hero.eyebrow": "Source available · iOS 26+",
|
||||||
"hero.title": "Hold to talk.<br>Polished text in any app.",
|
"hero.title": "Hold to talk.<br>Polished text in any app.",
|
||||||
"hero.lead": "A custom keyboard that transcribes your voice on-device and optionally polishes the result with the LLM you configure — WeChat, Notes, Mail, ChatGPT, and everywhere else you type.",
|
"hero.lead": "A custom keyboard that transcribes your voice on-device and optionally polishes the result with the LLM you configure — WeChat, Notes, Mail, ChatGPT, and everywhere else you type.",
|
||||||
"hero.cta.primary": "View on GitHub",
|
"hero.cta.primary": "View on GitHub",
|
||||||
@@ -768,13 +768,13 @@
|
|||||||
"privacy.title": "Privacy at a glance",
|
"privacy.title": "Privacy at a glance",
|
||||||
"privacy.body": "Audio is processed on your device. We do not log ordinary keystrokes. In cloud polish mode, only transcribed text — never audio — goes to the API provider you configure.",
|
"privacy.body": "Audio is processed on your device. We do not log ordinary keystrokes. In cloud polish mode, only transcribed text — never audio — goes to the API provider you configure.",
|
||||||
"privacy.cta": "Read full policy",
|
"privacy.cta": "Read full policy",
|
||||||
"footer.copy": "© OSGKeyboard — open source under MIT",
|
"footer.copy": "© OSGKeyboard — source available",
|
||||||
"footer.source": "Source code",
|
"footer.source": "Source code",
|
||||||
"footer.privacy": "Privacy"
|
"footer.privacy": "Privacy"
|
||||||
},
|
},
|
||||||
zh: {
|
zh: {
|
||||||
"nav.github": "GitHub",
|
"nav.github": "GitHub",
|
||||||
"hero.eyebrow": "开源 · 需要 iOS 26+",
|
"hero.eyebrow": "源码可见 · 需要 iOS 26+",
|
||||||
"hero.title": "按住说话,<br>任意 App 里获得润色文字。",
|
"hero.title": "按住说话,<br>任意 App 里获得润色文字。",
|
||||||
"hero.lead": "自定义键盘扩展:在设备端转写语音,并可选通过你配置的 LLM 润色结果。适用于微信、备忘录、邮件、ChatGPT 等所有出现键盘的场景。",
|
"hero.lead": "自定义键盘扩展:在设备端转写语音,并可选通过你配置的 LLM 润色结果。适用于微信、备忘录、邮件、ChatGPT 等所有出现键盘的场景。",
|
||||||
"hero.cta.primary": "GitHub 仓库",
|
"hero.cta.primary": "GitHub 仓库",
|
||||||
@@ -810,7 +810,7 @@
|
|||||||
"privacy.title": "隐私摘要",
|
"privacy.title": "隐私摘要",
|
||||||
"privacy.body": "音频在设备端处理。我们不记录普通击键。云端润色模式下,仅转写文字(非音频)会发送到你配置的 API 服务商。",
|
"privacy.body": "音频在设备端处理。我们不记录普通击键。云端润色模式下,仅转写文字(非音频)会发送到你配置的 API 服务商。",
|
||||||
"privacy.cta": "查看完整政策",
|
"privacy.cta": "查看完整政策",
|
||||||
"footer.copy": "© OSGKeyboard · MIT 开源",
|
"footer.copy": "© OSGKeyboard · 源码可见",
|
||||||
"footer.source": "源代码",
|
"footer.source": "源代码",
|
||||||
"footer.privacy": "隐私"
|
"footer.privacy": "隐私"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -45,6 +45,7 @@
|
|||||||
|
|
||||||
<h2>Data retention</h2>
|
<h2>Data retention</h2>
|
||||||
<p>Settings and API keys remain on your device until you delete the app or reset settings. Transcription results are passed to the host app you are typing in and are not stored long-term by OSGKeyboard.</p>
|
<p>Settings and API keys remain on your device until you delete the app or reset settings. Transcription results are passed to the host app you are typing in and are not stored long-term by OSGKeyboard.</p>
|
||||||
|
<p><strong>Voice history</strong> — successful transcripts may be saved locally in the main app’s History tab for your convenience. This history stays on your device only, is never uploaded, and can be cleared at any time from History or by resetting settings.</p>
|
||||||
|
|
||||||
<h2>Contact</h2>
|
<h2>Contact</h2>
|
||||||
<p>Questions: open an issue at <a href="https://github.com/hkgood/OSGKeyboard">github.com/hkgood/OSGKeyboard</a>.</p>
|
<p>Questions: open an issue at <a href="https://github.com/hkgood/OSGKeyboard">github.com/hkgood/OSGKeyboard</a>.</p>
|
||||||
@@ -81,6 +82,7 @@
|
|||||||
|
|
||||||
<h2>数据保留</h2>
|
<h2>数据保留</h2>
|
||||||
<p>设置与 API Key 保留在设备上,直至卸载或重置。识别结果写入你正在使用的宿主 App,OSGKeyboard 不会长期存储。</p>
|
<p>设置与 API Key 保留在设备上,直至卸载或重置。识别结果写入你正在使用的宿主 App,OSGKeyboard 不会长期存储。</p>
|
||||||
|
<p><strong>语音历史</strong> — 成功的转写可能保存在主 App「历史」页,仅供本机查看,不会上传,可随时在历史页清空或通过重置设置清除。</p>
|
||||||
|
|
||||||
<h2>联系</h2>
|
<h2>联系</h2>
|
||||||
<p>问题反馈:<a href="https://github.com/hkgood/OSGKeyboard">github.com/hkgood/OSGKeyboard</a></p>
|
<p>问题反馈:<a href="https://github.com/hkgood/OSGKeyboard">github.com/hkgood/OSGKeyboard</a></p>
|
||||||
|
|||||||
@@ -46,6 +46,7 @@
|
|||||||
|
|
||||||
<h2>Data retention</h2>
|
<h2>Data retention</h2>
|
||||||
<p>Settings and API keys remain on your device until you delete the app or reset settings. Transcription results are passed to the host app you are typing in and are not stored long-term by OSGKeyboard.</p>
|
<p>Settings and API keys remain on your device until you delete the app or reset settings. Transcription results are passed to the host app you are typing in and are not stored long-term by OSGKeyboard.</p>
|
||||||
|
<p><strong>Voice history</strong> — successful transcripts may be saved locally in the main app’s History tab for your convenience. This history stays on your device only, is never uploaded, and can be cleared at any time from History or by resetting settings.</p>
|
||||||
|
|
||||||
<h2>Contact</h2>
|
<h2>Contact</h2>
|
||||||
<p>Questions: open an issue at <a href="https://github.com/hkgood/OSGKeyboard">github.com/hkgood/OSGKeyboard</a>.</p>
|
<p>Questions: open an issue at <a href="https://github.com/hkgood/OSGKeyboard">github.com/hkgood/OSGKeyboard</a>.</p>
|
||||||
@@ -82,6 +83,7 @@
|
|||||||
|
|
||||||
<h2>数据保留</h2>
|
<h2>数据保留</h2>
|
||||||
<p>设置与 API Key 保留在设备上,直至卸载或重置。识别结果写入你正在使用的宿主 App,OSGKeyboard 不会长期存储。</p>
|
<p>设置与 API Key 保留在设备上,直至卸载或重置。识别结果写入你正在使用的宿主 App,OSGKeyboard 不会长期存储。</p>
|
||||||
|
<p><strong>语音历史</strong> — 成功的转写可能保存在主 App「历史」页,仅供本机查看,不会上传,可随时在历史页清空或通过重置设置清除。</p>
|
||||||
|
|
||||||
<h2>联系</h2>
|
<h2>联系</h2>
|
||||||
<p>问题反馈:<a href="https://github.com/hkgood/OSGKeyboard">github.com/hkgood/OSGKeyboard</a></p>
|
<p>问题反馈:<a href="https://github.com/hkgood/OSGKeyboard">github.com/hkgood/OSGKeyboard</a></p>
|
||||||
|
|||||||
@@ -65,6 +65,7 @@ targets:
|
|||||||
- path: OSGKeyboard/en.lproj
|
- path: OSGKeyboard/en.lproj
|
||||||
- path: OSGKeyboard/zh-Hans.lproj
|
- path: OSGKeyboard/zh-Hans.lproj
|
||||||
- path: OSGKeyboard/Resources/Fonts/MaterialIcons-Regular.ttf
|
- path: OSGKeyboard/Resources/Fonts/MaterialIcons-Regular.ttf
|
||||||
|
- path: OSGKeyboard/PrivacyInfo.xcprivacy
|
||||||
info:
|
info:
|
||||||
path: OSGKeyboard/Info.plist
|
path: OSGKeyboard/Info.plist
|
||||||
properties:
|
properties:
|
||||||
@@ -91,6 +92,7 @@ targets:
|
|||||||
- audio
|
- audio
|
||||||
NSAppTransportSecurity:
|
NSAppTransportSecurity:
|
||||||
NSAllowsArbitraryLoads: false
|
NSAllowsArbitraryLoads: false
|
||||||
|
ITSAppUsesNonExemptEncryption: false
|
||||||
CFBundleURLTypes:
|
CFBundleURLTypes:
|
||||||
- CFBundleURLName: com.osgkeyboard.ios.settings
|
- CFBundleURLName: com.osgkeyboard.ios.settings
|
||||||
CFBundleURLSchemes:
|
CFBundleURLSchemes:
|
||||||
|
|||||||
Reference in New Issue
Block a user