├── .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 |
--------------------------------------------------------------------------------