diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..c7e631c --- /dev/null +++ b/.editorconfig @@ -0,0 +1,18 @@ +# Editorconfig to keep settings the same across devices and editors +# https://editorconfig.org/ + +root = true + +[*] +charset = utf-8 +indent_style = tab +insert_final_newline = true +tab_width = 4 +trim_trailing_whitespace = true + +[*.{html,md,py,rs}] +indent_size = 4 +indent_style = space + +[*.{yml,yaml}] +indent_style = space diff --git a/.github/.gitlint b/.github/.gitlint new file mode 100644 index 0000000..9755ab3 --- /dev/null +++ b/.github/.gitlint @@ -0,0 +1,2 @@ +[general] +ignore=B6 diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS new file mode 100644 index 0000000..88d7df4 --- /dev/null +++ b/.github/CODEOWNERS @@ -0,0 +1,2 @@ +# Global owner +* @ElBe-Plaq diff --git a/.github/CODE_OF_CONDUCT.md b/.github/CODE_OF_CONDUCT.md new file mode 100644 index 0000000..defbeba --- /dev/null +++ b/.github/CODE_OF_CONDUCT.md @@ -0,0 +1,121 @@ +# Contributor Covenant Code of Conduct + +## Our Pledge + +We as members, contributors, and leaders pledge to make participation in our +community a harassment-free experience for everyone, regardless of age, body +size, visible or invisible disability, ethnicity, sex characteristics, gender +identity and expression, level of experience, education, socio-economic status, +nationality, personal appearance, race, religion, or sexual identity +and orientation. + +We pledge to act and interact in ways that contribute to an open, welcoming, +diverse, inclusive, and healthy community. + +## Our Standards + +Examples of behavior that contributes to a positive environment for our +community include: + +* Demonstrating empathy and kindness toward other people +* Being respectful of differing opinions, viewpoints, and experiences +* Giving and gracefully accepting constructive feedback +* Accepting responsibility and apologizing to those affected by our mistakes, and learning from the experience +* Focusing on what is best not just for us as individuals, but for the overall community + +Examples of unacceptable behavior include: + +* The use of sexualized language or imagery, and sexual attention or advances of any kind +* Trolling, insulting or derogatory comments, and personal or political attacks +* Public or private harassment +* Publishing others' private information, such as a physical or email address, without their explicit permission +* Other conduct which could reasonably be considered inappropriate in a professional setting + +## Enforcement Responsibilities + +Community leaders are responsible for clarifying and enforcing our standards of +acceptable behavior and will take appropriate and fair corrective action in +response to any behavior that they deem inappropriate, threatening, offensive, +or harmful. + +Community leaders have the right and responsibility to remove, edit, or reject +comments, commits, code, wiki edits, issues, and other contributions that are +not aligned to this Code of Conduct, and will communicate reasons for moderation +decisions when appropriate. + +## Scope + +This Code of Conduct applies within all community spaces, and also applies when +an individual is officially representing the community in public spaces. +Examples of representing our community include using an official e-mail address, +posting via an official social media account, or acting as an appointed +representative at an online or offline event. + +## Enforcement + +Instances of abusive, harassing, or otherwise unacceptable behavior may be +reported to the community leaders responsible for enforcement on +[our discord server](https://discord.gg/JVyyDukQqV). +All complaints will be reviewed and investigated promptly and fairly. + +All community leaders are obligated to respect the privacy and security of the +reporter of any incident. + +## Enforcement Guidelines + +Community leaders will follow these Community Impact Guidelines in determining +the consequences for any action they deem in violation of this Code of Conduct: + +### 1. Correction + +**Community Impact**: Use of inappropriate language or other behavior deemed +unprofessional or unwelcome in the community. + +**Consequence**: A private, written warning from community leaders, providing +clarity around the nature of the violation and an explanation of why the +behavior was inappropriate. A public apology may be requested. + +### 2. Warning + +**Community Impact**: A violation through a single incident or series +of actions. + +**Consequence**: A warning with consequences for continued behavior. No +interaction with the people involved, including unsolicited interaction with +those enforcing the Code of Conduct, for a specified period of time. This +includes avoiding interactions in community spaces as well as external channels +like social media. Violating these terms may lead to a temporary or +permanent ban. + +### 3. Temporary Ban + +**Community Impact**: A serious violation of community standards, including +sustained inappropriate behavior. + +**Consequence**: A temporary ban from any sort of interaction or public +communication with the community for a specified period of time. No public or +private interaction with the people involved, including unsolicited interaction +with those enforcing the Code of Conduct, is allowed during this period. +Violating these terms may lead to a permanent ban. + +### 4. Permanent Ban + +**Community Impact**: Demonstrating a pattern of violation of community +standards, including sustained inappropriate behavior, harassment of an +individual, or aggression toward or disparagement of classes of individuals. + +**Consequence**: A permanent ban from any sort of public interaction within +the community. + +## Attribution + +This Code of Conduct is adapted from the [Contributor Covenant](https://www.contributor-covenant.org/version/2/0/code_of_conduct.html), +version 2.0, available at +[https://www.contributor-covenant.org/version/2/0/code_of_conduct.html](https://www.contributor-covenant.org/version/2/0/code_of_conduct.html). + +Community Impact Guidelines were inspired by [Mozilla's code of conduct +enforcement ladder](https://github.com/mozilla/diversity). + +For answers to common questions about this code of conduct, see the FAQ at +[https://www.contributor-covenant.org/faq](https://www.contributor-covenant.org/faq). Translations are available at +[https://www.contributor-covenant.org/translations](https://www.contributor-covenant.org/translations). diff --git a/.github/CONTRIBUTING.md b/.github/CONTRIBUTING.md new file mode 100644 index 0000000..6bfdfdc --- /dev/null +++ b/.github/CONTRIBUTING.md @@ -0,0 +1,29 @@ +# How to contribute to this project + +Did you find a bug? You want to have a feature implemented? You made code better? +Please follow the instructions below on how to contribute to this project. + +## Notes + +Please do **not** report an issue of pull request if the issue is a security vulnerability. +There is a different way to report such issues. Issues or pull requests about a vulnerability will be removed. + +
+

Report vulnerabilities here

+
+ +## Contributing + +For any issues or proposals, open an issue. If you already wrote your proposed change(s), please open an issue +and a pull request. See below for making changes on your own. + +### Making changes on your own + +1. Create a new issue with a summary of your proposed changes and more. +2. Fork the repository. +3. Implement your changes +4. Test your changes. Run `cargo fmt`, `cargo check` ad `cargo clippy` to verify it works. +5. Create a pull request from your fork. +6. If it gets accepted, your changes will be in the next release. + +To everyone who has reported issues, proposed changes or just helped with peoples questions: Thank you! diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml new file mode 100644 index 0000000..fa8cce5 --- /dev/null +++ b/.github/FUNDING.yml @@ -0,0 +1,2 @@ +--- +ko_fi: elbeplaq diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md new file mode 100644 index 0000000..161380c --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -0,0 +1,33 @@ +--- +name: Bug report +about: Create a report to help us improve +title: "[BUG] TITLE" +labels: Bug +assignees: ElBe-Plaq +--- + +**Describe the bug** +A clear and concise description of what the bug is. + +**To Reproduce** +Steps to reproduce the behavior: + +1. Go to '...' +2. Click on '....' +3. Scroll down to '....' +4. See error + +**Expected behavior** +A clear and concise description of what you expected to happen. + +**Screenshots** +If applicable, add screenshots to help explain your problem. + +**Desktop (please complete the following information):** + +- OS: [e.g. iOS] +- Rust version [eg. 1.69] +- Version [e.g. 22] + +**Additional context** +Add any other context about the problem here. diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml new file mode 100644 index 0000000..a61c085 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -0,0 +1,6 @@ +--- +blank_issues_enabled: false +contact_links: + - name: Discord Server + url: https://discord.gg/JVyyDukQqV + about: Please ask and answer questions here or just chat with the communtiy. diff --git a/.github/ISSUE_TEMPLATE/documentation.md b/.github/ISSUE_TEMPLATE/documentation.md new file mode 100644 index 0000000..d45d81d --- /dev/null +++ b/.github/ISSUE_TEMPLATE/documentation.md @@ -0,0 +1,7 @@ +--- +name: Documentation +about: Improvements to the documentation. +title: "[DOCUMENTATION] TITLE" +labels: Documentation +assignees: ElBe-Plaq +--- diff --git a/.github/ISSUE_TEMPLATE/enhancement.md b/.github/ISSUE_TEMPLATE/enhancement.md new file mode 100644 index 0000000..54731f2 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/enhancement.md @@ -0,0 +1,7 @@ +--- +name: Enhancement +about: Improves something already existing. +title: "[ENHANCEMENT] TITLE" +labels: Enhancement +assignees: ElBe-Plaq +--- diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md new file mode 100644 index 0000000..36492b6 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feature_request.md @@ -0,0 +1,19 @@ +--- +name: Feature request +about: Suggest an idea for this project +title: "[FEATURE] TITLE" +labels: Feature request +assignees: ElBe-Plaq +--- + +**Is your feature request related to a problem? Please describe.** +A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] + +**Describe the solution you'd like** +A clear and concise description of what you want to happen. + +**Describe alternatives you've considered** +A clear and concise description of any alternative solutions or features you've considered. + +**Additional context** +Add any other context or screenshots about the feature request here. diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md new file mode 100644 index 0000000..6f9b702 --- /dev/null +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -0,0 +1,26 @@ +# Description + +Summary of all the changes made. + +Closes #(issues that get closed, if this pull request gets closed) + +## Type of change + + + +- [ ] Bug fix (non-breaking change which fixes an issue) +- [ ] New feature (non-breaking change which adds functionality) +- [ ] Breaking change (fix or feature that would cause existing functionality to not work as expected) +- [ ] This change requires a documentation update +- [ ] Something else + +# Checklist: + +- [ ] My code follows the style guidelines of this project +- [ ] I have performed a self-review of my code +- [ ] I have commented my code, particularly in hard-to-understand areas +- [ ] I have made corresponding changes to the documentation +- [ ] My changes generate no new errors or linter warnings +- [ ] My changes generate no problems with other code +- [ ] I have added tests that prove my fix is effective or that my feature works +- [ ] New and existing tests passed locally with my changes diff --git a/.github/README.md b/.github/README.md new file mode 100644 index 0000000..75df90e --- /dev/null +++ b/.github/README.md @@ -0,0 +1,101 @@ +

+ localizer-rs +

+

+ Localizer helps localize (translate) your rust applications using json files. +

+

+ + + + +

+ + + +## About this project + +Localizer is a tool to translate text using json files. + +## Installing + +Run the following command to add the package to your dependencies: + +```bash + +$ cargo add localizer-rs +... + +``` + +### Git + +To clone the repository locally using git run `git clone https://github.com/ElBe-Development/localizer-rs.git`. + +## Usage + +To use localizer-rs, you need a directory (eg. `translations`) with your translations files (eg. `en.json`). You then need to follow these steps: + +1. Import the localizer-rs crate: + + ```rust + + use localizer_rs; + + ``` + +2. Create a new config object: + + ```rust + + let config = localizer_rs::Config::new("DIRECTORY NAME", "LANGUAGE NAME"); + + ``` + +3. Translate your text: + + ```rust + + config.t("key", vec!["placeholder", "value"]); + + ``` + +## Example + +With the following `en.json` file. + +```json + +{ + "error": "{{color.red}}{{bold}}Error:{{end}} Something went wrong: {{details}}." +} + +``` + +And the following rust code. + +```rust + +use localizer_rs; + +fn main() { + let config: localizer_rs::Config = localizer_rs::Config::new("translations", "en"); + + println!("{:}", config.t("error", vec![("details", "Path not found")])); +} + +``` + +You will get the following output: + +```bash + +Error: Something went wrong: Path not found. + +``` + +Where `Error:` is red and bold. + +## Contact + +To contact us, get help or just chat with others, you can visit [our discord server](https://discord.gg/JVyyDukQqV). diff --git a/.github/SECURITY.md b/.github/SECURITY.md new file mode 100644 index 0000000..a6b3b74 --- /dev/null +++ b/.github/SECURITY.md @@ -0,0 +1,14 @@ +# Security Policy + +## Supported Versions + +| Version | Supported | +| -------- | ------------------ | +| `v1.0.0` | :white_check_mark: | + +## Reporting a Vulnerability + +To report a vulnerability, go to [our discord server](https://discord.gg/JVyyDukQqV), write an +[E-Mail](mailto:elbe.dev.plaq@gmail.com) or report it via +[GitHubs security reporting system](https://github.com/ElBe-Development/localizer-rs/security/advisories/new) +(preferred). diff --git a/.github/SUPPORT.md b/.github/SUPPORT.md new file mode 100644 index 0000000..bd2a2fd --- /dev/null +++ b/.github/SUPPORT.md @@ -0,0 +1,7 @@ +# Support + +You can get support on: + +- [our discord server](https://discord.gg/JVyyDukQqV) +- [GitHub discussions](https://github.com/ElBe-Development/localizer-rs/discussions) +- [GitHub issues](https://github.com/ElBe-Development/localizer-rs/issues) diff --git a/.github/auto_assign.yml b/.github/auto_assign.yml new file mode 100644 index 0000000..fb03d3c --- /dev/null +++ b/.github/auto_assign.yml @@ -0,0 +1,5 @@ +--- +addReviewers: false +addAssignees: true +assignees: + - ElBe-Plaq diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 0000000..ea3679d --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,33 @@ +# To get started with Dependabot version updates, you'll need to specify which +# package ecosystems to update and where the package manifests are located. +# Please see the documentation for all configuration options: +# https://docs.github.com/github/administering-a-repository/configuration-options-for-dependency-updates +--- +version: 2 +updates: + - package-ecosystem: "pip" + directory: "/" + schedule: + interval: "daily" + assignees: + - "ElBe-Plaq" + labels: + - "dependencies" + + - package-ecosystem: "cargo" + directory: "/" + schedule: + interval: "daily" + assignees: + - "ElBe-Plaq" + labels: + - "dependencies" + + - package-ecosystem: "github-actions" + directory: "/" + schedule: + interval: "daily" + assignees: + - "ElBe-Plaq" + labels: + - "dependencies" diff --git a/.github/example.png b/.github/example.png new file mode 100644 index 0000000..80da460 Binary files /dev/null and b/.github/example.png differ diff --git a/.github/labeler.yml b/.github/labeler.yml new file mode 100644 index 0000000..ac92c4e --- /dev/null +++ b/.github/labeler.yml @@ -0,0 +1,5 @@ +--- +example: + - "examples/*" +dependencies: + - "dev-requirements.txt" diff --git a/.github/mergify.yml b/.github/mergify.yml new file mode 100644 index 0000000..cb54a5e --- /dev/null +++ b/.github/mergify.yml @@ -0,0 +1,128 @@ +--- +pull_request_rules: + - name: Merge trusted bot updates, no conflicts are present and approved + conditions: + - or: + - "label~=Approved" + - "-#approved-reviews-by=0" + - "-conflict" + - "-draft" + - "-locked" + - "#check-failure=0" + - "author~=.*bot.*" + actions: + comment: + message: | + # Merging pull request + + Checks: + | Name | Status | + |------|--------| + {% for check in check_success %}| {{check}} | :white_check_mark: | + {% endfor %} + merge: + commit_message_template: | + {{title}} + + {{title}} in #{{number}} by @{{author}}, contributing to {{milestone}} + + Changed files: + {% for file in files %}- '{{file}}' + {% endfor %} + + Approved by: @{{ approved_reviews_by | join(', @') }} + + + {% for commit in commits %}Co-authored-by: {{commit.author}} <{{commit.email_author}}> + {% endfor %} + method: merge + delete_head_branch: + - name: Merge if approved, no conflicts are present and it's not a WIP + conditions: + - or: + - "-#approved-reviews-by=0" + - "label~=Approved" + - "label~=Able to merge" + - "-conflict" + - "-draft" + - "-locked" + - "#check-failure=0" + actions: + comment: + message: | + # Merging pull request + + Checks: + | Name | Status | + |------|--------| + {% for check in check_success %}| {{check}} | :white_check_mark: | + {% endfor %} + merge: + commit_message_template: | + {{title}} + + {{title}} in #{{number}} by @{{author}}, contributing to {{milestone}} + + Changed files: + {% for file in files %}- '{{file}}' + {% endfor %} + + Approved by: @{{ approved_reviews_by | join(', @') }} + + + {% for commit in commits %}Co-authored-by: {{commit.author}} <{{commit.email_author}}> + {% endfor %} + method: merge + delete_head_branch: + - name: Add review requested label and request review from ElBe + conditions: + - or: + - "#approved-reviews-by=0" + - "-label~=Approved" + - "-title~=^[WIP].*" + - "-label~=Declined" + - "-label~=Review requested" + - "-draft" + - "-locked" + - "-conflict" + actions: + label: + add: + - Review requested + request_reviews: + users: + - ElBe-Plaq + - name: Warn on conflicts and add label + conditions: + - conflict + actions: + comment: + message: "@{{author}} this pull request has one or more conflicts." + label: + add: + - Invalid + remove: + - Review requested + - name: Remove invalid label if not needed + conditions: + - -conflict + actions: + label: + add: + - Review requested + remove: + - Invalid + - name: Warn on failed checks + conditions: + - "-#check-failure=0" + actions: + comment: + message: | + # Checks failed + + Checks: + | Name | Status | + |------|--------| + {% for check in check_success %}| {{check}} | :white_check_mark: | + {% endfor %}{% for check in check_failure %}| {{check}} | :x: | + {% endfor %} diff --git a/.github/stale.yml b/.github/stale.yml new file mode 100644 index 0000000..a4d7996 --- /dev/null +++ b/.github/stale.yml @@ -0,0 +1,11 @@ +--- +daysUntilStale: 60 +daysUntilClose: 7 +exemptLabels: + - Security +staleLabel: stale +markComment: > + This issue has been automatically marked as stale because it has not had + recent activity. It will be closed if no further activity occurs. Thank you + for your contributions. +closeComment: false diff --git a/.github/workflows/dependencies.yml b/.github/workflows/dependencies.yml new file mode 100644 index 0000000..2962d4c --- /dev/null +++ b/.github/workflows/dependencies.yml @@ -0,0 +1,46 @@ +--- +name: Dependencies +permissions: read-all + +on: + push: + paths: ["dev-requirements.txt", "Cargo.toml"] + + pull_request: + paths: ["dev-requirements.txt", "Cargo.toml"] + +jobs: + install-python-dependencies: + runs-on: ubuntu-latest + strategy: + matrix: + python-version: ["3.8"] + steps: + - uses: actions/checkout@v3 + - name: Set up Python ${{ matrix.python-version }} + uses: actions/setup-python@v4 + with: + python-version: ${{ matrix.python-version }} + - name: Install dependencies from txt files + run: | + python -m pip install --upgrade pip + pip install -r dev-requirements.txt + install-rust-dependencies: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + - name: Install dependencies from Cargo.toml + run: cargo update + - name: Install dev-dependencies from Cargo.toml + run: cargo test --no-run + # ^ This is the only way to install dev-dependencies + + dependency-review: + runs-on: ubuntu-latest + steps: + - name: Checkout Repository + uses: actions/checkout@v3 + if: ${{ github.event_name != 'push' }} + - name: Dependency Review + if: ${{ github.event_name != 'push' }} + uses: actions/dependency-review-action@v3 diff --git a/.github/workflows/label.yml b/.github/workflows/label.yml new file mode 100644 index 0000000..34a042d --- /dev/null +++ b/.github/workflows/label.yml @@ -0,0 +1,25 @@ +# This workflow will triage pull requests and apply a label based on the +# paths that are modified in the pull request. +# +# To use this workflow, you will need to set up a .github/labeler.yml +# file with configuration. For more information, see: +# https://github.com/actions/labeler + +--- +name: Labeler +permissions: + pull-requests: write + +on: [pull_request_target] + +jobs: + label: + runs-on: ubuntu-latest + permissions: + contents: read + pull-requests: write + + steps: + - uses: actions/labeler@v4 + with: + repo-token: "${{ secrets.GITHUB_TOKEN }}" diff --git a/.github/workflows/megalinter.yml b/.github/workflows/megalinter.yml new file mode 100644 index 0000000..7c38085 --- /dev/null +++ b/.github/workflows/megalinter.yml @@ -0,0 +1,38 @@ +--- +name: MegaLinter +permissions: read-all + +on: + push: + pull_request: + branches: [main] + +env: + APPLY_FIXES: VALIDATE_ALL_CODEBASE + APPLY_FIXES_EVENT: all + APPLY_FIXES_MODE: commit + +concurrency: + group: ${{ github.ref }}-${{ github.workflow }} + cancel-in-progress: true + +jobs: + build: + name: MegaLinter + runs-on: ubuntu-latest + permissions: + contents: write + issues: write + pull-requests: write + steps: + - name: Checkout Code + uses: actions/checkout@v3 + with: + token: ${{ secrets.PAT || secrets.GITHUB_TOKEN }} + fetch-depth: 0 + - name: MegaLinter + id: ml + uses: oxsecurity/megalinter@v7 + env: + VALIDATE_ALL_CODEBASE: ${{ github.event_name == 'push' && github.ref == 'refs/heads/main' }} + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml new file mode 100644 index 0000000..2b42c87 --- /dev/null +++ b/.github/workflows/rust.yml @@ -0,0 +1,18 @@ +name: Rust +permissions: read-all + +on: + [push, pull_request] + +env: + CARGO_TERM_COLOR: always + +jobs: + build: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + - name: Build + run: cargo build --verbose + - name: Run tests + run: cargo test --verbose diff --git a/.mega-linter.yml b/.mega-linter.yml new file mode 100644 index 0000000..b10c5b8 --- /dev/null +++ b/.mega-linter.yml @@ -0,0 +1,16 @@ +--- +DISABLE_ERRORS_LINTERS: + - SPELL_CSPELL + - REPOSITORY_DEVSKIM # cspell:disable-line + - MAKEFILE_CHECKMAKE # cspell:disable-line + - HTML_HTMLHINT # cspell:disable-line + - MARKDOWN_MARKDOWN_LINK_CHECK + - REPOSITORY_DUSTILOCK # cspell:disable-line + - JAVASCRIPT_STANDARD + - REPOSITORY_TRUFFLEHOG # cspell:disable-line + - SPELL_LYCHEE +FILTER_REGEX_EXCLUDE: .*mypy.* # cspell:disable-line +EDITORCONFIG_EDITORCONFIG_CHECKER_FILTER_REGEX_EXCLUDE: "Docs/acknowledgements.md|src/Installer/Linux/installer.bash" +REPORT_OUTPUT_FOLDER: none +SHOW_ELAPSED_TIME: true +VALIDATE_ALL_CODEBASE: false diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml new file mode 100644 index 0000000..6c07b01 --- /dev/null +++ b/.pre-commit-config.yaml @@ -0,0 +1,38 @@ +--- +repos: + - repo: https://github.com/pre-commit/pre-commit-hooks + rev: v4.4.0 + hooks: + - id: check-added-large-files + - id: check-executables-have-shebangs + - id: check-json + - id: check-merge-conflict + - id: check-shebang-scripts-are-executable + - id: check-toml + - id: check-yaml + - id: pretty-format-json + args: [--autofix] + - id: requirements-txt-fixer + - id: trailing-whitespace + - repo: https://github.com/jorisroovers/gitlint + rev: v0.19.1 + hooks: + - id: gitlint + args: [--config=.github\.gitlint, --msg-filename] + - repo: https://github.com/doublify/pre-commit-rust + rev: v1.0 + hooks: + - id: fmt + - id: cargo-check + - repo: local + hooks: + - id: clippy + name: Run clippy linter + entry: cargo clippy -- -A non_snake_case + language: system + pass_filenames: false +ci: + skip: + - fmt + - cargo-check + - clippy diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..e7c9c3a --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,23 @@ +[package] +name = "localizer-rs" +description = "Localizer helps localize (translate) your rust applications using json files." +version = "1.0.0" +authors = [ + "ElBe-Plaq " +] +edition = "2021" +rust-version = "1.69" +documentation = "https://docs.rs/localizer_rs/" +readme = ".github/README.md" +repository = "https://github.com/ElBe-Development/localizer-rs/" +license-file = "LICENSE.txt" +keywords = ["i18n", "L10n", "json", "local", "translation"] +categories = [ + "internationalization", + "localization" +] +publish = ["localizer-rs"] + +[dependencies] +serde = "1.0.188" +serde_json = "1.0.106" diff --git a/LICENSE b/LICENSE.txt similarity index 100% rename from LICENSE rename to LICENSE.txt diff --git a/dev-requirements.txt b/dev-requirements.txt new file mode 100644 index 0000000..60d2bd3 --- /dev/null +++ b/dev-requirements.txt @@ -0,0 +1 @@ +pre-commit==3.3.2; python_version>='3.8' diff --git a/examples/main.rs b/examples/main.rs new file mode 100644 index 0000000..1086aa7 --- /dev/null +++ b/examples/main.rs @@ -0,0 +1,10 @@ +use localizer_rs; + +fn main() { + let config: localizer_rs::Config = localizer_rs::Config::new("translations", "en"); + + println!("{:}", config.t("error", vec![("details", "Something went wrong when trying to do stuff")])); + println!("{:}", config.t("success", vec![("balance", "$10"), ("user", "John Doe")])); + + println!("{:}", config.t("all", vec![])); +} \ No newline at end of file diff --git a/examples/translations/en.json b/examples/translations/en.json new file mode 100644 index 0000000..adebba9 --- /dev/null +++ b/examples/translations/en.json @@ -0,0 +1,7 @@ +{ + "test": "Something that can be translated.", + "error": "{{color.red}}{{bold}}Error:{{end}} {{details}}", + "success": "{{color.green}}{{bold}}Success:{{end}} Successfully transferred {{balance}} to {{user}}", + + "all": "{{bold}}{{underline}}Formatting options:{{end}}\n\n{{bold}}Bold text{{end}}\n{{italic}}Italic text{{end}}\n{{underline}}Underlined text{{end}}\n{{overline}}Overlined text{{end}}\n\n{{bold}}{{underline}}Colored text:{{end}}\n\n{{back.white}}{{color.black}}Black text{{end}}\n{{color.red}}Red text{{end}}\n{{color.green}}Green text{{end}}\n{{color.yellow}}Yellow text{{end}}\n{{color.blue}}Blue text{{end}}\n{{color.magenta}}Magenta text{{end}}\n{{color.cyan}}Cyan text{{end}}\n{{color.white}}White text{{end}}\n\n{{bold}}{{underline}}Bright colored text:{{end}}\n\n{{color.bright_black}}Bright black text{{end}}\n{{color.bright_red}}Bright red text{{end}}\n{{color.bright_green}}Bright green text{{end}}\n{{color.bright_yellow}}Bright yellow text{{end}}\n{{color.bright_blue}}Bright blue text{{end}}\n{{color.bright_magenta}}Bright magenta text{{end}}\n{{color.bright_cyan}}Bright cyan text{{end}}\n{{color.bright_white}}Bright white text{{end}}\n\n{{bold}}{{underline}}Colored background:{{end}}\n\n{{back.black}}Black background{{end}}\n{{back.red}}Red background{{end}}\n{{back.green}}Green text (background:{{end}}\n{{back.yellow}}Yellow background{{end}}\n{{back.blue}}Blue background{{end}}\n{{back.magenta}}Magenta background{{end}}\n{{back.cyan}}Cyan background{{end}}\n{{color.black}}{{back.white}}White background{{end}}\n{{back.bright_black}}Bright black background{{end}}\n{{back.bright_red}}Bright red background{{end}}\n{{back.bright_green}}Bright green background{{end}}\n{{back.bright_yellow}}Bright yellow background{{end}}\n{{back.bright_blue}}Bright blue background{{end}}\n{{back.bright_magenta}}Bright magenta background{{end}}\n{{back.bright_cyan}}Bright cyan background{{end}}\n{{back.bright_white}}Bright white background{{end}}" +} diff --git a/justfile b/justfile new file mode 100644 index 0000000..a3b4dd3 --- /dev/null +++ b/justfile @@ -0,0 +1,26 @@ +alias b := build +alias c := clean +alias l := lint +alias r := run +alias t := test + +# Compiles the rust source files +build *ARGUMENTS: + cargo build --release *ARGUMENTS + +# Removes temporary files +clean: + rm -rf target + rm -rf Tools/__pycache__ + +# Lints the rust source files +lint *ARGUMENTS: + cargo check *ARGUMENTS + +# Compiles and executes the main.rs file +run *ARGUMENTS: + cargo run *ARGUMENTS + +# Runs the unittests +test *ARGUMENTS: + cargo test *ARGUMENTS diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 0000000..4d84996 --- /dev/null +++ b/src/lib.rs @@ -0,0 +1,253 @@ +// localizer-rs +// Version: 1.0.0 + +// Copyright (c) 2023-present ElBe Development. + +// 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. + +//////////////////////////////// +// IMPORTS AND USE STATEMENTS // +//////////////////////////////// + +use std::fs::File; +use std::io::BufReader; +use std::path::Path; +use std::process::exit; + +use serde_json; + + +/////////////////// +// CONFIG OBJECT // +/////////////////// + +/// Localization config object. +/// +/// Use [`Config::new()`] to create config objects instead of using this struct. +/// +/// # Parameters +/// +/// - `path`: The directory containing the translation files. The directory is relative to the path the executable was executed from. +/// - `language`: The language to translate to. +/// +/// # Returns +/// +/// A new `Config` object with the specified path and language. +/// +/// # Examples +/// +/// ```rust +/// # use localizer_rs; +/// localizer_rs::Config { +/// path: "path".to_owned(), +/// language: "language".to_owned() +/// }; +/// ``` +#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)] +pub struct Config { + pub path: String, + pub language: String +} + + +////////////////////// +// CONFIG FUNCTIONS // +////////////////////// + +impl Config { + /// Creates a new config object. + /// + /// # Parameters + /// + /// - `path`: The directory containing the translation files. The directory is relative to the path the executable was executed from. + /// - `language`: The language to translate to. + /// + /// # Returns + /// + /// A new `Config` object with the specified path and language. + /// + /// # Panics + /// + /// Panics if the Path provided is invalid. + /// + /// # Examples + /// + /// ```rust + /// # use localizer_rs; + /// localizer_rs::Config::new("examples/translations", "language"); + /// ``` + /// + /// # See also + /// + /// - [`Config`] + pub fn new(path: &str, language: &str) -> Config { + let mut config: Config = Config { + path: "".to_string(), + language: "".to_string() + }.to_owned(); + config = config.set_language(language).to_owned(); + config = config.set_path(path).to_owned(); + + return config; + } + + /// Sets the path for the config object. + /// + /// # Parameters + /// + /// - `self`: The config object. + /// - `str_path`: The directory containing the translation files. The directory is relative to the path the executable was executed from. + /// + /// # Returns + /// + /// The modified `Config` object with the specified path. + /// + /// # Panics + /// + /// Panics if the Path provided is invalid. + /// + /// # Examples + /// + /// ```rust + /// # use localizer_rs; + /// # let config: localizer_rs::Config = localizer_rs::Config::new("examples/translations", "language"); + /// config.set_path("examples"); + /// ``` + /// + /// # See also + /// + /// - [`Config`] + pub fn set_path(&mut self, str_path: &str) -> &Config { + let path: &Path = Path::new(str_path); + + match path.try_exists() { + Ok(value) => { + if !value { + eprintln!("Translation path {:?} does not exist", str_path); + exit(1); + } + }, + Err(error) => { + eprintln!("Can't open translation path {:?}: {}", str_path, error); + exit(2); + } + } + + self.path = String::from(path.to_owned().to_str().expect("Expected valid path")); + return self; + } + + /// Sets the language for the config object. + /// + /// # Parameters + /// + /// - `self`: The config object. + /// - `language`: The language to translate to. + /// + /// # Returns + /// + /// The modified `Config` object with the specified language. + /// + /// # Examples + /// + /// ```rust + /// # use localizer_rs; + /// # let config: localizer_rs::Config = localizer_rs::Config::new("examples/translations", "language"); + /// config.set_language("en"); + /// ``` + /// + /// # See also + /// + /// - [`Config`] + pub fn set_language(&mut self, language: &str) -> &Config { + self.language = language.to_string(); + return self; + } + + /// Translates the specified key in the language specified in the config. + /// + /// # Parameters + /// + /// - `self`: The config object. + /// - `key`: The key to translate to. + /// - `arguments`: The arguments to replace. + /// + /// # Returns + /// + /// A `String` containing the translated value. + /// + /// # Examples + /// + /// ```rust + /// # use localizer_rs; + /// # let config: localizer_rs::Config = localizer_rs::Config::new("examples/translations", "en"); + /// config.t("test", vec![]); + /// ``` + /// + /// # See also + /// + /// - [`Config`] + pub fn t(&self, key: &str, arguments: Vec<(&str, &str)>) -> String { + return self.translate::(key, arguments); + } + + fn translate(&self, key: &str, mut arguments: Vec<(&str, &str)>) -> String + where + T: serde::Serialize + for<'de> serde::Deserialize<'de> + { + let mut colors: Vec<(&str, &str)> = vec![ + // Formatting codes + ("end", "\x1b[0m"), ("bold", "\x1b[1m"), ("italic", "\x1b[3m"), ("underline", "\x1b[4m"), ("overline", "\x1b[53m"), + + // Foreground colors + ("color.black", "\x1b[30m"), ("color.red", "\x1b[31m"), ("color.green", "\x1b[32m"), ("color.yellow", "\x1b[33m"), + ("color.blue", "\x1b[34m"), ("color.magenta", "\x1b[35m"), ("color.cyan", "\x1b[36m"), ("color.white", "\x1b[37m"), + + // Bright foreground colors + ("color.bright_black", "\x1b[90m"), ("color.bright_red", "\x1b[91m"), ("color.bright_green", "\x1b[92m"), + ("color.bright_yellow", "\x1b[93m"), ("color.bright_blue", "\x1b[94m"), ("color.bright_magenta", "\x1b[95m"), + ("color.bright_cyan", "\x1b[96m"), ("color.bright_white", "\x1b[97m"), + + // Background colors + ("back.black", "\x1b[40m"), ("back.red", "\x1b[41m"), ("back.green", "\x1b[42m"), ("back.yellow", "\x1b[43m"), + ("back.blue", "\x1b[44m"), ("back.magenta", "\x1b[45m"), ("back.cyan", "\x1b[46m"), ("back.white", "\x1b[47m"), + + // Bright background colors + ("back.bright_black", "\x1b[100m"), ("back.bright_red", "\x1b[101m"), ("back.bright_green", "\x1b[102m"), + ("back.bright_yellow", "\x1b[103m"), ("back.bright_blue", "\x1b[104m"), ("back.bright_magenta", "\x1b[105m"), + ("back.bright_cyan", "\x1b[106m"), ("back.bright_white", "\x1b[107m"), + ]; + arguments.append(&mut colors); + + let file: File = File::open(Path::new(format!("./{}/{}.json", &self.path, &self.language).as_str())).unwrap(); + let reader: BufReader = BufReader::new(file); + + let json: serde_json::Value = serde_json::to_value::(serde_json::from_reader::, T>(reader).unwrap()).unwrap().to_owned(); + let mut result: String = match json[key].as_str() { + Some(value) => value.to_string(), + None => "".to_string() + }; + + for (key, value) in arguments { + result = result.replace(("{{".to_owned() + key + "}}").as_str(), value); + } + + return result; + } +} diff --git a/tests/main.rs b/tests/main.rs new file mode 100644 index 0000000..4963182 --- /dev/null +++ b/tests/main.rs @@ -0,0 +1,78 @@ +// localizer-rs tests +// Version: 1.0.0-alpha1 + +// Copyright (c) 2023-present ElBe Development. + +// 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. + +//////////////////////////////// +// IMPORTS AND USE STATEMENTS // +//////////////////////////////// + +#[allow(unused_imports)] +use localizer_rs; + + +/////////// +// TESTS // +/////////// + +#[cfg(test)] +mod tests { + #[test] + fn test_config() { + let config: localizer_rs::Config = localizer_rs::Config::new("examples/translations", "en"); + + assert_eq!(config, localizer_rs::Config { + path: "examples/translations".to_owned(), + language: "en".to_owned() + }); + } + + #[test] + fn test_set_path() { + let mut config: localizer_rs::Config = localizer_rs::Config::new("examples/translations", "en"); + config.set_path("examples"); + + assert_eq!(config, localizer_rs::Config { + path: "examples".to_owned(), + language: "en".to_owned() + }); + + } + + #[test] + fn test_set_language() { + let mut config: localizer_rs::Config = localizer_rs::Config::new("examples/translations", "en"); + config.set_language("not_en"); + + assert_eq!(config, localizer_rs::Config { + path: "examples/translations".to_owned(), + language: "not_en".to_owned() + }); + } + + #[test] + fn test_translate() { + let config: localizer_rs::Config = localizer_rs::Config::new("examples/translations", "en"); + let translation: String = config.t("error", vec![("details", "Something went wrong")]); + + assert_eq!(translation.as_str(), "\x1b[31m\x1b[1mError:\x1b[0m Something went wrong"); + } +}