diff --git a/.github/workflows/django.yml b/.github/workflows/django.yml index 57d698d..a73e6cb 100644 --- a/.github/workflows/django.yml +++ b/.github/workflows/django.yml @@ -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 \ No newline at end of file + python3 -m tests.test_cli + python3 -m tests.test_api_helpers diff --git a/.gitignore b/.gitignore index 65833dd..658bd83 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,5 @@ env/ **/__pycache__ myenv -venv \ No newline at end of file +venv +i18nilize.egg-info diff --git a/i18nilize/__init__.py b/i18nilize/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/i18nilize/src/__init__.py b/i18nilize/src/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/i18nilize/src/internationalize/__pycache__/helpers.cpython-310.pyc b/i18nilize/src/internationalize/__pycache__/helpers.cpython-310.pyc deleted file mode 100644 index 4e4d4dc..0000000 Binary files a/i18nilize/src/internationalize/__pycache__/helpers.cpython-310.pyc and /dev/null differ diff --git a/i18nilize/src/internationalize/command_line.py b/i18nilize/src/internationalize/command_line.py new file mode 100644 index 0000000..e2c4124 --- /dev/null +++ b/i18nilize/src/internationalize/command_line.py @@ -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() \ No newline at end of file diff --git a/i18nilize/src/internationalize/globals.py b/i18nilize/src/internationalize/globals.py index ecb566b..a1b68e8 100644 --- a/i18nilize/src/internationalize/globals.py +++ b/i18nilize/src/internationalize/globals.py @@ -9,4 +9,6 @@ def __init__(self): TOKEN_ENDPOINT = f"{API_BASE_URL}token/" TRANSLATIONS_ENDPOINT = f"{API_BASE_URL}translations/" -token = GlobalToken() \ No newline at end of file +LANGUAGES_DIR = 'src/internationalize/languages' + +token = GlobalToken() diff --git a/i18nilize/src/internationalize/helpers.py b/i18nilize/src/internationalize/helpers.py index 2e5ec84..3983723 100644 --- a/i18nilize/src/internationalize/helpers.py +++ b/i18nilize/src/internationalize/helpers.py @@ -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 @@ -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 @@ -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) diff --git a/i18nilize/src/internationalize/languages/korean.json b/i18nilize/src/internationalize/languages/korean.json new file mode 100644 index 0000000..9e26dfe --- /dev/null +++ b/i18nilize/src/internationalize/languages/korean.json @@ -0,0 +1 @@ +{} \ No newline at end of file diff --git a/i18nilize/tests/__pycache__/test_parse_json.cpython-310.pyc b/i18nilize/tests/__pycache__/test_parse_json.cpython-310.pyc deleted file mode 100644 index d135271..0000000 Binary files a/i18nilize/tests/__pycache__/test_parse_json.cpython-310.pyc and /dev/null differ diff --git a/i18nilize/tests/test_cli.py b/i18nilize/tests/test_cli.py new file mode 100644 index 0000000..f742f86 --- /dev/null +++ b/i18nilize/tests/test_cli.py @@ -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() diff --git a/setup.py b/setup.py new file mode 100644 index 0000000..4182ca5 --- /dev/null +++ b/setup.py @@ -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'], + }, +) \ No newline at end of file