From 0268ae83d12a653df7188a6ed013ff1e48a31d4a Mon Sep 17 00:00:00 2001 From: Zaid Date: Fri, 4 Oct 2024 18:19:52 +0300 Subject: [PATCH] Init commit --- .github/workflows/release.yml | 30 ++++++++++ .github/workflows/ruff.yml | 35 +++++++++++ .gitignore | 2 + LICENSE | 21 +++++++ README.md | 89 +++++++++++++++++++++++++++- langs/__init__.py | 3 - pylangs/__init__.py | 4 ++ pylangs/_json.py | 13 ++++ pylangs/_lang.py | 108 ++++++++++++++++++++++++++++++++++ pylangs/_langs.py | 84 ++++++++++++++++++++++++++ pyproject.toml | 9 ++- 11 files changed, 388 insertions(+), 10 deletions(-) create mode 100644 .github/workflows/release.yml create mode 100644 .github/workflows/ruff.yml create mode 100644 .gitignore create mode 100644 LICENSE delete mode 100644 langs/__init__.py create mode 100644 pylangs/__init__.py create mode 100644 pylangs/_json.py create mode 100644 pylangs/_lang.py create mode 100644 pylangs/_langs.py diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 0000000..3874d45 --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,30 @@ +name: Release + +on: + push: + tags: ["*"] + +permissions: + contents: write + id-token: write + +jobs: + publish: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - name: Set up Python 3.10 + uses: actions/setup-python@v2 + with: + python-version: "3.10" + + - run: python -m pip install build + + - run: python -m build + + - uses: pypa/gh-action-pypi-publish@release/v1 + + - uses: softprops/action-gh-release@v1 + with: + generate_release_notes: true \ No newline at end of file diff --git a/.github/workflows/ruff.yml b/.github/workflows/ruff.yml new file mode 100644 index 0000000..5cd7fda --- /dev/null +++ b/.github/workflows/ruff.yml @@ -0,0 +1,35 @@ +name: ruff + +on: + push: + branches: [main] + pull_request: + +jobs: + format: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - name: Set up Python 3.10 + uses: actions/setup-python@v2 + with: + python-version: "3.10" + - name: Install ruff + run: | + python -m pip install --upgrade pip + pip install ruff + - run: ruff format --check pylangs + + lint: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - name: Set up Python 3.10 + uses: actions/setup-python@v2 + with: + python-version: "3.10" + - name: Install ruff + run: | + python -m pip install --upgrade pip + pip install ruff + - run: ruff check pylangs \ No newline at end of file diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..df2b903 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +__pycache__/ +test.py \ No newline at end of file diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..49fbff3 --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2024 ZAID + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. \ No newline at end of file diff --git a/README.md b/README.md index a17b0db..2f22dde 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,87 @@ -## PyLangs -- `PyLangs` is python library to manage languages in multi-languages projects. \ No newline at end of file +# PyLangs + +**PyLangs** is a Python library designed to simplify the management of multiple languages in projects that need to support internationalization. Whether you're building a website, app, or any multilingual system, ``PyLangs`` makes it easy to store and retrieve messages in various languages with organized categories. + +--- + +## Features +- **Easy to Use**: Simple API for adding and retrieving language-specific messages. +- **Category Support**: Organize your messages by categories (e.g., general, support, etc.). +- **Flexible**: Add messages for any language, and easily switch between them. +- **Fallback Support**: Default fallback to a specified language if a message is missing in the current language. +- **Customizable**: Supports adding, retrieving, and printing all messages or language-specific ones. + +## Installation +- Install ``PyLangs`` via pip: +```bash +pip install pylangs +``` + +## Usage +**Initialize the language management system.** +```python +from pylangs import Langs + +langs = Langs() +``` +### Simple Example +You can easily insert and retrieve messages in multiple languages. +```python +# Insert Arabic and English welcome messages +langs.insert("ar", "WELCOME_MESSAGE", "السلام عليكم ورحمة الله, وبركاته") +langs.insert("ar", "SUPPORT_WELCOME_MESSAGE", "اهلًا بك في الدعم الفني!") +langs.insert("en", "WELCOME_MESSAGE", "Hi, How are you?") +langs.insert("en", "SUPPORT_WELCOME_MESSAGE", "Hi, This is Support, How can I help you?") + +# Get messages in various scenarios +print(langs.get("ar", "WELCOME_MESSAGE")) # Should print Arabic welcome message +print(langs.get("ar", "SUPPORT_WELCOME_MESSAGE")) # Should print Arabic support welcome message +print(langs.get("en", "WELCOME_MESSAGE")) # Should print English welcome message +print(langs.get("en", "SUPPORT_WELCOME_MESSAGE")) # Should print English support welcome message +``` +### Using Categories +To further organize messages, you can assign them to categories, making it easier to handle messages for different contexts (e.g., general, support, etc.). + +```python +# Insert messages with categories +langs.insert("ar", "WELCOME_MESSAGE", "السلام عليكم ورحمة الله, وبركاته", category="GENERAL") +langs.insert("ar", "WELCOME_MESSAGE", "اهلًا بك في الدعم الفني!", category="SUPPORT") +langs.insert("en", "WELCOME_MESSAGE", "Hi, How are you?", category="GENERAL") +langs.insert("en", "WELCOME_MESSAGE", "Hi, Welcome to our support team, How can I help you today?", category="SUPPORT") + +# Retrieve categorized messages +print(langs.get("ar", "WELCOME_MESSAGE", category="GENERAL")) # Outputs: "السلام عليكم ورحمة الله, وبركاته" +print(langs.get("ar", "WELCOME_MESSAGE", category="SUPPORT")) # Outputs: "اهلًا بك في الدعم الفني!" +print(langs.get("en", "WELCOME_MESSAGE", category="GENERAL")) # Outputs: "Hi, How are you?" +print(langs.get("en", "WELCOME_MESSAGE", category="SUPPORT")) # Outputs: "Hi, Welcome to our support team, How can I help you today?" +``` + +### Printing All Messages +To print all messages across all languages: +```python +print(langs) +``` +To print messages for a specific language: +```python +print(langs.en) # Prints only English messages +print(langs.ar) # Prints only Arabic messages +``` +### Example Output +Here’s an example of what the output might look like: +```json +{ + "lang_code": "en", + "categories": { + "GENERAL": { + "WELCOME_MESSAGE": "Hi, How are you?" + }, + "SUPPORT": { + "WELCOME_MESSAGE": "Hi, Welcome to our support team, How can I help you today?" + } + } +} +``` +--- + +# LICENSE +- This project is licensed under the **MIT** License. See the ``LICENSE`` file for more information. \ No newline at end of file diff --git a/langs/__init__.py b/langs/__init__.py deleted file mode 100644 index 00f46a8..0000000 --- a/langs/__init__.py +++ /dev/null @@ -1,3 +0,0 @@ -print( - 'Just wait and see.' -) \ No newline at end of file diff --git a/pylangs/__init__.py b/pylangs/__init__.py new file mode 100644 index 0000000..2eafbda --- /dev/null +++ b/pylangs/__init__.py @@ -0,0 +1,4 @@ +from ._lang import Lang +from ._langs import Langs + +__all__ = ["Lang", "Langs"] diff --git a/pylangs/_json.py b/pylangs/_json.py new file mode 100644 index 0000000..c54c68d --- /dev/null +++ b/pylangs/_json.py @@ -0,0 +1,13 @@ +import json + + +class JsonViewer: + def __str__(self) -> str: + return json.dumps( + self, + ensure_ascii=False, + indent=2, + default=lambda x: ( + {k: v for k, v in x.__dict__.items() if (not k.startswith("_")) and v} + ), + ) diff --git a/pylangs/_lang.py b/pylangs/_lang.py new file mode 100644 index 0000000..8757069 --- /dev/null +++ b/pylangs/_lang.py @@ -0,0 +1,108 @@ +from collections import OrderedDict +from typing import Dict, Optional + +from ._json import JsonViewer + + +class Lang(JsonViewer): + """ + The Lang class is responsible for managing messages for a specific language. + It supports organizing messages by categories and provides methods to insert, + delete, and retrieve messages both within and outside of categories. + """ + + def __init__(self, lang_code: str) -> None: + """ + Initializes the Lang class with a language code and empty structures for storing messages. + + Attributes: + lang_code (str): The language code representing this Lang object. + categories (Dict[str, Dict[str, str]]): A nested dictionary to store messages organized by category. + messages (OrderedDict): A dictionary to store uncategorized messages. + """ + self.lang_code = lang_code + self.categories: Dict[str, Dict[str, str]] = OrderedDict() + self.messages = OrderedDict() + + def insert(self, k: str, v: str, category: Optional[str] = None) -> None: + """ + Inserts a message into the language, either into a specified category or into the general message store. + + Args: + k (str): The key or identifier for the message. + v (str): The actual message text. + category (Optional[str]): The category under which the message will be stored. + If None, the message is stored without a category. + """ + if category is not None: + return self.insert_to_category(category=category, k=k, v=v) + + return self.messages.update({k: v}) + + def delete(self, k: str, category: Optional[str] = None) -> None: + """ + Deletes a message from the language, either from a specified category or from the general message store. + + Args: + k (str): The key or identifier for the message to delete. + category (Optional[str]): The category from which the message will be deleted. + If None, the message is deleted from the general store. + """ + if category is not None: + return self.delete_from_category(category=category, k=k) + + del self.messages[k] + + def get(self, k: str, category: Optional[str] = None) -> Optional[str]: + """ + Retrieves a message from the language, either from a specified category or from the general message store. + + Args: + k (str): The key or identifier for the message. + category (Optional[str]): The category from which to retrieve the message. + If None, the message is retrieved from the general store. + + Returns: + Optional[str]: The message text if found, otherwise None. + """ + if category is not None: + return self.get_from_category(category=category, k=k) + + return self.messages.get(k, None) + + def get_from_category(self, category: str, k: str) -> Optional[str]: + """ + Retrieves a message from a specified category. + + Args: + category (str): The category from which to retrieve the message. + k (str): The key or identifier for the message. + + Returns: + Optional[str]: The message text if found, otherwise None. + """ + return self.categories.get(category).get(k) + + def insert_to_category(self, category: str, k: str, v: str) -> None: + """ + Inserts a message into a specific category. + + Args: + category (str): The category under which to store the message. + k (str): The key or identifier for the message. + v (str): The actual message text. + """ + if category not in self.categories: + self.categories.update({category: {}}) + + return self.categories[category].update({k: v}) + + def delete_from_category(self, category: str, k: str) -> None: + """ + Deletes a message from a specific category. + + Args: + category (str): The category from which to delete the message. + k (str): The key or identifier for the message. + """ + del self.categories[category][k] diff --git a/pylangs/_langs.py b/pylangs/_langs.py new file mode 100644 index 0000000..449520f --- /dev/null +++ b/pylangs/_langs.py @@ -0,0 +1,84 @@ +from collections import OrderedDict +from ._lang import Lang +from ._json import JsonViewer + +from typing import Dict, Optional + + +class Langs(JsonViewer): + """ + The Langs class provides a language management system for storing and retrieving + language-specific messages. It supports organizing messages by categories and allows + adding, retrieving, and deleting messages in multiple languages. + """ + + def __init__(self) -> None: + """ + Initializes the Langs class with an empty ordered dictionary to store languages. + Each language is stored as a Lang object keyed by its language code. + + Attributes: + langs (Dict[str, Lang]): An ordered dictionary where keys are language codes (e.g., 'en', 'ar'), + and values are Lang objects that store messages for that language. + """ + self.langs: Dict[str, Lang] = OrderedDict() + + def insert( + self, lang_code: str, k: str, v: str, category: Optional[str] = None + ) -> None: + """ + Inserts a message into the specified language and category. + + Args: + lang_code (str): The language code (e.g., 'en', 'ar'). + k (str): The key or identifier for the message. + v (str): The actual message text. + category (Optional[str]): The category under which the message falls (e.g., 'GENERAL', 'SUPPORT'). + If None, it will be stored without a category. + """ + lang = self.__get_lang(lang_code) + + return lang.insert(k=k, v=v, category=category) + + def delete(self, lang_code: str, k: str, category: Optional[str] = None) -> None: + """ + Deletes a message from the specified language and category. + + Args: + lang_code (str): The language code (e.g., 'en', 'ar'). + k (str): The key or identifier for the message to delete. + category (Optional[str]): The category under which the message is stored. If None, the message will + be deleted without considering a specific category. + """ + lang = self.__get_lang(lang_code) + + return lang.delete(k=k, category=category) + + def get( + self, lang_code: str, k: str, category: Optional[str] = None + ) -> Optional[str]: + """ + Retrieves a message from the specified language and category. + + Args: + lang_code (str): The language code (e.g., 'en', 'ar'). + k (str): The key or identifier for the message. + category (Optional[str]): The category under which the message is stored. If None, the message will + be retrieved without considering a specific category. + """ + lang = self.__get_lang(lang_code) + + return lang.get(k=k, category=category) + + def __get_lang(self, lang_code: str) -> Lang: + if lang_code.lower() in self.langs: + return self.langs.get(lang_code.lower()) + + self.langs.update({lang_code.lower(): Lang(lang_code.lower())}) + return self.langs.get(lang_code.lower()) + + def __getattr__(self, name: str) -> Lang: + return self.langs.get(name) + + def __getitem__(self, name: str) -> Lang: + return self.langs[name] diff --git a/pyproject.toml b/pyproject.toml index f8419d4..b469cbe 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,15 +1,14 @@ [project] name = "pylangs" -version = "0.0.1dev" +version = "0.0.1" authors = [ { name="ZAID", email="me@zaid.pro" }, ] -description = "Soon" +description = "A Python library designed to simplify the management of multiple languages in projects that need to support internationalization." readme = "README.md" -requires-python = ">=3.10" +requires-python = ">=3.8" [tool.setuptools] include-package-data = false [tool.setuptools.packages.find] -include = ["langs*"] -exclude = ["docs*", "tests*", "examples*"] \ No newline at end of file +include = ["langs*"] \ No newline at end of file