├── .gitattributes
├── .github
└── workflows
│ ├── codeql-analysis.yml
│ ├── gradle.yml
│ └── release.yml
├── .gitignore
├── CODE_OF_CONDUCT.md
├── CONTRIBUTING.md
├── LICENSE
├── README.md
└── TransifexNativeSDK
├── .gitignore
├── app
├── .gitignore
├── build.gradle
├── proguard-rules.pro
└── src
│ └── main
│ ├── AndroidManifest.xml
│ ├── assets
│ └── example.html
│ ├── java
│ └── com
│ │ └── transifex
│ │ └── myapplication
│ │ ├── MainActivity.java
│ │ ├── MyApplication.java
│ │ ├── SimpleIntentService.java
│ │ └── WebViewActivity.java
│ └── res
│ ├── drawable-v24
│ └── ic_launcher_foreground.xml
│ ├── drawable
│ ├── ic_launcher_background.xml
│ ├── state_list_icon.xml
│ └── vector_android.xml
│ ├── layout
│ ├── activity_main.xml
│ └── activity_web_view.xml
│ ├── menu
│ └── main_menu.xml
│ ├── mipmap-anydpi-v26
│ ├── ic_launcher.xml
│ └── ic_launcher_round.xml
│ ├── mipmap-hdpi
│ ├── ic_launcher.png
│ └── ic_launcher_round.png
│ ├── mipmap-mdpi
│ ├── ic_launcher.png
│ └── ic_launcher_round.png
│ ├── mipmap-xhdpi
│ ├── ic_launcher.png
│ └── ic_launcher_round.png
│ ├── mipmap-xxhdpi
│ ├── ic_launcher.png
│ └── ic_launcher_round.png
│ ├── mipmap-xxxhdpi
│ ├── ic_launcher.png
│ └── ic_launcher_round.png
│ ├── values-el
│ └── strings.xml
│ ├── values-night
│ └── styles.xml
│ └── values
│ ├── attrs.xml
│ ├── colors.xml
│ ├── strings.xml
│ └── styles.xml
├── build.gradle
├── clitool
├── .gitignore
├── build.gradle
├── src
│ ├── main
│ │ └── java
│ │ │ └── com
│ │ │ └── transifex
│ │ │ └── clitool
│ │ │ ├── MainClass.java
│ │ │ └── StringXMLConverter.java
│ └── test
│ │ └── java
│ │ └── com
│ │ └── transifex
│ │ └── clitool
│ │ ├── MainClassTest.java
│ │ └── StringXMLConverterTest.java
└── testFiles
│ ├── strings-test-at.xml
│ ├── strings-test-backslash.xml
│ ├── strings-test-htmlentities.xml
│ ├── strings-test-insidedoublequotes.xml
│ ├── strings-test-new-line-space-tab.xml
│ ├── strings-test-new-line.xml
│ ├── strings-test-quote.xml
│ ├── strings-test-space.xml
│ ├── strings-test-tab.xml
│ └── strings-test-unicode.xml
├── common
├── .gitignore
├── build.gradle
└── src
│ ├── main
│ └── java
│ │ └── com
│ │ └── transifex
│ │ └── common
│ │ ├── CDSHandler.java
│ │ ├── LocaleData.java
│ │ ├── Plurals.java
│ │ ├── TranslationMapStorage.java
│ │ ├── TranslationsDownloader.java
│ │ └── Utils.java
│ ├── test
│ └── java
│ │ └── com
│ │ └── transifex
│ │ └── common
│ │ ├── CDSHandlerTest.java
│ │ ├── LocaleDataTest.java
│ │ ├── PluralsTest.java
│ │ ├── TranslationMapStorageTest.java
│ │ └── TranslationsDownloaderTest.java
│ └── testFixtures
│ └── java
│ └── com
│ └── transifex
│ └── common
│ ├── CDSMockHelper.java
│ ├── StringTestData.java
│ └── TempDirHelper.java
├── doc
└── readme.html
├── gradle.properties
├── gradle
└── wrapper
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
├── gradlew
├── gradlew.bat
├── publish-helper.gradle
├── settings.gradle
└── txsdk
├── .gitignore
├── build.gradle
├── consumer-rules.pro
├── proguard-rules.pro
└── src
├── androidTest
├── assets
│ ├── test_fileInsteadLocaleDir
│ │ └── dummy_file
│ ├── test_normal
│ │ ├── el
│ │ │ └── strings.txt
│ │ └── es
│ │ │ └── strings.txt
│ ├── test_oneLocaleFileIsMissing
│ │ ├── el
│ │ │ └── strings.txt
│ │ └── es
│ │ │ └── wrong_file
│ ├── test_oneLocaleHasInvalidJson
│ │ ├── el
│ │ │ └── strings.txt
│ │ └── es
│ │ │ └── strings.txt
│ └── txnative
│ │ ├── el
│ │ └── txstrings.json
│ │ └── es
│ │ └── txstrings.json
└── java
│ └── com
│ └── transifex
│ └── txnative
│ ├── PluralsTest.java
│ ├── TranslationMapStorageAndroidTest.java
│ ├── TranslationMapStorageTest.java
│ └── cache
│ ├── TxDiskTranslationsProviderAssetsTest.java
│ └── TxStandardCacheTest.java
├── main
├── AndroidManifest.xml
├── java
│ └── com
│ │ └── transifex
│ │ └── txnative
│ │ ├── CDSHandlerAndroid.java
│ │ ├── LocaleState.java
│ │ ├── NativeCore.java
│ │ ├── TranslationMapStorageAndroid.java
│ │ ├── TxInterceptor.java
│ │ ├── TxNative.java
│ │ ├── TxResources.java
│ │ ├── Utils.java
│ │ ├── activity
│ │ ├── TxBaseActivity.java
│ │ └── TxBaseAppCompatActivity.java
│ │ ├── cache
│ │ ├── TxCache.java
│ │ ├── TxDecoratorCache.java
│ │ ├── TxDiskTranslationsProvider.java
│ │ ├── TxFileOutputCacheDecorator.java
│ │ ├── TxMemoryCache.java
│ │ ├── TxProviderBasedCache.java
│ │ ├── TxReadonlyCacheDecorator.java
│ │ ├── TxStandardCache.java
│ │ ├── TxTranslationsProvider.java
│ │ └── TxUpdateFilterCache.java
│ │ ├── missingpolicy
│ │ ├── AndroidMissingPolicy.java
│ │ ├── CompositeMissingPolicy.java
│ │ ├── MissingPolicy.java
│ │ ├── PseudoTranslationPolicy.java
│ │ ├── SourceStringPolicy.java
│ │ └── WrappedStringPolicy.java
│ │ ├── transformers
│ │ ├── SupportToolbarTransformer.java
│ │ ├── TextInputLayoutTransformer.java
│ │ ├── TextViewTransformer.java
│ │ ├── ToolbarTransformer.java
│ │ └── ViewTransformer.java
│ │ └── wrappers
│ │ ├── ResourcesWrapper.java
│ │ ├── TxContextWrapper.java
│ │ ├── TxContextWrappingDelegate2.kt
│ │ ├── TxContextWrappingDelegateJava2.java
│ │ └── VectorEnabledTintResourcesWrapper.java
└── res
│ └── values
│ └── strings.xml
└── test
├── java
└── com
│ └── transifex
│ └── txnative
│ ├── LocaleStateTest.java
│ ├── NativeCoreTest.java
│ ├── TxResourcesTest.java
│ ├── cache
│ ├── TxDecoratorCacheTest.java
│ ├── TxDiskTranslationsProviderFilesTest.java
│ ├── TxFileOutputCacheDecoratorTest.java
│ ├── TxMemoryCacheTest.java
│ ├── TxProviderBasedCacheTest.java
│ ├── TxReadonlyCacheDecoratorTest.java
│ └── TxUpdateFilterCacheTest.java
│ └── missingpolicy
│ ├── AndroidMissingPolicyTest.java
│ ├── CompositeMissingPolicyTest.java
│ ├── PseudoTranslationPolicyTest.java
│ ├── SourceStringPolicyTest.java
│ └── WrappedStringPolicyTest.java
└── res
├── values-el
└── strings.xml
├── values-es
└── strings.xml
└── values
└── strings.xml
/.gitattributes:
--------------------------------------------------------------------------------
1 | * text=auto eol=lf
2 | *.jar binary
3 | *.png binary
4 | *.jpg binary
5 | *.keystore binary
6 | *.exe binary
--------------------------------------------------------------------------------
/.github/workflows/codeql-analysis.yml:
--------------------------------------------------------------------------------
1 | # For most projects, this workflow file will not need changing; you simply need
2 | # to commit it to your repository.
3 | #
4 | # You may wish to alter this file to override the set of languages analyzed,
5 | # or to provide custom queries or build logic.
6 | #
7 | # ******** NOTE ********
8 | # We have attempted to detect the languages in your repository. Please check
9 | # the `language` matrix defined below to confirm you have the correct set of
10 | # supported CodeQL languages.
11 | #
12 | name: "CodeQL"
13 |
14 | on:
15 | push:
16 | branches: [ "devel" ]
17 | pull_request:
18 | # The branches below must be a subset of the branches above
19 | branches: [ "devel" ]
20 | schedule:
21 | - cron: '25 12 * * 4'
22 |
23 | jobs:
24 | analyze:
25 | name: Analyze
26 | runs-on: ubuntu-latest
27 | permissions:
28 | actions: read
29 | contents: read
30 | security-events: write
31 |
32 | strategy:
33 | fail-fast: false
34 | matrix:
35 | language: [ 'java' ]
36 | # CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python', 'ruby' ]
37 | # Learn more about CodeQL language support at https://aka.ms/codeql-docs/language-support
38 |
39 | steps:
40 | - name: Checkout repository
41 | uses: actions/checkout@v3
42 |
43 | # Initializes the CodeQL tools for scanning.
44 | - name: Initialize CodeQL
45 | uses: github/codeql-action/init@v2
46 | with:
47 | languages: ${{ matrix.language }}
48 | # If you wish to specify custom queries, you can do so here or in a config file.
49 | # By default, queries listed here will override any specified in a config file.
50 | # Prefix the list here with "+" to use these queries and those in the config file.
51 |
52 | # Details on CodeQL's query packs refer to : https://docs.github.com/en/code-security/code-scanning/automatically-scanning-your-code-for-vulnerabilities-and-errors/configuring-code-scanning#using-queries-in-ql-packs
53 | # queries: security-extended,security-and-quality
54 |
55 |
56 | # Autobuild attempts to build any compiled languages (C/C++, C#, or Java).
57 | # If this step fails, then you should remove it and run the build manually (see below)
58 | - name: Autobuild
59 | uses: github/codeql-action/autobuild@v2
60 |
61 | # ℹ️ Command-line programs to run using the OS shell.
62 | # 📚 See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun
63 |
64 | # If the Autobuild fails above, remove it and uncomment the following three lines.
65 | # modify them (or add more) to build your code if your project, please refer to the EXAMPLE below for guidance.
66 |
67 | # - run: |
68 | # echo "Run, Build Application using script"
69 | # ./location_of_script_within_repo/buildscript.sh
70 |
71 | - name: Perform CodeQL Analysis
72 | uses: github/codeql-action/analyze@v2
73 |
--------------------------------------------------------------------------------
/.github/workflows/gradle.yml:
--------------------------------------------------------------------------------
1 | # This workflow will build a Java project with Gradle
2 | # For more information see: https://help.github.com/actions/language-and-framework-guides/building-and-testing-java-with-gradle
3 |
4 | name: CI
5 |
6 | on:
7 | push:
8 | branches: [ devel ]
9 | pull_request:
10 | branches: [ devel ]
11 |
12 | jobs:
13 | test:
14 |
15 | runs-on: ubuntu-latest
16 |
17 | steps:
18 | - uses: actions/checkout@v4
19 | - name: Set up JDK 17
20 | uses: actions/setup-java@v3
21 | with:
22 | java-version: '17'
23 | distribution: 'temurin'
24 | architecture: x64
25 | - name: Set up Gradle
26 | uses: gradle/gradle-build-action@v2
27 | with:
28 | # Only write to the cache for builds on the 'main' and 'devel' branches. (Default is 'main' only.)
29 | # Builds on other branches will only read existing entries from the cache.
30 | cache-read-only: ${{ github.ref != 'refs/heads/main' && github.ref != 'refs/heads/devel' }}
31 | - name: Grant execute permission for gradlew
32 | working-directory: ./TransifexNativeSDK
33 | run: chmod +x gradlew
34 | - name: Unit Test
35 | working-directory: ./TransifexNativeSDK
36 | run: ./gradlew test
37 | - name: Upload test reports
38 | if: always()
39 | uses: actions/upload-artifact@v3
40 | with:
41 | name: unitTestReports
42 | path: |
43 | TransifexNativeSDK/clitool/build/reports/tests/test/
44 | TransifexNativeSDK/common/build/reports/tests/test/
45 | TransifexNativeSDK/txsdk/build/reports/tests/
46 |
--------------------------------------------------------------------------------
/.github/workflows/release.yml:
--------------------------------------------------------------------------------
1 | # This workflow is triggered by new releases and performes these steps:
2 | # * it builds and uploads the CLI tool as an artifact to the release that triggered it
3 | # * it generates and deploys the documentation to Github pages and it builds and
4 | # publishes the sdk to Maven.
5 | # If the tag ends with "SNAPSHOT", only publishing to Maven takes place.
6 |
7 | name: Release
8 |
9 | on:
10 | release:
11 | types: [published]
12 |
13 |
14 | jobs:
15 | build:
16 |
17 | runs-on: ubuntu-latest
18 |
19 | steps:
20 | - uses: actions/checkout@v4
21 | - name: Set up JDK 17
22 | uses: actions/setup-java@v3
23 | with:
24 | java-version: '17'
25 | distribution: 'temurin'
26 | architecture: x64
27 | - name: Set up Gradle
28 | uses: gradle/gradle-build-action@v2
29 | with:
30 | # Only write to the cache for builds on the 'main' and 'devel' branches. (Default is 'main' only.)
31 | # Builds on other branches will only read existing entries from the cache.
32 | cache-read-only: ${{ github.ref != 'refs/heads/main' && github.ref != 'refs/heads/devel' }}
33 | - name: Grant execute permission for gradlew
34 | working-directory: ./TransifexNativeSDK
35 | run: chmod +x gradlew
36 |
37 | - name: Assemble clitool jar
38 | if: ${{ !endsWith(github.event.release.tag_name, 'SNAPSHOT') }}
39 | working-directory: ./TransifexNativeSDK
40 | run: ./gradlew clitool:assemble
41 | - name : Upload clitool's jar to release
42 | if: ${{ !endsWith(github.event.release.tag_name, 'SNAPSHOT') }}
43 | uses: actions/upload-release-asset@v1.0.2
44 | env:
45 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
46 | with:
47 | upload_url: ${{ github.event.release.upload_url }}
48 | asset_name: 'transifex.jar'
49 | asset_path: TransifexNativeSDK/clitool/build/libs/transifex.jar
50 | asset_content_type: application/java-archive
51 |
52 | - name: Generate Javadoc
53 | if: ${{ !endsWith(github.event.release.tag_name, 'SNAPSHOT') }}
54 | working-directory: ./TransifexNativeSDK
55 | run: ./gradlew aggregatedJavadoc
56 | - name: Deploy to GitHub Pages
57 | if: ${{ !endsWith(github.event.release.tag_name, 'SNAPSHOT') }}
58 | uses: JamesIves/github-pages-deploy-action@4.1.1
59 | with:
60 | branch: documentation
61 | folder: TransifexNativeSDK/build/docs/javadoc
62 | target-folder: docs
63 |
64 | - name: Build and publish SDK to Maven
65 | working-directory: ./TransifexNativeSDK
66 | env:
67 | SIGNING_KEY_ID: ${{ secrets.SIGNING_KEY_ID }}
68 | SIGNING_PASSWORD: ${{ secrets.SIGNING_PASSWORD }}
69 | OSSRH_USERNAME: ${{ secrets.OSSRH_USERNAME }}
70 | OSSRH_PASSWORD: ${{ secrets.OSSRH_PASSWORD }}
71 | PGP_KEY_CONTENTS: ${{ secrets.PGP_KEY_CONTENTS }}
72 | run: |
73 | ./gradlew publishReleasePublicationToSonatypeRepository closeAndReleaseSonatypeStagingRepository
74 | ./gradlew cleanTmp
75 |
76 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | ### Android template
2 | # Built application files
3 | *.apk
4 | *.ap_
5 |
6 | # Files for the Dalvik VM
7 | *.dex
8 |
9 | # Java class files
10 | *.class
11 |
12 | # lint files
13 | lint.xml
14 |
15 | # proguard files
16 | seeds.txt
17 | unused.txt
18 | mapping.txt
19 |
20 | # Gradle files
21 | .gradle/
22 | build/
23 | !gradle-wrapper.jar
24 |
25 | # generated files
26 | bin/
27 | gen/
28 |
29 | # Android Studio
30 | **/.idea/*
31 | !**/.idea/scopes/
32 | *.ipr
33 | *.iws
34 | *.iml
35 |
36 | # Local configuration file (sdk path, etc)
37 | local.properties
38 |
39 | # Log Files
40 | *.log
--------------------------------------------------------------------------------
/CODE_OF_CONDUCT.md:
--------------------------------------------------------------------------------
1 | # Contributor Covenant Code of Conduct
2 |
3 | ## Our Pledge
4 |
5 | In the interest of fostering an open and welcoming environment, we as
6 | contributors and maintainers pledge to making participation in our project and
7 | our community a harassment-free experience for everyone, regardless of age, body
8 | size, disability, ethnicity, sex characteristics, gender identity and expression,
9 | level of experience, education, socio-economic status, nationality, personal
10 | appearance, race, religion, or sexual identity and orientation.
11 |
12 | ## Our Standards
13 |
14 | Examples of behavior that contributes to creating a positive environment
15 | include:
16 |
17 | * Using welcoming and inclusive language
18 | * Being respectful of differing viewpoints and experiences
19 | * Gracefully accepting constructive criticism
20 | * Focusing on what is best for the community
21 | * Showing empathy towards other community members
22 |
23 | Examples of unacceptable behavior by participants include:
24 |
25 | * The use of sexualized language or imagery and unwelcome sexual attention or
26 | advances
27 | * Trolling, insulting/derogatory comments, and personal or political attacks
28 | * Public or private harassment
29 | * Publishing others' private information, such as a physical or electronic
30 | address, without explicit permission
31 | * Other conduct which could reasonably be considered inappropriate in a
32 | professional setting
33 |
34 | ## Our Responsibilities
35 |
36 | Project maintainers are responsible for clarifying the standards of acceptable
37 | behavior and are expected to take appropriate and fair corrective action in
38 | response to any instances of unacceptable behavior.
39 |
40 | Project maintainers have the right and responsibility to remove, edit, or
41 | reject comments, commits, code, wiki edits, issues, and other contributions
42 | that are not aligned to this Code of Conduct, or to ban temporarily or
43 | permanently any contributor for other behaviors that they deem inappropriate,
44 | threatening, offensive, or harmful.
45 |
46 | ## Scope
47 |
48 | This Code of Conduct applies both within project spaces and in public spaces
49 | when an individual is representing the project or its community. Examples of
50 | representing a project or community include using an official project e-mail
51 | address, posting via an official social media account, or acting as an appointed
52 | representative at an online or offline event. Representation of a project may be
53 | further defined and clarified by project maintainers.
54 |
55 | ## Enforcement
56 |
57 | Instances of abusive, harassing, or otherwise unacceptable behavior may be
58 | reported by contacting the project team at support@transifex.com. All
59 | complaints will be reviewed and investigated and will result in a response that
60 | is deemed necessary and appropriate to the circumstances. The project team is
61 | obligated to maintain confidentiality with regard to the reporter of an incident.
62 | Further details of specific enforcement policies may be posted separately.
63 |
64 | Project maintainers who do not follow or enforce the Code of Conduct in good
65 | faith may face temporary or permanent repercussions as determined by other
66 | members of the project's leadership.
67 |
68 | ## Attribution
69 |
70 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4,
71 | available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html
72 |
73 | [homepage]: https://www.contributor-covenant.org
74 |
75 | For answers to common questions about this code of conduct, see
76 | https://www.contributor-covenant.org/faq
--------------------------------------------------------------------------------
/CONTRIBUTING.md:
--------------------------------------------------------------------------------
1 | # Contributing to Transifex Native Android SDK
2 |
3 | :tada: Thank you for your interest in contributing to this project! :tada:
4 |
5 | ## Table Of Contents
6 |
7 | * [Code of Conduct](#code-of-conduct)
8 | * [I just have a question](#i-just-have-a-question)
9 | * [Contributor License Agreement](#contributor-license-agreement)
10 | * [How to Contribute](#how-to-contribute)
11 | * [Reporting Bugs](#reporting-bugs)
12 | * [Suggesting Improvements](#suggesting-improvements)
13 | * [Creating Patches](#creating-patches)
14 |
15 | # Code of Conduct
16 |
17 | Please read this project's [Code of Conduct](/CODE_OF_CONDUCT.md) for the detailed standards on how to engage in this community.
18 |
19 | # I just have a question
20 |
21 | If you are not looking to contribute, but rather have a question about this project, you can visit the [Transifex Community](https://community.transifex.com/c/transifex-native) and ask there directly.
22 |
23 | # How to Contribute
24 |
25 | There are several ways to contribute to this project and they are all equally welcome. You can report a bug, suggest an improvement or submit an improvement yourself, be it a change in the code or in the documentation.
26 |
27 | ## Reporting Bugs
28 |
29 | Bugs in this project are tracked as [GitHub issues](https://guides.github.com/features/issues/). Before submitting a new issue, make sure you check the [list of exiting issues](https://github.com/transifex/transifex-java/issues), as it might already be there.
30 |
31 | When creating an issue, make sure you include the following:
32 | * A clear and descriptive title.
33 | * The exact steps to allow others to reproduce the issue.
34 | * The behavior you observed and the reason you find this problematic.
35 | * The behavior you expected instead and the reason for this.
36 | * Any material that would better showcase the issue, such as screenshots or animated GIFs.
37 | * The version(s) of this library you have seen this erroneous behavior in
38 | * Make sure you DO NOT INCLUDE any sensitive information, such as a token, a secret or a private URL to a Transifex project or resource
39 |
40 | ## Suggesting Improvements
41 |
42 | Improvement suggestions in this project are tracked as [GitHub issues](https://guides.github.com/features/issues/). Before submitting a new suggestion, make sure you check the [list of exiting issues](https://github.com/transifex/transifex-java/issues), as it might already be there.
43 |
44 | When creating an improvement issue, make sure you include the following:
45 | * A clear and descriptive title.
46 | * The exact steps to allow others to reproduce the issue.
47 | * The current behavior/functionality, what you would prefer to see instead and why.
48 | * An explanation on why this improvement would benefit most users of this library and not just your use case
49 | * Any material that could better showcase the current and the expected behavior, such as screenshots or animated GIFs.
50 | * Make sure you DO NOT INCLUDE any sensitive information, such as a token, a secret or a private URL to a Transifex project or resource
51 |
52 | ## Creating Patches
53 |
54 | ### Contributor License Agreement
55 |
56 | Before contributing with suggested changes, you must sign our [Contributor License Agreement](https://developers.transifex.com/docs/contributing).
57 |
58 | If you want to submit a patch that either fixes a bug or introduces a new feature, you need to have the following in mind:
59 | * The Pull Request (PR) handles one thing, i.e. fixes a certain bug or introduces a specific new functionality, instead of combining many at once.
60 | * Each commit should include one logical change each and should ideally be few in number per PR.
61 | * All [status checks](https://help.github.com/articles/about-status-checks), including the execution of the test suit, must pass.
62 | * The Rebase workflow is followed: each branch must be rebased over `origin/master` before being allowed to get merged.
63 |
64 | In order for a PR to be merged, at least one maintainer of this project must first review and approve it. The reviewer(s) may request changes before moving on.
65 |
--------------------------------------------------------------------------------
/TransifexNativeSDK/.gitignore:
--------------------------------------------------------------------------------
1 | *.iml
2 | .gradle
3 | /local.properties
4 | /.idea/caches
5 | /.idea/libraries
6 | /.idea/modules.xml
7 | /.idea/workspace.xml
8 | /.idea/navEditor.xml
9 | /.idea/assetWizardSettings.xml
10 | .DS_Store
11 | /build
12 | /captures
13 | .externalNativeBuild
14 | .cxx
15 | /tmp
16 |
--------------------------------------------------------------------------------
/TransifexNativeSDK/app/.gitignore:
--------------------------------------------------------------------------------
1 | /build
--------------------------------------------------------------------------------
/TransifexNativeSDK/app/build.gradle:
--------------------------------------------------------------------------------
1 | plugins {
2 | id 'com.android.application'
3 | }
4 |
5 | android {
6 |
7 | namespace 'com.transifex.myapplication'
8 |
9 | defaultConfig {
10 | applicationId "com.transifex.myapplication"
11 | minSdkVersion 18
12 | compileSdk 33
13 | targetSdkVersion 33
14 | versionCode 1
15 | versionName "1.0"
16 | // https://stackoverflow.com/a/41345440/941314
17 | // https://developer.android.com/build/shrink-code#unused-alt-resources
18 | // Needed for multilingual-support after Android N
19 | resourceConfigurations += ['en', 'el', 'de', 'fr', 'ar', 'sl']
20 |
21 | testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
22 | }
23 |
24 | buildTypes {
25 | minifyTesting {
26 | initWith(buildTypes["debug"])
27 | minifyEnabled true
28 | debuggable false
29 | proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
30 | matchingFallbacks = ['release']
31 | }
32 | release {
33 | minifyEnabled false
34 | proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
35 | }
36 | }
37 |
38 | compileOptions {
39 | sourceCompatibility JavaVersion.VERSION_1_8
40 | targetCompatibility JavaVersion.VERSION_1_8
41 | }
42 | }
43 |
44 | // Uncomment in combination with "setCompatVectorFromResourcesEnabled()" in "MyApplication" to
45 | // check the workaround in TxBaseAppCompatActivity.
46 | //configurations.all {
47 | // resolutionStrategy {
48 | // force 'androidx.appcompat:appcompat:1.3.0'
49 | // }
50 | //}
51 |
52 | dependencies {
53 | implementation fileTree(dir: "libs", include: ["*.jar"])
54 | implementation "androidx.appcompat:appcompat:$versions.appcompat"
55 | implementation "com.google.android.material:material:$versions.material"
56 | implementation 'androidx.constraintlayout:constraintlayout:2.1.4'
57 |
58 | testImplementation "junit:junit:$versions.junit"
59 |
60 | androidTestImplementation "androidx.test.ext:junit:$versions.androidXJunit"
61 | androidTestImplementation "androidx.test.espresso:espresso-core:$versions.androidxEspressoCore"
62 |
63 | implementation project(':txsdk')
64 | }
--------------------------------------------------------------------------------
/TransifexNativeSDK/app/proguard-rules.pro:
--------------------------------------------------------------------------------
1 | # Add project specific ProGuard rules here.
2 | # You can control the set of applied configuration files using the
3 | # proguardFiles setting in build.gradle.
4 | #
5 | # For more details, see
6 | # http://developer.android.com/guide/developing/tools/proguard.html
7 |
8 | # If your project uses WebView with JS, uncomment the following
9 | # and specify the fully qualified class name to the JavaScript interface
10 | # class:
11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview {
12 | # public *;
13 | #}
14 |
15 | # Uncomment this to preserve the line number information for
16 | # debugging stack traces.
17 | #-keepattributes SourceFile,LineNumberTable
18 |
19 | # If you keep the line number information, uncomment this to
20 | # hide the original source file name.
21 | #-renamesourcefileattribute SourceFile
--------------------------------------------------------------------------------
/TransifexNativeSDK/app/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
38 | * Each downloaded file is added on the
92 | * For each locale, a subdirectory under the provided directory is created and a file containing
93 | * the translations is created. Note that the provided directory should already exist. If a
94 | * translation file already exists, it's overwritten.
95 | *
96 | * @param localeCode An optional locale to fetch translations from; if set to
12 | * The directory is deleted, if it exists, during setup. It is also deleted during tear down.
13 | */
14 | public class TempDirHelper {
15 |
16 | private final File tempDir;
17 |
18 | public TempDirHelper() {
19 | tempDir = new File("build" + File.separator + "unitTestTempDir");
20 | }
21 |
22 | public TempDirHelper(@NonNull File dir) {
23 | tempDir = dir;
24 | }
25 |
26 | public void setUp() {
27 | if (tempDir.exists()) {
28 | Utils.deleteDirectory(tempDir);
29 | }
30 | }
31 |
32 | public void tearDown() {
33 | if (tempDir.exists()) {
34 | boolean deleted = Utils.deleteDirectory(tempDir);
35 | if (!deleted) {
36 | System.out.println("Could not delete tmp dir after test. Next test may fail.");
37 | }
38 | }
39 | }
40 |
41 | public File getFile() {
42 | return tempDir;
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/TransifexNativeSDK/gradle.properties:
--------------------------------------------------------------------------------
1 | # Project-wide Gradle settings.
2 | # IDE (e.g. Android Studio) users:
3 | # Gradle settings configured through the IDE *will override*
4 | # any settings specified in this file.
5 | # For more details on how to configure your build environment visit
6 | # http://www.gradle.org/docs/current/userguide/build_environment.html
7 | # Specifies the JVM arguments used for the daemon process.
8 | # The setting is particularly useful for tweaking memory settings.
9 | org.gradle.jvmargs=-Xmx2048m
10 | # When configured, Gradle will run in incubating parallel mode.
11 | # This option should only be used with decoupled projects. More details, visit
12 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
13 | # org.gradle.parallel=true
14 | # AndroidX package structure to make it clearer which packages are bundled with the
15 | # Android operating system, and which are packaged with your app"s APK
16 | # https://developer.android.com/topic/libraries/support-library/androidx-rn
17 | android.useAndroidX=true
18 | # Automatically convert third-party libraries to use AndroidX
19 | android.enableJetifier=true
20 |
21 | # https://developer.android.com/build/releases/past-releases/agp-8-0-0-release-notes#default-changes
22 | android.defaults.buildfeatures.buildconfig=true
23 | android.nonTransitiveRClass=false
24 | android.nonFinalResIds=false
--------------------------------------------------------------------------------
/TransifexNativeSDK/gradle/wrapper/gradle-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/transifex/transifex-java/4bee47f251769eceb56a12aa1fb188e458f3de5c/TransifexNativeSDK/gradle/wrapper/gradle-wrapper.jar
--------------------------------------------------------------------------------
/TransifexNativeSDK/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | #Mon Sep 20 14:45:43 EEST 2021
2 | distributionBase=GRADLE_USER_HOME
3 | distributionUrl=https\://services.gradle.org/distributions/gradle-8.0-bin.zip
4 | distributionPath=wrapper/dists
5 | zipStorePath=wrapper/dists
6 | zipStoreBase=GRADLE_USER_HOME
7 |
--------------------------------------------------------------------------------
/TransifexNativeSDK/gradlew:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env sh
2 |
3 | ##############################################################################
4 | ##
5 | ## Gradle start up script for UN*X
6 | ##
7 | ##############################################################################
8 |
9 | # Attempt to set APP_HOME
10 | # Resolve links: $0 may be a link
11 | PRG="$0"
12 | # Need this for relative symlinks.
13 | while [ -h "$PRG" ] ; do
14 | ls=`ls -ld "$PRG"`
15 | link=`expr "$ls" : '.*-> \(.*\)$'`
16 | if expr "$link" : '/.*' > /dev/null; then
17 | PRG="$link"
18 | else
19 | PRG=`dirname "$PRG"`"/$link"
20 | fi
21 | done
22 | SAVED="`pwd`"
23 | cd "`dirname \"$PRG\"`/" >/dev/null
24 | APP_HOME="`pwd -P`"
25 | cd "$SAVED" >/dev/null
26 |
27 | APP_NAME="Gradle"
28 | APP_BASE_NAME=`basename "$0"`
29 |
30 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
31 | DEFAULT_JVM_OPTS=""
32 |
33 | # Use the maximum available, or set MAX_FD != -1 to use that value.
34 | MAX_FD="maximum"
35 |
36 | warn () {
37 | echo "$*"
38 | }
39 |
40 | die () {
41 | echo
42 | echo "$*"
43 | echo
44 | exit 1
45 | }
46 |
47 | # OS specific support (must be 'true' or 'false').
48 | cygwin=false
49 | msys=false
50 | darwin=false
51 | nonstop=false
52 | case "`uname`" in
53 | CYGWIN* )
54 | cygwin=true
55 | ;;
56 | Darwin* )
57 | darwin=true
58 | ;;
59 | MINGW* )
60 | msys=true
61 | ;;
62 | NONSTOP* )
63 | nonstop=true
64 | ;;
65 | esac
66 |
67 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
68 |
69 | # Determine the Java command to use to start the JVM.
70 | if [ -n "$JAVA_HOME" ] ; then
71 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
72 | # IBM's JDK on AIX uses strange locations for the executables
73 | JAVACMD="$JAVA_HOME/jre/sh/java"
74 | else
75 | JAVACMD="$JAVA_HOME/bin/java"
76 | fi
77 | if [ ! -x "$JAVACMD" ] ; then
78 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
79 |
80 | Please set the JAVA_HOME variable in your environment to match the
81 | location of your Java installation."
82 | fi
83 | else
84 | JAVACMD="java"
85 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
86 |
87 | Please set the JAVA_HOME variable in your environment to match the
88 | location of your Java installation."
89 | fi
90 |
91 | # Increase the maximum file descriptors if we can.
92 | if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
93 | MAX_FD_LIMIT=`ulimit -H -n`
94 | if [ $? -eq 0 ] ; then
95 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
96 | MAX_FD="$MAX_FD_LIMIT"
97 | fi
98 | ulimit -n $MAX_FD
99 | if [ $? -ne 0 ] ; then
100 | warn "Could not set maximum file descriptor limit: $MAX_FD"
101 | fi
102 | else
103 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
104 | fi
105 | fi
106 |
107 | # For Darwin, add options to specify how the application appears in the dock
108 | if $darwin; then
109 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
110 | fi
111 |
112 | # For Cygwin, switch paths to Windows format before running java
113 | if $cygwin ; then
114 | APP_HOME=`cygpath --path --mixed "$APP_HOME"`
115 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
116 | JAVACMD=`cygpath --unix "$JAVACMD"`
117 |
118 | # We build the pattern for arguments to be converted via cygpath
119 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
120 | SEP=""
121 | for dir in $ROOTDIRSRAW ; do
122 | ROOTDIRS="$ROOTDIRS$SEP$dir"
123 | SEP="|"
124 | done
125 | OURCYGPATTERN="(^($ROOTDIRS))"
126 | # Add a user-defined pattern to the cygpath arguments
127 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then
128 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
129 | fi
130 | # Now convert the arguments - kludge to limit ourselves to /bin/sh
131 | i=0
132 | for arg in "$@" ; do
133 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
134 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
135 |
136 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
137 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
138 | else
139 | eval `echo args$i`="\"$arg\""
140 | fi
141 | i=$((i+1))
142 | done
143 | case $i in
144 | (0) set -- ;;
145 | (1) set -- "$args0" ;;
146 | (2) set -- "$args0" "$args1" ;;
147 | (3) set -- "$args0" "$args1" "$args2" ;;
148 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;;
149 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
150 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
151 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
152 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
153 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
154 | esac
155 | fi
156 |
157 | # Escape application args
158 | save () {
159 | for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
160 | echo " "
161 | }
162 | APP_ARGS=$(save "$@")
163 |
164 | # Collect all arguments for the java command, following the shell quoting and substitution rules
165 | eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
166 |
167 | # by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong
168 | if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then
169 | cd "$(dirname "$0")"
170 | fi
171 |
172 | exec "$JAVACMD" "$@"
173 |
--------------------------------------------------------------------------------
/TransifexNativeSDK/gradlew.bat:
--------------------------------------------------------------------------------
1 | @if "%DEBUG%" == "" @echo off
2 | @rem ##########################################################################
3 | @rem
4 | @rem Gradle startup script for Windows
5 | @rem
6 | @rem ##########################################################################
7 |
8 | @rem Set local scope for the variables with windows NT shell
9 | if "%OS%"=="Windows_NT" setlocal
10 |
11 | set DIRNAME=%~dp0
12 | if "%DIRNAME%" == "" set DIRNAME=.
13 | set APP_BASE_NAME=%~n0
14 | set APP_HOME=%DIRNAME%
15 |
16 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
17 | set DEFAULT_JVM_OPTS=
18 |
19 | @rem Find java.exe
20 | if defined JAVA_HOME goto findJavaFromJavaHome
21 |
22 | set JAVA_EXE=java.exe
23 | %JAVA_EXE% -version >NUL 2>&1
24 | if "%ERRORLEVEL%" == "0" goto init
25 |
26 | echo.
27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
28 | echo.
29 | echo Please set the JAVA_HOME variable in your environment to match the
30 | echo location of your Java installation.
31 |
32 | goto fail
33 |
34 | :findJavaFromJavaHome
35 | set JAVA_HOME=%JAVA_HOME:"=%
36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe
37 |
38 | if exist "%JAVA_EXE%" goto init
39 |
40 | echo.
41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
42 | echo.
43 | echo Please set the JAVA_HOME variable in your environment to match the
44 | echo location of your Java installation.
45 |
46 | goto fail
47 |
48 | :init
49 | @rem Get command-line arguments, handling Windows variants
50 |
51 | if not "%OS%" == "Windows_NT" goto win9xME_args
52 |
53 | :win9xME_args
54 | @rem Slurp the command line arguments.
55 | set CMD_LINE_ARGS=
56 | set _SKIP=2
57 |
58 | :win9xME_args_slurp
59 | if "x%~1" == "x" goto execute
60 |
61 | set CMD_LINE_ARGS=%*
62 |
63 | :execute
64 | @rem Setup the command line
65 |
66 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
67 |
68 | @rem Execute Gradle
69 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
70 |
71 | :end
72 | @rem End local scope for the variables with windows NT shell
73 | if "%ERRORLEVEL%"=="0" goto mainEnd
74 |
75 | :fail
76 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
77 | rem the _cmd.exe /c_ return code!
78 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
79 | exit /b 1
80 |
81 | :mainEnd
82 | if "%OS%"=="Windows_NT" endlocal
83 |
84 | :omega
85 |
--------------------------------------------------------------------------------
/TransifexNativeSDK/publish-helper.gradle:
--------------------------------------------------------------------------------
1 | apply plugin: 'maven-publish'
2 | apply plugin: 'signing'
3 |
4 | tasks.register('androidJavadoc', Javadoc) {
5 | if (plugins.hasPlugin('android-library')) {
6 | source = android.sourceSets.main.java.srcDirs
7 | classpath += project.files(android.getBootClasspath())
8 | android.libraryVariants.all { variant ->
9 | if (variant.name == 'release') {
10 | owner.classpath += variant.javaCompileProvider.get().classpath
11 | }
12 | }
13 | } else {
14 | source = sourceSets.main.allJava
15 | classpath += configurations.runtimeClasspath
16 | classpath += configurations.compileClasspath
17 | }
18 | exclude '**/R.html', '**/R.*.html', '**/index.html', '**/*.kt'
19 | options.encoding 'utf-8'
20 | options {
21 | addStringOption 'docencoding', 'utf-8'
22 | addStringOption 'charset', 'utf-8'
23 | addStringOption 'source', '8'
24 | addBooleanOption('Xdoclint:none', true)
25 | links 'https://d.android.com/reference'
26 | linksOffline 'https://d.android.com/reference/', 'https://d.android.com/reference/androidx/'
27 | }
28 | }
29 |
30 | tasks.register('androidJavadocJar', Jar) {
31 | dependsOn androidJavadoc
32 |
33 | archiveClassifier.set('javadoc')
34 | from androidJavadoc.destinationDir
35 | preserveFileTimestamps = false
36 | reproducibleFileOrder = true
37 | }
38 |
39 | tasks.register('javaSourcesJar', Jar) {
40 | archiveClassifier.set('sources')
41 | if (plugins.hasPlugin('android-library')) {
42 | from android.sourceSets.main.java.srcDirs
43 | } else {
44 | from sourceSets.main.allSource
45 | }
46 | preserveFileTimestamps = false
47 | reproducibleFileOrder = true
48 | }
49 |
50 | afterEvaluate {
51 | publishing {
52 | publications {
53 | // Creates a Maven publication called 'release'.
54 | release(MavenPublication) {
55 | if (plugins.hasPlugin('android-library')) {
56 | from components.release
57 |
58 | artifact androidJavadocJar
59 | // artifact javaSourcesJar is removed. It's created by AGP in the android
60 | // publishing block.
61 | }
62 | else if (plugins.hasPlugin('java')) {
63 | from components.java
64 | jar.preserveFileTimestamps = false
65 | jar.reproducibleFileOrder = true
66 |
67 | artifact androidJavadocJar
68 | artifact javaSourcesJar
69 | }
70 |
71 |
72 | groupId rootProject.ext.pomGroupID
73 | version rootProject.ext.sdkVersion
74 | pom {
75 | name = artifactId
76 | description = 'Transifex Native library for Android'
77 | url = 'https://github.com/transifex/transifex-java'
78 | licenses {
79 | license {
80 | name = 'The Apache License, Version 2.0'
81 | url = 'http://www.apache.org/licenses/LICENSE-2.0.txt'
82 | }
83 | }
84 | scm {
85 | connection = 'scm:git:https://github.com/transifex/transifex-java.git'
86 | url = 'https://github.com/transifex/transifex-java'
87 | }
88 | developers {
89 | developer {
90 | id = 'petrakeas'
91 | name = 'Petros Douvantzis'
92 | email = 'petrakeas@gmail.com'
93 | }
94 | }
95 | }
96 | }
97 | }
98 | // No repositories are configured here, since"gradle-nexus.publish-plugin" will create its
99 | // own using the configuration in the project's gradle file
100 | }
101 | }
102 |
103 | signing {
104 | sign publishing.publications
105 | }
--------------------------------------------------------------------------------
/TransifexNativeSDK/settings.gradle:
--------------------------------------------------------------------------------
1 | pluginManagement {
2 | repositories {
3 | gradlePluginPortal()
4 | google()
5 | mavenCentral()
6 | }
7 | }
8 | dependencyResolutionManagement {
9 | repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
10 | repositories {
11 | google()
12 | mavenCentral()
13 | }
14 | }
15 |
16 | include ':common'
17 | include ':clitool'
18 | include ':txsdk'
19 | include ':app'
--------------------------------------------------------------------------------
/TransifexNativeSDK/txsdk/.gitignore:
--------------------------------------------------------------------------------
1 | /build
--------------------------------------------------------------------------------
/TransifexNativeSDK/txsdk/build.gradle:
--------------------------------------------------------------------------------
1 | plugins {
2 | id 'com.android.library'
3 | id 'org.jetbrains.kotlin.android'
4 | }
5 |
6 | android {
7 | namespace = 'com.transifex.txnative'
8 |
9 | defaultConfig {
10 | minSdkVersion 18
11 | compileSdk 33
12 | targetSdkVersion 33
13 |
14 | testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
15 | consumerProguardFiles "consumer-rules.pro"
16 |
17 | // gradle.startParameter.taskNames.each {
18 | // if (it.contains("AndroidTest")) {
19 | // multiDexEnabled true
20 | // }
21 | // }
22 | }
23 |
24 | testOptions {
25 | unitTests.includeAndroidResources = true
26 | }
27 |
28 | buildTypes {
29 | release {
30 | minifyEnabled false
31 | proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
32 | }
33 | }
34 |
35 | compileOptions {
36 | sourceCompatibility JavaVersion.VERSION_1_8
37 | targetCompatibility JavaVersion.VERSION_1_8
38 | }
39 |
40 | kotlinOptions {
41 | jvmTarget = '1.8'
42 | }
43 |
44 | // https://developer.android.com/studio/publish-library/configure-pub-variants
45 | publishing {
46 | singleVariant('release') {
47 | // Currently, Javadoc generation from AGP does not have the same quality as the one
48 | // generated by the script at publish-helper.gradle so it's commented out.
49 | // withJavadocJar()
50 | withSourcesJar()
51 | }
52 | }
53 | }
54 |
55 | tasks.withType(Test).configureEach {
56 | systemProperty "file.encoding", "UTF-8"
57 | }
58 |
59 | configurations.configureEach {
60 | resolutionStrategy {
61 | force "androidx.annotation:annotation:$versions.androidXAnnotation"
62 | }
63 | }
64 |
65 | dependencies {
66 | implementation fileTree(dir: "libs", include: ["*.jar"])
67 | compileOnly "androidx.annotation:annotation:$versions.androidXAnnotation"
68 | implementation "androidx.appcompat:appcompat:$versions.appcompat"
69 | compileOnly "com.google.android.material:material:$versions.material"
70 | implementation("dev.b3nedikt.viewpump:viewpump:4.0.14") {
71 | exclude(group : "androidx.appcompat", module : "appcompat")
72 | }
73 |
74 | api project(':common')
75 |
76 | testCompileOnly "androidx.annotation:annotation:$versions.androidXAnnotation"
77 | testImplementation "junit:junit:$versions.junit"
78 | testImplementation "com.google.truth:truth:$versions.truth"
79 | testImplementation "androidx.test:core:$versions.androidXTestCore"
80 | testImplementation "org.robolectric:robolectric:$versions.roboelectric"
81 | testImplementation "com.squareup.okhttp3:mockwebserver:$versions.okhttp3Mockwebserver"
82 | testImplementation "org.mockito:mockito-core:$versions.mockitoCore"
83 | testImplementation testFixtures(project(':common'))
84 |
85 | androidTestCompileOnly "androidx.annotation:annotation:$versions.androidXAnnotation"
86 | androidTestImplementation "androidx.test.ext:junit:$versions.androidXJunit"
87 | androidTestImplementation "androidx.test.espresso:espresso-core:$versions.androidxEspressoCore"
88 | androidTestImplementation "com.google.truth:truth:$versions.truth"
89 | androidTestImplementation testFixtures(project(':common'))
90 | }
91 |
92 | apply from: '../publish-helper.gradle'
93 |
94 | afterEvaluate {
95 | publishing {
96 | publications {
97 | release(MavenPublication) {
98 | // Specify custom artifactId if needed,
99 | // otherwise it would use module's name by default.
100 | artifactId = "txsdk"
101 | }
102 | }
103 | }
104 | }
--------------------------------------------------------------------------------
/TransifexNativeSDK/txsdk/consumer-rules.pro:
--------------------------------------------------------------------------------
1 | # Keep LocaleData inner classes as is, so that Gson works as expected
2 | -keep class com.transifex.common.LocaleData { *; }
3 | -keep class com.transifex.common.LocaleData$* { *; }
4 |
5 | # Keep class names for better log ouput
6 | -keepnames class com.transifex.txnative.**
7 | -keepnames class com.transifex.common.**
8 |
9 | # Don't warn
10 | -dontwarn lombok.NonNull
--------------------------------------------------------------------------------
/TransifexNativeSDK/txsdk/proguard-rules.pro:
--------------------------------------------------------------------------------
1 | # Add project specific ProGuard rules here.
2 | # You can control the set of applied configuration files using the
3 | # proguardFiles setting in build.gradle.
4 | #
5 | # For more details, see
6 | # http://developer.android.com/guide/developing/tools/proguard.html
7 |
8 | # If your project uses WebView with JS, uncomment the following
9 | # and specify the fully qualified class name to the JavaScript interface
10 | # class:
11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview {
12 | # public *;
13 | #}
14 |
15 | # Uncomment this to preserve the line number information for
16 | # debugging stack traces.
17 | #-keepattributes SourceFile,LineNumberTable
18 |
19 | # If you keep the line number information, uncomment this to
20 | # hide the original source file name.
21 | #-renamesourcefileattribute SourceFile
--------------------------------------------------------------------------------
/TransifexNativeSDK/txsdk/src/androidTest/assets/test_fileInsteadLocaleDir/dummy_file:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/transifex/transifex-java/4bee47f251769eceb56a12aa1fb188e458f3de5c/TransifexNativeSDK/txsdk/src/androidTest/assets/test_fileInsteadLocaleDir/dummy_file
--------------------------------------------------------------------------------
/TransifexNativeSDK/txsdk/src/androidTest/assets/test_normal/el/strings.txt:
--------------------------------------------------------------------------------
1 | {"data":{"test_key":{"string":"Καλημέρα"},"another_key":{"string":"Καλό απόγευμα"},"key3":{"string":""}}}
--------------------------------------------------------------------------------
/TransifexNativeSDK/txsdk/src/androidTest/assets/test_normal/es/strings.txt:
--------------------------------------------------------------------------------
1 | {"data":{"test_key":{"string":"Buenos días"},"another_key":{"string":"Buenas tardes"},"key3":{"string":""}}}
--------------------------------------------------------------------------------
/TransifexNativeSDK/txsdk/src/androidTest/assets/test_oneLocaleFileIsMissing/el/strings.txt:
--------------------------------------------------------------------------------
1 | {"data":{"test_key":{"string":"Καλημέρα"},"another_key":{"string":"Καλό απόγευμα"},"key3":{"string":""}}}
--------------------------------------------------------------------------------
/TransifexNativeSDK/txsdk/src/androidTest/assets/test_oneLocaleFileIsMissing/es/wrong_file:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/transifex/transifex-java/4bee47f251769eceb56a12aa1fb188e458f3de5c/TransifexNativeSDK/txsdk/src/androidTest/assets/test_oneLocaleFileIsMissing/es/wrong_file
--------------------------------------------------------------------------------
/TransifexNativeSDK/txsdk/src/androidTest/assets/test_oneLocaleHasInvalidJson/el/strings.txt:
--------------------------------------------------------------------------------
1 | {"data":{"test_key":{"string":"Καλημέρα"},"another_key":{"string":"Καλό απόγευμα"},"key3":{"string":""}}}
--------------------------------------------------------------------------------
/TransifexNativeSDK/txsdk/src/androidTest/assets/test_oneLocaleHasInvalidJson/es/strings.txt:
--------------------------------------------------------------------------------
1 | not a JSON format
--------------------------------------------------------------------------------
/TransifexNativeSDK/txsdk/src/androidTest/assets/txnative/el/txstrings.json:
--------------------------------------------------------------------------------
1 | {"data":{"test_key":{"string":"Καλημέρα"},"another_key":{"string":"Καλό απόγευμα"},"key3":{"string":""}}}
--------------------------------------------------------------------------------
/TransifexNativeSDK/txsdk/src/androidTest/assets/txnative/es/txstrings.json:
--------------------------------------------------------------------------------
1 | {"data":{"test_key":{"string":"Buenos días"},"another_key":{"string":"Buenas tardes"},"key3":{"string":""}}}
--------------------------------------------------------------------------------
/TransifexNativeSDK/txsdk/src/androidTest/java/com/transifex/txnative/PluralsTest.java:
--------------------------------------------------------------------------------
1 | package com.transifex.txnative;
2 |
3 | import com.transifex.common.Plurals;
4 |
5 | import org.junit.Test;
6 | import org.junit.runner.RunWith;
7 |
8 | import androidx.test.ext.junit.runners.AndroidJUnit4;
9 | import androidx.test.filters.SmallTest;
10 |
11 | import static com.google.common.truth.Truth.assertThat;
12 |
13 | @RunWith(AndroidJUnit4.class)
14 | @SmallTest
15 | public class PluralsTest {
16 |
17 | // The following test is copied from the respective unit test. We just want to make sure the
18 | // internal regex patter compiles correctly on a device.
19 |
20 | @Test
21 | public void testFromICUString_oneIncorrectPluralStyle_parseRestOfThem() {
22 | String icuString = "{???, plural, WRONG {task} other {tasks}}";
23 | Plurals plurals = Plurals.fromICUString(icuString);
24 |
25 | assertThat(plurals).isNotNull();
26 | assertThat(plurals.zero).isNull();
27 | assertThat(plurals.one).isNull();
28 | assertThat(plurals.two).isNull();
29 | assertThat(plurals.few).isNull();
30 | assertThat(plurals.many).isNull();
31 | assertThat(plurals.other).isEqualTo("tasks");
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/TransifexNativeSDK/txsdk/src/androidTest/java/com/transifex/txnative/TranslationMapStorageAndroidTest.java:
--------------------------------------------------------------------------------
1 | package com.transifex.txnative;
2 |
3 | import android.content.Context;
4 | import android.content.res.AssetManager;
5 |
6 | import com.transifex.common.LocaleData;
7 |
8 | import org.junit.Before;
9 | import org.junit.Test;
10 | import org.junit.runner.RunWith;
11 |
12 | import androidx.test.ext.junit.runners.AndroidJUnit4;
13 | import androidx.test.filters.SmallTest;
14 | import androidx.test.platform.app.InstrumentationRegistry;
15 |
16 | import static com.google.common.truth.Truth.assertThat;
17 |
18 | @RunWith(AndroidJUnit4.class)
19 | @SmallTest
20 | public class TranslationMapStorageAndroidTest {
21 |
22 | // The tests rely on the following directories:
23 | //
24 | // androidTest/assets/test_normal
25 | // androidTest/assets/test_oneLocaleHasInvalidJson
26 | // androidTest/assets/test_oneLocaleFileIsMissing
27 | // androidTest/assets/test_fileInsteadLocaleDir
28 | //
29 | // Note that aapt2 will remove empty directories when packing the assets. So there is no reason
30 | // to test for an empty directory.
31 |
32 | Context appContext = null;
33 | AssetManager assetManager;
34 |
35 | @Before
36 | public void setUp() {
37 | appContext = InstrumentationRegistry.getInstrumentation().getTargetContext();
38 | assetManager = appContext.getAssets();
39 | }
40 |
41 | @Test
42 | public void testFromAssetsDirectory_dirDoesNotExist_returnNullTranslationMap() {
43 | TranslationMapStorageAndroid reader = new TranslationMapStorageAndroid(assetManager, "strings.txt");
44 | LocaleData.TranslationMap map = reader.fromAssetsDirectory("wrongDir");
45 |
46 | assertThat(map).isNull();
47 | }
48 |
49 | @Test
50 | public void testFromAssetsDirectory_haveFileWhereLocaleDirExpected_returnNullTranslationMap() {
51 | TranslationMapStorageAndroid reader = new TranslationMapStorageAndroid(assetManager, "strings.txt");
52 | LocaleData.TranslationMap map = reader.fromAssetsDirectory("test_fileInsteadLocaleDir");
53 |
54 | assertThat(map).isNull();
55 | }
56 |
57 | @Test
58 | public void testFromAssetsDirectory_normal_returnNormalTranslationMap() {
59 | TranslationMapStorageAndroid reader = new TranslationMapStorageAndroid(assetManager, "strings.txt");
60 | LocaleData.TranslationMap map = reader.fromAssetsDirectory("test_normal");
61 |
62 | assertThat(map).isNotNull();
63 | assertThat(map.getLocales()).containsExactly("el", "es");
64 |
65 | LocaleData.LocaleStrings elStrings = map.get("el");
66 | assertThat(elStrings).isNotNull();
67 | assertThat(elStrings.get("test_key")).isEqualTo("Καλημέρα");
68 | assertThat(elStrings.get("another_key")).isEqualTo("Καλό απόγευμα");
69 | assertThat(elStrings.get("key3")).isEqualTo("");
70 |
71 | LocaleData.LocaleStrings esStrings = map.get("es");
72 | assertThat(esStrings).isNotNull();
73 | assertThat(esStrings.get("test_key")).isEqualTo("Buenos días");
74 | assertThat(esStrings.get("another_key")).isEqualTo("Buenas tardes");
75 | assertThat(esStrings.get("key3")).isEqualTo("");
76 | }
77 |
78 | @Test
79 | public void testFromAssetsDirectory_oneLocaleHasInvalidJson_returnTranslationMapWithTheRestLocales() {
80 | TranslationMapStorageAndroid reader = new TranslationMapStorageAndroid(assetManager, "strings.txt");
81 | LocaleData.TranslationMap map = reader.fromAssetsDirectory("test_oneLocaleHasInvalidJson");
82 |
83 | assertThat(map).isNotNull();
84 | assertThat(map.getLocales()).containsExactly("el");
85 |
86 | LocaleData.LocaleStrings elStrings = map.get("el");
87 | assertThat(elStrings).isNotNull();
88 | assertThat(elStrings.get("test_key")).isEqualTo("Καλημέρα");
89 | assertThat(elStrings.get("another_key")).isEqualTo("Καλό απόγευμα");
90 | assertThat(elStrings.get("key3")).isEqualTo("");
91 | }
92 |
93 | @Test
94 | public void testFromAssetsDirector_oneLocaleHasEmptyDir_getTranslationMapWithTheRestLocales() {
95 | TranslationMapStorageAndroid reader = new TranslationMapStorageAndroid(assetManager, "strings.txt");
96 | LocaleData.TranslationMap map = reader.fromAssetsDirectory("test_oneLocaleFileIsMissing");
97 |
98 | assertThat(map).isNotNull();
99 | assertThat(map.getLocales()).containsExactly("el");
100 |
101 | LocaleData.LocaleStrings elStrings = map.get("el");
102 | assertThat(elStrings).isNotNull();
103 | assertThat(elStrings.get("test_key")).isEqualTo("Καλημέρα");
104 | assertThat(elStrings.get("another_key")).isEqualTo("Καλό απόγευμα");
105 | assertThat(elStrings.get("key3")).isEqualTo("");
106 | }
107 | }
--------------------------------------------------------------------------------
/TransifexNativeSDK/txsdk/src/androidTest/java/com/transifex/txnative/TranslationMapStorageTest.java:
--------------------------------------------------------------------------------
1 | package com.transifex.txnative;
2 |
3 | import android.content.Context;
4 |
5 | import com.transifex.common.LocaleData;
6 | import com.transifex.common.StringTestData;
7 | import com.transifex.common.TempDirHelper;
8 | import com.transifex.common.TranslationMapStorage;
9 |
10 | import org.junit.After;
11 | import org.junit.Before;
12 | import org.junit.Test;
13 | import org.junit.runner.RunWith;
14 |
15 | import java.io.File;
16 | import java.util.HashMap;
17 |
18 | import androidx.test.ext.junit.runners.AndroidJUnit4;
19 | import androidx.test.filters.SmallTest;
20 | import androidx.test.platform.app.InstrumentationRegistry;
21 |
22 | import static com.google.common.truth.Truth.assertThat;
23 |
24 | @RunWith(AndroidJUnit4.class)
25 | @SmallTest
26 | public class TranslationMapStorageTest {
27 |
28 | // The following test is copied from the respective unit test.
29 |
30 | Context appContext = null;
31 | TempDirHelper tempDirHelper = null;
32 |
33 | @Before
34 | public void setUp() {
35 | appContext = InstrumentationRegistry.getInstrumentation().getTargetContext();
36 | File filesDir = appContext.getFilesDir();
37 | File tempDir = new File(filesDir.getPath() + File.separator + "unitTestTempDir");
38 | tempDirHelper = new TempDirHelper(tempDir);
39 | tempDirHelper.setUp();
40 | }
41 |
42 | @After
43 | public void Teardown() {
44 | if (tempDirHelper != null) {
45 | tempDirHelper.tearDown();
46 | }
47 | }
48 |
49 | @Test
50 | // Check that we can read what was written
51 | public void testToDiskFromDisk_normal() {
52 | boolean tempDirCreated = tempDirHelper.getFile().mkdirs();
53 | assertThat(tempDirCreated).isTrue();
54 |
55 | LocaleData.TranslationMap translationMap = StringTestData.getElEsTranslationMap();
56 | TranslationMapStorage storage = new TranslationMapStorage("strings.txt");
57 | HashMap
38 | * If the operation fails, the translationMap will be empty.
39 | *
40 | * @param translationMap A {@link com.transifex.common.LocaleData.TranslationMap TranslationMap}
41 | * holding the results.
42 | */
43 | @WorkerThread
44 | void onComplete(@NonNull LocaleData.TranslationMap translationMap);
45 | }
46 |
47 | /**
48 | * Creates a CDSHandler instance.
49 | *
50 | * @param localeCodes An array of locale codes that can be downloaded from CDS. The source
51 | * locale can also be included.
52 | * @param token The API token to use for connecting to the CDS.
53 | * @param secret The API secret to use for connecting to the CDS.
54 | * @param csdHost The host of the Content Delivery Service.
55 | */
56 | public CDSHandlerAndroid(@Nullable String[] localeCodes, @NonNull String token, @Nullable String secret, @NonNull String csdHost) {
57 | super(localeCodes, token, secret, csdHost);
58 | mExecutor = Executors.newSingleThreadExecutor();
59 | }
60 |
61 | /**
62 | * Fetch translations from CDS.
63 | *
64 | * The method is asynchronous. The callback is called on a background thread.
65 | *
66 | * @param localeCode An optional locale to fetch translations from; if set to
19 | * Translations can be bundled in your application's Assets folder, using the Transifex command-line
20 | * tool.
21 | *
22 | * @see TranslationMapStorage
23 | */
24 | public class TranslationMapStorageAndroid extends TranslationMapStorage {
25 |
26 | public static final String TAG = TranslationMapStorageAndroid.class.getSimpleName();
27 |
28 | private final AssetFileProvider assetFileProvider;
29 |
30 | /**
31 | * Creates a new instance.
32 | *
33 | * @param manager An instance of Android's {@link AssetManager};
34 | * @param filename The name of a locale's translation file.
35 | *
36 | * @see TranslationMapStorage
37 | */
38 | public TranslationMapStorageAndroid(@NonNull AssetManager manager, @NonNull String filename) {
39 | super(filename);
40 | assetFileProvider = new AssetFileProvider(manager);
41 | }
42 |
43 | /**
44 | * Loads a {@link LocaleData.TranslationMap} from an application's Assets folder under the
45 | * provided path.
46 | *
47 | * @param srcDirectoryPath The path to the directory containing translations in the expected
48 | * format.
49 | *
50 | * @return The translation map or
25 | * ViewTransforms should change the text elements of views by calling their respective methods.
26 | * Since we have wrapped the context and thus the resources, all string related calls will be
27 | * handled by {@link TxResources}.
28 | *
12 | * Make sure your activities extend this class or have the same implementation.
13 | *
14 | * If your app uses
13 | * Make sure your activities extend this class or have the same implementation.
14 | */
15 | public class TxBaseAppCompatActivity extends AppCompatActivity {
16 |
17 | private AppCompatDelegate mAppCompatDelegate;
18 |
19 | @NonNull
20 | @Override
21 | public AppCompatDelegate getDelegate() {
22 | // Wrap AppCompat delegate
23 | if (mAppCompatDelegate == null) {
24 | mAppCompatDelegate = TxNative.wrapAppCompatDelegate(super.getDelegate(), this);
25 | }
26 | return mAppCompatDelegate;
27 | }
28 |
29 | // If your app uses AppCompat 1.3 or older and
30 | // "AppCompatDelegate.setCompatVectorFromResourcesEnabled(true);" is set , uncomment the
31 | // following lines. Otherwise, TxNative functionality will be impaired when running on older
32 | // platforms (< API 21).
33 | //
34 | // Starting from AppCompat 1.4.0, "setCompatVectorFromResourcesEnabled()" does not need this
35 | // workaround.
36 |
37 | // private Resources mResources; // final resources (VectorEnabledTintResourcesWrapper or TxResources)
38 | //
39 | // @SuppressLint("RestrictedApi")
40 | // @Override
41 | // public Resources getResources() {
42 | // Resources resources = getBaseContext().getResources();
43 | // if (mResources == null && VectorEnabledTintResources.shouldBeUsed()) {
44 | // // We wrap the Resources returned by the base context in VectorEnabledTintResourcesWrapper,
45 | // // similarly to what AppCompatActivity does. However, these resources are not always
46 | // // the same as the ones used internally by AppCompatActivity, but that's the best
47 | // // we can do.
48 | // mResources = new VectorEnabledTintResourcesWrapper(this, resources);
49 | // }
50 | // return mResources == null ? resources : mResources;
51 | // }
52 | }
53 |
--------------------------------------------------------------------------------
/TransifexNativeSDK/txsdk/src/main/java/com/transifex/txnative/cache/TxCache.java:
--------------------------------------------------------------------------------
1 | package com.transifex.txnative.cache;
2 |
3 | import com.transifex.common.LocaleData;
4 |
5 | import androidx.annotation.NonNull;
6 | import androidx.annotation.Nullable;
7 |
8 | /**
9 | * An interface for classes that act as cache for translations.
10 | */
11 | public interface TxCache {
12 |
13 | /**
14 | * Gets all translations from the cache in the form of a
15 | * {@link LocaleData.TranslationMap TranslationMap} object.
16 | *
17 | * The returned object should not be altered as the cache may use it internally.
18 | */
19 | @NonNull LocaleData.TranslationMap get();
20 |
21 | /**
22 | * Get the translation for a certain key and locale pair.
23 | *
24 | * @param key The key of the string.
25 | * @param locale The locale code.
26 | *
27 | * @return The string if the key was found in the cache;
37 | * The translation map should not be changed after providing it to the cache, because the cache
38 | * implementation may use it without making a copy.
39 | */
40 | void update(@NonNull LocaleData.TranslationMap translationMap);
41 | }
42 |
--------------------------------------------------------------------------------
/TransifexNativeSDK/txsdk/src/main/java/com/transifex/txnative/cache/TxDecoratorCache.java:
--------------------------------------------------------------------------------
1 | package com.transifex.txnative.cache;
2 |
3 | import com.transifex.common.LocaleData;
4 |
5 | import java.util.Set;
6 |
7 | import androidx.annotation.NonNull;
8 | import androidx.annotation.Nullable;
9 |
10 | /**
11 | * Decorator class managing an internal cache and propagating the get() and update() protocol method
12 | * calls to said cache. The class should be extended to add new capabilities.
13 | */
14 | public class TxDecoratorCache implements TxCache {
15 |
16 | protected final TxCache mInternalCache;
17 |
18 | /**
19 | * Creates a decorator with the provided cache.
20 | *
21 | * @param internalCache The cache to be used.
22 | */
23 | public TxDecoratorCache(@NonNull TxCache internalCache) {
24 | mInternalCache = internalCache;
25 | }
26 |
27 | @NonNull
28 | @Override
29 | public LocaleData.TranslationMap get() {
30 | return mInternalCache.get();
31 | }
32 |
33 | @Nullable
34 | @Override
35 | public String get(@NonNull String key, @NonNull String locale) {
36 | return mInternalCache.get(key, locale);
37 | }
38 |
39 | @Override
40 | public void update(@NonNull LocaleData.TranslationMap translationMap) {
41 | mInternalCache.update(translationMap);
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/TransifexNativeSDK/txsdk/src/main/java/com/transifex/txnative/cache/TxDiskTranslationsProvider.java:
--------------------------------------------------------------------------------
1 | package com.transifex.txnative.cache;
2 |
3 | import android.content.res.AssetManager;
4 | import android.util.Log;
5 |
6 | import com.transifex.common.LocaleData;
7 | import com.transifex.common.TranslationMapStorage;
8 | import com.transifex.txnative.TranslationMapStorageAndroid;
9 |
10 | import java.io.File;
11 | import java.io.IOException;
12 |
13 | import androidx.annotation.NonNull;
14 | import androidx.annotation.Nullable;
15 |
16 | /**
17 | * Translations provider that loads translations from disk or the application's raw asset files
18 | * depending on the constructor used.
19 | *
20 | * The directory should contain the translations in the format detailed in
21 | * {@link TranslationMapStorage}.
22 | *
23 | * If an error occurs during initialization, {@link #getTranslations()} will return
24 | *
22 | * Storing the translations happens asynchronously on a background thread after
23 | * {@link #update(LocaleData.TranslationMap)} is called.
24 | */
25 | public class TxFileOutputCacheDecorator extends TxDecoratorCache {
26 |
27 | public static final String TAG = TxFileOutputCacheDecorator.class.getSimpleName();
28 |
29 | private final File mDstDirectory;
30 | private final Executor mExecutor;
31 |
32 | /**
33 | * Creates a new instance with a specific directory for storing the translations to the disk
34 | * and an internal cache.
35 | *
36 | * @param dstDirectory The destination directory to write the translations to when the
37 | * {@link #update(LocaleData.TranslationMap)} is called.
38 | * @param internalCache The internal cache.
39 | */
40 | public TxFileOutputCacheDecorator(@NonNull File dstDirectory, @NonNull TxCache internalCache) {
41 | this(null, dstDirectory, internalCache);
42 | }
43 |
44 |
45 | /**
46 | * Creates a new instance with a specific directory for storing the translations to the disk
47 | * and an internal cache.
48 | *
49 | * @param executor The executor that will run the IO operations; if
66 | * For the serialization and writing of the translations on disk, {@link TranslationMapStorage}
67 | * is used internally. Each translation file uses "txstrings.json" as filename. Unlike
68 | * TranslationMapStorage, pre-existing translations are not kept.
69 | */
70 | @Override
71 | public void update(@NonNull final LocaleData.TranslationMap translationMap) {
72 | super.update(translationMap);
73 |
74 | try {
75 | mExecutor.execute(new Runnable() {
76 | @Override
77 | public void run() {
78 | // Delete existing translation files
79 | if (mDstDirectory.isDirectory()) {
80 | Utils.deleteDirectoryContents(mDstDirectory);
81 | }
82 | // Write translation files
83 | TranslationMapStorage storage = new TranslationMapStorage(TranslationMapStorage.DEFAULT_TRANSLATION_FILENAME);
84 | HashMap
12 | * Example usage:
13 | *
27 | * The translations providers update the internal cache in the given order. If they return a
28 | *
31 | * The providers' content is accessed synchronously in the class's constructor.
32 | *
33 | * @param providers An array of translations providers.
34 | * @param internalCache The internal cache to be used.
35 | */
36 | public TxProviderBasedCache(@NonNull TxTranslationsProvider[] providers, @NonNull TxCache internalCache) {
37 | super(internalCache);
38 |
39 | for (TxTranslationsProvider provider : providers) {
40 | LocaleData.TranslationMap translations = provider.getTranslations();
41 | if (translations != null && !translations.isEmpty()) {
42 | mInternalCache.update(provider.getTranslations());
43 | }
44 | }
45 | }
46 | }
47 |
--------------------------------------------------------------------------------
/TransifexNativeSDK/txsdk/src/main/java/com/transifex/txnative/cache/TxReadonlyCacheDecorator.java:
--------------------------------------------------------------------------------
1 | package com.transifex.txnative.cache;
2 |
3 | import com.transifex.common.LocaleData;
4 |
5 | import androidx.annotation.NonNull;
6 |
7 | /**
8 | * Decorator class that makes the internal cache read-only so that no update operations are allowed.
9 | */
10 | public class TxReadonlyCacheDecorator extends TxDecoratorCache {
11 |
12 | public TxReadonlyCacheDecorator(@NonNull TxCache internalCache) {
13 | super(internalCache);
14 | }
15 |
16 | /**
17 | * This method is a no-op as this cache decorator is read-only.
18 | */
19 | @Override
20 | public void update(@NonNull LocaleData.TranslationMap translationMap) {
21 | // No-op
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/TransifexNativeSDK/txsdk/src/main/java/com/transifex/txnative/cache/TxStandardCache.java:
--------------------------------------------------------------------------------
1 | package com.transifex.txnative.cache;
2 |
3 | import android.content.Context;
4 |
5 | import com.transifex.common.TranslationMapStorage;
6 |
7 | import java.io.File;
8 |
9 | import androidx.annotation.NonNull;
10 | import androidx.annotation.Nullable;
11 |
12 | /**
13 | * The standard cache configuration that the TxNative SDK is initialized with, if no other cache is
14 | * provided.
15 | *
16 | * TxStandardCache is backed by a {@link TxMemoryCache} which is initialized with existing translation
17 | * files from the app's Assets folder and the app's cache directory in this specific order. When
18 | * the cache is updated (when new translations become available after calling
19 | * {@link com.transifex.txnative.TxNative#fetchTranslations(String)}), TxStandardCache stores the new
20 | * translations in the app's cache directory. The in-memory translations are not affected by the
21 | * update though. A new instance of TxStandardCache has to be created (after an app restart by default)
22 | * to read the new translations saved in the app's cache directory
23 | */
24 | public class TxStandardCache {
25 |
26 | /**
27 | * Creates a cache with the configuration explained in {@link TxStandardCache}.
28 | *
29 | * @param context The app's context.
30 | * @param updatePolicy The update policy to be used when initializing the internal memory
31 | * cache with the stored contents from disk. If set to
13 | * You can use this policy to fall back to translations provided via
26 | * This constructor has been deprecated. A context is no longer needed.
27 | *
28 | * @param applicationContext The application context. Do not provide a context wrapped by
29 | * {@link com.transifex.txnative.TxNative#wrap(Context) TxNative#wrap(Context)}.
30 | */
31 | @Deprecated
32 | public AndroidMissingPolicy(@NonNull Context applicationContext) {
33 | }
34 |
35 | /**
36 | * Returns a translated string using Android's localization system.
37 | *
38 | * The result is equivalent to calling {@link android.content.res.Resources#getText(int)}
39 | * without using TxNative functionality.
40 | */
41 | @Override
42 | @NonNull public CharSequence get(@NonNull Resources resources,
43 | @NonNull CharSequence sourceString, @StringRes int id,
44 | @NonNull String resourceName, @NonNull String locale) {
45 | return resources.getText(id);
46 | }
47 |
48 | /**
49 | * Returns a translated quantity string using Android's localization system.
50 | *
51 | * The result is equivalent to calling {@link android.content.res.Resources#getQuantityText(int, int)}
52 | * without using TxNative functionality.
53 | */
54 | @Override
55 | @NonNull public CharSequence getQuantityString(@NonNull Resources resources,
56 | @NonNull CharSequence sourceQuantityString, @PluralsRes int id, int quantity,
57 | @NonNull String resourceName, @NonNull String locale) {
58 | return resources.getQuantityText(id, quantity);
59 | }
60 | }
61 |
--------------------------------------------------------------------------------
/TransifexNativeSDK/txsdk/src/main/java/com/transifex/txnative/missingpolicy/CompositeMissingPolicy.java:
--------------------------------------------------------------------------------
1 | package com.transifex.txnative.missingpolicy;
2 |
3 | import android.content.res.Resources;
4 |
5 | import androidx.annotation.NonNull;
6 | import androidx.annotation.PluralsRes;
7 | import androidx.annotation.StringRes;
8 |
9 | /**
10 | * Combines multiple policies to create a complex result.
11 | *
12 | * The result of each policy is fed to the next policy as source.
13 | */
14 | public class CompositeMissingPolicy implements MissingPolicy{
15 |
16 | private final MissingPolicy[] mMissingPolicies;
17 |
18 | /**
19 | * Creates a new instance with the provided missing policies.
20 | *
21 | * The order of the missing policies is important; the result of each policy is fed to the next
22 | * policy as source.
23 | *
24 | * @param missingPolicies The missing policies to be used.
25 | */
26 | public CompositeMissingPolicy(@NonNull MissingPolicy[] missingPolicies) {
27 | mMissingPolicies = missingPolicies;
28 | }
29 |
30 | /**
31 | * Returns a string after it has been fed to all of the provided policies from first to last.
32 | */
33 | @Override
34 | @NonNull public CharSequence get(@NonNull Resources resources,
35 | @NonNull CharSequence sourceString, @StringRes int id,
36 | @NonNull String resourceName, @NonNull String locale) {
37 | CharSequence string = sourceString;
38 | for (MissingPolicy policy : mMissingPolicies) {
39 | string = policy.get(resources, string, id, resourceName, locale);
40 | }
41 |
42 | return string;
43 | }
44 |
45 | /**
46 | * Returns a quantity string after it has been fed to all of the provided policies from first
47 | * to last.
48 | */
49 | @Override
50 | @NonNull public CharSequence getQuantityString(@NonNull Resources resources,
51 | @NonNull CharSequence sourceQuantityString, @PluralsRes int id, int quantity,
52 | @NonNull String resourceName, @NonNull String locale) {
53 | CharSequence quantityString = sourceQuantityString;
54 | for (MissingPolicy policy : mMissingPolicies) {
55 | quantityString = policy.getQuantityString(resources, quantityString, id, quantity,
56 | resourceName, locale);
57 | }
58 |
59 | return quantityString;
60 | }
61 | }
62 |
--------------------------------------------------------------------------------
/TransifexNativeSDK/txsdk/src/main/java/com/transifex/txnative/missingpolicy/MissingPolicy.java:
--------------------------------------------------------------------------------
1 | package com.transifex.txnative.missingpolicy;
2 |
3 | import android.content.res.Resources;
4 |
5 | import com.transifex.txnative.LocaleState;
6 | import com.transifex.txnative.TxResources;
7 |
8 | import androidx.annotation.NonNull;
9 | import androidx.annotation.PluralsRes;
10 | import androidx.annotation.StringRes;
11 |
12 | /**
13 | * An interface for classes that determine what translation is returned when the requested
14 | * translation is not available.
15 | */
16 | public interface MissingPolicy {
17 |
18 | /**
19 | * Return a string as a translation based on the given source string.
20 | *
21 | * Classes that implement this interface may choose to return anything relevant to the given
22 | * source string or not, based on their custom policy.
23 | *
24 | * @param resources A Resources object. This is the base resources object returned by
25 | * {@link TxResources#getBaseResources()}.
26 | * @param sourceString The source string.
27 | * @param id The string resource identifier as defined by
28 | * {@link Resources#getIdentifier(String, String, String)}.
29 | * @param resourceName The entry name of the string resource as defined by
30 | * {@link Resources#getResourceEntryName(int)}.
31 | * @param locale The current locale as returned by {@link LocaleState#getResolvedLocale()}.
32 | *
33 | * @return The translated string.
34 | */
35 | @NonNull CharSequence get(@NonNull Resources resources, @NonNull CharSequence sourceString,
36 | @StringRes int id, @NonNull String resourceName, @NonNull String locale);
37 |
38 | /**
39 | * Return a quantity string as a translation based on the given source quantity string and
40 | * quantity.
41 | *
42 | * Classes that implement this interface may choose to return anything relevant to the given
43 | * source string or not, based on their custom policy.
44 | *
45 | * @param resources A Resources object. This is the base resources object returned by
46 | * {@link TxResources#getBaseResources()}.
47 | * @param sourceQuantityString The source string having grammatically correct pluralization for
48 | * the given quantity.
49 | * @param id The plurals resource identifier as defined by
50 | * {@link Resources#getIdentifier(String, String, String)}.
51 | * @param quantity The number used to get the correct string for the current language's plural
52 | * rules.
53 | * @param resourceName The entry name of the plurals resource as defined by
54 | * {@link Resources#getResourceEntryName(int)}.
55 | * @param locale The current locale as returned by {@link LocaleState#getResolvedLocale()}.
56 | *
57 | * @return The translated string.
58 | */
59 | @NonNull CharSequence getQuantityString(@NonNull Resources resources,
60 | @NonNull CharSequence sourceQuantityString,
61 | @PluralsRes int id, int quantity,
62 | @NonNull String resourceName, @NonNull String locale);
63 | }
64 |
--------------------------------------------------------------------------------
/TransifexNativeSDK/txsdk/src/main/java/com/transifex/txnative/missingpolicy/SourceStringPolicy.java:
--------------------------------------------------------------------------------
1 | package com.transifex.txnative.missingpolicy;
2 |
3 | import android.content.res.Resources;
4 |
5 | import androidx.annotation.NonNull;
6 | import androidx.annotation.PluralsRes;
7 | import androidx.annotation.StringRes;
8 |
9 | /**
10 | * Returns the source string when the translation string is missing.
11 | */
12 | public class SourceStringPolicy implements MissingPolicy {
13 |
14 | /**
15 | * Return the source string as the translation string.
16 | */
17 | @Override
18 | @NonNull public CharSequence get(@NonNull Resources resources,
19 | @NonNull CharSequence sourceString, @StringRes int id,
20 | @NonNull String resourceName, @NonNull String locale) {
21 | return sourceString;
22 | }
23 |
24 | /**
25 | * Returns the source quantity string as the translation quantity string.
26 | */
27 | @Override
28 | @NonNull public CharSequence getQuantityString(@NonNull Resources resources,
29 | @NonNull CharSequence sourceQuantityString, @PluralsRes int id, int quantity,
30 | @NonNull String resourceName, @NonNull String locale) {
31 | return sourceQuantityString;
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/TransifexNativeSDK/txsdk/src/main/java/com/transifex/txnative/missingpolicy/WrappedStringPolicy.java:
--------------------------------------------------------------------------------
1 | package com.transifex.txnative.missingpolicy;
2 |
3 |
4 | import android.content.res.Resources;
5 | import android.text.SpannableStringBuilder;
6 | import android.text.Spanned;
7 | import android.text.SpannedString;
8 | import android.text.TextUtils;
9 |
10 | import androidx.annotation.NonNull;
11 | import androidx.annotation.Nullable;
12 | import androidx.annotation.PluralsRes;
13 | import androidx.annotation.StringRes;
14 |
15 | /**
16 | * Wraps the source string with a custom format.
17 | *
18 | * Example:
19 | *
20 | * {@code new WrappedStringPolicy(">>", "<<").get("Click here");}
21 | *
22 | * Returns:
23 | *
24 | * {@code ">>Click here<<"}
25 | */
26 | public class WrappedStringPolicy implements MissingPolicy{
27 |
28 | private final String start;
29 | private final String end;
30 | private final int length;
31 |
32 | /**
33 | * Creates a new instance with the provided strings.
34 | *
35 | * @param start The string to prepend the source String. Can be
55 | * If sourceString is {@link Spanned}, a {@link SpannedString} containing the same spans is
56 | * returned.
57 | */
58 | @NonNull CharSequence wrapString(@NonNull CharSequence sourceString) {
59 | if (TextUtils.isEmpty(start) && TextUtils.isEmpty(end)) {
60 | return sourceString;
61 | }
62 |
63 | boolean isSpanned = sourceString instanceof Spanned;
64 | if (isSpanned) {
65 | SpannableStringBuilder sb = new SpannableStringBuilder();
66 | if (!TextUtils.isEmpty(start)) {
67 | sb.append(start);
68 | }
69 | sb.append(sourceString);
70 | if (!TextUtils.isEmpty(end)) {
71 | sb.append(end);
72 | }
73 | return new SpannedString(sb);
74 | }
75 | else {
76 | StringBuilder sb = new StringBuilder(sourceString.length() + length);
77 | if (!TextUtils.isEmpty(start)) {
78 | sb.append(start);
79 | }
80 | sb.append(sourceString);
81 | if (!TextUtils.isEmpty(end)) {
82 | sb.append(end);
83 | }
84 | return sb.toString();
85 | }
86 | }
87 |
88 | /**
89 | * Returns a wrapped string.
90 | */
91 | @Override
92 | @NonNull public CharSequence get(@NonNull Resources resources,
93 | @NonNull CharSequence sourceString, @StringRes int id,
94 | @NonNull String resourceName, @NonNull String locale) {
95 | return wrapString(sourceString);
96 | }
97 |
98 | /**
99 | * Returns a wrapped quantity string.
100 | */
101 | @Override
102 | @NonNull public CharSequence getQuantityString(@NonNull Resources resources,
103 | @NonNull CharSequence sourceQuantityString, @PluralsRes int id, int quantity,
104 | @NonNull String resourceName, @NonNull String locale) {
105 | return wrapString(sourceQuantityString);
106 | }
107 | }
108 |
--------------------------------------------------------------------------------
/TransifexNativeSDK/txsdk/src/main/java/com/transifex/txnative/transformers/SupportToolbarTransformer.java:
--------------------------------------------------------------------------------
1 | package com.transifex.txnative.transformers;
2 |
3 |
4 | import android.content.Context;
5 | import android.util.AttributeSet;
6 | import android.view.View;
7 |
8 | import com.transifex.txnative.Utils;
9 |
10 | import androidx.annotation.NonNull;
11 | import androidx.appcompat.widget.Toolbar;
12 |
13 | public class SupportToolbarTransformer extends ViewTransformer {
14 |
15 | @Override
16 | public void transform(@NonNull Context context, @NonNull View view, @NonNull AttributeSet attrs) {
17 | super.transform(context, view, attrs);
18 |
19 | Toolbar toolbar = (Toolbar) view;
20 |
21 | int titleResourceId = Utils.getStringResourceId(context, attrs, android.R.attr.title);
22 | int titleCompatResourceId = Utils.getStringResourceId(context, attrs, androidx.appcompat.R.attr.title);
23 | if (titleResourceId != 0) {
24 | toolbar.setTitle(titleResourceId);
25 | }
26 | else if (titleCompatResourceId != 0) {
27 | toolbar.setTitle(titleCompatResourceId);
28 | }
29 |
30 | int subtitleResourceId = Utils.getStringResourceId(context, attrs, android.R.attr.subtitle);
31 | int subtitleCompatResourceId = Utils.getStringResourceId(context, attrs, androidx.appcompat.R.attr.subtitle);
32 | if (subtitleResourceId != 0) {
33 | toolbar.setSubtitle(subtitleResourceId);
34 | }
35 | else if (subtitleCompatResourceId != 0) {
36 | toolbar.setSubtitle(subtitleCompatResourceId);
37 | }
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/TransifexNativeSDK/txsdk/src/main/java/com/transifex/txnative/transformers/TextInputLayoutTransformer.java:
--------------------------------------------------------------------------------
1 | package com.transifex.txnative.transformers;
2 |
3 | import android.content.Context;
4 | import android.util.AttributeSet;
5 | import android.view.View;
6 |
7 | import com.google.android.material.textfield.TextInputLayout;
8 | import com.transifex.txnative.Utils;
9 |
10 | import androidx.annotation.NonNull;
11 |
12 | public class TextInputLayoutTransformer extends ViewTransformer{
13 |
14 | @Override
15 | public void transform(@NonNull Context context, @NonNull View view, @NonNull AttributeSet attrs) {
16 | super.transform(context, view, attrs);
17 |
18 | TextInputLayout textInputLayout = (TextInputLayout) view;
19 |
20 | int hintResourceId = Utils.getStringResourceId(context, attrs, android.R.attr.hint);
21 | if (hintResourceId != 0) {
22 | textInputLayout.setHint(hintResourceId);
23 | }
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/TransifexNativeSDK/txsdk/src/main/java/com/transifex/txnative/transformers/TextViewTransformer.java:
--------------------------------------------------------------------------------
1 | package com.transifex.txnative.transformers;
2 |
3 | import android.content.Context;
4 | import android.util.AttributeSet;
5 | import android.view.View;
6 | import android.widget.TextView;
7 |
8 | import com.transifex.txnative.Utils;
9 |
10 | import androidx.annotation.NonNull;
11 |
12 | public class TextViewTransformer extends ViewTransformer {
13 |
14 | @Override
15 | public void transform(@NonNull Context context, @NonNull View view, @NonNull AttributeSet attrs) {
16 | super.transform(context, view, attrs);
17 |
18 | TextView textView = (TextView) view;
19 |
20 | int textResourceId = Utils.getStringResourceId(context, attrs, android.R.attr.text);
21 | if (textResourceId != 0) {
22 | //String textResourceName = context.getResources().getResourceEntryName(textResourceId);
23 |
24 | // This will be handled by our overridden resources
25 | textView.setText(textResourceId);
26 | }
27 |
28 | int hintResourceId = Utils.getStringResourceId(context, attrs, android.R.attr.hint);
29 | if (hintResourceId != 0) {
30 | textView.setHint(hintResourceId);
31 | }
32 | }
33 |
34 | }
35 |
36 |
--------------------------------------------------------------------------------
/TransifexNativeSDK/txsdk/src/main/java/com/transifex/txnative/transformers/ToolbarTransformer.java:
--------------------------------------------------------------------------------
1 | package com.transifex.txnative.transformers;
2 |
3 | import android.content.Context;
4 | import android.os.Build;
5 | import android.util.AttributeSet;
6 | import android.view.View;
7 | import android.widget.Toolbar;
8 |
9 | import com.transifex.txnative.Utils;
10 |
11 | import androidx.annotation.NonNull;
12 | import androidx.annotation.RequiresApi;
13 |
14 | @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
15 | public class ToolbarTransformer extends ViewTransformer {
16 |
17 |
18 | @Override
19 | public void transform(@NonNull Context context, @NonNull View view, @NonNull AttributeSet attrs) {
20 | super.transform(context, view, attrs);
21 |
22 | Toolbar toolbar = (Toolbar) view;
23 |
24 | CharSequence xa = toolbar.getTitle();
25 |
26 | int titleResourceId = Utils.getStringResourceId(context, attrs, android.R.attr.title);
27 | int titleCompatResourceId = 0;
28 | if (Utils.isAppcompatPresent()) {
29 | titleCompatResourceId = Utils.getStringResourceId(context, attrs, androidx.appcompat.R.attr.title);
30 | }
31 | if (titleResourceId != 0) {
32 | toolbar.setTitle(titleResourceId);
33 | }
34 | else if (titleCompatResourceId != 0) {
35 | toolbar.setTitle(titleCompatResourceId);
36 | }
37 |
38 |
39 | int subtitleResourceId = Utils.getStringResourceId(context, attrs, android.R.attr.subtitle);
40 | int subtitleCompatResourceId = 0;
41 | if (Utils.isAppcompatPresent()) {
42 | subtitleCompatResourceId = Utils.getStringResourceId(context, attrs, androidx.appcompat.R.attr.subtitle);
43 | }
44 | if (subtitleResourceId != 0) {
45 | toolbar.setSubtitle(subtitleResourceId);
46 | }
47 | else if (subtitleCompatResourceId != 0) {
48 | toolbar.setSubtitle(subtitleCompatResourceId);
49 | }
50 | }
51 |
52 | }
53 |
--------------------------------------------------------------------------------
/TransifexNativeSDK/txsdk/src/main/java/com/transifex/txnative/transformers/ViewTransformer.java:
--------------------------------------------------------------------------------
1 | package com.transifex.txnative.transformers;
2 |
3 | import android.content.Context;
4 | import android.util.AttributeSet;
5 | import android.view.View;
6 |
7 | import com.transifex.txnative.Utils;
8 |
9 | import androidx.annotation.NonNull;
10 |
11 | public class ViewTransformer {
12 |
13 | public void transform(@NonNull Context context, @NonNull View view, @NonNull AttributeSet attrs) {
14 | int contentDescriptionId = Utils.getStringResourceId(context, attrs, android.R.attr.contentDescription);
15 | if (contentDescriptionId != 0) {
16 | view.setContentDescription(context.getString(contentDescriptionId));
17 | }
18 |
19 | if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.O) {
20 | int tooltipTextId = Utils.getStringResourceId(context, attrs, android.R.attr.tooltipText);
21 | if (tooltipTextId != 0) {
22 | view.setTooltipText(context.getString(tooltipTextId));
23 | }
24 | }
25 | }
26 |
27 | }
28 |
--------------------------------------------------------------------------------
/TransifexNativeSDK/txsdk/src/main/java/com/transifex/txnative/wrappers/TxContextWrapper.java:
--------------------------------------------------------------------------------
1 | package com.transifex.txnative.wrappers;
2 |
3 | import android.annotation.SuppressLint;
4 | import android.content.Context;
5 | import android.content.ContextWrapper;
6 | import android.content.res.Resources;
7 |
8 | import com.transifex.txnative.NativeCore;
9 | import com.transifex.txnative.TxResources;
10 |
11 | import androidx.annotation.NonNull;
12 |
13 | /**
14 | * Context wrapper that enables TxNative functionality by wrapping the base resources with
15 | * {@link TxResources}.
16 | */
17 | public class TxContextWrapper extends ContextWrapper {
18 |
19 | private Resources mWrappedResources;
20 | private final NativeCore mNativeCore;
21 |
22 | public TxContextWrapper(@NonNull Context base, @NonNull NativeCore nativeCore) {
23 | super(base);
24 | mNativeCore = nativeCore;
25 | }
26 |
27 | @SuppressLint("RestrictedApi")
28 | @Override
29 | public Resources getResources() {
30 | if (mWrappedResources == null) {
31 | mWrappedResources = new TxResources(super.getResources(), mNativeCore);
32 | }
33 | return mWrappedResources;
34 | }
35 | }
--------------------------------------------------------------------------------
/TransifexNativeSDK/txsdk/src/main/java/com/transifex/txnative/wrappers/TxContextWrappingDelegate2.kt:
--------------------------------------------------------------------------------
1 | package androidx.appcompat.app;
2 |
3 | import android.content.Context
4 | import android.content.res.Configuration
5 | import android.os.Bundle
6 | import android.util.AttributeSet
7 | import android.view.MenuInflater
8 | import android.view.View
9 | import android.view.ViewGroup
10 | import androidx.appcompat.view.ActionMode
11 | import androidx.appcompat.widget.Toolbar
12 | import com.transifex.txnative.TxNative
13 |
14 | // This class makes resource interception work in Appcompat 1.2.0 and later
15 | // https://github.com/JcMinarro/Philology/issues/35
16 | // https://stackoverflow.com/questions/55265834/change-locale-not-work-after-migrate-to-androidx/58004553#58004553
17 |
18 | /**
19 | * Wrapper of AppcompatDelegate that enables TxNative functionality by wrapping the base context.
20 | */
21 | class TxContextWrappingDelegate2(private val superDelegate: AppCompatDelegate) : AppCompatDelegate() {
22 |
23 | override fun getSupportActionBar() = superDelegate.supportActionBar
24 |
25 | override fun setSupportActionBar(toolbar: Toolbar?) = superDelegate.setSupportActionBar(toolbar)
26 |
27 | override fun getMenuInflater(): MenuInflater? = superDelegate.menuInflater
28 |
29 | override fun onCreate(savedInstanceState: Bundle?) {
30 | superDelegate.onCreate(savedInstanceState)
31 | removeActivityDelegate(superDelegate)
32 | addActiveDelegate(this)
33 | }
34 |
35 | override fun onPostCreate(savedInstanceState: Bundle?) = superDelegate.onPostCreate(savedInstanceState)
36 |
37 | override fun onConfigurationChanged(newConfig: Configuration?) = superDelegate.onConfigurationChanged(newConfig)
38 |
39 | override fun onStart() = superDelegate.onStart()
40 |
41 | override fun onStop() = superDelegate.onStop()
42 |
43 | override fun onPostResume() = superDelegate.onPostResume()
44 |
45 | override fun setTheme(themeResId: Int) = superDelegate.setTheme(themeResId)
46 |
47 | override fun filesMap
using the respective locale code
39 | * as key.
40 | */
41 | private static class DownloadTranslationsCallback implements CDSHandler.FetchCallback {
42 |
43 | File directory;
44 | String filename;
45 |
46 | HashMapnull
,
97 | * it will fetch translations for the locale codes configured in the
98 | * {@link CDSHandler} instance provided in the constructor.
99 | * @param tags An optional set of tags. If defined, only strings that have all of the given tags
100 | * will be fetched.
101 | * @param directory The directory on which to save the translations. The directory should
102 | * already exist.
103 | * @param filename The name of the translation file for a locale.
104 | *
105 | * @return A key-value map where each locale code points to the downloaded file containing the
106 | * translations. If an error occurs, some or all locale codes will be missing from the map.
107 | */
108 | @NonNull
109 | public HashMaptrue
if and only if the file or directory is successfully deleted;
37 | * false
otherwise
38 | */
39 | public static boolean deleteDirectory(File directoryToBeDeleted) {
40 | File[] allContents = directoryToBeDeleted.listFiles();
41 | if (allContents != null) {
42 | for (File file : allContents) {
43 | deleteDirectory(file);
44 | }
45 | }
46 | return directoryToBeDeleted.delete();
47 | }
48 |
49 | /**
50 | * Deletes the directory's contents
51 | *
52 | * @return true
if it's a directory and it's content is successfully deleted or
53 | * it's already empty; false
otherwise
54 | */
55 | public static boolean deleteDirectoryContents(File directoryToBeDeleted) {
56 | File[] allContents = directoryToBeDeleted.listFiles();
57 | boolean success = false;
58 | if (allContents != null) {
59 | success = true;
60 | for (File file : allContents) {
61 | success &= deleteDirectory(file);
62 | }
63 | }
64 | return success;
65 | }
66 |
67 | /**
68 | * URL encodes the provided string.
69 | */
70 | public static String urlEncode(String string) throws UnsupportedEncodingException {
71 | // Fixes URLEncoder's escaping of " " with "+" so that it works with URLs
72 | return URLEncoder.encode(string, "UTF-8").replace("+", "%20");
73 | }
74 | }
75 |
--------------------------------------------------------------------------------
/TransifexNativeSDK/common/src/test/java/com/transifex/common/PluralsTest.java:
--------------------------------------------------------------------------------
1 | package com.transifex.common;
2 |
3 | import org.junit.Test;
4 | import org.junit.function.ThrowingRunnable;
5 |
6 | import static com.google.common.truth.Truth.assertThat;
7 | import static org.junit.Assert.assertThrows;
8 |
9 | public class PluralsTest {
10 |
11 | @Test
12 | public void testFromICUString_normal() {
13 | String icuString = "{???, plural, zero {task0} one {task} two {task2} few {tasks} many {tasksss} other {tasks}}";
14 | Plurals plurals = Plurals.fromICUString(icuString);
15 |
16 | assertThat(plurals).isNotNull();
17 | assertThat(plurals.zero).isEqualTo("task0");
18 | assertThat(plurals.one).isEqualTo("task");
19 | assertThat(plurals.two).isEqualTo("task2");
20 | assertThat(plurals.few).isEqualTo("tasks");
21 | assertThat(plurals.many).isEqualTo("tasksss");
22 | assertThat(plurals.other).isEqualTo("tasks");
23 | }
24 |
25 | @Test
26 | public void testFromICUString_oneIncorrectPluralStyle_parseRestOfThem() {
27 | String icuString = "{???, plural, WRONG {task} other {tasks}}";
28 | Plurals plurals = Plurals.fromICUString(icuString);
29 |
30 | assertThat(plurals).isNotNull();
31 | assertThat(plurals.zero).isNull();
32 | assertThat(plurals.one).isNull();
33 | assertThat(plurals.two).isNull();
34 | assertThat(plurals.few).isNull();
35 | assertThat(plurals.many).isNull();
36 | assertThat(plurals.other).isEqualTo("tasks");
37 | }
38 |
39 | @Test
40 | public void testFromICUString_emptyString_returnNull() {
41 | String icuString = "";
42 | Plurals plurals = Plurals.fromICUString(icuString);
43 |
44 | assertThat(plurals).isNull();
45 | }
46 |
47 | @Test
48 | public void testBuilder_normal() {
49 | Plurals.Builder sb = new Plurals.Builder();
50 | sb.setZero("none")
51 | .setOne("just one")
52 | .setTwo("just two")
53 | .setFew("a few")
54 | .setMany("many")
55 | .setOther("other!");
56 | Plurals plurals = sb.buildString();
57 |
58 | assertThat(plurals.zero).isEqualTo("none");
59 | assertThat(plurals.one).isEqualTo("just one");
60 | assertThat(plurals.two).isEqualTo("just two");
61 | assertThat(plurals.few).isEqualTo("a few");
62 | assertThat(plurals.many).isEqualTo("many");
63 | assertThat(plurals.other).isEqualTo("other!");
64 | }
65 |
66 | @Test
67 | public void testBuilder_otherNotSpecified_throwException() {
68 | Plurals.Builder sb = new Plurals.Builder();
69 |
70 | assertThrows(Plurals.InvalidPluralsConfiguration.class, new ThrowingRunnable() {
71 | @Override
72 | public void run() throws Throwable {
73 | sb.buildString();
74 | }
75 | });
76 | }
77 |
78 | @Test
79 | public void testBuilderSetPlural_normal() {
80 | Plurals.Builder sb = new Plurals.Builder();
81 | sb.setPlural(Plurals.PluralType.OTHER, "other!");
82 | Plurals plurals = sb.buildString();
83 |
84 | assertThat(plurals.zero).isNull();
85 | assertThat(plurals.one).isNull();
86 | assertThat(plurals.two).isNull();
87 | assertThat(plurals.few).isNull();
88 | assertThat(plurals.many).isNull();
89 | assertThat(plurals.other).isEqualTo("other!");
90 | }
91 |
92 | @Test
93 | public void testBuilderSetPlural_nonSupportedPluralType_throwException() {
94 | Plurals.Builder sb = new Plurals.Builder();
95 |
96 | assertThrows(Plurals.NonSupportedPluralTypeException.class, new ThrowingRunnable() {
97 | @Override
98 | public void run() throws Throwable {
99 | sb.setPlural("Invalid plural type", "test");
100 | }
101 | });
102 | }
103 |
104 | @Test
105 | public void testToICUString_normal() {
106 | Plurals.Builder sb = new Plurals.Builder();
107 | sb.setZero("none")
108 | .setOne("just one")
109 | .setTwo("just two")
110 | .setFew("a few")
111 | .setMany("many")
112 | .setOther("other!");
113 | Plurals plurals = sb.buildString();
114 |
115 | String icuString = plurals.toICUString();
116 |
117 | assertThat(icuString).isEqualTo("{cnt, plural, zero {none} one {just one} two {just two} few {a few} many {many} other {other!}}");
118 | }
119 |
120 | @Test
121 | public void testGetPlural_normal() {
122 | Plurals.Builder sb = new Plurals.Builder();
123 | sb.setZero("none")
124 | .setOne("just one")
125 | .setTwo("just two")
126 | .setFew("a few")
127 | .setMany("many")
128 | .setOther("other!");
129 | Plurals plurals = sb.buildString();
130 |
131 | assertThat(plurals.getPlural(Plurals.PluralType.ZERO)).isEqualTo("none");
132 | assertThat(plurals.getPlural(Plurals.PluralType.ONE)).isEqualTo("just one");
133 | assertThat(plurals.getPlural(Plurals.PluralType.TWO)).isEqualTo("just two");
134 | assertThat(plurals.getPlural(Plurals.PluralType.FEW)).isEqualTo("a few");
135 | assertThat(plurals.getPlural(Plurals.PluralType.MANY)).isEqualTo("many");
136 | assertThat(plurals.getPlural(Plurals.PluralType.OTHER)).isEqualTo("other!");
137 | }
138 |
139 | @Test
140 | public void testGetPlural_nonSupportedPluralType_throwException() {
141 | Plurals.Builder sb = new Plurals.Builder();
142 | sb.setZero("none")
143 | .setOne("just one")
144 | .setTwo("just two")
145 | .setFew("a few")
146 | .setMany("many")
147 | .setOther("other!");
148 | Plurals plurals = sb.buildString();
149 |
150 | assertThrows(Plurals.NonSupportedPluralTypeException.class, new ThrowingRunnable() {
151 | @Override
152 | public void run() throws Throwable {
153 | plurals.getPlural("Invalid plural type");
154 | }
155 | });
156 |
157 | }
158 | }
--------------------------------------------------------------------------------
/TransifexNativeSDK/common/src/testFixtures/java/com/transifex/common/StringTestData.java:
--------------------------------------------------------------------------------
1 | package com.transifex.common;
2 |
3 | import java.util.HashMap;
4 |
5 | public class StringTestData {
6 |
7 | public static LocaleData.LocaleStrings getElLocaleStrings() {
8 | HashMap"build/unitTestTempDir"
, but can optionally specify a different directory.
11 | * null
,
67 | * it will fetch translations for the locale codes provided in the constructor.
68 | * @param tags An optional set of tags. If defined, only strings that have all of the given tags
69 | * will be fetched.
70 | * @param callback A callback function to call when the operation is complete.
71 | */
72 | public void fetchTranslationsAsync(@Nullable final String localeCode,
73 | @Nullable final Setnull
if the directory isn't found or it's empty.
51 | * If some locales fail to load, they won't be added in the returned map.
52 | */
53 | public @Nullable LocaleData.TranslationMap fromAssetsDirectory(@NonNull String srcDirectoryPath) {
54 | return fromDisk(assetFileProvider, assetFileProvider.getFile(srcDirectoryPath));
55 | }
56 |
57 | //region AssetFile
58 |
59 | /**
60 | * An implementation that uses Android's AssetManager.
61 | */
62 | private static class AssetFile implements AbstractFile {
63 |
64 | private final AssetManager manager;
65 | private final String pathname;
66 |
67 | public AssetFile(@NonNull AssetManager manager, @NonNull String pathname) {
68 | this.manager = manager;
69 | // Remove trailing slash
70 | if (pathname.endsWith("/")) {
71 | pathname = pathname.substring(0, pathname.length() - 1);
72 | }
73 | this.pathname = pathname;
74 | }
75 |
76 | @Nullable
77 | @Override
78 | public String[] list() {
79 | try {
80 | // If the dir does not exist, it returns an empty list. The java.io.File#list()
81 | // returns null.
82 | return manager.list(pathname);
83 | } catch (IOException ignored) {}
84 |
85 | return null;
86 | }
87 |
88 | @NonNull
89 | @Override
90 | public InputStream open() throws IOException {
91 | return manager.open(pathname);
92 | }
93 |
94 | @NonNull
95 | @Override
96 | public String getPath() {
97 | return pathname;
98 | }
99 |
100 | @NonNull
101 | @Override
102 | public String getAbsolutePath() {
103 | // There is no absolute path
104 | return pathname;
105 | }
106 |
107 | @Override
108 | public boolean isDirectory() {
109 | // There is no way to check if it's a directory
110 | return true;
111 | }
112 | }
113 |
114 | /**
115 | * A provider that returns an {@link AssetFile}.
116 | */
117 | private static class AssetFileProvider implements AbstractFileProvider {
118 |
119 | private final AssetManager manager;
120 |
121 | public AssetFileProvider(@NonNull AssetManager manager) {
122 | this.manager = manager;
123 | }
124 |
125 | @NonNull
126 | @Override
127 | public AssetFile getFile(@NonNull String pathname) {
128 | return new AssetFile(manager, pathname);
129 | }
130 | }
131 |
132 | //endregion
133 | }
134 |
--------------------------------------------------------------------------------
/TransifexNativeSDK/txsdk/src/main/java/com/transifex/txnative/TxInterceptor.java:
--------------------------------------------------------------------------------
1 | package com.transifex.txnative;
2 |
3 | import android.content.Context;
4 | import android.os.Build;
5 | import android.util.AttributeSet;
6 | import android.view.View;
7 | import android.widget.TextView;
8 | import android.widget.Toolbar;
9 |
10 | import com.google.android.material.textfield.TextInputLayout;
11 | import com.transifex.txnative.transformers.SupportToolbarTransformer;
12 | import com.transifex.txnative.transformers.TextInputLayoutTransformer;
13 | import com.transifex.txnative.transformers.TextViewTransformer;
14 | import com.transifex.txnative.transformers.ToolbarTransformer;
15 | import com.transifex.txnative.transformers.ViewTransformer;
16 |
17 | import androidx.annotation.NonNull;
18 | import dev.b3nedikt.viewpump.InflateResult;
19 | import dev.b3nedikt.viewpump.Interceptor;
20 |
21 | /**
22 | * ViewPump interceptor that transforms inflated views using the appropriate
23 | * {@link ViewTransformer}.
24 | * Appcompat
don't use this class. Instead use
15 | * {@link TxBaseAppCompatActivity}.
16 | */
17 | @Deprecated
18 | class TxBaseActivity extends Activity {
19 |
20 |
21 | @Override
22 | protected void attachBaseContext(Context base) {
23 | // Wrap the Activity context
24 | super.attachBaseContext(TxNative.wrap(base));
25 | }
26 |
27 | @Override
28 | public Resources getResources() {
29 | // Calling "getBaseContext().getResources()", instead of "super.getResources()", returns the
30 | // resources straight from TxResources and makes sure that the underlying assets are updated.
31 | // "super.getResources()" returns a cached resources object which may contain older assets.
32 | return getBaseContext().getResources();
33 | }
34 |
35 | }
36 |
--------------------------------------------------------------------------------
/TransifexNativeSDK/txsdk/src/main/java/com/transifex/txnative/activity/TxBaseAppCompatActivity.java:
--------------------------------------------------------------------------------
1 | package com.transifex.txnative.activity;
2 |
3 | import com.transifex.txnative.TxNative;
4 |
5 | import androidx.annotation.NonNull;
6 | import androidx.appcompat.app.AppCompatActivity;
7 | import androidx.appcompat.app.AppCompatDelegate;
8 |
9 | /**
10 | * A base activity that extends AppCompatActivity and implements context wrapping so that the
11 | * TxNative functionality is enabled.
12 | * null
if the provided locale
28 | * does not exist in the cache or the key does not exist for this locale; empty string if the
29 | * string has not yet been translated for this locale
30 | */
31 | @Nullable String get(@NonNull String key, @NonNull String locale);
32 |
33 | /**
34 | * Update the cache with the provided
35 | * {@link LocaleData.TranslationMap TranslationMap}.
36 | * null
.
25 | */
26 | public class TxDiskTranslationsProvider implements TxTranslationsProvider {
27 |
28 | public static final String TAG = TxDiskTranslationsProvider.class.getSimpleName();
29 |
30 | private final LocaleData.TranslationMap mTranslations;
31 |
32 | /**
33 | * Initializes the provider with a file directory containing translations and loads them
34 | * synchronously.
35 | *
36 | * @param srcDirectory The directory containing translations in the expected format.
37 | */
38 | public TxDiskTranslationsProvider(@NonNull File srcDirectory) {
39 | // Make a check to avoid TranslationMapStorage complaining about directory not existing.
40 | if (!srcDirectory.isDirectory()) {
41 | Log.d(TAG, "Translations directory does not exist yet: " + srcDirectory.getPath());
42 | mTranslations = null;
43 | return;
44 | }
45 |
46 | TranslationMapStorage storage = new TranslationMapStorage(TranslationMapStorage.DEFAULT_TRANSLATION_FILENAME);
47 | mTranslations = storage.fromDisk(srcDirectory);
48 | }
49 |
50 | /**
51 | * Initializes the provider with a directory under the application's raw asset files and loads
52 | * the translations synchronously.
53 | *
54 | * @param manager An asset manager instance.
55 | * @param srcDirectoryPath The path to the directory containing translations in the expected
56 | * format.
57 | */
58 | public TxDiskTranslationsProvider(@NonNull AssetManager manager, @NonNull String srcDirectoryPath) {
59 | // Make a check and print a debug log
60 | boolean dirContainsTranslations = false;
61 | try {
62 | String[] files = manager.list(srcDirectoryPath);
63 | dirContainsTranslations = files.length != 0;
64 | } catch (IOException ignored) {
65 | }
66 | if (!dirContainsTranslations) {
67 | Log.d(TAG, "No translations exist in the Assets folder: " + srcDirectoryPath);
68 | mTranslations = null;
69 | return;
70 | }
71 |
72 | TranslationMapStorageAndroid storage = new TranslationMapStorageAndroid(manager,
73 | TranslationMapStorage.DEFAULT_TRANSLATION_FILENAME);
74 | mTranslations = storage.fromAssetsDirectory(srcDirectoryPath);
75 | }
76 |
77 | @Override
78 | @Nullable
79 | public LocaleData.TranslationMap getTranslations() {
80 | return mTranslations;
81 | }
82 | }
83 |
--------------------------------------------------------------------------------
/TransifexNativeSDK/txsdk/src/main/java/com/transifex/txnative/cache/TxFileOutputCacheDecorator.java:
--------------------------------------------------------------------------------
1 | package com.transifex.txnative.cache;
2 |
3 | import android.util.Log;
4 |
5 | import com.transifex.common.LocaleData;
6 | import com.transifex.common.TranslationMapStorage;
7 | import com.transifex.common.Utils;
8 |
9 | import java.io.File;
10 | import java.util.HashMap;
11 | import java.util.concurrent.Executor;
12 | import java.util.concurrent.Executors;
13 | import java.util.concurrent.RejectedExecutionException;
14 |
15 | import androidx.annotation.NonNull;
16 | import androidx.annotation.Nullable;
17 |
18 | /**
19 | * Decorator class responsible for storing any updates of the translations to a directory specified
20 | * in the constructor.
21 | * null
is
50 | * provided, {@link Executors#newSingleThreadExecutor()} is used.
51 | * @param dstDirectory The destination directory to write the translations to when the
52 | * {@link #update(LocaleData.TranslationMap)} is called.
53 | * @param internalCache The internal cache.
54 | */
55 | protected TxFileOutputCacheDecorator(@Nullable Executor executor, @NonNull File dstDirectory,
56 | @NonNull TxCache internalCache) {
57 | super(internalCache);
58 | mExecutor = (executor != null) ? executor : Executors.newSingleThreadExecutor();
59 | mDstDirectory = dstDirectory;
60 | }
61 |
62 | /**
63 | * Updates the cache with the provided translations and writes them to the specified directory
64 | * after clearing its content, if any.
65 | *
14 | * TxCache cache = new TxProviderBasedCache(
15 | * new TxDiskTranslationsProvider[]{
16 | * new TxDiskTranslationsProvider(firstTranslationsDirectory),
17 | * new TxDiskTranslationsProvider(secondTranslationsDirectory)},
18 | * new TxMemoryCache());
19 | *
20 | */
21 | public class TxProviderBasedCache extends TxDecoratorCache {
22 |
23 | /**
24 | * Creates a provider-based cache with the given internal cache and updates it with with the
25 | * contents of the given translations providers.
26 | * null
or empty {@link LocaleData.TranslationMap} they are
29 | * ignored.
30 | * null
,
32 | * {@link TxUpdateFilterCache.TxCacheUpdatePolicy#REPLACE_ALL REPLACE_ALL} is used.
33 | * @param cachedTranslationsDirectory The directory where the cache will store new translations
34 | * when available and read translations from when initialized.
35 | * If set to null
it uses a "txnative" folder in
36 | * the app's internal cache directory.
37 | *
38 | * @return A TxCache instance.
39 | */
40 | public static TxCache getCache(@NonNull Context context,
41 | @Nullable @TxUpdateFilterCache.TxCacheUpdatePolicy Integer updatePolicy,
42 | @Nullable File cachedTranslationsDirectory) {
43 |
44 | if (updatePolicy == null) {
45 | updatePolicy = TxUpdateFilterCache.TxCacheUpdatePolicy.REPLACE_ALL;
46 | }
47 | if (cachedTranslationsDirectory == null) {
48 | cachedTranslationsDirectory = new File(context.getCacheDir() + File.separator
49 | + TranslationMapStorage.DEFAULT_TRANSLATIONS_DIR_NAME);
50 | }
51 |
52 | TxTranslationsProvider[] providers = new TxDiskTranslationsProvider[] {
53 | new TxDiskTranslationsProvider(
54 | context.getAssets(),
55 | TranslationMapStorage.DEFAULT_TRANSLATIONS_DIR_NAME),
56 | new TxDiskTranslationsProvider(cachedTranslationsDirectory)
57 | };
58 |
59 | return new TxFileOutputCacheDecorator(
60 | cachedTranslationsDirectory,
61 | new TxReadonlyCacheDecorator(
62 | new TxProviderBasedCache(
63 | providers,
64 | new TxUpdateFilterCache(
65 | updatePolicy,
66 | new TxMemoryCache()
67 | )
68 | )
69 | )
70 | );
71 | }
72 |
73 | }
74 |
--------------------------------------------------------------------------------
/TransifexNativeSDK/txsdk/src/main/java/com/transifex/txnative/cache/TxTranslationsProvider.java:
--------------------------------------------------------------------------------
1 | package com.transifex.txnative.cache;
2 |
3 | import com.transifex.common.LocaleData;
4 |
5 | import androidx.annotation.Nullable;
6 |
7 | /**
8 | * An interface for classes that act as providers of translations (e.g. extracting them from a file)
9 | */
10 | public interface TxTranslationsProvider {
11 |
12 | /**
13 | * Returns the translations from the provider.
14 | *
15 | * @return A {@link LocaleData.TranslationMap} object or null
if an error occurred.
16 | * The returned map can be empty if an error occurred.
17 | */
18 | @Nullable
19 | LocaleData.TranslationMap getTranslations();
20 | }
21 |
--------------------------------------------------------------------------------
/TransifexNativeSDK/txsdk/src/main/java/com/transifex/txnative/missingpolicy/AndroidMissingPolicy.java:
--------------------------------------------------------------------------------
1 | package com.transifex.txnative.missingpolicy;
2 |
3 | import android.content.Context;
4 | import android.content.res.Resources;
5 |
6 | import androidx.annotation.NonNull;
7 | import androidx.annotation.PluralsRes;
8 | import androidx.annotation.StringRes;
9 |
10 | /**
11 | * Returns a translated string using Android's localization system.
12 | * strings.xml
14 | * when a translation string can't be provided by TxNative's cache.
15 | */
16 | public class AndroidMissingPolicy implements MissingPolicy{
17 |
18 |
19 | public AndroidMissingPolicy(){
20 |
21 | }
22 |
23 | /**
24 | * Creates a new instance.
25 | * null
.
36 | * @param end The string to append to the source string. Can be null
.
37 | */
38 | public WrappedStringPolicy(@Nullable String start, @Nullable String end) {
39 | this.start = start;
40 | this.end = end;
41 |
42 | int length = 0;
43 | if (!TextUtils.isEmpty(start)) {
44 | length += start.length();
45 | }
46 | if (!TextUtils.isEmpty(end)) {
47 | length += end.length();
48 | }
49 | this.length = length;
50 | }
51 |
52 | /**
53 | * Wraps the provided sourceString with the start
and end
strings.
54 | *