|
1 | | -''' |
2 | | -Script: translate-en-messages.py |
3 | | -Version: 2024.5.14.1 |
4 | | -Description: Translate msg's from en/messages.json to [[output_langs]/messages.json] |
| 1 | +""" |
| 2 | +Script: remove-json-keys.py |
| 3 | +Version: 2023.9.21 |
| 4 | +Description: Remove specific key-value pairs from JSON files in a directory. |
5 | 5 | Author: Adam Lui |
6 | | -Homepage: https://github.com/adamlui/python-utils |
7 | | -''' |
| 6 | +Review: Hexakleo |
| 7 | +URL: https://github.com/adamlui/python-utils |
| 8 | +""" |
8 | 9 |
|
9 | | -import os, json |
10 | | -from sys import stdout # for dynamic prints |
11 | | -from translate import Translator |
| 10 | +import os |
| 11 | +import re |
12 | 12 |
|
13 | | -locales_folder = '_locales' ; provider = '' |
14 | | -target_langs = ['af', 'am', 'ar', 'az', 'be', 'bem', 'bg', 'bn', 'bo', 'bs', 'ca', 'ceb', 'cs', 'cy', 'da', 'de', 'dv', 'dz', 'el', 'en', 'en-GB', 'eo', 'es', 'et', 'eu', 'fa', 'fi', 'fo', 'fr', 'gd', 'gl', 'gu', 'haw', 'he', 'hi', 'hr', 'ht', 'hu', 'hy', 'id', 'is', 'it', 'ja', 'ka', 'kab', 'kk', 'km', 'kn', 'ko', 'ku', 'ky', 'la', 'lb', 'lo', 'lt', 'lv', 'mg', 'mi', 'mk', 'ml', 'mn', 'ms', 'mt', 'my', 'ne', 'nl', 'no', 'ny', 'pa', 'pap', 'pl', 'ps', 'pt', 'ro', 'ru', 'rw', 'sg', 'si', 'sk', 'sl', 'sm', 'sn', 'so', 'sr', 'sv', 'sw', 'ta', 'te', 'tg', 'th', 'ti', 'tk', 'tn', 'to', 'tpi', 'tr', 'uk', 'ur', 'uz', 'vi', 'xh', 'yi', 'zh', 'zh-CN', 'zh-HK', 'zh-SG', 'zh-TW', 'zu'] |
| 13 | +# Constants |
| 14 | +JSON_FOLDER = '_locales' |
15 | 15 |
|
16 | | -# UI initializations |
17 | | -terminal_width = os.get_terminal_size()[0] |
18 | | -def print_trunc(msg, end='\n') : print(msg if len(msg) < terminal_width else msg[0:terminal_width-4] + '...', end=end) |
19 | | -def overwrite_print(msg) : stdout.write('\r' + msg.ljust(terminal_width)[:terminal_width]) |
| 16 | +# UI Initialization |
| 17 | +os.system('color') # Enable color for terminal |
| 18 | +print('\033[0;92m') # Set font color to bright green |
| 19 | +TERMINAL_WIDTH = os.get_terminal_size()[0] |
20 | 20 |
|
21 | | -print('') |
22 | 21 |
|
23 | | -# Prompt user for keys to ignore |
24 | | -keys_to_ignore = [] |
| 22 | +def print_trunc(msg): |
| 23 | + """Prints a message truncated to fit within the terminal width.""" |
| 24 | + print(msg if len(msg) < TERMINAL_WIDTH else msg[:TERMINAL_WIDTH - 4] + '...') |
| 25 | + |
| 26 | + |
| 27 | +# Collect keys to remove |
| 28 | +keys_to_remove = [] |
25 | 29 | while True: |
26 | | - key = input('Enter key to ignore (or ENTER if done): ') |
27 | | - if not key : break |
28 | | - keys_to_ignore.append(key) |
| 30 | + key = input("Enter key to remove (or press ENTER if done): ") |
| 31 | + if not key: |
| 32 | + break |
| 33 | + keys_to_remove.append(key) |
29 | 34 |
|
30 | | -# Determine closest locales dir |
31 | | -print_trunc(f'\nSearching for { locales_folder }...') |
| 35 | +# Locate JSON directory |
| 36 | +print_trunc(f"Searching for {JSON_FOLDER}...") |
32 | 37 | script_dir = os.path.abspath(os.path.dirname(__file__)) |
33 | | -locales_dir = None |
34 | | -for root, dirs, files in os.walk(script_dir): # search script dir recursively |
35 | | - if locales_folder in dirs: |
36 | | - locales_dir = os.path.join(root, locales_folder) ; break |
37 | | -else: # search script parent dirs recursively |
| 38 | +json_dir = None |
| 39 | + |
| 40 | +# Search script directory and parent directories for the JSON folder |
| 41 | +for root, dirs, _ in os.walk(script_dir): |
| 42 | + if JSON_FOLDER in dirs: |
| 43 | + json_dir = os.path.join(root, JSON_FOLDER) |
| 44 | + break |
| 45 | + |
| 46 | +if not json_dir: |
38 | 47 | parent_dir = os.path.dirname(script_dir) |
39 | 48 | while parent_dir and parent_dir != script_dir: |
40 | | - for root, dirs, files in os.walk(parent_dir): |
41 | | - if locales_folder in dirs: |
42 | | - locales_dir = os.path.join(root, locales_folder) ; break |
43 | | - if locales_dir : break |
| 49 | + for root, dirs, _ in os.walk(parent_dir): |
| 50 | + if JSON_FOLDER in dirs: |
| 51 | + json_dir = os.path.join(root, JSON_FOLDER) |
| 52 | + break |
| 53 | + if json_dir: |
| 54 | + break |
44 | 55 | parent_dir = os.path.dirname(parent_dir) |
45 | | - else : locales_dir = None |
46 | | - |
47 | | -# Print result |
48 | | -if locales_dir : print_trunc(f'_locales directory found!\n\n>> { locales_dir }\n') |
49 | | -else : print_trunc(f'Unable to locate a { locales_folder } directory.') ; exit() |
50 | | - |
51 | | -# Load en/messages.json |
52 | | -msgs_filename = 'messages.json' |
53 | | -en_msgs_path = os.path.join(locales_dir, 'en', msgs_filename) |
54 | | -with open(en_msgs_path, 'r', encoding='utf-8') as en_file: |
55 | | - en_messages = json.load(en_file) |
56 | | - |
57 | | -# Combine [target_langs] w/ languages discovered in _locales |
58 | | -output_langs = list(set(target_langs)) # remove duplicates |
59 | | -for root, dirs, files in os.walk(locales_dir): |
60 | | - for folder in dirs: |
61 | | - folder_path = os.path.join(root, folder) |
62 | | - msgs_path = os.path.join(folder_path, msgs_filename) |
63 | | - discovered_lang = folder.replace('_', '-') |
64 | | - if os.path.exists(msgs_path) and discovered_lang not in output_langs : output_langs.append(discovered_lang) |
65 | | -output_langs.sort() # alphabetize languages |
66 | | - |
67 | | -# Create/update/translate [[output_langs]/messages.json] |
68 | | -langs_added, langs_skipped, langs_translated, langs_not_translated = [], [], [], [] |
69 | | -for lang_code in output_langs: |
70 | | - lang_added, lang_skipped, lang_translated = False, False, False |
71 | | - folder = lang_code.replace('-', '_') ; translated_msgs = {} |
72 | | - if '-' in lang_code: # cap suffix |
73 | | - sep_index = folder.index('_') |
74 | | - folder = folder[:sep_index] + '_' + folder[sep_index+1:].upper() |
75 | | - |
76 | | - # Skip English locales |
77 | | - if lang_code.startswith('en'): |
78 | | - print_trunc(f'Skipped {folder}/messages.json...') |
79 | | - langs_skipped.append(lang_code) ; langs_not_translated.append(lang_code) ; continue |
80 | | - |
81 | | - # Initialize target locale folder |
82 | | - folder_path = os.path.join(locales_dir, folder) |
83 | | - if not os.path.exists(folder_path): # if missing, create folder |
84 | | - os.makedirs(folder_path) ; langs_added.append(lang_code) ; lang_added = True |
85 | | - |
86 | | - # Initialize target messages |
87 | | - msgs_path = os.path.join(folder_path, msgs_filename) |
88 | | - if os.path.exists(msgs_path): |
89 | | - with open(msgs_path, 'r', encoding='utf-8') as messages_file : messages = json.load(messages_file) |
90 | | - else : messages = {} |
91 | | - |
92 | | - # Attempt translations |
93 | | - print_trunc(f"{ 'Adding' if not messages else 'Updating' } { folder }/messages.json...", end='') |
94 | | - stdout.flush() |
95 | | - en_keys = list(en_messages.keys()) |
96 | | - fail_flags = ['INVALID TARGET LANGUAGE', 'TOO MANY REQUESTS', 'MYMEMORY'] |
97 | | - for key in en_keys: |
98 | | - if key in keys_to_ignore: |
99 | | - translated_msg = en_messages[key]['message'] |
100 | | - translated_msgs[key] = { 'message': translated_msg } |
101 | | - continue |
102 | | - if key not in messages: |
103 | | - original_msg = translated_msg = en_messages[key]['message'] |
104 | | - try: |
105 | | - translator = Translator(provider=provider if provider else '', to_lang=lang_code) |
106 | | - translated_msg = translator.translate(original_msg).replace('"', "'").replace(''', "'") |
107 | | - if any(flag in translated_msg for flag in fail_flags): |
108 | | - translated_msg = original_msg |
109 | | - except Exception as e: |
110 | | - print_trunc(f'Translation failed for key "{key}" in {lang_code}/messages.json: {e}') |
111 | | - translated_msg = original_msg |
112 | | - translated_msgs[key] = { 'message': translated_msg } |
113 | | - else : translated_msgs[key] = messages[key] |
114 | | - |
115 | | - # Format messages |
116 | | - formatted_msgs = '{\n' |
117 | | - for index, (key, message_data) in enumerate(translated_msgs.items()): |
118 | | - formatted_msg = json.dumps(message_data, ensure_ascii=False) \ |
119 | | - .replace('{', '{ ').replace('}', ' }') # add spacing |
120 | | - formatted_msgs += ( f' "{key}": {formatted_msg}' |
121 | | - + ( ',\n' if index < len(translated_msgs) - 1 else '\n' )) # terminate line |
122 | | - formatted_msgs += '}' |
123 | | - with open(msgs_path, 'w', encoding='utf-8') as output_file : output_file.write(formatted_msgs + '\n') |
124 | | - |
125 | | - # Print file summary |
126 | | - if translated_msgs == messages : langs_skipped.append(lang_code) ; lang_skipped = True |
127 | | - elif translated_msgs != messages : langs_translated.append(lang_code) ; lang_translated = True |
128 | | - if not lang_translated : langs_not_translated.append(lang_code) |
129 | | - overwrite_print(f"{ 'Added' if lang_added else 'Skipped' if lang_skipped else 'Updated' } { folder }/messages.json") |
130 | | - |
131 | | -# Print final summary |
132 | | -print_trunc('\nAll messages.json files updated successfully!\n') |
133 | | -lang_data = [langs_translated, langs_skipped, langs_added, langs_not_translated] |
134 | | -for data in lang_data: |
135 | | - if data: |
136 | | - list_name = next(name for name, value in globals().items() if value is data) |
137 | | - status = list_name.split('langs_')[-1].replace('_', ' ') |
138 | | - print(f'Languages {status}: {len(data)}\n') # print tally |
139 | | - print('[ ' + ', '.join(data) + ' ]\n') # list languages |
| 56 | + |
| 57 | +if not json_dir: |
| 58 | + print_trunc(f"Unable to locate the {JSON_FOLDER} directory.") |
| 59 | + exit() |
| 60 | + |
| 61 | +print_trunc(f"JSON directory found: {json_dir}\n") |
| 62 | + |
| 63 | +# Process JSON files |
| 64 | +keys_removed = [] |
| 65 | +keys_skipped = [] |
| 66 | +processed_count = 0 |
| 67 | + |
| 68 | +for root, _, files in os.walk(json_dir): |
| 69 | + for filename in files: |
| 70 | + if filename.endswith('.json'): |
| 71 | + file_path = os.path.join(root, filename) |
| 72 | + with open(file_path, 'r', encoding='utf-8') as f: |
| 73 | + data = f.read() |
| 74 | + |
| 75 | + modified = False |
| 76 | + for key in keys_to_remove: |
| 77 | + re_key = fr'"{re.escape(key)}".*?[,\n]+.*?(?="|$)' |
| 78 | + data, count = re.subn(re_key, '', data) |
| 79 | + if count > 0: |
| 80 | + keys_removed.append((key, os.path.relpath(file_path, json_dir))) |
| 81 | + modified = True |
| 82 | + else: |
| 83 | + keys_skipped.append((key, os.path.relpath(file_path, json_dir))) |
| 84 | + |
| 85 | + if modified: |
| 86 | + with open(file_path, 'w', encoding='utf-8') as f: |
| 87 | + f.write(data) |
| 88 | + |
| 89 | + processed_count += 1 |
| 90 | + |
| 91 | +# Print summaries |
| 92 | +if keys_removed: |
| 93 | + print_trunc("\nKeys removed successfully!\n") |
| 94 | + for key, file_path in keys_removed: |
| 95 | + print(f'Removed key "{key}" in {file_path}') |
| 96 | +if keys_skipped: |
| 97 | + print_trunc("\nKeys skipped (not found):\n") |
| 98 | + for key, file_path in keys_skipped: |
| 99 | + print(f'Skipped key "{key}" in {file_path}') |
| 100 | + |
| 101 | +print_trunc("\nKey removal process completed!\n") |
| 102 | +print(f"Processed JSON Files: {processed_count}") |
| 103 | +print(f"Keys Removed: {len(keys_removed)}") |
| 104 | +print(f"Keys Skipped: {len(keys_skipped)}") |
0 commit comments