mirror of
https://github.com/nextcloud/desktop.git
synced 2025-10-26 11:17:43 +00:00
feat(file-provider): Integrated localizations from Transifex.
- Introduced TransifexStringCatalogSanitizer command-line utility. - Introduced dedicated Transifex CLI configuration file for NextcloudIntegration project. Signed-off-by: Iva Horn <iva.horn@icloud.com>
This commit is contained in:
parent
dd351b73dc
commit
6ba027c906
10
REUSE.toml
10
REUSE.toml
@ -29,6 +29,16 @@ precedence = "aggregate"
|
||||
SPDX-FileCopyrightText = "2014 ownCloud GmbH"
|
||||
SPDX-License-Identifier = "GPL-2.0-or-later"
|
||||
|
||||
[[annotations]]
|
||||
path = [
|
||||
"admin/osx/TransifexStringCatalogSanitizer/Package.swift",
|
||||
"admin/osx/TransifexStringCatalogSanitizer/Package.resolved",
|
||||
"admin/osx/TransifexStringCatalogSanitizer/README.md",
|
||||
]
|
||||
precedence = "aggregate"
|
||||
SPDX-FileCopyrightText = "2025 Nextcloud GmbH and Nextcloud contributors"
|
||||
SPDX-License-Identifier = "GPL-2.0-or-later"
|
||||
|
||||
[[annotations]]
|
||||
path = ["admin/osx/mac-crafter/Package.resolved", "shell_integration/MacOSX/NextcloudIntegration/NextcloudIntegration.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved"]
|
||||
precedence = "aggregate"
|
||||
|
||||
10
admin/osx/TransifexStringCatalogSanitizer/.gitignore
vendored
Normal file
10
admin/osx/TransifexStringCatalogSanitizer/.gitignore
vendored
Normal file
@ -0,0 +1,10 @@
|
||||
# SPDX-FileCopyrightText: 2025 Nextcloud GmbH and Nextcloud contributors
|
||||
# SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
.DS_Store
|
||||
/.build
|
||||
/Packages
|
||||
xcuserdata/
|
||||
DerivedData/
|
||||
.swiftpm
|
||||
.netrc
|
||||
15
admin/osx/TransifexStringCatalogSanitizer/Package.resolved
Normal file
15
admin/osx/TransifexStringCatalogSanitizer/Package.resolved
Normal file
@ -0,0 +1,15 @@
|
||||
{
|
||||
"originHash" : "29c76d8c60e24badae4a42909eb97c07dfa3a8dbfe5940de1e06ec95358c8fda",
|
||||
"pins" : [
|
||||
{
|
||||
"identity" : "swift-argument-parser",
|
||||
"kind" : "remoteSourceControl",
|
||||
"location" : "https://github.com/apple/swift-argument-parser.git",
|
||||
"state" : {
|
||||
"revision" : "309a47b2b1d9b5e991f36961c983ecec72275be3",
|
||||
"version" : "1.6.1"
|
||||
}
|
||||
}
|
||||
],
|
||||
"version" : 3
|
||||
}
|
||||
21
admin/osx/TransifexStringCatalogSanitizer/Package.swift
Normal file
21
admin/osx/TransifexStringCatalogSanitizer/Package.swift
Normal file
@ -0,0 +1,21 @@
|
||||
// swift-tools-version: 6.0
|
||||
// The swift-tools-version declares the minimum version of Swift required to build this package.
|
||||
|
||||
import PackageDescription
|
||||
|
||||
let package = Package(
|
||||
name: "TransifexStringCatalogSanitizer",
|
||||
dependencies: [
|
||||
.package(url: "https://github.com/apple/swift-argument-parser.git", from: "1.2.0"),
|
||||
],
|
||||
targets: [
|
||||
// Targets are the basic building blocks of a package, defining a module or a test suite.
|
||||
// Targets can depend on other targets in this package and products from dependencies.
|
||||
.executableTarget(
|
||||
name: "TransifexStringCatalogSanitizer",
|
||||
dependencies: [
|
||||
.product(name: "ArgumentParser", package: "swift-argument-parser"),
|
||||
]
|
||||
),
|
||||
]
|
||||
)
|
||||
15
admin/osx/TransifexStringCatalogSanitizer/README.md
Normal file
15
admin/osx/TransifexStringCatalogSanitizer/README.md
Normal file
@ -0,0 +1,15 @@
|
||||
# Transifex String Catalog Sanitizer
|
||||
|
||||
Sanitize Xcode string catalogs which were pulled from a Transifex online resource.
|
||||
|
||||
## Usage
|
||||
|
||||
See the integrated help for up to date reference.
|
||||
|
||||
```sh
|
||||
swift run TransifexStringCatalogSanitizer --help
|
||||
```
|
||||
|
||||
## Development
|
||||
|
||||
This Swift command-line utility can be easily run and debugged from Xcode.
|
||||
@ -0,0 +1,83 @@
|
||||
// SPDX-FileCopyrightText: 2025 Nextcloud GmbH and Nextcloud contributors
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
import ArgumentParser
|
||||
import Foundation
|
||||
|
||||
@main
|
||||
struct TransifexStringCatalogSanitizer: ParsableCommand {
|
||||
@Argument(help: "The string catalog file to sanitize.")
|
||||
var input: String
|
||||
|
||||
mutating func run() throws {
|
||||
let url = URL(fileURLWithPath: input)
|
||||
let data = try Data(contentsOf: url)
|
||||
|
||||
guard var root = try JSONSerialization.jsonObject(with: data) as? [String: Any] else {
|
||||
throw TransifexStringCatalogSanitizerError.jsonObject
|
||||
}
|
||||
|
||||
guard var strings = root["strings"] as? [String: Any] else {
|
||||
throw TransifexStringCatalogSanitizerError.missingStrings
|
||||
}
|
||||
|
||||
try sanitizeStrings(&strings)
|
||||
|
||||
// Update the root with modified strings
|
||||
root["strings"] = strings
|
||||
|
||||
// Write the processed data back to the original file
|
||||
let processedData = try JSONSerialization.data(withJSONObject: root, options: [.prettyPrinted, .sortedKeys])
|
||||
try processedData.write(to: url)
|
||||
}
|
||||
|
||||
private func sanitizeStrings(_ strings: inout [String: Any]) throws {
|
||||
for key in strings.keys.sorted() {
|
||||
print("💬 \"\(key)\"")
|
||||
|
||||
guard var string = strings[key] as? [String: Any] else {
|
||||
throw TransifexStringCatalogSanitizerError.missingString
|
||||
}
|
||||
|
||||
guard var localizations = string["localizations"] as? [String: Any] else {
|
||||
throw TransifexStringCatalogSanitizerError.missingLocalizations
|
||||
}
|
||||
|
||||
try sanitizeLocalizations(&localizations)
|
||||
|
||||
// Update the string with modified localizations
|
||||
string["localizations"] = localizations
|
||||
strings[key] = string
|
||||
}
|
||||
}
|
||||
|
||||
private func sanitizeLocalizations(_ localizations: inout [String: Any]) throws {
|
||||
var localizationsToRemove: [String] = []
|
||||
|
||||
for localeCode in localizations.keys.sorted() {
|
||||
guard let localization = localizations[localeCode] as? [String: Any] else {
|
||||
throw TransifexStringCatalogSanitizerError.missingLocalization
|
||||
}
|
||||
|
||||
guard let stringUnit = localization["stringUnit"] as? [String: Any] else {
|
||||
throw TransifexStringCatalogSanitizerError.missingStringUnit
|
||||
}
|
||||
|
||||
guard let value = stringUnit["value"] as? String else {
|
||||
throw TransifexStringCatalogSanitizerError.missingValue
|
||||
}
|
||||
|
||||
if value.isEmpty {
|
||||
print("\t❌ \(localeCode): empty, will be removed")
|
||||
localizationsToRemove.append(localeCode)
|
||||
} else {
|
||||
print("\t✅ \(localeCode): \"\(value)\"")
|
||||
}
|
||||
}
|
||||
|
||||
// Remove empty localizations
|
||||
for localeCode in localizationsToRemove {
|
||||
localizations.removeValue(forKey: localeCode)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,12 @@
|
||||
// SPDX-FileCopyrightText: 2025 Nextcloud GmbH and Nextcloud contributors
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
enum TransifexStringCatalogSanitizerError: Error {
|
||||
case jsonObject
|
||||
case missingLocalization
|
||||
case missingLocalizations
|
||||
case missingString
|
||||
case missingStrings
|
||||
case missingStringUnit
|
||||
case missingValue
|
||||
}
|
||||
19
shell_integration/MacOSX/NextcloudIntegration/.tx/config
Executable file
19
shell_integration/MacOSX/NextcloudIntegration/.tx/config
Executable file
@ -0,0 +1,19 @@
|
||||
# SPDX-FileCopyrightText: 2025 Nextcloud GmbH and Nextcloud contributors
|
||||
# SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
[main]
|
||||
host = https://app.transifex.com
|
||||
# Complete mapping from language codes on Transifex to the ones configured in the Xcode project for those where the codes diverge.
|
||||
lang_map = bg_BG: bg, bn_BD: bn, cs_CZ: cs, cy_GB: cy, es_AR: es-AR, es_DO: es-DO, es_EC: es-EC, es_SV: es-SV, es_GT: es-GT, es_HN: es-HN, es_419: es-419, es_MX: es-MX, es_NI: es-NI, es_PA: es-PA, es_PY: es-PY, es_PE: es-PE, es_PR: es-PR, es_UY: es-UY, et_EE: et, fi_FI: fi, hi_IN: hi, hu_HU: hu, ja_JP: ja, ka_GE: ka, lt_LT: lt, ms_MY: ms, nb_NO: nb-NO, nn_NO: nn-NO, pt_BR: pt-BR, pt_PT: pt-PT, sk_SK: sk, th_TH: th, ur_PK: ur, zh_CN: zh-Hans, zh_HK: zh-HK, zh_TW: zh-Hant-TW, zu_ZA: zu
|
||||
|
||||
[o:nextcloud:p:nextcloud:r:client-fileprovider]
|
||||
file_filter = FileProviderExt/Localizable.xcstrings
|
||||
source_file = FileProviderExt/Localizable.xcstrings
|
||||
source_lang = en
|
||||
type = XCSTRINGS
|
||||
|
||||
[o:nextcloud:p:nextcloud:r:client-fileproviderui]
|
||||
file_filter = FileProviderUIExt/Localizable.xcstrings
|
||||
source_file = FileProviderUIExt/Localizable.xcstrings
|
||||
source_lang = en
|
||||
type = XCSTRINGS
|
||||
@ -1,12 +1,124 @@
|
||||
{
|
||||
"sourceLanguage": "en",
|
||||
"strings": {
|
||||
"Allow automatic freeing up space": {
|
||||
"extractionState": "manual"
|
||||
"sourceLanguage" : "en",
|
||||
"strings" : {
|
||||
"Allow automatic freeing up space" : {
|
||||
"extractionState" : "manual",
|
||||
"localizations" : {
|
||||
"de" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Automatisches Freigeben von Speicherplatz zulassen"
|
||||
}
|
||||
},
|
||||
"de_DE" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Automatisches Freigeben von Speicherplatz zulassen"
|
||||
}
|
||||
},
|
||||
"en" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Allow automatic freeing up space"
|
||||
}
|
||||
},
|
||||
"es" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Permitir la liberación de espacio automática"
|
||||
}
|
||||
},
|
||||
"fr" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Autoriser la libération automatique de l'espace"
|
||||
}
|
||||
},
|
||||
"gl" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Permitir a liberación automática de espazo"
|
||||
}
|
||||
},
|
||||
"pt_BR" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Permitir liberação automática de espaço"
|
||||
}
|
||||
},
|
||||
"tr" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Yer açmak için dosyalar otomatik olarak silinsin"
|
||||
}
|
||||
},
|
||||
"zh_HK" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "允許自動釋放空間"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"Always keep downloaded": {
|
||||
"extractionState": "manual"
|
||||
"Always keep downloaded" : {
|
||||
"extractionState" : "manual",
|
||||
"localizations" : {
|
||||
"de" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Immer heruntergeladen halten"
|
||||
}
|
||||
},
|
||||
"de_DE" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Immer heruntergeladen halten"
|
||||
}
|
||||
},
|
||||
"en" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Always keep downloaded"
|
||||
}
|
||||
},
|
||||
"es" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Siempre mantener descargado"
|
||||
}
|
||||
},
|
||||
"fr" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Toujours conserver en local"
|
||||
}
|
||||
},
|
||||
"gl" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Manter sempre descargado"
|
||||
}
|
||||
},
|
||||
"pt_BR" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Sempre manter baixados"
|
||||
}
|
||||
},
|
||||
"tr" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Her zaman indirilmiş tutulsun"
|
||||
}
|
||||
},
|
||||
"zh_HK" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "一律保留已下載的下載"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"version": "1.0"
|
||||
"version" : "1.0"
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@ -3,6 +3,38 @@
|
||||
This is an Xcode project to build platform-specific components for the Nextcloud desktop client.
|
||||
As an example, this includes the file provider extension.
|
||||
|
||||
## Localization
|
||||
|
||||
Transifex is used for localization.
|
||||
Currently, [the file provider extension](https://app.transifex.com/nextcloud/nextcloud/client-fileprovider/) and [file provider UI extension](https://app.transifex.com/nextcloud/nextcloud/client-fileproviderui/) both have a resource there.
|
||||
These localizations are excluded from our usual and automated translation flow due to how Transifex synchronizes Xcode string catalogs and the danger of data loss.
|
||||
To pull updated localizations from Transifex into the Xcode project manually, follow the steps below.
|
||||
|
||||
### Configuration
|
||||
|
||||
The dedicated [`.tx/config`](.tx/config) file is used.
|
||||
|
||||
## Pull Translations
|
||||
|
||||
Run this in the "NextcloudIntegration" project folder of your repository clone:
|
||||
|
||||
```sh
|
||||
tx pull --force --all --mode=reviewed --minimum-perc=50
|
||||
```
|
||||
|
||||
### Sanitize Translations
|
||||
|
||||
Transifex returns empty strings for keys with untranslated localizations.
|
||||
To remove them, we use the Swift command-line utility [TransifexStringCatalogSanitizer](../../../admin/osx/TransifexStringCatalogSanitizer/).
|
||||
See its dedicated README for usage instructions.
|
||||
Use it for all updated Xcode string catalogs.
|
||||
|
||||
### Cleanup
|
||||
|
||||
1. Revert your changes to the Transifex configuration.
|
||||
2. Review the updated Xcode string catalogs.
|
||||
3. Commit the updated Xcode string catalogs.
|
||||
|
||||
## Nextcloud Developer Build
|
||||
|
||||
There is a special target in the Xcode project which integrates the `mac-crafter` command-line tool as an external build system in form of a scheme.
|
||||
|
||||
Loading…
Reference in New Issue
Block a user