diff --git a/.editorconfig b/.editorconfig
new file mode 100644
index 0000000..0872015
--- /dev/null
+++ b/.editorconfig
@@ -0,0 +1,14 @@
+root = true
+
+[*]
+indent_size = 2
+max_line_length = 100
+indent_style = space
+insert_final_newline = true
+trim_trailing_whitespace = true
+ij_kotlin_allow_trailing_comma = true
+ij_kotlin_allow_trailing_comma_on_call_site = true
+
+[*.{kt, kts}]
+ktlint_standard_filename = disabled
+ktlint_standard_max-line-length = disabled
\ No newline at end of file
diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS
new file mode 100644
index 0000000..2a97d97
--- /dev/null
+++ b/.github/CODEOWNERS
@@ -0,0 +1,16 @@
+# Lines starting with '#' are comments.
+# Each line is a file pattern followed by one or more owners.
+
+# More details are here: https://help.github.com/articles/about-codeowners/
+
+# The '*' pattern is global owners.
+# Not adding in this PR, but I'd like to try adding a global owner set with the entire team.
+# One interpretation of their docs is that global owners are added only if not removed
+# by a more local rule.
+
+# Order is important. The last matching pattern has the most precedence.
+# The folders are ordered as follows:
+
+# In each subsection folders are ordered first by depth, then alphabetically.
+# This should make it easy to add new rules without breaking existing ones.
+* @teogor
\ No newline at end of file
diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml
new file mode 100644
index 0000000..35787e1
--- /dev/null
+++ b/.github/FUNDING.yml
@@ -0,0 +1,13 @@
+# These are supported funding model platforms
+
+github: [ teogor ]
+patreon: # Replace with a single Patreon username
+open_collective: # Replace with a single Open Collective username
+ko_fi: # Replace with a single Ko-fi username
+tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel
+community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry
+liberapay: # Replace with a single Liberapay username
+issuehunt: # Replace with a single IssueHunt username
+otechie: # Replace with a single Otechie username
+lfx_crowdfunding: # Replace with a single LFX Crowdfunding project-name e.g., cloud-foundry
+custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2']
diff --git a/.github/workflows/publish-docs.yml b/.github/workflows/publish-docs.yml
new file mode 100644
index 0000000..df09483
--- /dev/null
+++ b/.github/workflows/publish-docs.yml
@@ -0,0 +1,106 @@
+name: Publish API Docs
+
+on:
+ workflow_dispatch:
+ push:
+ branches:
+ - main
+
+permissions:
+ contents: write
+ pages: write
+ id-token: write
+ pull-requests: write
+
+# Allow only one concurrent deployment, skipping runs queued between the run in-progress and latest queued.
+# However, do NOT cancel in-progress runs as we want to allow these production deployments to complete.
+concurrency:
+ group: "api-docs"
+ cancel-in-progress: false
+
+env:
+ DESTINATION_BRANCH: docs/${{ github.event.repository.owner.name }}/${{ github.event.repository.name }}/${{ github.sha }}
+ REPOSITORY: ${{ github.event.repository.name }}
+ REPO: ${{ github.event.repository.owner.name }}/${{ github.event.repository.name }}
+ DISPLAY_NAME: Xenoglot
+
+jobs:
+ synchronize-documentation:
+ runs-on: ubuntu-latest
+ name: Synchronize Repository API Documentation
+ steps:
+ - uses: actions/checkout@v4
+
+ # Grants execute permission to gradle (safety step)
+ - name: Grant Permissions to gradlew
+ run: chmod +x gradlew
+
+ - name: Setup Java
+ uses: actions/setup-java@v3
+ with:
+ distribution: temurin
+ java-version: 17
+
+ - name: Generate API Documentation
+ run: ./gradlew dokkaHtmlMultiModule
+
+ - name: Relocate API Documentation Assets
+ run: |
+ mkdir -p docs/${{ env.REPOSITORY }}/documentation
+ cp -r build/dokka/htmlMultiModule/* docs/${{ env.REPOSITORY }}/documentation
+
+ - id: publish-docs
+ name: Deploy API Documentation
+ uses: ./.github/workflows/sync-docs/
+ env:
+ API_TOKEN_GITHUB: ${{ secrets.PERSONAL_ACCESS_TOKEN }}
+ with:
+ source-file: docs/${{ env.REPOSITORY }}/documentation/
+ destination-folder: ${{ env.REPOSITORY }}
+ destination-repo: teogor/source.teogor.dev
+ user-email: open-source@teogor.dev
+ user-name: Teodor Grigor
+ destination-branch-create: ${{ env.DESTINATION_BRANCH }}
+
+ build-and-deploy:
+ needs: synchronize-documentation
+ name: Build and Deploy Updated API Documentation
+ runs-on: ubuntu-latest
+ steps:
+ - name: Checkout other repository
+ uses: actions/checkout@v3
+ with:
+ repository: teogor/source.teogor.dev
+ token: ${{ secrets.PERSONAL_ACCESS_TOKEN }}
+
+ - name: Create PR (Pull Request)
+ run: |
+ json=$(curl -L \
+ -X POST \
+ -H "Accept: application/vnd.github+json" \
+ -H "Authorization: Bearer ${{ secrets.PERSONAL_ACCESS_TOKEN }}" \
+ -H "X-GitHub-Api-Version: 2022-11-28" \
+ https://api.github.com/repos/teogor/source.teogor.dev/pulls \
+ -d '{
+ "title":"Updated documentation for `${{ env.REPO }}`",
+ "body":"## 🚀 **Updated Documentation for ${{ env.DISPLAY_NAME }}** 🚀\n\nThis pull request brings you the latest updates to the [${{ env.DISPLAY_NAME }}](https://github.com/${{ env.REPO }}) documentation, ensuring you have access to the most comprehensive and informative guide for using this powerful library.\n\n-------\n\n* Generated by [Publish Docs](https://github.com/${{ env.REPO }}/actions/workflows/publish-docs.yml)\n\n* From [${{ env.REPO }}](https://github.com/${{ env.REPO }})",
+ "head":"${{ env.DESTINATION_BRANCH }}",
+ "base":"main"
+ }' | jq .)
+
+ number=$(jq '.number' <<< ${json})
+
+ curl -L \
+ -X POST \
+ -H "Accept: application/vnd.github+json" \
+ -H "Authorization: Bearer ${{ secrets.PERSONAL_ACCESS_TOKEN }}" \
+ -H "X-GitHub-Api-Version: 2022-11-28" \
+ https://api.github.com/repos/teogor/source.teogor.dev/issues/${number}/labels \
+ -d '{"labels":["@documentation"]}'
+
+ curl -L \
+ -X PUT \
+ -H "Accept: application/vnd.github+json" \
+ -H "Authorization: Bearer ${{ secrets.PERSONAL_ACCESS_TOKEN }}" \
+ -H "X-GitHub-Api-Version: 2022-11-28" \
+ https://api.github.com/repos/teogor/source.teogor.dev/pulls/${number}/merge \
diff --git a/.github/workflows/publish-to-maven.yml b/.github/workflows/publish-to-maven.yml
new file mode 100644
index 0000000..fa14266
--- /dev/null
+++ b/.github/workflows/publish-to-maven.yml
@@ -0,0 +1,36 @@
+name: Publish to Maven
+
+on:
+ workflow_dispatch:
+
+jobs:
+ publish:
+ name: Snapshot build and publish
+ runs-on: ubuntu-latest
+ environment: PRODUCTION
+ timeout-minutes: 120
+ env:
+ ORG_GRADLE_PROJECT_mavenCentralUsername: ${{ secrets.OSSRH_USERNAME }}
+ ORG_GRADLE_PROJECT_mavenCentralPassword: ${{ secrets.OSSRH_PASSWORD }}
+ ORG_GRADLE_PROJECT_signingInMemoryKeyId: ${{ secrets.SIGNING_KEY_ID }}
+ ORG_GRADLE_PROJECT_signingInMemoryKeyPassword: ${{ secrets.SIGNING_PASSWORD }}
+ ORG_GRADLE_PROJECT_signingInMemoryKey: ${{ secrets.SIGNING_KEY }}
+ SONATYPE_CONNECT_TIMEOUT_SECONDS: 120
+ SONATYPE_CLOSE_TIMEOUT_SECONDS: 1800
+
+ steps:
+ - name: Check out code
+ uses: actions/checkout@v3.1.0
+
+ - name: Set up JDK 17
+ uses: actions/setup-java@v3.5.1
+ with:
+ distribution: 'zulu'
+ java-version: 17
+
+ - name: Grant Permission to Execute Gradle
+ run: chmod +x gradlew
+
+ - name: Publish to MavenCentral
+ run: |
+ ./gradlew :publish --no-configuration-cache
diff --git a/.github/workflows/sync-docs/action.yml b/.github/workflows/sync-docs/action.yml
new file mode 100644
index 0000000..4fd1113
--- /dev/null
+++ b/.github/workflows/sync-docs/action.yml
@@ -0,0 +1,65 @@
+name: 'Publish Docs'
+description: 'Comprehensive publish docs'
+branding:
+ icon: 'git-commit'
+ color: 'red'
+inputs:
+ source-file:
+ description: 'Source file from the origin directory'
+ required: true
+ destination-repo:
+ description: 'Destination repository'
+ required: true
+ destination-folder:
+ description: 'Directory to push the file to'
+ required: false
+ user-email:
+ description: 'Email for the git commit'
+ required: true
+ user-name:
+ description: 'GitHub username for the commit'
+ required: true
+ destination-branch:
+ description: 'branch to push file to, defaults to main'
+ required: false
+ destination-branch-create:
+ description: 'Destination branch to create for this commit'
+ required: false
+ commit-message:
+ description: 'A custom message for the commit'
+ required: false
+ rename:
+ description: 'Rename the destination file'
+ required: false
+ use-rsync:
+ description: 'Copy files/directories using rsync instead of cp. Experimental feature, please know your use case'
+ required: false
+ git-server:
+ description: 'Git server host, default github.com'
+ required: false
+ default: github.com
+runs:
+ using: "composite"
+ steps:
+ - name: Make all .sh files executable
+ run: find . -type f -name "*.sh" -exec chmod +x {} \;
+ shell: bash
+
+ - run: echo "${{ github.action_path }}" >> $GITHUB_PATH
+ shell: bash
+
+ - name: Execute Entrypoint Script
+ shell: bash
+ env:
+ SOURCE_FILE: ${{ inputs.source-file }}
+ DESTINATION_REPO: ${{ inputs.destination-repo }}
+ DESTINATION_FOLDER: ${{ inputs.destination-folder }}
+ USER_EMAIL: ${{ inputs.user-email }}
+ USER_NAME: ${{ inputs.user-name }}
+ DESTINATION_BRANCH: ${{ inputs.destination-branch }}
+ DESTINATION_BRANCH_CREATE: ${{ inputs.destination-branch-create }}
+ COMMIT_MESSAGE: ${{ inputs.commit-message }}
+ GIT_SERVER: ${{ inputs.git-server }}
+ RENAME: ${{ inputs.rename }}
+ USE_RSYNC: ${{ inputs.use-rsync }}
+ run: sync-files.sh
diff --git a/.github/workflows/sync-docs/sync-files.sh b/.github/workflows/sync-docs/sync-files.sh
new file mode 100644
index 0000000..b655fa1
--- /dev/null
+++ b/.github/workflows/sync-docs/sync-files.sh
@@ -0,0 +1,85 @@
+#!/bin/sh
+
+set -e
+set -x
+
+# Validate required input
+if [ -z "$SOURCE_FILE" ]; then
+ echo "Error: Source file must be specified."
+ exit 1
+fi
+
+# Set default values for optional variables
+if [ -z "$GIT_SERVER" ]; then
+ GIT_SERVER="github.com"
+fi
+
+if [ -z "$DESTINATION_BRANCH" ]; then
+ DESTINATION_BRANCH="main"
+fi
+
+# Clone the destination Git repository
+OUTPUT_BRANCH="$DESTINATION_BRANCH"
+
+CLONE_DIR=$(mktemp -d)
+echo "Cloning destination Git repository: $DESTINATION_REPO"
+
+git config --global user.email "$USER_EMAIL"
+git config --global user.name "$USER_NAME"
+
+git clone --single-branch --branch $DESTINATION_BRANCH "https://x-access-token:$API_TOKEN_GITHUB@$GIT_SERVER/$DESTINATION_REPO.git" "$CLONE_DIR"
+
+# Determine the destination file path
+DEST_COPY="$CLONE_DIR/$DESTINATION_FOLDER"
+
+if [ ! -z "$RENAME" ]; then
+ echo "Renaming file to: ${RENAME}"
+ DEST_COPY="$CLONE_DIR/$DESTINATION_FOLDER/$RENAME"
+fi
+
+# Delete the previous folder if it exists
+if [ -d "$DEST_COPY" ]; then
+ echo "Deleting existing folder: $DEST_COPY"
+ rm -rf "$DEST_COPY"
+fi
+
+# Copy the source file to the destination repository
+echo "Copying contents to Git repo: $SOURCE_FILE"
+
+mkdir -p $CLONE_DIR/$DESTINATION_FOLDER
+
+if [ -z "$USE_RSYNC" ]; then
+ echo "Copying using cp"
+ cp -R "$SOURCE_FILE" "$DEST_COPY"
+else
+ echo "Copying using rsync"
+ rsync -avrh "$SOURCE_FILE" "$DEST_COPY"
+fi
+
+# Check out the specified branch or create a new one
+cd "$CLONE_DIR"
+
+if [ ! -z "$DESTINATION_BRANCH_CREATE" ]; then
+ echo "Creating new branch: $DESTINATION_BRANCH_CREATE"
+ git checkout -b "$DESTINATION_BRANCH_CREATE"
+ OUTPUT_BRANCH="$DESTINATION_BRANCH_CREATE"
+fi
+
+if [ -z "$COMMIT_MESSAGE" ]; then
+ COMMIT_MESSAGE="Automated updates based on https://$GIT_SERVER/${GITHUB_REPOSITORY}/commit/${GITHUB_SHA}"
+fi
+
+# Add the copied file to the staging area
+echo "Adding git commit"
+git add .
+
+# Commit changes if there are any
+if git status | grep -q "Changes to be committed"; then
+ echo "Committing changes with message: $COMMIT_MESSAGE"
+ git commit --message "$COMMIT_MESSAGE"
+
+ echo "Pushing git commit to branch: $OUTPUT_BRANCH"
+ git push -u origin HEAD:"$OUTPUT_BRANCH"
+else
+ echo "No changes detected, skipping commit and push"
+fi
diff --git a/.gitignore b/.gitignore
index 347e252..8a11ac9 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,6 +1,9 @@
# Gradle files
.gradle/
build/
+!gradle/wrapper/gradle-wrapper.jar
+!**/src/main/**/build/
+!**/src/test/**/build/
# Local configuration file (sdk path, etc)
local.properties
@@ -15,6 +18,37 @@ captures/
*.apk
output.json
+### IntelliJ IDEA ###
+.idea/modules.xml
+.idea/jarRepositories.xml
+.idea/compiler.xml
+.idea/libraries/
+*.iws
+*.iml
+*.ipr
+out/
+!**/src/main/**/out/
+!**/src/test/**/out/
+
+### Eclipse ###
+.apt_generated
+.classpath
+.factorypath
+.project
+.settings
+.springBeans
+.sts4-cache
+bin/
+!**/src/main/**/bin/
+!**/src/test/**/bin/
+
+### NetBeans ###
+/nbproject/private/
+/nbbuild/
+/dist/
+/nbdist/
+/.nb-gradle/
+
# IntelliJ
*.iml
.idea/
@@ -22,12 +56,18 @@ misc.xml
deploymentTargetDropDown.xml
render.experimental.xml
+### VS Code ###
+.vscode/
+
+### Mac OS ###
+.DS_Store
+
# Keystore files
*.jks
*.keystore
-# Google Services (e.g. APIs or Firebase)
-google-services.json
-
# Android Profiling
*.hprof
+
+# Google Services (e.g. APIs or Firebase)
+google-services.json
diff --git a/.resources/bom/1.0.0-alpha01/dependencies-1.0.0-alpha01.json b/.resources/bom/1.0.0-alpha01/dependencies-1.0.0-alpha01.json
new file mode 100644
index 0000000..0de5644
--- /dev/null
+++ b/.resources/bom/1.0.0-alpha01/dependencies-1.0.0-alpha01.json
@@ -0,0 +1,392 @@
+[
+ {
+ "completeName": "Xenoglot Android",
+ "name": "android",
+ "displayName": "Android",
+ "description": "🌍 Xenoglot seamlessly masters languages and locales, empowering developers to integrate multilingual capabilities into their applications with ease.",
+ "groupId": "dev.teogor.xenoglot",
+ "artifactId": "android",
+ "version": {
+ "major": 1,
+ "minor": 0,
+ "patch": 0,
+ "flag": "Alpha",
+ "versionQualifier": 1
+ },
+ "path": ":android",
+ "dependencies": [
+ {
+ "type": "dev.teogor.winds.api.model.Dependency",
+ "implementationType": "api",
+ "group": "androidx.appcompat",
+ "artifact": "appcompat",
+ "version": "1.6.1"
+ },
+ {
+ "type": "dev.teogor.winds.api.model.LocalProjectDependency",
+ "implementationType": "debugAndroidTestCompileClasspath",
+ "projectName": "Xenoglot",
+ "modulePath": ":android"
+ },
+ {
+ "type": "dev.teogor.winds.api.model.Dependency",
+ "implementationType": "debugAndroidTestCompileClasspath",
+ "group": "androidx.appcompat",
+ "artifact": "appcompat",
+ "version": "1.6.1"
+ },
+ {
+ "type": "dev.teogor.winds.api.model.LocalProjectDependency",
+ "implementationType": "debugAndroidTestRuntimeClasspath",
+ "projectName": "Xenoglot",
+ "modulePath": ":android"
+ },
+ {
+ "type": "dev.teogor.winds.api.model.LocalProjectDependency",
+ "implementationType": "debugUnitTestCompileClasspath",
+ "projectName": "Xenoglot",
+ "modulePath": ":android"
+ },
+ {
+ "type": "dev.teogor.winds.api.model.Dependency",
+ "implementationType": "debugUnitTestCompileClasspath",
+ "group": "androidx.appcompat",
+ "artifact": "appcompat",
+ "version": "1.6.1"
+ },
+ {
+ "type": "dev.teogor.winds.api.model.LocalProjectDependency",
+ "implementationType": "debugUnitTestRuntimeClasspath",
+ "projectName": "Xenoglot",
+ "modulePath": ":android"
+ },
+ {
+ "type": "dev.teogor.winds.api.model.Dependency",
+ "implementationType": "dokkaGfmPartialPlugin",
+ "group": "org.jetbrains.dokka",
+ "artifact": "analysis-kotlin-descriptors",
+ "version": "1.9.0"
+ },
+ {
+ "type": "dev.teogor.winds.api.model.Dependency",
+ "implementationType": "dokkaGfmPartialPlugin",
+ "group": "org.jetbrains.dokka",
+ "artifact": "dokka-base",
+ "version": "1.9.0"
+ },
+ {
+ "type": "dev.teogor.winds.api.model.Dependency",
+ "implementationType": "dokkaGfmPlugin",
+ "group": "org.jetbrains.dokka",
+ "artifact": "analysis-kotlin-descriptors",
+ "version": "1.9.0"
+ },
+ {
+ "type": "dev.teogor.winds.api.model.Dependency",
+ "implementationType": "dokkaGfmPlugin",
+ "group": "org.jetbrains.dokka",
+ "artifact": "dokka-base",
+ "version": "1.9.0"
+ },
+ {
+ "type": "dev.teogor.winds.api.model.Dependency",
+ "implementationType": "dokkaHtmlPartialPlugin",
+ "group": "org.jetbrains.dokka",
+ "artifact": "analysis-kotlin-descriptors",
+ "version": "1.9.0"
+ },
+ {
+ "type": "dev.teogor.winds.api.model.Dependency",
+ "implementationType": "dokkaHtmlPartialPlugin",
+ "group": "org.jetbrains.dokka",
+ "artifact": "dokka-base",
+ "version": "1.9.0"
+ },
+ {
+ "type": "dev.teogor.winds.api.model.Dependency",
+ "implementationType": "dokkaHtmlPlugin",
+ "group": "org.jetbrains.dokka",
+ "artifact": "analysis-kotlin-descriptors",
+ "version": "1.9.0"
+ },
+ {
+ "type": "dev.teogor.winds.api.model.Dependency",
+ "implementationType": "dokkaHtmlPlugin",
+ "group": "org.jetbrains.dokka",
+ "artifact": "dokka-base",
+ "version": "1.9.0"
+ },
+ {
+ "type": "dev.teogor.winds.api.model.Dependency",
+ "implementationType": "dokkaJavadocPartialPlugin",
+ "group": "org.jetbrains.dokka",
+ "artifact": "analysis-kotlin-descriptors",
+ "version": "1.9.0"
+ },
+ {
+ "type": "dev.teogor.winds.api.model.Dependency",
+ "implementationType": "dokkaJavadocPartialPlugin",
+ "group": "org.jetbrains.dokka",
+ "artifact": "dokka-base",
+ "version": "1.9.0"
+ },
+ {
+ "type": "dev.teogor.winds.api.model.Dependency",
+ "implementationType": "dokkaJavadocPlugin",
+ "group": "org.jetbrains.dokka",
+ "artifact": "analysis-kotlin-descriptors",
+ "version": "1.9.0"
+ },
+ {
+ "type": "dev.teogor.winds.api.model.Dependency",
+ "implementationType": "dokkaJavadocPlugin",
+ "group": "org.jetbrains.dokka",
+ "artifact": "dokka-base",
+ "version": "1.9.0"
+ },
+ {
+ "type": "dev.teogor.winds.api.model.Dependency",
+ "implementationType": "dokkaJekyllPartialPlugin",
+ "group": "org.jetbrains.dokka",
+ "artifact": "analysis-kotlin-descriptors",
+ "version": "1.9.0"
+ },
+ {
+ "type": "dev.teogor.winds.api.model.Dependency",
+ "implementationType": "dokkaJekyllPartialPlugin",
+ "group": "org.jetbrains.dokka",
+ "artifact": "dokka-base",
+ "version": "1.9.0"
+ },
+ {
+ "type": "dev.teogor.winds.api.model.Dependency",
+ "implementationType": "dokkaJekyllPlugin",
+ "group": "org.jetbrains.dokka",
+ "artifact": "analysis-kotlin-descriptors",
+ "version": "1.9.0"
+ },
+ {
+ "type": "dev.teogor.winds.api.model.Dependency",
+ "implementationType": "dokkaJekyllPlugin",
+ "group": "org.jetbrains.dokka",
+ "artifact": "dokka-base",
+ "version": "1.9.0"
+ },
+ {
+ "type": "dev.teogor.winds.api.model.LocalProjectDependency",
+ "implementationType": "releaseUnitTestCompileClasspath",
+ "projectName": "Xenoglot",
+ "modulePath": ":android"
+ },
+ {
+ "type": "dev.teogor.winds.api.model.Dependency",
+ "implementationType": "releaseUnitTestCompileClasspath",
+ "group": "androidx.appcompat",
+ "artifact": "appcompat",
+ "version": "1.6.1"
+ },
+ {
+ "type": "dev.teogor.winds.api.model.LocalProjectDependency",
+ "implementationType": "releaseUnitTestRuntimeClasspath",
+ "projectName": "Xenoglot",
+ "modulePath": ":android"
+ }
+ ],
+ "canBePublished": true,
+ "names": [
+ "Xenoglot",
+ "Android"
+ ]
+ },
+ {
+ "completeName": "Xenoglot Core",
+ "name": "core",
+ "displayName": "Core",
+ "description": "🌍 Xenoglot seamlessly masters languages and locales, empowering developers to integrate multilingual capabilities into their applications with ease.",
+ "groupId": "dev.teogor.xenoglot",
+ "artifactId": "core",
+ "version": {
+ "major": 1,
+ "minor": 0,
+ "patch": 0,
+ "flag": "Alpha",
+ "versionQualifier": 1
+ },
+ "path": ":core",
+ "dependencies": [
+ {
+ "type": "dev.teogor.winds.api.model.Dependency",
+ "implementationType": "dokkaGfmPartialPlugin",
+ "group": "org.jetbrains.dokka",
+ "artifact": "analysis-kotlin-descriptors",
+ "version": "1.9.0"
+ },
+ {
+ "type": "dev.teogor.winds.api.model.Dependency",
+ "implementationType": "dokkaGfmPartialPlugin",
+ "group": "org.jetbrains.dokka",
+ "artifact": "dokka-base",
+ "version": "1.9.0"
+ },
+ {
+ "type": "dev.teogor.winds.api.model.Dependency",
+ "implementationType": "dokkaGfmPlugin",
+ "group": "org.jetbrains.dokka",
+ "artifact": "analysis-kotlin-descriptors",
+ "version": "1.9.0"
+ },
+ {
+ "type": "dev.teogor.winds.api.model.Dependency",
+ "implementationType": "dokkaGfmPlugin",
+ "group": "org.jetbrains.dokka",
+ "artifact": "dokka-base",
+ "version": "1.9.0"
+ },
+ {
+ "type": "dev.teogor.winds.api.model.Dependency",
+ "implementationType": "dokkaHtmlPartialPlugin",
+ "group": "org.jetbrains.dokka",
+ "artifact": "analysis-kotlin-descriptors",
+ "version": "1.9.0"
+ },
+ {
+ "type": "dev.teogor.winds.api.model.Dependency",
+ "implementationType": "dokkaHtmlPartialPlugin",
+ "group": "org.jetbrains.dokka",
+ "artifact": "dokka-base",
+ "version": "1.9.0"
+ },
+ {
+ "type": "dev.teogor.winds.api.model.Dependency",
+ "implementationType": "dokkaHtmlPlugin",
+ "group": "org.jetbrains.dokka",
+ "artifact": "analysis-kotlin-descriptors",
+ "version": "1.9.0"
+ },
+ {
+ "type": "dev.teogor.winds.api.model.Dependency",
+ "implementationType": "dokkaHtmlPlugin",
+ "group": "org.jetbrains.dokka",
+ "artifact": "dokka-base",
+ "version": "1.9.0"
+ },
+ {
+ "type": "dev.teogor.winds.api.model.Dependency",
+ "implementationType": "dokkaJavadocPartialPlugin",
+ "group": "org.jetbrains.dokka",
+ "artifact": "analysis-kotlin-descriptors",
+ "version": "1.9.0"
+ },
+ {
+ "type": "dev.teogor.winds.api.model.Dependency",
+ "implementationType": "dokkaJavadocPartialPlugin",
+ "group": "org.jetbrains.dokka",
+ "artifact": "dokka-base",
+ "version": "1.9.0"
+ },
+ {
+ "type": "dev.teogor.winds.api.model.Dependency",
+ "implementationType": "dokkaJavadocPlugin",
+ "group": "org.jetbrains.dokka",
+ "artifact": "analysis-kotlin-descriptors",
+ "version": "1.9.0"
+ },
+ {
+ "type": "dev.teogor.winds.api.model.Dependency",
+ "implementationType": "dokkaJavadocPlugin",
+ "group": "org.jetbrains.dokka",
+ "artifact": "dokka-base",
+ "version": "1.9.0"
+ },
+ {
+ "type": "dev.teogor.winds.api.model.Dependency",
+ "implementationType": "dokkaJekyllPartialPlugin",
+ "group": "org.jetbrains.dokka",
+ "artifact": "analysis-kotlin-descriptors",
+ "version": "1.9.0"
+ },
+ {
+ "type": "dev.teogor.winds.api.model.Dependency",
+ "implementationType": "dokkaJekyllPartialPlugin",
+ "group": "org.jetbrains.dokka",
+ "artifact": "dokka-base",
+ "version": "1.9.0"
+ },
+ {
+ "type": "dev.teogor.winds.api.model.Dependency",
+ "implementationType": "dokkaJekyllPlugin",
+ "group": "org.jetbrains.dokka",
+ "artifact": "analysis-kotlin-descriptors",
+ "version": "1.9.0"
+ },
+ {
+ "type": "dev.teogor.winds.api.model.Dependency",
+ "implementationType": "dokkaJekyllPlugin",
+ "group": "org.jetbrains.dokka",
+ "artifact": "dokka-base",
+ "version": "1.9.0"
+ },
+ {
+ "type": "dev.teogor.winds.api.model.Dependency",
+ "implementationType": "embeddedKotlin",
+ "group": "org.jetbrains.kotlin",
+ "artifact": "kotlin-stdlib-jdk8",
+ "version": "1.8.20"
+ },
+ {
+ "type": "dev.teogor.winds.api.model.Dependency",
+ "implementationType": "embeddedKotlin",
+ "group": "org.jetbrains.kotlin",
+ "artifact": "kotlin-reflect",
+ "version": "1.8.20"
+ },
+ {
+ "type": "dev.teogor.winds.api.model.Dependency",
+ "implementationType": "kotlinCompilerPluginClasspathMain",
+ "group": "org.jetbrains.kotlin",
+ "artifact": "kotlin-scripting-compiler-embeddable",
+ "version": "1.9.10"
+ },
+ {
+ "type": "dev.teogor.winds.api.model.Dependency",
+ "implementationType": "kotlinCompilerPluginClasspathMain",
+ "group": "org.jetbrains.kotlin",
+ "artifact": "kotlin-sam-with-receiver",
+ "version": "1.9.10"
+ },
+ {
+ "type": "dev.teogor.winds.api.model.Dependency",
+ "implementationType": "kotlinCompilerPluginClasspathMain",
+ "group": "org.jetbrains.kotlin",
+ "artifact": "kotlin-assignment",
+ "version": "1.9.10"
+ },
+ {
+ "type": "dev.teogor.winds.api.model.Dependency",
+ "implementationType": "kotlinCompilerPluginClasspathTest",
+ "group": "org.jetbrains.kotlin",
+ "artifact": "kotlin-scripting-compiler-embeddable",
+ "version": "1.9.10"
+ },
+ {
+ "type": "dev.teogor.winds.api.model.Dependency",
+ "implementationType": "kotlinCompilerPluginClasspathTest",
+ "group": "org.jetbrains.kotlin",
+ "artifact": "kotlin-sam-with-receiver",
+ "version": "1.9.10"
+ },
+ {
+ "type": "dev.teogor.winds.api.model.Dependency",
+ "implementationType": "kotlinCompilerPluginClasspathTest",
+ "group": "org.jetbrains.kotlin",
+ "artifact": "kotlin-assignment",
+ "version": "1.9.10"
+ }
+ ],
+ "canBePublished": true,
+ "names": [
+ "Xenoglot",
+ "Core"
+ ]
+ }
+]
\ No newline at end of file
diff --git a/.resources/bom/versions.json b/.resources/bom/versions.json
new file mode 100644
index 0000000..45f2028
--- /dev/null
+++ b/.resources/bom/versions.json
@@ -0,0 +1,12 @@
+[
+ {
+ "version": {
+ "major": 1,
+ "minor": 0,
+ "patch": 0,
+ "flag": "Alpha",
+ "versionQualifier": 1
+ },
+ "date": 1700339293
+ }
+]
\ No newline at end of file
diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md
new file mode 100644
index 0000000..82d6e58
--- /dev/null
+++ b/CODE_OF_CONDUCT.md
@@ -0,0 +1,51 @@
+# Teogor's Open Source Community Guidelines
+
+In the realm of Teogor, the creativity and collaboration of open source contributors are
+wholeheartedly celebrated. Teogor, as an individual, values the diverse tapestry of skills,
+experiences, cultures, and perspectives that weave into the projects and communities.
+
+All Teogor open source projects and communities adhere to a principle of inclusivity, embodying a
+commitment to treating each individual respectfully, regardless of gender identity, expression,
+sexual orientation, disabilities, neurodiversity, physical appearance, body size, ethnicity,
+nationality, race, age, religion, or analogous personal attributes.
+
+Teogor, as a person, places the highest importance on respectful behavior.
+
+Respectful behavior embodies:
+
+- Being considerate, kind, constructive, and supportive.
+- Refraining from engaging in derogatory, discriminatory, harassing, hateful, sexualized, or
+ physically threatening conduct, speech, or imagery.
+- Choosing not to participate in unwelcome physical contact.
+
+Certain Teogor open source projects may adopt a specific project code of conduct that outlines
+further expectations for participants. In most cases, these projects will align with the
+Teogor-developed [Contributor Covenant][].
+
+[Contributor Covenant]: https://source.teogor.dev/docs/releasing/template/CODE_OF_CONDUCT/
+
+## Seeking Peaceful Resolution
+
+In the realm of disagreements, Teogor recognizes that not all conflict is detrimental. Positive
+outcomes often arise from healthy debates and disagreements. However, as a persona, Teogor insists
+that disrespectful behavior is never acceptable.
+
+When encountering disrespectful behavior, Teogor encourages addressing the issue directly with the
+individuals involved. Many matters can be swiftly resolved, granting participants control over their
+dispute's outcome. If resolution is unattainable or if behavior is menacing or harassing, reporting
+the incident is encouraged. The environment strives to provide a welcoming and secure space for
+participants.
+
+## Reporting Concerns
+
+For specific Teogor open source projects, a designated Project Steward, embodying the persona of
+Teogor, will handle reports of code of conduct violations. In instances where a project lacks a
+Project Steward, issues can be reported by reaching out to open-source@teogor.dev.
+
+Each complaint will be meticulously investigated, although direct responses may not always be
+provided. Discretion will be exercised in determining how to address reported incidents, ranging
+from no action to permanent expulsion from the project and related spaces. Accused parties will be
+informed of the report's details, affording them an opportunity to discuss it prior to any action.
+Reporter identities will be kept confidential when sharing report specifics with the accused. For
+situations involving potential harm, such as ongoing harassment or threats, immediate action may be
+taken without advance notice.
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
new file mode 100644
index 0000000..ff4bebe
--- /dev/null
+++ b/CONTRIBUTING.md
@@ -0,0 +1,36 @@
+# How to become a contributor and submit your own code
+
+## Contributor License Agreements
+
+We'd love to accept your sample apps and patches! Before we can take them, we
+have to jump a couple of legal hurdles.
+
+Please fill out either the individual or corporate Contributor License Agreement
+(CLA).
+
+* If you are an individual writing original source code and you're sure you
+ own the intellectual property, then you'll need to sign
+ an [individual CLA](https://developers.google.com/open-source/cla/individual).
+* If you work for a company that wants to allow you to contribute your work,
+ then you'll need to sign
+ a [corporate CLA](https://developers.google.com/open-source/cla/corporate).
+
+Follow either of the two links above to access the appropriate CLA and
+instructions for how to sign and return it. Once we receive it, we'll be able to
+accept your pull requests.
+
+## Contributing A Patch
+
+1. Submit an issue describing your proposed change to the repo in question.
+1. The repo owner will respond to your issue promptly.
+1. If your proposed change is accepted, and you haven't already done so, sign a
+ Contributor License Agreement (see details above).
+1. Fork the desired repo, develop and test your code changes.
+1. Ensure that your code adheres to the existing style in the sample to which
+ you are contributing. Refer to the
+ [Google Cloud Platform Samples Style Guide](https://github.com/GoogleCloudPlatform/Template/wiki/style.html)
+ for the
+ recommended coding standards for this organization.
+1. Ensure that your code has an appropriate set of unit tests which all pass.
+1. Submit a pull request.
+
diff --git a/SECURITY.md b/SECURITY.md
new file mode 100644
index 0000000..7429714
--- /dev/null
+++ b/SECURITY.md
@@ -0,0 +1,41 @@
+# Security Policy
+
+## Supported Versions
+
+Please refer to this table to see which versions of the project are currently receiving security
+updates:
+
+| Version | Supported |
+|---------|--------------------|
+| Release | :white_check_mark: |
+| ALPHA | :x: |
+| BETA | :x: |
+
+
+## Reporting a Vulnerability
+
+If you discover a security vulnerability within this project, please follow these steps:
+
+1. **Do Not Disclose Publicly:** Do not publicly disclose the vulnerability until it has been
+ addressed and a security release is available.
+
+2. **Contact Us:** Report the vulnerability to us by sending an email
+ to [open-source@teogor.dev](mailto:open-source@teogor.dev). You can encrypt sensitive information
+ using our [PGP key](https://source.teogor.dev/security/pgp-key) to ensure the confidentiality of the report.
+
+3. **Provide Details:** Please provide us with detailed information about the vulnerability,
+ including steps to reproduce, affected versions, and potential impact.
+
+4. **Acknowledgment:** We will acknowledge your report within 48 hours to confirm that we have
+ received it and are investigating.
+
+5. **Resolution:** Our security team will work to validate and address the vulnerability. We will
+ keep you informed about our progress and expected timelines for resolution.
+
+6. **Disclosure:** Once the vulnerability is fixed and a release is available, we will work with you
+ to determine an appropriate timeline for public disclosure. We appreciate your patience during
+ this process.
+
+Thank you for helping us improve the security of our project.
+
+*[PGP key](https://source.teogor.dev/security/pgp-key)*
diff --git a/android/.gitignore b/android/.gitignore
new file mode 100644
index 0000000..42afabf
--- /dev/null
+++ b/android/.gitignore
@@ -0,0 +1 @@
+/build
\ No newline at end of file
diff --git a/android/api/android.api b/android/api/android.api
new file mode 100644
index 0000000..52d29a6
--- /dev/null
+++ b/android/api/android.api
@@ -0,0 +1,6 @@
+public final class dev/teogor/xenoglot/android/LanguageUtils {
+ public static final field INSTANCE Ldev/teogor/xenoglot/android/LanguageUtils;
+ public final fun setAppLanguage (Ljava/lang/String;)V
+ public final fun setAppLanguage (Ljava/util/Locale;)V
+}
+
diff --git a/android/build.gradle.kts b/android/build.gradle.kts
new file mode 100644
index 0000000..ae2af25
--- /dev/null
+++ b/android/build.gradle.kts
@@ -0,0 +1,46 @@
+/*
+ * Copyright 2023 teogor (Teodor Grigor)
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+plugins {
+ alias(libs.plugins.android.library)
+ alias(libs.plugins.kotlin.android)
+ id("dev.teogor.winds")
+}
+
+kotlin {
+ jvmToolchain(17)
+}
+
+android {
+ namespace = "dev.teogor.querent.common"
+
+ compileSdk = 34
+
+ defaultConfig {
+ minSdk = 21
+ }
+}
+
+dependencies {
+ api(libs.androidx.appcompat)
+}
+
+winds {
+ mavenPublish {
+ displayName = "Android"
+ name = "android"
+ }
+}
diff --git a/android/src/main/AndroidManifest.xml b/android/src/main/AndroidManifest.xml
new file mode 100644
index 0000000..49392cb
--- /dev/null
+++ b/android/src/main/AndroidManifest.xml
@@ -0,0 +1,32 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/android/src/main/kotlin/dev/teogor/xenoglot/android/LanguageUtils.kt b/android/src/main/kotlin/dev/teogor/xenoglot/android/LanguageUtils.kt
new file mode 100644
index 0000000..ab12b60
--- /dev/null
+++ b/android/src/main/kotlin/dev/teogor/xenoglot/android/LanguageUtils.kt
@@ -0,0 +1,48 @@
+/*
+ * Copyright 2023 teogor (Teodor Grigor)
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package dev.teogor.xenoglot.android
+
+import androidx.appcompat.app.AppCompatDelegate
+import androidx.core.os.LocaleListCompat
+import java.util.Locale
+
+/**
+ * Object that handles language settings and provides methods to change
+ * the application language.
+ */
+object LanguageUtils {
+
+ /**
+ * Changes the application language based on the specified locale.
+ *
+ * @param locale The locale to set as the application language.
+ */
+ fun setAppLanguage(locale: Locale) {
+ setAppLanguage(locale.language)
+ }
+
+ /**
+ * Changes the application language based on the specified language tag.
+ *
+ * @param languageTag The language tag to set as the application language.
+ */
+ fun setAppLanguage(languageTag: String) {
+ AppCompatDelegate.setApplicationLocales(
+ LocaleListCompat.forLanguageTags(languageTag),
+ )
+ }
+}
diff --git a/bom/.gitignore b/bom/.gitignore
new file mode 100644
index 0000000..42afabf
--- /dev/null
+++ b/bom/.gitignore
@@ -0,0 +1 @@
+/build
\ No newline at end of file
diff --git a/bom/build.gradle.kts b/bom/build.gradle.kts
new file mode 100644
index 0000000..0981989
--- /dev/null
+++ b/bom/build.gradle.kts
@@ -0,0 +1,27 @@
+/*
+ * Copyright 2023 teogor (Teodor Grigor)
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+plugins {
+ id("dev.teogor.winds")
+}
+
+winds {
+ mavenPublish {
+ displayName = "BoM"
+ name = "bom"
+
+ defineBoM()
+ }
+}
diff --git a/build.gradle.kts b/build.gradle.kts
new file mode 100644
index 0000000..6dca5b4
--- /dev/null
+++ b/build.gradle.kts
@@ -0,0 +1,198 @@
+import com.vanniktech.maven.publish.SonatypeHost
+import dev.teogor.winds.api.MavenPublish
+import dev.teogor.winds.api.getValue
+import dev.teogor.winds.api.model.Developer
+import dev.teogor.winds.api.model.LicenseType
+import dev.teogor.winds.api.model.Version
+import dev.teogor.winds.api.provider.Scm
+import dev.teogor.winds.gradle.utils.afterWindsPluginConfiguration
+import dev.teogor.winds.gradle.utils.attachTo
+import org.jetbrains.dokka.gradle.DokkaMultiModuleTask
+import org.jetbrains.dokka.gradle.DokkaPlugin
+import org.jetbrains.dokka.gradle.DokkaTask
+
+buildscript {
+ repositories {
+ google()
+ mavenCentral()
+ }
+}
+
+plugins {
+ // Kotlin Suite
+ alias(libs.plugins.kotlin.jvm) apply false
+ alias(libs.plugins.kotlin.multiplatform) apply false
+ alias(libs.plugins.kotlin.android) apply false
+
+ // Android Tools
+ alias(libs.plugins.android.library) apply false
+
+ alias(libs.plugins.winds)
+ alias(libs.plugins.vanniktech.maven)
+ alias(libs.plugins.dokka)
+ alias(libs.plugins.spotless)
+ alias(libs.plugins.api.validator)
+}
+
+winds {
+ buildFeatures {
+ mavenPublish = true
+
+ docsGenerator = true
+ }
+
+ mavenPublish {
+ displayName = "Xenoglot"
+ name = "xenoglot"
+
+ canBePublished = false
+
+ description =
+ "\uD83C\uDF0D Xenoglot seamlessly masters languages and locales, empowering developers to integrate multilingual capabilities into their applications with ease."
+
+ groupId = "dev.teogor.xenoglot"
+ artifactIdElements = 1
+
+ version = Version(
+ major = 1,
+ minor = 0,
+ patch = 0,
+ ).setAlphaRelease(1)
+
+ // TODO winds
+ // required by dokka
+ project.version = version!!.toString()
+
+ inceptionYear = 2023
+
+ sourceControlManagement(
+ Scm.Git(
+ owner = "teogor",
+ repo = "xenoglot",
+ ),
+ )
+
+ addLicense(LicenseType.APACHE_2_0)
+
+ addDeveloper(TeogorDeveloper())
+ }
+
+ docsGenerator {
+ name = "Xenoglot"
+ identifier = "xenoglot"
+ alertOnDependentModules = true
+ }
+}
+
+afterWindsPluginConfiguration { winds ->
+ val mavenPublish: MavenPublish by winds
+ if (mavenPublish.canBePublished) {
+ mavenPublishing {
+ publishToMavenCentral(SonatypeHost.S01)
+ signAllPublications()
+
+ @Suppress("UnstableApiUsage")
+ pom {
+ coordinates(
+ groupId = mavenPublish.groupId!!,
+ artifactId = mavenPublish.artifactId!!,
+ version = mavenPublish.version!!.toString(),
+ )
+ mavenPublish attachTo this
+ }
+ }
+ }
+}
+
+data class TeogorDeveloper(
+ override val id: String = "teogor",
+ override val name: String = "Teodor Grigor",
+ override val email: String = "open-source@teogor.dev",
+ override val url: String = "https://teogor.dev",
+ override val roles: List = listOf("Code Owner", "Developer", "Designer", "Maintainer"),
+ override val timezone: String = "UTC+2",
+ override val organization: String = "Teogor",
+ override val organizationUrl: String = "https://github.com/teogor",
+) : Developer
+
+val ktlintVersion = "0.50.0"
+
+val excludedProjects = listOf(
+ project.name,
+)
+
+subprojects {
+ if (!excludedProjects.contains(this.name)) {
+ apply()
+ configure {
+ kotlin {
+ target("**/*.kt")
+ targetExclude("**/build/**/*.kt")
+ ktlint(ktlintVersion)
+ .userData(
+ mapOf(
+ "ktlint_code_style" to "official",
+ "ij_kotlin_allow_trailing_comma" to "true",
+ // These rules were introduced in ktlint 0.46.0 and should not be
+ // enabled without further discussion. They are disabled for now.
+ // See: https://github.com/pinterest/ktlint/releases/tag/0.46.0
+ "disabled_rules" to
+ "filename," +
+ "annotation,annotation-spacing," +
+ "argument-list-wrapping," +
+ "double-colon-spacing," +
+ "enum-entry-name-case," +
+ "multiline-if-else," +
+ "no-empty-first-line-in-method-block," +
+ "package-name," +
+ "trailing-comma," +
+ "spacing-around-angle-brackets," +
+ "spacing-between-declarations-with-annotations," +
+ "spacing-between-declarations-with-comments," +
+ "unary-op-spacing," +
+ "no-trailing-spaces," +
+ "no-wildcard-imports," +
+ "max-line-length",
+ ),
+ )
+ licenseHeaderFile(rootProject.file("spotless/copyright.kt"))
+ trimTrailingWhitespace()
+ endWithNewline()
+ }
+ format("kts") {
+ target("**/*.kts")
+ targetExclude("**/build/**/*.kts")
+ // Look for the first line that doesn't have a block comment (assumed to be the license)
+ licenseHeaderFile(rootProject.file("spotless/copyright.kts"), "(^(?![\\/ ]\\*).*$)")
+ }
+ format("xml") {
+ target("**/*.xml")
+ targetExclude("**/build/**/*.xml")
+ // Look for the first XML tag that isn't a comment (