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:
Iva Horn 2025-10-07 12:13:05 +02:00
parent dd351b73dc
commit 6ba027c906
11 changed files with 4218 additions and 83 deletions

View File

@ -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"

View 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

View 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
}

View 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"),
]
),
]
)

View 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.

View File

@ -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)
}
}
}

View File

@ -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
}

View 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

View File

@ -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"
}

View File

@ -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.