Compare commits

..

2 Commits

Author SHA1 Message Date
Nivedin
37153b6401 feat: tabs UI in env selector 2023-05-11 17:47:06 +05:30
Nivedin
f3f9891017 feat: env selector change 2023-05-08 13:00:47 +05:30
533 changed files with 17094 additions and 40509 deletions

View File

@@ -5,5 +5,5 @@
"features": { "features": {
"ghcr.io/NicoVIII/devcontainer-features/pnpm:1": {} "ghcr.io/NicoVIII/devcontainer-features/pnpm:1": {}
}, },
"postCreateCommand": "cp .env.example .env && pnpm i" "postCreateCommand": "mv .env.example .env && pnpm i"
} }

View File

@@ -1,2 +0,0 @@
node_modules
**/*/node_modules

View File

@@ -13,7 +13,6 @@ SESSION_SECRET='add some secret here'
# Hoppscotch App Domain Config # Hoppscotch App Domain Config
REDIRECT_URL="http://localhost:3000" REDIRECT_URL="http://localhost:3000"
WHITELISTED_ORIGINS = "http://localhost:3170,http://localhost:3000,http://localhost:3100" WHITELISTED_ORIGINS = "http://localhost:3170,http://localhost:3000,http://localhost:3100"
VITE_ALLOWED_AUTH_PROVIDERS = GOOGLE,GITHUB,MICROSOFT,EMAIL
# Google Auth Config # Google Auth Config
GOOGLE_CLIENT_ID="************************************************" GOOGLE_CLIENT_ID="************************************************"
@@ -32,7 +31,6 @@ MICROSOFT_CLIENT_ID="************************************************"
MICROSOFT_CLIENT_SECRET="************************************************" MICROSOFT_CLIENT_SECRET="************************************************"
MICROSOFT_CALLBACK_URL="http://localhost:3170/v1/auth/microsoft/callback" MICROSOFT_CALLBACK_URL="http://localhost:3170/v1/auth/microsoft/callback"
MICROSOFT_SCOPE="user.read" MICROSOFT_SCOPE="user.read"
MICROSOFT_TENANT="common"
# Mailer config # Mailer config
MAILER_SMTP_URL="smtps://user@domain.com:pass@smtp.domain.com" MAILER_SMTP_URL="smtps://user@domain.com:pass@smtp.domain.com"
@@ -53,7 +51,7 @@ VITE_ADMIN_URL=http://localhost:3100
# Backend URLs # Backend URLs
VITE_BACKEND_GQL_URL=http://localhost:3170/graphql VITE_BACKEND_GQL_URL=http://localhost:3170/graphql
VITE_BACKEND_WS_URL=ws://localhost:3170/graphql VITE_BACKEND_WS_URL=wss://localhost:3170/graphql
VITE_BACKEND_API_URL=http://localhost:3170/v1 VITE_BACKEND_API_URL=http://localhost:3170/v1
# Terms Of Service And Privacy Policy Links (Optional) # Terms Of Service And Privacy Policy Links (Optional)

View File

@@ -1,84 +0,0 @@
name: "Push containers to Docker Hub on release"
on:
push:
tags:
- '*.*.*'
jobs:
build:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v3
- name: Setup environment
run: cp .env.example .env
- name: Setup QEMU
uses: docker/setup-qemu-action@v3
- name: Setup Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Log in to Docker Hub
uses: docker/login-action@v2
with:
username: ${{ secrets.DOCKER_USERNAME }}
password: ${{ secrets.DOCKER_PASSWORD }}
- name: Build and push the backend container
uses: docker/build-push-action@v4
with:
context: .
file: ./prod.Dockerfile
target: backend
push: true
platforms: |
linux/amd64
linux/arm64
tags: |
${{ secrets.DOCKER_ORG_NAME }}/${{ secrets.DOCKER_BACKEND_CONTAINER_NAME }}:latest
${{ secrets.DOCKER_ORG_NAME }}/${{ secrets.DOCKER_BACKEND_CONTAINER_NAME }}:${{ github.ref_name }}
- name: Build and push the frontend container
uses: docker/build-push-action@v4
with:
context: .
file: ./prod.Dockerfile
target: app
push: true
platforms: |
linux/amd64
linux/arm64
tags: |
${{ secrets.DOCKER_ORG_NAME }}/${{ secrets.DOCKER_FRONTEND_CONTAINER_NAME }}:latest
${{ secrets.DOCKER_ORG_NAME }}/${{ secrets.DOCKER_FRONTEND_CONTAINER_NAME }}:${{ github.ref_name }}
- name: Build and push the admin dashboard container
uses: docker/build-push-action@v4
with:
context: .
file: ./prod.Dockerfile
target: sh_admin
push: true
platforms: |
linux/amd64
linux/arm64
tags: |
${{ secrets.DOCKER_ORG_NAME }}/${{ secrets.DOCKER_SH_ADMIN_CONTAINER_NAME }}:latest
${{ secrets.DOCKER_ORG_NAME }}/${{ secrets.DOCKER_SH_ADMIN_CONTAINER_NAME }}:${{ github.ref_name }}
- name: Build and push the AIO container
uses: docker/build-push-action@v4
with:
context: .
file: ./prod.Dockerfile
target: aio
push: true
platforms: |
linux/amd64
linux/arm64
tags: |
${{ secrets.DOCKER_ORG_NAME }}/${{ secrets.DOCKER_AIO_CONTAINER_NAME }}:latest
${{ secrets.DOCKER_ORG_NAME }}/${{ secrets.DOCKER_AIO_CONTAINER_NAME }}:${{ github.ref_name }}

View File

@@ -2,9 +2,9 @@ name: Node.js CI
on: on:
push: push:
branches: [main, staging, "release/**"] branches: [main, staging]
pull_request: pull_request:
branches: [main, staging, "release/**"] branches: [main, staging]
jobs: jobs:
test: test:

View File

@@ -1,42 +0,0 @@
name: Deploy to Netlify (ui)
on:
push:
branches: [main]
# run this workflow only if an update is made to the ui package
paths:
- "packages/hoppscotch-ui/**"
workflow_dispatch:
jobs:
deploy:
name: Deploy
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v3
- name: Setup environment
run: mv .env.example .env
- name: Setup pnpm
uses: pnpm/action-setup@v2.2.4
with:
version: 8
run_install: true
- name: Setup node
uses: actions/setup-node@v3
with:
node-version: ${{ matrix.node }}
cache: pnpm
- name: Build site
run: pnpm run generate-ui
# Deploy the ui site with netlify-cli
- name: Deploy to Netlify (ui)
run: npx netlify-cli@15.11.0 deploy --dir=packages/hoppscotch-ui/.histoire/dist --prod
env:
NETLIFY_SITE_ID: ${{ secrets.NETLIFY_UI_SITE_ID }}
NETLIFY_AUTH_TOKEN: ${{ secrets.NETLIFY_AUTH_TOKEN }}

View File

@@ -1,8 +1,3 @@
module.exports = { module.exports = {
semi: false, semi: false
trailingComma: "es5",
singleQuote: false,
printWidth: 80,
useTabs: false,
tabWidth: 2
} }

View File

@@ -6,8 +6,8 @@ We as members, contributors, and leaders pledge to make participation in our
community a harassment-free experience for everyone, regardless of age, body community a harassment-free experience for everyone, regardless of age, body
size, visible or invisible disability, ethnicity, sex characteristics, gender size, visible or invisible disability, ethnicity, sex characteristics, gender
identity and expression, level of experience, education, socio-economic status, identity and expression, level of experience, education, socio-economic status,
nationality, personal appearance, race, caste, color, religion, or sexual nationality, personal appearance, race, religion, or sexual identity
identity and orientation. and orientation.
We pledge to act and interact in ways that contribute to an open, welcoming, We pledge to act and interact in ways that contribute to an open, welcoming,
diverse, inclusive, and healthy community. diverse, inclusive, and healthy community.
@@ -22,17 +22,17 @@ community include:
* Giving and gracefully accepting constructive feedback * Giving and gracefully accepting constructive feedback
* Accepting responsibility and apologizing to those affected by our mistakes, * Accepting responsibility and apologizing to those affected by our mistakes,
and learning from the experience and learning from the experience
* Focusing on what is best not just for us as individuals, but for the overall * Focusing on what is best not just for us as individuals, but for the
community overall community
Examples of unacceptable behavior include: Examples of unacceptable behavior include:
* The use of sexualized language or imagery, and sexual attention or advances of * The use of sexualized language or imagery, and sexual attention or
any kind advances of any kind
* Trolling, insulting or derogatory comments, and personal or political attacks * Trolling, insulting or derogatory comments, and personal or political attacks
* Public or private harassment * Public or private harassment
* Publishing others' private information, such as a physical or email address, * Publishing others' private information, such as a physical or email
without their explicit permission address, without their explicit permission
* Other conduct which could reasonably be considered inappropriate in a * Other conduct which could reasonably be considered inappropriate in a
professional setting professional setting
@@ -82,15 +82,15 @@ behavior was inappropriate. A public apology may be requested.
### 2. Warning ### 2. Warning
**Community Impact**: A violation through a single incident or series of **Community Impact**: A violation through a single incident or series
actions. of actions.
**Consequence**: A warning with consequences for continued behavior. No **Consequence**: A warning with consequences for continued behavior. No
interaction with the people involved, including unsolicited interaction with interaction with the people involved, including unsolicited interaction with
those enforcing the Code of Conduct, for a specified period of time. This those enforcing the Code of Conduct, for a specified period of time. This
includes avoiding interactions in community spaces as well as external channels includes avoiding interactions in community spaces as well as external channels
like social media. Violating these terms may lead to a temporary or permanent like social media. Violating these terms may lead to a temporary or
ban. permanent ban.
### 3. Temporary Ban ### 3. Temporary Ban
@@ -106,27 +106,23 @@ Violating these terms may lead to a permanent ban.
### 4. Permanent Ban ### 4. Permanent Ban
**Community Impact**: Demonstrating a pattern of violation of community **Community Impact**: Demonstrating a pattern of violation of community
standards, including sustained inappropriate behavior, harassment of an standards, including sustained inappropriate behavior, harassment of an
individual, or aggression toward or disparagement of classes of individuals. individual, or aggression toward or disparagement of classes of individuals.
**Consequence**: A permanent ban from any sort of public interaction within the **Consequence**: A permanent ban from any sort of public interaction within
community. the community.
## Attribution ## Attribution
This Code of Conduct is adapted from the [Contributor Covenant][homepage], This Code of Conduct is adapted from the [Contributor Covenant][homepage],
version 2.1, available at version 2.0, available at
[https://www.contributor-covenant.org/version/2/1/code_of_conduct.html][v2.1]. https://www.contributor-covenant.org/version/2/0/code_of_conduct.html.
Community Impact Guidelines were inspired by Community Impact Guidelines were inspired by [Mozilla's code of conduct
[Mozilla's code of conduct enforcement ladder][Mozilla CoC]. enforcement ladder](https://github.com/mozilla/diversity).
For answers to common questions about this code of conduct, see the FAQ at
[https://www.contributor-covenant.org/faq][FAQ]. Translations are available at
[https://www.contributor-covenant.org/translations][translations].
[homepage]: https://www.contributor-covenant.org [homepage]: https://www.contributor-covenant.org
[v2.1]: https://www.contributor-covenant.org/version/2/1/code_of_conduct.html
[Mozilla CoC]: https://github.com/mozilla/diversity For answers to common questions about this code of conduct, see the FAQ at
[FAQ]: https://www.contributor-covenant.org/faq https://www.contributor-covenant.org/faq. Translations are available at
[translations]: https://www.contributor-covenant.org/translations https://www.contributor-covenant.org/translations.

190
README.md
View File

@@ -2,18 +2,23 @@
<a href="https://hoppscotch.io"> <a href="https://hoppscotch.io">
<img <img
src="https://avatars.githubusercontent.com/u/56705483" src="https://avatars.githubusercontent.com/u/56705483"
alt="Hoppscotch" alt="Hoppscotch Logo"
height="64" height="64"
/> />
</a> </a>
<h3> <br />
<p>
<h3>
<b>
Hoppscotch
</b>
</h3>
</p>
<p>
<b> <b>
Hoppscotch Open source API development ecosystem
</b> </b>
</h3> </p>
<b>
Open Source API Development Ecosystem
</b>
<p> <p>
[![contributions welcome](https://img.shields.io/badge/contributions-welcome-brightgreen?logo=github)](CODE_OF_CONDUCT.md) [![Website](https://img.shields.io/website?url=https%3A%2F%2Fhoppscotch.io&logo=hoppscotch)](https://hoppscotch.io) [![Tests](https://github.com/hoppscotch/hoppscotch/actions/workflows/tests.yml/badge.svg)](https://github.com/hoppscotch/hoppscotch/actions) [![Tweet](https://img.shields.io/twitter/url?url=https%3A%2F%2Fhoppscotch.io%2F)](https://twitter.com/share?text=%F0%9F%91%BD%20Hoppscotch%20%E2%80%A2%20Open%20source%20API%20development%20ecosystem%20-%20Helps%20you%20create%20requests%20faster,%20saving%20precious%20time%20on%20development.&url=https://hoppscotch.io&hashtags=hoppscotch&via=hoppscotch_io) [![contributions welcome](https://img.shields.io/badge/contributions-welcome-brightgreen?logo=github)](CODE_OF_CONDUCT.md) [![Website](https://img.shields.io/website?url=https%3A%2F%2Fhoppscotch.io&logo=hoppscotch)](https://hoppscotch.io) [![Tests](https://github.com/hoppscotch/hoppscotch/actions/workflows/tests.yml/badge.svg)](https://github.com/hoppscotch/hoppscotch/actions) [![Tweet](https://img.shields.io/twitter/url?url=https%3A%2F%2Fhoppscotch.io%2F)](https://twitter.com/share?text=%F0%9F%91%BD%20Hoppscotch%20%E2%80%A2%20Open%20source%20API%20development%20ecosystem%20-%20Helps%20you%20create%20requests%20faster,%20saving%20precious%20time%20on%20development.&url=https://hoppscotch.io&hashtags=hoppscotch&via=hoppscotch_io)
@@ -29,18 +34,23 @@
</p> </p>
<br /> <br />
<p> <p>
<a href="https://hoppscotch.io"> <a href="https://hoppscotch.io/#gh-light-mode-only" target="_blank">
<picture> <img
<source media="(prefers-color-scheme: dark)" srcset="./packages/hoppscotch-common/public/images/banner-dark.png"> src="./packages/hoppscotch-common/public/images/banner-light.png"
<source media="(prefers-color-scheme: light)" srcset="./packages/hoppscotch-common/public/images/banner-light.png"> alt="Hoppscotch"
<img alt="Hoppscotch" src="./packages/hoppscotch-common/public/images/banner-dark.png"> width="100%"
</picture> />
</a>
<a href="https://hoppscotch.io/#gh-dark-mode-only" target="_blank">
<img
src="./packages/hoppscotch-common/public/images/banner-dark.png"
alt="Hoppscotch"
width="100%"
/>
</a> </a>
</p> </p>
</div> </div>
_We highly recommend you take a look at the [**Hoppscotch Documentation**](https://docs.hoppscotch.io) to learn more about the app._
#### **Support** #### **Support**
[![Chat on Discord](https://img.shields.io/badge/chat-Discord-7289DA?logo=discord)](https://hoppscotch.io/discord) [![Chat on Telegram](https://img.shields.io/badge/chat-Telegram-2CA5E0?logo=telegram)](https://hoppscotch.io/telegram) [![Discuss on GitHub](https://img.shields.io/badge/discussions-GitHub-333333?logo=github)](https://github.com/hoppscotch/hoppscotch/discussions) [![Chat on Discord](https://img.shields.io/badge/chat-Discord-7289DA?logo=discord)](https://hoppscotch.io/discord) [![Chat on Telegram](https://img.shields.io/badge/chat-Telegram-2CA5E0?logo=telegram)](https://hoppscotch.io/telegram) [![Discuss on GitHub](https://img.shields.io/badge/discussions-GitHub-333333?logo=github)](https://github.com/hoppscotch/hoppscotch/discussions)
@@ -49,9 +59,9 @@ _We highly recommend you take a look at the [**Hoppscotch Documentation**](https
❤️ **Lightweight:** Crafted with minimalistic UI design. ❤️ **Lightweight:** Crafted with minimalistic UI design.
⚡️ **Fast:** Send requests and get responses in real time. ⚡️ **Fast:** Send requests and get/copy responses in real-time.
🗄️ **HTTP Methods:** Request methods define the type of action you are requesting to be performed. **HTTP Methods**
- `GET` - Requests retrieve resource information - `GET` - Requests retrieve resource information
- `POST` - The server creates a new entry in a database - `POST` - The server creates a new entry in a database
@@ -64,15 +74,17 @@ _We highly recommend you take a look at the [**Hoppscotch Documentation**](https
- `TRACE` - Performs a message loop-back test along the path to the target resource - `TRACE` - Performs a message loop-back test along the path to the target resource
- `<custom>` - Some APIs use custom request methods such as `LIST`. Type in your custom methods. - `<custom>` - Some APIs use custom request methods such as `LIST`. Type in your custom methods.
🌈 **Theming:** Customizable combinations for background, foreground, and accent colors — [customize now](https://hoppscotch.io/settings). 🌈 **Make it yours:** Customizable combinations for background, foreground, and accent colors — [customize now](https://hoppscotch.io/settings).
- Choose a theme: System preference, Light, Dark, and Black **Theming**
- Choose accent colors: Green, Teal, Blue, Indigo, Purple, Yellow, Orange, Red, and Pink
- Choose a theme: System (default), Light, Dark, and Black
- Choose accent color: Green (default), Teal, Blue, Indigo, Purple, Yellow, Orange, Red, and Pink
- Distraction-free Zen mode - Distraction-free Zen mode
_Customized themes are synced with your cloud/local session._ _Customized themes are synced with cloud / local session_
🔥 **PWA:** Install as a [Progressive Web App](https://web.dev/progressive-web-apps) on your device. 🔥 **PWA:** Install as a [PWA](https://web.dev/what-are-pwas/) on your device.
- Instant loading with Service Workers - Instant loading with Service Workers
- Offline support - Offline support
@@ -95,7 +107,7 @@ _Customized themes are synced with your cloud/local session._
📡 **Server-Sent Events:** Receive a stream of updates from a server over an HTTP connection without resorting to polling. 📡 **Server-Sent Events:** Receive a stream of updates from a server over an HTTP connection without resorting to polling.
🌩 **Socket.IO:** Send and Receive data with the SocketIO server. 🌩 **Socket.IO:** Send and Receive data with SocketIO server.
🦟 **MQTT:** Subscribe and Publish to topics of an MQTT Broker. 🦟 **MQTT:** Subscribe and Publish to topics of an MQTT Broker.
@@ -115,7 +127,7 @@ _Customized themes are synced with your cloud/local session._
- OAuth 2.0 - OAuth 2.0
- OIDC Access Token/PKCE - OIDC Access Token/PKCE
📢 **Headers:** Describes the format the body of your request is being sent in. 📢 **Headers:** Describes the format the body of your request is being sent as.
📫 **Parameters:** Use request parameters to set varying parts in simulated requests. 📫 **Parameters:** Use request parameters to set varying parts in simulated requests.
@@ -125,14 +137,14 @@ _Customized themes are synced with your cloud/local session._
- FormData, JSON, and many more - FormData, JSON, and many more
- Toggle between key-value and RAW input parameter list - Toggle between key-value and RAW input parameter list
📮 **Response:** Contains the status line, headers, and the message/response body. 👋 **Response:** Contains the status line, headers, and the message/response body.
- Copy the response to the clipboard - Copy response to clipboard
- Download the response as a file - Download response as a file
- View response headers - View response headers
- View raw and preview HTML, image, JSON, and XML responses - View raw and preview of HTML, image, JSON, XML responses
**History:** Request entries are synced with your cloud/local session storage. **History:** Request entries are synced with cloud / local session storage to restore with a single click.
📁 **Collections:** Keep your API requests organized with collections and folders. Reuse them with a single click. 📁 **Collections:** Keep your API requests organized with collections and folders. Reuse them with a single click.
@@ -140,32 +152,7 @@ _Customized themes are synced with your cloud/local session._
- Nested folders - Nested folders
- Export and import as a file or GitHub gist - Export and import as a file or GitHub gist
_Collections are synced with your cloud/local session storage._ _Collections are synced with cloud / local session storage_
📜 **Pre-Request Scripts:** Snippets of code associated with a request that is executed before the request is sent.
- Set environment variables
- Include timestamp in the request headers
- Send a random alphanumeric string in the URL parameters
- Any JavaScript functions
👨‍👩‍👧‍👦 **Teams:** Helps you collaborate across your teams to design, develop, and test APIs faster.
- Create unlimited teams
- Create unlimited shared collections
- Create unlimited team members
- Role-based access control
- Cloud sync
- Multiple devices
👥 **Workspaces:** Organize your personal and team collections environments into workspaces. Easily switch between workspaces to manage multiple projects.
- Create unlimited workspaces
- Switch between personal and team workspaces
⌨️ **Keyboard Shortcuts:** Optimized for efficiency.
> **[Read our documentation on Keyboard Shortcuts](https://docs.hoppscotch.io/documentation/features/shortcuts)**
🌐 **Proxy:** Enable Proxy Mode from Settings to access blocked APIs. 🌐 **Proxy:** Enable Proxy Mode from Settings to access blocked APIs.
@@ -174,31 +161,60 @@ _Collections are synced with your cloud/local session storage._
- Access APIs served in non-HTTPS (`http://`) endpoints - Access APIs served in non-HTTPS (`http://`) endpoints
- Use your Proxy URL - Use your Proxy URL
_Official proxy server is hosted by Hoppscotch - **[GitHub](https://github.com/hoppscotch/proxyscotch)** - **[Privacy Policy](https://docs.hoppscotch.io/support/privacy)**._ _Official proxy server is hosted by Hoppscotch - **[GitHub](https://github.com/hoppscotch/proxyscotch)** - **[Privacy Policy](https://docs.hoppscotch.io/support/privacy)**_
📜 **Pre-Request Scripts β:** Snippets of code associated with a request that is executed before the request is sent.
- Set environment variables
- Include timestamp in the request headers
- Send a random alphanumeric string in the URL parameters
- Any JavaScript functions
📄 **API Documentation:** Create and share dynamic API documentation easily, quickly.
1. Add your requests to Collections and Folders
2. Export Collections and easily share your APIs with the rest of your team
3. Import Collections and Generate Documentation on-the-go
⌨️ **Keyboard Shortcuts:** Optimized for efficiency.
> **[Read our documentation on Keyboard Shortcuts](https://docs.hoppscotch.io/documentation/features/shortcuts)**
🌎 **i18n:** Experience the app in your language. 🌎 **i18n:** Experience the app in your language.
Help us to translate Hoppscotch. Please read [`TRANSLATIONS`](TRANSLATIONS.md) for details on our [`CODE OF CONDUCT`](CODE_OF_CONDUCT.md) and the process for submitting pull requests to us. Help us to translate Hoppscotch. Please read [`TRANSLATIONS`](TRANSLATIONS.md) for details on our [`CODE OF CONDUCT`](CODE_OF_CONDUCT.md), and the process for submitting pull requests to us.
☁️ **Auth + Sync:** Sign in and sync your data in real-time across all your devices. 📦 **Add-ons:** Official add-ons for hoppscotch.
**Sign in with:** - **[Proxy](https://github.com/hoppscotch/proxyscotch)** - A simple proxy server created for Hoppscotch
- **[CLI β](https://github.com/hoppscotch/hopp-cli)** - A CLI solution for Hoppscotch
- **[Browser Extensions](https://github.com/hoppscotch/hoppscotch-extension)** - Browser extensions that simplifies access to Hoppscotch
[![Firefox](https://raw.github.com/alrra/browser-logos/master/src/firefox/firefox_16x16.png) **Firefox**](https://addons.mozilla.org/en-US/firefox/addon/hoppscotch) &nbsp;|&nbsp; [![Chrome](https://raw.github.com/alrra/browser-logos/master/src/chrome/chrome_16x16.png) **Chrome**](https://chrome.google.com/webstore/detail/hoppscotch-extension-for-c/amknoiejhlmhancpahfcfcfhllgkpbld)
> **Extensions fixes `CORS` issues.**
- **[Hopp-Doc-Gen](https://github.com/hoppscotch/hopp-doc-gen)** - An API doc generator CLI for Hoppscotch
_Add-ons are developed and maintained under **[Hoppscotch Organization](https://github.com/hoppscotch)**._
☁️ **Auth + Sync:** Sign in and sync your data in real-time.
**Sign in with**
- GitHub - GitHub
- Google - Google
- Microsoft - Microsoft
- Email - Email
- SSO (Single Sign-On)[^EE]
**🔄 Synchronize your data:** Handoff to continue tasks on your other devices. **Synchronize your data**
- Workspaces
- History - History
- Collections - Collections
- Environments - Environments
- Settings - Settings
**Post-Request Tests:** Write tests associated with a request that is executed after the request's response. **Post-Request Tests β:** Write tests associated with a request that is executed after the request's response.
- Check the status code as an integer - Check the status code as an integer
- Filter response headers - Filter response headers
@@ -206,7 +222,7 @@ Help us to translate Hoppscotch. Please read [`TRANSLATIONS`](TRANSLATIONS.md) f
- Set environment variables - Set environment variables
- Write JavaScript code - Write JavaScript code
🌱 **Environments:** Environment variables allow you to store and reuse values in your requests and scripts. 🌱 **Environments** : Environment variables allow you to store and reuse values in your requests and scripts.
- Unlimited environments and variables - Unlimited environments and variables
- Initialize through the pre-request script - Initialize through the pre-request script
@@ -225,31 +241,22 @@ Help us to translate Hoppscotch. Please read [`TRANSLATIONS`](TRANSLATIONS.md) f
</details> </details>
👨‍👩‍👧‍👦 **Teams β:** Helps you collaborate across your team to design, develop, and test APIs faster.
- Unlimited teams
- Unlimited shared collections
- Unlimited team members
- Role-based access control
- Cloud sync
- Multiple devices
🚚 **Bulk Edit:** Edit key-value pairs in bulk. 🚚 **Bulk Edit:** Edit key-value pairs in bulk.
- Entries are separated by newline - Entries are separated by newline
- Keys and values are separated by `:` - Keys and values are separated by `:`
- Prepend `#` to any row you want to add but keep disabled - Prepend `#` to any row you want to add but keep disabled
🎛️ **Admin dashboard:** Manage your team and invite members. **For more features, please read our [documentation](https://docs.hoppscotch.io).**
- Insights
- Manage users
- Manage teams
📦 **Add-ons:** Official add-ons for hoppscotch.
- **[Hoppscotch CLI](https://github.com/hoppscotch/hopp-cli)** - Command-line interface for Hoppscotch.
- **[Proxy](https://github.com/hoppscotch/proxyscotch)** - A simple proxy server created for Hoppscotch.
- **[Browser Extensions](https://github.com/hoppscotch/hoppscotch-extension)** - Browser extensions that enhance your Hoppscotch experience.
[![Firefox](https://raw.github.com/alrra/browser-logos/master/src/firefox/firefox_16x16.png) **Firefox**](https://addons.mozilla.org/en-US/firefox/addon/hoppscotch) &nbsp;|&nbsp; [![Chrome](https://raw.github.com/alrra/browser-logos/master/src/chrome/chrome_16x16.png) **Chrome**](https://chrome.google.com/webstore/detail/hoppscotch-extension-for-c/amknoiejhlmhancpahfcfcfhllgkpbld)
> **Extensions fix `CORS` issues.**
_Add-ons are developed and maintained under **[Hoppscotch Organization](https://github.com/hoppscotch)**._
**For a complete list of features, please read our [documentation](https://docs.hoppscotch.io).**
## **Demo** ## **Demo**
@@ -261,9 +268,18 @@ _Add-ons are developed and maintained under **[Hoppscotch Organization](https://
2. Click "Send" to simulate the request 2. Click "Send" to simulate the request
3. View the response 3. View the response
## **Built with**
- [HTML](https://developer.mozilla.org/en-US/docs/Web/HTML)
- [CSS](https://developer.mozilla.org/en-US/docs/Web/CSS), [SCSS](https://sass-lang.com), [Windi CSS](https://windicss.org)
- [JavaScript](https://developer.mozilla.org/en-US/docs/Web/JavaScript)
- [TypeScript](https://www.typescriptlang.org)
- [Vue](https://vuejs.org)
- [Vite](https://vitejs.dev)
## **Developing** ## **Developing**
Follow our [self-hosting documentation](https://docs.hoppscotch.io/documentation/self-host/getting-started) to get started with the development environment. Follow our [self-hosting guide](https://docs.hoppscotch.io/documentation/self-host/getting-started) to get started with the development environment.
## **Contributing** ## **Contributing**
@@ -281,7 +297,7 @@ See the [`CHANGELOG`](CHANGELOG.md) file for details.
## **Authors** ## **Authors**
This project owes its existence to the collective efforts of all those who contribute — [contribute now](CONTRIBUTING.md). This project exists thanks to all the people who contribute — [contribute](CONTRIBUTING.md).
<div align="center"> <div align="center">
<a href="https://github.com/hoppscotch/hoppscotch/graphs/contributors"> <a href="https://github.com/hoppscotch/hoppscotch/graphs/contributors">
@@ -293,6 +309,4 @@ This project owes its existence to the collective efforts of all those who contr
## **License** ## **License**
This project is licensed under the [MIT License](https://opensource.org/licenses/MIT) see the [`LICENSE`](LICENSE) file for details. This project is licensed under the [MIT License](https://opensource.org/licenses/MIT) - see the [`LICENSE`](LICENSE) file for details.
[^EE]: Enterprise edition feature. [Learn more](https://docs.hoppscotch.io/documentation/self-host/getting-started).

View File

@@ -2,9 +2,8 @@
This document outlines security procedures and general policies for the Hoppscotch project. This document outlines security procedures and general policies for the Hoppscotch project.
- [Security Policy](#security-policy) 1. [Reporting a security vulnerability](#reporting-a-security-vulnerability)
- [Reporting a security vulnerability](#reporting-a-security-vulnerability) 3. [Incident response process](#incident-response-process)
- [Incident response process](#incident-response-process)
## Reporting a security vulnerability ## Reporting a security vulnerability

View File

@@ -9,24 +9,26 @@ Before you start working on a new language, please look through the [open pull r
if there is no existing translation, you can create a new one by following these steps: if there is no existing translation, you can create a new one by following these steps:
1. **[Fork the repository](https://github.com/hoppscotch/hoppscotch/fork).** 1. **[Fork the repository](https://github.com/hoppscotch/hoppscotch/fork).**
2. **Checkout the `main` branch for latest translations.** 2. **Checkout the `i18n` branch for latest translations.**
3. **Create a new branch for your translation with base branch `main`.** 3. **Create a new branch for your translation with base branch `i18n`.**
4. **Create target language file in the [`/packages/hoppscotch-common/locales`](https://github.com/hoppscotch/hoppscotch/tree/main/packages/hoppscotch-common/locales) directory.** 4. **Create target language file in the [`/packages/hoppscotch-common/locales`](https://github.com/hoppscotch/hoppscotch/tree/main/packages/hoppscotch-common/locales) directory.**
5. **Copy the contents of the source file [`/packages/hoppscotch-common/locales/en.json`](https://github.com/hoppscotch/hoppscotch/blob/main/packages/hoppscotch-common/locales/en.json) to the target language file.** 5. **Copy the contents of the source file [`/packages/hoppscotch-common/locales/en.json`](https://github.com/hoppscotch/hoppscotch/blob/main/packages/hoppscotch-common/locales/en.json) to the target language file.**
6. **Translate the strings in the target language file.** 6. **Translate the strings in the target language file.**
7. **Add your language entry to [`/packages/hoppscotch-common/languages.json`](https://github.com/hoppscotch/hoppscotch/blob/main/packages/hoppscotch-common/languages.json).** 7. **Add your language entry to [`/packages/hoppscotch-common/languages.json`](https://github.com/hoppscotch/hoppscotch/blob/main/packages/hoppscotch-common/languages.json).**
8. **Save and commit changes.** 8. **Save & commit changes.**
9. **Send a pull request.** 9. **Send a pull request.**
_You may send a pull request before all steps above are complete: e.g., you may want to ask for help with translations, or getting tests to pass. However, your pull request will not be merged until all steps above are complete._ _You may send a pull request before all steps above are complete: e.g., you may want to ask for help with translations, or getting tests to pass. However, your pull request will not be merged until all steps above are complete._
`i18n` branch will be merged into `main` branch once every week.
Completing an initial translation of the whole site is a fairly large task. One way to break that task up is to work with other translators through pull requests on your fork. You can also [add collaborators to your fork](https://help.github.com/en/github/setting-up-and-managing-your-github-user-account/inviting-collaborators-to-a-personal-repository) if you'd like to invite other translators to commit directly to your fork and share responsibility for merging pull requests. Completing an initial translation of the whole site is a fairly large task. One way to break that task up is to work with other translators through pull requests on your fork. You can also [add collaborators to your fork](https://help.github.com/en/github/setting-up-and-managing-your-github-user-account/inviting-collaborators-to-a-personal-repository) if you'd like to invite other translators to commit directly to your fork and share responsibility for merging pull requests.
## Updating a translation ## Updating a translation
### Corrections ### Corrections
If you notice spelling or grammar errors, typos, or opportunities for better phrasing, open a pull request with your suggested fix. If you see a problem that you aren't sure of or don't have time to fix, [open an issue](https://github.com/hoppscotch/hoppscotch/issues/new/choose). If you notice spelling or grammar errors, typos, or opportunities for better phrasing, open a pull request with your suggested fix. If you see a problem that you aren't sure of or don't have time to fix, open an issue.
### Broken links ### Broken links

View File

@@ -1,11 +0,0 @@
:3000 {
try_files {path} /
root * /site/selfhost-web
file_server
}
:3100 {
try_files {path} /
root * /site/sh-admin
file_server
}

View File

@@ -1,72 +0,0 @@
#!/usr/local/bin/node
// @ts-check
import { execSync, spawn } from "child_process"
import fs from "fs"
import process from "process"
function runChildProcessWithPrefix(command, args, prefix) {
const childProcess = spawn(command, args);
childProcess.stdout.on('data', (data) => {
const output = data.toString().trim().split('\n');
output.forEach((line) => {
console.log(`${prefix} | ${line}`);
});
});
childProcess.stderr.on('data', (data) => {
const error = data.toString().trim().split('\n');
error.forEach((line) => {
console.error(`${prefix} | ${line}`);
});
});
childProcess.on('close', (code) => {
console.log(`${prefix} Child process exited with code ${code}`);
});
childProcess.on('error', (stuff) => {
console.log("error")
console.log(stuff)
})
return childProcess
}
const envFileContent = Object.entries(process.env)
.filter(([env]) => env.startsWith("VITE_"))
.map(([env, val]) => `${env}=${
(val.startsWith("\"") && val.endsWith("\""))
? val
: `"${val}"`
}`)
.join("\n")
fs.writeFileSync("build.env", envFileContent)
execSync(`npx import-meta-env -x build.env -e build.env -p "/site/**/*"`)
fs.rmSync("build.env")
const caddyProcess = runChildProcessWithPrefix("caddy", ["run", "--config", "/etc/caddy/Caddyfile", "--adapter", "caddyfile"], "App/Admin Dashboard Caddy")
const backendProcess = runChildProcessWithPrefix("pnpm", ["run", "start:prod"], "Backend Server")
caddyProcess.on("exit", (code) => {
console.log(`Exiting process because Caddy Server exited with code ${code}`)
process.exit(code)
})
backendProcess.on("exit", (code) => {
console.log(`Exiting process because Backend Server exited with code ${code}`)
process.exit(code)
})
process.on('SIGINT', () => {
console.log("SIGINT received, exiting...")
caddyProcess.kill("SIGINT")
backendProcess.kill("SIGINT")
process.exit(0)
})

View File

@@ -8,25 +8,23 @@ services:
hoppscotch-backend: hoppscotch-backend:
container_name: hoppscotch-backend container_name: hoppscotch-backend
build: build:
dockerfile: prod.Dockerfile dockerfile: packages/hoppscotch-backend/Dockerfile
context: . context: .
target: backend target: prod
env_file: env_file:
- ./.env - ./.env
restart: always restart: always
environment: environment:
# Edit the below line to match your PostgresDB URL if you have an outside DB (make sure to update the .env file as well) # Edit the below line to match your PostgresDB URL if you have an outside DB (make sure to update the .env file as well)
- DATABASE_URL=postgresql://postgres:testpass@hoppscotch-db:5432/hoppscotch?connect_timeout=300 - DATABASE_URL=postgresql://postgres:testpass@hoppscotch-db:5432/hoppscotch?connect_timeout=300
- PORT=3170 - PORT=3000
volumes: volumes:
# Uncomment the line below when modifying code. Only applicable when using the "dev" target. - ./packages/hoppscotch-backend/:/usr/src/app
# - ./packages/hoppscotch-backend/:/usr/src/app
- /usr/src/app/node_modules/ - /usr/src/app/node_modules/
depends_on: depends_on:
hoppscotch-db: - hoppscotch-db
condition: service_healthy
ports: ports:
- "3170:3170" - "3170:3000"
# The main hoppscotch app. This will be hosted at port 3000 # The main hoppscotch app. This will be hosted at port 3000
# NOTE: To do TLS or play around with how the app is hosted, you can look into the Caddyfile for # NOTE: To do TLS or play around with how the app is hosted, you can look into the Caddyfile for
@@ -34,9 +32,8 @@ services:
hoppscotch-app: hoppscotch-app:
container_name: hoppscotch-app container_name: hoppscotch-app
build: build:
dockerfile: prod.Dockerfile dockerfile: packages/hoppscotch-selfhost-web/Dockerfile
context: . context: .
target: app
env_file: env_file:
- ./.env - ./.env
depends_on: depends_on:
@@ -50,9 +47,8 @@ services:
hoppscotch-sh-admin: hoppscotch-sh-admin:
container_name: hoppscotch-sh-admin container_name: hoppscotch-sh-admin
build: build:
dockerfile: prod.Dockerfile dockerfile: packages/hoppscotch-sh-admin/Dockerfile
context: . context: .
target: sh_admin
env_file: env_file:
- ./.env - ./.env
depends_on: depends_on:
@@ -60,91 +56,16 @@ services:
ports: ports:
- "3100:8080" - "3100:8080"
# The service that spins up all 3 services at once in one container
hoppscotch-aio:
container_name: hoppscotch-aio
build:
dockerfile: prod.Dockerfile
context: .
target: aio
env_file:
- ./.env
depends_on:
hoppscotch-db:
condition: service_healthy
ports:
- "3000:3000"
- "3100:3100"
- "3170:3170"
# The preset DB service, you can delete/comment the below lines if # The preset DB service, you can delete/comment the below lines if
# you are using an external postgres instance # you are using an external postgres instance
# This will be exposed at port 5432 # This will be exposed at port 5432
hoppscotch-db: hoppscotch-db:
image: postgres:15 image: postgres
ports: ports:
- "5432:5432" - "5432:5432"
user: postgres
environment: environment:
# The default user defined by the docker image
POSTGRES_USER: postgres
# NOTE: Please UPDATE THIS PASSWORD! # NOTE: Please UPDATE THIS PASSWORD!
POSTGRES_PASSWORD: testpass POSTGRES_PASSWORD: testpass
POSTGRES_DB: hoppscotch POSTGRES_DB: hoppscotch
healthcheck:
test:
[
"CMD-SHELL",
"sh -c 'pg_isready -U $${POSTGRES_USER} -d $${POSTGRES_DB}'"
]
interval: 5s
timeout: 5s
retries: 10
# All the services listed below are deprececated
hoppscotch-old-backend:
container_name: hoppscotch-old-backend
build:
dockerfile: packages/hoppscotch-backend/Dockerfile
context: .
target: prod
env_file:
- ./.env
restart: always
environment:
# Edit the below line to match your PostgresDB URL if you have an outside DB (make sure to update the .env file as well)
- DATABASE_URL=postgresql://postgres:testpass@hoppscotch-db:5432/hoppscotch?connect_timeout=300
- PORT=3000
volumes:
# Uncomment the line below when modifying code. Only applicable when using the "dev" target.
# - ./packages/hoppscotch-backend/:/usr/src/app
- /usr/src/app/node_modules/
depends_on:
hoppscotch-db:
condition: service_healthy
ports:
- "3170:3000"
hoppscotch-old-app:
container_name: hoppscotch-old-app
build:
dockerfile: packages/hoppscotch-selfhost-web/Dockerfile
context: .
env_file:
- ./.env
depends_on:
- hoppscotch-old-backend
ports:
- "3000:8080"
hoppscotch-old-sh-admin:
container_name: hoppscotch-old-sh-admin
build:
dockerfile: packages/hoppscotch-sh-admin/Dockerfile
context: .
env_file:
- ./.env
depends_on:
- hoppscotch-old-backend
ports:
- "3100:8080"

View File

@@ -1,14 +0,0 @@
#!/bin/bash
curlCheck() {
if ! curl -s --head "$1" | head -n 1 | grep -q "HTTP/1.[01] [23].."; then
echo "URL request failed!"
exit 1
else
echo "URL request succeeded!"
fi
}
curlCheck "http://localhost:3000"
curlCheck "http://localhost:3100"
curlCheck "http://localhost:3170/ping"

View File

@@ -11,7 +11,7 @@
"dev": "pnpm -r do-dev", "dev": "pnpm -r do-dev",
"gen-gql": "cross-env GQL_SCHEMA_EMIT_LOCATION='../../../gql-gen/backend-schema.gql' pnpm -r generate-gql-sdl", "gen-gql": "cross-env GQL_SCHEMA_EMIT_LOCATION='../../../gql-gen/backend-schema.gql' pnpm -r generate-gql-sdl",
"generate": "pnpm -r do-build-prod", "generate": "pnpm -r do-build-prod",
"start": "http-server packages/hoppscotch-selfhost-web/dist -p 3000", "start": "http-server packages/hoppscotch-web/dist -p 3000",
"lint": "pnpm -r do-lint", "lint": "pnpm -r do-lint",
"typecheck": "pnpm -r do-typecheck", "typecheck": "pnpm -r do-typecheck",
"lintfix": "pnpm -r do-lintfix", "lintfix": "pnpm -r do-lintfix",
@@ -22,22 +22,15 @@
"workspaces": [ "workspaces": [
"./packages/*" "./packages/*"
], ],
"dependencies": {
"husky": "^7.0.4",
"lint-staged": "^12.3.8"
},
"devDependencies": { "devDependencies": {
"@commitlint/cli": "^16.2.3", "@commitlint/cli": "^16.2.3",
"@commitlint/config-conventional": "^16.2.1", "@commitlint/config-conventional": "^16.2.1",
"@types/node": "17.0.27", "@types/node": "^17.0.24",
"cross-env": "^7.0.3", "cross-env": "^7.0.3",
"http-server": "^14.1.1", "http-server": "^14.1.1"
"husky": "^7.0.4",
"lint-staged": "12.4.0"
},
"pnpm": {
"packageExtensions": {
"httpsnippet@^3.0.1": {
"peerDependencies": {
"ajv": "6.12.3"
}
}
}
} }
} }

View File

@@ -17,16 +17,16 @@
"types": "dist/index.d.ts", "types": "dist/index.d.ts",
"sideEffects": false, "sideEffects": false,
"dependencies": { "dependencies": {
"@codemirror/language": "^6.9.2", "@codemirror/language": "^6.2.0",
"@lezer/highlight": "1.1.4", "@lezer/highlight": "^1.0.0",
"@lezer/lr": "^1.3.13" "@lezer/lr": "^1.2.0"
}, },
"devDependencies": { "devDependencies": {
"@lezer/generator": "^1.5.1", "@lezer/generator": "^1.1.0",
"mocha": "^9.2.2", "mocha": "^9.2.2",
"rollup": "^3.29.3", "rollup": "^2.70.2",
"rollup-plugin-dts": "^6.0.2", "rollup-plugin-dts": "^4.2.1",
"rollup-plugin-ts": "^3.4.5", "rollup-plugin-ts": "^2.0.7",
"typescript": "^5.2.2" "typescript": "^4.6.3"
} }
} }

View File

@@ -1,24 +0,0 @@
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
lerna-debug.log*
node_modules
dist
dist-ssr
*.local
# Editor directories and files
.vscode/*
!.vscode/extensions.json
.idea
.DS_Store
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?

View File

@@ -1,141 +0,0 @@
# dioc
A small and lightweight dependency injection / inversion of control system.
### About
`dioc` is a really simple **DI/IOC** system where you write services (which are singletons per container) that can depend on each other and emit events that can be listened upon.
### Demo
```ts
import { Service, Container } from "dioc"
// Here is a simple service, which you can define by extending the Service class
// and providing an ID static field (of type string)
export class PersistenceService extends Service {
// This should be unique for each container
public static ID = "PERSISTENCE_SERVICE"
public read(key: string): string | undefined {
// ...
}
public write(key: string, value: string) {
// ...
}
}
type TodoServiceEvent =
| { type: "TODO_CREATED"; index: number }
| { type: "TODO_DELETED"; index: number }
// Services have a built in event system
// Define the generic argument to say what are the possible emitted values
export class TodoService extends Service<TodoServiceEvent> {
public static ID = "TODO_SERVICE"
// Inject persistence service into this service
private readonly persistence = this.bind(PersistenceService)
public todos = []
// Service constructors cannot have arguments
constructor() {
super()
this.todos = JSON.parse(this.persistence.read("todos") ?? "[]")
}
public addTodo(text: string) {
// ...
// You can access services via the bound fields
this.persistence.write("todos", JSON.stringify(this.todos))
// This is how you emit an event
this.emit({
type: "TODO_CREATED",
index,
})
}
public removeTodo(index: number) {
// ...
this.emit({
type: "TODO_DELETED",
index,
})
}
}
// Services need a container to run in
const container = new Container()
// You can initialize and get services using Container#bind
// It will automatically initialize the service (and its dependencies)
const todoService = container.bind(TodoService) // Returns an instance of TodoService
```
### Demo (Unit Test)
`dioc/testing` contains `TestContainer` which lets you bind mocked services to the container.
```ts
import { TestContainer } from "dioc/testing"
import { TodoService, PersistenceService } from "./demo.ts" // The above demo code snippet
import { describe, it, expect, vi } from "vitest"
describe("TodoService", () => {
it("addTodo writes to persistence", () => {
const container = new TestContainer()
const writeFn = vi.fn()
// The first parameter is the service to mock and the second parameter
// is the mocked service fields and functions
container.bindMock(PersistenceService, {
read: () => undefined, // Not really important for this test
write: writeFn,
})
// the peristence service bind in TodoService will now use the
// above defined mocked implementation
const todoService = container.bind(TodoService)
todoService.addTodo("sup")
expect(writeFn).toHaveBeenCalledOnce()
expect(writeFn).toHaveBeenCalledWith("todos", JSON.stringify(["sup"]))
})
})
```
### Demo (Vue)
`dioc/vue` contains a Vue Plugin and a `useService` composable that allows Vue components to use the defined services.
In the app entry point:
```ts
import { createApp } from "vue"
import { diocPlugin } from "dioc/vue"
const app = createApp()
app.use(diocPlugin, {
container: new Container(), // You can pass in the container you want to provide to the components here
})
```
In your Vue components:
```vue
<script setup>
import { TodoService } from "./demo.ts" // The above demo
import { useService } from "dioc/vue"
const todoService = useService(TodoService) // Returns an instance of the TodoService class
</script>
```

View File

@@ -1,2 +0,0 @@
export { default } from "./dist/main.d.ts"
export * from "./dist/main.d.ts"

View File

@@ -1,147 +0,0 @@
import { Service } from "./service"
import { Observable, Subject } from 'rxjs'
/**
* Stores the current container instance in the current operating context.
*
* NOTE: This should not be used outside of dioc library code
*/
export let currentContainer: Container | null = null
/**
* The events emitted by the container
*
* `SERVICE_BIND` - emitted when a service is bound to the container directly or as a dependency to another service
* `SERVICE_INIT` - emitted when a service is initialized
*/
export type ContainerEvent =
| {
type: 'SERVICE_BIND';
/** The Service ID of the service being bounded (the dependency) */
boundeeID: string;
/**
* The Service ID of the bounder that is binding the boundee (the dependent)
*
* NOTE: This will be undefined if the service is bound directly to the container
*/
bounderID: string | undefined
}
| {
type: 'SERVICE_INIT';
/** The Service ID of the service being initialized */
serviceID: string
}
/**
* The dependency injection container, allows for services to be initialized and maintains the dependency trees.
*/
export class Container {
/** Used during the `bind` operation to detect circular dependencies */
private bindStack: string[] = []
/** The map of bound services to their IDs */
protected boundMap = new Map<string, Service<unknown>>()
/** The RxJS observable representing the event stream */
protected event$ = new Subject<ContainerEvent>()
/**
* Returns whether a container has the given service bound
* @param service The service to check for
*/
public hasBound<
T extends typeof Service<any> & { ID: string }
>(service: T): boolean {
return this.boundMap.has(service.ID)
}
/**
* Returns the service bound to the container with the given ID or if not found, undefined.
*
* NOTE: This is an advanced method and should not be used as much as possible.
*
* @param serviceID The ID of the service to get
*/
public getBoundServiceWithID(serviceID: string): Service<unknown> | undefined {
return this.boundMap.get(serviceID)
}
/**
* Binds a service to the container. This is equivalent to marking a service as a dependency.
* @param service The class reference of a service to bind
* @param bounder The class reference of the service that is binding the service (if bound directly to the container, this should be undefined)
*/
public bind<T extends typeof Service<any> & { ID: string }>(
service: T,
bounder: ((typeof Service<T>) & { ID: string }) | undefined = undefined
): InstanceType<T> {
// We need to store the current container in a variable so that we can restore it after the bind operation
const oldCurrentContainer = currentContainer;
currentContainer = this;
// If the service is already bound, return the existing instance
if (this.hasBound(service)) {
this.event$.next({
type: 'SERVICE_BIND',
boundeeID: service.ID,
bounderID: bounder?.ID // Return the bounder ID if it is defined, else assume its the container
})
return this.boundMap.get(service.ID) as InstanceType<T> // Casted as InstanceType<T> because service IDs and types are expected to match
}
// Detect circular dependency and throw error
if (this.bindStack.findIndex((serviceID) => serviceID === service.ID) !== -1) {
const circularServices = `${this.bindStack.join(' -> ')} -> ${service.ID}`
throw new Error(`Circular dependency detected.\nChain: ${circularServices}`)
}
// Push the service ID onto the bind stack to detect circular dependencies
this.bindStack.push(service.ID)
// Initialize the service and emit events
// NOTE: We need to cast the service to any as TypeScript thinks that the service is abstract
const instance: Service<any> = new (service as any)()
this.boundMap.set(service.ID, instance)
this.bindStack.pop()
this.event$.next({
type: 'SERVICE_INIT',
serviceID: service.ID,
})
this.event$.next({
type: 'SERVICE_BIND',
boundeeID: service.ID,
bounderID: bounder?.ID
})
// Restore the current container
currentContainer = oldCurrentContainer;
// We expect the return type to match the service definition
return instance as InstanceType<T>
}
/**
* Returns an iterator of the currently bound service IDs and their instances
*/
public getBoundServices(): IterableIterator<[string, Service<any>]> {
return this.boundMap.entries()
}
/**
* Returns the public container event stream
*/
public getEventStream(): Observable<ContainerEvent> {
return this.event$.asObservable()
}
}

View File

@@ -1,2 +0,0 @@
export * from "./container"
export * from "./service"

View File

@@ -1,65 +0,0 @@
import { Observable, Subject } from 'rxjs'
import { Container, currentContainer } from './container'
/**
* A Dioc service that can bound to a container and can bind dependency services.
*
* NOTE: Services cannot have a constructor that takes arguments.
*
* @template EventDef The type of events that can be emitted by the service. These will be accessible by event streams
*/
export abstract class Service<EventDef = {}> {
/**
* The internal event stream of the service
*/
private event$ = new Subject<EventDef>()
/** The container the service is bound to */
#container: Container
constructor() {
if (!currentContainer) {
throw new Error(
`Tried to initialize service with no container (ID: ${ (this.constructor as any).ID })`
)
}
this.#container = currentContainer
}
/**
* Binds a dependency service into this service.
* @param service The class reference of the service to bind
*/
protected bind<T extends typeof Service<any> & { ID: string }>(service: T): InstanceType<T> {
if (!currentContainer) {
throw new Error('No currentContainer defined.')
}
return currentContainer.bind(service, this.constructor as typeof Service<any> & { ID: string })
}
/**
* Returns the container the service is bound to
*/
protected getContainer(): Container {
return this.#container
}
/**
* Emits an event on the service's event stream
* @param event The event to emit
*/
protected emit(event: EventDef) {
this.event$.next(event)
}
/**
* Returns the event stream of the service
*/
public getEventStream(): Observable<EventDef> {
return this.event$.asObservable()
}
}

View File

@@ -1,33 +0,0 @@
import { Container, Service } from "./main";
/**
* A container that can be used for writing tests, contains additional methods
* for binding suitable for writing tests. (see `bindMock`).
*/
export class TestContainer extends Container {
/**
* Binds a mock service to the container.
*
* @param service
* @param mock
*/
public bindMock<
T extends typeof Service<any> & { ID: string },
U extends Partial<InstanceType<T>>
>(service: T, mock: U): U {
if (this.boundMap.has(service.ID)) {
throw new Error(`Service '${service.ID}' already bound to container. Did you already call bindMock on this ?`)
}
this.boundMap.set(service.ID, mock as any)
this.event$.next({
type: "SERVICE_BIND",
boundeeID: service.ID,
bounderID: undefined,
})
return mock
}
}

View File

@@ -1,34 +0,0 @@
import { Plugin, inject } from "vue"
import { Container } from "./container"
import { Service } from "./service"
const VUE_CONTAINER_KEY = Symbol()
// TODO: Some Vue version issue with plugin generics is breaking type checking
/**
* The Vue Dioc Plugin, this allows the composables to work and access the container
*
* NOTE: Make sure you add `vue` as dependency to be able to use this plugin (duh)
*/
export const diocPlugin: Plugin = {
install(app, { container }) {
app.provide(VUE_CONTAINER_KEY, container)
}
}
/**
* A composable that binds a service to a Vue Component
*
* @param service The class reference of the service to bind
*/
export function useService<
T extends typeof Service<any> & { ID: string }
>(service: T): InstanceType<T> {
const container = inject(VUE_CONTAINER_KEY) as Container | undefined | null
if (!container) {
throw new Error("Container not found, did you forget to install the dioc plugin?")
}
return container.bind(service)
}

View File

@@ -1,54 +0,0 @@
{
"name": "dioc",
"private": true,
"version": "0.1.0",
"type": "module",
"files": [
"dist",
"index.d.ts"
],
"main": "./dist/counter.umd.cjs",
"module": "./dist/counter.js",
"types": "./index.d.ts",
"exports": {
".": {
"types": "./dist/main.d.ts",
"require": "./dist/index.cjs",
"import": "./dist/index.js"
},
"./vue": {
"types": "./dist/vue.d.ts",
"require": "./dist/vue.cjs",
"import": "./dist/vue.js"
},
"./testing": {
"types": "./dist/testing.d.ts",
"require": "./dist/testing.cjs",
"import": "./dist/testing.js"
}
},
"scripts": {
"dev": "vite",
"build": "vite build && tsc --emitDeclarationOnly",
"prepare": "pnpm run build",
"test": "vitest run",
"do-test": "pnpm run test",
"test:watch": "vitest"
},
"devDependencies": {
"typescript": "^4.9.4",
"vite": "^4.0.4",
"vitest": "^0.29.3"
},
"dependencies": {
"rxjs": "^7.8.1"
},
"peerDependencies": {
"vue": "^3.2.25"
},
"peerDependenciesMeta": {
"vue": {
"optional": true
}
}
}

View File

@@ -1,262 +0,0 @@
import { it, expect, describe, vi } from "vitest"
import { Service } from "../lib/service"
import { Container, currentContainer, ContainerEvent } from "../lib/container"
class TestServiceA extends Service {
public static ID = "TestServiceA"
}
class TestServiceB extends Service {
public static ID = "TestServiceB"
// Marked public to allow for testing
public readonly serviceA = this.bind(TestServiceA)
}
describe("Container", () => {
describe("getBoundServiceWithID", () => {
it("returns the service instance if it is bound to the container", () => {
const container = new Container()
const service = container.bind(TestServiceA)
expect(container.getBoundServiceWithID(TestServiceA.ID)).toBe(service)
})
it("returns undefined if the service is not bound to the container", () => {
const container = new Container()
expect(container.getBoundServiceWithID(TestServiceA.ID)).toBeUndefined()
})
})
describe("bind", () => {
it("correctly binds the service to it", () => {
const container = new Container()
const service = container.bind(TestServiceA)
// @ts-expect-error getContainer is defined as a protected property, but we are leveraging it here to check
expect(service.getContainer()).toBe(container)
})
it("after bind, the current container is set back to its previous value", () => {
const originalValue = currentContainer
const container = new Container()
container.bind(TestServiceA)
expect(currentContainer).toBe(originalValue)
})
it("dependent services are registered in the same container", () => {
const container = new Container()
const serviceB = container.bind(TestServiceB)
// @ts-expect-error getContainer is defined as a protected property, but we are leveraging it here to check
expect(serviceB.serviceA.getContainer()).toBe(container)
})
it("binding an already initialized service returns the initialized instance (services are singletons)", () => {
const container = new Container()
const serviceA = container.bind(TestServiceA)
const serviceA2 = container.bind(TestServiceA)
expect(serviceA).toBe(serviceA2)
})
it("binding a service which is a dependency of another service returns the same instance created from the dependency resolution (services are singletons)", () => {
const container = new Container()
const serviceB = container.bind(TestServiceB)
const serviceA = container.bind(TestServiceA)
expect(serviceB.serviceA).toBe(serviceA)
})
it("binding an initialized service as a dependency returns the same instance", () => {
const container = new Container()
const serviceA = container.bind(TestServiceA)
const serviceB = container.bind(TestServiceB)
expect(serviceB.serviceA).toBe(serviceA)
})
it("container emits an init event when an uninitialized service is initialized via bind and event only called once", () => {
const container = new Container()
const serviceFunc = vi.fn<
[ContainerEvent & { type: "SERVICE_INIT" }],
void
>()
container.getEventStream().subscribe((ev) => {
if (ev.type === "SERVICE_INIT") {
serviceFunc(ev)
}
})
const instance = container.bind(TestServiceA)
expect(serviceFunc).toHaveBeenCalledOnce()
expect(serviceFunc).toHaveBeenCalledWith(<ContainerEvent>{
type: "SERVICE_INIT",
serviceID: TestServiceA.ID,
})
})
it("the bind event emitted has an undefined bounderID when the service is bound directly to the container", () => {
const container = new Container()
const serviceFunc = vi.fn<
[ContainerEvent & { type: "SERVICE_BIND" }],
void
>()
container.getEventStream().subscribe((ev) => {
if (ev.type === "SERVICE_BIND") {
serviceFunc(ev)
}
})
container.bind(TestServiceA)
expect(serviceFunc).toHaveBeenCalledOnce()
expect(serviceFunc).toHaveBeenCalledWith(<ContainerEvent>{
type: "SERVICE_BIND",
boundeeID: TestServiceA.ID,
bounderID: undefined,
})
})
it("the bind event emitted has the correct bounderID when the service is bound to another service", () => {
const container = new Container()
const serviceFunc = vi.fn<
[ContainerEvent & { type: "SERVICE_BIND" }],
void
>()
container.getEventStream().subscribe((ev) => {
// We only care about the bind event of TestServiceA
if (ev.type === "SERVICE_BIND" && ev.boundeeID === TestServiceA.ID) {
serviceFunc(ev)
}
})
container.bind(TestServiceB)
expect(serviceFunc).toHaveBeenCalledOnce()
expect(serviceFunc).toHaveBeenCalledWith(<ContainerEvent>{
type: "SERVICE_BIND",
boundeeID: TestServiceA.ID,
bounderID: TestServiceB.ID,
})
})
})
describe("hasBound", () => {
it("returns true if the given service is bound to the container", () => {
const container = new Container()
container.bind(TestServiceA)
expect(container.hasBound(TestServiceA)).toEqual(true)
})
it("returns false if the given service is not bound to the container", () => {
const container = new Container()
expect(container.hasBound(TestServiceA)).toEqual(false)
})
it("returns true when the service is bound because it is a dependency of another service", () => {
const container = new Container()
container.bind(TestServiceB)
expect(container.hasBound(TestServiceA)).toEqual(true)
})
})
describe("getEventStream", () => {
it("returns an observable which emits events correctly when services are initialized", () => {
const container = new Container()
const serviceFunc = vi.fn<
[ContainerEvent & { type: "SERVICE_INIT" }],
void
>()
container.getEventStream().subscribe((ev) => {
if (ev.type === "SERVICE_INIT") {
serviceFunc(ev)
}
})
container.bind(TestServiceB)
expect(serviceFunc).toHaveBeenCalledTimes(2)
expect(serviceFunc).toHaveBeenNthCalledWith(1, <ContainerEvent>{
type: "SERVICE_INIT",
serviceID: TestServiceA.ID,
})
expect(serviceFunc).toHaveBeenNthCalledWith(2, <ContainerEvent>{
type: "SERVICE_INIT",
serviceID: TestServiceB.ID,
})
})
it("returns an observable which emits events correctly when services are bound", () => {
const container = new Container()
const serviceFunc = vi.fn<
[ContainerEvent & { type: "SERVICE_BIND" }],
void
>()
container.getEventStream().subscribe((ev) => {
if (ev.type === "SERVICE_BIND") {
serviceFunc(ev)
}
})
container.bind(TestServiceB)
expect(serviceFunc).toHaveBeenCalledTimes(2)
expect(serviceFunc).toHaveBeenNthCalledWith(1, <ContainerEvent>{
type: "SERVICE_BIND",
boundeeID: TestServiceA.ID,
bounderID: TestServiceB.ID,
})
expect(serviceFunc).toHaveBeenNthCalledWith(2, <ContainerEvent>{
type: "SERVICE_BIND",
boundeeID: TestServiceB.ID,
bounderID: undefined,
})
})
})
describe("getBoundServices", () => {
it("returns an iterator over all services bound to the container in the format [service id, service instance]", () => {
const container = new Container()
const instanceB = container.bind(TestServiceB)
const instanceA = instanceB.serviceA
expect(Array.from(container.getBoundServices())).toEqual([
[TestServiceA.ID, instanceA],
[TestServiceB.ID, instanceB],
])
})
it("returns an empty iterator if no services are bound", () => {
const container = new Container()
expect(Array.from(container.getBoundServices())).toEqual([])
})
})
})

View File

@@ -1,66 +0,0 @@
import { describe, expect, it, vi } from "vitest"
import { Service, Container } from "../lib/main"
class TestServiceA extends Service {
public static ID = "TestServiceA"
}
class TestServiceB extends Service<"test"> {
public static ID = "TestServiceB"
// Marked public to allow for testing
public readonly serviceA = this.bind(TestServiceA)
public emitTestEvent() {
this.emit("test")
}
}
describe("Service", () => {
describe("constructor", () => {
it("throws an error if the service is initialized without a container", () => {
expect(() => new TestServiceA()).toThrowError(
"Tried to initialize service with no container (ID: TestServiceA)"
)
})
})
describe("bind", () => {
it("correctly binds the dependency service using the container", () => {
const container = new Container()
const serviceA = container.bind(TestServiceA)
const serviceB = container.bind(TestServiceB)
expect(serviceB.serviceA).toBe(serviceA)
})
})
describe("getContainer", () => {
it("returns the container the service is bound to", () => {
const container = new Container()
const serviceA = container.bind(TestServiceA)
// @ts-expect-error getContainer is a protected member, we are just using it to help with testing
expect(serviceA.getContainer()).toBe(container)
})
})
describe("getEventStream", () => {
it("returns the valid event stream of the service", () => {
const container = new Container()
const serviceB = container.bind(TestServiceB)
const serviceFunc = vi.fn()
serviceB.getEventStream().subscribe(serviceFunc)
serviceB.emitTestEvent()
expect(serviceFunc).toHaveBeenCalledOnce()
expect(serviceFunc).toHaveBeenCalledWith("test")
})
})
})

View File

@@ -1,92 +0,0 @@
import { describe, expect, it, vi } from "vitest"
import { TestContainer } from "../lib/testing"
import { Service } from "../lib/service"
import { ContainerEvent } from "../lib/container"
class TestServiceA extends Service {
public static ID = "TestServiceA"
public test() {
return "real"
}
}
class TestServiceB extends Service {
public static ID = "TestServiceB"
// declared public to help with testing
public readonly serviceA = this.bind(TestServiceA)
public test() {
return this.serviceA.test()
}
}
describe("TestContainer", () => {
describe("bindMock", () => {
it("returns the fake service defined", () => {
const container = new TestContainer()
const fakeService = {
test: () => "fake",
}
const result = container.bindMock(TestServiceA, fakeService)
expect(result).toBe(fakeService)
})
it("new services bound to the container get the mock service", () => {
const container = new TestContainer()
const fakeServiceA = {
test: () => "fake",
}
container.bindMock(TestServiceA, fakeServiceA)
const serviceB = container.bind(TestServiceB)
expect(serviceB.serviceA).toBe(fakeServiceA)
})
it("container emits SERVICE_BIND event", () => {
const container = new TestContainer()
const fakeServiceA = {
test: () => "fake",
}
const serviceFunc = vi.fn<[ContainerEvent, void]>()
container.getEventStream().subscribe((ev) => {
serviceFunc(ev)
})
container.bindMock(TestServiceA, fakeServiceA)
expect(serviceFunc).toHaveBeenCalledOnce()
expect(serviceFunc).toHaveBeenCalledWith(<ContainerEvent>{
type: "SERVICE_BIND",
boundeeID: TestServiceA.ID,
bounderID: undefined,
})
})
it("throws if service already bound", () => {
const container = new TestContainer()
const fakeServiceA = {
test: () => "fake",
}
container.bindMock(TestServiceA, fakeServiceA)
expect(() => {
container.bindMock(TestServiceA, fakeServiceA)
}).toThrowError(
"Service 'TestServiceA' already bound to container. Did you already call bindMock on this ?"
)
})
})
})

View File

@@ -1,2 +0,0 @@
export { default } from "./dist/testing.d.ts"
export * from "./dist/testing.d.ts"

View File

@@ -1,21 +0,0 @@
{
"compilerOptions": {
"target": "ESNext",
"useDefineForClassFields": true,
"module": "ESNext",
"lib": ["ESNext", "DOM"],
"moduleResolution": "Node",
"strict": true,
"declaration": true,
"sourceMap": true,
"outDir": "dist",
"resolveJsonModule": true,
"isolatedModules": true,
"esModuleInterop": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"noImplicitReturns": true,
"skipLibCheck": true
},
"include": ["lib"]
}

View File

@@ -1,16 +0,0 @@
import { defineConfig } from 'vite'
export default defineConfig({
build: {
lib: {
entry: {
index: './lib/main.ts',
vue: './lib/vue.ts',
testing: './lib/testing.ts',
},
},
rollupOptions: {
external: ['vue'],
}
},
})

View File

@@ -1,7 +0,0 @@
import { defineConfig } from "vitest/config"
export default defineConfig({
test: {
}
})

View File

@@ -1,2 +0,0 @@
export { default } from "./dist/vue.d.ts"
export * from "./dist/vue.d.ts"

View File

@@ -1,6 +1,6 @@
{ {
"name": "hoppscotch-backend", "name": "hoppscotch-backend",
"version": "2023.8.2", "version": "2023.4.1",
"description": "", "description": "",
"author": "", "author": "",
"private": true, "private": true,
@@ -24,17 +24,18 @@
"do-test": "pnpm run test" "do-test": "pnpm run test"
}, },
"dependencies": { "dependencies": {
"@apollo/server": "^4.9.4", "@nestjs-modules/mailer": "^1.8.1",
"@nestjs-modules/mailer": "^1.9.1", "@nestjs/apollo": "^10.1.6",
"@nestjs/apollo": "^12.0.9", "@nestjs/common": "^9.2.1",
"@nestjs/common": "^10.2.6", "@nestjs/core": "^9.2.1",
"@nestjs/core": "^10.2.6", "@nestjs/graphql": "^10.1.6",
"@nestjs/graphql": "^12.0.9", "@nestjs/jwt": "^10.0.1",
"@nestjs/jwt": "^10.1.1", "@nestjs/passport": "^9.0.0",
"@nestjs/passport": "^10.0.2", "@nestjs/platform-express": "^9.2.1",
"@nestjs/platform-express": "^10.2.6", "@nestjs/throttler": "^4.0.0",
"@nestjs/throttler": "^5.0.0", "@prisma/client": "^4.7.1",
"@prisma/client": "^4.16.2", "apollo-server-express": "^3.11.1",
"apollo-server-plugin-base": "^3.7.1",
"argon2": "^0.30.3", "argon2": "^0.30.3",
"bcrypt": "^5.1.0", "bcrypt": "^5.1.0",
"cookie": "^0.5.0", "cookie": "^0.5.0",
@@ -42,9 +43,9 @@
"express": "^4.17.1", "express": "^4.17.1",
"express-session": "^1.17.3", "express-session": "^1.17.3",
"fp-ts": "^2.13.1", "fp-ts": "^2.13.1",
"graphql": "^16.8.1", "graphql": "^15.5.0",
"graphql-query-complexity": "^0.12.0", "graphql-query-complexity": "^0.12.0",
"graphql-redis-subscriptions": "^2.6.0", "graphql-redis-subscriptions": "^2.5.0",
"graphql-subscriptions": "^2.0.0", "graphql-subscriptions": "^2.0.0",
"handlebars": "^4.7.7", "handlebars": "^4.7.7",
"io-ts": "^2.2.16", "io-ts": "^2.2.16",
@@ -56,15 +57,15 @@
"passport-jwt": "^4.0.1", "passport-jwt": "^4.0.1",
"passport-local": "^1.0.0", "passport-local": "^1.0.0",
"passport-microsoft": "^1.0.0", "passport-microsoft": "^1.0.0",
"prisma": "^4.16.2", "prisma": "^4.7.1",
"reflect-metadata": "^0.1.13", "reflect-metadata": "^0.1.13",
"rimraf": "^3.0.2", "rimraf": "^3.0.2",
"rxjs": "^7.6.0" "rxjs": "^7.6.0"
}, },
"devDependencies": { "devDependencies": {
"@nestjs/cli": "^10.1.18", "@nestjs/cli": "^9.1.5",
"@nestjs/schematics": "^10.0.2", "@nestjs/schematics": "^9.0.3",
"@nestjs/testing": "^10.2.6", "@nestjs/testing": "^9.2.1",
"@relmify/jest-fp-ts": "^2.0.2", "@relmify/jest-fp-ts": "^2.0.2",
"@types/argon2": "^0.15.0", "@types/argon2": "^0.15.0",
"@types/bcrypt": "^5.0.0", "@types/bcrypt": "^5.0.0",

View File

@@ -5,7 +5,7 @@ datasource db {
generator client { generator client {
provider = "prisma-client-js" provider = "prisma-client-js"
binaryTargets = ["native", "debian-openssl-1.1.x", "debian-openssl-3.0.x"] binaryTargets = ["native", "debian-openssl-1.1.x"]
} }
model Team { model Team {

View File

@@ -411,23 +411,6 @@ export class AdminResolver {
return deletedTeam.right; return deletedTeam.right;
} }
@Mutation(() => Boolean, {
description: 'Revoke a team Invite by Invite ID',
})
@UseGuards(GqlAuthGuard, GqlAdminGuard)
async revokeTeamInviteByAdmin(
@Args({
name: 'inviteID',
description: 'Team Invite ID',
type: () => ID,
})
inviteID: string,
): Promise<boolean> {
const invite = await this.adminService.revokeTeamInviteByID(inviteID);
if (E.isLeft(invite)) throwErr(invite.left);
return true;
}
/* Subscriptions */ /* Subscriptions */
@Subscription(() => InvitedUser, { @Subscription(() => InvitedUser, {

View File

@@ -11,7 +11,6 @@ import {
INVALID_EMAIL, INVALID_EMAIL,
ONLY_ONE_ADMIN_ACCOUNT, ONLY_ONE_ADMIN_ACCOUNT,
TEAM_INVITE_ALREADY_MEMBER, TEAM_INVITE_ALREADY_MEMBER,
TEAM_INVITE_NO_INVITE_FOUND,
USER_ALREADY_INVITED, USER_ALREADY_INVITED,
USER_IS_ADMIN, USER_IS_ADMIN,
USER_NOT_FOUND, USER_NOT_FOUND,
@@ -74,7 +73,7 @@ export class AdminService {
try { try {
await this.mailerService.sendUserInvitationEmail(inviteeEmail, { await this.mailerService.sendUserInvitationEmail(inviteeEmail, {
template: 'user-invitation', template: 'code-your-own',
variables: { variables: {
inviteeEmail: inviteeEmail, inviteeEmail: inviteeEmail,
magicLink: `${process.env.VITE_BASE_URL}`, magicLink: `${process.env.VITE_BASE_URL}`,
@@ -182,7 +181,7 @@ export class AdminService {
* @returns an array team invitations * @returns an array team invitations
*/ */
async pendingInvitationCountInTeam(teamID: string) { async pendingInvitationCountInTeam(teamID: string) {
const invitations = await this.teamInvitationService.getTeamInvitations( const invitations = await this.teamInvitationService.getAllTeamInvitations(
teamID, teamID,
); );
@@ -237,11 +236,11 @@ export class AdminService {
const user = await this.userService.findUserByEmail(userEmail); const user = await this.userService.findUserByEmail(userEmail);
if (O.isNone(user)) return E.left(USER_NOT_FOUND); if (O.isNone(user)) return E.left(USER_NOT_FOUND);
const teamMember = await this.teamService.getTeamMemberTE( const isUserAlreadyMember = await this.teamService.getTeamMemberTE(
teamID, teamID,
user.value.uid, user.value.uid,
)(); )();
if (E.isLeft(teamMember)) { if (E.left(isUserAlreadyMember)) {
const addedUser = await this.teamService.addMemberToTeamWithEmail( const addedUser = await this.teamService.addMemberToTeamWithEmail(
teamID, teamID,
userEmail, userEmail,
@@ -249,18 +248,6 @@ export class AdminService {
); );
if (E.isLeft(addedUser)) return E.left(addedUser.left); if (E.isLeft(addedUser)) return E.left(addedUser.left);
const userInvitation =
await this.teamInvitationService.getTeamInviteByEmailAndTeamID(
userEmail,
teamID,
);
if (E.isRight(userInvitation)) {
await this.teamInvitationService.revokeInvitation(
userInvitation.right.id,
);
}
return E.right(addedUser.right); return E.right(addedUser.right);
} }
@@ -417,19 +404,4 @@ export class AdminService {
if (E.isLeft(team)) return E.left(team.left); if (E.isLeft(team)) return E.left(team.left);
return E.right(team.right); return E.right(team.right);
} }
/**
* Revoke a team invite by ID
* @param inviteID Team Invite ID
* @returns an Either of boolean or error
*/
async revokeTeamInviteByID(inviteID: string) {
const teamInvite = await this.teamInvitationService.revokeInvitation(
inviteID,
);
if (E.isLeft(teamInvite)) return E.left(teamInvite.left);
return E.right(teamInvite.right);
}
} }

View File

@@ -1,9 +0,0 @@
import { Controller, Get } from '@nestjs/common';
@Controller('ping')
export class AppController {
@Get()
ping(): string {
return 'Success';
}
}

View File

@@ -19,7 +19,6 @@ import { UserCollectionModule } from './user-collection/user-collection.module';
import { ShortcodeModule } from './shortcode/shortcode.module'; import { ShortcodeModule } from './shortcode/shortcode.module';
import { COOKIES_NOT_FOUND } from './errors'; import { COOKIES_NOT_FOUND } from './errors';
import { ThrottlerModule } from '@nestjs/throttler'; import { ThrottlerModule } from '@nestjs/throttler';
import { AppController } from './app.controller';
@Module({ @Module({
imports: [ imports: [
@@ -27,7 +26,12 @@ import { AppController } from './app.controller';
buildSchemaOptions: { buildSchemaOptions: {
numberScalarMode: 'integer', numberScalarMode: 'integer',
}, },
cors: {
origin: process.env.WHITELISTED_ORIGINS.split(','),
credentials: true,
},
playground: process.env.PRODUCTION !== 'true', playground: process.env.PRODUCTION !== 'true',
debug: process.env.PRODUCTION !== 'true',
autoSchemaFile: true, autoSchemaFile: true,
installSubscriptionHandlers: true, installSubscriptionHandlers: true,
subscriptions: { subscriptions: {
@@ -57,12 +61,10 @@ import { AppController } from './app.controller';
}), }),
driver: ApolloDriver, driver: ApolloDriver,
}), }),
ThrottlerModule.forRoot([ ThrottlerModule.forRoot({
{ ttl: +process.env.RATE_LIMIT_TTL,
ttl: +process.env.RATE_LIMIT_TTL, limit: +process.env.RATE_LIMIT_MAX,
limit: +process.env.RATE_LIMIT_MAX, }),
},
]),
UserModule, UserModule,
AuthModule, AuthModule,
AdminModule, AdminModule,
@@ -79,6 +81,5 @@ import { AppController } from './app.controller';
ShortcodeModule, ShortcodeModule,
], ],
providers: [GQLComplexityPlugin], providers: [GQLComplexityPlugin],
controllers: [AppController],
}) })
export class AppModule {} export class AppModule {}

View File

@@ -2,9 +2,9 @@ import {
Body, Body,
Controller, Controller,
Get, Get,
InternalServerErrorException,
Post, Post,
Query, Query,
Req,
Request, Request,
Res, Res,
UseGuards, UseGuards,
@@ -19,18 +19,12 @@ import { JwtAuthGuard } from './guards/jwt-auth.guard';
import { GqlUser } from 'src/decorators/gql-user.decorator'; import { GqlUser } from 'src/decorators/gql-user.decorator';
import { AuthUser } from 'src/types/AuthUser'; import { AuthUser } from 'src/types/AuthUser';
import { RTCookie } from 'src/decorators/rt-cookie.decorator'; import { RTCookie } from 'src/decorators/rt-cookie.decorator';
import { import { authCookieHandler, throwHTTPErr } from './helper';
AuthProvider,
authCookieHandler,
authProviderCheck,
throwHTTPErr,
} from './helper';
import { GoogleSSOGuard } from './guards/google-sso.guard'; import { GoogleSSOGuard } from './guards/google-sso.guard';
import { GithubSSOGuard } from './guards/github-sso.guard'; import { GithubSSOGuard } from './guards/github-sso.guard';
import { MicrosoftSSOGuard } from './guards/microsoft-sso-.guard'; import { MicrosoftSSOGuard } from './guards/microsoft-sso-.guard';
import { ThrottlerBehindProxyGuard } from 'src/guards/throttler-behind-proxy.guard'; import { ThrottlerBehindProxyGuard } from 'src/guards/throttler-behind-proxy.guard';
import { SkipThrottle } from '@nestjs/throttler'; import { SkipThrottle } from '@nestjs/throttler';
import { AUTH_PROVIDER_NOT_SPECIFIED } from 'src/errors';
@UseGuards(ThrottlerBehindProxyGuard) @UseGuards(ThrottlerBehindProxyGuard)
@Controller({ path: 'auth', version: '1' }) @Controller({ path: 'auth', version: '1' })
@@ -45,9 +39,6 @@ export class AuthController {
@Body() authData: SignInMagicDto, @Body() authData: SignInMagicDto,
@Query('origin') origin: string, @Query('origin') origin: string,
) { ) {
if (!authProviderCheck(AuthProvider.EMAIL))
throwHTTPErr({ message: AUTH_PROVIDER_NOT_SPECIFIED, statusCode: 404 });
const deviceIdToken = await this.authService.signInMagicLink( const deviceIdToken = await this.authService.signInMagicLink(
authData.email, authData.email,
origin, origin,

View File

@@ -11,7 +11,6 @@ import { RTJwtStrategy } from './strategies/rt-jwt.strategy';
import { GoogleStrategy } from './strategies/google.strategy'; import { GoogleStrategy } from './strategies/google.strategy';
import { GithubStrategy } from './strategies/github.strategy'; import { GithubStrategy } from './strategies/github.strategy';
import { MicrosoftStrategy } from './strategies/microsoft.strategy'; import { MicrosoftStrategy } from './strategies/microsoft.strategy';
import { AuthProvider, authProviderCheck } from './helper';
@Module({ @Module({
imports: [ imports: [
@@ -27,9 +26,9 @@ import { AuthProvider, authProviderCheck } from './helper';
AuthService, AuthService,
JwtStrategy, JwtStrategy,
RTJwtStrategy, RTJwtStrategy,
...(authProviderCheck(AuthProvider.GOOGLE) ? [GoogleStrategy] : []), GoogleStrategy,
...(authProviderCheck(AuthProvider.GITHUB) ? [GithubStrategy] : []), GithubStrategy,
...(authProviderCheck(AuthProvider.MICROSOFT) ? [MicrosoftStrategy] : []), MicrosoftStrategy,
], ],
controllers: [AuthController], controllers: [AuthController],
}) })

View File

@@ -228,11 +228,11 @@ export class AuthService {
url = process.env.VITE_BASE_URL; url = process.env.VITE_BASE_URL;
} }
await this.mailerService.sendEmail(email, { await this.mailerService.sendAuthEmail(email, {
template: 'user-invitation', template: 'code-your-own',
variables: { variables: {
inviteeEmail: email, inviteeEmail: email,
magicLink: `${url}/enter?token=${generatedTokens.token}`, magicLink: `${url}/magic-link?token=${generatedTokens.token}`,
}, },
}); });

View File

@@ -1,20 +1,8 @@
import { CanActivate, ExecutionContext, Injectable } from '@nestjs/common'; import { ExecutionContext, Injectable } from '@nestjs/common';
import { AuthGuard } from '@nestjs/passport'; import { AuthGuard } from '@nestjs/passport';
import { AuthProvider, authProviderCheck, throwHTTPErr } from '../helper';
import { Observable } from 'rxjs';
import { AUTH_PROVIDER_NOT_SPECIFIED } from 'src/errors';
@Injectable() @Injectable()
export class GithubSSOGuard extends AuthGuard('github') implements CanActivate { export class GithubSSOGuard extends AuthGuard('github') {
canActivate(
context: ExecutionContext,
): boolean | Promise<boolean> | Observable<boolean> {
if (!authProviderCheck(AuthProvider.GITHUB))
throwHTTPErr({ message: AUTH_PROVIDER_NOT_SPECIFIED, statusCode: 404 });
return super.canActivate(context);
}
getAuthenticateOptions(context: ExecutionContext) { getAuthenticateOptions(context: ExecutionContext) {
const req = context.switchToHttp().getRequest(); const req = context.switchToHttp().getRequest();

View File

@@ -1,20 +1,8 @@
import { CanActivate, ExecutionContext, Injectable } from '@nestjs/common'; import { ExecutionContext, Injectable } from '@nestjs/common';
import { AuthGuard } from '@nestjs/passport'; import { AuthGuard } from '@nestjs/passport';
import { AuthProvider, authProviderCheck, throwHTTPErr } from '../helper';
import { Observable } from 'rxjs';
import { AUTH_PROVIDER_NOT_SPECIFIED } from 'src/errors';
@Injectable() @Injectable()
export class GoogleSSOGuard extends AuthGuard('google') implements CanActivate { export class GoogleSSOGuard extends AuthGuard('google') {
canActivate(
context: ExecutionContext,
): boolean | Promise<boolean> | Observable<boolean> {
if (!authProviderCheck(AuthProvider.GOOGLE))
throwHTTPErr({ message: AUTH_PROVIDER_NOT_SPECIFIED, statusCode: 404 });
return super.canActivate(context);
}
getAuthenticateOptions(context: ExecutionContext) { getAuthenticateOptions(context: ExecutionContext) {
const req = context.switchToHttp().getRequest(); const req = context.switchToHttp().getRequest();

View File

@@ -1,26 +1,8 @@
import { CanActivate, ExecutionContext, Injectable } from '@nestjs/common'; import { ExecutionContext, Injectable } from '@nestjs/common';
import { AuthGuard } from '@nestjs/passport'; import { AuthGuard } from '@nestjs/passport';
import { AuthProvider, authProviderCheck, throwHTTPErr } from '../helper';
import { Observable } from 'rxjs';
import { AUTH_PROVIDER_NOT_SPECIFIED } from 'src/errors';
@Injectable() @Injectable()
export class MicrosoftSSOGuard export class MicrosoftSSOGuard extends AuthGuard('microsoft') {
extends AuthGuard('microsoft')
implements CanActivate
{
canActivate(
context: ExecutionContext,
): boolean | Promise<boolean> | Observable<boolean> {
if (!authProviderCheck(AuthProvider.MICROSOFT))
throwHTTPErr({
message: AUTH_PROVIDER_NOT_SPECIFIED,
statusCode: 404,
});
return super.canActivate(context);
}
getAuthenticateOptions(context: ExecutionContext) { getAuthenticateOptions(context: ExecutionContext) {
const req = context.switchToHttp().getRequest(); const req = context.switchToHttp().getRequest();

View File

@@ -1,11 +1,10 @@
import { HttpException, HttpStatus } from '@nestjs/common'; import { ForbiddenException, HttpException, HttpStatus } from '@nestjs/common';
import { DateTime } from 'luxon'; import { DateTime } from 'luxon';
import { AuthError } from 'src/types/AuthError'; import { AuthError } from 'src/types/AuthError';
import { AuthTokens } from 'src/types/AuthTokens'; import { AuthTokens } from 'src/types/AuthTokens';
import { Response } from 'express'; import { Response } from 'express';
import * as cookie from 'cookie'; import * as cookie from 'cookie';
import { AUTH_PROVIDER_NOT_SPECIFIED, COOKIES_NOT_FOUND } from 'src/errors'; import { COOKIES_NOT_FOUND } from 'src/errors';
import { throwErr } from 'src/utils';
enum AuthTokenType { enum AuthTokenType {
ACCESS_TOKEN = 'access_token', ACCESS_TOKEN = 'access_token',
@@ -17,13 +16,6 @@ export enum Origin {
APP = 'app', APP = 'app',
} }
export enum AuthProvider {
GOOGLE = 'GOOGLE',
GITHUB = 'GITHUB',
MICROSOFT = 'MICROSOFT',
EMAIL = 'EMAIL',
}
/** /**
* This function allows throw to be used as an expression * This function allows throw to be used as an expression
* @param errMessage Message present in the error message * @param errMessage Message present in the error message
@@ -71,7 +63,7 @@ export const authCookieHandler = (
}); });
if (!redirect) { if (!redirect) {
return res.status(HttpStatus.OK).send(); res.status(HttpStatus.OK).send();
} }
// check to see if redirectUrl is a whitelisted url // check to see if redirectUrl is a whitelisted url
@@ -80,7 +72,7 @@ export const authCookieHandler = (
// if it is not redirect by default to REDIRECT_URL // if it is not redirect by default to REDIRECT_URL
redirectUrl = process.env.REDIRECT_URL; redirectUrl = process.env.REDIRECT_URL;
return res.status(HttpStatus.OK).redirect(redirectUrl); res.status(HttpStatus.OK).redirect(redirectUrl);
}; };
/** /**
@@ -105,25 +97,3 @@ export const subscriptionContextCookieParser = (rawCookies: string) => {
refresh_token: cookies[AuthTokenType.REFRESH_TOKEN], refresh_token: cookies[AuthTokenType.REFRESH_TOKEN],
}; };
}; };
/**
* Check to see if given auth provider is present in the VITE_ALLOWED_AUTH_PROVIDERS env variable
*
* @param provider Provider we want to check the presence of
* @returns Boolean if provider specified is present or not
*/
export function authProviderCheck(provider: string) {
if (!provider) {
throwErr(AUTH_PROVIDER_NOT_SPECIFIED);
}
const envVariables = process.env.VITE_ALLOWED_AUTH_PROVIDERS
? process.env.VITE_ALLOWED_AUTH_PROVIDERS.split(',').map((provider) =>
provider.trim().toUpperCase(),
)
: [];
if (!envVariables.includes(provider.toUpperCase())) return false;
return true;
}

View File

@@ -17,7 +17,7 @@ export class MicrosoftStrategy extends PassportStrategy(Strategy) {
clientSecret: process.env.MICROSOFT_CLIENT_SECRET, clientSecret: process.env.MICROSOFT_CLIENT_SECRET,
callbackURL: process.env.MICROSOFT_CALLBACK_URL, callbackURL: process.env.MICROSOFT_CALLBACK_URL,
scope: [process.env.MICROSOFT_SCOPE], scope: [process.env.MICROSOFT_SCOPE],
tenant: process.env.MICROSOFT_TENANT, passReqToCallback: true,
store: true, store: true,
}); });
} }

View File

@@ -23,31 +23,7 @@ export const AUTH_FAIL = 'auth/fail';
export const JSON_INVALID = 'json_invalid'; export const JSON_INVALID = 'json_invalid';
/** /**
* Auth Provider not specified * Tried to delete an user data document from fb firestore but failed.
* (Auth)
*/
export const AUTH_PROVIDER_NOT_SPECIFIED = 'auth/provider_not_specified';
/**
* Environment variable "VITE_ALLOWED_AUTH_PROVIDERS" is not present in .env file
*/
export const ENV_NOT_FOUND_KEY_AUTH_PROVIDERS =
'"VITE_ALLOWED_AUTH_PROVIDERS" is not present in .env file';
/**
* Environment variable "VITE_ALLOWED_AUTH_PROVIDERS" is empty in .env file
*/
export const ENV_EMPTY_AUTH_PROVIDERS =
'"VITE_ALLOWED_AUTH_PROVIDERS" is empty in .env file';
/**
* Environment variable "VITE_ALLOWED_AUTH_PROVIDERS" contains unsupported provider in .env file
*/
export const ENV_NOT_SUPPORT_AUTH_PROVIDERS =
'"VITE_ALLOWED_AUTH_PROVIDERS" contains an unsupported auth provider in .env file';
/**
* Tried to delete a user data document from fb firestore but failed.
* (FirebaseService) * (FirebaseService)
*/ */
export const USER_FB_DOCUMENT_DELETION_FAILED = export const USER_FB_DOCUMENT_DELETION_FAILED =
@@ -255,7 +231,7 @@ export const TEAM_COLL_INVALID_JSON = 'team_coll/invalid_json';
export const TEAM_NOT_OWNER = 'team_coll/team_not_owner' as const; export const TEAM_NOT_OWNER = 'team_coll/team_not_owner' as const;
/** /**
* Tried to perform an action on a request that doesn't accept their member role level * Tried to perform action on a request that doesn't accept their member role level
* (GqlRequestTeamMemberGuard) * (GqlRequestTeamMemberGuard)
*/ */
export const TEAM_REQ_NOT_REQUIRED_ROLE = 'team_req/not_required_role'; export const TEAM_REQ_NOT_REQUIRED_ROLE = 'team_req/not_required_role';
@@ -286,7 +262,7 @@ export const TEAM_REQ_REORDERING_FAILED = 'team_req/reordering_failed' as const;
export const SENDER_EMAIL_INVALID = 'mailer/sender_email_invalid' as const; export const SENDER_EMAIL_INVALID = 'mailer/sender_email_invalid' as const;
/** /**
* Tried to perform an action on a request when the user is not even a member of the team * Tried to perform action on a request when the user is not even member of the team
* (GqlRequestTeamMemberGuard, GqlCollectionTeamMemberGuard) * (GqlRequestTeamMemberGuard, GqlCollectionTeamMemberGuard)
*/ */
export const TEAM_REQ_NOT_MEMBER = 'team_req/not_member'; export const TEAM_REQ_NOT_MEMBER = 'team_req/not_member';
@@ -331,18 +307,11 @@ export const SHORTCODE_INVALID_JSON = 'shortcode/invalid_json' as const;
export const SHORTCODE_ALREADY_EXISTS = 'shortcode/already_exists' as const; export const SHORTCODE_ALREADY_EXISTS = 'shortcode/already_exists' as const;
/** /**
* Invalid or non-existent TEAM ENVIRONMENT ID * Invalid or non-existent TEAM ENVIRONMMENT ID
* (TeamEnvironmentsService) * (TeamEnvironmentsService)
*/ */
export const TEAM_ENVIRONMENT_NOT_FOUND = 'team_environment/not_found' as const; export const TEAM_ENVIRONMENT_NOT_FOUND = 'team_environment/not_found' as const;
/**
* Invalid TEAM ENVIRONMENT name
* (TeamEnvironmentsService)
*/
export const TEAM_ENVIRONMENT_SHORT_NAME =
'team_environment/short_name' as const;
/** /**
* The user is not a member of the team of the given environment * The user is not a member of the team of the given environment
* (GqlTeamEnvTeamGuard) * (GqlTeamEnvTeamGuard)
@@ -371,7 +340,7 @@ export const USER_SETTINGS_NULL_SETTINGS =
'user_settings/null_settings' as const; 'user_settings/null_settings' as const;
/* /*
* Global environment doesn't exist for the user * Global environment doesnt exists for the user
* (UserEnvironmentsService) * (UserEnvironmentsService)
*/ */
export const USER_ENVIRONMENT_GLOBAL_ENV_DOES_NOT_EXISTS = export const USER_ENVIRONMENT_GLOBAL_ENV_DOES_NOT_EXISTS =

View File

@@ -93,7 +93,9 @@ export async function emitGQLSchemaFile() {
numberScalarMode: 'integer', numberScalarMode: 'integer',
}); });
const schemaString = printSchema(schema); const schemaString = printSchema(schema, {
commentDescriptions: true,
});
logger.log(`Writing schema to GQL_SCHEMA_EMIT_LOCATION (${destination})`); logger.log(`Writing schema to GQL_SCHEMA_EMIT_LOCATION (${destination})`);

View File

@@ -3,7 +3,8 @@ import { Injectable } from '@nestjs/common';
@Injectable() @Injectable()
export class ThrottlerBehindProxyGuard extends ThrottlerGuard { export class ThrottlerBehindProxyGuard extends ThrottlerGuard {
protected async getTracker(req: Record<string, any>): Promise<string> { protected getTracker(req: Record<string, any>): string {
return req.ips.length ? req.ips[0] : req.ip; // individualize IP extraction to meet your own needs return req.ips.length ? req.ips[0] : req.ip; // individualize IP extraction to meet your own needs
// learn more: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-Forwarded-For#directives
} }
} }

View File

@@ -8,7 +8,7 @@ export type MailDescription = {
}; };
export type UserMagicLinkMailDescription = { export type UserMagicLinkMailDescription = {
template: 'user-invitation'; template: 'code-your-own';
variables: { variables: {
inviteeEmail: string; inviteeEmail: string;
magicLink: string; magicLink: string;
@@ -16,7 +16,7 @@ export type UserMagicLinkMailDescription = {
}; };
export type AdminUserInvitationMailDescription = { export type AdminUserInvitationMailDescription = {
template: 'user-invitation'; template: 'code-your-own';
variables: { variables: {
inviteeEmail: string; inviteeEmail: string;
magicLink: string; magicLink: string;

View File

@@ -5,6 +5,7 @@ import {
UserMagicLinkMailDescription, UserMagicLinkMailDescription,
} from './MailDescriptions'; } from './MailDescriptions';
import { throwErr } from 'src/utils'; import { throwErr } from 'src/utils';
import * as TE from 'fp-ts/TaskEither';
import { EMAIL_FAILED } from 'src/errors'; import { EMAIL_FAILED } from 'src/errors';
import { MailerService as NestMailerService } from '@nestjs-modules/mailer'; import { MailerService as NestMailerService } from '@nestjs-modules/mailer';
@@ -27,21 +28,40 @@ export class MailerService {
case 'team-invitation': case 'team-invitation':
return `${mailDesc.variables.invitee} invited you to join ${mailDesc.variables.invite_team_name} in Hoppscotch`; return `${mailDesc.variables.invitee} invited you to join ${mailDesc.variables.invite_team_name} in Hoppscotch`;
case 'user-invitation': case 'code-your-own':
return 'Sign in to Hoppscotch'; return 'Sign in to Hoppscotch';
} }
} }
/** /**
* Sends an email to the given email address given a mail description * Sends an email to the given email address given a mail description
* @param to Receiver's email id * @param to The email address to be sent to (NOTE: this is not validated)
* @param mailDesc Definition of what email to be sent * @param mailDesc Definition of what email to be sent
* @returns Response if email was send successfully or not
*/ */
async sendEmail( sendMail(
to: string, to: string,
mailDesc: MailDescription | UserMagicLinkMailDescription, mailDesc: MailDescription | UserMagicLinkMailDescription,
) { ) {
return TE.tryCatch(
async () => {
await this.nestMailerService.sendMail({
to,
template: mailDesc.template,
subject: this.resolveSubjectForMailDesc(mailDesc),
context: mailDesc.variables,
});
},
() => EMAIL_FAILED,
);
}
/**
*
* @param to Receiver's email id
* @param mailDesc Details of email to be sent for Magic-Link auth
* @returns Response if email was send successfully or not
*/
async sendAuthEmail(to: string, mailDesc: UserMagicLinkMailDescription) {
try { try {
await this.nestMailerService.sendMail({ await this.nestMailerService.sendMail({
to, to,

View File

@@ -14,7 +14,7 @@
--> -->
<style type="text/css" rel="stylesheet" media="all"> <style type="text/css" rel="stylesheet" media="all">
/* Base ------------------------------ */ /* Base ------------------------------ */
@import url("https://fonts.googleapis.com/css?family=Nunito+Sans:400,700&display=swap"); @import url("https://fonts.googleapis.com/css?family=Nunito+Sans:400,700&display=swap");
body { body {
width: 100% !important; width: 100% !important;
@@ -22,19 +22,19 @@
margin: 0; margin: 0;
-webkit-text-size-adjust: none; -webkit-text-size-adjust: none;
} }
a { a {
color: #3869D4; color: #3869D4;
} }
a img { a img {
border: none; border: none;
} }
td { td {
word-break: break-word; word-break: break-word;
} }
.preheader { .preheader {
display: none !important; display: none !important;
visibility: hidden; visibility: hidden;
@@ -47,13 +47,13 @@
overflow: hidden; overflow: hidden;
} }
/* Type ------------------------------ */ /* Type ------------------------------ */
body, body,
td, td,
th { th {
font-family: "Nunito Sans", Helvetica, Arial, sans-serif; font-family: "Nunito Sans", Helvetica, Arial, sans-serif;
} }
h1 { h1 {
margin-top: 0; margin-top: 0;
color: #333333; color: #333333;
@@ -61,7 +61,7 @@
font-weight: bold; font-weight: bold;
text-align: left; text-align: left;
} }
h2 { h2 {
margin-top: 0; margin-top: 0;
color: #333333; color: #333333;
@@ -69,7 +69,7 @@
font-weight: bold; font-weight: bold;
text-align: left; text-align: left;
} }
h3 { h3 {
margin-top: 0; margin-top: 0;
color: #333333; color: #333333;
@@ -77,12 +77,12 @@
font-weight: bold; font-weight: bold;
text-align: left; text-align: left;
} }
td, td,
th { th {
font-size: 16px; font-size: 16px;
} }
p, p,
ul, ul,
ol, ol,
@@ -91,25 +91,25 @@
font-size: 16px; font-size: 16px;
line-height: 1.625; line-height: 1.625;
} }
p.sub { p.sub {
font-size: 13px; font-size: 13px;
} }
/* Utilities ------------------------------ */ /* Utilities ------------------------------ */
.align-right { .align-right {
text-align: right; text-align: right;
} }
.align-left { .align-left {
text-align: left; text-align: left;
} }
.align-center { .align-center {
text-align: center; text-align: center;
} }
/* Buttons ------------------------------ */ /* Buttons ------------------------------ */
.button { .button {
background-color: #3869D4; background-color: #3869D4;
border-top: 10px solid #3869D4; border-top: 10px solid #3869D4;
@@ -124,7 +124,7 @@
-webkit-text-size-adjust: none; -webkit-text-size-adjust: none;
box-sizing: border-box; box-sizing: border-box;
} }
.button--green { .button--green {
background-color: #22BC66; background-color: #22BC66;
border-top: 10px solid #22BC66; border-top: 10px solid #22BC66;
@@ -132,7 +132,7 @@
border-bottom: 10px solid #22BC66; border-bottom: 10px solid #22BC66;
border-left: 18px solid #22BC66; border-left: 18px solid #22BC66;
} }
.button--red { .button--red {
background-color: #FF6136; background-color: #FF6136;
border-top: 10px solid #FF6136; border-top: 10px solid #FF6136;
@@ -140,7 +140,7 @@
border-bottom: 10px solid #FF6136; border-bottom: 10px solid #FF6136;
border-left: 18px solid #FF6136; border-left: 18px solid #FF6136;
} }
@media only screen and (max-width: 500px) { @media only screen and (max-width: 500px) {
.button { .button {
width: 100% !important; width: 100% !important;
@@ -148,21 +148,21 @@
} }
} }
/* Attribute list ------------------------------ */ /* Attribute list ------------------------------ */
.attributes { .attributes {
margin: 0 0 21px; margin: 0 0 21px;
} }
.attributes_content { .attributes_content {
background-color: #F4F4F7; background-color: #F4F4F7;
padding: 16px; padding: 16px;
} }
.attributes_item { .attributes_item {
padding: 0; padding: 0;
} }
/* Related Items ------------------------------ */ /* Related Items ------------------------------ */
.related { .related {
width: 100%; width: 100%;
margin: 0; margin: 0;
@@ -171,31 +171,31 @@
-premailer-cellpadding: 0; -premailer-cellpadding: 0;
-premailer-cellspacing: 0; -premailer-cellspacing: 0;
} }
.related_item { .related_item {
padding: 10px 0; padding: 10px 0;
color: #CBCCCF; color: #CBCCCF;
font-size: 15px; font-size: 15px;
line-height: 18px; line-height: 18px;
} }
.related_item-title { .related_item-title {
display: block; display: block;
margin: .5em 0 0; margin: .5em 0 0;
} }
.related_item-thumb { .related_item-thumb {
display: block; display: block;
padding-bottom: 10px; padding-bottom: 10px;
} }
.related_heading { .related_heading {
border-top: 1px solid #CBCCCF; border-top: 1px solid #CBCCCF;
text-align: center; text-align: center;
padding: 25px 0 10px; padding: 25px 0 10px;
} }
/* Discount Code ------------------------------ */ /* Discount Code ------------------------------ */
.discount { .discount {
width: 100%; width: 100%;
margin: 0; margin: 0;
@@ -206,33 +206,33 @@
background-color: #F4F4F7; background-color: #F4F4F7;
border: 2px dashed #CBCCCF; border: 2px dashed #CBCCCF;
} }
.discount_heading { .discount_heading {
text-align: center; text-align: center;
} }
.discount_body { .discount_body {
text-align: center; text-align: center;
font-size: 15px; font-size: 15px;
} }
/* Social Icons ------------------------------ */ /* Social Icons ------------------------------ */
.social { .social {
width: auto; width: auto;
} }
.social td { .social td {
padding: 0; padding: 0;
width: auto; width: auto;
} }
.social_icon { .social_icon {
height: 20px; height: 20px;
margin: 0 8px 10px 8px; margin: 0 8px 10px 8px;
padding: 0; padding: 0;
} }
/* Data table ------------------------------ */ /* Data table ------------------------------ */
.purchase { .purchase {
width: 100%; width: 100%;
margin: 0; margin: 0;
@@ -241,7 +241,7 @@
-premailer-cellpadding: 0; -premailer-cellpadding: 0;
-premailer-cellspacing: 0; -premailer-cellspacing: 0;
} }
.purchase_content { .purchase_content {
width: 100%; width: 100%;
margin: 0; margin: 0;
@@ -250,50 +250,50 @@
-premailer-cellpadding: 0; -premailer-cellpadding: 0;
-premailer-cellspacing: 0; -premailer-cellspacing: 0;
} }
.purchase_item { .purchase_item {
padding: 10px 0; padding: 10px 0;
color: #51545E; color: #51545E;
font-size: 15px; font-size: 15px;
line-height: 18px; line-height: 18px;
} }
.purchase_heading { .purchase_heading {
padding-bottom: 8px; padding-bottom: 8px;
border-bottom: 1px solid #EAEAEC; border-bottom: 1px solid #EAEAEC;
} }
.purchase_heading p { .purchase_heading p {
margin: 0; margin: 0;
color: #85878E; color: #85878E;
font-size: 12px; font-size: 12px;
} }
.purchase_footer { .purchase_footer {
padding-top: 15px; padding-top: 15px;
border-top: 1px solid #EAEAEC; border-top: 1px solid #EAEAEC;
} }
.purchase_total { .purchase_total {
margin: 0; margin: 0;
text-align: right; text-align: right;
font-weight: bold; font-weight: bold;
color: #333333; color: #333333;
} }
.purchase_total--label { .purchase_total--label {
padding: 0 15px 0 0; padding: 0 15px 0 0;
} }
body { body {
background-color: #F2F4F6; background-color: #F2F4F6;
color: #51545E; color: #51545E;
} }
p { p {
color: #51545E; color: #51545E;
} }
.email-wrapper { .email-wrapper {
width: 100%; width: 100%;
margin: 0; margin: 0;
@@ -303,7 +303,7 @@
-premailer-cellspacing: 0; -premailer-cellspacing: 0;
background-color: #F2F4F6; background-color: #F2F4F6;
} }
.email-content { .email-content {
width: 100%; width: 100%;
margin: 0; margin: 0;
@@ -313,16 +313,16 @@
-premailer-cellspacing: 0; -premailer-cellspacing: 0;
} }
/* Masthead ----------------------- */ /* Masthead ----------------------- */
.email-masthead { .email-masthead {
padding: 25px 0; padding: 25px 0;
text-align: center; text-align: center;
} }
.email-masthead_logo { .email-masthead_logo {
width: 94px; width: 94px;
} }
.email-masthead_name { .email-masthead_name {
font-size: 16px; font-size: 16px;
font-weight: bold; font-weight: bold;
@@ -331,7 +331,7 @@
text-shadow: 0 1px 0 white; text-shadow: 0 1px 0 white;
} }
/* Body ------------------------------ */ /* Body ------------------------------ */
.email-body { .email-body {
width: 100%; width: 100%;
margin: 0; margin: 0;
@@ -340,7 +340,7 @@
-premailer-cellpadding: 0; -premailer-cellpadding: 0;
-premailer-cellspacing: 0; -premailer-cellspacing: 0;
} }
.email-body_inner { .email-body_inner {
width: 570px; width: 570px;
margin: 0 auto; margin: 0 auto;
@@ -350,7 +350,7 @@
-premailer-cellspacing: 0; -premailer-cellspacing: 0;
background-color: #FFFFFF; background-color: #FFFFFF;
} }
.email-footer { .email-footer {
width: 570px; width: 570px;
margin: 0 auto; margin: 0 auto;
@@ -360,11 +360,11 @@
-premailer-cellspacing: 0; -premailer-cellspacing: 0;
text-align: center; text-align: center;
} }
.email-footer p { .email-footer p {
color: #A8AAAF; color: #A8AAAF;
} }
.body-action { .body-action {
width: 100%; width: 100%;
margin: 30px auto; margin: 30px auto;
@@ -374,25 +374,25 @@
-premailer-cellspacing: 0; -premailer-cellspacing: 0;
text-align: center; text-align: center;
} }
.body-sub { .body-sub {
margin-top: 25px; margin-top: 25px;
padding-top: 25px; padding-top: 25px;
border-top: 1px solid #EAEAEC; border-top: 1px solid #EAEAEC;
} }
.content-cell { .content-cell {
padding: 45px; padding: 45px;
} }
/*Media Queries ------------------------------ */ /*Media Queries ------------------------------ */
@media only screen and (max-width: 600px) { @media only screen and (max-width: 600px) {
.email-body_inner, .email-body_inner,
.email-footer { .email-footer {
width: 100% !important; width: 100% !important;
} }
} }
@media (prefers-color-scheme: dark) { @media (prefers-color-scheme: dark) {
body, body,
.email-body, .email-body,

View File

@@ -5,13 +5,11 @@ import * as cookieParser from 'cookie-parser';
import { VersioningType } from '@nestjs/common'; import { VersioningType } from '@nestjs/common';
import * as session from 'express-session'; import * as session from 'express-session';
import { emitGQLSchemaFile } from './gql-schema'; import { emitGQLSchemaFile } from './gql-schema';
import { checkEnvironmentAuthProvider } from './utils';
async function bootstrap() { async function bootstrap() {
console.log(`Running in production: ${process.env.PRODUCTION}`); console.log(`Running in production: ${process.env.PRODUCTION}`);
console.log(`Port: ${process.env.PORT}`); console.log(`Port: ${process.env.PORT}`);
console.log(`Database: ${process.env.DATABASE_URL}`);
checkEnvironmentAuthProvider();
const app = await NestFactory.create(AppModule); const app = await NestFactory.create(AppModule);

View File

@@ -1,9 +1,8 @@
import { GraphQLSchemaHost } from '@nestjs/graphql'; import { GraphQLSchemaHost } from '@nestjs/graphql';
import { import {
ApolloServerPlugin, ApolloServerPlugin,
BaseContext,
GraphQLRequestListener, GraphQLRequestListener,
} from '@apollo/server'; } from 'apollo-server-plugin-base';
import { Plugin } from '@nestjs/apollo'; import { Plugin } from '@nestjs/apollo';
import { GraphQLError } from 'graphql'; import { GraphQLError } from 'graphql';
import { import {
@@ -18,7 +17,7 @@ const COMPLEXITY_LIMIT = 50;
export class GQLComplexityPlugin implements ApolloServerPlugin { export class GQLComplexityPlugin implements ApolloServerPlugin {
constructor(private gqlSchemaHost: GraphQLSchemaHost) {} constructor(private gqlSchemaHost: GraphQLSchemaHost) {}
async requestDidStart(): Promise<GraphQLRequestListener<BaseContext>> { async requestDidStart(): Promise<GraphQLRequestListener> {
const { schema } = this.gqlSchemaHost; const { schema } = this.gqlSchemaHost;
return { return {

View File

@@ -150,7 +150,7 @@ export class ShortcodeService implements UserDataHandler, OnModuleInit {
orderBy: { orderBy: {
createdOn: 'desc', createdOn: 'desc',
}, },
skip: args.cursor ? 1 : 0, skip: 1,
take: args.take, take: args.take,
cursor: args.cursor ? { id: args.cursor } : undefined, cursor: args.cursor ? { id: args.cursor } : undefined,
}); });

View File

@@ -1,5 +1,5 @@
import { Team, TeamCollection as DBTeamCollection } from '@prisma/client'; import { Team, TeamCollection as DBTeamCollection } from '@prisma/client';
import { mockDeep, mockReset } from 'jest-mock-extended'; import { mock, mockDeep, mockReset } from 'jest-mock-extended';
import { import {
TEAM_COLL_DEST_SAME, TEAM_COLL_DEST_SAME,
TEAM_COLL_INVALID_JSON, TEAM_COLL_INVALID_JSON,
@@ -17,6 +17,9 @@ import { PrismaService } from 'src/prisma/prisma.service';
import { PubSubService } from 'src/pubsub/pubsub.service'; import { PubSubService } from 'src/pubsub/pubsub.service';
import { AuthUser } from 'src/types/AuthUser'; import { AuthUser } from 'src/types/AuthUser';
import { TeamCollectionService } from './team-collection.service'; import { TeamCollectionService } from './team-collection.service';
import { TeamCollection } from './team-collection.model';
import { TeamCollectionModule } from './team-collection.module';
import * as E from 'fp-ts/Either';
const mockPrisma = mockDeep<PrismaService>(); const mockPrisma = mockDeep<PrismaService>();
const mockPubSub = mockDeep<PubSubService>(); const mockPubSub = mockDeep<PubSubService>();

View File

@@ -1,5 +1,15 @@
import { CanActivate, ExecutionContext, Injectable } from '@nestjs/common'; import { CanActivate, ExecutionContext, Injectable } from '@nestjs/common';
import { Reflector } from '@nestjs/core'; import { Reflector } from '@nestjs/core';
import * as TE from 'fp-ts/TaskEither';
import * as O from 'fp-ts/Option';
import * as S from 'fp-ts/string';
import { pipe } from 'fp-ts/function';
import {
getAnnotatedRequiredRoles,
getGqlArg,
getUserFromGQLContext,
throwErr,
} from 'src/utils';
import { TeamEnvironmentsService } from './team-environments.service'; import { TeamEnvironmentsService } from './team-environments.service';
import { import {
BUG_AUTH_NO_USER_CTX, BUG_AUTH_NO_USER_CTX,
@@ -9,10 +19,6 @@ import {
TEAM_ENVIRONMENT_NOT_FOUND, TEAM_ENVIRONMENT_NOT_FOUND,
} from 'src/errors'; } from 'src/errors';
import { TeamService } from 'src/team/team.service'; import { TeamService } from 'src/team/team.service';
import { GqlExecutionContext } from '@nestjs/graphql';
import * as E from 'fp-ts/Either';
import { TeamMemberRole } from '@prisma/client';
import { throwErr } from 'src/utils';
/** /**
* A guard which checks whether the caller of a GQL Operation * A guard which checks whether the caller of a GQL Operation
@@ -27,31 +33,50 @@ export class GqlTeamEnvTeamGuard implements CanActivate {
private readonly teamService: TeamService, private readonly teamService: TeamService,
) {} ) {}
async canActivate(context: ExecutionContext): Promise<boolean> { canActivate(context: ExecutionContext): Promise<boolean> {
const requireRoles = this.reflector.get<TeamMemberRole[]>( return pipe(
'requiresTeamRole', TE.Do,
context.getHandler(),
);
if (!requireRoles) throw new Error(BUG_TEAM_ENV_GUARD_NO_REQUIRE_ROLES);
const gqlExecCtx = GqlExecutionContext.create(context); TE.bindW('requiredRoles', () =>
pipe(
getAnnotatedRequiredRoles(this.reflector, context),
TE.fromOption(() => BUG_TEAM_ENV_GUARD_NO_REQUIRE_ROLES),
),
),
const { user } = gqlExecCtx.getContext().req; TE.bindW('user', () =>
if (user == undefined) throw new Error(BUG_AUTH_NO_USER_CTX); pipe(
getUserFromGQLContext(context),
TE.fromOption(() => BUG_AUTH_NO_USER_CTX),
),
),
const { id } = gqlExecCtx.getArgs<{ id: string }>(); TE.bindW('envID', () =>
if (!id) throwErr(BUG_TEAM_ENV_GUARD_NO_ENV_ID); pipe(
getGqlArg('id', context),
O.fromPredicate(S.isString),
TE.fromOption(() => BUG_TEAM_ENV_GUARD_NO_ENV_ID),
),
),
const teamEnvironment = TE.bindW('membership', ({ envID, user }) =>
await this.teamEnvironmentService.getTeamEnvironment(id); pipe(
if (E.isLeft(teamEnvironment)) throwErr(TEAM_ENVIRONMENT_NOT_FOUND); this.teamEnvironmentService.getTeamEnvironment(envID),
TE.fromTaskOption(() => TEAM_ENVIRONMENT_NOT_FOUND),
TE.chainW((env) =>
pipe(
this.teamService.getTeamMemberTE(env.teamID, user.uid),
TE.mapLeft(() => TEAM_ENVIRONMENT_NOT_TEAM_MEMBER),
),
),
),
),
const member = await this.teamService.getTeamMember( TE.map(({ membership, requiredRoles }) =>
teamEnvironment.right.teamID, requiredRoles.includes(membership.role),
user.uid, ),
);
if (!member) throwErr(TEAM_ENVIRONMENT_NOT_TEAM_MEMBER);
return requireRoles.includes(member.role); TE.getOrElse(throwErr),
)();
} }
} }

View File

@@ -1,41 +0,0 @@
import { ArgsType, Field, ID } from '@nestjs/graphql';
@ArgsType()
export class CreateTeamEnvironmentArgs {
@Field({
name: 'name',
description: 'Name of the Team Environment',
})
name: string;
@Field(() => ID, {
name: 'teamID',
description: 'ID of the Team',
})
teamID: string;
@Field({
name: 'variables',
description: 'JSON string of the variables object',
})
variables: string;
}
@ArgsType()
export class UpdateTeamEnvironmentArgs {
@Field(() => ID, {
name: 'id',
description: 'ID of the Team Environment',
})
id: string;
@Field({
name: 'name',
description: 'Name of the Team Environment',
})
name: string;
@Field({
name: 'variables',
description: 'JSON string of the variables object',
})
variables: string;
}

View File

@@ -13,11 +13,6 @@ import { throwErr } from 'src/utils';
import { GqlTeamEnvTeamGuard } from './gql-team-env-team.guard'; import { GqlTeamEnvTeamGuard } from './gql-team-env-team.guard';
import { TeamEnvironment } from './team-environments.model'; import { TeamEnvironment } from './team-environments.model';
import { TeamEnvironmentsService } from './team-environments.service'; import { TeamEnvironmentsService } from './team-environments.service';
import * as E from 'fp-ts/Either';
import {
CreateTeamEnvironmentArgs,
UpdateTeamEnvironmentArgs,
} from './input-type.args';
@UseGuards(GqlThrottlerGuard) @UseGuards(GqlThrottlerGuard)
@Resolver(() => 'TeamEnvironment') @Resolver(() => 'TeamEnvironment')
@@ -34,18 +29,29 @@ export class TeamEnvironmentsResolver {
}) })
@UseGuards(GqlAuthGuard, GqlTeamMemberGuard) @UseGuards(GqlAuthGuard, GqlTeamMemberGuard)
@RequiresTeamRole(TeamMemberRole.OWNER, TeamMemberRole.EDITOR) @RequiresTeamRole(TeamMemberRole.OWNER, TeamMemberRole.EDITOR)
async createTeamEnvironment( createTeamEnvironment(
@Args() args: CreateTeamEnvironmentArgs, @Args({
name: 'name',
description: 'Name of the Team Environment',
})
name: string,
@Args({
name: 'teamID',
description: 'ID of the Team',
type: () => ID,
})
teamID: string,
@Args({
name: 'variables',
description: 'JSON string of the variables object',
})
variables: string,
): Promise<TeamEnvironment> { ): Promise<TeamEnvironment> {
const teamEnvironment = return this.teamEnvironmentsService.createTeamEnvironment(
await this.teamEnvironmentsService.createTeamEnvironment( name,
args.name, teamID,
args.teamID, variables,
args.variables, )();
);
if (E.isLeft(teamEnvironment)) throwErr(teamEnvironment.left);
return teamEnvironment.right;
} }
@Mutation(() => Boolean, { @Mutation(() => Boolean, {
@@ -53,7 +59,7 @@ export class TeamEnvironmentsResolver {
}) })
@UseGuards(GqlAuthGuard, GqlTeamEnvTeamGuard) @UseGuards(GqlAuthGuard, GqlTeamEnvTeamGuard)
@RequiresTeamRole(TeamMemberRole.OWNER, TeamMemberRole.EDITOR) @RequiresTeamRole(TeamMemberRole.OWNER, TeamMemberRole.EDITOR)
async deleteTeamEnvironment( deleteTeamEnvironment(
@Args({ @Args({
name: 'id', name: 'id',
description: 'ID of the Team Environment', description: 'ID of the Team Environment',
@@ -61,12 +67,10 @@ export class TeamEnvironmentsResolver {
}) })
id: string, id: string,
): Promise<boolean> { ): Promise<boolean> {
const isDeleted = await this.teamEnvironmentsService.deleteTeamEnvironment( return pipe(
id, this.teamEnvironmentsService.deleteTeamEnvironment(id),
); TE.getOrElse(throwErr),
)();
if (E.isLeft(isDeleted)) throwErr(isDeleted.left);
return isDeleted.right;
} }
@Mutation(() => TeamEnvironment, { @Mutation(() => TeamEnvironment, {
@@ -75,19 +79,28 @@ export class TeamEnvironmentsResolver {
}) })
@UseGuards(GqlAuthGuard, GqlTeamEnvTeamGuard) @UseGuards(GqlAuthGuard, GqlTeamEnvTeamGuard)
@RequiresTeamRole(TeamMemberRole.OWNER, TeamMemberRole.EDITOR) @RequiresTeamRole(TeamMemberRole.OWNER, TeamMemberRole.EDITOR)
async updateTeamEnvironment( updateTeamEnvironment(
@Args() @Args({
args: UpdateTeamEnvironmentArgs, name: 'id',
description: 'ID of the Team Environment',
type: () => ID,
})
id: string,
@Args({
name: 'name',
description: 'Name of the Team Environment',
})
name: string,
@Args({
name: 'variables',
description: 'JSON string of the variables object',
})
variables: string,
): Promise<TeamEnvironment> { ): Promise<TeamEnvironment> {
const updatedTeamEnvironment = return pipe(
await this.teamEnvironmentsService.updateTeamEnvironment( this.teamEnvironmentsService.updateTeamEnvironment(id, name, variables),
args.id, TE.getOrElse(throwErr),
args.name, )();
args.variables,
);
if (E.isLeft(updatedTeamEnvironment)) throwErr(updatedTeamEnvironment.left);
return updatedTeamEnvironment.right;
} }
@Mutation(() => TeamEnvironment, { @Mutation(() => TeamEnvironment, {
@@ -95,7 +108,7 @@ export class TeamEnvironmentsResolver {
}) })
@UseGuards(GqlAuthGuard, GqlTeamEnvTeamGuard) @UseGuards(GqlAuthGuard, GqlTeamEnvTeamGuard)
@RequiresTeamRole(TeamMemberRole.OWNER, TeamMemberRole.EDITOR) @RequiresTeamRole(TeamMemberRole.OWNER, TeamMemberRole.EDITOR)
async deleteAllVariablesFromTeamEnvironment( deleteAllVariablesFromTeamEnvironment(
@Args({ @Args({
name: 'id', name: 'id',
description: 'ID of the Team Environment', description: 'ID of the Team Environment',
@@ -103,13 +116,10 @@ export class TeamEnvironmentsResolver {
}) })
id: string, id: string,
): Promise<TeamEnvironment> { ): Promise<TeamEnvironment> {
const teamEnvironment = return pipe(
await this.teamEnvironmentsService.deleteAllVariablesFromTeamEnvironment( this.teamEnvironmentsService.deleteAllVariablesFromTeamEnvironment(id),
id, TE.getOrElse(throwErr),
); )();
if (E.isLeft(teamEnvironment)) throwErr(teamEnvironment.left);
return teamEnvironment.right;
} }
@Mutation(() => TeamEnvironment, { @Mutation(() => TeamEnvironment, {
@@ -117,7 +127,7 @@ export class TeamEnvironmentsResolver {
}) })
@UseGuards(GqlAuthGuard, GqlTeamEnvTeamGuard) @UseGuards(GqlAuthGuard, GqlTeamEnvTeamGuard)
@RequiresTeamRole(TeamMemberRole.OWNER, TeamMemberRole.EDITOR) @RequiresTeamRole(TeamMemberRole.OWNER, TeamMemberRole.EDITOR)
async createDuplicateEnvironment( createDuplicateEnvironment(
@Args({ @Args({
name: 'id', name: 'id',
description: 'ID of the Team Environment', description: 'ID of the Team Environment',
@@ -125,12 +135,10 @@ export class TeamEnvironmentsResolver {
}) })
id: string, id: string,
): Promise<TeamEnvironment> { ): Promise<TeamEnvironment> {
const res = await this.teamEnvironmentsService.createDuplicateEnvironment( return pipe(
id, this.teamEnvironmentsService.createDuplicateEnvironment(id),
); TE.getOrElse(throwErr),
)();
if (E.isLeft(res)) throwErr(res.left);
return res.right;
} }
/* Subscriptions */ /* Subscriptions */

View File

@@ -2,11 +2,7 @@ import { mockDeep, mockReset } from 'jest-mock-extended';
import { PrismaService } from 'src/prisma/prisma.service'; import { PrismaService } from 'src/prisma/prisma.service';
import { TeamEnvironment } from './team-environments.model'; import { TeamEnvironment } from './team-environments.model';
import { TeamEnvironmentsService } from './team-environments.service'; import { TeamEnvironmentsService } from './team-environments.service';
import { import { TEAM_ENVIRONMENT_NOT_FOUND } from 'src/errors';
JSON_INVALID,
TEAM_ENVIRONMENT_NOT_FOUND,
TEAM_ENVIRONMENT_SHORT_NAME,
} from 'src/errors';
const mockPrisma = mockDeep<PrismaService>(); const mockPrisma = mockDeep<PrismaService>();
@@ -35,81 +31,125 @@ beforeEach(() => {
describe('TeamEnvironmentsService', () => { describe('TeamEnvironmentsService', () => {
describe('getTeamEnvironment', () => { describe('getTeamEnvironment', () => {
test('should successfully return a TeamEnvironment with valid ID', async () => { test('queries the db with the id', async () => {
mockPrisma.teamEnvironment.findFirstOrThrow.mockResolvedValueOnce( mockPrisma.teamEnvironment.findFirst.mockResolvedValue(teamEnvironment);
teamEnvironment,
);
const result = await teamEnvironmentsService.getTeamEnvironment( await teamEnvironmentsService.getTeamEnvironment('123')();
teamEnvironment.id,
expect(mockPrisma.teamEnvironment.findFirst).toHaveBeenCalledWith(
expect.objectContaining({
where: {
id: '123',
},
}),
); );
expect(result).toEqualRight(teamEnvironment);
}); });
test('should throw TEAM_ENVIRONMENT_NOT_FOUND with invalid ID', async () => { test('requests prisma to reject the query promise if not found', async () => {
mockPrisma.teamEnvironment.findFirstOrThrow.mockRejectedValueOnce( mockPrisma.teamEnvironment.findFirst.mockResolvedValue(teamEnvironment);
'RejectOnNotFound',
);
const result = await teamEnvironmentsService.getTeamEnvironment( await teamEnvironmentsService.getTeamEnvironment('123')();
teamEnvironment.id,
expect(mockPrisma.teamEnvironment.findFirst).toHaveBeenCalledWith(
expect.objectContaining({
rejectOnNotFound: true,
}),
); );
expect(result).toEqualLeft(TEAM_ENVIRONMENT_NOT_FOUND); });
test('should return a Some of the correct environment if exists', async () => {
mockPrisma.teamEnvironment.findFirst.mockResolvedValue(teamEnvironment);
const result = await teamEnvironmentsService.getTeamEnvironment('123')();
expect(result).toEqualSome(teamEnvironment);
});
test('should return a None if the environment does not exist', async () => {
mockPrisma.teamEnvironment.findFirst.mockRejectedValue('NotFoundError');
const result = await teamEnvironmentsService.getTeamEnvironment('123')();
expect(result).toBeNone();
}); });
}); });
describe('createTeamEnvironment', () => { describe('createTeamEnvironment', () => {
test('should successfully create and return a new team environment given valid inputs', async () => { test('should create and return a new team environment given a valid name,variable and team ID', async () => {
mockPrisma.teamEnvironment.create.mockResolvedValue(teamEnvironment); mockPrisma.teamEnvironment.create.mockResolvedValue(teamEnvironment);
const result = await teamEnvironmentsService.createTeamEnvironment( const result = await teamEnvironmentsService.createTeamEnvironment(
teamEnvironment.name, teamEnvironment.name,
teamEnvironment.teamID, teamEnvironment.teamID,
JSON.stringify(teamEnvironment.variables), JSON.stringify(teamEnvironment.variables),
); )();
expect(result).toEqualRight({ expect(result).toEqual(<TeamEnvironment>{
...teamEnvironment, id: teamEnvironment.id,
name: teamEnvironment.name,
teamID: teamEnvironment.teamID,
variables: JSON.stringify(teamEnvironment.variables), variables: JSON.stringify(teamEnvironment.variables),
}); });
}); });
test('should throw TEAM_ENVIRONMENT_SHORT_NAME if input TeamEnvironment name is invalid', async () => { test('should reject if given team ID is invalid', async () => {
const result = await teamEnvironmentsService.createTeamEnvironment( mockPrisma.teamEnvironment.create.mockRejectedValue(null as any);
'12',
teamEnvironment.teamID,
JSON.stringify(teamEnvironment.variables),
);
expect(result).toEqualLeft(TEAM_ENVIRONMENT_SHORT_NAME); await expect(
teamEnvironmentsService.createTeamEnvironment(
teamEnvironment.name,
'invalidteamid',
JSON.stringify(teamEnvironment.variables),
),
).rejects.toBeDefined();
});
test('should reject if provided team environment name is not a string', async () => {
mockPrisma.teamEnvironment.create.mockRejectedValue(null as any);
await expect(
teamEnvironmentsService.createTeamEnvironment(
null as any,
teamEnvironment.teamID,
JSON.stringify(teamEnvironment.variables),
),
).rejects.toBeDefined();
});
test('should reject if provided variable is not a string', async () => {
mockPrisma.teamEnvironment.create.mockRejectedValue(null as any);
await expect(
teamEnvironmentsService.createTeamEnvironment(
teamEnvironment.name,
teamEnvironment.teamID,
null as any,
),
).rejects.toBeDefined();
}); });
test('should send pubsub message to "team_environment/<teamID>/created" if team environment is created successfully', async () => { test('should send pubsub message to "team_environment/<teamID>/created" if team environment is created successfully', async () => {
mockPrisma.teamEnvironment.create.mockResolvedValue(teamEnvironment); mockPrisma.teamEnvironment.create.mockResolvedValueOnce(teamEnvironment);
const result = await teamEnvironmentsService.createTeamEnvironment( const result = await teamEnvironmentsService.createTeamEnvironment(
teamEnvironment.name, teamEnvironment.name,
teamEnvironment.teamID, teamEnvironment.teamID,
JSON.stringify(teamEnvironment.variables), JSON.stringify(teamEnvironment.variables),
); )();
expect(mockPubSub.publish).toHaveBeenCalledWith( expect(mockPubSub.publish).toHaveBeenCalledWith(
`team_environment/${teamEnvironment.teamID}/created`, `team_environment/${teamEnvironment.teamID}/created`,
{ result,
...teamEnvironment,
variables: JSON.stringify(teamEnvironment.variables),
},
); );
}); });
}); });
describe('deleteTeamEnvironment', () => { describe('deleteTeamEnvironment', () => {
test('should successfully delete a TeamEnvironment with a valid ID', async () => { test('should resolve to true given a valid team environment ID', async () => {
mockPrisma.teamEnvironment.delete.mockResolvedValueOnce(teamEnvironment); mockPrisma.teamEnvironment.delete.mockResolvedValueOnce(teamEnvironment);
const result = await teamEnvironmentsService.deleteTeamEnvironment( const result = await teamEnvironmentsService.deleteTeamEnvironment(
teamEnvironment.id, teamEnvironment.id,
); )();
expect(result).toEqualRight(true); expect(result).toEqualRight(true);
}); });
@@ -119,7 +159,7 @@ describe('TeamEnvironmentsService', () => {
const result = await teamEnvironmentsService.deleteTeamEnvironment( const result = await teamEnvironmentsService.deleteTeamEnvironment(
'invalidid', 'invalidid',
); )();
expect(result).toEqualLeft(TEAM_ENVIRONMENT_NOT_FOUND); expect(result).toEqualLeft(TEAM_ENVIRONMENT_NOT_FOUND);
}); });
@@ -129,7 +169,7 @@ describe('TeamEnvironmentsService', () => {
const result = await teamEnvironmentsService.deleteTeamEnvironment( const result = await teamEnvironmentsService.deleteTeamEnvironment(
teamEnvironment.id, teamEnvironment.id,
); )();
expect(mockPubSub.publish).toHaveBeenCalledWith( expect(mockPubSub.publish).toHaveBeenCalledWith(
`team_environment/${teamEnvironment.teamID}/deleted`, `team_environment/${teamEnvironment.teamID}/deleted`,
@@ -142,7 +182,7 @@ describe('TeamEnvironmentsService', () => {
}); });
describe('updateVariablesInTeamEnvironment', () => { describe('updateVariablesInTeamEnvironment', () => {
test('should successfully add new variable to a team environment', async () => { test('should add new variable to a team environment', async () => {
mockPrisma.teamEnvironment.update.mockResolvedValueOnce({ mockPrisma.teamEnvironment.update.mockResolvedValueOnce({
...teamEnvironment, ...teamEnvironment,
variables: [{ key: 'value' }], variables: [{ key: 'value' }],
@@ -152,7 +192,7 @@ describe('TeamEnvironmentsService', () => {
teamEnvironment.id, teamEnvironment.id,
teamEnvironment.name, teamEnvironment.name,
JSON.stringify([{ key: 'value' }]), JSON.stringify([{ key: 'value' }]),
); )();
expect(result).toEqualRight(<TeamEnvironment>{ expect(result).toEqualRight(<TeamEnvironment>{
...teamEnvironment, ...teamEnvironment,
@@ -160,7 +200,7 @@ describe('TeamEnvironmentsService', () => {
}); });
}); });
test('should successfully add new variable to already existing list of variables in a team environment', async () => { test('should add new variable to already existing list of variables in a team environment', async () => {
mockPrisma.teamEnvironment.update.mockResolvedValueOnce({ mockPrisma.teamEnvironment.update.mockResolvedValueOnce({
...teamEnvironment, ...teamEnvironment,
variables: [{ key: 'value' }, { key_2: 'value_2' }], variables: [{ key: 'value' }, { key_2: 'value_2' }],
@@ -170,7 +210,7 @@ describe('TeamEnvironmentsService', () => {
teamEnvironment.id, teamEnvironment.id,
teamEnvironment.name, teamEnvironment.name,
JSON.stringify([{ key: 'value' }, { key_2: 'value_2' }]), JSON.stringify([{ key: 'value' }, { key_2: 'value_2' }]),
); )();
expect(result).toEqualRight(<TeamEnvironment>{ expect(result).toEqualRight(<TeamEnvironment>{
...teamEnvironment, ...teamEnvironment,
@@ -178,7 +218,7 @@ describe('TeamEnvironmentsService', () => {
}); });
}); });
test('should successfully edit existing variables in a team environment', async () => { test('should edit existing variables in a team environment', async () => {
mockPrisma.teamEnvironment.update.mockResolvedValueOnce({ mockPrisma.teamEnvironment.update.mockResolvedValueOnce({
...teamEnvironment, ...teamEnvironment,
variables: [{ key: '1234' }], variables: [{ key: '1234' }],
@@ -188,7 +228,7 @@ describe('TeamEnvironmentsService', () => {
teamEnvironment.id, teamEnvironment.id,
teamEnvironment.name, teamEnvironment.name,
JSON.stringify([{ key: '1234' }]), JSON.stringify([{ key: '1234' }]),
); )();
expect(result).toEqualRight(<TeamEnvironment>{ expect(result).toEqualRight(<TeamEnvironment>{
...teamEnvironment, ...teamEnvironment,
@@ -196,7 +236,22 @@ describe('TeamEnvironmentsService', () => {
}); });
}); });
test('should successfully edit name of an existing team environment', async () => { test('should delete existing variable in a team environment', async () => {
mockPrisma.teamEnvironment.update.mockResolvedValueOnce(teamEnvironment);
const result = await teamEnvironmentsService.updateTeamEnvironment(
teamEnvironment.id,
teamEnvironment.name,
JSON.stringify([{}]),
)();
expect(result).toEqualRight(<TeamEnvironment>{
...teamEnvironment,
variables: JSON.stringify([{}]),
});
});
test('should edit name of an existing team environment', async () => {
mockPrisma.teamEnvironment.update.mockResolvedValueOnce({ mockPrisma.teamEnvironment.update.mockResolvedValueOnce({
...teamEnvironment, ...teamEnvironment,
variables: [{ key: '123' }], variables: [{ key: '123' }],
@@ -206,7 +261,7 @@ describe('TeamEnvironmentsService', () => {
teamEnvironment.id, teamEnvironment.id,
teamEnvironment.name, teamEnvironment.name,
JSON.stringify([{ key: '123' }]), JSON.stringify([{ key: '123' }]),
); )();
expect(result).toEqualRight(<TeamEnvironment>{ expect(result).toEqualRight(<TeamEnvironment>{
...teamEnvironment, ...teamEnvironment,
@@ -214,24 +269,14 @@ describe('TeamEnvironmentsService', () => {
}); });
}); });
test('should throw TEAM_ENVIRONMENT_SHORT_NAME if input TeamEnvironment name is invalid', async () => { test('should reject to TEAM_ENVIRONMMENT_NOT_FOUND if provided id is invalid', async () => {
const result = await teamEnvironmentsService.updateTeamEnvironment(
teamEnvironment.id,
'12',
JSON.stringify([{ key: 'value' }]),
);
expect(result).toEqualLeft(TEAM_ENVIRONMENT_SHORT_NAME);
});
test('should throw TEAM_ENVIRONMMENT_NOT_FOUND if provided id is invalid', async () => {
mockPrisma.teamEnvironment.update.mockRejectedValue('RecordNotFound'); mockPrisma.teamEnvironment.update.mockRejectedValue('RecordNotFound');
const result = await teamEnvironmentsService.updateTeamEnvironment( const result = await teamEnvironmentsService.updateTeamEnvironment(
'invalidid', 'invalidid',
teamEnvironment.name, teamEnvironment.name,
JSON.stringify(teamEnvironment.variables), JSON.stringify(teamEnvironment.variables),
); )();
expect(result).toEqualLeft(TEAM_ENVIRONMENT_NOT_FOUND); expect(result).toEqualLeft(TEAM_ENVIRONMENT_NOT_FOUND);
}); });
@@ -243,7 +288,7 @@ describe('TeamEnvironmentsService', () => {
teamEnvironment.id, teamEnvironment.id,
teamEnvironment.name, teamEnvironment.name,
JSON.stringify([{ key: 'value' }]), JSON.stringify([{ key: 'value' }]),
); )();
expect(mockPubSub.publish).toHaveBeenCalledWith( expect(mockPubSub.publish).toHaveBeenCalledWith(
`team_environment/${teamEnvironment.teamID}/updated`, `team_environment/${teamEnvironment.teamID}/updated`,
@@ -256,13 +301,13 @@ describe('TeamEnvironmentsService', () => {
}); });
describe('deleteAllVariablesFromTeamEnvironment', () => { describe('deleteAllVariablesFromTeamEnvironment', () => {
test('should successfully delete all variables in a team environment', async () => { test('should delete all variables in a team environment', async () => {
mockPrisma.teamEnvironment.update.mockResolvedValueOnce(teamEnvironment); mockPrisma.teamEnvironment.update.mockResolvedValueOnce(teamEnvironment);
const result = const result =
await teamEnvironmentsService.deleteAllVariablesFromTeamEnvironment( await teamEnvironmentsService.deleteAllVariablesFromTeamEnvironment(
teamEnvironment.id, teamEnvironment.id,
); )();
expect(result).toEqualRight(<TeamEnvironment>{ expect(result).toEqualRight(<TeamEnvironment>{
...teamEnvironment, ...teamEnvironment,
@@ -270,13 +315,13 @@ describe('TeamEnvironmentsService', () => {
}); });
}); });
test('should throw TEAM_ENVIRONMMENT_NOT_FOUND if provided id is invalid', async () => { test('should reject to TEAM_ENVIRONMMENT_NOT_FOUND if provided id is invalid', async () => {
mockPrisma.teamEnvironment.update.mockRejectedValue('RecordNotFound'); mockPrisma.teamEnvironment.update.mockRejectedValue('RecordNotFound');
const result = const result =
await teamEnvironmentsService.deleteAllVariablesFromTeamEnvironment( await teamEnvironmentsService.deleteAllVariablesFromTeamEnvironment(
'invalidid', 'invalidid',
); )();
expect(result).toEqualLeft(TEAM_ENVIRONMENT_NOT_FOUND); expect(result).toEqualLeft(TEAM_ENVIRONMENT_NOT_FOUND);
}); });
@@ -287,7 +332,7 @@ describe('TeamEnvironmentsService', () => {
const result = const result =
await teamEnvironmentsService.deleteAllVariablesFromTeamEnvironment( await teamEnvironmentsService.deleteAllVariablesFromTeamEnvironment(
teamEnvironment.id, teamEnvironment.id,
); )();
expect(mockPubSub.publish).toHaveBeenCalledWith( expect(mockPubSub.publish).toHaveBeenCalledWith(
`team_environment/${teamEnvironment.teamID}/updated`, `team_environment/${teamEnvironment.teamID}/updated`,
@@ -300,58 +345,56 @@ describe('TeamEnvironmentsService', () => {
}); });
describe('createDuplicateEnvironment', () => { describe('createDuplicateEnvironment', () => {
test('should successfully duplicate an existing team environment', async () => { test('should duplicate an existing team environment', async () => {
mockPrisma.teamEnvironment.findFirstOrThrow.mockResolvedValueOnce( mockPrisma.teamEnvironment.findFirst.mockResolvedValueOnce(
teamEnvironment, teamEnvironment,
); );
mockPrisma.teamEnvironment.create.mockResolvedValueOnce({ mockPrisma.teamEnvironment.create.mockResolvedValueOnce({
id: 'newid',
...teamEnvironment, ...teamEnvironment,
id: 'newid',
}); });
const result = await teamEnvironmentsService.createDuplicateEnvironment( const result = await teamEnvironmentsService.createDuplicateEnvironment(
teamEnvironment.id, teamEnvironment.id,
); )();
expect(result).toEqualRight(<TeamEnvironment>{ expect(result).toEqualRight(<TeamEnvironment>{
id: 'newid',
...teamEnvironment, ...teamEnvironment,
id: 'newid',
variables: JSON.stringify(teamEnvironment.variables), variables: JSON.stringify(teamEnvironment.variables),
}); });
}); });
test('should throw TEAM_ENVIRONMMENT_NOT_FOUND if provided id is invalid', async () => { test('should reject to TEAM_ENVIRONMMENT_NOT_FOUND if provided id is invalid', async () => {
mockPrisma.teamEnvironment.findFirstOrThrow.mockRejectedValue( mockPrisma.teamEnvironment.findFirst.mockRejectedValue('NotFoundError');
'NotFoundError',
);
const result = await teamEnvironmentsService.createDuplicateEnvironment( const result = await teamEnvironmentsService.createDuplicateEnvironment(
teamEnvironment.id, teamEnvironment.id,
); )();
expect(result).toEqualLeft(TEAM_ENVIRONMENT_NOT_FOUND); expect(result).toEqualLeft(TEAM_ENVIRONMENT_NOT_FOUND);
}); });
test('should send pubsub message to "team_environment/<teamID>/created" if team environment is updated successfully', async () => { test('should send pubsub message to "team_environment/<teamID>/created" if team environment is updated successfully', async () => {
mockPrisma.teamEnvironment.findFirstOrThrow.mockResolvedValueOnce( mockPrisma.teamEnvironment.findFirst.mockResolvedValueOnce(
teamEnvironment, teamEnvironment,
); );
mockPrisma.teamEnvironment.create.mockResolvedValueOnce({ mockPrisma.teamEnvironment.create.mockResolvedValueOnce({
id: 'newid',
...teamEnvironment, ...teamEnvironment,
id: 'newid',
}); });
const result = await teamEnvironmentsService.createDuplicateEnvironment( const result = await teamEnvironmentsService.createDuplicateEnvironment(
teamEnvironment.id, teamEnvironment.id,
); )();
expect(mockPubSub.publish).toHaveBeenCalledWith( expect(mockPubSub.publish).toHaveBeenCalledWith(
`team_environment/${teamEnvironment.teamID}/created`, `team_environment/${teamEnvironment.teamID}/created`,
{ {
id: 'newid',
...teamEnvironment, ...teamEnvironment,
id: 'newid',
variables: JSON.stringify([{}]), variables: JSON.stringify([{}]),
}, },
); );

View File

@@ -1,14 +1,15 @@
import { Injectable } from '@nestjs/common'; import { Injectable } from '@nestjs/common';
import { TeamEnvironment as DBTeamEnvironment, Prisma } from '@prisma/client'; import { pipe } from 'fp-ts/function';
import * as T from 'fp-ts/Task';
import * as TO from 'fp-ts/TaskOption';
import * as TE from 'fp-ts/TaskEither';
import * as A from 'fp-ts/Array';
import { Prisma } from '@prisma/client';
import { PrismaService } from 'src/prisma/prisma.service'; import { PrismaService } from 'src/prisma/prisma.service';
import { PubSubService } from 'src/pubsub/pubsub.service'; import { PubSubService } from 'src/pubsub/pubsub.service';
import { TeamEnvironment } from './team-environments.model'; import { TeamEnvironment } from './team-environments.model';
import { import { TEAM_ENVIRONMENT_NOT_FOUND } from 'src/errors';
TEAM_ENVIRONMENT_NOT_FOUND,
TEAM_ENVIRONMENT_SHORT_NAME,
} from 'src/errors';
import * as E from 'fp-ts/Either';
import { isValidLength } from 'src/utils';
@Injectable() @Injectable()
export class TeamEnvironmentsService { export class TeamEnvironmentsService {
constructor( constructor(
@@ -16,217 +17,219 @@ export class TeamEnvironmentsService {
private readonly pubsub: PubSubService, private readonly pubsub: PubSubService,
) {} ) {}
TITLE_LENGTH = 3; getTeamEnvironment(id: string) {
return TO.tryCatch(() =>
/** this.prisma.teamEnvironment.findFirst({
* TeamEnvironments are saved in the DB in the following way where: { id },
* [{ key: value }, { key: value },....] rejectOnNotFound: true,
* }),
*/
/**
* Typecast a database TeamEnvironment to a TeamEnvironment model
* @param teamEnvironment database TeamEnvironment
* @returns TeamEnvironment model
*/
private cast(teamEnvironment: DBTeamEnvironment): TeamEnvironment {
return {
id: teamEnvironment.id,
name: teamEnvironment.name,
teamID: teamEnvironment.teamID,
variables: JSON.stringify(teamEnvironment.variables),
};
}
/**
* Get details of a TeamEnvironment.
*
* @param id TeamEnvironment ID
* @returns Either of a TeamEnvironment or error message
*/
async getTeamEnvironment(id: string) {
try {
const teamEnvironment =
await this.prisma.teamEnvironment.findFirstOrThrow({
where: { id },
});
return E.right(teamEnvironment);
} catch (error) {
return E.left(TEAM_ENVIRONMENT_NOT_FOUND);
}
}
/**
* Create a new TeamEnvironment.
*
* @param name name of new TeamEnvironment
* @param teamID teamID of new TeamEnvironment
* @param variables JSONified string of contents of new TeamEnvironment
* @returns Either of a TeamEnvironment or error message
*/
async createTeamEnvironment(name: string, teamID: string, variables: string) {
const isTitleValid = isValidLength(name, this.TITLE_LENGTH);
if (!isTitleValid) return E.left(TEAM_ENVIRONMENT_SHORT_NAME);
const result = await this.prisma.teamEnvironment.create({
data: {
name: name,
teamID: teamID,
variables: JSON.parse(variables),
},
});
const createdTeamEnvironment = this.cast(result);
this.pubsub.publish(
`team_environment/${createdTeamEnvironment.teamID}/created`,
createdTeamEnvironment,
); );
return E.right(createdTeamEnvironment);
} }
/** createTeamEnvironment(name: string, teamID: string, variables: string) {
* Delete a TeamEnvironment. return pipe(
* () =>
* @param id TeamEnvironment ID this.prisma.teamEnvironment.create({
* @returns Either of boolean or error message data: {
*/ name: name,
async deleteTeamEnvironment(id: string) { teamID: teamID,
try { variables: JSON.parse(variables),
const result = await this.prisma.teamEnvironment.delete({ },
where: { }),
id: id, T.chainFirst(
}, (environment) => () =>
}); this.pubsub.publish(
`team_environment/${environment.teamID}/created`,
const deletedTeamEnvironment = this.cast(result); <TeamEnvironment>{
id: environment.id,
this.pubsub.publish( name: environment.name,
`team_environment/${deletedTeamEnvironment.teamID}/deleted`, teamID: environment.teamID,
deletedTeamEnvironment, variables: JSON.stringify(environment.variables),
); },
),
return E.right(true); ),
} catch (error) { T.map((data) => {
return E.left(TEAM_ENVIRONMENT_NOT_FOUND); return <TeamEnvironment>{
} id: data.id,
name: data.name,
teamID: data.teamID,
variables: JSON.stringify(data.variables),
};
}),
);
} }
/** deleteTeamEnvironment(id: string) {
* Update a TeamEnvironment. return pipe(
* TE.tryCatch(
* @param id TeamEnvironment ID () =>
* @param name TeamEnvironment name this.prisma.teamEnvironment.delete({
* @param variables JSONified string of contents of new TeamEnvironment where: {
* @returns Either of a TeamEnvironment or error message id: id,
*/ },
async updateTeamEnvironment(id: string, name: string, variables: string) { }),
try { () => TEAM_ENVIRONMENT_NOT_FOUND,
const isTitleValid = isValidLength(name, this.TITLE_LENGTH); ),
if (!isTitleValid) return E.left(TEAM_ENVIRONMENT_SHORT_NAME); TE.chainFirst((environment) =>
TE.fromTask(() =>
const result = await this.prisma.teamEnvironment.update({ this.pubsub.publish(
where: { id: id }, `team_environment/${environment.teamID}/deleted`,
data: { <TeamEnvironment>{
name, id: environment.id,
variables: JSON.parse(variables), name: environment.name,
}, teamID: environment.teamID,
}); variables: JSON.stringify(environment.variables),
},
const updatedTeamEnvironment = this.cast(result); ),
),
this.pubsub.publish( ),
`team_environment/${updatedTeamEnvironment.teamID}/updated`, TE.map((data) => true),
updatedTeamEnvironment, );
);
return E.right(updatedTeamEnvironment);
} catch (error) {
return E.left(TEAM_ENVIRONMENT_NOT_FOUND);
}
} }
/** updateTeamEnvironment(id: string, name: string, variables: string) {
* Clear contents of a TeamEnvironment. return pipe(
* TE.tryCatch(
* @param id TeamEnvironment ID () =>
* @returns Either of a TeamEnvironment or error message this.prisma.teamEnvironment.update({
*/ where: { id: id },
async deleteAllVariablesFromTeamEnvironment(id: string) { data: {
try { name,
const result = await this.prisma.teamEnvironment.update({ variables: JSON.parse(variables),
where: { id: id }, },
data: { }),
variables: [], () => TEAM_ENVIRONMENT_NOT_FOUND,
}, ),
}); TE.chainFirst((environment) =>
TE.fromTask(() =>
const teamEnvironment = this.cast(result); this.pubsub.publish(
`team_environment/${environment.teamID}/updated`,
this.pubsub.publish( <TeamEnvironment>{
`team_environment/${teamEnvironment.teamID}/updated`, id: environment.id,
teamEnvironment, name: environment.name,
); teamID: environment.teamID,
variables: JSON.stringify(environment.variables),
return E.right(teamEnvironment); },
} catch (error) { ),
return E.left(TEAM_ENVIRONMENT_NOT_FOUND); ),
} ),
TE.map(
(environment) =>
<TeamEnvironment>{
id: environment.id,
name: environment.name,
teamID: environment.teamID,
variables: JSON.stringify(environment.variables),
},
),
);
} }
/** deleteAllVariablesFromTeamEnvironment(id: string) {
* Create a duplicate of a existing TeamEnvironment. return pipe(
* TE.tryCatch(
* @param id TeamEnvironment ID () =>
* @returns Either of a TeamEnvironment or error message this.prisma.teamEnvironment.update({
*/ where: { id: id },
async createDuplicateEnvironment(id: string) { data: {
try { variables: [],
const environment = await this.prisma.teamEnvironment.findFirstOrThrow({ },
where: { }),
id: id, () => TEAM_ENVIRONMENT_NOT_FOUND,
}, ),
}); TE.chainFirst((environment) =>
TE.fromTask(() =>
const result = await this.prisma.teamEnvironment.create({ this.pubsub.publish(
data: { `team_environment/${environment.teamID}/updated`,
name: environment.name, <TeamEnvironment>{
teamID: environment.teamID, id: environment.id,
variables: environment.variables as Prisma.JsonArray, name: environment.name,
}, teamID: environment.teamID,
}); variables: JSON.stringify(environment.variables),
},
const duplicatedTeamEnvironment = this.cast(result); ),
),
this.pubsub.publish( ),
`team_environment/${duplicatedTeamEnvironment.teamID}/created`, TE.map(
duplicatedTeamEnvironment, (environment) =>
); <TeamEnvironment>{
id: environment.id,
return E.right(duplicatedTeamEnvironment); name: environment.name,
} catch (error) { teamID: environment.teamID,
return E.left(TEAM_ENVIRONMENT_NOT_FOUND); variables: JSON.stringify(environment.variables),
} },
),
);
} }
/** createDuplicateEnvironment(id: string) {
* Fetch all TeamEnvironments of a team. return pipe(
* TE.tryCatch(
* @param teamID teamID of new TeamEnvironment () =>
* @returns List of TeamEnvironments this.prisma.teamEnvironment.findFirst({
*/ where: {
async fetchAllTeamEnvironments(teamID: string) { id: id,
const result = await this.prisma.teamEnvironment.findMany({ },
where: { rejectOnNotFound: true,
teamID: teamID, }),
}, () => TEAM_ENVIRONMENT_NOT_FOUND,
}); ),
const teamEnvironments = result.map((item) => { TE.chain((environment) =>
return this.cast(item); TE.fromTask(() =>
}); this.prisma.teamEnvironment.create({
data: {
name: environment.name,
teamID: environment.teamID,
variables: environment.variables as Prisma.JsonArray,
},
}),
),
),
TE.chainFirst((environment) =>
TE.fromTask(() =>
this.pubsub.publish(
`team_environment/${environment.teamID}/created`,
<TeamEnvironment>{
id: environment.id,
name: environment.name,
teamID: environment.teamID,
variables: JSON.stringify(environment.variables),
},
),
),
),
TE.map(
(environment) =>
<TeamEnvironment>{
id: environment.id,
name: environment.name,
teamID: environment.teamID,
variables: JSON.stringify(environment.variables),
},
),
);
}
return teamEnvironments; fetchAllTeamEnvironments(teamID: string) {
return pipe(
() =>
this.prisma.teamEnvironment.findMany({
where: {
teamID: teamID,
},
}),
T.map(
A.map(
(environment) =>
<TeamEnvironment>{
id: environment.id,
name: environment.name,
teamID: environment.teamID,
variables: JSON.stringify(environment.variables),
},
),
),
);
} }
/** /**

View File

@@ -11,6 +11,6 @@ export class TeamEnvsTeamResolver {
description: 'Returns all Team Environments for the given Team', description: 'Returns all Team Environments for the given Team',
}) })
teamEnvironments(@Parent() team: Team): Promise<TeamEnvironment[]> { teamEnvironments(@Parent() team: Team): Promise<TeamEnvironment[]> {
return this.teamEnvironmentService.fetchAllTeamEnvironments(team.id); return this.teamEnvironmentService.fetchAllTeamEnvironments(team.id)();
} }
} }

View File

@@ -1,20 +0,0 @@
import { ArgsType, Field, ID } from '@nestjs/graphql';
import { TeamMemberRole } from 'src/team/team.model';
@ArgsType()
export class CreateTeamInvitationArgs {
@Field(() => ID, {
name: 'teamID',
description: 'ID of the Team ID to invite from',
})
teamID: string;
@Field({ name: 'inviteeEmail', description: 'Email of the user to invite' })
inviteeEmail: string;
@Field(() => TeamMemberRole, {
name: 'inviteeRole',
description: 'Role to be given to the user',
})
inviteeRole: TeamMemberRole;
}

View File

@@ -12,10 +12,15 @@ import { TeamInvitation } from './team-invitation.model';
import { TeamInvitationService } from './team-invitation.service'; import { TeamInvitationService } from './team-invitation.service';
import { pipe } from 'fp-ts/function'; import { pipe } from 'fp-ts/function';
import * as TE from 'fp-ts/TaskEither'; import * as TE from 'fp-ts/TaskEither';
import * as E from 'fp-ts/Either';
import * as O from 'fp-ts/Option'; import * as O from 'fp-ts/Option';
import { Team, TeamMember, TeamMemberRole } from 'src/team/team.model'; import { Team, TeamMember, TeamMemberRole } from 'src/team/team.model';
import { TEAM_INVITE_NO_INVITE_FOUND, USER_NOT_FOUND } from 'src/errors'; import { EmailCodec } from 'src/types/Email';
import {
INVALID_EMAIL,
TEAM_INVITE_EMAIL_DO_NOT_MATCH,
TEAM_INVITE_NO_INVITE_FOUND,
USER_NOT_FOUND,
} from 'src/errors';
import { GqlUser } from 'src/decorators/gql-user.decorator'; import { GqlUser } from 'src/decorators/gql-user.decorator';
import { User } from 'src/user/user.model'; import { User } from 'src/user/user.model';
import { UseGuards } from '@nestjs/common'; import { UseGuards } from '@nestjs/common';
@@ -31,8 +36,6 @@ import { UserService } from 'src/user/user.service';
import { PubSubService } from 'src/pubsub/pubsub.service'; import { PubSubService } from 'src/pubsub/pubsub.service';
import { GqlThrottlerGuard } from 'src/guards/gql-throttler.guard'; import { GqlThrottlerGuard } from 'src/guards/gql-throttler.guard';
import { SkipThrottle } from '@nestjs/throttler'; import { SkipThrottle } from '@nestjs/throttler';
import { AuthUser } from 'src/types/AuthUser';
import { CreateTeamInvitationArgs } from './input-type.args';
@UseGuards(GqlThrottlerGuard) @UseGuards(GqlThrottlerGuard)
@Resolver(() => TeamInvitation) @Resolver(() => TeamInvitation)
@@ -76,8 +79,8 @@ export class TeamInvitationResolver {
'Gets the Team Invitation with the given ID, or null if not exists', 'Gets the Team Invitation with the given ID, or null if not exists',
}) })
@UseGuards(GqlAuthGuard, TeamInviteViewerGuard) @UseGuards(GqlAuthGuard, TeamInviteViewerGuard)
async teamInvitation( teamInvitation(
@GqlUser() user: AuthUser, @GqlUser() user: User,
@Args({ @Args({
name: 'inviteID', name: 'inviteID',
description: 'ID of the Team Invitation to lookup', description: 'ID of the Team Invitation to lookup',
@@ -85,11 +88,17 @@ export class TeamInvitationResolver {
}) })
inviteID: string, inviteID: string,
): Promise<TeamInvitation> { ): Promise<TeamInvitation> {
const teamInvitation = await this.teamInvitationService.getInvitation( return pipe(
inviteID, this.teamInvitationService.getInvitation(inviteID),
); TE.fromTaskOption(() => TEAM_INVITE_NO_INVITE_FOUND),
if (O.isNone(teamInvitation)) throwErr(TEAM_INVITE_NO_INVITE_FOUND); TE.chainW(
return teamInvitation.value; TE.fromPredicate(
(a) => a.inviteeEmail.toLowerCase() === user.email?.toLowerCase(),
() => TEAM_INVITE_EMAIL_DO_NOT_MATCH,
),
),
TE.getOrElse(throwErr),
)();
} }
@Mutation(() => TeamInvitation, { @Mutation(() => TeamInvitation, {
@@ -97,19 +106,56 @@ export class TeamInvitationResolver {
}) })
@UseGuards(GqlAuthGuard, GqlTeamMemberGuard) @UseGuards(GqlAuthGuard, GqlTeamMemberGuard)
@RequiresTeamRole(TeamMemberRole.OWNER) @RequiresTeamRole(TeamMemberRole.OWNER)
async createTeamInvitation( createTeamInvitation(
@GqlUser() user: AuthUser, @GqlUser()
@Args() args: CreateTeamInvitationArgs, user: User,
): Promise<TeamInvitation> {
const teamInvitation = await this.teamInvitationService.createInvitation(
user,
args.teamID,
args.inviteeEmail,
args.inviteeRole,
);
if (E.isLeft(teamInvitation)) throwErr(teamInvitation.left); @Args({
return teamInvitation.right; name: 'teamID',
description: 'ID of the Team ID to invite from',
type: () => ID,
})
teamID: string,
@Args({
name: 'inviteeEmail',
description: 'Email of the user to invite',
})
inviteeEmail: string,
@Args({
name: 'inviteeRole',
type: () => TeamMemberRole,
description: 'Role to be given to the user',
})
inviteeRole: TeamMemberRole,
): Promise<TeamInvitation> {
return pipe(
TE.Do,
// Validate email
TE.bindW('email', () =>
pipe(
EmailCodec.decode(inviteeEmail),
TE.fromEither,
TE.mapLeft(() => INVALID_EMAIL),
),
),
// Validate and get Team
TE.bindW('team', () => this.teamService.getTeamWithIDTE(teamID)),
// Create team
TE.chainW(({ email, team }) =>
this.teamInvitationService.createInvitation(
user,
team,
email,
inviteeRole,
),
),
// If failed, throw err (so the message is passed) else return value
TE.getOrElse(throwErr),
)();
} }
@Mutation(() => Boolean, { @Mutation(() => Boolean, {
@@ -117,7 +163,7 @@ export class TeamInvitationResolver {
}) })
@UseGuards(GqlAuthGuard, TeamInviteTeamOwnerGuard) @UseGuards(GqlAuthGuard, TeamInviteTeamOwnerGuard)
@RequiresTeamRole(TeamMemberRole.OWNER) @RequiresTeamRole(TeamMemberRole.OWNER)
async revokeTeamInvitation( revokeTeamInvitation(
@Args({ @Args({
name: 'inviteID', name: 'inviteID',
type: () => ID, type: () => ID,
@@ -125,19 +171,19 @@ export class TeamInvitationResolver {
}) })
inviteID: string, inviteID: string,
): Promise<true> { ): Promise<true> {
const isRevoked = await this.teamInvitationService.revokeInvitation( return pipe(
inviteID, this.teamInvitationService.revokeInvitation(inviteID),
); TE.map(() => true as const),
if (E.isLeft(isRevoked)) throwErr(isRevoked.left); TE.getOrElse(throwErr),
return true; )();
} }
@Mutation(() => TeamMember, { @Mutation(() => TeamMember, {
description: 'Accept an Invitation', description: 'Accept an Invitation',
}) })
@UseGuards(GqlAuthGuard, TeamInviteeGuard) @UseGuards(GqlAuthGuard, TeamInviteeGuard)
async acceptTeamInvitation( acceptTeamInvitation(
@GqlUser() user: AuthUser, @GqlUser() user: User,
@Args({ @Args({
name: 'inviteID', name: 'inviteID',
type: () => ID, type: () => ID,
@@ -145,12 +191,10 @@ export class TeamInvitationResolver {
}) })
inviteID: string, inviteID: string,
): Promise<TeamMember> { ): Promise<TeamMember> {
const teamMember = await this.teamInvitationService.acceptInvitation( return pipe(
inviteID, this.teamInvitationService.acceptInvitation(inviteID, user),
user, TE.getOrElse(throwErr),
); )();
if (E.isLeft(teamMember)) throwErr(teamMember.left);
return teamMember.right;
} }
// Subscriptions // Subscriptions

View File

@@ -1,25 +1,24 @@
import { Injectable } from '@nestjs/common'; import { Injectable } from '@nestjs/common';
import * as T from 'fp-ts/Task';
import * as O from 'fp-ts/Option'; import * as O from 'fp-ts/Option';
import * as E from 'fp-ts/Either'; import * as TO from 'fp-ts/TaskOption';
import * as TE from 'fp-ts/TaskEither';
import { pipe, flow, constVoid } from 'fp-ts/function';
import { PrismaService } from 'src/prisma/prisma.service'; import { PrismaService } from 'src/prisma/prisma.service';
import { TeamInvitation as DBTeamInvitation } from '@prisma/client'; import { Team, TeamMemberRole } from 'src/team/team.model';
import { TeamMember, TeamMemberRole } from 'src/team/team.model'; import { Email } from 'src/types/Email';
import { User } from 'src/user/user.model';
import { TeamService } from 'src/team/team.service'; import { TeamService } from 'src/team/team.service';
import { import {
INVALID_EMAIL,
TEAM_INVALID_ID,
TEAM_INVITE_ALREADY_MEMBER, TEAM_INVITE_ALREADY_MEMBER,
TEAM_INVITE_EMAIL_DO_NOT_MATCH, TEAM_INVITE_EMAIL_DO_NOT_MATCH,
TEAM_INVITE_MEMBER_HAS_INVITE, TEAM_INVITE_MEMBER_HAS_INVITE,
TEAM_INVITE_NO_INVITE_FOUND, TEAM_INVITE_NO_INVITE_FOUND,
TEAM_MEMBER_NOT_FOUND,
} from 'src/errors'; } from 'src/errors';
import { TeamInvitation } from './team-invitation.model'; import { TeamInvitation } from './team-invitation.model';
import { MailerService } from 'src/mailer/mailer.service'; import { MailerService } from 'src/mailer/mailer.service';
import { UserService } from 'src/user/user.service'; import { UserService } from 'src/user/user.service';
import { PubSubService } from 'src/pubsub/pubsub.service'; import { PubSubService } from 'src/pubsub/pubsub.service';
import { validateEmail } from '../utils';
import { AuthUser } from 'src/types/AuthUser';
@Injectable() @Injectable()
export class TeamInvitationService { export class TeamInvitationService {
@@ -30,221 +29,245 @@ export class TeamInvitationService {
private readonly mailerService: MailerService, private readonly mailerService: MailerService,
private readonly pubsub: PubSubService, private readonly pubsub: PubSubService,
) {} ) {
this.getInvitation = this.getInvitation.bind(this);
/**
* Cast a DBTeamInvitation to a TeamInvitation
* @param dbTeamInvitation database TeamInvitation
* @returns TeamInvitation model
*/
cast(dbTeamInvitation: DBTeamInvitation): TeamInvitation {
return {
...dbTeamInvitation,
inviteeRole: TeamMemberRole[dbTeamInvitation.inviteeRole],
};
} }
/** getInvitation(inviteID: string): TO.TaskOption<TeamInvitation> {
* Get the team invite return pipe(
* @param inviteID invite id () =>
* @returns an Option of team invitation or none this.prisma.teamInvitation.findUnique({
*/ where: {
async getInvitation(inviteID: string) { id: inviteID,
try {
const dbInvitation = await this.prisma.teamInvitation.findUniqueOrThrow({
where: {
id: inviteID,
},
});
return O.some(this.cast(dbInvitation));
} catch (e) {
return O.none;
}
}
/**
* Get the team invite for an invitee with email and teamID.
* @param inviteeEmail invitee email
* @param teamID team id
* @returns an Either of team invitation for the invitee or error
*/
async getTeamInviteByEmailAndTeamID(inviteeEmail: string, teamID: string) {
const isEmailValid = validateEmail(inviteeEmail);
if (!isEmailValid) return E.left(INVALID_EMAIL);
try {
const teamInvite = await this.prisma.teamInvitation.findUniqueOrThrow({
where: {
teamID_inviteeEmail: {
inviteeEmail: inviteeEmail,
teamID: teamID,
}, },
}, }),
}); TO.fromTask,
TO.chain(flow(O.fromNullable, TO.fromOption)),
return E.right(teamInvite); TO.map((x) => x as TeamInvitation),
} catch (e) { );
return E.left(TEAM_INVITE_NO_INVITE_FOUND);
}
} }
/** getInvitationWithEmail(email: Email, team: Team) {
* Create a team invitation return pipe(
* @param creator creator of the invitation () =>
* @param teamID team id this.prisma.teamInvitation.findUnique({
* @param inviteeEmail invitee email where: {
* @param inviteeRole invitee role teamID_inviteeEmail: {
* @returns an Either of team invitation or error message inviteeEmail: email,
*/ teamID: team.id,
async createInvitation( },
creator: AuthUser, },
teamID: string, }),
inviteeEmail: string, TO.fromTask,
TO.chain(flow(O.fromNullable, TO.fromOption)),
);
}
createInvitation(
creator: User,
team: Team,
inviteeEmail: Email,
inviteeRole: TeamMemberRole, inviteeRole: TeamMemberRole,
) { ) {
// validate email return pipe(
const isEmailValid = validateEmail(inviteeEmail); // Perform all validation checks
if (!isEmailValid) return E.left(INVALID_EMAIL); TE.sequenceArray([
// creator should be a TeamMember
pipe(
this.teamService.getTeamMemberTE(team.id, creator.uid),
TE.map(constVoid),
),
// team ID should valid // Invitee should not be a team member
const team = await this.teamService.getTeamWithID(teamID); pipe(
if (!team) return E.left(TEAM_INVALID_ID); async () => await this.userService.findUserByEmail(inviteeEmail),
TO.foldW(
() => TE.right(undefined), // If no user, short circuit to completion
(user) =>
pipe(
// If user is found, check if team member
this.teamService.getTeamMemberTE(team.id, user.uid),
TE.foldW(
() => TE.right(undefined), // Not team-member, this is good
() => TE.left(TEAM_INVITE_ALREADY_MEMBER), // Is team member, not good
),
),
),
TE.map(constVoid),
),
// invitation creator should be a TeamMember // Should not have an existing invite
const isTeamMember = await this.teamService.getTeamMember( pipe(
team.id, this.getInvitationWithEmail(inviteeEmail, team),
creator.uid, TE.fromTaskOption(() => null),
TE.swap,
TE.map(constVoid),
TE.mapLeft(() => TEAM_INVITE_MEMBER_HAS_INVITE),
),
]),
// Create the invitation
TE.chainTaskK(
() => () =>
this.prisma.teamInvitation.create({
data: {
teamID: team.id,
inviteeEmail,
inviteeRole,
creatorUid: creator.uid,
},
}),
),
// Send email, this is a side effect
TE.chainFirstTaskK((invitation) =>
pipe(
this.mailerService.sendMail(inviteeEmail, {
template: 'team-invitation',
variables: {
invitee: creator.displayName ?? 'A Hoppscotch User',
action_url: `https://hoppscotch.io/join-team?id=${invitation.id}`,
invite_team_name: team.name,
},
}),
TE.getOrElseW(() => T.of(undefined)), // This value doesn't matter as we don't mind the return value (chainFirst) as long as the task completes
),
),
// Send PubSub topic
TE.chainFirstTaskK((invitation) =>
TE.fromTask(async () => {
const inv: TeamInvitation = {
id: invitation.id,
teamID: invitation.teamID,
creatorUid: invitation.creatorUid,
inviteeEmail: invitation.inviteeEmail,
inviteeRole: TeamMemberRole[invitation.inviteeRole],
};
this.pubsub.publish(`team/${inv.teamID}/invite_added`, inv);
}),
),
// Map to model type
TE.map((x) => x as TeamInvitation),
); );
if (!isTeamMember) return E.left(TEAM_MEMBER_NOT_FOUND); }
// Checking to see if the invitee is already part of the team or not revokeInvitation(inviteID: string) {
const inviteeUser = await this.userService.findUserByEmail(inviteeEmail); return pipe(
if (O.isSome(inviteeUser)) { // Make sure invite exists
// invitee should not already a member this.getInvitation(inviteID),
const isTeamMember = await this.teamService.getTeamMember( TE.fromTaskOption(() => TEAM_INVITE_NO_INVITE_FOUND),
team.id,
inviteeUser.value.uid,
);
if (isTeamMember) return E.left(TEAM_INVITE_ALREADY_MEMBER);
}
// check invitee already invited earlier or not // Delete team invitation
const teamInvitation = await this.getTeamInviteByEmailAndTeamID( TE.chainTaskK(
inviteeEmail, () => () =>
team.id, this.prisma.teamInvitation.delete({
where: {
id: inviteID,
},
}),
),
// Emit Pubsub Event
TE.chainFirst((invitation) =>
TE.fromTask(() =>
this.pubsub.publish(
`team/${invitation.teamID}/invite_removed`,
invitation.id,
),
),
),
// We are not returning anything
TE.map(constVoid),
); );
if (E.isRight(teamInvitation)) return E.left(TEAM_INVITE_MEMBER_HAS_INVITE); }
// create the invitation getAllInvitationsInTeam(team: Team) {
const dbInvitation = await this.prisma.teamInvitation.create({ return pipe(
data: { () =>
teamID: team.id, this.prisma.teamInvitation.findMany({
inviteeEmail, where: {
inviteeRole, teamID: team.id,
creatorUid: creator.uid, },
}, }),
}); T.map((x) => x as TeamInvitation[]),
);
}
await this.mailerService.sendEmail(inviteeEmail, { acceptInvitation(inviteID: string, acceptedBy: User) {
template: 'team-invitation', return pipe(
variables: { TE.Do,
invitee: creator.displayName ?? 'A Hoppscotch User',
action_url: `${process.env.VITE_BASE_URL}/join-team?id=${dbInvitation.id}`,
invite_team_name: team.name,
},
});
const invitation = this.cast(dbInvitation); // First get the invitation
this.pubsub.publish(`team/${invitation.teamID}/invite_added`, invitation); TE.bindW('invitation', () =>
pipe(
this.getInvitation(inviteID),
TE.fromTaskOption(() => TEAM_INVITE_NO_INVITE_FOUND),
),
),
return E.right(invitation); // Validation checks
TE.chainFirstW(({ invitation }) =>
TE.sequenceArray([
// Make sure the invited user is not part of the team
pipe(
this.teamService.getTeamMemberTE(invitation.teamID, acceptedBy.uid),
TE.swap,
TE.bimap(
() => TEAM_INVITE_ALREADY_MEMBER,
constVoid, // The return type is ignored
),
),
// Make sure the invited user and accepting user has the same email
pipe(
undefined,
TE.fromPredicate(
(a) => acceptedBy.email === invitation.inviteeEmail,
() => TEAM_INVITE_EMAIL_DO_NOT_MATCH,
),
),
]),
),
// Add the team member
// TODO: Somehow bring subscriptions to this ?
TE.bindW('teamMember', ({ invitation }) =>
pipe(
TE.tryCatch(
() =>
this.teamService.addMemberToTeam(
invitation.teamID,
acceptedBy.uid,
invitation.inviteeRole,
),
() => TEAM_INVITE_ALREADY_MEMBER, // Can only fail if Team Member already exists, which we checked, but due to async lets assert that here too
),
),
),
TE.chainFirstW(({ invitation }) => this.revokeInvitation(invitation.id)),
TE.map(({ teamMember }) => teamMember),
);
} }
/** /**
* Revoke a team invitation * Fetch the count invitations for a given team.
* @param inviteID invite id
* @returns an Either of true or error message
*/
async revokeInvitation(inviteID: string) {
// check if the invite exists
const invitation = await this.getInvitation(inviteID);
if (O.isNone(invitation)) return E.left(TEAM_INVITE_NO_INVITE_FOUND);
// delete the invite
await this.prisma.teamInvitation.delete({
where: {
id: inviteID,
},
});
this.pubsub.publish(
`team/${invitation.value.teamID}/invite_removed`,
invitation.value.id,
);
return E.right(true);
}
/**
* Accept a team invitation
* @param inviteID invite id
* @param acceptedBy user who accepted the invitation
* @returns an Either of team member or error message
*/
async acceptInvitation(inviteID: string, acceptedBy: AuthUser) {
// check if the invite exists
const invitation = await this.getInvitation(inviteID);
if (O.isNone(invitation)) return E.left(TEAM_INVITE_NO_INVITE_FOUND);
// make sure the user is not already a member of the team
const teamMemberInvitee = await this.teamService.getTeamMember(
invitation.value.teamID,
acceptedBy.uid,
);
if (teamMemberInvitee) return E.left(TEAM_INVITE_ALREADY_MEMBER);
// make sure the user is the same as the invitee
if (
acceptedBy.email.toLowerCase() !==
invitation.value.inviteeEmail.toLowerCase()
)
return E.left(TEAM_INVITE_EMAIL_DO_NOT_MATCH);
// add the user to the team
let teamMember: TeamMember;
try {
teamMember = await this.teamService.addMemberToTeam(
invitation.value.teamID,
acceptedBy.uid,
invitation.value.inviteeRole,
);
} catch (e) {
return E.left(TEAM_INVITE_ALREADY_MEMBER);
}
// delete the invite
await this.revokeInvitation(inviteID);
return E.right(teamMember);
}
/**
* Fetch all team invitations for a given team.
* @param teamID team id * @param teamID team id
* @returns array of team invitations for a team * @returns a count team invitations for a team
*/ */
async getTeamInvitations(teamID: string) { async getAllTeamInvitations(teamID: string) {
const dbInvitations = await this.prisma.teamInvitation.findMany({ const invitations = await this.prisma.teamInvitation.findMany({
where: { where: {
teamID: teamID, teamID: teamID,
}, },
}); });
const invitations: TeamInvitation[] = dbInvitations.map((dbInvitation) =>
this.cast(dbInvitation),
);
return invitations; return invitations;
} }
} }

View File

@@ -1,21 +1,21 @@
import { CanActivate, ExecutionContext, Injectable } from '@nestjs/common'; import { CanActivate, ExecutionContext, Injectable } from '@nestjs/common';
import { pipe } from 'fp-ts/function';
import { TeamService } from 'src/team/team.service'; import { TeamService } from 'src/team/team.service';
import { TeamInvitationService } from './team-invitation.service'; import { TeamInvitationService } from './team-invitation.service';
import * as O from 'fp-ts/Option'; import * as O from 'fp-ts/Option';
import * as T from 'fp-ts/Task';
import * as TE from 'fp-ts/TaskEither';
import { GqlExecutionContext } from '@nestjs/graphql'; import { GqlExecutionContext } from '@nestjs/graphql';
import { import {
BUG_AUTH_NO_USER_CTX, BUG_AUTH_NO_USER_CTX,
BUG_TEAM_INVITE_NO_INVITE_ID, BUG_TEAM_INVITE_NO_INVITE_ID,
TEAM_INVITE_NO_INVITE_FOUND, TEAM_INVITE_NO_INVITE_FOUND,
TEAM_MEMBER_NOT_FOUND,
TEAM_NOT_REQUIRED_ROLE, TEAM_NOT_REQUIRED_ROLE,
} from 'src/errors'; } from 'src/errors';
import { User } from 'src/user/user.model';
import { throwErr } from 'src/utils'; import { throwErr } from 'src/utils';
import { TeamMemberRole } from 'src/team/team.model'; import { TeamMemberRole } from 'src/team/team.model';
/**
* This guard only allows team owner to execute the resolver
*/
@Injectable() @Injectable()
export class TeamInviteTeamOwnerGuard implements CanActivate { export class TeamInviteTeamOwnerGuard implements CanActivate {
constructor( constructor(
@@ -24,30 +24,48 @@ export class TeamInviteTeamOwnerGuard implements CanActivate {
) {} ) {}
async canActivate(context: ExecutionContext): Promise<boolean> { async canActivate(context: ExecutionContext): Promise<boolean> {
// Get GQL context return pipe(
const gqlExecCtx = GqlExecutionContext.create(context); TE.Do,
// Get user TE.bindW('gqlCtx', () => TE.of(GqlExecutionContext.create(context))),
const { user } = gqlExecCtx.getContext().req;
if (!user) throwErr(BUG_AUTH_NO_USER_CTX);
// Get the invite // Get the invite
const { inviteID } = gqlExecCtx.getArgs<{ inviteID: string }>(); TE.bindW('invite', ({ gqlCtx }) =>
if (!inviteID) throwErr(BUG_TEAM_INVITE_NO_INVITE_ID); pipe(
O.fromNullable(gqlCtx.getArgs<{ inviteID?: string }>().inviteID),
TE.fromOption(() => BUG_TEAM_INVITE_NO_INVITE_ID),
TE.chainW((inviteID) =>
pipe(
this.teamInviteService.getInvitation(inviteID),
TE.fromTaskOption(() => TEAM_INVITE_NO_INVITE_FOUND),
),
),
),
),
const invitation = await this.teamInviteService.getInvitation(inviteID); TE.bindW('user', ({ gqlCtx }) =>
if (O.isNone(invitation)) throwErr(TEAM_INVITE_NO_INVITE_FOUND); pipe(
gqlCtx.getContext().req.user,
O.fromNullable,
TE.fromOption(() => BUG_AUTH_NO_USER_CTX),
),
),
// Fetch team member details of this user TE.bindW('userMember', ({ invite, user }) =>
const teamMember = await this.teamService.getTeamMember( this.teamService.getTeamMemberTE(invite.teamID, user.uid),
invitation.value.teamID, ),
user.uid,
);
if (!teamMember) throwErr(TEAM_MEMBER_NOT_FOUND); TE.chainW(
if (teamMember.role !== TeamMemberRole.OWNER) TE.fromPredicate(
throwErr(TEAM_NOT_REQUIRED_ROLE); ({ userMember }) => userMember.role === TeamMemberRole.OWNER,
() => TEAM_NOT_REQUIRED_ROLE,
),
),
return true; TE.fold(
(err) => throwErr(err),
() => T.of(true),
),
)();
} }
} }

View File

@@ -1,23 +1,20 @@
import { CanActivate, ExecutionContext, Injectable } from '@nestjs/common'; import { CanActivate, ExecutionContext, Injectable } from '@nestjs/common';
import { TeamInvitationService } from './team-invitation.service'; import { TeamInvitationService } from './team-invitation.service';
import { pipe, flow } from 'fp-ts/function';
import * as TE from 'fp-ts/TaskEither';
import * as T from 'fp-ts/Task';
import * as O from 'fp-ts/Option'; import * as O from 'fp-ts/Option';
import { GqlExecutionContext } from '@nestjs/graphql'; import { GqlExecutionContext } from '@nestjs/graphql';
import { import {
BUG_AUTH_NO_USER_CTX, BUG_AUTH_NO_USER_CTX,
BUG_TEAM_INVITE_NO_INVITE_ID, BUG_TEAM_INVITE_NO_INVITE_ID,
TEAM_INVITE_NOT_VALID_VIEWER,
TEAM_INVITE_NO_INVITE_FOUND, TEAM_INVITE_NO_INVITE_FOUND,
TEAM_MEMBER_NOT_FOUND,
} from 'src/errors'; } from 'src/errors';
import { User } from 'src/user/user.model';
import { throwErr } from 'src/utils'; import { throwErr } from 'src/utils';
import { TeamService } from 'src/team/team.service'; import { TeamService } from 'src/team/team.service';
/**
* This guard only allows user to execute the resolver
* 1. If user is invitee, allow
* 2. Or else, if user is team member, allow
*
* TLDR: Allow if user is invitee or team member
*/
@Injectable() @Injectable()
export class TeamInviteViewerGuard implements CanActivate { export class TeamInviteViewerGuard implements CanActivate {
constructor( constructor(
@@ -26,32 +23,50 @@ export class TeamInviteViewerGuard implements CanActivate {
) {} ) {}
async canActivate(context: ExecutionContext): Promise<boolean> { async canActivate(context: ExecutionContext): Promise<boolean> {
// Get GQL context return pipe(
const gqlExecCtx = GqlExecutionContext.create(context); TE.Do,
// Get user // Get GQL Context
const { user } = gqlExecCtx.getContext().req; TE.bindW('gqlCtx', () => TE.of(GqlExecutionContext.create(context))),
if (!user) throwErr(BUG_AUTH_NO_USER_CTX);
// Get the invite // Get user
const { inviteID } = gqlExecCtx.getArgs<{ inviteID: string }>(); TE.bindW('user', ({ gqlCtx }) =>
if (!inviteID) throwErr(BUG_TEAM_INVITE_NO_INVITE_ID); pipe(
O.fromNullable(gqlCtx.getContext<{ user?: User }>().user),
TE.fromOption(() => BUG_AUTH_NO_USER_CTX),
),
),
const invitation = await this.teamInviteService.getInvitation(inviteID); // Get the invite
if (O.isNone(invitation)) throwErr(TEAM_INVITE_NO_INVITE_FOUND); TE.bindW('invite', ({ gqlCtx }) =>
pipe(
O.fromNullable(gqlCtx.getArgs<{ inviteID?: string }>().inviteID),
TE.fromOption(() => BUG_TEAM_INVITE_NO_INVITE_ID),
TE.chainW(
flow(
this.teamInviteService.getInvitation,
TE.fromTaskOption(() => TEAM_INVITE_NO_INVITE_FOUND),
),
),
),
),
// Check if the user and the invite email match, else if user is a team member // Check if the user and the invite email match, else if we can resolver the user as a team member
if ( // any better solution ?
user.email?.toLowerCase() !== invitation.value.inviteeEmail.toLowerCase() TE.chainW(({ user, invite }) =>
) { user.email?.toLowerCase() === invite.inviteeEmail.toLowerCase()
const teamMember = await this.teamService.getTeamMember( ? TE.of(true)
invitation.value.teamID, : pipe(
user.uid, this.teamService.getTeamMemberTE(invite.teamID, user.uid),
); TE.map(() => true),
),
),
if (!teamMember) throwErr(TEAM_MEMBER_NOT_FOUND); TE.mapLeft((e) =>
} e === 'team/member_not_found' ? TEAM_INVITE_NOT_VALID_VIEWER : e,
),
return true; TE.fold(throwErr, () => T.of(true)),
)();
} }
} }

View File

@@ -1,7 +1,11 @@
import { CanActivate, ExecutionContext, Injectable } from '@nestjs/common'; import { CanActivate, ExecutionContext, Injectable } from '@nestjs/common';
import { TeamInvitationService } from './team-invitation.service'; import { TeamInvitationService } from './team-invitation.service';
import { pipe, flow } from 'fp-ts/function';
import * as O from 'fp-ts/Option'; import * as O from 'fp-ts/Option';
import * as T from 'fp-ts/Task';
import * as TE from 'fp-ts/TaskEither';
import { GqlExecutionContext } from '@nestjs/graphql'; import { GqlExecutionContext } from '@nestjs/graphql';
import { User } from 'src/user/user.model';
import { import {
BUG_AUTH_NO_USER_CTX, BUG_AUTH_NO_USER_CTX,
BUG_TEAM_INVITE_NO_INVITE_ID, BUG_TEAM_INVITE_NO_INVITE_ID,
@@ -20,26 +24,44 @@ export class TeamInviteeGuard implements CanActivate {
constructor(private readonly teamInviteService: TeamInvitationService) {} constructor(private readonly teamInviteService: TeamInvitationService) {}
async canActivate(context: ExecutionContext): Promise<boolean> { async canActivate(context: ExecutionContext): Promise<boolean> {
// Get GQL Context return pipe(
const gqlExecCtx = GqlExecutionContext.create(context); TE.Do,
// Get user // Get execution context
const { user } = gqlExecCtx.getContext().req; TE.bindW('gqlCtx', () => TE.of(GqlExecutionContext.create(context))),
if (!user) throwErr(BUG_AUTH_NO_USER_CTX);
// Get the invite // Get user
const { inviteID } = gqlExecCtx.getArgs<{ inviteID: string }>(); TE.bindW('user', ({ gqlCtx }) =>
if (!inviteID) throwErr(BUG_TEAM_INVITE_NO_INVITE_ID); pipe(
O.fromNullable(gqlCtx.getContext<{ user?: User }>().user),
TE.fromOption(() => BUG_AUTH_NO_USER_CTX),
),
),
const invitation = await this.teamInviteService.getInvitation(inviteID); // Get invite
if (O.isNone(invitation)) throwErr(TEAM_INVITE_NO_INVITE_FOUND); TE.bindW('invite', ({ gqlCtx }) =>
pipe(
O.fromNullable(gqlCtx.getArgs<{ inviteID?: string }>().inviteID),
TE.fromOption(() => BUG_TEAM_INVITE_NO_INVITE_ID),
TE.chainW(
flow(
this.teamInviteService.getInvitation,
TE.fromTaskOption(() => TEAM_INVITE_NO_INVITE_FOUND),
),
),
),
),
if ( // Check if the emails match
user.email.toLowerCase() !== invitation.value.inviteeEmail.toLowerCase() TE.chainW(
) { TE.fromPredicate(
throwErr(TEAM_INVITE_EMAIL_DO_NOT_MATCH); ({ user, invite }) => user.email === invite.inviteeEmail,
} () => TEAM_INVITE_EMAIL_DO_NOT_MATCH,
),
),
return true; // Fold it to a promise
TE.fold(throwErr, () => T.of(true)),
)();
} }
} }

View File

@@ -12,6 +12,6 @@ export class TeamTeamInviteExtResolver {
complexity: 10, complexity: 10,
}) })
teamInvitations(@Parent() team: Team): Promise<TeamInvitation[]> { teamInvitations(@Parent() team: Team): Promise<TeamInvitation[]> {
return this.teamInviteService.getTeamInvitations(team.id); return this.teamInviteService.getAllInvitationsInTeam(team)();
} }
} }

View File

@@ -2,7 +2,7 @@ import { HttpStatus } from '@nestjs/common';
/** /**
** Custom interface to handle errors specific to Auth module ** Custom interface to handle errors specific to Auth module
** Since its REST we need to return the HTTP status code along with the error message ** Since its REST we need to return HTTP status code along with error message
*/ */
export type AuthError = { export type AuthError = {
message: string; message: string;

View File

@@ -24,8 +24,6 @@ beforeEach(() => {
mockPubSub.publish.mockClear(); mockPubSub.publish.mockClear();
}); });
const date = new Date();
describe('UserHistoryService', () => { describe('UserHistoryService', () => {
describe('fetchUserHistory', () => { describe('fetchUserHistory', () => {
test('Should return a list of users REST history if exists', async () => { test('Should return a list of users REST history if exists', async () => {
@@ -142,15 +140,13 @@ describe('UserHistoryService', () => {
}); });
describe('createUserHistory', () => { describe('createUserHistory', () => {
test('Should resolve right and create a REST request to users history and return a `UserHistory` object', async () => { test('Should resolve right and create a REST request to users history and return a `UserHistory` object', async () => {
const executedOn = new Date();
mockPrisma.userHistory.create.mockResolvedValueOnce({ mockPrisma.userHistory.create.mockResolvedValueOnce({
userUid: 'abc', userUid: 'abc',
id: '1', id: '1',
request: [{}], request: [{}],
responseMetadata: [{}], responseMetadata: [{}],
reqType: ReqType.REST, reqType: ReqType.REST,
executedOn, executedOn: new Date(),
isStarred: false, isStarred: false,
}); });
@@ -160,7 +156,7 @@ describe('UserHistoryService', () => {
request: JSON.stringify([{}]), request: JSON.stringify([{}]),
responseMetadata: JSON.stringify([{}]), responseMetadata: JSON.stringify([{}]),
reqType: ReqType.REST, reqType: ReqType.REST,
executedOn, executedOn: new Date(),
isStarred: false, isStarred: false,
}; };
@@ -174,15 +170,13 @@ describe('UserHistoryService', () => {
).toEqualRight(userHistory); ).toEqualRight(userHistory);
}); });
test('Should resolve right and create a GQL request to users history and return a `UserHistory` object', async () => { test('Should resolve right and create a GQL request to users history and return a `UserHistory` object', async () => {
const executedOn = new Date();
mockPrisma.userHistory.create.mockResolvedValueOnce({ mockPrisma.userHistory.create.mockResolvedValueOnce({
userUid: 'abc', userUid: 'abc',
id: '1', id: '1',
request: [{}], request: [{}],
responseMetadata: [{}], responseMetadata: [{}],
reqType: ReqType.GQL, reqType: ReqType.GQL,
executedOn, executedOn: new Date(),
isStarred: false, isStarred: false,
}); });
@@ -192,7 +186,7 @@ describe('UserHistoryService', () => {
request: JSON.stringify([{}]), request: JSON.stringify([{}]),
responseMetadata: JSON.stringify([{}]), responseMetadata: JSON.stringify([{}]),
reqType: ReqType.GQL, reqType: ReqType.GQL,
executedOn, executedOn: new Date(),
isStarred: false, isStarred: false,
}; };
@@ -216,15 +210,13 @@ describe('UserHistoryService', () => {
).toEqualLeft(USER_HISTORY_INVALID_REQ_TYPE); ).toEqualLeft(USER_HISTORY_INVALID_REQ_TYPE);
}); });
test('Should create a GQL request to users history and publish a created subscription', async () => { test('Should create a GQL request to users history and publish a created subscription', async () => {
const executedOn = new Date();
mockPrisma.userHistory.create.mockResolvedValueOnce({ mockPrisma.userHistory.create.mockResolvedValueOnce({
userUid: 'abc', userUid: 'abc',
id: '1', id: '1',
request: [{}], request: [{}],
responseMetadata: [{}], responseMetadata: [{}],
reqType: ReqType.GQL, reqType: ReqType.GQL,
executedOn, executedOn: new Date(),
isStarred: false, isStarred: false,
}); });
@@ -234,7 +226,7 @@ describe('UserHistoryService', () => {
request: JSON.stringify([{}]), request: JSON.stringify([{}]),
responseMetadata: JSON.stringify([{}]), responseMetadata: JSON.stringify([{}]),
reqType: ReqType.GQL, reqType: ReqType.GQL,
executedOn, executedOn: new Date(),
isStarred: false, isStarred: false,
}; };
@@ -251,15 +243,13 @@ describe('UserHistoryService', () => {
); );
}); });
test('Should create a REST request to users history and publish a created subscription', async () => { test('Should create a REST request to users history and publish a created subscription', async () => {
const executedOn = new Date();
mockPrisma.userHistory.create.mockResolvedValueOnce({ mockPrisma.userHistory.create.mockResolvedValueOnce({
userUid: 'abc', userUid: 'abc',
id: '1', id: '1',
request: [{}], request: [{}],
responseMetadata: [{}], responseMetadata: [{}],
reqType: ReqType.REST, reqType: ReqType.REST,
executedOn, executedOn: new Date(),
isStarred: false, isStarred: false,
}); });
@@ -269,7 +259,7 @@ describe('UserHistoryService', () => {
request: JSON.stringify([{}]), request: JSON.stringify([{}]),
responseMetadata: JSON.stringify([{}]), responseMetadata: JSON.stringify([{}]),
reqType: ReqType.REST, reqType: ReqType.REST,
executedOn, executedOn: new Date(),
isStarred: false, isStarred: false,
}; };
@@ -331,15 +321,13 @@ describe('UserHistoryService', () => {
).toEqualLeft(USER_HISTORY_NOT_FOUND); ).toEqualLeft(USER_HISTORY_NOT_FOUND);
}); });
test('Should star/unstar a request in the history and publish a updated subscription', async () => { test('Should star/unstar a request in the history and publish a updated subscription', async () => {
const executedOn = new Date();
mockPrisma.userHistory.findFirst.mockResolvedValueOnce({ mockPrisma.userHistory.findFirst.mockResolvedValueOnce({
userUid: 'abc', userUid: 'abc',
id: '1', id: '1',
request: [{}], request: [{}],
responseMetadata: [{}], responseMetadata: [{}],
reqType: ReqType.REST, reqType: ReqType.REST,
executedOn, executedOn: new Date(),
isStarred: false, isStarred: false,
}); });
@@ -349,7 +337,7 @@ describe('UserHistoryService', () => {
request: [{}], request: [{}],
responseMetadata: [{}], responseMetadata: [{}],
reqType: ReqType.REST, reqType: ReqType.REST,
executedOn, executedOn: new Date(),
isStarred: true, isStarred: true,
}); });
@@ -359,7 +347,7 @@ describe('UserHistoryService', () => {
request: JSON.stringify([{}]), request: JSON.stringify([{}]),
responseMetadata: JSON.stringify([{}]), responseMetadata: JSON.stringify([{}]),
reqType: ReqType.REST, reqType: ReqType.REST,
executedOn, executedOn: new Date(),
isStarred: true, isStarred: true,
}; };
@@ -372,15 +360,13 @@ describe('UserHistoryService', () => {
}); });
describe('removeRequestFromHistory', () => { describe('removeRequestFromHistory', () => {
test('Should resolve right and delete request from users history', async () => { test('Should resolve right and delete request from users history', async () => {
const executedOn = new Date();
mockPrisma.userHistory.delete.mockResolvedValueOnce({ mockPrisma.userHistory.delete.mockResolvedValueOnce({
userUid: 'abc', userUid: 'abc',
id: '1', id: '1',
request: [{}], request: [{}],
responseMetadata: [{}], responseMetadata: [{}],
reqType: ReqType.REST, reqType: ReqType.REST,
executedOn: executedOn, executedOn: new Date(),
isStarred: false, isStarred: false,
}); });
@@ -390,7 +376,7 @@ describe('UserHistoryService', () => {
request: JSON.stringify([{}]), request: JSON.stringify([{}]),
responseMetadata: JSON.stringify([{}]), responseMetadata: JSON.stringify([{}]),
reqType: ReqType.REST, reqType: ReqType.REST,
executedOn: executedOn, executedOn: new Date(),
isStarred: false, isStarred: false,
}; };
@@ -398,7 +384,7 @@ describe('UserHistoryService', () => {
await userHistoryService.removeRequestFromHistory('abc', '1'), await userHistoryService.removeRequestFromHistory('abc', '1'),
).toEqualRight(userHistory); ).toEqualRight(userHistory);
}); });
test('Should resolve left and error out when req id is invalid', async () => { test('Should resolve left and error out when req id is invalid ', async () => {
mockPrisma.userHistory.delete.mockResolvedValueOnce(null); mockPrisma.userHistory.delete.mockResolvedValueOnce(null);
return expect( return expect(
@@ -412,7 +398,7 @@ describe('UserHistoryService', () => {
request: [{}], request: [{}],
responseMetadata: [{}], responseMetadata: [{}],
reqType: ReqType.REST, reqType: ReqType.REST,
executedOn: date, executedOn: new Date(),
isStarred: false, isStarred: false,
}); });
@@ -422,7 +408,7 @@ describe('UserHistoryService', () => {
request: JSON.stringify([{}]), request: JSON.stringify([{}]),
responseMetadata: JSON.stringify([{}]), responseMetadata: JSON.stringify([{}]),
reqType: ReqType.REST, reqType: ReqType.REST,
executedOn: date, executedOn: new Date(),
isStarred: false, isStarred: false,
}; };

View File

@@ -9,13 +9,7 @@ import * as E from 'fp-ts/Either';
import * as A from 'fp-ts/Array'; import * as A from 'fp-ts/Array';
import { TeamMemberRole } from './team/team.model'; import { TeamMemberRole } from './team/team.model';
import { User } from './user/user.model'; import { User } from './user/user.model';
import { import { JSON_INVALID } from './errors';
ENV_EMPTY_AUTH_PROVIDERS,
ENV_NOT_FOUND_KEY_AUTH_PROVIDERS,
ENV_NOT_SUPPORT_AUTH_PROVIDERS,
JSON_INVALID,
} from './errors';
import { AuthProvider } from './auth/helper';
/** /**
* A workaround to throw an exception in an expression. * A workaround to throw an exception in an expression.
@@ -158,31 +152,3 @@ export function isValidLength(title: string, length: number) {
return true; return true;
} }
/**
* This function is called by bootstrap() in main.ts
* It checks if the "VITE_ALLOWED_AUTH_PROVIDERS" environment variable is properly set or not.
* If not, it throws an error.
*/
export function checkEnvironmentAuthProvider() {
if (!process.env.hasOwnProperty('VITE_ALLOWED_AUTH_PROVIDERS')) {
throw new Error(ENV_NOT_FOUND_KEY_AUTH_PROVIDERS);
}
if (process.env.VITE_ALLOWED_AUTH_PROVIDERS === '') {
throw new Error(ENV_EMPTY_AUTH_PROVIDERS);
}
const givenAuthProviders = process.env.VITE_ALLOWED_AUTH_PROVIDERS.split(
',',
).map((provider) => provider.toLocaleUpperCase());
const supportedAuthProviders = Object.values(AuthProvider).map(
(provider: string) => provider.toLocaleUpperCase(),
);
for (const givenAuthProvider of givenAuthProviders) {
if (!supportedAuthProviders.includes(givenAuthProvider)) {
throw new Error(ENV_NOT_SUPPORT_AUTH_PROVIDERS);
}
}
}

View File

@@ -0,0 +1,128 @@
# Contributor Covenant Code of Conduct
## Our Pledge
We as members, contributors, and leaders pledge to make participation in our
community a harassment-free experience for everyone, regardless of age, body
size, visible or invisible disability, ethnicity, sex characteristics, gender
identity and expression, level of experience, education, socio-economic status,
nationality, personal appearance, race, religion, or sexual identity
and orientation.
We pledge to act and interact in ways that contribute to an open, welcoming,
diverse, inclusive, and healthy community.
## Our Standards
Examples of behavior that contributes to a positive environment for our
community include:
- Demonstrating empathy and kindness toward other people
- Being respectful of differing opinions, viewpoints, and experiences
- Giving and gracefully accepting constructive feedback
- Accepting responsibility and apologizing to those affected by our mistakes,
and learning from the experience
- Focusing on what is best not just for us as individuals, but for the
overall community
Examples of unacceptable behavior include:
- The use of sexualized language or imagery, and sexual attention or
advances of any kind
- Trolling, insulting or derogatory comments, and personal or political attacks
- Public or private harassment
- Publishing others' private information, such as a physical or email
address, without their explicit permission
- Other conduct which could reasonably be considered inappropriate in a
professional setting
## Enforcement Responsibilities
Community leaders are responsible for clarifying and enforcing our standards of
acceptable behavior and will take appropriate and fair corrective action in
response to any behavior that they deem inappropriate, threatening, offensive,
or harmful.
Community leaders have the right and responsibility to remove, edit, or reject
comments, commits, code, wiki edits, issues, and other contributions that are
not aligned to this Code of Conduct, and will communicate reasons for moderation
decisions when appropriate.
## Scope
This Code of Conduct applies within all community spaces, and also applies when
an individual is officially representing the community in public spaces.
Examples of representing our community include using an official e-mail address,
posting via an official social media account, or acting as an appointed
representative at an online or offline event.
## Enforcement
Instances of abusive, harassing, or otherwise unacceptable behavior may be
reported to the community leaders responsible for enforcement at
support@hoppscotch.io.
All complaints will be reviewed and investigated promptly and fairly.
All community leaders are obligated to respect the privacy and security of the
reporter of any incident.
## Enforcement Guidelines
Community leaders will follow these Community Impact Guidelines in determining
the consequences for any action they deem in violation of this Code of Conduct:
### 1. Correction
**Community Impact**: Use of inappropriate language or other behavior deemed
unprofessional or unwelcome in the community.
**Consequence**: A private, written warning from community leaders, providing
clarity around the nature of the violation and an explanation of why the
behavior was inappropriate. A public apology may be requested.
### 2. Warning
**Community Impact**: A violation through a single incident or series
of actions.
**Consequence**: A warning with consequences for continued behavior. No
interaction with the people involved, including unsolicited interaction with
those enforcing the Code of Conduct, for a specified period of time. This
includes avoiding interactions in community spaces as well as external channels
like social media. Violating these terms may lead to a temporary or
permanent ban.
### 3. Temporary Ban
**Community Impact**: A serious violation of community standards, including
sustained inappropriate behavior.
**Consequence**: A temporary ban from any sort of interaction or public
communication with the community for a specified period of time. No public or
private interaction with the people involved, including unsolicited interaction
with those enforcing the Code of Conduct, is allowed during this period.
Violating these terms may lead to a permanent ban.
### 4. Permanent Ban
**Community Impact**: Demonstrating a pattern of violation of community
standards, including sustained inappropriate behavior, harassment of an
individual, or aggression toward or disparagement of classes of individuals.
**Consequence**: A permanent ban from any sort of public interaction within
the community.
## Attribution
This Code of Conduct is adapted from the [Contributor Covenant][homepage],
version 2.0, available at
https://www.contributor-covenant.org/version/2/0/code_of_conduct.html.
Community Impact Guidelines were inspired by [Mozilla's code of conduct
enforcement ladder](https://github.com/mozilla/diversity).
[homepage]: https://www.contributor-covenant.org
For answers to common questions about this code of conduct, see the FAQ at
https://www.contributor-covenant.org/faq. Translations are available at
https://www.contributor-covenant.org/translations.

View File

@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2022
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View File

@@ -1,19 +1,29 @@
<div align="center">
<a href="https://hoppscotch.io">
<img
src="https://avatars.githubusercontent.com/u/56705483"
alt="Hoppscotch Logo"
height="64"
/>
</a>
</div>
<div align="center">
# Hoppscotch CLI <font size=2><sup>ALPHA</sup></font> # Hoppscotch CLI <font size=2><sup>ALPHA</sup></font>
A CLI to run Hoppscotch Test Scripts in CI environments. </div>
A CLI to run Hoppscotch test scripts in CI environments.
### **Commands:** ### **Commands:**
- `hopp test [options] [file]`: testing hoppscotch collection.json file - `hopp test [options] [file]`: testing hoppscotch collection.json file
### **Usage:** ### **Usage:**
```
```bash
hopp [options or commands] arguments hopp [options or commands] arguments
``` ```
### **Options:** ### **Options:**
- `-v`, `--ver`: see the current version of the CLI - `-v`, `--ver`: see the current version of the CLI
- `-h`, `--help`: display help for command - `-h`, `--help`: display help for command
@@ -35,21 +45,17 @@ hopp [options or commands] arguments
- Executes and outputs test-script response. - Executes and outputs test-script response.
#### Options: #### Options:
##### `-e <file_path>` / `--env <file_path>` ##### `-e <file_path>` / `--env <file_path>`
- Accepts path to env.json with contents in below format: - Accepts path to env.json with contents in below format:
```json ```json
{ {
"ENV1":"value1", "ENV1":"value1",
"ENV2":"value2" "ENV2":"value2"
} }
``` ```
- You can now access those variables using `pw.env.get('<var_name>')` - You can now access those variables using `pw.env.get('<var_name>')`
Taking the above example, `pw.env.get("ENV1")` will return `"value1"` Taking the above example, `pw.env.get("ENV1")` will return `"value1"`
## Install ## Install
@@ -69,59 +75,4 @@ npm i -g @hoppscotch/cli
## **Contributing:** ## **Contributing:**
When contributing to this repository, please first discuss the change you wish to make via issue, To get started contributing to the repository, please read **[CONTRIBUTING.md](./CONTRIBUTING.md)**
email, or any other method with the owners of this repository before making a change.
Please note we have a code of conduct, please follow it in all your interactions with the project.
## Pull Request Process
1. Ensure any install or build dependencies are removed before the end of the layer when doing a
build.
2. Update the README.md with details of changes to the interface, this includes new environment
variables, exposed ports, useful file locations and container parameters.
3. Increase the version numbers in any examples files and the README.md to the new version that this
Pull Request would represent. The versioning scheme we use is [SemVer](https://semver.org).
4. You may merge the Pull Request once you have the sign-off of two other developers, or if you
do not have permission to do that, you may request the second reviewer merge it for you.
## Set Up The Development Environment
1. After cloning the repository, execute the following commands:
```bash
pnpm install
pnpm run build
```
2. In order to test locally, you can use two types of package linking:
1. The 'pnpm exec' way (preferred since it does not hamper your original installation of the CLI):
```bash
pnpm link @hoppscotch/cli
// Then to use or test the CLI:
pnpm exec hopp
// After testing, to remove the package linking:
pnpm rm @hoppscotch/cli
```
2. The 'global' way (warning: this might override the globally installed CLI, if exists):
```bash
sudo pnpm link --global
// Then to use or test the CLI:
hopp
// After testing, to remove the package linking:
sudo pnpm rm --global @hoppscotch/cli
```
3. To use the Typescript watch scripts:
```bash
pnpm run dev
```

View File

@@ -1,6 +1,6 @@
{ {
"name": "@hoppscotch/cli", "name": "@hoppscotch/cli",
"version": "0.3.3", "version": "0.3.1",
"description": "A CLI to run Hoppscotch test scripts in CI environments.", "description": "A CLI to run Hoppscotch test scripts in CI environments.",
"homepage": "https://hoppscotch.io", "homepage": "https://hoppscotch.io",
"main": "dist/index.js", "main": "dist/index.js",
@@ -40,6 +40,9 @@
"@hoppscotch/js-sandbox": "workspace:^", "@hoppscotch/js-sandbox": "workspace:^",
"@relmify/jest-fp-ts": "^2.0.2", "@relmify/jest-fp-ts": "^2.0.2",
"@swc/core": "^1.2.181", "@swc/core": "^1.2.181",
"@types/axios": "^0.14.0",
"@types/chalk": "^2.2.0",
"@types/commander": "^2.12.2",
"@types/jest": "^27.4.1", "@types/jest": "^27.4.1",
"@types/lodash": "^4.14.181", "@types/lodash": "^4.14.181",
"@types/qs": "^6.9.7", "@types/qs": "^6.9.7",
@@ -55,7 +58,6 @@
"qs": "^6.10.3", "qs": "^6.10.3",
"ts-jest": "^27.1.4", "ts-jest": "^27.1.4",
"tsup": "^5.12.7", "tsup": "^5.12.7",
"typescript": "^4.6.4", "typescript": "^4.6.4"
"zod": "^3.22.2"
} }
} }

View File

@@ -42,17 +42,12 @@ export const handleError = <T extends HoppErrorCode>(error: HoppError<T>) => {
switch (error.code) { switch (error.code) {
case "FILE_NOT_FOUND": case "FILE_NOT_FOUND":
ERROR_MSG = `File doesn't exist: ${error.path}`; ERROR_MSG = `File doesn't exists: ${error.path}`;
break; break;
case "UNKNOWN_COMMAND": case "UNKNOWN_COMMAND":
ERROR_MSG = `Unavailable command: ${error.command}`; ERROR_MSG = `Unavailable command: ${error.command}`;
break; break;
case "MALFORMED_ENV_FILE": case "MALFORMED_ENV_FILE":
ERROR_MSG = `The environment file is not of the correct format.`;
break;
case "BULK_ENV_FILE":
ERROR_MSG = `CLI doesn't support bulk environments export.`;
break;
case "MALFORMED_COLLECTION": case "MALFORMED_COLLECTION":
ERROR_MSG = `${error.path}\n${parseErrorData(error.data)}`; ERROR_MSG = `${error.path}\n${parseErrorData(error.data)}`;
break; break;
@@ -87,4 +82,4 @@ export const handleError = <T extends HoppErrorCode>(error: HoppError<T>) => {
if (!S.isEmpty(ERROR_MSG)) { if (!S.isEmpty(ERROR_MSG)) {
console.error(ERROR_CODE, ERROR_MSG); console.error(ERROR_CODE, ERROR_MSG);
} }
}; };

View File

@@ -1,45 +1,27 @@
import { error } from "../../types/errors"; import { error } from "../../types/errors";
import { import { HoppEnvs, HoppEnvPair } from "../../types/request";
HoppEnvs,
HoppEnvPair,
HoppEnvKeyPairObject,
HoppEnvExportObject,
HoppBulkEnvExportObject,
} from "../../types/request";
import { readJsonFile } from "../../utils/mutators"; import { readJsonFile } from "../../utils/mutators";
/** /**
* Parses env json file for given path and validates the parsed env json object. * Parses env json file for given path and validates the parsed env json object.
* @param path Path of env.json file to be parsed. * @param path Path of env.json file to be parsed.
* @returns For successful parsing we get HoppEnvs object. * @returns For successful parsing we get HoppEnvs object.
*/ */
export async function parseEnvsData(path: string) { export async function parseEnvsData(path: string) {
const contents = await readJsonFile(path); const contents = await readJsonFile(path)
const envPairs: Array<HoppEnvPair> = [];
const HoppEnvKeyPairResult = HoppEnvKeyPairObject.safeParse(contents);
const HoppEnvExportObjectResult = HoppEnvExportObject.safeParse(contents);
const HoppBulkEnvExportObjectResult =
HoppBulkEnvExportObject.safeParse(contents);
// CLI doesnt support bulk environments export. if(!(contents && typeof contents === "object" && !Array.isArray(contents))) {
// Hence we check for this case and throw an error if it matches the format. throw error({ code: "MALFORMED_ENV_FILE", path, data: null })
if (HoppBulkEnvExportObjectResult.success) {
throw error({ code: "BULK_ENV_FILE", path, data: error });
} }
// Checks if the environment file is of the correct format. const envPairs: Array<HoppEnvPair> = []
// If it doesnt match either of them, we throw an error.
if (!(HoppEnvKeyPairResult.success || HoppEnvExportObjectResult.success)) {
throw error({ code: "MALFORMED_ENV_FILE", path, data: error });
}
if (HoppEnvKeyPairResult.success) { for( const [key,value] of Object.entries(contents)) {
for (const [key, value] of Object.entries(HoppEnvKeyPairResult.data)) { if(typeof value !== "string") {
envPairs.push({ key, value }); throw error({ code: "MALFORMED_ENV_FILE", path, data: {value: value} })
} }
} else if (HoppEnvExportObjectResult.success) {
const { key, value } = HoppEnvExportObjectResult.data.variables[0];
envPairs.push({ key, value });
}
return <HoppEnvs>{ global: [], selected: envPairs }; envPairs.push({key, value})
}
return <HoppEnvs>{ global: [], selected: envPairs }
} }

View File

@@ -24,7 +24,6 @@ type HoppErrors = {
REQUEST_ERROR: HoppErrorData; REQUEST_ERROR: HoppErrorData;
INVALID_ARGUMENT: HoppErrorData; INVALID_ARGUMENT: HoppErrorData;
MALFORMED_ENV_FILE: HoppErrorPath & HoppErrorData; MALFORMED_ENV_FILE: HoppErrorPath & HoppErrorData;
BULK_ENV_FILE: HoppErrorPath & HoppErrorData;
INVALID_FILE_TYPE: HoppErrorData; INVALID_FILE_TYPE: HoppErrorData;
}; };

View File

@@ -1,7 +1,6 @@
import { HoppCollection, HoppRESTRequest } from "@hoppscotch/data"; import { HoppCollection, HoppRESTRequest } from "@hoppscotch/data";
import { TestReport } from "../interfaces/response"; import { TestReport } from "../interfaces/response";
import { HoppCLIError } from "./errors"; import { HoppCLIError } from "./errors";
import { z } from "zod";
export type FormDataEntry = { export type FormDataEntry = {
key: string; key: string;
@@ -10,22 +9,6 @@ export type FormDataEntry = {
export type HoppEnvPair = { key: string; value: string }; export type HoppEnvPair = { key: string; value: string };
export const HoppEnvKeyPairObject = z.record(z.string(), z.string());
// Shape of the single environment export object that is exported from the app.
export const HoppEnvExportObject = z.object({
name: z.string(),
variables: z.array(
z.object({
key: z.string(),
value: z.string(),
})
),
});
// Shape of the bulk environment export object that is exported from the app.
export const HoppBulkEnvExportObject = z.array(HoppEnvExportObject);
export type HoppEnvs = { export type HoppEnvs = {
global: HoppEnvPair[]; global: HoppEnvPair[];
selected: HoppEnvPair[]; selected: HoppEnvPair[];

View File

@@ -6,6 +6,7 @@ module.exports = {
env: { env: {
browser: true, browser: true,
node: true, node: true,
jest: true,
}, },
parserOptions: { parserOptions: {
sourceType: "module", sourceType: "module",
@@ -29,18 +30,8 @@ module.exports = {
"import/named": "off", // because, named import issue with typescript see: https://github.com/typescript-eslint/typescript-eslint/issues/154 "import/named": "off", // because, named import issue with typescript see: https://github.com/typescript-eslint/typescript-eslint/issues/154
"no-console": "off", "no-console": "off",
"no-debugger": process.env.HOPP_LINT_FOR_PROD === "true" ? "error" : "warn", "no-debugger": process.env.HOPP_LINT_FOR_PROD === "true" ? "error" : "warn",
"prettier/prettier": [ "prettier/prettier":
process.env.HOPP_LINT_FOR_PROD === "true" ? "error" : "warn", process.env.HOPP_LINT_FOR_PROD === "true" ? "error" : "warn",
{},
{
semi: false,
trailingComma: "es5",
singleQuote: false,
printWidth: 80,
useTabs: false,
tabWidth: 2,
},
],
"vue/multi-word-component-names": "off", "vue/multi-word-component-names": "off",
"vue/no-side-effects-in-computed-properties": "off", "vue/no-side-effects-in-computed-properties": "off",
"import/no-named-as-default": "off", "import/no-named-as-default": "off",

View File

@@ -1,8 +1,3 @@
module.exports = { module.exports = {
semi: false, semi: false
trailingComma: "es5",
singleQuote: false,
printWidth: 80,
useTabs: false,
tabWidth: 2
} }

View File

@@ -1 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="currentColor" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-star"><polygon points="12 2 15.09 8.26 22 9.27 17 14.14 18.18 21.02 12 17.77 5.82 21.02 7 14.14 2 9.27 8.91 8.26 12 2"/></svg>

Before

Width:  |  Height:  |  Size: 337 B

View File

@@ -4,7 +4,6 @@
@apply after:backface-hidden; @apply after:backface-hidden;
@apply selection:bg-accentDark; @apply selection:bg-accentDark;
@apply selection:text-accentContrast; @apply selection:text-accentContrast;
@apply overscroll-none;
} }
:root { :root {
@@ -31,10 +30,6 @@
@apply h-0; @apply h-0;
} }
.no-scrollbar {
scrollbar-width: none;
}
input::placeholder, input::placeholder,
textarea::placeholder, textarea::placeholder,
.cm-placeholder { .cm-placeholder {
@@ -137,10 +132,10 @@ a {
.cm-tooltip { .cm-tooltip {
.tippy-box { .tippy-box {
@apply shadow-none #{!important}; @apply shadow-none;
@apply fixed; @apply fixed;
@apply inline-flex; @apply inline-flex;
@apply -mt-7.5; @apply -mt-8;
} }
} }
@@ -170,6 +165,12 @@ a {
@apply truncate; @apply truncate;
@apply sm:inline-flex; @apply sm:inline-flex;
} }
.env-icon {
@apply transition;
@apply inline-flex;
@apply items-center;
}
} }
.tippy-svg-arrow { .tippy-svg-arrow {
@@ -188,11 +189,10 @@ a {
@apply border-solid border-dividerDark; @apply border-solid border-dividerDark;
@apply rounded; @apply rounded;
@apply shadow-lg; @apply shadow-lg;
@apply max-w-[45vw] #{!important};
.tippy-content { .tippy-content {
@apply flex flex-col; @apply flex flex-col;
@apply max-h-[45vh]; @apply max-h-56;
@apply items-stretch; @apply items-stretch;
@apply overflow-y-auto; @apply overflow-y-auto;
@apply text-secondary text-body; @apply text-secondary text-body;
@@ -200,10 +200,6 @@ a {
@apply leading-normal; @apply leading-normal;
@apply focus:outline-none; @apply focus:outline-none;
scroll-behavior: smooth; scroll-behavior: smooth;
& > span {
@apply block #{!important};
}
} }
.tippy-svg-arrow { .tippy-svg-arrow {
@@ -219,7 +215,6 @@ a {
[data-v-tippy] { [data-v-tippy] {
@apply flex flex-1; @apply flex flex-1;
@apply truncate;
} }
[interactive] > div { [interactive] > div {
@@ -330,7 +325,7 @@ pre.ace_editor {
@apply after:font-icon; @apply after:font-icon;
@apply after:text-current; @apply after:text-current;
@apply after:right-3; @apply after:right-3;
@apply after:content-["\e5cf"]; @apply after:content-["\e313"];
@apply after:text-lg; @apply after:text-lg;
} }
@@ -485,10 +480,6 @@ pre.ace_editor {
} }
} }
.cm-scroller {
@apply overscroll-y-auto;
}
.cm-editor { .cm-editor {
.cm-line::selection { .cm-line::selection {
@apply bg-accentDark #{!important}; @apply bg-accentDark #{!important};
@@ -501,22 +492,6 @@ pre.ace_editor {
} }
} }
.cm-panel.cm-search [name="close"] {
@apply flex;
@apply items-center;
@apply justify-center;
@apply min-h-5;
@apply min-w-5;
@apply bg-primaryDark #{!important};
@apply sticky #{!important};
@apply right-0 #{!important};
@apply ml-auto #{!important};
@apply my-auto #{!important};
@apply rounded #{!important};
@apply outline #{!important};
@apply outline-divider #{!important};
}
.shortcut-key { .shortcut-key {
@apply inline-flex; @apply inline-flex;
@apply font-sans; @apply font-sans;
@@ -592,11 +567,3 @@ details[open] summary .indicator {
@apply rounded; @apply rounded;
@apply border-0; @apply border-0;
} }
.gql-operation-not-highlight {
@apply opacity-50;
}
.gql-operation-highlight {
@apply opacity-100;
}

View File

@@ -1,23 +1,8 @@
@mixin base-theme { @mixin base-theme {
--font-sans: "Inter Variable", sans-serif; --font-sans: "Inter", sans-serif;
--font-icon: "Material Symbols Rounded Variable"; --font-mono: "Roboto Mono", monospace;
--font-mono: "Roboto Mono Variable", monospace; --font-icon: "Material Icons";
--font-size-body: 0.75rem; --font-size-tiny: calc(var(--font-size-body) - 0.062rem);
--font-size-tiny: 0.688rem;
--line-height-body: 1rem;
--upper-primary-sticky-fold: 4.125rem;
--upper-secondary-sticky-fold: 6.188rem;
--upper-tertiary-sticky-fold: 8.25rem;
--upper-fourth-sticky-fold: 10.2rem;
--upper-mobile-primary-sticky-fold: 6.625rem;
--upper-mobile-secondary-sticky-fold: 8.688rem;
--upper-mobile-sticky-fold: 10.75rem;
--upper-mobile-tertiary-sticky-fold: 8.25rem;
--lower-primary-sticky-fold: 3rem;
--lower-secondary-sticky-fold: 5.063rem;
--lower-tertiary-sticky-fold: 7.125rem;
--lower-fourth-sticky-fold: 9.188rem;
--sidebar-primary-sticky-fold: 2rem;
} }
@mixin dark-theme { @mixin dark-theme {
@@ -215,26 +200,23 @@
:root { :root {
@include base-theme; @include base-theme;
@include dark-theme; @include dark-theme;
@include dark-editor-theme;
@include green-theme; @include green-theme;
@include dark-editor-theme;
} }
:root.light { :root.light {
@include light-theme; @include light-theme;
@include light-editor-theme; @include light-editor-theme;
color-scheme: light;
} }
:root.dark { :root.dark {
@include dark-theme; @include dark-theme;
@include dark-editor-theme; @include dark-editor-theme;
color-scheme: dark;
} }
:root.black { :root.black {
@include black-theme; @include black-theme;
@include black-editor-theme; @include black-editor-theme;
color-scheme: dark;
} }
:root[data-accent="blue"] { :root[data-accent="blue"] {
@@ -272,3 +254,63 @@
:root[data-accent="yellow"] { :root[data-accent="yellow"] {
@include yellow-theme; @include yellow-theme;
} }
@mixin font-small {
--font-size-body: 0.75rem;
--line-height-body: 1rem;
--upper-primary-sticky-fold: 4.125rem;
--upper-secondary-sticky-fold: 6.188rem;
--upper-tertiary-sticky-fold: 8.25rem;
--upper-mobile-primary-sticky-fold: 6.625rem;
--upper-mobile-secondary-sticky-fold: 8.688rem;
--upper-mobile-sticky-fold: 10.75rem;
--upper-mobile-tertiary-sticky-fold: 8.25rem;
--lower-primary-sticky-fold: 3rem;
--lower-secondary-sticky-fold: 5.063rem;
--lower-tertiary-sticky-fold: 7.125rem;
--sidebar-primary-sticky-fold: 2rem;
}
@mixin font-medium {
--font-size-body: 0.875rem;
--line-height-body: 1.25rem;
--upper-primary-sticky-fold: 4.375rem;
--upper-secondary-sticky-fold: 6.688rem;
--upper-tertiary-sticky-fold: 9rem;
--upper-mobile-primary-sticky-fold: 7.125rem;
--upper-mobile-secondary-sticky-fold: 9.438rem;
--upper-mobile-sticky-fold: 11.75rem;
--upper-mobile-tertiary-sticky-fold: 9rem;
--lower-primary-sticky-fold: 3.25rem;
--lower-secondary-sticky-fold: 5.563rem;
--lower-tertiary-sticky-fold: 7.875rem;
--sidebar-primary-sticky-fold: 2.25rem;
}
@mixin font-large {
--font-size-body: 1rem;
--line-height-body: 1.5rem;
--upper-primary-sticky-fold: 4.625rem;
--upper-secondary-sticky-fold: 7.188rem;
--upper-tertiary-sticky-fold: 9.75rem;
--upper-mobile-primary-sticky-fold: 7.625rem;
--upper-mobile-secondary-sticky-fold: 10.188rem;
--upper-mobile-sticky-fold: 12.75rem;
--upper-mobile-tertiary-sticky-fold: 9.75rem;
--lower-primary-sticky-fold: 3.5rem;
--lower-secondary-sticky-fold: 6.063rem;
--lower-tertiary-sticky-fold: 8.625rem;
--sidebar-primary-sticky-fold: 2.5rem;
}
:root[data-font-size="small"] {
@include font-small;
}
:root[data-font-size="medium"] {
@include font-medium;
}
:root[data-font-size="large"] {
@include font-large;
}

View File

@@ -5,7 +5,6 @@
"choose_file": "Kies 'n lêer", "choose_file": "Kies 'n lêer",
"clear": "Duidelik", "clear": "Duidelik",
"clear_all": "Maak alles skoon", "clear_all": "Maak alles skoon",
"clear_history": "Clear all History",
"close": "Close", "close": "Close",
"connect": "Koppel", "connect": "Koppel",
"connecting": "Connecting", "connecting": "Connecting",
@@ -32,7 +31,6 @@
"paste": "Paste", "paste": "Paste",
"prettify": "Prettify", "prettify": "Prettify",
"remove": "Verwyder", "remove": "Verwyder",
"rename": "Rename",
"restore": "Herstel", "restore": "Herstel",
"save": "Stoor", "save": "Stoor",
"scroll_to_bottom": "Scroll to bottom", "scroll_to_bottom": "Scroll to bottom",
@@ -79,8 +77,6 @@
"search": "Soek", "search": "Soek",
"share": "Deel", "share": "Deel",
"shortcuts": "Kortpaaie", "shortcuts": "Kortpaaie",
"social_description": "Follow us on social media to stay updated with the latest news, updates and releases.",
"social_links": "Social links",
"spotlight": "Kollig", "spotlight": "Kollig",
"status": "Status", "status": "Status",
"status_description": "Check the status of the website", "status_description": "Check the status of the website",
@@ -135,15 +131,12 @@
"renamed": "Versameling hernoem", "renamed": "Versameling hernoem",
"request_in_use": "Request in use", "request_in_use": "Request in use",
"save_as": "Stoor as", "save_as": "Stoor as",
"save_to_collection": "Save to Collection",
"select": "Kies 'n versameling", "select": "Kies 'n versameling",
"select_location": "Kies ligging", "select_location": "Kies ligging",
"select_team": "Kies 'n span", "select_team": "Kies 'n span",
"team_collections": "Spanversamelings" "team_collections": "Spanversamelings"
}, },
"confirm": { "confirm": {
"close_unsaved_tab": "Are you sure you want to close this tab?",
"close_unsaved_tabs": "Are you sure you want to close all tabs? {count} unsaved tabs will be lost.",
"exit_team": "Are you sure you want to leave this team?", "exit_team": "Are you sure you want to leave this team?",
"logout": "Weet u seker dat u wil afmeld?", "logout": "Weet u seker dat u wil afmeld?",
"remove_collection": "Weet u seker dat u hierdie versameling permanent wil uitvee?", "remove_collection": "Weet u seker dat u hierdie versameling permanent wil uitvee?",
@@ -157,11 +150,6 @@
"save_unsaved_tab": "Do you want to save changes made in this tab?", "save_unsaved_tab": "Do you want to save changes made in this tab?",
"sync": "Is u seker dat u hierdie werkruimte wil sinkroniseer?" "sync": "Is u seker dat u hierdie werkruimte wil sinkroniseer?"
}, },
"context_menu": {
"add_parameters": "Add to parameters",
"open_request_in_new_tab": "Open request in new tab",
"set_environment_variable": "Set as variable"
},
"count": { "count": {
"header": "Koptekst {count}", "header": "Koptekst {count}",
"message": "Boodskap {count}", "message": "Boodskap {count}",
@@ -204,31 +192,17 @@
"create_new": "Skep nuwe omgewing", "create_new": "Skep nuwe omgewing",
"created": "Environment created", "created": "Environment created",
"deleted": "Environment deletion", "deleted": "Environment deletion",
"duplicated": "Environment duplicated",
"edit": "Bewerk omgewing", "edit": "Bewerk omgewing",
"empty_variables": "No variables",
"global": "Global",
"global_variables": "Global variables",
"invalid_name": "Gee 'n geldige naam vir die omgewing", "invalid_name": "Gee 'n geldige naam vir die omgewing",
"list": "Environment variables",
"my_environments": "My Environments", "my_environments": "My Environments",
"name": "Name",
"nested_overflow": "nested environment variables are limited to 10 levels", "nested_overflow": "nested environment variables are limited to 10 levels",
"new": "Nuwe omgewing", "new": "Nuwe omgewing",
"no_active_environment": "No active environment",
"no_environment": "Geen omgewing nie", "no_environment": "Geen omgewing nie",
"no_environment_description": "No environments were selected. Choose what to do with the following variables.", "no_environment_description": "No environments were selected. Choose what to do with the following variables.",
"quick_peek": "Environment Quick Peek",
"replace_with_variable": "Replace with variable",
"scope": "Scope",
"select": "Kies omgewing", "select": "Kies omgewing",
"set": "Set environment",
"set_as_environment": "Set as environment",
"team_environments": "Team Environments", "team_environments": "Team Environments",
"title": "Omgewings", "title": "Omgewings",
"updated": "Environment updation", "updated": "Environment updation",
"value": "Value",
"variable": "Variable",
"variable_list": "Veranderlike lys" "variable_list": "Veranderlike lys"
}, },
"error": { "error": {
@@ -252,7 +226,6 @@
"no_duration": "Geen duur nie", "no_duration": "Geen duur nie",
"no_results_found": "No matches found", "no_results_found": "No matches found",
"page_not_found": "This page could not be found", "page_not_found": "This page could not be found",
"proxy_error": "Proxy error",
"script_fail": "Kon nie voorafversoekskrip uitvoer nie", "script_fail": "Kon nie voorafversoekskrip uitvoer nie",
"something_went_wrong": "Iets het verkeerd geloop", "something_went_wrong": "Iets het verkeerd geloop",
"test_script_fail": "Could not execute post-request script" "test_script_fail": "Could not execute post-request script"
@@ -278,13 +251,9 @@
"renamed": "Vouer hernoem" "renamed": "Vouer hernoem"
}, },
"graphql": { "graphql": {
"connection_switch_confirm": "Do you want to connect with the latest GraphQL endpoint?",
"connection_switch_new_url": "Switching to a tab will disconnected you from the active GraphQL connection. New connection URL is",
"connection_switch_url": "You're connected to a GraphQL endpoint the connection URL is",
"mutations": "Mutasies", "mutations": "Mutasies",
"schema": "Skema", "schema": "Skema",
"subscriptions": "Inskrywings", "subscriptions": "Inskrywings"
"switch_connection": "Switch connection"
}, },
"group": { "group": {
"time": "Time", "time": "Time",
@@ -338,36 +307,13 @@
"json_description": "Import collections from a Hoppscotch Collections JSON file", "json_description": "Import collections from a Hoppscotch Collections JSON file",
"title": "Invoer" "title": "Invoer"
}, },
"inspections": {
"description": "Inspect possible errors",
"environment": {
"add_environment": "Add to Environment",
"not_found": "Environment variable “{environment}” not found."
},
"header": {
"cookie": "The browser doesn't allow Hoppscotch to set the Cookie Header. While we're working on the Hoppscotch Desktop App (coming soon), please use the Authorization Header instead."
},
"response": {
"401_error": "Please check your authentication credentials.",
"404_error": "Please check your request URL and method type.",
"cors_error": "Please check your Cross-Origin Resource Sharing configuration.",
"default_error": "Please check your request.",
"network_error": "Please check your network connection."
},
"title": "Inspector",
"url": {
"extension_not_installed": "Extension not installed.",
"extension_unknown_origin": "Make sure you've added the API endpoint's origin to the Hoppscotch Browser Extension list.",
"extention_enable_action": "Enable Browser Extension",
"extention_not_enabled": "Extension not enabled."
}
},
"layout": { "layout": {
"collapse_collection": "Collapse or Expand Collections", "collapse_collection": "Collapse or Expand Collections",
"collapse_sidebar": "Collapse or Expand the sidebar", "collapse_sidebar": "Collapse or Expand the sidebar",
"column": "Vertical layout", "column": "Vertical layout",
"name": "Layout", "name": "Layout",
"row": "Horizontal layout" "row": "Horizontal layout",
"zen_mode": "Zen -modus"
}, },
"modal": { "modal": {
"close_unsaved_tab": "You have unsaved changes", "close_unsaved_tab": "You have unsaved changes",
@@ -472,7 +418,6 @@
"payload": "Nuttingslading", "payload": "Nuttingslading",
"query": "Navraag", "query": "Navraag",
"raw_body": "Rou versoeksliggaam", "raw_body": "Rou versoeksliggaam",
"rename": "Rename Request",
"renamed": "Versoek hernoem", "renamed": "Versoek hernoem",
"run": "Hardloop", "run": "Hardloop",
"save": "Stoor", "save": "Stoor",
@@ -480,7 +425,6 @@
"saved": "Versoek gestoor", "saved": "Versoek gestoor",
"share": "Deel", "share": "Deel",
"share_description": "Share Hoppscotch with your friends", "share_description": "Share Hoppscotch with your friends",
"stop": "Stop",
"title": "Versoek", "title": "Versoek",
"type": "Soort versoek", "type": "Soort versoek",
"url": "URL", "url": "URL",
@@ -488,7 +432,6 @@
"view_my_links": "View my links" "view_my_links": "View my links"
}, },
"response": { "response": {
"audio": "Audio",
"body": "Reaksie liggaam", "body": "Reaksie liggaam",
"filter_response_body": "Filter JSON response body (uses JSONPath syntax)", "filter_response_body": "Filter JSON response body (uses JSONPath syntax)",
"headers": "Opskrifte", "headers": "Opskrifte",
@@ -502,7 +445,6 @@
"status": "Status", "status": "Status",
"time": "Tyd", "time": "Tyd",
"title": "Reaksie", "title": "Reaksie",
"video": "Video",
"waiting_for_connection": "wag vir verbinding", "waiting_for_connection": "wag vir verbinding",
"xml": "XML" "xml": "XML"
}, },
@@ -515,6 +457,7 @@
"account_name_description": "Dit is u vertoonnaam.", "account_name_description": "Dit is u vertoonnaam.",
"background": "Agtergrond", "background": "Agtergrond",
"black_mode": "Swart", "black_mode": "Swart",
"change_font_size": "Verander lettergrootte",
"choose_language": "Kies taal", "choose_language": "Kies taal",
"dark_mode": "Donker", "dark_mode": "Donker",
"delete_account": "Delete account", "delete_account": "Delete account",
@@ -527,6 +470,10 @@
"extensions": "Uitbreidings", "extensions": "Uitbreidings",
"extensions_use_toggle": "Gebruik die blaaieruitbreiding om versoeke te stuur (indien teenwoordig)", "extensions_use_toggle": "Gebruik die blaaieruitbreiding om versoeke te stuur (indien teenwoordig)",
"follow": "Follow Us", "follow": "Follow Us",
"font_size": "Skrifgrootte",
"font_size_large": "Groot",
"font_size_medium": "Medium",
"font_size_small": "Klein",
"interceptor": "Onderskepper", "interceptor": "Onderskepper",
"interceptor_description": "Middelware tussen toepassing en API's.", "interceptor_description": "Middelware tussen toepassing en API's.",
"language": "Taal", "language": "Taal",
@@ -591,27 +538,19 @@
"settings": "Gaan na die instellingsbladsy", "settings": "Gaan na die instellingsbladsy",
"title": "Navigasie" "title": "Navigasie"
}, },
"others": {
"prettify": "Prettify Editor's Content",
"title": "Others"
},
"request": { "request": {
"copy_request_link": "Kopieer versoekskakel", "copy_request_link": "Kopieer versoekskakel",
"delete_method": "Kies DELETE metode", "delete_method": "Kies DELETE metode",
"get_method": "Kies GET -metode", "get_method": "Kies GET -metode",
"head_method": "Kies HOOF metode", "head_method": "Kies HOOF metode",
"import_curl": "Import cURL",
"method": "Metode", "method": "Metode",
"next_method": "Kies Volgende metode", "next_method": "Kies Volgende metode",
"post_method": "Kies POST -metode", "post_method": "Kies POST -metode",
"previous_method": "Kies Vorige metode", "previous_method": "Kies Vorige metode",
"put_method": "Kies PUT -metode", "put_method": "Kies PUT -metode",
"rename": "Rename Request",
"reset_request": "Herstel versoek", "reset_request": "Herstel versoek",
"save_request": "Save Request",
"save_to_collections": "Stoor in versamelings", "save_to_collections": "Stoor in versamelings",
"send_request": "Stuur versoek", "send_request": "Stuur versoek",
"show_code": "Generate code snippet",
"title": "Versoek" "title": "Versoek"
}, },
"response": { "response": {
@@ -641,85 +580,6 @@
"log": "Meld", "log": "Meld",
"url": "URL" "url": "URL"
}, },
"spotlight": {
"change_language": "Change Language",
"environments": {
"delete": "Delete current environment",
"duplicate": "Duplicate current environment",
"duplicate_global": "Duplicate global environment",
"edit": "Edit current environment",
"edit_global": "Edit global environment",
"new": "Create new environment",
"new_variable": "Create a new environment variable",
"title": "Environments"
},
"general": {
"chat": "Chat with support",
"help_menu": "Help and support",
"open_docs": "Read Documentation",
"open_github": "Open GitHub repository",
"open_keybindings": "Keyboard shortcuts",
"social": "Social",
"title": "General"
},
"graphql": {
"connect": "Connect to server",
"disconnect": "Disconnect from server"
},
"miscellaneous": {
"invite": "Invite your friends to Hoppscotch",
"title": "Miscellaneous"
},
"request": {
"save_as_new": "Save as new request",
"select_method": "Select method",
"switch_to": "Switch to",
"tab_authorization": "Authorization tab",
"tab_body": "Body tab",
"tab_headers": "Headers tab",
"tab_parameters": "Parameters tab",
"tab_pre_request_script": "Pre-request script tab",
"tab_query": "Query tab",
"tab_tests": "Tests tab",
"tab_variables": "Variables tab"
},
"response": {
"copy": "Copy response",
"download": "Download response as file",
"title": "Response"
},
"section": {
"interceptor": "Interceptor",
"interface": "Interface",
"theme": "Theme",
"user": "User"
},
"settings": {
"change_interceptor": "Change Interceptor",
"change_language": "Change Language",
"theme": {
"black": "Black",
"dark": "Dark",
"light": "Light",
"system": "System preference"
}
},
"tab": {
"close_current": "Close current tab",
"close_others": "Close all other tabs",
"duplicate": "Duplicate current tab",
"new_tab": "Open a new tab",
"title": "Tabs"
},
"workspace": {
"delete": "Delete current team",
"edit": "Edit current team",
"invite": "Invite people to team",
"new": "Create new team",
"switch_to_personal": "Switch to your personal workspace",
"title": "Teams"
}
},
"sse": { "sse": {
"event_type": "Soort gebeurtenis", "event_type": "Soort gebeurtenis",
"log": "Meld", "log": "Meld",
@@ -777,11 +637,8 @@
"tab": { "tab": {
"authorization": "Magtiging", "authorization": "Magtiging",
"body": "Liggaam", "body": "Liggaam",
"close": "Close Tab",
"close_others": "Close other Tabs",
"collections": "Versamelings", "collections": "Versamelings",
"documentation": "Dokumentasie", "documentation": "Dokumentasie",
"duplicate": "Duplicate Tab",
"environments": "Environments", "environments": "Environments",
"headers": "Opskrifte", "headers": "Opskrifte",
"history": "Geskiedenis", "history": "Geskiedenis",

View File

@@ -5,7 +5,6 @@
"choose_file": "اختيار ملف", "choose_file": "اختيار ملف",
"clear": "امسح", "clear": "امسح",
"clear_all": "امسح الكل", "clear_all": "امسح الكل",
"clear_history": "Clear all History",
"close": "Close", "close": "Close",
"connect": "الاتصال", "connect": "الاتصال",
"connecting": "Connecting", "connecting": "Connecting",
@@ -32,7 +31,6 @@
"paste": "لصق", "paste": "لصق",
"prettify": "جمال", "prettify": "جمال",
"remove": "ازالة", "remove": "ازالة",
"rename": "Rename",
"restore": "اعادة", "restore": "اعادة",
"save": "حفظ", "save": "حفظ",
"scroll_to_bottom": "Scroll to bottom", "scroll_to_bottom": "Scroll to bottom",
@@ -79,8 +77,6 @@
"search": "بحث", "search": "بحث",
"share": "يشارك", "share": "يشارك",
"shortcuts": "الاختصارات", "shortcuts": "الاختصارات",
"social_description": "Follow us on social media to stay updated with the latest news, updates and releases.",
"social_links": "Social links",
"spotlight": "أضواء كاشفة", "spotlight": "أضواء كاشفة",
"status": "حالة", "status": "حالة",
"status_description": "Check the status of the website", "status_description": "Check the status of the website",
@@ -135,15 +131,12 @@
"renamed": "تمت إعادة تسمية المجموعة", "renamed": "تمت إعادة تسمية المجموعة",
"request_in_use": "Request in use", "request_in_use": "Request in use",
"save_as": "حفظ باسم", "save_as": "حفظ باسم",
"save_to_collection": "Save to Collection",
"select": "حدد مجموعة", "select": "حدد مجموعة",
"select_location": "اختر موقعا", "select_location": "اختر موقعا",
"select_team": "اختر فريقًا", "select_team": "اختر فريقًا",
"team_collections": "مجموعات الفريق" "team_collections": "مجموعات الفريق"
}, },
"confirm": { "confirm": {
"close_unsaved_tab": "Are you sure you want to close this tab?",
"close_unsaved_tabs": "Are you sure you want to close all tabs? {count} unsaved tabs will be lost.",
"exit_team": "هل أنت متأكد أنك تريد مغادرة هذا الفريق؟", "exit_team": "هل أنت متأكد أنك تريد مغادرة هذا الفريق؟",
"logout": "هل أنت متأكد أنك تريد تسجيل الخروج؟", "logout": "هل أنت متأكد أنك تريد تسجيل الخروج؟",
"remove_collection": "هل أنت متأكد أنك تريد حذف هذه المجموعة نهائيًا؟", "remove_collection": "هل أنت متأكد أنك تريد حذف هذه المجموعة نهائيًا؟",
@@ -157,11 +150,6 @@
"save_unsaved_tab": "Do you want to save changes made in this tab?", "save_unsaved_tab": "Do you want to save changes made in this tab?",
"sync": "هل أنت متأكد أنك تريد مزامنة مساحة العمل هذه؟" "sync": "هل أنت متأكد أنك تريد مزامنة مساحة العمل هذه؟"
}, },
"context_menu": {
"add_parameters": "Add to parameters",
"open_request_in_new_tab": "Open request in new tab",
"set_environment_variable": "Set as variable"
},
"count": { "count": {
"header": "رأس {count}", "header": "رأس {count}",
"message": "الرسالة {count}", "message": "الرسالة {count}",
@@ -204,31 +192,17 @@
"create_new": "انشاء بيئة جديدة", "create_new": "انشاء بيئة جديدة",
"created": "Environment created", "created": "Environment created",
"deleted": "حذف بيئة العمل", "deleted": "حذف بيئة العمل",
"duplicated": "Environment duplicated",
"edit": "تحرير البيئة", "edit": "تحرير البيئة",
"empty_variables": "No variables",
"global": "Global",
"global_variables": "Global variables",
"invalid_name": "الرجاء تقديم اسم صالح للبيئة", "invalid_name": "الرجاء تقديم اسم صالح للبيئة",
"list": "Environment variables",
"my_environments": "My Environments", "my_environments": "My Environments",
"name": "Name",
"nested_overflow": "nested environment variables are limited to 10 levels", "nested_overflow": "nested environment variables are limited to 10 levels",
"new": "بيئة جديدة", "new": "بيئة جديدة",
"no_active_environment": "No active environment",
"no_environment": "لا بيئة", "no_environment": "لا بيئة",
"no_environment_description": "لم يتم اختيار أي بيئة عمل. اختر ما تريد فعله بالمتغيرات التالية.", "no_environment_description": "لم يتم اختيار أي بيئة عمل. اختر ما تريد فعله بالمتغيرات التالية.",
"quick_peek": "Environment Quick Peek",
"replace_with_variable": "Replace with variable",
"scope": "Scope",
"select": "حدد البيئة", "select": "حدد البيئة",
"set": "Set environment",
"set_as_environment": "Set as environment",
"team_environments": "Team Environments", "team_environments": "Team Environments",
"title": "البيئات", "title": "البيئات",
"updated": "تحديث بيئة العمل", "updated": "تحديث بيئة العمل",
"value": "Value",
"variable": "Variable",
"variable_list": "قائمة متغيرة" "variable_list": "قائمة متغيرة"
}, },
"error": { "error": {
@@ -252,7 +226,6 @@
"no_duration": "لا مدة", "no_duration": "لا مدة",
"no_results_found": "No matches found", "no_results_found": "No matches found",
"page_not_found": "This page could not be found", "page_not_found": "This page could not be found",
"proxy_error": "Proxy error",
"script_fail": "تعذر تنفيذ نص الطلب المسبق", "script_fail": "تعذر تنفيذ نص الطلب المسبق",
"something_went_wrong": "هناك خطأ ما", "something_went_wrong": "هناك خطأ ما",
"test_script_fail": "Could not execute post-request script" "test_script_fail": "Could not execute post-request script"
@@ -278,13 +251,9 @@
"renamed": "تمت إعادة تسمية المجلد" "renamed": "تمت إعادة تسمية المجلد"
}, },
"graphql": { "graphql": {
"connection_switch_confirm": "Do you want to connect with the latest GraphQL endpoint?",
"connection_switch_new_url": "Switching to a tab will disconnected you from the active GraphQL connection. New connection URL is",
"connection_switch_url": "You're connected to a GraphQL endpoint the connection URL is",
"mutations": "الطفرات", "mutations": "الطفرات",
"schema": "مخطط", "schema": "مخطط",
"subscriptions": "الاشتراكات", "subscriptions": "الاشتراكات"
"switch_connection": "Switch connection"
}, },
"group": { "group": {
"time": "Time", "time": "Time",
@@ -338,36 +307,13 @@
"json_description": "استيراد مجموعة من ملفHoppscotch Collections JSON file", "json_description": "استيراد مجموعة من ملفHoppscotch Collections JSON file",
"title": "يستورد" "title": "يستورد"
}, },
"inspections": {
"description": "Inspect possible errors",
"environment": {
"add_environment": "Add to Environment",
"not_found": "Environment variable “{environment}” not found."
},
"header": {
"cookie": "The browser doesn't allow Hoppscotch to set the Cookie Header. While we're working on the Hoppscotch Desktop App (coming soon), please use the Authorization Header instead."
},
"response": {
"401_error": "Please check your authentication credentials.",
"404_error": "Please check your request URL and method type.",
"cors_error": "Please check your Cross-Origin Resource Sharing configuration.",
"default_error": "Please check your request.",
"network_error": "Please check your network connection."
},
"title": "Inspector",
"url": {
"extension_not_installed": "Extension not installed.",
"extension_unknown_origin": "Make sure you've added the API endpoint's origin to the Hoppscotch Browser Extension list.",
"extention_enable_action": "Enable Browser Extension",
"extention_not_enabled": "Extension not enabled."
}
},
"layout": { "layout": {
"collapse_collection": "Collapse or Expand Collections", "collapse_collection": "Collapse or Expand Collections",
"collapse_sidebar": "Collapse or Expand the sidebar", "collapse_sidebar": "Collapse or Expand the sidebar",
"column": "تصيم عمودي", "column": "تصيم عمودي",
"name": "Layout", "name": "Layout",
"row": "تصميم افقي" "row": "تصميم افقي",
"zen_mode": "وضع Zen"
}, },
"modal": { "modal": {
"close_unsaved_tab": "You have unsaved changes", "close_unsaved_tab": "You have unsaved changes",
@@ -472,7 +418,6 @@
"payload": "الحمولة", "payload": "الحمولة",
"query": "استفسار", "query": "استفسار",
"raw_body": "نص طلب خام", "raw_body": "نص طلب خام",
"rename": "Rename Request",
"renamed": "تمت إعادة تسمية الطلب", "renamed": "تمت إعادة تسمية الطلب",
"run": "يركض", "run": "يركض",
"save": "يحفظ", "save": "يحفظ",
@@ -480,7 +425,6 @@
"saved": "تم حفظ الطلب", "saved": "تم حفظ الطلب",
"share": "يشارك", "share": "يشارك",
"share_description": "Share Hoppscotch with your friends", "share_description": "Share Hoppscotch with your friends",
"stop": "Stop",
"title": "طلب", "title": "طلب",
"type": "نوع الطلب", "type": "نوع الطلب",
"url": "URL", "url": "URL",
@@ -488,7 +432,6 @@
"view_my_links": "View my links" "view_my_links": "View my links"
}, },
"response": { "response": {
"audio": "Audio",
"body": "هيئة الاستجابة", "body": "هيئة الاستجابة",
"filter_response_body": "Filter JSON response body (uses JSONPath syntax)", "filter_response_body": "Filter JSON response body (uses JSONPath syntax)",
"headers": "الرؤوس", "headers": "الرؤوس",
@@ -502,7 +445,6 @@
"status": "حالة", "status": "حالة",
"time": "وقت", "time": "وقت",
"title": "إجابة", "title": "إجابة",
"video": "Video",
"waiting_for_connection": "في انتظار الاتصال", "waiting_for_connection": "في انتظار الاتصال",
"xml": "XML" "xml": "XML"
}, },
@@ -515,6 +457,7 @@
"account_name_description": "هذا هو اسم العرض الخاص بك.", "account_name_description": "هذا هو اسم العرض الخاص بك.",
"background": "خلفية", "background": "خلفية",
"black_mode": "أسود", "black_mode": "أسود",
"change_font_size": "تغيير حجم الخط",
"choose_language": "اختر اللغة", "choose_language": "اختر اللغة",
"dark_mode": "داكن", "dark_mode": "داكن",
"delete_account": "Delete account", "delete_account": "Delete account",
@@ -527,6 +470,10 @@
"extensions": "ملحقات", "extensions": "ملحقات",
"extensions_use_toggle": "استخدم امتداد المتصفح لإرسال الطلبات (إن وجدت)", "extensions_use_toggle": "استخدم امتداد المتصفح لإرسال الطلبات (إن وجدت)",
"follow": "Follow Us", "follow": "Follow Us",
"font_size": "حجم الخط",
"font_size_large": "كبير",
"font_size_medium": "متوسط",
"font_size_small": "صغير",
"interceptor": "المعترض", "interceptor": "المعترض",
"interceptor_description": "البرامج الوسيطة بين التطبيق وواجهات برمجة التطبيقات.", "interceptor_description": "البرامج الوسيطة بين التطبيق وواجهات برمجة التطبيقات.",
"language": "لغة", "language": "لغة",
@@ -591,27 +538,19 @@
"settings": "انتقل إلى صفحة الإعدادات", "settings": "انتقل إلى صفحة الإعدادات",
"title": "التنقل" "title": "التنقل"
}, },
"others": {
"prettify": "Prettify Editor's Content",
"title": "Others"
},
"request": { "request": {
"copy_request_link": "نسخ ارتباط الطلب", "copy_request_link": "نسخ ارتباط الطلب",
"delete_method": "حدد طريقة الحذف", "delete_method": "حدد طريقة الحذف",
"get_method": "حدد طريقة GET", "get_method": "حدد طريقة GET",
"head_method": "حدد طريقة HEAD", "head_method": "حدد طريقة HEAD",
"import_curl": "Import cURL",
"method": "طريقة", "method": "طريقة",
"next_method": "حدد الطريقة التالية", "next_method": "حدد الطريقة التالية",
"post_method": "حدد طريقة POST", "post_method": "حدد طريقة POST",
"previous_method": "حدد الطريقة السابقة", "previous_method": "حدد الطريقة السابقة",
"put_method": "حدد طريقة PUT", "put_method": "حدد طريقة PUT",
"rename": "Rename Request",
"reset_request": "طلب إعادة التعيين", "reset_request": "طلب إعادة التعيين",
"save_request": "Save Request",
"save_to_collections": "حفظ في المجموعات", "save_to_collections": "حفظ في المجموعات",
"send_request": "ارسل طلب", "send_request": "ارسل طلب",
"show_code": "Generate code snippet",
"title": "طلب" "title": "طلب"
}, },
"response": { "response": {
@@ -641,85 +580,6 @@
"log": "سجل", "log": "سجل",
"url": "URL" "url": "URL"
}, },
"spotlight": {
"change_language": "Change Language",
"environments": {
"delete": "Delete current environment",
"duplicate": "Duplicate current environment",
"duplicate_global": "Duplicate global environment",
"edit": "Edit current environment",
"edit_global": "Edit global environment",
"new": "Create new environment",
"new_variable": "Create a new environment variable",
"title": "Environments"
},
"general": {
"chat": "Chat with support",
"help_menu": "Help and support",
"open_docs": "Read Documentation",
"open_github": "Open GitHub repository",
"open_keybindings": "Keyboard shortcuts",
"social": "Social",
"title": "General"
},
"graphql": {
"connect": "Connect to server",
"disconnect": "Disconnect from server"
},
"miscellaneous": {
"invite": "Invite your friends to Hoppscotch",
"title": "Miscellaneous"
},
"request": {
"save_as_new": "Save as new request",
"select_method": "Select method",
"switch_to": "Switch to",
"tab_authorization": "Authorization tab",
"tab_body": "Body tab",
"tab_headers": "Headers tab",
"tab_parameters": "Parameters tab",
"tab_pre_request_script": "Pre-request script tab",
"tab_query": "Query tab",
"tab_tests": "Tests tab",
"tab_variables": "Variables tab"
},
"response": {
"copy": "Copy response",
"download": "Download response as file",
"title": "Response"
},
"section": {
"interceptor": "Interceptor",
"interface": "Interface",
"theme": "Theme",
"user": "User"
},
"settings": {
"change_interceptor": "Change Interceptor",
"change_language": "Change Language",
"theme": {
"black": "Black",
"dark": "Dark",
"light": "Light",
"system": "System preference"
}
},
"tab": {
"close_current": "Close current tab",
"close_others": "Close all other tabs",
"duplicate": "Duplicate current tab",
"new_tab": "Open a new tab",
"title": "Tabs"
},
"workspace": {
"delete": "Delete current team",
"edit": "Edit current team",
"invite": "Invite people to team",
"new": "Create new team",
"switch_to_personal": "Switch to your personal workspace",
"title": "Teams"
}
},
"sse": { "sse": {
"event_type": "نوع الحدث", "event_type": "نوع الحدث",
"log": "سجل", "log": "سجل",
@@ -777,11 +637,8 @@
"tab": { "tab": {
"authorization": "تفويض", "authorization": "تفويض",
"body": "الجسم", "body": "الجسم",
"close": "Close Tab",
"close_others": "Close other Tabs",
"collections": "المجموعات", "collections": "المجموعات",
"documentation": "توثيق", "documentation": "توثيق",
"duplicate": "Duplicate Tab",
"environments": "Environments", "environments": "Environments",
"headers": "الرؤوس", "headers": "الرؤوس",
"history": "تاريخ", "history": "تاريخ",

View File

@@ -5,7 +5,6 @@
"choose_file": "Triar un fitxer", "choose_file": "Triar un fitxer",
"clear": "Netejar", "clear": "Netejar",
"clear_all": "Neteja-ho tot", "clear_all": "Neteja-ho tot",
"clear_history": "Clear all History",
"close": "Close", "close": "Close",
"connect": "Connectar", "connect": "Connectar",
"connecting": "Connecting", "connecting": "Connecting",
@@ -32,7 +31,6 @@
"paste": "Enganxar", "paste": "Enganxar",
"prettify": "Fes-ho bonic", "prettify": "Fes-ho bonic",
"remove": "Eliminar", "remove": "Eliminar",
"rename": "Rename",
"restore": "Restaurar", "restore": "Restaurar",
"save": "Guardar", "save": "Guardar",
"scroll_to_bottom": "Desplaceu-vos cap avall", "scroll_to_bottom": "Desplaceu-vos cap avall",
@@ -79,8 +77,6 @@
"search": "Cercar", "search": "Cercar",
"share": "Compartir", "share": "Compartir",
"shortcuts": "Dreceres", "shortcuts": "Dreceres",
"social_description": "Follow us on social media to stay updated with the latest news, updates and releases.",
"social_links": "Social links",
"spotlight": "Destacar", "spotlight": "Destacar",
"status": "Estat", "status": "Estat",
"status_description": "Comproveu l'estat de la web", "status_description": "Comproveu l'estat de la web",
@@ -135,15 +131,12 @@
"renamed": "S'ha canviat el nom de la col·lecció", "renamed": "S'ha canviat el nom de la col·lecció",
"request_in_use": "Request in use", "request_in_use": "Request in use",
"save_as": "Guardar com", "save_as": "Guardar com",
"save_to_collection": "Save to Collection",
"select": "Seleccionar una col·lecció", "select": "Seleccionar una col·lecció",
"select_location": "Seleccionar la ubicació", "select_location": "Seleccionar la ubicació",
"select_team": "Seleccionar un equip", "select_team": "Seleccionar un equip",
"team_collections": "Col·leccions per equips" "team_collections": "Col·leccions per equips"
}, },
"confirm": { "confirm": {
"close_unsaved_tab": "Are you sure you want to close this tab?",
"close_unsaved_tabs": "Are you sure you want to close all tabs? {count} unsaved tabs will be lost.",
"exit_team": "Està segur que vol deixar aquest equip?", "exit_team": "Està segur que vol deixar aquest equip?",
"logout": "Està segur que vol tancar la sessió?", "logout": "Està segur que vol tancar la sessió?",
"remove_collection": "Està segur que vol suprimir permanentment aquesta col·lecció?", "remove_collection": "Està segur que vol suprimir permanentment aquesta col·lecció?",
@@ -157,11 +150,6 @@
"save_unsaved_tab": "Do you want to save changes made in this tab?", "save_unsaved_tab": "Do you want to save changes made in this tab?",
"sync": "Està segur que vol sincronitzar aquest espai de treball?" "sync": "Està segur que vol sincronitzar aquest espai de treball?"
}, },
"context_menu": {
"add_parameters": "Add to parameters",
"open_request_in_new_tab": "Open request in new tab",
"set_environment_variable": "Set as variable"
},
"count": { "count": {
"header": "Capçalera {count}", "header": "Capçalera {count}",
"message": "Missatges {count}", "message": "Missatges {count}",
@@ -204,31 +192,17 @@
"create_new": "Crea un entorn nou", "create_new": "Crea un entorn nou",
"created": "Etorn creat", "created": "Etorn creat",
"deleted": "Entorn eliminat", "deleted": "Entorn eliminat",
"duplicated": "Environment duplicated",
"edit": "Editar l'entorn", "edit": "Editar l'entorn",
"empty_variables": "No variables",
"global": "Global",
"global_variables": "Global variables",
"invalid_name": "Proporcioneu un nom vàlid per a l'entorn", "invalid_name": "Proporcioneu un nom vàlid per a l'entorn",
"list": "Environment variables",
"my_environments": "My Environments", "my_environments": "My Environments",
"name": "Name",
"nested_overflow": "Les variables d'entorn niuades estan limitades a 10 nivells", "nested_overflow": "Les variables d'entorn niuades estan limitades a 10 nivells",
"new": "Nou entorn", "new": "Nou entorn",
"no_active_environment": "No active environment",
"no_environment": "Sense entorn", "no_environment": "Sense entorn",
"no_environment_description": "No s'ha seleccionat cap entorn. Trieu què voleu fer amb les variables següents.", "no_environment_description": "No s'ha seleccionat cap entorn. Trieu què voleu fer amb les variables següents.",
"quick_peek": "Environment Quick Peek",
"replace_with_variable": "Replace with variable",
"scope": "Scope",
"select": "Seleccioneu un entorn", "select": "Seleccioneu un entorn",
"set": "Set environment",
"set_as_environment": "Set as environment",
"team_environments": "Team Environments", "team_environments": "Team Environments",
"title": "Entorns", "title": "Entorns",
"updated": "Entorn actualitzat", "updated": "Entorn actualitzat",
"value": "Value",
"variable": "Variable",
"variable_list": "Llista de variables" "variable_list": "Llista de variables"
}, },
"error": { "error": {
@@ -252,7 +226,6 @@
"no_duration": "Sense durada", "no_duration": "Sense durada",
"no_results_found": "No s'ha trobat cap coincidència", "no_results_found": "No s'ha trobat cap coincidència",
"page_not_found": "This page could not be found", "page_not_found": "This page could not be found",
"proxy_error": "Proxy error",
"script_fail": "No s'ha pogut executar l'script de sol·licitud prèvia", "script_fail": "No s'ha pogut executar l'script de sol·licitud prèvia",
"something_went_wrong": "Alguna cosa ha anat malament", "something_went_wrong": "Alguna cosa ha anat malament",
"test_script_fail": "No s'ha pogut executar l'script posterior a la sol·licitud" "test_script_fail": "No s'ha pogut executar l'script posterior a la sol·licitud"
@@ -278,13 +251,9 @@
"renamed": "S'ha canviat el nom de la carpeta" "renamed": "S'ha canviat el nom de la carpeta"
}, },
"graphql": { "graphql": {
"connection_switch_confirm": "Do you want to connect with the latest GraphQL endpoint?",
"connection_switch_new_url": "Switching to a tab will disconnected you from the active GraphQL connection. New connection URL is",
"connection_switch_url": "You're connected to a GraphQL endpoint the connection URL is",
"mutations": "Mutacions", "mutations": "Mutacions",
"schema": "Esquema", "schema": "Esquema",
"subscriptions": "Subscripcions", "subscriptions": "Subscripcions"
"switch_connection": "Switch connection"
}, },
"group": { "group": {
"time": "Time", "time": "Time",
@@ -338,36 +307,13 @@
"json_description": "Importar col·leccions des d'un fitxer JSON de col·leccions Hoppscotch", "json_description": "Importar col·leccions des d'un fitxer JSON de col·leccions Hoppscotch",
"title": "Importació" "title": "Importació"
}, },
"inspections": {
"description": "Inspect possible errors",
"environment": {
"add_environment": "Add to Environment",
"not_found": "Environment variable “{environment}” not found."
},
"header": {
"cookie": "The browser doesn't allow Hoppscotch to set the Cookie Header. While we're working on the Hoppscotch Desktop App (coming soon), please use the Authorization Header instead."
},
"response": {
"401_error": "Please check your authentication credentials.",
"404_error": "Please check your request URL and method type.",
"cors_error": "Please check your Cross-Origin Resource Sharing configuration.",
"default_error": "Please check your request.",
"network_error": "Please check your network connection."
},
"title": "Inspector",
"url": {
"extension_not_installed": "Extension not installed.",
"extension_unknown_origin": "Make sure you've added the API endpoint's origin to the Hoppscotch Browser Extension list.",
"extention_enable_action": "Enable Browser Extension",
"extention_not_enabled": "Extension not enabled."
}
},
"layout": { "layout": {
"collapse_collection": "Amagar o Ampliar Col·leccions", "collapse_collection": "Amagar o Ampliar Col·leccions",
"collapse_sidebar": "Amagar o Ampliar la barra lateral", "collapse_sidebar": "Amagar o Ampliar la barra lateral",
"column": "Distribució vertical", "column": "Distribució vertical",
"name": "Distribució", "name": "Distribució",
"row": "Distribució horitzontal" "row": "Distribució horitzontal",
"zen_mode": "Mode Zen"
}, },
"modal": { "modal": {
"close_unsaved_tab": "You have unsaved changes", "close_unsaved_tab": "You have unsaved changes",
@@ -472,7 +418,6 @@
"payload": "Payload", "payload": "Payload",
"query": "Consulta", "query": "Consulta",
"raw_body": "Cos de sol·licitud sense processar", "raw_body": "Cos de sol·licitud sense processar",
"rename": "Rename Request",
"renamed": "S'ha canviat el nom de la sol·licitud", "renamed": "S'ha canviat el nom de la sol·licitud",
"run": "Executar", "run": "Executar",
"save": "Guardar", "save": "Guardar",
@@ -480,7 +425,6 @@
"saved": "S'ha desat la sol·licitud", "saved": "S'ha desat la sol·licitud",
"share": "Compartir", "share": "Compartir",
"share_description": "Comparteix Hoppscotch amb els teus amics", "share_description": "Comparteix Hoppscotch amb els teus amics",
"stop": "Stop",
"title": "Sol·licitud", "title": "Sol·licitud",
"type": "Tipus de sol·licitud", "type": "Tipus de sol·licitud",
"url": "URL", "url": "URL",
@@ -488,7 +432,6 @@
"view_my_links": "Visualitzar els meus enllaços" "view_my_links": "Visualitzar els meus enllaços"
}, },
"response": { "response": {
"audio": "Audio",
"body": "Cos de resposta", "body": "Cos de resposta",
"filter_response_body": "Filtrar el cos de la resposta JSON (utilitza la sintaxi JSONPath)", "filter_response_body": "Filtrar el cos de la resposta JSON (utilitza la sintaxi JSONPath)",
"headers": "Capçaleres", "headers": "Capçaleres",
@@ -502,7 +445,6 @@
"status": "Estat", "status": "Estat",
"time": "Temps", "time": "Temps",
"title": "Resposta", "title": "Resposta",
"video": "Video",
"waiting_for_connection": "esperant la connexió", "waiting_for_connection": "esperant la connexió",
"xml": "XML" "xml": "XML"
}, },
@@ -515,6 +457,7 @@
"account_name_description": "Aquest és el vostre nom d'exposició", "account_name_description": "Aquest és el vostre nom d'exposició",
"background": "Fons", "background": "Fons",
"black_mode": "Negre", "black_mode": "Negre",
"change_font_size": "Canvia la mida de la lletra",
"choose_language": "Tria l'idioma", "choose_language": "Tria l'idioma",
"dark_mode": "Fosc", "dark_mode": "Fosc",
"delete_account": "Delete account", "delete_account": "Delete account",
@@ -527,6 +470,10 @@
"extensions": "Extensions", "extensions": "Extensions",
"extensions_use_toggle": "Utilitzeu l'extensió del navegador per enviar sol·licituds (si n'hi ha)", "extensions_use_toggle": "Utilitzeu l'extensió del navegador per enviar sol·licituds (si n'hi ha)",
"follow": "Segueix-nos", "follow": "Segueix-nos",
"font_size": "Mida de la font",
"font_size_large": "Gran",
"font_size_medium": "Mitjà",
"font_size_small": "Petit",
"interceptor": "Interceptor", "interceptor": "Interceptor",
"interceptor_description": "Middleware entre aplicació i APIs.", "interceptor_description": "Middleware entre aplicació i APIs.",
"language": "Llenguatge", "language": "Llenguatge",
@@ -591,27 +538,19 @@
"settings": "Anar a la pàgina de Configuració", "settings": "Anar a la pàgina de Configuració",
"title": "Navegació" "title": "Navegació"
}, },
"others": {
"prettify": "Prettify Editor's Content",
"title": "Others"
},
"request": { "request": {
"copy_request_link": "Copiar l'enllaç de la sol·licitud", "copy_request_link": "Copiar l'enllaç de la sol·licitud",
"delete_method": "Seleccionar el mètode DELETE", "delete_method": "Seleccionar el mètode DELETE",
"get_method": "Seleccionar el mètode GET", "get_method": "Seleccionar el mètode GET",
"head_method": "Seleccionar el mètode HEAD", "head_method": "Seleccionar el mètode HEAD",
"import_curl": "Import cURL",
"method": "Mètode", "method": "Mètode",
"next_method": "Seleccionar mètode Següent", "next_method": "Seleccionar mètode Següent",
"post_method": "Seleccionar mètode POST", "post_method": "Seleccionar mètode POST",
"previous_method": "Seleccionar mètode Anterior", "previous_method": "Seleccionar mètode Anterior",
"put_method": "Seleccionar mètode PUT", "put_method": "Seleccionar mètode PUT",
"rename": "Rename Request",
"reset_request": "Sol·licitud de restabliment", "reset_request": "Sol·licitud de restabliment",
"save_request": "Save Request",
"save_to_collections": "Guardar a les col·leccions", "save_to_collections": "Guardar a les col·leccions",
"send_request": "Enviar sol.licitud", "send_request": "Enviar sol.licitud",
"show_code": "Generate code snippet",
"title": "Sol·licitud" "title": "Sol·licitud"
}, },
"response": { "response": {
@@ -641,85 +580,6 @@
"log": "Registre", "log": "Registre",
"url": "URL" "url": "URL"
}, },
"spotlight": {
"change_language": "Change Language",
"environments": {
"delete": "Delete current environment",
"duplicate": "Duplicate current environment",
"duplicate_global": "Duplicate global environment",
"edit": "Edit current environment",
"edit_global": "Edit global environment",
"new": "Create new environment",
"new_variable": "Create a new environment variable",
"title": "Environments"
},
"general": {
"chat": "Chat with support",
"help_menu": "Help and support",
"open_docs": "Read Documentation",
"open_github": "Open GitHub repository",
"open_keybindings": "Keyboard shortcuts",
"social": "Social",
"title": "General"
},
"graphql": {
"connect": "Connect to server",
"disconnect": "Disconnect from server"
},
"miscellaneous": {
"invite": "Invite your friends to Hoppscotch",
"title": "Miscellaneous"
},
"request": {
"save_as_new": "Save as new request",
"select_method": "Select method",
"switch_to": "Switch to",
"tab_authorization": "Authorization tab",
"tab_body": "Body tab",
"tab_headers": "Headers tab",
"tab_parameters": "Parameters tab",
"tab_pre_request_script": "Pre-request script tab",
"tab_query": "Query tab",
"tab_tests": "Tests tab",
"tab_variables": "Variables tab"
},
"response": {
"copy": "Copy response",
"download": "Download response as file",
"title": "Response"
},
"section": {
"interceptor": "Interceptor",
"interface": "Interface",
"theme": "Theme",
"user": "User"
},
"settings": {
"change_interceptor": "Change Interceptor",
"change_language": "Change Language",
"theme": {
"black": "Black",
"dark": "Dark",
"light": "Light",
"system": "System preference"
}
},
"tab": {
"close_current": "Close current tab",
"close_others": "Close all other tabs",
"duplicate": "Duplicate current tab",
"new_tab": "Open a new tab",
"title": "Tabs"
},
"workspace": {
"delete": "Delete current team",
"edit": "Edit current team",
"invite": "Invite people to team",
"new": "Create new team",
"switch_to_personal": "Switch to your personal workspace",
"title": "Teams"
}
},
"sse": { "sse": {
"event_type": "Tipus d'esdeveniment", "event_type": "Tipus d'esdeveniment",
"log": "Registre", "log": "Registre",
@@ -777,11 +637,8 @@
"tab": { "tab": {
"authorization": "Autorització", "authorization": "Autorització",
"body": "Cos", "body": "Cos",
"close": "Close Tab",
"close_others": "Close other Tabs",
"collections": "Col·leccions", "collections": "Col·leccions",
"documentation": "Documentació", "documentation": "Documentació",
"duplicate": "Duplicate Tab",
"environments": "Environments", "environments": "Environments",
"headers": "Capçaleres", "headers": "Capçaleres",
"history": "Historial", "history": "Historial",

View File

@@ -5,7 +5,6 @@
"choose_file": "选择文件", "choose_file": "选择文件",
"clear": "清除", "clear": "清除",
"clear_all": "全部清除", "clear_all": "全部清除",
"clear_history": "Clear all History",
"close": "关闭", "close": "关闭",
"connect": "连接", "connect": "连接",
"connecting": "连接中", "connecting": "连接中",
@@ -20,7 +19,7 @@
"edit": "编辑", "edit": "编辑",
"filter": "过滤", "filter": "过滤",
"go_back": "返回", "go_back": "返回",
"go_forward": "前进", "go_forward": "Go forward",
"group_by": "分组方式", "group_by": "分组方式",
"label": "标签", "label": "标签",
"learn_more": "了解更多", "learn_more": "了解更多",
@@ -32,7 +31,6 @@
"paste": "粘贴", "paste": "粘贴",
"prettify": "美化", "prettify": "美化",
"remove": "移除", "remove": "移除",
"rename": "Rename",
"restore": "恢复", "restore": "恢复",
"save": "保存", "save": "保存",
"scroll_to_bottom": "滚动至底部", "scroll_to_bottom": "滚动至底部",
@@ -42,9 +40,9 @@
"start": "开始", "start": "开始",
"starting": "正在开始", "starting": "正在开始",
"stop": "停止", "stop": "停止",
"to_close": "关闭", "to_close": "关闭",
"to_navigate": "定位", "to_navigate": "定位",
"to_select": "选择", "to_select": "选择",
"turn_off": "关闭", "turn_off": "关闭",
"turn_on": "开启", "turn_on": "开启",
"undo": "撤消", "undo": "撤消",
@@ -79,8 +77,6 @@
"search": "搜索", "search": "搜索",
"share": "分享", "share": "分享",
"shortcuts": "快捷方式", "shortcuts": "快捷方式",
"social_description": "Follow us on social media to stay updated with the latest news, updates and releases.",
"social_links": "Social links",
"spotlight": "聚光灯", "spotlight": "聚光灯",
"status": "状态", "status": "状态",
"status_description": "检查网站状态", "status_description": "检查网站状态",
@@ -122,28 +118,25 @@
}, },
"collection": { "collection": {
"created": "集合已创建", "created": "集合已创建",
"different_parent": "不能用不同的父类来重新排序集合", "different_parent": "Cannot reorder collection with different parent",
"edit": "编辑集合", "edit": "编辑集合",
"invalid_name": "请提供有效的集合名称", "invalid_name": "请提供有效的集合名称",
"invalid_root_move": "该集合已经在根级了", "invalid_root_move": "Collection already in the root",
"moved": "移动完成", "moved": "Moved Successfully",
"my_collections": "我的集合", "my_collections": "我的集合",
"name": "我的新集合", "name": "我的新集合",
"name_length_insufficient": "集合名字至少需要 3 个字符", "name_length_insufficient": "集合名字至少需要 3 个字符",
"new": "新建集合", "new": "新建集合",
"order_changed": "集合顺序已更新", "order_changed": "Collection Order Updated",
"renamed": "集合已更名", "renamed": "集合已更名",
"request_in_use": "请求正在使用中", "request_in_use": "请求正在使用中",
"save_as": "另存为", "save_as": "另存为",
"save_to_collection": "Save to Collection",
"select": "选择一个集合", "select": "选择一个集合",
"select_location": "选择位置", "select_location": "选择位置",
"select_team": "选择一个团队", "select_team": "选择一个团队",
"team_collections": "团队集合" "team_collections": "团队集合"
}, },
"confirm": { "confirm": {
"close_unsaved_tab": "Are you sure you want to close this tab?",
"close_unsaved_tabs": "Are you sure you want to close all tabs? {count} unsaved tabs will be lost.",
"exit_team": "你确定要离开此团队吗?", "exit_team": "你确定要离开此团队吗?",
"logout": "你确定要登出吗?", "logout": "你确定要登出吗?",
"remove_collection": "你确定要永久删除该集合吗?", "remove_collection": "你确定要永久删除该集合吗?",
@@ -154,14 +147,9 @@
"remove_team": "你确定要删除该团队吗?", "remove_team": "你确定要删除该团队吗?",
"remove_telemetry": "你确定要退出遥测服务吗?", "remove_telemetry": "你确定要退出遥测服务吗?",
"request_change": "你确定你要放弃当前的请求,未保存的修改将被丢失。", "request_change": "你确定你要放弃当前的请求,未保存的修改将被丢失。",
"save_unsaved_tab": "你想保存在此标签页中所作的修改吗?", "save_unsaved_tab": "Do you want to save changes made in this tab?",
"sync": "您确定要同步该工作区吗?" "sync": "您确定要同步该工作区吗?"
}, },
"context_menu": {
"add_parameters": "Add to parameters",
"open_request_in_new_tab": "Open request in new tab",
"set_environment_variable": "Set as variable"
},
"count": { "count": {
"header": "请求头 {count}", "header": "请求头 {count}",
"message": "消息 {count}", "message": "消息 {count}",
@@ -189,7 +177,7 @@
"members": "团队为空", "members": "团队为空",
"parameters": "该请求没有任何参数", "parameters": "该请求没有任何参数",
"pending_invites": "此团队无待办邀请", "pending_invites": "此团队无待办邀请",
"profile": "登录以查看你的个人资料", "profile": "登录以查看你的个人档案",
"protocols": "协议为空", "protocols": "协议为空",
"schema": "连接至 GraphQL 端点", "schema": "连接至 GraphQL 端点",
"shortcodes": "Shortcodes 为空", "shortcodes": "Shortcodes 为空",
@@ -204,38 +192,24 @@
"create_new": "创建新环境", "create_new": "创建新环境",
"created": "环境已创建", "created": "环境已创建",
"deleted": "环境已删除", "deleted": "环境已删除",
"duplicated": "Environment duplicated",
"edit": "编辑环境", "edit": "编辑环境",
"empty_variables": "No variables",
"global": "Global",
"global_variables": "Global variables",
"invalid_name": "请提供有效的环境名称", "invalid_name": "请提供有效的环境名称",
"list": "Environment variables",
"my_environments": "我的环境", "my_environments": "我的环境",
"name": "Name",
"nested_overflow": "环境嵌套深度超过限制10层", "nested_overflow": "环境嵌套深度超过限制10层",
"new": "新建环境", "new": "新建环境",
"no_active_environment": "No active environment",
"no_environment": "无环境", "no_environment": "无环境",
"no_environment_description": "没有选择环境。选择如何处理以下变量。", "no_environment_description": "没有选择环境。选择如何处理以下变量。",
"quick_peek": "Environment Quick Peek",
"replace_with_variable": "Replace with variable",
"scope": "Scope",
"select": "选择环境", "select": "选择环境",
"set": "Set environment",
"set_as_environment": "Set as environment",
"team_environments": "团队环境", "team_environments": "团队环境",
"title": "环境", "title": "环境",
"updated": "环境已更新", "updated": "环境已更新",
"value": "Value",
"variable": "Variable",
"variable_list": "变量列表" "variable_list": "变量列表"
}, },
"error": { "error": {
"browser_support_sse": "该浏览器似乎不支持 SSE。", "browser_support_sse": "该浏览器似乎不支持 SSE。",
"check_console_details": "检查控制台日志以获悉详情", "check_console_details": "检查控制台日志以获悉详情",
"curl_invalid_format": "cURL 格式不正确", "curl_invalid_format": "cURL 格式不正确",
"danger_zone": "危险区域", "danger_zone": "Danger zone",
"delete_account": "您的帐号目前为这些团队的拥有者:", "delete_account": "您的帐号目前为这些团队的拥有者:",
"delete_account_description": "您在删除帐号前必须先将您自己从团队中移除、转移拥有权,或是删除团队。", "delete_account_description": "您在删除帐号前必须先将您自己从团队中移除、转移拥有权,或是删除团队。",
"empty_req_name": "空请求名称", "empty_req_name": "空请求名称",
@@ -245,14 +219,13 @@
"incorrect_email": "电子邮箱错误", "incorrect_email": "电子邮箱错误",
"invalid_link": "无效链接", "invalid_link": "无效链接",
"invalid_link_description": "你点击的链接无效或已过期。", "invalid_link_description": "你点击的链接无效或已过期。",
"json_parsing_failed": "不合法的 JSON", "json_parsing_failed": "Invalid JSON",
"json_prettify_invalid_body": "无法美化无效的请求头,处理 JSON 语法错误并重试", "json_prettify_invalid_body": "无法美化无效的请求头,处理 JSON 语法错误并重试",
"network_error": "好像发生了网络错误,请重试。", "network_error": "好像发生了网络错误,请重试。",
"network_fail": "无法发送请求", "network_fail": "无法发送请求",
"no_duration": "无持续时间", "no_duration": "无持续时间",
"no_results_found": "找不到结果", "no_results_found": "找不到结果",
"page_not_found": "找不到此頁面", "page_not_found": "找不到此頁面",
"proxy_error": "Proxy error",
"script_fail": "无法执行预请求脚本", "script_fail": "无法执行预请求脚本",
"something_went_wrong": "发生了一些错误", "something_went_wrong": "发生了一些错误",
"test_script_fail": "无法执行请求脚本" "test_script_fail": "无法执行请求脚本"
@@ -278,13 +251,9 @@
"renamed": "文件夹已更名" "renamed": "文件夹已更名"
}, },
"graphql": { "graphql": {
"connection_switch_confirm": "Do you want to connect with the latest GraphQL endpoint?",
"connection_switch_new_url": "Switching to a tab will disconnected you from the active GraphQL connection. New connection URL is",
"connection_switch_url": "You're connected to a GraphQL endpoint the connection URL is",
"mutations": "变更", "mutations": "变更",
"schema": "模式", "schema": "模式",
"subscriptions": "订阅", "subscriptions": "订阅"
"switch_connection": "Switch connection"
}, },
"group": { "group": {
"time": "时间", "time": "时间",
@@ -338,46 +307,23 @@
"json_description": "从 Hoppscotch 的集合文件导入JSON", "json_description": "从 Hoppscotch 的集合文件导入JSON",
"title": "导入" "title": "导入"
}, },
"inspections": {
"description": "Inspect possible errors",
"environment": {
"add_environment": "Add to Environment",
"not_found": "Environment variable “{environment}” not found."
},
"header": {
"cookie": "The browser doesn't allow Hoppscotch to set the Cookie Header. While we're working on the Hoppscotch Desktop App (coming soon), please use the Authorization Header instead."
},
"response": {
"401_error": "Please check your authentication credentials.",
"404_error": "Please check your request URL and method type.",
"cors_error": "Please check your Cross-Origin Resource Sharing configuration.",
"default_error": "Please check your request.",
"network_error": "Please check your network connection."
},
"title": "Inspector",
"url": {
"extension_not_installed": "Extension not installed.",
"extension_unknown_origin": "Make sure you've added the API endpoint's origin to the Hoppscotch Browser Extension list.",
"extention_enable_action": "Enable Browser Extension",
"extention_not_enabled": "Extension not enabled."
}
},
"layout": { "layout": {
"collapse_collection": "折叠/展开集合", "collapse_collection": "折叠/展开集合",
"collapse_sidebar": "折叠/展开边栏", "collapse_sidebar": "折叠/展开边栏",
"column": "垂直布局", "column": "垂直布局",
"name": "布局", "name": "布局",
"row": "水平布局" "row": "水平布局",
"zen_mode": "ZEN 模式"
}, },
"modal": { "modal": {
"close_unsaved_tab": "有未保存的变更", "close_unsaved_tab": "You have unsaved changes",
"collections": "集合", "collections": "集合",
"confirm": "确认", "confirm": "确认",
"edit_request": "编辑请求", "edit_request": "编辑请求",
"import_export": "导入/导出" "import_export": "导入/导出"
}, },
"mqtt": { "mqtt": {
"already_subscribed": "您已经订阅了此主。", "already_subscribed": "您已经订阅了此主。",
"clean_session": "清除会话", "clean_session": "清除会话",
"clear_input": "清除输入", "clear_input": "清除输入",
"clear_input_on_send": "发送后清除输入", "clear_input_on_send": "发送后清除输入",
@@ -409,7 +355,7 @@
"navigation": { "navigation": {
"doc": "文档", "doc": "文档",
"graphql": "GraphQL", "graphql": "GraphQL",
"profile": "个人资料", "profile": "个人档案",
"realtime": "实时", "realtime": "实时",
"rest": "REST", "rest": "REST",
"settings": "设置" "settings": "设置"
@@ -431,7 +377,7 @@
"owner_description": "所有者可以添加、编辑和删除请求、集合及团队成员。", "owner_description": "所有者可以添加、编辑和删除请求、集合及团队成员。",
"roles": "角色", "roles": "角色",
"roles_description": "角色用以控制共享集合的访问权限。", "roles_description": "角色用以控制共享集合的访问权限。",
"updated": "已更新", "updated": "档案已更新",
"viewer": "查看者", "viewer": "查看者",
"viewer_description": "查看者只可查看与使用请求。" "viewer_description": "查看者只可查看与使用请求。"
}, },
@@ -450,8 +396,8 @@
"text": "文字" "text": "文字"
}, },
"copy_link": "复制链接", "copy_link": "复制链接",
"different_collection": "不能对来自不同集合的请求进行重新排序", "different_collection": "Cannot reorder requests from different collections",
"duplicated": "重复的请求", "duplicated": "Request duplicated",
"duration": "持续时间", "duration": "持续时间",
"enter_curl": "输入 cURL", "enter_curl": "输入 cURL",
"generate_code": "生成代码", "generate_code": "生成代码",
@@ -459,10 +405,10 @@
"header_list": "请求头列表", "header_list": "请求头列表",
"invalid_name": "请提供请求名称", "invalid_name": "请提供请求名称",
"method": "方法", "method": "方法",
"moved": "请求移动完成", "moved": "Request moved",
"name": "请求名称", "name": "请求名称",
"new": "新请求", "new": "新请求",
"order_changed": "请求顺序更新完成", "order_changed": "Request Order Updated",
"override": "覆盖", "override": "覆盖",
"override_help": "设置 <kbd>Content-Type</kbd> 头", "override_help": "设置 <kbd>Content-Type</kbd> 头",
"overriden": "覆盖", "overriden": "覆盖",
@@ -472,7 +418,6 @@
"payload": "负载", "payload": "负载",
"query": "查询", "query": "查询",
"raw_body": "原始请求体", "raw_body": "原始请求体",
"rename": "Rename Request",
"renamed": "请求重命名", "renamed": "请求重命名",
"run": "运行", "run": "运行",
"save": "保存", "save": "保存",
@@ -480,7 +425,6 @@
"saved": "请求已保存", "saved": "请求已保存",
"share": "分享", "share": "分享",
"share_description": "分享 Hoppscotch 给你的朋友", "share_description": "分享 Hoppscotch 给你的朋友",
"stop": "Stop",
"title": "请求", "title": "请求",
"type": "请求类型", "type": "请求类型",
"url": "URL", "url": "URL",
@@ -488,7 +432,6 @@
"view_my_links": "查看我的链接" "view_my_links": "查看我的链接"
}, },
"response": { "response": {
"audio": "Audio",
"body": "响应体", "body": "响应体",
"filter_response_body": "筛选JSON响应本体使用JSONPath语法", "filter_response_body": "筛选JSON响应本体使用JSONPath语法",
"headers": "响应头", "headers": "响应头",
@@ -502,7 +445,6 @@
"status": "状态", "status": "状态",
"time": "时间", "time": "时间",
"title": "响应", "title": "响应",
"video": "Video",
"waiting_for_connection": "等待连接", "waiting_for_connection": "等待连接",
"xml": "XML" "xml": "XML"
}, },
@@ -515,6 +457,7 @@
"account_name_description": "这是您的显示名称。", "account_name_description": "这是您的显示名称。",
"background": "背景", "background": "背景",
"black_mode": "黑色", "black_mode": "黑色",
"change_font_size": "更改字体大小",
"choose_language": "选择语言", "choose_language": "选择语言",
"dark_mode": "暗色", "dark_mode": "暗色",
"delete_account": "刪除账号", "delete_account": "刪除账号",
@@ -527,15 +470,19 @@
"extensions": "扩展", "extensions": "扩展",
"extensions_use_toggle": "使用浏览器扩展发送请求(如果存在)", "extensions_use_toggle": "使用浏览器扩展发送请求(如果存在)",
"follow": "关注我们", "follow": "关注我们",
"font_size": "字体大小",
"font_size_large": "大",
"font_size_medium": "中",
"font_size_small": "小",
"interceptor": "拦截器", "interceptor": "拦截器",
"interceptor_description": "应用程序和 API 之间的中间件。", "interceptor_description": "应用程序和 API 之间的中间件。",
"language": "语言", "language": "语言",
"light_mode": "亮色", "light_mode": "亮色",
"official_proxy_hosting": "官方代理由 Hoppscotch 托管。", "official_proxy_hosting": "官方代理由 Hoppscotch 托管。",
"profile": "个人资料", "profile": "个人档案",
"profile_description": "更新你的资料", "profile_description": "更新你的档案详情",
"profile_email": "电子邮箱地址", "profile_email": "电子邮箱地址",
"profile_name": "名称", "profile_name": "档案名称",
"proxy": "网络代理", "proxy": "网络代理",
"proxy_url": "代理网址", "proxy_url": "代理网址",
"proxy_use_toggle": "使用代理中间件发送请求", "proxy_use_toggle": "使用代理中间件发送请求",
@@ -585,33 +532,25 @@
"documentation": "前往文档页面", "documentation": "前往文档页面",
"forward": "前往下一页面", "forward": "前往下一页面",
"graphql": "前往 GraphQL 页面", "graphql": "前往 GraphQL 页面",
"profile": "前往个人资料页面", "profile": "前往个人档案页面",
"realtime": "前往实时页面", "realtime": "前往实时页面",
"rest": "前往 REST 页面", "rest": "前往 REST 页面",
"settings": "前往设置页面", "settings": "前往设置页面",
"title": "导航" "title": "导航"
}, },
"others": {
"prettify": "Prettify Editor's Content",
"title": "Others"
},
"request": { "request": {
"copy_request_link": "复制请求链接", "copy_request_link": "复制请求链接",
"delete_method": "选择 DELETE 方法", "delete_method": "选择 DELETE 方法",
"get_method": "选择 GET 方法", "get_method": "选择 GET 方法",
"head_method": "选择 HEAD 方法", "head_method": "选择 HEAD 方法",
"import_curl": "Import cURL",
"method": "方法", "method": "方法",
"next_method": "选择下一个方法", "next_method": "选择下一个方法",
"post_method": "选择 POST 方法", "post_method": "选择 POST 方法",
"previous_method": "选择上一个方法", "previous_method": "选择上一个方法",
"put_method": "选择 PUT 方法", "put_method": "选择 PUT 方法",
"rename": "Rename Request",
"reset_request": "重置请求", "reset_request": "重置请求",
"save_request": "Save Request",
"save_to_collections": "保存到集合", "save_to_collections": "保存到集合",
"send_request": "发送请求", "send_request": "发送请求",
"show_code": "Generate code snippet",
"title": "请求" "title": "请求"
}, },
"response": { "response": {
@@ -635,91 +574,12 @@
}, },
"socketio": { "socketio": {
"communication": "通讯", "communication": "通讯",
"connection_not_authorized": "此 SocketIO 连接未使用任何验证。", "connection_not_authorized": "此SocketIO连接未使用任何验证。",
"event_name": "事件名称", "event_name": "事件名称",
"events": "事件", "events": "事件",
"log": "日志", "log": "日志",
"url": "URL" "url": "URL"
}, },
"spotlight": {
"change_language": "Change Language",
"environments": {
"delete": "Delete current environment",
"duplicate": "Duplicate current environment",
"duplicate_global": "Duplicate global environment",
"edit": "Edit current environment",
"edit_global": "Edit global environment",
"new": "Create new environment",
"new_variable": "Create a new environment variable",
"title": "Environments"
},
"general": {
"chat": "Chat with support",
"help_menu": "Help and support",
"open_docs": "Read Documentation",
"open_github": "Open GitHub repository",
"open_keybindings": "Keyboard shortcuts",
"social": "Social",
"title": "General"
},
"graphql": {
"connect": "Connect to server",
"disconnect": "Disconnect from server"
},
"miscellaneous": {
"invite": "Invite your friends to Hoppscotch",
"title": "Miscellaneous"
},
"request": {
"save_as_new": "Save as new request",
"select_method": "Select method",
"switch_to": "Switch to",
"tab_authorization": "Authorization tab",
"tab_body": "Body tab",
"tab_headers": "Headers tab",
"tab_parameters": "Parameters tab",
"tab_pre_request_script": "Pre-request script tab",
"tab_query": "Query tab",
"tab_tests": "Tests tab",
"tab_variables": "Variables tab"
},
"response": {
"copy": "Copy response",
"download": "Download response as file",
"title": "Response"
},
"section": {
"interceptor": "Interceptor",
"interface": "Interface",
"theme": "Theme",
"user": "User"
},
"settings": {
"change_interceptor": "Change Interceptor",
"change_language": "Change Language",
"theme": {
"black": "Black",
"dark": "Dark",
"light": "Light",
"system": "System preference"
}
},
"tab": {
"close_current": "Close current tab",
"close_others": "Close all other tabs",
"duplicate": "Duplicate current tab",
"new_tab": "Open a new tab",
"title": "Tabs"
},
"workspace": {
"delete": "Delete current team",
"edit": "Edit current team",
"invite": "Invite people to team",
"new": "Create new team",
"switch_to_personal": "Switch to your personal workspace",
"title": "Teams"
}
},
"sse": { "sse": {
"event_type": "事件类型", "event_type": "事件类型",
"log": "日志", "log": "日志",
@@ -754,12 +614,12 @@
"none": "无", "none": "无",
"nothing_found": "没有找到", "nothing_found": "没有找到",
"published_error": "将信息:{topic}发布至主题:{message}时发生错误", "published_error": "将信息:{topic}发布至主题:{message}时发生错误",
"published_message": "已将此信息:{message} 发布至主题:{topic}", "published_message": "已将此信息:{message}发布至主题:{topic}",
"reconnection_error": "重连失败", "reconnection_error": "重连失败",
"subscribed_failed": "无法订阅此主{topic}", "subscribed_failed": "无法订阅此主{topic}",
"subscribed_success": "成功订阅此主{topic}", "subscribed_success": "成功订阅此主{topic}",
"unsubscribed_failed": "无法取消订阅此主{topic}", "unsubscribed_failed": "无法取消订阅此主{topic}",
"unsubscribed_success": "成功取消订阅此主{topic}", "unsubscribed_success": "成功取消订阅此主{topic}",
"waiting_send_request": "等待发送请求" "waiting_send_request": "等待发送请求"
}, },
"support": { "support": {
@@ -777,12 +637,9 @@
"tab": { "tab": {
"authorization": "授权", "authorization": "授权",
"body": "请求体", "body": "请求体",
"close": "Close Tab",
"close_others": "Close other Tabs",
"collections": "集合", "collections": "集合",
"documentation": "帮助文档", "documentation": "帮助文档",
"duplicate": "Duplicate Tab", "environments": "Environments",
"environments": "环境",
"headers": "请求头", "headers": "请求头",
"history": "历史记录", "history": "历史记录",
"mqtt": "MQTT", "mqtt": "MQTT",
@@ -807,7 +664,7 @@
"email_do_not_match": "邮箱无法与你的帐户信息匹配。请联系你的团队者。", "email_do_not_match": "邮箱无法与你的帐户信息匹配。请联系你的团队者。",
"exit": "退出团队", "exit": "退出团队",
"exit_disabled": "团队所有者无法退出团队", "exit_disabled": "团队所有者无法退出团队",
"invalid_coll_id": "无效的集合 ID", "invalid_coll_id": "Invalid collection ID",
"invalid_email_format": "电子邮箱格式无效", "invalid_email_format": "电子邮箱格式无效",
"invalid_id": "无效的团队 ID请联系你的团队者。", "invalid_id": "无效的团队 ID请联系你的团队者。",
"invalid_invite_link": "无效的邀请链接", "invalid_invite_link": "无效的邀请链接",
@@ -831,7 +688,7 @@
"member_removed": "用户已移除", "member_removed": "用户已移除",
"member_role_updated": "用户角色已更新", "member_role_updated": "用户角色已更新",
"members": "成员", "members": "成员",
"more_members": "+{count} 更多", "more_members": "+{count} more",
"name_length_insufficient": "团队名称至少为 6 个字符", "name_length_insufficient": "团队名称至少为 6 个字符",
"name_updated": "团队名称已更新", "name_updated": "团队名称已更新",
"new": "新团队", "new": "新团队",
@@ -839,13 +696,13 @@
"new_name": "我的新团队", "new_name": "我的新团队",
"no_access": "你没有编辑集合的权限", "no_access": "你没有编辑集合的权限",
"no_invite_found": "未找到邀请。请联系你的团队者。", "no_invite_found": "未找到邀请。请联系你的团队者。",
"no_request_found": "请求不存在", "no_request_found": "Request not found.",
"not_found": "没有找到团队,请联系您的团队所有者。", "not_found": "没有找到团队,请联系您的团队所有者。",
"not_valid_viewer": "你不是有效的查看者。请联系你的团队者。", "not_valid_viewer": "你不是有效的查看者。请联系你的团队者。",
"parent_coll_move": "不能将集合移动到一个子集合", "parent_coll_move": "Cannot move collection to a child collection",
"pending_invites": "待办邀请", "pending_invites": "待办邀请",
"permissions": "权限", "permissions": "权限",
"same_target_destination": "目标相同", "same_target_destination": "Same target and destination",
"saved": "团队已保存", "saved": "团队已保存",
"select_a_team": "选择团队", "select_a_team": "选择团队",
"title": "团队", "title": "团队",
@@ -875,9 +732,9 @@
"url": "URL" "url": "URL"
}, },
"workspace": { "workspace": {
"change": "切换工作空间", "change": "Change workspace",
"personal": "我的工作空间", "personal": "My Workspace",
"team": "团队工作空间", "team": "Team Workspace",
"title": "工作空间" "title": "Workspaces"
} }
} }

View File

@@ -5,7 +5,6 @@
"choose_file": "Vyberte soubor", "choose_file": "Vyberte soubor",
"clear": "Průhledná", "clear": "Průhledná",
"clear_all": "Vymazat vše", "clear_all": "Vymazat vše",
"clear_history": "Clear all History",
"close": "Close", "close": "Close",
"connect": "Připojit", "connect": "Připojit",
"connecting": "Connecting", "connecting": "Connecting",
@@ -32,7 +31,6 @@
"paste": "Paste", "paste": "Paste",
"prettify": "Prettify", "prettify": "Prettify",
"remove": "Odstranit", "remove": "Odstranit",
"rename": "Rename",
"restore": "Obnovit", "restore": "Obnovit",
"save": "Uložit", "save": "Uložit",
"scroll_to_bottom": "Scroll to bottom", "scroll_to_bottom": "Scroll to bottom",
@@ -79,8 +77,6 @@
"search": "Vyhledávání", "search": "Vyhledávání",
"share": "Podíl", "share": "Podíl",
"shortcuts": "Klávesové zkratky", "shortcuts": "Klávesové zkratky",
"social_description": "Follow us on social media to stay updated with the latest news, updates and releases.",
"social_links": "Social links",
"spotlight": "Reflektor", "spotlight": "Reflektor",
"status": "Postavení", "status": "Postavení",
"status_description": "Check the status of the website", "status_description": "Check the status of the website",
@@ -135,15 +131,12 @@
"renamed": "Sbírka přejmenována", "renamed": "Sbírka přejmenována",
"request_in_use": "Request in use", "request_in_use": "Request in use",
"save_as": "Uložit jako", "save_as": "Uložit jako",
"save_to_collection": "Save to Collection",
"select": "Vyberte sbírku", "select": "Vyberte sbírku",
"select_location": "Vyberte umístění", "select_location": "Vyberte umístění",
"select_team": "Vyberte tým", "select_team": "Vyberte tým",
"team_collections": "Týmové sbírky" "team_collections": "Týmové sbírky"
}, },
"confirm": { "confirm": {
"close_unsaved_tab": "Are you sure you want to close this tab?",
"close_unsaved_tabs": "Are you sure you want to close all tabs? {count} unsaved tabs will be lost.",
"exit_team": "Are you sure you want to leave this team?", "exit_team": "Are you sure you want to leave this team?",
"logout": "Opravdu se chcete odhlásit?", "logout": "Opravdu se chcete odhlásit?",
"remove_collection": "Opravdu chcete tuto sbírku trvale smazat?", "remove_collection": "Opravdu chcete tuto sbírku trvale smazat?",
@@ -157,11 +150,6 @@
"save_unsaved_tab": "Do you want to save changes made in this tab?", "save_unsaved_tab": "Do you want to save changes made in this tab?",
"sync": "Opravdu chcete synchronizovat tento pracovní prostor?" "sync": "Opravdu chcete synchronizovat tento pracovní prostor?"
}, },
"context_menu": {
"add_parameters": "Add to parameters",
"open_request_in_new_tab": "Open request in new tab",
"set_environment_variable": "Set as variable"
},
"count": { "count": {
"header": "Záhlaví {count}", "header": "Záhlaví {count}",
"message": "Zpráva {count}", "message": "Zpráva {count}",
@@ -204,31 +192,17 @@
"create_new": "Vytvořit nové prostředí", "create_new": "Vytvořit nové prostředí",
"created": "Environment created", "created": "Environment created",
"deleted": "Environment deletion", "deleted": "Environment deletion",
"duplicated": "Environment duplicated",
"edit": "Upravit prostředí", "edit": "Upravit prostředí",
"empty_variables": "No variables",
"global": "Global",
"global_variables": "Global variables",
"invalid_name": "Zadejte platný název prostředí", "invalid_name": "Zadejte platný název prostředí",
"list": "Environment variables",
"my_environments": "My Environments", "my_environments": "My Environments",
"name": "Name",
"nested_overflow": "nested environment variables are limited to 10 levels", "nested_overflow": "nested environment variables are limited to 10 levels",
"new": "Nové prostředí", "new": "Nové prostředí",
"no_active_environment": "No active environment",
"no_environment": "Žádné prostředí", "no_environment": "Žádné prostředí",
"no_environment_description": "No environments were selected. Choose what to do with the following variables.", "no_environment_description": "No environments were selected. Choose what to do with the following variables.",
"quick_peek": "Environment Quick Peek",
"replace_with_variable": "Replace with variable",
"scope": "Scope",
"select": "Vyberte prostředí", "select": "Vyberte prostředí",
"set": "Set environment",
"set_as_environment": "Set as environment",
"team_environments": "Team Environments", "team_environments": "Team Environments",
"title": "Prostředí", "title": "Prostředí",
"updated": "Environment updation", "updated": "Environment updation",
"value": "Value",
"variable": "Variable",
"variable_list": "Seznam proměnných" "variable_list": "Seznam proměnných"
}, },
"error": { "error": {
@@ -252,7 +226,6 @@
"no_duration": "Žádné trvání", "no_duration": "Žádné trvání",
"no_results_found": "No matches found", "no_results_found": "No matches found",
"page_not_found": "This page could not be found", "page_not_found": "This page could not be found",
"proxy_error": "Proxy error",
"script_fail": "Skript předběžného požadavku nelze spustit", "script_fail": "Skript předběžného požadavku nelze spustit",
"something_went_wrong": "Něco se pokazilo", "something_went_wrong": "Něco se pokazilo",
"test_script_fail": "Could not execute post-request script" "test_script_fail": "Could not execute post-request script"
@@ -278,13 +251,9 @@
"renamed": "Složka přejmenována" "renamed": "Složka přejmenována"
}, },
"graphql": { "graphql": {
"connection_switch_confirm": "Do you want to connect with the latest GraphQL endpoint?",
"connection_switch_new_url": "Switching to a tab will disconnected you from the active GraphQL connection. New connection URL is",
"connection_switch_url": "You're connected to a GraphQL endpoint the connection URL is",
"mutations": "Mutace", "mutations": "Mutace",
"schema": "Schéma", "schema": "Schéma",
"subscriptions": "Předplatné", "subscriptions": "Předplatné"
"switch_connection": "Switch connection"
}, },
"group": { "group": {
"time": "Time", "time": "Time",
@@ -338,36 +307,13 @@
"json_description": "Import collections from a Hoppscotch Collections JSON file", "json_description": "Import collections from a Hoppscotch Collections JSON file",
"title": "Import" "title": "Import"
}, },
"inspections": {
"description": "Inspect possible errors",
"environment": {
"add_environment": "Add to Environment",
"not_found": "Environment variable “{environment}” not found."
},
"header": {
"cookie": "The browser doesn't allow Hoppscotch to set the Cookie Header. While we're working on the Hoppscotch Desktop App (coming soon), please use the Authorization Header instead."
},
"response": {
"401_error": "Please check your authentication credentials.",
"404_error": "Please check your request URL and method type.",
"cors_error": "Please check your Cross-Origin Resource Sharing configuration.",
"default_error": "Please check your request.",
"network_error": "Please check your network connection."
},
"title": "Inspector",
"url": {
"extension_not_installed": "Extension not installed.",
"extension_unknown_origin": "Make sure you've added the API endpoint's origin to the Hoppscotch Browser Extension list.",
"extention_enable_action": "Enable Browser Extension",
"extention_not_enabled": "Extension not enabled."
}
},
"layout": { "layout": {
"collapse_collection": "Collapse or Expand Collections", "collapse_collection": "Collapse or Expand Collections",
"collapse_sidebar": "Collapse or Expand the sidebar", "collapse_sidebar": "Collapse or Expand the sidebar",
"column": "Vertical layout", "column": "Vertical layout",
"name": "Layout", "name": "Layout",
"row": "Horizontal layout" "row": "Horizontal layout",
"zen_mode": "Zenový režim"
}, },
"modal": { "modal": {
"close_unsaved_tab": "You have unsaved changes", "close_unsaved_tab": "You have unsaved changes",
@@ -472,7 +418,6 @@
"payload": "Užitečné zatížení", "payload": "Užitečné zatížení",
"query": "Dotaz", "query": "Dotaz",
"raw_body": "Raw Request Body", "raw_body": "Raw Request Body",
"rename": "Rename Request",
"renamed": "Žádost přejmenována", "renamed": "Žádost přejmenována",
"run": "Běh", "run": "Běh",
"save": "Uložit", "save": "Uložit",
@@ -480,7 +425,6 @@
"saved": "Žádost uložena", "saved": "Žádost uložena",
"share": "Podíl", "share": "Podíl",
"share_description": "Share Hoppscotch with your friends", "share_description": "Share Hoppscotch with your friends",
"stop": "Stop",
"title": "Žádost", "title": "Žádost",
"type": "Typ požadavku", "type": "Typ požadavku",
"url": "URL", "url": "URL",
@@ -488,7 +432,6 @@
"view_my_links": "View my links" "view_my_links": "View my links"
}, },
"response": { "response": {
"audio": "Audio",
"body": "Odpovědní orgán", "body": "Odpovědní orgán",
"filter_response_body": "Filter JSON response body (uses JSONPath syntax)", "filter_response_body": "Filter JSON response body (uses JSONPath syntax)",
"headers": "Záhlaví", "headers": "Záhlaví",
@@ -502,7 +445,6 @@
"status": "Postavení", "status": "Postavení",
"time": "Čas", "time": "Čas",
"title": "Odezva", "title": "Odezva",
"video": "Video",
"waiting_for_connection": "čekání na připojení", "waiting_for_connection": "čekání na připojení",
"xml": "XML" "xml": "XML"
}, },
@@ -515,6 +457,7 @@
"account_name_description": "Toto je vaše zobrazované jméno.", "account_name_description": "Toto je vaše zobrazované jméno.",
"background": "Pozadí", "background": "Pozadí",
"black_mode": "Černá", "black_mode": "Černá",
"change_font_size": "Změnit velikost písma",
"choose_language": "Vyber jazyk", "choose_language": "Vyber jazyk",
"dark_mode": "Temný", "dark_mode": "Temný",
"delete_account": "Delete account", "delete_account": "Delete account",
@@ -527,6 +470,10 @@
"extensions": "Rozšíření", "extensions": "Rozšíření",
"extensions_use_toggle": "K odeslání požadavků použijte rozšíření prohlížeče (je -li k dispozici)", "extensions_use_toggle": "K odeslání požadavků použijte rozšíření prohlížeče (je -li k dispozici)",
"follow": "Follow Us", "follow": "Follow Us",
"font_size": "Velikost písma",
"font_size_large": "Velký",
"font_size_medium": "Střední",
"font_size_small": "Malý",
"interceptor": "Interceptor", "interceptor": "Interceptor",
"interceptor_description": "Middleware mezi aplikací a API.", "interceptor_description": "Middleware mezi aplikací a API.",
"language": "Jazyk", "language": "Jazyk",
@@ -591,27 +538,19 @@
"settings": "Přejděte na stránku Nastavení", "settings": "Přejděte na stránku Nastavení",
"title": "Navigace" "title": "Navigace"
}, },
"others": {
"prettify": "Prettify Editor's Content",
"title": "Others"
},
"request": { "request": {
"copy_request_link": "Kopírovat požadavek na odkaz", "copy_request_link": "Kopírovat požadavek na odkaz",
"delete_method": "Vyberte metodu ODSTRANIT", "delete_method": "Vyberte metodu ODSTRANIT",
"get_method": "Vyberte metodu ZÍSKAT", "get_method": "Vyberte metodu ZÍSKAT",
"head_method": "Vyberte metodu HEAD", "head_method": "Vyberte metodu HEAD",
"import_curl": "Import cURL",
"method": "Metoda", "method": "Metoda",
"next_method": "Vyberte Další metoda", "next_method": "Vyberte Další metoda",
"post_method": "Vyberte metodu POST", "post_method": "Vyberte metodu POST",
"previous_method": "Vyberte předchozí metodu", "previous_method": "Vyberte předchozí metodu",
"put_method": "Vyberte metodu PUT", "put_method": "Vyberte metodu PUT",
"rename": "Rename Request",
"reset_request": "Resetovat požadavek", "reset_request": "Resetovat požadavek",
"save_request": "Save Request",
"save_to_collections": "Uložit do sbírek", "save_to_collections": "Uložit do sbírek",
"send_request": "Poslat žádost", "send_request": "Poslat žádost",
"show_code": "Generate code snippet",
"title": "Žádost" "title": "Žádost"
}, },
"response": { "response": {
@@ -641,85 +580,6 @@
"log": "Záznam", "log": "Záznam",
"url": "URL" "url": "URL"
}, },
"spotlight": {
"change_language": "Change Language",
"environments": {
"delete": "Delete current environment",
"duplicate": "Duplicate current environment",
"duplicate_global": "Duplicate global environment",
"edit": "Edit current environment",
"edit_global": "Edit global environment",
"new": "Create new environment",
"new_variable": "Create a new environment variable",
"title": "Environments"
},
"general": {
"chat": "Chat with support",
"help_menu": "Help and support",
"open_docs": "Read Documentation",
"open_github": "Open GitHub repository",
"open_keybindings": "Keyboard shortcuts",
"social": "Social",
"title": "General"
},
"graphql": {
"connect": "Connect to server",
"disconnect": "Disconnect from server"
},
"miscellaneous": {
"invite": "Invite your friends to Hoppscotch",
"title": "Miscellaneous"
},
"request": {
"save_as_new": "Save as new request",
"select_method": "Select method",
"switch_to": "Switch to",
"tab_authorization": "Authorization tab",
"tab_body": "Body tab",
"tab_headers": "Headers tab",
"tab_parameters": "Parameters tab",
"tab_pre_request_script": "Pre-request script tab",
"tab_query": "Query tab",
"tab_tests": "Tests tab",
"tab_variables": "Variables tab"
},
"response": {
"copy": "Copy response",
"download": "Download response as file",
"title": "Response"
},
"section": {
"interceptor": "Interceptor",
"interface": "Interface",
"theme": "Theme",
"user": "User"
},
"settings": {
"change_interceptor": "Change Interceptor",
"change_language": "Change Language",
"theme": {
"black": "Black",
"dark": "Dark",
"light": "Light",
"system": "System preference"
}
},
"tab": {
"close_current": "Close current tab",
"close_others": "Close all other tabs",
"duplicate": "Duplicate current tab",
"new_tab": "Open a new tab",
"title": "Tabs"
},
"workspace": {
"delete": "Delete current team",
"edit": "Edit current team",
"invite": "Invite people to team",
"new": "Create new team",
"switch_to_personal": "Switch to your personal workspace",
"title": "Teams"
}
},
"sse": { "sse": {
"event_type": "Typ události", "event_type": "Typ události",
"log": "Záznam", "log": "Záznam",
@@ -777,11 +637,8 @@
"tab": { "tab": {
"authorization": "Povolení", "authorization": "Povolení",
"body": "Tělo", "body": "Tělo",
"close": "Close Tab",
"close_others": "Close other Tabs",
"collections": "Sbírky", "collections": "Sbírky",
"documentation": "Dokumentace", "documentation": "Dokumentace",
"duplicate": "Duplicate Tab",
"environments": "Environments", "environments": "Environments",
"headers": "Záhlaví", "headers": "Záhlaví",
"history": "Dějiny", "history": "Dějiny",

View File

@@ -5,7 +5,6 @@
"choose_file": "Vælg en fil", "choose_file": "Vælg en fil",
"clear": "Klar", "clear": "Klar",
"clear_all": "Slet alt", "clear_all": "Slet alt",
"clear_history": "Clear all History",
"close": "Close", "close": "Close",
"connect": "Opret forbindelse", "connect": "Opret forbindelse",
"connecting": "Connecting", "connecting": "Connecting",
@@ -32,7 +31,6 @@
"paste": "Paste", "paste": "Paste",
"prettify": "Prettify", "prettify": "Prettify",
"remove": "Fjerne", "remove": "Fjerne",
"rename": "Rename",
"restore": "Gendan", "restore": "Gendan",
"save": "Gemme", "save": "Gemme",
"scroll_to_bottom": "Scroll to bottom", "scroll_to_bottom": "Scroll to bottom",
@@ -79,8 +77,6 @@
"search": "Søg", "search": "Søg",
"share": "Del", "share": "Del",
"shortcuts": "Genveje", "shortcuts": "Genveje",
"social_description": "Follow us on social media to stay updated with the latest news, updates and releases.",
"social_links": "Social links",
"spotlight": "Spotlight", "spotlight": "Spotlight",
"status": "Status", "status": "Status",
"status_description": "Check the status of the website", "status_description": "Check the status of the website",
@@ -135,15 +131,12 @@
"renamed": "Samling omdøbt", "renamed": "Samling omdøbt",
"request_in_use": "Request in use", "request_in_use": "Request in use",
"save_as": "Gem som", "save_as": "Gem som",
"save_to_collection": "Save to Collection",
"select": "Vælg en samling", "select": "Vælg en samling",
"select_location": "Vælg placering", "select_location": "Vælg placering",
"select_team": "Vælg et hold", "select_team": "Vælg et hold",
"team_collections": "Teamsamlinger" "team_collections": "Teamsamlinger"
}, },
"confirm": { "confirm": {
"close_unsaved_tab": "Are you sure you want to close this tab?",
"close_unsaved_tabs": "Are you sure you want to close all tabs? {count} unsaved tabs will be lost.",
"exit_team": "Are you sure you want to leave this team?", "exit_team": "Are you sure you want to leave this team?",
"logout": "Er du sikker på, at du vil logge af?", "logout": "Er du sikker på, at du vil logge af?",
"remove_collection": "Er du sikker på, at du vil slette denne samling permanent?", "remove_collection": "Er du sikker på, at du vil slette denne samling permanent?",
@@ -157,11 +150,6 @@
"save_unsaved_tab": "Do you want to save changes made in this tab?", "save_unsaved_tab": "Do you want to save changes made in this tab?",
"sync": "Er du sikker på, at du vil synkronisere dette arbejdsområde?" "sync": "Er du sikker på, at du vil synkronisere dette arbejdsområde?"
}, },
"context_menu": {
"add_parameters": "Add to parameters",
"open_request_in_new_tab": "Open request in new tab",
"set_environment_variable": "Set as variable"
},
"count": { "count": {
"header": "Overskrift {count}", "header": "Overskrift {count}",
"message": "Besked {count}", "message": "Besked {count}",
@@ -204,31 +192,17 @@
"create_new": "Skab nyt miljø", "create_new": "Skab nyt miljø",
"created": "Environment created", "created": "Environment created",
"deleted": "Environment deletion", "deleted": "Environment deletion",
"duplicated": "Environment duplicated",
"edit": "Rediger miljø", "edit": "Rediger miljø",
"empty_variables": "No variables",
"global": "Global",
"global_variables": "Global variables",
"invalid_name": "Angiv et gyldigt navn på miljøet", "invalid_name": "Angiv et gyldigt navn på miljøet",
"list": "Environment variables",
"my_environments": "My Environments", "my_environments": "My Environments",
"name": "Name",
"nested_overflow": "nested environment variables are limited to 10 levels", "nested_overflow": "nested environment variables are limited to 10 levels",
"new": "Nyt miljø", "new": "Nyt miljø",
"no_active_environment": "No active environment",
"no_environment": "Intet miljø", "no_environment": "Intet miljø",
"no_environment_description": "No environments were selected. Choose what to do with the following variables.", "no_environment_description": "No environments were selected. Choose what to do with the following variables.",
"quick_peek": "Environment Quick Peek",
"replace_with_variable": "Replace with variable",
"scope": "Scope",
"select": "Vælg miljø", "select": "Vælg miljø",
"set": "Set environment",
"set_as_environment": "Set as environment",
"team_environments": "Team Environments", "team_environments": "Team Environments",
"title": "Miljøer", "title": "Miljøer",
"updated": "Environment updation", "updated": "Environment updation",
"value": "Value",
"variable": "Variable",
"variable_list": "Variabel liste" "variable_list": "Variabel liste"
}, },
"error": { "error": {
@@ -252,7 +226,6 @@
"no_duration": "Ingen varighed", "no_duration": "Ingen varighed",
"no_results_found": "No matches found", "no_results_found": "No matches found",
"page_not_found": "This page could not be found", "page_not_found": "This page could not be found",
"proxy_error": "Proxy error",
"script_fail": "Kunne ikke udføre pre-request script", "script_fail": "Kunne ikke udføre pre-request script",
"something_went_wrong": "Noget gik galt", "something_went_wrong": "Noget gik galt",
"test_script_fail": "Could not execute post-request script" "test_script_fail": "Could not execute post-request script"
@@ -278,13 +251,9 @@
"renamed": "Mappen omdøbt" "renamed": "Mappen omdøbt"
}, },
"graphql": { "graphql": {
"connection_switch_confirm": "Do you want to connect with the latest GraphQL endpoint?",
"connection_switch_new_url": "Switching to a tab will disconnected you from the active GraphQL connection. New connection URL is",
"connection_switch_url": "You're connected to a GraphQL endpoint the connection URL is",
"mutations": "Mutationer", "mutations": "Mutationer",
"schema": "Skema", "schema": "Skema",
"subscriptions": "Abonnementer", "subscriptions": "Abonnementer"
"switch_connection": "Switch connection"
}, },
"group": { "group": {
"time": "Time", "time": "Time",
@@ -338,36 +307,13 @@
"json_description": "Import collections from a Hoppscotch Collections JSON file", "json_description": "Import collections from a Hoppscotch Collections JSON file",
"title": "Importere" "title": "Importere"
}, },
"inspections": {
"description": "Inspect possible errors",
"environment": {
"add_environment": "Add to Environment",
"not_found": "Environment variable “{environment}” not found."
},
"header": {
"cookie": "The browser doesn't allow Hoppscotch to set the Cookie Header. While we're working on the Hoppscotch Desktop App (coming soon), please use the Authorization Header instead."
},
"response": {
"401_error": "Please check your authentication credentials.",
"404_error": "Please check your request URL and method type.",
"cors_error": "Please check your Cross-Origin Resource Sharing configuration.",
"default_error": "Please check your request.",
"network_error": "Please check your network connection."
},
"title": "Inspector",
"url": {
"extension_not_installed": "Extension not installed.",
"extension_unknown_origin": "Make sure you've added the API endpoint's origin to the Hoppscotch Browser Extension list.",
"extention_enable_action": "Enable Browser Extension",
"extention_not_enabled": "Extension not enabled."
}
},
"layout": { "layout": {
"collapse_collection": "Collapse or Expand Collections", "collapse_collection": "Collapse or Expand Collections",
"collapse_sidebar": "Collapse or Expand the sidebar", "collapse_sidebar": "Collapse or Expand the sidebar",
"column": "Vertical layout", "column": "Vertical layout",
"name": "Layout", "name": "Layout",
"row": "Horizontal layout" "row": "Horizontal layout",
"zen_mode": "Zen -tilstand"
}, },
"modal": { "modal": {
"close_unsaved_tab": "You have unsaved changes", "close_unsaved_tab": "You have unsaved changes",
@@ -472,7 +418,6 @@
"payload": "Nyttelast", "payload": "Nyttelast",
"query": "Forespørgsel", "query": "Forespørgsel",
"raw_body": "Raw Request Body", "raw_body": "Raw Request Body",
"rename": "Rename Request",
"renamed": "Anmodning omdøbt", "renamed": "Anmodning omdøbt",
"run": "Løb", "run": "Løb",
"save": "Gemme", "save": "Gemme",
@@ -480,7 +425,6 @@
"saved": "Anmodning gemt", "saved": "Anmodning gemt",
"share": "Del", "share": "Del",
"share_description": "Share Hoppscotch with your friends", "share_description": "Share Hoppscotch with your friends",
"stop": "Stop",
"title": "Anmodning", "title": "Anmodning",
"type": "Anmodningstype", "type": "Anmodningstype",
"url": "URL", "url": "URL",
@@ -488,7 +432,6 @@
"view_my_links": "View my links" "view_my_links": "View my links"
}, },
"response": { "response": {
"audio": "Audio",
"body": "Svarorgan", "body": "Svarorgan",
"filter_response_body": "Filter JSON response body (uses JSONPath syntax)", "filter_response_body": "Filter JSON response body (uses JSONPath syntax)",
"headers": "Overskrifter", "headers": "Overskrifter",
@@ -502,7 +445,6 @@
"status": "Status", "status": "Status",
"time": "Tid", "time": "Tid",
"title": "Respons", "title": "Respons",
"video": "Video",
"waiting_for_connection": "venter på forbindelse", "waiting_for_connection": "venter på forbindelse",
"xml": "XML" "xml": "XML"
}, },
@@ -515,6 +457,7 @@
"account_name_description": "Dette er dit visningsnavn.", "account_name_description": "Dette er dit visningsnavn.",
"background": "Baggrund", "background": "Baggrund",
"black_mode": "Sort", "black_mode": "Sort",
"change_font_size": "Skift skriftstørrelse",
"choose_language": "Vælg sprog", "choose_language": "Vælg sprog",
"dark_mode": "Mørk", "dark_mode": "Mørk",
"delete_account": "Delete account", "delete_account": "Delete account",
@@ -527,6 +470,10 @@
"extensions": "Udvidelser", "extensions": "Udvidelser",
"extensions_use_toggle": "Brug browserudvidelsen til at sende anmodninger (hvis de findes)", "extensions_use_toggle": "Brug browserudvidelsen til at sende anmodninger (hvis de findes)",
"follow": "Follow Us", "follow": "Follow Us",
"font_size": "Skriftstørrelse",
"font_size_large": "Stor",
"font_size_medium": "Medium",
"font_size_small": "Lille",
"interceptor": "Aflytter", "interceptor": "Aflytter",
"interceptor_description": "Middleware mellem applikation og API'er.", "interceptor_description": "Middleware mellem applikation og API'er.",
"language": "Sprog", "language": "Sprog",
@@ -591,27 +538,19 @@
"settings": "Gå til siden Indstillinger", "settings": "Gå til siden Indstillinger",
"title": "Navigation" "title": "Navigation"
}, },
"others": {
"prettify": "Prettify Editor's Content",
"title": "Others"
},
"request": { "request": {
"copy_request_link": "Kopiér anmodningslink", "copy_request_link": "Kopiér anmodningslink",
"delete_method": "Vælg SLET metode", "delete_method": "Vælg SLET metode",
"get_method": "Vælg GET -metode", "get_method": "Vælg GET -metode",
"head_method": "Vælg HEAD -metode", "head_method": "Vælg HEAD -metode",
"import_curl": "Import cURL",
"method": "Metode", "method": "Metode",
"next_method": "Vælg Næste metode", "next_method": "Vælg Næste metode",
"post_method": "Vælg POST -metode", "post_method": "Vælg POST -metode",
"previous_method": "Vælg Forrige metode", "previous_method": "Vælg Forrige metode",
"put_method": "Vælg PUT -metode", "put_method": "Vælg PUT -metode",
"rename": "Rename Request",
"reset_request": "Nulstil anmodning", "reset_request": "Nulstil anmodning",
"save_request": "Save Request",
"save_to_collections": "Gem i samlinger", "save_to_collections": "Gem i samlinger",
"send_request": "Send anmodning", "send_request": "Send anmodning",
"show_code": "Generate code snippet",
"title": "Anmodning" "title": "Anmodning"
}, },
"response": { "response": {
@@ -641,85 +580,6 @@
"log": "Log", "log": "Log",
"url": "URL" "url": "URL"
}, },
"spotlight": {
"change_language": "Change Language",
"environments": {
"delete": "Delete current environment",
"duplicate": "Duplicate current environment",
"duplicate_global": "Duplicate global environment",
"edit": "Edit current environment",
"edit_global": "Edit global environment",
"new": "Create new environment",
"new_variable": "Create a new environment variable",
"title": "Environments"
},
"general": {
"chat": "Chat with support",
"help_menu": "Help and support",
"open_docs": "Read Documentation",
"open_github": "Open GitHub repository",
"open_keybindings": "Keyboard shortcuts",
"social": "Social",
"title": "General"
},
"graphql": {
"connect": "Connect to server",
"disconnect": "Disconnect from server"
},
"miscellaneous": {
"invite": "Invite your friends to Hoppscotch",
"title": "Miscellaneous"
},
"request": {
"save_as_new": "Save as new request",
"select_method": "Select method",
"switch_to": "Switch to",
"tab_authorization": "Authorization tab",
"tab_body": "Body tab",
"tab_headers": "Headers tab",
"tab_parameters": "Parameters tab",
"tab_pre_request_script": "Pre-request script tab",
"tab_query": "Query tab",
"tab_tests": "Tests tab",
"tab_variables": "Variables tab"
},
"response": {
"copy": "Copy response",
"download": "Download response as file",
"title": "Response"
},
"section": {
"interceptor": "Interceptor",
"interface": "Interface",
"theme": "Theme",
"user": "User"
},
"settings": {
"change_interceptor": "Change Interceptor",
"change_language": "Change Language",
"theme": {
"black": "Black",
"dark": "Dark",
"light": "Light",
"system": "System preference"
}
},
"tab": {
"close_current": "Close current tab",
"close_others": "Close all other tabs",
"duplicate": "Duplicate current tab",
"new_tab": "Open a new tab",
"title": "Tabs"
},
"workspace": {
"delete": "Delete current team",
"edit": "Edit current team",
"invite": "Invite people to team",
"new": "Create new team",
"switch_to_personal": "Switch to your personal workspace",
"title": "Teams"
}
},
"sse": { "sse": {
"event_type": "Begivenhedstype", "event_type": "Begivenhedstype",
"log": "Log", "log": "Log",
@@ -777,11 +637,8 @@
"tab": { "tab": {
"authorization": "Bemyndigelse", "authorization": "Bemyndigelse",
"body": "Legeme", "body": "Legeme",
"close": "Close Tab",
"close_others": "Close other Tabs",
"collections": "Samlinger", "collections": "Samlinger",
"documentation": "Dokumentation", "documentation": "Dokumentation",
"duplicate": "Duplicate Tab",
"environments": "Environments", "environments": "Environments",
"headers": "Overskrifter", "headers": "Overskrifter",
"history": "Historie", "history": "Historie",

View File

@@ -5,7 +5,6 @@
"choose_file": "Datei auswählen", "choose_file": "Datei auswählen",
"clear": "Zurücksetzen", "clear": "Zurücksetzen",
"clear_all": "Alles zurücksetzen", "clear_all": "Alles zurücksetzen",
"clear_history": "Clear all History",
"close": "Close", "close": "Close",
"connect": "Verbinden", "connect": "Verbinden",
"connecting": "Connecting", "connecting": "Connecting",
@@ -32,7 +31,6 @@
"paste": "Einfügen", "paste": "Einfügen",
"prettify": "Verschönern", "prettify": "Verschönern",
"remove": "Entfernen", "remove": "Entfernen",
"rename": "Rename",
"restore": "Wiederherstellen", "restore": "Wiederherstellen",
"save": "Speichern", "save": "Speichern",
"scroll_to_bottom": "Scroll to bottom", "scroll_to_bottom": "Scroll to bottom",
@@ -79,8 +77,6 @@
"search": "Suche", "search": "Suche",
"share": "Teilen", "share": "Teilen",
"shortcuts": "Verknüpfungen", "shortcuts": "Verknüpfungen",
"social_description": "Follow us on social media to stay updated with the latest news, updates and releases.",
"social_links": "Social links",
"spotlight": "Scheinwerfer", "spotlight": "Scheinwerfer",
"status": "Status", "status": "Status",
"status_description": "Überprüfe den Status der Webseite", "status_description": "Überprüfe den Status der Webseite",
@@ -135,15 +131,12 @@
"renamed": "Sammlung umbenannt", "renamed": "Sammlung umbenannt",
"request_in_use": "Anfrage wird ausgeführt", "request_in_use": "Anfrage wird ausgeführt",
"save_as": "Speichern als", "save_as": "Speichern als",
"save_to_collection": "Save to Collection",
"select": "Wähle eine Sammlung", "select": "Wähle eine Sammlung",
"select_location": "Ort auswählen", "select_location": "Ort auswählen",
"select_team": "Wähle ein Team", "select_team": "Wähle ein Team",
"team_collections": "Teamsammlungen" "team_collections": "Teamsammlungen"
}, },
"confirm": { "confirm": {
"close_unsaved_tab": "Are you sure you want to close this tab?",
"close_unsaved_tabs": "Are you sure you want to close all tabs? {count} unsaved tabs will be lost.",
"exit_team": "Möchtest Du dieses Team wirklich verlassen?", "exit_team": "Möchtest Du dieses Team wirklich verlassen?",
"logout": "Möchtest Du Dich wirklich abmelden?", "logout": "Möchtest Du Dich wirklich abmelden?",
"remove_collection": "Möchtest Du diese Sammlung wirklich endgültig löschen?", "remove_collection": "Möchtest Du diese Sammlung wirklich endgültig löschen?",
@@ -157,11 +150,6 @@
"save_unsaved_tab": "Do you want to save changes made in this tab?", "save_unsaved_tab": "Do you want to save changes made in this tab?",
"sync": "Möchtest Du diesen Arbeitsbereich wirklich synchronisieren?" "sync": "Möchtest Du diesen Arbeitsbereich wirklich synchronisieren?"
}, },
"context_menu": {
"add_parameters": "Add to parameters",
"open_request_in_new_tab": "Open request in new tab",
"set_environment_variable": "Set as variable"
},
"count": { "count": {
"header": "Header {count}", "header": "Header {count}",
"message": "Nachricht {count}", "message": "Nachricht {count}",
@@ -204,31 +192,17 @@
"create_new": "Neue Umgebung erstellen", "create_new": "Neue Umgebung erstellen",
"created": "Umgebung erzeugt", "created": "Umgebung erzeugt",
"deleted": "Umgebung löschen", "deleted": "Umgebung löschen",
"duplicated": "Environment duplicated",
"edit": "Umgebung bearbeiten", "edit": "Umgebung bearbeiten",
"empty_variables": "No variables",
"global": "Global",
"global_variables": "Global variables",
"invalid_name": "Bitte gib einen gültigen Namen für die Umgebung an", "invalid_name": "Bitte gib einen gültigen Namen für die Umgebung an",
"list": "Environment variables",
"my_environments": "My Environments", "my_environments": "My Environments",
"name": "Name",
"nested_overflow": "Verschachtelte Umgebungsvariablen sind limitert auf 10 Unterebenen", "nested_overflow": "Verschachtelte Umgebungsvariablen sind limitert auf 10 Unterebenen",
"new": "Neue Umgebung", "new": "Neue Umgebung",
"no_active_environment": "No active environment",
"no_environment": "Keine Umgebung", "no_environment": "Keine Umgebung",
"no_environment_description": "Es wurden keine Umgebungen ausgewählt. Wähle aus, was mit den untenstehenden Variablen geschehen soll.", "no_environment_description": "Es wurden keine Umgebungen ausgewählt. Wähle aus, was mit den untenstehenden Variablen geschehen soll.",
"quick_peek": "Environment Quick Peek",
"replace_with_variable": "Replace with variable",
"scope": "Scope",
"select": "Umgebung auswählen", "select": "Umgebung auswählen",
"set": "Set environment",
"set_as_environment": "Set as environment",
"team_environments": "Team Environments", "team_environments": "Team Environments",
"title": "Umgebungen", "title": "Umgebungen",
"updated": "Umgebung aktualisiert", "updated": "Umgebung aktualisiert",
"value": "Value",
"variable": "Variable",
"variable_list": "Variablenliste" "variable_list": "Variablenliste"
}, },
"error": { "error": {
@@ -252,7 +226,6 @@
"no_duration": "Keine Dauer", "no_duration": "Keine Dauer",
"no_results_found": "No matches found", "no_results_found": "No matches found",
"page_not_found": "This page could not be found", "page_not_found": "This page could not be found",
"proxy_error": "Proxy error",
"script_fail": "Pre-Request-Skripte konnte nicht ausgeführt werden", "script_fail": "Pre-Request-Skripte konnte nicht ausgeführt werden",
"something_went_wrong": "Etwas ist schief gelaufen", "something_went_wrong": "Etwas ist schief gelaufen",
"test_script_fail": "Testskripts konnten nicht ausgeführt werden" "test_script_fail": "Testskripts konnten nicht ausgeführt werden"
@@ -278,13 +251,9 @@
"renamed": "Ordner umbenannt" "renamed": "Ordner umbenannt"
}, },
"graphql": { "graphql": {
"connection_switch_confirm": "Do you want to connect with the latest GraphQL endpoint?",
"connection_switch_new_url": "Switching to a tab will disconnected you from the active GraphQL connection. New connection URL is",
"connection_switch_url": "You're connected to a GraphQL endpoint the connection URL is",
"mutations": "Mutationen", "mutations": "Mutationen",
"schema": "Schema", "schema": "Schema",
"subscriptions": "Abonnements", "subscriptions": "Abonnements"
"switch_connection": "Switch connection"
}, },
"group": { "group": {
"time": "Time", "time": "Time",
@@ -338,36 +307,13 @@
"json_description": "Hoppscotch Sammlungsdatei (JSON) importieren", "json_description": "Hoppscotch Sammlungsdatei (JSON) importieren",
"title": "Importieren" "title": "Importieren"
}, },
"inspections": {
"description": "Inspect possible errors",
"environment": {
"add_environment": "Add to Environment",
"not_found": "Environment variable “{environment}” not found."
},
"header": {
"cookie": "The browser doesn't allow Hoppscotch to set the Cookie Header. While we're working on the Hoppscotch Desktop App (coming soon), please use the Authorization Header instead."
},
"response": {
"401_error": "Please check your authentication credentials.",
"404_error": "Please check your request URL and method type.",
"cors_error": "Please check your Cross-Origin Resource Sharing configuration.",
"default_error": "Please check your request.",
"network_error": "Please check your network connection."
},
"title": "Inspector",
"url": {
"extension_not_installed": "Extension not installed.",
"extension_unknown_origin": "Make sure you've added the API endpoint's origin to the Hoppscotch Browser Extension list.",
"extention_enable_action": "Enable Browser Extension",
"extention_not_enabled": "Extension not enabled."
}
},
"layout": { "layout": {
"collapse_collection": "Sammlungen ein- oder ausklappen", "collapse_collection": "Sammlungen ein- oder ausklappen",
"collapse_sidebar": "Seitenleiste ein- oder ausklappen", "collapse_sidebar": "Seitenleiste ein- oder ausklappen",
"column": "Vertikales Layout", "column": "Vertikales Layout",
"name": "Layout", "name": "Layout",
"row": "Horizontales Layout" "row": "Horizontales Layout",
"zen_mode": "Zen-Modus"
}, },
"modal": { "modal": {
"close_unsaved_tab": "You have unsaved changes", "close_unsaved_tab": "You have unsaved changes",
@@ -472,7 +418,6 @@
"payload": "Nutzlast", "payload": "Nutzlast",
"query": "Anfrage", "query": "Anfrage",
"raw_body": "Roher Anfragetext", "raw_body": "Roher Anfragetext",
"rename": "Rename Request",
"renamed": "Anfrage umbenannt", "renamed": "Anfrage umbenannt",
"run": "Ausführen", "run": "Ausführen",
"save": "Speichern", "save": "Speichern",
@@ -480,7 +425,6 @@
"saved": "Anfrage gespeichert", "saved": "Anfrage gespeichert",
"share": "Teilen", "share": "Teilen",
"share_description": "Teile Hoppscotch mit Deinen Freunden", "share_description": "Teile Hoppscotch mit Deinen Freunden",
"stop": "Stop",
"title": "Anfrage", "title": "Anfrage",
"type": "Anfragetyp", "type": "Anfragetyp",
"url": "URL", "url": "URL",
@@ -488,7 +432,6 @@
"view_my_links": "View my links" "view_my_links": "View my links"
}, },
"response": { "response": {
"audio": "Audio",
"body": "Antworttext", "body": "Antworttext",
"filter_response_body": "Filter JSON response body (uses JSONPath syntax)", "filter_response_body": "Filter JSON response body (uses JSONPath syntax)",
"headers": "Header", "headers": "Header",
@@ -502,7 +445,6 @@
"status": "Status", "status": "Status",
"time": "Zeit", "time": "Zeit",
"title": "Antwort", "title": "Antwort",
"video": "Video",
"waiting_for_connection": "auf Verbindung warten", "waiting_for_connection": "auf Verbindung warten",
"xml": "XML" "xml": "XML"
}, },
@@ -515,6 +457,7 @@
"account_name_description": "Dies ist Dein Anzeigename.", "account_name_description": "Dies ist Dein Anzeigename.",
"background": "Hintergrund", "background": "Hintergrund",
"black_mode": "Schwarz", "black_mode": "Schwarz",
"change_font_size": "Schriftgröße ändern",
"choose_language": "Sprache wählen", "choose_language": "Sprache wählen",
"dark_mode": "Dunkel", "dark_mode": "Dunkel",
"delete_account": "Delete account", "delete_account": "Delete account",
@@ -527,6 +470,10 @@
"extensions": "Erweiterungen", "extensions": "Erweiterungen",
"extensions_use_toggle": "Verwende die Browsererweiterung, um Anfragen zu senden (falls vorhanden)", "extensions_use_toggle": "Verwende die Browsererweiterung, um Anfragen zu senden (falls vorhanden)",
"follow": "Folge uns", "follow": "Folge uns",
"font_size": "Schriftgröße",
"font_size_large": "Groß",
"font_size_medium": "Mittel",
"font_size_small": "Klein",
"interceptor": "Interceptor", "interceptor": "Interceptor",
"interceptor_description": "Middleware zwischen Anwendung und APIs.", "interceptor_description": "Middleware zwischen Anwendung und APIs.",
"language": "Sprache", "language": "Sprache",
@@ -591,27 +538,19 @@
"settings": "Einstellungen öffnen", "settings": "Einstellungen öffnen",
"title": "Navigation" "title": "Navigation"
}, },
"others": {
"prettify": "Prettify Editor's Content",
"title": "Others"
},
"request": { "request": {
"copy_request_link": "Anfragelink kopieren", "copy_request_link": "Anfragelink kopieren",
"delete_method": "DELETE-Methode auswählen", "delete_method": "DELETE-Methode auswählen",
"get_method": "GET-Methode auswählen", "get_method": "GET-Methode auswählen",
"head_method": "HEAD-Methode auswählen", "head_method": "HEAD-Methode auswählen",
"import_curl": "Import cURL",
"method": "Methode", "method": "Methode",
"next_method": "Nächste Methode auswählen", "next_method": "Nächste Methode auswählen",
"post_method": "POST-Methode auswählen", "post_method": "POST-Methode auswählen",
"previous_method": "Vorherige Methode auswählen", "previous_method": "Vorherige Methode auswählen",
"put_method": "PUT-Methode auswählen", "put_method": "PUT-Methode auswählen",
"rename": "Rename Request",
"reset_request": "Anfrage zurücksetzen", "reset_request": "Anfrage zurücksetzen",
"save_request": "Save Request",
"save_to_collections": "In Sammlungen speichern", "save_to_collections": "In Sammlungen speichern",
"send_request": "Anfrage senden", "send_request": "Anfrage senden",
"show_code": "Generate code snippet",
"title": "Anfrage" "title": "Anfrage"
}, },
"response": { "response": {
@@ -641,85 +580,6 @@
"log": "Protokoll", "log": "Protokoll",
"url": "URL" "url": "URL"
}, },
"spotlight": {
"change_language": "Change Language",
"environments": {
"delete": "Delete current environment",
"duplicate": "Duplicate current environment",
"duplicate_global": "Duplicate global environment",
"edit": "Edit current environment",
"edit_global": "Edit global environment",
"new": "Create new environment",
"new_variable": "Create a new environment variable",
"title": "Environments"
},
"general": {
"chat": "Chat with support",
"help_menu": "Help and support",
"open_docs": "Read Documentation",
"open_github": "Open GitHub repository",
"open_keybindings": "Keyboard shortcuts",
"social": "Social",
"title": "General"
},
"graphql": {
"connect": "Connect to server",
"disconnect": "Disconnect from server"
},
"miscellaneous": {
"invite": "Invite your friends to Hoppscotch",
"title": "Miscellaneous"
},
"request": {
"save_as_new": "Save as new request",
"select_method": "Select method",
"switch_to": "Switch to",
"tab_authorization": "Authorization tab",
"tab_body": "Body tab",
"tab_headers": "Headers tab",
"tab_parameters": "Parameters tab",
"tab_pre_request_script": "Pre-request script tab",
"tab_query": "Query tab",
"tab_tests": "Tests tab",
"tab_variables": "Variables tab"
},
"response": {
"copy": "Copy response",
"download": "Download response as file",
"title": "Response"
},
"section": {
"interceptor": "Interceptor",
"interface": "Interface",
"theme": "Theme",
"user": "User"
},
"settings": {
"change_interceptor": "Change Interceptor",
"change_language": "Change Language",
"theme": {
"black": "Black",
"dark": "Dark",
"light": "Light",
"system": "System preference"
}
},
"tab": {
"close_current": "Close current tab",
"close_others": "Close all other tabs",
"duplicate": "Duplicate current tab",
"new_tab": "Open a new tab",
"title": "Tabs"
},
"workspace": {
"delete": "Delete current team",
"edit": "Edit current team",
"invite": "Invite people to team",
"new": "Create new team",
"switch_to_personal": "Switch to your personal workspace",
"title": "Teams"
}
},
"sse": { "sse": {
"event_type": "Ereignistyp", "event_type": "Ereignistyp",
"log": "Protokoll", "log": "Protokoll",
@@ -777,11 +637,8 @@
"tab": { "tab": {
"authorization": "Autorisierung", "authorization": "Autorisierung",
"body": "Anfragekörper", "body": "Anfragekörper",
"close": "Close Tab",
"close_others": "Close other Tabs",
"collections": "Sammlungen", "collections": "Sammlungen",
"documentation": "Dokumentation", "documentation": "Dokumentation",
"duplicate": "Duplicate Tab",
"environments": "Environments", "environments": "Environments",
"headers": "Header", "headers": "Header",
"history": "Verlauf", "history": "Verlauf",

View File

@@ -5,7 +5,6 @@
"choose_file": "Επιλέξτε ένα αρχείο", "choose_file": "Επιλέξτε ένα αρχείο",
"clear": "Σαφή", "clear": "Σαφή",
"clear_all": "Τα καθαρίζω όλα", "clear_all": "Τα καθαρίζω όλα",
"clear_history": "Clear all History",
"close": "Close", "close": "Close",
"connect": "Συνδέω-συωδεομαι", "connect": "Συνδέω-συωδεομαι",
"connecting": "Connecting", "connecting": "Connecting",
@@ -32,7 +31,6 @@
"paste": "Paste", "paste": "Paste",
"prettify": "Ωραιοποιώ", "prettify": "Ωραιοποιώ",
"remove": "Αφαιρώ", "remove": "Αφαιρώ",
"rename": "Rename",
"restore": "Επαναφέρω", "restore": "Επαναφέρω",
"save": "Αποθηκεύσετε", "save": "Αποθηκεύσετε",
"scroll_to_bottom": "Scroll to bottom", "scroll_to_bottom": "Scroll to bottom",
@@ -79,8 +77,6 @@
"search": "Αναζήτηση", "search": "Αναζήτηση",
"share": "Μερίδιο", "share": "Μερίδιο",
"shortcuts": "Συντομεύσεις", "shortcuts": "Συντομεύσεις",
"social_description": "Follow us on social media to stay updated with the latest news, updates and releases.",
"social_links": "Social links",
"spotlight": "Προβολέας θέατρου", "spotlight": "Προβολέας θέατρου",
"status": "Κατάσταση", "status": "Κατάσταση",
"status_description": "Ελέγξτε το status της Ιστοσελίδας", "status_description": "Ελέγξτε το status της Ιστοσελίδας",
@@ -135,15 +131,12 @@
"renamed": "Η συλλογή μετονομάστηκε", "renamed": "Η συλλογή μετονομάστηκε",
"request_in_use": "Request in use", "request_in_use": "Request in use",
"save_as": "Αποθήκευση ως", "save_as": "Αποθήκευση ως",
"save_to_collection": "Save to Collection",
"select": "Επιλέξτε μια Συλλογή", "select": "Επιλέξτε μια Συλλογή",
"select_location": "Επιλέξτε τοποθεσία", "select_location": "Επιλέξτε τοποθεσία",
"select_team": "Επιλέξτε μια ομάδα", "select_team": "Επιλέξτε μια ομάδα",
"team_collections": "Συλλογές ομάδων" "team_collections": "Συλλογές ομάδων"
}, },
"confirm": { "confirm": {
"close_unsaved_tab": "Are you sure you want to close this tab?",
"close_unsaved_tabs": "Are you sure you want to close all tabs? {count} unsaved tabs will be lost.",
"exit_team": "Are you sure you want to leave this team?", "exit_team": "Are you sure you want to leave this team?",
"logout": "Είστε βέβαιοι ότι θέλετε να αποσυνδεθείτε?", "logout": "Είστε βέβαιοι ότι θέλετε να αποσυνδεθείτε?",
"remove_collection": "Είστε βέβαιοι ότι θέλετε να διαγράψετε οριστικά αυτήν τη συλλογή;", "remove_collection": "Είστε βέβαιοι ότι θέλετε να διαγράψετε οριστικά αυτήν τη συλλογή;",
@@ -157,11 +150,6 @@
"save_unsaved_tab": "Do you want to save changes made in this tab?", "save_unsaved_tab": "Do you want to save changes made in this tab?",
"sync": "Είστε βέβαιοι ότι θέλετε να συγχρονίσετε αυτόν τον χώρο εργασίας;" "sync": "Είστε βέβαιοι ότι θέλετε να συγχρονίσετε αυτόν τον χώρο εργασίας;"
}, },
"context_menu": {
"add_parameters": "Add to parameters",
"open_request_in_new_tab": "Open request in new tab",
"set_environment_variable": "Set as variable"
},
"count": { "count": {
"header": "Κεφαλίδα {count}", "header": "Κεφαλίδα {count}",
"message": "Μήνυμα {count}", "message": "Μήνυμα {count}",
@@ -204,31 +192,17 @@
"create_new": "Δημιουργήστε νέο περιβάλλον", "create_new": "Δημιουργήστε νέο περιβάλλον",
"created": "Το Περιβάλλον δημιουργήθηκε", "created": "Το Περιβάλλον δημιουργήθηκε",
"deleted": "Διαγραφή Περιβάλλοντος", "deleted": "Διαγραφή Περιβάλλοντος",
"duplicated": "Environment duplicated",
"edit": "Επεξεργασία Περιβάλλοντος", "edit": "Επεξεργασία Περιβάλλοντος",
"empty_variables": "No variables",
"global": "Global",
"global_variables": "Global variables",
"invalid_name": "Καταχωρίστε ένα έγκυρο όνομα για το περιβάλλον", "invalid_name": "Καταχωρίστε ένα έγκυρο όνομα για το περιβάλλον",
"list": "Environment variables",
"my_environments": "Τα Περιβάλλοντα μου", "my_environments": "Τα Περιβάλλοντα μου",
"name": "Name",
"nested_overflow": "Οι 'φωλιασμένες' μεταβλητές περιβάλλοντος είναι περιορισμένες σε 10 επίπεδα", "nested_overflow": "Οι 'φωλιασμένες' μεταβλητές περιβάλλοντος είναι περιορισμένες σε 10 επίπεδα",
"new": "Νέο Περιβάλλον", "new": "Νέο Περιβάλλον",
"no_active_environment": "No active environment",
"no_environment": "Χωρίς περιβάλλον", "no_environment": "Χωρίς περιβάλλον",
"no_environment_description": "Δέν επιλέχθηκε κάποιο περιβάλλον. Διαλέξτε τι θέλετε να κάνετε με τις παρακάτω μεταβλητές.", "no_environment_description": "Δέν επιλέχθηκε κάποιο περιβάλλον. Διαλέξτε τι θέλετε να κάνετε με τις παρακάτω μεταβλητές.",
"quick_peek": "Environment Quick Peek",
"replace_with_variable": "Replace with variable",
"scope": "Scope",
"select": "Επιλέξτε περιβάλλον", "select": "Επιλέξτε περιβάλλον",
"set": "Set environment",
"set_as_environment": "Set as environment",
"team_environments": "Περιβάλλοντα Ομάδας", "team_environments": "Περιβάλλοντα Ομάδας",
"title": "Περιβάλλοντα", "title": "Περιβάλλοντα",
"updated": "Αναβάθμιση Περιβάλλοντος", "updated": "Αναβάθμιση Περιβάλλοντος",
"value": "Value",
"variable": "Variable",
"variable_list": "Λίστα μεταβλητών" "variable_list": "Λίστα μεταβλητών"
}, },
"error": { "error": {
@@ -252,7 +226,6 @@
"no_duration": "Χωρίς διάρκεια", "no_duration": "Χωρίς διάρκεια",
"no_results_found": "Δεν βρέθηκαν αντιστοιχίες", "no_results_found": "Δεν βρέθηκαν αντιστοιχίες",
"page_not_found": "Αυτή η σελίδα δεν βρέθηκε", "page_not_found": "Αυτή η σελίδα δεν βρέθηκε",
"proxy_error": "Proxy error",
"script_fail": "Δεν ήταν δυνατή η εκτέλεση του σεναρίου πριν από το αίτημα", "script_fail": "Δεν ήταν δυνατή η εκτέλεση του σεναρίου πριν από το αίτημα",
"something_went_wrong": "Κάτι πήγε στραβά", "something_went_wrong": "Κάτι πήγε στραβά",
"test_script_fail": "Δεν μπορεσε να εκτελεστεί το post-request script" "test_script_fail": "Δεν μπορεσε να εκτελεστεί το post-request script"
@@ -278,13 +251,9 @@
"renamed": "Ο φάκελος μετονομάστηκε" "renamed": "Ο φάκελος μετονομάστηκε"
}, },
"graphql": { "graphql": {
"connection_switch_confirm": "Do you want to connect with the latest GraphQL endpoint?",
"connection_switch_new_url": "Switching to a tab will disconnected you from the active GraphQL connection. New connection URL is",
"connection_switch_url": "You're connected to a GraphQL endpoint the connection URL is",
"mutations": "Μεταλλάξεις", "mutations": "Μεταλλάξεις",
"schema": "Σχήμα", "schema": "Σχήμα",
"subscriptions": "Συνδρομές", "subscriptions": "Συνδρομές"
"switch_connection": "Switch connection"
}, },
"group": { "group": {
"time": "Time", "time": "Time",
@@ -338,36 +307,13 @@
"json_description": "Εισαγωγή συλλογών αρχείο JSON Hoppscotch Collections", "json_description": "Εισαγωγή συλλογών αρχείο JSON Hoppscotch Collections",
"title": "Εισαγωγή" "title": "Εισαγωγή"
}, },
"inspections": {
"description": "Inspect possible errors",
"environment": {
"add_environment": "Add to Environment",
"not_found": "Environment variable “{environment}” not found."
},
"header": {
"cookie": "The browser doesn't allow Hoppscotch to set the Cookie Header. While we're working on the Hoppscotch Desktop App (coming soon), please use the Authorization Header instead."
},
"response": {
"401_error": "Please check your authentication credentials.",
"404_error": "Please check your request URL and method type.",
"cors_error": "Please check your Cross-Origin Resource Sharing configuration.",
"default_error": "Please check your request.",
"network_error": "Please check your network connection."
},
"title": "Inspector",
"url": {
"extension_not_installed": "Extension not installed.",
"extension_unknown_origin": "Make sure you've added the API endpoint's origin to the Hoppscotch Browser Extension list.",
"extention_enable_action": "Enable Browser Extension",
"extention_not_enabled": "Extension not enabled."
}
},
"layout": { "layout": {
"collapse_collection": "Σύμπτυξη ή επέκταση Συλλογών", "collapse_collection": "Σύμπτυξη ή επέκταση Συλλογών",
"collapse_sidebar": "Σύμπτυξη ή επέκταση του sidebar", "collapse_sidebar": "Σύμπτυξη ή επέκταση του sidebar",
"column": "Κατακόρυφη Διάταξη", "column": "Κατακόρυφη Διάταξη",
"name": "Διάταξη", "name": "Διάταξη",
"row": "Οριζόντια Διάταξη" "row": "Οριζόντια Διάταξη",
"zen_mode": "Λειτουργία Zen"
}, },
"modal": { "modal": {
"close_unsaved_tab": "You have unsaved changes", "close_unsaved_tab": "You have unsaved changes",
@@ -472,7 +418,6 @@
"payload": "Φορτίο επί πληρωμή", "payload": "Φορτίο επί πληρωμή",
"query": "Ερώτηση", "query": "Ερώτηση",
"raw_body": "Σώμα Ακατέργαστου Αιτήματος", "raw_body": "Σώμα Ακατέργαστου Αιτήματος",
"rename": "Rename Request",
"renamed": "Το αίτημα μετονομάστηκε", "renamed": "Το αίτημα μετονομάστηκε",
"run": "Τρέξιμο", "run": "Τρέξιμο",
"save": "Σώσει", "save": "Σώσει",
@@ -480,7 +425,6 @@
"saved": "Το αίτημα αποθηκεύτηκε", "saved": "Το αίτημα αποθηκεύτηκε",
"share": "Μερίδιο", "share": "Μερίδιο",
"share_description": "Κοινοποίηση Hoppscotch στους φίλους σου", "share_description": "Κοινοποίηση Hoppscotch στους φίλους σου",
"stop": "Stop",
"title": "Αίτηση", "title": "Αίτηση",
"type": "Τύπος αιτήματος", "type": "Τύπος αιτήματος",
"url": "URL", "url": "URL",
@@ -488,7 +432,6 @@
"view_my_links": "Προβολή των links μου" "view_my_links": "Προβολή των links μου"
}, },
"response": { "response": {
"audio": "Audio",
"body": "Σώμα απόκρισης", "body": "Σώμα απόκρισης",
"filter_response_body": "Filter JSON response body (uses JSONPath syntax)", "filter_response_body": "Filter JSON response body (uses JSONPath syntax)",
"headers": "Κεφαλίδες", "headers": "Κεφαλίδες",
@@ -502,7 +445,6 @@
"status": "Κατάσταση", "status": "Κατάσταση",
"time": "χρόνος", "time": "χρόνος",
"title": "Απάντηση", "title": "Απάντηση",
"video": "Video",
"waiting_for_connection": "περιμένοντας τη σύνδεση", "waiting_for_connection": "περιμένοντας τη σύνδεση",
"xml": "XML" "xml": "XML"
}, },
@@ -515,6 +457,7 @@
"account_name_description": "Αυτό είναι το εμφανιζόμενο όνομά σας.", "account_name_description": "Αυτό είναι το εμφανιζόμενο όνομά σας.",
"background": "Ιστορικό", "background": "Ιστορικό",
"black_mode": "Μαύρος", "black_mode": "Μαύρος",
"change_font_size": "Αλλαγή μεγέθους γραμματοσειράς",
"choose_language": "Διάλεξε γλώσσα", "choose_language": "Διάλεξε γλώσσα",
"dark_mode": "Σκοτάδι", "dark_mode": "Σκοτάδι",
"delete_account": "Delete account", "delete_account": "Delete account",
@@ -527,6 +470,10 @@
"extensions": "Επεκτάσεις", "extensions": "Επεκτάσεις",
"extensions_use_toggle": "Χρησιμοποιήστε την επέκταση του προγράμματος περιήγησης για να στείλετε αιτήματα (εάν υπάρχουν)", "extensions_use_toggle": "Χρησιμοποιήστε την επέκταση του προγράμματος περιήγησης για να στείλετε αιτήματα (εάν υπάρχουν)",
"follow": "Ακολούθησε Μας", "follow": "Ακολούθησε Μας",
"font_size": "Μέγεθος γραμματοσειράς",
"font_size_large": "Μεγάλο",
"font_size_medium": "Μεσαίο",
"font_size_small": "Μικρό",
"interceptor": "Αναχαιτιστής", "interceptor": "Αναχαιτιστής",
"interceptor_description": "Middleware μεταξύ εφαρμογής και API.", "interceptor_description": "Middleware μεταξύ εφαρμογής και API.",
"language": "Γλώσσα", "language": "Γλώσσα",
@@ -591,27 +538,19 @@
"settings": "Μεταβείτε στη σελίδα Ρυθμίσεις", "settings": "Μεταβείτε στη σελίδα Ρυθμίσεις",
"title": "Πλοήγηση" "title": "Πλοήγηση"
}, },
"others": {
"prettify": "Prettify Editor's Content",
"title": "Others"
},
"request": { "request": {
"copy_request_link": "Αντιγραφή συνδέσμου αιτήματος", "copy_request_link": "Αντιγραφή συνδέσμου αιτήματος",
"delete_method": "Επιλέξτε ΔΙΑΓΡΑΦΗ μεθόδου", "delete_method": "Επιλέξτε ΔΙΑΓΡΑΦΗ μεθόδου",
"get_method": "Επιλέξτε μέθοδο GET", "get_method": "Επιλέξτε μέθοδο GET",
"head_method": "Επιλέξτε μέθοδο HEAD", "head_method": "Επιλέξτε μέθοδο HEAD",
"import_curl": "Import cURL",
"method": "Μέθοδος", "method": "Μέθοδος",
"next_method": "Επιλέξτε Επόμενη μέθοδος", "next_method": "Επιλέξτε Επόμενη μέθοδος",
"post_method": "Επιλέξτε μέθοδο POST", "post_method": "Επιλέξτε μέθοδο POST",
"previous_method": "Επιλέξτε Προηγούμενη μέθοδος", "previous_method": "Επιλέξτε Προηγούμενη μέθοδος",
"put_method": "Επιλέξτε μέθοδο PUT", "put_method": "Επιλέξτε μέθοδο PUT",
"rename": "Rename Request",
"reset_request": "Επαναφορά αιτήματος", "reset_request": "Επαναφορά αιτήματος",
"save_request": "Save Request",
"save_to_collections": "Αποθήκευση στις Συλλογές", "save_to_collections": "Αποθήκευση στις Συλλογές",
"send_request": "Στείλε αίτημα", "send_request": "Στείλε αίτημα",
"show_code": "Generate code snippet",
"title": "Αίτηση" "title": "Αίτηση"
}, },
"response": { "response": {
@@ -641,85 +580,6 @@
"log": "Logs", "log": "Logs",
"url": "URL" "url": "URL"
}, },
"spotlight": {
"change_language": "Change Language",
"environments": {
"delete": "Delete current environment",
"duplicate": "Duplicate current environment",
"duplicate_global": "Duplicate global environment",
"edit": "Edit current environment",
"edit_global": "Edit global environment",
"new": "Create new environment",
"new_variable": "Create a new environment variable",
"title": "Environments"
},
"general": {
"chat": "Chat with support",
"help_menu": "Help and support",
"open_docs": "Read Documentation",
"open_github": "Open GitHub repository",
"open_keybindings": "Keyboard shortcuts",
"social": "Social",
"title": "General"
},
"graphql": {
"connect": "Connect to server",
"disconnect": "Disconnect from server"
},
"miscellaneous": {
"invite": "Invite your friends to Hoppscotch",
"title": "Miscellaneous"
},
"request": {
"save_as_new": "Save as new request",
"select_method": "Select method",
"switch_to": "Switch to",
"tab_authorization": "Authorization tab",
"tab_body": "Body tab",
"tab_headers": "Headers tab",
"tab_parameters": "Parameters tab",
"tab_pre_request_script": "Pre-request script tab",
"tab_query": "Query tab",
"tab_tests": "Tests tab",
"tab_variables": "Variables tab"
},
"response": {
"copy": "Copy response",
"download": "Download response as file",
"title": "Response"
},
"section": {
"interceptor": "Interceptor",
"interface": "Interface",
"theme": "Theme",
"user": "User"
},
"settings": {
"change_interceptor": "Change Interceptor",
"change_language": "Change Language",
"theme": {
"black": "Black",
"dark": "Dark",
"light": "Light",
"system": "System preference"
}
},
"tab": {
"close_current": "Close current tab",
"close_others": "Close all other tabs",
"duplicate": "Duplicate current tab",
"new_tab": "Open a new tab",
"title": "Tabs"
},
"workspace": {
"delete": "Delete current team",
"edit": "Edit current team",
"invite": "Invite people to team",
"new": "Create new team",
"switch_to_personal": "Switch to your personal workspace",
"title": "Teams"
}
},
"sse": { "sse": {
"event_type": "Τύπος συμβάντος", "event_type": "Τύπος συμβάντος",
"log": "Logs", "log": "Logs",
@@ -777,11 +637,8 @@
"tab": { "tab": {
"authorization": "Εξουσιοδότηση", "authorization": "Εξουσιοδότηση",
"body": "Σώμα", "body": "Σώμα",
"close": "Close Tab",
"close_others": "Close other Tabs",
"collections": "Συλλογές", "collections": "Συλλογές",
"documentation": "Τεκμηρίωση", "documentation": "Τεκμηρίωση",
"duplicate": "Duplicate Tab",
"environments": "Environments", "environments": "Environments",
"headers": "Κεφαλίδες", "headers": "Κεφαλίδες",
"history": "Ιστορία", "history": "Ιστορία",

View File

@@ -5,7 +5,6 @@
"choose_file": "Choose a file", "choose_file": "Choose a file",
"clear": "Clear", "clear": "Clear",
"clear_all": "Clear all", "clear_all": "Clear all",
"clear_history": "Clear all History",
"close": "Close", "close": "Close",
"connect": "Connect", "connect": "Connect",
"connecting": "Connecting", "connecting": "Connecting",
@@ -32,7 +31,6 @@
"paste": "Paste", "paste": "Paste",
"prettify": "Prettify", "prettify": "Prettify",
"remove": "Remove", "remove": "Remove",
"rename": "Rename",
"restore": "Restore", "restore": "Restore",
"save": "Save", "save": "Save",
"scroll_to_bottom": "Scroll to bottom", "scroll_to_bottom": "Scroll to bottom",
@@ -79,8 +77,6 @@
"search": "Search", "search": "Search",
"share": "Share", "share": "Share",
"shortcuts": "Shortcuts", "shortcuts": "Shortcuts",
"social_description": "Follow us on social media to stay updated with the latest news, updates and releases.",
"social_links": "Social links",
"spotlight": "Spotlight", "spotlight": "Spotlight",
"status": "Status", "status": "Status",
"status_description": "Check the status of the website", "status_description": "Check the status of the website",
@@ -112,7 +108,6 @@
}, },
"authorization": { "authorization": {
"generate_token": "Generate Token", "generate_token": "Generate Token",
"graphql_headers": "Authorization Headers are sent as part of the payload to connection_init",
"include_in_url": "Include in URL", "include_in_url": "Include in URL",
"learn": "Learn how", "learn": "Learn how",
"pass_key_by": "Pass by", "pass_key_by": "Pass by",
@@ -125,7 +120,6 @@
"created": "Collection created", "created": "Collection created",
"different_parent": "Cannot reorder collection with different parent", "different_parent": "Cannot reorder collection with different parent",
"edit": "Edit Collection", "edit": "Edit Collection",
"import_or_create": "Import or create a collection",
"invalid_name": "Please provide a name for the collection", "invalid_name": "Please provide a name for the collection",
"invalid_root_move": "Collection already in the root", "invalid_root_move": "Collection already in the root",
"moved": "Moved Successfully", "moved": "Moved Successfully",
@@ -137,15 +131,12 @@
"renamed": "Collection renamed", "renamed": "Collection renamed",
"request_in_use": "Request in use", "request_in_use": "Request in use",
"save_as": "Save as", "save_as": "Save as",
"save_to_collection": "Save to Collection",
"select": "Select a Collection", "select": "Select a Collection",
"select_location": "Select location", "select_location": "Select location",
"select_team": "Select a team", "select_team": "Select a team",
"team_collections": "Team Collections" "team_collections": "Team Collections"
}, },
"confirm": { "confirm": {
"close_unsaved_tab": "Are you sure you want to close this tab?",
"close_unsaved_tabs": "Are you sure you want to close all tabs? {count} unsaved tabs will be lost.",
"exit_team": "Are you sure you want to leave this team?", "exit_team": "Are you sure you want to leave this team?",
"logout": "Are you sure you want to logout?", "logout": "Are you sure you want to logout?",
"remove_collection": "Are you sure you want to permanently delete this collection?", "remove_collection": "Are you sure you want to permanently delete this collection?",
@@ -159,11 +150,6 @@
"save_unsaved_tab": "Do you want to save changes made in this tab?", "save_unsaved_tab": "Do you want to save changes made in this tab?",
"sync": "Would you like to restore your workspace from cloud? This will discard your local progress." "sync": "Would you like to restore your workspace from cloud? This will discard your local progress."
}, },
"context_menu": {
"add_parameters": "Add to parameters",
"open_request_in_new_tab": "Open request in new tab",
"set_environment_variable": "Set as variable"
},
"count": { "count": {
"header": "Header {count}", "header": "Header {count}",
"message": "Message {count}", "message": "Message {count}",
@@ -206,32 +192,17 @@
"create_new": "Create new environment", "create_new": "Create new environment",
"created": "Environment created", "created": "Environment created",
"deleted": "Environment deletion", "deleted": "Environment deletion",
"duplicated": "Environment duplicated",
"edit": "Edit Environment", "edit": "Edit Environment",
"empty_variables": "No variables",
"global": "Global",
"global_variables": "Global variables",
"import_or_create": "Import or create a environment",
"invalid_name": "Please provide a name for the environment", "invalid_name": "Please provide a name for the environment",
"list": "Environment variables",
"my_environments": "My Environments", "my_environments": "My Environments",
"name": "Name",
"nested_overflow": "nested environment variables are limited to 10 levels", "nested_overflow": "nested environment variables are limited to 10 levels",
"new": "New Environment", "new": "New Environment",
"no_active_environment": "No active environment",
"no_environment": "No environment", "no_environment": "No environment",
"no_environment_description": "No environments were selected. Choose what to do with the following variables.", "no_environment_description": "No environments were selected. Choose what to do with the following variables.",
"quick_peek": "Environment Quick Peek",
"replace_with_variable": "Replace with variable",
"scope": "Scope",
"select": "Select environment", "select": "Select environment",
"set": "Set environment",
"set_as_environment": "Set as environment",
"team_environments": "Team Environments", "team_environments": "Team Environments",
"title": "Environments", "title": "Environments",
"updated": "Environment updated", "updated": "Environment updated",
"value": "Value",
"variable": "Variable",
"variable_list": "Variable List" "variable_list": "Variable List"
}, },
"error": { "error": {
@@ -252,12 +223,9 @@
"json_prettify_invalid_body": "Couldn't prettify an invalid body, solve json syntax errors and try again", "json_prettify_invalid_body": "Couldn't prettify an invalid body, solve json syntax errors and try again",
"network_error": "There seems to be a network error. Please try again.", "network_error": "There seems to be a network error. Please try again.",
"network_fail": "Could not send request", "network_fail": "Could not send request",
"no_collections_to_export": "No collections to export. Please create a collection to get started.",
"no_duration": "No duration", "no_duration": "No duration",
"no_environments_to_export": "No environments to export. Please create an environment to get started.",
"no_results_found": "No matches found", "no_results_found": "No matches found",
"page_not_found": "This page could not be found", "page_not_found": "This page could not be found",
"proxy_error": "Proxy error",
"script_fail": "Could not execute pre-request script", "script_fail": "Could not execute pre-request script",
"something_went_wrong": "Something went wrong", "something_went_wrong": "Something went wrong",
"test_script_fail": "Could not execute post-request script" "test_script_fail": "Could not execute post-request script"
@@ -283,13 +251,9 @@
"renamed": "Folder renamed" "renamed": "Folder renamed"
}, },
"graphql": { "graphql": {
"connection_switch_confirm": "Do you want to connect with the latest GraphQL endpoint?",
"connection_switch_new_url": "Switching to a tab will disconnected you from the active GraphQL connection. New connection URL is",
"connection_switch_url": "You're connected to a GraphQL endpoint the connection URL is",
"mutations": "Mutations", "mutations": "Mutations",
"schema": "Schema", "schema": "Schema",
"subscriptions": "Subscriptions", "subscriptions": "Subscriptions"
"switch_connection": "Switch connection"
}, },
"group": { "group": {
"time": "Time", "time": "Time",
@@ -343,36 +307,13 @@
"json_description": "Import collections from a Hoppscotch Collections JSON file", "json_description": "Import collections from a Hoppscotch Collections JSON file",
"title": "Import" "title": "Import"
}, },
"inspections": {
"description": "Inspect possible errors",
"environment": {
"add_environment": "Add to Environment",
"not_found": "Environment variable “{environment}” not found."
},
"header": {
"cookie": "The browser doesn't allow Hoppscotch to set the Cookie Header. While we're working on the Hoppscotch Desktop App (coming soon), please use the Authorization Header instead."
},
"response": {
"401_error": "Please check your authentication credentials.",
"404_error": "Please check your request URL and method type.",
"cors_error": "Please check your Cross-Origin Resource Sharing configuration.",
"default_error": "Please check your request.",
"network_error": "Please check your network connection."
},
"title": "Inspector",
"url": {
"extension_not_installed": "Extension not installed.",
"extension_unknown_origin": "Make sure you've added the API endpoint's origin to the Hoppscotch Browser Extension list.",
"extention_enable_action": "Enable Browser Extension",
"extention_not_enabled": "Extension not enabled."
}
},
"layout": { "layout": {
"collapse_collection": "Collapse or Expand Collections", "collapse_collection": "Collapse or Expand Collections",
"collapse_sidebar": "Collapse or Expand the sidebar", "collapse_sidebar": "Collapse or Expand the sidebar",
"column": "Vertical layout", "column": "Vertical layout",
"name": "Layout", "name": "Layout",
"row": "Horizontal layout" "row": "Horizontal layout",
"zen_mode": "Zen mode"
}, },
"modal": { "modal": {
"close_unsaved_tab": "You have unsaved changes", "close_unsaved_tab": "You have unsaved changes",
@@ -461,7 +402,6 @@
"enter_curl": "Enter cURL command", "enter_curl": "Enter cURL command",
"generate_code": "Generate code", "generate_code": "Generate code",
"generated_code": "Generated code", "generated_code": "Generated code",
"go_to_authorization_tab": "Go to Authorization",
"header_list": "Header List", "header_list": "Header List",
"invalid_name": "Please provide a name for the request", "invalid_name": "Please provide a name for the request",
"method": "Method", "method": "Method",
@@ -478,7 +418,6 @@
"payload": "Payload", "payload": "Payload",
"query": "Query", "query": "Query",
"raw_body": "Raw Request Body", "raw_body": "Raw Request Body",
"rename": "Rename Request",
"renamed": "Request renamed", "renamed": "Request renamed",
"run": "Run", "run": "Run",
"save": "Save", "save": "Save",
@@ -486,7 +425,6 @@
"saved": "Request saved", "saved": "Request saved",
"share": "Share", "share": "Share",
"share_description": "Share Hoppscotch with your friends", "share_description": "Share Hoppscotch with your friends",
"stop": "Stop",
"title": "Request", "title": "Request",
"type": "Request type", "type": "Request type",
"url": "URL", "url": "URL",
@@ -494,7 +432,6 @@
"view_my_links": "View my links" "view_my_links": "View my links"
}, },
"response": { "response": {
"audio": "Audio",
"body": "Response Body", "body": "Response Body",
"filter_response_body": "Filter JSON response body (uses JSONPath syntax)", "filter_response_body": "Filter JSON response body (uses JSONPath syntax)",
"headers": "Headers", "headers": "Headers",
@@ -508,7 +445,6 @@
"status": "Status", "status": "Status",
"time": "Time", "time": "Time",
"title": "Response", "title": "Response",
"video": "Video",
"waiting_for_connection": "waiting for connection", "waiting_for_connection": "waiting for connection",
"xml": "XML" "xml": "XML"
}, },
@@ -521,6 +457,7 @@
"account_name_description": "This is your display name.", "account_name_description": "This is your display name.",
"background": "Background", "background": "Background",
"black_mode": "Black", "black_mode": "Black",
"change_font_size": "Change font size",
"choose_language": "Choose language", "choose_language": "Choose language",
"dark_mode": "Dark", "dark_mode": "Dark",
"delete_account": "Delete account", "delete_account": "Delete account",
@@ -533,6 +470,10 @@
"extensions": "Browser extension", "extensions": "Browser extension",
"extensions_use_toggle": "Use the browser extension to send requests (if present)", "extensions_use_toggle": "Use the browser extension to send requests (if present)",
"follow": "Follow us", "follow": "Follow us",
"font_size": "Font size",
"font_size_large": "Large",
"font_size_medium": "Medium",
"font_size_small": "Small",
"interceptor": "Interceptor", "interceptor": "Interceptor",
"interceptor_description": "Middleware between application and APIs.", "interceptor_description": "Middleware between application and APIs.",
"language": "Language", "language": "Language",
@@ -597,27 +538,19 @@
"settings": "Go to Settings page", "settings": "Go to Settings page",
"title": "Navigation" "title": "Navigation"
}, },
"others": {
"prettify": "Prettify Editor's Content",
"title": "Others"
},
"request": { "request": {
"copy_request_link": "Copy Request Link", "copy_request_link": "Copy Request Link",
"delete_method": "Select DELETE method", "delete_method": "Select DELETE method",
"get_method": "Select GET method", "get_method": "Select GET method",
"head_method": "Select HEAD method", "head_method": "Select HEAD method",
"import_curl": "Import cURL",
"method": "Method", "method": "Method",
"next_method": "Select Next method", "next_method": "Select Next method",
"post_method": "Select POST method", "post_method": "Select POST method",
"previous_method": "Select Previous method", "previous_method": "Select Previous method",
"put_method": "Select PUT method", "put_method": "Select PUT method",
"rename": "Rename Request",
"reset_request": "Reset Request", "reset_request": "Reset Request",
"save_request": "Save Request",
"save_to_collections": "Save to Collections", "save_to_collections": "Save to Collections",
"send_request": "Send Request", "send_request": "Send Request",
"show_code": "Generate code snippet",
"title": "Request" "title": "Request"
}, },
"response": { "response": {
@@ -626,10 +559,10 @@
"title": "Response" "title": "Response"
}, },
"theme": { "theme": {
"black": "Switch theme to Black Mode", "black": "Switch theme to black mode",
"dark": "Switch theme to Dark Mode", "dark": "Switch theme to dark mode",
"light": "Switch theme to Light Mode", "light": "Switch theme to light mode",
"system": "Switch theme to System Mode", "system": "Switch theme to system mode",
"title": "Theme" "title": "Theme"
} }
}, },
@@ -647,85 +580,6 @@
"log": "Log", "log": "Log",
"url": "URL" "url": "URL"
}, },
"spotlight": {
"change_language": "Change Language",
"environments": {
"delete": "Delete current environment",
"duplicate": "Duplicate current environment",
"duplicate_global": "Duplicate global environment",
"edit": "Edit current environment",
"edit_global": "Edit global environment",
"new": "Create new environment",
"new_variable": "Create a new environment variable",
"title": "Environments"
},
"general": {
"chat": "Chat with support",
"help_menu": "Help and support",
"open_docs": "Read Documentation",
"open_github": "Open GitHub repository",
"open_keybindings": "Keyboard shortcuts",
"social": "Social",
"title": "General"
},
"graphql": {
"connect": "Connect to server",
"disconnect": "Disconnect from server"
},
"miscellaneous": {
"invite": "Invite your friends to Hoppscotch",
"title": "Miscellaneous"
},
"request": {
"save_as_new": "Save as new request",
"select_method": "Select method",
"switch_to": "Switch to",
"tab_authorization": "Authorization tab",
"tab_body": "Body tab",
"tab_headers": "Headers tab",
"tab_parameters": "Parameters tab",
"tab_pre_request_script": "Pre-request script tab",
"tab_query": "Query tab",
"tab_tests": "Tests tab",
"tab_variables": "Variables tab"
},
"response": {
"copy": "Copy response",
"download": "Download response as file",
"title": "Response"
},
"section": {
"interceptor": "Interceptor",
"interface": "Interface",
"theme": "Theme",
"user": "User"
},
"settings": {
"change_interceptor": "Change Interceptor",
"change_language": "Change Language",
"theme": {
"black": "Black",
"dark": "Dark",
"light": "Light",
"system": "System preference"
}
},
"tab": {
"close_current": "Close current tab",
"close_others": "Close all other tabs",
"duplicate": "Duplicate current tab",
"new_tab": "Open a new tab",
"title": "Tabs"
},
"workspace": {
"delete": "Delete current team",
"edit": "Edit current team",
"invite": "Invite people to team",
"new": "Create new team",
"switch_to_personal": "Switch to your personal workspace",
"title": "Teams"
}
},
"sse": { "sse": {
"event_type": "Event type", "event_type": "Event type",
"log": "Log", "log": "Log",
@@ -749,11 +603,9 @@
"disconnected_from": "Disconnected from {name}", "disconnected_from": "Disconnected from {name}",
"docs_generated": "Documentation generated", "docs_generated": "Documentation generated",
"download_started": "Download started", "download_started": "Download started",
"download_failed": "Download failed",
"enabled": "Enabled", "enabled": "Enabled",
"file_imported": "File imported", "file_imported": "File imported",
"finished_in": "Finished in {duration} ms", "finished_in": "Finished in {duration} ms",
"hide": "Hide",
"history_deleted": "History deleted", "history_deleted": "History deleted",
"linewrap": "Wrap lines", "linewrap": "Wrap lines",
"loading": "Loading...", "loading": "Loading...",
@@ -764,7 +616,6 @@
"published_error": "Something went wrong while publishing msg: {topic} to topic: {message}", "published_error": "Something went wrong while publishing msg: {topic} to topic: {message}",
"published_message": "Published message: {message} to topic: {topic}", "published_message": "Published message: {message} to topic: {topic}",
"reconnection_error": "Failed to reconnect", "reconnection_error": "Failed to reconnect",
"show":"Show",
"subscribed_failed": "Failed to subscribe to topic: {topic}", "subscribed_failed": "Failed to subscribe to topic: {topic}",
"subscribed_success": "Successfully subscribed to topic: {topic}", "subscribed_success": "Successfully subscribed to topic: {topic}",
"unsubscribed_failed": "Failed to unsubscribe from topic: {topic}", "unsubscribed_failed": "Failed to unsubscribe from topic: {topic}",
@@ -774,7 +625,7 @@
"support": { "support": {
"changelog": "Read more about latest releases", "changelog": "Read more about latest releases",
"chat": "Questions? Chat with us!", "chat": "Questions? Chat with us!",
"community": "Ask questions and help others", "community": "Ask questions and help others",
"documentation": "Read more about Hoppscotch", "documentation": "Read more about Hoppscotch",
"forum": "Ask questions and get answers", "forum": "Ask questions and get answers",
"github": "Follow us on Github", "github": "Follow us on Github",
@@ -786,11 +637,8 @@
"tab": { "tab": {
"authorization": "Authorization", "authorization": "Authorization",
"body": "Body", "body": "Body",
"close": "Close Tab",
"close_others": "Close other Tabs",
"collections": "Collections", "collections": "Collections",
"documentation": "Documentation", "documentation": "Documentation",
"duplicate": "Duplicate Tab",
"environments": "Environments", "environments": "Environments",
"headers": "Headers", "headers": "Headers",
"history": "History", "history": "History",
@@ -846,14 +694,12 @@
"new": "New Team", "new": "New Team",
"new_created": "New team created", "new_created": "New team created",
"new_name": "My New Team", "new_name": "My New Team",
"no_access": "You do not have edit access to this team", "no_access": "You do not have edit access to these collections",
"no_invite_found": "Invitation not found. Contact your team owner.", "no_invite_found": "Invitation not found. Contact your team owner.",
"no_request_found": "Request not found.", "no_request_found": "Request not found.",
"not_found": "Team not found. Contact your team owner.", "not_found": "Team not found. Contact your team owner.",
"not_valid_viewer": "You are not a valid viewer. Contact your team owner.", "not_valid_viewer": "You are not a valid viewer. Contact your team owner.",
"parent_coll_move": "Cannot move collection to a child collection", "parent_coll_move": "Cannot move collection to a child collection",
"success_invites": "Success invites",
"failed_invites": "Failed invites",
"pending_invites": "Pending invites", "pending_invites": "Pending invites",
"permissions": "Permissions", "permissions": "Permissions",
"same_target_destination": "Same target and destination", "same_target_destination": "Same target and destination",

View File

@@ -1,46 +1,44 @@
{ {
"action": { "action": {
"autoscroll": "Desplazamiento automático", "autoscroll": "Autoscroll",
"cancel": "Cancelar", "cancel": "Cancelar",
"choose_file": "Seleccionar archivo", "choose_file": "Seleccionar archivo",
"clear": "Limpiar", "clear": "Limpiar",
"clear_all": "Limpiar todo", "clear_all": "Limpiar todo",
"clear_history": "Clear all History",
"close": "Cerrar", "close": "Cerrar",
"connect": "Conectar", "connect": "Conectar",
"connecting": "Conectando", "connecting": "Connecting",
"copy": "Copiar", "copy": "Copiar",
"delete": "Borrar", "delete": "Borrar",
"disconnect": "Desconectar", "disconnect": "Desconectar",
"dismiss": "Descartar", "dismiss": "Descartar",
"dont_save": "No guardar", "dont_save": "Don't save",
"download_file": "Descargar archivo", "download_file": "Descargar archivo",
"drag_to_reorder": "Arrastrar para reordenar", "drag_to_reorder": "Arrastrar para reordenar",
"duplicate": "Duplicar", "duplicate": "Duplicar",
"edit": "Editar", "edit": "Editar",
"filter": "Filtrar", "filter": "Filter",
"go_back": "Volver", "go_back": "Volver",
"go_forward": "Adelante", "go_forward": "Go forward",
"group_by": "Agrupar por", "group_by": "Group by",
"label": "Etiqueta", "label": "Etiqueta",
"learn_more": "Aprender más", "learn_more": "Aprender más",
"less": "Menos", "less": "Menos",
"more": "Más", "more": "Más",
"new": "Nuevo", "new": "Nuevo",
"no": "No", "no": "No",
"open_workspace": "Abrir espacio de trabajo", "open_workspace": "Open workspace",
"paste": "Pegar", "paste": "Pegar",
"prettify": "Embellecer", "prettify": "Embellecer",
"remove": "Eliminar", "remove": "Eliminar",
"rename": "Rename",
"restore": "Restaurar", "restore": "Restaurar",
"save": "Guardar", "save": "Guardar",
"scroll_to_bottom": "Desplazar hacia abajo", "scroll_to_bottom": "Scroll to bottom",
"scroll_to_top": "Desplazar hacia arriba", "scroll_to_top": "Scroll to top",
"search": "Buscar", "search": "Buscar",
"send": "Enviar", "send": "Enviar",
"start": "Comenzar", "start": "Comenzar",
"starting": "Iniciando", "starting": "Starting",
"stop": "Detener", "stop": "Detener",
"to_close": "para cerrar", "to_close": "para cerrar",
"to_navigate": "para navegar", "to_navigate": "para navegar",
@@ -58,16 +56,16 @@
"chat_with_us": "Habla con nosotros", "chat_with_us": "Habla con nosotros",
"contact_us": "Contáctanos", "contact_us": "Contáctanos",
"copy": "Copiar", "copy": "Copiar",
"copy_user_id": "Copiar token de autenticación de usuario", "copy_user_id": "Copy User Auth Token",
"developer_option": "Opciones para desarrolladores", "developer_option": "Developer options",
"developer_option_description": "Herramientas para desarrolladores que ayudan en el desarrollo y mantenimiento de Hoppscotch.", "developer_option_description": "Developer tools which helps in development and maintenance of Hoppscotch.",
"discord": "Discord", "discord": "Discord",
"documentation": "Documentación", "documentation": "Documentación",
"github": "GitHub", "github": "GitHub",
"help": "Ayuda y comentarios", "help": "Ayuda y comentarios",
"home": "Inicio", "home": "Inicio",
"invite": "Invitar", "invite": "Invitar",
"invite_description": "En Hoppscotch, diseñamos una interfaz simple e intuitiva para crear y administrar tus APIs. Hoppscotch es una herramienta que le ayuda a crear, probar, documentar y compartir tus APIs.", "invite_description": "En Hoppscotch, diseñamos una interfaz simple e intuitiva para crear y administrar sus APIs. Hoppscotch es una herramienta que le ayuda a crear, probar, documentar y compartir sus APIs.",
"invite_your_friends": "Invita a tus amigos", "invite_your_friends": "Invita a tus amigos",
"join_discord_community": "Únete a nuestra comunidad Discord", "join_discord_community": "Únete a nuestra comunidad Discord",
"keyboard_shortcuts": "Atajos de teclado", "keyboard_shortcuts": "Atajos de teclado",
@@ -79,11 +77,9 @@
"search": "Buscar", "search": "Buscar",
"share": "Compartir", "share": "Compartir",
"shortcuts": "Atajos", "shortcuts": "Atajos",
"social_description": "Follow us on social media to stay updated with the latest news, updates and releases.",
"social_links": "Social links",
"spotlight": "Destacar", "spotlight": "Destacar",
"status": "Estado", "status": "Estado",
"status_description": "Comprobar el estado del sitio web", "status_description": "Check the status of the website",
"terms_and_privacy": "Términos y privacidad", "terms_and_privacy": "Términos y privacidad",
"twitter": "Twitter", "twitter": "Twitter",
"type_a_command_search": "Escribe un comando o buscar algo…", "type_a_command_search": "Escribe un comando o buscar algo…",
@@ -122,45 +118,37 @@
}, },
"collection": { "collection": {
"created": "Colección creada", "created": "Colección creada",
"different_parent": "No se puede reordenar la colección con un padre diferente", "different_parent": "Cannot reorder collection with different parent",
"edit": "Editar colección", "edit": "Editar colección",
"invalid_name": "Proporciona un nombre válido para la colección.", "invalid_name": "Proporciona un nombre válido para la colección.",
"invalid_root_move": "La colección ya está en la raíz", "invalid_root_move": "Collection already in the root",
"moved": "Movido con éxito", "moved": "Moved Successfully",
"my_collections": "Mis colecciones", "my_collections": "Mis colecciones",
"name": "Mi nueva colección", "name": "Mi nueva colección",
"name_length_insufficient": "El nombre de la colección debe tener al menos 3 caracteres", "name_length_insufficient": "El nombre de la colección debe tener al menos 3 caracteres",
"new": "Nueva colección", "new": "Nueva colección",
"order_changed": "Orden de colección actualizada", "order_changed": "Collection Order Updated",
"renamed": "Colección renombrada", "renamed": "Colección renombrada",
"request_in_use": "Solicitud en uso", "request_in_use": "Petición en uso",
"save_as": "Guardar como", "save_as": "Guardar como",
"save_to_collection": "Save to Collection",
"select": "Seleccionar colección", "select": "Seleccionar colección",
"select_location": "Seleccionar ubicación", "select_location": "Seleccionar ubicación",
"select_team": "Seleccionar equipo", "select_team": "Seleccionar equipo",
"team_collections": "Colecciones de equipos" "team_collections": "Colecciones de equipos"
}, },
"confirm": { "confirm": {
"close_unsaved_tab": "Are you sure you want to close this tab?",
"close_unsaved_tabs": "Are you sure you want to close all tabs? {count} unsaved tabs will be lost.",
"exit_team": "¿Estás seguro de que quieres dejar este equipo?", "exit_team": "¿Estás seguro de que quieres dejar este equipo?",
"logout": "¿Estás seguro de que deseas cerrar la sesión?", "logout": "¿Está seguro de que desea cerrar la sesión?",
"remove_collection": "¿Estás seguro de que deseas eliminar esta colección de forma permanente?", "remove_collection": "¿Está seguro de que desea eliminar esta colección de forma permanente?",
"remove_environment": "¿Estás seguro de que deseas eliminar este entorno de forma permanente?", "remove_environment": "¿Está seguro de que desea eliminar este entorno de forma permanente?",
"remove_folder": "¿Estás seguro de que deseas eliminar esta carpeta de forma permanente?", "remove_folder": "¿Está seguro de que desea eliminar esta carpeta de forma permanente?",
"remove_history": "¿Estás seguro de que deseas eliminar todo el historial de forma permanente?", "remove_history": "¿Está seguro de que desea eliminar todo el historial de forma permanente?",
"remove_request": "¿Estás seguro de que deseas eliminar esta solicitud de forma permanente?", "remove_request": "¿Está seguro de que desea eliminar esta petición de forma permanente?",
"remove_team": "¿Estás seguro de que deseas eliminar este equipo?", "remove_team": "¿Está seguro de que desea eliminar este equipo?",
"remove_telemetry": "¿Estás seguro de que deseas darse de baja de la telemetría?", "remove_telemetry": "¿Está seguro de que desea darse de baja de la telemetría?",
"request_change": "¿Estás seguro de que deseas descartar la solicitud actual, los cambios no guardados se perderán.", "request_change": "Are you sure you want to discard current request, unsaved changes will be lost.",
"save_unsaved_tab": "¿Deseas guardar los cambios realizados en esta pestaña?", "save_unsaved_tab": "Do you want to save changes made in this tab?",
"sync": "¿Estás seguro de que deseas sincronizar este espacio de trabajo?" "sync": "¿Está seguro de que desea sincronizar este espacio de trabajo?"
},
"context_menu": {
"add_parameters": "Add to parameters",
"open_request_in_new_tab": "Open request in new tab",
"set_environment_variable": "Set as variable"
}, },
"count": { "count": {
"header": "Encabezado {count}", "header": "Encabezado {count}",
@@ -175,28 +163,28 @@
"generate_message": "Importar cualquier colección de Hoppscotch para generar documentación de la API sobre la marcha." "generate_message": "Importar cualquier colección de Hoppscotch para generar documentación de la API sobre la marcha."
}, },
"empty": { "empty": {
"authorization": "Esta solicitud no utiliza ninguna autorización.", "authorization": "Esta petición no utiliza ninguna autorización.",
"body": "Esta solicitud no tiene cuerpo", "body": "Esta petición no tiene cuerpo",
"collection": "La colección está vacía", "collection": "La colección está vacía",
"collections": "Las colecciones están vacías", "collections": "Las colecciones están vacías",
"documentation": "Conectarse a un punto final de GraphQL para ver la documentación", "documentation": "Conectarse a un punto final de GraphQL para ver la documentación",
"endpoint": "El punto final no puede estar vacío", "endpoint": "El punto final no puede estar vacío",
"environments": "Los entornos están vacíos", "environments": "Los entornos están vacíos",
"folder": "La carpeta está vacía", "folder": "La carpeta está vacía",
"headers": "Esta solicitud no tiene encabezados", "headers": "Esta petición no tiene encabezados",
"history": "El historial está vacío", "history": "El historial está vacío",
"invites": "La lista de invitados está vacía", "invites": "La lista de invitados está vacía",
"members": "El equipo está vacío", "members": "El equipo está vacío",
"parameters": "Esta solicitud no tiene ningún parámetro", "parameters": "Esta petición no tiene ningún parámetro",
"pending_invites": "No hay invitaciones pendientes para este equipo", "pending_invites": "No hay invitaciones pendientes para este equipo",
"profile": "Iniciar sesión para ver tu perfil", "profile": "Iniciar sesión para ver tu perfil",
"protocols": "Los protocolos están vacíos", "protocols": "Los protocolos están vacíos",
"schema": "Conectarse a un punto final de GraphQL", "schema": "Conectarse a un punto final de GraphQL",
"shortcodes": "Aún no se han creado Shortcodes", "shortcodes": "Los shortcodes están vacíos",
"subscription": "Subscriptions are empty", "subscription": "Subscriptions are empty",
"team_name": "Nombre del equipo vacío", "team_name": "Nombre del equipo vacío",
"teams": "Los equipos están vacíos", "teams": "Los equipos están vacíos",
"tests": "No hay pruebas para esta solicitud" "tests": "No hay pruebas para esta petición"
}, },
"environment": { "environment": {
"add_to_global": "Añadir a Global", "add_to_global": "Añadir a Global",
@@ -204,55 +192,40 @@
"create_new": "Crear un nuevo entorno", "create_new": "Crear un nuevo entorno",
"created": "Environment created", "created": "Environment created",
"deleted": "Eliminar el entorno", "deleted": "Eliminar el entorno",
"duplicated": "Environment duplicated",
"edit": "Editar entorno", "edit": "Editar entorno",
"empty_variables": "No variables",
"global": "Global",
"global_variables": "Global variables",
"invalid_name": "Proporciona un nombre válido para el entorno.", "invalid_name": "Proporciona un nombre válido para el entorno.",
"list": "Environment variables", "my_environments": "My Environments",
"my_environments": "Mis entornos",
"name": "Name",
"nested_overflow": "las variables de entorno anidadas están limitadas a 10 niveles", "nested_overflow": "las variables de entorno anidadas están limitadas a 10 niveles",
"new": "Nuevo entorno", "new": "Nuevo entorno",
"no_active_environment": "No active environment",
"no_environment": "Sin entorno", "no_environment": "Sin entorno",
"no_environment_description": "No se ha seleccionado ningún entorno. Elije qué hacer con las siguientes variables.", "no_environment_description": "No se ha seleccionado ningún entorno. Elije qué hacer con las siguientes variables.",
"quick_peek": "Environment Quick Peek",
"replace_with_variable": "Replace with variable",
"scope": "Scope",
"select": "Seleccionar entorno", "select": "Seleccionar entorno",
"set": "Set environment", "team_environments": "Team Environments",
"set_as_environment": "Set as environment",
"team_environments": "Entornos de trabajo en equipo",
"title": "Entornos", "title": "Entornos",
"updated": "Entorno actualizado", "updated": "Actualización del entorno",
"value": "Value",
"variable": "Variable",
"variable_list": "Lista de variables" "variable_list": "Lista de variables"
}, },
"error": { "error": {
"browser_support_sse": "Este navegador no parece ser compatible con los eventos enviados por el servidor.", "browser_support_sse": "Este navegador no parece ser compatible con los eventos enviados por el servidor.",
"check_console_details": "Consulta el registro de la consola para obtener más detalles.", "check_console_details": "Consulta el registro de la consola para obtener más detalles.",
"curl_invalid_format": "cURL no está formateado correctamente", "curl_invalid_format": "cURL no está formateado correctamente",
"danger_zone": "Zona de peligro", "danger_zone": "Danger zone",
"delete_account": "Tu cuenta es actualmente propietaria en estos equipos:", "delete_account": "Your account is currently an owner in these teams:",
"delete_account_description": "Para poder eliminar tu cuenta, debes darte de baja, transferir la propiedad o eliminar estos equipos.", "delete_account_description": "You must either remove yourself, transfer ownership, or delete these teams before you can delete your account.",
"empty_req_name": "Nombre de solicitud vacío", "empty_req_name": "Nombre de petición vacío",
"f12_details": "(F12 para más detalles)", "f12_details": "(F12 para más detalles)",
"gql_prettify_invalid_query": "No se puede aplicar embellecedor a una consulta no válida, resuelve los errores de sintaxis de la consulta y vuelve a intentarlo", "gql_prettify_invalid_query": "No se puede aplicar embellecedor a una consulta no válida, resuelve los errores de sintaxis de la consulta y vuelve a intentarlo",
"incomplete_config_urls": "URLs de configuración incompletas", "incomplete_config_urls": "URLs de configuración incompletas",
"incorrect_email": "Correo electrónico incorrecto", "incorrect_email": "Correo electrónico incorrecto",
"invalid_link": "Enlace no válido", "invalid_link": "Enlace no válido",
"invalid_link_description": "El enlace que has pulsado no es válido o ha caducado.", "invalid_link_description": "El enlace que has pulsado no es válido o ha caducado.",
"json_parsing_failed": "JSON no válido", "json_parsing_failed": "Invalid JSON",
"json_prettify_invalid_body": "No se puede aplicar embellecedor a un cuerpo inválido, resuelve errores de sintaxis json y vuelve a intentarlo", "json_prettify_invalid_body": "No se puede aplicar embellecedor a un cuerpo inválido, resuelve errores de sintaxis json y vuelve a intentarlo",
"network_error": "Parece que hay un error de red. Por favor, inténtalo de nuevo.", "network_error": "Parece que hay un error de red. Por favor, inténtalo de nuevo.",
"network_fail": "No se pudo enviar la solicitud", "network_fail": "No se pudo enviar la petición",
"no_duration": "Sin duración", "no_duration": "Sin duración",
"no_results_found": "No se han encontrado coincidencias", "no_results_found": "No matches found",
"page_not_found": "No se ha podido encontrar esta página", "page_not_found": "This page could not be found",
"proxy_error": "Proxy error",
"script_fail": "No se pudo ejecutar el script de solicitud previa", "script_fail": "No se pudo ejecutar el script de solicitud previa",
"something_went_wrong": "Algo salió mal", "something_went_wrong": "Algo salió mal",
"test_script_fail": "No se ha podido ejecutar la secuencia de comandos posterior a la solicitud" "test_script_fail": "No se ha podido ejecutar la secuencia de comandos posterior a la solicitud"
@@ -278,16 +251,12 @@
"renamed": "Carpeta renombrada" "renamed": "Carpeta renombrada"
}, },
"graphql": { "graphql": {
"connection_switch_confirm": "Do you want to connect with the latest GraphQL endpoint?",
"connection_switch_new_url": "Switching to a tab will disconnected you from the active GraphQL connection. New connection URL is",
"connection_switch_url": "You're connected to a GraphQL endpoint the connection URL is",
"mutations": "Mutaciones", "mutations": "Mutaciones",
"schema": "Esquema", "schema": "Esquema",
"subscriptions": "Suscripciones", "subscriptions": "Suscripciones"
"switch_connection": "Switch connection"
}, },
"group": { "group": {
"time": "Tiempo", "time": "Time",
"url": "URL" "url": "URL"
}, },
"header": { "header": {
@@ -296,19 +265,19 @@
"save_workspace": "Guardar mi espacio de trabajo" "save_workspace": "Guardar mi espacio de trabajo"
}, },
"helpers": { "helpers": {
"authorization": "El encabezado de autorización se generará automáticamente cuando se envía la solicitud.", "authorization": "El encabezado de autorización se generará automáticamente cuando se envía la petición.",
"generate_documentation_first": "Generar la documentación primero", "generate_documentation_first": "Generar la documentación primero",
"network_fail": "No se puede acceder a la API. Comprueba tu conexión de red y vuelve a intentarlo.", "network_fail": "No se puede acceder a la API. Comprueba tu conexión de red y vuelve a intentarlo.",
"offline": "Parece estar desconectado. Es posible que los datos de este espacio de trabajo no estén actualizados.", "offline": "Parece estar desconectado. Es posible que los datos de este espacio de trabajo no estén actualizados.",
"offline_short": "Pareces estar desconectado.", "offline_short": "Pareces estar desconectado.",
"post_request_tests": "Los scripts de prueba están escritos en JavaScript y se ejecutan después de recibir la respuesta.", "post_request_tests": "Los scripts de prueba están escritos en JavaScript y se ejecutan después de recibir la respuesta.",
"pre_request_script": "Los scripts previos a la solicitud están escritos en JavaScript y se ejecutan antes de que se envíe la solicitud.", "pre_request_script": "Los scripts previos a la petición están escritos en JavaScript y se ejecutan antes de que se envíe la petición.",
"script_fail": "Parece que hay un problema técnico en el script de solicitud previa. Comprueba el error a continuación y corrige el script en consecuencia.", "script_fail": "Parece que hay un problema técnico en el script de solicitud previa. Comprueba el error a continuación y corrige el script en consecuencia.",
"test_script_fail": "Parece que hay un error con el script de prueba. Por favor, corrige los errores y ejecute las pruebas de nuevo", "test_script_fail": "Parece que hay un error con el script de prueba. Por favor, corrige los errores y ejecute las pruebas de nuevo",
"tests": "Escribir un script de prueba para automatizar la depuración." "tests": "Escribir un script de prueba para automatizar la depuración."
}, },
"hide": { "hide": {
"collection": "Colapsar el panel de colecciones", "collection": "Collapse Collection Panel",
"more": "Ocultar más", "more": "Ocultar más",
"preview": "Ocultar vista previa", "preview": "Ocultar vista previa",
"sidebar": "Ocultar barra lateral" "sidebar": "Ocultar barra lateral"
@@ -338,64 +307,41 @@
"json_description": "Importar colecciones desde un archivo JSON de colecciones de Hoppscotch", "json_description": "Importar colecciones desde un archivo JSON de colecciones de Hoppscotch",
"title": "Importar" "title": "Importar"
}, },
"inspections": {
"description": "Inspect possible errors",
"environment": {
"add_environment": "Add to Environment",
"not_found": "Environment variable “{environment}” not found."
},
"header": {
"cookie": "The browser doesn't allow Hoppscotch to set the Cookie Header. While we're working on the Hoppscotch Desktop App (coming soon), please use the Authorization Header instead."
},
"response": {
"401_error": "Please check your authentication credentials.",
"404_error": "Please check your request URL and method type.",
"cors_error": "Please check your Cross-Origin Resource Sharing configuration.",
"default_error": "Please check your request.",
"network_error": "Please check your network connection."
},
"title": "Inspector",
"url": {
"extension_not_installed": "Extension not installed.",
"extension_unknown_origin": "Make sure you've added the API endpoint's origin to the Hoppscotch Browser Extension list.",
"extention_enable_action": "Enable Browser Extension",
"extention_not_enabled": "Extension not enabled."
}
},
"layout": { "layout": {
"collapse_collection": "Contraer o expandir colecciones", "collapse_collection": "Collapse or Expand Collections",
"collapse_sidebar": "Contraer o expandir la barra lateral", "collapse_sidebar": "Collapse or Expand the sidebar",
"column": "Disposición vertical", "column": "Disposición vertical",
"name": "Diseño", "name": "Layout",
"row": "Disposición horizontal" "row": "Disposición horizontal",
"zen_mode": "Modo zen"
}, },
"modal": { "modal": {
"close_unsaved_tab": "Tienes cambios sin guardar", "close_unsaved_tab": "You have unsaved changes",
"collections": "Colecciones", "collections": "Colecciones",
"confirm": "Confirmar", "confirm": "Confirmar",
"edit_request": "Editar solicitud", "edit_request": "Editar petición",
"import_export": "Importación y exportación" "import_export": "Importación y exportación"
}, },
"mqtt": { "mqtt": {
"already_subscribed": "Ya estás suscrito a este tema.", "already_subscribed": "You are already subscribed to this topic.",
"clean_session": "Borrar sesión", "clean_session": "Clean Session",
"clear_input": "Borrar entrada", "clear_input": "Clear input",
"clear_input_on_send": "Borrar entrada al enviar", "clear_input_on_send": "Clear input on send",
"client_id": "Identificación del cliente", "client_id": "Client ID",
"color": "Elige un color", "color": "Pick a color",
"communication": "Comunicación", "communication": "Comunicación",
"connection_config": "Configuración de conexión", "connection_config": "Connection Config",
"connection_not_authorized": "Esta conexión MQTT no utiliza ninguna autenticación.", "connection_not_authorized": "This MQTT connection does not use any authentication.",
"invalid_topic": "Indica un tema para la suscripción", "invalid_topic": "Please provide a topic for the subscription",
"keep_alive": "Mantenerse vivo", "keep_alive": "Keep Alive",
"log": "Registro", "log": "Registro",
"lw_message": "Mensaje de última voluntad", "lw_message": "Last-Will Message",
"lw_qos": "QoS de última voluntad", "lw_qos": "Last-Will QoS",
"lw_retain": "Última voluntad", "lw_retain": "Last-Will Retain",
"lw_topic": "Tema de última voluntad", "lw_topic": "Last-Will Topic",
"message": "Mensaje", "message": "Mensaje",
"new": "Nueva suscripción", "new": "New Subscription",
"not_connected": "Por favor, inicia primero una conexión MQTT.", "not_connected": "Please start a MQTT connection first.",
"publish": "Publicar", "publish": "Publicar",
"qos": "QoS", "qos": "QoS",
"ssl": "SSL", "ssl": "SSL",
@@ -407,7 +353,7 @@
"url": "URL" "url": "URL"
}, },
"navigation": { "navigation": {
"doc": "Documentación", "doc": "Docs",
"graphql": "GraphQL", "graphql": "GraphQL",
"profile": "Perfil", "profile": "Perfil",
"realtime": "Tiempo real", "realtime": "Tiempo real",
@@ -417,7 +363,7 @@
"preRequest": { "preRequest": {
"javascript_code": "Código JavaScript", "javascript_code": "Código JavaScript",
"learn": "Leer documentación", "learn": "Leer documentación",
"script": "Script previo a la solicitud", "script": "Script previo a la petición",
"snippets": "Fragmentos" "snippets": "Fragmentos"
}, },
"profile": { "profile": {
@@ -439,58 +385,55 @@
"star": "Eliminar estrella" "star": "Eliminar estrella"
}, },
"request": { "request": {
"added": "Solicitud agregada", "added": "Petición agregada",
"authorization": "Autorización", "authorization": "Autorización",
"body": "Cuerpo de la solicitud", "body": "Cuerpo de la petición",
"choose_language": "Seleccionar lenguaje", "choose_language": "Seleccionar lenguaje",
"content_type": "Tipo de contenido", "content_type": "Tipo de contenido",
"content_type_titles": { "content_type_titles": {
"others": "Otros", "others": "Others",
"structured": "Estructurado", "structured": "Structured",
"text": "Texto" "text": "Text"
}, },
"copy_link": "Copiar enlace", "copy_link": "Copiar enlace",
"different_collection": "No se pueden reordenar solicitudes de diferentes colecciones", "different_collection": "Cannot reorder requests from different collections",
"duplicated": "Solicitud duplicada", "duplicated": "Request duplicated",
"duration": "Duración", "duration": "Duración",
"enter_curl": "Ingrese cURL", "enter_curl": "Ingrese cURL",
"generate_code": "Generar código", "generate_code": "Generar código",
"generated_code": "Código generado", "generated_code": "Código generado",
"header_list": "Lista de encabezados", "header_list": "Lista de encabezados",
"invalid_name": "Proporciona un nombre para la solicitud.", "invalid_name": "Proporciona un nombre para la petición.",
"method": "Método", "method": "Método",
"moved": "Request moved", "moved": "Request moved",
"name": "Nombre de solicitud", "name": "Nombre de petición",
"new": "Nueva solicitud", "new": "New Request",
"order_changed": "Orden de solicitudes actualizadas", "order_changed": "Request Order Updated",
"override": "Anular", "override": "Override",
"override_help": "Establecer <kbd>Content-Type</kbd> en las cabeceras", "override_help": "Set <kbd>Content-Type</kbd> in Headers",
"overriden": "Anulado", "overriden": "Overridden",
"parameter_list": "Parámetros de consulta", "parameter_list": "Parámetros de consulta",
"parameters": "Parámetros", "parameters": "Parámetros",
"path": "Ruta", "path": "Ruta",
"payload": "Carga útil", "payload": "Carga útil",
"query": "Consulta", "query": "Consulta",
"raw_body": "Cuerpo de solicitud sin procesar", "raw_body": "Cuerpo de petición sin procesar",
"rename": "Rename Request", "renamed": "Petición renombrada",
"renamed": "Solicitud renombrada",
"run": "Ejecutar", "run": "Ejecutar",
"save": "Guardar", "save": "Guardar",
"save_as": "Guardar como", "save_as": "Guardar como",
"saved": "Solicitud guardada", "saved": "Petición guardada",
"share": "Compartir", "share": "Compartir",
"share_description": "Comparte Hoppscotch con tus amigos", "share_description": "Share Hoppscotch with your friends",
"stop": "Stop", "title": "Petición",
"title": "Solicitud", "type": "Tipo de petición",
"type": "Tipo de solicitud",
"url": "URL", "url": "URL",
"variables": "Variables", "variables": "Variables",
"view_my_links": "Ver mis enlaces" "view_my_links": "Ver mis enlaces"
}, },
"response": { "response": {
"audio": "Audio",
"body": "Cuerpo de respuesta", "body": "Cuerpo de respuesta",
"filter_response_body": "Filtrar el cuerpo de la respuesta JSON (utiliza la sintaxis JSONPath)", "filter_response_body": "Filter JSON response body (uses JSONPath syntax)",
"headers": "Encabezados", "headers": "Encabezados",
"html": "HTML", "html": "HTML",
"image": "Imagen", "image": "Imagen",
@@ -502,23 +445,23 @@
"status": "Estado", "status": "Estado",
"time": "Tiempo", "time": "Tiempo",
"title": "Respuesta", "title": "Respuesta",
"video": "Video",
"waiting_for_connection": "esperando la conexión", "waiting_for_connection": "esperando la conexión",
"xml": "XML" "xml": "XML"
}, },
"settings": { "settings": {
"accent_color": "Color de acentuación", "accent_color": "Color de acentuación",
"account": "Cuenta", "account": "Cuenta",
"account_deleted": "Tu cuenta ha sido eliminada", "account_deleted": "Your account has been deleted",
"account_description": "Personaliza la configuración de tu cuenta.", "account_description": "Personaliza la configuración de tu cuenta.",
"account_email_description": "Tu dirección de correo electrónico principal.", "account_email_description": "Tu dirección de correo electrónico principal.",
"account_name_description": "Este es tu nombre para mostrar.", "account_name_description": "Este es tu nombre para mostrar.",
"background": "Fondo", "background": "Fondo",
"black_mode": "Negro", "black_mode": "Negro",
"change_font_size": "Cambiar tamaño de fuente",
"choose_language": "Elegir idioma", "choose_language": "Elegir idioma",
"dark_mode": "Oscuro", "dark_mode": "Oscuro",
"delete_account": "Eliminar cuenta", "delete_account": "Delete account",
"delete_account_description": "Una vez que elimines tu cuenta, todos tus datos se borrarán permanentemente. Esta acción no se puede deshacer.", "delete_account_description": "Once you delete your account, all your data will be permanently deleted. This action cannot be undone.",
"expand_navigation": "Expandir la navegación", "expand_navigation": "Expandir la navegación",
"experiments": "Experimentos", "experiments": "Experimentos",
"experiments_notice": "Esta es una colección de experimentos en los que estamos trabajando que podrían resultar útiles, divertidos, ambos o ninguno. No son definitivos y es posible que no sean estables, por lo que si sucede algo demasiado extraño, no se asuste. Solo apaga la maldita cosa. Fuera de bromas,", "experiments_notice": "Esta es una colección de experimentos en los que estamos trabajando que podrían resultar útiles, divertidos, ambos o ninguno. No son definitivos y es posible que no sean estables, por lo que si sucede algo demasiado extraño, no se asuste. Solo apaga la maldita cosa. Fuera de bromas,",
@@ -527,13 +470,17 @@
"extensions": "Extensiones", "extensions": "Extensiones",
"extensions_use_toggle": "Utilizar la extensión del navegador para enviar peticiones (si está presente)", "extensions_use_toggle": "Utilizar la extensión del navegador para enviar peticiones (si está presente)",
"follow": "Síguenos", "follow": "Síguenos",
"font_size": "Tamaño de fuente",
"font_size_large": "Grande",
"font_size_medium": "Mediano",
"font_size_small": "Pequeño",
"interceptor": "Interceptador", "interceptor": "Interceptador",
"interceptor_description": "Middleware entre la aplicación y las APIs.", "interceptor_description": "Middleware entre la aplicación y las APIs.",
"language": "Idioma", "language": "Idioma",
"light_mode": "Luz", "light_mode": "Luz",
"official_proxy_hosting": "El proxy oficial está alojado en Hoppscotch.", "official_proxy_hosting": "El proxy oficial está alojado en Hoppscotch.",
"profile": "Perfil", "profile": "Perfil",
"profile_description": "Actualiza los datos de tu perfil", "profile_description": "Update your profile details",
"profile_email": "Correo electrónico", "profile_email": "Correo electrónico",
"profile_name": "Nombre de perfil", "profile_name": "Nombre de perfil",
"proxy": "Proxy", "proxy": "Proxy",
@@ -541,8 +488,8 @@
"proxy_use_toggle": "Utilizar el middleware de proxy para enviar peticiones", "proxy_use_toggle": "Utilizar el middleware de proxy para enviar peticiones",
"read_the": "Leer el", "read_the": "Leer el",
"reset_default": "Restablecer a los predeterminados", "reset_default": "Restablecer a los predeterminados",
"short_codes": "Shortcodes", "short_codes": "Short codes",
"short_codes_description": "Shortcodes creados por ti.", "short_codes_description": "Short codes which were created by you.",
"sidebar_on_left": "Barra lateral a la izquierda", "sidebar_on_left": "Barra lateral a la izquierda",
"sync": "Sincronizar", "sync": "Sincronizar",
"sync_collections": "Colecciones", "sync_collections": "Colecciones",
@@ -556,15 +503,15 @@
"theme_description": "Personaliza el tema de tu aplicación.", "theme_description": "Personaliza el tema de tu aplicación.",
"use_experimental_url_bar": "Utilizar la barra de URL experimental con resaltado de entorno", "use_experimental_url_bar": "Utilizar la barra de URL experimental con resaltado de entorno",
"user": "Usuario", "user": "Usuario",
"verified_email": "Correo electrónico verificado", "verified_email": "Verified email",
"verify_email": "Verificar correo electrónico" "verify_email": "Verificar correo electrónico"
}, },
"shortcodes": { "shortcodes": {
"actions": "Acciones", "actions": "Actions",
"created_on": "Creado el", "created_on": "Created on",
"deleted": "Código corto eliminado", "deleted": "Shortcode deleted",
"method": "Método", "method": "Method",
"not_found": "Shortcode no encontrado", "not_found": "Shortcode not found",
"short_code": "Short code", "short_code": "Short code",
"url": "URL" "url": "URL"
}, },
@@ -591,28 +538,20 @@
"settings": "Ir a la página de configuración", "settings": "Ir a la página de configuración",
"title": "Navegación" "title": "Navegación"
}, },
"others": {
"prettify": "Prettify Editor's Content",
"title": "Others"
},
"request": { "request": {
"copy_request_link": "Copiar enlace de solicitud", "copy_request_link": "Copiar enlace de petición",
"delete_method": "Seleccionar método DELETE", "delete_method": "Seleccionar método DELETE",
"get_method": "Seleccionar método GET", "get_method": "Seleccionar método GET",
"head_method": "Seleccionar método HEAD", "head_method": "Seleccionar método HEAD",
"import_curl": "Import cURL",
"method": "Método", "method": "Método",
"next_method": "Seleccionar método siguiente", "next_method": "Seleccionar método siguiente",
"post_method": "Seleccionar método POST", "post_method": "Seleccionar método POST",
"previous_method": "Seleccionar método anterior", "previous_method": "Seleccionar método anterior",
"put_method": "Seleccionar método PUT", "put_method": "Seleccionar método PUT",
"rename": "Rename Request", "reset_request": "Petición de reinicio",
"reset_request": "Solicitud de reinicio",
"save_request": "Save Request",
"save_to_collections": "Guardar en colecciones", "save_to_collections": "Guardar en colecciones",
"send_request": "Enviar solicitud", "send_request": "Enviar petición",
"show_code": "Generate code snippet", "title": "Petición"
"title": "Solicitud"
}, },
"response": { "response": {
"copy": "Copiar la respuesta al portapapeles", "copy": "Copiar la respuesta al portapapeles",
@@ -641,85 +580,6 @@
"log": "Registro", "log": "Registro",
"url": "URL" "url": "URL"
}, },
"spotlight": {
"change_language": "Change Language",
"environments": {
"delete": "Delete current environment",
"duplicate": "Duplicate current environment",
"duplicate_global": "Duplicate global environment",
"edit": "Edit current environment",
"edit_global": "Edit global environment",
"new": "Create new environment",
"new_variable": "Create a new environment variable",
"title": "Environments"
},
"general": {
"chat": "Chat with support",
"help_menu": "Help and support",
"open_docs": "Read Documentation",
"open_github": "Open GitHub repository",
"open_keybindings": "Keyboard shortcuts",
"social": "Social",
"title": "General"
},
"graphql": {
"connect": "Connect to server",
"disconnect": "Disconnect from server"
},
"miscellaneous": {
"invite": "Invite your friends to Hoppscotch",
"title": "Miscellaneous"
},
"request": {
"save_as_new": "Save as new request",
"select_method": "Select method",
"switch_to": "Switch to",
"tab_authorization": "Authorization tab",
"tab_body": "Body tab",
"tab_headers": "Headers tab",
"tab_parameters": "Parameters tab",
"tab_pre_request_script": "Pre-request script tab",
"tab_query": "Query tab",
"tab_tests": "Tests tab",
"tab_variables": "Variables tab"
},
"response": {
"copy": "Copy response",
"download": "Download response as file",
"title": "Response"
},
"section": {
"interceptor": "Interceptor",
"interface": "Interface",
"theme": "Theme",
"user": "User"
},
"settings": {
"change_interceptor": "Change Interceptor",
"change_language": "Change Language",
"theme": {
"black": "Black",
"dark": "Dark",
"light": "Light",
"system": "System preference"
}
},
"tab": {
"close_current": "Close current tab",
"close_others": "Close all other tabs",
"duplicate": "Duplicate current tab",
"new_tab": "Open a new tab",
"title": "Tabs"
},
"workspace": {
"delete": "Delete current team",
"edit": "Edit current team",
"invite": "Invite people to team",
"new": "Create new team",
"switch_to_personal": "Switch to your personal workspace",
"title": "Teams"
}
},
"sse": { "sse": {
"event_type": "Tipo de evento", "event_type": "Tipo de evento",
"log": "Registro", "log": "Registro",
@@ -733,8 +593,8 @@
"connected_to": "Conectado a {name}", "connected_to": "Conectado a {name}",
"connecting_to": "Conectando con {name}...", "connecting_to": "Conectando con {name}...",
"connection_error": "Failed to connect", "connection_error": "Failed to connect",
"connection_failed": "Error de conexión", "connection_failed": "Connection failed",
"connection_lost": "Conexión perdida", "connection_lost": "Connection lost",
"copied_to_clipboard": "Copiado al portapapeles", "copied_to_clipboard": "Copiado al portapapeles",
"deleted": "Eliminado", "deleted": "Eliminado",
"deprecated": "OBSOLETO", "deprecated": "OBSOLETO",
@@ -749,18 +609,18 @@
"history_deleted": "Historial eliminado", "history_deleted": "Historial eliminado",
"linewrap": "Envolver líneas", "linewrap": "Envolver líneas",
"loading": "Cargando...", "loading": "Cargando...",
"message_received": "Mensaje: {mensaje} llegó sobre el tema: {topic}", "message_received": "Message: {message} arrived on topic: {topic}",
"mqtt_subscription_failed": "Algo ha ido mal al suscribirse al tema: {topic}", "mqtt_subscription_failed": "Something went wrong while subscribing to topic: {topic}",
"none": "Ninguno", "none": "Ninguno",
"nothing_found": "Nada encontrado para", "nothing_found": "Nada encontrado para",
"published_error": "Algo ha ido mal al publicar el mensaje: {topic} al tema: {message}", "published_error": "Something went wrong while publishing msg: {topic} to topic: {message}",
"published_message": "Mensaje publicado: {mensaje} al tema: {topic}", "published_message": "Published message: {message} to topic: {topic}",
"reconnection_error": "Fallo en la reconexión", "reconnection_error": "Failed to reconnect",
"subscribed_failed": "Error al suscribirse al tema: {topic}", "subscribed_failed": "Failed to subscribe to topic: {topic}",
"subscribed_success": "Suscrito con éxito al tema: {topic}", "subscribed_success": "Successfully subscribed to topic: {topic}",
"unsubscribed_failed": "Error al darse de baja del tema: {topic}", "unsubscribed_failed": "Failed to unsubscribe from topic: {topic}",
"unsubscribed_success": "Se ha cancelado la suscripción al tema: {topic}", "unsubscribed_success": "Successfully unsubscribed from topic: {topic}",
"waiting_send_request": "Esperando para enviar solicitud" "waiting_send_request": "Esperando para enviar petición"
}, },
"support": { "support": {
"changelog": "Leer más sobre los últimos lanzamientos", "changelog": "Leer más sobre los últimos lanzamientos",
@@ -777,17 +637,14 @@
"tab": { "tab": {
"authorization": "Autorización", "authorization": "Autorización",
"body": "Cuerpo", "body": "Cuerpo",
"close": "Close Tab",
"close_others": "Close other Tabs",
"collections": "Colecciones", "collections": "Colecciones",
"documentation": "Documentación", "documentation": "Documentación",
"duplicate": "Duplicate Tab",
"environments": "Environments", "environments": "Environments",
"headers": "Encabezados", "headers": "Encabezados",
"history": "Historial", "history": "Historial",
"mqtt": "MQTT", "mqtt": "MQTT",
"parameters": "Parámetros", "parameters": "Parámetros",
"pre_request_script": "Script previo a la solicitud", "pre_request_script": "Script previo a la petición",
"queries": "Consultas", "queries": "Consultas",
"query": "Consulta", "query": "Consulta",
"schema": "Esquema", "schema": "Esquema",
@@ -807,9 +664,9 @@
"email_do_not_match": "El correo electrónico no coincide con los datos de tu cuenta. Ponte en contacto con el propietario de tu equipo.", "email_do_not_match": "El correo electrónico no coincide con los datos de tu cuenta. Ponte en contacto con el propietario de tu equipo.",
"exit": "Salir del equipo", "exit": "Salir del equipo",
"exit_disabled": "Solo el propietario puede salir del equipo", "exit_disabled": "Solo el propietario puede salir del equipo",
"invalid_coll_id": "Identificador de colección no válido", "invalid_coll_id": "Invalid collection ID",
"invalid_email_format": "El formato de correo electrónico no es válido", "invalid_email_format": "El formato de correo electrónico no es válido",
"invalid_id": "Identificador de equipo inválido. Ponte en contacto con el propietario de tu equipo.", "invalid_id": "ID de equipo inválido. Ponte en contacto con el propietario de tu equipo.",
"invalid_invite_link": "Enlace de invitación inválido", "invalid_invite_link": "Enlace de invitación inválido",
"invalid_invite_link_description": "El enlace que has seguido no es válido. Ponte en contacto con el propietario de tu equipo.", "invalid_invite_link_description": "El enlace que has seguido no es válido. Ponte en contacto con el propietario de tu equipo.",
"invalid_member_permission": "Proporcionar un permiso válido al miembro del equipo", "invalid_member_permission": "Proporcionar un permiso válido al miembro del equipo",
@@ -826,7 +683,7 @@
"login_to_continue": "Iniciar sesión para continuar", "login_to_continue": "Iniciar sesión para continuar",
"login_to_continue_description": "Tienes que estar conectado para unirte a un equipo.", "login_to_continue_description": "Tienes que estar conectado para unirte a un equipo.",
"logout_and_try_again": "Cerrar la sesión e iniciar sesión con otra cuenta", "logout_and_try_again": "Cerrar la sesión e iniciar sesión con otra cuenta",
"member_has_invite": "Este Identificador de correo electrónico ya tiene una invitación. Ponte en contacto con el propietario de tu equipo.", "member_has_invite": "Este ID de correo electrónico ya tiene una invitación. Ponte en contacto con el propietario de tu equipo.",
"member_not_found": "Miembro no encontrado. Ponte en contacto con el propietario de tu equipo.", "member_not_found": "Miembro no encontrado. Ponte en contacto con el propietario de tu equipo.",
"member_removed": "Usuario eliminado", "member_removed": "Usuario eliminado",
"member_role_updated": "Funciones de usuario actualizadas", "member_role_updated": "Funciones de usuario actualizadas",
@@ -839,10 +696,10 @@
"new_name": "Mi nuevo equipo", "new_name": "Mi nuevo equipo",
"no_access": "No tienes acceso de edición a estas colecciones.", "no_access": "No tienes acceso de edición a estas colecciones.",
"no_invite_found": "No se ha encontrado la invitación. Ponte en contacto con el propietario de tu equipo.", "no_invite_found": "No se ha encontrado la invitación. Ponte en contacto con el propietario de tu equipo.",
"no_request_found": "Solicitud no encontrada.", "no_request_found": "Request not found.",
"not_found": "Equipo no encontrado. Ponte en contacto con el propietario de tu equipo.", "not_found": "Equipo no encontrado. Ponte en contacto con el propietario de tu equipo.",
"not_valid_viewer": "No eres un espectador válido. Ponte en contacto con el propietario de tu equipo.", "not_valid_viewer": "No eres un espectador válido. Ponte en contacto con el propietario de tu equipo.",
"parent_coll_move": "No se puede mover la colección a una colección hija", "parent_coll_move": "Cannot move collection to a child collection",
"pending_invites": "Invitaciones pendientes", "pending_invites": "Invitaciones pendientes",
"permissions": "Permisos", "permissions": "Permisos",
"same_target_destination": "Same target and destination", "same_target_destination": "Same target and destination",
@@ -850,12 +707,12 @@
"select_a_team": "Seleccionar un equipo", "select_a_team": "Seleccionar un equipo",
"title": "Equipos", "title": "Equipos",
"we_sent_invite_link": "¡Hemos enviado un enlace de invitación a todos los invitados!", "we_sent_invite_link": "¡Hemos enviado un enlace de invitación a todos los invitados!",
"we_sent_invite_link_description": "Pide a todos los invitados que revisen tu bandeja de entrada. Haz clic en el enlace para unirse al equipo." "we_sent_invite_link_description": "Pide a todos los invitados que revisen su bandeja de entrada. Haz clic en el enlace para unirse al equipo."
}, },
"team_environment": { "team_environment": {
"deleted": "Entorno eliminado", "deleted": "Environment Deleted",
"duplicate": "Entorno duplicado", "duplicate": "Environment Duplicated",
"not_found": "Entorno no encontrado." "not_found": "Environment not found."
}, },
"test": { "test": {
"failed": "prueba fallida", "failed": "prueba fallida",
@@ -875,9 +732,9 @@
"url": "URL" "url": "URL"
}, },
"workspace": { "workspace": {
"change": "Cambiar el espacio de trabajo", "change": "Change workspace",
"personal": "Mi espacio de trabajo", "personal": "My Workspace",
"team": "Espacio de trabajo en equipo", "team": "Team Workspace",
"title": "Espacios de trabajo" "title": "Workspaces"
} }
} }

View File

@@ -5,7 +5,6 @@
"choose_file": "Valitse tiedosto", "choose_file": "Valitse tiedosto",
"clear": "Asia selvä", "clear": "Asia selvä",
"clear_all": "Tyhjennä", "clear_all": "Tyhjennä",
"clear_history": "Clear all History",
"close": "Close", "close": "Close",
"connect": "Kytkeä", "connect": "Kytkeä",
"connecting": "Connecting", "connecting": "Connecting",
@@ -32,7 +31,6 @@
"paste": "Paste", "paste": "Paste",
"prettify": "Koristella", "prettify": "Koristella",
"remove": "Poista", "remove": "Poista",
"rename": "Rename",
"restore": "Palauttaa", "restore": "Palauttaa",
"save": "Tallentaa", "save": "Tallentaa",
"scroll_to_bottom": "Scroll to bottom", "scroll_to_bottom": "Scroll to bottom",
@@ -79,8 +77,6 @@
"search": "Hae", "search": "Hae",
"share": "Jaa", "share": "Jaa",
"shortcuts": "Pikanäppäimet", "shortcuts": "Pikanäppäimet",
"social_description": "Follow us on social media to stay updated with the latest news, updates and releases.",
"social_links": "Social links",
"spotlight": "Valokeila", "spotlight": "Valokeila",
"status": "Tila", "status": "Tila",
"status_description": "Check the status of the website", "status_description": "Check the status of the website",
@@ -135,15 +131,12 @@
"renamed": "Kokoelma nimetty uudelleen", "renamed": "Kokoelma nimetty uudelleen",
"request_in_use": "Request in use", "request_in_use": "Request in use",
"save_as": "Tallenna nimellä", "save_as": "Tallenna nimellä",
"save_to_collection": "Save to Collection",
"select": "Valitse kokoelma", "select": "Valitse kokoelma",
"select_location": "Valitse sijainti", "select_location": "Valitse sijainti",
"select_team": "Valitse joukkue", "select_team": "Valitse joukkue",
"team_collections": "Joukkuekokoelmat" "team_collections": "Joukkuekokoelmat"
}, },
"confirm": { "confirm": {
"close_unsaved_tab": "Are you sure you want to close this tab?",
"close_unsaved_tabs": "Are you sure you want to close all tabs? {count} unsaved tabs will be lost.",
"exit_team": "Are you sure you want to leave this team?", "exit_team": "Are you sure you want to leave this team?",
"logout": "Haluatko varmasti kirjautua ulos?", "logout": "Haluatko varmasti kirjautua ulos?",
"remove_collection": "Haluatko varmasti poistaa tämän kokoelman pysyvästi?", "remove_collection": "Haluatko varmasti poistaa tämän kokoelman pysyvästi?",
@@ -157,11 +150,6 @@
"save_unsaved_tab": "Do you want to save changes made in this tab?", "save_unsaved_tab": "Do you want to save changes made in this tab?",
"sync": "Haluatko varmasti synkronoida tämän työtilan?" "sync": "Haluatko varmasti synkronoida tämän työtilan?"
}, },
"context_menu": {
"add_parameters": "Add to parameters",
"open_request_in_new_tab": "Open request in new tab",
"set_environment_variable": "Set as variable"
},
"count": { "count": {
"header": "Otsikko {count}", "header": "Otsikko {count}",
"message": "Viesti {count}", "message": "Viesti {count}",
@@ -204,31 +192,17 @@
"create_new": "Luo uusi ympäristö", "create_new": "Luo uusi ympäristö",
"created": "Environment created", "created": "Environment created",
"deleted": "Environment deletion", "deleted": "Environment deletion",
"duplicated": "Environment duplicated",
"edit": "Muokkaa ympäristöä", "edit": "Muokkaa ympäristöä",
"empty_variables": "No variables",
"global": "Global",
"global_variables": "Global variables",
"invalid_name": "Anna ympäristölle kelvollinen nimi", "invalid_name": "Anna ympäristölle kelvollinen nimi",
"list": "Environment variables",
"my_environments": "My Environments", "my_environments": "My Environments",
"name": "Name",
"nested_overflow": "nested environment variables are limited to 10 levels", "nested_overflow": "nested environment variables are limited to 10 levels",
"new": "Uusi ympäristö", "new": "Uusi ympäristö",
"no_active_environment": "No active environment",
"no_environment": "Ei ympäristöä", "no_environment": "Ei ympäristöä",
"no_environment_description": "No environments were selected. Choose what to do with the following variables.", "no_environment_description": "No environments were selected. Choose what to do with the following variables.",
"quick_peek": "Environment Quick Peek",
"replace_with_variable": "Replace with variable",
"scope": "Scope",
"select": "Valitse ympäristö", "select": "Valitse ympäristö",
"set": "Set environment",
"set_as_environment": "Set as environment",
"team_environments": "Team Environments", "team_environments": "Team Environments",
"title": "Ympäristöt", "title": "Ympäristöt",
"updated": "Environment updation", "updated": "Environment updation",
"value": "Value",
"variable": "Variable",
"variable_list": "Muuttujien luettelo" "variable_list": "Muuttujien luettelo"
}, },
"error": { "error": {
@@ -252,7 +226,6 @@
"no_duration": "Ei kestoa", "no_duration": "Ei kestoa",
"no_results_found": "No matches found", "no_results_found": "No matches found",
"page_not_found": "This page could not be found", "page_not_found": "This page could not be found",
"proxy_error": "Proxy error",
"script_fail": "Ennakkopyyntöskriptiä ei voitu suorittaa", "script_fail": "Ennakkopyyntöskriptiä ei voitu suorittaa",
"something_went_wrong": "Jotain meni pieleen", "something_went_wrong": "Jotain meni pieleen",
"test_script_fail": "Could not execute post-request script" "test_script_fail": "Could not execute post-request script"
@@ -278,13 +251,9 @@
"renamed": "Kansio nimettiin uudelleen" "renamed": "Kansio nimettiin uudelleen"
}, },
"graphql": { "graphql": {
"connection_switch_confirm": "Do you want to connect with the latest GraphQL endpoint?",
"connection_switch_new_url": "Switching to a tab will disconnected you from the active GraphQL connection. New connection URL is",
"connection_switch_url": "You're connected to a GraphQL endpoint the connection URL is",
"mutations": "Mutaatiot", "mutations": "Mutaatiot",
"schema": "Kaavio", "schema": "Kaavio",
"subscriptions": "Tilaukset", "subscriptions": "Tilaukset"
"switch_connection": "Switch connection"
}, },
"group": { "group": {
"time": "Time", "time": "Time",
@@ -338,36 +307,13 @@
"json_description": "Import collections from a Hoppscotch Collections JSON file", "json_description": "Import collections from a Hoppscotch Collections JSON file",
"title": "Tuonti" "title": "Tuonti"
}, },
"inspections": {
"description": "Inspect possible errors",
"environment": {
"add_environment": "Add to Environment",
"not_found": "Environment variable “{environment}” not found."
},
"header": {
"cookie": "The browser doesn't allow Hoppscotch to set the Cookie Header. While we're working on the Hoppscotch Desktop App (coming soon), please use the Authorization Header instead."
},
"response": {
"401_error": "Please check your authentication credentials.",
"404_error": "Please check your request URL and method type.",
"cors_error": "Please check your Cross-Origin Resource Sharing configuration.",
"default_error": "Please check your request.",
"network_error": "Please check your network connection."
},
"title": "Inspector",
"url": {
"extension_not_installed": "Extension not installed.",
"extension_unknown_origin": "Make sure you've added the API endpoint's origin to the Hoppscotch Browser Extension list.",
"extention_enable_action": "Enable Browser Extension",
"extention_not_enabled": "Extension not enabled."
}
},
"layout": { "layout": {
"collapse_collection": "Collapse or Expand Collections", "collapse_collection": "Collapse or Expand Collections",
"collapse_sidebar": "Collapse or Expand the sidebar", "collapse_sidebar": "Collapse or Expand the sidebar",
"column": "Vertical layout", "column": "Vertical layout",
"name": "Layout", "name": "Layout",
"row": "Horizontal layout" "row": "Horizontal layout",
"zen_mode": "Zen -tila"
}, },
"modal": { "modal": {
"close_unsaved_tab": "You have unsaved changes", "close_unsaved_tab": "You have unsaved changes",
@@ -472,7 +418,6 @@
"payload": "Hyötykuorma", "payload": "Hyötykuorma",
"query": "Kysely", "query": "Kysely",
"raw_body": "Raaka pyynnön runko", "raw_body": "Raaka pyynnön runko",
"rename": "Rename Request",
"renamed": "Pyyntö nimettiin uudelleen", "renamed": "Pyyntö nimettiin uudelleen",
"run": "Juosta", "run": "Juosta",
"save": "Tallentaa", "save": "Tallentaa",
@@ -480,7 +425,6 @@
"saved": "Pyyntö tallennettu", "saved": "Pyyntö tallennettu",
"share": "Jaa", "share": "Jaa",
"share_description": "Share Hoppscotch with your friends", "share_description": "Share Hoppscotch with your friends",
"stop": "Stop",
"title": "Pyyntö", "title": "Pyyntö",
"type": "Pyynnön tyyppi", "type": "Pyynnön tyyppi",
"url": "URL -osoite", "url": "URL -osoite",
@@ -488,7 +432,6 @@
"view_my_links": "View my links" "view_my_links": "View my links"
}, },
"response": { "response": {
"audio": "Audio",
"body": "Vastauselin", "body": "Vastauselin",
"filter_response_body": "Filter JSON response body (uses JSONPath syntax)", "filter_response_body": "Filter JSON response body (uses JSONPath syntax)",
"headers": "Otsikot", "headers": "Otsikot",
@@ -502,7 +445,6 @@
"status": "Tila", "status": "Tila",
"time": "Aika", "time": "Aika",
"title": "Vastaus", "title": "Vastaus",
"video": "Video",
"waiting_for_connection": "yhteyttä odotellessa", "waiting_for_connection": "yhteyttä odotellessa",
"xml": "XML" "xml": "XML"
}, },
@@ -515,6 +457,7 @@
"account_name_description": "Tämä on näyttönimesi.", "account_name_description": "Tämä on näyttönimesi.",
"background": "Tausta", "background": "Tausta",
"black_mode": "Musta", "black_mode": "Musta",
"change_font_size": "Vaihda fontin kokoa",
"choose_language": "Valitse kieli", "choose_language": "Valitse kieli",
"dark_mode": "Tumma", "dark_mode": "Tumma",
"delete_account": "Delete account", "delete_account": "Delete account",
@@ -527,6 +470,10 @@
"extensions": "Laajennukset", "extensions": "Laajennukset",
"extensions_use_toggle": "Käytä pyyntöjen lähettämiseen selainlaajennusta (jos sellainen on)", "extensions_use_toggle": "Käytä pyyntöjen lähettämiseen selainlaajennusta (jos sellainen on)",
"follow": "Follow Us", "follow": "Follow Us",
"font_size": "Fonttikoko",
"font_size_large": "Suuri",
"font_size_medium": "Keskikokoinen",
"font_size_small": "Pieni",
"interceptor": "Torjuntahävittäjä", "interceptor": "Torjuntahävittäjä",
"interceptor_description": "Sovellusliittymien ja sovellusliittymien välinen väliohjelmisto.", "interceptor_description": "Sovellusliittymien ja sovellusliittymien välinen väliohjelmisto.",
"language": "Kieli", "language": "Kieli",
@@ -591,27 +538,19 @@
"settings": "Siirry Asetukset -sivulle", "settings": "Siirry Asetukset -sivulle",
"title": "Navigointi" "title": "Navigointi"
}, },
"others": {
"prettify": "Prettify Editor's Content",
"title": "Others"
},
"request": { "request": {
"copy_request_link": "Kopioi pyyntölinkki", "copy_request_link": "Kopioi pyyntölinkki",
"delete_method": "Valitse POISTA menetelmä", "delete_method": "Valitse POISTA menetelmä",
"get_method": "Valitse GET -menetelmä", "get_method": "Valitse GET -menetelmä",
"head_method": "Valitse HEAD -menetelmä", "head_method": "Valitse HEAD -menetelmä",
"import_curl": "Import cURL",
"method": "Menetelmä", "method": "Menetelmä",
"next_method": "Valitse Seuraava menetelmä", "next_method": "Valitse Seuraava menetelmä",
"post_method": "Valitse POST -menetelmä", "post_method": "Valitse POST -menetelmä",
"previous_method": "Valitse Edellinen menetelmä", "previous_method": "Valitse Edellinen menetelmä",
"put_method": "Valitse PUT -menetelmä", "put_method": "Valitse PUT -menetelmä",
"rename": "Rename Request",
"reset_request": "Nollaa pyyntö", "reset_request": "Nollaa pyyntö",
"save_request": "Save Request",
"save_to_collections": "Tallenna kokoelmiin", "save_to_collections": "Tallenna kokoelmiin",
"send_request": "Lähetä pyyntö", "send_request": "Lähetä pyyntö",
"show_code": "Generate code snippet",
"title": "Pyyntö" "title": "Pyyntö"
}, },
"response": { "response": {
@@ -641,85 +580,6 @@
"log": "Hirsi", "log": "Hirsi",
"url": "URL -osoite" "url": "URL -osoite"
}, },
"spotlight": {
"change_language": "Change Language",
"environments": {
"delete": "Delete current environment",
"duplicate": "Duplicate current environment",
"duplicate_global": "Duplicate global environment",
"edit": "Edit current environment",
"edit_global": "Edit global environment",
"new": "Create new environment",
"new_variable": "Create a new environment variable",
"title": "Environments"
},
"general": {
"chat": "Chat with support",
"help_menu": "Help and support",
"open_docs": "Read Documentation",
"open_github": "Open GitHub repository",
"open_keybindings": "Keyboard shortcuts",
"social": "Social",
"title": "General"
},
"graphql": {
"connect": "Connect to server",
"disconnect": "Disconnect from server"
},
"miscellaneous": {
"invite": "Invite your friends to Hoppscotch",
"title": "Miscellaneous"
},
"request": {
"save_as_new": "Save as new request",
"select_method": "Select method",
"switch_to": "Switch to",
"tab_authorization": "Authorization tab",
"tab_body": "Body tab",
"tab_headers": "Headers tab",
"tab_parameters": "Parameters tab",
"tab_pre_request_script": "Pre-request script tab",
"tab_query": "Query tab",
"tab_tests": "Tests tab",
"tab_variables": "Variables tab"
},
"response": {
"copy": "Copy response",
"download": "Download response as file",
"title": "Response"
},
"section": {
"interceptor": "Interceptor",
"interface": "Interface",
"theme": "Theme",
"user": "User"
},
"settings": {
"change_interceptor": "Change Interceptor",
"change_language": "Change Language",
"theme": {
"black": "Black",
"dark": "Dark",
"light": "Light",
"system": "System preference"
}
},
"tab": {
"close_current": "Close current tab",
"close_others": "Close all other tabs",
"duplicate": "Duplicate current tab",
"new_tab": "Open a new tab",
"title": "Tabs"
},
"workspace": {
"delete": "Delete current team",
"edit": "Edit current team",
"invite": "Invite people to team",
"new": "Create new team",
"switch_to_personal": "Switch to your personal workspace",
"title": "Teams"
}
},
"sse": { "sse": {
"event_type": "Tapahtumatyyppi", "event_type": "Tapahtumatyyppi",
"log": "Hirsi", "log": "Hirsi",
@@ -777,11 +637,8 @@
"tab": { "tab": {
"authorization": "Valtuutus", "authorization": "Valtuutus",
"body": "Runko", "body": "Runko",
"close": "Close Tab",
"close_others": "Close other Tabs",
"collections": "Kokoelmat", "collections": "Kokoelmat",
"documentation": "Dokumentointi", "documentation": "Dokumentointi",
"duplicate": "Duplicate Tab",
"environments": "Environments", "environments": "Environments",
"headers": "Otsikot", "headers": "Otsikot",
"history": "Historia", "history": "Historia",

View File

@@ -5,7 +5,6 @@
"choose_file": "Choisir un fichier", "choose_file": "Choisir un fichier",
"clear": "Effacer", "clear": "Effacer",
"clear_all": "Tout effacer", "clear_all": "Tout effacer",
"clear_history": "Clear all History",
"close": "Close", "close": "Close",
"connect": "Connecter", "connect": "Connecter",
"connecting": "Connecting", "connecting": "Connecting",
@@ -32,7 +31,6 @@
"paste": "Coller", "paste": "Coller",
"prettify": "Formater", "prettify": "Formater",
"remove": "Supprimer", "remove": "Supprimer",
"rename": "Rename",
"restore": "Restaurer", "restore": "Restaurer",
"save": "Sauvegarder", "save": "Sauvegarder",
"scroll_to_bottom": "Scroll to bottom", "scroll_to_bottom": "Scroll to bottom",
@@ -79,8 +77,6 @@
"search": "Chercher", "search": "Chercher",
"share": "Partager", "share": "Partager",
"shortcuts": "Raccourcis", "shortcuts": "Raccourcis",
"social_description": "Follow us on social media to stay updated with the latest news, updates and releases.",
"social_links": "Social links",
"spotlight": "Projecteur", "spotlight": "Projecteur",
"status": "Statut", "status": "Statut",
"status_description": "Vérifier l'état du site web", "status_description": "Vérifier l'état du site web",
@@ -135,15 +131,12 @@
"renamed": "Collection renommée", "renamed": "Collection renommée",
"request_in_use": "Demande en cours d'utilisation", "request_in_use": "Demande en cours d'utilisation",
"save_as": "Enregistrer sous", "save_as": "Enregistrer sous",
"save_to_collection": "Save to Collection",
"select": "Sélectionnez une collection", "select": "Sélectionnez une collection",
"select_location": "Sélectionnez l'emplacement", "select_location": "Sélectionnez l'emplacement",
"select_team": "Sélectionnez une équipe", "select_team": "Sélectionnez une équipe",
"team_collections": "Collections de l'équipe" "team_collections": "Collections de l'équipe"
}, },
"confirm": { "confirm": {
"close_unsaved_tab": "Are you sure you want to close this tab?",
"close_unsaved_tabs": "Are you sure you want to close all tabs? {count} unsaved tabs will be lost.",
"exit_team": "Are you sure you want to leave this team?", "exit_team": "Are you sure you want to leave this team?",
"logout": "Êtes-vous sûr de vouloir vous déconnecter?", "logout": "Êtes-vous sûr de vouloir vous déconnecter?",
"remove_collection": "Voulez-vous vraiment supprimer définitivement cette collection ?", "remove_collection": "Voulez-vous vraiment supprimer définitivement cette collection ?",
@@ -157,11 +150,6 @@
"save_unsaved_tab": "Do you want to save changes made in this tab?", "save_unsaved_tab": "Do you want to save changes made in this tab?",
"sync": "Voulez-vous vraiment synchroniser cet espace de travail ?" "sync": "Voulez-vous vraiment synchroniser cet espace de travail ?"
}, },
"context_menu": {
"add_parameters": "Add to parameters",
"open_request_in_new_tab": "Open request in new tab",
"set_environment_variable": "Set as variable"
},
"count": { "count": {
"header": "En-tête {count}", "header": "En-tête {count}",
"message": "Message {compte}", "message": "Message {compte}",
@@ -204,31 +192,17 @@
"create_new": "Créer un nouvel environnement", "create_new": "Créer un nouvel environnement",
"created": "Environnement créé", "created": "Environnement créé",
"deleted": "Environnement supprimé", "deleted": "Environnement supprimé",
"duplicated": "Environment duplicated",
"edit": "Modifier l'environnement", "edit": "Modifier l'environnement",
"empty_variables": "No variables",
"global": "Global",
"global_variables": "Global variables",
"invalid_name": "Veuillez fournir un nom valide pour l'environnement", "invalid_name": "Veuillez fournir un nom valide pour l'environnement",
"list": "Environment variables",
"my_environments": "My Environments", "my_environments": "My Environments",
"name": "Name",
"nested_overflow": "les variables d'environnement imbriquées sont limitées à 10 niveaux", "nested_overflow": "les variables d'environnement imbriquées sont limitées à 10 niveaux",
"new": "Nouvel environnement", "new": "Nouvel environnement",
"no_active_environment": "No active environment",
"no_environment": "Pas d'environnement", "no_environment": "Pas d'environnement",
"no_environment_description": "Aucun environnement n'a été sélectionné. Choisissez ce qu'il faut faire avec les variables suivantes.", "no_environment_description": "Aucun environnement n'a été sélectionné. Choisissez ce qu'il faut faire avec les variables suivantes.",
"quick_peek": "Environment Quick Peek",
"replace_with_variable": "Replace with variable",
"scope": "Scope",
"select": "Sélectionnez l'environnement", "select": "Sélectionnez l'environnement",
"set": "Set environment",
"set_as_environment": "Set as environment",
"team_environments": "Team Environments", "team_environments": "Team Environments",
"title": "Environnements", "title": "Environnements",
"updated": "Mise à jour de l'environnement", "updated": "Mise à jour de l'environnement",
"value": "Value",
"variable": "Variable",
"variable_list": "Liste des variables" "variable_list": "Liste des variables"
}, },
"error": { "error": {
@@ -252,7 +226,6 @@
"no_duration": "Pas de durée", "no_duration": "Pas de durée",
"no_results_found": "Aucune correspondance trouvée", "no_results_found": "Aucune correspondance trouvée",
"page_not_found": "Cette page n'a pas pu être trouvée", "page_not_found": "Cette page n'a pas pu être trouvée",
"proxy_error": "Proxy error",
"script_fail": "Impossible d'exécuter le script de pré-requête", "script_fail": "Impossible d'exécuter le script de pré-requête",
"something_went_wrong": "Quelque chose s'est mal passé", "something_went_wrong": "Quelque chose s'est mal passé",
"test_script_fail": "Impossible d'exécuter le script post-requête" "test_script_fail": "Impossible d'exécuter le script post-requête"
@@ -278,13 +251,9 @@
"renamed": "Dossier renommé" "renamed": "Dossier renommé"
}, },
"graphql": { "graphql": {
"connection_switch_confirm": "Do you want to connect with the latest GraphQL endpoint?",
"connection_switch_new_url": "Switching to a tab will disconnected you from the active GraphQL connection. New connection URL is",
"connection_switch_url": "You're connected to a GraphQL endpoint the connection URL is",
"mutations": "Mutations", "mutations": "Mutations",
"schema": "Schéma", "schema": "Schéma",
"subscriptions": "Abonnements", "subscriptions": "Abonnements"
"switch_connection": "Switch connection"
}, },
"group": { "group": {
"time": "Time", "time": "Time",
@@ -338,36 +307,13 @@
"json_description": "Importer des collections depuis un fichier JSON Hoppscotch", "json_description": "Importer des collections depuis un fichier JSON Hoppscotch",
"title": "Importer" "title": "Importer"
}, },
"inspections": {
"description": "Inspect possible errors",
"environment": {
"add_environment": "Add to Environment",
"not_found": "Environment variable “{environment}” not found."
},
"header": {
"cookie": "The browser doesn't allow Hoppscotch to set the Cookie Header. While we're working on the Hoppscotch Desktop App (coming soon), please use the Authorization Header instead."
},
"response": {
"401_error": "Please check your authentication credentials.",
"404_error": "Please check your request URL and method type.",
"cors_error": "Please check your Cross-Origin Resource Sharing configuration.",
"default_error": "Please check your request.",
"network_error": "Please check your network connection."
},
"title": "Inspector",
"url": {
"extension_not_installed": "Extension not installed.",
"extension_unknown_origin": "Make sure you've added the API endpoint's origin to the Hoppscotch Browser Extension list.",
"extention_enable_action": "Enable Browser Extension",
"extention_not_enabled": "Extension not enabled."
}
},
"layout": { "layout": {
"collapse_collection": "Réduire ou développer les collections", "collapse_collection": "Réduire ou développer les collections",
"collapse_sidebar": "Réduire ou développer la barre latérale", "collapse_sidebar": "Réduire ou développer la barre latérale",
"column": "Disposition verticale", "column": "Disposition verticale",
"name": "Disposition", "name": "Disposition",
"row": "Disposition horizontale" "row": "Disposition horizontale",
"zen_mode": "Mode Zen"
}, },
"modal": { "modal": {
"close_unsaved_tab": "You have unsaved changes", "close_unsaved_tab": "You have unsaved changes",
@@ -472,7 +418,6 @@
"payload": "Charge utile", "payload": "Charge utile",
"query": "Requête", "query": "Requête",
"raw_body": "Corps de requête brut", "raw_body": "Corps de requête brut",
"rename": "Rename Request",
"renamed": "Requête renommée", "renamed": "Requête renommée",
"run": "Lancer", "run": "Lancer",
"save": "Sauvegarder", "save": "Sauvegarder",
@@ -480,7 +425,6 @@
"saved": "Requête enregistrée", "saved": "Requête enregistrée",
"share": "Partager", "share": "Partager",
"share_description": "Partagez Hoppscotch avec vos amis", "share_description": "Partagez Hoppscotch avec vos amis",
"stop": "Stop",
"title": "Requête", "title": "Requête",
"type": "Type de requête", "type": "Type de requête",
"url": "URL", "url": "URL",
@@ -488,7 +432,6 @@
"view_my_links": "Voir mes liens" "view_my_links": "Voir mes liens"
}, },
"response": { "response": {
"audio": "Audio",
"body": "Corps de réponse", "body": "Corps de réponse",
"filter_response_body": "Filter JSON response body (uses JSONPath syntax)", "filter_response_body": "Filter JSON response body (uses JSONPath syntax)",
"headers": "En-têtes", "headers": "En-têtes",
@@ -502,7 +445,6 @@
"status": "Statut", "status": "Statut",
"time": "Temps", "time": "Temps",
"title": "Réponse", "title": "Réponse",
"video": "Video",
"waiting_for_connection": "En attente de connexion", "waiting_for_connection": "En attente de connexion",
"xml": "XML" "xml": "XML"
}, },
@@ -515,6 +457,7 @@
"account_name_description": "Ceci est votre nom d'affichage.", "account_name_description": "Ceci est votre nom d'affichage.",
"background": "Fond", "background": "Fond",
"black_mode": "Noir", "black_mode": "Noir",
"change_font_size": "Changer la taille de la police",
"choose_language": "Choisissez la langue", "choose_language": "Choisissez la langue",
"dark_mode": "Sombre", "dark_mode": "Sombre",
"delete_account": "Delete account", "delete_account": "Delete account",
@@ -527,6 +470,10 @@
"extensions": "Extensions", "extensions": "Extensions",
"extensions_use_toggle": "Utilisez l'extension de navigateur pour envoyer des requêtes (le cas échéant)", "extensions_use_toggle": "Utilisez l'extension de navigateur pour envoyer des requêtes (le cas échéant)",
"follow": "Follow Us", "follow": "Follow Us",
"font_size": "Taille de police",
"font_size_large": "Grande",
"font_size_medium": "Moyenne",
"font_size_small": "Petite",
"interceptor": "Intercepteur", "interceptor": "Intercepteur",
"interceptor_description": "Middleware entre l'application et les API.", "interceptor_description": "Middleware entre l'application et les API.",
"language": "Langue", "language": "Langue",
@@ -591,27 +538,19 @@
"settings": "Aller à la page Paramètres", "settings": "Aller à la page Paramètres",
"title": "Navigation" "title": "Navigation"
}, },
"others": {
"prettify": "Prettify Editor's Content",
"title": "Others"
},
"request": { "request": {
"copy_request_link": "Copier le lien de requête", "copy_request_link": "Copier le lien de requête",
"delete_method": "Sélectionnez la méthode DELETE", "delete_method": "Sélectionnez la méthode DELETE",
"get_method": "Sélectionnez la méthode GET", "get_method": "Sélectionnez la méthode GET",
"head_method": "Sélectionnez la méthode HEAD", "head_method": "Sélectionnez la méthode HEAD",
"import_curl": "Import cURL",
"method": "Méthode", "method": "Méthode",
"next_method": "Sélectionnez la méthode suivante", "next_method": "Sélectionnez la méthode suivante",
"post_method": "Sélectionnez la méthode POST", "post_method": "Sélectionnez la méthode POST",
"previous_method": "Sélectionnez la méthode précédente", "previous_method": "Sélectionnez la méthode précédente",
"put_method": "Sélectionnez la méthode PUT", "put_method": "Sélectionnez la méthode PUT",
"rename": "Rename Request",
"reset_request": "Réinitialiser la requête", "reset_request": "Réinitialiser la requête",
"save_request": "Save Request",
"save_to_collections": "Enregistrer dans les collections", "save_to_collections": "Enregistrer dans les collections",
"send_request": "Envoyer la requête", "send_request": "Envoyer la requête",
"show_code": "Generate code snippet",
"title": "Requête" "title": "Requête"
}, },
"response": { "response": {
@@ -641,85 +580,6 @@
"log": "Infos", "log": "Infos",
"url": "URL" "url": "URL"
}, },
"spotlight": {
"change_language": "Change Language",
"environments": {
"delete": "Delete current environment",
"duplicate": "Duplicate current environment",
"duplicate_global": "Duplicate global environment",
"edit": "Edit current environment",
"edit_global": "Edit global environment",
"new": "Create new environment",
"new_variable": "Create a new environment variable",
"title": "Environments"
},
"general": {
"chat": "Chat with support",
"help_menu": "Help and support",
"open_docs": "Read Documentation",
"open_github": "Open GitHub repository",
"open_keybindings": "Keyboard shortcuts",
"social": "Social",
"title": "General"
},
"graphql": {
"connect": "Connect to server",
"disconnect": "Disconnect from server"
},
"miscellaneous": {
"invite": "Invite your friends to Hoppscotch",
"title": "Miscellaneous"
},
"request": {
"save_as_new": "Save as new request",
"select_method": "Select method",
"switch_to": "Switch to",
"tab_authorization": "Authorization tab",
"tab_body": "Body tab",
"tab_headers": "Headers tab",
"tab_parameters": "Parameters tab",
"tab_pre_request_script": "Pre-request script tab",
"tab_query": "Query tab",
"tab_tests": "Tests tab",
"tab_variables": "Variables tab"
},
"response": {
"copy": "Copy response",
"download": "Download response as file",
"title": "Response"
},
"section": {
"interceptor": "Interceptor",
"interface": "Interface",
"theme": "Theme",
"user": "User"
},
"settings": {
"change_interceptor": "Change Interceptor",
"change_language": "Change Language",
"theme": {
"black": "Black",
"dark": "Dark",
"light": "Light",
"system": "System preference"
}
},
"tab": {
"close_current": "Close current tab",
"close_others": "Close all other tabs",
"duplicate": "Duplicate current tab",
"new_tab": "Open a new tab",
"title": "Tabs"
},
"workspace": {
"delete": "Delete current team",
"edit": "Edit current team",
"invite": "Invite people to team",
"new": "Create new team",
"switch_to_personal": "Switch to your personal workspace",
"title": "Teams"
}
},
"sse": { "sse": {
"event_type": "Type d'événement", "event_type": "Type d'événement",
"log": "Infos", "log": "Infos",
@@ -727,7 +587,7 @@
}, },
"state": { "state": {
"bulk_mode": "Modification groupée", "bulk_mode": "Modification groupée",
"bulk_mode_placeholder": "Les entrées sont séparées par une nouvelle ligne\nLes clés et les valeurs sont séparées par :\nAjoutez # à n'importe quelle ligne que vous souhaitez ajouter mais garder désactivée", "bulk_mode_placeholder": "Les entrées sont séparées par une nouvelle ligne\nLes clés et les valeurs sont séparées par :\nAjoutez # à n'importe quelle ligne que vous souhaitez ajouter mais garder désactivée",
"cleared": "Effacé", "cleared": "Effacé",
"connected": "Connecté", "connected": "Connecté",
"connected_to": "Connecté à {nom}", "connected_to": "Connecté à {nom}",
@@ -777,11 +637,8 @@
"tab": { "tab": {
"authorization": "Autorisation", "authorization": "Autorisation",
"body": "Corps", "body": "Corps",
"close": "Close Tab",
"close_others": "Close other Tabs",
"collections": "Collections", "collections": "Collections",
"documentation": "Documentation", "documentation": "Documentation",
"duplicate": "Duplicate Tab",
"environments": "Environments", "environments": "Environments",
"headers": "En-têtes", "headers": "En-têtes",
"history": "Histoire", "history": "Histoire",
@@ -832,7 +689,7 @@
"member_role_updated": "Rôles des utilisateurs mis à jour", "member_role_updated": "Rôles des utilisateurs mis à jour",
"members": "Membres", "members": "Membres",
"more_members": "+{count} more", "more_members": "+{count} more",
"name_length_insufficient": "Le nom de l'équipe doit comporter au moins 6 caractères", "name_length_insufficient": "Le nom de l'équipe doit comporter au moins 6 caractères",
"name_updated": "Nom de l'équipe mis à jour", "name_updated": "Nom de l'équipe mis à jour",
"new": "Nouvelle équipe", "new": "Nouvelle équipe",
"new_created": "Nouvelle équipe créée", "new_created": "Nouvelle équipe créée",

Some files were not shown because too many files have changed in this diff Show More