Skip to content

Commit 8dcf17f

Browse files
committed
Add script to pull Transifex translations
1 parent 8067944 commit 8dcf17f

File tree

6 files changed

+273
-62
lines changed

6 files changed

+273
-62
lines changed

.tx/config

Lines changed: 6 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -1,62 +1,8 @@
11
[main]
2-
host = https://www.transifex.com
2+
host = https://app.transifex.com
33

4-
[o:synonym:p:bitkit:r:cards]
5-
file_filter = Bitkit/Resources/Localization/<lang>/<lang>_cards.json
6-
source_file = Bitkit/Resources/Localization/en/en_cards.json
7-
source_lang = en
8-
type = STRUCTURED_JSON
9-
10-
[o:synonym:p:bitkit:r:common]
11-
file_filter = Bitkit/Resources/Localization/<lang>/<lang>_common.json
12-
source_file = Bitkit/Resources/Localization/en/en_common.json
13-
source_lang = en
14-
type = STRUCTURED_JSON
15-
16-
[o:synonym:p:bitkit:r:fee]
17-
file_filter = Bitkit/Resources/Localization/<lang>/<lang>_fee.json
18-
source_file = Bitkit/Resources/Localization/en/en_fee.json
19-
source_lang = en
20-
type = STRUCTURED_JSON
21-
22-
[o:synonym:p:bitkit:r:lightning]
23-
file_filter = Bitkit/Resources/Localization/<lang>/<lang>_lightning.json
24-
source_file = Bitkit/Resources/Localization/en/en_lightning.json
25-
source_lang = en
26-
type = STRUCTURED_JSON
27-
28-
[o:synonym:p:bitkit:r:onboarding]
29-
file_filter = Bitkit/Resources/Localization/<lang>/<lang>_onboarding.json
30-
source_file = Bitkit/Resources/Localization/en/en_onboarding.json
31-
source_lang = en
32-
type = STRUCTURED_JSON
33-
34-
[o:synonym:p:bitkit:r:other]
35-
file_filter = Bitkit/Resources/Localization/<lang>/<lang>_other.json
36-
source_file = Bitkit/Resources/Localization/en/en_other.json
37-
source_lang = en
38-
type = STRUCTURED_JSON
39-
40-
[o:synonym:p:bitkit:r:security]
41-
file_filter = Bitkit/Resources/Localization/<lang>/<lang>_security.json
42-
source_file = Bitkit/Resources/Localization/en/en_security.json
43-
source_lang = en
44-
type = STRUCTURED_JSON
45-
46-
[o:synonym:p:bitkit:r:settingss]
47-
file_filter = Bitkit/Resources/Localization/<lang>/<lang>_settings.json
48-
source_file = Bitkit/Resources/Localization/en/en_settings.json
49-
source_lang = en
50-
type = STRUCTURED_JSON
51-
52-
[o:synonym:p:bitkit:r:slashtags]
53-
file_filter = Bitkit/Resources/Localization/<lang>/<lang>_slashtags.json
54-
source_file = Bitkit/Resources/Localization/en/en_slashtags.json
55-
source_lang = en
56-
type = STRUCTURED_JSON
57-
58-
[o:synonym:p:bitkit:r:wallet]
59-
file_filter = Bitkit/Resources/Localization/<lang>/<lang>_wallet.json
60-
source_file = Bitkit/Resources/Localization/en/en_wallet.json
61-
source_lang = en
62-
type = STRUCTURED_JSON
4+
[o:synonym:p:bitkit:r:localizablestrings]
5+
file_filter = Bitkit/Resources/Localization/<lang>.lproj/Localizable.strings
6+
source_file = Bitkit/Resources/Localization/en.lproj/Localizable.strings
7+
source_lang = en
8+
type = IOS

Bitkit/Resources/Localization/ar.lproj/Localizable.strings

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
"common__delete_yes" = "نعم، احذف";
2+
"settings__backup__category_contacts" = "جهات الاتصال";
23
"slashtags__your_name" = "اسمك";
34
"slashtags__your_name_capital" = "اسمك";
45
"slashtags__contact_name" = "اسم جهة الاتصال";

Bitkit/Resources/Localization/es.lproj/Localizable.strings

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -351,6 +351,7 @@
351351
"security__use_pin" = "Utilizar código PIN";
352352
"security__wiped_title" = "Datos de Monedero Borrados";
353353
"security__wiped_message" = "Bitkit ha sido restaurado y todos los datos de monedero han sido borrados.";
354+
"settings__settings" = "Ajustes";
354355
"settings__dev_enabled_title" = "Opciones de desarrollo activadas";
355356
"settings__dev_disabled_title" = "Opciones de desarrollo desactivadas";
356357
"settings__general_title" = "General";

Bitkit/Resources/Localization/fr.lproj/Localizable.strings

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@
44
"cards__discount__description" = "Solde Dépenses";
55
"cards__quickpay__title" = "QuickPay";
66
"cards__quickpay__description" = "Scannez et payez";
7+
"cards__notifications__title" = "Soyez rémunéré";
8+
"cards__notifications__description" = "Sans ouvrir Bitkit";
79
"cards__invite__title" = "Inviter";
810
"cards__invite__description" = "Partager Bitkit";
911
"cards__lightning__title" = "Dépenser";
@@ -590,9 +592,29 @@
590592
"settings__general__speed_fee_total_fiat" = "₿ {feeSats} pour une transaction moyenne ({fiatSymbol}{fiatFormatted} )";
591593
"settings__general__tags" = "Tags";
592594
"settings__general__tags_previously" = "Étiquettes précédemment utilisées";
595+
"settings__general__language" = "Langue";
596+
"settings__general__language_title" = "Langue";
597+
"settings__general__language_other" = "Langue de l'interface";
593598
"settings__widgets__nav_title" = "Widgets";
594599
"settings__widgets__showWidgets" = "Widgets";
595600
"settings__widgets__showWidgetTitles" = "Afficher les titres des widgets";
601+
"settings__notifications__nav_title" = "Paiements en arrière-plan";
602+
"settings__notifications__intro__title" = "Soyez rémunéré\n<accent>de manière passive</accent>";
603+
"settings__notifications__intro__text" = "Activez les notifications pour être payé, même lorsque votre application Bitkit est fermée.";
604+
"settings__notifications__intro__button" = "Activer";
605+
"settings__notifications__settings__toggle" = "Soyez rémunéré lorsque Bitkit est fermé";
606+
"settings__notifications__settings__enabled" = "Les paiements en arrière-plan sont activés. Vous pouvez recevoir des fonds même lorsque l'application est fermée (si votre appareil est connecté à Internet).";
607+
"settings__notifications__settings__disabled" = "Activez les paiements en arrière-plan pour recevoir des fonds même lorsque l'application est fermée (si votre appareil est connecté à Internet).";
608+
"settings__notifications__settings__denied" = "Les paiements en arrière-plan sont désactivés, car vous avez refusé les notifications.";
609+
"settings__notifications__settings__preview__title" = "Paiement reçu";
610+
"settings__notifications__settings__preview__text" = "Ouvrez Bitkit pour voir les détails";
611+
"settings__notifications__settings__preview__time" = "Il y a 3 min";
612+
"settings__notifications__settings__privacy__label" = "Confidentialité";
613+
"settings__notifications__settings__privacy__text" = "Inclure le montant dans les notifications";
614+
"settings__notifications__settings__notifications__label" = "Notifications";
615+
"settings__notifications__settings__notifications__text" = "Pour activer les paiements en arrière-plan, veuillez activer les notifications dans les paramètres iOS Bitkit.";
616+
"settings__notifications__settings__button__enabled" = "Personnaliser dans {plateforme} Paramètres Bitkit";
617+
"settings__notifications__settings__button__disabled" = "Activer dans {plateforme} Paramètres Bitkit";
596618
"settings__quickpay__nav_title" = "QuickPay";
597619
"settings__quickpay__intro__title" = "<accent>Sans friction</accent>\npaiements";
598620
"settings__quickpay__intro__description" = "Bitkit QuickPay accélère le passage à la caisse en payant automatiquement les QR codes lorsqu\'ils sont scannés.";
@@ -765,6 +787,10 @@
765787
"settings__gap__reset" = "Réinitialiser";
766788
"settings__gap__gap_limit_update_title" = "Limite de l\'écart d\'adressage Mise à jour";
767789
"settings__gap__gap_limit_update_description" = "Les modifications prendront effet après le redémarrage de l\'application.";
790+
"settings__gap__look_behind" = "Voir en arrière";
791+
"settings__gap__look_ahead" = "Voir en avant";
792+
"settings__gap__look_behind_change" = "Voir changement en arrière";
793+
"settings__gap__look_ahead_change" = "Voir changement en avant";
768794
"settings__rgs__server_url" = "URL du serveur Rapid-Gossip-Sync";
769795
"settings__rgs__button_connect" = "Connecter";
770796
"settings__rgs__update_success_title" = "Mise à jour du serveur Rapid-Gossip-Sync";
@@ -1026,6 +1052,9 @@
10261052
"wallet__activity_tabs__sent" = "Envoyé";
10271053
"wallet__activity_tabs__received" = "Reçu";
10281054
"wallet__activity_tabs__other" = "Autre";
1055+
"wallet__activity_group_month" = "Ce mois-ci";
1056+
"wallet__activity_group_week" = "Cette semaine";
1057+
"wallet__activity_group_year" = "Cette année";
10291058
"wallet__details_savings_title" = "Épargnes";
10301059
"wallet__details_spending_title" = "Dépenses";
10311060
"wallet__savings__title" = "Épargnes";

README.md

Lines changed: 24 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -31,9 +31,31 @@ xcodebuild -workspace Bitkit.xcodeproj/project.xcworkspace \
3131

3232
## Localization
3333

34-
Localization files are synced from Transifex. Use [bitkit-transifex-sync](https://github.com/synonymdev/bitkit-transifex-sync) to sync the translations.
34+
### Pulling Translations
3535

36-
This checks for missing translations and validates that all translation keys used in the Swift code exist in the .strings files. (This check is also automated in GitHub Actions)
36+
To pull the latest translations from Transifex:
37+
38+
1. **Install Transifex CLI** (if not already installed):
39+
- Follow the installation instructions: [Transifex CLI Installation](https://developers.transifex.com/docs/cli)
40+
41+
2. **Authenticate with Transifex** (if not already configured):
42+
- Create a `.transifexrc` file in your home directory (`~/.transifexrc`) with your API token:
43+
```ini
44+
[https://www.transifex.com]
45+
rest_hostname = https://rest.api.transifex.com
46+
token = YOUR_API_TOKEN_HERE
47+
```
48+
- You can get your API token from your [Transifex account settings](https://www.transifex.com/user/settings/api/)
49+
- The CLI will prompt you for an API token if one is not configured
50+
51+
3. **Pull translations**:
52+
```sh
53+
./scripts/pull-translations.sh
54+
```
55+
56+
### Validating Translations
57+
58+
This checks for missing translations and validates that all translation keys used in the Swift code exist in the `.strings` files. (This check is also automated in GitHub Actions)
3759

3860
```bash
3961
node scripts/validate-translations.js

scripts/pull-translations.sh

Lines changed: 212 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,212 @@
1+
#!/bin/bash
2+
3+
# Script to pull translations from Transifex and clean up empty files/directories
4+
#
5+
# Note: Uses --mode onlytranslated to only download translated strings, which may result
6+
# in empty files for incomplete translations that will be cleaned up by this script.
7+
8+
set -e
9+
10+
# Validate script is run from project root
11+
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
12+
PROJECT_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)"
13+
LOCALIZATION_DIR="$PROJECT_ROOT/Bitkit/Resources/Localization"
14+
15+
if [ ! -d "$LOCALIZATION_DIR" ]; then
16+
echo "Error: Localization directory not found: $LOCALIZATION_DIR"
17+
echo "Please run this script from the project root directory."
18+
exit 1
19+
fi
20+
21+
if [ ! -f "$PROJECT_ROOT/.tx/config" ]; then
22+
echo "Error: Transifex config not found: $PROJECT_ROOT/.tx/config"
23+
echo "Please ensure Transifex is configured for this project."
24+
exit 1
25+
fi
26+
27+
# Helper function to rename or merge directories
28+
rename_or_merge() {
29+
local src="$1"
30+
local dst="$2"
31+
local src_name=$(basename "$src")
32+
local dst_name=$(basename "$dst")
33+
local merge_errors=0
34+
35+
if [ ! -d "$src" ]; then
36+
echo " Warning: Source directory does not exist: $src_name"
37+
return 1
38+
fi
39+
40+
if [ ! -d "$dst" ]; then
41+
echo " Renaming: $src_name -> $dst_name"
42+
if mv "$src" "$dst"; then
43+
return 0
44+
else
45+
echo " Error: Failed to rename $src_name to $dst_name"
46+
return 1
47+
fi
48+
else
49+
echo " Merging: $src_name -> $dst_name"
50+
while IFS= read -r -d '' item; do
51+
if ! mv "$item" "$dst/" 2>/dev/null; then
52+
echo " Warning: Failed to move $(basename "$item") from $src_name"
53+
merge_errors=$((merge_errors + 1))
54+
fi
55+
done < <(find "$src" -mindepth 1 -maxdepth 1 -print0 2>/dev/null)
56+
57+
if [ "$merge_errors" -eq 0 ]; then
58+
if rmdir "$src" 2>/dev/null; then
59+
return 0
60+
else
61+
echo " Warning: Could not remove empty directory: $src_name"
62+
return 1
63+
fi
64+
else
65+
echo " Error: Some files could not be merged from $src_name"
66+
return 1
67+
fi
68+
fi
69+
}
70+
71+
# Validate .strings file has basic structure
72+
validate_strings_file() {
73+
local file="$1"
74+
# Basic validation: check file exists and is readable
75+
if [ ! -r "$file" ]; then
76+
echo " Warning: $file is not readable, skipping normalization"
77+
return 1
78+
fi
79+
# Check if file has at least one translation entry (basic format check)
80+
if ! grep -q '^"[^"]*"[[:space:]]*=' "$file" 2>/dev/null && [ -s "$file" ]; then
81+
echo " Warning: $file does not appear to be a valid .strings file, skipping normalization"
82+
return 1
83+
fi
84+
return 0
85+
}
86+
87+
echo "Pulling translations from Transifex..."
88+
89+
# Check if tx command is available
90+
if ! command -v tx &> /dev/null; then
91+
echo "Error: Transifex CLI (tx) is not installed or not in PATH"
92+
echo "Please install it: https://developers.transifex.com/docs/cli"
93+
exit 1
94+
fi
95+
96+
# Run tx pull and check for errors
97+
set +e
98+
tx pull -a --mode onlytranslated
99+
TX_EXIT_CODE=$?
100+
set -e
101+
102+
if [ "$TX_EXIT_CODE" -ne 0 ]; then
103+
echo "Error: Transifex pull failed with exit code $TX_EXIT_CODE"
104+
echo "Please check your Transifex configuration and authentication."
105+
exit 1
106+
fi
107+
108+
echo ""
109+
echo "Renaming and cleaning up directories..."
110+
111+
RENAMED_COUNT=0
112+
113+
# Process all .lproj directories
114+
while IFS= read -r dir; do
115+
dir_name=$(basename "$dir")
116+
dir_path=$(dirname "$dir")
117+
118+
case "$dir_name" in
119+
arb.lproj)
120+
rename_or_merge "$dir" "$dir_path/ar.lproj" && RENAMED_COUNT=$((RENAMED_COUNT + 1))
121+
;;
122+
es_419.lproj)
123+
rename_or_merge "$dir" "$dir_path/es-419.lproj" && RENAMED_COUNT=$((RENAMED_COUNT + 1))
124+
;;
125+
es_ES.lproj)
126+
rename_or_merge "$dir" "$dir_path/es.lproj" && RENAMED_COUNT=$((RENAMED_COUNT + 1))
127+
;;
128+
pt_PT.lproj|pt-PT.lproj)
129+
rename_or_merge "$dir" "$dir_path/pt.lproj" && RENAMED_COUNT=$((RENAMED_COUNT + 1))
130+
;;
131+
pt_BR.lproj)
132+
rename_or_merge "$dir" "$dir_path/pt-BR.lproj" && RENAMED_COUNT=$((RENAMED_COUNT + 1))
133+
;;
134+
*_*.lproj)
135+
new_name=$(echo "$dir_name" | sed 's/_/-/g')
136+
[ "$new_name" != "$dir_name" ] && rename_or_merge "$dir" "$dir_path/$new_name" && RENAMED_COUNT=$((RENAMED_COUNT + 1))
137+
;;
138+
esac
139+
done < <(find "$LOCALIZATION_DIR" -type d -name "*.lproj" 2>/dev/null | sort)
140+
141+
echo " Renamed $RENAMED_COUNT directories"
142+
143+
echo ""
144+
echo "Normalizing .strings files..."
145+
146+
# Normalize .strings files: remove trailing whitespace and empty string entries
147+
NORMALIZED_COUNT=0
148+
while IFS= read -r file; do
149+
if ! validate_strings_file "$file"; then
150+
continue
151+
fi
152+
153+
if awk '{
154+
gsub(/[[:space:]]+$/, "")
155+
if ($0 ~ /^"[^"]*"[[:space:]]*=[[:space:]]*"";[[:space:]]*$/) next
156+
print
157+
}' "$file" > "$file.tmp"; then
158+
if mv "$file.tmp" "$file" 2>/dev/null; then
159+
NORMALIZED_COUNT=$((NORMALIZED_COUNT + 1))
160+
else
161+
echo " Warning: Failed to replace $file"
162+
rm -f "$file.tmp"
163+
fi
164+
else
165+
echo " Warning: Failed to normalize $file"
166+
rm -f "$file.tmp"
167+
fi
168+
done < <(find "$LOCALIZATION_DIR" -type f -name "*.strings" 2>/dev/null)
169+
170+
echo " Normalized $NORMALIZED_COUNT files"
171+
172+
echo ""
173+
echo "Cleaning up empty files and directories..."
174+
175+
EMPTY_COUNT=0
176+
DELETED_DIRS=0
177+
declare -a dirs_to_check
178+
179+
# Delete empty .strings files and collect their directories
180+
set +e
181+
while IFS= read -r file; do
182+
line_count=$(wc -l < "$file" 2>/dev/null | tr -d ' ' || echo "0")
183+
translation_count=$(grep -c '^"[^"]*" = ' "$file" 2>/dev/null || echo "0")
184+
185+
if [ "$line_count" -le 2 ] || [ "$translation_count" -eq 0 ]; then
186+
echo " Deleting empty file: $file"
187+
dirs_to_check+=("$(dirname "$file")")
188+
rm -f "$file"
189+
EMPTY_COUNT=$((EMPTY_COUNT + 1))
190+
fi
191+
done < <(find "$LOCALIZATION_DIR" -type f -name "*.strings" 2>/dev/null)
192+
set -e
193+
194+
# Remove empty .lproj directories (process depth-first by reverse sorting paths)
195+
for dir in $(printf '%s\n' "${dirs_to_check[@]}" | sort -u | sort -r); do
196+
[ -d "$dir" ] || continue
197+
set +e
198+
file_count=$(find "$dir" -type f ! -name '.DS_Store' 2>/dev/null | wc -l | tr -d ' ')
199+
set -e
200+
if [ "$file_count" -eq 0 ]; then
201+
echo " Deleting empty directory: $dir"
202+
if rmdir "$dir" 2>/dev/null; then
203+
DELETED_DIRS=$((DELETED_DIRS + 1))
204+
fi
205+
fi
206+
done
207+
208+
echo ""
209+
echo "Complete!"
210+
echo " Renamed: $RENAMED_COUNT"
211+
echo " Normalized: $NORMALIZED_COUNT"
212+
echo " Deleted files: $EMPTY_COUNT, Deleted dirs: $DELETED_DIRS"

0 commit comments

Comments
 (0)