Skip to content

Commit

Permalink
Merge pull request #15 from ubclaunchpad/create_cli
Browse files Browse the repository at this point in the history
Add CLI to pip
  • Loading branch information
angelafeliciaa authored Nov 20, 2024
2 parents 4143305 + f00e7ba commit 86cca58
Show file tree
Hide file tree
Showing 12 changed files with 291 additions and 7 deletions.
3 changes: 2 additions & 1 deletion .github/workflows/django.yml
Original file line number Diff line number Diff line change
Expand Up @@ -40,4 +40,5 @@ jobs:
cd i18nilize
python3 -m tests.test_read_file
python3 -m tests.test_parse_json
python3 -m tests.test_api_helpers
python3 -m tests.test_cli
python3 -m tests.test_api_helpers
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
env/
**/__pycache__
myenv
venv
venv
i18nilize.egg-info
Empty file added i18nilize/__init__.py
Empty file.
Empty file added i18nilize/src/__init__.py
Empty file.
Binary file not shown.
47 changes: 47 additions & 0 deletions i18nilize/src/internationalize/command_line.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
#from src.internationalize.helpers import add_language
import json
import argparse
from i18nilize.src.internationalize.helpers import add_language, add_update_translated_word, delete_translation

def cli():
# initialize the parser
parser = argparse.ArgumentParser(description="internationalization for translation")
subparsers = parser.add_subparsers(dest='command')

# sub parser for add_language
add_lang_parser = subparsers.add_parser('add-language')
add_lang_parser.add_argument('language')

# sub parser for add
add_parser = subparsers.add_parser('add')
add_parser.add_argument('language')
add_parser.add_argument('original_word')
add_parser.add_argument('translated_word')

# sub parser for update
update_parser = subparsers.add_parser('update')
update_parser.add_argument('language')
update_parser.add_argument('original_word')
update_parser.add_argument('translated_word')

# sub parser for delete
delete_parser = subparsers.add_parser('delete')
delete_parser.add_argument('language')
delete_parser.add_argument('original_word')
delete_parser.add_argument('translated_word')

# the subparser is used because different CLIs use a different amount of inputs

args = parser.parse_args()

# depending on the command, do different things
if args.command == 'add-language':
add_language(args.language)
elif args.command == 'add' or args.command == 'update':
add_update_translated_word(args.language, args.original_word, args.translated_word)
elif args.command == 'delete':
delete_translation(args.language, args.original_word, args.translated_word)
else:
print("Invalid command")

cli()
4 changes: 3 additions & 1 deletion i18nilize/src/internationalize/globals.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,4 +9,6 @@ def __init__(self):
TOKEN_ENDPOINT = f"{API_BASE_URL}token/"
TRANSLATIONS_ENDPOINT = f"{API_BASE_URL}translations/"

token = GlobalToken()
LANGUAGES_DIR = 'src/internationalize/languages'

token = GlobalToken()
57 changes: 53 additions & 4 deletions i18nilize/src/internationalize/helpers.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,7 @@
import json
import sys
import os
import requests
from geocoder import ip
from geopy.geocoders import Nominatim
from babel.languages import get_official_languages
from . import globals

# Function to parse json file, given its path
Expand All @@ -23,6 +21,57 @@ def get_json(file_path):
raise e
return data

# Adds a json file corresponding to the added language
def add_language(language):
os.makedirs(globals.LANGUAGES_DIR, exist_ok=True)
file_path = os.path.join(globals.LANGUAGES_DIR, f"{language.lower()}.json")

if os.path.exists(file_path):
return

initial_content = {}
with open(file_path, 'w') as file:
json.dump(initial_content, file, indent=4)
print(f"Language added.")

# Adds/updates a translated word under the given language in the default JSON file
def add_update_translated_word(language, original_word, translated_word):
file_path = os.path.join(globals.LANGUAGES_DIR, f"{language.lower()}.json")

if not os.path.exists(file_path):
print(f"Error: Language '{language}' does not exist. Add the language before adding a translation.")
sys.exit(1)

data = get_json(file_path)

data[original_word] = translated_word
with open(file_path, 'w') as file:
json.dump(data, file, indent=4)
print(f"{original_word}: {translated_word} added to translations.")

# Deletes a translated word for the given language
def delete_translation(language, original_word, translated_word):
file_path = os.path.join(globals.LANGUAGES_DIR, f"{language.lower()}.json")

if not os.path.exists(file_path):
print(f"Error: Language '{language}' does not exist.")
sys.exit(1)

data = get_json(file_path)

if original_word not in data:
print(f"Error: Original word '{original_word}' does not exist in language '{language}'.")
sys.exit(1)

if data[original_word] != translated_word:
print(f"Error: Translated word for '{original_word}' does not match '{translated_word}'.")
sys.exit(1)

del data[original_word]
with open(file_path, 'w') as file:
json.dump(data, file, indent=4)
print(f"Translation for '{original_word}' deleted successfully from language '{language}'.")

# Input:
# - file_path: path of json file
# Output: Token in json file
Expand Down Expand Up @@ -71,7 +120,7 @@ def generate_file(language, token):
return

file_content = response.json()

# transforms the dictionary object above into a JSON object
json_object = json.dumps(file_content, indent=4)
create_json(json_object, language)
Expand Down
1 change: 1 addition & 0 deletions i18nilize/src/internationalize/languages/korean.json
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{}
Binary file not shown.
161 changes: 161 additions & 0 deletions i18nilize/tests/test_cli.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,161 @@
import unittest, os, json, timeit
from unittest.mock import patch
from src.internationalize.helpers import delete_translation, get_json, make_translation_map, get_translation, add_language, add_update_translated_word

# Create your tests here.
# To test:
# In i18nilize directory, run python -m tests.test_cli

class TestCLI(unittest.TestCase):
def setUp(self):
self.languages_dir = "src/internationalize/languages"
os.makedirs(self.languages_dir, exist_ok=True)

def test_add_new_language(self):
new_language = "Japanese"
add_language(new_language)
new_language_path = os.path.join(self.languages_dir, f"{new_language.lower()}.json")
self.assertTrue(os.path.exists(new_language_path))

with open(new_language_path, "r") as file:
data = json.load(file)
self.assertEqual(data, {})

# clean up test
os.remove(new_language_path)

def test_add_word_language_exists(self):
existing_language = "French"
file_path = os.path.join(self.languages_dir, f"{existing_language.lower()}.json")

# save original state
with open(file_path, "r") as file:
original_translations = json.load(file)

data = get_json(file_path)
self.assertEqual(len(data), 2)

add_update_translated_word("French", "good", "bien")
data = get_json(file_path)
self.assertEqual(len(data), 3)
self.assertEqual(data["good"], "bien")

# clean up test
with open(file_path, 'w') as file:
json.dump(original_translations, file, indent=4)

def test_update_word_language_exists(self):
existing_language = "French"
file_path = os.path.join(self.languages_dir, f"{existing_language.lower()}.json")

# save original state
with open(file_path, "r") as file:
original_translations = json.load(file)

data = get_json(file_path)
self.assertEqual(len(data), 2)

add_update_translated_word("French", "thanks", "merc")
data = get_json(file_path)
self.assertEqual(len(data), 2)
self.assertEqual(data["thanks"], "merc")

# clean up test
with open(file_path, 'w') as file:
json.dump(original_translations, file, indent=4)

@patch('builtins.print')
def test_add_word_language_does_not_exist(self, mock_print):
with self.assertRaises(SystemExit) as context:
add_update_translated_word("NonExistentLanguage", "a", "b")
self.assertTrue(context.exception.code, 1)

mock_print.assert_called_once_with("Error: Language 'NonExistentLanguage' does not exist. Add the language before adding a translation.")

def test_delete_translation_success(self):
language = "German"
add_language(language)
file_path = os.path.join(self.languages_dir, f"{language.lower()}.json")

initial_translations = {
"goodbye": "auf Wiedersehen",
"thank you": "danke"
}
with open(file_path, "w") as file:
json.dump(initial_translations, file, indent=4)

data = get_json(file_path)
self.assertIn("goodbye", data)
self.assertEqual(data["goodbye"], "auf Wiedersehen")

delete_translation(language, "goodbye", "auf Wiedersehen")

# verify deletion
data = get_json(file_path)
self.assertNotIn("goodbye", data)
self.assertIn("thank you", data)

@patch('builtins.print')
def test_delete_translation_language_does_not_exist(self, mock_print):
language = "Russian"
original_word = "hello"
translated_word = "привет"

with self.assertRaises(SystemExit) as context:
delete_translation(language, original_word, translated_word)
self.assertEqual(context.exception.code, 1)

mock_print.assert_called_once_with(f"Error: Language '{language}' does not exist.")

def test_delete_translation_word_does_not_exist(self):
language = "Chinese"
add_language(language)
file_path = os.path.join(self.languages_dir, f"{language.lower()}.json")

initial_translations = {
"thank you": "谢谢"
}
with open(file_path, "w") as file:
json.dump(initial_translations, file, indent=4)

data = get_json(file_path)
self.assertIn("thank you", data)
self.assertNotIn("good morning", data)

with patch('builtins.print') as mock_print, self.assertRaises(SystemExit) as context:
delete_translation(language, "good morning", "早上好")
self.assertEqual(context.exception.code, 1)
mock_print.assert_called_once_with(f"Error: Original word 'good morning' does not exist in language '{language}'.")

# ensure existing translations remain unchanged
data = get_json(file_path)
self.assertIn("thank you", data)
self.assertEqual(data["thank you"], "谢谢")

def test_delete_translation_word_mismatch(self):
language = "Korean"
add_language(language)
file_path = os.path.join(self.languages_dir, f"{language.lower()}.json")

initial_translations = {
"welcome": "환영합니다"
}
with open(file_path, "w") as file:
json.dump(initial_translations, file, indent=4)

data = get_json(file_path)
self.assertIn("welcome", data)
self.assertEqual(data["welcome"], "환영합니다")

with patch('builtins.print') as mock_print, self.assertRaises(SystemExit) as context:
delete_translation(language, "welcome", "환영해요")
self.assertEqual(context.exception.code, 1)
mock_print.assert_called_once_with(f"Error: Translated word for 'welcome' does not match '환영해요'.")

# ensure existing translations remain unchanged
data = get_json(file_path)
self.assertIn("welcome", data)
self.assertEqual(data["welcome"], "환영합니다")

if __name__ == '__main__':
unittest.main()
22 changes: 22 additions & 0 deletions setup.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import setuptools
from setuptools import find_packages

# setup file for the command line
# before testing, do the following command:
# pip install -e
# within the virtual environment, this will enable you to use i18nilize CLI
setuptools.setup(
# package name
name='i18nilize',

# arbitrary version #
version='1.0',

# downloads the necessary packages (ex. json)
packages=find_packages(),

# directs the script towards the function located in that file
entry_points = {
'console_scripts': ['i18nilize=i18nilize.src.internationalize.command_line:cli'],
},
)

0 comments on commit 86cca58

Please sign in to comment.