Last active
May 2, 2026 12:21
-
-
Save eduardomozart/c6e3203d8484cdad509ff25c4ef0d48d to your computer and use it in GitHub Desktop.
Python QA script for .po files: checks for missing camelCase variables, Title Case mismatches, missing ampersands (UI mnemonics), and identical translations.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #!/usr/bin/env python3 | |
| import polib | |
| import re | |
| import sys | |
| import os | |
| from collections import Counter | |
| def find_camel_case(text): | |
| """Finds camelCase variables/keys (e.g., userId).""" | |
| camel_case_pattern = re.compile(r'\b[a-z]+[A-Z][a-zA-Z]*\b') | |
| return camel_case_pattern.findall(text) | |
| def find_title_case(text): | |
| """Finds words that start with an uppercase letter.""" | |
| # Supports accented characters naturally via \w+ | |
| words = re.findall(r'\w+', text) | |
| return [w for w in words if w[0].isupper()] | |
| def check_po_file(file_path): | |
| try: | |
| po = polib.pofile(file_path) | |
| except Exception as e: | |
| print(f"Error reading {file_path}: {e}") | |
| sys.exit(1) | |
| issues_found = 0 | |
| for entry in po: | |
| # Skip obsolete texts (deleted from the system) | |
| if entry.obsolete: | |
| continue | |
| # Handle Singular and Plural entries | |
| comparisons = [] | |
| if entry.msgstr_plural: | |
| for key, translation in entry.msgstr_plural.items(): | |
| if not translation: continue | |
| original = entry.msgid if str(key) == '0' else entry.msgid_plural | |
| comparisons.append((original, translation)) | |
| else: | |
| if not entry.msgstr: continue | |
| comparisons.append((entry.msgid, entry.msgstr)) | |
| # Analyze each text pair | |
| for original_text, translated_text in comparisons: | |
| issue_details = [] | |
| # --- 1. IDENTICAL STRINGS CHECK --- | |
| if original_text.strip() == translated_text.strip(): | |
| issue_details.append("Identical strings: Translation is exactly the same as the original.") | |
| # --- 2. CAMELCASE CHECK (Missing or Extra) --- | |
| msgid_camel_words = find_camel_case(original_text) | |
| msgstr_camel_words = find_camel_case(translated_text) | |
| msgid_counts = Counter(msgid_camel_words) | |
| msgstr_counts = Counter(msgstr_camel_words) | |
| # Check if any camelCase from the original is missing in the translation | |
| for word, expected_count in msgid_counts.items(): | |
| actual_count = msgstr_counts.get(word, 0) | |
| if actual_count < expected_count: | |
| issue_details.append(f"Missing camelCase: '{word}' (expected {expected_count}, found {actual_count})") | |
| # Check if the translation inserted a camelCase that didn't exist in the original | |
| for word, actual_count in msgstr_counts.items(): | |
| expected_count = msgid_counts.get(word, 0) | |
| if actual_count > expected_count: | |
| issue_details.append(f"Extra camelCase: '{word}' (expected {expected_count}, found {actual_count})") | |
| # --- 3. CAPITALIZATION COUNT CHECK (Title Case) --- | |
| id_titles = find_title_case(original_text) | |
| str_titles = find_title_case(translated_text) | |
| if len(id_titles) != len(str_titles): | |
| issue_details.append( | |
| f"Capitalization mismatch: Original has {len(id_titles)} capitalized words, " | |
| f"but translation has {len(str_titles)}." | |
| ) | |
| # --- 4. AMPERSAND (&) / MNEMONICS CHECK --- | |
| if '&' in original_text and '&' not in translated_text: | |
| issue_details.append("Missing ampersand (&): Original contains '&' (often a keyboard mnemonic shortcut) but the translation does not.") | |
| # If any issues were found, print them to the console | |
| if issue_details: | |
| issues_found += 1 | |
| print(f"Issue found at line {entry.linenum}:") | |
| print(f" Original: '{original_text}'") | |
| print(f" Translation: '{translated_text}'") | |
| # Highlight if the text is marked as fuzzy (unconfirmed translation) | |
| if 'fuzzy' in entry.flags: | |
| print(" ⚠️ WARNING: This entry is marked as 'fuzzy'.") | |
| for detail in issue_details: | |
| print(f" - {detail}") | |
| print("-" * 70) | |
| if issues_found == 0: | |
| print(f"Success: No issues found in {file_path}.") | |
| else: | |
| print(f"\nTotal issues found: {issues_found}") | |
| if __name__ == "__main__": | |
| if len(sys.argv) != 2: | |
| print("Usage: ./po_check_quality.py <path_to_po_file>") | |
| sys.exit(1) | |
| po_file_path = sys.argv[1] | |
| # Check if the file exists before trying to open it | |
| if not os.path.isfile(po_file_path): | |
| print(f"Error: The file '{po_file_path}' does not exist. Please check the path and try again.") | |
| sys.exit(1) | |
| check_po_file(po_file_path) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment