Skip to content
This repository has been archived by the owner on May 31, 2022. It is now read-only.

🌐 Interactive Translation Manager #150

Merged
merged 13 commits into from
Oct 24, 2021
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 23 additions & 0 deletions tools/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
# Translation Manager

For a faster translation process, consider using the interactive translation manager.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note that many people who visit our repo are using GitHub for the first time, so this will only be faster for a percentage of users. It might be worth noting that this tool requires familiarity with the command line.


## How to use it

Run the following command inside this folder to spawn an interactive translation shell:

```bash
python translation_manager.py translate_interactive <TARGET_LANGUAGE>
```

Replace `<TARGET_LANGUAGE>` with the language you want to translate to.

> Note: The provided language must be a short letter code (ISO 639-1).

If you want to change the source language the translation is being started from (default: english), use this command instead:

```bash
python translation_manager.py translate_interactive <TARGET_LANGUAGE> <SOURCE_LANGUAGE>
```

**Under construction 🚧👷**
133 changes: 128 additions & 5 deletions tools/translation_manager.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,12 @@
#!/usr/bin/env python3

from functools import reduce
import operator
import json
import copy
import glob
import os
import sys

import fire

Expand All @@ -11,10 +15,38 @@
INDENT = 4


def open_locale_file(fname):
"""Opens a locale file"""
with open(fname) as json_file:
return json.load(json_file)
class InvalidLanguageError(Exception):
"""Exception raised for invalid language inputs.

Attributes
----------
`language` : input language which caused the error
"""

def __init__(self, language=""):
self.language = language
super().__init__(self.language)

def __str__(self):
if self.language:
return f"Invalid language: {self.language}\nPlease check if it is an ISO 639-1 language flag!"
else:
return f"Invalid language!\nPlease check if it is an ISO 639-1 language flag!"


def open_locale_file(fname) -> dict:
"""Opens a locale file.

Raises
------
`InvalidLanguageError`
If the input language flag is invalid.
"""
try:
with open(fname) as json_file:
return json.load(json_file)
except FileNotFoundError:
raise InvalidLanguageError()


def write_json(data):
Expand Down Expand Up @@ -87,6 +119,31 @@ def get_locale_files(path=LOCALE_DIR):
return glob.glob(f'{path}/*.json')


def recurse_dict(d: dict, keys=()):
"""Generator.

Returns
-------
Iterable of the nested dictionary as (compound_keys, value):
- compound_keys: cookie-trail
- values: nested value after following the compound_keys
"""
if type(d) == dict:
for key in d:
for value in recurse_dict(d[key], keys + (key, )):
yield value
else:
yield (keys, d)

def nested_get(d: dict, *keys):
"""Returns value of nested dict for given compound_keys."""
return reduce(operator.getitem, keys, d)

def nested_set(d: dict, value, *keys):
"""Sets value in nested dictionary for given keys path."""
nested_get(d, *keys[:-1])[keys[-1]] = value


class TranslationManager(object):
"""Command line tool for managing i18n files"""
def alphabetize(self):
Expand Down Expand Up @@ -136,6 +193,72 @@ def trim_dead_keys(self, locale="en"):
trim_dead_keys(primary_dict, dest_dict)
export(dest_dict, fname)

def translate_interactive(self, dest:str, source:str="en"):
"""Spawns interactive translating session.

User is asked to submit (missing) key-translations for a given language.

Parameters
----------
`dest` : str
The destination language flag [ISO 639-1].
`source` : str, optional
The source language flag [ISO 639-1]. Defaults to english language.
"""
# Load locale
source_fname = f"{LOCALE_DIR}/{source}.json"
dest_fname = f"{LOCALE_DIR}/{dest}.json"
source_dict = open_locale_file(source_fname)
dest_dict = open_locale_file(dest_fname)

# Make sure target json has the same keys as the source one
copy_new_keys_to_locale(source_dict, dest_dict)

# Dict comparison generator
dict_comparator = list(zip(recurse_dict(source_dict), recurse_dict(dest_dict)))
n_keys = len(dict_comparator)

# Start interactive translation session
print(f"Translating from {source} -> {dest}")
print("Enter a translation for the given key.")
print("Leave the prompt empty if the current translation is good enough.")
cols, _ = os.get_terminal_size()
print("-"*cols)
for i, [[_, source_value], [compound_key, dest_value]] in enumerate(dict_comparator):
# Prompt user to input new translation
counter = f"## {i} out of {n_keys} ##"
key_trail = "## Key: " + '->'.join(compound_key) + " ##"
string_source = f"{source}: {source_value}"
string_dest = f"{dest}: {dest_value}"
prompt = ">> "
print(counter)
print(key_trail)
print(string_source)
print(string_dest)
string_translated = input(prompt)

# Clear terminal
cols, _ = os.get_terminal_size()
for i in range(int(len(counter)/cols)
+int(len(key_trail)/cols)
+int(len(string_source)/cols)
+int(len(string_dest)/cols)
+int((len(prompt)+len(string_translated))/cols)
+5):
sys.stdout.write("\033[F") # back to previous line
sys.stdout.write("\033[K") # clear line

# Save user input to dict
if string_translated:
nested_set(dest_dict, string_translated, *compound_key)

# export modified dict and save
export(dest_dict,dest_fname)
print("Done! 🎉")


if __name__ == '__main__':
fire.Fire(TranslationManager)
try:
fire.Fire(TranslationManager)
except KeyboardInterrupt:
print("\nAbort.")