├── .docs-config ├── styles.css └── typedoc.json ├── .github ├── dependabot.yml └── workflows │ ├── basic.yml │ ├── dependency-analysis.yml │ ├── docs.yml │ ├── lint-pr-title.yml │ ├── publish.yml │ └── release.yml ├── .gitignore ├── .husky └── pre-commit ├── .npmignore ├── .prettierrc ├── .release-it.json ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── EXAMPLES.md ├── LICENSE ├── README.md ├── eslint.config.js ├── jest.config.js ├── package-lock.json ├── package.json ├── src ├── ai │ └── index.ts ├── applications │ └── index.ts ├── bundles │ └── index.ts ├── clients │ └── index.ts ├── core │ ├── http-client-error.ts │ ├── index.ts │ └── internal │ │ ├── axios │ │ └── axiosProvider.ts │ │ ├── fetch │ │ ├── fetchClient.ts │ │ └── fetchClientError.ts │ │ └── retry │ │ └── index.ts ├── dictionaries │ └── index.ts ├── distributions │ └── index.ts ├── fields │ └── index.ts ├── glossaries │ └── index.ts ├── index.ts ├── issues │ └── index.ts ├── labels │ └── index.ts ├── languages │ └── index.ts ├── machineTranslation │ └── index.ts ├── notifications │ └── index.ts ├── organizationWebhooks │ └── index.ts ├── projectsGroups │ └── index.ts ├── reports │ └── index.ts ├── screenshots │ └── index.ts ├── securityLogs │ └── index.ts ├── sourceFiles │ └── index.ts ├── sourceStrings │ └── index.ts ├── stringComments │ └── index.ts ├── stringTranslations │ └── index.ts ├── tasks │ └── index.ts ├── teams │ └── index.ts ├── translationMemory │ └── index.ts ├── translationStatus │ └── index.ts ├── translations │ └── index.ts ├── uploadStorage │ └── index.ts ├── users │ └── index.ts ├── vendors │ └── index.ts ├── webhooks │ └── index.ts └── workflows │ └── index.ts ├── tests ├── ai │ └── api.test.ts ├── applications │ └── api.test.ts ├── bundles │ └── api.test.ts ├── clients │ └── api.test.ts ├── core │ └── error-handling.test.ts ├── dictionaries │ └── api.test.ts ├── distributions │ └── api.test.ts ├── fields │ └── api.test.ts ├── glossaries │ └── api.test.ts ├── internal │ └── retry │ │ └── retry.test.ts ├── issues │ └── api.test.ts ├── labels │ └── api.test.ts ├── languages │ └── api.test.ts ├── machineTranslation │ └── api.test.ts ├── notifications │ └── api.test.ts ├── organizationWebhooks │ └── api.test.ts ├── projectsGroups │ └── api.test.ts ├── reports │ └── api.test.ts ├── screenshots │ └── api.test.ts ├── securityLogs │ └── api.test.ts ├── sourceFiles │ ├── api.fetch.test.ts │ └── api.test.ts ├── sourceStrings │ └── api.test.ts ├── stringComments │ └── api.test.ts ├── stringTranslations │ └── api.test.ts ├── tasks │ └── api.test.ts ├── teams │ └── api.test.ts ├── translationMemory │ └── api.test.ts ├── translationStatus │ └── api.test.ts ├── translations │ └── api.test.ts ├── uploadStorage │ └── api.test.ts ├── users │ └── api.test.ts ├── vendors │ └── api.test.ts ├── webhooks │ └── api.test.ts └── workflows │ └── api.test.ts └── tsconfig.json /.docs-config/styles.css: -------------------------------------------------------------------------------- 1 | :root { 2 | /* Light */ 3 | --light-color-background: #fcfcfc; 4 | --light-color-secondary-background: #fff; 5 | --light-color-text: #263238; 6 | --light-color-text-aside: #707070; 7 | --light-color-link: #4da6ff; 8 | --light-color-menu-divider: #eee; 9 | --light-color-menu-divider-focus: #000; 10 | --light-color-menu-label: #707070; 11 | --light-color-panel: var(--light-color-secondary-background); 12 | --light-color-panel-divider: #eee; 13 | --light-color-comment-tag: #707070; 14 | --light-color-comment-tag-text: #fff; 15 | --light-color-ts: #43a047; 16 | --light-color-ts-interface: #647f1b; 17 | --light-color-ts-enum: #937210; 18 | --light-color-ts-class: #2196f3; 19 | --light-color-ts-private: #707070; 20 | --light-color-toolbar: #fff; 21 | --light-color-toolbar-text: #333; 22 | --light-icon-filter: invert(0); 23 | --light-external-icon: url("data:image/svg+xml;utf8,"); 24 | 25 | /* Dark */ 26 | --dark-color-background: #36393f; 27 | --dark-color-secondary-background: #2f3136; 28 | --dark-color-text: #ffffff; 29 | --dark-color-text-aside: #e6e4e4; 30 | --dark-color-link: #00aff4; 31 | --dark-color-menu-divider: #eee; 32 | --dark-color-menu-divider-focus: #000; 33 | --dark-color-menu-label: #707070; 34 | --dark-color-panel: var(--dark-color-secondary-background); 35 | --dark-color-panel-divider: #818181; 36 | --dark-color-comment-tag: #dcddde; 37 | --dark-color-comment-tag-text: #2f3136; 38 | --dark-color-ts: #c97dff; 39 | --dark-color-ts-interface: #9cbe3c; 40 | --dark-color-ts-enum: #d6ab29; 41 | --dark-color-ts-class: #3695f3; 42 | --dark-color-ts-private: #e2e2e2; 43 | --dark-color-toolbar: #34373c; 44 | --dark-color-toolbar-text: #ffffff; 45 | --dark-icon-filter: invert(1); 46 | --dark-external-icon: url("data:image/svg+xml;utf8,"); 47 | } 48 | -------------------------------------------------------------------------------- /.docs-config/typedoc.json: -------------------------------------------------------------------------------- 1 | { 2 | "entryPoints": ["../src/index.ts"], 3 | "out": "../docs", 4 | "customCss": "styles.css", 5 | "excludeInternal": true, 6 | "excludeProtected": true, 7 | "excludeExternals": true, 8 | "excludePrivate": true, 9 | "hideGenerator": true 10 | } -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | # To get started with Dependabot version updates, you'll need to specify which 2 | # package ecosystems to update and where the package manifests are located. 3 | # Please see the documentation for all configuration options: 4 | # https://docs.github.com/github/administering-a-repository/configuration-options-for-dependency-updates 5 | 6 | version: 2 7 | updates: 8 | - package-ecosystem: "npm" # See documentation for possible values 9 | directory: "/" # Location of package manifests 10 | target-branch: "master" 11 | schedule: 12 | interval: "weekly" 13 | groups: 14 | dev-dependencies: 15 | dependency-type: "development" 16 | ignore: 17 | - dependency-name: "*" #All the dependency it gonna ignore for major update 18 | update-types: [ "version-update:semver-major" ] 19 | - dependency-name: "typedoc" -------------------------------------------------------------------------------- /.github/workflows/basic.yml: -------------------------------------------------------------------------------- 1 | name: Tests 2 | 3 | on: 4 | pull_request: 5 | branches: 6 | - 'master' 7 | push: 8 | branches: 9 | - '*' 10 | paths-ignore: 11 | - 'README.md' 12 | - 'LICENSE' 13 | - 'CODE_OF_CONDUCT.md' 14 | - 'CONTRIBUTING.md' 15 | 16 | jobs: 17 | tests: 18 | strategy: 19 | matrix: 20 | node-version: [ 18.x, 20.x ] 21 | os: [ ubuntu-latest, windows-latest, macos-latest ] 22 | 23 | runs-on: ${{ matrix.os }} 24 | steps: 25 | - uses: actions/checkout@v4 26 | 27 | - name: Cache node modules 28 | uses: actions/cache@v4 29 | env: 30 | cache-name: cache-node-modules 31 | with: 32 | path: ~/.npm 33 | key: ${{ runner.os }}-build-${{ env.cache-name }}-${{ hashFiles('**/package-lock.json') }} 34 | restore-keys: | 35 | ${{ runner.os }}-build-${{ env.cache-name }}- 36 | ${{ runner.os }}-build- 37 | ${{ runner.os }}- 38 | 39 | - name: Use Node.js ${{ matrix.node-version }} 40 | uses: actions/setup-node@v4 41 | with: 42 | node-version: ${{ matrix.node-version }} 43 | cache: 'npm' 44 | 45 | - name: Print NPM version 46 | run: npm -v 47 | 48 | - name: Install dependencies 49 | run: npm install 50 | 51 | - name: Build 52 | run: npm run build 53 | 54 | - name: Tests 55 | run: npm run test 56 | 57 | - name: Publish Dry Run 58 | run: npm publish --dry-run 59 | 60 | code-coverage: 61 | runs-on: ubuntu-latest 62 | needs: tests 63 | steps: 64 | - uses: actions/checkout@v4 65 | 66 | - name: Cache node modules 67 | uses: actions/cache@v4 68 | env: 69 | cache-name: cache-node-modules 70 | with: 71 | path: ~/.npm 72 | key: ${{ runner.os }}-build-${{ env.cache-name }}-${{ hashFiles('**/package-lock.json') }} 73 | restore-keys: | 74 | ${{ runner.os }}-build-${{ env.cache-name }}- 75 | ${{ runner.os }}-build- 76 | ${{ runner.os }}- 77 | 78 | - name: Use Node.js 18.x 79 | uses: actions/setup-node@v4 80 | with: 81 | node-version: 18.x 82 | cache: 'npm' 83 | 84 | - name: Install dependencies 85 | run: npm install 86 | 87 | - name: Lint 88 | run: npm run lint-ci 89 | 90 | - name: Generate code coverage report 91 | run: npm run test-coverage 92 | 93 | - name: Test Report 94 | uses: dorny/test-reporter@v1 95 | if: ${{ (success() || failure()) && github.ref == 'refs/heads/master' }} 96 | with: 97 | name: Test results 98 | path: junit.xml 99 | reporter: jest-junit 100 | 101 | - name: Upload coverage report to Codecov 102 | uses: codecov/codecov-action@v4 103 | with: 104 | token: ${{ secrets.CODECOV_TOKEN }} 105 | -------------------------------------------------------------------------------- /.github/workflows/dependency-analysis.yml: -------------------------------------------------------------------------------- 1 | name: Dependency Analysis 2 | 3 | on: 4 | push: 5 | branches: 6 | - master 7 | schedule: 8 | - cron: '0 0 * * MON' 9 | workflow_dispatch: 10 | 11 | jobs: 12 | dependency-analysis: 13 | uses: crowdin/.github/.github/workflows/dependency-analysis.yml@main 14 | secrets: 15 | FOSSA_API_KEY: ${{ secrets.FOSSA_API_KEY }} 16 | -------------------------------------------------------------------------------- /.github/workflows/docs.yml: -------------------------------------------------------------------------------- 1 | name: Documentation 2 | 3 | on: 4 | release: 5 | types: [published] 6 | repository_dispatch: 7 | types: [publish] 8 | 9 | jobs: 10 | build-and-deploy-docs: 11 | runs-on: ubuntu-latest 12 | steps: 13 | - uses: actions/checkout@v4 14 | 15 | - name: Setup Node.js 16 | uses: actions/setup-node@v4 17 | with: 18 | node-version: 18 19 | cache: 'npm' 20 | 21 | - name: Install dependencies 22 | run: npm ci 23 | 24 | - name: Generate docs 25 | run: npm run generate-docs 26 | 27 | - name: Deploy 🚀 28 | uses: JamesIves/github-pages-deploy-action@v4 29 | with: 30 | branch: gh-pages 31 | folder: docs 32 | -------------------------------------------------------------------------------- /.github/workflows/lint-pr-title.yml: -------------------------------------------------------------------------------- 1 | name: lint-pr-title 2 | 3 | on: 4 | pull_request_target: 5 | types: 6 | - opened 7 | - reopened 8 | - edited 9 | - synchronize 10 | 11 | jobs: 12 | main: 13 | runs-on: ubuntu-latest 14 | 15 | steps: 16 | - uses: amannn/action-semantic-pull-request@v5 17 | env: 18 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 19 | -------------------------------------------------------------------------------- /.github/workflows/publish.yml: -------------------------------------------------------------------------------- 1 | name: Publish 2 | 3 | on: 4 | release: 5 | types: [published] 6 | repository_dispatch: 7 | types: [publish] 8 | workflow_dispatch: 9 | 10 | jobs: 11 | publish-npm: 12 | runs-on: ubuntu-latest 13 | permissions: 14 | contents: read 15 | id-token: write 16 | steps: 17 | - uses: actions/checkout@v4 18 | 19 | - uses: actions/setup-node@v4 20 | with: 21 | node-version: 18 22 | registry-url: https://registry.npmjs.org/ 23 | 24 | - name: Install npm CLI 25 | run: npm install -g npm@10 #todo upgrade to the latest 26 | 27 | - name: Install dependencies and build 28 | run: | 29 | npm -v 30 | npm ci 31 | npm run build 32 | 33 | - name: Publish Dry Run 34 | run: | 35 | npm publish --dry-run 36 | 37 | - name: Publish the package 38 | run: | 39 | npm publish --provenance 40 | env: 41 | NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} 42 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: Release 2 | run-name: Release ${{ github.event.inputs.version }} version 3 | 4 | on: 5 | workflow_dispatch: 6 | inputs: 7 | version: 8 | type: choice 9 | description: Version 10 | options: 11 | - patch 12 | - minor 13 | - major 14 | 15 | jobs: 16 | version: 17 | permissions: 18 | contents: write 19 | uses: crowdin/.github/.github/workflows/bump-version.yml@main 20 | 21 | publish: 22 | runs-on: ubuntu-latest 23 | needs: version 24 | permissions: 25 | contents: write 26 | steps: 27 | - uses: peter-evans/repository-dispatch@v3 28 | with: 29 | event-type: publish 30 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | out 2 | node_modules 3 | .idea 4 | coverage 5 | junit.xml 6 | .vscode 7 | sandbox.js 8 | docs 9 | .DS_Store -------------------------------------------------------------------------------- /.husky/pre-commit: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | . "$(dirname "$0")/_/husky.sh" 3 | 4 | npm run lint-ci && npm test 5 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | src 2 | ci 3 | tests 4 | .vscode 5 | .eslint 6 | .prettierrc 7 | .idea 8 | coverage 9 | junit.xml 10 | sandbox.js 11 | jestconfig.json 12 | tsconfig.json 13 | yarn.lock 14 | node_modules 15 | docs 16 | .docs-config 17 | .github 18 | eslint.config.js 19 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "semi": true, 3 | "trailingComma": "all", 4 | "singleQuote": true, 5 | "printWidth": 120, 6 | "tabWidth": 4 7 | } 8 | -------------------------------------------------------------------------------- /.release-it.json: -------------------------------------------------------------------------------- 1 | { 2 | "git": { 3 | "push": true, 4 | "commit": true, 5 | "commitMessage": "chore: version ${version} [skip ci]", 6 | "requireBranch": "master", 7 | "tag": true 8 | }, 9 | "github": { 10 | "release": true, 11 | "autoGenerate": true, 12 | "releaseName": "${version}" 13 | }, 14 | "npm": { 15 | "publish": false 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /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@crowdin.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 77 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 | :tada: First off, thanks for taking the time to contribute! :tada: 4 | 5 | The Crowdin API client provides methods that essentially call Crowdin's APIs. This makes it much easier for other developers to make calls to Crowdin's APIs, as the client abstracts a lot of the work required. In short, the API client provides a lightweight interface for making API requests to Crowdin. 6 | 7 | The following is a set of guidelines for contributing to Crowdin JavaScript Client. These are mostly guidelines, not rules. Use your best judgment, and feel free to propose changes to this document in a pull request. 8 | 9 | This project and everyone participating in it are governed by the [Code of Conduct](/CODE_OF_CONDUCT.md). By participating, you are expected to uphold this code. 10 | 11 | ## How can I contribute? 12 | 13 | ### Star this repo 14 | 15 | It's quick and goes a long way! :stars: 16 | 17 | ### Reporting Bugs 18 | 19 | This section guides you through submitting a bug report for Crowdin JavaScript Client. Following these guidelines helps maintainers, and the community understand your report :pencil:, reproduce the behavior :computer:, and find related reports :mag_right:. 20 | 21 | When you are creating a bug report, please include as many details as possible. 22 | 23 | #### How Do I Submit a Bug Report? 24 | 25 | Bugs are tracked as [GitHub issues](https://github.com/crowdin/crowdin-api-client-js/issues/). 26 | 27 | Explain the problem and include additional details to help reproduce the problem: 28 | 29 | * **Use a clear and descriptive title** for the issue to identify the problem. 30 | * **Describe the exact steps which reproduce the problem** in as many details as possible. Don't just say what you did, but explain how you did it. 31 | * **Describe the behavior you observed after following the steps** and point out what exactly is the problem with that behavior. 32 | * **Explain which behavior you expected to see instead and why.** 33 | 34 | Include details about your environment. 35 | 36 | ### Suggesting Enhancements 37 | 38 | This section guides you through submitting an enhancement suggestion for Crowdin JavaScript Client. Following these guidelines helps maintainers and the community understand your suggestion :pencil: and find related suggestions :mag_right:. 39 | 40 | When you are creating an enhancement suggestion, please include as many details as possible. 41 | 42 | #### How Do I Submit an Enhancement Suggestion? 43 | 44 | Enhancement suggestions are tracked as [GitHub issues](https://github.com/crowdin/crowdin-api-client-js/issues/). 45 | 46 | Create an issue on that repository and provide the following information: 47 | 48 | * **Use a clear and descriptive title** for the issue to identify the suggestion. 49 | * **Provide a step-by-step description of the suggested enhancement** in as many details as possible. 50 | * **Describe the current behavior** and **explain which behavior you expected to see instead** and why. 51 | * **Explain why this enhancement would be useful** to most JavaScript Client users. 52 | 53 | ### Your First Code Contribution 54 | 55 | Unsure where to begin contributing to Crowdin JavaScript Client? You can start by looking through these `good-first-issue` and `help-wanted` issues: 56 | 57 | * [Good first issue](https://github.com/crowdin/crowdin-api-client-js/issues?q=is%3Aopen+is%3Aissue+label%3A%22good+first+issue%22) - issues which should only require a small amount of code, and a test or two. 58 | * [Help wanted](https://github.com/crowdin/crowdin-api-client-js/issues?q=is%3Aopen+is%3Aissue+label%3A%22help+wanted%22) - issues which should be a bit more involved than `Good first issue` issues. 59 | 60 | #### Pull Request Checklist 61 | 62 | Before sending your pull requests, make sure you followed the list below: 63 | 64 | - Read these guidelines. 65 | - Read [Code of Conduct](/CODE_OF_CONDUCT.md). 66 | - Ensure that your code adheres to standard conventions, as used in the rest of the project. 67 | - Ensure that there are unit tests for your code. 68 | - Run unit tests. 69 | - Ensure that docs are correctly generating. 70 | 71 | > **Note** 72 | > This project uses the [Conventional Commits](https://www.conventionalcommits.org/en/v1.0.0/) specification for commit messages and PR titles. 73 | 74 | #### Docs 75 | 76 | ##### Generate 77 | 78 | To generate the docs run the following command: 79 | 80 | ```console 81 | npm run generate-docs 82 | ``` 83 | 84 | ##### Preview 85 | 86 | To preview the docs locally, run the following commands: 87 | 88 | ```console 89 | npm install http-server -g 90 | 91 | http-server docs 92 | ``` 93 | 94 | Open `http://127.0.0.1:8080` in browser 95 | 96 | #### Philosophy of code contribution 97 | 98 | - Include unit tests when you contribute new features, as they help to a) prove that your code works correctly, and b) guard against future breaking changes to lower the maintenance cost. 99 | - Bug fixes also generally require unit tests, because the presence of bugs usually indicates insufficient test coverage. 100 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 crowdin 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /eslint.config.js: -------------------------------------------------------------------------------- 1 | //@ts-check 2 | 3 | const eslint = require('@eslint/js'); 4 | const tseslint = require('typescript-eslint'); 5 | const eslintPrettierRecommended = require('eslint-plugin-prettier/recommended'); 6 | 7 | module.exports = tseslint.config( 8 | eslint.configs.recommended, 9 | tseslint.configs.recommended, 10 | eslintPrettierRecommended, 11 | { 12 | languageOptions: { 13 | parserOptions: { 14 | ecmaVersion: 2018, // Allows for the parsing of modern ECMAScript features 15 | sourceType: 'module' // Allows for the use of imports 16 | }, 17 | }, 18 | rules: { 19 | eqeqeq: 2, 20 | 'no-console': [2, { 'allow': ['warn', 'error'] }], 21 | 'no-throw-literal': 2, 22 | '@typescript-eslint/no-unused-vars': 2, 23 | '@typescript-eslint/no-explicit-any': 1, 24 | '@typescript-eslint/explicit-function-return-type': 2, 25 | '@typescript-eslint/no-namespace': 0, 26 | '@typescript-eslint/no-non-null-assertion': 'off', 27 | 'no-unused-expressions': 2, 28 | curly: 2, 29 | semi: 2, 30 | 'brace-style': 2, 31 | quotes: [2, 'single', 'avoid-escape'], 32 | 'prefer-rest-params': 'warn', 33 | }, 34 | ignores: ['**/node_modules/*', 'website/*', 'README.md', '**/npm/*'] 35 | } 36 | ); -------------------------------------------------------------------------------- /jest.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | testEnvironment: 'node', 3 | testRunner: 'jest-circus/runner', 4 | transform: { 5 | '^.+\\.(t|j)sx?$': 'ts-jest', 6 | }, 7 | moduleNameMapper: { 8 | /** 9 | * Jest will resolve the ESM build of axios by default, even though 10 | * it fully supports CJS. 11 | * @see https://github.com/axios/axios/issues/5101 12 | */ 13 | '^axios$': require.resolve('axios'), 14 | }, 15 | testRegex: '(/tests/.*|(\\.|/)(test|spec))\\.(jsx?|tsx?)$', 16 | moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx', 'json', 'node'], 17 | coverageReporters: ['text', 'cobertura'], 18 | }; 19 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@crowdin/crowdin-api-client", 3 | "version": "1.45.0", 4 | "description": "JavaScript library for Crowdin API", 5 | "main": "out/index.js", 6 | "types": "out/index.d.ts", 7 | "repository": { 8 | "type": "git", 9 | "url": "git+https://github.com/crowdin/crowdin-api-client-js.git" 10 | }, 11 | "files": [ 12 | "out/**/*" 13 | ], 14 | "scripts": { 15 | "prepare": "husky install", 16 | "test": "jest", 17 | "build": "tsc -p ./", 18 | "watch": "tsc -watch -p ./", 19 | "lint": "eslint --fix \"{src,tests}/**/*.{js,ts}\"", 20 | "lint-ci": "eslint \"{src,tests}/**/*.{js,ts}\"", 21 | "test-coverage": "jest --ci --reporters=jest-junit --reporters=default --coverage --coverageReporters=cobertura --coverageReporters=html", 22 | "generate-docs": "typedoc src/index.ts --options .docs-config/typedoc.json" 23 | }, 24 | "keywords": [ 25 | "Crowdin", 26 | "API", 27 | "client", 28 | "JavaScript", 29 | "TypeScript", 30 | "localization", 31 | "translation", 32 | "i18n", 33 | "l10n", 34 | "crowdin-api-client", 35 | "crowdin-api", 36 | "crowdin-client", 37 | "crowdin-js", 38 | "crowdin-js-client" 39 | ], 40 | "license": "MIT", 41 | "dependencies": { 42 | "axios": "^1" 43 | }, 44 | "devDependencies": { 45 | "@eslint/js": "^9.20.0", 46 | "@types/jest": "^27.0.0", 47 | "@types/nock": "^10.0.3", 48 | "@types/node": "^12.0.10", 49 | "eslint": "^9.20.1", 50 | "eslint-config-prettier": "^10.0.1", 51 | "eslint-plugin-prettier": "^5.2.3", 52 | "husky": "^7.0.0", 53 | "jest": "^27.0.0", 54 | "jest-circus": "^27.0.0", 55 | "jest-junit": "^15.0.0", 56 | "lint-staged": ">=8", 57 | "nock": "^10.0.6", 58 | "ts-jest": "^27.0.0", 59 | "typedoc": "^0.26.2", 60 | "typescript": "^4.1.2", 61 | "typescript-eslint": "^8.24.0" 62 | }, 63 | "engines": { 64 | "node": ">=12.9.0" 65 | }, 66 | "lint-staged": { 67 | "*.js": [ 68 | "eslint --fix", 69 | "git add" 70 | ] 71 | }, 72 | "bugs": { 73 | "url": "https://github.com/crowdin/crowdin-api-client-js/issues" 74 | }, 75 | "homepage": "https://github.com/crowdin/crowdin-api-client-js#readme", 76 | "directories": { 77 | "test": "tests" 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /src/applications/index.ts: -------------------------------------------------------------------------------- 1 | import { CrowdinApi, ResponseObject, PatchRequest, Pagination, ResponseList } from '../core'; 2 | 3 | /** 4 | * Crowdin Apps are web applications that can be integrated with Crowdin to extend its functionality. 5 | * 6 | * Use the API to manage the necessary app data. 7 | */ 8 | export class Applications extends CrowdinApi { 9 | /** 10 | * @param options optional pagination parameters for the request 11 | * @see https://developer.crowdin.com/api/v2/#operation/api.applications.installations.getMany 12 | */ 13 | listApplicationInstallations(options?: Pagination): Promise> { 14 | const url = `${this.url}/applications/installations`; 15 | return this.getList(url, options?.limit, options?.offset); 16 | } 17 | 18 | /** 19 | * @param request request body 20 | * @see https://developer.crowdin.com/api/v2/#operation/api.applications.installations.post 21 | */ 22 | installApplication( 23 | request: ApplicationsModel.InstallApplication, 24 | ): Promise> { 25 | const url = `${this.url}/applications/installations`; 26 | return this.post(url, request, this.defaultConfig()); 27 | } 28 | 29 | /** 30 | * @param applicationId application identifier 31 | * @see https://developer.crowdin.com/api/v2/#operation/api.applications.installations.get 32 | */ 33 | getApplicationInstallation(applicationId: string): Promise> { 34 | const url = `${this.url}/applications/installations/${applicationId}`; 35 | return this.get(url, this.defaultConfig()); 36 | } 37 | 38 | /** 39 | * @param applicationId application identifier 40 | * @param force force delete the application 41 | * @see https://developer.crowdin.com/api/v2/#operation/api.applications.installations.delete 42 | */ 43 | deleteApplicationInstallation( 44 | applicationId: string, 45 | force?: boolean, 46 | ): Promise> { 47 | const url = `${this.url}/applications/installations/${applicationId}`; 48 | if (force) { 49 | this.addQueryParam(url, 'force', String(force)); 50 | } 51 | return this.delete(url, this.defaultConfig()); 52 | } 53 | 54 | /** 55 | * @param applicationId application identifier 56 | * @param request request body 57 | * @see https://developer.crowdin.com/api/v2/#operation/api.applications.installations.patch 58 | */ 59 | editApplicationInstallation( 60 | applicationId: string, 61 | request: PatchRequest[], 62 | ): Promise> { 63 | const url = `${this.url}/applications/installations/${applicationId}`; 64 | return this.patch(url, request, this.defaultConfig()); 65 | } 66 | 67 | /** 68 | * @param applicationId application identifier 69 | * @param path path implemented by the application 70 | * @see https://developer.crowdin.com/api/v2/#operation/api.applications.api.get 71 | */ 72 | getApplicationData(applicationId: string, path: string): Promise> { 73 | const url = `${this.url}/applications/${applicationId}/api/${path}`; 74 | return this.get(url, this.defaultConfig()); 75 | } 76 | 77 | /** 78 | * @param applicationId application identifier 79 | * @param path path implemented by the application 80 | * @param request request body 81 | * @see https://developer.crowdin.com/api/v2/#operation/api.applications.api.put 82 | */ 83 | updateOrRestoreApplicationData(applicationId: string, path: string, request: any): Promise> { 84 | const url = `${this.url}/applications/${applicationId}/api/${path}`; 85 | return this.put(url, request, this.defaultConfig()); 86 | } 87 | 88 | /** 89 | * @param applicationId application identifier 90 | * @param path path implemented by the application 91 | * @param request request body 92 | * @see https://developer.crowdin.com/api/v2/#operation/api.applications.api.post 93 | */ 94 | addApplicationData(applicationId: string, path: string, request: any): Promise> { 95 | const url = `${this.url}/applications/${applicationId}/api/${path}`; 96 | return this.post(url, request, this.defaultConfig()); 97 | } 98 | 99 | /** 100 | * @param applicationId application identifier 101 | * @param path path implemented by the application 102 | * @see https://developer.crowdin.com/api/v2/#operation/api.applications.api.delete 103 | */ 104 | deleteApplicationData(applicationId: string, path: string): Promise { 105 | const url = `${this.url}/applications/${applicationId}/api/${path}`; 106 | return this.delete(url, this.defaultConfig()); 107 | } 108 | 109 | /** 110 | * @param applicationId application identifier 111 | * @param path path implemented by the application 112 | * @param request request body 113 | * @see https://developer.crowdin.com/api/v2/#operation/api.applications.api.patch 114 | */ 115 | editApplicationData(applicationId: string, path: string, request: any): Promise> { 116 | const url = `${this.url}/applications/${applicationId}/api/${path}`; 117 | return this.patch(url, request, this.defaultConfig()); 118 | } 119 | } 120 | 121 | export namespace ApplicationsModel { 122 | export interface Application { 123 | identifier: string; 124 | name: string; 125 | description: string; 126 | logo: string; 127 | baseUrl: string; 128 | manifestUrl: string; 129 | createdAt: string; 130 | modules: ApplicationModule[]; 131 | scopes: string[]; 132 | permissions: ApplicationPermissions; 133 | defaultPermissions: any; 134 | limitReached: boolean; 135 | } 136 | 137 | export interface InstallApplication { 138 | url: string; 139 | permissions?: ApplicationPermissions; 140 | modules?: ApplicationModule[]; 141 | } 142 | 143 | export interface ApplicationPermissions { 144 | user: { 145 | value: 'all' | 'owner' | 'managers' | 'guests' | 'restricted'; 146 | ids: number[]; 147 | }; 148 | project: { 149 | value: 'own' | 'restricted'; 150 | ids: number[]; 151 | }; 152 | } 153 | 154 | export interface ApplicationModule { 155 | key: string; 156 | type?: string; 157 | data?: any; 158 | authenticationType?: string; 159 | permissions: Omit; 160 | } 161 | } 162 | -------------------------------------------------------------------------------- /src/bundles/index.ts: -------------------------------------------------------------------------------- 1 | import { 2 | CrowdinApi, 3 | DownloadLink, 4 | PaginationOptions, 5 | PatchRequest, 6 | ResponseList, 7 | ResponseObject, 8 | Status, 9 | } from '../core'; 10 | import { SourceFilesModel } from '../sourceFiles'; 11 | 12 | export class Bundles extends CrowdinApi { 13 | /** 14 | * @param projectId project identifier 15 | * @param options optional parameters for the request 16 | * @see https://developer.crowdin.com/api/v2/#operation/api.projects.bundles.getMany 17 | */ 18 | listBundles(projectId: number, options?: PaginationOptions): Promise> { 19 | const url = `${this.url}/projects/${projectId}/bundles`; 20 | return this.getList(url, options?.limit, options?.offset); 21 | } 22 | 23 | /** 24 | * @param projectId project identifier 25 | * @param request request body 26 | * @see https://developer.crowdin.com/api/v2/#operation/api.projects.bundles.post 27 | */ 28 | addBundle( 29 | projectId: number, 30 | request: BundlesModel.CreateBundleRequest, 31 | ): Promise> { 32 | const url = `${this.url}/projects/${projectId}/bundles`; 33 | return this.post(url, request, this.defaultConfig()); 34 | } 35 | 36 | /** 37 | * @param projectId project identifier 38 | * @param bundleId bundle identifier 39 | * @see https://developer.crowdin.com/api/v2/#operation/api.projects.bundles.get 40 | */ 41 | getBundle(projectId: number, bundleId: number): Promise> { 42 | const url = `${this.url}/projects/${projectId}/bundles/${bundleId}`; 43 | return this.get(url, this.defaultConfig()); 44 | } 45 | 46 | /** 47 | * @param projectId project identifier 48 | * @param bundleId bundle identifier 49 | * @see https://developer.crowdin.com/api/v2/#operation/api.projects.bundles.delete 50 | */ 51 | deleteBundle(projectId: number, bundleId: number): Promise { 52 | const url = `${this.url}/projects/${projectId}/bundles/${bundleId}`; 53 | return this.delete(url, this.defaultConfig()); 54 | } 55 | 56 | /** 57 | * @param projectId project identifier 58 | * @param bundleId bundle identifier 59 | * @param request request body 60 | * @see https://developer.crowdin.com/api/v2/#operation/api.projects.bundles.patch 61 | */ 62 | editBundle( 63 | projectId: number, 64 | bundleId: number, 65 | request: PatchRequest[], 66 | ): Promise> { 67 | const url = `${this.url}/projects/${projectId}/bundles/${bundleId}`; 68 | return this.patch(url, request, this.defaultConfig()); 69 | } 70 | 71 | /** 72 | * @param projectId project identifier 73 | * @param bundleId bundle identifier 74 | * @param exportId export identifier 75 | * @see https://developer.crowdin.com/api/v2/#operation/api.projects.bundles.exports.download.get 76 | */ 77 | downloadBundle(projectId: number, bundleId: number, exportId: string): Promise> { 78 | const url = `${this.url}/projects/${projectId}/bundles/${bundleId}/exports/${exportId}/download`; 79 | return this.get(url, this.defaultConfig()); 80 | } 81 | 82 | /** 83 | * @param projectId project identifier 84 | * @param bundleId bundle identifier 85 | * @see https://developer.crowdin.com/api/v2/#operation/api.projects.bundles.exports.post 86 | */ 87 | exportBundle(projectId: number, bundleId: number): Promise>> { 88 | const url = `${this.url}/projects/${projectId}/bundles/${bundleId}/exports`; 89 | return this.post(url, undefined, this.defaultConfig()); 90 | } 91 | 92 | /** 93 | * @param projectId project identifier 94 | * @param bundleId bundle identifier 95 | * @param exportId export identifier 96 | * @see https://developer.crowdin.com/api/v2/#operation/api.projects.bundles.exports.get 97 | */ 98 | checkBundleExportStatus( 99 | projectId: number, 100 | bundleId: number, 101 | exportId: string, 102 | ): Promise>> { 103 | const url = `${this.url}/projects/${projectId}/bundles/${bundleId}/exports/${exportId}`; 104 | return this.get(url, this.defaultConfig()); 105 | } 106 | 107 | /** 108 | * @param projectId project identifier 109 | * @param bundleId bundle identifier 110 | * @param options optional parameters for the request 111 | * @see https://developer.crowdin.com/api/v2/#operation/api.projects.bundles.files.getMany 112 | */ 113 | listBundleFiles( 114 | projectId: number, 115 | bundleId: number, 116 | options?: PaginationOptions, 117 | ): Promise> { 118 | const url = `${this.url}/projects/${projectId}/bundles/${bundleId}/files`; 119 | return this.getList(url, options?.limit, options?.offset); 120 | } 121 | 122 | /** 123 | * @param projectId project identifier 124 | * @param bundleId bundle identifier 125 | * @param options optional parameters for the request 126 | */ 127 | listBundleBranches( 128 | projectId: number, 129 | bundleId: number, 130 | options?: PaginationOptions, 131 | ): Promise> { 132 | const url = `${this.url}/projects/${projectId}/bundles/${bundleId}/branches`; 133 | return this.getList(url, options?.limit, options?.offset); 134 | } 135 | } 136 | 137 | export namespace BundlesModel { 138 | export interface Bundle { 139 | id: number; 140 | name: string; 141 | format: string; 142 | sourcePatterns: string[]; 143 | ignorePatterns: string[]; 144 | exportPattern: string; 145 | isMultilingual: boolean; 146 | includeProjectSourceLanguage: boolean; 147 | labelIds: number[]; 148 | excludeLabelIds: number[]; 149 | createdAt: string; 150 | webUrl: string; 151 | updatedAt: string; 152 | } 153 | 154 | export interface CreateBundleRequest { 155 | name: string; 156 | format: string; 157 | sourcePatterns: string[]; 158 | ignorePatterns?: string[]; 159 | exportPattern: string; 160 | isMultilingual?: boolean; 161 | includeProjectSourceLanguage?: boolean; 162 | includeInContextPseudoLanguage?: boolean; 163 | labelIds?: number[]; 164 | excludeLabelIds?: number[]; 165 | } 166 | 167 | export interface ExportAttributes { 168 | bundleId: number; 169 | } 170 | } 171 | -------------------------------------------------------------------------------- /src/clients/index.ts: -------------------------------------------------------------------------------- 1 | import { CrowdinApi, PaginationOptions, ResponseList } from '../core'; 2 | 3 | /** 4 | * Clients are the organizations that order professional translation services from Vendors. 5 | * Clients can invite an existing organization to become a Vendor for them. 6 | * 7 | * Use the API to get a list of the Clients you already cooperate with as a Vendor. 8 | */ 9 | export class Clients extends CrowdinApi { 10 | /** 11 | * @param options optional pagination parameters for the request 12 | * @see https://developer.crowdin.com/enterprise/api/v2/#operation/api.clients.getMany 13 | */ 14 | listClients(options?: PaginationOptions): Promise> { 15 | const url = `${this.url}/clients`; 16 | return this.getList(url, options?.limit, options?.offset); 17 | } 18 | } 19 | 20 | export namespace ClientsModel { 21 | export interface Client { 22 | id: number; 23 | name: string; 24 | description: string; 25 | status: 'pending' | 'confirmed' | 'rejected'; 26 | webUrl: string; 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/core/http-client-error.ts: -------------------------------------------------------------------------------- 1 | import { AxiosError } from 'axios'; 2 | import { FetchClientJsonPayloadError } from './internal/fetch/fetchClientError'; 3 | 4 | export type HttpClientError = AxiosError | FetchClientJsonPayloadError | Error; 5 | 6 | export const toHttpClientError = (error?: unknown): HttpClientError => 7 | error instanceof AxiosError || error instanceof FetchClientJsonPayloadError || error instanceof Error 8 | ? error 9 | : new Error(`unknown http client error: ${error}`); 10 | -------------------------------------------------------------------------------- /src/core/internal/axios/axiosProvider.ts: -------------------------------------------------------------------------------- 1 | import axios, { AxiosInstance } from 'axios'; 2 | 3 | /** 4 | * @internal 5 | */ 6 | export class AxiosProvider { 7 | private static readonly CROWDIN_API_MAX_CONCURRENT_REQUESTS = 15; 8 | private static readonly CROWDIN_API_REQUESTS_INTERVAL_MS = 10; 9 | 10 | private pendingRequests = 0; 11 | axios: AxiosInstance = axios.create({}); 12 | 13 | constructor() { 14 | this.configureRequest(); 15 | this.configureResponse(); 16 | } 17 | 18 | private configureRequest(): void { 19 | this.axios.interceptors.request.use((config) => { 20 | return new Promise((resolve) => { 21 | const interval = setInterval(() => { 22 | if (this.pendingRequests < AxiosProvider.CROWDIN_API_MAX_CONCURRENT_REQUESTS) { 23 | this.pendingRequests++; 24 | clearInterval(interval); 25 | resolve(config); 26 | } 27 | }, AxiosProvider.CROWDIN_API_REQUESTS_INTERVAL_MS); 28 | }); 29 | }); 30 | } 31 | 32 | private configureResponse(): void { 33 | this.axios.interceptors.response.use( 34 | (response) => { 35 | this.pendingRequests = Math.max(0, this.pendingRequests - 1); 36 | return Promise.resolve(response.data); 37 | }, 38 | (error: unknown) => { 39 | this.pendingRequests = Math.max(0, this.pendingRequests - 1); 40 | return Promise.reject(error); 41 | }, 42 | ); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/core/internal/fetch/fetchClient.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Import DOM types for this module _just_ to expose adequate types for 3 | * fetch interfaces 4 | */ 5 | /// 6 | import { HttpClient } from '../..'; 7 | import { FetchClientJsonPayloadError } from './fetchClientError'; 8 | 9 | /** 10 | * @internal 11 | */ 12 | export class FetchClient implements HttpClient { 13 | private maxConcurrentRequests = 15; 14 | private requestIntervalMs = 10; 15 | private pendingRequests = 0; 16 | 17 | private timeout: number | undefined; 18 | 19 | withTimeout(timeout?: number): this { 20 | this.timeout = timeout; 21 | return this; 22 | } 23 | 24 | get(url: string, config?: { headers: Record }): Promise { 25 | return this.request(url, 'GET', config); 26 | } 27 | delete(url: string, config?: { headers: Record }): Promise { 28 | return this.request(url, 'DELETE', config); 29 | } 30 | head(url: string, config?: { headers: Record }): Promise { 31 | return this.request(url, 'HEAD', config); 32 | } 33 | post(url: string, data?: unknown, config?: { headers: Record }): Promise { 34 | return this.request(url, 'POST', config, data); 35 | } 36 | put(url: string, data?: unknown, config?: { headers: Record }): Promise { 37 | return this.request(url, 'PUT', config, data); 38 | } 39 | patch(url: string, data?: unknown, config?: { headers: Record }): Promise { 40 | return this.request(url, 'PATCH', config, data); 41 | } 42 | 43 | private async request( 44 | url: string, 45 | method: string, 46 | config?: { headers: Record }, 47 | data?: unknown, 48 | ): Promise { 49 | let body: RequestInit['body']; 50 | if (data) { 51 | if (typeof data === 'object' && !this.isBuffer(data)) { 52 | body = JSON.stringify(data); 53 | config = config ?? { headers: {} }; 54 | config.headers = config.headers ?? {}; 55 | config.headers['Content-Type'] = 'application/json'; 56 | } else { 57 | body = data as BodyInit; 58 | } 59 | } 60 | await this.waitInQueue(); 61 | 62 | let request; 63 | const headers = config ? config.headers : {}; 64 | 65 | if (this.timeout) { 66 | const controller = new AbortController(); 67 | const timeoutId = setTimeout(() => controller.abort(), this.timeout); 68 | request = fetch(url, { method, headers, body, signal: controller.signal }).then((res) => { 69 | clearTimeout(timeoutId); 70 | return res; 71 | }); 72 | } else { 73 | request = fetch(url, { method, headers, body }); 74 | } 75 | 76 | return request 77 | .then(async (res) => { 78 | if (res.status === 204) { 79 | return {}; 80 | } 81 | const text = await res.text(); 82 | const json = text ? JSON.parse(text) : {}; 83 | if (res.status >= 200 && res.status < 300) { 84 | return json; 85 | } else { 86 | throw new FetchClientJsonPayloadError(res.statusText, json, res.status); 87 | } 88 | }) 89 | .finally(() => (this.pendingRequests = Math.max(0, this.pendingRequests - 1))); 90 | } 91 | 92 | private isBuffer(data: unknown): boolean { 93 | if (typeof ArrayBuffer === 'function') { 94 | return ArrayBuffer.isView(data); 95 | } else if (typeof Buffer === 'function') { 96 | return Buffer.isBuffer(data); 97 | } else { 98 | return false; 99 | } 100 | } 101 | 102 | private waitInQueue(): Promise { 103 | return new Promise((resolve) => { 104 | const interval = setInterval(() => { 105 | if (this.pendingRequests < this.maxConcurrentRequests) { 106 | this.pendingRequests++; 107 | clearInterval(interval); 108 | resolve(); 109 | } 110 | }, this.requestIntervalMs); 111 | }); 112 | } 113 | } 114 | -------------------------------------------------------------------------------- /src/core/internal/fetch/fetchClientError.ts: -------------------------------------------------------------------------------- 1 | type Json = string | number | boolean | null | { [key: string]: Json } | Json[]; 2 | 3 | export class FetchClientJsonPayloadError extends Error { 4 | public readonly payload: unknown; 5 | public readonly statusCode: number; 6 | constructor(msg: string, payload: unknown, statusCode: number) { 7 | super(msg); 8 | this.payload = payload; 9 | this.statusCode = statusCode; 10 | /** 11 | * Support instanceof operator 12 | * @see https://www.typescriptlang.org/docs/handbook/release-notes/typescript-2-2.html#support-for-newtarget 13 | */ 14 | Object.setPrototypeOf(this, new.target.prototype); 15 | } 16 | get jsonPayload(): Json { 17 | if (typeof this.payload === 'object' && this.payload) { 18 | return this.payload as Json; 19 | } 20 | return null; 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/core/internal/retry/index.ts: -------------------------------------------------------------------------------- 1 | export interface RetryConfig { 2 | //amount of retries 3 | retries: number; 4 | //wait interval in ms between retries 5 | waitInterval: number; 6 | //array of conditions which will check if retry should not be applied 7 | conditions: SkipRetryCondition[]; 8 | } 9 | 10 | export interface SkipRetryCondition { 11 | test(error: unknown): boolean; 12 | } 13 | 14 | /** 15 | * @internal 16 | */ 17 | export class RetryService { 18 | constructor(private config: RetryConfig) {} 19 | 20 | /** 21 | * @param func function to execute 22 | */ 23 | async executeAsyncFunc(func: () => Promise): Promise { 24 | for (let i = 0; i <= this.config.retries; i++) { 25 | try { 26 | const result = await func(); 27 | return result; 28 | } catch (error) { 29 | const skip = this.config.conditions 30 | .map((condition) => condition.test(error)) 31 | .find((skip) => skip === true); 32 | if (skip || i === this.config.retries) { 33 | throw error; 34 | } 35 | await this.wait(); 36 | } 37 | } 38 | throw new Error('Wrong retry configuration. Failed to retrieve value.'); 39 | } 40 | 41 | /** 42 | * @param func function to execute 43 | */ 44 | async executeSyncFunc(func: () => T): Promise { 45 | for (let i = 0; i <= this.config.retries; i++) { 46 | try { 47 | const result = func(); 48 | return result; 49 | } catch (error) { 50 | const skip = this.config.conditions 51 | .map((condition) => condition.test(error)) 52 | .find((skip) => skip === true); 53 | if (skip || i === this.config.retries) { 54 | throw error; 55 | } 56 | await this.wait(); 57 | } 58 | } 59 | throw new Error('Wrong retry configuration. Failed to retrieve value.'); 60 | } 61 | 62 | private wait(): Promise { 63 | return new Promise((res): void => { 64 | setTimeout(() => res(), this.config.waitInterval); 65 | }); 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /src/dictionaries/index.ts: -------------------------------------------------------------------------------- 1 | import { CrowdinApi, isOptionalString, PatchRequest, ResponseList, ResponseObject } from '../core'; 2 | 3 | /** 4 | * Dictionaries allow you to create a storage of words that should be skipped by the spell checker. 5 | * 6 | * Use API to get the list of organization dictionaries and to edit a specific dictionary. 7 | */ 8 | export class Dictionaries extends CrowdinApi { 9 | /** 10 | * @param projectId project identifier 11 | * @param options optional parameters for listing dictionaries 12 | * @see https://developer.crowdin.com/api/v2/#operation/api.projects.dictionaries.getMany 13 | */ 14 | listDictionaries( 15 | projectId: number, 16 | options?: DictionariesModel.ListDictionariesOptions, 17 | ): Promise>; 18 | /** 19 | * @param projectId project identifier 20 | * @param languageIds filter progress by Language Identifiers 21 | * @deprecated optional parameters should be passed through an object 22 | * @see https://developer.crowdin.com/api/v2/#operation/api.projects.dictionaries.getMany 23 | */ 24 | listDictionaries(projectId: number, languageIds?: string): Promise>; 25 | listDictionaries( 26 | projectId: number, 27 | options?: string | DictionariesModel.ListDictionariesOptions, 28 | ): Promise> { 29 | if (isOptionalString(options, '1' in arguments)) { 30 | options = { languageIds: options }; 31 | } 32 | let url = `${this.url}/projects/${projectId}/dictionaries`; 33 | url = this.addQueryParam(url, 'languageIds', options.languageIds); 34 | return this.get(url, this.defaultConfig()); 35 | } 36 | 37 | /** 38 | * @param projectId project identifier 39 | * @param languageId language identifier 40 | * @param request request body 41 | * @see https://developer.crowdin.com/api/v2/#operation/api.projects.dictionaries.patch 42 | */ 43 | editDictionary( 44 | projectId: number, 45 | languageId: string, 46 | request: PatchRequest[], 47 | ): Promise> { 48 | const url = `${this.url}/projects/${projectId}/dictionaries/${languageId}`; 49 | return this.patch(url, request, this.defaultConfig()); 50 | } 51 | } 52 | 53 | export namespace DictionariesModel { 54 | export interface Dictionary { 55 | languageId: string; 56 | words: string[]; 57 | } 58 | 59 | export interface ListDictionariesOptions { 60 | languageIds?: string; 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /src/distributions/index.ts: -------------------------------------------------------------------------------- 1 | import { CrowdinApi, isOptionalNumber, PaginationOptions, PatchRequest, ResponseList, ResponseObject } from '../core'; 2 | 3 | export class Distributions extends CrowdinApi { 4 | /** 5 | * @param projectId project identifier 6 | * @param options optional pagination parameters for the request 7 | * @see https://developer.crowdin.com/api/v2/#operation/api.projects.distributions.getMany 8 | */ 9 | listDistributions( 10 | projectId: number, 11 | options?: PaginationOptions, 12 | ): Promise>; 13 | /** 14 | * @param projectId project identifier 15 | * @param limit maximum number of items to retrieve (default 25) 16 | * @param offset starting offset in the collection (default 0) 17 | * @deprecated optional parameters should be passed through an object 18 | * @see https://developer.crowdin.com/api/v2/#operation/api.projects.distributions.getMany 19 | */ 20 | listDistributions( 21 | projectId: number, 22 | limit?: number, 23 | offset?: number, 24 | ): Promise>; 25 | listDistributions( 26 | projectId: number, 27 | options?: number | PaginationOptions, 28 | deprecatedOffset?: number, 29 | ): Promise> { 30 | if (isOptionalNumber(options, '1' in arguments)) { 31 | options = { limit: options, offset: deprecatedOffset }; 32 | } 33 | const url = `${this.url}/projects/${projectId}/distributions`; 34 | return this.getList(url, options.limit, options.offset); 35 | } 36 | 37 | /** 38 | * @param projectId project identifier 39 | * @param request request body 40 | * @see https://developer.crowdin.com/api/v2/#operation/api.projects.distributions.post 41 | */ 42 | createDistribution( 43 | projectId: number, 44 | request: 45 | | DistributionsModel.CreateDistributionRequest 46 | | DistributionsModel.CreateDistributionStringsBasedRequest, 47 | ): Promise> { 48 | const url = `${this.url}/projects/${projectId}/distributions`; 49 | return this.post(url, request, this.defaultConfig()); 50 | } 51 | 52 | /** 53 | * @param projectId project identifier 54 | * @param hash hash 55 | * @see https://developer.crowdin.com/api/v2/#operation/api.projects.distributions.get 56 | */ 57 | getDistribution(projectId: number, hash: string): Promise> { 58 | const url = `${this.url}/projects/${projectId}/distributions/${hash}`; 59 | return this.get(url, this.defaultConfig()); 60 | } 61 | 62 | /** 63 | * @param projectId project identifier 64 | * @param hash hash 65 | * @see https://developer.crowdin.com/api/v2/#operation/api.projects.distributions.delete 66 | */ 67 | deleteDistribution(projectId: number, hash: string): Promise { 68 | const url = `${this.url}/projects/${projectId}/distributions/${hash}`; 69 | return this.delete(url, this.defaultConfig()); 70 | } 71 | 72 | /** 73 | * @param projectId project identifier 74 | * @param hash hash 75 | * @param request request body 76 | * @see https://developer.crowdin.com/api/v2/#operation/api.projects.distributions.patch 77 | */ 78 | editDistribution( 79 | projectId: number, 80 | hash: string, 81 | request: PatchRequest[], 82 | ): Promise> { 83 | const url = `${this.url}/projects/${projectId}/distributions/${hash}`; 84 | return this.patch(url, request, this.defaultConfig()); 85 | } 86 | 87 | /** 88 | * @param projectId project identifier 89 | * @param hash hash 90 | * @see https://developer.crowdin.com/api/v2/#operation/api.projects.distributions.release.get 91 | */ 92 | getDistributionRelease( 93 | projectId: number, 94 | hash: string, 95 | ): Promise< 96 | ResponseObject 97 | > { 98 | const url = `${this.url}/projects/${projectId}/distributions/${hash}/release`; 99 | return this.get(url, this.defaultConfig()); 100 | } 101 | 102 | /** 103 | * @param projectId project identifier 104 | * @param hash hash 105 | * @param request request body 106 | * @see https://developer.crowdin.com/api/v2/#operation/api.projects.distributions.release.post 107 | */ 108 | createDistributionRelease( 109 | projectId: number, 110 | hash: string, 111 | ): Promise< 112 | ResponseObject 113 | > { 114 | const url = `${this.url}/projects/${projectId}/distributions/${hash}/release`; 115 | return this.post(url, {}, this.defaultConfig()); 116 | } 117 | } 118 | 119 | export namespace DistributionsModel { 120 | export interface Distribution { 121 | hash: string; 122 | manifestUrl: string; 123 | name: string; 124 | bundleIds: number[]; 125 | createdAt: string; 126 | updatedAt: string; 127 | exportMode: ExportMode; 128 | fileIds: number[]; 129 | } 130 | 131 | export interface CreateDistributionRequest { 132 | exportMode?: ExportMode; 133 | name: string; 134 | fileIds?: number[]; 135 | bundleIds?: number[]; 136 | } 137 | 138 | export interface CreateDistributionStringsBasedRequest { 139 | name: string; 140 | bundleIds: number[]; 141 | } 142 | 143 | export interface DistributionRelease { 144 | status: string; 145 | progress: number; 146 | currentLanguageId: string; 147 | currentFileId: number; 148 | date: string; 149 | } 150 | 151 | export interface DistributionStringsBasedRelease { 152 | status: string; 153 | progress: number; 154 | currentLanguageId: string; 155 | currentBranchId: number; 156 | date: string; 157 | } 158 | 159 | export type ExportMode = 'default' | 'bundle'; 160 | } 161 | -------------------------------------------------------------------------------- /src/fields/index.ts: -------------------------------------------------------------------------------- 1 | import { CrowdinApi, PaginationOptions, PatchRequest, ResponseList, ResponseObject } from '../core'; 2 | 3 | export class Fields extends CrowdinApi { 4 | /** 5 | * @param options optional parameters for the request 6 | * @see https://developer.crowdin.com/enterprise/api/v2/#operation/api.fields.getMany 7 | */ 8 | listFields(options?: FieldsModel.ListFieldsParams): Promise> { 9 | let url = `${this.url}/fields`; 10 | url = this.addQueryParam(url, 'search', options?.search); 11 | url = this.addQueryParam(url, 'entity', options?.entity); 12 | url = this.addQueryParam(url, 'type', options?.type); 13 | return this.getList(url, options?.limit, options?.offset); 14 | } 15 | 16 | /** 17 | * @param request request body 18 | * @see https://developer.crowdin.com/enterprise/api/v2/#operation/api.fields.post 19 | */ 20 | addField(request: FieldsModel.AddFieldRequest): Promise> { 21 | const url = `${this.url}/fields`; 22 | return this.post(url, request, this.defaultConfig()); 23 | } 24 | 25 | /** 26 | * @param fieldId field identifier 27 | * @see https://developer.crowdin.com/enterprise/api/v2/#operation/api.fields.get 28 | */ 29 | getField(fieldId: number): Promise> { 30 | const url = `${this.url}/fields/${fieldId}`; 31 | return this.get(url, this.defaultConfig()); 32 | } 33 | 34 | /** 35 | * @param fieldId field identifier 36 | * @see https://developer.crowdin.com/api/v2/#operation/api.fields.delete 37 | */ 38 | deleteField(fieldId: number): Promise { 39 | const url = `${this.url}/fields/${fieldId}`; 40 | return this.delete(url, this.defaultConfig()); 41 | } 42 | 43 | /** 44 | * @param fieldId field identifier 45 | * @param request request body 46 | * @see https://developer.crowdin.com/api/v2/#operation/api.fields.patch 47 | */ 48 | editField(fieldId: number, request: PatchRequest[]): Promise> { 49 | const url = `${this.url}/fields/${fieldId}`; 50 | return this.patch(url, request, this.defaultConfig()); 51 | } 52 | } 53 | 54 | export namespace FieldsModel { 55 | export type Entity = 'project' | 'user' | 'task' | 'file' | 'translation' | 'string'; 56 | 57 | export type Type = 58 | | 'checkbox' 59 | | 'radiobuttons' 60 | | 'date' 61 | | 'datetime' 62 | | 'number' 63 | | 'labels' 64 | | 'select' 65 | | 'multiselect' 66 | | 'text' 67 | | 'textarea' 68 | | 'url'; 69 | 70 | export type Place = 71 | | 'projectCreateModal' 72 | | 'projectHeader' 73 | | 'projectDetails' 74 | | 'projectCrowdsourceDetails' 75 | | 'projectSettings' 76 | | 'projectTaskEditCreate' 77 | | 'projectTaskDetails' 78 | | 'projectTaskBoardCard' 79 | | 'fileDetails' 80 | | 'fileSettings' 81 | | 'userEditModal' 82 | | 'userDetails' 83 | | 'userPopover' 84 | | 'stringEditModal' 85 | | 'stringDetails' 86 | | 'translationUnderContent'; 87 | 88 | export interface Location { 89 | place: Place; 90 | } 91 | 92 | export interface Option { 93 | label: string; 94 | value: string; 95 | } 96 | 97 | export interface OtherFieldConfig { 98 | locations: Location[]; 99 | } 100 | 101 | export interface ListFieldConfig extends OtherFieldConfig { 102 | options: Option[]; 103 | } 104 | 105 | export interface NumberFieldConfig extends OtherFieldConfig { 106 | min: number; 107 | max: number; 108 | units: string; 109 | } 110 | 111 | export type Config = ListFieldConfig | NumberFieldConfig | OtherFieldConfig; 112 | 113 | export interface ListFieldsParams extends PaginationOptions { 114 | search?: string; 115 | entity?: Entity; 116 | type?: Type; 117 | } 118 | 119 | export interface Field { 120 | id: number; 121 | name: string; 122 | slug: string; 123 | type: Type; 124 | description: string; 125 | entities: Entity[]; 126 | config: Config; 127 | createdAt: string; 128 | updatedAt: string; 129 | } 130 | 131 | export interface AddFieldRequest { 132 | name: string; 133 | slug: string; 134 | type: Type; 135 | description?: string; 136 | entities: Entity[]; 137 | config?: Config; 138 | } 139 | } 140 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | import { Ai } from './ai'; 2 | import { Applications } from './applications'; 3 | import { Bundles } from './bundles'; 4 | import { Clients } from './clients'; 5 | import { ClientConfig, Credentials, CrowdinApi } from './core'; 6 | import { Dictionaries } from './dictionaries'; 7 | import { Distributions } from './distributions'; 8 | import { Fields } from './fields'; 9 | import { Glossaries } from './glossaries'; 10 | import { Issues } from './issues'; 11 | import { Labels } from './labels'; 12 | import { Languages } from './languages'; 13 | import { MachineTranslation } from './machineTranslation'; 14 | import { Notifications } from './notifications'; 15 | import { OrganizationWebhooks } from './organizationWebhooks'; 16 | import { ProjectsGroups } from './projectsGroups'; 17 | import { Reports } from './reports'; 18 | import { Screenshots } from './screenshots'; 19 | import { SecurityLogs } from './securityLogs'; 20 | import { SourceFiles } from './sourceFiles'; 21 | import { SourceStrings } from './sourceStrings'; 22 | import { StringComments } from './stringComments'; 23 | import { StringTranslations } from './stringTranslations'; 24 | import { Tasks } from './tasks'; 25 | import { Teams } from './teams'; 26 | import { TranslationMemory } from './translationMemory'; 27 | import { TranslationStatus } from './translationStatus'; 28 | import { Translations } from './translations'; 29 | import { UploadStorage } from './uploadStorage'; 30 | import { Users } from './users'; 31 | import { Vendors } from './vendors'; 32 | import { Webhooks } from './webhooks'; 33 | import { Workflows } from './workflows'; 34 | 35 | export * from './ai'; 36 | export * from './applications'; 37 | export * from './bundles'; 38 | export * from './clients'; 39 | export * from './core'; 40 | export * from './dictionaries'; 41 | export * from './distributions'; 42 | export * from './fields'; 43 | export * from './glossaries'; 44 | export * from './issues'; 45 | export * from './labels'; 46 | export * from './languages'; 47 | export * from './machineTranslation'; 48 | export * from './notifications'; 49 | export * from './organizationWebhooks'; 50 | export * from './projectsGroups'; 51 | export * from './reports'; 52 | export * from './screenshots'; 53 | export * from './securityLogs'; 54 | export * from './sourceFiles'; 55 | export * from './sourceStrings'; 56 | export * from './stringComments'; 57 | export * from './stringTranslations'; 58 | export * from './tasks'; 59 | export * from './teams'; 60 | export * from './translationMemory'; 61 | export * from './translationStatus'; 62 | export * from './translations'; 63 | export * from './uploadStorage'; 64 | export * from './users'; 65 | export * from './vendors'; 66 | export * from './webhooks'; 67 | export * from './workflows'; 68 | 69 | /** 70 | * @internal 71 | */ 72 | export default class Client extends CrowdinApi { 73 | readonly aiApi: Ai; 74 | readonly applicationsApi: Applications; 75 | readonly sourceFilesApi: SourceFiles; 76 | readonly glossariesApi: Glossaries; 77 | readonly languagesApi: Languages; 78 | readonly translationsApi: Translations; 79 | readonly translationStatusApi: TranslationStatus; 80 | readonly projectsGroupsApi: ProjectsGroups; 81 | readonly reportsApi: Reports; 82 | readonly screenshotsApi: Screenshots; 83 | readonly sourceStringsApi: SourceStrings; 84 | readonly uploadStorageApi: UploadStorage; 85 | readonly tasksApi: Tasks; 86 | readonly translationMemoryApi: TranslationMemory; 87 | readonly webhooksApi: Webhooks; 88 | readonly organizationWebhooksApi: OrganizationWebhooks; 89 | readonly machineTranslationApi: MachineTranslation; 90 | readonly stringTranslationsApi: StringTranslations; 91 | readonly workflowsApi: Workflows; 92 | readonly usersApi: Users; 93 | readonly vendorsApi: Vendors; 94 | /** 95 | * @deprecated use stringCommentsApi instead 96 | */ 97 | readonly issuesApi: Issues; 98 | readonly teamsApi: Teams; 99 | readonly distributionsApi: Distributions; 100 | readonly dictionariesApi: Dictionaries; 101 | readonly labelsApi: Labels; 102 | readonly stringCommentsApi: StringComments; 103 | readonly bundlesApi: Bundles; 104 | readonly notificationsApi: Notifications; 105 | readonly clientsApi: Clients; 106 | readonly securityLogsApi: SecurityLogs; 107 | readonly fieldsApi: Fields; 108 | 109 | constructor(credentials: Credentials, config?: ClientConfig) { 110 | super(credentials, config); 111 | this.aiApi = new Ai(credentials, config); 112 | this.applicationsApi = new Applications(credentials, config); 113 | this.sourceFilesApi = new SourceFiles(credentials, config); 114 | this.glossariesApi = new Glossaries(credentials, config); 115 | this.languagesApi = new Languages(credentials, config); 116 | this.translationsApi = new Translations(credentials, config); 117 | this.translationStatusApi = new TranslationStatus(credentials, config); 118 | this.projectsGroupsApi = new ProjectsGroups(credentials, config); 119 | this.reportsApi = new Reports(credentials, config); 120 | this.screenshotsApi = new Screenshots(credentials, config); 121 | this.sourceStringsApi = new SourceStrings(credentials, config); 122 | this.uploadStorageApi = new UploadStorage(credentials, config); 123 | this.tasksApi = new Tasks(credentials, config); 124 | this.translationMemoryApi = new TranslationMemory(credentials, config); 125 | this.webhooksApi = new Webhooks(credentials, config); 126 | this.organizationWebhooksApi = new OrganizationWebhooks(credentials, config); 127 | this.machineTranslationApi = new MachineTranslation(credentials, config); 128 | this.stringTranslationsApi = new StringTranslations(credentials, config); 129 | this.workflowsApi = new Workflows(credentials, config); 130 | this.usersApi = new Users(credentials, config); 131 | this.vendorsApi = new Vendors(credentials, config); 132 | this.issuesApi = new Issues(credentials, config); 133 | this.teamsApi = new Teams(credentials, config); 134 | this.distributionsApi = new Distributions(credentials, config); 135 | this.dictionariesApi = new Dictionaries(credentials, config); 136 | this.labelsApi = new Labels(credentials, config); 137 | this.stringCommentsApi = new StringComments(credentials, config); 138 | this.bundlesApi = new Bundles(credentials, config); 139 | this.notificationsApi = new Notifications(credentials, config); 140 | this.clientsApi = new Clients(credentials, config); 141 | this.securityLogsApi = new SecurityLogs(credentials, config); 142 | this.fieldsApi = new Fields(credentials, config); 143 | } 144 | } 145 | -------------------------------------------------------------------------------- /src/issues/index.ts: -------------------------------------------------------------------------------- 1 | import { CrowdinApi, isOptionalNumber, PaginationOptions, PatchRequest, ResponseList, ResponseObject } from '../core'; 2 | 3 | /** 4 | * @deprecated 5 | * @ignore 6 | */ 7 | export class Issues extends CrowdinApi { 8 | /** 9 | * @deprecated 10 | * @param projectId project identifier 11 | * @param options optional parameters for listing reported issues 12 | * @see https://developer.crowdin.com/api/v2/#operation/api.projects.issues.getMany 13 | */ 14 | listReportedIssues( 15 | projectId: number, 16 | options?: IssuesModel.ListReportedIssuesOptions, 17 | ): Promise>; 18 | /** 19 | * @param projectId project identifier 20 | * @param limit maximum number of items to retrieve (default 25) 21 | * @param offset starting offset in the collection (default 0) 22 | * @param type defines the issue type 23 | * @param status defines the issue resolution status 24 | * @deprecated optional parameters should be passed through an object 25 | * @see https://developer.crowdin.com/api/v2/#operation/api.projects.issues.getMany 26 | */ 27 | listReportedIssues( 28 | projectId: number, 29 | limit?: number, 30 | offset?: number, 31 | type?: IssuesModel.Type, 32 | status?: IssuesModel.Status, 33 | ): Promise>; 34 | listReportedIssues( 35 | projectId: number, 36 | options?: number | IssuesModel.ListReportedIssuesOptions, 37 | deprecatedOffset?: number, 38 | deprecatedType?: IssuesModel.Type, 39 | deprecatedStatus?: IssuesModel.Status, 40 | ): Promise> { 41 | if (isOptionalNumber(options, '1' in arguments)) { 42 | options = { 43 | limit: options, 44 | offset: deprecatedOffset, 45 | type: deprecatedType, 46 | status: deprecatedStatus, 47 | }; 48 | } 49 | let url = `${this.url}/projects/${projectId}/issues`; 50 | url = this.addQueryParam(url, 'type', options.type); 51 | url = this.addQueryParam(url, 'status', options.status); 52 | return this.getList(url, options.limit, deprecatedOffset); 53 | } 54 | 55 | /** 56 | * @deprecated 57 | * @param projectId project identifier 58 | * @param issueId issue identifier 59 | * @param request request body 60 | * @see https://developer.crowdin.com/api/v2/#operation/api.projects.issues.patch 61 | */ 62 | editIssue(projectId: number, issueId: number, request: PatchRequest[]): Promise> { 63 | const url = `${this.url}/projects/${projectId}/issues/${issueId}`; 64 | return this.patch(url, request, this.defaultConfig()); 65 | } 66 | } 67 | 68 | /** 69 | * @deprecated 70 | */ 71 | export namespace IssuesModel { 72 | export type Type = 'all' | 'general_question' | 'translation_mistake' | 'context_request' | 'source_mistake'; 73 | 74 | export type Status = 'all' | 'resolved' | 'unresolved'; 75 | 76 | export interface Issue { 77 | id: number; 78 | text: string; 79 | userId: number; 80 | stringId: number; 81 | user: User; 82 | string: string; 83 | languageId: string; 84 | type: Type; 85 | status: Status; 86 | createdAt: string; 87 | } 88 | 89 | export interface User { 90 | id: number; 91 | username: string; 92 | fullName: string; 93 | avatarUrl: string; 94 | } 95 | 96 | export interface String { 97 | id: number; 98 | text: string; 99 | type: string; 100 | hasPlurals: boolean; 101 | isIcu: boolean; 102 | context: string; 103 | fileId: number; 104 | } 105 | 106 | export interface ListReportedIssuesOptions extends PaginationOptions { 107 | type?: IssuesModel.Type; 108 | status?: IssuesModel.Status; 109 | } 110 | } 111 | -------------------------------------------------------------------------------- /src/labels/index.ts: -------------------------------------------------------------------------------- 1 | import { CrowdinApi, isOptionalNumber, PaginationOptions, PatchRequest, ResponseList, ResponseObject } from '../core'; 2 | import { ScreenshotsModel } from '../screenshots'; 3 | import { SourceStringsModel } from '../sourceStrings'; 4 | 5 | export class Labels extends CrowdinApi { 6 | /** 7 | * @param projectId project identifier 8 | * @param options optional pagination parameters for the request 9 | * @see https://developer.crowdin.com/api/v2/#operation/api.projects.labels.getMany 10 | */ 11 | listLabels(projectId: number, options?: LabelsModel.ListLabelsParams): Promise>; 12 | /** 13 | * @param projectId project identifier 14 | * @param limit maximum number of items to retrieve (default 25) 15 | * @param offset starting offset in the collection (default 0) 16 | * @deprecated optional parameters should be passed through an object 17 | * @see https://developer.crowdin.com/api/v2/#operation/api.projects.labels.getMany 18 | */ 19 | listLabels(projectId: number, limit?: number, offset?: number): Promise>; 20 | listLabels( 21 | projectId: number, 22 | options?: number | LabelsModel.ListLabelsParams, 23 | deprecatedOffset?: number, 24 | ): Promise> { 25 | if (isOptionalNumber(options, '1' in arguments)) { 26 | options = { limit: options, offset: deprecatedOffset }; 27 | } 28 | let url = `${this.url}/projects/${projectId}/labels`; 29 | url = this.addQueryParam(url, 'orderBy', options.orderBy); 30 | return this.getList(url, options.limit, options.offset); 31 | } 32 | 33 | /** 34 | * @param projectId project identifier 35 | * @param request request body 36 | * @see https://developer.crowdin.com/api/v2/#operation/api.projects.labels.post 37 | */ 38 | addLabel(projectId: number, request: LabelsModel.AddLabelRequest): Promise> { 39 | const url = `${this.url}/projects/${projectId}/labels`; 40 | return this.post(url, request, this.defaultConfig()); 41 | } 42 | 43 | /** 44 | * @param projectId project identifier 45 | * @param labelId label identifier 46 | * @see https://developer.crowdin.com/api/v2/#operation/api.projects.labels.get 47 | */ 48 | getLabel(projectId: number, labelId: number): Promise> { 49 | const url = `${this.url}/projects/${projectId}/labels/${labelId}`; 50 | return this.get(url, this.defaultConfig()); 51 | } 52 | 53 | /** 54 | * @param projectId project identifier 55 | * @param labelId label identifier 56 | * @see https://developer.crowdin.com/api/v2/#operation/api.projects.labels.delete 57 | */ 58 | deleteLabel(projectId: number, labelId: number): Promise { 59 | const url = `${this.url}/projects/${projectId}/labels/${labelId}`; 60 | return this.delete(url, this.defaultConfig()); 61 | } 62 | 63 | /** 64 | * @param projectId project identifier 65 | * @param labelId label identifier 66 | * @param request request body 67 | * @see https://developer.crowdin.com/api/v2/#operation/api.projects.labels.patch 68 | */ 69 | editLabel(projectId: number, labelId: number, request: PatchRequest[]): Promise> { 70 | const url = `${this.url}/projects/${projectId}/labels/${labelId}`; 71 | return this.patch(url, request, this.defaultConfig()); 72 | } 73 | 74 | /** 75 | * @param projectId project identifier 76 | * @param labelId label identifier 77 | * @param request request body 78 | * @see https://developer.crowdin.com/api/v2/#operation/api.projects.labels.screenshots.post 79 | */ 80 | assignLabelToScreenshots( 81 | projectId: number, 82 | labelId: number, 83 | request: LabelsModel.AssignLabelToScreenshotsRequet, 84 | ): Promise> { 85 | const url = `${this.url}/projects/${projectId}/labels/${labelId}/screenshots`; 86 | return this.post(url, request, this.defaultConfig()); 87 | } 88 | 89 | /** 90 | * @param projectId project identifier 91 | * @param labelId label identifier 92 | * @param screenshotIds screenshot identifiers 93 | * @see https://developer.crowdin.com/api/v2/#operation/api.projects.labels.screenshots.deleteMany 94 | */ 95 | unassignLabelFromScreenshots( 96 | projectId: number, 97 | labelId: number, 98 | screenshotIds: string, 99 | ): Promise> { 100 | let url = `${this.url}/projects/${projectId}/labels/${labelId}/screenshots`; 101 | url = this.addQueryParam(url, 'screenshotIds', screenshotIds); 102 | return this.delete(url, this.defaultConfig()); 103 | } 104 | 105 | /** 106 | * @param projectId project identifier 107 | * @param labelId label identifier 108 | * @param request request body 109 | * @see https://developer.crowdin.com/api/v2/#operation/api.projects.labels.strings.post 110 | */ 111 | assignLabelToString( 112 | projectId: number, 113 | labelId: number, 114 | request: LabelsModel.AssignLabelToStringsRequet, 115 | ): Promise> { 116 | const url = `${this.url}/projects/${projectId}/labels/${labelId}/strings`; 117 | return this.post(url, request, this.defaultConfig()); 118 | } 119 | 120 | /** 121 | * @param projectId project identifier 122 | * @param labelId label identifier 123 | * @param stringIds string identifiers 124 | * @see https://developer.crowdin.com/api/v2/#operation/api.projects.labels.strings.deleteMany 125 | */ 126 | unassignLabelFromString( 127 | projectId: number, 128 | labelId: number, 129 | stringIds: string, 130 | ): Promise> { 131 | let url = `${this.url}/projects/${projectId}/labels/${labelId}/strings`; 132 | url = this.addQueryParam(url, 'stringIds', stringIds); 133 | return this.delete(url, this.defaultConfig()); 134 | } 135 | } 136 | 137 | export namespace LabelsModel { 138 | export interface ListLabelsParams extends PaginationOptions { 139 | orderBy?: string; 140 | } 141 | 142 | export interface Label { 143 | id: number; 144 | title: string; 145 | isSystem?: boolean; 146 | } 147 | 148 | export interface AddLabelRequest { 149 | title: string; 150 | } 151 | 152 | export interface AssignLabelToStringsRequet { 153 | stringIds: number[]; 154 | } 155 | 156 | export interface AssignLabelToScreenshotsRequet { 157 | screenshotIds: number[]; 158 | } 159 | } 160 | -------------------------------------------------------------------------------- /src/languages/index.ts: -------------------------------------------------------------------------------- 1 | import { CrowdinApi, isOptionalNumber, PaginationOptions, PatchRequest, ResponseList, ResponseObject } from '../core'; 2 | 3 | /** 4 | * Crowdin supports more than 300 world languages and custom languages created in the system. 5 | * 6 | * Use API to get the list of all supported languages and retrieve additional details (e.g. text direction, internal code) on specific language. 7 | */ 8 | export class Languages extends CrowdinApi { 9 | /** 10 | * @param options optional pagination parameters for the request 11 | * @see https://developer.crowdin.com/api/v2/#operation/api.languages.getMany 12 | */ 13 | listSupportedLanguages(options?: PaginationOptions): Promise>; 14 | /** 15 | * @param limit maximum number of items to retrieve (default 25) 16 | * @param offset starting offset in the collection (default 0) 17 | * @deprecated optional parameters should be passed through an object 18 | * @see https://developer.crowdin.com/api/v2/#operation/api.languages.getMany 19 | */ 20 | listSupportedLanguages(limit?: number, offset?: number): Promise>; 21 | listSupportedLanguages( 22 | options?: number | PaginationOptions, 23 | deprecatedOffset?: number, 24 | ): Promise> { 25 | if (isOptionalNumber(options, '0' in arguments)) { 26 | options = { limit: options, offset: deprecatedOffset }; 27 | } 28 | const url = `${this.url}/languages`; 29 | return this.getList(url, options.limit, options.offset); 30 | } 31 | 32 | /** 33 | * @param request request body 34 | * @see https://developer.crowdin.com/api/v2/#operation/api.languages.post 35 | */ 36 | addCustomLanguage(request: LanguagesModel.AddLanguageRequest): Promise> { 37 | const url = `${this.url}/languages`; 38 | return this.post(url, request, this.defaultConfig()); 39 | } 40 | 41 | /** 42 | * @param languageId language identifier 43 | * @see https://developer.crowdin.com/api/v2/#operation/api.languages.get 44 | */ 45 | getLanguage(languageId: string): Promise> { 46 | const url = `${this.url}/languages/${languageId}`; 47 | return this.get(url, this.defaultConfig()); 48 | } 49 | 50 | /** 51 | * @param languageId language identifier 52 | * @see https://developer.crowdin.com/api/v2/#operation/api.languages.delete 53 | */ 54 | deleteCustomLanguage(languageId: string): Promise { 55 | const url = `${this.url}/languages/${languageId}`; 56 | return this.delete(url, this.defaultConfig()); 57 | } 58 | 59 | /** 60 | * @param languageId language identifier 61 | * @param request request body 62 | * @see https://developer.crowdin.com/api/v2/#operation/api.languages.patch 63 | */ 64 | editCustomLanguage(languageId: string, request: PatchRequest[]): Promise> { 65 | const url = `${this.url}/languages/${languageId}`; 66 | return this.patch(url, request, this.defaultConfig()); 67 | } 68 | } 69 | 70 | export namespace LanguagesModel { 71 | export interface Language { 72 | id: string; 73 | name: string; 74 | editorCode: string; 75 | twoLettersCode: string; 76 | threeLettersCode: string; 77 | locale: string; 78 | androidCode: string; 79 | osxCode: string; 80 | osxLocale: string; 81 | pluralCategoryNames: string[]; 82 | pluralRules: string; 83 | pluralExamples: string[]; 84 | textDirection: TextDirection; 85 | dialectOf: string; 86 | } 87 | 88 | export interface AddLanguageRequest { 89 | name: string; 90 | code: string; 91 | localeCode: string; 92 | textDirection: TextDirection; 93 | pluralCategoryNames: string[]; 94 | threeLettersCode: string; 95 | twoLettersCode?: string; 96 | dialectOf?: string; 97 | } 98 | 99 | export type TextDirection = 'ltr' | 'rtl'; 100 | } 101 | -------------------------------------------------------------------------------- /src/machineTranslation/index.ts: -------------------------------------------------------------------------------- 1 | import { CrowdinApi, isOptionalNumber, PaginationOptions, PatchRequest, ResponseList, ResponseObject } from '../core'; 2 | 3 | /** 4 | * Machine Translation Engines (MTE) are the sources for pre-translations. 5 | * 6 | * Use API to add, update, and delete specific MTE. 7 | */ 8 | export class MachineTranslation extends CrowdinApi { 9 | /** 10 | * @param options optional parameters for the request 11 | * @see https://developer.crowdin.com/api/v2/#operation/api.mts.getMany 12 | */ 13 | listMts( 14 | options?: MachineTranslationModel.ListMTsOptions, 15 | ): Promise>; 16 | /** 17 | * @param groupId group identifier 18 | * @param limit maximum number of items to retrieve (default 25) 19 | * @param offset starting offset in the collection (default 0) 20 | * @deprecated optional parameters should be passed through an object 21 | * @see https://developer.crowdin.com/api/v2/#operation/api.mts.getMany 22 | */ 23 | listMts( 24 | groupId?: number, 25 | limit?: number, 26 | offset?: number, 27 | ): Promise>; 28 | listMts( 29 | options?: number | MachineTranslationModel.ListMTsOptions, 30 | deprecatedLimit?: number, 31 | deprecatedOffset?: number, 32 | ): Promise> { 33 | if (isOptionalNumber(options, '0' in arguments)) { 34 | options = { groupId: options, limit: deprecatedLimit, offset: deprecatedOffset }; 35 | } 36 | let url = `${this.url}/mts`; 37 | url = this.addQueryParam(url, 'groupId', options.groupId); 38 | return this.getList(url, options.limit, options.offset); 39 | } 40 | 41 | /** 42 | * @param request request body 43 | * @see https://support.crowdin.com/enterprise/api/#operation/api.mts.post 44 | */ 45 | createMt( 46 | request: MachineTranslationModel.CreateMachineTranslationRequest, 47 | ): Promise> { 48 | const url = `${this.url}/mts`; 49 | return this.post(url, request, this.defaultConfig()); 50 | } 51 | 52 | /** 53 | * @param mtId mt identifier 54 | * @see https://developer.crowdin.com/api/v2/#operation/api.mts.getMany 55 | */ 56 | getMt(mtId: number): Promise> { 57 | const url = `${this.url}/mts/${mtId}`; 58 | return this.get(url, this.defaultConfig()); 59 | } 60 | 61 | /** 62 | * @param mtId mt identifier 63 | * @see https://support.crowdin.com/enterprise/api/#operation/api.mts.delete 64 | */ 65 | deleteMt(mtId: number): Promise { 66 | const url = `${this.url}/mts/${mtId}`; 67 | return this.delete(url, this.defaultConfig()); 68 | } 69 | 70 | /** 71 | * @param mtId mt identifier 72 | * @param request request body 73 | * @see https://support.crowdin.com/enterprise/api/#operation/api.mts.patch 74 | */ 75 | updateMt( 76 | mtId: number, 77 | request: PatchRequest[], 78 | ): Promise> { 79 | const url = `${this.url}/mts/${mtId}`; 80 | return this.patch(url, request, this.defaultConfig()); 81 | } 82 | 83 | /** 84 | * @param mtId mt identifier 85 | * @param request request body 86 | * @see https://developer.crowdin.com/api/v2/#operation/api.mts.translations.post 87 | */ 88 | translate( 89 | mtId: number, 90 | request: MachineTranslationModel.TranslateRequest, 91 | ): Promise> { 92 | const url = `${this.url}/mts/${mtId}/translations`; 93 | return this.post(url, request, this.defaultConfig()); 94 | } 95 | } 96 | 97 | export namespace MachineTranslationModel { 98 | export interface MachineTranslation { 99 | id: number; 100 | groupId: number; 101 | name: string; 102 | type: number; 103 | credentials: Credentials; 104 | projectIds: number[]; 105 | supportedLanguageIds: string[]; 106 | supportedLanguagePairs: Record; 107 | enabledLanguageIds: string[]; 108 | enabledProjectIds: number[]; 109 | isEnabled: boolean; 110 | } 111 | 112 | export type Credentials = 113 | | { apiKey: string } 114 | | { credentials: string } 115 | | { model: string; apiKey: string } 116 | | { isSystemCredentials: boolean; apiKey: string } 117 | | { endpoint: string; apiKey: string } 118 | | { url: string } 119 | | { accessKey: string; secretKey: string }; 120 | 121 | export interface CreateMachineTranslationRequest { 122 | name: string; 123 | type: string; 124 | credentials: Credentials; 125 | groupId?: number; 126 | enabledLanguageIds?: string[]; 127 | enabledProjectIds?: number[]; 128 | isEnabled?: boolean; 129 | } 130 | 131 | export interface TranslateRequest { 132 | languageRecognitionProvider?: LanguageRecognitionProvider; 133 | sourceLanguageId?: string; 134 | targetLanguageId: string; 135 | strings?: string[]; 136 | } 137 | 138 | export interface TranslateResponse { 139 | sourceLanguageId: string; 140 | targetLanguageId: string; 141 | strings: string[]; 142 | translations: string[]; 143 | } 144 | 145 | export type LanguageRecognitionProvider = 'crowdin' | 'engine'; 146 | 147 | export interface ListMTsOptions extends PaginationOptions { 148 | groupId?: number; 149 | } 150 | } 151 | -------------------------------------------------------------------------------- /src/notifications/index.ts: -------------------------------------------------------------------------------- 1 | import { CrowdinApi } from '../core'; 2 | 3 | export class Notifications extends CrowdinApi { 4 | /** 5 | * @param request request body 6 | * @see https://developer.crowdin.com/api/v2/#operation/api.notify.post 7 | */ 8 | sendNotificationToAuthenticatedUser(request: NotificationsModel.Notification): Promise { 9 | const url = `${this.url}/notify`; 10 | return this.post(url, request, this.defaultConfig()); 11 | } 12 | 13 | /** 14 | * @param projectId project identifier 15 | * @param request request body 16 | * @see https://developer.crowdin.com/api/v2/#operation/api.projects.notify.post 17 | */ 18 | sendNotificationToProjectMembers( 19 | projectId: number, 20 | request: NotificationsModel.NotificationByUsers | NotificationsModel.NotificationByRole, 21 | ): Promise { 22 | const url = `${this.url}/projects/${projectId}/notify`; 23 | return this.post(url, request, this.defaultConfig()); 24 | } 25 | 26 | /** 27 | * @param request request body 28 | * @see https://developer.crowdin.com/enterprise/api/v2/#operation/api.notify.post 29 | */ 30 | sendNotificationToOrganizationMembers( 31 | request: 32 | | NotificationsModel.Notification 33 | | NotificationsModel.NotificationByUsers 34 | | NotificationsModel.NotificationByRole, 35 | ): Promise { 36 | const url = `${this.url}/notify`; 37 | return this.post(url, request, this.defaultConfig()); 38 | } 39 | } 40 | 41 | export namespace NotificationsModel { 42 | export interface Notification { 43 | message: string; 44 | } 45 | 46 | export interface NotificationByUsers extends Notification { 47 | userIds: number[]; 48 | } 49 | 50 | export interface NotificationByRole extends Notification { 51 | role: 'owner' | 'admin'; 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /src/organizationWebhooks/index.ts: -------------------------------------------------------------------------------- 1 | import { CrowdinApi, PaginationOptions, PatchRequest, ResponseList, ResponseObject } from '../core'; 2 | import { WebhooksModel } from '../webhooks'; 3 | 4 | /** 5 | * Webhooks allow you to collect information about events that happen in your Crowdin account. 6 | * 7 | * You can select the request type, content type, and add a custom payload, which allows you to create integrations with other systems on your own. 8 | */ 9 | export class OrganizationWebhooks extends CrowdinApi { 10 | /** 11 | * @param options optional pagination parameters for the request 12 | * @see https://developer.crowdin.com/api/v2/#operation/api.webhooks.getMany 13 | */ 14 | listWebhooks(options?: PaginationOptions): Promise> { 15 | const url = `${this.url}/webhooks`; 16 | return this.getList(url, options?.limit, options?.offset); 17 | } 18 | 19 | /** 20 | * @param request request body 21 | * @see https://developer.crowdin.com/api/v2/#operation/api.webhooks.post 22 | */ 23 | addWebhook( 24 | request: OrganizationWebhooksModel.AddOrganizationWebhookRequest, 25 | ): Promise> { 26 | const url = `${this.url}/webhooks`; 27 | return this.post(url, request, this.defaultConfig()); 28 | } 29 | 30 | /** 31 | * @param webhookId webhook identifier 32 | * @see https://developer.crowdin.com/api/v2/#operation/api.webhooks.get 33 | */ 34 | getWebhook(webhookId: number): Promise> { 35 | const url = `${this.url}/webhooks/${webhookId}`; 36 | return this.get(url, this.defaultConfig()); 37 | } 38 | 39 | /** 40 | * @param webhookId webhook identifier 41 | * @see https://developer.crowdin.com/api/v2/#operation/api.webhooks.delete 42 | */ 43 | deleteWebhook(webhookId: number): Promise { 44 | const url = `${this.url}/webhooks/${webhookId}`; 45 | return this.delete(url, this.defaultConfig()); 46 | } 47 | 48 | /** 49 | * @param webhookId webhook identifier 50 | * @param request request body 51 | * @see https://developer.crowdin.com/api/v2/#operation/api.webhooks.patch 52 | */ 53 | editWebhook( 54 | webhookId: number, 55 | request: PatchRequest[], 56 | ): Promise> { 57 | const url = `${this.url}/webhooks/${webhookId}`; 58 | return this.patch(url, request, this.defaultConfig()); 59 | } 60 | } 61 | 62 | export namespace OrganizationWebhooksModel { 63 | export type OrganizationWebhook = Omit & { 64 | events: Event[]; 65 | }; 66 | 67 | export type AddOrganizationWebhookRequest = Omit & { 68 | events: Event[]; 69 | }; 70 | 71 | export type Event = 'group.created' | 'group.deleted' | 'project.created' | 'project.deleted'; 72 | } 73 | -------------------------------------------------------------------------------- /src/securityLogs/index.ts: -------------------------------------------------------------------------------- 1 | import { CrowdinApi, PaginationOptions, ResponseList, ResponseObject } from '../core'; 2 | 3 | export class SecurityLogs extends CrowdinApi { 4 | /** 5 | * @param options optional parameters for the request 6 | * @see https://support.crowdin.com/enterprise/api/#operation/api.projects.workflow-steps.getMany 7 | */ 8 | listOrganizationSecurityLogs( 9 | options?: SecurityLogsModel.ListOrganizationSecurityLogsParams, 10 | ): Promise> { 11 | let url = `${this.url}/security-logs`; 12 | url = this.addQueryParam(url, 'event', options?.event); 13 | url = this.addQueryParam(url, 'createdAfter', options?.createdAfter); 14 | url = this.addQueryParam(url, 'createdBefore', options?.createdBefore); 15 | url = this.addQueryParam(url, 'ipAddress', options?.ipAddress); 16 | url = this.addQueryParam(url, 'userId', options?.userId); 17 | return this.getList(url, options?.limit, options?.offset); 18 | } 19 | 20 | /** 21 | * @param securityLogId security log identifier 22 | * @see https://developer.crowdin.com/enterprise/api/v2/#operation/api.security-logs.get 23 | */ 24 | getOrganizationSecurityLog(securityLogId: number): Promise> { 25 | const url = `${this.url}/security-logs/${securityLogId}`; 26 | return this.get(url, this.defaultConfig()); 27 | } 28 | 29 | /** 30 | * @param userId user identifier 31 | * @param options optional parameters for the request 32 | * @see https://developer.crowdin.com/api/v2/#operation/api.users.security-logs.getMany 33 | */ 34 | listUserSecurityLogs( 35 | userId: number, 36 | options?: SecurityLogsModel.ListUserSecurityLogsParams, 37 | ): Promise> { 38 | let url = `${this.url}/users/${userId}/security-logs`; 39 | url = this.addQueryParam(url, 'event', options?.event); 40 | url = this.addQueryParam(url, 'createdAfter', options?.createdAfter); 41 | url = this.addQueryParam(url, 'createdBefore', options?.createdBefore); 42 | url = this.addQueryParam(url, 'ipAddress', options?.ipAddress); 43 | return this.getList(url, options?.limit, options?.offset); 44 | } 45 | 46 | /** 47 | * @param userId security log identifier 48 | * @param securityLogId security log identifier 49 | * @see https://developer.crowdin.com/api/v2/#operation/api.users.security-logs.get 50 | */ 51 | getUserSecurityLog(userId: number, securityLogId: number): Promise> { 52 | const url = `${this.url}/users/${userId}/security-logs/${securityLogId}`; 53 | return this.get(url, this.defaultConfig()); 54 | } 55 | } 56 | 57 | export namespace SecurityLogsModel { 58 | export type Event = 59 | | 'login' 60 | | 'password.set' 61 | | 'password.change' 62 | | 'email.change' 63 | | 'login.change' 64 | | 'personal_token.issued' 65 | | 'personal_token.revoked' 66 | | 'mfa.enabled' 67 | | 'mfa.disabled' 68 | | 'session.revoke' 69 | | 'session.revoke_all' 70 | | 'sso.connect' 71 | | 'sso.disconnect' 72 | | 'user.remove' 73 | | 'application.connected' 74 | | 'application.disconnected' 75 | | 'webauthn.created' 76 | | 'webauthn.deleted' 77 | | 'trusted_device.remove' 78 | | 'trusted_device.remove_all' 79 | | 'device_verification.enabled' 80 | | 'device_verification.disabled'; 81 | 82 | export interface ListOrganizationSecurityLogsParams extends PaginationOptions { 83 | event?: Event; 84 | createdAfter?: string; 85 | createdBefore?: string; 86 | ipAddress?: string; 87 | userId?: number; 88 | } 89 | 90 | export type ListUserSecurityLogsParams = Omit; 91 | 92 | export interface SecurityLog { 93 | id: number; 94 | event: string; 95 | info: string; 96 | userId: number; 97 | location: string; 98 | ipAddress: string; 99 | deviceName: string; 100 | createdAt: string; 101 | } 102 | } 103 | -------------------------------------------------------------------------------- /src/stringComments/index.ts: -------------------------------------------------------------------------------- 1 | import { CrowdinApi, isOptionalNumber, PaginationOptions, PatchRequest, ResponseList, ResponseObject } from '../core'; 2 | 3 | /** 4 | * Use API to list, add, get, edit or delete string comments. 5 | */ 6 | export class StringComments extends CrowdinApi { 7 | /** 8 | * @param projectId project identifier 9 | * @param options optional parameters for the requesr 10 | * @see https://developer.crowdin.com/api/v2/#operation/api.projects.comments.getMany 11 | */ 12 | listStringComments( 13 | projectId: number, 14 | options?: StringCommentsModel.ListStringCommentsOptions, 15 | ): Promise>; 16 | /** 17 | * @param projectId project identifier 18 | * @param stringId string identifier 19 | * @param type defines string comment type 20 | * @param targetLanguageId defines target language id. It can be one target language id or a list of comma-separated ones 21 | * @param issueType defines issue type. It can be one issue type or a list of comma-separated ones 22 | * @param issueStatus defines issue resolution status 23 | * @deprecated optional parameters should be passed through an object 24 | * @see https://developer.crowdin.com/api/v2/#operation/api.projects.comments.getMany 25 | */ 26 | listStringComments( 27 | projectId: number, 28 | stringId?: number, 29 | type?: StringCommentsModel.Type, 30 | targetLanguageId?: string, 31 | issueType?: StringCommentsModel.IssueType, 32 | issueStatus?: StringCommentsModel.IssueStatus, 33 | ): Promise>; 34 | 35 | listStringComments( 36 | projectId: number, 37 | options?: number | StringCommentsModel.ListStringCommentsOptions, 38 | deprecatedType?: StringCommentsModel.Type, 39 | deprecatedTargetLanguageId?: string, 40 | deprecatedIssueType?: StringCommentsModel.IssueType, 41 | deprecatedIssueStatus?: StringCommentsModel.IssueStatus, 42 | ): Promise> { 43 | let url = `${this.url}/projects/${projectId}/comments`; 44 | if (isOptionalNumber(options, '1' in arguments)) { 45 | options = { 46 | stringId: options, 47 | type: deprecatedType, 48 | targetLanguageId: deprecatedTargetLanguageId, 49 | issueStatus: deprecatedIssueStatus, 50 | issueType: deprecatedIssueType, 51 | }; 52 | } 53 | url = this.addQueryParam(url, 'stringId', options.stringId); 54 | url = this.addQueryParam(url, 'type', options.type); 55 | url = this.addQueryParam(url, 'targetLanguageId', options.targetLanguageId); 56 | url = this.addQueryParam(url, 'issueType', options.issueType); 57 | url = this.addQueryParam(url, 'issueStatus', options.issueStatus); 58 | url = this.addQueryParam(url, 'orderBy', options.orderBy); 59 | return this.getList(url, options.limit, options.offset); 60 | } 61 | 62 | /** 63 | * @param projectId project identifier 64 | * @param request request body 65 | * @see https://developer.crowdin.com/api/v2/#operation/api.projects.comments.post 66 | */ 67 | addStringComment( 68 | projectId: number, 69 | request: StringCommentsModel.AddStringCommentRequest, 70 | ): Promise> { 71 | const url = `${this.url}/projects/${projectId}/comments`; 72 | return this.post(url, request, this.defaultConfig()); 73 | } 74 | 75 | /** 76 | * @param projectId project identifier 77 | * @param stringCommentId string comment identifier 78 | * @see https://developer.crowdin.com/api/v2/#operation/api.projects.comments.get 79 | */ 80 | getStringComment( 81 | projectId: number, 82 | stringCommentId: number, 83 | ): Promise> { 84 | const url = `${this.url}/projects/${projectId}/comments/${stringCommentId}`; 85 | return this.get(url, this.defaultConfig()); 86 | } 87 | 88 | /** 89 | * @param projectId project identifier 90 | * @param stringCommentId string comment identifier 91 | * @see https://developer.crowdin.com/api/v2/#operation/api.projects.comments.delete 92 | */ 93 | deleteStringComment(projectId: number, stringCommentId: number): Promise { 94 | const url = `${this.url}/projects/${projectId}/comments/${stringCommentId}`; 95 | return this.delete(url, this.defaultConfig()); 96 | } 97 | 98 | /** 99 | * @param projectId project identifier 100 | * @param stringCommentId string comment identifier 101 | * @param request request body 102 | * @see https://developer.crowdin.com/api/v2/#operation/api.projects.comments.patch 103 | */ 104 | editStringComment( 105 | projectId: number, 106 | stringCommentId: number, 107 | request: PatchRequest[], 108 | ): Promise> { 109 | const url = `${this.url}/projects/${projectId}/comments/${stringCommentId}`; 110 | return this.patch(url, request, this.defaultConfig()); 111 | } 112 | 113 | /** 114 | * @param projectId project identifier 115 | * @param request request body 116 | * @see https://developer.crowdin.com/api/v2/#operation/api.projects.comments.batchPatch 117 | */ 118 | stringCommentBatchOperations( 119 | projectId: number, 120 | request: PatchRequest[], 121 | ): Promise> { 122 | const url = `${this.url}/projects/${projectId}/comments`; 123 | return this.patch(url, request, this.defaultConfig()); 124 | } 125 | } 126 | 127 | export namespace StringCommentsModel { 128 | export interface ListStringCommentsOptions extends PaginationOptions { 129 | stringId?: number; 130 | type?: Type; 131 | targetLanguageId?: string; 132 | issueType?: IssueType; 133 | issueStatus?: IssueStatus; 134 | orderBy?: string; 135 | } 136 | 137 | export interface StringComment { 138 | id: number; 139 | isShared?: boolean; 140 | text: string; 141 | userId: number; 142 | stringId: number; 143 | user: User; 144 | string: StringModel; 145 | projectId: number; 146 | languageId: string; 147 | type: Type; 148 | issueType: IssueType; 149 | issueStatus: IssueStatus; 150 | resolverId: number; 151 | senderOrganization: { 152 | id: number; 153 | domain: string; 154 | }; 155 | resolverOrganization: { 156 | id: number; 157 | domain: string; 158 | }; 159 | resolver: User; 160 | resolvedAt: string; 161 | createdAt: string; 162 | } 163 | 164 | export interface User { 165 | id: number; 166 | username: string; 167 | fullName: string; 168 | avatarUrl: string; 169 | } 170 | 171 | export interface StringModel { 172 | id: number; 173 | text: string; 174 | type: string; 175 | hasPlurals: boolean; 176 | isIcu: boolean; 177 | context: string; 178 | fileId: number; 179 | } 180 | 181 | export interface AddStringCommentRequest { 182 | stringId: number; 183 | text: string; 184 | targetLanguageId: string; 185 | type: Type; 186 | isShared?: boolean; 187 | issueType?: IssueType; 188 | } 189 | 190 | export type Type = 'comment' | 'issue'; 191 | 192 | export type IssueType = 'general_question' | 'translation_mistake' | 'context_request' | 'source_mistake'; 193 | 194 | export type IssueStatus = 'unresolved' | 'resolved'; 195 | } 196 | -------------------------------------------------------------------------------- /src/vendors/index.ts: -------------------------------------------------------------------------------- 1 | import { CrowdinApi, isOptionalNumber, PaginationOptions, ResponseList } from '../core'; 2 | 3 | /** 4 | * Vendors are the organizations that provide professional translation services. 5 | * To assign a Vendor to a project workflow you should invite an existing Organization to be a Vendor for you. 6 | * 7 | * Use API to get the list of the Vendors you already invited to your organization. 8 | */ 9 | export class Vendors extends CrowdinApi { 10 | /** 11 | * @param options optional pagination parameters for the request 12 | * @see https://support.crowdin.com/enterprise/api/#operation/api.vendors.getMany 13 | */ 14 | listVendors(options?: PaginationOptions): Promise>; 15 | /** 16 | * @param limit maximum number of items to retrieve (default 25) 17 | * @param offset starting offset in the collection (default 0) 18 | * @deprecated optional parameters should be passed through an object 19 | * @see https://support.crowdin.com/enterprise/api/#operation/api.vendors.getMany 20 | */ 21 | listVendors(limit?: number, offset?: number): Promise>; 22 | listVendors( 23 | options?: number | PaginationOptions, 24 | deprecatedOffset?: number, 25 | ): Promise> { 26 | if (isOptionalNumber(options, '0' in arguments)) { 27 | options = { limit: options, offset: deprecatedOffset }; 28 | } 29 | const url = `${this.url}/vendors`; 30 | return this.getList(url, options.limit, options.offset); 31 | } 32 | } 33 | 34 | export namespace VendorsModel { 35 | export interface Vendor { 36 | id: number; 37 | name: string; 38 | description: string; 39 | status: 'pending' | 'confirmed' | 'rejected'; 40 | webUrl: string; 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/webhooks/index.ts: -------------------------------------------------------------------------------- 1 | import { CrowdinApi, isOptionalNumber, PaginationOptions, PatchRequest, ResponseList, ResponseObject } from '../core'; 2 | 3 | /** 4 | * Webhooks allow you to collect information about events that happen in your Crowdin projects. 5 | * 6 | * You can select the request type, content type, and add a custom payload, which allows you to create integrations with other systems on your own. 7 | */ 8 | export class Webhooks extends CrowdinApi { 9 | /** 10 | * @param projectId project identifier 11 | * @param options optional pagination parameters for the request 12 | * @see https://developer.crowdin.com/api/v2/#operation/api.projects.webhooks.getMany 13 | */ 14 | listWebhooks(projectId: number, options?: PaginationOptions): Promise>; 15 | /** 16 | * @param projectId project identifier 17 | * @param limit maximum number of items to retrieve (default 25) 18 | * @param offset starting offset in the collection (default 0) 19 | * @deprecated optional parameters should be passed through an object 20 | * @see https://developer.crowdin.com/api/v2/#operation/api.projects.webhooks.getMany 21 | */ 22 | listWebhooks(projectId: number, limit?: number, offset?: number): Promise>; 23 | listWebhooks( 24 | projectId: number, 25 | options?: number | PaginationOptions, 26 | deprecatedOffset?: number, 27 | ): Promise> { 28 | if (isOptionalNumber(options, '1' in arguments)) { 29 | options = { limit: options, offset: deprecatedOffset }; 30 | } 31 | const url = `${this.url}/projects/${projectId}/webhooks`; 32 | return this.getList(url, options.limit, options.offset); 33 | } 34 | 35 | /** 36 | * @param projectId project identifier 37 | * @param request request body 38 | * @see https://developer.crowdin.com/api/v2/#operation/api.projects.webhooks.post 39 | */ 40 | addWebhook( 41 | projectId: number, 42 | request: WebhooksModel.AddWebhookRequest, 43 | ): Promise> { 44 | const url = `${this.url}/projects/${projectId}/webhooks`; 45 | return this.post(url, request, this.defaultConfig()); 46 | } 47 | 48 | /** 49 | * @param projectId project identifier 50 | * @param webhookId webhook identifier 51 | * @see https://developer.crowdin.com/api/v2/#operation/api.projects.webhooks.get 52 | */ 53 | getWebhook(projectId: number, webhookId: number): Promise> { 54 | const url = `${this.url}/projects/${projectId}/webhooks/${webhookId}`; 55 | return this.get(url, this.defaultConfig()); 56 | } 57 | 58 | /** 59 | * @param projectId project identifier 60 | * @param webhookId webhook identifier 61 | * @see https://developer.crowdin.com/api/v2/#operation/api.projects.webhooks.delete 62 | */ 63 | deleteWebhook(projectId: number, webhookId: number): Promise { 64 | const url = `${this.url}/projects/${projectId}/webhooks/${webhookId}`; 65 | return this.delete(url, this.defaultConfig()); 66 | } 67 | 68 | /** 69 | * @param projectId project identifier 70 | * @param webhookId webhook identifier 71 | * @param request request body 72 | * @see https://developer.crowdin.com/api/v2/#operation/api.projects.webhooks.patch 73 | */ 74 | editWebhook( 75 | projectId: number, 76 | webhookId: number, 77 | request: PatchRequest[], 78 | ): Promise> { 79 | const url = `${this.url}/projects/${projectId}/webhooks/${webhookId}`; 80 | return this.patch(url, request, this.defaultConfig()); 81 | } 82 | } 83 | 84 | export namespace WebhooksModel { 85 | export interface Webhook { 86 | id: number; 87 | projectId: number; 88 | name: string; 89 | url: string; 90 | events: Event[]; 91 | headers: Record; 92 | payload: Record; 93 | isActive: boolean; 94 | batchingEnabled: boolean; 95 | requestType: RequestType; 96 | contentType: ContentType; 97 | createdAt: string; 98 | updatedAt: string; 99 | } 100 | 101 | export interface AddWebhookRequest { 102 | name: string; 103 | url: string; 104 | events: Event[]; 105 | requestType: RequestType; 106 | isActive?: boolean; 107 | batchingEnabled?: boolean; 108 | contentType?: ContentType; 109 | headers?: Record; 110 | payload?: Record; 111 | } 112 | 113 | export type ContentType = 'multipart/form-data' | 'application/json' | 'application/x-www-form-urlencoded'; 114 | 115 | export type Event = 116 | | 'file.added' 117 | | 'file.updated' 118 | | 'file.reverted' 119 | | 'file.deleted' 120 | | 'file.translated' 121 | | 'file.approved' 122 | | 'project.translated' 123 | | 'project.approved' 124 | | 'project.built' 125 | | 'translation.updated' 126 | | 'string.added' 127 | | 'string.updated' 128 | | 'string.deleted' 129 | | 'stringComment.created' 130 | | 'stringComment.updated' 131 | | 'stringComment.deleted' 132 | | 'stringComment.restored' 133 | | 'suggestion.added' 134 | | 'suggestion.updated' 135 | | 'suggestion.deleted' 136 | | 'suggestion.approved' 137 | | 'suggestion.disapproved' 138 | | 'task.added' 139 | | 'task.statusChanged' 140 | | 'task.deleted'; 141 | 142 | export type RequestType = 'POST' | 'GET'; 143 | } 144 | -------------------------------------------------------------------------------- /src/workflows/index.ts: -------------------------------------------------------------------------------- 1 | import { CrowdinApi, isOptionalNumber, PaginationOptions, ResponseList, ResponseObject } from '../core'; 2 | import { SourceStringsModel } from '../sourceStrings'; 3 | 4 | /** 5 | * Workflows are the sequences of steps that content in your project should go through (e.g. pre-translation, translation, proofreading). 6 | * You can use a default template or create the one that works best for you and assign it to the needed projects. 7 | * 8 | * Use API to get the list of workflow templates available in your organization and to check the details of a specific template. 9 | */ 10 | export class Workflows extends CrowdinApi { 11 | /** 12 | * @param projectId project identifier 13 | * @param options optional pagination parameters for the request 14 | * @see https://support.crowdin.com/enterprise/api/#operation/api.projects.workflow-steps.getMany 15 | */ 16 | listWorkflowSteps( 17 | projectId: number, 18 | options?: PaginationOptions, 19 | ): Promise>; 20 | /** 21 | * @param projectId project identifier 22 | * @param limit maximum number of items to retrieve (default 25) 23 | * @param offset starting offset in the collection (default 0) 24 | * @deprecated optional parameters should be passed through an object 25 | * @see https://support.crowdin.com/enterprise/api/#operation/api.projects.workflow-steps.getMany 26 | */ 27 | listWorkflowSteps( 28 | projectId: number, 29 | limit?: number, 30 | offset?: number, 31 | ): Promise>; 32 | listWorkflowSteps( 33 | projectId: number, 34 | options?: number | PaginationOptions, 35 | deprecatedOffset?: number, 36 | ): Promise> { 37 | if (isOptionalNumber(options, '1' in arguments)) { 38 | options = { limit: options, offset: deprecatedOffset }; 39 | } 40 | const url = `${this.url}/projects/${projectId}/workflow-steps`; 41 | return this.getList(url, options.limit, options.offset); 42 | } 43 | 44 | /** 45 | * @param projectId project identifier 46 | * @param stepId workflow step identifier 47 | * @see https://support.crowdin.com/enterprise/api/#operation/api.projects.workflow-steps.getMany 48 | */ 49 | getWorkflowStep(projectId: number, stepId: number): Promise> { 50 | const url = `${this.url}/projects/${projectId}/workflow-steps/${stepId}`; 51 | return this.get(url, this.defaultConfig()); 52 | } 53 | 54 | /** 55 | * @param projectId project identifier 56 | * @param stepId workflow step identifier 57 | * @param options optional parameters for the request 58 | * @see https://support.crowdin.com/developer/enterprise/api/v2/#tag/Workflows/operation/api.projects.workflow-steps.strings.getMany 59 | */ 60 | listStringsOnTheWorkflowStep( 61 | projectId: number, 62 | stepId: number, 63 | options?: WorkflowModel.ListStringsOntheWorkflowStepOptions, 64 | ): Promise> { 65 | let url = `${this.url}/projects/${projectId}/workflow-steps/${stepId}/strings`; 66 | url = this.addQueryParam(url, 'languageIds', options?.languageIds); 67 | url = this.addQueryParam(url, 'orderBy', options?.orderBy); 68 | url = this.addQueryParam(url, 'status', options?.status); 69 | return this.getList(url, options?.limit, options?.offset); 70 | } 71 | 72 | /** 73 | * @param options optional parameters for the request 74 | * @see https://support.crowdin.com/enterprise/api/#operation/api.workflow-templates.getMany 75 | */ 76 | listWorkflowTemplates( 77 | options?: WorkflowModel.ListWorkflowTemplatesOptions, 78 | ): Promise>; 79 | /** 80 | * @param groupId group identifier 81 | * @param limit maximum number of items to retrieve (default 25) 82 | * @param offset starting offset in the collection (default 0) 83 | * @deprecated optional parameters should be passed through an object 84 | * @see https://support.crowdin.com/enterprise/api/#operation/api.workflow-templates.getMany 85 | */ 86 | listWorkflowTemplates( 87 | groupId?: number, 88 | limit?: number, 89 | offset?: number, 90 | ): Promise>; 91 | listWorkflowTemplates( 92 | options?: number | WorkflowModel.ListWorkflowTemplatesOptions, 93 | deprecatedLimit?: number, 94 | deprecatedOffset?: number, 95 | ): Promise> { 96 | let url = `${this.url}/workflow-templates`; 97 | if (isOptionalNumber(options, '0' in arguments)) { 98 | options = { groupId: options, limit: deprecatedLimit, offset: deprecatedOffset }; 99 | } 100 | url = this.addQueryParam(url, 'groupId', options.groupId); 101 | return this.getList(url, options.limit, options.offset); 102 | } 103 | 104 | /** 105 | * @param templateId workflow template identifier 106 | * @see https://support.crowdin.com/enterprise/api/#operation/api.workflow-templates.get 107 | */ 108 | getWorkflowTemplateInfo(templateId: number): Promise> { 109 | const url = `${this.url}/workflow-templates/${templateId}`; 110 | return this.get(url, this.defaultConfig()); 111 | } 112 | } 113 | 114 | export namespace WorkflowModel { 115 | export interface WorkflowStep { 116 | id: number; 117 | title: string; 118 | type: string; 119 | languages: string[]; 120 | config: { 121 | assignees: { [language: string]: number[] }; 122 | }; 123 | } 124 | 125 | export interface ListWorkflowTemplatesOptions extends PaginationOptions { 126 | groupId?: number; 127 | } 128 | 129 | export interface ListStringsOntheWorkflowStepOptions extends PaginationOptions { 130 | languageIds?: string; 131 | orderBy?: string; 132 | status?: 'todo' | 'done' | 'pending' | 'incomplete' | 'need_review'; 133 | } 134 | 135 | export interface Workflow { 136 | id: number; 137 | title: string; 138 | description: string; 139 | groupId: number; 140 | isDefault: boolean; 141 | webUrl: string; 142 | steps: { 143 | id: number; 144 | languages: string[]; 145 | assignees: number[]; 146 | vendorId: number; 147 | config: { 148 | minRelevant: number; 149 | autoSubstitution: boolean; 150 | }; 151 | mtId: number; 152 | }[]; 153 | } 154 | } 155 | -------------------------------------------------------------------------------- /tests/applications/api.test.ts: -------------------------------------------------------------------------------- 1 | import * as nock from 'nock'; 2 | import { Credentials, Applications } from '../../src'; 3 | 4 | describe('Applications API', () => { 5 | let scope: nock.Scope; 6 | const credentials: Credentials = { 7 | token: 'testToken', 8 | organization: 'testOrg', 9 | }; 10 | const api: Applications = new Applications(credentials); 11 | const applicationId = 'abc'; 12 | const path = 'test'; 13 | const url = `/applications/${applicationId}/api/${path}`; 14 | const installUrl = '/applications/installations'; 15 | 16 | beforeAll(() => { 17 | scope = nock(api.url) 18 | .post(installUrl, undefined, { 19 | reqheaders: { 20 | Authorization: `Bearer ${api.token}`, 21 | }, 22 | }) 23 | .reply(200) 24 | .get(installUrl + `/${applicationId}`, undefined, { 25 | reqheaders: { 26 | Authorization: `Bearer ${api.token}`, 27 | }, 28 | }) 29 | .reply(200) 30 | .get(installUrl, undefined, { 31 | reqheaders: { 32 | Authorization: `Bearer ${api.token}`, 33 | }, 34 | }) 35 | .reply(200) 36 | .patch( 37 | installUrl + `/${applicationId}`, 38 | [ 39 | { 40 | op: 'replace', 41 | path: '/permissions', 42 | }, 43 | ], 44 | { 45 | reqheaders: { 46 | Authorization: `Bearer ${api.token}`, 47 | }, 48 | }, 49 | ) 50 | .reply(200) 51 | .delete(installUrl + `/${applicationId}`, undefined, { 52 | reqheaders: { 53 | Authorization: `Bearer ${api.token}`, 54 | }, 55 | }) 56 | .reply(200) 57 | .post( 58 | url, 59 | {}, 60 | { 61 | reqheaders: { 62 | Authorization: `Bearer ${api.token}`, 63 | }, 64 | }, 65 | ) 66 | .reply(200) 67 | .get(url, undefined, { 68 | reqheaders: { 69 | Authorization: `Bearer ${api.token}`, 70 | }, 71 | }) 72 | .reply(200) 73 | .put( 74 | url, 75 | { key1: 1 }, 76 | { 77 | reqheaders: { 78 | Authorization: `Bearer ${api.token}`, 79 | }, 80 | }, 81 | ) 82 | .reply(200) 83 | .patch( 84 | url, 85 | { key2: 2 }, 86 | { 87 | reqheaders: { 88 | Authorization: `Bearer ${api.token}`, 89 | }, 90 | }, 91 | ) 92 | .reply(200) 93 | .delete(url, undefined, { 94 | reqheaders: { 95 | Authorization: `Bearer ${api.token}`, 96 | }, 97 | }) 98 | .reply(200); 99 | }); 100 | 101 | afterAll(() => { 102 | scope.done(); 103 | }); 104 | 105 | it('List Application Installations', async () => { 106 | await api.listApplicationInstallations(); 107 | }); 108 | 109 | it('Install Application', async () => { 110 | await api.installApplication({ 111 | url: 'https://localhost.dev/crowdin.json', 112 | }); 113 | }); 114 | 115 | it('Get Application Installation', async () => { 116 | await api.getApplicationInstallation(applicationId); 117 | }); 118 | 119 | it('Edit Application Installation', async () => { 120 | await api.editApplicationInstallation(applicationId, [ 121 | { 122 | op: 'replace', 123 | path: '/permissions', 124 | }, 125 | ]); 126 | }); 127 | 128 | it('Delete Application Installation', async () => { 129 | await api.deleteApplicationInstallation(applicationId); 130 | }); 131 | 132 | it('Add Application Data', async () => { 133 | await api.addApplicationData(applicationId, path, {}); 134 | }); 135 | 136 | it('Get Application Data', async () => { 137 | await api.getApplicationData(applicationId, path); 138 | }); 139 | 140 | it('Update or Restore Application Data', async () => { 141 | await api.updateOrRestoreApplicationData(applicationId, path, { key1: 1 }); 142 | }); 143 | 144 | it('Edit Application Data', async () => { 145 | await api.editApplicationData(applicationId, path, { key2: 2 }); 146 | }); 147 | 148 | it('Delete Application Data', async () => { 149 | await api.deleteApplicationData(applicationId, path); 150 | }); 151 | }); 152 | -------------------------------------------------------------------------------- /tests/bundles/api.test.ts: -------------------------------------------------------------------------------- 1 | import * as nock from 'nock'; 2 | import { Bundles, Credentials } from '../../src'; 3 | 4 | describe('Bundles API', () => { 5 | let scope: nock.Scope; 6 | const credentials: Credentials = { 7 | token: 'testToken', 8 | organization: 'testOrg', 9 | }; 10 | const api: Bundles = new Bundles(credentials); 11 | const projectId = 2; 12 | const bundleId = 3; 13 | const fileId = 4; 14 | const branchId = 41; 15 | const exportId = '123'; 16 | const exportUrl = 'test.com'; 17 | const name = 'test'; 18 | const format = 'crowdin-resx'; 19 | const exportPattern = 'strings-%two_letter_code%.resx'; 20 | 21 | const limit = 25; 22 | 23 | beforeAll(() => { 24 | scope = nock(api.url) 25 | .get(`/projects/${projectId}/bundles`, undefined, { 26 | reqheaders: { 27 | Authorization: `Bearer ${api.token}`, 28 | }, 29 | }) 30 | .reply(200, { 31 | data: [ 32 | { 33 | data: { 34 | id: bundleId, 35 | }, 36 | }, 37 | ], 38 | pagination: { 39 | offset: 0, 40 | limit: limit, 41 | }, 42 | }) 43 | .post( 44 | `/projects/${projectId}/bundles`, 45 | { 46 | format, 47 | name, 48 | sourcePatterns: [], 49 | exportPattern, 50 | }, 51 | { 52 | reqheaders: { 53 | Authorization: `Bearer ${api.token}`, 54 | }, 55 | }, 56 | ) 57 | .reply(200, { 58 | data: { 59 | id: bundleId, 60 | }, 61 | }) 62 | .get(`/projects/${projectId}/bundles/${bundleId}`, undefined, { 63 | reqheaders: { 64 | Authorization: `Bearer ${api.token}`, 65 | }, 66 | }) 67 | .reply(200, { 68 | data: { 69 | id: bundleId, 70 | }, 71 | }) 72 | .delete(`/projects/${projectId}/bundles/${bundleId}`, undefined, { 73 | reqheaders: { 74 | Authorization: `Bearer ${api.token}`, 75 | }, 76 | }) 77 | .reply(200) 78 | .patch( 79 | `/projects/${projectId}/bundles/${bundleId}`, 80 | [ 81 | { 82 | value: format, 83 | op: 'replace', 84 | path: '/format', 85 | }, 86 | ], 87 | { 88 | reqheaders: { 89 | Authorization: `Bearer ${api.token}`, 90 | }, 91 | }, 92 | ) 93 | .reply(200, { 94 | data: { 95 | id: bundleId, 96 | }, 97 | }) 98 | .get(`/projects/${projectId}/bundles/${bundleId}/exports/${exportId}/download`, undefined, { 99 | reqheaders: { 100 | Authorization: `Bearer ${api.token}`, 101 | }, 102 | }) 103 | .reply(200, { 104 | data: { 105 | url: exportUrl, 106 | }, 107 | }) 108 | .post(`/projects/${projectId}/bundles/${bundleId}/exports`, undefined, { 109 | reqheaders: { 110 | Authorization: `Bearer ${api.token}`, 111 | }, 112 | }) 113 | .reply(200, { 114 | data: { 115 | identifier: exportId, 116 | }, 117 | }) 118 | .get(`/projects/${projectId}/bundles/${bundleId}/exports/${exportId}`, undefined, { 119 | reqheaders: { 120 | Authorization: `Bearer ${api.token}`, 121 | }, 122 | }) 123 | .reply(200, { 124 | data: { 125 | identifier: exportId, 126 | }, 127 | }) 128 | .get(`/projects/${projectId}/bundles/${bundleId}/files`, undefined, { 129 | reqheaders: { 130 | Authorization: `Bearer ${api.token}`, 131 | }, 132 | }) 133 | .reply(200, { 134 | data: [ 135 | { 136 | data: { 137 | id: fileId, 138 | }, 139 | }, 140 | ], 141 | pagination: { 142 | offset: 0, 143 | limit: limit, 144 | }, 145 | }) 146 | .get(`/projects/${projectId}/bundles/${bundleId}/branches`, undefined, { 147 | reqheaders: { 148 | Authorization: `Bearer ${api.token}`, 149 | }, 150 | }) 151 | .reply(200, { 152 | data: [ 153 | { 154 | data: { 155 | id: branchId, 156 | }, 157 | }, 158 | ], 159 | pagination: { 160 | offset: 0, 161 | limit: limit, 162 | }, 163 | }); 164 | }); 165 | 166 | afterAll(() => { 167 | scope.done(); 168 | }); 169 | 170 | it('List bundles', async () => { 171 | const bundles = await api.listBundles(projectId); 172 | expect(bundles.data.length).toBe(1); 173 | expect(bundles.data[0].data.id).toBe(bundleId); 174 | expect(bundles.pagination.limit).toBe(limit); 175 | }); 176 | 177 | it('Add bundle', async () => { 178 | const bundle = await api.addBundle(projectId, { 179 | exportPattern, 180 | format, 181 | name, 182 | sourcePatterns: [], 183 | }); 184 | expect(bundle.data.id).toBe(bundleId); 185 | }); 186 | 187 | it('Get bundle', async () => { 188 | const bundle = await api.getBundle(projectId, bundleId); 189 | expect(bundle.data.id).toBe(bundleId); 190 | }); 191 | 192 | it('Delete bundle', async () => { 193 | await api.deleteBundle(projectId, bundleId); 194 | }); 195 | 196 | it('Edit bundle', async () => { 197 | const bundle = await api.editBundle(projectId, bundleId, [ 198 | { 199 | op: 'replace', 200 | path: '/format', 201 | value: format, 202 | }, 203 | ]); 204 | expect(bundle.data.id).toBe(bundleId); 205 | }); 206 | 207 | it('Download bundle', async () => { 208 | const download = await api.downloadBundle(projectId, bundleId, exportId); 209 | expect(download.data.url).toBe(exportUrl); 210 | }); 211 | 212 | it('Export bundle', async () => { 213 | const resp = await api.exportBundle(projectId, bundleId); 214 | expect(resp.data.identifier).toBe(exportId); 215 | }); 216 | 217 | it('Check bundle export status', async () => { 218 | const resp = await api.checkBundleExportStatus(projectId, bundleId, exportId); 219 | expect(resp.data.identifier).toBe(exportId); 220 | }); 221 | 222 | it('Bundle list files', async () => { 223 | const files = await api.listBundleFiles(projectId, bundleId); 224 | expect(files.data.length).toBe(1); 225 | expect(files.data[0].data.id).toBe(fileId); 226 | expect(files.pagination.limit).toBe(limit); 227 | }); 228 | 229 | it('Bundle list branches', async () => { 230 | const branches = await api.listBundleBranches(projectId, bundleId); 231 | expect(branches.data.length).toBe(1); 232 | expect(branches.data[0].data.id).toBe(branchId); 233 | expect(branches.pagination.limit).toBe(limit); 234 | }); 235 | }); 236 | -------------------------------------------------------------------------------- /tests/clients/api.test.ts: -------------------------------------------------------------------------------- 1 | import * as nock from 'nock'; 2 | import { Clients, Credentials } from '../../src'; 3 | 4 | describe('Clients API', () => { 5 | let scope: nock.Scope; 6 | const credentials: Credentials = { 7 | token: 'testToken', 8 | organization: 'testOrg', 9 | }; 10 | const api: Clients = new Clients(credentials); 11 | const id = 2; 12 | 13 | const limit = 25; 14 | 15 | beforeAll(() => { 16 | scope = nock(api.url) 17 | .get('/clients', undefined, { 18 | reqheaders: { 19 | Authorization: `Bearer ${api.token}`, 20 | }, 21 | }) 22 | .reply(200, { 23 | data: [ 24 | { 25 | data: { 26 | id: id, 27 | }, 28 | }, 29 | ], 30 | pagination: { 31 | offset: 0, 32 | limit: limit, 33 | }, 34 | }); 35 | }); 36 | 37 | afterAll(() => { 38 | scope.done(); 39 | }); 40 | 41 | it('List Clients', async () => { 42 | const clients = await api.listClients(); 43 | expect(clients.data.length).toBe(1); 44 | expect(clients.data[0].data.id).toBe(id); 45 | expect(clients.pagination.limit).toBe(limit); 46 | }); 47 | }); 48 | -------------------------------------------------------------------------------- /tests/core/error-handling.test.ts: -------------------------------------------------------------------------------- 1 | import { AxiosError } from 'axios'; 2 | import { CrowdinValidationError, handleHttpClientError } from '../../src/core/'; 3 | import { FetchClientJsonPayloadError } from '../../src/core/internal/fetch/fetchClientError'; 4 | 5 | const genericCrowdinErrorPayload = { 6 | errors: [ 7 | { 8 | error: { 9 | key: 'ERROR_KEY', 10 | errors: [ 11 | { 12 | message: 'test_errors_error_msg', 13 | code: 403, 14 | }, 15 | ], 16 | }, 17 | }, 18 | ], 19 | }; 20 | 21 | const genericCrowdinSingleErrorPayload = { 22 | error: { 23 | message: 'test_errors_error_msg', 24 | code: 403, 25 | }, 26 | }; 27 | 28 | const stringBatchOperationsErrorPayload = { 29 | errors: [ 30 | { 31 | index: 0, 32 | errors: [ 33 | { 34 | error: { 35 | key: 'ERROR_KEY', 36 | errors: [ 37 | { 38 | message: 'test_errors_error_msg', 39 | code: 'isEmpty', 40 | }, 41 | ], 42 | }, 43 | }, 44 | ], 45 | }, 46 | { 47 | index: 1, 48 | errors: [ 49 | { 50 | error: { 51 | key: 'ERROR_KEY', 52 | errors: [ 53 | { 54 | message: 'test_errors_error_msg', 55 | code: 'isEmpty', 56 | }, 57 | ], 58 | }, 59 | }, 60 | ], 61 | }, 62 | ], 63 | }; 64 | 65 | const taskCreationErrorPayload = { 66 | errors: [ 67 | { 68 | error: { 69 | key: 0, 70 | errors: [ 71 | { 72 | code: 'languageId', 73 | message: { 74 | languageNotSupported: 'This language pair is not supported by vendor', 75 | }, 76 | }, 77 | ], 78 | }, 79 | }, 80 | ], 81 | }; 82 | 83 | const unrecognizedErrorPayload = { 84 | errors: [{ foo: 'bar' }], 85 | }; 86 | 87 | const createAxiosError = (errorPayload: unknown): AxiosError => { 88 | /** 89 | * Create an axios error matching Crowdin error responses. 90 | * @see https://github.com/axios/axios/blob/3772c8fe74112a56e3e9551f894d899bc3a9443a/test/specs/core/AxiosError.spec.js#L7 91 | */ 92 | const request = { path: '/api/foo' }; 93 | const response = { 94 | status: 200, 95 | data: errorPayload, 96 | }; 97 | // eslint-disable-next-line @typescript-eslint/no-explicit-any 98 | return new AxiosError('Boom!', 'ESOMETHING', {} as any, request, response as any); 99 | }; 100 | 101 | describe('core http error handling', () => { 102 | it('should extract Crowdin API messages with axios client', async () => { 103 | const error = createAxiosError(genericCrowdinErrorPayload); 104 | try { 105 | handleHttpClientError(error); 106 | throw new Error('expected re-throw'); 107 | } catch (e) { 108 | const err = e as CrowdinValidationError; 109 | expect(err.code).toBe(400); 110 | expect(err.message).toBe('test_errors_error_msg'); 111 | expect(err.validationCodes).toEqual([ 112 | { 113 | codes: [403], 114 | key: 'ERROR_KEY', 115 | }, 116 | ]); 117 | } 118 | }); 119 | 120 | it('should extract Crowdin API single message with axios client', async () => { 121 | const error = createAxiosError(genericCrowdinSingleErrorPayload); 122 | expect(() => handleHttpClientError(error)).toThrowError(genericCrowdinSingleErrorPayload.error.message); 123 | }); 124 | 125 | it('should print full error message for stringBatchOperations axios errors', async () => { 126 | const error = createAxiosError(stringBatchOperationsErrorPayload); 127 | expect(() => handleHttpClientError(error)).toThrowError( 128 | JSON.stringify(stringBatchOperationsErrorPayload.errors, null, 2), 129 | ); 130 | }); 131 | 132 | it('should print full error message for taskCreation axios errors', async () => { 133 | const error = createAxiosError(taskCreationErrorPayload); 134 | expect(() => handleHttpClientError(error)).toThrowError( 135 | JSON.stringify(taskCreationErrorPayload.errors, null, 2), 136 | ); 137 | }); 138 | 139 | it('should return default message for unrecognized axios errors', async () => { 140 | const error = createAxiosError(unrecognizedErrorPayload); 141 | expect(() => handleHttpClientError(error)).toThrowError('Validation error'); 142 | }); 143 | 144 | it('should extract Crowdin API messages with fetch client', async () => { 145 | const error = new FetchClientJsonPayloadError('foo', genericCrowdinErrorPayload, 418); 146 | try { 147 | handleHttpClientError(error); 148 | throw new Error('expected re-throw'); 149 | } catch (e) { 150 | const err = e as CrowdinValidationError; 151 | expect(err.code).toBe(400); 152 | expect(err.message).toBe('test_errors_error_msg'); 153 | expect(err.validationCodes).toEqual([ 154 | { 155 | codes: [403], 156 | key: 'ERROR_KEY', 157 | }, 158 | ]); 159 | } 160 | }); 161 | 162 | it('should produce meaningful error messages on non-Crowdin http errors', () => { 163 | const error = new Error('generic_error'); 164 | try { 165 | handleHttpClientError(error); 166 | throw new Error('expected re-throw'); 167 | } catch (e) { 168 | const err = e as CrowdinValidationError; 169 | expect(err.code).toBe(500); 170 | expect(err.message).toBe('generic_error'); 171 | expect(err.validationCodes).toBeUndefined(); 172 | } 173 | }); 174 | }); 175 | -------------------------------------------------------------------------------- /tests/dictionaries/api.test.ts: -------------------------------------------------------------------------------- 1 | import * as nock from 'nock'; 2 | import { Credentials, Dictionaries } from '../../src'; 3 | 4 | describe('Dictionaries API', () => { 5 | let scope: nock.Scope; 6 | const credentials: Credentials = { 7 | token: 'testToken', 8 | organization: 'testOrg', 9 | }; 10 | const api: Dictionaries = new Dictionaries(credentials); 11 | const projectId = 19; 12 | const languageId = 'es'; 13 | const word1 = 'Hello'; 14 | const word2 = 'World'; 15 | 16 | const limit = 25; 17 | 18 | beforeAll(() => { 19 | scope = nock(api.url) 20 | .get(`/projects/${projectId}/dictionaries`, undefined, { 21 | reqheaders: { 22 | Authorization: `Bearer ${api.token}`, 23 | }, 24 | }) 25 | .reply(200, { 26 | data: [ 27 | { 28 | data: { 29 | languageId, 30 | words: [word1, word2], 31 | }, 32 | }, 33 | ], 34 | pagination: { 35 | offset: 0, 36 | limit: limit, 37 | }, 38 | }) 39 | .patch( 40 | `/projects/${projectId}/dictionaries/${languageId}`, 41 | [ 42 | { 43 | op: 'remove', 44 | path: '/words/0', 45 | }, 46 | ], 47 | { 48 | reqheaders: { 49 | Authorization: `Bearer ${api.token}`, 50 | }, 51 | }, 52 | ) 53 | .reply(200, { 54 | data: { 55 | languageId, 56 | words: [word2], 57 | }, 58 | }); 59 | }); 60 | 61 | afterAll(() => { 62 | scope.done(); 63 | }); 64 | 65 | it('List Dictionaries', async () => { 66 | const dictionaries = await api.listDictionaries(projectId); 67 | expect(dictionaries.data.length).toBe(1); 68 | expect(dictionaries.data[0].data.languageId).toBe(languageId); 69 | expect(dictionaries.data[0].data.words.length).toBe(2); 70 | expect(dictionaries.data[0].data.words[0]).toBe(word1); 71 | expect(dictionaries.data[0].data.words[1]).toBe(word2); 72 | expect(dictionaries.pagination.limit).toBe(limit); 73 | }); 74 | 75 | it('Edit Dictionary', async () => { 76 | const dictionary = await api.editDictionary(projectId, languageId, [ 77 | { 78 | op: 'remove', 79 | path: '/words/0', 80 | }, 81 | ]); 82 | expect(dictionary.data.languageId).toBe(languageId); 83 | expect(dictionary.data.words.length).toBe(1); 84 | expect(dictionary.data.words[0]).toBe(word2); 85 | }); 86 | }); 87 | -------------------------------------------------------------------------------- /tests/distributions/api.test.ts: -------------------------------------------------------------------------------- 1 | import * as nock from 'nock'; 2 | import { Credentials, Distributions } from '../../src'; 3 | 4 | describe('Distributions API', () => { 5 | let scope: nock.Scope; 6 | const credentials: Credentials = { 7 | token: 'testToken', 8 | organization: 'testOrg', 9 | }; 10 | const api: Distributions = new Distributions(credentials); 11 | const projectId = 2; 12 | const hash = 'qweqweqweq'; 13 | const name = 'test'; 14 | const limit = 25; 15 | 16 | beforeAll(() => { 17 | scope = nock(api.url) 18 | .get(`/projects/${projectId}/distributions`, undefined, { 19 | reqheaders: { 20 | Authorization: `Bearer ${api.token}`, 21 | }, 22 | }) 23 | .reply(200, { 24 | data: [ 25 | { 26 | data: { 27 | hash, 28 | }, 29 | }, 30 | ], 31 | pagination: { 32 | offset: 0, 33 | limit: limit, 34 | }, 35 | }) 36 | .post( 37 | `/projects/${projectId}/distributions`, 38 | { 39 | name, 40 | fileIds: [], 41 | }, 42 | { 43 | reqheaders: { 44 | Authorization: `Bearer ${api.token}`, 45 | }, 46 | }, 47 | ) 48 | .reply(200, { 49 | data: { 50 | hash, 51 | }, 52 | }) 53 | .get(`/projects/${projectId}/distributions/${hash}`, undefined, { 54 | reqheaders: { 55 | Authorization: `Bearer ${api.token}`, 56 | }, 57 | }) 58 | .reply(200, { 59 | data: { 60 | hash, 61 | }, 62 | }) 63 | .delete(`/projects/${projectId}/distributions/${hash}`, undefined, { 64 | reqheaders: { 65 | Authorization: `Bearer ${api.token}`, 66 | }, 67 | }) 68 | .reply(200) 69 | .patch( 70 | `/projects/${projectId}/distributions/${hash}`, 71 | [ 72 | { 73 | value: name, 74 | op: 'replace', 75 | path: '/name', 76 | }, 77 | ], 78 | { 79 | reqheaders: { 80 | Authorization: `Bearer ${api.token}`, 81 | }, 82 | }, 83 | ) 84 | .reply(200, { 85 | data: { 86 | hash, 87 | name, 88 | }, 89 | }) 90 | .get(`/projects/${projectId}/distributions/${hash}/release`, undefined, { 91 | reqheaders: { 92 | Authorization: `Bearer ${api.token}`, 93 | }, 94 | }) 95 | .reply(200, { 96 | data: { 97 | progress: 0, 98 | }, 99 | }) 100 | .post(`/projects/${projectId}/distributions/${hash}/release`, undefined, { 101 | reqheaders: { 102 | Authorization: `Bearer ${api.token}`, 103 | }, 104 | }) 105 | .reply(200, { 106 | data: { 107 | progress: 0, 108 | }, 109 | }); 110 | }); 111 | 112 | afterAll(() => { 113 | scope.done(); 114 | }); 115 | 116 | it('List distributions', async () => { 117 | const distributions = await api.listDistributions(projectId); 118 | expect(distributions.data.length).toBe(1); 119 | expect(distributions.data[0].data.hash).toBe(hash); 120 | expect(distributions.pagination.limit).toBe(limit); 121 | }); 122 | 123 | it('Create distribution', async () => { 124 | const distribution = await api.createDistribution(projectId, { 125 | fileIds: [], 126 | name, 127 | }); 128 | expect(distribution.data.hash).toBe(hash); 129 | }); 130 | 131 | it('Get distribution', async () => { 132 | const distribution = await api.getDistribution(projectId, hash); 133 | expect(distribution.data.hash).toBe(hash); 134 | }); 135 | 136 | it('Delete distribution', async () => { 137 | await api.deleteDistribution(projectId, hash); 138 | }); 139 | 140 | it('Edit distribution', async () => { 141 | const distribution = await api.editDistribution(projectId, hash, [ 142 | { 143 | op: 'replace', 144 | path: '/name', 145 | value: name, 146 | }, 147 | ]); 148 | expect(distribution.data.hash).toBe(hash); 149 | expect(distribution.data.name).toBe(name); 150 | }); 151 | 152 | it('Get distribution release', async () => { 153 | const distributionRelease = await api.getDistributionRelease(projectId, hash); 154 | expect(distributionRelease.data.progress).toBe(0); 155 | }); 156 | 157 | it('Create distribution release', async () => { 158 | const distributionRelease = await api.createDistributionRelease(projectId, hash); 159 | expect(distributionRelease.data.progress).toBe(0); 160 | }); 161 | }); 162 | -------------------------------------------------------------------------------- /tests/fields/api.test.ts: -------------------------------------------------------------------------------- 1 | import * as nock from 'nock'; 2 | import { Credentials, Fields } from '../../src/'; 3 | 4 | describe('Fields API', () => { 5 | let scope: nock.Scope; 6 | const credentials: Credentials = { 7 | token: 'testToken', 8 | organization: 'testOrg', 9 | }; 10 | const api: Fields = new Fields(credentials); 11 | const fieldId = 2; 12 | const fieldMock = { 13 | id: fieldId, 14 | name: 'Field 2', 15 | slug: 'field-2', 16 | type: 'text', 17 | entities: ['project', 'user'], 18 | config: { 19 | locations: [ 20 | { 21 | place: 'projectHeader', 22 | }, 23 | ], 24 | }, 25 | }; 26 | 27 | const limit = 25; 28 | 29 | beforeAll(() => { 30 | scope = nock(api.url) 31 | .get('/fields', undefined, { 32 | reqheaders: { 33 | Authorization: `Bearer ${api.token}`, 34 | }, 35 | }) 36 | .reply(200, { 37 | data: [ 38 | { 39 | data: { 40 | ...fieldMock, 41 | }, 42 | }, 43 | ], 44 | pagination: { 45 | offset: 0, 46 | limit: limit, 47 | }, 48 | }) 49 | .post( 50 | '/fields', 51 | { 52 | name: fieldMock.name, 53 | slug: fieldMock.slug, 54 | type: fieldMock.type, 55 | entities: fieldMock.entities, 56 | config: fieldMock.config, 57 | }, 58 | { 59 | reqheaders: { 60 | Authorization: `Bearer ${api.token}`, 61 | }, 62 | }, 63 | ) 64 | .reply(200, { 65 | data: { 66 | ...fieldMock, 67 | }, 68 | }) 69 | .get(`/fields/${fieldId}`, undefined, { 70 | reqheaders: { 71 | Authorization: `Bearer ${api.token}`, 72 | }, 73 | }) 74 | .reply(200, { 75 | data: { 76 | ...fieldMock, 77 | }, 78 | }) 79 | .delete(`/fields/${fieldId}`, undefined, { 80 | reqheaders: { 81 | Authorization: `Bearer ${api.token}`, 82 | }, 83 | }) 84 | .reply(204) 85 | .patch( 86 | `/fields/${fieldId}`, 87 | [ 88 | { 89 | op: 'replace', 90 | path: '/name', 91 | value: fieldMock.name, 92 | }, 93 | ], 94 | { 95 | reqheaders: { 96 | Authorization: `Bearer ${api.token}`, 97 | }, 98 | }, 99 | ) 100 | .reply(200, { 101 | data: { 102 | ...fieldMock, 103 | }, 104 | }); 105 | }); 106 | 107 | afterAll(() => { 108 | scope.done(); 109 | }); 110 | 111 | it('List fields', async () => { 112 | const fields = await api.listFields(); 113 | expect(fields.data.length).toBe(1); 114 | expect(fields.data[0].data.id).toBe(fieldId); 115 | expect(fields.pagination.limit).toBe(limit); 116 | }); 117 | 118 | it('Add field', async () => { 119 | const field = await api.addField({ 120 | name: fieldMock.name, 121 | slug: fieldMock.slug, 122 | type: 'text', 123 | entities: ['project', 'user'], 124 | config: { 125 | locations: [ 126 | { 127 | place: 'projectHeader', 128 | }, 129 | ], 130 | }, 131 | }); 132 | expect(field.data.id).toBe(fieldId); 133 | }); 134 | 135 | it('Get field', async () => { 136 | const field = await api.getField(fieldId); 137 | expect(field.data.id).toBe(fieldId); 138 | }); 139 | 140 | it('Delete field', async () => { 141 | await api.deleteField(fieldId); 142 | }); 143 | 144 | it('Edit field', async () => { 145 | const field = await api.editField(fieldId, [ 146 | { 147 | op: 'replace', 148 | path: '/name', 149 | value: fieldMock.name, 150 | }, 151 | ]); 152 | expect(field.data.id).toBe(fieldId); 153 | expect(field.data.name).toBe(fieldMock.name); 154 | expect(field.data.slug).toBe(fieldMock.slug); 155 | expect(field.data.type).toBe(fieldMock.type); 156 | expect(field.data.entities).toEqual(fieldMock.entities); 157 | expect(field.data.config).toEqual(fieldMock.config); 158 | }); 159 | }); 160 | -------------------------------------------------------------------------------- /tests/internal/retry/retry.test.ts: -------------------------------------------------------------------------------- 1 | import { RetryService, SkipRetryCondition } from '../../../src/core/internal/retry'; 2 | 3 | describe('Retry Mechanism', () => { 4 | it('Should retry with async function', async () => { 5 | const result = 5; 6 | let executed = false; 7 | const asyncFunc = (): Promise => { 8 | return new Promise((res, rej): void => { 9 | setTimeout((): void => { 10 | if (!executed) { 11 | executed = true; 12 | rej(); 13 | } else { 14 | res(result); 15 | } 16 | }, 100); 17 | }); 18 | }; 19 | const retries = 1; 20 | const waitInterval = 150; 21 | const retryService = new RetryService({ 22 | retries, 23 | waitInterval, 24 | conditions: [], 25 | }); 26 | const executedResult = await retryService.executeAsyncFunc(asyncFunc); 27 | expect(executedResult).toBe(result); 28 | }); 29 | 30 | it('Should retry with sync function', async () => { 31 | const result = 7; 32 | let executed = false; 33 | const syncFunc = (): number => { 34 | if (!executed) { 35 | executed = true; 36 | throw Error('error!'); 37 | } else { 38 | return result; 39 | } 40 | }; 41 | const retries = 1; 42 | const waitInterval = 50; 43 | const retryService = new RetryService({ 44 | retries, 45 | waitInterval, 46 | conditions: [], 47 | }); 48 | const executedResult = await retryService.executeSyncFunc(syncFunc); 49 | expect(executedResult).toBe(result); 50 | }); 51 | 52 | it('Should retry with conditions', async () => { 53 | const result = 7; 54 | let executed = false; 55 | let conditionInvoked = false; 56 | const syncFunc = (): number => { 57 | if (!executed) { 58 | executed = true; 59 | throw Error('error!'); 60 | } else { 61 | return result; 62 | } 63 | }; 64 | const condition: SkipRetryCondition = { 65 | test(): boolean { 66 | conditionInvoked = true; 67 | return false; 68 | }, 69 | }; 70 | const retries = 1; 71 | const waitInterval = 50; 72 | const retryService = new RetryService({ 73 | retries, 74 | waitInterval, 75 | conditions: [condition], 76 | }); 77 | const executedResult = await retryService.executeSyncFunc(syncFunc); 78 | expect(executedResult).toBe(result); 79 | expect(conditionInvoked).toBe(true); 80 | }); 81 | }); 82 | -------------------------------------------------------------------------------- /tests/issues/api.test.ts: -------------------------------------------------------------------------------- 1 | import * as nock from 'nock'; 2 | import { Credentials, Issues } from '../../src'; 3 | 4 | describe('Issues API', () => { 5 | let scope: nock.Scope; 6 | const credentials: Credentials = { 7 | token: 'testToken', 8 | organization: 'testOrg', 9 | }; 10 | const api: Issues = new Issues(credentials); 11 | const projectId = 2; 12 | const issueId = 21; 13 | 14 | const limit = 25; 15 | 16 | beforeAll(() => { 17 | scope = nock(api.url) 18 | .get(`/projects/${projectId}/issues`, undefined, { 19 | reqheaders: { 20 | Authorization: `Bearer ${api.token}`, 21 | }, 22 | }) 23 | .reply(200, { 24 | data: [ 25 | { 26 | data: { 27 | id: issueId, 28 | }, 29 | }, 30 | ], 31 | pagination: { 32 | offset: 0, 33 | limit: limit, 34 | }, 35 | }) 36 | .patch( 37 | `/projects/${projectId}/issues/${issueId}`, 38 | [ 39 | { 40 | value: 'unresolved', 41 | op: 'replace', 42 | path: '/status', 43 | }, 44 | ], 45 | { 46 | reqheaders: { 47 | Authorization: `Bearer ${api.token}`, 48 | }, 49 | }, 50 | ) 51 | .reply(200, { 52 | data: { 53 | id: issueId, 54 | status: 'unresolved', 55 | }, 56 | }); 57 | }); 58 | 59 | afterAll(() => { 60 | scope.done(); 61 | }); 62 | 63 | it('List reported issues', async () => { 64 | const issues = await api.listReportedIssues(projectId); 65 | expect(issues.data.length).toBe(1); 66 | expect(issues.data[0].data.id).toBe(issueId); 67 | expect(issues.pagination.limit).toBe(limit); 68 | }); 69 | 70 | it('Edit issue', async () => { 71 | const issue = await api.editIssue(projectId, issueId, [ 72 | { 73 | value: 'unresolved', 74 | op: 'replace', 75 | path: '/status', 76 | }, 77 | ]); 78 | expect(issue.data.id).toBe(issueId); 79 | expect(issue.data.status).toBe('unresolved'); 80 | }); 81 | }); 82 | -------------------------------------------------------------------------------- /tests/languages/api.test.ts: -------------------------------------------------------------------------------- 1 | import * as nock from 'nock'; 2 | import { Credentials, Languages } from '../../src'; 3 | 4 | describe('Languages API', () => { 5 | let scope: nock.Scope; 6 | const credentials: Credentials = { 7 | token: 'testToken', 8 | organization: 'testOrg', 9 | }; 10 | const api: Languages = new Languages(credentials); 11 | const languageId = 'es'; 12 | const name = 'Test'; 13 | const code = '12'; 14 | const localeCode = 't'; 15 | const threeLettersCode = 'tst'; 16 | const textDirection = 'ltr'; 17 | 18 | const limit = 25; 19 | 20 | beforeAll(() => { 21 | scope = nock(api.url) 22 | .get('/languages', undefined, { 23 | reqheaders: { 24 | Authorization: `Bearer ${api.token}`, 25 | }, 26 | }) 27 | .reply(200, { 28 | data: [ 29 | { 30 | data: { 31 | id: languageId, 32 | }, 33 | }, 34 | ], 35 | pagination: { 36 | offset: 0, 37 | limit: limit, 38 | }, 39 | }) 40 | .get(`/languages/${languageId}`, undefined, { 41 | reqheaders: { 42 | Authorization: `Bearer ${api.token}`, 43 | }, 44 | }) 45 | .reply(200, { 46 | data: { 47 | id: languageId, 48 | }, 49 | }) 50 | .post( 51 | '/languages', 52 | { 53 | name: name, 54 | code: code, 55 | localeCode: localeCode, 56 | threeLettersCode: threeLettersCode, 57 | textDirection: textDirection, 58 | pluralCategoryNames: [], 59 | }, 60 | { 61 | reqheaders: { 62 | Authorization: `Bearer ${api.token}`, 63 | }, 64 | }, 65 | ) 66 | .reply(200, { 67 | data: { 68 | id: languageId, 69 | }, 70 | }) 71 | .delete(`/languages/${languageId}`, undefined, { 72 | reqheaders: { 73 | Authorization: `Bearer ${api.token}`, 74 | }, 75 | }) 76 | .reply(200) 77 | .patch( 78 | `/languages/${languageId}`, 79 | [ 80 | { 81 | value: name, 82 | op: 'replace', 83 | path: '/name', 84 | }, 85 | ], 86 | { 87 | reqheaders: { 88 | Authorization: `Bearer ${api.token}`, 89 | }, 90 | }, 91 | ) 92 | .reply(200, { 93 | data: { 94 | id: languageId, 95 | name: name, 96 | }, 97 | }); 98 | }); 99 | 100 | afterAll(() => { 101 | scope.done(); 102 | }); 103 | 104 | it('List supported languages', async () => { 105 | const languages = await api.listSupportedLanguages(); 106 | expect(languages.data.length).toBe(1); 107 | expect(languages.data[0].data.id).toBe(languageId); 108 | expect(languages.pagination.limit).toBe(limit); 109 | }); 110 | 111 | it('Get language', async () => { 112 | const language = await api.getLanguage(languageId); 113 | expect(language.data.id).toBe(languageId); 114 | }); 115 | 116 | it('Add custom language', async () => { 117 | const language = await api.addCustomLanguage({ 118 | name: name, 119 | code: code, 120 | localeCode: localeCode, 121 | pluralCategoryNames: [], 122 | textDirection: textDirection, 123 | threeLettersCode: threeLettersCode, 124 | }); 125 | expect(language.data.id).toBe(languageId); 126 | }); 127 | 128 | it('Delete custom language', async () => { 129 | await api.deleteCustomLanguage(languageId); 130 | }); 131 | 132 | it('Edit custom language', async () => { 133 | const language = await api.editCustomLanguage(languageId, [ 134 | { 135 | value: name, 136 | op: 'replace', 137 | path: '/name', 138 | }, 139 | ]); 140 | expect(language.data.id).toBe(languageId); 141 | expect(language.data.name).toBe(name); 142 | }); 143 | }); 144 | -------------------------------------------------------------------------------- /tests/machineTranslation/api.test.ts: -------------------------------------------------------------------------------- 1 | import * as nock from 'nock'; 2 | import { Credentials, MachineTranslation } from '../../src'; 3 | 4 | describe('Machine Translation engines (MTs) API', () => { 5 | let scope: nock.Scope; 6 | const credentials: Credentials = { 7 | token: 'testToken', 8 | organization: 'testOrg', 9 | }; 10 | const api: MachineTranslation = new MachineTranslation(credentials); 11 | const mtId = 2; 12 | const groupId = 3; 13 | const name = 'test'; 14 | const type = 'type'; 15 | const lang = 'us'; 16 | const apiKey = 'test'; 17 | 18 | const limit = 25; 19 | 20 | beforeAll(() => { 21 | scope = nock(api.url) 22 | .get('/mts', undefined, { 23 | reqheaders: { 24 | Authorization: `Bearer ${api.token}`, 25 | }, 26 | }) 27 | .query({ 28 | groupId: groupId, 29 | }) 30 | .reply(200, { 31 | data: [ 32 | { 33 | data: { 34 | id: mtId, 35 | }, 36 | }, 37 | ], 38 | pagination: { 39 | offset: 0, 40 | limit: limit, 41 | }, 42 | }) 43 | .post( 44 | '/mts', 45 | { 46 | name: name, 47 | type: type, 48 | credentials: { apiKey }, 49 | }, 50 | { 51 | reqheaders: { 52 | Authorization: `Bearer ${api.token}`, 53 | }, 54 | }, 55 | ) 56 | .reply(200, { 57 | data: { 58 | id: mtId, 59 | }, 60 | }) 61 | .get(`/mts/${mtId}`, undefined, { 62 | reqheaders: { 63 | Authorization: `Bearer ${api.token}`, 64 | }, 65 | }) 66 | .reply(200, { 67 | data: { 68 | id: mtId, 69 | }, 70 | }) 71 | .delete(`/mts/${mtId}`, undefined, { 72 | reqheaders: { 73 | Authorization: `Bearer ${api.token}`, 74 | }, 75 | }) 76 | .reply(200) 77 | .patch( 78 | `/mts/${mtId}`, 79 | [ 80 | { 81 | value: name, 82 | op: 'replace', 83 | path: '/name', 84 | }, 85 | ], 86 | { 87 | reqheaders: { 88 | Authorization: `Bearer ${api.token}`, 89 | }, 90 | }, 91 | ) 92 | .reply(200, { 93 | data: { 94 | id: mtId, 95 | name: name, 96 | }, 97 | }) 98 | .post( 99 | `/mts/${mtId}/translations`, 100 | { 101 | targetLanguageId: lang, 102 | }, 103 | { 104 | reqheaders: { 105 | Authorization: `Bearer ${api.token}`, 106 | }, 107 | }, 108 | ) 109 | .reply(200, { 110 | data: { 111 | targetLanguageId: lang, 112 | }, 113 | }); 114 | }); 115 | 116 | afterAll(() => { 117 | scope.done(); 118 | }); 119 | 120 | it('List MTs', async () => { 121 | const mts = await api.listMts({ groupId }); 122 | expect(mts.data.length).toBe(1); 123 | expect(mts.data[0].data.id).toBe(mtId); 124 | expect(mts.pagination.limit).toBe(limit); 125 | }); 126 | 127 | it('Create MT', async () => { 128 | const mt = await api.createMt({ 129 | name: name, 130 | type: type, 131 | credentials: { apiKey }, 132 | }); 133 | expect(mt.data.id).toBe(mtId); 134 | }); 135 | 136 | it('Get MT', async () => { 137 | const mt = await api.getMt(mtId); 138 | expect(mt.data.id).toBe(mtId); 139 | }); 140 | 141 | it('Delete MT', async () => { 142 | await api.deleteMt(mtId); 143 | }); 144 | 145 | it('Update MT', async () => { 146 | const mt = await api.updateMt(mtId, [ 147 | { 148 | op: 'replace', 149 | path: '/name', 150 | value: name, 151 | }, 152 | ]); 153 | expect(mt.data.id).toBe(mtId); 154 | expect(mt.data.name).toBe(name); 155 | }); 156 | 157 | it('Translate via MT', async () => { 158 | const translations = await api.translate(mtId, { 159 | targetLanguageId: lang, 160 | }); 161 | expect(translations.data.targetLanguageId).toBe(lang); 162 | }); 163 | }); 164 | -------------------------------------------------------------------------------- /tests/notifications/api.test.ts: -------------------------------------------------------------------------------- 1 | import * as nock from 'nock'; 2 | import { Credentials, Notifications } from '../../src'; 3 | 4 | describe('Notifications API', () => { 5 | let scope: nock.Scope; 6 | const credentials: Credentials = { 7 | token: 'testToken', 8 | organization: 'testOrg', 9 | }; 10 | const api: Notifications = new Notifications(credentials); 11 | const projectId = 2; 12 | const message = 'Hello'; 13 | const role = 'admin'; 14 | const userId = 123; 15 | 16 | beforeAll(() => { 17 | scope = nock(api.url) 18 | .post( 19 | '/notify', 20 | { 21 | message, 22 | }, 23 | { 24 | reqheaders: { 25 | Authorization: `Bearer ${api.token}`, 26 | }, 27 | }, 28 | ) 29 | .reply(200) 30 | .post( 31 | `/projects/${projectId}/notify`, 32 | { 33 | message, 34 | role, 35 | }, 36 | { 37 | reqheaders: { 38 | Authorization: `Bearer ${api.token}`, 39 | }, 40 | }, 41 | ) 42 | .reply(200) 43 | .post( 44 | '/notify', 45 | { 46 | message, 47 | userIds: [userId], 48 | }, 49 | { 50 | reqheaders: { 51 | Authorization: `Bearer ${api.token}`, 52 | }, 53 | }, 54 | ) 55 | .reply(200); 56 | }); 57 | 58 | afterAll(() => { 59 | scope.done(); 60 | }); 61 | 62 | it('Send Notification to Authenticated User', async () => { 63 | await api.sendNotificationToAuthenticatedUser({ message }); 64 | }); 65 | 66 | it('Send Notification To Project Members', async () => { 67 | await api.sendNotificationToProjectMembers(projectId, { message, role }); 68 | }); 69 | 70 | it('Send Notification To Organization Members', async () => { 71 | await api.sendNotificationToOrganizationMembers({ message, userIds: [userId] }); 72 | }); 73 | }); 74 | -------------------------------------------------------------------------------- /tests/organizationWebhooks/api.test.ts: -------------------------------------------------------------------------------- 1 | import * as nock from 'nock'; 2 | import { Credentials, OrganizationWebhooks } from '../../src/index'; 3 | 4 | describe('Organization Webhooks API', () => { 5 | let scope: nock.Scope; 6 | const credentials: Credentials = { 7 | token: 'testToken', 8 | organization: 'testOrg', 9 | }; 10 | const api: OrganizationWebhooks = new OrganizationWebhooks(credentials); 11 | const webhookId = 3; 12 | const name = 'test'; 13 | const url = 'test.com'; 14 | const requestType = 'GET'; 15 | 16 | const limit = 25; 17 | 18 | beforeAll(() => { 19 | scope = nock(api.url) 20 | .get('/webhooks', undefined, { 21 | reqheaders: { 22 | Authorization: `Bearer ${api.token}`, 23 | }, 24 | }) 25 | .reply(200, { 26 | data: [ 27 | { 28 | data: { 29 | id: webhookId, 30 | }, 31 | }, 32 | ], 33 | pagination: { 34 | offset: 0, 35 | limit: limit, 36 | }, 37 | }) 38 | .post( 39 | '/webhooks', 40 | { 41 | name: name, 42 | url: url, 43 | events: ['project.created'], 44 | requestType: requestType, 45 | }, 46 | { 47 | reqheaders: { 48 | Authorization: `Bearer ${api.token}`, 49 | }, 50 | }, 51 | ) 52 | .reply(200, { 53 | data: { 54 | id: webhookId, 55 | }, 56 | }) 57 | .get(`/webhooks/${webhookId}`, undefined, { 58 | reqheaders: { 59 | Authorization: `Bearer ${api.token}`, 60 | }, 61 | }) 62 | .reply(200, { 63 | data: { 64 | id: webhookId, 65 | }, 66 | }) 67 | .delete(`/webhooks/${webhookId}`, undefined, { 68 | reqheaders: { 69 | Authorization: `Bearer ${api.token}`, 70 | }, 71 | }) 72 | .reply(200) 73 | .patch( 74 | `/webhooks/${webhookId}`, 75 | [ 76 | { 77 | value: name, 78 | op: 'replace', 79 | path: '/name', 80 | }, 81 | ], 82 | { 83 | reqheaders: { 84 | Authorization: `Bearer ${api.token}`, 85 | }, 86 | }, 87 | ) 88 | .reply(200, { 89 | data: { 90 | id: webhookId, 91 | name: name, 92 | }, 93 | }); 94 | }); 95 | 96 | afterAll(() => { 97 | scope.done(); 98 | }); 99 | 100 | it('List webhooks', async () => { 101 | const webhooks = await api.listWebhooks(); 102 | expect(webhooks.data.length).toBe(1); 103 | expect(webhooks.data[0].data.id).toBe(webhookId); 104 | expect(webhooks.pagination.limit).toBe(limit); 105 | }); 106 | 107 | it('Add webhook', async () => { 108 | const webhook = await api.addWebhook({ 109 | name: name, 110 | url: url, 111 | events: ['project.created'], 112 | requestType: requestType, 113 | }); 114 | expect(webhook.data.id).toBe(webhookId); 115 | }); 116 | 117 | it('Get webhook', async () => { 118 | const webhook = await api.getWebhook(webhookId); 119 | expect(webhook.data.id).toBe(webhookId); 120 | }); 121 | 122 | it('Delete webhook', async () => { 123 | await api.deleteWebhook(webhookId); 124 | }); 125 | 126 | it('Edit webhook', async () => { 127 | const webhook = await api.editWebhook(webhookId, [ 128 | { 129 | op: 'replace', 130 | path: '/name', 131 | value: name, 132 | }, 133 | ]); 134 | expect(webhook.data.id).toBe(webhookId); 135 | expect(webhook.data.name).toBe(name); 136 | }); 137 | }); 138 | -------------------------------------------------------------------------------- /tests/securityLogs/api.test.ts: -------------------------------------------------------------------------------- 1 | import * as nock from 'nock'; 2 | import { Credentials, SecurityLogs } from '../../src'; 3 | 4 | describe('SecurityLogs API', () => { 5 | let scope: nock.Scope; 6 | const credentials: Credentials = { 7 | token: 'testToken', 8 | organization: 'testOrg', 9 | }; 10 | const api: SecurityLogs = new SecurityLogs(credentials); 11 | const userId = 2; 12 | const securityLogId = 4; 13 | 14 | const limit = 25; 15 | 16 | beforeAll(() => { 17 | scope = nock(api.url) 18 | .get('/security-logs', undefined, { 19 | reqheaders: { 20 | Authorization: `Bearer ${api.token}`, 21 | }, 22 | }) 23 | .reply(200, { 24 | data: [ 25 | { 26 | data: { 27 | id: securityLogId, 28 | userId, 29 | }, 30 | }, 31 | ], 32 | pagination: { 33 | offset: 0, 34 | limit: limit, 35 | }, 36 | }) 37 | .get(`/security-logs/${securityLogId}`, undefined, { 38 | reqheaders: { 39 | Authorization: `Bearer ${api.token}`, 40 | }, 41 | }) 42 | .reply(200, { 43 | data: { 44 | id: securityLogId, 45 | userId, 46 | }, 47 | }) 48 | .get(`/users/${userId}/security-logs`, undefined, { 49 | reqheaders: { 50 | Authorization: `Bearer ${api.token}`, 51 | }, 52 | }) 53 | .reply(200, { 54 | data: [ 55 | { 56 | data: { 57 | id: securityLogId, 58 | userId, 59 | }, 60 | }, 61 | ], 62 | pagination: { 63 | offset: 0, 64 | limit: limit, 65 | }, 66 | }) 67 | .get(`/users/${userId}/security-logs/${securityLogId}`, undefined, { 68 | reqheaders: { 69 | Authorization: `Bearer ${api.token}`, 70 | }, 71 | }) 72 | .reply(200, { 73 | data: { 74 | id: securityLogId, 75 | userId, 76 | }, 77 | }); 78 | }); 79 | 80 | afterAll(() => { 81 | scope.done(); 82 | }); 83 | 84 | it('List Organization Security Logs', async () => { 85 | const logs = await api.listOrganizationSecurityLogs(); 86 | expect(logs.data.length).toBe(1); 87 | expect(logs.data[0].data.id).toBe(securityLogId); 88 | expect(logs.data[0].data.userId).toBe(userId); 89 | expect(logs.pagination.limit).toBe(limit); 90 | }); 91 | 92 | it('Get Organization Security Log', async () => { 93 | const log = await api.getOrganizationSecurityLog(securityLogId); 94 | expect(log.data.id).toBe(securityLogId); 95 | expect(log.data.userId).toBe(userId); 96 | }); 97 | 98 | it('List User Security Logs', async () => { 99 | const logs = await api.listUserSecurityLogs(userId); 100 | expect(logs.data.length).toBe(1); 101 | expect(logs.data[0].data.id).toBe(securityLogId); 102 | expect(logs.data[0].data.userId).toBe(userId); 103 | expect(logs.pagination.limit).toBe(limit); 104 | }); 105 | 106 | it('Get User Security Log', async () => { 107 | const log = await api.getUserSecurityLog(userId, securityLogId); 108 | expect(log.data.id).toBe(securityLogId); 109 | expect(log.data.userId).toBe(userId); 110 | }); 111 | }); 112 | -------------------------------------------------------------------------------- /tests/sourceStrings/api.test.ts: -------------------------------------------------------------------------------- 1 | import * as nock from 'nock'; 2 | import { Credentials, SourceStrings } from '../../src'; 3 | 4 | describe('Source Strings API', () => { 5 | let scope: nock.Scope; 6 | const credentials: Credentials = { 7 | token: 'testToken', 8 | organization: 'testOrg', 9 | }; 10 | const api: SourceStrings = new SourceStrings(credentials); 11 | const projectId = 2; 12 | const stringIdentifier = '222'; 13 | const stringId = 123; 14 | const stringText = 'text. Sample text'; 15 | const uploadId = '123-123'; 16 | const branchId = 1212; 17 | const storageId = 2332; 18 | const fileId = 111; 19 | 20 | const limit = 25; 21 | 22 | beforeAll(() => { 23 | scope = nock(api.url) 24 | .get(`/projects/${projectId}/strings/uploads/${uploadId}`, undefined, { 25 | reqheaders: { 26 | Authorization: `Bearer ${api.token}`, 27 | }, 28 | }) 29 | .reply(200, { 30 | data: { 31 | identifier: uploadId, 32 | }, 33 | }) 34 | .post( 35 | `/projects/${projectId}/strings/uploads`, 36 | { 37 | storageId, 38 | branchId, 39 | }, 40 | { 41 | reqheaders: { 42 | Authorization: `Bearer ${api.token}`, 43 | }, 44 | }, 45 | ) 46 | .reply(200, { 47 | data: { 48 | identifier: uploadId, 49 | }, 50 | }) 51 | .get(`/projects/${projectId}/strings`, undefined, { 52 | reqheaders: { 53 | Authorization: `Bearer ${api.token}`, 54 | }, 55 | }) 56 | .reply(200, { 57 | data: [ 58 | { 59 | data: { 60 | id: stringId, 61 | text: stringText, 62 | }, 63 | }, 64 | ], 65 | pagination: { 66 | offset: 0, 67 | limit: limit, 68 | }, 69 | }) 70 | .post( 71 | `/projects/${projectId}/strings`, 72 | { 73 | identifier: stringIdentifier, 74 | text: stringText, 75 | fileId, 76 | }, 77 | { 78 | reqheaders: { 79 | Authorization: `Bearer ${api.token}`, 80 | }, 81 | }, 82 | ) 83 | .reply(200, { 84 | data: { 85 | id: stringId, 86 | text: stringText, 87 | }, 88 | }) 89 | .patch( 90 | `/projects/${projectId}/strings`, 91 | [ 92 | { 93 | value: stringText, 94 | op: 'replace', 95 | path: `/${stringId}/text`, 96 | }, 97 | ], 98 | { 99 | reqheaders: { 100 | Authorization: `Bearer ${api.token}`, 101 | }, 102 | }, 103 | ) 104 | .reply(200, { 105 | data: [ 106 | { 107 | data: { 108 | id: stringId, 109 | text: stringText, 110 | }, 111 | }, 112 | ], 113 | pagination: { 114 | offset: 0, 115 | limit: limit, 116 | }, 117 | }) 118 | .get(`/projects/${projectId}/strings/${stringId}`, undefined, { 119 | reqheaders: { 120 | Authorization: `Bearer ${api.token}`, 121 | }, 122 | }) 123 | .reply(200, { 124 | data: { 125 | id: stringId, 126 | text: stringText, 127 | }, 128 | }) 129 | .delete(`/projects/${projectId}/strings/${stringId}`, undefined, { 130 | reqheaders: { 131 | Authorization: `Bearer ${api.token}`, 132 | }, 133 | }) 134 | .reply(200) 135 | .patch( 136 | `/projects/${projectId}/strings/${stringId}`, 137 | [ 138 | { 139 | value: stringText, 140 | op: 'replace', 141 | path: '/text', 142 | }, 143 | ], 144 | { 145 | reqheaders: { 146 | Authorization: `Bearer ${api.token}`, 147 | }, 148 | }, 149 | ) 150 | .reply(200, { 151 | data: { 152 | id: stringId, 153 | text: stringText, 154 | }, 155 | }); 156 | }); 157 | 158 | afterAll(() => { 159 | scope.done(); 160 | }); 161 | 162 | it('Upload strings status', async () => { 163 | const status = await api.uploadStringsStatus(projectId, uploadId); 164 | expect(status.data.identifier).toBe(uploadId); 165 | }); 166 | 167 | it('Upload strings', async () => { 168 | const status = await api.uploadStrings(projectId, { 169 | branchId, 170 | storageId, 171 | }); 172 | expect(status.data.identifier).toBe(uploadId); 173 | }); 174 | 175 | it('List project strings', async () => { 176 | const strings = await api.listProjectStrings(projectId); 177 | expect(strings.data.length).toBe(1); 178 | expect(strings.data[0].data.id).toBe(stringId); 179 | expect(strings.data[0].data.text).toBe(stringText); 180 | expect(strings.pagination.limit).toBe(limit); 181 | }); 182 | 183 | it('Add string', async () => { 184 | const string = await api.addString(projectId, { 185 | identifier: stringIdentifier, 186 | text: stringText, 187 | fileId, 188 | }); 189 | expect(string.data.id).toBe(stringId); 190 | expect(string.data.text).toBe(stringText); 191 | }); 192 | 193 | it('String batch operations', async () => { 194 | const strings = await api.stringBatchOperations(projectId, [ 195 | { 196 | op: 'replace', 197 | path: `/${stringId}/text`, 198 | value: stringText, 199 | }, 200 | ]); 201 | expect(strings.data.length).toBe(1); 202 | const string = strings.data[0]; 203 | expect(string.data.id).toBe(stringId); 204 | expect(string.data.text).toBe(stringText); 205 | }); 206 | 207 | it('Get string', async () => { 208 | const string = await api.getString(projectId, stringId); 209 | expect(string.data.id).toBe(stringId); 210 | expect(string.data.text).toBe(stringText); 211 | }); 212 | 213 | it('Delete string', async () => { 214 | await api.deleteString(projectId, stringId); 215 | }); 216 | 217 | it('Edit string', async () => { 218 | const string = await api.editString(projectId, stringId, [ 219 | { 220 | op: 'replace', 221 | path: '/text', 222 | value: stringText, 223 | }, 224 | ]); 225 | expect(string.data.id).toBe(stringId); 226 | expect(string.data.text).toBe(stringText); 227 | }); 228 | }); 229 | -------------------------------------------------------------------------------- /tests/stringComments/api.test.ts: -------------------------------------------------------------------------------- 1 | import * as nock from 'nock'; 2 | import { Credentials, StringComments } from '../../src'; 3 | 4 | describe('String Comments API', () => { 5 | let scope: nock.Scope; 6 | const credentials: Credentials = { 7 | token: 'testToken', 8 | organization: 'testOrg', 9 | }; 10 | const api: StringComments = new StringComments(credentials); 11 | const projectId = 2; 12 | const stringId = 3; 13 | const stringCommentId = 4; 14 | const text = 'test'; 15 | const languageId = 'uk'; 16 | const type = 'comment'; 17 | const issueType = 'translation_mistake'; 18 | const limit = 25; 19 | 20 | beforeAll(() => { 21 | scope = nock(api.url) 22 | .get(`/projects/${projectId}/comments`, undefined, { 23 | reqheaders: { 24 | Authorization: `Bearer ${api.token}`, 25 | }, 26 | }) 27 | .query({ 28 | stringId, 29 | }) 30 | .reply(200, { 31 | data: [ 32 | { 33 | data: { 34 | id: stringCommentId, 35 | }, 36 | }, 37 | ], 38 | pagination: { 39 | offset: 0, 40 | limit: limit, 41 | }, 42 | }) 43 | .post( 44 | `/projects/${projectId}/comments`, 45 | { 46 | text, 47 | type, 48 | targetLanguageId: languageId, 49 | stringId, 50 | }, 51 | { 52 | reqheaders: { 53 | Authorization: `Bearer ${api.token}`, 54 | }, 55 | }, 56 | ) 57 | .reply(200, { 58 | data: { 59 | id: stringCommentId, 60 | }, 61 | }) 62 | .get(`/projects/${projectId}/comments/${stringCommentId}`, undefined, { 63 | reqheaders: { 64 | Authorization: `Bearer ${api.token}`, 65 | }, 66 | }) 67 | .reply(200, { 68 | data: { 69 | id: stringCommentId, 70 | }, 71 | }) 72 | .delete(`/projects/${projectId}/comments/${stringCommentId}`, undefined, { 73 | reqheaders: { 74 | Authorization: `Bearer ${api.token}`, 75 | }, 76 | }) 77 | .reply(200) 78 | .patch( 79 | `/projects/${projectId}/comments/${stringCommentId}`, 80 | [ 81 | { 82 | value: type, 83 | op: 'replace', 84 | path: '/type', 85 | }, 86 | ], 87 | { 88 | reqheaders: { 89 | Authorization: `Bearer ${api.token}`, 90 | }, 91 | }, 92 | ) 93 | .reply(200, { 94 | data: { 95 | id: stringCommentId, 96 | type, 97 | }, 98 | }) 99 | .patch( 100 | `/projects/${projectId}/comments`, 101 | [ 102 | { 103 | op: 'add', 104 | path: '/-', 105 | value: { 106 | text: text, 107 | stringId: stringId, 108 | type: type, 109 | targetLanguageId: languageId, 110 | issueType: issueType, 111 | }, 112 | }, 113 | ], 114 | { 115 | reqheaders: { 116 | Authorization: `Bearer ${api.token}`, 117 | }, 118 | }, 119 | ) 120 | .reply(200, { 121 | data: [ 122 | { 123 | data: { 124 | id: stringCommentId, 125 | string: { 126 | id: stringId, 127 | }, 128 | text: text, 129 | type: type, 130 | issueType: issueType, 131 | }, 132 | }, 133 | ], 134 | }); 135 | }); 136 | 137 | afterAll(() => { 138 | scope.done(); 139 | }); 140 | 141 | it('List string comment', async () => { 142 | const comments = await api.listStringComments(projectId, { stringId }); 143 | expect(comments.data.length).toBe(1); 144 | expect(comments.data[0].data.id).toBe(stringCommentId); 145 | expect(comments.pagination.limit).toBe(limit); 146 | }); 147 | 148 | it('Add string comment', async () => { 149 | const comment = await api.addStringComment(projectId, { 150 | text, 151 | targetLanguageId: languageId, 152 | type, 153 | stringId, 154 | }); 155 | expect(comment.data.id).toBe(stringCommentId); 156 | }); 157 | 158 | it('Get string comment', async () => { 159 | const comment = await api.getStringComment(projectId, stringCommentId); 160 | expect(comment.data.id).toBe(stringCommentId); 161 | }); 162 | 163 | it('Delete string comment', async () => { 164 | await api.deleteStringComment(projectId, stringCommentId); 165 | }); 166 | 167 | it('Edit string comment', async () => { 168 | const comment = await api.editStringComment(projectId, stringCommentId, [ 169 | { 170 | op: 'replace', 171 | path: '/type', 172 | value: type, 173 | }, 174 | ]); 175 | expect(comment.data.id).toBe(stringCommentId); 176 | expect(comment.data.type).toBe(type); 177 | }); 178 | 179 | it('String Comment Batch Operations', async () => { 180 | const translations = await api.stringCommentBatchOperations(projectId, [ 181 | { 182 | op: 'add', 183 | path: '/-', 184 | value: { 185 | text: text, 186 | stringId: stringId, 187 | type: type, 188 | targetLanguageId: languageId, 189 | issueType: issueType, 190 | }, 191 | }, 192 | ]); 193 | 194 | expect(translations.data.length).toBe(1); 195 | expect(translations.data[0].data.string.id).toBe(stringId); 196 | expect(translations.data[0].data.text).toBe(text); 197 | expect(translations.data[0].data.type).toBe(type); 198 | expect(translations.data[0].data.issueType).toBe(issueType); 199 | }); 200 | }); 201 | -------------------------------------------------------------------------------- /tests/translationStatus/api.test.ts: -------------------------------------------------------------------------------- 1 | import * as nock from 'nock'; 2 | import { Credentials, TranslationStatus } from '../../src'; 3 | 4 | describe('Translation Status API', () => { 5 | let scope: nock.Scope; 6 | const credentials: Credentials = { 7 | token: 'testToken', 8 | organization: 'testOrg', 9 | }; 10 | const api: TranslationStatus = new TranslationStatus(credentials); 11 | const projectId = 2; 12 | const branchId = 3; 13 | const directoryId = 4; 14 | const fileId = 5; 15 | const phrasesCount = 10; 16 | const languageId = 'uk'; 17 | 18 | const limit = 25; 19 | 20 | beforeAll(() => { 21 | scope = nock(api.url) 22 | .get(`/projects/${projectId}/branches/${branchId}/languages/progress`, undefined, { 23 | reqheaders: { 24 | Authorization: `Bearer ${api.token}`, 25 | }, 26 | }) 27 | .reply(200, { 28 | data: [ 29 | { 30 | data: { 31 | phrases: { 32 | total: phrasesCount, 33 | }, 34 | }, 35 | }, 36 | ], 37 | pagination: { 38 | offset: 0, 39 | limit: limit, 40 | }, 41 | }) 42 | .get(`/projects/${projectId}/directories/${directoryId}/languages/progress`, undefined, { 43 | reqheaders: { 44 | Authorization: `Bearer ${api.token}`, 45 | }, 46 | }) 47 | .reply(200, { 48 | data: [ 49 | { 50 | data: { 51 | phrases: { 52 | total: phrasesCount, 53 | }, 54 | }, 55 | }, 56 | ], 57 | pagination: { 58 | offset: 0, 59 | limit: limit, 60 | }, 61 | }) 62 | .get(`/projects/${projectId}/languages/${languageId}/progress`, undefined, { 63 | reqheaders: { 64 | Authorization: `Bearer ${api.token}`, 65 | }, 66 | }) 67 | .reply(200, { 68 | data: [ 69 | { 70 | data: { 71 | phrases: { 72 | total: phrasesCount, 73 | }, 74 | fileId: fileId, 75 | }, 76 | }, 77 | ], 78 | pagination: { 79 | offset: 0, 80 | limit: limit, 81 | }, 82 | }) 83 | .get(`/projects/${projectId}/languages/progress`, undefined, { 84 | reqheaders: { 85 | Authorization: `Bearer ${api.token}`, 86 | }, 87 | }) 88 | .reply(200, { 89 | data: [ 90 | { 91 | data: { 92 | phrases: { 93 | total: phrasesCount, 94 | }, 95 | }, 96 | }, 97 | ], 98 | pagination: { 99 | offset: 0, 100 | limit: limit, 101 | }, 102 | }) 103 | .get(`/projects/${projectId}/files/${fileId}/languages/progress`, undefined, { 104 | reqheaders: { 105 | Authorization: `Bearer ${api.token}`, 106 | }, 107 | }) 108 | .reply(200, { 109 | data: [ 110 | { 111 | data: { 112 | phrases: { 113 | total: phrasesCount, 114 | }, 115 | }, 116 | }, 117 | ], 118 | pagination: { 119 | offset: 0, 120 | limit: limit, 121 | }, 122 | }) 123 | .get(`/projects/${projectId}/qa-checks`, undefined, { 124 | reqheaders: { 125 | Authorization: `Bearer ${api.token}`, 126 | }, 127 | }) 128 | .reply(200, { 129 | data: [ 130 | { 131 | data: { 132 | languageId: languageId, 133 | }, 134 | }, 135 | ], 136 | pagination: { 137 | offset: 0, 138 | limit: limit, 139 | }, 140 | }); 141 | }); 142 | 143 | afterAll(() => { 144 | scope.done(); 145 | }); 146 | 147 | it('Get branch progress', async () => { 148 | const progress = await api.getBranchProgress(projectId, branchId); 149 | expect(progress.data.length).toBe(1); 150 | expect(progress.data[0].data.phrases.total).toBe(phrasesCount); 151 | expect(progress.pagination.limit).toBe(limit); 152 | }); 153 | 154 | it('Get directory progress', async () => { 155 | const progress = await api.getDirectoryProgress(projectId, directoryId); 156 | expect(progress.data.length).toBe(1); 157 | expect(progress.data[0].data.phrases.total).toBe(phrasesCount); 158 | expect(progress.pagination.limit).toBe(limit); 159 | }); 160 | 161 | it('Get language progress', async () => { 162 | const progress = await api.getLanguageProgress(projectId, languageId); 163 | expect(progress.data.length).toBe(1); 164 | expect(progress.data[0].data.phrases.total).toBe(phrasesCount); 165 | expect(progress.data[0].data.fileId).toBe(fileId); 166 | expect(progress.pagination.limit).toBe(limit); 167 | }); 168 | 169 | it('Get project progress', async () => { 170 | const progress = await api.getProjectProgress(projectId); 171 | expect(progress.data.length).toBe(1); 172 | expect(progress.data[0].data.phrases.total).toBe(phrasesCount); 173 | expect(progress.pagination.limit).toBe(limit); 174 | }); 175 | 176 | it('Get file progress', async () => { 177 | const progress = await api.getFileProgress(projectId, fileId); 178 | expect(progress.data.length).toBe(1); 179 | expect(progress.data[0].data.phrases.total).toBe(phrasesCount); 180 | expect(progress.pagination.limit).toBe(limit); 181 | }); 182 | 183 | it('List QA Check Issues', async () => { 184 | const qaChecks = await api.listQaCheckIssues(projectId); 185 | expect(qaChecks.data.length).toBe(1); 186 | expect(qaChecks.data[0].data.languageId).toBe(languageId); 187 | expect(qaChecks.pagination.limit).toBe(limit); 188 | }); 189 | }); 190 | -------------------------------------------------------------------------------- /tests/uploadStorage/api.test.ts: -------------------------------------------------------------------------------- 1 | import * as nock from 'nock'; 2 | import { Credentials, CrowdinApi, UploadStorage } from '../../src'; 3 | 4 | class TestCrowdinApi extends CrowdinApi { 5 | public encodeUrlParam(param: string | number | boolean): string { 6 | return super.encodeUrlParam(param); 7 | } 8 | } 9 | 10 | describe('Upload Storage API', () => { 11 | let scope: nock.Scope; 12 | const credentials: Credentials = { 13 | token: 'testToken', 14 | organization: 'testOrg', 15 | }; 16 | const api: UploadStorage = new UploadStorage(credentials); 17 | const testApi: TestCrowdinApi = new TestCrowdinApi(credentials); 18 | const storageId = 2; 19 | const fileName = 'words.txt'; 20 | const urlEncodedFileName = encodeURIComponent(fileName); 21 | const fileContent = 'test text.'; 22 | 23 | const limit = 25; 24 | 25 | beforeAll(() => { 26 | scope = nock(api.url) 27 | .get('/storages', undefined, { 28 | reqheaders: { 29 | Authorization: `Bearer ${api.token}`, 30 | }, 31 | }) 32 | .reply(200, { 33 | data: [ 34 | { 35 | data: { 36 | id: storageId, 37 | }, 38 | }, 39 | ], 40 | pagination: { 41 | offset: 0, 42 | limit: limit, 43 | }, 44 | }) 45 | .post('/storages', fileContent, { 46 | reqheaders: { 47 | 'Crowdin-API-FileName': urlEncodedFileName, 48 | Authorization: `Bearer ${api.token}`, 49 | }, 50 | }) 51 | .reply(200, { 52 | data: { 53 | id: storageId, 54 | }, 55 | }) 56 | .get(`/storages/${storageId}`, undefined, { 57 | reqheaders: { 58 | Authorization: `Bearer ${api.token}`, 59 | }, 60 | }) 61 | .reply(200, { 62 | data: { 63 | id: storageId, 64 | }, 65 | }) 66 | .delete(`/storages/${storageId}`, undefined, { 67 | reqheaders: { 68 | Authorization: `Bearer ${api.token}`, 69 | }, 70 | }) 71 | .reply(200); 72 | }); 73 | 74 | afterAll(() => { 75 | scope.done(); 76 | }); 77 | 78 | it('List storages', async () => { 79 | const storages = await api.listStorages(); 80 | expect(storages.data.length).toBe(1); 81 | expect(storages.data[0].data.id).toBe(storageId); 82 | expect(storages.pagination.limit).toBe(limit); 83 | }); 84 | 85 | it('Add storage', async () => { 86 | const storage = await api.addStorage(fileName, fileContent); 87 | expect(storage.data.id).toBe(storageId); 88 | }); 89 | 90 | it('Get storage', async () => { 91 | const storage = await api.getStorage(storageId); 92 | expect(storage.data.id).toBe(storageId); 93 | }); 94 | 95 | it('Delete storage', async () => { 96 | await api.deleteStorage(storageId); 97 | }); 98 | 99 | it('URL encodes a Cyrillic filename', async () => { 100 | const fileName = 'абвгд'; 101 | const urlEncodeFileName = testApi.encodeUrlParam(fileName); 102 | expect(urlEncodeFileName).toBe('%D0%B0%D0%B1%D0%B2%D0%B3%D0%B4'); 103 | }); 104 | 105 | it('URL encodes a non-Cyrillic filename', async () => { 106 | const fileName = 'filename'; 107 | const urlEncodeFileName = testApi.encodeUrlParam(fileName); 108 | expect(urlEncodeFileName).toBe('filename'); 109 | }); 110 | }); 111 | -------------------------------------------------------------------------------- /tests/vendors/api.test.ts: -------------------------------------------------------------------------------- 1 | import * as nock from 'nock'; 2 | import { Credentials, Vendors } from '../../src'; 3 | 4 | describe('Vendors API', () => { 5 | let scope: nock.Scope; 6 | const credentials: Credentials = { 7 | token: 'testToken', 8 | organization: 'testOrg', 9 | }; 10 | const api: Vendors = new Vendors(credentials); 11 | const id = 2; 12 | 13 | const limit = 25; 14 | 15 | beforeAll(() => { 16 | scope = nock(api.url) 17 | .get('/vendors', undefined, { 18 | reqheaders: { 19 | Authorization: `Bearer ${api.token}`, 20 | }, 21 | }) 22 | .reply(200, { 23 | data: [ 24 | { 25 | data: { 26 | id: id, 27 | }, 28 | }, 29 | ], 30 | pagination: { 31 | offset: 0, 32 | limit: limit, 33 | }, 34 | }); 35 | }); 36 | 37 | afterAll(() => { 38 | scope.done(); 39 | }); 40 | 41 | it('List Vendors', async () => { 42 | const vendors = await api.listVendors(); 43 | expect(vendors.data.length).toBe(1); 44 | expect(vendors.data[0].data.id).toBe(id); 45 | expect(vendors.pagination.limit).toBe(limit); 46 | }); 47 | }); 48 | -------------------------------------------------------------------------------- /tests/webhooks/api.test.ts: -------------------------------------------------------------------------------- 1 | import * as nock from 'nock'; 2 | import { Credentials, Webhooks } from '../../src/index'; 3 | 4 | describe('Web-hooks API', () => { 5 | let scope: nock.Scope; 6 | const credentials: Credentials = { 7 | token: 'testToken', 8 | organization: 'testOrg', 9 | }; 10 | const api: Webhooks = new Webhooks(credentials); 11 | const projectId = 2; 12 | const webhookId = 3; 13 | const name = 'test'; 14 | const url = 'test.com'; 15 | const requestType = 'GET'; 16 | 17 | const limit = 25; 18 | 19 | beforeAll(() => { 20 | scope = nock(api.url) 21 | .get(`/projects/${projectId}/webhooks`, undefined, { 22 | reqheaders: { 23 | Authorization: `Bearer ${api.token}`, 24 | }, 25 | }) 26 | .reply(200, { 27 | data: [ 28 | { 29 | data: { 30 | id: webhookId, 31 | }, 32 | }, 33 | ], 34 | pagination: { 35 | offset: 0, 36 | limit: limit, 37 | }, 38 | }) 39 | .post( 40 | `/projects/${projectId}/webhooks`, 41 | { 42 | name: name, 43 | url: url, 44 | events: ['file.added'], 45 | requestType: requestType, 46 | }, 47 | { 48 | reqheaders: { 49 | Authorization: `Bearer ${api.token}`, 50 | }, 51 | }, 52 | ) 53 | .reply(200, { 54 | data: { 55 | id: webhookId, 56 | }, 57 | }) 58 | .get(`/projects/${projectId}/webhooks/${webhookId}`, undefined, { 59 | reqheaders: { 60 | Authorization: `Bearer ${api.token}`, 61 | }, 62 | }) 63 | .reply(200, { 64 | data: { 65 | id: webhookId, 66 | }, 67 | }) 68 | .delete(`/projects/${projectId}/webhooks/${webhookId}`, undefined, { 69 | reqheaders: { 70 | Authorization: `Bearer ${api.token}`, 71 | }, 72 | }) 73 | .reply(200) 74 | .patch( 75 | `/projects/${projectId}/webhooks/${webhookId}`, 76 | [ 77 | { 78 | value: name, 79 | op: 'replace', 80 | path: '/name', 81 | }, 82 | ], 83 | { 84 | reqheaders: { 85 | Authorization: `Bearer ${api.token}`, 86 | }, 87 | }, 88 | ) 89 | .reply(200, { 90 | data: { 91 | id: webhookId, 92 | name: name, 93 | }, 94 | }); 95 | }); 96 | 97 | afterAll(() => { 98 | scope.done(); 99 | }); 100 | 101 | it('List webhooks', async () => { 102 | const webhooks = await api.listWebhooks(projectId); 103 | expect(webhooks.data.length).toBe(1); 104 | expect(webhooks.data[0].data.id).toBe(webhookId); 105 | expect(webhooks.pagination.limit).toBe(limit); 106 | }); 107 | 108 | it('Add webhook', async () => { 109 | const webhook = await api.addWebhook(projectId, { 110 | name: name, 111 | url: url, 112 | events: ['file.added'], 113 | requestType: requestType, 114 | }); 115 | expect(webhook.data.id).toBe(webhookId); 116 | }); 117 | 118 | it('Get webhook', async () => { 119 | const webhook = await api.getWebhook(projectId, webhookId); 120 | expect(webhook.data.id).toBe(webhookId); 121 | }); 122 | 123 | it('Delete webhook', async () => { 124 | await api.deleteWebhook(projectId, webhookId); 125 | }); 126 | 127 | it('Edit webhook', async () => { 128 | const webhook = await api.editWebhook(projectId, webhookId, [ 129 | { 130 | op: 'replace', 131 | path: '/name', 132 | value: name, 133 | }, 134 | ]); 135 | expect(webhook.data.id).toBe(webhookId); 136 | expect(webhook.data.name).toBe(name); 137 | }); 138 | }); 139 | -------------------------------------------------------------------------------- /tests/workflows/api.test.ts: -------------------------------------------------------------------------------- 1 | import * as nock from 'nock'; 2 | import { Credentials, Workflows } from '../../src'; 3 | 4 | describe('Workflows API', () => { 5 | let scope: nock.Scope; 6 | const credentials: Credentials = { 7 | token: 'testToken', 8 | organization: 'testOrg', 9 | }; 10 | const api: Workflows = new Workflows(credentials); 11 | const id = 2; 12 | const projectId = 4; 13 | const stringId = 123; 14 | 15 | const limit = 25; 16 | 17 | beforeAll(() => { 18 | scope = nock(api.url) 19 | .get(`/projects/${projectId}/workflow-steps`, undefined, { 20 | reqheaders: { 21 | Authorization: `Bearer ${api.token}`, 22 | }, 23 | }) 24 | .reply(200, { 25 | data: [ 26 | { 27 | data: { 28 | id: id, 29 | }, 30 | }, 31 | ], 32 | pagination: { 33 | offset: 0, 34 | limit: limit, 35 | }, 36 | }) 37 | .get(`/projects/${projectId}/workflow-steps/${id}`, undefined, { 38 | reqheaders: { 39 | Authorization: `Bearer ${api.token}`, 40 | }, 41 | }) 42 | .reply(200, { 43 | data: { 44 | id: id, 45 | }, 46 | }) 47 | .get(`/projects/${projectId}/workflow-steps/${id}/strings`, undefined, { 48 | reqheaders: { 49 | Authorization: `Bearer ${api.token}`, 50 | }, 51 | }) 52 | .reply(200, { 53 | data: [ 54 | { 55 | data: { 56 | id: stringId, 57 | }, 58 | }, 59 | ], 60 | pagination: { 61 | offset: 0, 62 | limit: limit, 63 | }, 64 | }) 65 | .get('/workflow-templates', undefined, { 66 | reqheaders: { 67 | Authorization: `Bearer ${api.token}`, 68 | }, 69 | }) 70 | .reply(200, { 71 | data: [ 72 | { 73 | data: { 74 | id: id, 75 | }, 76 | }, 77 | ], 78 | pagination: { 79 | offset: 0, 80 | limit: limit, 81 | }, 82 | }) 83 | .get(`/workflow-templates/${id}`, undefined, { 84 | reqheaders: { 85 | Authorization: `Bearer ${api.token}`, 86 | }, 87 | }) 88 | .reply(200, { 89 | data: { 90 | id: id, 91 | }, 92 | }); 93 | }); 94 | 95 | afterAll(() => { 96 | scope.done(); 97 | }); 98 | 99 | it('List Workflow steps', async () => { 100 | const workflowSteps = await api.listWorkflowSteps(projectId); 101 | expect(workflowSteps.data.length).toBe(1); 102 | expect(workflowSteps.data[0].data.id).toBe(id); 103 | expect(workflowSteps.pagination.limit).toBe(limit); 104 | }); 105 | 106 | it('Get Workflow step info', async () => { 107 | const workflowStep = await api.getWorkflowStep(projectId, id); 108 | expect(workflowStep.data.id).toBe(id); 109 | }); 110 | 111 | it('List Strings on the Workflow Step', async () => { 112 | const strings = await api.listStringsOnTheWorkflowStep(projectId, id); 113 | expect(strings.data.length).toBe(1); 114 | expect(strings.data[0].data.id).toBe(stringId); 115 | expect(strings.pagination.limit).toBe(limit); 116 | }); 117 | 118 | it('List Workflow Templates', async () => { 119 | const workflows = await api.listWorkflowTemplates(); 120 | expect(workflows.data.length).toBe(1); 121 | expect(workflows.data[0].data.id).toBe(id); 122 | expect(workflows.pagination.limit).toBe(limit); 123 | }); 124 | 125 | it('Get Workflow Template Info', async () => { 126 | const workflow = await api.getWorkflowTemplateInfo(id); 127 | expect(workflow.data.id).toBe(id); 128 | }); 129 | }); 130 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "commonjs", 4 | "declaration": true, 5 | "target": "es2019", 6 | "outDir": "./out", 7 | "lib": [ 8 | "es2021" 9 | ], 10 | "strict": true 11 | }, 12 | "include": [ 13 | "src" 14 | ], 15 | "exclude": [ 16 | "node_modules", 17 | "tests" 18 | ] 19 | } 20 | --------------------------------------------------------------------------------