diff --git a/Dockerfile b/Dockerfile index 1b596b97..41e8948e 100644 --- a/Dockerfile +++ b/Dockerfile @@ -5,11 +5,21 @@ RUN apk add --no-cache \ gcompat \ supervisor \ curl \ + wget \ + openssl \ su-exec # Enable pnpm RUN corepack enable pnpm +# Install storage system for S3-compatible storage +COPY infra/install-minio.sh /tmp/install-minio.sh +RUN chmod +x /tmp/install-minio.sh && /tmp/install-minio.sh + +# Install storage client (mc) +RUN wget https://dl.min.io/client/mc/release/linux-amd64/mc -O /usr/local/bin/mc && \ + chmod +x /usr/local/bin/mc + # Set working directory WORKDIR /app @@ -119,11 +129,14 @@ RUN mkdir -p /etc/supervisor/conf.d # Copy server start script and configuration files COPY infra/server-start.sh /app/server-start.sh +COPY infra/start-minio.sh /app/start-minio.sh +COPY infra/minio-setup.sh /app/minio-setup.sh +COPY infra/load-minio-credentials.sh /app/load-minio-credentials.sh COPY infra/configs.json /app/infra/configs.json COPY infra/providers.json /app/infra/providers.json COPY infra/check-missing.js /app/infra/check-missing.js -RUN chmod +x /app/server-start.sh -RUN chown -R palmr:nodejs /app/server-start.sh /app/infra +RUN chmod +x /app/server-start.sh /app/start-minio.sh /app/minio-setup.sh /app/load-minio-credentials.sh +RUN chown -R palmr:nodejs /app/server-start.sh /app/start-minio.sh /app/minio-setup.sh /app/load-minio-credentials.sh /app/infra # Copy supervisor configuration COPY infra/supervisord.conf /etc/supervisor/conf.d/supervisord.conf @@ -144,9 +157,42 @@ export DATABASE_URL="file:/app/server/prisma/palmr.db" export NEXT_PUBLIC_DEFAULT_LANGUAGE=\${DEFAULT_LANGUAGE:-en-US} # Ensure /app/server directory exists for bind mounts -mkdir -p /app/server/uploads /app/server/temp-uploads /app/server/prisma - -echo "Data directories ready for first run..." +mkdir -p /app/server/uploads /app/server/temp-uploads /app/server/prisma /app/server/minio-data + +# CRITICAL: Fix permissions BEFORE starting any services +# This runs on EVERY startup to handle updates and corrupted metadata +echo "🔐 Fixing permissions for internal storage..." + +# DYNAMIC: Detect palmr user's actual UID and GID +# Works with any Docker --user configuration +PALMR_UID=\$(id -u palmr 2>/dev/null || echo "1001") +PALMR_GID=\$(id -g palmr 2>/dev/null || echo "1001") +echo " Target user: palmr (UID:\$PALMR_UID, GID:\$PALMR_GID)" + +# ALWAYS remove storage system metadata to prevent corruption issues +# This is safe - storage system recreates it automatically +# User data (files) are NOT in .minio.sys, they're safe +if [ -d "/app/server/minio-data/.minio.sys" ]; then + echo " 🧹 Cleaning storage system metadata (safe, auto-regenerated)..." + rm -rf /app/server/minio-data/.minio.sys 2>/dev/null || true +fi + +# Fix ownership and permissions (safe for updates) +echo " 🔧 Setting ownership and permissions..." +chown -R \$PALMR_UID:\$PALMR_GID /app/server 2>/dev/null || echo " ⚠️ chown skipped" +chmod -R 755 /app/server 2>/dev/null || echo " ⚠️ chmod skipped" + +# Verify critical directories are writable +if touch /app/server/.test-write 2>/dev/null; then + rm -f /app/server/.test-write + echo " ✅ Storage directory is writable" +else + echo " ❌ FATAL: /app/server is NOT writable!" + echo " Check Docker volume permissions" + ls -la /app/server 2>/dev/null || true +fi + +echo "✅ Storage ready, starting services..." # Start supervisor exec /usr/bin/supervisord -c /etc/supervisor/conf.d/supervisord.conf @@ -158,7 +204,7 @@ RUN chmod +x /app/start.sh VOLUME ["/app/server"] # Expose ports -EXPOSE 3333 5487 +EXPOSE 3333 5487 9379 9378 # Health check HEALTHCHECK --interval=30s --timeout=10s --start-period=60s --retries=3 \ diff --git a/apps/docs/content/docs/2.0.0-beta/api.mdx b/apps/docs/content/docs/2.0.0-beta/api.mdx deleted file mode 100644 index bb26dec3..00000000 --- a/apps/docs/content/docs/2.0.0-beta/api.mdx +++ /dev/null @@ -1,47 +0,0 @@ ---- -title: 🔌 API Endpoints -tag: v2.0.0-beta ---- - -## 📚 Accessing the API Documentation - -Palmr. provides a **comprehensive, well-documented, and fully typed API** that has been carefully designed to ensure maximum developer productivity and ease of integration. - -### 🎯 Scalar-Based Documentation - -This API can be accessed through dedicated documentation endpoints at: - -- **In a production environment:** `{your_server_domain}/docs` or `{your_server_ip}/docs` -- **In a local environment:** http://localhost:3333/docs - -The API documentation is powered by **[Scalar](https://scalar.com/)**, which provides developers with a sophisticated and fully interactive interface for exploring, testing, and validating all available requests within the Palmr. ecosystem. This modern documentation platform enables real-time testing and visualization of API responses. Below is an example screenshot of the API documentation interface: - -![Palmr API Documentation](/assets/v2/api-docs/scalar.png) - -We have made a deliberate decision to **not provide an online version** of the API documentation, as the endpoints and functionality may vary significantly depending on the specific version of Palmr. you have deployed in your environment. To ensure you're always working with accurate and version-specific documentation, we strongly recommend accessing the documentation only after initializing your API service. It's important to note that the API service is specifically designated as the **server** component within the official Palmr. GitHub repository. - -We strongly recommend utilizing **Scalar** as your primary tool for querying and testing the API, as the entire documentation system has been carefully optimized and designed with Scalar integration in mind. Scalar provides developers with an exceptionally intuitive and feature-rich interactive environment that streamlines the process of exploring endpoints, constructing and sending requests, and analyzing responses directly within its sophisticated interface. - ---- - -### 🔄 Swagger-Based Documentation - -Understanding that developers may have different preferences or requirements, we also maintain a **Swagger-based version** of the documentation for those who prefer this widely-adopted documentation standard or require compatibility with existing tools and workflows. - -![Palmr API Documentation](/assets/v2/api-docs/swagger.png) - -This alternative documentation format can be accessed at the following endpoints: - -- **In a production environment:** `{your_server_domain}/swagger` or `{your_server_ip}/swagger` -- **In a local environment:** http://localhost:3333/swagger - -Rest assured that both the Scalar and Swagger documentation versions maintain complete parity in terms of endpoint coverage and provide equally comprehensive documentation levels, ensuring successful testing and system integration regardless of your chosen documentation platform. - -These carefully curated documentation options have been implemented to ensure that developers have access to all the necessary resources, detailed information, and interactive tools required for seamless integration between Palmr. and both internal systems and third-party services. - -### 🔗 Useful Links - -For additional information and detailed documentation about the tools that power our API documentation, please refer to these official resources: - -- [Scalar Official Website](https://scalar.com/) -- [Swagger Official Website](https://swagger.io/) diff --git a/apps/docs/content/docs/2.0.0-beta/architecture.mdx b/apps/docs/content/docs/2.0.0-beta/architecture.mdx deleted file mode 100644 index 8a6b49c6..00000000 --- a/apps/docs/content/docs/2.0.0-beta/architecture.mdx +++ /dev/null @@ -1,61 +0,0 @@ ---- -title: 🏗 Architecture of Palmr. ---- - -## 🔍 Overview - -Understanding the architecture of Palmr. is crucial for both deploying and scaling the application. Below is a diagram illustrating the main components: - -![Palmr Banner](/assets/v2/general/architecture.png) - -## 🛠 Technologies Used - -Each component in the Palmr. architecture plays a vital role in ensuring reliability, performance, and scalability. The stack is built with simplicity, performance, and flexibility in mind, everything is self-hosted, developer-friendly, and designed to scale without adding unnecessary complexity. - -### 💾 PostgreSQL - -Palmr. uses **PostgreSQL** as the primary database solution. It's a powerful, open-source relational database that’s trusted by developers around the world. PostgreSQL is fully ACID-compliant, which means it handles transactions safely and reliably. It’s perfect for storing structured data like user accounts, file metadata, transfer logs, and anything else that requires consistency. With advanced features like full-text search, custom data types (like JSONB), and strong indexing capabilities, PostgreSQL gives us the tools to scale efficiently without giving up query performance or flexibility. - -- Provides reliable and secure data storage, ensuring consistency and high performance for all database operations. -- Powerful indexing, query optimization, and support for complex data types. -- Ideal for handling large amounts of metadata and transactional data in a predictable and scalable way. - -### 🎨 Next.js 15 + React + TypeScript - -The frontend of Palmr. is built using **Next.js 15**, along with **React** and **TypeScript**, forming a modern stack that’s easy to maintain and super fast for end users. Next.js 15 brings server components, server actions, and a new app router system that makes rendering dynamic content incredibly efficient. This allows us to load only what’s needed, when it’s needed which makes the app feel snappy even under load. React provides a clean, component-based structure that makes it easy to break the UI into reusable pieces, and TypeScript helps prevent bugs before they even happen by enforcing static typing and better code navigation. Whether it's SSR, static pages, or dynamic user interactions, this trio handles it all seamlessly. - -- **React** enables the creation of a dynamic and responsive user interface with a component-based architecture. -- **TypeScript** adds static typing, enhancing code quality and reducing runtime errors. -- **Next.js 15** handles routing, server-side rendering, and server components for performance at scale. - -### 📦 MinIO - -Palmr. uses **MinIO** for object storage. MinIO is a lightweight, high-performance, S3-compatible storage solution that makes file handling simple and scalable. Every file uploaded to Palmr. Whether it's a personal file transfer or a shared asset is stored in MinIO. It’s built to handle huge amounts of data and can be deployed locally, on-premise, or in the cloud. Because it speaks the same API as Amazon S3, integrating with it is straightforward and familiar to most developers. And since it’s self-hosted, we have full control over performance, redundancy, and security. - -- Supports high-throughput file storage and retrieval. -- Ensures data integrity and redundancy. -- Compatible with AWS S3 APIs, making integration seamless. - -### ⚡ Fastify - -The backend of Palmr. is powered by **Fastify**, a super-fast Node.js web framework optimized for performance and low overhead. It’s designed to handle lots of concurrent requests with minimal resource usage, which is key for scalable backend services. Fastify also has a built-in schema validation system that ensures all incoming data is properly validated before reaching business logic, which helps prevent bugs and security issues. It follows a plugin-based architecture, making it easy to keep route handlers, services, and middlewares cleanly separated and easy to extend as the project grows. - -- Provides fast request handling with a lightweight core. -- Built-in schema-based validation for secure and reliable API handling. -- Supports plugin-based architecture for easy extensibility. - -### 🔄 How It Works - -1. **Frontend** — React + TypeScript + Next.js 15 handle the user interface and user interactions. -2. **Backend** — Fastify processes requests and communicates with the database and storage layers. -3. **Database** — PostgreSQL stores metadata and transactional data. -4. **Object Storage** — MinIO stores the actual files and ensures scalable, high-performance storage. - -### 📚 Useful Links - -- [PostgreSQL Documentation](https://www.postgresql.org/docs/) -- [Next.js Documentation](https://nextjs.org/docs) -- [React Documentation](https://react.dev/) -- [TypeScript Handbook](https://www.typescriptlang.org/docs/) -- [MinIO Documentation](https://min.io/docs/minio/container/index.html) -- [Fastify Documentation](https://fastify.dev/docs/latest/) diff --git a/apps/docs/content/docs/2.0.0-beta/available-languages.mdx b/apps/docs/content/docs/2.0.0-beta/available-languages.mdx deleted file mode 100644 index e91d3744..00000000 --- a/apps/docs/content/docs/2.0.0-beta/available-languages.mdx +++ /dev/null @@ -1,52 +0,0 @@ ---- -title: 🌐 Available languages ---- - -The project leverages next-intl, a powerful and flexible internationalization (i18n) library, to provide comprehensive language support across the entire application. This robust solution enables seamless translation management, date/time formatting, number formatting, and pluralization rules across different locales. The integration of next-intl ensures consistent internationalization throughout the application's components, pages, and features, while maintaining optimal performance through efficient bundle splitting and lazy loading of language resources. With its TypeScript support and React Server Components compatibility, next-intl serves as the foundation for delivering a truly global and accessible user experience. - -## 🗣️ Available Languages in Palmr. - ---- - -| Language | Code | Description | Translation | -| ------------- | ----- | -------------------------------------------------------- | ----------- | -| 🇺🇸 English | en-US | Primary development language and default fallback option | 100% | -| 🇧🇷 Portuguese | pt-BR | Standard Brazilian Portuguese support | 100% | -| 🇫🇷 French | fr-FR | Standard French language support | 100% | -| 🇪🇸 Spanish | es-ES | Standard Spanish language support | 100% | -| 🇩🇪 German | de-DE | Standard German language support | 100% | -| 🇷🇺 Russian | ru-RU | Standard Russian language support | 100% | -| 🇮🇳 Hindi | hi-IN | Standard Hindi language support | 100% | -| 🇸🇦 Arabic | ar-SA | Standard Arabic language support with RTL | 100% | -| 🇯🇵 Japanese | ja-JP | Standard Japanese language support | 100% | -| 🇰🇷 Korean | ko-KR | Standard Korean language support | 100% | -| 🇹🇷 Turkish | tr-TR | Standard Turkish language support | 100% | -| 🇨🇳 Chinese | zh-CN | Standard Simplified Chinese support | 100% | - -### 🔄 Language Selection - -The application provides two convenient methods for language selection, ensuring a seamless user experience: - -### 🤖 1. Automatic Detection - -- The application features sophisticated automatic detection of the user's preferred browser language settings -- Intelligently utilizes the browser's preconfigured language preferences to set the initial application language - -### 👆 2. Manual Selection - -![](/assets/v1/main/language/language-selector.png) - -- Users have complete control to manually select their preferred language through an intuitive language selector interface in the UI -- Selected language preferences are persistently stored in the browser's localStorage for a consistent experience across sessions - -### ⭐ Default Language - -English (en-US) serves as the system's fallback language, ensuring consistent functionality even when language detection or selection encounters issues. This means that if a user's preferred language is not available or if there are any problems with language selection, the application will automatically default to English. This fallback mechanism is crucial for maintaining a seamless user experience and preventing any potential language-related disruptions. Additionally, all new features and updates are first implemented in English before being translated into other supported languages, ensuring that the English version always remains the most up-to-date and comprehensive. - -### 🔍 Language Detection - -The application employs advanced detection mechanisms to automatically identify and apply the user's browser language settings as the initial language configuration. For maximum flexibility, users can easily override this selection at any time using the convenient language switcher, accessible via the globe icon prominently displayed in the navigation bar. - -### ↔️ RTL Support - -The application incorporates comprehensive right-to-left (RTL) text handling capabilities, with particular attention paid to Arabic (ar-SA) language requirements, ensuring proper text alignment, layout direction, and user interface elements. diff --git a/apps/docs/content/docs/2.0.0-beta/configuring-smtp.mdx b/apps/docs/content/docs/2.0.0-beta/configuring-smtp.mdx deleted file mode 100644 index 876bc4e6..00000000 --- a/apps/docs/content/docs/2.0.0-beta/configuring-smtp.mdx +++ /dev/null @@ -1,69 +0,0 @@ ---- -title: ✉️ Configuring SMTP ---- - -For Palmr to function with all its best features, we need to configure our email server. To make this easier, there is a built-in configuration panel inside **Settings** in Palmr. However, only users with an **ADMIN** profile can access and configure these settings. - -## ❓ Why Configure SMTP? - -The main functionalities that depend on SMTP configuration are: - -- 🔑 **Password Reset** – Users who forget their password and cannot access the **Settings** panel need this feature. -- 📧 **Email Notifications** – Recipients will receive emails when new shares are sent to them. - -Now, let's go through the step-by-step process to configure the **SMTP Server**. - ---- - -### 🔧 Accessing SMTP Settings - -To access **Settings**, an **ADMIN** user must click on the profile picture in the **header** and select **Settings** from the dropdown menu. - -![Dropdown Menu](/assets/v1/main/smtp/dropdown-menu.png) - -Once inside the **Settings** panel, click on the **Email** card to expand the SMTP configuration options. - -![Closed Settings Card](/assets/v1/main/smtp/closed-card.png) - -After expanding the card, the following SMTP configuration fields will appear: - -![Opened Settings Card](/assets/v1/main/smtp/opened-card.png) - ---- - -### ⚙️ Configuring SMTP Server - -The first step is to **enable SMTP** by selecting "Yes" in the **SMTP Enabled** field. - -![SMTP Enabled](/assets/v1/main/smtp/smtp-enabled.png) - -Once SMTP is enabled, you can configure the other necessary fields: - -- **Sender Name** – This will appear as the sender’s name in emails. (Example: "Palmr") -- **Sender Email** – The email address from which notifications will be sent. (Example: "noreply@palmr.app") -- **SMTP Server** – The SMTP server address. You can use any email service provider. For Gmail, use `smtp.gmail.com` (this is the recommended option and set as default). -- **SMTP Port** – The server port. For Gmail, the standard port is **587**. -- **SMTP Username** – The username for the SMTP server. For Gmail, enter your email address. -- **SMTP Password** – The SMTP password. (Generate an App Password for Gmail) - -> **Important:** If using **Gmail**, you need to generate an **App Password** instead of using your standard email password. -> For other email services, consult the official documentation of the service provider you are using. We recommend using Gmail for simplicity and limits the number of emails sent. - ---- - -### 🔐 Generating a Gmail App Password - -To generate an App Password for Gmail: - -1. Go to [Google My Account](https://myaccount.google.com/). -2. Select **Security**. -3. Scroll down to **App Passwords**. -4. Generate a new password specifically for Palmr. - -For a complete guide, refer to: **[How to set up SMTP credentials with Gmail](https://medium.com/rails-to-rescue/how-to-set-up-smtp-credentials-with-gmail-for-your-app-send-email-cf236d11087d)**. - ---- - -### ✅ Finalizing SMTP Configuration - -After entering the correct information, save the settings. Palmr is now ready to send emails for password resets and share notifications! diff --git a/apps/docs/content/docs/2.0.0-beta/contribute.mdx b/apps/docs/content/docs/2.0.0-beta/contribute.mdx deleted file mode 100644 index 989eccb5..00000000 --- a/apps/docs/content/docs/2.0.0-beta/contribute.mdx +++ /dev/null @@ -1,231 +0,0 @@ ---- -title: 🤝 How to Contribute ---- - -## 👋 Introduction - -Thank you for your interest in contributing to the **Palmr.** project! Contributions are what make the open-source community such an amazing place to learn, inspire, and create. Whether you're fixing bugs, adding new features, improving documentation, or sharing ideas, every contribution helps make Palmr better for everyone. We welcome contributors of all experience levels - from beginners to seasoned developers. This comprehensive guide will walk you through the process of contributing to Palmr, from setting up your development environment to submitting your first pull request. We're excited to have you join our community of contributors! - ---- - -### 🔑 GitHub Login - -Before you can contribute, you need to be logged into your GitHub account. If you don't have an account yet, you can sign up for free at **[GitHub](https://github.com/)**. Having a GitHub account is essential as it allows you to fork repositories, submit pull requests, and interact with other contributors. Make sure to use a professional email address when creating your account, and consider enabling two-factor authentication for enhanced security. Once your account is set up, you can customize your profile and start exploring the vast open-source community on GitHub. - ---- - -### 🔍 Access the Repository - -Once you're logged in, go to the Palmr repository by clicking on this link: **[https://github.com/kyantech/Palmr](https://github.com/kyantech/Palmr)**. The repository contains all the source code, documentation, and resources for the Palmr project. Take a moment to explore the repository structure, including the README file, which provides an overview of the project. - -Alternatively, you can search for "Palmr" in the GitHub search bar and click on the repository owned by **kyantech**. When searching, make sure you're looking at the official repository by checking the owner and repository name. The repository should have a description that matches the Palmr project and show recent activity from the maintainers. You can also check the number of stars, forks, and watchers to verify you're accessing the correct repository. - ---- - -### 🍴 Fork the Repository - -To contribute to the project, you'll need to create your own copy of the repository. This is called a **fork**. Here's how to do it: - -1. Click the **Fork** button at the top right of the repository page. -2. Select where you want to fork the repository (your personal account or an organization). -3. Wait a few moments while GitHub creates your fork. -4. This will create a copy of the repository under your GitHub account. -5. You'll be automatically redirected to your forked repository once it's ready. - -The fork will maintain a connection to the original repository, allowing you to: - -- Keep your fork synchronized with the original repository -- Submit pull requests from your fork to the original repository -- Work independently on your own copy without affecting the original - ---- - -### 📥 Clone the Fork - -Next, you’ll need to clone your forked repository to your local machine. Here’s how: - -1. On your forked repository page, click the **Code** button. -2. Copy the repository URL (HTTPS or SSH). -3. Open your terminal or command prompt and run the following command to clone the repository: - - ```bash - git clone - ``` - -4. Navigate into the cloned directory: - - ```bash - cd Palmr - ``` - ---- - -### 🔄 Set up Base Branch - -Before making changes, ensure your local repository is set up to track the `next` branch from the original Palmr repository. Here’s how: - -1. Add the original Palmr repository as a remote: - - ```bash - git remote add upstream https://github.com/kyantech/Palmr.git - ``` - -2. Fetch the latest changes from the `next` branch: - - ```bash - git fetch upstream next - ``` - -3. Create a new branch for your contribution based on `upstream/next`: - - ```bash - git checkout -b your-branch-name upstream/next - ``` - ---- - -### ✏️ Make Local Changes - -Now you're ready to make your contributions! This could include: - -- Fixing a bug -- Adding a new feature -- Improving documentation -- Writing tests -- Enhancing performance -- Adding translations -- Improving accessibility -- Refactoring code -- Adding examples -- Reporting issues - -Make your changes in your local repository using your preferred code editor. Here are some tips for making changes: - -1. **Follow the Style Guide**: Make sure your code follows the project's coding standards and style guidelines -2. **Test Your Changes**: Run tests locally to ensure your changes don't break existing functionality -3. **Keep Changes Focused**: Make small, focused changes that address a single issue or feature -4. **Document Your Changes**: Add or update documentation as needed to explain your changes -5. **Review Your Work**: Double-check your changes before committing to ensure quality - -Remember to regularly save your work and test your changes incrementally to catch any issues early in the development process. - ---- - -### 📝 Use Conventional Commits - -Once you’ve made your changes, commit them to your branch using **Conventional Commits**. Conventional Commits help maintain a clean and consistent commit history. Here’s how to format your commit messages: - -**Commit Message Format:** -`(): ` - -**Examples:** - -- `feat: add user authentication` -- `fix(api): resolve null pointer exception` -- `docs: update README file` -- `chore: update dependencies` - -**Steps to Commit:** - -1. Stage your changes: - - ```bash - git add . - ``` - -2. Commit your changes with a properly formatted message: - - ```bash - git commit -m "feat: add new feature for user profiles" - ``` - ---- - -### 📤 Push Changes - -After committing your changes, you'll need to push them to your forked repository on GitHub. This step synchronizes your local changes with your remote repository. Here's how to do it: - -1. First, ensure your branch is up-to-date with any remote changes: - - ```bash - git pull origin your-branch-name - ``` - -2. Then push your commits to your forked repository: - ```bash - git push origin your-branch-name - ``` - -If this is the first time pushing this branch, you might need to set the upstream branch: - -```bash -git push -u origin your-branch-name -``` - -If you encounter any errors while pushing: - -- Make sure you have the correct permissions on your fork -- Verify your remote URL is correct using `git remote -v` -- Check if you need to authenticate with GitHub - ---- - -### 🔀 Create Pull Request - -Now that your changes are on GitHub, you can open a **Pull Request (PR)** to propose your changes to the `next` branch of the Palmr repository. Here’s how: - -1. Go to your forked repository on GitHub. -2. Click the **Pull Request** button. -3. On the PR creation page: - - Set the **base repository** to `kyantech/Palmr`. - - Set the **base branch** to `next`. - - Set the **head repository** to your forked repository. - - Set the **compare branch** to your branch (`your-branch-name`). -4. Fill out the PR form with a clear title and description of your changes. -5. Click **Create Pull Request**. - ---- - -### ⏳ Await Review - -Once your PR is submitted, the maintainers will review your changes. They may provide feedback or request additional changes. During this process: - -- **Monitor Your PR**: Keep an eye on GitHub notifications for any comments or requests -- **Be Responsive**: Try to address feedback promptly and professionally -- **Make Updates**: If changes are requested, update your branch and push the new commits -- **Ask Questions**: Don't hesitate to ask for clarification if needed -- **Be Patient**: The review process may take some time depending on maintainer availability - -Remember that code review is a collaborative process aimed at ensuring code quality. Stay engaged and maintain open communication with the maintainers throughout the review process. - ---- - -## 💡 Contribution Tips - -To ensure your contribution is accepted, follow these tips: - -- **Use Conventional Commits**: Write clear and consistent commit messages using the Conventional Commits format. -- **Keep Your PRs Small**: Focus on one issue or feature per PR to make it easier to review. -- **Be Patient**: Maintainers are often volunteers and may take some time to review your PR. -- **Write Tests**: Include tests for any new features or bug fixes you implement. -- **Follow Code Style**: Adhere to the project's coding standards and style guidelines. -- **Update Documentation**: Keep documentation in sync with code changes. -- **Engage in Discussion**: Participate in PR discussions and be open to feedback. -- **Review Others' PRs**: Help the community by reviewing other contributors' pull requests. -- **Stay Updated**: Keep your fork synchronized with the main repository. - ---- - -## ⭐ Why Contribute? - -Contributing to open-source projects like Palmr has many benefits: - -1. **Improves the Project**: Your contributions help make the project better for everyone. -2. **Builds Your Skills**: You’ll gain experience working with Git, GitHub, and collaborative coding. -3. **Supports the Community**: Open-source thrives on community contributions. Your work helps sustain the project. - ---- - -## 🎉 Final Words - -That's it! You've successfully contributed to the **🌴 Palmr.** project on GitHub. Thank you for your time and effort in making Palmr better for everyone. We appreciate your contribution! diff --git a/apps/docs/content/docs/2.0.0-beta/generate-share.mdx b/apps/docs/content/docs/2.0.0-beta/generate-share.mdx deleted file mode 100644 index 957df316..00000000 --- a/apps/docs/content/docs/2.0.0-beta/generate-share.mdx +++ /dev/null @@ -1,150 +0,0 @@ ---- -title: 🔗 Managing shares ---- - -## 📤 Creating a share - -Creating a share in Palmr is designed to be a straightforward and user-friendly experience that anyone can master quickly. While the platform offers several methods for share creation, we recommend beginning with the most accessible approach: utilizing the **Home Page**, particularly through the well-organized **Recent Shares** section. - ---- - -### Home Page - -When you visit the home page, you'll immediately notice the **"Recent Shares"** section, which serves as your central hub for file sharing. For new users who haven't created any shares yet, this section presents a clean, welcoming interface as follows: - -![](/assets/v1/main/shares/share-section.png) - -For first-time users, the interface features a prominently positioned **"Create Share"** button that stands out against the clean background, making it impossible to miss. - -![](/assets/v1/main/shares/create-first-share.png) - -> Note: One of Palmr's unique features is its flexibility - you don't need to upload files to create a share! This might seem counterintuitive at first, but it's a deliberately designed feature that many users find invaluable for their specific workflows and use cases. This approach allows you to set up the sharing framework first and add content later. - -This design philosophy reflects Palmr's user-centric approach: you can establish your share's parameters and settings first, then populate it with files at your convenience. Furthermore, all aspects of your share remain fully editable at any time, providing maximum flexibility. - -To initiate the share creation process, locate and click the **"Create Share"** button positioned centrally within the **Recent Shares** section. This action will trigger the appearance of a comprehensive **Create Share** modal window: - -![](/assets/v1/main/shares/create-share-modal.png) - -Within the modal, you'll find various fields to customize your share. While some fields are optional, they each serve important functions in controlling how your share behaves. Pay particular attention to these three powerful security options - **Expiration Date**, **Max Views**, and **Password** - as they significantly enhance your control over how recipients interact with your shared content: - -- **Password:** Adding this extra layer of security ensures that only recipients with the correct password can gain access to your shared content, providing an additional verification step. -- **Max Views:** This feature allows you to set a specific limit on the number of times your share can be accessed. Once this threshold is reached, the share becomes inaccessible unless you, as the creator, choose to adjust or remove the viewing limit. -- **Expiration Date:** By setting this parameter, you can determine precisely how long your share remains accessible. After the specified date passes, the share automatically deactivates, though you retain the ability to extend this deadline if needed. - -While the **name** field isn't mandatory, we strongly encourage users to assign meaningful names to their shares for easier management and organization of multiple shares over time. - -After successfully creating a share, the **Recent Shares** section transforms into an informative table display, presenting comprehensive details through the following columns: - -- **Name** -- **Created At** -- **Expires At** -- **Status** -- **Security** -- **Files** -- **Recipients** -- **Actions** - -![](/assets/v1/main/shares/shares-table.png) - -For your convenience, once you've created your first share, a **"New Share"** button appears in the upper right corner of the **Recent Shares** section, making it easy to create additional shares. - -![](/assets/v1/main/shares/new-share-btn.png) - -This conveniently positioned button provides quick access to the **Create Share** modal whenever you need to create another share. - -To maintain a clean and manageable interface, the **Recent Shares** section displays your **last 5 shares** by default. When you need to access your complete sharing history, simply click the **"View All"** button to see your entire collection of shares. - -![](/assets/v1/main/shares/recent-shares-filled.png) - -![](/assets/v1/main/shares/view-all-button.png) - -Upon clicking this button, you'll be seamlessly redirected to the comprehensive **Shares Management** page. - -![](/assets/v1/main/shares/my-shares-page.png) - -For quick access to your complete share collection, you can also reach the **Shares Management** page by clicking the conveniently placed **"My Shares"** card on the home page. - -![](/assets/v1/main/shares/my-shares-card.png) - ---- - -### 📊 Shares Management Page - -The **Shares Management** page functions similarly to the **Uploads Management** page but offers expanded capabilities. Here, you'll find a complete overview where you can **add, remove, edit, and view all created shares** without being restricted to the five-share limit of the Recent Shares section. - -Each share has an **Actions** column with the following options: - -![](/assets/v1/main/shares/actions-column.png) - ---- - -## ✏️ Edit Share - -The **Edit** button provides access to a comprehensive interface where you can modify and update all share details as needed. - -![](/assets/v1/main/shares/edit-share-modal.png) - -## 📁 Manage Files - ---- - -Through the **Manage Files** button, you gain complete control over the content of your share, with the ability to both add new files and remove existing ones. - -![](/assets/v1/main/shares/manage-files-modal.png) - ---- - -## 👥 Manage Recipients - -The **Manage Recipients** button opens an interface where you can maintain your recipient list, adding or removing access as needed. - -> Note: For email notifications to function properly, please ensure that your system's SMTP settings are correctly configured and active. - -![](/assets/v1/main/shares/manage-recipients-modal.png) - ---- - -## 👀 View Share Details - -The **View Details** option provides a comprehensive overview of all aspects and settings associated with your share. - -![](/assets/v1/main/shares/share-details-modal.png) - ---- - -## 🔗 Generate Share Link - -The **Generate Link** feature allows you to create a customizable sharing link for easy distribution of your content. - -![](/assets/v1/main/shares/generate-share-link-modal.png) - -After generation, the system presents you with the link for immediate viewing and copying. - -![](/assets/v1/main/shares/copy-link-modal.png) - -A convenient dropdown menu provides options to either edit the generated link's settings or copy it to your clipboard. - -![](/assets/v1/main/shares/dropdown-with-copy.png) - -When recipients access your generated link, they'll be able to both **view and download** the shared files according to the permissions you've set. - -![](/assets/v1/main/shares/share-screen.png) - ---- - -## Delete Share - -Clicking the - -**Delete** - -button allows you to permanently remove any share that's no longer needed. - -![](/assets/v1/main/shares/delete-share-modal.png) - ---- - -### 📝 Summary - -Palmr's share creation system exemplifies intuitive design and flexible functionality. The platform offers comprehensive features including file-free share creation, customizable share settings, recipient management, and secure link generation. The dedicated **Shares Management** page serves as your command center, providing complete oversight and control of your entire sharing ecosystem. diff --git a/apps/docs/content/docs/2.0.0-beta/gh-sponsor.mdx b/apps/docs/content/docs/2.0.0-beta/gh-sponsor.mdx deleted file mode 100644 index 73cef33e..00000000 --- a/apps/docs/content/docs/2.0.0-beta/gh-sponsor.mdx +++ /dev/null @@ -1,111 +0,0 @@ ---- -title: 💝 Github Sponsors ---- - -## 👋 Introduction - -Sponsoring a project on GitHub is a powerful way to support its development and ensure its long-term sustainability. This tutorial will guide you through the process of sponsoring the **Palmr.** project on GitHub using GitHub Sponsors. - -By becoming a sponsor, you'll not only help maintain and improve the project, but you'll also: - -- Support continuous development and bug fixes -- Enable new feature implementations -- Help cover hosting and infrastructure costs -- Show appreciation for the developers' hard work -- Join a community of supporters who value open source - ---- - -### 🔑 GitHub Login - -Before you can sponsor a project, you need to be logged into your GitHub account. If you don't have an account yet, you can sign up for free at [GitHub](https://github.com/). Having an account also allows you to: - -- Follow project updates -- Report issues -- Contribute to discussions -- Access sponsor-only content (if available) - ---- - -### 🔍 Access the Repository - -Once you're logged in, go to the Palmr. repository by clicking on this link: [https://github.com/kyantech/Palmr](https://github.com/kyantech/Palmr). - -Alternatively, you can search for "Palmr" in the GitHub search bar and click on the repository owned by **Kyantech**. - -You can also: - -- Star the repository to show your support -- Watch it for updates -- Fork it if you want to contribute - ---- - -### 💖 Click the Sponsor Button - -On the Palmr repository page, you'll see a **Sponsor** button at the top right corner of the page. Click this button to proceed. The button is typically highlighted in a distinct color to make it easily visible. - -![Palmr Sponsor Button](/assets/v1/sponsor/sponsor-btn.png) - -Pro tip: You can also access the sponsorship page directly through your GitHub dashboard under "Sponsoring" if you've sponsored us before. - ---- - -### 💰 Choose a Custom Sponsorship - -GitHub Sponsors allows you to sponsor the project with a **custom amount starting at $1**: - -1. On the sponsorship page, look for the option to **enter a custom amount**. -2. Type in the amount you'd like to sponsor (e.g., $1, $5, $10, or any amount you choose). -3. Select the billing frequency (**monthly** or **one-time**). -4. Consider setting up automatic annual sponsorship for a simplified experience. -5. Look for any special tier benefits that might be available at different sponsorship levels. - -![Sponsor Page Example](/assets/v1/sponsor/sponsor-page.png) - ---- - -### ✅ Complete Your Sponsorship - -After entering your custom amount and selecting the billing frequency, you'll be prompted to enter your payment details. Follow the instructions to complete the sponsorship process. - -Once done, you'll officially be a **Palmr sponsor**! 🙌 - ---- - -### ⭐ Why Sponsoring Matters - -- 🧱 Supports Sustainability - Your sponsorship helps keep the project alive and maintained long-term. - -- 🚀 Encourages Innovation - Financial support gives developers the freedom to try new ideas and push boundaries. - -- 🫶 Shows Deep Appreciation - Sponsoring is a meaningful, tangible way to thank developers for their work. - -- 🏆 Earns You Recognition - Many projects publicly thank their sponsors — you might appear in the README or on the site! - -- 🌱 Helps Open Source Thrive - Open-source projects rely on community support. Sponsoring helps the ecosystem grow. - ---- - -## 🎁 What Happens After Sponsoring - -Once you become a sponsor: - -1. You'll receive a confirmation email from GitHub -2. Your name will appear in our sponsors list -3. You'll get access to sponsor-only updates and content -4. You'll be invited to our private Discord channel -5. You'll receive early access to new features - ---- - -## 🌟 Final Words - -That's it! You've successfully sponsored the **Palmr.** project on GitHub. -Your support will help ensure this open-source project continues to evolve and thrive. -**We appreciate you and welcome you to our community!** 🎉 diff --git a/apps/docs/content/docs/2.0.0-beta/gh-star.mdx b/apps/docs/content/docs/2.0.0-beta/gh-star.mdx deleted file mode 100644 index 5dfa96c8..00000000 --- a/apps/docs/content/docs/2.0.0-beta/gh-star.mdx +++ /dev/null @@ -1,129 +0,0 @@ ---- -title: ⭐ Star on Github ---- - -## 👋 Introduction - -Starring a project on GitHub is a great way to show appreciation for a repository and to bookmark it for easy access later. This tutorial will guide you through the process of starring the **Palmr** project on GitHub. By starring our repository, you help increase its visibility and support the ongoing development of the project. - ---- - -### 🔑 GitHub Login - -Before you can star a project, you need to be logged into your GitHub account. If you don't have an account yet, you can sign up for free at [GitHub](https://github.com/). Here's how to get started: - -1. Visit [GitHub's signup page](https://github.com/signup) -2. Enter your email address -3. Create a strong password -4. Choose a username -5. Complete the verification process -6. Select your preferences and complete the setup - -Once your account is created, make sure to verify your email address to access all GitHub features. - ---- - -### 🔍 Access the Repository - -There are several ways to find and access the Palmr repository: - -1. **Direct Link**: Go to the Palmr repository by clicking on this link: [https://github.com/kyantech/Palmr](https://github.com/kyantech/Palmr) - -2. **Search Method**: - - Go to GitHub's main page - - Click the search bar at the top - - Type "Palmr" - - Look for the repository owned by **Kyantech** - - Click on the repository name to access it - -3. **Through Profile**: - - Visit Kyantech's GitHub profile - - Navigate to the "Repositories" tab - - Find and click on "Palmr" - ---- - -### 🌟 Find the Star Button - -Once you're on the Palmr repository page, you'll find the "Star" button in the top-right section of the page. Here's what to look for: - -1. Look at the top navigation bar of the repository -2. Find the row of action buttons (Watch, Fork, Star) -3. The Star button will be prominently displayed with a star icon ⭐ -4. You may see the current star count next to the button - -![Palmr Profile Menu](/assets/v1/sponsor/star-btn.png) - ---- - -### ✅ Confirm the Star - -After clicking the "Star" button, several things will happen: - -1. The button will change from "Star" to "Unstar" -2. The star count will increase by one -3. The button will fill with color to indicate it's active -4. The repository will be added to your starred repositories list - -You can access your starred repositories anytime by: - -- Clicking your profile picture -- Selecting "Your stars" from the dropdown menu -- Or visiting: https://github.com/[your-username]?tab=stars - -To remove your star, simply click the "Unstar" button at any time. - -![Palmr Profile Menu](/assets/v1/sponsor/starred-button.png) - ---- - -### 💫 Why Starring Matters - -Starring a repository on GitHub is more than just a bookmarking tool—it’s a way to support the project and its developers. Here’s why starring is so important: - -- 🙌 Shows Appreciation - Starring a repository is a simple way to show your appreciation for the hard work and effort that goes into maintaining and developing a project. - -- 📈 Increases Visibility - The more stars a repository has, the more visible it becomes on GitHub. - -- 💪 Encourages Developers - Seeing stars motivates developers to continue improving the project. - -- 🔍 Helps with Discovery - GitHub prioritizes repositories with more stars in trending and recommendations. - -- 📚 Tracks Your Interests - Starred repositories are saved to your list for easy access later. - -- 🌍 Supports Open Source - Your stars help sustain the open-source community and the Palmr project. - ---- - -## 💝 Small Action / Big Impact - -Starring a repository takes just a second, but it can have a huge impact on the project's growth and development. Here's what your star means to us: - -- Helps drive project momentum -- Encourages more community participation -- Provides valuable feedback on project value -- Helps us set and achieve development goals -- Supports sustainable open-source development - -Your star is more than just a number - it's a vote of confidence in our vision and helps shape the future of Palmr. - ---- - -## 🎉 Final Words - -That's it! You've successfully starred the **Palmr** project on GitHub. Thank you for supporting Palmr and becoming part of our growing community! Your support helps us continue improving and expanding the project for everyone. - -Feel free to explore our other ways to contribute, like: - -- Sharing Palmr with others -- Reporting issues -- Contributing code -- Joining discussions - -Every interaction helps make Palmr. better! diff --git a/apps/docs/content/docs/2.0.0-beta/github-architecture.mdx b/apps/docs/content/docs/2.0.0-beta/github-architecture.mdx deleted file mode 100644 index 514b0da2..00000000 --- a/apps/docs/content/docs/2.0.0-beta/github-architecture.mdx +++ /dev/null @@ -1,144 +0,0 @@ ---- -title: Github Architecture ---- - -import { File, Files, Folder } from "fumadocs-ui/components/files"; - -## Project Structure - - - - - - - - - - - - - - - - -## Core Components - -### 🖥️ Frontend Application (apps/web) - -**Technology Stack:** - -- Next.js 15 (App Router) -- React 18 -- TypeScript -- TailwindCSS -- shadcn/ui components -- next-intl for internationalization - -Palmr.'s frontend is built with **Next.js 15**, using the App Router and Server Components for performance, scalability, and flexibility. The structure is modular and feature-based, keeping things easy to evolve as the product grows. UI logic runs on **React 18**, and **TypeScript** adds type safety that prevents a lot of silent bugs. Styles are handled with **TailwindCSS**, letting us build clean, responsive interfaces quickly. For components, we rely on **shadcn/ui**, a headless component library built with Radix UI and Tailwind, it’s fast, accessible, and fully customizable. - -Internationalization is handled by **next-intl**, which integrates perfectly with Next.js routing. It supports locale-aware routes, per-page translation loading, and plural rules, all without any extra client-side bloat. It’s flexible, lightweight, and great for apps with multilingual audiences. - -The frontend is organized with: - -- A **components-based architecture** for modular UI -- **Custom hooks** to isolate logic and side effects -- A **route protection system** using session cookies and middleware -- A **file management interface** integrated with the backend -- A **reusable modal system** used for file actions, confirmations, and more -- **Dynamic, locale-aware routing** using next-intl - ---- - -### ⚙️ Backend Service (apps/server) - -**Technology Stack:** - -- Fastify -- PostgreSQL -- MinIO (S3-compatible) -- Prisma ORM - -The backend is built on **Fastify**, a blazing-fast web framework for Node.js. It’s lightweight, modular, and optimized for high performance. Every route is validated using JSON schema, which helps keep the API consistent and secure. Auth flows are built using JWTs stored in HTTP-only cookies, and everything from file uploads to token-based sharing goes through this layer. - -Data is stored in **PostgreSQL**, which handles user info, file metadata, session tokens, and more. For actual file storage, we use **MinIO**, a fast, S3-compatible object store that’s self-hosted and super scalable. It’s perfect for keeping user files isolated and safe. We use **Prisma** as our ORM to simplify database access, it gives us type-safe queries and easy-to-read code. - -Key features include: - -- **Authentication/authorization** with JWT + cookie sessions -- **File management logic** including uploads, deletes, and renames -- **Storage operations** to handle bucket creation, usage tracking, and cleanup -- A **share system** that generates tokenized public file links -- Schema-based request validation for all endpoints -- Prisma models that keep the database logic predictable and type-safe - ---- - -### 📚 Documentation (apps/docs) - -**Technology Stack:** - -- Fumadocs -- MDX (Markdown + JSX) -- React-based components - -The docs are built using **Fumadocs**, a modern documentation system built on top of Next.js. It uses **MDX**, so you can mix Markdown with interactive React components, perfect for developer tools and open-source projects. Pages are fast, versionable, and easy to customize. The goal is to keep the documentation as close to the codebase as possible, using shared components where needed and reusing UI patterns directly from the app. - -It supports sidebar navigation, keyboard shortcuts, dark mode, and even interactive demos or UI previews. The file tree you see above, for example, is powered by a real React component from the docs. - -- Built with **Fumadocs**, powered by Next.js -- Supports **MDX** and full React component embedding -- Ideal for technical docs and live code samples -- Styled using the same Tailwind setup from the main app - ---- - -### 🏗️ Infrastructure - -Palmr. is fully containerized using **Docker**, which means every service: frontend, backend, database, storage, runs in its own isolated environment. With `docker-compose`, the whole stack spins up locally with a single command. It’s also easy to deploy to services like Fly.io, Render, or your own VPS. - -Volumes are used to persist data locally, and containers are networked together so that all services can talk to each other securely. - -- **Docker-first architecture** with all services containerized -- Configurable through `.env` and compose overrides -- Local volumes and named networks -- Easy to deploy and scale on any container-compatible infra - ---- - -## Key Features - -### 📂 File Management - -Files are at the heart of Palmr. Users can upload files via the frontend, and they’re streamed directly to MinIO. The backend handles metadata (name, size, type, ownership), and also handles deletion, renaming, and public sharing. Every file operation is tracked, and all actions can be scoped per user. - -- Upload/download with instant feedback -- File previews, type validation, and size limits -- Token-based sharing system -- Disk usage tracking by user - -### 👤 User System - -Authentication is done through secure JWTs, stored in HTTP-only cookies for safety. Signup and login flows are simple and fast, and user info is kept in sync across the frontend and backend. - -- Cookie-based session management -- Role support and future admin access -- Profile updates and password reset flows -- Logged-in user state handled via custom hooks - -### 💾 Storage System - -MinIO is used for all file storage. It’s fast, lightweight, and fully S3-compatible which means it can scale easily and integrates with tons of other tools. The backend tracks usage, handles cleanup of orphaned files, and ensures that every file on disk has a matching database record. - -- S3-compatible object storage via MinIO -- Upload validation and automatic cleanup -- Usage tracking and quotas (per user or global) -- Secure access to stored files with signed URLs - -### 🌐 Internationalization - -Palmr. supports multiple languages using **next-intl**, which is deeply integrated into the routing system. It loads only the necessary translations per route, supports nested namespaces, and makes it easy to keep things organized even at scale. - -- Per-locale localstorage (`en-US`, `pt-BR`, etc.) -- Translation loading by namespace/page -- Full pluralization and formatting support -- Easy translation management via JSON files diff --git a/apps/docs/content/docs/2.0.0-beta/index.mdx b/apps/docs/content/docs/2.0.0-beta/index.mdx deleted file mode 100644 index 4938fbf2..00000000 --- a/apps/docs/content/docs/2.0.0-beta/index.mdx +++ /dev/null @@ -1,52 +0,0 @@ ---- -title: 🌴 Welcome to Palmr. ---- - -![Palmr Banner](/assets/v2/general/banner.png) - -**Palmr.** is a powerful and **flexible open-source alternative** to popular file transfer services like **WeTransfer**, **SendGB**, **Send Anywhere** and **Files.fm**. The key advantage of Palmr. is that you can **host it on your own infrastructure**, such as a **dedicated server** or **VPS**, giving you full control over your files and data security without relying on third-party services or worrying about artificial limits or high fees. - -## **Why Choose Palmr.?** - -### 🚫 **No Artificial Limits** - -Unlike traditional file transfer services that impose arbitrary restrictions, Palmr. takes a fundamentally different approach by removing all artificial constraints on file sizes and quantities. The only practical limitation you'll encounter is the **available storage** space on your server or VPS infrastructure. This means that as long as you have adequate storage capacity in your hosting environment, you have complete freedom to transfer files of any size and in any quantity. There are no premium tiers to unlock additional features, no intrusive advertisements to navigate around, and absolutely no hidden charges or unexpected fees that might surprise you later. This unrestricted approach ensures that your file transfer capabilities are determined solely by your infrastructure choices rather than arbitrary service limitations. - -### 🌟 **Open Source and Free** - -Palmr. is completely **open source** and free to use, which means there are no licensing fees, subscription costs, or hidden charges associated with implementing and maintaining the software. This commitment to open source principles ensures complete transparency and freedom in how you utilize the platform. You can: - -- Deploy it on any infrastructure of your choice (VPS, dedicated server, Docker, cloud platforms, or other hosting environments), giving you complete flexibility in your hosting decisions. -- Review and audit the entire codebase to ensure security and integrity, allowing you to verify that the software meets your organization's security standards and compliance requirements. -- Contribute improvements or custom features to enhance the platform's functionality, participating in the collaborative development process that makes open source software so powerful. -- Adapt it for different use cases as needed, whether you're using it for personal file sharing, business operations, or specialized applications that require custom modifications. - -### 🔒 **Security and Privacy Under Your Control** - -When you host Palmr. on your infrastructure, you maintain **full control over your data** through end-to-end management of the storage and transfer process. By keeping files within your own hosting environment, you eliminate security vulnerabilities that could arise from third-party handling. Your files never get stored or processed by external services, ensuring complete **privacy** and **confidentiality** of transferred information. This allows you to implement security protocols and compliance measures that match your specific requirements. - -By eliminating third-party involvement, you gain peace of mind knowing that the files are never handled by external providers. This guarantees that you retain full control over data handling and processing, reinforcing the confidentiality of sensitive information throughout its lifecycle. - -With Palmr., your security and privacy are entirely in your hands, making it a powerful and reliable solution for organizations that require full control over their data, whether for internal use or for compliance with regulations. - -### 🎨 **Highly Customizable** - -Palmr. offers extensive customization options that allow you to tailor every aspect of the platform to perfectly align with your brand identity and create an optimal user experience that matches your specific requirements: - -- Add your own **logo** to maintain consistent branding across your file-sharing platform and establish a professional, unified presence. -- Set a **custom app name** that reflects your organization's identity and helps users instantly recognize your branded file-sharing solution. -- Configure an **SMTP server** for email notifications, enabling seamless communication with users about file transfers, updates, and system alerts. -- Customize text throughout the interface to create a unique user experience that resonates with your audience and maintains your brand voice. - -### 👥 **Complete User and Admin Management** - -Palmr. provides a comprehensive and sophisticated user and admin management system that puts you in complete control of your file-sharing environment: - -- Create and manage multiple **administrators** with different permission levels to ensure proper oversight and delegation of responsibilities. -- Add unlimited **users** to accommodate teams of any size, from small workgroups to large enterprises. -- Control who can view, upload, and manage files with granular permission settings that allow you to implement precise access controls. -- Easily monitor storage usage through detailed analytics and reporting tools that help you optimize resource allocation. - -### ⚡ **Fast, Lightweight, and Scalable** - -Palmr. demonstrates exceptional technical capabilities through its streamlined and expandable architecture, engineered to deliver optimal operational efficiency. The system effectively processes substantial file transfers and manages high-volume user activity while consistently maintaining superior transfer rates. The platform's contemporary infrastructure automatically adjusts to accommodate increased utilization, facilitating uninterrupted functionality as operational requirements expand. Through advanced resource allocation and refined programming, the system delivers a highly responsive user experience that accommodates organizational growth while maintaining consistent performance standards and operational stability. diff --git a/apps/docs/content/docs/2.0.0-beta/installation.mdx b/apps/docs/content/docs/2.0.0-beta/installation.mdx deleted file mode 100644 index 5028bb2a..00000000 --- a/apps/docs/content/docs/2.0.0-beta/installation.mdx +++ /dev/null @@ -1,260 +0,0 @@ ---- -title: 🐳 Installation (Docker Compose) ---- - -Installation via Docker Compose is the simplest way to run the project across different environments. For it to run correctly, we need two main tools installed in our environment: - -- Docker ([https://docs.docker.com](https://docs.docker.com/)) -- Docker Compose ([https://docs.docker.com/compose](https://docs.docker.com/compose/)) - -> _It's worth emphasizing that Palmr. was fully developed in a MacOS environment and extensively tested on Linux servers. Therefore, we can guarantee the best system performance in these environments. Windows and other environments have not been tested yet, and potential bugs may occur during execution. However, remember that we are still in a beta version of Palmr., and errors or bugs can occur in any operating system. If you identify any issues, we appreciate your help in notifying us through our GitHub [issues page](https://github.com/kyantech/Palmr/issues)._ - ---- - -## ⚡ Quick Start - -Having installed [Docker](https://docs.docker.com/) and [Docker Compose](https://docs.docker.com/compose/) in our environment, we can proceed with the simple installation using these tools. - -In the root folder of our project, we can find our `docker-compose.yaml` file, which is the only file needed to run the project via Docker and Docker Compose. This is because the pre-built images are already in our [DockerHub](https://hub.docker.com/repositories/kyantech) and are only referenced in the `docker-compose.yaml` - -Any changes needed for execution can be made directly in our `docker-compose.yaml` or via environment variables, which we will show later in this tutorial. - -Next, let's look at the content of our `docker-compose.yaml`. - ---- - -## 🐳 Docker Compose Content - -Below is the complete content of our `docker-compose.yaml` that can be copied directly from here or from our official repository ([Docker Compose](https://github.com/kyantech/Palmr/blob/main/docker-compose.yaml)). - -```yaml -services: - palmr: - image: kyantech/palmr:latest # Make sure to use the correct version (latest) of the image - container_name: palmr - depends_on: - postgres: - condition: "service_healthy" - minio: - condition: "service_healthy" - environment: - # Server environment variables - - PORT=${API_INTERNAL_PORT:-3333} # Port for the backend service - - DATABASE_URL=postgresql://postgres:${POSTGRESQL_PASSWORD:-postgresRootPassword}@postgres:5432/palmr_db?schema=public # Database URL with configurable password through POSTGRESQL_PASSWORD env var - - MINIO_ENDPOINT=${MINIO_ENDPOINT:-minio} # This can change if your MinIO is at a different address - - MINIO_PORT=${MINIO_INTERNAL_API_PORT:-6421} # Default MinIO port (Change if yours is not the default) - - MINIO_USE_SSL=false # MinIO uses SSL by default, but you can change it to true if needed - - MINIO_ROOT_USER=${MINIO_ROOT_USER:-minio_root_user} # MinIO credentials can be configured through MINIO_ROOT_USER env vars - - MINIO_ROOT_PASSWORD=${MINIO_ROOT_PASSWORD:-minioRootPassword} # MinIO credentials can be configured through MINIO_ROOT_PASSWORD env vars - - MINIO_REGION=sa-east-1 # MinIO region - This is needed for MinIO to work properly - - MINIO_BUCKET_NAME=files # MinIO bucket name - This is needed for MinIO to work properly, dont change it if you don't know what you are doing - - FRONTEND_URL=${APP_URL:-http://${SERVER_IP:-localhost}:${APP_EXTERNAL_PORT:-5487}} # Frontend URL - Make sure to use the correct frontend URL, depends on where the frontend is running, its prepared for localhost, but you can change it to your frontend URL if needed - - SERVER_IP=${SERVER_IP:-localhost} # Server IP - Make sure to use the correct server IP if you running on a cloud provider or a virtual machine. This prepared for localhost, but you can change it to your server IP if needed - - MAX_FILESIZE=${MAX_FILESIZE:-1073741824} # Max Filesize for upload - Declared in Bytes. Default is 1GiB - - # Web environment variables - - NODE_ENV=production - - NEXT_TELEMETRY_DISABLED=1 - - API_BASE_URL=http://palmr:${API_INTERNAL_PORT:-3333} # Using Docker service name for internal communication - ports: - - "${API_EXTERNAL_PORT:-3333}:3333" # Server port - - "${APP_EXTERNAL_PORT:-5487}:5487" # Web port - restart: unless-stopped - healthcheck: - test: - [ - "CMD", - "wget", - "--no-verbose", - "http://palmr:${API_INTERNAL_PORT:-3333}/health", - "&&", - "wget", - "--no-verbose", - "http://palmr:${APP_INTERNAL_PORT:-5487}", - ] - interval: 30s - timeout: 10s - retries: 3 - start_period: 60s - - minio: - image: minio/minio:RELEASE.2025-03-12T18-04-18Z # Use only version RELEASE.2025-03-12T18-04-18Z to avoid compatibility issues with the backend - container_name: minio - environment: - # MinIO credentials - same as above, configurable through environment variables - - MINIO_ROOT_USER=${MINIO_ROOT_USER:-minio_root_user} - - MINIO_ROOT_PASSWORD=${MINIO_ROOT_PASSWORD:-minioRootPassword} - - MINIO_SITE_REGION=sa-east-1 - command: server /data --address ":${MINIO_INTERNAL_API_PORT:-6421}" --console-address ":${MINIO_INTERNAL_CONSOLE_PORT:-6422}" - volumes: - - minio_data:/data - ports: - - "${MINIO_EXTERNAL_API_PORT:-6421}:${MINIO_INTERNAL_API_PORT:-6421}" - - "${MINIO_EXTERNAL_CONSOLE_PORT:-6422}:${MINIO_INTERNAL_CONSOLE_PORT:-6422}" - restart: unless-stopped - healthcheck: - test: ["CMD", "curl", "-f", "http://localhost:${MINIO_INTERNAL_API_PORT:-6421}/minio/health/ready"] - interval: 10s - timeout: 5s - retries: 5 - - minio-init: - image: minio/mc:RELEASE.2025-03-12T17-29-24Z # Use only version RELEASE.2025-03-12T17-29-24Z to avoid compatibility issues with the backend and MinIO - container_name: minio-init - depends_on: - minio: - condition: "service_healthy" - restart: "no" - # The entrypoint script will create a bucket called "files" and set it to be publicly readable using the MinIO client (mc). - entrypoint: > - sh -c " - sleep 5 && - mc alias set myminio http://minio:${MINIO_INTERNAL_API_PORT:-6421} ${MINIO_ROOT_USER:-minio_root_user} ${MINIO_ROOT_PASSWORD:-minioRootPassword} && - mc mb myminio/files --ignore-existing && - mc anonymous set download myminio/files - " - - postgres: - image: bitnami/postgresql:17.2.0 # You can use any postgres version you prefer, but remember that some versions might not be compatible - container_name: palmr-postgres - environment: - # PostgreSQL credentials configurable through environment variables - # POSTGRESQL_USERNAME, POSTGRESQL_PASSWORD, and POSTGRES_DB can be set to override defaults - - POSTGRESQL_USERNAME=${POSTGRESQL_USERNAME:-postgres} - - POSTGRESQL_PASSWORD=${POSTGRESQL_PASSWORD:-postgresRootPassword} - - POSTGRESQL_DATABASE=${POSTGRES_DATABASE:-palmr_db} - volumes: - - postgres_data:/bitnami/postgresql - ports: - - "5432:5432" - restart: unless-stopped - healthcheck: - test: ["CMD", "pg_isready", "-U", "palmr"] - interval: 10s - timeout: 5s - retries: 5 - -volumes: - minio_data: - postgres_data: -``` - -Notice that the `docker-compose.yaml` has several comments that help you configure your own compose to meet your environment's needs. Let's give an overview of some changes we can make. - ---- - -### ⚙️ Services Overview - -Palmr. consists of four main services, each with specific responsibilities. Below, we present a detailed view of each component: - -| **Service** | **Image** | **Exposed Ports** | **Main Features** | -| ------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------- | -| palmr | [kyantech/palmr:latest](https://hub.docker.com/repository/docker/kyantech/palmr/general) | **3333** (API)
**5487** (Web) | • Combined backend API and frontend service
• Depends on services: postgres and minio
• Has healthcheck to ensure availability | -| minio (Storage) | [minio/minio:RELEASE.2025-03-12T18-04-18Z](https://hub.docker.com/layers/minio/minio/RELEASE.2025-03-12T18-04-18Z/images/sha256-85f3e4cd1ca92a2711553ab79f222bcd8b75aa2c77a1a0b0ccf80d38e8ab2fe5) | **6421**(API)
**6422**(Console) | • File storage service
• Persistent volume for data | -| minio-init (Config) | [minio/mc:RELEASE.2025-03-12T17-29-24Z](https://hub.docker.com/layers/minio/mc/RELEASE.2025-03-12T17-29-24Z/images/sha256-68d8c80f43908b02daa285e55547131870a1d36b3ffe272c26d7d8f4d52d1e5c) | N/A | • Initially configures the "files" bucket
• Runs only once during initialization | -| postgres (Database) | [bitnami/postgresql:17.2.0](https://hub.docker.com/layers/bitnami/postgresql/17.2.0/images/sha256-29c614afad4f514b12b5c0f4d006f38c98fa4b18c3582732ff93b3fe9a79d875) | **5432** | • PostgreSQL database
• Persistent volume for data | - ---- - -### 🛠️ Available Environment Variables - -The table below shows all environment variables that can be set - -| **Variable** | **Default Value** | **Description** | -| --------------------------- | --------------------- | ------------------------------------------------- | -| API_INTERNAL_PORT | 3333 | Internal API port in container | -| API_EXTERNAL_PORT | 3333 | Exposed port on host for API | -| POSTGRES_PASSWORD | postgresRootPassword | PostgreSQL database password | -| APP_EXTERNAL_PORT | 5487 | Exposed port on host for frontend | -| APP_URL | http://localhost:5487 | Complete frontend URL | -| SERVER_IP | localhost | IP of the server where the application is running | -| MINIO_ROOT_USER | minio_root_user | MinIO admin user | -| MINIO_ROOT_PASSWORD | minioRootPassword | MinIO admin password | -| MINIO_INTERNAL_API_PORT | 6421 | Internal MinIO API port | -| MINIO_INTERNAL_CONSOLE_PORT | 6422 | Internal MinIO console port | -| MINIO_EXTERNAL_API_PORT | 6421 | Exposed port on host for MinIO API | -| MINIO_EXTERNAL_CONSOLE_PORT | 6422 | Exposed port on host for MinIO console | -| POSTGRESQL_USERNAME | postgres | PostgreSQL user | -| POSTGRESQL_DATABASE | palmr_db | Database name | -| MAX_FILESIZE | 1073741824 | Max Uploadsize per file. Unit in Bytes | - -> _All these variables can be configured through a .env file in the project root or defined directly in the environment where docker-compose will be executed. The best way to do this is up to you. But be careful to replace correctly if doing directly in the compose instead of providing an environment var._ - -#### 🗂️ Persistent Volumes - -- minio_data: Stores MinIO files -- postgres_data: Stores PostgreSQL data - ---- - -### 💻 Local Execution - -In a localhost environment, there's no mystery. If you don't want to change any service exposure ports, you can simply run: - -```bash -docker compose pull && docker compose up -d -``` - -This will execute all necessary services and give you access to the following URLs (if you haven't changed any ports): - -- **Frontend:** [http://localhost:5487](http://localhost:5487) -- **Backend:** [http://localhost:3333](http://localhost:3333/) -- **MinIO API:** [http://localhost:6421](http://localhost:6421) -- **MinIO Console:** [http://localhost:6422](http://localhost:6422) -- **Postgres Database:** [http://localhost:5432](http://localhost:5432/) (Connection only) - -> _If you have changed any port, simply access the URL with the port you configured._ - ---- - -### 🌐 Production (VPS or other) - -For production environments, whether it's your VPS, Homelab Server, or other, the execution is very similar to the localhost environment, except for some particularities that may occur in some cases. - -We mainly need to pay attention to the following points: - -- Correctly set the `SERVER_IP` env var with our server's IP, otherwise some redirects and queries will fail during App execution. -- Set the `APP_URL` - regardless of whether the frontend is on the same server and with the same IP, it's extremely important to set this environment variable, otherwise sharing links will be generated incorrectly. -- For all environment variables that are `PASSWORD`, it's highly recommended to generate secure passwords and replace them as env vars. -- Lastly, make sure no docker service will conflict with any existing ones in your environment. If there is a conflict, simply change the execution ports via environment var or in the docker compose. - -To generate a .env file with just the `server_ip` configuration, you can run this command: - -```bash -curl -fsSL https://gist.githubusercontent.com/danielalves96/5a68913d70e5e31b68b7331dc17dfa9c/raw | bash -``` - -> execute this command in your server terminal, at same path of docker-compose.yaml. - -Basically, by paying attention to these points, you can quickly execute the project with the same command we used for localhost: - -```bash -docker compose pull && docker compose up -d -``` - -⚠️ This makes sure you're always running the latest beta version of the image, otherwise, Docker might reuse an outdated one from cache. - -At this stage, if you encounter any errors, it's worth reviewing your `docker-compose.yaml` and trying again, paying close attention to the points mentioned above. - -> _First test without using reverse proxies like Caddy, Traefik, etc... if you plan to use them. Access the services via `server_ip:port` after confirming they work, then make the necessary routing configurations as desired._ - -If you haven't changed the execution ports, you'll have access on your server at: - -- **Frontend:** `[server_ip]:5487` -- **Backend:** `[server_ip]:3333` -- **MinIO API:** `[server_ip]:6421` -- **MinIO Console:** `[server_ip]:6422` -- **Postgres Database:** `[server_ip]:5432` (Connection only) - -> _If you've changed any port, simply access the URL with the port you configured._ - -It's worth noting that this is just a quick start and we're not going into details about any of the developed services, but it's recommended for execution in any environment. However, if your focus is on using Palmr. with high availability in mind, it's recommended to use a container orchestrator prepared for this, such as Kubernetes or similar, but we don't cover this type of configuration in our documentation. - ---- - -## 📚 Additional Resources - -- [Docker Documentation](https://docs.docker.com/) -- [Docker Compose Documentation](https://docs.docker.com/compose/) -- [MinIO Documentation](https://min.io/docs/minio/container/index.html) -- [PostgreSQL Documentation](https://www.postgresql.org/docs/) diff --git a/apps/docs/content/docs/2.0.0-beta/manage-users.mdx b/apps/docs/content/docs/2.0.0-beta/manage-users.mdx deleted file mode 100644 index 9f8496b0..00000000 --- a/apps/docs/content/docs/2.0.0-beta/manage-users.mdx +++ /dev/null @@ -1,81 +0,0 @@ ---- -title: 👥 Users Management ---- - -Managing users in **Palmr.** is a straightforward and intuitive process that ensures proper access control and organization within your system. This comprehensive step-by-step guide will walk you through the essential processes of creating, modifying, and managing user accounts within the application. - -## 🔐 Accessing User Management - -To initiate the user management process, you'll need to navigate to the dedicated **User Management** section. This centralized hub provides all the tools necessary for effective user administration: - -1. Locate and click on the **user icon** prominently displayed in the application's header area, where all primary navigation controls are situated. -2. Upon clicking, a comprehensive dropdown menu will appear, presenting various options. From these choices, locate and select **"User Management"** to proceed - -![Menu](/assets/v1/ui/menu.png) - ---- - -### 📊 User Management Dashboard - -Upon selecting **User Management**, the system will automatically direct you to the comprehensive **User Management Dashboard**, your central control panel for all user-related operations. - -- During your initial access to the system, you'll notice that the user list contains only the **default Admin** user account, which serves as the primary administrative account. -- Should you find it necessary to modify the Admin user's information or credentials, please note that these changes must be implemented through the dedicated **Profile Management** section, which provides specialized tools for administrator profile maintenance. -- For security purposes and to maintain system integrity, it's important to understand that the currently logged-in Admin user's details cannot be altered directly from within the **User Management Dashboard**. - -![Users Management Page](/assets/v1/main/users/users-management.png) - ---- - -### 👤 Adding a New User - -1. To initiate the process of adding a new user to the system, locate and click the **"Add User"** button, which is conveniently positioned in the top right corner of your screen for easy access. - -![Add Users Button](/assets/v1/main/users/add-users-btn.png) - -1. Upon clicking, an interactive modal form will be displayed, presenting you with all the necessary fields to input the new user's comprehensive details and access permissions: - -![Add Users Modal](/assets/v1/main/users/add-user-modal.png) - -1. After carefully entering all the required user information and reviewing for accuracy, click the **"Create"** button to finalize the process. ✨ If you need to start over or decide not to proceed, simply click the **"Cancel"** button to terminate the user creation process without saving any changes. -2. Following successful user creation, the new account will be immediately visible in the user list, confirming the completion of the process. 🎉 - -![New Users Table](/assets/v1/main/users/new-user-table.png) - ---- - -### ⚙️ Managing User Actions - -Within the **User List** interface, you'll find a comprehensive **"Actions"** column containing a dropdown menu for each user account, providing access to all available management functions. - -![Add User Actions Dropdown](/assets/v1/main/users/add-user-actions-dropdown.png) - -The following administrative actions are available for your convenience: - -- 📝 **Edit User** – Provides access to a detailed modal form for comprehensive user information updates: -- Modify various user details including their assigned role and permissions within the system. -- Implement security measures such as password changes and access control modifications. - -![Edit User Modal](/assets/v1/main/users/edit-user-modal.png) - -- 🔒 **Deactivate User** – Temporarily suspends user access by marking the account as inactive, effectively preventing any system login attempts while maintaining their account information. -- 🔓 **Activate User** – Restores full system access for previously deactivated users, enabling them to resume normal account activities and authentication. -- ❌ **Delete User** – Executes a permanent removal of the user account from the system, including all associated data and permissions. - ---- - -## ⚠️ Troubleshooting - -In the event of any challenges or issues during the user management process, please refer to the following troubleshooting guidelines: - -### 🚫 User Creation Fails - -- Carefully verify that all required fields (including name, email address, and role assignments) have been completed with accurate information. -- Perform a thorough check to ensure the email address isn't already associated with an existing account in the system. -- Confirm that your system maintains a stable and active connection to the backend services necessary for user management. - -### 🔑 User Cannot Log In - -- First, verify that the user's account status is set to **Active** in the system. -- Double-check that the user is attempting to authenticate with their correct email address and current password combination. -- If authentication issues persist, initiate a password reset procedure to establish new credentials. diff --git a/apps/docs/content/docs/2.0.0-beta/manual-installation.mdx b/apps/docs/content/docs/2.0.0-beta/manual-installation.mdx deleted file mode 100644 index 8bb7cde9..00000000 --- a/apps/docs/content/docs/2.0.0-beta/manual-installation.mdx +++ /dev/null @@ -1,211 +0,0 @@ ---- -title: 📦 Manual Installation ---- - -Manual installation requires more detailed attention and hands-on configuration compared to the streamlined process offered by Docker Compose. While this approach demands additional effort and technical understanding, following our comprehensive step-by-step guide will ensure a clean and successful project execution with full control over each component. - -An important consideration in this manual setup process is that while we'll be handling the frontend and backend deployments directly, we still require Docker or an equivalent third-party service to manage our Postgres database and MinIO object storage infrastructure. For the purposes of this tutorial, we've chosen to utilize Docker with Docker Compose to establish and configure both MinIO and Postgres, as this provides a reliable and well-tested environment. - -To facilitate this setup, we've included a pre-configured Docker Compose file within the `apps/server` directory that handles the initialization and configuration of both MinIO and Postgres - two essential components for the application's core functionality. While this is our recommended approach, you maintain the flexibility to implement alternative solutions that better suit your specific needs or infrastructure requirements. Our decision to leverage Docker Compose for these particular services stems from its ability to significantly streamline the configuration process, especially when dealing with complex third-party services like Postgres and MinIO. - -Should you be interested in exploring alternative deployment methods for hosting MinIO and Postgres without utilizing Docker and Docker Compose, comprehensive information is available through their respective official channels and documentation: - -- MinIO GitHub: [Visit MinIO GitHub](https://github.com/minio/minio) -- Postgres Github: [Visit Postgres GitHub](https://github.com/postgres/postgres) -- MinIO documentation: [View MinIO docs](https://min.io/docs/minio/linux/index.html) -- Postgres documentation: [View Postgres docs](https://www.postgresql.org/docs/) - -With these foundational concepts established, we can now proceed with our detailed, step-by-step installation guide. - ---- - -## ✅ Prerequisites - -Before proceeding with the installation, it's essential to ensure that your development environment is properly configured with all the necessary tools and dependencies. Please verify that you have the following software components installed and properly configured on your system: - -- Docker *(Required only if you plan to use Docker + Docker Compose)* -- Docker Compose *(Required only if you plan to use Docker + Docker Compose)* -- Node.js *(Essential for running JavaScript/TypeScript applications)* -- pnpm *(Our preferred package manager)* -- Git *(For version control and repository management)* - -⚠️ A critical note regarding package management: This repository has been specifically developed and thoroughly tested using the pnpm package manager. While technically possible to use alternative package managers such as `npm`, `yarn`, or `bun`, we strongly advise against this approach. - ---- - -## Running the Application - -### 📥 Clone the Repository - -To begin the installation process, you'll need to obtain a local copy of the codebase. Start by cloning the official repository using the following Git command: - -```bash -git clone https://github.com/kyantech/Palmr.git -``` - -Upon successful cloning, you'll find yourself with a new directory containing the project structure. Within this directory, there's a crucial folder named 'apps' that houses three essential components: docs, server, and web. For the purposes of this installation guide, we'll focus primarily on the server and web directories, which contain our robust backend infrastructure (built with Fastify) and our responsive frontend application (developed using React + Vite), respectively. - ---- - -### ⚙️ Set Up Backend Services - -The next phase involves setting up our backend services. First, navigate to the backend directory where you'll find the Docker Compose configuration file for our essential services - MinIO and Postgres: - -```bash -cd ./apps/server -``` - -Once you're in the correct directory, initiate the services by executing the following command: - -```bash -docker compose up -d -``` - -This command initializes both Postgres and MinIO services in detached mode, allowing them to run seamlessly in the background. While the configuration file at `apps/server/docker-compose.yaml` can be customized to suit specific needs, we strongly recommend maintaining the default configuration for your initial setup. This approach ensures a smooth installation process, and you can always refine the settings once you have a working implementation. - -Now that our essential services are operational through Docker Compose, we can proceed with the core backend setup. During this phase, we'll be preparing the application for production deployment rather than development mode. This process requires careful attention to both the backend and frontend components. - -Since our current working directory is already set to the server folder, let's begin with the backend configuration. - -#### 🔑 Set Up Environment Variables - -A crucial preliminary step is configuring the environment variables that Palmr requires for proper operation. We've provided a template file named `.env.example` in the repository to streamline this process. - -Execute this straightforward command to create your environment configuration: - -```bash -cp .env.example .env -``` - -This operation creates a new `.env` file in the root directory, populated with all the necessary environmental configurations. - -#### 📦 Install Dependencies - -The next crucial step involves initializing our database connection through Prisma, our chosen Object-Relational Mapping (ORM) tool. However, before we can utilize Prisma effectively, we need to ensure all backend dependencies are properly installed. With your Node.js environment and pnpm package manager ready, execute: - -```bash -pnpm install -``` - -#### ⚡ Generate Prisma Client - -After successfully installing all dependencies, proceed with generating the Prisma client by running: - -```bash -pnpm dlx prisma generate -``` - -This essential command generates the Prisma client specifically tailored for our project, establishing the necessary interface for seamless database interactions and operations. - -#### 🔄 Deploy Prisma Migrations - -With the client generation complete, deploy the database schema using: - -```bash -pnpm dlx prisma migrate deploy -``` - -This command ensures all your database migrations are properly implemented, establishing the required database structure. - -#### 🌱 Seed the Database - -Following the successful migration deployment, we'll populate the database with initial data using our seeding script. Execute: - -```bash -pnpm db:seed -``` - -This process will populate your database with the necessary initial data, preparing it for application use. - -#### 🏗️ Build and Run the Backend - -With all preparatory steps completed, we can now build the backend application: - -```bash -pnpm run build -``` - -Once the build process successfully concludes, start the backend service: - -```bash -pnpm start -``` - -To verify that your backend is functioning correctly, you can access the comprehensive API documentation at: - -```bash -http://localhost:3333/docs -``` - -This documentation interface provides detailed information about all available API endpoints and their usage. - ---- - -### 🎨 Set Up Frontend - -The frontend configuration process follows a similar pattern to the backend setup, though it's somewhat simplified as it doesn't require Docker container management - we'll only need to configure and run the service itself. - -#### 📂 Navigate to the Frontend Directory - -If your current location is the server directory, use: - -```bash -cd ../web -``` - -Alternatively, if you're starting from the repository root, navigate with: - -```bash -cd apps/web -``` - -#### ⚙️ Set Up Environment Variables - -After reaching the web directory, begin by creating your frontend environment configuration: - -```bash -cp .env.example .env -``` - -This step mirrors our backend environment setup, ensuring all necessary variables are properly configured. - -#### 📦 Install Dependencies - -Proceed with installing all required frontend dependencies: - -```bash -pnpm install -``` - -#### 💻 Build and Run the Frontend - -The final stage of our frontend setup is straightforward. First, create a production build: - -```bash -pnpm run build -``` - -After the build completes successfully, launch the frontend service: - -```bash -pnpm serve -``` - -Once the service initialization is complete, you can access the full application through your web browser at: - -```bash -http://localhost:3000 -``` - ---- - -## 🎉 Conclusion - -Congratulations! You've successfully completed the comprehensive setup process for deploying a production-ready instance of Palmr. This detailed guide has walked you through each crucial step, from initial repository cloning to final application deployment. - -## 🔗 Useful Links - -- [Docker Documentation](https://docs.docker.com/) -- [Node.js Documentation](https://nodejs.org/en/docs/) -- [pnpm Documentation](https://pnpm.io/) -- [Prisma Documentation](https://www.prisma.io/docs/) diff --git a/apps/docs/content/docs/2.0.0-beta/meta.json b/apps/docs/content/docs/2.0.0-beta/meta.json deleted file mode 100644 index d8461e63..00000000 --- a/apps/docs/content/docs/2.0.0-beta/meta.json +++ /dev/null @@ -1,29 +0,0 @@ -{ - "description": "(Deprecated)", - "icon": "Trash2", - "pages": [ - "---Introduction---", - "index", - "architecture", - "github-architecture", - "installation", - "manual-installation", - "api", - "s3-providers", - "---How to use Palmr.---", - "manage-users", - "uploading-files", - "generate-share", - "configuring-smtp", - "available-languages", - "---Developers---", - "contribute", - "open-an-issue", - "---Sponsor this project---", - "gh-star", - "gh-sponsor", - "..." - ], - "root": true, - "title": "v2.0.0-beta" -} diff --git a/apps/docs/content/docs/2.0.0-beta/open-an-issue.mdx b/apps/docs/content/docs/2.0.0-beta/open-an-issue.mdx deleted file mode 100644 index 295d7d3c..00000000 --- a/apps/docs/content/docs/2.0.0-beta/open-an-issue.mdx +++ /dev/null @@ -1,141 +0,0 @@ ---- -title: 🎫 How to open an issue ---- - -## 👋 Introduction - -Opening an issue on GitHub is a great way to report bugs, request features, or ask questions about a project. This tutorial will guide you through the process of opening an issue for the **Palmr.** project on GitHub. - -Issues are an essential communication tool in open source development that help track bugs, feature requests, and general questions. They create a transparent record of project discussions and improvements. Whether you've found a bug that needs fixing, have an idea for a new feature, or just need clarification about how something works, creating an issue is the first step to getting your voice heard. - ---- - -### 🔑 GitHub Login - -Before you can open an issue, you need to be logged into your GitHub account. If you don't have an account yet, you can sign up for free at [GitHub](https://github.com/). Having a GitHub account allows you to: - -- Create and manage issues -- Comment on existing issues -- Receive notifications about updates -- Collaborate with other developers - -Once you have an account, make sure you're logged in before proceeding to the next steps. - ---- - -### 🔍 Access the Repository - -There are several ways to access the Palmr repository: - -1. **Direct Link**: - Go to the Palmr repository by clicking this link: [https://github.com/kyantech/Palmr](https://github.com/kyantech/Palmr) - -2. **GitHub Search**: - - Click the search bar at the top of GitHub - - Type "Palmr" or "kyantech/Palmr" - - Look for the repository owned by **Kyantech** - - Click on the repository name to access it - -3. **Through Organization**: - - Visit [Kyantech's GitHub profile](https://github.com/kyantech) - - Navigate to the "Repositories" tab - - Find and click on "Palmr" in the repository list - ---- - -### 📋 Open the Issues Tab - -To access the issues section: - -1. Look at the navigation bar near the top of the repository page -2. Find the **Issues** tab - it's usually between "Code" and "Pull requests" -3. Click on the **Issues** tab to open the issues section -4. You'll see a list of all existing issues, both open and closed - -The issues tab shows important information like: - -- Number of open issues -- Issue labels and categories -- Issue status (open/closed) -- Recent activity -- Assigned contributors - -![Palmr Profile Menu](/assets/v1/developers/issues-tab.png) - ---- - -### ➕ Create New Issue - -To start creating a new issue: - -1. Look for the green **New Issue** button on the right side of the issues page -2. Click the button to open the issue creation form -3. If there are multiple issue templates available, choose the most appropriate one for your needs -4. Take time to read through the template requirements carefully -5. Make sure you have all necessary information ready before starting - -Pro Tips: - -- Before creating a new issue, search existing issues to avoid duplicates -- Review any contribution guidelines or issue templates -- Consider adding relevant labels when creating your issue -- Include system information if reporting a bug -- Reference related issues or pull requests if applicable -- Use markdown formatting to make your issue more readable - -![Palmr Profile Menu](/assets/v1/developers/new-issue-btn.png) - ---- - -### 📝 Fill Out the Form - -You’ll now see a form where you can provide details about your issue. Here’s how to fill it out: - -1. **Title**: Write a clear and concise title that summarizes the issue. -2. **Description**: Provide a detailed description of the issue. Include steps to reproduce the problem (if it’s a bug), expected behavior, and actual behavior. If you’re requesting a feature, explain why it would be useful. -3. **Labels (Optional)**: Add labels to categorize your issue (e.g., `bug`, `enhancement`, `question`). This helps the maintainers organize and prioritize issues. -4. **Attachments (Optional)**: You can attach screenshots, logs, or other files to help explain the issue. - -![Palmr Profile Menu](/assets/v1/developers/new-issue-form.png) - ---- - -### ✅ Submit the Issue - -Once you've filled out the form, click the **Create** button at the bottom of the page. Your issue will now be visible to the project maintainers and other contributors. You can track the status of your issue and receive notifications when there are updates or responses. Feel free to participate in any follow-up discussions in the comments section of your issue. - ---- - -### 💡 Tips for Issues - -To ensure your issue is addressed quickly and effectively, follow these tips: - -- **Be Clear and Specific**: Provide as much detail as possible. -- **Use a Descriptive Title**: A good title helps maintainers understand the issue at a glance. -- **Include Steps to Reproduce**: If it’s a bug, explain how to reproduce it. -- **Be Polite and Respectful**: Remember that maintainers and contributors are volunteering their time. - ---- - -### ⭐ Why Issues Matter - -Opening issues is a key part of contributing to open-source projects. Here’s why it matters: - -1. **Improves the Project**: Your feedback helps identify bugs and suggest new features. -2. **Helps Maintainers**: Clear and detailed issues make it easier for maintainers to address problems. -3. **Encourages Collaboration**: Issues can spark discussions and attract contributors to help solve problems. - ---- - -### 🎉 Final Words - -Congratulations on creating your first issue for the **Palmr** project! Your contribution is valuable and helps make the project better for everyone. Remember: - -- Stay engaged with your issue's progress -- Help others when you can -- Share your knowledge and experiences -- Consider contributing code if possible -- Star the repository to show support -- Spread the word about Palmr - -Thank you for being part of our open-source community. Your participation helps make Palmr better for everyone! diff --git a/apps/docs/content/docs/2.0.0-beta/uploading-files.mdx b/apps/docs/content/docs/2.0.0-beta/uploading-files.mdx deleted file mode 100644 index 03bd4b31..00000000 --- a/apps/docs/content/docs/2.0.0-beta/uploading-files.mdx +++ /dev/null @@ -1,90 +0,0 @@ ---- -title: 📤 Uploading Files ---- - -To upload a file in Palmr, the process has been designed to be straightforward and user-friendly. The platform offers two convenient locations where users can upload their files, ensuring flexibility and ease of access. Each location has been optimized to provide a seamless upload experience. - -Before we delve into the specifics of file uploading, it's essential to understand that file sharing stands at the heart of Palmr's functionality. This core feature enables users to collaborate and distribute content efficiently. To initiate this collaborative process, you'll need to first upload one or more files to your account. Once your files are uploaded, you can proceed to create a sharing session, which serves as a container that can encompass either a single file or multiple files, depending on your specific needs. - -Now let's explore the file upload process in detail. As previously mentioned, the procedure has been streamlined for maximum efficiency, and you can initiate file uploads from two distinct locations: **the Home Page** or **the My Files Page**. We will thoroughly examine both options in the following sections, though it's worth noting that the underlying upload mechanism remains consistent regardless of which location you choose. - ---- - -## 🏠 Home Page - -On the home page, you'll find a dedicated "Recent Uploads" section. When you're using Palmr for the first time and haven't uploaded any files yet, this section will appear in its initial state: - -![Recent Uploads Section](/assets/v1/main/upload/recent-uploads.png) - -To begin the upload process, locate and click the "Upload File" button. This action will trigger a modal window where you can browse and select the desired file from your device. For enhanced user experience, certain file formats including images, audio files, and video content will automatically generate a preview within the modal. - -![Upload File Button](/assets/v1/main/upload/upload-file-button.png) - -**Example with an image:** - -![Preview Example](/assets/v1/main/upload/preview-example.png) - -Upon selecting your file, you'll be presented with two options: confirm the upload by clicking the "Upload" button, or if you need to make changes, you can abort the process by selecting the "Cancel" button. - -![Upload and Cancel Buttons](/assets/v1/main/upload/upload-cancel-buttons.png) - -After successfully uploading one or more files, the "Recent Uploads" section will automatically refresh to display your newly added content: - -![Recent Uploads with Files](/assets/v1/main/upload/recent-uploads-filled.png) - -Should you wish to upload additional files from this view, simply click the "Upload File" button positioned in the upper right corner of the section, then follow the same straightforward procedure outlined above. - -![New Upload Button](/assets/v1/main/upload/new-upload-button.png) - -It's important to note that the home page list displays only your last 5 uploads for quick access. For a comprehensive view of your uploaded files or to upload additional content, you'll need to navigate to the "My Files" page. This can be accomplished in two ways: either click the "View All" button located in the upper right corner of the section, or select the "My Files" card directly from the home page. - -![View All Button](/assets/v1/main/upload/view-all-button.png) - -Or: - -![My Files Card](/assets/v1/main/files/my-files-card.png) - ---- - -## 📂 My Files Page - -Upon accessing the **"My Files"** page, you'll be presented with this comprehensive layout: - -This interface provides enhanced functionality, allowing you to **filter** through your uploaded files for better organization. You can also continue uploading new files by clicking the **"Upload File"** button and following the previously described upload procedure. - -For clarity, it's worth mentioning that the tables found in both the **"Recent Files"** section and the **"My Files"** page share the same structure and organization — the primary distinction lies in the quantity of files displayed. While the **"Recent Files"** section provides quick access to your five most recent uploads, the **"My Files"** table presents a comprehensive view of your entire upload history. - -The table provides detailed information through the following fields: - -- **Name** -- **Description** -- **Size** -- **Created At** -- **Updated At** -- **Actions** - ---- - -### ⚙️ Actions Column - -Within the **"Actions"** column, you'll discover an interactive icon that reveals the following dropdown menu: - -![Actions Dropdown](/assets/v1/main/files/actions-dropdown.png) - -While most options are self-explanatory, let's examine the Edit functionality in detail: - -- Edit – Opens a modal where you can modify various file attributes including the file name, description, and other relevant details. - - ![Edit Modal](/assets/v1/main/files/edit-modal.png) - -- The platform also provides the ability to **delete** files directly through the dropdown menu by selecting the **Delete** option. - -For enhanced user experience, the preview functionality is available for common media formats including images, audio files, PDFs, and videos. For all other file types, you can easily access the content through the **Download** option in the dropdown menu. - ---- - -## 📝 Notes and Recommendations - -- To ensure optimal performance and user experience, we strongly recommend uploading files that have been properly optimized. -- For troubleshooting purposes, all upload errors and related issues are systematically logged and can be accessed through the administrative panel. -- System administrators have full control over file access permissions and can manage them as needed. diff --git a/apps/docs/content/docs/3.2-beta/cleanup-orphan-files.mdx b/apps/docs/content/docs/3.2-beta/cleanup-orphan-files.mdx deleted file mode 100644 index b3202d5d..00000000 --- a/apps/docs/content/docs/3.2-beta/cleanup-orphan-files.mdx +++ /dev/null @@ -1,374 +0,0 @@ ---- -title: Cleanup Orphan Files -icon: Trash2 ---- - -This guide provides detailed instructions on how to identify and remove orphan file records from your Palmr database. Orphan files are database entries that reference files that no longer exist in the storage system, typically resulting from failed uploads or interrupted transfers. - -## When and why to use this tool - -The orphan file cleanup script is designed to maintain database integrity by removing stale file records. Consider using this tool if: - -- Users are experiencing "File not found" errors when attempting to download files that appear in the UI -- You've identified failed uploads that left incomplete database records -- You're performing routine database maintenance -- You've migrated storage systems and need to verify file consistency -- You need to free up quota space occupied by phantom file records - -> **Note:** This script only removes **database records** for files that don't exist in storage. It does not delete physical files. Files that exist in storage will remain untouched. - -## How the cleanup works - -Palmr provides a maintenance script that scans all file records in the database and verifies their existence in the storage system (either filesystem or S3). The script operates in two modes: - -- **Dry-run mode (default):** Identifies orphan files and displays what would be deleted without making any changes -- **Confirmation mode:** Actually removes the orphan database records after explicit confirmation - -The script maintains safety by: -- Checking file existence before marking as orphan -- Providing detailed statistics and file listings -- Requiring explicit `--confirm` flag to delete records -- Working with both filesystem and S3 storage providers -- Preserving all files that exist in storage - -## Understanding orphan files - -### What are orphan files? - -Orphan files occur when: - -1. **Failed chunked uploads:** A large file upload starts, creates a database record, but the upload fails before completion -2. **Interrupted transfers:** Network issues or server restarts interrupt file transfers mid-process -3. **Manual deletions:** Files are manually deleted from storage without removing the database record -4. **Storage migrations:** Files are moved or lost during storage system changes - -### Why they cause problems - -When orphan records exist in the database: -- Users see files in the UI that cannot be downloaded -- Download attempts result in "ENOENT: no such file or directory" errors -- Storage quota calculations become inaccurate -- The system returns 500 errors instead of proper 404 responses (in older versions) - -### Renamed files with suffixes - -Files with duplicate names are automatically renamed with suffixes (e.g., `file (1).png`, `file (2).png`). Sometimes the upload fails after the database record is created but before the physical file is saved, creating an orphan record with a suffix. - -**Example:** -``` -Database record: photo (1).png → objectName: user123/1758805195682-Rjn9at692HdR.png -Physical file: Does not exist ❌ -``` - -## Step-by-step instructions - -### 1. Access the server environment - -**For Docker installations:** - -```bash -docker exec -it /bin/sh -cd /app/palmr-app -``` - -**For bare-metal installations:** - -```bash -cd /path/to/palmr/apps/server -``` - -### 2. Run the cleanup script in dry-run mode - -First, run the script without the `--confirm` flag to see what would be deleted: - -```bash -pnpm cleanup:orphan-files -``` - -This will: -- Scan all file records in the database -- Check if each file exists in storage -- Display a summary of orphan files -- Show what would be deleted (without actually deleting) - -### 3. Review the output - -The script will provide detailed information about orphan files: - -```text -Starting orphan file cleanup... -Storage mode: Filesystem -Found 7 files in database -❌ Orphan: photo(1).png (cmddjchw80000gmiimqnxga2g/1758805195682-Rjn9at692HdR.png) -❌ Orphan: document.pdf (cmddjchw80000gmiimqnxga2g/1758803757558-JQxlvF816UVo.pdf) - -📊 Summary: - Total files in DB: 7 - ✅ Files with storage: 5 - ❌ Orphan files: 2 - -🗑️ Orphan files to be deleted: - - photo(1).png (0.76 MB) - cmddjchw80000gmiimqnxga2g/1758805195682-Rjn9at692HdR.png - - document.pdf (2.45 MB) - cmddjchw80000gmiimqnxga2g/1758803757558-JQxlvF816UVo.pdf - -⚠️ Dry run mode. To actually delete orphan records, run with --confirm flag: - pnpm cleanup:orphan-files:confirm -``` - -### 4. Confirm and execute the cleanup - -If you're satisfied with the results and want to proceed with the deletion: - -```bash -pnpm cleanup:orphan-files:confirm -``` - -This will remove the orphan database records and display a confirmation: - -```text -🗑️ Deleting orphan file records... - ✓ Deleted: photo(1).png - ✓ Deleted: document.pdf - -✅ Cleanup complete! - Deleted 2 orphan file records -``` - -## Example session - -Below is a complete example of running the cleanup script: - -```bash -$ pnpm cleanup:orphan-files - -> palmr-api@3.2.3-beta cleanup:orphan-files -> tsx src/scripts/cleanup-orphan-files.ts - -Starting orphan file cleanup... -Storage mode: Filesystem -Found 15 files in database -❌ Orphan: video.mp4 (user123/1758803869037-1WhtnrQioeFQ.mp4) -❌ Orphan: image(1).png (user123/1758805195682-Rjn9at692HdR.png) -❌ Orphan: image(2).png (user123/1758803757558-JQxlvF816UVo.png) - -📊 Summary: - Total files in DB: 15 - ✅ Files with storage: 12 - ❌ Orphan files: 3 - -🗑️ Orphan files to be deleted: - - video.mp4 (97.09 MB) - user123/1758803869037-1WhtnrQioeFQ.mp4 - - image(1).png (0.01 MB) - user123/1758805195682-Rjn9at692HdR.png - - image(2).png (0.76 MB) - user123/1758803757558-JQxlvF816UVo.png - -⚠️ Dry run mode. To actually delete orphan records, run with --confirm flag: - pnpm cleanup:orphan-files:confirm - -$ pnpm cleanup:orphan-files:confirm - -> palmr-api@3.2.3-beta cleanup:orphan-files:confirm -> tsx src/scripts/cleanup-orphan-files.ts --confirm - -Starting orphan file cleanup... -Storage mode: Filesystem -Found 15 files in database -❌ Orphan: video.mp4 (user123/1758803869037-1WhtnrQioeFQ.mp4) -❌ Orphan: image(1).png (user123/1758805195682-Rjn9at692HdR.png) -❌ Orphan: image(2).png (user123/1758803757558-JQxlvF816UVo.png) - -📊 Summary: - Total files in DB: 15 - ✅ Files with storage: 12 - ❌ Orphan files: 3 - -🗑️ Orphan files to be deleted: - - video.mp4 (97.09 MB) - user123/1758803869037-1WhtnrQioeFQ.mp4 - - image(1).png (0.01 MB) - user123/1758805195682-Rjn9at692HdR.png - - image(2).png (0.76 MB) - user123/1758803757558-JQxlvF816UVo.png - -🗑️ Deleting orphan file records... - ✓ Deleted: video.mp4 - ✓ Deleted: image(1).png - ✓ Deleted: image(2).png - -✅ Cleanup complete! - Deleted 3 orphan file records - -Script completed successfully -``` - -## Troubleshooting common issues - -### No orphan files found - -```text -📊 Summary: - Total files in DB: 10 - ✅ Files with storage: 10 - ❌ Orphan files: 0 - -✨ No orphan files found! -``` - -**This is good!** It means your database is in sync with your storage system. - -### Script cannot connect to database - -If you see database connection errors: - -1. Verify the database file exists: - ```bash - ls -la prisma/palmr.db - ``` - -2. Check database permissions: - ```bash - chmod 644 prisma/palmr.db - ``` - -3. Ensure you're in the correct directory: - ```bash - pwd # Should show .../palmr/apps/server - ``` - -### Storage provider errors - -For **S3 storage:** -- Verify your S3 credentials are configured correctly -- Check that the bucket is accessible -- Ensure network connectivity to S3 - -For **Filesystem storage:** -- Verify the uploads directory exists and is readable -- Check file system permissions -- Ensure sufficient disk space - -### Script fails to delete records - -If deletion fails for specific files: -- Check database locks (close other connections) -- Verify you have write permissions to the database -- Review the error message for specific details - -## Understanding the output - -### File statistics - -The script provides several key metrics: - -- **Total files in DB:** All file records in your database -- **Files with storage:** Records where the physical file exists -- **Orphan files:** Records where the physical file is missing - -### File information - -For each orphan file, you'll see: - -- **Name:** Display name in the UI -- **Size:** File size as recorded in the database -- **Object name:** Internal storage path - -Example: `photo(1).png (0.76 MB) - user123/1758805195682-Rjn9at692HdR.png` - -## Prevention and best practices - -### Prevent orphan files from occurring - -1. **Monitor upload failures:** Check server logs for upload errors -2. **Stable network:** Ensure reliable network connectivity for large uploads -3. **Adequate resources:** Provide sufficient disk space and memory -4. **Regular maintenance:** Run this script periodically as part of maintenance - -### When to run cleanup - -Consider running the cleanup script: - -- **Monthly:** As part of routine database maintenance -- **After incidents:** Following server crashes or storage issues -- **Before migrations:** Before moving to new storage systems -- **When users report errors:** If download failures are reported - -### Safe cleanup practices - -1. **Always run dry-run first:** Review what will be deleted before confirming -2. **Backup your database:** Create a backup before running with `--confirm` -3. **Check during low usage:** Run during off-peak hours to minimize disruption -4. **Document the cleanup:** Keep records of when and why cleanup was performed -5. **Verify after cleanup:** Check that file counts match expectations - -## Technical details - -### How files are stored - -When files are uploaded to Palmr: - -1. Frontend generates a safe object name using random identifiers -2. Backend creates the final `objectName` as: `${userId}/${timestamp}-${randomId}.${extension}` -3. If a duplicate name exists, the **display name** gets a suffix, but `objectName` remains unique -4. Physical file is stored using `objectName`, display name is stored separately in database - -### Storage providers - -The script works with both storage providers: - -- **FilesystemStorageProvider:** Uses `fs.promises.access()` to check file existence -- **S3StorageProvider:** Uses `HeadObjectCommand` to verify objects in S3 bucket - -### Database schema - -Files table structure: -```typescript -{ - name: string // Display name (can have suffixes like "file (1).png") - objectName: string // Physical storage path (always unique) - size: bigint // File size in bytes - extension: string // File extension - userId: string // Owner of the file - folderId: string? // Parent folder (null for root) -} -``` - -## Related improvements - -### Download validation (v3.2.3-beta+) - -Starting from version 3.2.3-beta, Palmr includes enhanced download validation: - -- Files are checked for existence **before** attempting download -- Returns proper 404 error if file is missing (instead of 500) -- Provides helpful error message to users - -This prevents errors when trying to download orphan files that haven't been cleaned up yet. - -## Security considerations - -- **Read-only by default:** Dry-run mode is safe and doesn't modify data -- **Explicit confirmation:** Requires `--confirm` flag to delete records -- **No file deletion:** Only removes database records, never deletes physical files -- **Audit trail:** All actions are logged to console -- **Permission-based:** Only users with server access can run the script - -> **Important:** This script does not delete physical files from storage. It only removes database records for files that don't exist. This is intentional to prevent accidental data loss. - -## FAQ - -**Q: Will this delete my files?** -A: No. The script only removes database records for files that are already missing from storage. Physical files are never deleted. - -**Q: Can I undo the cleanup?** -A: No. Once orphan records are deleted, they cannot be recovered. Always run dry-run mode first and backup your database. - -**Q: Why do orphan files have suffixes like (1), (2)?** -A: When duplicate files are uploaded, Palmr renames them with suffixes. If the upload fails after creating the database record, an orphan with a suffix remains. - -**Q: How often should I run this script?** -A: Monthly maintenance is usually sufficient. Run more frequently if you experience many upload failures. - -**Q: Does this work with S3 storage?** -A: Yes! The script automatically detects your storage provider (filesystem or S3) and works with both. - -**Q: What if I have thousands of orphan files?** -A: The script handles large numbers efficiently. Consider running during off-peak hours for very large cleanups. - -**Q: Can this fix "File not found" errors?** -A: Yes, if the errors are caused by orphan database records. The script removes those records, preventing future errors. diff --git a/apps/docs/content/docs/3.2-beta/download-memory-management.mdx b/apps/docs/content/docs/3.2-beta/download-memory-management.mdx deleted file mode 100644 index cfc4eb3b..00000000 --- a/apps/docs/content/docs/3.2-beta/download-memory-management.mdx +++ /dev/null @@ -1,391 +0,0 @@ ---- -title: Memory Management -icon: Download ---- - -import { Callout } from "fumadocs-ui/components/callout"; -import { Tab, Tabs } from "fumadocs-ui/components/tabs"; - -Palmr implements an intelligent memory management system that prevents crashes during large file downloads (3GB+ by default), maintaining unlimited download capacity through adaptive resource control and an automatic queue system. - -## How It Works - -### Automatic Resource Detection - -The system automatically detects available container/system memory and configures appropriate limits based on available infrastructure: - -```typescript -const totalMemoryGB = require("os").totalmem() / 1024 ** 3; -``` - -### System Configuration - -The system supports two configuration approaches that you can choose based on your needs: - - - - Manually configure all parameters for total control over the system: - - ```bash - # Custom configuration (overrides auto-scaling) - DOWNLOAD_MAX_CONCURRENT=8 # Maximum simultaneous downloads - DOWNLOAD_MEMORY_THRESHOLD_MB=1536 # Memory threshold in MB - DOWNLOAD_QUEUE_SIZE=40 # Maximum queue size - DOWNLOAD_AUTO_SCALE=false # Disable auto-scaling - ``` - - - Manual configuration offers total control and predictability for specific environments where you know exactly the available resources. - - - - - Automatic configuration based on detected system memory: - - | Available Memory | Concurrent Downloads | Memory Threshold | Queue Size | Recommended Use | - |------------------|----------------------|-------------------|------------|--------------------| - | ≤ 2GB | 1 | 256MB | 5 | Development | - | 2GB - 4GB | 2 | 512MB | 10 | Small Environment | - | 4GB - 8GB | 3 | 1GB | 15 | Standard Production| - | 8GB - 16GB | 5 | 2GB | 25 | High Performance | - | > 16GB | 10 | 4GB | 50 | Enterprise | - - - Auto-scaling automatically adapts to different environments without manual configuration, perfect for flexible deployment. - - - - - -If environment variables are configured, they take **priority** over auto-scaling. - -## Download Queue System - -### How It Works - -The memory management system only activates for files larger than the configured minimum size (3GB by default). Smaller files bypass the queue system entirely and download immediately without memory management. - -When a user requests a download for a large file but all slots are occupied, the system automatically queues the download instead of returning a 429 error. The queue processes downloads in FIFO order (first in, first out). - -### Practical Example - -Consider a system with 8GB RAM (5 concurrent downloads, queue of 25, 3GB minimum) where users want to download files of various sizes: - -```bash -# Small files (< 3GB): Bypass queue entirely -[DOWNLOAD MANAGER] File document.pdf (0.05GB) below threshold (3.0GB), bypassing queue - -# Large files 1-5: Start immediately -[DOWNLOAD MANAGER] Immediate start: 1734567890-abc123def -[DOWNLOAD MANAGER] Starting video1.mp4 (5.2GB) - -# Large files 6-10: Automatically queued -[DOWNLOAD MANAGER] Queued: 1734567891-def456ghi (Position: 1/25) -[DOWNLOAD MANAGER] Queued file: video2.mp4 (8.1GB) - -# When download 1 finishes: download 6 starts automatically -[DOWNLOAD MANAGER] Processing queue: 1734567891-def456ghi (4 remaining) -[DOWNLOAD MANAGER] Starting queued file: video2.mp4 (8.1GB) -``` - -### System Benefits - -**User Experience** - -- Users don't receive errors, they simply wait in queue -- Downloads start automatically when slots become available -- Transparent operation without client changes -- Fair processing order with FIFO queue - -**Technical Features** - -- Limited buffers (64KB per stream) for controlled memory usage -- Automatic backpressure control with pipeline streams -- Adaptive memory throttling based on usage patterns -- Forced garbage collection after large downloads -- Smart timeout handling (30 minutes for queued downloads) -- Automatic cleanup of orphaned downloads every 30 seconds - -## Container Compatibility - -The system works with Docker, Kubernetes, and any containerized environment: - - - - - ```bash - # Example: Container with 8GB - docker run -m 8g palmr/server - # Result: 5 concurrent downloads, queue of 25, threshold 2GB - ``` - - - - - ```yaml - apiVersion: apps/v1 - kind: Deployment - spec: - template: - spec: - containers: - - name: palmr-server - resources: - limits: - memory: "4Gi" # Detects 4GB - cpu: "2" - requests: - memory: "2Gi" - cpu: "1" - # Result: 3 concurrent downloads, queue of 15, threshold 1GB - ``` - - - - - ```yaml - services: - palmr-server: - image: palmr/server - deploy: - resources: - limits: - memory: 16G # Detects 16GB - # Result: 10 concurrent downloads, queue of 50, threshold 4GB - ``` - - - - -## Configuration - -### Environment Variables - -Configure the download memory management system using these environment variables: - -| Variable | Default | Description | -| ------------------------------ | ---------- | ----------------------------------------------------- | -| `DOWNLOAD_MAX_CONCURRENT` | auto-scale | Maximum number of simultaneous downloads | -| `DOWNLOAD_MEMORY_THRESHOLD_MB` | auto-scale | Memory limit in MB before throttling | -| `DOWNLOAD_QUEUE_SIZE` | auto-scale | Maximum download queue size | -| `DOWNLOAD_AUTO_SCALE` | `true` | Enable/disable auto-scaling based on system memory | -| `DOWNLOAD_MIN_FILE_SIZE_GB` | `3.0` | Minimum file size in GB to activate memory management | - -### Configuration Examples by Scenario - - - - Configuration optimized for personal use or small groups (4GB RAM): - - ```bash - DOWNLOAD_MAX_CONCURRENT=2 - DOWNLOAD_MEMORY_THRESHOLD_MB=1024 - DOWNLOAD_QUEUE_SIZE=8 - DOWNLOAD_MIN_FILE_SIZE_GB=2.0 - DOWNLOAD_AUTO_SCALE=false - ``` - - - - Configuration for corporate environments with multiple users (16GB RAM): - - ```bash - DOWNLOAD_MAX_CONCURRENT=12 - DOWNLOAD_MEMORY_THRESHOLD_MB=4096 - DOWNLOAD_QUEUE_SIZE=60 - DOWNLOAD_MIN_FILE_SIZE_GB=5.0 - DOWNLOAD_AUTO_SCALE=false - ``` - - - - Configuration for maximum performance and throughput (32GB RAM): - - ```bash - DOWNLOAD_MAX_CONCURRENT=20 - DOWNLOAD_MEMORY_THRESHOLD_MB=8192 - DOWNLOAD_QUEUE_SIZE=100 - DOWNLOAD_MIN_FILE_SIZE_GB=10.0 - DOWNLOAD_AUTO_SCALE=false - ``` - - - - For environments with limited or shared resources: - - ```bash - DOWNLOAD_MAX_CONCURRENT=3 - DOWNLOAD_MEMORY_THRESHOLD_MB=1024 - DOWNLOAD_QUEUE_SIZE=15 - DOWNLOAD_MIN_FILE_SIZE_GB=1.0 - DOWNLOAD_AUTO_SCALE=false - ``` - - - - -### Additional Configuration - -For optimal performance with large downloads, consider these additional settings: - -```bash -# Force garbage collection (recommended for large downloads) -NODE_OPTIONS="--expose-gc" - -# Adjust timeout for very large downloads -KEEP_ALIVE_TIMEOUT=300000 -REQUEST_TIMEOUT=0 -``` - -## Monitoring and Logs - -### System Logs - -The system provides detailed logs to track operation: - -```bash -[DOWNLOAD MANAGER] System Memory: 8.0GB, Max Concurrent: 5, Memory Threshold: 2048MB, Queue Size: 25 -[DOWNLOAD] Requesting slot for 1734567890-abc123def: video.mp4 (15.2GB) -[DOWNLOAD MANAGER] Queued: 1734567890-abc123def (Position: 3/25) -[DOWNLOAD MANAGER] Processing queue: 1734567890-abc123def (2 remaining) -[DOWNLOAD] Starting 1734567890-abc123def: video.mp4 (15.2GB) -[MEMORY THROTTLE] video.mp4 - Pausing stream due to high memory usage: 1843MB -[DOWNLOAD] Applying throttling: 100ms delay for 1734567890-abc123def -``` - -### Configuration Validation - -The system automatically validates configurations at startup and provides warnings or errors: - -**Warnings** - -- `DOWNLOAD_MAX_CONCURRENT > 50`: May cause performance issues -- `DOWNLOAD_MEMORY_THRESHOLD_MB < 128MB`: Downloads may be throttled frequently -- `DOWNLOAD_MEMORY_THRESHOLD_MB > 16GB`: System may run out of memory -- `DOWNLOAD_QUEUE_SIZE > 1000`: May consume significant memory -- `DOWNLOAD_QUEUE_SIZE < DOWNLOAD_MAX_CONCURRENT`: Queue smaller than concurrent downloads - -**Errors** - -- `DOWNLOAD_MAX_CONCURRENT < 1`: Invalid value -- `DOWNLOAD_QUEUE_SIZE < 1`: Invalid value - -## Queue Management APIs - -The system provides REST APIs to monitor and manage the download queue: - -### Get Queue Status - -```http -GET /api/filesystem/download-queue/status -``` - -**Response:** - -```json -{ - "data": { - "queueLength": 3, - "maxQueueSize": 25, - "activeDownloads": 5, - "maxConcurrent": 5, - "queuedDownloads": [ - { - "downloadId": "1734567890-abc123def", - "position": 1, - "waitTime": 45000, - "fileName": "video.mp4", - "fileSize": 16106127360 - } - ] - }, - "status": "success" -} -``` - -### Cancel Download - -```http -DELETE /api/filesystem/download-queue/{downloadId} -``` - -**Response:** - -```json -{ - "downloadId": "1734567890-abc123def", - "message": "Download cancelled successfully" -} -``` - -### Clear Queue (Admin) - -```http -DELETE /api/filesystem/download-queue -``` - -**Response:** - -```json -{ - "clearedCount": 8, - "message": "Download queue cleared successfully" -} -``` - -## Troubleshooting - -### Common Issues - -**Downloads failing with "Download queue is full"** - -_Cause:_ Too many simultaneous downloads with a full queue - -_Solutions:_ - -- Wait for some downloads to finish -- Check for orphaned downloads in queue -- Consider increasing container resources -- Use API to clear queue if necessary - -**Downloads stay too long in queue** - -_Cause:_ Active downloads are slow or stuck - -_Solutions:_ - -- Check logs for orphaned downloads -- Use API to cancel specific downloads -- Check client network connections -- Monitor memory throttling - -**Very slow downloads** - -_Cause:_ Active throttling due to high memory usage - -_Solutions:_ - -- Check other processes consuming memory -- Consider increasing container resources -- Monitor throttling logs -- Check number of simultaneous downloads - -## Summary - -This system enables unlimited downloads (including 50TB+ files) without compromising system stability through: - -**Key Features** - -- Auto-configuration based on available resources -- Automatic FIFO queue system for pending downloads -- Adaptive control of simultaneous downloads -- Intelligent throttling when needed - -**System Benefits** - -- Management APIs to monitor and control queue -- Automatic cleanup of resources and orphaned downloads -- Full compatibility with Docker/Kubernetes -- Perfect user experience with no 429 errors - -The system maintains high performance for small/medium files while preventing crashes with gigantic files, offering a seamless experience where users never see 429 errors, they simply wait in queue until their download starts automatically. diff --git a/apps/docs/content/docs/3.2-beta/quick-start.mdx b/apps/docs/content/docs/3.2-beta/quick-start.mdx deleted file mode 100644 index 9483af8b..00000000 --- a/apps/docs/content/docs/3.2-beta/quick-start.mdx +++ /dev/null @@ -1,409 +0,0 @@ ---- -title: Quick Start (Docker) -icon: "Rocket" ---- - -import { Callout } from "fumadocs-ui/components/callout"; -import { Tab, Tabs } from "fumadocs-ui/components/tabs"; - -import { Card, CardGrid } from "@/components/ui/card"; - -Welcome to the fastest way to deploy Palmr. - your secure, self-hosted file sharing solution. This guide will have you up and running in minutes, whether you're new to self-hosting or an experienced developer. - -Palmr. offers flexible deployment options to match your infrastructure needs. This guide focuses on Docker deployment with our recommended filesystem storage, perfect for most use cases. - -## Prerequisites - -Before you begin, make sure you have: - -- **Docker** - Container runtime ([installation guide](https://docs.docker.com/get-docker/)) -- **Docker Compose** - Multi-container orchestration ([installation guide](https://docs.docker.com/compose/install/)) -- **2GB+ available disk space** for the application and your files -- **Port 5487** available for the web interface -- **Port 3333** available for API access (optional) - - - **Platform Support**: Palmr. is developed on macOS and extensively tested on Linux servers. While we haven't formally - tested other platforms, Docker's cross-platform nature should ensure compatibility. Report any issues on our [GitHub - repository](https://github.com/kyantech/Palmr/issues). - - -## Storage Options - -Palmr. supports two storage approaches for persistent data: - -- **Named Volumes (Recommended)** - Docker-managed storage with optimal performance and no permission issues -- **Bind Mounts** - Direct host filesystem access, ideal for development and direct file management - -## Deployment Options - -Choose your storage method based on your needs: - - - - Docker-managed storage that provides the best balance of performance, security, and ease of use: - - - **No Permission Issues**: Docker handles all permission management automatically - - **Performance**: Optimized for container workloads with better I/O performance - - **Production Ready**: Recommended for production deployments - - ### Configuration - - Create a `docker-compose.yml` file: - - ```yaml - services: - palmr: - image: kyantech/palmr:latest - container_name: palmr - restart: unless-stopped - ports: - - "5487:5487" # Web interface - # - "3333:3333" # API (optional) - environment: - # Optional: Uncomment and configure as needed (if you don`t use, you can remove) - # - ENABLE_S3=true # Set to true to enable S3-compatible storage - # - DISABLE_FILESYSTEM_ENCRYPTION=true # Set to false to enable file encryption - # - ENCRYPTION_KEY=your-secure-key-min-32-chars # Required only if encryption is enabled - # - PALMR_UID=1000 # UID for the container processes (default is 1000) - # - PALMR_GID=1000 # GID for the container processes (default is 1000) - # - SECURE_SITE=false # Set to true if you are using a reverse proxy - # - DEFAULT_LANGUAGE=en-US # Default language for the application (optional, defaults to en-US) - # - PRESIGNED_URL_EXPIRATION=3600 # Duration in seconds for presigned URL expiration (optional, defaults to 3600 seconds / 1 hour) - # - DOWNLOAD_MAX_CONCURRENT=5 # Maximum simultaneous downloads (auto-scales if not set) - # - DOWNLOAD_MEMORY_THRESHOLD_MB=2048 # Memory threshold in MB before throttling (auto-scales if not set) - # - DOWNLOAD_QUEUE_SIZE=25 # Maximum queue size for pending downloads (auto-scales if not set) - # - DOWNLOAD_MIN_FILE_SIZE_GB=3.0 # Minimum file size in GB to activate memory management (default: 3.0) - # - DOWNLOAD_AUTO_SCALE=true # Enable auto-scaling based on system memory (default: true) - # - NODE_OPTIONS=--expose-gc # Enable garbage collection for large downloads (recommended for production) - # - NEXT_PUBLIC_UPLOAD_CHUNK_SIZE_MB=100 # Chunk size in MB for large file uploads (OPTIONAL - auto-calculates if not set) - volumes: - - palmr_data:/app/server - - volumes: - palmr_data: - ``` - - - **Having upload or permission issues?** Add `PALMR_UID=1000` and `PALMR_GID=1000` to your environment variables. Check our [UID/GID Configuration](/docs/3.2-beta/uid-gid-configuration) guide for more details. - - - ### Deploy - - ```bash - docker-compose up -d - ``` - - - - Direct mapping to host filesystem directories, providing direct file access: - - - **Direct Access**: Files are directly accessible from your host system - - **Development Friendly**: Easy to inspect, modify, or backup files manually - - **Platform Dependent**: May require UID/GID configuration, especially on NAS systems - - ### Configuration - - Create a `docker-compose.yml` file: - - ```yaml - services: - palmr: - image: kyantech/palmr:latest - container_name: palmr - restart: unless-stopped - ports: - - "5487:5487" # Web interface - # - "3333:3333" # API (optional) - environment: - # Optional: Uncomment and configure as needed (if you don`t use, you can remove) - # - ENABLE_S3=true # Set to true to enable S3-compatible storage - # - DISABLE_FILESYSTEM_ENCRYPTION=false # Set to false to enable file encryption - # - ENCRYPTION_KEY=your-secure-key-min-32-chars # Required only if encryption is enabled - # - PALMR_UID=1000 # UID for the container processes (default is 1000) - # - PALMR_GID=1000 # GID for the container processes (default is 1000) - # - SECURE_SITE=false # Set to true if you are using a reverse proxy - # - DEFAULT_LANGUAGE=en-US # Default language for the application (optional, defaults to en-US) - # - PRESIGNED_URL_EXPIRATION=3600 # Duration in seconds for presigned URL expiration (optional, defaults to 3600 seconds / 1 hour) - # - DOWNLOAD_MAX_CONCURRENT=5 # Maximum simultaneous downloads (auto-scales if not set) - # - DOWNLOAD_MEMORY_THRESHOLD_MB=2048 # Memory threshold in MB before throttling (auto-scales if not set) - # - DOWNLOAD_QUEUE_SIZE=25 # Maximum queue size for pending downloads (auto-scales if not set) - # - DOWNLOAD_MIN_FILE_SIZE_GB=3.0 # Minimum file size in GB to activate memory management (default: 3.0) - # - DOWNLOAD_AUTO_SCALE=true # Enable auto-scaling based on system memory (default: true) - # - NODE_OPTIONS=--expose-gc # Enable garbage collection for large downloads (recommended for production) - volumes: - - ./data:/app/server - ``` - - - **Having upload or permission issues?** Add `PALMR_UID=1000` and `PALMR_GID=1000` to your environment variables. Check our [UID/GID Configuration](/docs/3.2-beta/uid-gid-configuration) guide for more details. - - - ### Deploy - - ```bash - docker-compose up -d - ``` - - - - -## Configuration - -Customize Palmr's behavior with these environment variables: - -| Variable | Default | Description | -| ---------------------------------- | -------------- | ------------------------------------------------------------------------------------------------------------------------------------- | -| `ENABLE_S3` | `false` | Enable S3-compatible storage backends | -| `S3_ENDPOINT` | - | S3 server endpoint URL (required when using S3) | -| `S3_PORT` | - | S3 server port (optional when using S3) | -| `S3_USE_SSL` | - | Enable SSL for S3 connections (optional when using S3) | -| `S3_ACCESS_KEY` | - | S3 access key for authentication (required when using S3) | -| `S3_SECRET_KEY` | - | S3 secret key for authentication (required when using S3) | -| `S3_REGION` | - | S3 region configuration (optional when using S3) | -| `S3_BUCKET_NAME` | - | S3 bucket name for file storage (required when using S3) | -| `S3_FORCE_PATH_STYLE` | `false` | Force path-style S3 URLs (optional when using S3) | -| `S3_REJECT_UNAUTHORIZED` | `true` | Enable strict SSL certificate validation for S3 (set to `false` for self-signed certificates) | -| `ENCRYPTION_KEY` | - | **Required when encryption is enabled**: 32+ character key for file encryption | -| `DISABLE_FILESYSTEM_ENCRYPTION` | `true` | Disable file encryption for better performance (set to `false` to enable encryption) | -| `PRESIGNED_URL_EXPIRATION` | `3600` | Duration in seconds for presigned URL expiration (applies to both filesystem and S3 storage) | -| `CUSTOM_PATH` | - | Custom base path for disk space detection in manual installations with symlinks | -| `SECURE_SITE` | `false` | Enable secure cookies for HTTPS/reverse proxy deployments | -| `DEFAULT_LANGUAGE` | `en-US` | Default application language ([see available languages](/docs/3.2-beta/available-languages)) | -| `PALMR_UID` | `1000` | User ID for container processes (helps with file permissions) | -| `PALMR_GID` | `1000` | Group ID for container processes (helps with file permissions) | -| `NODE_OPTIONS` | - | Node.js options (recommended: `--expose-gc` for garbage collection in production) | -| `DOWNLOAD_MAX_CONCURRENT` | auto-scale | Maximum number of simultaneous downloads (see [Download Memory Management](/docs/3.2-beta/download-memory-management)) | -| `DOWNLOAD_MEMORY_THRESHOLD_MB` | auto-scale | Memory threshold in MB before throttling | -| `DOWNLOAD_QUEUE_SIZE` | auto-scale | Maximum queue size for pending downloads | -| `DOWNLOAD_MIN_FILE_SIZE_GB` | `3.0` | Minimum file size in GB to activate memory management | -| `NEXT_PUBLIC_UPLOAD_CHUNK_SIZE_MB` | auto-calculate | Chunk size in MB for large file uploads (see [Chunked Upload Configuration](/docs/3.2-beta/quick-start#chunked-upload-configuration)) | -| `DOWNLOAD_AUTO_SCALE` | `true` | Enable auto-scaling based on system memory | - - - **Performance First**: Palmr runs without encryption by default for optimal speed and lower resource usage—perfect for - most use cases. - - - - **Encryption Notice**: To enable encryption, set `DISABLE_FILESYSTEM_ENCRYPTION=false` and provide a 32+ character - `ENCRYPTION_KEY`. **Important**: This choice is permanent—switching encryption modes after uploading files will break - access to existing uploads. - - - - **Using a Reverse Proxy?** Set `SECURE_SITE=true` and check our [Reverse Proxy - Configuration](/docs/3.2-beta/reverse-proxy-configuration) guide for proper HTTPS setup. - - -### Generate Encryption Keys (Optional) - -Need file encryption? Generate a secure key: - - - -> **Pro Tip**: Only enable encryption if you're handling sensitive data. For most users, the default unencrypted mode provides better performance. - -## Access Your Instance - -Once deployed, open Palmr in your browser: - -- **Web Interface**: `http://localhost:5487` (local) or `http://YOUR_SERVER_IP:5487` (remote) -- **API Documentation**: `http://localhost:3333/docs` (if port 3333 is exposed) - - - **Learn More**: For complete API documentation, authentication, and integration examples, see our [API - Reference](/docs/3.2-beta/api) guide - - - - **Production Ready?** Configure HTTPS with a valid SSL certificate for secure production deployments. - - ---- - -## Docker CLI Alternative - -Prefer Docker commands over Compose? Here are the equivalent commands: - - - - - ```bash - docker run -d \ - --name palmr \ - # Optional: Uncomment and configure as needed (if you don`t use, you can remove) - # -e ENABLE_S3=true \ # Set to true to enable S3-compatible storage (OPTIONAL - default is false) - # -e DISABLE_FILESYSTEM_ENCRYPTION=false \ # Set to false to enable file encryption (ENCRYPTION_KEY becomes required) | (OPTIONAL - default is true) - # -e ENCRYPTION_KEY=your-secure-key-min-32-chars # Required only if encryption is enabled - # -e PALMR_UID=1000 # UID for the container processes (default is 1000) - # -e PALMR_GID=1000 # GID for the container processes (default is 1000) - # -e SECURE_SITE=false # Set to true if you are using a reverse proxy - # -e DEFAULT_LANGUAGE=en-US # Default language for the application (optional, defaults to en-US) - -p 5487:5487 \ - -p 3333:3333 \ - -v palmr_data:/app/server \ - --restart unless-stopped \ - kyantech/palmr:latest - ``` - - - - **Permission Issues?** Add `-e PALMR_UID=1000 -e PALMR_GID=1000` to the command above. See our [UID/GID Configuration](/docs/3.2-beta/uid-gid-configuration) guide for details. - - - - - - - ```bash - docker run -d \ - --name palmr \ - # Optional: Uncomment and configure as needed (if you don`t use, you can remove) - # -e ENABLE_S3=true \ # Set to true to enable S3-compatible storage (OPTIONAL - default is false) - # -e DISABLE_FILESYSTEM_ENCRYPTION=true \ # Set to false to enable file encryption (ENCRYPTION_KEY becomes required) | (OPTIONAL - default is true) - # -e ENCRYPTION_KEY=your-secure-key-min-32-chars # Required only if encryption is enabled - # -e PALMR_UID=1000 # UID for the container processes (default is 1000) - # -e PALMR_GID=1000 # GID for the container processes (default is 1000) - # -e SECURE_SITE=false # Set to true if you are using a reverse proxy - # -e DEFAULT_LANGUAGE=en-US # Default language for the application (optional, defaults to en-US) - -p 5487:5487 \ - -p 3333:3333 \ - -v $(pwd)/data:/app/server \ - --restart unless-stopped \ - kyantech/palmr:latest - ``` - - - **Permission Issues?** Add `-e PALMR_UID=1000 -e PALMR_GID=1000` to the command above. See our [UID/GID Configuration](/docs/3.2-beta/uid-gid-configuration) guide for details. - - - - - ---- - -## Common Configuration Options - -### Presigned URL Expiration - -Palmr. uses temporary URLs (presigned URLs) for secure file access. These URLs expire after a configurable time period to enhance security. - -**Default:** 1 hour (3600 seconds) - -You can customize this for all storage types (filesystem or S3) by adding: - -```yaml -environment: - - PRESIGNED_URL_EXPIRATION=7200 # 2 hours -``` - -**When to adjust:** - -- **Shorter time (1800 = 30 min):** Higher security, but users may need to refresh download links -- **Longer time (7200-21600 = 2-6 hours):** Better for large file transfers, but URLs stay valid longer -- **Default (3600 = 1 hour):** Good balance for most use cases - -### File Encryption - -For filesystem storage, you can enable file encryption: - -```yaml -environment: - - DISABLE_FILESYSTEM_ENCRYPTION=false - - ENCRYPTION_KEY=your-secure-32-character-key-here -``` - -**Note:** S3 storage handles encryption through your S3 provider's encryption features. - -### Chunked Upload Configuration - -Palmr supports configurable chunked uploading for large files. You can customize the chunk size by setting the following environment variable: - -```yaml -environment: - - NEXT_PUBLIC_UPLOAD_CHUNK_SIZE_MB=100 # Chunk size in MB -``` - -**How it works:** - -- If `NEXT_PUBLIC_UPLOAD_CHUNK_SIZE_MB` is set, Palmr will use this value (in megabytes) as the chunk size for all file uploads that exceed this threshold. -- If not set or left empty, Palmr automatically calculates optimal chunk sizes based on file size: - - Files ≤ 100MB: uploaded without chunking - - Files > 100MB and ≤ 1GB: 75MB chunks - - Files > 1GB: 150MB chunks - -**When to configure:** - -- **Default (not set):** Recommended for most use cases. Palmr will intelligently determine the best chunk size. -- **Custom value:** Set this if you have specific network conditions or want to optimize for your infrastructure (e.g., slower connections may benefit from smaller chunks like 50MB, while fast networks can handle larger chunks like 200MB, or the upload size per payload may be limited by a proxy like Cloudflare) - ---- - -## Maintenance - -### Updates - -Keep Palmr up to date with the latest features and security patches: - -```bash -docker-compose pull -docker-compose up -d -``` - -### Backup Your Data - -**Named Volumes:** - -```bash -docker run --rm -v palmr_data:/data -v $(pwd):/backup alpine tar czf /backup/palmr-backup.tar.gz -C /data . -``` - -**Bind Mounts:** - -```bash -tar czf palmr-backup.tar.gz ./data -``` - -### Restore From Backup - -**Named Volumes:** - -```bash -docker-compose down -docker run --rm -v palmr_data:/data -v $(pwd):/backup alpine tar xzf /backup/palmr-backup.tar.gz -C /data -docker-compose up -d -``` - -**Bind Mounts:** - -```bash -docker-compose down -tar xzf palmr-backup.tar.gz -docker-compose up -d -``` - ---- - -## What's Next? - -Your Palmr instance is ready! Here's what you can explore: - -### Advanced Configuration - -- **[UID/GID Configuration](/docs/3.2-beta/uid-gid-configuration)** - Configure user permissions for NAS systems and custom environments -- **[Download Memory Management](/docs/3.2-beta/download-memory-management)** - Configure large file download handling and queue system -- **[S3 Storage](/docs/3.2-beta/s3-configuration)** - Scale with Amazon S3 or compatible storage providers -- **[Manual Installation](/docs/3.2-beta/manual-installation)** - Manual installation and custom configurations - -### Integration & Development - -- **[API Reference](/docs/3.2-beta/api)** - Integrate Palmr. with your applications - - - **Need help?** Check our [Troubleshooting Guide](/docs/3.2-beta/troubleshooting) for common issues and solutions. - - ---- - -**Questions?** Visit our [GitHub Issues](https://github.com/kyantech/Palmr/issues) or join the community discussions. diff --git a/apps/docs/content/docs/3.2-beta/troubleshooting.mdx b/apps/docs/content/docs/3.2-beta/troubleshooting.mdx deleted file mode 100644 index 068d5823..00000000 --- a/apps/docs/content/docs/3.2-beta/troubleshooting.mdx +++ /dev/null @@ -1,381 +0,0 @@ ---- -title: Troubleshooting -icon: "Bug" ---- - -Common issues and solutions for Palmr. deployment and usage. - -## 🚨 Permission Denied Errors - -**Most common issue**: Permission denied when uploading files or accessing data directories. - -### Quick Diagnosis - -```bash -# Check if this is a permission issue -docker-compose logs palmr | grep -i "permission\|denied\|eacces" - -# Common error messages: -# EACCES: permission denied, open '/app/server/uploads/file.txt' -# Error: EACCES: permission denied, mkdir '/app/server/temp-uploads' -``` - -### The Root Cause - -**Palmr. defaults**: UID 1001, GID 1001 -**Linux standard**: UID 1000, GID 1000 - -When using bind mounts, your host directories may have different ownership than Palmr's default UID/GID. - -### Solution 1: Environment Variables (Recommended) - -Set Palmr. to use your host system's UID/GID: - -```bash -# Find your UID/GID -id -# Output: uid=1000(user) gid=1000(group) groups=1000(group),27(sudo) -``` - -Add to your `docker-compose.yaml`: - -```yaml -services: - palmr: - environment: - - PALMR_UID=1000 # Your UID - - PALMR_GID=1000 # Your GID - # ... rest of config -``` - -Restart the container: - -```bash -docker-compose down && docker-compose up -d -``` - -### Solution 2: Change Host Directory Ownership - -If you prefer to keep Palmr's defaults: - -```bash -# For single data directory -chown -R 1001:1001 ./data - -# For separate upload/temp directories -mkdir -p uploads temp-uploads -chown -R 1001:1001 uploads temp-uploads -``` - -### Solution 3: Docker Volume (Avoid the Issue) - -Use Docker named volumes instead of bind mounts: - -```yaml -volumes: - - palmr_data:/app/server # Named volume (no permission issues) - # Instead of: - # - ./data:/app/server # Bind mount (permission issues) - -volumes: - palmr_data: -``` - ---- - -## 🐳 Container Issues - -### Container Won't Start - -**Check logs first:** - -```bash -docker-compose logs palmr -``` - -**Common issues:** - -1. **Port already in use** - - ```bash - # Check what's using port 5487 - sudo lsof -i :5487 - - # Or change port in docker-compose.yaml - ports: - - "5488:5487" # Use different host port - ``` - -2. **Invalid encryption key** - - ```bash - # Error: Encryption key must be at least 32 characters (only if encryption is enabled) - # Fix: Either disable encryption or provide a valid key - environment: - - DISABLE_FILESYSTEM_ENCRYPTION=true # Disable encryption (default) - # OR enable encryption with: - # - DISABLE_FILESYSTEM_ENCRYPTION=false - # - ENCRYPTION_KEY=your-very-long-secure-key-at-least-32-characters - ``` - -3. **Missing environment variables** - ```bash - # Check variables are set (encryption is optional) - docker exec palmr env | grep -E "DISABLE_FILESYSTEM_ENCRYPTION|ENCRYPTION_KEY|DATABASE_URL" - ``` - -### Container Starts But App Doesn't Load - -```bash -# Check if services are running inside container -docker exec palmr ps aux - -# Check if ports are bound correctly -docker port palmr - -# Test API directly -curl http://localhost:3333/health -``` - ---- - -## 📁 File Upload Issues - -### Files Not Uploading - -1. **Check disk space:** - - ```bash - df -h - # Ensure you have enough space in upload directory - ``` - -2. **Check file permissions in container:** - - ```bash - docker exec palmr ls -la /app/server/uploads/ - # Should show ownership by palmr user (UID 1001) - ``` - -3. **Check upload limits:** - - Default max file size is configurable - - Check browser network tab for 413 errors - -### Files Upload But Can't Be Downloaded - -```bash -# Check file exists and is readable -docker exec palmr ls -la /app/server/uploads/ - -# Check file ownership -docker exec palmr stat /app/server/uploads/your-file.txt -``` - ---- - -## 🔑 Authentication Issues - -### Can't Login to Admin Account - -1. **Reset admin password:** - - ```bash - # Using the built-in reset script - docker exec -it palmr /app/palmr-app/reset-password.sh - ``` - -2. **Check database permissions:** - ```bash - docker exec palmr ls -la /app/server/prisma/ - # palmr.db should be writable by palmr user (UID 1001) - ``` - -### OIDC Authentication Not Working - -See our [OIDC Configuration Guide](/docs/3.0-beta/oidc-authentication) for detailed setup. - ---- - -## 🌐 Network Issues - -### Can't Access Web Interface - -1. **From localhost:** - - ```bash - # Test if container is responding - curl http://localhost:5487 - - # Check if port is bound - docker port palmr 5487 - ``` - -2. **From external network:** - - ```bash - # Check firewall - sudo ufw status - - # Test from external IP - curl http://YOUR_SERVER_IP:5487 - ``` - -### API Not Accessible - -```bash -# Check if API port is exposed -docker port palmr 3333 - -# Test API health -curl http://localhost:3333/health - -# Check API documentation -curl http://localhost:3333/docs -``` - ---- - -## 💾 Database Issues - -### Database Connection Errors - -```bash -# Check database file exists and is writable -docker exec palmr ls -la /app/server/prisma/palmr.db - -# Check database logs -docker-compose logs palmr | grep -i database - -# Verify Prisma schema (run from palmr-app directory) -docker exec palmr sh -c "cd /app/palmr-app && npx prisma db push" -``` - -### Database Corruption - -```bash -# Stop container -docker-compose down - -# Backup existing database -cp ./data/prisma/palmr.db ./data/prisma/palmr.db.backup - -# Let Palmr recreate database on startup -rm ./data/prisma/palmr.db - -# Start container (will recreate database) -docker-compose up -d -``` - ---- - -## 🚀 Performance Issues - -### Slow File Uploads - -1. **Check available disk space:** - - ```bash - df -h - ``` - -2. **Monitor container resources:** - - ```bash - docker stats palmr - ``` - -3. **Check temp directory permissions:** - ```bash - docker exec palmr ls -la /app/server/temp-uploads/ - ``` - -### High Memory Usage - -```bash -# Check container memory usage -docker stats palmr - -# Check Node.js memory usage -docker exec palmr ps aux | grep node -``` - ---- - -## 🔍 Diagnostic Commands - -### Complete Health Check - -```bash -#!/bin/bash -echo "=== Palmr. Health Check ===" - -echo "1. Container Status:" -docker ps | grep palmr - -echo "2. Container Logs (last 20 lines):" -docker-compose logs --tail=20 palmr - -echo "3. Port Status:" -docker port palmr - -echo "4. File Permissions:" -docker exec palmr ls -la /app/server/ - -echo "5. Application Files:" -docker exec palmr ls -la /app/palmr-app/ - -echo "6. Environment Variables:" -docker exec palmr env | grep -E "PALMR_|DISABLE_FILESYSTEM_ENCRYPTION|ENCRYPTION_|DATABASE_" - -echo "7. API Health:" -curl -s http://localhost:3333/health || echo "API not accessible" - -echo "8. Web Interface:" -curl -s -o /dev/null -w "%{http_code}" http://localhost:5487 || echo "Web interface not accessible" - -echo "9. Disk Space:" -df -h - -echo "=== End Health Check ===" -``` - -### Log Analysis - -```bash -# Check for common errors -docker-compose logs palmr 2>&1 | grep -E "error|Error|ERROR|failed|Failed|FAILED" - -# Check permission issues -docker-compose logs palmr 2>&1 | grep -E "permission|denied|EACCES" - -# Check startup sequence -docker-compose logs palmr 2>&1 | grep -E "🌴|🔧|🚀|✅" -``` - ---- - -## 📞 Getting Help - -If none of these solutions work: - -1. **Gather diagnostic information:** - - ```bash - # Run the complete health check above - # Save the output to a file - ``` - -2. **Check our documentation:** - - [UID/GID Configuration](/docs/3.0-beta/uid-gid-configuration) - - [Quick Start Guide](/docs/3.0-beta/quick-start) - - [API Reference](/docs/3.0-beta/api) - -3. **Open an issue on GitHub:** - - Include your `docker-compose.yaml` - - Include relevant log output - - Describe your system (OS, Docker version, etc.) - - [GitHub Issues](https://github.com/kyantech/Palmr/issues) - -4. **Check existing issues:** - - Search [closed issues](https://github.com/kyantech/Palmr/issues?q=is%3Aissue+is%3Aclosed) for similar problems - - Look for recent [discussions](https://github.com/kyantech/Palmr/discussions) diff --git a/apps/docs/content/docs/meta.json b/apps/docs/content/docs/meta.json index 832e9399..55269ea1 100644 --- a/apps/docs/content/docs/meta.json +++ b/apps/docs/content/docs/meta.json @@ -1,3 +1,5 @@ { - "pages": ["3.2-beta", "2.0.0-beta"] -} + "pages": [ + "v3-beta" + ] +} \ No newline at end of file diff --git a/apps/docs/content/docs/3.2-beta/api.mdx b/apps/docs/content/docs/v3-beta/api.mdx similarity index 100% rename from apps/docs/content/docs/3.2-beta/api.mdx rename to apps/docs/content/docs/v3-beta/api.mdx diff --git a/apps/docs/content/docs/3.2-beta/architecture.mdx b/apps/docs/content/docs/v3-beta/architecture.mdx similarity index 94% rename from apps/docs/content/docs/3.2-beta/architecture.mdx rename to apps/docs/content/docs/v3-beta/architecture.mdx index b68b396e..7ac63d2d 100644 --- a/apps/docs/content/docs/3.2-beta/architecture.mdx +++ b/apps/docs/content/docs/v3-beta/architecture.mdx @@ -47,11 +47,11 @@ Palmr. uses **filesystem storage** as the default storage solution, keeping thin By default, filesystem storage operates without encryption for optimal performance, providing fast uploads and downloads with minimal CPU overhead. This approach is ideal for most use cases where performance is prioritized. -If you need to protect sensitive files at rest, you can enable encryption by setting `DISABLE_FILESYSTEM_ENCRYPTION=false` and providing an `ENCRYPTION_KEY` in your configuration. When enabled, Palmr uses AES-256-CBC encryption, which adds CPU overhead during uploads (encryption) and downloads (decryption), particularly for large files or in resource-constrained environments like containers or low-end VMs. +File encryption is handled at the storage layer. For external S3 storage, encryption is managed by your S3 provider. For internal storage, files are stored securely with the built-in S3-compatible server. For optimal performance with encryption enabled, ensure your hardware supports AES-NI acceleration (check with `cat /proc/cpuinfo | grep aes` on Linux). -As an alternative, consider using S3-compatible object storage (e.g., AWS S3 or MinIO), which can offload file storage from the local filesystem and potentially reduce local CPU overhead for encryption/decryption. See [S3 Providers](/docs/3.2-beta/s3-providers) for setup instructions. +As an alternative, consider using S3-compatible object storage (e.g., AWS S3 or MinIO), which can offload file storage from the local filesystem and potentially reduce local CPU overhead for encryption/decryption. See [S3 Providers](/docs/v3-beta/s3-providers) for setup instructions. ### Fastify + Zod + TypeScript @@ -127,7 +127,7 @@ Palmr. is designed to be flexible in how you handle file storage: **Optional S3-compatible storage:** -- Enable S3 storage by setting `ENABLE_S3=true`, look at [S3 Providers](/docs/3.2-beta/s3-providers) for more information. +- Enable S3 storage by setting `ENABLE_S3=true`, look at [S3 Providers](/docs/v3-beta/s3-providers) for more information. - Compatible with AWS S3, MinIO, and other S3-compatible services - Ideal for cloud deployments and distributed setups - Provides additional scalability and redundancy options diff --git a/apps/docs/content/docs/3.2-beta/available-languages.mdx b/apps/docs/content/docs/v3-beta/available-languages.mdx similarity index 76% rename from apps/docs/content/docs/3.2-beta/available-languages.mdx rename to apps/docs/content/docs/v3-beta/available-languages.mdx index 81311752..8a9ab890 100644 --- a/apps/docs/content/docs/3.2-beta/available-languages.mdx +++ b/apps/docs/content/docs/v3-beta/available-languages.mdx @@ -9,7 +9,7 @@ The integration of next-intl ensures consistent internationalization throughout ## Supported languages -Palmr currently supports 15 languages with complete translations across all application features and interfaces. +Palmr currently supports 23 languages with complete translations across all application features and interfaces. --- @@ -30,6 +30,14 @@ Palmr currently supports 15 languages with complete translations across all appl | Italian | it-IT | Standard Italian language support | 100% | | Dutch | nl-NL | Standard Dutch language support | 100% | | Polish | pl-PL | Standard Polish language support | 100% | +| Greek | el-GR | Standard Greek language support | 100% | +| Persian | fa-IR | Standard Persian (Farsi) language support with RTL | 100% | +| Hebrew | he-IL | Standard Hebrew language support with RTL | 100% | +| Indonesian | id-ID | Standard Indonesian language support | 100% | +| Swedish | sv-SE | Standard Swedish language support | 100% | +| Thai | th-TH | Standard Thai language support | 100% | +| Ukrainian | uk-UA | Standard Ukrainian language support | 100% | +| Vietnamese | vi-VN | Standard Vietnamese language support | 100% | ## Language selection @@ -61,6 +69,10 @@ For maximum flexibility, users can easily override this automatic selection at a ## RTL support -The application incorporates comprehensive right-to-left (RTL) text handling capabilities, with particular attention paid to Arabic (ar-SA) language requirements. This includes proper text alignment, layout direction, and user interface elements that adapt to RTL reading patterns. +The application incorporates comprehensive right-to-left (RTL) text handling capabilities for languages that require this reading direction. Currently supported RTL languages include: -RTL support ensures that Arabic-speaking users receive a native and intuitive experience, with all interface elements properly oriented and text flowing in the correct direction for optimal readability and usability. +- **Arabic (ar-SA)** - Standard Arabic with full RTL support +- **Persian (fa-IR)** - Farsi language with RTL text direction +- **Hebrew (he-IL)** - Hebrew language with RTL support + +This includes proper text alignment, layout direction, and user interface elements that adapt to RTL reading patterns. RTL support ensures that speakers of these languages receive a native and intuitive experience, with all interface elements properly oriented and text flowing in the correct direction for optimal readability and usability. diff --git a/apps/docs/content/docs/3.2-beta/configuring-smtp.mdx b/apps/docs/content/docs/v3-beta/configuring-smtp.mdx similarity index 100% rename from apps/docs/content/docs/3.2-beta/configuring-smtp.mdx rename to apps/docs/content/docs/v3-beta/configuring-smtp.mdx diff --git a/apps/docs/content/docs/3.2-beta/contribute.mdx b/apps/docs/content/docs/v3-beta/contribute.mdx similarity index 100% rename from apps/docs/content/docs/3.2-beta/contribute.mdx rename to apps/docs/content/docs/v3-beta/contribute.mdx diff --git a/apps/docs/content/docs/3.2-beta/gh-sponsor.mdx b/apps/docs/content/docs/v3-beta/gh-sponsor.mdx similarity index 100% rename from apps/docs/content/docs/3.2-beta/gh-sponsor.mdx rename to apps/docs/content/docs/v3-beta/gh-sponsor.mdx diff --git a/apps/docs/content/docs/3.2-beta/gh-star.mdx b/apps/docs/content/docs/v3-beta/gh-star.mdx similarity index 100% rename from apps/docs/content/docs/3.2-beta/gh-star.mdx rename to apps/docs/content/docs/v3-beta/gh-star.mdx diff --git a/apps/docs/content/docs/3.2-beta/github-architecture.mdx b/apps/docs/content/docs/v3-beta/github-architecture.mdx similarity index 100% rename from apps/docs/content/docs/3.2-beta/github-architecture.mdx rename to apps/docs/content/docs/v3-beta/github-architecture.mdx diff --git a/apps/docs/content/docs/3.2-beta/index.mdx b/apps/docs/content/docs/v3-beta/index.mdx similarity index 100% rename from apps/docs/content/docs/3.2-beta/index.mdx rename to apps/docs/content/docs/v3-beta/index.mdx diff --git a/apps/docs/content/docs/3.2-beta/manual-installation.mdx b/apps/docs/content/docs/v3-beta/manual-installation.mdx similarity index 77% rename from apps/docs/content/docs/3.2-beta/manual-installation.mdx rename to apps/docs/content/docs/v3-beta/manual-installation.mdx index 20fcd798..bf764a66 100644 --- a/apps/docs/content/docs/3.2-beta/manual-installation.mdx +++ b/apps/docs/content/docs/v3-beta/manual-installation.mdx @@ -5,7 +5,7 @@ icon: Cog Hey there! Looking to run **Palmr.** your way, with complete control over every piece of the stack? This manual installation guide is for you. No Docker, no pre-built containers just the raw source code to tweak, customize, and deploy as you see fit. -> **Prefer a quicker setup?** If this hands-on approach feels like overkill, check out our [**Quick Start (Docker)**](/docs/3.2-beta/quick-start) guide for a fast, containerized deployment. This manual path is tailored for developers who want to dive deep, modify the codebase, or integrate custom services. +> **Prefer a quicker setup?** If this hands-on approach feels like overkill, check out our [**Quick Start (Docker)**](/docs/v3-beta/quick-start) guide for a fast, containerized deployment. This manual path is tailored for developers who want to dive deep, modify the codebase, or integrate custom services. Here's what you'll do at a glance: @@ -38,6 +38,10 @@ Let's make sure your environment is ready to roll. Check that you've got these t - **Memory**: At least 4GB RAM is suggested. - **Storage**: Depends on how much file storage you'll need. - **CPU**: 2+ cores for smooth performance. +- **S3-Compatible Storage**: **REQUIRED** - Either: + - External S3 service (AWS S3, DigitalOcean Spaces, etc.) + - Local Minio server running on port 9379 + - Another S3-compatible service --- @@ -71,7 +75,31 @@ Before anything else, we need to set up the environment variables Palmr. relies cp .env.example .env ``` -This copies the template into a new `.env` file with all the necessary settings. Open it up if you need to tweak anything specific to your setup. +This copies the template into a new `.env` file with all the necessary settings. **Important**: You must configure S3-compatible storage for Palmr v3+ to work. Edit the `.env` file to either: + +**Option 1: External S3 Service** +```bash +ENABLE_S3=true +S3_ENDPOINT=s3.amazonaws.com +S3_ACCESS_KEY=your-access-key +S3_SECRET_KEY=your-secret-key +S3_BUCKET_NAME=palmr-files +S3_REGION=us-east-1 +``` + +**Option 2: Local Minio (for development)** +```bash +# First, start Minio locally: +# docker run -p 9379:9000 -e MINIO_ROOT_USER=minioadmin -e MINIO_ROOT_PASSWORD=minioadmin minio/minio server /data + +ENABLE_S3=true +S3_ENDPOINT=localhost:9379 +S3_ACCESS_KEY=minioadmin +S3_SECRET_KEY=minioadmin +S3_BUCKET_NAME=palmr-files +S3_USE_SSL=false +S3_FORCE_PATH_STYLE=true +``` #### Install dependencies @@ -165,26 +193,7 @@ cp .env.example .env This creates a `.env` file with the necessary configurations for the frontend. -##### Upload Configuration - -Palmr. supports configurable chunked uploading for large files. You can customize the chunk size by setting the following environment variable in your `.env` file: - -```bash -NEXT_PUBLIC_UPLOAD_CHUNK_SIZE_MB=100 -``` - -**How it works:** - -- If `NEXT_PUBLIC_UPLOAD_CHUNK_SIZE_MB` is set, Palmr. will use this value (in megabytes) as the chunk size for all file uploads that exceed this threshold. -- If not set or left empty, Palmr. automatically calculates optimal chunk sizes based on file size: - - Files ≤ 100MB: uploaded without chunking - - Files > 100MB and ≤ 1GB: 75MB chunks - - Files > 1GB: 150MB chunks -**When to configure:** - -- **Default (not set):** Recommended for most use cases. Palmr. will intelligently determine the best chunk size. -- **Custom value:** Set this if you have specific network conditions or want to optimize for your infrastructure (e.g., slower connections may benefit from smaller chunks like 50MB, while fast networks can handle larger chunks like 200MB, or the upload size per payload may be limited by a proxy like Cloudflare) #### Install dependencies @@ -220,7 +229,13 @@ You should see the full Palmr. application ready to go! ## Quick notes -This guide sets up Palmr. using the local file system for storage. Want to use an S3-compatible object storage instead? You can configure that in the `.env` file. Check the Palmr. documentation for details on setting up S3 storage just update the environment variables, then build and run as shown here. +**Storage Requirement**: Palmr v3+ requires S3-compatible storage to function. This guide assumes you've configured either external S3 or local Minio as shown in the environment variables section above. Without proper S3 configuration, file uploads will not work. + +**Development Tip**: For local development, we recommend using Minio as it's lightweight and S3-compatible. Start it with Docker before running Palmr: + +```bash +docker run -p 9379:9000 -e MINIO_ROOT_USER=minioadmin -e MINIO_ROOT_PASSWORD=minioadmin minio/minio server /data +``` ### Custom Installation Paths and Symlinks @@ -240,6 +255,9 @@ This tells Palmr. to check your custom path first when determining available dis Here's a quick reference for all the commands we've covered perfect for copy-pasting once you're familiar with the steps: ```bash +# --- Prerequisites (if using local Minio) --- +docker run -p 9379:9000 -e MINIO_ROOT_USER=minioadmin -e MINIO_ROOT_PASSWORD=minioadmin minio/minio server /data + # --- Backend (Fastify API) --- cd apps/server pnpm install @@ -264,10 +282,10 @@ pnpm serve Palmr. is now up and running locally . Here are some suggested next steps: -- **Manage Users**: Dive into the [Users Management](/docs/3.2-beta/manage-users) guide. +- **Manage Users**: Dive into the [Users Management](/docs/v3-beta/manage-users) guide. - **Switch to Object Storage**: Update `.env` variables to use an S3-compatible bucket (see Quick Notes above). - **Secure Your Instance**: Put Palmr. behind a reverse proxy like **Nginx** or **Caddy** and enable HTTPS. -- **Learn the Internals**: Explore how everything connects in the [Architecture](/docs/3.2-beta/architecture) overview. +- **Learn the Internals**: Explore how everything connects in the [Architecture](/docs/v3-beta/architecture) overview. Jump into whichever area fits your needs our docs are designed for exploration in any order. diff --git a/apps/docs/content/docs/3.2-beta/meta.json b/apps/docs/content/docs/v3-beta/meta.json similarity index 85% rename from apps/docs/content/docs/3.2-beta/meta.json rename to apps/docs/content/docs/v3-beta/meta.json index 2d02c92e..310d47c4 100644 --- a/apps/docs/content/docs/3.2-beta/meta.json +++ b/apps/docs/content/docs/v3-beta/meta.json @@ -14,11 +14,8 @@ "available-languages", "uid-gid-configuration", "reverse-proxy-configuration", - "download-memory-management", "password-reset-without-smtp", - "cleanup-orphan-files", "oidc-authentication", - "troubleshooting", "---Developers---", "architecture", "github-architecture", @@ -31,5 +28,5 @@ "gh-sponsor" ], "root": true, - "title": "v3.2-beta" -} + "title": "v3-beta" +} \ No newline at end of file diff --git a/apps/docs/content/docs/3.2-beta/oidc-authentication/auth0.mdx b/apps/docs/content/docs/v3-beta/oidc-authentication/auth0.mdx similarity index 99% rename from apps/docs/content/docs/3.2-beta/oidc-authentication/auth0.mdx rename to apps/docs/content/docs/v3-beta/oidc-authentication/auth0.mdx index be8f13b5..45417092 100644 --- a/apps/docs/content/docs/3.2-beta/oidc-authentication/auth0.mdx +++ b/apps/docs/content/docs/v3-beta/oidc-authentication/auth0.mdx @@ -360,7 +360,7 @@ With Auth0 authentication configured, you might want to: - **Review security settings**: Ensure your authentication setup meets your security requirements - **Monitor usage**: Keep track of authentication patterns and user activity -For more information about OIDC authentication in Palmr, see the [OIDC Authentication overview](/docs/3.2-beta/oidc-authentication). +For more information about OIDC authentication in Palmr, see the [OIDC Authentication overview](/docs/v3-beta/oidc-authentication). ## Useful resources diff --git a/apps/docs/content/docs/3.2-beta/oidc-authentication/authentik.mdx b/apps/docs/content/docs/v3-beta/oidc-authentication/authentik.mdx similarity index 99% rename from apps/docs/content/docs/3.2-beta/oidc-authentication/authentik.mdx rename to apps/docs/content/docs/v3-beta/oidc-authentication/authentik.mdx index 3bf2c7a6..3e444077 100644 --- a/apps/docs/content/docs/3.2-beta/oidc-authentication/authentik.mdx +++ b/apps/docs/content/docs/v3-beta/oidc-authentication/authentik.mdx @@ -374,7 +374,7 @@ With Authentik authentication configured, you might want to: - **Review security settings**: Ensure your authentication setup meets your security requirements - **Monitor usage**: Keep track of authentication patterns and user activity -For more information about OIDC authentication in Palmr, see the [OIDC Authentication overview](/docs/3.2-beta/oidc-authentication). +For more information about OIDC authentication in Palmr, see the [OIDC Authentication overview](/docs/v3-beta/oidc-authentication). ## Useful resources diff --git a/apps/docs/content/docs/3.2-beta/oidc-authentication/discord.mdx b/apps/docs/content/docs/v3-beta/oidc-authentication/discord.mdx similarity index 99% rename from apps/docs/content/docs/3.2-beta/oidc-authentication/discord.mdx rename to apps/docs/content/docs/v3-beta/oidc-authentication/discord.mdx index e4316e6f..26982017 100644 --- a/apps/docs/content/docs/3.2-beta/oidc-authentication/discord.mdx +++ b/apps/docs/content/docs/v3-beta/oidc-authentication/discord.mdx @@ -332,7 +332,7 @@ With Discord authentication configured, you might want to: - **Review security settings**: Ensure your authentication setup meets your security requirements - **Monitor usage**: Keep track of authentication patterns and user activity -For more information about OIDC authentication in Palmr, see the [OIDC Authentication overview](/docs/3.2-beta/oidc-authentication). +For more information about OIDC authentication in Palmr, see the [OIDC Authentication overview](/docs/v3-beta/oidc-authentication). ## Useful resources diff --git a/apps/docs/content/docs/3.2-beta/oidc-authentication/frontegg.mdx b/apps/docs/content/docs/v3-beta/oidc-authentication/frontegg.mdx similarity index 99% rename from apps/docs/content/docs/3.2-beta/oidc-authentication/frontegg.mdx rename to apps/docs/content/docs/v3-beta/oidc-authentication/frontegg.mdx index 97712dd0..13bd7c38 100644 --- a/apps/docs/content/docs/3.2-beta/oidc-authentication/frontegg.mdx +++ b/apps/docs/content/docs/v3-beta/oidc-authentication/frontegg.mdx @@ -314,7 +314,7 @@ With Frontegg authentication configured, you might want to: - **Review security settings**: Ensure your authentication setup meets your security requirements - **Monitor usage**: Keep track of authentication patterns and user activity -For more information about OIDC authentication in Palmr, see the [OIDC Authentication overview](/docs/3.2-beta/oidc-authentication). +For more information about OIDC authentication in Palmr, see the [OIDC Authentication overview](/docs/v3-beta/oidc-authentication). ## Useful resources diff --git a/apps/docs/content/docs/3.2-beta/oidc-authentication/github.mdx b/apps/docs/content/docs/v3-beta/oidc-authentication/github.mdx similarity index 99% rename from apps/docs/content/docs/3.2-beta/oidc-authentication/github.mdx rename to apps/docs/content/docs/v3-beta/oidc-authentication/github.mdx index d9f2bbc5..6aa60c9c 100644 --- a/apps/docs/content/docs/3.2-beta/oidc-authentication/github.mdx +++ b/apps/docs/content/docs/v3-beta/oidc-authentication/github.mdx @@ -295,7 +295,7 @@ With GitHub authentication configured, you might want to: - **Review security settings**: Ensure your authentication setup meets your security requirements - **Monitor usage**: Keep track of authentication patterns and user activity -For more information about OIDC authentication in Palmr, see the [OIDC Authentication overview](/docs/3.2-beta/oidc-authentication). +For more information about OIDC authentication in Palmr, see the [OIDC Authentication overview](/docs/v3-beta/oidc-authentication). ## Useful resources diff --git a/apps/docs/content/docs/3.2-beta/oidc-authentication/google.mdx b/apps/docs/content/docs/v3-beta/oidc-authentication/google.mdx similarity index 99% rename from apps/docs/content/docs/3.2-beta/oidc-authentication/google.mdx rename to apps/docs/content/docs/v3-beta/oidc-authentication/google.mdx index 1222b4ae..510d31c3 100644 --- a/apps/docs/content/docs/3.2-beta/oidc-authentication/google.mdx +++ b/apps/docs/content/docs/v3-beta/oidc-authentication/google.mdx @@ -325,7 +325,7 @@ With Google authentication configured, you might want to: - **Review security settings**: Ensure your authentication setup meets your security requirements - **Monitor usage**: Keep track of authentication patterns and user activity -For more information about OIDC authentication in Palmr, see the [OIDC Authentication overview](/docs/3.2-beta/oidc-authentication). +For more information about OIDC authentication in Palmr, see the [OIDC Authentication overview](/docs/v3-beta/oidc-authentication). ## Useful resources diff --git a/apps/docs/content/docs/3.2-beta/oidc-authentication/index.mdx b/apps/docs/content/docs/v3-beta/oidc-authentication/index.mdx similarity index 88% rename from apps/docs/content/docs/3.2-beta/oidc-authentication/index.mdx rename to apps/docs/content/docs/v3-beta/oidc-authentication/index.mdx index 26b66672..9d802dad 100644 --- a/apps/docs/content/docs/3.2-beta/oidc-authentication/index.mdx +++ b/apps/docs/content/docs/v3-beta/oidc-authentication/index.mdx @@ -38,14 +38,14 @@ Before configuring OIDC authentication, ensure you have: Palmr's OIDC implementation is compatible with any OpenID Connect compliant provider, including as official providers: -- **[Google](/docs/3.2-beta/oidc-authentication/google)** -- **[Discord](/docs/3.2-beta/oidc-authentication/discord)** -- **[Github](/docs/3.2-beta/oidc-authentication/github)** -- **[Zitadel](/docs/3.2-beta/oidc-authentication/zitadel)** -- **[Auth0](/docs/3.2-beta/oidc-authentication/auth0)** -- **[Authentik](/docs/3.2-beta/oidc-authentication/authentik)** -- **[Frontegg](/docs/3.2-beta/oidc-authentication/frontegg)** -- **[Kinde Auth](/docs/3.2-beta/oidc-authentication/kinde-auth)** +- **[Google](/docs/v3-beta/oidc-authentication/google)** +- **[Discord](/docs/v3-beta/oidc-authentication/discord)** +- **[Github](/docs/v3-beta/oidc-authentication/github)** +- **[Zitadel](/docs/v3-beta/oidc-authentication/zitadel)** +- **[Auth0](/docs/v3-beta/oidc-authentication/auth0)** +- **[Authentik](/docs/v3-beta/oidc-authentication/authentik)** +- **[Frontegg](/docs/v3-beta/oidc-authentication/frontegg)** +- **[Kinde Auth](/docs/v3-beta/oidc-authentication/kinde-auth)** Although these are the official providers (internally tested with 100% success), you can connect any OIDC provider by providing your credentials and connection URL. We've developed a practical way to integrate virtually all OIDC providers available in the market. In this documentation, you can consult how to configure each of the official providers, as well as include other providers not listed as official. Just below, you will find instructions on how to access the OIDC provider configuration. For specific details about configuring each provider, select the desired option in the sidebar, in the "OIDC Authentication" section. diff --git a/apps/docs/content/docs/3.2-beta/oidc-authentication/kinde-auth.mdx b/apps/docs/content/docs/v3-beta/oidc-authentication/kinde-auth.mdx similarity index 99% rename from apps/docs/content/docs/3.2-beta/oidc-authentication/kinde-auth.mdx rename to apps/docs/content/docs/v3-beta/oidc-authentication/kinde-auth.mdx index 92ad308f..cb7d340f 100644 --- a/apps/docs/content/docs/3.2-beta/oidc-authentication/kinde-auth.mdx +++ b/apps/docs/content/docs/v3-beta/oidc-authentication/kinde-auth.mdx @@ -359,7 +359,7 @@ With Kinde Auth authentication configured, you might want to: - **Review security settings**: Ensure your authentication setup meets your security requirements - **Monitor usage**: Keep track of authentication patterns and user activity -For more information about OIDC authentication in Palmr, see the [OIDC Authentication overview](/docs/3.2-beta/oidc-authentication). +For more information about OIDC authentication in Palmr, see the [OIDC Authentication overview](/docs/v3-beta/oidc-authentication). ## Useful resources diff --git a/apps/docs/content/docs/3.2-beta/oidc-authentication/meta.json b/apps/docs/content/docs/v3-beta/oidc-authentication/meta.json similarity index 100% rename from apps/docs/content/docs/3.2-beta/oidc-authentication/meta.json rename to apps/docs/content/docs/v3-beta/oidc-authentication/meta.json diff --git a/apps/docs/content/docs/3.2-beta/oidc-authentication/other.mdx b/apps/docs/content/docs/v3-beta/oidc-authentication/other.mdx similarity index 100% rename from apps/docs/content/docs/3.2-beta/oidc-authentication/other.mdx rename to apps/docs/content/docs/v3-beta/oidc-authentication/other.mdx diff --git a/apps/docs/content/docs/3.2-beta/oidc-authentication/pocket-id.mdx b/apps/docs/content/docs/v3-beta/oidc-authentication/pocket-id.mdx similarity index 98% rename from apps/docs/content/docs/3.2-beta/oidc-authentication/pocket-id.mdx rename to apps/docs/content/docs/v3-beta/oidc-authentication/pocket-id.mdx index 109f5d10..374eccd6 100644 --- a/apps/docs/content/docs/3.2-beta/oidc-authentication/pocket-id.mdx +++ b/apps/docs/content/docs/v3-beta/oidc-authentication/pocket-id.mdx @@ -270,10 +270,10 @@ After configuring Pocket ID authentication: - **User management**: Review auto-registration settings - **Backup verification**: Test backup and restore procedures -For more information about OIDC authentication in Palmr, see the [OIDC Authentication overview](/docs/3.2-beta/oidc-authentication). +For more information about OIDC authentication in Palmr, see the [OIDC Authentication overview](/docs/v3-beta/oidc-authentication). ## Useful resources - [Pocket ID Documentation](https://docs.pocket-id.org) - [OIDC Specification](https://openid.net/specs/openid-connect-core-1_0.html) -- [Palmr OIDC Overview](/docs/3.2-beta/oidc-authentication) +- [Palmr OIDC Overview](/docs/v3-beta/oidc-authentication) diff --git a/apps/docs/content/docs/3.2-beta/oidc-authentication/zitadel.mdx b/apps/docs/content/docs/v3-beta/oidc-authentication/zitadel.mdx similarity index 99% rename from apps/docs/content/docs/3.2-beta/oidc-authentication/zitadel.mdx rename to apps/docs/content/docs/v3-beta/oidc-authentication/zitadel.mdx index c95074e6..f439f291 100644 --- a/apps/docs/content/docs/3.2-beta/oidc-authentication/zitadel.mdx +++ b/apps/docs/content/docs/v3-beta/oidc-authentication/zitadel.mdx @@ -413,7 +413,7 @@ With Zitadel authentication configured, you might want to: - **Review security settings**: Ensure your authentication setup meets your security requirements - **Monitor usage**: Keep track of authentication patterns and user activity -For more information about OIDC authentication in Palmr, see the [OIDC Authentication overview](/docs/3.2-beta/oidc-authentication). +For more information about OIDC authentication in Palmr, see the [OIDC Authentication overview](/docs/v3-beta/oidc-authentication). ## Useful resources diff --git a/apps/docs/content/docs/3.2-beta/open-an-issue.mdx b/apps/docs/content/docs/v3-beta/open-an-issue.mdx similarity index 100% rename from apps/docs/content/docs/3.2-beta/open-an-issue.mdx rename to apps/docs/content/docs/v3-beta/open-an-issue.mdx diff --git a/apps/docs/content/docs/3.2-beta/password-reset-without-smtp.mdx b/apps/docs/content/docs/v3-beta/password-reset-without-smtp.mdx similarity index 100% rename from apps/docs/content/docs/3.2-beta/password-reset-without-smtp.mdx rename to apps/docs/content/docs/v3-beta/password-reset-without-smtp.mdx diff --git a/apps/docs/content/docs/v3-beta/quick-start.mdx b/apps/docs/content/docs/v3-beta/quick-start.mdx new file mode 100644 index 00000000..a4b8b87e --- /dev/null +++ b/apps/docs/content/docs/v3-beta/quick-start.mdx @@ -0,0 +1,386 @@ +--- +title: Quick Start (Docker) +icon: "Rocket" +--- + +import { Callout } from "fumadocs-ui/components/callout"; +import { Tab, Tabs } from "fumadocs-ui/components/tabs"; + +import { Card, CardGrid } from "@/components/ui/card"; + +Welcome to the fastest way to deploy Palmr. - your secure, self-hosted file sharing solution. This guide will have you up and running in minutes, whether you're new to self-hosting or an experienced developer. + +Palmr. offers flexible deployment options to match your infrastructure needs. This guide focuses on Docker deployment with internal S3-compatible storage, perfect for most use cases. + +## Prerequisites + +Before you begin, make sure you have: + +- **Docker** - Container runtime ([installation guide](https://docs.docker.com/get-docker/)) +- **Docker Compose** - Multi-container orchestration ([installation guide](https://docs.docker.com/compose/install/)) +- **2GB+ available disk space** for the application and your files +- **Port 5487** available for the web interface +- **Port 9379** available for internal S3-compatible storage (**REQUIRED** unless using external S3) +- **Port 3333** available for API access (optional) + + + **Critical**: Port **9379** is **REQUIRED** for internal storage operations. This port exposes Palmr's built-in S3-compatible storage server. If you cannot expose this port, you **must** configure external S3 storage using the `ENABLE_S3=true` option. + + + + **Platform Support**: Palmr. is developed on macOS and extensively tested on Linux servers. While we haven't formally + tested other platforms, Docker's cross-platform nature should ensure compatibility. Report any issues on our [GitHub + repository](https://github.com/kyantech/Palmr/issues). + + +## Storage Options + +Palmr. supports two storage approaches for persistent data: + +- **Named Volumes (Recommended)** - Docker-managed storage with optimal performance and no permission issues +- **Bind Mounts** - Direct host filesystem access, ideal for development and direct file management + +## Deployment Options + +Choose your storage method based on your needs: + + + + Docker-managed storage that provides the best balance of performance, security, and ease of use: + + - **No Permission Issues**: Docker handles all permission management automatically + - **Performance**: Optimized for container workloads with better I/O performance + - **Production Ready**: Recommended for production deployments + + ### Configuration + + Create a `docker-compose.yml` file: + + ```yaml + services: + palmr: + image: kyantech/palmr:latest + container_name: palmr + environment: + # ============================================================================== + # STORAGE CONFIGURATION + # ============================================================================== + # By default, Palmr uses internal storage - ZERO CONFIG NEEDED! + # Files are managed automatically with no setup required. + # + # Want to use external S3 storage (AWS, S3-compatible, etc)? Just add: + # - ENABLE_S3=true # Enable external S3 + # - S3_ENDPOINT=s3.amazonaws.com # Your S3 endpoint + # - S3_ACCESS_KEY=your-access-key # Your access key + # - S3_SECRET_KEY=your-secret-key # Your secret key + # - S3_BUCKET_NAME=palmr-files # Your bucket name + # - S3_REGION=us-east-1 # Region (optional) + # - S3_USE_SSL=true # Use SSL (optional) + # - S3_FORCE_PATH_STYLE=false # Path-style URLs (optional, true for Minio) + # - S3_REJECT_UNAUTHORIZED=true # Reject self-signed certs (optional) + # + # ============================================================================== + # USER/GROUP CONFIGURATION + # ============================================================================== + # - PALMR_UID=1000 # UID for container processes (optional) + # - PALMR_GID=1000 # GID for container processes (optional) + # + # ============================================================================== + # APPLICATION SETTINGS + # ============================================================================== + # - DEFAULT_LANGUAGE=en-US # Default language (optional) + # - PRESIGNED_URL_EXPIRATION=3600 # Presigned URL expiration in seconds (optional) + # - SECURE_SITE=true # Set true if using HTTPS reverse proxy (optional) + STORAGE_URL: "https://your-domain:9379" # REQUIRED for internal storage: Full storage URL with protocol. Replace with your actual domain/IP. + # + ports: + - "9379:9379" # Internal storage (S3-compatible, REQUIRED for file uploads when using internal storage) + - "5487:5487" # Web interface + - "3333:3333" # API (optional, only if you need direct API access) + volumes: + - palmr_data:/app/server + restart: unless-stopped + + volumes: + palmr_data: + ``` + + + **Having upload or permission issues?** Add `PALMR_UID=1000` and `PALMR_GID=1000` to your environment variables. Check our [UID/GID Configuration](/docs/v3-beta/uid-gid-configuration) guide for more details. + + + ### Deploy + + ```bash + docker-compose up -d + ``` + + + + Direct mapping to host filesystem directories, providing direct file access: + + - **Direct Access**: Files are directly accessible from your host system + - **Development Friendly**: Easy to inspect, modify, or backup files manually + - **Platform Dependent**: May require UID/GID configuration, especially on NAS systems + + ### Configuration + + Create a `docker-compose.yml` file: + + ```yaml + services: + palmr: + image: kyantech/palmr:latest + container_name: palmr + environment: + # ============================================================================== + # STORAGE CONFIGURATION + # ============================================================================== + # By default, Palmr uses internal storage - ZERO CONFIG NEEDED! + # Files are managed automatically with no setup required. + # + # Want to use external S3 storage (AWS, S3-compatible, etc)? Just add: + # - ENABLE_S3=true # Enable external S3 + # - S3_ENDPOINT=s3.amazonaws.com # Your S3 endpoint + # - S3_ACCESS_KEY=your-access-key # Your access key + # - S3_SECRET_KEY=your-secret-key # Your secret key + # - S3_BUCKET_NAME=palmr-files # Your bucket name + # - S3_REGION=us-east-1 # Region (optional) + # - S3_USE_SSL=true # Use SSL (optional) + # - S3_FORCE_PATH_STYLE=false # Path-style URLs (optional, true for Minio) + # - S3_REJECT_UNAUTHORIZED=true # Reject self-signed certs (optional) + # + # ============================================================================== + # USER/GROUP CONFIGURATION + # ============================================================================== + # - PALMR_UID=1000 # UID for container processes (optional) + # - PALMR_GID=1000 # GID for container processes (optional) + # + # ============================================================================== + # APPLICATION SETTINGS + # ============================================================================== + # - DEFAULT_LANGUAGE=en-US # Default language (optional) + # - PRESIGNED_URL_EXPIRATION=3600 # Presigned URL expiration in seconds (optional) + # - SECURE_SITE=true # Set true if using HTTPS reverse proxy (optional) + STORAGE_URL: "https://your-domain:9379" # REQUIRED for internal storage: Full storage URL with protocol. Replace with your actual domain/IP. + # + ports: + - "9379:9379" # Internal storage (S3-compatible, REQUIRED for file uploads when using internal storage) + - "5487:5487" # Web interface + - "3333:3333" # API (optional, only if you need direct API access) + volumes: + - ./data:/app/server + restart: unless-stopped + ``` + + + **Having upload or permission issues?** Add `PALMR_UID=1000` and `PALMR_GID=1000` to your environment variables. Check our [UID/GID Configuration](/docs/v3-beta/uid-gid-configuration) guide for more details. + + + ### Deploy + + ```bash + docker-compose up -d + ``` + + + + +## Configuration + +Customize Palmr's behavior with these environment variables: + +| Variable | Default | Description | +| ---------------------------- | ------- | ------------------------------------------------------------------------------------------------------------------------- | +| `STORAGE_URL` | - | **REQUIRED for internal storage**: Full storage URL with protocol (e.g., `https://yourdomain:9379`) | +| `ENABLE_S3` | `false` | Enable S3-compatible storage backends (if true, STORAGE_URL is not needed) | +| `S3_ENDPOINT` | - | S3 server endpoint URL (required when using S3) | +| `S3_ACCESS_KEY` | - | S3 access key for authentication (required when using S3) | +| `S3_SECRET_KEY` | - | S3 secret key for authentication (required when using S3) | +| `S3_BUCKET_NAME` | - | S3 bucket name for file storage (required when using S3) | +| `S3_REGION` | - | S3 region configuration (optional when using S3) | +| `S3_USE_SSL` | `true` | Enable SSL for S3 connections (optional when using S3) | +| `S3_FORCE_PATH_STYLE` | `false` | Force path-style S3 URLs (optional when using S3, set to `true` for Minio) | +| `S3_REJECT_UNAUTHORIZED` | `true` | Enable strict SSL certificate validation for S3 (set to `false` for self-signed certificates) | +| `PALMR_UID` | `1000` | User ID for container processes (helps with file permissions) | +| `PALMR_GID` | `1000` | Group ID for container processes (helps with file permissions) | +| `DEFAULT_LANGUAGE` | `en-US` | Default application language ([see available languages](/docs/v3-beta/available-languages)) | +| `PRESIGNED_URL_EXPIRATION` | `3600` | Duration in seconds for presigned URL expiration (applies to both internal and S3 storage) | +| `SECURE_SITE` | `false` | Enable secure cookies for HTTPS/reverse proxy deployments (set to `true` when using HTTPS) | + + + **Storage Architecture**: Palmr v3.3+ uses an internal S3-compatible storage server for better performance and compatibility. Port 9379 **must** be accessible for file operations to work properly. + + + + **Using a Reverse Proxy?** Set `SECURE_SITE=true` and check our [Reverse Proxy + Configuration](/docs/v3-beta/reverse-proxy-configuration) guide for proper HTTPS setup. + + + + +## Access Your Instance + +Once deployed, open Palmr in your browser: + +- **Web Interface**: `http://localhost:5487` (local) or `http://YOUR_SERVER_IP:5487` (remote) +- **Internal Storage**: `http://localhost:9379` (S3-compatible API, **REQUIRED** for file operations) +- **API Documentation**: `http://localhost:3333/docs` (if port 3333 is exposed) + + + **Learn More**: For complete API documentation, authentication, and integration examples, see our [API + Reference](/docs/v3-beta/api) guide + + + + **Production Ready?** Configure HTTPS with a valid SSL certificate for secure production deployments. + + +--- + +## Docker CLI Alternative + +Prefer Docker commands over Compose? Here are the equivalent commands: + + + + + ```bash + docker run -d \ + --name palmr \ + -e STORAGE_URL="https://your-domain:9379" \ + # Optional: Configure as needed + # -e ENABLE_S3=true \ # Set to true to enable S3-compatible storage + # -e S3_ENDPOINT=s3.amazonaws.com \ + # -e S3_ACCESS_KEY=your-access-key \ + # -e S3_SECRET_KEY=your-secret-key \ + # -e S3_BUCKET_NAME=palmr-files \ + # -e PALMR_UID=1000 \ + # -e PALMR_GID=1000 \ + # -e SECURE_SITE=true \ + # -e DEFAULT_LANGUAGE=en-US \ + -p 9379:9379 \ + -p 5487:5487 \ + -p 3333:3333 \ + -v palmr_data:/app/server \ + --restart unless-stopped \ + kyantech/palmr:latest + ``` + + + + **Permission Issues?** Add `-e PALMR_UID=1000 -e PALMR_GID=1000` to the command above. See our [UID/GID Configuration](/docs/v3-beta/uid-gid-configuration) guide for details. + + + + + + + ```bash + docker run -d \ + --name palmr \ + -e STORAGE_URL="https://your-domain:9379" \ + # Optional: Configure as needed + # -e ENABLE_S3=true \ # Set to true to enable S3-compatible storage + # -e S3_ENDPOINT=s3.amazonaws.com \ + # -e S3_ACCESS_KEY=your-access-key \ + # -e S3_SECRET_KEY=your-secret-key \ + # -e S3_BUCKET_NAME=palmr-files \ + # -e PALMR_UID=1000 \ + # -e PALMR_GID=1000 \ + # -e SECURE_SITE=true \ + # -e DEFAULT_LANGUAGE=en-US \ + -p 9379:9379 \ + -p 5487:5487 \ + -p 3333:3333 \ + -v $(pwd)/data:/app/server \ + --restart unless-stopped \ + kyantech/palmr:latest + ``` + + + **Permission Issues?** Add `-e PALMR_UID=1000 -e PALMR_GID=1000` to the command above. See our [UID/GID Configuration](/docs/v3-beta/uid-gid-configuration) guide for details. + + + + + +--- + +## Common Configuration Options + +### Storage URL Configuration (REQUIRED) + +Palmr requires the `STORAGE_URL` environment variable when using internal storage. This tells Palmr how to generate download URLs for files. + +```yaml +environment: + - STORAGE_URL="https://your-domain.com:9379" # Use your actual domain + # or for local testing: + - STORAGE_URL="http://localhost:9379" +``` + +**Important Notes:** +- **Must include protocol** (`http://` or `https://`) +- **Must include port 9379** unless you're using a reverse proxy +- **Use your actual domain/IP** that clients will access +- **Not needed if using S3** (`ENABLE_S3=true`) + +### Presigned URL Expiration + +Palmr. uses temporary URLs (presigned URLs) for secure file access. These URLs expire after a configurable time period to enhance security. + +**Default:** 1 hour (3600 seconds) + +You can customize this for all storage types (internal or S3) by adding: + +```yaml +environment: + - PRESIGNED_URL_EXPIRATION=7200 # 2 hours +``` + +**When to adjust:** + +- **Shorter time (1800 = 30 min):** Higher security, but users may need to refresh download links +- **Longer time (7200-21600 = 2-6 hours):** Better for large file transfers, but URLs stay valid longer +- **Default (3600 = 1 hour):** Good balance for most use cases + + + + + +--- + +## Maintenance + +### Updates + +Keep Palmr up to date with the latest features and security patches: + +```bash +docker-compose pull +docker-compose up -d +``` + + + +--- + +## What's Next? + +Your Palmr instance is ready! Here's what you can explore: + +### Advanced Configuration + +- **[UID/GID Configuration](/docs/v3-beta/uid-gid-configuration)** - Configure user permissions for NAS systems and custom environments +- **[Download Memory Management](/docs/v3-beta/download-memory-management)** - Configure large file download handling and queue system +- **[S3 Storage](/docs/v3-beta/s3-configuration)** - Scale with Amazon S3 or compatible storage providers +- **[Manual Installation](/docs/v3-beta/manual-installation)** - Manual installation and custom configurations + +### Integration & Development + +- **[API Reference](/docs/v3-beta/api)** - Integrate Palmr. with your applications + +--- + +**Questions?** Visit our [GitHub Issues](https://github.com/kyantech/Palmr/issues) or join the community discussions. diff --git a/apps/docs/content/docs/3.2-beta/reverse-proxy-configuration.mdx b/apps/docs/content/docs/v3-beta/reverse-proxy-configuration.mdx similarity index 95% rename from apps/docs/content/docs/3.2-beta/reverse-proxy-configuration.mdx rename to apps/docs/content/docs/v3-beta/reverse-proxy-configuration.mdx index 81cc6e74..76e35a2f 100644 --- a/apps/docs/content/docs/3.2-beta/reverse-proxy-configuration.mdx +++ b/apps/docs/content/docs/v3-beta/reverse-proxy-configuration.mdx @@ -127,11 +127,10 @@ proxy_pass_header Set-Cookie; environment: - PALMR_UID=1000 # Your host UID (check with: id) - PALMR_GID=1000 # Your host GID - - DISABLE_FILESYSTEM_ENCRYPTION=true # Set to false to enable file encryption - # - ENCRYPTION_KEY=your-key-here # Required only if encryption is enabled + ``` -> **💡 Note**: Check your host UID/GID with `id` command and use those values. See [UID/GID Configuration](/docs/3.2-beta/uid-gid-configuration) for detailed setup. +> **💡 Note**: Check your host UID/GID with `id` command and use those values. See [UID/GID Configuration](/docs/v3-beta/uid-gid-configuration) for detailed setup. --- diff --git a/apps/docs/content/docs/3.2-beta/s3-providers.mdx b/apps/docs/content/docs/v3-beta/s3-providers.mdx similarity index 100% rename from apps/docs/content/docs/3.2-beta/s3-providers.mdx rename to apps/docs/content/docs/v3-beta/s3-providers.mdx diff --git a/apps/docs/content/docs/3.2-beta/screenshots.mdx b/apps/docs/content/docs/v3-beta/screenshots.mdx similarity index 100% rename from apps/docs/content/docs/3.2-beta/screenshots.mdx rename to apps/docs/content/docs/v3-beta/screenshots.mdx diff --git a/apps/docs/content/docs/3.2-beta/translation-management.mdx b/apps/docs/content/docs/v3-beta/translation-management.mdx similarity index 100% rename from apps/docs/content/docs/3.2-beta/translation-management.mdx rename to apps/docs/content/docs/v3-beta/translation-management.mdx diff --git a/apps/docs/content/docs/3.2-beta/uid-gid-configuration.mdx b/apps/docs/content/docs/v3-beta/uid-gid-configuration.mdx similarity index 89% rename from apps/docs/content/docs/3.2-beta/uid-gid-configuration.mdx rename to apps/docs/content/docs/v3-beta/uid-gid-configuration.mdx index bcc646b8..223eb9ec 100644 --- a/apps/docs/content/docs/3.2-beta/uid-gid-configuration.mdx +++ b/apps/docs/content/docs/v3-beta/uid-gid-configuration.mdx @@ -23,13 +23,13 @@ Palmr. supports runtime UID/GID configuration to resolve permission conflicts wh ```bash # When you see errors like: -EACCES: permission denied, open '/app/server/uploads/file.txt' +EACCES: permission denied, open '/app/server/data/database.db' ``` ```bash # Or when checking permissions: -$ ls -la uploads/ -drwxr-xr-x 2 user user 4096 Jan 15 10:00 uploads/ +$ ls -la data/ +drwxr-xr-x 2 user user 4096 Jan 15 10:00 data/ # Container tries to write with different UID/GID than directory owner ``` @@ -47,11 +47,12 @@ services: environment: - PALMR_UID=1000 - PALMR_GID=1000 + - STORAGE_URL="https://your-domain:9379" ports: + - "9379:9379" - "5487:5487" volumes: - - ./uploads:/app/server/uploads:rw - - ./temp-uploads:/app/server/temp-uploads:rw + - ./data:/app/server restart: unless-stopped ``` @@ -60,9 +61,9 @@ services: If you prefer to keep Palmr's defaults: ```bash -# Create directories with correct ownership -mkdir -p uploads temp-uploads -chown -R 1001:1001 uploads temp-uploads +# Create directory with correct ownership +mkdir -p data +chown -R 1001:1001 data ``` ## Environment Variables @@ -104,7 +105,9 @@ services: environment: - PALMR_UID=1000 - PALMR_GID=1000 + - STORAGE_URL="https://your-domain:9379" ports: + - "9379:9379" - "5487:5487" volumes: - ./data:/app/server @@ -121,7 +124,9 @@ services: environment: - PALMR_UID=1026 - PALMR_GID=100 + - STORAGE_URL="https://your-synology-nas:9379" ports: + - "9379:9379" - "5487:5487" volumes: - /volume1/docker/palmr:/app/server @@ -138,7 +143,9 @@ services: environment: - PALMR_UID=1000 - PALMR_GID=100 + - STORAGE_URL="https://your-qnap-nas:9379" ports: + - "9379:9379" - "5487:5487" volumes: - /share/Container/palmr:/app/server @@ -158,7 +165,7 @@ services: id # 2. Check directory ownership -ls -la uploads/ temp-uploads/ +ls -la data/ # 3. Fix via environment variables (preferred) # Add to docker-compose.yaml: @@ -166,7 +173,7 @@ ls -la uploads/ temp-uploads/ # - PALMR_GID=1000 # 4. Or fix via chown (alternative) -chown -R 1001:1001 uploads temp-uploads +chown -R 1001:1001 data ``` **Error**: Container starts but files aren't accessible @@ -215,13 +222,12 @@ cat /etc/passwd | grep -v nobody **Docker bind mount issues:** ```bash -# Check if directories exist and are writable -test -w uploads && echo "uploads writable" || echo "uploads NOT writable" -test -w temp-uploads && echo "temp-uploads writable" || echo "temp-uploads NOT writable" +# Check if data directory exists and is writable +test -w data && echo "data writable" || echo "data NOT writable" -# Create directories with correct permissions -mkdir -p uploads temp-uploads -sudo chown -R $(id -u):$(id -g) uploads temp-uploads +# Create directory with correct permissions +mkdir -p data +sudo chown -R $(id -u):$(id -g) data ``` --- @@ -260,9 +266,6 @@ To add UID/GID configuration to running installations: ```bash cp -r ./data ./data-backup - # or - cp -r ./uploads ./uploads-backup - cp -r ./temp-uploads ./temp-uploads-backup ``` 3. **Check your UID/GID** diff --git a/apps/docs/package.json b/apps/docs/package.json index 21eff63f..57f940ac 100644 --- a/apps/docs/package.json +++ b/apps/docs/package.json @@ -1,6 +1,6 @@ { "name": "palmr-docs", - "version": "3.2.5-beta", + "version": "3.3.0-beta", "description": "Docs for Palmr", "private": true, "author": "Daniel Luiz Alves ", @@ -28,6 +28,7 @@ "validate": "pnpm lint && pnpm type-check" }, "dependencies": { + "@radix-ui/react-dialog": "^1.1.15", "class-variance-authority": "^0.7.1", "clsx": "^2.1.1", "fumadocs-core": "15.2.7", diff --git a/apps/docs/pnpm-lock.yaml b/apps/docs/pnpm-lock.yaml index d32871a8..639e153f 100644 --- a/apps/docs/pnpm-lock.yaml +++ b/apps/docs/pnpm-lock.yaml @@ -1,12 +1,16 @@ -lockfileVersion: "9.0" +lockfileVersion: '9.0' settings: autoInstallPeers: true excludeLinksFromLockfile: false importers: + .: dependencies: + '@radix-ui/react-dialog': + specifier: ^1.1.15 + version: 1.1.15(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) class-variance-authority: specifier: ^0.7.1 version: 0.7.1 @@ -41,34 +45,34 @@ importers: specifier: ^3.2.0 version: 3.3.1 devDependencies: - "@eslint/eslintrc": + '@eslint/eslintrc': specifier: 3.3.1 version: 3.3.1 - "@eslint/js": + '@eslint/js': specifier: 9.30.0 version: 9.30.0 - "@ianvs/prettier-plugin-sort-imports": + '@ianvs/prettier-plugin-sort-imports': specifier: 4.4.2 version: 4.4.2(prettier@3.6.2) - "@tailwindcss/postcss": + '@tailwindcss/postcss': specifier: ^4.1.11 version: 4.1.11 - "@types/mdx": + '@types/mdx': specifier: ^2.0.13 version: 2.0.13 - "@types/node": + '@types/node': specifier: 22.14.0 version: 22.14.0 - "@types/react": + '@types/react': specifier: ^19.1.8 version: 19.1.8 - "@types/react-dom": + '@types/react-dom': specifier: ^19.1.6 version: 19.1.6(@types/react@19.1.8) - "@typescript-eslint/eslint-plugin": + '@typescript-eslint/eslint-plugin': specifier: 8.35.1 version: 8.35.1(@typescript-eslint/parser@8.35.1(eslint@9.30.0(jiti@2.4.2))(typescript@5.8.3))(eslint@9.30.0(jiti@2.4.2))(typescript@5.8.3) - "@typescript-eslint/parser": + '@typescript-eslint/parser': specifier: 8.35.1 version: 8.35.1(eslint@9.30.0(jiti@2.4.2))(typescript@5.8.3) eslint: @@ -103,1855 +107,1593 @@ importers: version: 5.8.3 packages: - "@alloc/quick-lru@5.2.0": - resolution: - { integrity: sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw== } - engines: { node: ">=10" } - - "@ampproject/remapping@2.3.0": - resolution: - { integrity: sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw== } - engines: { node: ">=6.0.0" } - - "@babel/code-frame@7.27.1": - resolution: - { integrity: sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg== } - engines: { node: ">=6.9.0" } - - "@babel/generator@7.28.0": - resolution: - { integrity: sha512-lJjzvrbEeWrhB4P3QBsH7tey117PjLZnDbLiQEKjQ/fNJTjuq4HSqgFA+UNSwZT8D7dxxbnuSBMsa1lrWzKlQg== } - engines: { node: ">=6.9.0" } - - "@babel/helper-globals@7.28.0": - resolution: - { integrity: sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw== } - engines: { node: ">=6.9.0" } - - "@babel/helper-string-parser@7.27.1": - resolution: - { integrity: sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA== } - engines: { node: ">=6.9.0" } - - "@babel/helper-validator-identifier@7.27.1": - resolution: - { integrity: sha512-D2hP9eA+Sqx1kBZgzxZh0y1trbuU+JoDkiEwqhQ36nodYqJwyEIhPSdMNd7lOm/4io72luTPWH20Yda0xOuUow== } - engines: { node: ">=6.9.0" } - - "@babel/parser@7.28.0": - resolution: - { integrity: sha512-jVZGvOxOuNSsuQuLRTh13nU0AogFlw32w/MT+LV6D3sP5WdbW61E77RnkbaO2dUvmPAYrBDJXGn5gGS6tH4j8g== } - engines: { node: ">=6.0.0" } + + '@alloc/quick-lru@5.2.0': + resolution: {integrity: sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw==} + engines: {node: '>=10'} + + '@ampproject/remapping@2.3.0': + resolution: {integrity: sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==} + engines: {node: '>=6.0.0'} + + '@babel/code-frame@7.27.1': + resolution: {integrity: sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg==} + engines: {node: '>=6.9.0'} + + '@babel/generator@7.28.0': + resolution: {integrity: sha512-lJjzvrbEeWrhB4P3QBsH7tey117PjLZnDbLiQEKjQ/fNJTjuq4HSqgFA+UNSwZT8D7dxxbnuSBMsa1lrWzKlQg==} + engines: {node: '>=6.9.0'} + + '@babel/helper-globals@7.28.0': + resolution: {integrity: sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw==} + engines: {node: '>=6.9.0'} + + '@babel/helper-string-parser@7.27.1': + resolution: {integrity: sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==} + engines: {node: '>=6.9.0'} + + '@babel/helper-validator-identifier@7.27.1': + resolution: {integrity: sha512-D2hP9eA+Sqx1kBZgzxZh0y1trbuU+JoDkiEwqhQ36nodYqJwyEIhPSdMNd7lOm/4io72luTPWH20Yda0xOuUow==} + engines: {node: '>=6.9.0'} + + '@babel/parser@7.28.0': + resolution: {integrity: sha512-jVZGvOxOuNSsuQuLRTh13nU0AogFlw32w/MT+LV6D3sP5WdbW61E77RnkbaO2dUvmPAYrBDJXGn5gGS6tH4j8g==} + engines: {node: '>=6.0.0'} hasBin: true - "@babel/template@7.27.2": - resolution: - { integrity: sha512-LPDZ85aEJyYSd18/DkjNh4/y1ntkE5KwUHWTiqgRxruuZL2F1yuHligVHLvcHY2vMHXttKFpJn6LwfI7cw7ODw== } - engines: { node: ">=6.9.0" } - - "@babel/traverse@7.28.0": - resolution: - { integrity: sha512-mGe7UK5wWyh0bKRfupsUchrQGqvDbZDbKJw+kcRGSmdHVYrv+ltd0pnpDTVpiTqnaBru9iEvA8pz8W46v0Amwg== } - engines: { node: ">=6.9.0" } - - "@babel/types@7.28.0": - resolution: - { integrity: sha512-jYnje+JyZG5YThjHiF28oT4SIZLnYOcSBb6+SDaFIyzDVSkXQmQQYclJ2R+YxcdmK0AX6x1E5OQNtuh3jHDrUg== } - engines: { node: ">=6.9.0" } - - "@emnapi/core@1.4.3": - resolution: - { integrity: sha512-4m62DuCE07lw01soJwPiBGC0nAww0Q+RY70VZ+n49yDIO13yyinhbWCeNnaob0lakDtWQzSdtNWzJeOJt2ma+g== } - - "@emnapi/runtime@1.4.3": - resolution: - { integrity: sha512-pBPWdu6MLKROBX05wSNKcNb++m5Er+KQ9QkB+WVM+pW2Kx9hoSrVTnu3BdkI5eBLZoKu/J6mW/B6i6bJB2ytXQ== } - - "@emnapi/wasi-threads@1.0.2": - resolution: - { integrity: sha512-5n3nTJblwRi8LlXkJ9eBzu+kZR8Yxcc7ubakyQTFzPMtIhFpUBRbsnc2Dv88IZDIbCDlBiWrknhB4Lsz7mg6BA== } - - "@esbuild/aix-ppc64@0.25.5": - resolution: - { integrity: sha512-9o3TMmpmftaCMepOdA5k/yDw8SfInyzWWTjYTFCX3kPSDJMROQTb8jg+h9Cnwnmm1vOzvxN7gIfB5V2ewpjtGA== } - engines: { node: ">=18" } + '@babel/template@7.27.2': + resolution: {integrity: sha512-LPDZ85aEJyYSd18/DkjNh4/y1ntkE5KwUHWTiqgRxruuZL2F1yuHligVHLvcHY2vMHXttKFpJn6LwfI7cw7ODw==} + engines: {node: '>=6.9.0'} + + '@babel/traverse@7.28.0': + resolution: {integrity: sha512-mGe7UK5wWyh0bKRfupsUchrQGqvDbZDbKJw+kcRGSmdHVYrv+ltd0pnpDTVpiTqnaBru9iEvA8pz8W46v0Amwg==} + engines: {node: '>=6.9.0'} + + '@babel/types@7.28.0': + resolution: {integrity: sha512-jYnje+JyZG5YThjHiF28oT4SIZLnYOcSBb6+SDaFIyzDVSkXQmQQYclJ2R+YxcdmK0AX6x1E5OQNtuh3jHDrUg==} + engines: {node: '>=6.9.0'} + + '@emnapi/core@1.4.3': + resolution: {integrity: sha512-4m62DuCE07lw01soJwPiBGC0nAww0Q+RY70VZ+n49yDIO13yyinhbWCeNnaob0lakDtWQzSdtNWzJeOJt2ma+g==} + + '@emnapi/runtime@1.4.3': + resolution: {integrity: sha512-pBPWdu6MLKROBX05wSNKcNb++m5Er+KQ9QkB+WVM+pW2Kx9hoSrVTnu3BdkI5eBLZoKu/J6mW/B6i6bJB2ytXQ==} + + '@emnapi/wasi-threads@1.0.2': + resolution: {integrity: sha512-5n3nTJblwRi8LlXkJ9eBzu+kZR8Yxcc7ubakyQTFzPMtIhFpUBRbsnc2Dv88IZDIbCDlBiWrknhB4Lsz7mg6BA==} + + '@esbuild/aix-ppc64@0.25.5': + resolution: {integrity: sha512-9o3TMmpmftaCMepOdA5k/yDw8SfInyzWWTjYTFCX3kPSDJMROQTb8jg+h9Cnwnmm1vOzvxN7gIfB5V2ewpjtGA==} + engines: {node: '>=18'} cpu: [ppc64] os: [aix] - "@esbuild/android-arm64@0.25.5": - resolution: - { integrity: sha512-VGzGhj4lJO+TVGV1v8ntCZWJktV7SGCs3Pn1GRWI1SBFtRALoomm8k5E9Pmwg3HOAal2VDc2F9+PM/rEY6oIDg== } - engines: { node: ">=18" } + '@esbuild/android-arm64@0.25.5': + resolution: {integrity: sha512-VGzGhj4lJO+TVGV1v8ntCZWJktV7SGCs3Pn1GRWI1SBFtRALoomm8k5E9Pmwg3HOAal2VDc2F9+PM/rEY6oIDg==} + engines: {node: '>=18'} cpu: [arm64] os: [android] - "@esbuild/android-arm@0.25.5": - resolution: - { integrity: sha512-AdJKSPeEHgi7/ZhuIPtcQKr5RQdo6OO2IL87JkianiMYMPbCtot9fxPbrMiBADOWWm3T2si9stAiVsGbTQFkbA== } - engines: { node: ">=18" } + '@esbuild/android-arm@0.25.5': + resolution: {integrity: sha512-AdJKSPeEHgi7/ZhuIPtcQKr5RQdo6OO2IL87JkianiMYMPbCtot9fxPbrMiBADOWWm3T2si9stAiVsGbTQFkbA==} + engines: {node: '>=18'} cpu: [arm] os: [android] - "@esbuild/android-x64@0.25.5": - resolution: - { integrity: sha512-D2GyJT1kjvO//drbRT3Hib9XPwQeWd9vZoBJn+bu/lVsOZ13cqNdDeqIF/xQ5/VmWvMduP6AmXvylO/PIc2isw== } - engines: { node: ">=18" } + '@esbuild/android-x64@0.25.5': + resolution: {integrity: sha512-D2GyJT1kjvO//drbRT3Hib9XPwQeWd9vZoBJn+bu/lVsOZ13cqNdDeqIF/xQ5/VmWvMduP6AmXvylO/PIc2isw==} + engines: {node: '>=18'} cpu: [x64] os: [android] - "@esbuild/darwin-arm64@0.25.5": - resolution: - { integrity: sha512-GtaBgammVvdF7aPIgH2jxMDdivezgFu6iKpmT+48+F8Hhg5J/sfnDieg0aeG/jfSvkYQU2/pceFPDKlqZzwnfQ== } - engines: { node: ">=18" } + '@esbuild/darwin-arm64@0.25.5': + resolution: {integrity: sha512-GtaBgammVvdF7aPIgH2jxMDdivezgFu6iKpmT+48+F8Hhg5J/sfnDieg0aeG/jfSvkYQU2/pceFPDKlqZzwnfQ==} + engines: {node: '>=18'} cpu: [arm64] os: [darwin] - "@esbuild/darwin-x64@0.25.5": - resolution: - { integrity: sha512-1iT4FVL0dJ76/q1wd7XDsXrSW+oLoquptvh4CLR4kITDtqi2e/xwXwdCVH8hVHU43wgJdsq7Gxuzcs6Iq/7bxQ== } - engines: { node: ">=18" } + '@esbuild/darwin-x64@0.25.5': + resolution: {integrity: sha512-1iT4FVL0dJ76/q1wd7XDsXrSW+oLoquptvh4CLR4kITDtqi2e/xwXwdCVH8hVHU43wgJdsq7Gxuzcs6Iq/7bxQ==} + engines: {node: '>=18'} cpu: [x64] os: [darwin] - "@esbuild/freebsd-arm64@0.25.5": - resolution: - { integrity: sha512-nk4tGP3JThz4La38Uy/gzyXtpkPW8zSAmoUhK9xKKXdBCzKODMc2adkB2+8om9BDYugz+uGV7sLmpTYzvmz6Sw== } - engines: { node: ">=18" } + '@esbuild/freebsd-arm64@0.25.5': + resolution: {integrity: sha512-nk4tGP3JThz4La38Uy/gzyXtpkPW8zSAmoUhK9xKKXdBCzKODMc2adkB2+8om9BDYugz+uGV7sLmpTYzvmz6Sw==} + engines: {node: '>=18'} cpu: [arm64] os: [freebsd] - "@esbuild/freebsd-x64@0.25.5": - resolution: - { integrity: sha512-PrikaNjiXdR2laW6OIjlbeuCPrPaAl0IwPIaRv+SMV8CiM8i2LqVUHFC1+8eORgWyY7yhQY+2U2fA55mBzReaw== } - engines: { node: ">=18" } + '@esbuild/freebsd-x64@0.25.5': + resolution: {integrity: sha512-PrikaNjiXdR2laW6OIjlbeuCPrPaAl0IwPIaRv+SMV8CiM8i2LqVUHFC1+8eORgWyY7yhQY+2U2fA55mBzReaw==} + engines: {node: '>=18'} cpu: [x64] os: [freebsd] - "@esbuild/linux-arm64@0.25.5": - resolution: - { integrity: sha512-Z9kfb1v6ZlGbWj8EJk9T6czVEjjq2ntSYLY2cw6pAZl4oKtfgQuS4HOq41M/BcoLPzrUbNd+R4BXFyH//nHxVg== } - engines: { node: ">=18" } + '@esbuild/linux-arm64@0.25.5': + resolution: {integrity: sha512-Z9kfb1v6ZlGbWj8EJk9T6czVEjjq2ntSYLY2cw6pAZl4oKtfgQuS4HOq41M/BcoLPzrUbNd+R4BXFyH//nHxVg==} + engines: {node: '>=18'} cpu: [arm64] os: [linux] - "@esbuild/linux-arm@0.25.5": - resolution: - { integrity: sha512-cPzojwW2okgh7ZlRpcBEtsX7WBuqbLrNXqLU89GxWbNt6uIg78ET82qifUy3W6OVww6ZWobWub5oqZOVtwolfw== } - engines: { node: ">=18" } + '@esbuild/linux-arm@0.25.5': + resolution: {integrity: sha512-cPzojwW2okgh7ZlRpcBEtsX7WBuqbLrNXqLU89GxWbNt6uIg78ET82qifUy3W6OVww6ZWobWub5oqZOVtwolfw==} + engines: {node: '>=18'} cpu: [arm] os: [linux] - "@esbuild/linux-ia32@0.25.5": - resolution: - { integrity: sha512-sQ7l00M8bSv36GLV95BVAdhJ2QsIbCuCjh/uYrWiMQSUuV+LpXwIqhgJDcvMTj+VsQmqAHL2yYaasENvJ7CDKA== } - engines: { node: ">=18" } + '@esbuild/linux-ia32@0.25.5': + resolution: {integrity: sha512-sQ7l00M8bSv36GLV95BVAdhJ2QsIbCuCjh/uYrWiMQSUuV+LpXwIqhgJDcvMTj+VsQmqAHL2yYaasENvJ7CDKA==} + engines: {node: '>=18'} cpu: [ia32] os: [linux] - "@esbuild/linux-loong64@0.25.5": - resolution: - { integrity: sha512-0ur7ae16hDUC4OL5iEnDb0tZHDxYmuQyhKhsPBV8f99f6Z9KQM02g33f93rNH5A30agMS46u2HP6qTdEt6Q1kg== } - engines: { node: ">=18" } + '@esbuild/linux-loong64@0.25.5': + resolution: {integrity: sha512-0ur7ae16hDUC4OL5iEnDb0tZHDxYmuQyhKhsPBV8f99f6Z9KQM02g33f93rNH5A30agMS46u2HP6qTdEt6Q1kg==} + engines: {node: '>=18'} cpu: [loong64] os: [linux] - "@esbuild/linux-mips64el@0.25.5": - resolution: - { integrity: sha512-kB/66P1OsHO5zLz0i6X0RxlQ+3cu0mkxS3TKFvkb5lin6uwZ/ttOkP3Z8lfR9mJOBk14ZwZ9182SIIWFGNmqmg== } - engines: { node: ">=18" } + '@esbuild/linux-mips64el@0.25.5': + resolution: {integrity: sha512-kB/66P1OsHO5zLz0i6X0RxlQ+3cu0mkxS3TKFvkb5lin6uwZ/ttOkP3Z8lfR9mJOBk14ZwZ9182SIIWFGNmqmg==} + engines: {node: '>=18'} cpu: [mips64el] os: [linux] - "@esbuild/linux-ppc64@0.25.5": - resolution: - { integrity: sha512-UZCmJ7r9X2fe2D6jBmkLBMQetXPXIsZjQJCjgwpVDz+YMcS6oFR27alkgGv3Oqkv07bxdvw7fyB71/olceJhkQ== } - engines: { node: ">=18" } + '@esbuild/linux-ppc64@0.25.5': + resolution: {integrity: sha512-UZCmJ7r9X2fe2D6jBmkLBMQetXPXIsZjQJCjgwpVDz+YMcS6oFR27alkgGv3Oqkv07bxdvw7fyB71/olceJhkQ==} + engines: {node: '>=18'} cpu: [ppc64] os: [linux] - "@esbuild/linux-riscv64@0.25.5": - resolution: - { integrity: sha512-kTxwu4mLyeOlsVIFPfQo+fQJAV9mh24xL+y+Bm6ej067sYANjyEw1dNHmvoqxJUCMnkBdKpvOn0Ahql6+4VyeA== } - engines: { node: ">=18" } + '@esbuild/linux-riscv64@0.25.5': + resolution: {integrity: sha512-kTxwu4mLyeOlsVIFPfQo+fQJAV9mh24xL+y+Bm6ej067sYANjyEw1dNHmvoqxJUCMnkBdKpvOn0Ahql6+4VyeA==} + engines: {node: '>=18'} cpu: [riscv64] os: [linux] - "@esbuild/linux-s390x@0.25.5": - resolution: - { integrity: sha512-K2dSKTKfmdh78uJ3NcWFiqyRrimfdinS5ErLSn3vluHNeHVnBAFWC8a4X5N+7FgVE1EjXS1QDZbpqZBjfrqMTQ== } - engines: { node: ">=18" } + '@esbuild/linux-s390x@0.25.5': + resolution: {integrity: sha512-K2dSKTKfmdh78uJ3NcWFiqyRrimfdinS5ErLSn3vluHNeHVnBAFWC8a4X5N+7FgVE1EjXS1QDZbpqZBjfrqMTQ==} + engines: {node: '>=18'} cpu: [s390x] os: [linux] - "@esbuild/linux-x64@0.25.5": - resolution: - { integrity: sha512-uhj8N2obKTE6pSZ+aMUbqq+1nXxNjZIIjCjGLfsWvVpy7gKCOL6rsY1MhRh9zLtUtAI7vpgLMK6DxjO8Qm9lJw== } - engines: { node: ">=18" } + '@esbuild/linux-x64@0.25.5': + resolution: {integrity: sha512-uhj8N2obKTE6pSZ+aMUbqq+1nXxNjZIIjCjGLfsWvVpy7gKCOL6rsY1MhRh9zLtUtAI7vpgLMK6DxjO8Qm9lJw==} + engines: {node: '>=18'} cpu: [x64] os: [linux] - "@esbuild/netbsd-arm64@0.25.5": - resolution: - { integrity: sha512-pwHtMP9viAy1oHPvgxtOv+OkduK5ugofNTVDilIzBLpoWAM16r7b/mxBvfpuQDpRQFMfuVr5aLcn4yveGvBZvw== } - engines: { node: ">=18" } + '@esbuild/netbsd-arm64@0.25.5': + resolution: {integrity: sha512-pwHtMP9viAy1oHPvgxtOv+OkduK5ugofNTVDilIzBLpoWAM16r7b/mxBvfpuQDpRQFMfuVr5aLcn4yveGvBZvw==} + engines: {node: '>=18'} cpu: [arm64] os: [netbsd] - "@esbuild/netbsd-x64@0.25.5": - resolution: - { integrity: sha512-WOb5fKrvVTRMfWFNCroYWWklbnXH0Q5rZppjq0vQIdlsQKuw6mdSihwSo4RV/YdQ5UCKKvBy7/0ZZYLBZKIbwQ== } - engines: { node: ">=18" } + '@esbuild/netbsd-x64@0.25.5': + resolution: {integrity: sha512-WOb5fKrvVTRMfWFNCroYWWklbnXH0Q5rZppjq0vQIdlsQKuw6mdSihwSo4RV/YdQ5UCKKvBy7/0ZZYLBZKIbwQ==} + engines: {node: '>=18'} cpu: [x64] os: [netbsd] - "@esbuild/openbsd-arm64@0.25.5": - resolution: - { integrity: sha512-7A208+uQKgTxHd0G0uqZO8UjK2R0DDb4fDmERtARjSHWxqMTye4Erz4zZafx7Di9Cv+lNHYuncAkiGFySoD+Mw== } - engines: { node: ">=18" } + '@esbuild/openbsd-arm64@0.25.5': + resolution: {integrity: sha512-7A208+uQKgTxHd0G0uqZO8UjK2R0DDb4fDmERtARjSHWxqMTye4Erz4zZafx7Di9Cv+lNHYuncAkiGFySoD+Mw==} + engines: {node: '>=18'} cpu: [arm64] os: [openbsd] - "@esbuild/openbsd-x64@0.25.5": - resolution: - { integrity: sha512-G4hE405ErTWraiZ8UiSoesH8DaCsMm0Cay4fsFWOOUcz8b8rC6uCvnagr+gnioEjWn0wC+o1/TAHt+It+MpIMg== } - engines: { node: ">=18" } + '@esbuild/openbsd-x64@0.25.5': + resolution: {integrity: sha512-G4hE405ErTWraiZ8UiSoesH8DaCsMm0Cay4fsFWOOUcz8b8rC6uCvnagr+gnioEjWn0wC+o1/TAHt+It+MpIMg==} + engines: {node: '>=18'} cpu: [x64] os: [openbsd] - "@esbuild/sunos-x64@0.25.5": - resolution: - { integrity: sha512-l+azKShMy7FxzY0Rj4RCt5VD/q8mG/e+mDivgspo+yL8zW7qEwctQ6YqKX34DTEleFAvCIUviCFX1SDZRSyMQA== } - engines: { node: ">=18" } + '@esbuild/sunos-x64@0.25.5': + resolution: {integrity: sha512-l+azKShMy7FxzY0Rj4RCt5VD/q8mG/e+mDivgspo+yL8zW7qEwctQ6YqKX34DTEleFAvCIUviCFX1SDZRSyMQA==} + engines: {node: '>=18'} cpu: [x64] os: [sunos] - "@esbuild/win32-arm64@0.25.5": - resolution: - { integrity: sha512-O2S7SNZzdcFG7eFKgvwUEZ2VG9D/sn/eIiz8XRZ1Q/DO5a3s76Xv0mdBzVM5j5R639lXQmPmSo0iRpHqUUrsxw== } - engines: { node: ">=18" } + '@esbuild/win32-arm64@0.25.5': + resolution: {integrity: sha512-O2S7SNZzdcFG7eFKgvwUEZ2VG9D/sn/eIiz8XRZ1Q/DO5a3s76Xv0mdBzVM5j5R639lXQmPmSo0iRpHqUUrsxw==} + engines: {node: '>=18'} cpu: [arm64] os: [win32] - "@esbuild/win32-ia32@0.25.5": - resolution: - { integrity: sha512-onOJ02pqs9h1iMJ1PQphR+VZv8qBMQ77Klcsqv9CNW2w6yLqoURLcgERAIurY6QE63bbLuqgP9ATqajFLK5AMQ== } - engines: { node: ">=18" } + '@esbuild/win32-ia32@0.25.5': + resolution: {integrity: sha512-onOJ02pqs9h1iMJ1PQphR+VZv8qBMQ77Klcsqv9CNW2w6yLqoURLcgERAIurY6QE63bbLuqgP9ATqajFLK5AMQ==} + engines: {node: '>=18'} cpu: [ia32] os: [win32] - "@esbuild/win32-x64@0.25.5": - resolution: - { integrity: sha512-TXv6YnJ8ZMVdX+SXWVBo/0p8LTcrUYngpWjvm91TMjjBQii7Oz11Lw5lbDV5Y0TzuhSJHwiH4hEtC1I42mMS0g== } - engines: { node: ">=18" } + '@esbuild/win32-x64@0.25.5': + resolution: {integrity: sha512-TXv6YnJ8ZMVdX+SXWVBo/0p8LTcrUYngpWjvm91TMjjBQii7Oz11Lw5lbDV5Y0TzuhSJHwiH4hEtC1I42mMS0g==} + engines: {node: '>=18'} cpu: [x64] os: [win32] - "@eslint-community/eslint-utils@4.7.0": - resolution: - { integrity: sha512-dyybb3AcajC7uha6CvhdVRJqaKyn7w2YKqKyAN37NKYgZT36w+iRb0Dymmc5qEJ549c/S31cMMSFd75bteCpCw== } - engines: { node: ^12.22.0 || ^14.17.0 || >=16.0.0 } + '@eslint-community/eslint-utils@4.7.0': + resolution: {integrity: sha512-dyybb3AcajC7uha6CvhdVRJqaKyn7w2YKqKyAN37NKYgZT36w+iRb0Dymmc5qEJ549c/S31cMMSFd75bteCpCw==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} peerDependencies: eslint: ^6.0.0 || ^7.0.0 || >=8.0.0 - "@eslint-community/regexpp@4.12.1": - resolution: - { integrity: sha512-CCZCDJuduB9OUkFkY2IgppNZMi2lBQgD2qzwXkEia16cge2pijY/aXi96CJMquDMn3nJdlPV1A5KrJEXwfLNzQ== } - engines: { node: ^12.0.0 || ^14.0.0 || >=16.0.0 } - - "@eslint/config-array@0.21.0": - resolution: - { integrity: sha512-ENIdc4iLu0d93HeYirvKmrzshzofPw6VkZRKQGe9Nv46ZnWUzcF1xV01dcvEg/1wXUR61OmmlSfyeyO7EvjLxQ== } - engines: { node: ^18.18.0 || ^20.9.0 || >=21.1.0 } - - "@eslint/config-helpers@0.3.0": - resolution: - { integrity: sha512-ViuymvFmcJi04qdZeDc2whTHryouGcDlaxPqarTD0ZE10ISpxGUVZGZDx4w01upyIynL3iu6IXH2bS1NhclQMw== } - engines: { node: ^18.18.0 || ^20.9.0 || >=21.1.0 } - - "@eslint/core@0.14.0": - resolution: - { integrity: sha512-qIbV0/JZr7iSDjqAc60IqbLdsj9GDt16xQtWD+B78d/HAlvysGdZZ6rpJHGAc2T0FQx1X6thsSPdnoiGKdNtdg== } - engines: { node: ^18.18.0 || ^20.9.0 || >=21.1.0 } - - "@eslint/core@0.15.1": - resolution: - { integrity: sha512-bkOp+iumZCCbt1K1CmWf0R9pM5yKpDv+ZXtvSyQpudrI9kuFLp+bM2WOPXImuD/ceQuaa8f5pj93Y7zyECIGNA== } - engines: { node: ^18.18.0 || ^20.9.0 || >=21.1.0 } - - "@eslint/eslintrc@3.3.1": - resolution: - { integrity: sha512-gtF186CXhIl1p4pJNGZw8Yc6RlshoePRvE0X91oPGb3vZ8pM3qOS9W9NGPat9LziaBV7XrJWGylNQXkGcnM3IQ== } - engines: { node: ^18.18.0 || ^20.9.0 || >=21.1.0 } - - "@eslint/js@9.30.0": - resolution: - { integrity: sha512-Wzw3wQwPvc9sHM+NjakWTcPx11mbZyiYHuwWa/QfZ7cIRX7WK54PSk7bdyXDaoaopUcMatv1zaQvOAAO8hCdww== } - engines: { node: ^18.18.0 || ^20.9.0 || >=21.1.0 } - - "@eslint/object-schema@2.1.6": - resolution: - { integrity: sha512-RBMg5FRL0I0gs51M/guSAj5/e14VQ4tpZnQNWwuDT66P14I43ItmPfIZRhO9fUVIPOAQXU47atlywZ/czoqFPA== } - engines: { node: ^18.18.0 || ^20.9.0 || >=21.1.0 } - - "@eslint/plugin-kit@0.3.3": - resolution: - { integrity: sha512-1+WqvgNMhmlAambTvT3KPtCl/Ibr68VldY2XY40SL1CE0ZXiakFR/cbTspaF5HsnpDMvcYYoJHfl4980NBjGag== } - engines: { node: ^18.18.0 || ^20.9.0 || >=21.1.0 } - - "@floating-ui/core@1.7.2": - resolution: - { integrity: sha512-wNB5ooIKHQc+Kui96jE/n69rHFWAVoxn5CAzL1Xdd8FG03cgY3MLO+GF9U3W737fYDSgPWA6MReKhBQBop6Pcw== } - - "@floating-ui/dom@1.7.2": - resolution: - { integrity: sha512-7cfaOQuCS27HD7DX+6ib2OrnW+b4ZBwDNnCcT0uTyidcmyWb03FnQqJybDBoCnpdxwBSfA94UAYlRCt7mV+TbA== } - - "@floating-ui/react-dom@2.1.4": - resolution: - { integrity: sha512-JbbpPhp38UmXDDAu60RJmbeme37Jbgsm7NrHGgzYYFKmblzRUh6Pa641dII6LsjwF4XlScDrde2UAzDo/b9KPw== } + '@eslint-community/regexpp@4.12.1': + resolution: {integrity: sha512-CCZCDJuduB9OUkFkY2IgppNZMi2lBQgD2qzwXkEia16cge2pijY/aXi96CJMquDMn3nJdlPV1A5KrJEXwfLNzQ==} + engines: {node: ^12.0.0 || ^14.0.0 || >=16.0.0} + + '@eslint/config-array@0.21.0': + resolution: {integrity: sha512-ENIdc4iLu0d93HeYirvKmrzshzofPw6VkZRKQGe9Nv46ZnWUzcF1xV01dcvEg/1wXUR61OmmlSfyeyO7EvjLxQ==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@eslint/config-helpers@0.3.0': + resolution: {integrity: sha512-ViuymvFmcJi04qdZeDc2whTHryouGcDlaxPqarTD0ZE10ISpxGUVZGZDx4w01upyIynL3iu6IXH2bS1NhclQMw==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@eslint/core@0.14.0': + resolution: {integrity: sha512-qIbV0/JZr7iSDjqAc60IqbLdsj9GDt16xQtWD+B78d/HAlvysGdZZ6rpJHGAc2T0FQx1X6thsSPdnoiGKdNtdg==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@eslint/core@0.15.1': + resolution: {integrity: sha512-bkOp+iumZCCbt1K1CmWf0R9pM5yKpDv+ZXtvSyQpudrI9kuFLp+bM2WOPXImuD/ceQuaa8f5pj93Y7zyECIGNA==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@eslint/eslintrc@3.3.1': + resolution: {integrity: sha512-gtF186CXhIl1p4pJNGZw8Yc6RlshoePRvE0X91oPGb3vZ8pM3qOS9W9NGPat9LziaBV7XrJWGylNQXkGcnM3IQ==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@eslint/js@9.30.0': + resolution: {integrity: sha512-Wzw3wQwPvc9sHM+NjakWTcPx11mbZyiYHuwWa/QfZ7cIRX7WK54PSk7bdyXDaoaopUcMatv1zaQvOAAO8hCdww==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@eslint/object-schema@2.1.6': + resolution: {integrity: sha512-RBMg5FRL0I0gs51M/guSAj5/e14VQ4tpZnQNWwuDT66P14I43ItmPfIZRhO9fUVIPOAQXU47atlywZ/czoqFPA==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@eslint/plugin-kit@0.3.3': + resolution: {integrity: sha512-1+WqvgNMhmlAambTvT3KPtCl/Ibr68VldY2XY40SL1CE0ZXiakFR/cbTspaF5HsnpDMvcYYoJHfl4980NBjGag==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@floating-ui/core@1.7.2': + resolution: {integrity: sha512-wNB5ooIKHQc+Kui96jE/n69rHFWAVoxn5CAzL1Xdd8FG03cgY3MLO+GF9U3W737fYDSgPWA6MReKhBQBop6Pcw==} + + '@floating-ui/dom@1.7.2': + resolution: {integrity: sha512-7cfaOQuCS27HD7DX+6ib2OrnW+b4ZBwDNnCcT0uTyidcmyWb03FnQqJybDBoCnpdxwBSfA94UAYlRCt7mV+TbA==} + + '@floating-ui/react-dom@2.1.4': + resolution: {integrity: sha512-JbbpPhp38UmXDDAu60RJmbeme37Jbgsm7NrHGgzYYFKmblzRUh6Pa641dII6LsjwF4XlScDrde2UAzDo/b9KPw==} peerDependencies: - react: ">=16.8.0" - react-dom: ">=16.8.0" - - "@floating-ui/utils@0.2.10": - resolution: - { integrity: sha512-aGTxbpbg8/b5JfU1HXSrbH3wXZuLPJcNEcZQFMxLs3oSzgtVu6nFPkbbGGUvBcUjKV2YyB9Wxxabo+HEH9tcRQ== } - - "@formatjs/intl-localematcher@0.6.1": - resolution: - { integrity: sha512-ePEgLgVCqi2BBFnTMWPfIghu6FkbZnnBVhO2sSxvLfrdFw7wCHAHiDoM2h4NRgjbaY7+B7HgOLZGkK187pZTZg== } - - "@humanfs/core@0.19.1": - resolution: - { integrity: sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA== } - engines: { node: ">=18.18.0" } - - "@humanfs/node@0.16.6": - resolution: - { integrity: sha512-YuI2ZHQL78Q5HbhDiBA1X4LmYdXCKCMQIfw0pw7piHJwyREFebJUvrQN4cMssyES6x+vfUbx1CIpaQUKYdQZOw== } - engines: { node: ">=18.18.0" } - - "@humanwhocodes/module-importer@1.0.1": - resolution: - { integrity: sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA== } - engines: { node: ">=12.22" } - - "@humanwhocodes/retry@0.3.1": - resolution: - { integrity: sha512-JBxkERygn7Bv/GbN5Rv8Ul6LVknS+5Bp6RgDC/O8gEBU/yeH5Ui5C/OlWrTb6qct7LjjfT6Re2NxB0ln0yYybA== } - engines: { node: ">=18.18" } - - "@humanwhocodes/retry@0.4.3": - resolution: - { integrity: sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ== } - engines: { node: ">=18.18" } - - "@ianvs/prettier-plugin-sort-imports@4.4.2": - resolution: - { integrity: sha512-KkVFy3TLh0OFzimbZglMmORi+vL/i2OFhEs5M07R9w0IwWAGpsNNyE4CY/2u0YoMF5bawKC2+8/fUH60nnNtjw== } + react: '>=16.8.0' + react-dom: '>=16.8.0' + + '@floating-ui/utils@0.2.10': + resolution: {integrity: sha512-aGTxbpbg8/b5JfU1HXSrbH3wXZuLPJcNEcZQFMxLs3oSzgtVu6nFPkbbGGUvBcUjKV2YyB9Wxxabo+HEH9tcRQ==} + + '@formatjs/intl-localematcher@0.6.1': + resolution: {integrity: sha512-ePEgLgVCqi2BBFnTMWPfIghu6FkbZnnBVhO2sSxvLfrdFw7wCHAHiDoM2h4NRgjbaY7+B7HgOLZGkK187pZTZg==} + + '@humanfs/core@0.19.1': + resolution: {integrity: sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==} + engines: {node: '>=18.18.0'} + + '@humanfs/node@0.16.6': + resolution: {integrity: sha512-YuI2ZHQL78Q5HbhDiBA1X4LmYdXCKCMQIfw0pw7piHJwyREFebJUvrQN4cMssyES6x+vfUbx1CIpaQUKYdQZOw==} + engines: {node: '>=18.18.0'} + + '@humanwhocodes/module-importer@1.0.1': + resolution: {integrity: sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==} + engines: {node: '>=12.22'} + + '@humanwhocodes/retry@0.3.1': + resolution: {integrity: sha512-JBxkERygn7Bv/GbN5Rv8Ul6LVknS+5Bp6RgDC/O8gEBU/yeH5Ui5C/OlWrTb6qct7LjjfT6Re2NxB0ln0yYybA==} + engines: {node: '>=18.18'} + + '@humanwhocodes/retry@0.4.3': + resolution: {integrity: sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==} + engines: {node: '>=18.18'} + + '@ianvs/prettier-plugin-sort-imports@4.4.2': + resolution: {integrity: sha512-KkVFy3TLh0OFzimbZglMmORi+vL/i2OFhEs5M07R9w0IwWAGpsNNyE4CY/2u0YoMF5bawKC2+8/fUH60nnNtjw==} peerDependencies: - "@vue/compiler-sfc": 2.7.x || 3.x + '@vue/compiler-sfc': 2.7.x || 3.x prettier: 2 || 3 || ^4.0.0-0 peerDependenciesMeta: - "@vue/compiler-sfc": + '@vue/compiler-sfc': optional: true - "@img/sharp-darwin-arm64@0.34.2": - resolution: - { integrity: sha512-OfXHZPppddivUJnqyKoi5YVeHRkkNE2zUFT2gbpKxp/JZCFYEYubnMg+gOp6lWfasPrTS+KPosKqdI+ELYVDtg== } - engines: { node: ^18.17.0 || ^20.3.0 || >=21.0.0 } + '@img/sharp-darwin-arm64@0.34.2': + resolution: {integrity: sha512-OfXHZPppddivUJnqyKoi5YVeHRkkNE2zUFT2gbpKxp/JZCFYEYubnMg+gOp6lWfasPrTS+KPosKqdI+ELYVDtg==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} cpu: [arm64] os: [darwin] - "@img/sharp-darwin-x64@0.34.2": - resolution: - { integrity: sha512-dYvWqmjU9VxqXmjEtjmvHnGqF8GrVjM2Epj9rJ6BUIXvk8slvNDJbhGFvIoXzkDhrJC2jUxNLz/GUjjvSzfw+g== } - engines: { node: ^18.17.0 || ^20.3.0 || >=21.0.0 } + '@img/sharp-darwin-x64@0.34.2': + resolution: {integrity: sha512-dYvWqmjU9VxqXmjEtjmvHnGqF8GrVjM2Epj9rJ6BUIXvk8slvNDJbhGFvIoXzkDhrJC2jUxNLz/GUjjvSzfw+g==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} cpu: [x64] os: [darwin] - "@img/sharp-libvips-darwin-arm64@1.1.0": - resolution: - { integrity: sha512-HZ/JUmPwrJSoM4DIQPv/BfNh9yrOA8tlBbqbLz4JZ5uew2+o22Ik+tHQJcih7QJuSa0zo5coHTfD5J8inqj9DA== } + '@img/sharp-libvips-darwin-arm64@1.1.0': + resolution: {integrity: sha512-HZ/JUmPwrJSoM4DIQPv/BfNh9yrOA8tlBbqbLz4JZ5uew2+o22Ik+tHQJcih7QJuSa0zo5coHTfD5J8inqj9DA==} cpu: [arm64] os: [darwin] - "@img/sharp-libvips-darwin-x64@1.1.0": - resolution: - { integrity: sha512-Xzc2ToEmHN+hfvsl9wja0RlnXEgpKNmftriQp6XzY/RaSfwD9th+MSh0WQKzUreLKKINb3afirxW7A0fz2YWuQ== } + '@img/sharp-libvips-darwin-x64@1.1.0': + resolution: {integrity: sha512-Xzc2ToEmHN+hfvsl9wja0RlnXEgpKNmftriQp6XzY/RaSfwD9th+MSh0WQKzUreLKKINb3afirxW7A0fz2YWuQ==} cpu: [x64] os: [darwin] - "@img/sharp-libvips-linux-arm64@1.1.0": - resolution: - { integrity: sha512-IVfGJa7gjChDET1dK9SekxFFdflarnUB8PwW8aGwEoF3oAsSDuNUTYS+SKDOyOJxQyDC1aPFMuRYLoDInyV9Ew== } + '@img/sharp-libvips-linux-arm64@1.1.0': + resolution: {integrity: sha512-IVfGJa7gjChDET1dK9SekxFFdflarnUB8PwW8aGwEoF3oAsSDuNUTYS+SKDOyOJxQyDC1aPFMuRYLoDInyV9Ew==} cpu: [arm64] os: [linux] - "@img/sharp-libvips-linux-arm@1.1.0": - resolution: - { integrity: sha512-s8BAd0lwUIvYCJyRdFqvsj+BJIpDBSxs6ivrOPm/R7piTs5UIwY5OjXrP2bqXC9/moGsyRa37eYWYCOGVXxVrA== } + '@img/sharp-libvips-linux-arm@1.1.0': + resolution: {integrity: sha512-s8BAd0lwUIvYCJyRdFqvsj+BJIpDBSxs6ivrOPm/R7piTs5UIwY5OjXrP2bqXC9/moGsyRa37eYWYCOGVXxVrA==} cpu: [arm] os: [linux] - "@img/sharp-libvips-linux-ppc64@1.1.0": - resolution: - { integrity: sha512-tiXxFZFbhnkWE2LA8oQj7KYR+bWBkiV2nilRldT7bqoEZ4HiDOcePr9wVDAZPi/Id5fT1oY9iGnDq20cwUz8lQ== } + '@img/sharp-libvips-linux-ppc64@1.1.0': + resolution: {integrity: sha512-tiXxFZFbhnkWE2LA8oQj7KYR+bWBkiV2nilRldT7bqoEZ4HiDOcePr9wVDAZPi/Id5fT1oY9iGnDq20cwUz8lQ==} cpu: [ppc64] os: [linux] - "@img/sharp-libvips-linux-s390x@1.1.0": - resolution: - { integrity: sha512-xukSwvhguw7COyzvmjydRb3x/09+21HykyapcZchiCUkTThEQEOMtBj9UhkaBRLuBrgLFzQ2wbxdeCCJW/jgJA== } + '@img/sharp-libvips-linux-s390x@1.1.0': + resolution: {integrity: sha512-xukSwvhguw7COyzvmjydRb3x/09+21HykyapcZchiCUkTThEQEOMtBj9UhkaBRLuBrgLFzQ2wbxdeCCJW/jgJA==} cpu: [s390x] os: [linux] - "@img/sharp-libvips-linux-x64@1.1.0": - resolution: - { integrity: sha512-yRj2+reB8iMg9W5sULM3S74jVS7zqSzHG3Ol/twnAAkAhnGQnpjj6e4ayUz7V+FpKypwgs82xbRdYtchTTUB+Q== } + '@img/sharp-libvips-linux-x64@1.1.0': + resolution: {integrity: sha512-yRj2+reB8iMg9W5sULM3S74jVS7zqSzHG3Ol/twnAAkAhnGQnpjj6e4ayUz7V+FpKypwgs82xbRdYtchTTUB+Q==} cpu: [x64] os: [linux] - "@img/sharp-libvips-linuxmusl-arm64@1.1.0": - resolution: - { integrity: sha512-jYZdG+whg0MDK+q2COKbYidaqW/WTz0cc1E+tMAusiDygrM4ypmSCjOJPmFTvHHJ8j/6cAGyeDWZOsK06tP33w== } + '@img/sharp-libvips-linuxmusl-arm64@1.1.0': + resolution: {integrity: sha512-jYZdG+whg0MDK+q2COKbYidaqW/WTz0cc1E+tMAusiDygrM4ypmSCjOJPmFTvHHJ8j/6cAGyeDWZOsK06tP33w==} cpu: [arm64] os: [linux] - "@img/sharp-libvips-linuxmusl-x64@1.1.0": - resolution: - { integrity: sha512-wK7SBdwrAiycjXdkPnGCPLjYb9lD4l6Ze2gSdAGVZrEL05AOUJESWU2lhlC+Ffn5/G+VKuSm6zzbQSzFX/P65A== } + '@img/sharp-libvips-linuxmusl-x64@1.1.0': + resolution: {integrity: sha512-wK7SBdwrAiycjXdkPnGCPLjYb9lD4l6Ze2gSdAGVZrEL05AOUJESWU2lhlC+Ffn5/G+VKuSm6zzbQSzFX/P65A==} cpu: [x64] os: [linux] - "@img/sharp-linux-arm64@0.34.2": - resolution: - { integrity: sha512-D8n8wgWmPDakc83LORcfJepdOSN6MvWNzzz2ux0MnIbOqdieRZwVYY32zxVx+IFUT8er5KPcyU3XXsn+GzG/0Q== } - engines: { node: ^18.17.0 || ^20.3.0 || >=21.0.0 } + '@img/sharp-linux-arm64@0.34.2': + resolution: {integrity: sha512-D8n8wgWmPDakc83LORcfJepdOSN6MvWNzzz2ux0MnIbOqdieRZwVYY32zxVx+IFUT8er5KPcyU3XXsn+GzG/0Q==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} cpu: [arm64] os: [linux] - "@img/sharp-linux-arm@0.34.2": - resolution: - { integrity: sha512-0DZzkvuEOqQUP9mo2kjjKNok5AmnOr1jB2XYjkaoNRwpAYMDzRmAqUIa1nRi58S2WswqSfPOWLNOr0FDT3H5RQ== } - engines: { node: ^18.17.0 || ^20.3.0 || >=21.0.0 } + '@img/sharp-linux-arm@0.34.2': + resolution: {integrity: sha512-0DZzkvuEOqQUP9mo2kjjKNok5AmnOr1jB2XYjkaoNRwpAYMDzRmAqUIa1nRi58S2WswqSfPOWLNOr0FDT3H5RQ==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} cpu: [arm] os: [linux] - "@img/sharp-linux-s390x@0.34.2": - resolution: - { integrity: sha512-EGZ1xwhBI7dNISwxjChqBGELCWMGDvmxZXKjQRuqMrakhO8QoMgqCrdjnAqJq/CScxfRn+Bb7suXBElKQpPDiw== } - engines: { node: ^18.17.0 || ^20.3.0 || >=21.0.0 } + '@img/sharp-linux-s390x@0.34.2': + resolution: {integrity: sha512-EGZ1xwhBI7dNISwxjChqBGELCWMGDvmxZXKjQRuqMrakhO8QoMgqCrdjnAqJq/CScxfRn+Bb7suXBElKQpPDiw==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} cpu: [s390x] os: [linux] - "@img/sharp-linux-x64@0.34.2": - resolution: - { integrity: sha512-sD7J+h5nFLMMmOXYH4DD9UtSNBD05tWSSdWAcEyzqW8Cn5UxXvsHAxmxSesYUsTOBmUnjtxghKDl15EvfqLFbQ== } - engines: { node: ^18.17.0 || ^20.3.0 || >=21.0.0 } + '@img/sharp-linux-x64@0.34.2': + resolution: {integrity: sha512-sD7J+h5nFLMMmOXYH4DD9UtSNBD05tWSSdWAcEyzqW8Cn5UxXvsHAxmxSesYUsTOBmUnjtxghKDl15EvfqLFbQ==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} cpu: [x64] os: [linux] - "@img/sharp-linuxmusl-arm64@0.34.2": - resolution: - { integrity: sha512-NEE2vQ6wcxYav1/A22OOxoSOGiKnNmDzCYFOZ949xFmrWZOVII1Bp3NqVVpvj+3UeHMFyN5eP/V5hzViQ5CZNA== } - engines: { node: ^18.17.0 || ^20.3.0 || >=21.0.0 } + '@img/sharp-linuxmusl-arm64@0.34.2': + resolution: {integrity: sha512-NEE2vQ6wcxYav1/A22OOxoSOGiKnNmDzCYFOZ949xFmrWZOVII1Bp3NqVVpvj+3UeHMFyN5eP/V5hzViQ5CZNA==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} cpu: [arm64] os: [linux] - "@img/sharp-linuxmusl-x64@0.34.2": - resolution: - { integrity: sha512-DOYMrDm5E6/8bm/yQLCWyuDJwUnlevR8xtF8bs+gjZ7cyUNYXiSf/E8Kp0Ss5xasIaXSHzb888V1BE4i1hFhAA== } - engines: { node: ^18.17.0 || ^20.3.0 || >=21.0.0 } + '@img/sharp-linuxmusl-x64@0.34.2': + resolution: {integrity: sha512-DOYMrDm5E6/8bm/yQLCWyuDJwUnlevR8xtF8bs+gjZ7cyUNYXiSf/E8Kp0Ss5xasIaXSHzb888V1BE4i1hFhAA==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} cpu: [x64] os: [linux] - "@img/sharp-wasm32@0.34.2": - resolution: - { integrity: sha512-/VI4mdlJ9zkaq53MbIG6rZY+QRN3MLbR6usYlgITEzi4Rpx5S6LFKsycOQjkOGmqTNmkIdLjEvooFKwww6OpdQ== } - engines: { node: ^18.17.0 || ^20.3.0 || >=21.0.0 } + '@img/sharp-wasm32@0.34.2': + resolution: {integrity: sha512-/VI4mdlJ9zkaq53MbIG6rZY+QRN3MLbR6usYlgITEzi4Rpx5S6LFKsycOQjkOGmqTNmkIdLjEvooFKwww6OpdQ==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} cpu: [wasm32] - "@img/sharp-win32-arm64@0.34.2": - resolution: - { integrity: sha512-cfP/r9FdS63VA5k0xiqaNaEoGxBg9k7uE+RQGzuK9fHt7jib4zAVVseR9LsE4gJcNWgT6APKMNnCcnyOtmSEUQ== } - engines: { node: ^18.17.0 || ^20.3.0 || >=21.0.0 } + '@img/sharp-win32-arm64@0.34.2': + resolution: {integrity: sha512-cfP/r9FdS63VA5k0xiqaNaEoGxBg9k7uE+RQGzuK9fHt7jib4zAVVseR9LsE4gJcNWgT6APKMNnCcnyOtmSEUQ==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} cpu: [arm64] os: [win32] - "@img/sharp-win32-ia32@0.34.2": - resolution: - { integrity: sha512-QLjGGvAbj0X/FXl8n1WbtQ6iVBpWU7JO94u/P2M4a8CFYsvQi4GW2mRy/JqkRx0qpBzaOdKJKw8uc930EX2AHw== } - engines: { node: ^18.17.0 || ^20.3.0 || >=21.0.0 } + '@img/sharp-win32-ia32@0.34.2': + resolution: {integrity: sha512-QLjGGvAbj0X/FXl8n1WbtQ6iVBpWU7JO94u/P2M4a8CFYsvQi4GW2mRy/JqkRx0qpBzaOdKJKw8uc930EX2AHw==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} cpu: [ia32] os: [win32] - "@img/sharp-win32-x64@0.34.2": - resolution: - { integrity: sha512-aUdT6zEYtDKCaxkofmmJDJYGCf0+pJg3eU9/oBuqvEeoB9dKI6ZLc/1iLJCTuJQDO4ptntAlkUmHgGjyuobZbw== } - engines: { node: ^18.17.0 || ^20.3.0 || >=21.0.0 } + '@img/sharp-win32-x64@0.34.2': + resolution: {integrity: sha512-aUdT6zEYtDKCaxkofmmJDJYGCf0+pJg3eU9/oBuqvEeoB9dKI6ZLc/1iLJCTuJQDO4ptntAlkUmHgGjyuobZbw==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} cpu: [x64] os: [win32] - "@isaacs/fs-minipass@4.0.1": - resolution: - { integrity: sha512-wgm9Ehl2jpeqP3zw/7mo3kRHFp5MEDhqAdwy1fTGkHAwnkGOVsgpvQhL8B5n1qlb01jV3n/bI0ZfZp5lWA1k4w== } - engines: { node: ">=18.0.0" } + '@isaacs/fs-minipass@4.0.1': + resolution: {integrity: sha512-wgm9Ehl2jpeqP3zw/7mo3kRHFp5MEDhqAdwy1fTGkHAwnkGOVsgpvQhL8B5n1qlb01jV3n/bI0ZfZp5lWA1k4w==} + engines: {node: '>=18.0.0'} - "@jridgewell/gen-mapping@0.3.12": - resolution: - { integrity: sha512-OuLGC46TjB5BbN1dH8JULVVZY4WTdkF7tV9Ys6wLL1rubZnCMstOhNHueU5bLCrnRuDhKPDM4g6sw4Bel5Gzqg== } + '@jridgewell/gen-mapping@0.3.12': + resolution: {integrity: sha512-OuLGC46TjB5BbN1dH8JULVVZY4WTdkF7tV9Ys6wLL1rubZnCMstOhNHueU5bLCrnRuDhKPDM4g6sw4Bel5Gzqg==} - "@jridgewell/resolve-uri@3.1.2": - resolution: - { integrity: sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw== } - engines: { node: ">=6.0.0" } + '@jridgewell/resolve-uri@3.1.2': + resolution: {integrity: sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==} + engines: {node: '>=6.0.0'} - "@jridgewell/sourcemap-codec@1.5.4": - resolution: - { integrity: sha512-VT2+G1VQs/9oz078bLrYbecdZKs912zQlkelYpuf+SXF+QvZDYJlbx/LSx+meSAwdDFnF8FVXW92AVjjkVmgFw== } + '@jridgewell/sourcemap-codec@1.5.4': + resolution: {integrity: sha512-VT2+G1VQs/9oz078bLrYbecdZKs912zQlkelYpuf+SXF+QvZDYJlbx/LSx+meSAwdDFnF8FVXW92AVjjkVmgFw==} - "@jridgewell/trace-mapping@0.3.29": - resolution: - { integrity: sha512-uw6guiW/gcAGPDhLmd77/6lW8QLeiV5RUTsAX46Db6oLhGaVj4lhnPwb184s1bkc8kdVg/+h988dro8GRDpmYQ== } + '@jridgewell/trace-mapping@0.3.29': + resolution: {integrity: sha512-uw6guiW/gcAGPDhLmd77/6lW8QLeiV5RUTsAX46Db6oLhGaVj4lhnPwb184s1bkc8kdVg/+h988dro8GRDpmYQ==} - "@mdx-js/mdx@3.1.0": - resolution: - { integrity: sha512-/QxEhPAvGwbQmy1Px8F899L5Uc2KZ6JtXwlCgJmjSTBedwOZkByYcBG4GceIGPXRDsmfxhHazuS+hlOShRLeDw== } + '@mdx-js/mdx@3.1.0': + resolution: {integrity: sha512-/QxEhPAvGwbQmy1Px8F899L5Uc2KZ6JtXwlCgJmjSTBedwOZkByYcBG4GceIGPXRDsmfxhHazuS+hlOShRLeDw==} - "@napi-rs/wasm-runtime@0.2.11": - resolution: - { integrity: sha512-9DPkXtvHydrcOsopiYpUgPHpmj0HWZKMUnL2dZqpvC42lsratuBG06V5ipyno0fUek5VlFsNQ+AcFATSrJXgMA== } + '@napi-rs/wasm-runtime@0.2.11': + resolution: {integrity: sha512-9DPkXtvHydrcOsopiYpUgPHpmj0HWZKMUnL2dZqpvC42lsratuBG06V5ipyno0fUek5VlFsNQ+AcFATSrJXgMA==} - "@next/env@15.3.4": - resolution: - { integrity: sha512-ZkdYzBseS6UjYzz6ylVKPOK+//zLWvD6Ta+vpoye8cW11AjiQjGYVibF0xuvT4L0iJfAPfZLFidaEzAOywyOAQ== } + '@next/env@15.3.4': + resolution: {integrity: sha512-ZkdYzBseS6UjYzz6ylVKPOK+//zLWvD6Ta+vpoye8cW11AjiQjGYVibF0xuvT4L0iJfAPfZLFidaEzAOywyOAQ==} - "@next/eslint-plugin-next@15.3.4": - resolution: - { integrity: sha512-lBxYdj7TI8phbJcLSAqDt57nIcobEign5NYIKCiy0hXQhrUbTqLqOaSDi568U6vFg4hJfBdZYsG4iP/uKhCqgg== } + '@next/eslint-plugin-next@15.3.4': + resolution: {integrity: sha512-lBxYdj7TI8phbJcLSAqDt57nIcobEign5NYIKCiy0hXQhrUbTqLqOaSDi568U6vFg4hJfBdZYsG4iP/uKhCqgg==} - "@next/swc-darwin-arm64@15.3.4": - resolution: - { integrity: sha512-z0qIYTONmPRbwHWvpyrFXJd5F9YWLCsw3Sjrzj2ZvMYy9NPQMPZ1NjOJh4ojr4oQzcGYwgJKfidzehaNa1BpEg== } - engines: { node: ">= 10" } + '@next/swc-darwin-arm64@15.3.4': + resolution: {integrity: sha512-z0qIYTONmPRbwHWvpyrFXJd5F9YWLCsw3Sjrzj2ZvMYy9NPQMPZ1NjOJh4ojr4oQzcGYwgJKfidzehaNa1BpEg==} + engines: {node: '>= 10'} cpu: [arm64] os: [darwin] - "@next/swc-darwin-x64@15.3.4": - resolution: - { integrity: sha512-Z0FYJM8lritw5Wq+vpHYuCIzIlEMjewG2aRkc3Hi2rcbULknYL/xqfpBL23jQnCSrDUGAo/AEv0Z+s2bff9Zkw== } - engines: { node: ">= 10" } + '@next/swc-darwin-x64@15.3.4': + resolution: {integrity: sha512-Z0FYJM8lritw5Wq+vpHYuCIzIlEMjewG2aRkc3Hi2rcbULknYL/xqfpBL23jQnCSrDUGAo/AEv0Z+s2bff9Zkw==} + engines: {node: '>= 10'} cpu: [x64] os: [darwin] - "@next/swc-linux-arm64-gnu@15.3.4": - resolution: - { integrity: sha512-l8ZQOCCg7adwmsnFm8m5q9eIPAHdaB2F3cxhufYtVo84pymwKuWfpYTKcUiFcutJdp9xGHC+F1Uq3xnFU1B/7g== } - engines: { node: ">= 10" } + '@next/swc-linux-arm64-gnu@15.3.4': + resolution: {integrity: sha512-l8ZQOCCg7adwmsnFm8m5q9eIPAHdaB2F3cxhufYtVo84pymwKuWfpYTKcUiFcutJdp9xGHC+F1Uq3xnFU1B/7g==} + engines: {node: '>= 10'} cpu: [arm64] os: [linux] - "@next/swc-linux-arm64-musl@15.3.4": - resolution: - { integrity: sha512-wFyZ7X470YJQtpKot4xCY3gpdn8lE9nTlldG07/kJYexCUpX1piX+MBfZdvulo+t1yADFVEuzFfVHfklfEx8kw== } - engines: { node: ">= 10" } + '@next/swc-linux-arm64-musl@15.3.4': + resolution: {integrity: sha512-wFyZ7X470YJQtpKot4xCY3gpdn8lE9nTlldG07/kJYexCUpX1piX+MBfZdvulo+t1yADFVEuzFfVHfklfEx8kw==} + engines: {node: '>= 10'} cpu: [arm64] os: [linux] - "@next/swc-linux-x64-gnu@15.3.4": - resolution: - { integrity: sha512-gEbH9rv9o7I12qPyvZNVTyP/PWKqOp8clvnoYZQiX800KkqsaJZuOXkWgMa7ANCCh/oEN2ZQheh3yH8/kWPSEg== } - engines: { node: ">= 10" } + '@next/swc-linux-x64-gnu@15.3.4': + resolution: {integrity: sha512-gEbH9rv9o7I12qPyvZNVTyP/PWKqOp8clvnoYZQiX800KkqsaJZuOXkWgMa7ANCCh/oEN2ZQheh3yH8/kWPSEg==} + engines: {node: '>= 10'} cpu: [x64] os: [linux] - "@next/swc-linux-x64-musl@15.3.4": - resolution: - { integrity: sha512-Cf8sr0ufuC/nu/yQ76AnarbSAXcwG/wj+1xFPNbyNo8ltA6kw5d5YqO8kQuwVIxk13SBdtgXrNyom3ZosHAy4A== } - engines: { node: ">= 10" } + '@next/swc-linux-x64-musl@15.3.4': + resolution: {integrity: sha512-Cf8sr0ufuC/nu/yQ76AnarbSAXcwG/wj+1xFPNbyNo8ltA6kw5d5YqO8kQuwVIxk13SBdtgXrNyom3ZosHAy4A==} + engines: {node: '>= 10'} cpu: [x64] os: [linux] - "@next/swc-win32-arm64-msvc@15.3.4": - resolution: - { integrity: sha512-ay5+qADDN3rwRbRpEhTOreOn1OyJIXS60tg9WMYTWCy3fB6rGoyjLVxc4dR9PYjEdR2iDYsaF5h03NA+XuYPQQ== } - engines: { node: ">= 10" } + '@next/swc-win32-arm64-msvc@15.3.4': + resolution: {integrity: sha512-ay5+qADDN3rwRbRpEhTOreOn1OyJIXS60tg9WMYTWCy3fB6rGoyjLVxc4dR9PYjEdR2iDYsaF5h03NA+XuYPQQ==} + engines: {node: '>= 10'} cpu: [arm64] os: [win32] - "@next/swc-win32-x64-msvc@15.3.4": - resolution: - { integrity: sha512-4kDt31Bc9DGyYs41FTL1/kNpDeHyha2TC0j5sRRoKCyrhNcfZ/nRQkAUlF27mETwm8QyHqIjHJitfcza2Iykfg== } - engines: { node: ">= 10" } + '@next/swc-win32-x64-msvc@15.3.4': + resolution: {integrity: sha512-4kDt31Bc9DGyYs41FTL1/kNpDeHyha2TC0j5sRRoKCyrhNcfZ/nRQkAUlF27mETwm8QyHqIjHJitfcza2Iykfg==} + engines: {node: '>= 10'} cpu: [x64] os: [win32] - "@nodelib/fs.scandir@2.1.5": - resolution: - { integrity: sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g== } - engines: { node: ">= 8" } - - "@nodelib/fs.stat@2.0.5": - resolution: - { integrity: sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A== } - engines: { node: ">= 8" } - - "@nodelib/fs.walk@1.2.8": - resolution: - { integrity: sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg== } - engines: { node: ">= 8" } - - "@nolyfill/is-core-module@1.0.39": - resolution: - { integrity: sha512-nn5ozdjYQpUCZlWGuxcJY/KpxkWQs4DcbMCmKojjyrYDEAGy4Ce19NN4v5MduafTwJlbKc99UA8YhSVqq9yPZA== } - engines: { node: ">=12.4.0" } - - "@orama/orama@3.1.10": - resolution: - { integrity: sha512-YNou2xlCIgPhMDe1TBEmp1wsAPFCL7Fd11rct7YfXYYiNAVBNL2rWoEydJRDJFVmqgt0l6mzSg35sDQ3i8yfKQ== } - engines: { node: ">= 20.0.0" } - - "@pkgr/core@0.2.7": - resolution: - { integrity: sha512-YLT9Zo3oNPJoBjBc4q8G2mjU4tqIbf5CEOORbUUr48dCD9q3umJ3IPlVqOqDakPfd2HuwccBaqlGhN4Gmr5OWg== } - engines: { node: ^12.20.0 || ^14.18.0 || >=16.0.0 } - - "@radix-ui/number@1.1.1": - resolution: - { integrity: sha512-MkKCwxlXTgz6CFoJx3pCwn07GKp36+aZyu/u2Ln2VrA5DcdyCZkASEDBTd8x5whTQQL5CiYf4prXKLcgQdv29g== } - - "@radix-ui/primitive@1.1.2": - resolution: - { integrity: sha512-XnbHrrprsNqZKQhStrSwgRUQzoCI1glLzdw79xiZPoofhGICeZRSQ3dIxAKH1gb3OHfNf4d6f+vAv3kil2eggA== } - - "@radix-ui/react-accordion@1.2.11": - resolution: - { integrity: sha512-l3W5D54emV2ues7jjeG1xcyN7S3jnK3zE2zHqgn0CmMsy9lNJwmgcrmaxS+7ipw15FAivzKNzH3d5EcGoFKw0A== } + '@nodelib/fs.scandir@2.1.5': + resolution: {integrity: sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==} + engines: {node: '>= 8'} + + '@nodelib/fs.stat@2.0.5': + resolution: {integrity: sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==} + engines: {node: '>= 8'} + + '@nodelib/fs.walk@1.2.8': + resolution: {integrity: sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==} + engines: {node: '>= 8'} + + '@nolyfill/is-core-module@1.0.39': + resolution: {integrity: sha512-nn5ozdjYQpUCZlWGuxcJY/KpxkWQs4DcbMCmKojjyrYDEAGy4Ce19NN4v5MduafTwJlbKc99UA8YhSVqq9yPZA==} + engines: {node: '>=12.4.0'} + + '@orama/orama@3.1.10': + resolution: {integrity: sha512-YNou2xlCIgPhMDe1TBEmp1wsAPFCL7Fd11rct7YfXYYiNAVBNL2rWoEydJRDJFVmqgt0l6mzSg35sDQ3i8yfKQ==} + engines: {node: '>= 20.0.0'} + + '@pkgr/core@0.2.7': + resolution: {integrity: sha512-YLT9Zo3oNPJoBjBc4q8G2mjU4tqIbf5CEOORbUUr48dCD9q3umJ3IPlVqOqDakPfd2HuwccBaqlGhN4Gmr5OWg==} + engines: {node: ^12.20.0 || ^14.18.0 || >=16.0.0} + + '@radix-ui/number@1.1.1': + resolution: {integrity: sha512-MkKCwxlXTgz6CFoJx3pCwn07GKp36+aZyu/u2Ln2VrA5DcdyCZkASEDBTd8x5whTQQL5CiYf4prXKLcgQdv29g==} + + '@radix-ui/primitive@1.1.2': + resolution: {integrity: sha512-XnbHrrprsNqZKQhStrSwgRUQzoCI1glLzdw79xiZPoofhGICeZRSQ3dIxAKH1gb3OHfNf4d6f+vAv3kil2eggA==} + + '@radix-ui/primitive@1.1.3': + resolution: {integrity: sha512-JTF99U/6XIjCBo0wqkU5sK10glYe27MRRsfwoiq5zzOEZLHU3A3KCMa5X/azekYRCJ0HlwI0crAXS/5dEHTzDg==} + + '@radix-ui/react-accordion@1.2.11': + resolution: {integrity: sha512-l3W5D54emV2ues7jjeG1xcyN7S3jnK3zE2zHqgn0CmMsy9lNJwmgcrmaxS+7ipw15FAivzKNzH3d5EcGoFKw0A==} peerDependencies: - "@types/react": "*" - "@types/react-dom": "*" + '@types/react': '*' + '@types/react-dom': '*' react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc peerDependenciesMeta: - "@types/react": + '@types/react': optional: true - "@types/react-dom": + '@types/react-dom': optional: true - "@radix-ui/react-arrow@1.1.7": - resolution: - { integrity: sha512-F+M1tLhO+mlQaOWspE8Wstg+z6PwxwRd8oQ8IXceWz92kfAmalTRf0EjrouQeo7QssEPfCn05B4Ihs1K9WQ/7w== } + '@radix-ui/react-arrow@1.1.7': + resolution: {integrity: sha512-F+M1tLhO+mlQaOWspE8Wstg+z6PwxwRd8oQ8IXceWz92kfAmalTRf0EjrouQeo7QssEPfCn05B4Ihs1K9WQ/7w==} peerDependencies: - "@types/react": "*" - "@types/react-dom": "*" + '@types/react': '*' + '@types/react-dom': '*' react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc peerDependenciesMeta: - "@types/react": + '@types/react': optional: true - "@types/react-dom": + '@types/react-dom': optional: true - "@radix-ui/react-collapsible@1.1.11": - resolution: - { integrity: sha512-2qrRsVGSCYasSz1RFOorXwl0H7g7J1frQtgpQgYrt+MOidtPAINHn9CPovQXb83r8ahapdx3Tu0fa/pdFFSdPg== } + '@radix-ui/react-collapsible@1.1.11': + resolution: {integrity: sha512-2qrRsVGSCYasSz1RFOorXwl0H7g7J1frQtgpQgYrt+MOidtPAINHn9CPovQXb83r8ahapdx3Tu0fa/pdFFSdPg==} peerDependencies: - "@types/react": "*" - "@types/react-dom": "*" + '@types/react': '*' + '@types/react-dom': '*' react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc peerDependenciesMeta: - "@types/react": + '@types/react': optional: true - "@types/react-dom": + '@types/react-dom': optional: true - "@radix-ui/react-collection@1.1.7": - resolution: - { integrity: sha512-Fh9rGN0MoI4ZFUNyfFVNU4y9LUz93u9/0K+yLgA2bwRojxM8JU1DyvvMBabnZPBgMWREAJvU2jjVzq+LrFUglw== } + '@radix-ui/react-collection@1.1.7': + resolution: {integrity: sha512-Fh9rGN0MoI4ZFUNyfFVNU4y9LUz93u9/0K+yLgA2bwRojxM8JU1DyvvMBabnZPBgMWREAJvU2jjVzq+LrFUglw==} peerDependencies: - "@types/react": "*" - "@types/react-dom": "*" + '@types/react': '*' + '@types/react-dom': '*' react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc peerDependenciesMeta: - "@types/react": + '@types/react': optional: true - "@types/react-dom": + '@types/react-dom': optional: true - "@radix-ui/react-compose-refs@1.1.2": - resolution: - { integrity: sha512-z4eqJvfiNnFMHIIvXP3CY57y2WJs5g2v3X0zm9mEJkrkNv4rDxu+sg9Jh8EkXyeqBkB7SOcboo9dMVqhyrACIg== } + '@radix-ui/react-compose-refs@1.1.2': + resolution: {integrity: sha512-z4eqJvfiNnFMHIIvXP3CY57y2WJs5g2v3X0zm9mEJkrkNv4rDxu+sg9Jh8EkXyeqBkB7SOcboo9dMVqhyrACIg==} peerDependencies: - "@types/react": "*" + '@types/react': '*' react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc peerDependenciesMeta: - "@types/react": + '@types/react': optional: true - "@radix-ui/react-context@1.1.2": - resolution: - { integrity: sha512-jCi/QKUM2r1Ju5a3J64TH2A5SpKAgh0LpknyqdQ4m6DCV0xJ2HG1xARRwNGPQfi1SLdLWZ1OJz6F4OMBBNiGJA== } + '@radix-ui/react-context@1.1.2': + resolution: {integrity: sha512-jCi/QKUM2r1Ju5a3J64TH2A5SpKAgh0LpknyqdQ4m6DCV0xJ2HG1xARRwNGPQfi1SLdLWZ1OJz6F4OMBBNiGJA==} peerDependencies: - "@types/react": "*" + '@types/react': '*' react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc peerDependenciesMeta: - "@types/react": + '@types/react': optional: true - "@radix-ui/react-dialog@1.1.14": - resolution: - { integrity: sha512-+CpweKjqpzTmwRwcYECQcNYbI8V9VSQt0SNFKeEBLgfucbsLssU6Ppq7wUdNXEGb573bMjFhVjKVll8rmV6zMw== } + '@radix-ui/react-dialog@1.1.15': + resolution: {integrity: sha512-TCglVRtzlffRNxRMEyR36DGBLJpeusFcgMVD9PZEzAKnUs1lKCgX5u9BmC2Yg+LL9MgZDugFFs1Vl+Jp4t/PGw==} peerDependencies: - "@types/react": "*" - "@types/react-dom": "*" + '@types/react': '*' + '@types/react-dom': '*' react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc peerDependenciesMeta: - "@types/react": + '@types/react': + optional: true + '@types/react-dom': optional: true - "@types/react-dom": + + '@radix-ui/react-direction@1.1.1': + resolution: {integrity: sha512-1UEWRX6jnOA2y4H5WczZ44gOOjTEmlqv1uNW4GAJEO5+bauCBhv8snY65Iw5/VOS/ghKN9gr2KjnLKxrsvoMVw==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': optional: true - "@radix-ui/react-direction@1.1.1": - resolution: - { integrity: sha512-1UEWRX6jnOA2y4H5WczZ44gOOjTEmlqv1uNW4GAJEO5+bauCBhv8snY65Iw5/VOS/ghKN9gr2KjnLKxrsvoMVw== } + '@radix-ui/react-dismissable-layer@1.1.10': + resolution: {integrity: sha512-IM1zzRV4W3HtVgftdQiiOmA0AdJlCtMLe00FXaHwgt3rAnNsIyDqshvkIW3hj/iu5hu8ERP7KIYki6NkqDxAwQ==} peerDependencies: - "@types/react": "*" + '@types/react': '*' + '@types/react-dom': '*' react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc peerDependenciesMeta: - "@types/react": + '@types/react': + optional: true + '@types/react-dom': optional: true - "@radix-ui/react-dismissable-layer@1.1.10": - resolution: - { integrity: sha512-IM1zzRV4W3HtVgftdQiiOmA0AdJlCtMLe00FXaHwgt3rAnNsIyDqshvkIW3hj/iu5hu8ERP7KIYki6NkqDxAwQ== } + '@radix-ui/react-dismissable-layer@1.1.11': + resolution: {integrity: sha512-Nqcp+t5cTB8BinFkZgXiMJniQH0PsUt2k51FUhbdfeKvc4ACcG2uQniY/8+h1Yv6Kza4Q7lD7PQV0z0oicE0Mg==} peerDependencies: - "@types/react": "*" - "@types/react-dom": "*" + '@types/react': '*' + '@types/react-dom': '*' react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc peerDependenciesMeta: - "@types/react": + '@types/react': + optional: true + '@types/react-dom': optional: true - "@types/react-dom": + + '@radix-ui/react-focus-guards@1.1.2': + resolution: {integrity: sha512-fyjAACV62oPV925xFCrH8DR5xWhg9KYtJT4s3u54jxp+L/hbpTY2kIeEFFbFe+a/HCE94zGQMZLIpVTPVZDhaA==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': optional: true - "@radix-ui/react-focus-guards@1.1.2": - resolution: - { integrity: sha512-fyjAACV62oPV925xFCrH8DR5xWhg9KYtJT4s3u54jxp+L/hbpTY2kIeEFFbFe+a/HCE94zGQMZLIpVTPVZDhaA== } + '@radix-ui/react-focus-guards@1.1.3': + resolution: {integrity: sha512-0rFg/Rj2Q62NCm62jZw0QX7a3sz6QCQU0LpZdNrJX8byRGaGVTqbrW9jAoIAHyMQqsNpeZ81YgSizOt5WXq0Pw==} peerDependencies: - "@types/react": "*" + '@types/react': '*' react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc peerDependenciesMeta: - "@types/react": + '@types/react': optional: true - "@radix-ui/react-focus-scope@1.1.7": - resolution: - { integrity: sha512-t2ODlkXBQyn7jkl6TNaw/MtVEVvIGelJDCG41Okq/KwUsJBwQ4XVZsHAVUkK4mBv3ewiAS3PGuUWuY2BoK4ZUw== } + '@radix-ui/react-focus-scope@1.1.7': + resolution: {integrity: sha512-t2ODlkXBQyn7jkl6TNaw/MtVEVvIGelJDCG41Okq/KwUsJBwQ4XVZsHAVUkK4mBv3ewiAS3PGuUWuY2BoK4ZUw==} peerDependencies: - "@types/react": "*" - "@types/react-dom": "*" + '@types/react': '*' + '@types/react-dom': '*' react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc peerDependenciesMeta: - "@types/react": + '@types/react': optional: true - "@types/react-dom": + '@types/react-dom': optional: true - "@radix-ui/react-id@1.1.1": - resolution: - { integrity: sha512-kGkGegYIdQsOb4XjsfM97rXsiHaBwco+hFI66oO4s9LU+PLAC5oJ7khdOVFxkhsmlbpUqDAvXw11CluXP+jkHg== } + '@radix-ui/react-id@1.1.1': + resolution: {integrity: sha512-kGkGegYIdQsOb4XjsfM97rXsiHaBwco+hFI66oO4s9LU+PLAC5oJ7khdOVFxkhsmlbpUqDAvXw11CluXP+jkHg==} peerDependencies: - "@types/react": "*" + '@types/react': '*' react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc peerDependenciesMeta: - "@types/react": + '@types/react': optional: true - "@radix-ui/react-navigation-menu@1.2.13": - resolution: - { integrity: sha512-WG8wWfDiJlSF5hELjwfjSGOXcBR/ZMhBFCGYe8vERpC39CQYZeq1PQ2kaYHdye3V95d06H89KGMsVCIE4LWo3g== } + '@radix-ui/react-navigation-menu@1.2.13': + resolution: {integrity: sha512-WG8wWfDiJlSF5hELjwfjSGOXcBR/ZMhBFCGYe8vERpC39CQYZeq1PQ2kaYHdye3V95d06H89KGMsVCIE4LWo3g==} peerDependencies: - "@types/react": "*" - "@types/react-dom": "*" + '@types/react': '*' + '@types/react-dom': '*' react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc peerDependenciesMeta: - "@types/react": + '@types/react': optional: true - "@types/react-dom": + '@types/react-dom': optional: true - "@radix-ui/react-popover@1.1.14": - resolution: - { integrity: sha512-ODz16+1iIbGUfFEfKx2HTPKizg2MN39uIOV8MXeHnmdd3i/N9Wt7vU46wbHsqA0xoaQyXVcs0KIlBdOA2Y95bw== } + '@radix-ui/react-popover@1.1.14': + resolution: {integrity: sha512-ODz16+1iIbGUfFEfKx2HTPKizg2MN39uIOV8MXeHnmdd3i/N9Wt7vU46wbHsqA0xoaQyXVcs0KIlBdOA2Y95bw==} peerDependencies: - "@types/react": "*" - "@types/react-dom": "*" + '@types/react': '*' + '@types/react-dom': '*' react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc peerDependenciesMeta: - "@types/react": + '@types/react': optional: true - "@types/react-dom": + '@types/react-dom': optional: true - "@radix-ui/react-popper@1.2.7": - resolution: - { integrity: sha512-IUFAccz1JyKcf/RjB552PlWwxjeCJB8/4KxT7EhBHOJM+mN7LdW+B3kacJXILm32xawcMMjb2i0cIZpo+f9kiQ== } + '@radix-ui/react-popper@1.2.7': + resolution: {integrity: sha512-IUFAccz1JyKcf/RjB552PlWwxjeCJB8/4KxT7EhBHOJM+mN7LdW+B3kacJXILm32xawcMMjb2i0cIZpo+f9kiQ==} peerDependencies: - "@types/react": "*" - "@types/react-dom": "*" + '@types/react': '*' + '@types/react-dom': '*' react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc peerDependenciesMeta: - "@types/react": + '@types/react': optional: true - "@types/react-dom": + '@types/react-dom': optional: true - "@radix-ui/react-portal@1.1.9": - resolution: - { integrity: sha512-bpIxvq03if6UNwXZ+HTK71JLh4APvnXntDc6XOX8UVq4XQOVl7lwok0AvIl+b8zgCw3fSaVTZMpAPPagXbKmHQ== } + '@radix-ui/react-portal@1.1.9': + resolution: {integrity: sha512-bpIxvq03if6UNwXZ+HTK71JLh4APvnXntDc6XOX8UVq4XQOVl7lwok0AvIl+b8zgCw3fSaVTZMpAPPagXbKmHQ==} peerDependencies: - "@types/react": "*" - "@types/react-dom": "*" + '@types/react': '*' + '@types/react-dom': '*' react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc peerDependenciesMeta: - "@types/react": + '@types/react': optional: true - "@types/react-dom": + '@types/react-dom': optional: true - "@radix-ui/react-presence@1.1.4": - resolution: - { integrity: sha512-ueDqRbdc4/bkaQT3GIpLQssRlFgWaL/U2z/S31qRwwLWoxHLgry3SIfCwhxeQNbirEUXFa+lq3RL3oBYXtcmIA== } + '@radix-ui/react-presence@1.1.4': + resolution: {integrity: sha512-ueDqRbdc4/bkaQT3GIpLQssRlFgWaL/U2z/S31qRwwLWoxHLgry3SIfCwhxeQNbirEUXFa+lq3RL3oBYXtcmIA==} peerDependencies: - "@types/react": "*" - "@types/react-dom": "*" + '@types/react': '*' + '@types/react-dom': '*' react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc peerDependenciesMeta: - "@types/react": + '@types/react': optional: true - "@types/react-dom": + '@types/react-dom': optional: true - "@radix-ui/react-primitive@2.1.3": - resolution: - { integrity: sha512-m9gTwRkhy2lvCPe6QJp4d3G1TYEUHn/FzJUtq9MjH46an1wJU+GdoGC5VLof8RX8Ft/DlpshApkhswDLZzHIcQ== } + '@radix-ui/react-presence@1.1.5': + resolution: {integrity: sha512-/jfEwNDdQVBCNvjkGit4h6pMOzq8bHkopq458dPt2lMjx+eBQUohZNG9A7DtO/O5ukSbxuaNGXMjHicgwy6rQQ==} peerDependencies: - "@types/react": "*" - "@types/react-dom": "*" + '@types/react': '*' + '@types/react-dom': '*' react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc peerDependenciesMeta: - "@types/react": + '@types/react': optional: true - "@types/react-dom": + '@types/react-dom': optional: true - "@radix-ui/react-roving-focus@1.1.10": - resolution: - { integrity: sha512-dT9aOXUen9JSsxnMPv/0VqySQf5eDQ6LCk5Sw28kamz8wSOW2bJdlX2Bg5VUIIcV+6XlHpWTIuTPCf/UNIyq8Q== } + '@radix-ui/react-primitive@2.1.3': + resolution: {integrity: sha512-m9gTwRkhy2lvCPe6QJp4d3G1TYEUHn/FzJUtq9MjH46an1wJU+GdoGC5VLof8RX8Ft/DlpshApkhswDLZzHIcQ==} peerDependencies: - "@types/react": "*" - "@types/react-dom": "*" + '@types/react': '*' + '@types/react-dom': '*' react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc peerDependenciesMeta: - "@types/react": + '@types/react': optional: true - "@types/react-dom": + '@types/react-dom': optional: true - "@radix-ui/react-scroll-area@1.2.9": - resolution: - { integrity: sha512-YSjEfBXnhUELsO2VzjdtYYD4CfQjvao+lhhrX5XsHD7/cyUNzljF1FHEbgTPN7LH2MClfwRMIsYlqTYpKTTe2A== } + '@radix-ui/react-roving-focus@1.1.10': + resolution: {integrity: sha512-dT9aOXUen9JSsxnMPv/0VqySQf5eDQ6LCk5Sw28kamz8wSOW2bJdlX2Bg5VUIIcV+6XlHpWTIuTPCf/UNIyq8Q==} peerDependencies: - "@types/react": "*" - "@types/react-dom": "*" + '@types/react': '*' + '@types/react-dom': '*' react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc peerDependenciesMeta: - "@types/react": + '@types/react': optional: true - "@types/react-dom": + '@types/react-dom': optional: true - "@radix-ui/react-slot@1.2.3": - resolution: - { integrity: sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A== } + '@radix-ui/react-scroll-area@1.2.9': + resolution: {integrity: sha512-YSjEfBXnhUELsO2VzjdtYYD4CfQjvao+lhhrX5XsHD7/cyUNzljF1FHEbgTPN7LH2MClfwRMIsYlqTYpKTTe2A==} peerDependencies: - "@types/react": "*" + '@types/react': '*' + '@types/react-dom': '*' react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc peerDependenciesMeta: - "@types/react": + '@types/react': + optional: true + '@types/react-dom': optional: true - "@radix-ui/react-tabs@1.1.12": - resolution: - { integrity: sha512-GTVAlRVrQrSw3cEARM0nAx73ixrWDPNZAruETn3oHCNP6SbZ/hNxdxp+u7VkIEv3/sFoLq1PfcHrl7Pnp0CDpw== } + '@radix-ui/react-slot@1.2.3': + resolution: {integrity: sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A==} peerDependencies: - "@types/react": "*" - "@types/react-dom": "*" + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + '@radix-ui/react-tabs@1.1.12': + resolution: {integrity: sha512-GTVAlRVrQrSw3cEARM0nAx73ixrWDPNZAruETn3oHCNP6SbZ/hNxdxp+u7VkIEv3/sFoLq1PfcHrl7Pnp0CDpw==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc peerDependenciesMeta: - "@types/react": + '@types/react': optional: true - "@types/react-dom": + '@types/react-dom': optional: true - "@radix-ui/react-use-callback-ref@1.1.1": - resolution: - { integrity: sha512-FkBMwD+qbGQeMu1cOHnuGB6x4yzPjho8ap5WtbEJ26umhgqVXbhekKUQO+hZEL1vU92a3wHwdp0HAcqAUF5iDg== } + '@radix-ui/react-use-callback-ref@1.1.1': + resolution: {integrity: sha512-FkBMwD+qbGQeMu1cOHnuGB6x4yzPjho8ap5WtbEJ26umhgqVXbhekKUQO+hZEL1vU92a3wHwdp0HAcqAUF5iDg==} peerDependencies: - "@types/react": "*" + '@types/react': '*' react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc peerDependenciesMeta: - "@types/react": + '@types/react': optional: true - "@radix-ui/react-use-controllable-state@1.2.2": - resolution: - { integrity: sha512-BjasUjixPFdS+NKkypcyyN5Pmg83Olst0+c6vGov0diwTEo6mgdqVR6hxcEgFuh4QrAs7Rc+9KuGJ9TVCj0Zzg== } + '@radix-ui/react-use-controllable-state@1.2.2': + resolution: {integrity: sha512-BjasUjixPFdS+NKkypcyyN5Pmg83Olst0+c6vGov0diwTEo6mgdqVR6hxcEgFuh4QrAs7Rc+9KuGJ9TVCj0Zzg==} peerDependencies: - "@types/react": "*" + '@types/react': '*' react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc peerDependenciesMeta: - "@types/react": + '@types/react': optional: true - "@radix-ui/react-use-effect-event@0.0.2": - resolution: - { integrity: sha512-Qp8WbZOBe+blgpuUT+lw2xheLP8q0oatc9UpmiemEICxGvFLYmHm9QowVZGHtJlGbS6A6yJ3iViad/2cVjnOiA== } + '@radix-ui/react-use-effect-event@0.0.2': + resolution: {integrity: sha512-Qp8WbZOBe+blgpuUT+lw2xheLP8q0oatc9UpmiemEICxGvFLYmHm9QowVZGHtJlGbS6A6yJ3iViad/2cVjnOiA==} peerDependencies: - "@types/react": "*" + '@types/react': '*' react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc peerDependenciesMeta: - "@types/react": + '@types/react': optional: true - "@radix-ui/react-use-escape-keydown@1.1.1": - resolution: - { integrity: sha512-Il0+boE7w/XebUHyBjroE+DbByORGR9KKmITzbR7MyQ4akpORYP/ZmbhAr0DG7RmmBqoOnZdy2QlvajJ2QA59g== } + '@radix-ui/react-use-escape-keydown@1.1.1': + resolution: {integrity: sha512-Il0+boE7w/XebUHyBjroE+DbByORGR9KKmITzbR7MyQ4akpORYP/ZmbhAr0DG7RmmBqoOnZdy2QlvajJ2QA59g==} peerDependencies: - "@types/react": "*" + '@types/react': '*' react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc peerDependenciesMeta: - "@types/react": + '@types/react': optional: true - "@radix-ui/react-use-layout-effect@1.1.1": - resolution: - { integrity: sha512-RbJRS4UWQFkzHTTwVymMTUv8EqYhOp8dOOviLj2ugtTiXRaRQS7GLGxZTLL1jWhMeoSCf5zmcZkqTl9IiYfXcQ== } + '@radix-ui/react-use-layout-effect@1.1.1': + resolution: {integrity: sha512-RbJRS4UWQFkzHTTwVymMTUv8EqYhOp8dOOviLj2ugtTiXRaRQS7GLGxZTLL1jWhMeoSCf5zmcZkqTl9IiYfXcQ==} peerDependencies: - "@types/react": "*" + '@types/react': '*' react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc peerDependenciesMeta: - "@types/react": + '@types/react': optional: true - "@radix-ui/react-use-previous@1.1.1": - resolution: - { integrity: sha512-2dHfToCj/pzca2Ck724OZ5L0EVrr3eHRNsG/b3xQJLA2hZpVCS99bLAX+hm1IHXDEnzU6by5z/5MIY794/a8NQ== } + '@radix-ui/react-use-previous@1.1.1': + resolution: {integrity: sha512-2dHfToCj/pzca2Ck724OZ5L0EVrr3eHRNsG/b3xQJLA2hZpVCS99bLAX+hm1IHXDEnzU6by5z/5MIY794/a8NQ==} peerDependencies: - "@types/react": "*" + '@types/react': '*' react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc peerDependenciesMeta: - "@types/react": + '@types/react': optional: true - "@radix-ui/react-use-rect@1.1.1": - resolution: - { integrity: sha512-QTYuDesS0VtuHNNvMh+CjlKJ4LJickCMUAqjlE3+j8w+RlRpwyX3apEQKGFzbZGdo7XNG1tXa+bQqIE7HIXT2w== } + '@radix-ui/react-use-rect@1.1.1': + resolution: {integrity: sha512-QTYuDesS0VtuHNNvMh+CjlKJ4LJickCMUAqjlE3+j8w+RlRpwyX3apEQKGFzbZGdo7XNG1tXa+bQqIE7HIXT2w==} peerDependencies: - "@types/react": "*" + '@types/react': '*' react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc peerDependenciesMeta: - "@types/react": + '@types/react': optional: true - "@radix-ui/react-use-size@1.1.1": - resolution: - { integrity: sha512-ewrXRDTAqAXlkl6t/fkXWNAhFX9I+CkKlw6zjEwk86RSPKwZr3xpBRso655aqYafwtnbpHLj6toFzmd6xdVptQ== } + '@radix-ui/react-use-size@1.1.1': + resolution: {integrity: sha512-ewrXRDTAqAXlkl6t/fkXWNAhFX9I+CkKlw6zjEwk86RSPKwZr3xpBRso655aqYafwtnbpHLj6toFzmd6xdVptQ==} peerDependencies: - "@types/react": "*" + '@types/react': '*' react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc peerDependenciesMeta: - "@types/react": + '@types/react': optional: true - "@radix-ui/react-visually-hidden@1.2.3": - resolution: - { integrity: sha512-pzJq12tEaaIhqjbzpCuv/OypJY/BPavOofm+dbab+MHLajy277+1lLm6JFcGgF5eskJ6mquGirhXY2GD/8u8Ug== } + '@radix-ui/react-visually-hidden@1.2.3': + resolution: {integrity: sha512-pzJq12tEaaIhqjbzpCuv/OypJY/BPavOofm+dbab+MHLajy277+1lLm6JFcGgF5eskJ6mquGirhXY2GD/8u8Ug==} peerDependencies: - "@types/react": "*" - "@types/react-dom": "*" + '@types/react': '*' + '@types/react-dom': '*' react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc peerDependenciesMeta: - "@types/react": + '@types/react': optional: true - "@types/react-dom": + '@types/react-dom': optional: true - "@radix-ui/rect@1.1.1": - resolution: - { integrity: sha512-HPwpGIzkl28mWyZqG52jiqDJ12waP11Pa1lGoiyUkIEuMLBP0oeK/C89esbXrxsky5we7dfd8U58nm0SgAWpVw== } + '@radix-ui/rect@1.1.1': + resolution: {integrity: sha512-HPwpGIzkl28mWyZqG52jiqDJ12waP11Pa1lGoiyUkIEuMLBP0oeK/C89esbXrxsky5we7dfd8U58nm0SgAWpVw==} - "@rtsao/scc@1.1.0": - resolution: - { integrity: sha512-zt6OdqaDoOnJ1ZYsCYGt9YmWzDXl4vQdKTyJev62gFhRGKdx7mcT54V9KIjg+d2wi9EXsPvAPKe7i7WjfVWB8g== } + '@rtsao/scc@1.1.0': + resolution: {integrity: sha512-zt6OdqaDoOnJ1ZYsCYGt9YmWzDXl4vQdKTyJev62gFhRGKdx7mcT54V9KIjg+d2wi9EXsPvAPKe7i7WjfVWB8g==} - "@rushstack/eslint-patch@1.12.0": - resolution: - { integrity: sha512-5EwMtOqvJMMa3HbmxLlF74e+3/HhwBTMcvt3nqVJgGCozO6hzIPOBlwm8mGVNR9SN2IJpxSnlxczyDjcn7qIyw== } + '@rushstack/eslint-patch@1.12.0': + resolution: {integrity: sha512-5EwMtOqvJMMa3HbmxLlF74e+3/HhwBTMcvt3nqVJgGCozO6hzIPOBlwm8mGVNR9SN2IJpxSnlxczyDjcn7qIyw==} - "@shikijs/core@3.7.0": - resolution: - { integrity: sha512-yilc0S9HvTPyahHpcum8eonYrQtmGTU0lbtwxhA6jHv4Bm1cAdlPFRCJX4AHebkCm75aKTjjRAW+DezqD1b/cg== } + '@shikijs/core@3.7.0': + resolution: {integrity: sha512-yilc0S9HvTPyahHpcum8eonYrQtmGTU0lbtwxhA6jHv4Bm1cAdlPFRCJX4AHebkCm75aKTjjRAW+DezqD1b/cg==} - "@shikijs/engine-javascript@3.7.0": - resolution: - { integrity: sha512-0t17s03Cbv+ZcUvv+y33GtX75WBLQELgNdVghnsdhTgU3hVcWcMsoP6Lb0nDTl95ZJfbP1mVMO0p3byVh3uuzA== } + '@shikijs/engine-javascript@3.7.0': + resolution: {integrity: sha512-0t17s03Cbv+ZcUvv+y33GtX75WBLQELgNdVghnsdhTgU3hVcWcMsoP6Lb0nDTl95ZJfbP1mVMO0p3byVh3uuzA==} - "@shikijs/engine-oniguruma@3.7.0": - resolution: - { integrity: sha512-5BxcD6LjVWsGu4xyaBC5bu8LdNgPCVBnAkWTtOCs/CZxcB22L8rcoWfv7Hh/3WooVjBZmFtyxhgvkQFedPGnFw== } + '@shikijs/engine-oniguruma@3.7.0': + resolution: {integrity: sha512-5BxcD6LjVWsGu4xyaBC5bu8LdNgPCVBnAkWTtOCs/CZxcB22L8rcoWfv7Hh/3WooVjBZmFtyxhgvkQFedPGnFw==} - "@shikijs/langs@3.7.0": - resolution: - { integrity: sha512-1zYtdfXLr9xDKLTGy5kb7O0zDQsxXiIsw1iIBcNOO8Yi5/Y1qDbJ+0VsFoqTlzdmneO8Ij35g7QKF8kcLyznCQ== } + '@shikijs/langs@3.7.0': + resolution: {integrity: sha512-1zYtdfXLr9xDKLTGy5kb7O0zDQsxXiIsw1iIBcNOO8Yi5/Y1qDbJ+0VsFoqTlzdmneO8Ij35g7QKF8kcLyznCQ==} - "@shikijs/rehype@3.7.0": - resolution: - { integrity: sha512-YjAZxhQnBXE8ehppKGzuVGPoE4pjVsxqzkWhBZlkP495AjlR++MgfiRFcQfDt3qX5lK3gEDTcghB/8E3yNrWqQ== } + '@shikijs/rehype@3.7.0': + resolution: {integrity: sha512-YjAZxhQnBXE8ehppKGzuVGPoE4pjVsxqzkWhBZlkP495AjlR++MgfiRFcQfDt3qX5lK3gEDTcghB/8E3yNrWqQ==} - "@shikijs/themes@3.7.0": - resolution: - { integrity: sha512-VJx8497iZPy5zLiiCTSIaOChIcKQwR0FebwE9S3rcN0+J/GTWwQ1v/bqhTbpbY3zybPKeO8wdammqkpXc4NVjQ== } + '@shikijs/themes@3.7.0': + resolution: {integrity: sha512-VJx8497iZPy5zLiiCTSIaOChIcKQwR0FebwE9S3rcN0+J/GTWwQ1v/bqhTbpbY3zybPKeO8wdammqkpXc4NVjQ==} - "@shikijs/transformers@3.7.0": - resolution: - { integrity: sha512-VplaqIMRNsNOorCXJHkbF5S0pT6xm8Z/s7w7OPZLohf8tR93XH0krvUafpNy/ozEylrWuShJF0+ftEB+wFRwGA== } + '@shikijs/transformers@3.7.0': + resolution: {integrity: sha512-VplaqIMRNsNOorCXJHkbF5S0pT6xm8Z/s7w7OPZLohf8tR93XH0krvUafpNy/ozEylrWuShJF0+ftEB+wFRwGA==} - "@shikijs/types@3.7.0": - resolution: - { integrity: sha512-MGaLeaRlSWpnP0XSAum3kP3a8vtcTsITqoEPYdt3lQG3YCdQH4DnEhodkYcNMcU0uW0RffhoD1O3e0vG5eSBBg== } + '@shikijs/types@3.7.0': + resolution: {integrity: sha512-MGaLeaRlSWpnP0XSAum3kP3a8vtcTsITqoEPYdt3lQG3YCdQH4DnEhodkYcNMcU0uW0RffhoD1O3e0vG5eSBBg==} - "@shikijs/vscode-textmate@10.0.2": - resolution: - { integrity: sha512-83yeghZ2xxin3Nj8z1NMd/NCuca+gsYXswywDy5bHvwlWL8tpTQmzGeUuHd9FC3E/SBEMvzJRwWEOz5gGes9Qg== } + '@shikijs/vscode-textmate@10.0.2': + resolution: {integrity: sha512-83yeghZ2xxin3Nj8z1NMd/NCuca+gsYXswywDy5bHvwlWL8tpTQmzGeUuHd9FC3E/SBEMvzJRwWEOz5gGes9Qg==} - "@standard-schema/spec@1.0.0": - resolution: - { integrity: sha512-m2bOd0f2RT9k8QJx1JN85cZYyH1RqFBdlwtkSlf4tBDYLCiiZnv1fIIwacK6cqwXavOydf0NPToMQgpKq+dVlA== } + '@standard-schema/spec@1.0.0': + resolution: {integrity: sha512-m2bOd0f2RT9k8QJx1JN85cZYyH1RqFBdlwtkSlf4tBDYLCiiZnv1fIIwacK6cqwXavOydf0NPToMQgpKq+dVlA==} - "@swc/counter@0.1.3": - resolution: - { integrity: sha512-e2BR4lsJkkRlKZ/qCHPw9ZaSxc0MVUd7gtbtaB7aMvHeJVYe8sOB8DBZkP2DtISHGSku9sCK6T6cnY0CtXrOCQ== } + '@swc/counter@0.1.3': + resolution: {integrity: sha512-e2BR4lsJkkRlKZ/qCHPw9ZaSxc0MVUd7gtbtaB7aMvHeJVYe8sOB8DBZkP2DtISHGSku9sCK6T6cnY0CtXrOCQ==} - "@swc/helpers@0.5.15": - resolution: - { integrity: sha512-JQ5TuMi45Owi4/BIMAJBoSQoOJu12oOk/gADqlcUL9JEdHB8vyjUSsxqeNXnmXHjYKMi2WcYtezGEEhqUI/E2g== } + '@swc/helpers@0.5.15': + resolution: {integrity: sha512-JQ5TuMi45Owi4/BIMAJBoSQoOJu12oOk/gADqlcUL9JEdHB8vyjUSsxqeNXnmXHjYKMi2WcYtezGEEhqUI/E2g==} - "@tailwindcss/node@4.1.11": - resolution: - { integrity: sha512-yzhzuGRmv5QyU9qLNg4GTlYI6STedBWRE7NjxP45CsFYYq9taI0zJXZBMqIC/c8fViNLhmrbpSFS57EoxUmD6Q== } + '@tailwindcss/node@4.1.11': + resolution: {integrity: sha512-yzhzuGRmv5QyU9qLNg4GTlYI6STedBWRE7NjxP45CsFYYq9taI0zJXZBMqIC/c8fViNLhmrbpSFS57EoxUmD6Q==} - "@tailwindcss/oxide-android-arm64@4.1.11": - resolution: - { integrity: sha512-3IfFuATVRUMZZprEIx9OGDjG3Ou3jG4xQzNTvjDoKmU9JdmoCohQJ83MYd0GPnQIu89YoJqvMM0G3uqLRFtetg== } - engines: { node: ">= 10" } + '@tailwindcss/oxide-android-arm64@4.1.11': + resolution: {integrity: sha512-3IfFuATVRUMZZprEIx9OGDjG3Ou3jG4xQzNTvjDoKmU9JdmoCohQJ83MYd0GPnQIu89YoJqvMM0G3uqLRFtetg==} + engines: {node: '>= 10'} cpu: [arm64] os: [android] - "@tailwindcss/oxide-darwin-arm64@4.1.11": - resolution: - { integrity: sha512-ESgStEOEsyg8J5YcMb1xl8WFOXfeBmrhAwGsFxxB2CxY9evy63+AtpbDLAyRkJnxLy2WsD1qF13E97uQyP1lfQ== } - engines: { node: ">= 10" } + '@tailwindcss/oxide-darwin-arm64@4.1.11': + resolution: {integrity: sha512-ESgStEOEsyg8J5YcMb1xl8WFOXfeBmrhAwGsFxxB2CxY9evy63+AtpbDLAyRkJnxLy2WsD1qF13E97uQyP1lfQ==} + engines: {node: '>= 10'} cpu: [arm64] os: [darwin] - "@tailwindcss/oxide-darwin-x64@4.1.11": - resolution: - { integrity: sha512-EgnK8kRchgmgzG6jE10UQNaH9Mwi2n+yw1jWmof9Vyg2lpKNX2ioe7CJdf9M5f8V9uaQxInenZkOxnTVL3fhAw== } - engines: { node: ">= 10" } + '@tailwindcss/oxide-darwin-x64@4.1.11': + resolution: {integrity: sha512-EgnK8kRchgmgzG6jE10UQNaH9Mwi2n+yw1jWmof9Vyg2lpKNX2ioe7CJdf9M5f8V9uaQxInenZkOxnTVL3fhAw==} + engines: {node: '>= 10'} cpu: [x64] os: [darwin] - "@tailwindcss/oxide-freebsd-x64@4.1.11": - resolution: - { integrity: sha512-xdqKtbpHs7pQhIKmqVpxStnY1skuNh4CtbcyOHeX1YBE0hArj2romsFGb6yUmzkq/6M24nkxDqU8GYrKrz+UcA== } - engines: { node: ">= 10" } + '@tailwindcss/oxide-freebsd-x64@4.1.11': + resolution: {integrity: sha512-xdqKtbpHs7pQhIKmqVpxStnY1skuNh4CtbcyOHeX1YBE0hArj2romsFGb6yUmzkq/6M24nkxDqU8GYrKrz+UcA==} + engines: {node: '>= 10'} cpu: [x64] os: [freebsd] - "@tailwindcss/oxide-linux-arm-gnueabihf@4.1.11": - resolution: - { integrity: sha512-ryHQK2eyDYYMwB5wZL46uoxz2zzDZsFBwfjssgB7pzytAeCCa6glsiJGjhTEddq/4OsIjsLNMAiMlHNYnkEEeg== } - engines: { node: ">= 10" } + '@tailwindcss/oxide-linux-arm-gnueabihf@4.1.11': + resolution: {integrity: sha512-ryHQK2eyDYYMwB5wZL46uoxz2zzDZsFBwfjssgB7pzytAeCCa6glsiJGjhTEddq/4OsIjsLNMAiMlHNYnkEEeg==} + engines: {node: '>= 10'} cpu: [arm] os: [linux] - "@tailwindcss/oxide-linux-arm64-gnu@4.1.11": - resolution: - { integrity: sha512-mYwqheq4BXF83j/w75ewkPJmPZIqqP1nhoghS9D57CLjsh3Nfq0m4ftTotRYtGnZd3eCztgbSPJ9QhfC91gDZQ== } - engines: { node: ">= 10" } + '@tailwindcss/oxide-linux-arm64-gnu@4.1.11': + resolution: {integrity: sha512-mYwqheq4BXF83j/w75ewkPJmPZIqqP1nhoghS9D57CLjsh3Nfq0m4ftTotRYtGnZd3eCztgbSPJ9QhfC91gDZQ==} + engines: {node: '>= 10'} cpu: [arm64] os: [linux] - "@tailwindcss/oxide-linux-arm64-musl@4.1.11": - resolution: - { integrity: sha512-m/NVRFNGlEHJrNVk3O6I9ggVuNjXHIPoD6bqay/pubtYC9QIdAMpS+cswZQPBLvVvEF6GtSNONbDkZrjWZXYNQ== } - engines: { node: ">= 10" } + '@tailwindcss/oxide-linux-arm64-musl@4.1.11': + resolution: {integrity: sha512-m/NVRFNGlEHJrNVk3O6I9ggVuNjXHIPoD6bqay/pubtYC9QIdAMpS+cswZQPBLvVvEF6GtSNONbDkZrjWZXYNQ==} + engines: {node: '>= 10'} cpu: [arm64] os: [linux] - "@tailwindcss/oxide-linux-x64-gnu@4.1.11": - resolution: - { integrity: sha512-YW6sblI7xukSD2TdbbaeQVDysIm/UPJtObHJHKxDEcW2exAtY47j52f8jZXkqE1krdnkhCMGqP3dbniu1Te2Fg== } - engines: { node: ">= 10" } + '@tailwindcss/oxide-linux-x64-gnu@4.1.11': + resolution: {integrity: sha512-YW6sblI7xukSD2TdbbaeQVDysIm/UPJtObHJHKxDEcW2exAtY47j52f8jZXkqE1krdnkhCMGqP3dbniu1Te2Fg==} + engines: {node: '>= 10'} cpu: [x64] os: [linux] - "@tailwindcss/oxide-linux-x64-musl@4.1.11": - resolution: - { integrity: sha512-e3C/RRhGunWYNC3aSF7exsQkdXzQ/M+aYuZHKnw4U7KQwTJotnWsGOIVih0s2qQzmEzOFIJ3+xt7iq67K/p56Q== } - engines: { node: ">= 10" } + '@tailwindcss/oxide-linux-x64-musl@4.1.11': + resolution: {integrity: sha512-e3C/RRhGunWYNC3aSF7exsQkdXzQ/M+aYuZHKnw4U7KQwTJotnWsGOIVih0s2qQzmEzOFIJ3+xt7iq67K/p56Q==} + engines: {node: '>= 10'} cpu: [x64] os: [linux] - "@tailwindcss/oxide-wasm32-wasi@4.1.11": - resolution: - { integrity: sha512-Xo1+/GU0JEN/C/dvcammKHzeM6NqKovG+6921MR6oadee5XPBaKOumrJCXvopJ/Qb5TH7LX/UAywbqrP4lax0g== } - engines: { node: ">=14.0.0" } + '@tailwindcss/oxide-wasm32-wasi@4.1.11': + resolution: {integrity: sha512-Xo1+/GU0JEN/C/dvcammKHzeM6NqKovG+6921MR6oadee5XPBaKOumrJCXvopJ/Qb5TH7LX/UAywbqrP4lax0g==} + engines: {node: '>=14.0.0'} cpu: [wasm32] bundledDependencies: - - "@napi-rs/wasm-runtime" - - "@emnapi/core" - - "@emnapi/runtime" - - "@tybys/wasm-util" - - "@emnapi/wasi-threads" + - '@napi-rs/wasm-runtime' + - '@emnapi/core' + - '@emnapi/runtime' + - '@tybys/wasm-util' + - '@emnapi/wasi-threads' - tslib - "@tailwindcss/oxide-win32-arm64-msvc@4.1.11": - resolution: - { integrity: sha512-UgKYx5PwEKrac3GPNPf6HVMNhUIGuUh4wlDFR2jYYdkX6pL/rn73zTq/4pzUm8fOjAn5L8zDeHp9iXmUGOXZ+w== } - engines: { node: ">= 10" } + '@tailwindcss/oxide-win32-arm64-msvc@4.1.11': + resolution: {integrity: sha512-UgKYx5PwEKrac3GPNPf6HVMNhUIGuUh4wlDFR2jYYdkX6pL/rn73zTq/4pzUm8fOjAn5L8zDeHp9iXmUGOXZ+w==} + engines: {node: '>= 10'} cpu: [arm64] os: [win32] - "@tailwindcss/oxide-win32-x64-msvc@4.1.11": - resolution: - { integrity: sha512-YfHoggn1j0LK7wR82TOucWc5LDCguHnoS879idHekmmiR7g9HUtMw9MI0NHatS28u/Xlkfi9w5RJWgz2Dl+5Qg== } - engines: { node: ">= 10" } + '@tailwindcss/oxide-win32-x64-msvc@4.1.11': + resolution: {integrity: sha512-YfHoggn1j0LK7wR82TOucWc5LDCguHnoS879idHekmmiR7g9HUtMw9MI0NHatS28u/Xlkfi9w5RJWgz2Dl+5Qg==} + engines: {node: '>= 10'} cpu: [x64] os: [win32] - "@tailwindcss/oxide@4.1.11": - resolution: - { integrity: sha512-Q69XzrtAhuyfHo+5/HMgr1lAiPP/G40OMFAnws7xcFEYqcypZmdW8eGXaOUIeOl1dzPJBPENXgbjsOyhg2nkrg== } - engines: { node: ">= 10" } + '@tailwindcss/oxide@4.1.11': + resolution: {integrity: sha512-Q69XzrtAhuyfHo+5/HMgr1lAiPP/G40OMFAnws7xcFEYqcypZmdW8eGXaOUIeOl1dzPJBPENXgbjsOyhg2nkrg==} + engines: {node: '>= 10'} - "@tailwindcss/postcss@4.1.11": - resolution: - { integrity: sha512-q/EAIIpF6WpLhKEuQSEVMZNMIY8KhWoAemZ9eylNAih9jxMGAYPPWBn3I9QL/2jZ+e7OEz/tZkX5HwbBR4HohA== } + '@tailwindcss/postcss@4.1.11': + resolution: {integrity: sha512-q/EAIIpF6WpLhKEuQSEVMZNMIY8KhWoAemZ9eylNAih9jxMGAYPPWBn3I9QL/2jZ+e7OEz/tZkX5HwbBR4HohA==} - "@tybys/wasm-util@0.9.0": - resolution: - { integrity: sha512-6+7nlbMVX/PVDCwaIQ8nTOPveOcFLSt8GcXdx8hD0bt39uWxYT88uXzqTd4fTvqta7oeUJqudepapKNt2DYJFw== } + '@tybys/wasm-util@0.9.0': + resolution: {integrity: sha512-6+7nlbMVX/PVDCwaIQ8nTOPveOcFLSt8GcXdx8hD0bt39uWxYT88uXzqTd4fTvqta7oeUJqudepapKNt2DYJFw==} - "@types/debug@4.1.12": - resolution: - { integrity: sha512-vIChWdVG3LG1SMxEvI/AK+FWJthlrqlTu7fbrlywTkkaONwk/UAGaULXRlf8vkzFBLVm0zkMdCquhL5aOjhXPQ== } + '@types/debug@4.1.12': + resolution: {integrity: sha512-vIChWdVG3LG1SMxEvI/AK+FWJthlrqlTu7fbrlywTkkaONwk/UAGaULXRlf8vkzFBLVm0zkMdCquhL5aOjhXPQ==} - "@types/estree-jsx@1.0.5": - resolution: - { integrity: sha512-52CcUVNFyfb1A2ALocQw/Dd1BQFNmSdkuC3BkZ6iqhdMfQz7JWOFRuJFloOzjk+6WijU56m9oKXFAXc7o3Towg== } + '@types/estree-jsx@1.0.5': + resolution: {integrity: sha512-52CcUVNFyfb1A2ALocQw/Dd1BQFNmSdkuC3BkZ6iqhdMfQz7JWOFRuJFloOzjk+6WijU56m9oKXFAXc7o3Towg==} - "@types/estree@1.0.8": - resolution: - { integrity: sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w== } + '@types/estree@1.0.8': + resolution: {integrity: sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==} - "@types/hast@3.0.4": - resolution: - { integrity: sha512-WPs+bbQw5aCj+x6laNGWLH3wviHtoCv/P3+otBhbOhJgG8qtpdAMlTCxLtsTWA7LH1Oh/bFCHsBn0TPS5m30EQ== } + '@types/hast@3.0.4': + resolution: {integrity: sha512-WPs+bbQw5aCj+x6laNGWLH3wviHtoCv/P3+otBhbOhJgG8qtpdAMlTCxLtsTWA7LH1Oh/bFCHsBn0TPS5m30EQ==} - "@types/json-schema@7.0.15": - resolution: - { integrity: sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA== } + '@types/json-schema@7.0.15': + resolution: {integrity: sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==} - "@types/json5@0.0.29": - resolution: - { integrity: sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ== } + '@types/json5@0.0.29': + resolution: {integrity: sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==} - "@types/mdast@4.0.4": - resolution: - { integrity: sha512-kGaNbPh1k7AFzgpud/gMdvIm5xuECykRR+JnWKQno9TAXVa6WIVCGTPvYGekIDL4uwCZQSYbUxNBSb1aUo79oA== } + '@types/mdast@4.0.4': + resolution: {integrity: sha512-kGaNbPh1k7AFzgpud/gMdvIm5xuECykRR+JnWKQno9TAXVa6WIVCGTPvYGekIDL4uwCZQSYbUxNBSb1aUo79oA==} - "@types/mdx@2.0.13": - resolution: - { integrity: sha512-+OWZQfAYyio6YkJb3HLxDrvnx6SWWDbC0zVPfBRzUk0/nqoDyf6dNxQi3eArPe8rJ473nobTMQ/8Zk+LxJ+Yuw== } + '@types/mdx@2.0.13': + resolution: {integrity: sha512-+OWZQfAYyio6YkJb3HLxDrvnx6SWWDbC0zVPfBRzUk0/nqoDyf6dNxQi3eArPe8rJ473nobTMQ/8Zk+LxJ+Yuw==} - "@types/ms@2.1.0": - resolution: - { integrity: sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA== } + '@types/ms@2.1.0': + resolution: {integrity: sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA==} - "@types/node@22.14.0": - resolution: - { integrity: sha512-Kmpl+z84ILoG+3T/zQFyAJsU6EPTmOCj8/2+83fSN6djd6I4o7uOuGIH6vq3PrjY5BGitSbFuMN18j3iknubbA== } + '@types/node@22.14.0': + resolution: {integrity: sha512-Kmpl+z84ILoG+3T/zQFyAJsU6EPTmOCj8/2+83fSN6djd6I4o7uOuGIH6vq3PrjY5BGitSbFuMN18j3iknubbA==} - "@types/react-dom@19.1.6": - resolution: - { integrity: sha512-4hOiT/dwO8Ko0gV1m/TJZYk3y0KBnY9vzDh7W+DH17b2HFSOGgdj33dhihPeuy3l0q23+4e+hoXHV6hCC4dCXw== } + '@types/react-dom@19.1.6': + resolution: {integrity: sha512-4hOiT/dwO8Ko0gV1m/TJZYk3y0KBnY9vzDh7W+DH17b2HFSOGgdj33dhihPeuy3l0q23+4e+hoXHV6hCC4dCXw==} peerDependencies: - "@types/react": ^19.0.0 + '@types/react': ^19.0.0 - "@types/react@19.1.8": - resolution: - { integrity: sha512-AwAfQ2Wa5bCx9WP8nZL2uMZWod7J7/JSplxbTmBQ5ms6QpqNYm672H0Vu9ZVKVngQ+ii4R/byguVEUZQyeg44g== } + '@types/react@19.1.8': + resolution: {integrity: sha512-AwAfQ2Wa5bCx9WP8nZL2uMZWod7J7/JSplxbTmBQ5ms6QpqNYm672H0Vu9ZVKVngQ+ii4R/byguVEUZQyeg44g==} - "@types/unist@2.0.11": - resolution: - { integrity: sha512-CmBKiL6NNo/OqgmMn95Fk9Whlp2mtvIv+KNpQKN2F4SjvrEesubTRWGYSg+BnWZOnlCaSTU1sMpsBOzgbYhnsA== } + '@types/unist@2.0.11': + resolution: {integrity: sha512-CmBKiL6NNo/OqgmMn95Fk9Whlp2mtvIv+KNpQKN2F4SjvrEesubTRWGYSg+BnWZOnlCaSTU1sMpsBOzgbYhnsA==} - "@types/unist@3.0.3": - resolution: - { integrity: sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q== } + '@types/unist@3.0.3': + resolution: {integrity: sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==} - "@typescript-eslint/eslint-plugin@8.35.1": - resolution: - { integrity: sha512-9XNTlo7P7RJxbVeICaIIIEipqxLKguyh+3UbXuT2XQuFp6d8VOeDEGuz5IiX0dgZo8CiI6aOFLg4e8cF71SFVg== } - engines: { node: ^18.18.0 || ^20.9.0 || >=21.1.0 } + '@typescript-eslint/eslint-plugin@8.35.1': + resolution: {integrity: sha512-9XNTlo7P7RJxbVeICaIIIEipqxLKguyh+3UbXuT2XQuFp6d8VOeDEGuz5IiX0dgZo8CiI6aOFLg4e8cF71SFVg==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: - "@typescript-eslint/parser": ^8.35.1 + '@typescript-eslint/parser': ^8.35.1 eslint: ^8.57.0 || ^9.0.0 - typescript: ">=4.8.4 <5.9.0" + typescript: '>=4.8.4 <5.9.0' - "@typescript-eslint/parser@8.35.1": - resolution: - { integrity: sha512-3MyiDfrfLeK06bi/g9DqJxP5pV74LNv4rFTyvGDmT3x2p1yp1lOd+qYZfiRPIOf/oON+WRZR5wxxuF85qOar+w== } - engines: { node: ^18.18.0 || ^20.9.0 || >=21.1.0 } + '@typescript-eslint/parser@8.35.1': + resolution: {integrity: sha512-3MyiDfrfLeK06bi/g9DqJxP5pV74LNv4rFTyvGDmT3x2p1yp1lOd+qYZfiRPIOf/oON+WRZR5wxxuF85qOar+w==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: eslint: ^8.57.0 || ^9.0.0 - typescript: ">=4.8.4 <5.9.0" + typescript: '>=4.8.4 <5.9.0' - "@typescript-eslint/project-service@8.35.1": - resolution: - { integrity: sha512-VYxn/5LOpVxADAuP3NrnxxHYfzVtQzLKeldIhDhzC8UHaiQvYlXvKuVho1qLduFbJjjy5U5bkGwa3rUGUb1Q6Q== } - engines: { node: ^18.18.0 || ^20.9.0 || >=21.1.0 } + '@typescript-eslint/project-service@8.35.1': + resolution: {integrity: sha512-VYxn/5LOpVxADAuP3NrnxxHYfzVtQzLKeldIhDhzC8UHaiQvYlXvKuVho1qLduFbJjjy5U5bkGwa3rUGUb1Q6Q==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: - typescript: ">=4.8.4 <5.9.0" + typescript: '>=4.8.4 <5.9.0' - "@typescript-eslint/scope-manager@8.35.1": - resolution: - { integrity: sha512-s/Bpd4i7ht2934nG+UoSPlYXd08KYz3bmjLEb7Ye1UVob0d1ENiT3lY8bsCmik4RqfSbPw9xJJHbugpPpP5JUg== } - engines: { node: ^18.18.0 || ^20.9.0 || >=21.1.0 } + '@typescript-eslint/scope-manager@8.35.1': + resolution: {integrity: sha512-s/Bpd4i7ht2934nG+UoSPlYXd08KYz3bmjLEb7Ye1UVob0d1ENiT3lY8bsCmik4RqfSbPw9xJJHbugpPpP5JUg==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - "@typescript-eslint/tsconfig-utils@8.35.1": - resolution: - { integrity: sha512-K5/U9VmT9dTHoNowWZpz+/TObS3xqC5h0xAIjXPw+MNcKV9qg6eSatEnmeAwkjHijhACH0/N7bkhKvbt1+DXWQ== } - engines: { node: ^18.18.0 || ^20.9.0 || >=21.1.0 } + '@typescript-eslint/tsconfig-utils@8.35.1': + resolution: {integrity: sha512-K5/U9VmT9dTHoNowWZpz+/TObS3xqC5h0xAIjXPw+MNcKV9qg6eSatEnmeAwkjHijhACH0/N7bkhKvbt1+DXWQ==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: - typescript: ">=4.8.4 <5.9.0" + typescript: '>=4.8.4 <5.9.0' - "@typescript-eslint/type-utils@8.35.1": - resolution: - { integrity: sha512-HOrUBlfVRz5W2LIKpXzZoy6VTZzMu2n8q9C2V/cFngIC5U1nStJgv0tMV4sZPzdf4wQm9/ToWUFPMN9Vq9VJQQ== } - engines: { node: ^18.18.0 || ^20.9.0 || >=21.1.0 } + '@typescript-eslint/type-utils@8.35.1': + resolution: {integrity: sha512-HOrUBlfVRz5W2LIKpXzZoy6VTZzMu2n8q9C2V/cFngIC5U1nStJgv0tMV4sZPzdf4wQm9/ToWUFPMN9Vq9VJQQ==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: eslint: ^8.57.0 || ^9.0.0 - typescript: ">=4.8.4 <5.9.0" + typescript: '>=4.8.4 <5.9.0' - "@typescript-eslint/types@8.35.1": - resolution: - { integrity: sha512-q/O04vVnKHfrrhNAscndAn1tuQhIkwqnaW+eu5waD5IPts2eX1dgJxgqcPx5BX109/qAz7IG6VrEPTOYKCNfRQ== } - engines: { node: ^18.18.0 || ^20.9.0 || >=21.1.0 } + '@typescript-eslint/types@8.35.1': + resolution: {integrity: sha512-q/O04vVnKHfrrhNAscndAn1tuQhIkwqnaW+eu5waD5IPts2eX1dgJxgqcPx5BX109/qAz7IG6VrEPTOYKCNfRQ==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - "@typescript-eslint/typescript-estree@8.35.1": - resolution: - { integrity: sha512-Vvpuvj4tBxIka7cPs6Y1uvM7gJgdF5Uu9F+mBJBPY4MhvjrjWGK4H0lVgLJd/8PWZ23FTqsaJaLEkBCFUk8Y9g== } - engines: { node: ^18.18.0 || ^20.9.0 || >=21.1.0 } + '@typescript-eslint/typescript-estree@8.35.1': + resolution: {integrity: sha512-Vvpuvj4tBxIka7cPs6Y1uvM7gJgdF5Uu9F+mBJBPY4MhvjrjWGK4H0lVgLJd/8PWZ23FTqsaJaLEkBCFUk8Y9g==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: - typescript: ">=4.8.4 <5.9.0" + typescript: '>=4.8.4 <5.9.0' - "@typescript-eslint/utils@8.35.1": - resolution: - { integrity: sha512-lhnwatFmOFcazAsUm3ZnZFpXSxiwoa1Lj50HphnDe1Et01NF4+hrdXONSUHIcbVu2eFb1bAf+5yjXkGVkXBKAQ== } - engines: { node: ^18.18.0 || ^20.9.0 || >=21.1.0 } + '@typescript-eslint/utils@8.35.1': + resolution: {integrity: sha512-lhnwatFmOFcazAsUm3ZnZFpXSxiwoa1Lj50HphnDe1Et01NF4+hrdXONSUHIcbVu2eFb1bAf+5yjXkGVkXBKAQ==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: eslint: ^8.57.0 || ^9.0.0 - typescript: ">=4.8.4 <5.9.0" + typescript: '>=4.8.4 <5.9.0' - "@typescript-eslint/visitor-keys@8.35.1": - resolution: - { integrity: sha512-VRwixir4zBWCSTP/ljEo091lbpypz57PoeAQ9imjG+vbeof9LplljsL1mos4ccG6H9IjfrVGM359RozUnuFhpw== } - engines: { node: ^18.18.0 || ^20.9.0 || >=21.1.0 } + '@typescript-eslint/visitor-keys@8.35.1': + resolution: {integrity: sha512-VRwixir4zBWCSTP/ljEo091lbpypz57PoeAQ9imjG+vbeof9LplljsL1mos4ccG6H9IjfrVGM359RozUnuFhpw==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - "@ungap/structured-clone@1.3.0": - resolution: - { integrity: sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g== } + '@ungap/structured-clone@1.3.0': + resolution: {integrity: sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g==} - "@unrs/resolver-binding-android-arm-eabi@1.11.0": - resolution: - { integrity: sha512-LRw5BW29sYj9NsQC6QoqeLVQhEa+BwVINYyMlcve+6stwdBsSt5UB7zw4UZB4+4PNqIVilHoMaPWCb/KhABHQw== } + '@unrs/resolver-binding-android-arm-eabi@1.11.0': + resolution: {integrity: sha512-LRw5BW29sYj9NsQC6QoqeLVQhEa+BwVINYyMlcve+6stwdBsSt5UB7zw4UZB4+4PNqIVilHoMaPWCb/KhABHQw==} cpu: [arm] os: [android] - "@unrs/resolver-binding-android-arm64@1.11.0": - resolution: - { integrity: sha512-zYX8D2zcWCAHqghA8tPjbp7LwjVXbIZP++mpU/Mrf5jUVlk3BWIxkeB8yYzZi5GpFSlqMcRZQxQqbMI0c2lASQ== } + '@unrs/resolver-binding-android-arm64@1.11.0': + resolution: {integrity: sha512-zYX8D2zcWCAHqghA8tPjbp7LwjVXbIZP++mpU/Mrf5jUVlk3BWIxkeB8yYzZi5GpFSlqMcRZQxQqbMI0c2lASQ==} cpu: [arm64] os: [android] - "@unrs/resolver-binding-darwin-arm64@1.11.0": - resolution: - { integrity: sha512-YsYOT049hevAY/lTYD77GhRs885EXPeAfExG5KenqMJ417nYLS2N/kpRpYbABhFZBVQn+2uRPasTe4ypmYoo3w== } + '@unrs/resolver-binding-darwin-arm64@1.11.0': + resolution: {integrity: sha512-YsYOT049hevAY/lTYD77GhRs885EXPeAfExG5KenqMJ417nYLS2N/kpRpYbABhFZBVQn+2uRPasTe4ypmYoo3w==} cpu: [arm64] os: [darwin] - "@unrs/resolver-binding-darwin-x64@1.11.0": - resolution: - { integrity: sha512-PSjvk3OZf1aZImdGY5xj9ClFG3bC4gnSSYWrt+id0UAv+GwwVldhpMFjAga8SpMo2T1GjV9UKwM+QCsQCQmtdA== } + '@unrs/resolver-binding-darwin-x64@1.11.0': + resolution: {integrity: sha512-PSjvk3OZf1aZImdGY5xj9ClFG3bC4gnSSYWrt+id0UAv+GwwVldhpMFjAga8SpMo2T1GjV9UKwM+QCsQCQmtdA==} cpu: [x64] os: [darwin] - "@unrs/resolver-binding-freebsd-x64@1.11.0": - resolution: - { integrity: sha512-KC/iFaEN/wsTVYnHClyHh5RSYA9PpuGfqkFua45r4sweXpC0KHZ+BYY7ikfcGPt5w1lMpR1gneFzuqWLQxsRKg== } + '@unrs/resolver-binding-freebsd-x64@1.11.0': + resolution: {integrity: sha512-KC/iFaEN/wsTVYnHClyHh5RSYA9PpuGfqkFua45r4sweXpC0KHZ+BYY7ikfcGPt5w1lMpR1gneFzuqWLQxsRKg==} cpu: [x64] os: [freebsd] - "@unrs/resolver-binding-linux-arm-gnueabihf@1.11.0": - resolution: - { integrity: sha512-CDh/0v8uot43cB4yKtDL9CVY8pbPnMV0dHyQCE4lFz6PW/+9tS0i9eqP5a91PAqEBVMqH1ycu+k8rP6wQU846w== } + '@unrs/resolver-binding-linux-arm-gnueabihf@1.11.0': + resolution: {integrity: sha512-CDh/0v8uot43cB4yKtDL9CVY8pbPnMV0dHyQCE4lFz6PW/+9tS0i9eqP5a91PAqEBVMqH1ycu+k8rP6wQU846w==} cpu: [arm] os: [linux] - "@unrs/resolver-binding-linux-arm-musleabihf@1.11.0": - resolution: - { integrity: sha512-+TE7epATDSnvwr3L/hNHX3wQ8KQYB+jSDTdywycg3qDqvavRP8/HX9qdq/rMcnaRDn4EOtallb3vL/5wCWGCkw== } + '@unrs/resolver-binding-linux-arm-musleabihf@1.11.0': + resolution: {integrity: sha512-+TE7epATDSnvwr3L/hNHX3wQ8KQYB+jSDTdywycg3qDqvavRP8/HX9qdq/rMcnaRDn4EOtallb3vL/5wCWGCkw==} cpu: [arm] os: [linux] - "@unrs/resolver-binding-linux-arm64-gnu@1.11.0": - resolution: - { integrity: sha512-VBAYGg3VahofpQ+L4k/ZO8TSICIbUKKTaMYOWHWfuYBFqPbSkArZZLezw3xd27fQkxX4BaLGb/RKnW0dH9Y/UA== } + '@unrs/resolver-binding-linux-arm64-gnu@1.11.0': + resolution: {integrity: sha512-VBAYGg3VahofpQ+L4k/ZO8TSICIbUKKTaMYOWHWfuYBFqPbSkArZZLezw3xd27fQkxX4BaLGb/RKnW0dH9Y/UA==} cpu: [arm64] os: [linux] - "@unrs/resolver-binding-linux-arm64-musl@1.11.0": - resolution: - { integrity: sha512-9IgGFUUb02J1hqdRAHXpZHIeUHRrbnGo6vrRbz0fREH7g+rzQy53/IBSyadZ/LG5iqMxukriNPu4hEMUn+uWEg== } + '@unrs/resolver-binding-linux-arm64-musl@1.11.0': + resolution: {integrity: sha512-9IgGFUUb02J1hqdRAHXpZHIeUHRrbnGo6vrRbz0fREH7g+rzQy53/IBSyadZ/LG5iqMxukriNPu4hEMUn+uWEg==} cpu: [arm64] os: [linux] - "@unrs/resolver-binding-linux-ppc64-gnu@1.11.0": - resolution: - { integrity: sha512-LR4iQ/LPjMfivpL2bQ9kmm3UnTas3U+umcCnq/CV7HAkukVdHxrDD1wwx74MIWbbgzQTLPYY7Ur2MnnvkYJCBQ== } + '@unrs/resolver-binding-linux-ppc64-gnu@1.11.0': + resolution: {integrity: sha512-LR4iQ/LPjMfivpL2bQ9kmm3UnTas3U+umcCnq/CV7HAkukVdHxrDD1wwx74MIWbbgzQTLPYY7Ur2MnnvkYJCBQ==} cpu: [ppc64] os: [linux] - "@unrs/resolver-binding-linux-riscv64-gnu@1.11.0": - resolution: - { integrity: sha512-HCupFQwMrRhrOg7YHrobbB5ADg0Q8RNiuefqMHVsdhEy9lLyXm/CxsCXeLJdrg27NAPsCaMDtdlm8Z2X8x91Tg== } + '@unrs/resolver-binding-linux-riscv64-gnu@1.11.0': + resolution: {integrity: sha512-HCupFQwMrRhrOg7YHrobbB5ADg0Q8RNiuefqMHVsdhEy9lLyXm/CxsCXeLJdrg27NAPsCaMDtdlm8Z2X8x91Tg==} cpu: [riscv64] os: [linux] - "@unrs/resolver-binding-linux-riscv64-musl@1.11.0": - resolution: - { integrity: sha512-Ckxy76A5xgjWa4FNrzcKul5qFMWgP5JSQ5YKd0XakmWOddPLSkQT+uAvUpQNnFGNbgKzv90DyQlxPDYPQ4nd6A== } + '@unrs/resolver-binding-linux-riscv64-musl@1.11.0': + resolution: {integrity: sha512-Ckxy76A5xgjWa4FNrzcKul5qFMWgP5JSQ5YKd0XakmWOddPLSkQT+uAvUpQNnFGNbgKzv90DyQlxPDYPQ4nd6A==} cpu: [riscv64] os: [linux] - "@unrs/resolver-binding-linux-s390x-gnu@1.11.0": - resolution: - { integrity: sha512-HfO0PUCCRte2pMJmVyxPI+eqT7KuV3Fnvn2RPvMe5mOzb2BJKf4/Vth8sSt9cerQboMaTVpbxyYjjLBWIuI5BQ== } + '@unrs/resolver-binding-linux-s390x-gnu@1.11.0': + resolution: {integrity: sha512-HfO0PUCCRte2pMJmVyxPI+eqT7KuV3Fnvn2RPvMe5mOzb2BJKf4/Vth8sSt9cerQboMaTVpbxyYjjLBWIuI5BQ==} cpu: [s390x] os: [linux] - "@unrs/resolver-binding-linux-x64-gnu@1.11.0": - resolution: - { integrity: sha512-9PZdjP7tLOEjpXHS6+B/RNqtfVUyDEmaViPOuSqcbomLdkJnalt5RKQ1tr2m16+qAufV0aDkfhXtoO7DQos/jg== } + '@unrs/resolver-binding-linux-x64-gnu@1.11.0': + resolution: {integrity: sha512-9PZdjP7tLOEjpXHS6+B/RNqtfVUyDEmaViPOuSqcbomLdkJnalt5RKQ1tr2m16+qAufV0aDkfhXtoO7DQos/jg==} cpu: [x64] os: [linux] - "@unrs/resolver-binding-linux-x64-musl@1.11.0": - resolution: - { integrity: sha512-qkE99ieiSKMnFJY/EfyGKVtNra52/k+lVF/PbO4EL5nU6AdvG4XhtJ+WHojAJP7ID9BNIra/yd75EHndewNRfA== } + '@unrs/resolver-binding-linux-x64-musl@1.11.0': + resolution: {integrity: sha512-qkE99ieiSKMnFJY/EfyGKVtNra52/k+lVF/PbO4EL5nU6AdvG4XhtJ+WHojAJP7ID9BNIra/yd75EHndewNRfA==} cpu: [x64] os: [linux] - "@unrs/resolver-binding-wasm32-wasi@1.11.0": - resolution: - { integrity: sha512-MjXek8UL9tIX34gymvQLecz2hMaQzOlaqYJJBomwm1gsvK2F7hF+YqJJ2tRyBDTv9EZJGMt4KlKkSD/gZWCOiw== } - engines: { node: ">=14.0.0" } + '@unrs/resolver-binding-wasm32-wasi@1.11.0': + resolution: {integrity: sha512-MjXek8UL9tIX34gymvQLecz2hMaQzOlaqYJJBomwm1gsvK2F7hF+YqJJ2tRyBDTv9EZJGMt4KlKkSD/gZWCOiw==} + engines: {node: '>=14.0.0'} cpu: [wasm32] - "@unrs/resolver-binding-win32-arm64-msvc@1.11.0": - resolution: - { integrity: sha512-9LT6zIGO7CHybiQSh7DnQGwFMZvVr0kUjah6qQfkH2ghucxPV6e71sUXJdSM4Ba0MaGE6DC/NwWf7mJmc3DAng== } + '@unrs/resolver-binding-win32-arm64-msvc@1.11.0': + resolution: {integrity: sha512-9LT6zIGO7CHybiQSh7DnQGwFMZvVr0kUjah6qQfkH2ghucxPV6e71sUXJdSM4Ba0MaGE6DC/NwWf7mJmc3DAng==} cpu: [arm64] os: [win32] - "@unrs/resolver-binding-win32-ia32-msvc@1.11.0": - resolution: - { integrity: sha512-HYchBYOZ7WN266VjoGm20xFv5EonG/ODURRgwl9EZT7Bq1nLEs6VKJddzfFdXEAho0wfFlt8L/xIiE29Pmy1RA== } + '@unrs/resolver-binding-win32-ia32-msvc@1.11.0': + resolution: {integrity: sha512-HYchBYOZ7WN266VjoGm20xFv5EonG/ODURRgwl9EZT7Bq1nLEs6VKJddzfFdXEAho0wfFlt8L/xIiE29Pmy1RA==} cpu: [ia32] os: [win32] - "@unrs/resolver-binding-win32-x64-msvc@1.11.0": - resolution: - { integrity: sha512-+oLKLHw3I1UQo4MeHfoLYF+e6YBa8p5vYUw3Rgt7IDzCs+57vIZqQlIo62NDpYM0VG6BjWOwnzBczMvbtH8hag== } + '@unrs/resolver-binding-win32-x64-msvc@1.11.0': + resolution: {integrity: sha512-+oLKLHw3I1UQo4MeHfoLYF+e6YBa8p5vYUw3Rgt7IDzCs+57vIZqQlIo62NDpYM0VG6BjWOwnzBczMvbtH8hag==} cpu: [x64] os: [win32] acorn-jsx@5.3.2: - resolution: - { integrity: sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ== } + resolution: {integrity: sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==} peerDependencies: acorn: ^6.0.0 || ^7.0.0 || ^8.0.0 acorn@8.15.0: - resolution: - { integrity: sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg== } - engines: { node: ">=0.4.0" } + resolution: {integrity: sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==} + engines: {node: '>=0.4.0'} hasBin: true ajv@6.12.6: - resolution: - { integrity: sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g== } + resolution: {integrity: sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==} ansi-styles@4.3.0: - resolution: - { integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg== } - engines: { node: ">=8" } + resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==} + engines: {node: '>=8'} argparse@2.0.1: - resolution: - { integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q== } + resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==} aria-hidden@1.2.6: - resolution: - { integrity: sha512-ik3ZgC9dY/lYVVM++OISsaYDeg1tb0VtP5uL3ouh1koGOaUMDPpbFIei4JkFimWUFPn90sbMNMXQAIVOlnYKJA== } - engines: { node: ">=10" } + resolution: {integrity: sha512-ik3ZgC9dY/lYVVM++OISsaYDeg1tb0VtP5uL3ouh1koGOaUMDPpbFIei4JkFimWUFPn90sbMNMXQAIVOlnYKJA==} + engines: {node: '>=10'} aria-query@5.3.2: - resolution: - { integrity: sha512-COROpnaoap1E2F000S62r6A60uHZnmlvomhfyT2DlTcrY1OrBKn2UhH7qn5wTC9zMvD0AY7csdPSNwKP+7WiQw== } - engines: { node: ">= 0.4" } + resolution: {integrity: sha512-COROpnaoap1E2F000S62r6A60uHZnmlvomhfyT2DlTcrY1OrBKn2UhH7qn5wTC9zMvD0AY7csdPSNwKP+7WiQw==} + engines: {node: '>= 0.4'} array-buffer-byte-length@1.0.2: - resolution: - { integrity: sha512-LHE+8BuR7RYGDKvnrmcuSq3tDcKv9OFEXQt/HpbZhY7V6h0zlUXutnAD82GiFx9rdieCMjkvtcsPqBwgUl1Iiw== } - engines: { node: ">= 0.4" } + resolution: {integrity: sha512-LHE+8BuR7RYGDKvnrmcuSq3tDcKv9OFEXQt/HpbZhY7V6h0zlUXutnAD82GiFx9rdieCMjkvtcsPqBwgUl1Iiw==} + engines: {node: '>= 0.4'} array-includes@3.1.9: - resolution: - { integrity: sha512-FmeCCAenzH0KH381SPT5FZmiA/TmpndpcaShhfgEN9eCVjnFBqq3l1xrI42y8+PPLI6hypzou4GXw00WHmPBLQ== } - engines: { node: ">= 0.4" } + resolution: {integrity: sha512-FmeCCAenzH0KH381SPT5FZmiA/TmpndpcaShhfgEN9eCVjnFBqq3l1xrI42y8+PPLI6hypzou4GXw00WHmPBLQ==} + engines: {node: '>= 0.4'} array.prototype.findlast@1.2.5: - resolution: - { integrity: sha512-CVvd6FHg1Z3POpBLxO6E6zr+rSKEQ9L6rZHAaY7lLfhKsWYUBBOuMs0e9o24oopj6H+geRCX0YJ+TJLBK2eHyQ== } - engines: { node: ">= 0.4" } + resolution: {integrity: sha512-CVvd6FHg1Z3POpBLxO6E6zr+rSKEQ9L6rZHAaY7lLfhKsWYUBBOuMs0e9o24oopj6H+geRCX0YJ+TJLBK2eHyQ==} + engines: {node: '>= 0.4'} array.prototype.findlastindex@1.2.6: - resolution: - { integrity: sha512-F/TKATkzseUExPlfvmwQKGITM3DGTK+vkAsCZoDc5daVygbJBnjEUCbgkAvVFsgfXfX4YIqZ/27G3k3tdXrTxQ== } - engines: { node: ">= 0.4" } + resolution: {integrity: sha512-F/TKATkzseUExPlfvmwQKGITM3DGTK+vkAsCZoDc5daVygbJBnjEUCbgkAvVFsgfXfX4YIqZ/27G3k3tdXrTxQ==} + engines: {node: '>= 0.4'} array.prototype.flat@1.3.3: - resolution: - { integrity: sha512-rwG/ja1neyLqCuGZ5YYrznA62D4mZXg0i1cIskIUKSiqF3Cje9/wXAls9B9s1Wa2fomMsIv8czB8jZcPmxCXFg== } - engines: { node: ">= 0.4" } + resolution: {integrity: sha512-rwG/ja1neyLqCuGZ5YYrznA62D4mZXg0i1cIskIUKSiqF3Cje9/wXAls9B9s1Wa2fomMsIv8czB8jZcPmxCXFg==} + engines: {node: '>= 0.4'} array.prototype.flatmap@1.3.3: - resolution: - { integrity: sha512-Y7Wt51eKJSyi80hFrJCePGGNo5ktJCslFuboqJsbf57CCPcm5zztluPlc4/aD8sWsKvlwatezpV4U1efk8kpjg== } - engines: { node: ">= 0.4" } + resolution: {integrity: sha512-Y7Wt51eKJSyi80hFrJCePGGNo5ktJCslFuboqJsbf57CCPcm5zztluPlc4/aD8sWsKvlwatezpV4U1efk8kpjg==} + engines: {node: '>= 0.4'} array.prototype.tosorted@1.1.4: - resolution: - { integrity: sha512-p6Fx8B7b7ZhL/gmUsAy0D15WhvDccw3mnGNbZpi3pmeJdxtWsj2jEaI4Y6oo3XiHfzuSgPwKc04MYt6KgvC/wA== } - engines: { node: ">= 0.4" } + resolution: {integrity: sha512-p6Fx8B7b7ZhL/gmUsAy0D15WhvDccw3mnGNbZpi3pmeJdxtWsj2jEaI4Y6oo3XiHfzuSgPwKc04MYt6KgvC/wA==} + engines: {node: '>= 0.4'} arraybuffer.prototype.slice@1.0.4: - resolution: - { integrity: sha512-BNoCY6SXXPQ7gF2opIP4GBE+Xw7U+pHMYKuzjgCN3GwiaIR09UUeKfheyIry77QtrCBlC0KK0q5/TER/tYh3PQ== } - engines: { node: ">= 0.4" } + resolution: {integrity: sha512-BNoCY6SXXPQ7gF2opIP4GBE+Xw7U+pHMYKuzjgCN3GwiaIR09UUeKfheyIry77QtrCBlC0KK0q5/TER/tYh3PQ==} + engines: {node: '>= 0.4'} ast-types-flow@0.0.8: - resolution: - { integrity: sha512-OH/2E5Fg20h2aPrbe+QL8JZQFko0YZaF+j4mnQ7BGhfavO7OpSLa8a0y9sBwomHdSbkhTS8TQNayBfnW5DwbvQ== } + resolution: {integrity: sha512-OH/2E5Fg20h2aPrbe+QL8JZQFko0YZaF+j4mnQ7BGhfavO7OpSLa8a0y9sBwomHdSbkhTS8TQNayBfnW5DwbvQ==} astring@1.9.0: - resolution: - { integrity: sha512-LElXdjswlqjWrPpJFg1Fx4wpkOCxj1TDHlSV4PlaRxHGWko024xICaa97ZkMfs6DRKlCguiAI+rbXv5GWwXIkg== } + resolution: {integrity: sha512-LElXdjswlqjWrPpJFg1Fx4wpkOCxj1TDHlSV4PlaRxHGWko024xICaa97ZkMfs6DRKlCguiAI+rbXv5GWwXIkg==} hasBin: true async-function@1.0.0: - resolution: - { integrity: sha512-hsU18Ae8CDTR6Kgu9DYf0EbCr/a5iGL0rytQDobUcdpYOKokk8LEjVphnXkDkgpi0wYVsqrXuP0bZxJaTqdgoA== } - engines: { node: ">= 0.4" } + resolution: {integrity: sha512-hsU18Ae8CDTR6Kgu9DYf0EbCr/a5iGL0rytQDobUcdpYOKokk8LEjVphnXkDkgpi0wYVsqrXuP0bZxJaTqdgoA==} + engines: {node: '>= 0.4'} available-typed-arrays@1.0.7: - resolution: - { integrity: sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ== } - engines: { node: ">= 0.4" } + resolution: {integrity: sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ==} + engines: {node: '>= 0.4'} axe-core@4.10.3: - resolution: - { integrity: sha512-Xm7bpRXnDSX2YE2YFfBk2FnF0ep6tmG7xPh8iHee8MIcrgq762Nkce856dYtJYLkuIoYZvGfTs/PbZhideTcEg== } - engines: { node: ">=4" } + resolution: {integrity: sha512-Xm7bpRXnDSX2YE2YFfBk2FnF0ep6tmG7xPh8iHee8MIcrgq762Nkce856dYtJYLkuIoYZvGfTs/PbZhideTcEg==} + engines: {node: '>=4'} axobject-query@4.1.0: - resolution: - { integrity: sha512-qIj0G9wZbMGNLjLmg1PT6v2mE9AH2zlnADJD/2tC6E00hgmhUOfEB6greHPAfLRSufHqROIUTkw6E+M3lH0PTQ== } - engines: { node: ">= 0.4" } + resolution: {integrity: sha512-qIj0G9wZbMGNLjLmg1PT6v2mE9AH2zlnADJD/2tC6E00hgmhUOfEB6greHPAfLRSufHqROIUTkw6E+M3lH0PTQ==} + engines: {node: '>= 0.4'} bail@2.0.2: - resolution: - { integrity: sha512-0xO6mYd7JB2YesxDKplafRpsiOzPt9V02ddPCLbY1xYGPOX24NTyN50qnUxgCPcSoYMhKpAuBTjQoRZCAkUDRw== } + resolution: {integrity: sha512-0xO6mYd7JB2YesxDKplafRpsiOzPt9V02ddPCLbY1xYGPOX24NTyN50qnUxgCPcSoYMhKpAuBTjQoRZCAkUDRw==} balanced-match@1.0.2: - resolution: - { integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw== } + resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} brace-expansion@1.1.12: - resolution: - { integrity: sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg== } + resolution: {integrity: sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==} brace-expansion@2.0.2: - resolution: - { integrity: sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ== } + resolution: {integrity: sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==} braces@3.0.3: - resolution: - { integrity: sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA== } - engines: { node: ">=8" } + resolution: {integrity: sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==} + engines: {node: '>=8'} busboy@1.6.0: - resolution: - { integrity: sha512-8SFQbg/0hQ9xy3UNTB0YEnsNBbWfhf7RtnzpL7TkBiTBRfrQ9Fxcnz7VJsleJpyp6rVLvXiuORqjlHi5q+PYuA== } - engines: { node: ">=10.16.0" } + resolution: {integrity: sha512-8SFQbg/0hQ9xy3UNTB0YEnsNBbWfhf7RtnzpL7TkBiTBRfrQ9Fxcnz7VJsleJpyp6rVLvXiuORqjlHi5q+PYuA==} + engines: {node: '>=10.16.0'} call-bind-apply-helpers@1.0.2: - resolution: - { integrity: sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ== } - engines: { node: ">= 0.4" } + resolution: {integrity: sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==} + engines: {node: '>= 0.4'} call-bind@1.0.8: - resolution: - { integrity: sha512-oKlSFMcMwpUg2ednkhQ454wfWiU/ul3CkJe/PEHcTKuiX6RpbehUiFMXu13HalGZxfUwCQzZG747YXBn1im9ww== } - engines: { node: ">= 0.4" } + resolution: {integrity: sha512-oKlSFMcMwpUg2ednkhQ454wfWiU/ul3CkJe/PEHcTKuiX6RpbehUiFMXu13HalGZxfUwCQzZG747YXBn1im9ww==} + engines: {node: '>= 0.4'} call-bound@1.0.4: - resolution: - { integrity: sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg== } - engines: { node: ">= 0.4" } + resolution: {integrity: sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==} + engines: {node: '>= 0.4'} callsites@3.1.0: - resolution: - { integrity: sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ== } - engines: { node: ">=6" } + resolution: {integrity: sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==} + engines: {node: '>=6'} caniuse-lite@1.0.30001727: - resolution: - { integrity: sha512-pB68nIHmbN6L/4C6MH1DokyR3bYqFwjaSs/sWDHGj4CTcFtQUQMuJftVwWkXq7mNWOybD3KhUv3oWHoGxgP14Q== } + resolution: {integrity: sha512-pB68nIHmbN6L/4C6MH1DokyR3bYqFwjaSs/sWDHGj4CTcFtQUQMuJftVwWkXq7mNWOybD3KhUv3oWHoGxgP14Q==} ccount@2.0.1: - resolution: - { integrity: sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg== } + resolution: {integrity: sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg==} chalk@4.1.2: - resolution: - { integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA== } - engines: { node: ">=10" } + resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==} + engines: {node: '>=10'} character-entities-html4@2.1.0: - resolution: - { integrity: sha512-1v7fgQRj6hnSwFpq1Eu0ynr/CDEw0rXo2B61qXrLNdHZmPKgb7fqS1a2JwF0rISo9q77jDI8VMEHoApn8qDoZA== } + resolution: {integrity: sha512-1v7fgQRj6hnSwFpq1Eu0ynr/CDEw0rXo2B61qXrLNdHZmPKgb7fqS1a2JwF0rISo9q77jDI8VMEHoApn8qDoZA==} character-entities-legacy@3.0.0: - resolution: - { integrity: sha512-RpPp0asT/6ufRm//AJVwpViZbGM/MkjQFxJccQRHmISF/22NBtsHqAWmL+/pmkPWoIUJdWyeVleTl1wydHATVQ== } + resolution: {integrity: sha512-RpPp0asT/6ufRm//AJVwpViZbGM/MkjQFxJccQRHmISF/22NBtsHqAWmL+/pmkPWoIUJdWyeVleTl1wydHATVQ==} character-entities@2.0.2: - resolution: - { integrity: sha512-shx7oQ0Awen/BRIdkjkvz54PnEEI/EjwXDSIZp86/KKdbafHh1Df/RYGBhn4hbe2+uKC9FnT5UCEdyPz3ai9hQ== } + resolution: {integrity: sha512-shx7oQ0Awen/BRIdkjkvz54PnEEI/EjwXDSIZp86/KKdbafHh1Df/RYGBhn4hbe2+uKC9FnT5UCEdyPz3ai9hQ==} character-reference-invalid@2.0.1: - resolution: - { integrity: sha512-iBZ4F4wRbyORVsu0jPV7gXkOsGYjGHPmAyv+HiHG8gi5PtC9KI2j1+v8/tlibRvjoWX027ypmG/n0HtO5t7unw== } + resolution: {integrity: sha512-iBZ4F4wRbyORVsu0jPV7gXkOsGYjGHPmAyv+HiHG8gi5PtC9KI2j1+v8/tlibRvjoWX027ypmG/n0HtO5t7unw==} chokidar@4.0.3: - resolution: - { integrity: sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA== } - engines: { node: ">= 14.16.0" } + resolution: {integrity: sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==} + engines: {node: '>= 14.16.0'} chownr@3.0.0: - resolution: - { integrity: sha512-+IxzY9BZOQd/XuYPRmrvEVjF/nqj5kgT4kEq7VofrDoM1MxoRjEWkrCC3EtLi59TVawxTAn+orJwFQcrqEN1+g== } - engines: { node: ">=18" } + resolution: {integrity: sha512-+IxzY9BZOQd/XuYPRmrvEVjF/nqj5kgT4kEq7VofrDoM1MxoRjEWkrCC3EtLi59TVawxTAn+orJwFQcrqEN1+g==} + engines: {node: '>=18'} class-variance-authority@0.7.1: - resolution: - { integrity: sha512-Ka+9Trutv7G8M6WT6SeiRWz792K5qEqIGEGzXKhAE6xOWAY6pPH8U+9IY3oCMv6kqTmLsv7Xh/2w2RigkePMsg== } + resolution: {integrity: sha512-Ka+9Trutv7G8M6WT6SeiRWz792K5qEqIGEGzXKhAE6xOWAY6pPH8U+9IY3oCMv6kqTmLsv7Xh/2w2RigkePMsg==} client-only@0.0.1: - resolution: - { integrity: sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA== } + resolution: {integrity: sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA==} clsx@2.1.1: - resolution: - { integrity: sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA== } - engines: { node: ">=6" } + resolution: {integrity: sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==} + engines: {node: '>=6'} collapse-white-space@2.1.0: - resolution: - { integrity: sha512-loKTxY1zCOuG4j9f6EPnuyyYkf58RnhhWTvRoZEokgB+WbdXehfjFviyOVYkqzEWz1Q5kRiZdBYS5SwxbQYwzw== } + resolution: {integrity: sha512-loKTxY1zCOuG4j9f6EPnuyyYkf58RnhhWTvRoZEokgB+WbdXehfjFviyOVYkqzEWz1Q5kRiZdBYS5SwxbQYwzw==} color-convert@2.0.1: - resolution: - { integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ== } - engines: { node: ">=7.0.0" } + resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==} + engines: {node: '>=7.0.0'} color-name@1.1.4: - resolution: - { integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA== } + resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==} color-string@1.9.1: - resolution: - { integrity: sha512-shrVawQFojnZv6xM40anx4CkoDP+fZsw/ZerEMsW/pyzsRbElpsL/DBVW7q3ExxwusdNXI3lXpuhEZkzs8p5Eg== } + resolution: {integrity: sha512-shrVawQFojnZv6xM40anx4CkoDP+fZsw/ZerEMsW/pyzsRbElpsL/DBVW7q3ExxwusdNXI3lXpuhEZkzs8p5Eg==} color@4.2.3: - resolution: - { integrity: sha512-1rXeuUUiGGrykh+CeBdu5Ie7OJwinCgQY0bc7GCRxy5xVHy+moaqkpL/jqQq0MtQOeYcrqEz4abc5f0KtU7W4A== } - engines: { node: ">=12.5.0" } + resolution: {integrity: sha512-1rXeuUUiGGrykh+CeBdu5Ie7OJwinCgQY0bc7GCRxy5xVHy+moaqkpL/jqQq0MtQOeYcrqEz4abc5f0KtU7W4A==} + engines: {node: '>=12.5.0'} comma-separated-tokens@2.0.3: - resolution: - { integrity: sha512-Fu4hJdvzeylCfQPp9SGWidpzrMs7tTrlu6Vb8XGaRGck8QSNZJJp538Wrb60Lax4fPwR64ViY468OIUTbRlGZg== } + resolution: {integrity: sha512-Fu4hJdvzeylCfQPp9SGWidpzrMs7tTrlu6Vb8XGaRGck8QSNZJJp538Wrb60Lax4fPwR64ViY468OIUTbRlGZg==} compute-scroll-into-view@3.1.1: - resolution: - { integrity: sha512-VRhuHOLoKYOy4UbilLbUzbYg93XLjv2PncJC50EuTWPA3gaja1UjBsUP/D/9/juV3vQFr6XBEzn9KCAHdUvOHw== } + resolution: {integrity: sha512-VRhuHOLoKYOy4UbilLbUzbYg93XLjv2PncJC50EuTWPA3gaja1UjBsUP/D/9/juV3vQFr6XBEzn9KCAHdUvOHw==} concat-map@0.0.1: - resolution: - { integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg== } + resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==} cross-spawn@7.0.6: - resolution: - { integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA== } - engines: { node: ">= 8" } + resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==} + engines: {node: '>= 8'} cssesc@3.0.0: - resolution: - { integrity: sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg== } - engines: { node: ">=4" } + resolution: {integrity: sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==} + engines: {node: '>=4'} hasBin: true csstype@3.1.3: - resolution: - { integrity: sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw== } + resolution: {integrity: sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==} damerau-levenshtein@1.0.8: - resolution: - { integrity: sha512-sdQSFB7+llfUcQHUQO3+B8ERRj0Oa4w9POWMI/puGtuf7gFywGmkaLCElnudfTiKZV+NvHqL0ifzdrI8Ro7ESA== } + resolution: {integrity: sha512-sdQSFB7+llfUcQHUQO3+B8ERRj0Oa4w9POWMI/puGtuf7gFywGmkaLCElnudfTiKZV+NvHqL0ifzdrI8Ro7ESA==} data-view-buffer@1.0.2: - resolution: - { integrity: sha512-EmKO5V3OLXh1rtK2wgXRansaK1/mtVdTUEiEI0W8RkvgT05kfxaH29PliLnpLP73yYO6142Q72QNa8Wx/A5CqQ== } - engines: { node: ">= 0.4" } + resolution: {integrity: sha512-EmKO5V3OLXh1rtK2wgXRansaK1/mtVdTUEiEI0W8RkvgT05kfxaH29PliLnpLP73yYO6142Q72QNa8Wx/A5CqQ==} + engines: {node: '>= 0.4'} data-view-byte-length@1.0.2: - resolution: - { integrity: sha512-tuhGbE6CfTM9+5ANGf+oQb72Ky/0+s3xKUpHvShfiz2RxMFgFPjsXuRLBVMtvMs15awe45SRb83D6wH4ew6wlQ== } - engines: { node: ">= 0.4" } + resolution: {integrity: sha512-tuhGbE6CfTM9+5ANGf+oQb72Ky/0+s3xKUpHvShfiz2RxMFgFPjsXuRLBVMtvMs15awe45SRb83D6wH4ew6wlQ==} + engines: {node: '>= 0.4'} data-view-byte-offset@1.0.1: - resolution: - { integrity: sha512-BS8PfmtDGnrgYdOonGZQdLZslWIeCGFP9tpan0hi1Co2Zr2NKADsvGYA8XxuG/4UWgJ6Cjtv+YJnB6MM69QGlQ== } - engines: { node: ">= 0.4" } + resolution: {integrity: sha512-BS8PfmtDGnrgYdOonGZQdLZslWIeCGFP9tpan0hi1Co2Zr2NKADsvGYA8XxuG/4UWgJ6Cjtv+YJnB6MM69QGlQ==} + engines: {node: '>= 0.4'} debug@3.2.7: - resolution: - { integrity: sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ== } + resolution: {integrity: sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==} peerDependencies: - supports-color: "*" + supports-color: '*' peerDependenciesMeta: supports-color: optional: true debug@4.4.1: - resolution: - { integrity: sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ== } - engines: { node: ">=6.0" } + resolution: {integrity: sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==} + engines: {node: '>=6.0'} peerDependencies: - supports-color: "*" + supports-color: '*' peerDependenciesMeta: supports-color: optional: true decode-named-character-reference@1.2.0: - resolution: - { integrity: sha512-c6fcElNV6ShtZXmsgNgFFV5tVX2PaV4g+MOAkb8eXHvn6sryJBrZa9r0zV6+dtTyoCKxtDy5tyQ5ZwQuidtd+Q== } + resolution: {integrity: sha512-c6fcElNV6ShtZXmsgNgFFV5tVX2PaV4g+MOAkb8eXHvn6sryJBrZa9r0zV6+dtTyoCKxtDy5tyQ5ZwQuidtd+Q==} deep-is@0.1.4: - resolution: - { integrity: sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ== } + resolution: {integrity: sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==} define-data-property@1.1.4: - resolution: - { integrity: sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A== } - engines: { node: ">= 0.4" } + resolution: {integrity: sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==} + engines: {node: '>= 0.4'} define-properties@1.2.1: - resolution: - { integrity: sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg== } - engines: { node: ">= 0.4" } + resolution: {integrity: sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==} + engines: {node: '>= 0.4'} dequal@2.0.3: - resolution: - { integrity: sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA== } - engines: { node: ">=6" } + resolution: {integrity: sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==} + engines: {node: '>=6'} detect-libc@2.0.4: - resolution: - { integrity: sha512-3UDv+G9CsCKO1WKMGw9fwq/SWJYbI0c5Y7LU1AXYoDdbhE2AHQ6N6Nb34sG8Fj7T5APy8qXDCKuuIHd1BR0tVA== } - engines: { node: ">=8" } + resolution: {integrity: sha512-3UDv+G9CsCKO1WKMGw9fwq/SWJYbI0c5Y7LU1AXYoDdbhE2AHQ6N6Nb34sG8Fj7T5APy8qXDCKuuIHd1BR0tVA==} + engines: {node: '>=8'} detect-node-es@1.1.0: - resolution: - { integrity: sha512-ypdmJU/TbBby2Dxibuv7ZLW3Bs1QEmM7nHjEANfohJLvE0XVujisn1qPJcZxg+qDucsr+bP6fLD1rPS3AhJ7EQ== } + resolution: {integrity: sha512-ypdmJU/TbBby2Dxibuv7ZLW3Bs1QEmM7nHjEANfohJLvE0XVujisn1qPJcZxg+qDucsr+bP6fLD1rPS3AhJ7EQ==} devlop@1.1.0: - resolution: - { integrity: sha512-RWmIqhcFf1lRYBvNmr7qTNuyCt/7/ns2jbpp1+PalgE/rDQcBT0fioSMUpJ93irlUhC5hrg4cYqe6U+0ImW0rA== } + resolution: {integrity: sha512-RWmIqhcFf1lRYBvNmr7qTNuyCt/7/ns2jbpp1+PalgE/rDQcBT0fioSMUpJ93irlUhC5hrg4cYqe6U+0ImW0rA==} doctrine@2.1.0: - resolution: - { integrity: sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw== } - engines: { node: ">=0.10.0" } + resolution: {integrity: sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==} + engines: {node: '>=0.10.0'} dunder-proto@1.0.1: - resolution: - { integrity: sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A== } - engines: { node: ">= 0.4" } + resolution: {integrity: sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==} + engines: {node: '>= 0.4'} emoji-regex@9.2.2: - resolution: - { integrity: sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg== } + resolution: {integrity: sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==} enhanced-resolve@5.18.2: - resolution: - { integrity: sha512-6Jw4sE1maoRJo3q8MsSIn2onJFbLTOjY9hlx4DZXmOKvLRd1Ok2kXmAGXaafL2+ijsJZ1ClYbl/pmqr9+k4iUQ== } - engines: { node: ">=10.13.0" } + resolution: {integrity: sha512-6Jw4sE1maoRJo3q8MsSIn2onJFbLTOjY9hlx4DZXmOKvLRd1Ok2kXmAGXaafL2+ijsJZ1ClYbl/pmqr9+k4iUQ==} + engines: {node: '>=10.13.0'} es-abstract@1.24.0: - resolution: - { integrity: sha512-WSzPgsdLtTcQwm4CROfS5ju2Wa1QQcVeT37jFjYzdFz1r9ahadC8B8/a4qxJxM+09F18iumCdRmlr96ZYkQvEg== } - engines: { node: ">= 0.4" } + resolution: {integrity: sha512-WSzPgsdLtTcQwm4CROfS5ju2Wa1QQcVeT37jFjYzdFz1r9ahadC8B8/a4qxJxM+09F18iumCdRmlr96ZYkQvEg==} + engines: {node: '>= 0.4'} es-define-property@1.0.1: - resolution: - { integrity: sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g== } - engines: { node: ">= 0.4" } + resolution: {integrity: sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==} + engines: {node: '>= 0.4'} es-errors@1.3.0: - resolution: - { integrity: sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw== } - engines: { node: ">= 0.4" } + resolution: {integrity: sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==} + engines: {node: '>= 0.4'} es-iterator-helpers@1.2.1: - resolution: - { integrity: sha512-uDn+FE1yrDzyC0pCo961B2IHbdM8y/ACZsKD4dG6WqrjV53BADjwa7D+1aom2rsNVfLyDgU/eigvlJGJ08OQ4w== } - engines: { node: ">= 0.4" } + resolution: {integrity: sha512-uDn+FE1yrDzyC0pCo961B2IHbdM8y/ACZsKD4dG6WqrjV53BADjwa7D+1aom2rsNVfLyDgU/eigvlJGJ08OQ4w==} + engines: {node: '>= 0.4'} es-object-atoms@1.1.1: - resolution: - { integrity: sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA== } - engines: { node: ">= 0.4" } + resolution: {integrity: sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==} + engines: {node: '>= 0.4'} es-set-tostringtag@2.1.0: - resolution: - { integrity: sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA== } - engines: { node: ">= 0.4" } + resolution: {integrity: sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==} + engines: {node: '>= 0.4'} es-shim-unscopables@1.1.0: - resolution: - { integrity: sha512-d9T8ucsEhh8Bi1woXCf+TIKDIROLG5WCkxg8geBCbvk22kzwC5G2OnXVMO6FUsvQlgUUXQ2itephWDLqDzbeCw== } - engines: { node: ">= 0.4" } + resolution: {integrity: sha512-d9T8ucsEhh8Bi1woXCf+TIKDIROLG5WCkxg8geBCbvk22kzwC5G2OnXVMO6FUsvQlgUUXQ2itephWDLqDzbeCw==} + engines: {node: '>= 0.4'} es-to-primitive@1.3.0: - resolution: - { integrity: sha512-w+5mJ3GuFL+NjVtJlvydShqE1eN3h3PbI7/5LAsYJP/2qtuMXjfL2LpHSRqo4b4eSF5K/DH1JXKUAHSB2UW50g== } - engines: { node: ">= 0.4" } + resolution: {integrity: sha512-w+5mJ3GuFL+NjVtJlvydShqE1eN3h3PbI7/5LAsYJP/2qtuMXjfL2LpHSRqo4b4eSF5K/DH1JXKUAHSB2UW50g==} + engines: {node: '>= 0.4'} esast-util-from-estree@2.0.0: - resolution: - { integrity: sha512-4CyanoAudUSBAn5K13H4JhsMH6L9ZP7XbLVe/dKybkxMO7eDyLsT8UHl9TRNrU2Gr9nz+FovfSIjuXWJ81uVwQ== } + resolution: {integrity: sha512-4CyanoAudUSBAn5K13H4JhsMH6L9ZP7XbLVe/dKybkxMO7eDyLsT8UHl9TRNrU2Gr9nz+FovfSIjuXWJ81uVwQ==} esast-util-from-js@2.0.1: - resolution: - { integrity: sha512-8Ja+rNJ0Lt56Pcf3TAmpBZjmx8ZcK5Ts4cAzIOjsjevg9oSXJnl6SUQ2EevU8tv3h6ZLWmoKL5H4fgWvdvfETw== } + resolution: {integrity: sha512-8Ja+rNJ0Lt56Pcf3TAmpBZjmx8ZcK5Ts4cAzIOjsjevg9oSXJnl6SUQ2EevU8tv3h6ZLWmoKL5H4fgWvdvfETw==} esbuild@0.25.5: - resolution: - { integrity: sha512-P8OtKZRv/5J5hhz0cUAdu/cLuPIKXpQl1R9pZtvmHWQvrAUVd0UNIPT4IB4W3rNOqVO0rlqHmCIbSwxh/c9yUQ== } - engines: { node: ">=18" } + resolution: {integrity: sha512-P8OtKZRv/5J5hhz0cUAdu/cLuPIKXpQl1R9pZtvmHWQvrAUVd0UNIPT4IB4W3rNOqVO0rlqHmCIbSwxh/c9yUQ==} + engines: {node: '>=18'} hasBin: true escape-string-regexp@4.0.0: - resolution: - { integrity: sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA== } - engines: { node: ">=10" } + resolution: {integrity: sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==} + engines: {node: '>=10'} escape-string-regexp@5.0.0: - resolution: - { integrity: sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw== } - engines: { node: ">=12" } + resolution: {integrity: sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw==} + engines: {node: '>=12'} eslint-config-next@15.3.4: - resolution: - { integrity: sha512-WqeumCq57QcTP2lYlV6BRUySfGiBYEXlQ1L0mQ+u4N4X4ZhUVSSQ52WtjqHv60pJ6dD7jn+YZc0d1/ZSsxccvg== } + resolution: {integrity: sha512-WqeumCq57QcTP2lYlV6BRUySfGiBYEXlQ1L0mQ+u4N4X4ZhUVSSQ52WtjqHv60pJ6dD7jn+YZc0d1/ZSsxccvg==} peerDependencies: eslint: ^7.23.0 || ^8.0.0 || ^9.0.0 - typescript: ">=3.3.1" + typescript: '>=3.3.1' peerDependenciesMeta: typescript: optional: true eslint-config-prettier@9.1.0: - resolution: - { integrity: sha512-NSWl5BFQWEPi1j4TjVNItzYV7dZXZ+wP6I6ZhrBGpChQhZRUaElihE9uRRkcbRnNb76UMKDF3r+WTmNcGPKsqw== } + resolution: {integrity: sha512-NSWl5BFQWEPi1j4TjVNItzYV7dZXZ+wP6I6ZhrBGpChQhZRUaElihE9uRRkcbRnNb76UMKDF3r+WTmNcGPKsqw==} hasBin: true peerDependencies: - eslint: ">=7.0.0" + eslint: '>=7.0.0' eslint-import-resolver-node@0.3.9: - resolution: - { integrity: sha512-WFj2isz22JahUv+B788TlO3N6zL3nNJGU8CcZbPZvVEkBPaJdCV4vy5wyghty5ROFbCRnm132v8BScu5/1BQ8g== } + resolution: {integrity: sha512-WFj2isz22JahUv+B788TlO3N6zL3nNJGU8CcZbPZvVEkBPaJdCV4vy5wyghty5ROFbCRnm132v8BScu5/1BQ8g==} eslint-import-resolver-typescript@3.10.1: - resolution: - { integrity: sha512-A1rHYb06zjMGAxdLSkN2fXPBwuSaQ0iO5M/hdyS0Ajj1VBaRp0sPD3dn1FhME3c/JluGFbwSxyCfqdSbtQLAHQ== } - engines: { node: ^14.18.0 || >=16.0.0 } + resolution: {integrity: sha512-A1rHYb06zjMGAxdLSkN2fXPBwuSaQ0iO5M/hdyS0Ajj1VBaRp0sPD3dn1FhME3c/JluGFbwSxyCfqdSbtQLAHQ==} + engines: {node: ^14.18.0 || >=16.0.0} peerDependencies: - eslint: "*" - eslint-plugin-import: "*" - eslint-plugin-import-x: "*" + eslint: '*' + eslint-plugin-import: '*' + eslint-plugin-import-x: '*' peerDependenciesMeta: eslint-plugin-import: optional: true @@ -1959,17 +1701,16 @@ packages: optional: true eslint-module-utils@2.12.1: - resolution: - { integrity: sha512-L8jSWTze7K2mTg0vos/RuLRS5soomksDPoJLXIslC7c8Wmut3bx7CPpJijDcBZtxQ5lrbUdM+s0OlNbz0DCDNw== } - engines: { node: ">=4" } + resolution: {integrity: sha512-L8jSWTze7K2mTg0vos/RuLRS5soomksDPoJLXIslC7c8Wmut3bx7CPpJijDcBZtxQ5lrbUdM+s0OlNbz0DCDNw==} + engines: {node: '>=4'} peerDependencies: - "@typescript-eslint/parser": "*" - eslint: "*" - eslint-import-resolver-node: "*" - eslint-import-resolver-typescript: "*" - eslint-import-resolver-webpack: "*" + '@typescript-eslint/parser': '*' + eslint: '*' + eslint-import-resolver-node: '*' + eslint-import-resolver-typescript: '*' + eslint-import-resolver-webpack: '*' peerDependenciesMeta: - "@typescript-eslint/parser": + '@typescript-eslint/parser': optional: true eslint: optional: true @@ -1981,172 +1722,141 @@ packages: optional: true eslint-plugin-import@2.32.0: - resolution: - { integrity: sha512-whOE1HFo/qJDyX4SnXzP4N6zOWn79WhnCUY/iDR0mPfQZO8wcYE4JClzI2oZrhBnnMUCBCHZhO6VQyoBU95mZA== } - engines: { node: ">=4" } + resolution: {integrity: sha512-whOE1HFo/qJDyX4SnXzP4N6zOWn79WhnCUY/iDR0mPfQZO8wcYE4JClzI2oZrhBnnMUCBCHZhO6VQyoBU95mZA==} + engines: {node: '>=4'} peerDependencies: - "@typescript-eslint/parser": "*" + '@typescript-eslint/parser': '*' eslint: ^2 || ^3 || ^4 || ^5 || ^6 || ^7.2.0 || ^8 || ^9 peerDependenciesMeta: - "@typescript-eslint/parser": + '@typescript-eslint/parser': optional: true eslint-plugin-jsx-a11y@6.10.2: - resolution: - { integrity: sha512-scB3nz4WmG75pV8+3eRUQOHZlNSUhFNq37xnpgRkCCELU3XMvXAxLk1eqWWyE22Ki4Q01Fnsw9BA3cJHDPgn2Q== } - engines: { node: ">=4.0" } + resolution: {integrity: sha512-scB3nz4WmG75pV8+3eRUQOHZlNSUhFNq37xnpgRkCCELU3XMvXAxLk1eqWWyE22Ki4Q01Fnsw9BA3cJHDPgn2Q==} + engines: {node: '>=4.0'} peerDependencies: eslint: ^3 || ^4 || ^5 || ^6 || ^7 || ^8 || ^9 eslint-plugin-prettier@5.5.1: - resolution: - { integrity: sha512-dobTkHT6XaEVOo8IO90Q4DOSxnm3Y151QxPJlM/vKC0bVy+d6cVWQZLlFiuZPP0wS6vZwSKeJgKkcS+KfMBlRw== } - engines: { node: ^14.18.0 || >=16.0.0 } + resolution: {integrity: sha512-dobTkHT6XaEVOo8IO90Q4DOSxnm3Y151QxPJlM/vKC0bVy+d6cVWQZLlFiuZPP0wS6vZwSKeJgKkcS+KfMBlRw==} + engines: {node: ^14.18.0 || >=16.0.0} peerDependencies: - "@types/eslint": ">=8.0.0" - eslint: ">=8.0.0" - eslint-config-prettier: ">= 7.0.0 <10.0.0 || >=10.1.0" - prettier: ">=3.0.0" + '@types/eslint': '>=8.0.0' + eslint: '>=8.0.0' + eslint-config-prettier: '>= 7.0.0 <10.0.0 || >=10.1.0' + prettier: '>=3.0.0' peerDependenciesMeta: - "@types/eslint": + '@types/eslint': optional: true eslint-config-prettier: optional: true eslint-plugin-react-hooks@5.2.0: - resolution: - { integrity: sha512-+f15FfK64YQwZdJNELETdn5ibXEUQmW1DZL6KXhNnc2heoy/sg9VJJeT7n8TlMWouzWqSWavFkIhHyIbIAEapg== } - engines: { node: ">=10" } + resolution: {integrity: sha512-+f15FfK64YQwZdJNELETdn5ibXEUQmW1DZL6KXhNnc2heoy/sg9VJJeT7n8TlMWouzWqSWavFkIhHyIbIAEapg==} + engines: {node: '>=10'} peerDependencies: eslint: ^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0 || ^9.0.0 eslint-plugin-react@7.37.5: - resolution: - { integrity: sha512-Qteup0SqU15kdocexFNAJMvCJEfa2xUKNV4CC1xsVMrIIqEy3SQ/rqyxCWNzfrd3/ldy6HMlD2e0JDVpDg2qIA== } - engines: { node: ">=4" } + resolution: {integrity: sha512-Qteup0SqU15kdocexFNAJMvCJEfa2xUKNV4CC1xsVMrIIqEy3SQ/rqyxCWNzfrd3/ldy6HMlD2e0JDVpDg2qIA==} + engines: {node: '>=4'} peerDependencies: eslint: ^3 || ^4 || ^5 || ^6 || ^7 || ^8 || ^9.7 eslint-scope@8.4.0: - resolution: - { integrity: sha512-sNXOfKCn74rt8RICKMvJS7XKV/Xk9kA7DyJr8mJik3S7Cwgy3qlkkmyS2uQB3jiJg6VNdZd/pDBJu0nvG2NlTg== } - engines: { node: ^18.18.0 || ^20.9.0 || >=21.1.0 } + resolution: {integrity: sha512-sNXOfKCn74rt8RICKMvJS7XKV/Xk9kA7DyJr8mJik3S7Cwgy3qlkkmyS2uQB3jiJg6VNdZd/pDBJu0nvG2NlTg==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} eslint-visitor-keys@3.4.3: - resolution: - { integrity: sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag== } - engines: { node: ^12.22.0 || ^14.17.0 || >=16.0.0 } + resolution: {integrity: sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} eslint-visitor-keys@4.2.1: - resolution: - { integrity: sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ== } - engines: { node: ^18.18.0 || ^20.9.0 || >=21.1.0 } + resolution: {integrity: sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} eslint@9.30.0: - resolution: - { integrity: sha512-iN/SiPxmQu6EVkf+m1qpBxzUhE12YqFLOSySuOyVLJLEF9nzTf+h/1AJYc1JWzCnktggeNrjvQGLngDzXirU6g== } - engines: { node: ^18.18.0 || ^20.9.0 || >=21.1.0 } + resolution: {integrity: sha512-iN/SiPxmQu6EVkf+m1qpBxzUhE12YqFLOSySuOyVLJLEF9nzTf+h/1AJYc1JWzCnktggeNrjvQGLngDzXirU6g==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} hasBin: true peerDependencies: - jiti: "*" + jiti: '*' peerDependenciesMeta: jiti: optional: true espree@10.4.0: - resolution: - { integrity: sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ== } - engines: { node: ^18.18.0 || ^20.9.0 || >=21.1.0 } + resolution: {integrity: sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} esquery@1.6.0: - resolution: - { integrity: sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg== } - engines: { node: ">=0.10" } + resolution: {integrity: sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg==} + engines: {node: '>=0.10'} esrecurse@4.3.0: - resolution: - { integrity: sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag== } - engines: { node: ">=4.0" } + resolution: {integrity: sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==} + engines: {node: '>=4.0'} estraverse@5.3.0: - resolution: - { integrity: sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA== } - engines: { node: ">=4.0" } + resolution: {integrity: sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==} + engines: {node: '>=4.0'} estree-util-attach-comments@3.0.0: - resolution: - { integrity: sha512-cKUwm/HUcTDsYh/9FgnuFqpfquUbwIqwKM26BVCGDPVgvaCl/nDCCjUfiLlx6lsEZ3Z4RFxNbOQ60pkaEwFxGw== } + resolution: {integrity: sha512-cKUwm/HUcTDsYh/9FgnuFqpfquUbwIqwKM26BVCGDPVgvaCl/nDCCjUfiLlx6lsEZ3Z4RFxNbOQ60pkaEwFxGw==} estree-util-build-jsx@3.0.1: - resolution: - { integrity: sha512-8U5eiL6BTrPxp/CHbs2yMgP8ftMhR5ww1eIKoWRMlqvltHF8fZn5LRDvTKuxD3DUn+shRbLGqXemcP51oFCsGQ== } + resolution: {integrity: sha512-8U5eiL6BTrPxp/CHbs2yMgP8ftMhR5ww1eIKoWRMlqvltHF8fZn5LRDvTKuxD3DUn+shRbLGqXemcP51oFCsGQ==} estree-util-is-identifier-name@3.0.0: - resolution: - { integrity: sha512-hFtqIDZTIUZ9BXLb8y4pYGyk6+wekIivNVTcmvk8NoOh+VeRn5y6cEHzbURrWbfp1fIqdVipilzj+lfaadNZmg== } + resolution: {integrity: sha512-hFtqIDZTIUZ9BXLb8y4pYGyk6+wekIivNVTcmvk8NoOh+VeRn5y6cEHzbURrWbfp1fIqdVipilzj+lfaadNZmg==} estree-util-scope@1.0.0: - resolution: - { integrity: sha512-2CAASclonf+JFWBNJPndcOpA8EMJwa0Q8LUFJEKqXLW6+qBvbFZuF5gItbQOs/umBUkjviCSDCbBwU2cXbmrhQ== } + resolution: {integrity: sha512-2CAASclonf+JFWBNJPndcOpA8EMJwa0Q8LUFJEKqXLW6+qBvbFZuF5gItbQOs/umBUkjviCSDCbBwU2cXbmrhQ==} estree-util-to-js@2.0.0: - resolution: - { integrity: sha512-WDF+xj5rRWmD5tj6bIqRi6CkLIXbbNQUcxQHzGysQzvHmdYG2G7p/Tf0J0gpxGgkeMZNTIjT/AoSvC9Xehcgdg== } + resolution: {integrity: sha512-WDF+xj5rRWmD5tj6bIqRi6CkLIXbbNQUcxQHzGysQzvHmdYG2G7p/Tf0J0gpxGgkeMZNTIjT/AoSvC9Xehcgdg==} estree-util-value-to-estree@3.4.0: - resolution: - { integrity: sha512-Zlp+gxis+gCfK12d3Srl2PdX2ybsEA8ZYy6vQGVQTNNYLEGRQQ56XB64bjemN8kxIKXP1nC9ip4Z+ILy9LGzvQ== } + resolution: {integrity: sha512-Zlp+gxis+gCfK12d3Srl2PdX2ybsEA8ZYy6vQGVQTNNYLEGRQQ56XB64bjemN8kxIKXP1nC9ip4Z+ILy9LGzvQ==} estree-util-visit@2.0.0: - resolution: - { integrity: sha512-m5KgiH85xAhhW8Wta0vShLcUvOsh3LLPI2YVwcbio1l7E09NTLL1EyMZFM1OyWowoH0skScNbhOPl4kcBgzTww== } + resolution: {integrity: sha512-m5KgiH85xAhhW8Wta0vShLcUvOsh3LLPI2YVwcbio1l7E09NTLL1EyMZFM1OyWowoH0skScNbhOPl4kcBgzTww==} estree-walker@3.0.3: - resolution: - { integrity: sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g== } + resolution: {integrity: sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==} esutils@2.0.3: - resolution: - { integrity: sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g== } - engines: { node: ">=0.10.0" } + resolution: {integrity: sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==} + engines: {node: '>=0.10.0'} extend@3.0.2: - resolution: - { integrity: sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g== } + resolution: {integrity: sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==} fast-deep-equal@3.1.3: - resolution: - { integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q== } + resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==} fast-diff@1.3.0: - resolution: - { integrity: sha512-VxPP4NqbUjj6MaAOafWeUn2cXWLcCtljklUtZf0Ind4XQ+QPtmA0b18zZy0jIQx+ExRVCR/ZQpBmik5lXshNsw== } + resolution: {integrity: sha512-VxPP4NqbUjj6MaAOafWeUn2cXWLcCtljklUtZf0Ind4XQ+QPtmA0b18zZy0jIQx+ExRVCR/ZQpBmik5lXshNsw==} fast-glob@3.3.1: - resolution: - { integrity: sha512-kNFPyjhh5cKjrUltxs+wFx+ZkbRaxxmZ+X0ZU31SOsxCEtP9VPgtq2teZw1DebupL5GmDaNQ6yKMMVcM41iqDg== } - engines: { node: ">=8.6.0" } + resolution: {integrity: sha512-kNFPyjhh5cKjrUltxs+wFx+ZkbRaxxmZ+X0ZU31SOsxCEtP9VPgtq2teZw1DebupL5GmDaNQ6yKMMVcM41iqDg==} + engines: {node: '>=8.6.0'} fast-glob@3.3.3: - resolution: - { integrity: sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg== } - engines: { node: ">=8.6.0" } + resolution: {integrity: sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==} + engines: {node: '>=8.6.0'} fast-json-stable-stringify@2.1.0: - resolution: - { integrity: sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw== } + resolution: {integrity: sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==} fast-levenshtein@2.0.6: - resolution: - { integrity: sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw== } + resolution: {integrity: sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==} fastq@1.19.1: - resolution: - { integrity: sha512-GwLTyxkCXjXbxqIhTsMI2Nui8huMPtnxg7krajPJAjnEG/iiOS7i+zCtWGZR9G0NBKbXKh6X9m9UIsYX/N6vvQ== } + resolution: {integrity: sha512-GwLTyxkCXjXbxqIhTsMI2Nui8huMPtnxg7krajPJAjnEG/iiOS7i+zCtWGZR9G0NBKbXKh6X9m9UIsYX/N6vvQ==} fdir@6.4.6: - resolution: - { integrity: sha512-hiFoqpyZcfNm1yc4u8oWCf9A2c4D3QjCrks3zmoVKVxpQRzmPNar1hUJcBG2RQHvEVGDN+Jm81ZheVLAQMK6+w== } + resolution: {integrity: sha512-hiFoqpyZcfNm1yc4u8oWCf9A2c4D3QjCrks3zmoVKVxpQRzmPNar1hUJcBG2RQHvEVGDN+Jm81ZheVLAQMK6+w==} peerDependencies: picomatch: ^3 || ^4 peerDependenciesMeta: @@ -2154,43 +1864,36 @@ packages: optional: true file-entry-cache@8.0.0: - resolution: - { integrity: sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ== } - engines: { node: ">=16.0.0" } + resolution: {integrity: sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==} + engines: {node: '>=16.0.0'} fill-range@7.1.1: - resolution: - { integrity: sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg== } - engines: { node: ">=8" } + resolution: {integrity: sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==} + engines: {node: '>=8'} find-up@5.0.0: - resolution: - { integrity: sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng== } - engines: { node: ">=10" } + resolution: {integrity: sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==} + engines: {node: '>=10'} flat-cache@4.0.1: - resolution: - { integrity: sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw== } - engines: { node: ">=16" } + resolution: {integrity: sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==} + engines: {node: '>=16'} flatted@3.3.3: - resolution: - { integrity: sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg== } + resolution: {integrity: sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg==} for-each@0.3.5: - resolution: - { integrity: sha512-dKx12eRCVIzqCxFGplyFKJMPvLEWgmNtUrpTiJIR5u97zEhRG8ySrtboPHZXx7daLxQVrl643cTzbab2tkQjxg== } - engines: { node: ">= 0.4" } + resolution: {integrity: sha512-dKx12eRCVIzqCxFGplyFKJMPvLEWgmNtUrpTiJIR5u97zEhRG8ySrtboPHZXx7daLxQVrl643cTzbab2tkQjxg==} + engines: {node: '>= 0.4'} framer-motion@12.23.0: - resolution: - { integrity: sha512-xf6NxTGAyf7zR4r2KlnhFmsRfKIbjqeBupEDBAaEtVIBJX96sAon00kMlsKButSIRwPSHjbRrAPnYdJJ9kyhbA== } + resolution: {integrity: sha512-xf6NxTGAyf7zR4r2KlnhFmsRfKIbjqeBupEDBAaEtVIBJX96sAon00kMlsKButSIRwPSHjbRrAPnYdJJ9kyhbA==} peerDependencies: - "@emotion/is-prop-valid": "*" + '@emotion/is-prop-valid': '*' react: ^18.0.0 || ^19.0.0 react-dom: ^18.0.0 || ^19.0.0 peerDependenciesMeta: - "@emotion/is-prop-valid": + '@emotion/is-prop-valid': optional: true react: optional: true @@ -2198,16 +1901,15 @@ packages: optional: true fumadocs-core@15.2.7: - resolution: - { integrity: sha512-uPMOlU+W4nT+qg1hyr8QBI+tMMpDys7QtIWWKFBhZFnVYCHPGuYKykSn5MbCkVkRH5HW3KMpm6zThAWk7gLCjQ== } + resolution: {integrity: sha512-uPMOlU+W4nT+qg1hyr8QBI+tMMpDys7QtIWWKFBhZFnVYCHPGuYKykSn5MbCkVkRH5HW3KMpm6zThAWk7gLCjQ==} peerDependencies: - "@oramacloud/client": 1.x.x || 2.x.x + '@oramacloud/client': 1.x.x || 2.x.x algoliasearch: 4.24.0 next: 14.x.x || 15.x.x react: 18.x.x || 19.x.x react-dom: 18.x.x || 19.x.x peerDependenciesMeta: - "@oramacloud/client": + '@oramacloud/client': optional: true algoliasearch: optional: true @@ -2219,16 +1921,15 @@ packages: optional: true fumadocs-mdx@11.6.10: - resolution: - { integrity: sha512-W13mGPKDviKHq1FdxJqbBmA8vQ0niEISUUREJU8u3q1g5lQgnZ9whZjTnvijnqiGNbBsjb8CmjU20OlmwG6nhA== } + resolution: {integrity: sha512-W13mGPKDviKHq1FdxJqbBmA8vQ0niEISUUREJU8u3q1g5lQgnZ9whZjTnvijnqiGNbBsjb8CmjU20OlmwG6nhA==} hasBin: true peerDependencies: - "@fumadocs/mdx-remote": ^1.2.0 + '@fumadocs/mdx-remote': ^1.2.0 fumadocs-core: ^14.0.0 || ^15.0.0 next: ^15.3.0 vite: 6.x.x peerDependenciesMeta: - "@fumadocs/mdx-remote": + '@fumadocs/mdx-remote': optional: true next: optional: true @@ -2236,8 +1937,7 @@ packages: optional: true fumadocs-ui@15.2.7: - resolution: - { integrity: sha512-sixoRJdXW2ZyIG0DnuNLbv4Ov4xxu0H2A60una62TJ2uLrxIwFiFq2FfzhmZlT1EK0Gg9pzl2zM4QlFDdLKGjA== } + resolution: {integrity: sha512-sixoRJdXW2ZyIG0DnuNLbv4Ov4xxu0H2A60una62TJ2uLrxIwFiFq2FfzhmZlT1EK0Gg9pzl2zM4QlFDdLKGjA==} peerDependencies: next: 14.x.x || 15.x.x react: 18.x.x || 19.x.x @@ -2248,787 +1948,619 @@ packages: optional: true function-bind@1.1.2: - resolution: - { integrity: sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA== } + resolution: {integrity: sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==} function.prototype.name@1.1.8: - resolution: - { integrity: sha512-e5iwyodOHhbMr/yNrc7fDYG4qlbIvI5gajyzPnb5TCwyhjApznQh1BMFou9b30SevY43gCJKXycoCBjMbsuW0Q== } - engines: { node: ">= 0.4" } + resolution: {integrity: sha512-e5iwyodOHhbMr/yNrc7fDYG4qlbIvI5gajyzPnb5TCwyhjApznQh1BMFou9b30SevY43gCJKXycoCBjMbsuW0Q==} + engines: {node: '>= 0.4'} functions-have-names@1.2.3: - resolution: - { integrity: sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ== } + resolution: {integrity: sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==} get-intrinsic@1.3.0: - resolution: - { integrity: sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ== } - engines: { node: ">= 0.4" } + resolution: {integrity: sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==} + engines: {node: '>= 0.4'} get-nonce@1.0.1: - resolution: - { integrity: sha512-FJhYRoDaiatfEkUK8HKlicmu/3SGFD51q3itKDGoSTysQJBnfOcxU5GxnhE1E6soB76MbT0MBtnKJuXyAx+96Q== } - engines: { node: ">=6" } + resolution: {integrity: sha512-FJhYRoDaiatfEkUK8HKlicmu/3SGFD51q3itKDGoSTysQJBnfOcxU5GxnhE1E6soB76MbT0MBtnKJuXyAx+96Q==} + engines: {node: '>=6'} get-proto@1.0.1: - resolution: - { integrity: sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g== } - engines: { node: ">= 0.4" } + resolution: {integrity: sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==} + engines: {node: '>= 0.4'} get-symbol-description@1.1.0: - resolution: - { integrity: sha512-w9UMqWwJxHNOvoNzSJ2oPF5wvYcvP7jUvYzhp67yEhTi17ZDBBC1z9pTdGuzjD+EFIqLSYRweZjqfiPzQ06Ebg== } - engines: { node: ">= 0.4" } + resolution: {integrity: sha512-w9UMqWwJxHNOvoNzSJ2oPF5wvYcvP7jUvYzhp67yEhTi17ZDBBC1z9pTdGuzjD+EFIqLSYRweZjqfiPzQ06Ebg==} + engines: {node: '>= 0.4'} get-tsconfig@4.10.1: - resolution: - { integrity: sha512-auHyJ4AgMz7vgS8Hp3N6HXSmlMdUyhSUrfBF16w153rxtLIEOE+HGqaBppczZvnHLqQJfiHotCYpNhl0lUROFQ== } + resolution: {integrity: sha512-auHyJ4AgMz7vgS8Hp3N6HXSmlMdUyhSUrfBF16w153rxtLIEOE+HGqaBppczZvnHLqQJfiHotCYpNhl0lUROFQ==} github-slugger@2.0.0: - resolution: - { integrity: sha512-IaOQ9puYtjrkq7Y0Ygl9KDZnrf/aiUJYUpVf89y8kyaxbRG7Y1SrX/jaumrv81vc61+kiMempujsM3Yw7w5qcw== } + resolution: {integrity: sha512-IaOQ9puYtjrkq7Y0Ygl9KDZnrf/aiUJYUpVf89y8kyaxbRG7Y1SrX/jaumrv81vc61+kiMempujsM3Yw7w5qcw==} glob-parent@5.1.2: - resolution: - { integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow== } - engines: { node: ">= 6" } + resolution: {integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==} + engines: {node: '>= 6'} glob-parent@6.0.2: - resolution: - { integrity: sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A== } - engines: { node: ">=10.13.0" } + resolution: {integrity: sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==} + engines: {node: '>=10.13.0'} globals@14.0.0: - resolution: - { integrity: sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ== } - engines: { node: ">=18" } + resolution: {integrity: sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==} + engines: {node: '>=18'} globalthis@1.0.4: - resolution: - { integrity: sha512-DpLKbNU4WylpxJykQujfCcwYWiV/Jhm50Goo0wrVILAv5jOr9d+H+UR3PhSCD2rCCEIg0uc+G+muBTwD54JhDQ== } - engines: { node: ">= 0.4" } + resolution: {integrity: sha512-DpLKbNU4WylpxJykQujfCcwYWiV/Jhm50Goo0wrVILAv5jOr9d+H+UR3PhSCD2rCCEIg0uc+G+muBTwD54JhDQ==} + engines: {node: '>= 0.4'} gopd@1.2.0: - resolution: - { integrity: sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg== } - engines: { node: ">= 0.4" } + resolution: {integrity: sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==} + engines: {node: '>= 0.4'} graceful-fs@4.2.11: - resolution: - { integrity: sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ== } + resolution: {integrity: sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==} graphemer@1.4.0: - resolution: - { integrity: sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag== } + resolution: {integrity: sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==} has-bigints@1.1.0: - resolution: - { integrity: sha512-R3pbpkcIqv2Pm3dUwgjclDRVmWpTJW2DcMzcIhEXEx1oh/CEMObMm3KLmRJOdvhM7o4uQBnwr8pzRK2sJWIqfg== } - engines: { node: ">= 0.4" } + resolution: {integrity: sha512-R3pbpkcIqv2Pm3dUwgjclDRVmWpTJW2DcMzcIhEXEx1oh/CEMObMm3KLmRJOdvhM7o4uQBnwr8pzRK2sJWIqfg==} + engines: {node: '>= 0.4'} has-flag@4.0.0: - resolution: - { integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ== } - engines: { node: ">=8" } + resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==} + engines: {node: '>=8'} has-property-descriptors@1.0.2: - resolution: - { integrity: sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg== } + resolution: {integrity: sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==} has-proto@1.2.0: - resolution: - { integrity: sha512-KIL7eQPfHQRC8+XluaIw7BHUwwqL19bQn4hzNgdr+1wXoU0KKj6rufu47lhY7KbJR2C6T6+PfyN0Ea7wkSS+qQ== } - engines: { node: ">= 0.4" } + resolution: {integrity: sha512-KIL7eQPfHQRC8+XluaIw7BHUwwqL19bQn4hzNgdr+1wXoU0KKj6rufu47lhY7KbJR2C6T6+PfyN0Ea7wkSS+qQ==} + engines: {node: '>= 0.4'} has-symbols@1.1.0: - resolution: - { integrity: sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ== } - engines: { node: ">= 0.4" } + resolution: {integrity: sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==} + engines: {node: '>= 0.4'} has-tostringtag@1.0.2: - resolution: - { integrity: sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw== } - engines: { node: ">= 0.4" } + resolution: {integrity: sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==} + engines: {node: '>= 0.4'} hasown@2.0.2: - resolution: - { integrity: sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ== } - engines: { node: ">= 0.4" } + resolution: {integrity: sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==} + engines: {node: '>= 0.4'} hast-util-to-estree@3.1.3: - resolution: - { integrity: sha512-48+B/rJWAp0jamNbAAf9M7Uf//UVqAoMmgXhBdxTDJLGKY+LRnZ99qcG+Qjl5HfMpYNzS5v4EAwVEF34LeAj7w== } + resolution: {integrity: sha512-48+B/rJWAp0jamNbAAf9M7Uf//UVqAoMmgXhBdxTDJLGKY+LRnZ99qcG+Qjl5HfMpYNzS5v4EAwVEF34LeAj7w==} hast-util-to-html@9.0.5: - resolution: - { integrity: sha512-OguPdidb+fbHQSU4Q4ZiLKnzWo8Wwsf5bZfbvu7//a9oTYoqD/fWpe96NuHkoS9h0ccGOTe0C4NGXdtS0iObOw== } + resolution: {integrity: sha512-OguPdidb+fbHQSU4Q4ZiLKnzWo8Wwsf5bZfbvu7//a9oTYoqD/fWpe96NuHkoS9h0ccGOTe0C4NGXdtS0iObOw==} hast-util-to-jsx-runtime@2.3.6: - resolution: - { integrity: sha512-zl6s8LwNyo1P9uw+XJGvZtdFF1GdAkOg8ujOw+4Pyb76874fLps4ueHXDhXWdk6YHQ6OgUtinliG7RsYvCbbBg== } + resolution: {integrity: sha512-zl6s8LwNyo1P9uw+XJGvZtdFF1GdAkOg8ujOw+4Pyb76874fLps4ueHXDhXWdk6YHQ6OgUtinliG7RsYvCbbBg==} hast-util-to-string@3.0.1: - resolution: - { integrity: sha512-XelQVTDWvqcl3axRfI0xSeoVKzyIFPwsAGSLIsKdJKQMXDYJS4WYrBNF/8J7RdhIcFI2BOHgAifggsvsxp/3+A== } + resolution: {integrity: sha512-XelQVTDWvqcl3axRfI0xSeoVKzyIFPwsAGSLIsKdJKQMXDYJS4WYrBNF/8J7RdhIcFI2BOHgAifggsvsxp/3+A==} hast-util-whitespace@3.0.0: - resolution: - { integrity: sha512-88JUN06ipLwsnv+dVn+OIYOvAuvBMy/Qoi6O7mQHxdPXpjy+Cd6xRkWwux7DKO+4sYILtLBRIKgsdpS2gQc7qw== } + resolution: {integrity: sha512-88JUN06ipLwsnv+dVn+OIYOvAuvBMy/Qoi6O7mQHxdPXpjy+Cd6xRkWwux7DKO+4sYILtLBRIKgsdpS2gQc7qw==} html-void-elements@3.0.0: - resolution: - { integrity: sha512-bEqo66MRXsUGxWHV5IP0PUiAWwoEjba4VCzg0LjFJBpchPaTfyfCKTG6bc5F8ucKec3q5y6qOdGyYTSBEvhCrg== } + resolution: {integrity: sha512-bEqo66MRXsUGxWHV5IP0PUiAWwoEjba4VCzg0LjFJBpchPaTfyfCKTG6bc5F8ucKec3q5y6qOdGyYTSBEvhCrg==} ignore@5.3.2: - resolution: - { integrity: sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g== } - engines: { node: ">= 4" } + resolution: {integrity: sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==} + engines: {node: '>= 4'} ignore@7.0.5: - resolution: - { integrity: sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg== } - engines: { node: ">= 4" } + resolution: {integrity: sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg==} + engines: {node: '>= 4'} image-size@2.0.2: - resolution: - { integrity: sha512-IRqXKlaXwgSMAMtpNzZa1ZAe8m+Sa1770Dhk8VkSsP9LS+iHD62Zd8FQKs8fbPiagBE7BzoFX23cxFnwshpV6w== } - engines: { node: ">=16.x" } + resolution: {integrity: sha512-IRqXKlaXwgSMAMtpNzZa1ZAe8m+Sa1770Dhk8VkSsP9LS+iHD62Zd8FQKs8fbPiagBE7BzoFX23cxFnwshpV6w==} + engines: {node: '>=16.x'} hasBin: true import-fresh@3.3.1: - resolution: - { integrity: sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ== } - engines: { node: ">=6" } + resolution: {integrity: sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==} + engines: {node: '>=6'} imurmurhash@0.1.4: - resolution: - { integrity: sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA== } - engines: { node: ">=0.8.19" } + resolution: {integrity: sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==} + engines: {node: '>=0.8.19'} inline-style-parser@0.2.4: - resolution: - { integrity: sha512-0aO8FkhNZlj/ZIbNi7Lxxr12obT7cL1moPfE4tg1LkX7LlLfC6DeX4l2ZEud1ukP9jNQyNnfzQVqwbwmAATY4Q== } + resolution: {integrity: sha512-0aO8FkhNZlj/ZIbNi7Lxxr12obT7cL1moPfE4tg1LkX7LlLfC6DeX4l2ZEud1ukP9jNQyNnfzQVqwbwmAATY4Q==} internal-slot@1.1.0: - resolution: - { integrity: sha512-4gd7VpWNQNB4UKKCFFVcp1AVv+FMOgs9NKzjHKusc8jTMhd5eL1NqQqOpE0KzMds804/yHlglp3uxgluOqAPLw== } - engines: { node: ">= 0.4" } + resolution: {integrity: sha512-4gd7VpWNQNB4UKKCFFVcp1AVv+FMOgs9NKzjHKusc8jTMhd5eL1NqQqOpE0KzMds804/yHlglp3uxgluOqAPLw==} + engines: {node: '>= 0.4'} is-alphabetical@2.0.1: - resolution: - { integrity: sha512-FWyyY60MeTNyeSRpkM2Iry0G9hpr7/9kD40mD/cGQEuilcZYS4okz8SN2Q6rLCJ8gbCt6fN+rC+6tMGS99LaxQ== } + resolution: {integrity: sha512-FWyyY60MeTNyeSRpkM2Iry0G9hpr7/9kD40mD/cGQEuilcZYS4okz8SN2Q6rLCJ8gbCt6fN+rC+6tMGS99LaxQ==} is-alphanumerical@2.0.1: - resolution: - { integrity: sha512-hmbYhX/9MUMF5uh7tOXyK/n0ZvWpad5caBA17GsC6vyuCqaWliRG5K1qS9inmUhEMaOBIW7/whAnSwveW/LtZw== } + resolution: {integrity: sha512-hmbYhX/9MUMF5uh7tOXyK/n0ZvWpad5caBA17GsC6vyuCqaWliRG5K1qS9inmUhEMaOBIW7/whAnSwveW/LtZw==} is-array-buffer@3.0.5: - resolution: - { integrity: sha512-DDfANUiiG2wC1qawP66qlTugJeL5HyzMpfr8lLK+jMQirGzNod0B12cFB/9q838Ru27sBwfw78/rdoU7RERz6A== } - engines: { node: ">= 0.4" } + resolution: {integrity: sha512-DDfANUiiG2wC1qawP66qlTugJeL5HyzMpfr8lLK+jMQirGzNod0B12cFB/9q838Ru27sBwfw78/rdoU7RERz6A==} + engines: {node: '>= 0.4'} is-arrayish@0.3.2: - resolution: - { integrity: sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ== } + resolution: {integrity: sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ==} is-async-function@2.1.1: - resolution: - { integrity: sha512-9dgM/cZBnNvjzaMYHVoxxfPj2QXt22Ev7SuuPrs+xav0ukGB0S6d4ydZdEiM48kLx5kDV+QBPrpVnFyefL8kkQ== } - engines: { node: ">= 0.4" } + resolution: {integrity: sha512-9dgM/cZBnNvjzaMYHVoxxfPj2QXt22Ev7SuuPrs+xav0ukGB0S6d4ydZdEiM48kLx5kDV+QBPrpVnFyefL8kkQ==} + engines: {node: '>= 0.4'} is-bigint@1.1.0: - resolution: - { integrity: sha512-n4ZT37wG78iz03xPRKJrHTdZbe3IicyucEtdRsV5yglwc3GyUfbAfpSeD0FJ41NbUNSt5wbhqfp1fS+BgnvDFQ== } - engines: { node: ">= 0.4" } + resolution: {integrity: sha512-n4ZT37wG78iz03xPRKJrHTdZbe3IicyucEtdRsV5yglwc3GyUfbAfpSeD0FJ41NbUNSt5wbhqfp1fS+BgnvDFQ==} + engines: {node: '>= 0.4'} is-boolean-object@1.2.2: - resolution: - { integrity: sha512-wa56o2/ElJMYqjCjGkXri7it5FbebW5usLw/nPmCMs5DeZ7eziSYZhSmPRn0txqeW4LnAmQQU7FgqLpsEFKM4A== } - engines: { node: ">= 0.4" } + resolution: {integrity: sha512-wa56o2/ElJMYqjCjGkXri7it5FbebW5usLw/nPmCMs5DeZ7eziSYZhSmPRn0txqeW4LnAmQQU7FgqLpsEFKM4A==} + engines: {node: '>= 0.4'} is-bun-module@2.0.0: - resolution: - { integrity: sha512-gNCGbnnnnFAUGKeZ9PdbyeGYJqewpmc2aKHUEMO5nQPWU9lOmv7jcmQIv+qHD8fXW6W7qfuCwX4rY9LNRjXrkQ== } + resolution: {integrity: sha512-gNCGbnnnnFAUGKeZ9PdbyeGYJqewpmc2aKHUEMO5nQPWU9lOmv7jcmQIv+qHD8fXW6W7qfuCwX4rY9LNRjXrkQ==} is-callable@1.2.7: - resolution: - { integrity: sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA== } - engines: { node: ">= 0.4" } + resolution: {integrity: sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==} + engines: {node: '>= 0.4'} is-core-module@2.16.1: - resolution: - { integrity: sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w== } - engines: { node: ">= 0.4" } + resolution: {integrity: sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==} + engines: {node: '>= 0.4'} is-data-view@1.0.2: - resolution: - { integrity: sha512-RKtWF8pGmS87i2D6gqQu/l7EYRlVdfzemCJN/P3UOs//x1QE7mfhvzHIApBTRf7axvT6DMGwSwBXYCT0nfB9xw== } - engines: { node: ">= 0.4" } + resolution: {integrity: sha512-RKtWF8pGmS87i2D6gqQu/l7EYRlVdfzemCJN/P3UOs//x1QE7mfhvzHIApBTRf7axvT6DMGwSwBXYCT0nfB9xw==} + engines: {node: '>= 0.4'} is-date-object@1.1.0: - resolution: - { integrity: sha512-PwwhEakHVKTdRNVOw+/Gyh0+MzlCl4R6qKvkhuvLtPMggI1WAHt9sOwZxQLSGpUaDnrdyDsomoRgNnCfKNSXXg== } - engines: { node: ">= 0.4" } + resolution: {integrity: sha512-PwwhEakHVKTdRNVOw+/Gyh0+MzlCl4R6qKvkhuvLtPMggI1WAHt9sOwZxQLSGpUaDnrdyDsomoRgNnCfKNSXXg==} + engines: {node: '>= 0.4'} is-decimal@2.0.1: - resolution: - { integrity: sha512-AAB9hiomQs5DXWcRB1rqsxGUstbRroFOPPVAomNk/3XHR5JyEZChOyTWe2oayKnsSsr/kcGqF+z6yuH6HHpN0A== } + resolution: {integrity: sha512-AAB9hiomQs5DXWcRB1rqsxGUstbRroFOPPVAomNk/3XHR5JyEZChOyTWe2oayKnsSsr/kcGqF+z6yuH6HHpN0A==} is-extglob@2.1.1: - resolution: - { integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ== } - engines: { node: ">=0.10.0" } + resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==} + engines: {node: '>=0.10.0'} is-finalizationregistry@1.1.1: - resolution: - { integrity: sha512-1pC6N8qWJbWoPtEjgcL2xyhQOP491EQjeUo3qTKcmV8YSDDJrOepfG8pcC7h/QgnQHYSv0mJ3Z/ZWxmatVrysg== } - engines: { node: ">= 0.4" } + resolution: {integrity: sha512-1pC6N8qWJbWoPtEjgcL2xyhQOP491EQjeUo3qTKcmV8YSDDJrOepfG8pcC7h/QgnQHYSv0mJ3Z/ZWxmatVrysg==} + engines: {node: '>= 0.4'} is-generator-function@1.1.0: - resolution: - { integrity: sha512-nPUB5km40q9e8UfN/Zc24eLlzdSf9OfKByBw9CIdw4H1giPMeA0OIJvbchsCu4npfI2QcMVBsGEBHKZ7wLTWmQ== } - engines: { node: ">= 0.4" } + resolution: {integrity: sha512-nPUB5km40q9e8UfN/Zc24eLlzdSf9OfKByBw9CIdw4H1giPMeA0OIJvbchsCu4npfI2QcMVBsGEBHKZ7wLTWmQ==} + engines: {node: '>= 0.4'} is-glob@4.0.3: - resolution: - { integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg== } - engines: { node: ">=0.10.0" } + resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==} + engines: {node: '>=0.10.0'} is-hexadecimal@2.0.1: - resolution: - { integrity: sha512-DgZQp241c8oO6cA1SbTEWiXeoxV42vlcJxgH+B3hi1AiqqKruZR3ZGF8In3fj4+/y/7rHvlOZLZtgJ/4ttYGZg== } + resolution: {integrity: sha512-DgZQp241c8oO6cA1SbTEWiXeoxV42vlcJxgH+B3hi1AiqqKruZR3ZGF8In3fj4+/y/7rHvlOZLZtgJ/4ttYGZg==} is-map@2.0.3: - resolution: - { integrity: sha512-1Qed0/Hr2m+YqxnM09CjA2d/i6YZNfF6R2oRAOj36eUdS6qIV/huPJNSEpKbupewFs+ZsJlxsjjPbc0/afW6Lw== } - engines: { node: ">= 0.4" } + resolution: {integrity: sha512-1Qed0/Hr2m+YqxnM09CjA2d/i6YZNfF6R2oRAOj36eUdS6qIV/huPJNSEpKbupewFs+ZsJlxsjjPbc0/afW6Lw==} + engines: {node: '>= 0.4'} is-negative-zero@2.0.3: - resolution: - { integrity: sha512-5KoIu2Ngpyek75jXodFvnafB6DJgr3u8uuK0LEZJjrU19DrMD3EVERaR8sjz8CCGgpZvxPl9SuE1GMVPFHx1mw== } - engines: { node: ">= 0.4" } + resolution: {integrity: sha512-5KoIu2Ngpyek75jXodFvnafB6DJgr3u8uuK0LEZJjrU19DrMD3EVERaR8sjz8CCGgpZvxPl9SuE1GMVPFHx1mw==} + engines: {node: '>= 0.4'} is-number-object@1.1.1: - resolution: - { integrity: sha512-lZhclumE1G6VYD8VHe35wFaIif+CTy5SJIi5+3y4psDgWu4wPDoBhF8NxUOinEc7pHgiTsT6MaBb92rKhhD+Xw== } - engines: { node: ">= 0.4" } + resolution: {integrity: sha512-lZhclumE1G6VYD8VHe35wFaIif+CTy5SJIi5+3y4psDgWu4wPDoBhF8NxUOinEc7pHgiTsT6MaBb92rKhhD+Xw==} + engines: {node: '>= 0.4'} is-number@7.0.0: - resolution: - { integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng== } - engines: { node: ">=0.12.0" } + resolution: {integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==} + engines: {node: '>=0.12.0'} is-plain-obj@4.1.0: - resolution: - { integrity: sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg== } - engines: { node: ">=12" } + resolution: {integrity: sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg==} + engines: {node: '>=12'} is-regex@1.2.1: - resolution: - { integrity: sha512-MjYsKHO5O7mCsmRGxWcLWheFqN9DJ/2TmngvjKXihe6efViPqc274+Fx/4fYj/r03+ESvBdTXK0V6tA3rgez1g== } - engines: { node: ">= 0.4" } + resolution: {integrity: sha512-MjYsKHO5O7mCsmRGxWcLWheFqN9DJ/2TmngvjKXihe6efViPqc274+Fx/4fYj/r03+ESvBdTXK0V6tA3rgez1g==} + engines: {node: '>= 0.4'} is-set@2.0.3: - resolution: - { integrity: sha512-iPAjerrse27/ygGLxw+EBR9agv9Y6uLeYVJMu+QNCoouJ1/1ri0mGrcWpfCqFZuzzx3WjtwxG098X+n4OuRkPg== } - engines: { node: ">= 0.4" } + resolution: {integrity: sha512-iPAjerrse27/ygGLxw+EBR9agv9Y6uLeYVJMu+QNCoouJ1/1ri0mGrcWpfCqFZuzzx3WjtwxG098X+n4OuRkPg==} + engines: {node: '>= 0.4'} is-shared-array-buffer@1.0.4: - resolution: - { integrity: sha512-ISWac8drv4ZGfwKl5slpHG9OwPNty4jOWPRIhBpxOoD+hqITiwuipOQ2bNthAzwA3B4fIjO4Nln74N0S9byq8A== } - engines: { node: ">= 0.4" } + resolution: {integrity: sha512-ISWac8drv4ZGfwKl5slpHG9OwPNty4jOWPRIhBpxOoD+hqITiwuipOQ2bNthAzwA3B4fIjO4Nln74N0S9byq8A==} + engines: {node: '>= 0.4'} is-string@1.1.1: - resolution: - { integrity: sha512-BtEeSsoaQjlSPBemMQIrY1MY0uM6vnS1g5fmufYOtnxLGUZM2178PKbhsk7Ffv58IX+ZtcvoGwccYsh0PglkAA== } - engines: { node: ">= 0.4" } + resolution: {integrity: sha512-BtEeSsoaQjlSPBemMQIrY1MY0uM6vnS1g5fmufYOtnxLGUZM2178PKbhsk7Ffv58IX+ZtcvoGwccYsh0PglkAA==} + engines: {node: '>= 0.4'} is-symbol@1.1.1: - resolution: - { integrity: sha512-9gGx6GTtCQM73BgmHQXfDmLtfjjTUDSyoxTCbp5WtoixAhfgsDirWIcVQ/IHpvI5Vgd5i/J5F7B9cN/WlVbC/w== } - engines: { node: ">= 0.4" } + resolution: {integrity: sha512-9gGx6GTtCQM73BgmHQXfDmLtfjjTUDSyoxTCbp5WtoixAhfgsDirWIcVQ/IHpvI5Vgd5i/J5F7B9cN/WlVbC/w==} + engines: {node: '>= 0.4'} is-typed-array@1.1.15: - resolution: - { integrity: sha512-p3EcsicXjit7SaskXHs1hA91QxgTw46Fv6EFKKGS5DRFLD8yKnohjF3hxoju94b/OcMZoQukzpPpBE9uLVKzgQ== } - engines: { node: ">= 0.4" } + resolution: {integrity: sha512-p3EcsicXjit7SaskXHs1hA91QxgTw46Fv6EFKKGS5DRFLD8yKnohjF3hxoju94b/OcMZoQukzpPpBE9uLVKzgQ==} + engines: {node: '>= 0.4'} is-weakmap@2.0.2: - resolution: - { integrity: sha512-K5pXYOm9wqY1RgjpL3YTkF39tni1XajUIkawTLUo9EZEVUFga5gSQJF8nNS7ZwJQ02y+1YCNYcMh+HIf1ZqE+w== } - engines: { node: ">= 0.4" } + resolution: {integrity: sha512-K5pXYOm9wqY1RgjpL3YTkF39tni1XajUIkawTLUo9EZEVUFga5gSQJF8nNS7ZwJQ02y+1YCNYcMh+HIf1ZqE+w==} + engines: {node: '>= 0.4'} is-weakref@1.1.1: - resolution: - { integrity: sha512-6i9mGWSlqzNMEqpCp93KwRS1uUOodk2OJ6b+sq7ZPDSy2WuI5NFIxp/254TytR8ftefexkWn5xNiHUNpPOfSew== } - engines: { node: ">= 0.4" } + resolution: {integrity: sha512-6i9mGWSlqzNMEqpCp93KwRS1uUOodk2OJ6b+sq7ZPDSy2WuI5NFIxp/254TytR8ftefexkWn5xNiHUNpPOfSew==} + engines: {node: '>= 0.4'} is-weakset@2.0.4: - resolution: - { integrity: sha512-mfcwb6IzQyOKTs84CQMrOwW4gQcaTOAWJ0zzJCl2WSPDrWk/OzDaImWFH3djXhb24g4eudZfLRozAvPGw4d9hQ== } - engines: { node: ">= 0.4" } + resolution: {integrity: sha512-mfcwb6IzQyOKTs84CQMrOwW4gQcaTOAWJ0zzJCl2WSPDrWk/OzDaImWFH3djXhb24g4eudZfLRozAvPGw4d9hQ==} + engines: {node: '>= 0.4'} isarray@2.0.5: - resolution: - { integrity: sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw== } + resolution: {integrity: sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==} isexe@2.0.0: - resolution: - { integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw== } + resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==} iterator.prototype@1.1.5: - resolution: - { integrity: sha512-H0dkQoCa3b2VEeKQBOxFph+JAbcrQdE7KC0UkqwpLmv2EC4P41QXP+rqo9wYodACiG5/WM5s9oDApTU8utwj9g== } - engines: { node: ">= 0.4" } + resolution: {integrity: sha512-H0dkQoCa3b2VEeKQBOxFph+JAbcrQdE7KC0UkqwpLmv2EC4P41QXP+rqo9wYodACiG5/WM5s9oDApTU8utwj9g==} + engines: {node: '>= 0.4'} jiti@2.4.2: - resolution: - { integrity: sha512-rg9zJN+G4n2nfJl5MW3BMygZX56zKPNVEYYqq7adpmMh4Jn2QNEwhvQlFy6jPVdcod7txZtKHWnyZiA3a0zP7A== } + resolution: {integrity: sha512-rg9zJN+G4n2nfJl5MW3BMygZX56zKPNVEYYqq7adpmMh4Jn2QNEwhvQlFy6jPVdcod7txZtKHWnyZiA3a0zP7A==} hasBin: true js-tokens@4.0.0: - resolution: - { integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ== } + resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==} js-yaml@4.1.0: - resolution: - { integrity: sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA== } + resolution: {integrity: sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==} hasBin: true jsesc@3.1.0: - resolution: - { integrity: sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA== } - engines: { node: ">=6" } + resolution: {integrity: sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==} + engines: {node: '>=6'} hasBin: true json-buffer@3.0.1: - resolution: - { integrity: sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ== } + resolution: {integrity: sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==} json-schema-traverse@0.4.1: - resolution: - { integrity: sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg== } + resolution: {integrity: sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==} json-stable-stringify-without-jsonify@1.0.1: - resolution: - { integrity: sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw== } + resolution: {integrity: sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==} json5@1.0.2: - resolution: - { integrity: sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA== } + resolution: {integrity: sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA==} hasBin: true jsx-ast-utils@3.3.5: - resolution: - { integrity: sha512-ZZow9HBI5O6EPgSJLUb8n2NKgmVWTwCvHGwFuJlMjvLFqlGG6pjirPhtdsseaLZjSibD8eegzmYpUZwoIlj2cQ== } - engines: { node: ">=4.0" } + resolution: {integrity: sha512-ZZow9HBI5O6EPgSJLUb8n2NKgmVWTwCvHGwFuJlMjvLFqlGG6pjirPhtdsseaLZjSibD8eegzmYpUZwoIlj2cQ==} + engines: {node: '>=4.0'} keyv@4.5.4: - resolution: - { integrity: sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw== } + resolution: {integrity: sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==} language-subtag-registry@0.3.23: - resolution: - { integrity: sha512-0K65Lea881pHotoGEa5gDlMxt3pctLi2RplBb7Ezh4rRdLEOtgi7n4EwK9lamnUCkKBqaeKRVebTq6BAxSkpXQ== } + resolution: {integrity: sha512-0K65Lea881pHotoGEa5gDlMxt3pctLi2RplBb7Ezh4rRdLEOtgi7n4EwK9lamnUCkKBqaeKRVebTq6BAxSkpXQ==} language-tags@1.0.9: - resolution: - { integrity: sha512-MbjN408fEndfiQXbFQ1vnd+1NoLDsnQW41410oQBXiyXDMYH5z505juWa4KUE1LqxRC7DgOgZDbKLxHIwm27hA== } - engines: { node: ">=0.10" } + resolution: {integrity: sha512-MbjN408fEndfiQXbFQ1vnd+1NoLDsnQW41410oQBXiyXDMYH5z505juWa4KUE1LqxRC7DgOgZDbKLxHIwm27hA==} + engines: {node: '>=0.10'} levn@0.4.1: - resolution: - { integrity: sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ== } - engines: { node: ">= 0.8.0" } + resolution: {integrity: sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==} + engines: {node: '>= 0.8.0'} lightningcss-darwin-arm64@1.30.1: - resolution: - { integrity: sha512-c8JK7hyE65X1MHMN+Viq9n11RRC7hgin3HhYKhrMyaXflk5GVplZ60IxyoVtzILeKr+xAJwg6zK6sjTBJ0FKYQ== } - engines: { node: ">= 12.0.0" } + resolution: {integrity: sha512-c8JK7hyE65X1MHMN+Viq9n11RRC7hgin3HhYKhrMyaXflk5GVplZ60IxyoVtzILeKr+xAJwg6zK6sjTBJ0FKYQ==} + engines: {node: '>= 12.0.0'} cpu: [arm64] os: [darwin] lightningcss-darwin-x64@1.30.1: - resolution: - { integrity: sha512-k1EvjakfumAQoTfcXUcHQZhSpLlkAuEkdMBsI/ivWw9hL+7FtilQc0Cy3hrx0AAQrVtQAbMI7YjCgYgvn37PzA== } - engines: { node: ">= 12.0.0" } + resolution: {integrity: sha512-k1EvjakfumAQoTfcXUcHQZhSpLlkAuEkdMBsI/ivWw9hL+7FtilQc0Cy3hrx0AAQrVtQAbMI7YjCgYgvn37PzA==} + engines: {node: '>= 12.0.0'} cpu: [x64] os: [darwin] lightningcss-freebsd-x64@1.30.1: - resolution: - { integrity: sha512-kmW6UGCGg2PcyUE59K5r0kWfKPAVy4SltVeut+umLCFoJ53RdCUWxcRDzO1eTaxf/7Q2H7LTquFHPL5R+Gjyig== } - engines: { node: ">= 12.0.0" } + resolution: {integrity: sha512-kmW6UGCGg2PcyUE59K5r0kWfKPAVy4SltVeut+umLCFoJ53RdCUWxcRDzO1eTaxf/7Q2H7LTquFHPL5R+Gjyig==} + engines: {node: '>= 12.0.0'} cpu: [x64] os: [freebsd] lightningcss-linux-arm-gnueabihf@1.30.1: - resolution: - { integrity: sha512-MjxUShl1v8pit+6D/zSPq9S9dQ2NPFSQwGvxBCYaBYLPlCWuPh9/t1MRS8iUaR8i+a6w7aps+B4N0S1TYP/R+Q== } - engines: { node: ">= 12.0.0" } + resolution: {integrity: sha512-MjxUShl1v8pit+6D/zSPq9S9dQ2NPFSQwGvxBCYaBYLPlCWuPh9/t1MRS8iUaR8i+a6w7aps+B4N0S1TYP/R+Q==} + engines: {node: '>= 12.0.0'} cpu: [arm] os: [linux] lightningcss-linux-arm64-gnu@1.30.1: - resolution: - { integrity: sha512-gB72maP8rmrKsnKYy8XUuXi/4OctJiuQjcuqWNlJQ6jZiWqtPvqFziskH3hnajfvKB27ynbVCucKSm2rkQp4Bw== } - engines: { node: ">= 12.0.0" } + resolution: {integrity: sha512-gB72maP8rmrKsnKYy8XUuXi/4OctJiuQjcuqWNlJQ6jZiWqtPvqFziskH3hnajfvKB27ynbVCucKSm2rkQp4Bw==} + engines: {node: '>= 12.0.0'} cpu: [arm64] os: [linux] lightningcss-linux-arm64-musl@1.30.1: - resolution: - { integrity: sha512-jmUQVx4331m6LIX+0wUhBbmMX7TCfjF5FoOH6SD1CttzuYlGNVpA7QnrmLxrsub43ClTINfGSYyHe2HWeLl5CQ== } - engines: { node: ">= 12.0.0" } + resolution: {integrity: sha512-jmUQVx4331m6LIX+0wUhBbmMX7TCfjF5FoOH6SD1CttzuYlGNVpA7QnrmLxrsub43ClTINfGSYyHe2HWeLl5CQ==} + engines: {node: '>= 12.0.0'} cpu: [arm64] os: [linux] lightningcss-linux-x64-gnu@1.30.1: - resolution: - { integrity: sha512-piWx3z4wN8J8z3+O5kO74+yr6ze/dKmPnI7vLqfSqI8bccaTGY5xiSGVIJBDd5K5BHlvVLpUB3S2YCfelyJ1bw== } - engines: { node: ">= 12.0.0" } + resolution: {integrity: sha512-piWx3z4wN8J8z3+O5kO74+yr6ze/dKmPnI7vLqfSqI8bccaTGY5xiSGVIJBDd5K5BHlvVLpUB3S2YCfelyJ1bw==} + engines: {node: '>= 12.0.0'} cpu: [x64] os: [linux] lightningcss-linux-x64-musl@1.30.1: - resolution: - { integrity: sha512-rRomAK7eIkL+tHY0YPxbc5Dra2gXlI63HL+v1Pdi1a3sC+tJTcFrHX+E86sulgAXeI7rSzDYhPSeHHjqFhqfeQ== } - engines: { node: ">= 12.0.0" } + resolution: {integrity: sha512-rRomAK7eIkL+tHY0YPxbc5Dra2gXlI63HL+v1Pdi1a3sC+tJTcFrHX+E86sulgAXeI7rSzDYhPSeHHjqFhqfeQ==} + engines: {node: '>= 12.0.0'} cpu: [x64] os: [linux] lightningcss-win32-arm64-msvc@1.30.1: - resolution: - { integrity: sha512-mSL4rqPi4iXq5YVqzSsJgMVFENoa4nGTT/GjO2c0Yl9OuQfPsIfncvLrEW6RbbB24WtZ3xP/2CCmI3tNkNV4oA== } - engines: { node: ">= 12.0.0" } + resolution: {integrity: sha512-mSL4rqPi4iXq5YVqzSsJgMVFENoa4nGTT/GjO2c0Yl9OuQfPsIfncvLrEW6RbbB24WtZ3xP/2CCmI3tNkNV4oA==} + engines: {node: '>= 12.0.0'} cpu: [arm64] os: [win32] lightningcss-win32-x64-msvc@1.30.1: - resolution: - { integrity: sha512-PVqXh48wh4T53F/1CCu8PIPCxLzWyCnn/9T5W1Jpmdy5h9Cwd+0YQS6/LwhHXSafuc61/xg9Lv5OrCby6a++jg== } - engines: { node: ">= 12.0.0" } + resolution: {integrity: sha512-PVqXh48wh4T53F/1CCu8PIPCxLzWyCnn/9T5W1Jpmdy5h9Cwd+0YQS6/LwhHXSafuc61/xg9Lv5OrCby6a++jg==} + engines: {node: '>= 12.0.0'} cpu: [x64] os: [win32] lightningcss@1.30.1: - resolution: - { integrity: sha512-xi6IyHML+c9+Q3W0S4fCQJOym42pyurFiJUHEcEyHS0CeKzia4yZDEsLlqOFykxOdHpNy0NmvVO31vcSqAxJCg== } - engines: { node: ">= 12.0.0" } + resolution: {integrity: sha512-xi6IyHML+c9+Q3W0S4fCQJOym42pyurFiJUHEcEyHS0CeKzia4yZDEsLlqOFykxOdHpNy0NmvVO31vcSqAxJCg==} + engines: {node: '>= 12.0.0'} locate-path@6.0.0: - resolution: - { integrity: sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw== } - engines: { node: ">=10" } + resolution: {integrity: sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==} + engines: {node: '>=10'} lodash.merge@4.6.2: - resolution: - { integrity: sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ== } + resolution: {integrity: sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==} longest-streak@3.1.0: - resolution: - { integrity: sha512-9Ri+o0JYgehTaVBBDoMqIl8GXtbWg711O3srftcHhZ0dqnETqLaoIK0x17fUw9rFSlK/0NlsKe0Ahhyl5pXE2g== } + resolution: {integrity: sha512-9Ri+o0JYgehTaVBBDoMqIl8GXtbWg711O3srftcHhZ0dqnETqLaoIK0x17fUw9rFSlK/0NlsKe0Ahhyl5pXE2g==} loose-envify@1.4.0: - resolution: - { integrity: sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q== } + resolution: {integrity: sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==} hasBin: true lru-cache@11.1.0: - resolution: - { integrity: sha512-QIXZUBJUx+2zHUdQujWejBkcD9+cs94tLn0+YL8UrCh+D5sCXZ4c7LaEH48pNwRY3MLDgqUFyhlCyjJPf1WP0A== } - engines: { node: 20 || >=22 } + resolution: {integrity: sha512-QIXZUBJUx+2zHUdQujWejBkcD9+cs94tLn0+YL8UrCh+D5sCXZ4c7LaEH48pNwRY3MLDgqUFyhlCyjJPf1WP0A==} + engines: {node: 20 || >=22} lucide-react@0.487.0: - resolution: - { integrity: sha512-aKqhOQ+YmFnwq8dWgGjOuLc8V1R9/c/yOd+zDY4+ohsR2Jo05lSGc3WsstYPIzcTpeosN7LoCkLReUUITvaIvw== } + resolution: {integrity: sha512-aKqhOQ+YmFnwq8dWgGjOuLc8V1R9/c/yOd+zDY4+ohsR2Jo05lSGc3WsstYPIzcTpeosN7LoCkLReUUITvaIvw==} peerDependencies: react: ^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0 lucide-react@0.525.0: - resolution: - { integrity: sha512-Tm1txJ2OkymCGkvwoHt33Y2JpN5xucVq1slHcgE6Lk0WjDfjgKWor5CdVER8U6DvcfMwh4M8XxmpTiyzfmfDYQ== } + resolution: {integrity: sha512-Tm1txJ2OkymCGkvwoHt33Y2JpN5xucVq1slHcgE6Lk0WjDfjgKWor5CdVER8U6DvcfMwh4M8XxmpTiyzfmfDYQ==} peerDependencies: react: ^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0 magic-string@0.30.17: - resolution: - { integrity: sha512-sNPKHvyjVf7gyjwS4xGTaW/mCnF8wnjtifKBEhxfZ7E/S8tQ0rssrwGNn6q8JH/ohItJfSQp9mBtQYuTlH5QnA== } + resolution: {integrity: sha512-sNPKHvyjVf7gyjwS4xGTaW/mCnF8wnjtifKBEhxfZ7E/S8tQ0rssrwGNn6q8JH/ohItJfSQp9mBtQYuTlH5QnA==} markdown-extensions@2.0.0: - resolution: - { integrity: sha512-o5vL7aDWatOTX8LzaS1WMoaoxIiLRQJuIKKe2wAw6IeULDHaqbiqiggmx+pKvZDb1Sj+pE46Sn1T7lCqfFtg1Q== } - engines: { node: ">=16" } + resolution: {integrity: sha512-o5vL7aDWatOTX8LzaS1WMoaoxIiLRQJuIKKe2wAw6IeULDHaqbiqiggmx+pKvZDb1Sj+pE46Sn1T7lCqfFtg1Q==} + engines: {node: '>=16'} markdown-table@3.0.4: - resolution: - { integrity: sha512-wiYz4+JrLyb/DqW2hkFJxP7Vd7JuTDm77fvbM8VfEQdmSMqcImWeeRbHwZjBjIFki/VaMK2BhFi7oUUZeM5bqw== } + resolution: {integrity: sha512-wiYz4+JrLyb/DqW2hkFJxP7Vd7JuTDm77fvbM8VfEQdmSMqcImWeeRbHwZjBjIFki/VaMK2BhFi7oUUZeM5bqw==} math-intrinsics@1.1.0: - resolution: - { integrity: sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g== } - engines: { node: ">= 0.4" } + resolution: {integrity: sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==} + engines: {node: '>= 0.4'} mdast-util-find-and-replace@3.0.2: - resolution: - { integrity: sha512-Tmd1Vg/m3Xz43afeNxDIhWRtFZgM2VLyaf4vSTYwudTyeuTneoL3qtWMA5jeLyz/O1vDJmmV4QuScFCA2tBPwg== } + resolution: {integrity: sha512-Tmd1Vg/m3Xz43afeNxDIhWRtFZgM2VLyaf4vSTYwudTyeuTneoL3qtWMA5jeLyz/O1vDJmmV4QuScFCA2tBPwg==} mdast-util-from-markdown@2.0.2: - resolution: - { integrity: sha512-uZhTV/8NBuw0WHkPTrCqDOl0zVe1BIng5ZtHoDk49ME1qqcjYmmLmOf0gELgcRMxN4w2iuIeVso5/6QymSrgmA== } + resolution: {integrity: sha512-uZhTV/8NBuw0WHkPTrCqDOl0zVe1BIng5ZtHoDk49ME1qqcjYmmLmOf0gELgcRMxN4w2iuIeVso5/6QymSrgmA==} mdast-util-gfm-autolink-literal@2.0.1: - resolution: - { integrity: sha512-5HVP2MKaP6L+G6YaxPNjuL0BPrq9orG3TsrZ9YXbA3vDw/ACI4MEsnoDpn6ZNm7GnZgtAcONJyPhOP8tNJQavQ== } + resolution: {integrity: sha512-5HVP2MKaP6L+G6YaxPNjuL0BPrq9orG3TsrZ9YXbA3vDw/ACI4MEsnoDpn6ZNm7GnZgtAcONJyPhOP8tNJQavQ==} mdast-util-gfm-footnote@2.1.0: - resolution: - { integrity: sha512-sqpDWlsHn7Ac9GNZQMeUzPQSMzR6Wv0WKRNvQRg0KqHh02fpTz69Qc1QSseNX29bhz1ROIyNyxExfawVKTm1GQ== } + resolution: {integrity: sha512-sqpDWlsHn7Ac9GNZQMeUzPQSMzR6Wv0WKRNvQRg0KqHh02fpTz69Qc1QSseNX29bhz1ROIyNyxExfawVKTm1GQ==} mdast-util-gfm-strikethrough@2.0.0: - resolution: - { integrity: sha512-mKKb915TF+OC5ptj5bJ7WFRPdYtuHv0yTRxK2tJvi+BDqbkiG7h7u/9SI89nRAYcmap2xHQL9D+QG/6wSrTtXg== } + resolution: {integrity: sha512-mKKb915TF+OC5ptj5bJ7WFRPdYtuHv0yTRxK2tJvi+BDqbkiG7h7u/9SI89nRAYcmap2xHQL9D+QG/6wSrTtXg==} mdast-util-gfm-table@2.0.0: - resolution: - { integrity: sha512-78UEvebzz/rJIxLvE7ZtDd/vIQ0RHv+3Mh5DR96p7cS7HsBhYIICDBCu8csTNWNO6tBWfqXPWekRuj2FNOGOZg== } + resolution: {integrity: sha512-78UEvebzz/rJIxLvE7ZtDd/vIQ0RHv+3Mh5DR96p7cS7HsBhYIICDBCu8csTNWNO6tBWfqXPWekRuj2FNOGOZg==} mdast-util-gfm-task-list-item@2.0.0: - resolution: - { integrity: sha512-IrtvNvjxC1o06taBAVJznEnkiHxLFTzgonUdy8hzFVeDun0uTjxxrRGVaNFqkU1wJR3RBPEfsxmU6jDWPofrTQ== } + resolution: {integrity: sha512-IrtvNvjxC1o06taBAVJznEnkiHxLFTzgonUdy8hzFVeDun0uTjxxrRGVaNFqkU1wJR3RBPEfsxmU6jDWPofrTQ==} mdast-util-gfm@3.1.0: - resolution: - { integrity: sha512-0ulfdQOM3ysHhCJ1p06l0b0VKlhU0wuQs3thxZQagjcjPrlFRqY215uZGHHJan9GEAXd9MbfPjFJz+qMkVR6zQ== } + resolution: {integrity: sha512-0ulfdQOM3ysHhCJ1p06l0b0VKlhU0wuQs3thxZQagjcjPrlFRqY215uZGHHJan9GEAXd9MbfPjFJz+qMkVR6zQ==} mdast-util-mdx-expression@2.0.1: - resolution: - { integrity: sha512-J6f+9hUp+ldTZqKRSg7Vw5V6MqjATc+3E4gf3CFNcuZNWD8XdyI6zQ8GqH7f8169MM6P7hMBRDVGnn7oHB9kXQ== } + resolution: {integrity: sha512-J6f+9hUp+ldTZqKRSg7Vw5V6MqjATc+3E4gf3CFNcuZNWD8XdyI6zQ8GqH7f8169MM6P7hMBRDVGnn7oHB9kXQ==} mdast-util-mdx-jsx@3.2.0: - resolution: - { integrity: sha512-lj/z8v0r6ZtsN/cGNNtemmmfoLAFZnjMbNyLzBafjzikOM+glrjNHPlf6lQDOTccj9n5b0PPihEBbhneMyGs1Q== } + resolution: {integrity: sha512-lj/z8v0r6ZtsN/cGNNtemmmfoLAFZnjMbNyLzBafjzikOM+glrjNHPlf6lQDOTccj9n5b0PPihEBbhneMyGs1Q==} mdast-util-mdx@3.0.0: - resolution: - { integrity: sha512-JfbYLAW7XnYTTbUsmpu0kdBUVe+yKVJZBItEjwyYJiDJuZ9w4eeaqks4HQO+R7objWgS2ymV60GYpI14Ug554w== } + resolution: {integrity: sha512-JfbYLAW7XnYTTbUsmpu0kdBUVe+yKVJZBItEjwyYJiDJuZ9w4eeaqks4HQO+R7objWgS2ymV60GYpI14Ug554w==} mdast-util-mdxjs-esm@2.0.1: - resolution: - { integrity: sha512-EcmOpxsZ96CvlP03NghtH1EsLtr0n9Tm4lPUJUBccV9RwUOneqSycg19n5HGzCf+10LozMRSObtVr3ee1WoHtg== } + resolution: {integrity: sha512-EcmOpxsZ96CvlP03NghtH1EsLtr0n9Tm4lPUJUBccV9RwUOneqSycg19n5HGzCf+10LozMRSObtVr3ee1WoHtg==} mdast-util-phrasing@4.1.0: - resolution: - { integrity: sha512-TqICwyvJJpBwvGAMZjj4J2n0X8QWp21b9l0o7eXyVJ25YNWYbJDVIyD1bZXE6WtV6RmKJVYmQAKWa0zWOABz2w== } + resolution: {integrity: sha512-TqICwyvJJpBwvGAMZjj4J2n0X8QWp21b9l0o7eXyVJ25YNWYbJDVIyD1bZXE6WtV6RmKJVYmQAKWa0zWOABz2w==} mdast-util-to-hast@13.2.0: - resolution: - { integrity: sha512-QGYKEuUsYT9ykKBCMOEDLsU5JRObWQusAolFMeko/tYPufNkRffBAQjIE+99jbA87xv6FgmjLtwjh9wBWajwAA== } + resolution: {integrity: sha512-QGYKEuUsYT9ykKBCMOEDLsU5JRObWQusAolFMeko/tYPufNkRffBAQjIE+99jbA87xv6FgmjLtwjh9wBWajwAA==} mdast-util-to-markdown@2.1.2: - resolution: - { integrity: sha512-xj68wMTvGXVOKonmog6LwyJKrYXZPvlwabaryTjLh9LuvovB/KAH+kvi8Gjj+7rJjsFi23nkUxRQv1KqSroMqA== } + resolution: {integrity: sha512-xj68wMTvGXVOKonmog6LwyJKrYXZPvlwabaryTjLh9LuvovB/KAH+kvi8Gjj+7rJjsFi23nkUxRQv1KqSroMqA==} mdast-util-to-string@4.0.0: - resolution: - { integrity: sha512-0H44vDimn51F0YwvxSJSm0eCDOJTRlmN0R1yBh4HLj9wiV1Dn0QoXGbvFAWj2hSItVTlCmBF1hqKlIyUBVFLPg== } + resolution: {integrity: sha512-0H44vDimn51F0YwvxSJSm0eCDOJTRlmN0R1yBh4HLj9wiV1Dn0QoXGbvFAWj2hSItVTlCmBF1hqKlIyUBVFLPg==} merge2@1.4.1: - resolution: - { integrity: sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg== } - engines: { node: ">= 8" } + resolution: {integrity: sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==} + engines: {node: '>= 8'} micromark-core-commonmark@2.0.3: - resolution: - { integrity: sha512-RDBrHEMSxVFLg6xvnXmb1Ayr2WzLAWjeSATAoxwKYJV94TeNavgoIdA0a9ytzDSVzBy2YKFK+emCPOEibLeCrg== } + resolution: {integrity: sha512-RDBrHEMSxVFLg6xvnXmb1Ayr2WzLAWjeSATAoxwKYJV94TeNavgoIdA0a9ytzDSVzBy2YKFK+emCPOEibLeCrg==} micromark-extension-gfm-autolink-literal@2.1.0: - resolution: - { integrity: sha512-oOg7knzhicgQ3t4QCjCWgTmfNhvQbDDnJeVu9v81r7NltNCVmhPy1fJRX27pISafdjL+SVc4d3l48Gb6pbRypw== } + resolution: {integrity: sha512-oOg7knzhicgQ3t4QCjCWgTmfNhvQbDDnJeVu9v81r7NltNCVmhPy1fJRX27pISafdjL+SVc4d3l48Gb6pbRypw==} micromark-extension-gfm-footnote@2.1.0: - resolution: - { integrity: sha512-/yPhxI1ntnDNsiHtzLKYnE3vf9JZ6cAisqVDauhp4CEHxlb4uoOTxOCJ+9s51bIB8U1N1FJ1RXOKTIlD5B/gqw== } + resolution: {integrity: sha512-/yPhxI1ntnDNsiHtzLKYnE3vf9JZ6cAisqVDauhp4CEHxlb4uoOTxOCJ+9s51bIB8U1N1FJ1RXOKTIlD5B/gqw==} micromark-extension-gfm-strikethrough@2.1.0: - resolution: - { integrity: sha512-ADVjpOOkjz1hhkZLlBiYA9cR2Anf8F4HqZUO6e5eDcPQd0Txw5fxLzzxnEkSkfnD0wziSGiv7sYhk/ktvbf1uw== } + resolution: {integrity: sha512-ADVjpOOkjz1hhkZLlBiYA9cR2Anf8F4HqZUO6e5eDcPQd0Txw5fxLzzxnEkSkfnD0wziSGiv7sYhk/ktvbf1uw==} micromark-extension-gfm-table@2.1.1: - resolution: - { integrity: sha512-t2OU/dXXioARrC6yWfJ4hqB7rct14e8f7m0cbI5hUmDyyIlwv5vEtooptH8INkbLzOatzKuVbQmAYcbWoyz6Dg== } + resolution: {integrity: sha512-t2OU/dXXioARrC6yWfJ4hqB7rct14e8f7m0cbI5hUmDyyIlwv5vEtooptH8INkbLzOatzKuVbQmAYcbWoyz6Dg==} micromark-extension-gfm-tagfilter@2.0.0: - resolution: - { integrity: sha512-xHlTOmuCSotIA8TW1mDIM6X2O1SiX5P9IuDtqGonFhEK0qgRI4yeC6vMxEV2dgyr2TiD+2PQ10o+cOhdVAcwfg== } + resolution: {integrity: sha512-xHlTOmuCSotIA8TW1mDIM6X2O1SiX5P9IuDtqGonFhEK0qgRI4yeC6vMxEV2dgyr2TiD+2PQ10o+cOhdVAcwfg==} micromark-extension-gfm-task-list-item@2.1.0: - resolution: - { integrity: sha512-qIBZhqxqI6fjLDYFTBIa4eivDMnP+OZqsNwmQ3xNLE4Cxwc+zfQEfbs6tzAo2Hjq+bh6q5F+Z8/cksrLFYWQQw== } + resolution: {integrity: sha512-qIBZhqxqI6fjLDYFTBIa4eivDMnP+OZqsNwmQ3xNLE4Cxwc+zfQEfbs6tzAo2Hjq+bh6q5F+Z8/cksrLFYWQQw==} micromark-extension-gfm@3.0.0: - resolution: - { integrity: sha512-vsKArQsicm7t0z2GugkCKtZehqUm31oeGBV/KVSorWSy8ZlNAv7ytjFhvaryUiCUJYqs+NoE6AFhpQvBTM6Q4w== } + resolution: {integrity: sha512-vsKArQsicm7t0z2GugkCKtZehqUm31oeGBV/KVSorWSy8ZlNAv7ytjFhvaryUiCUJYqs+NoE6AFhpQvBTM6Q4w==} micromark-extension-mdx-expression@3.0.1: - resolution: - { integrity: sha512-dD/ADLJ1AeMvSAKBwO22zG22N4ybhe7kFIZ3LsDI0GlsNr2A3KYxb0LdC1u5rj4Nw+CHKY0RVdnHX8vj8ejm4Q== } + resolution: {integrity: sha512-dD/ADLJ1AeMvSAKBwO22zG22N4ybhe7kFIZ3LsDI0GlsNr2A3KYxb0LdC1u5rj4Nw+CHKY0RVdnHX8vj8ejm4Q==} micromark-extension-mdx-jsx@3.0.2: - resolution: - { integrity: sha512-e5+q1DjMh62LZAJOnDraSSbDMvGJ8x3cbjygy2qFEi7HCeUT4BDKCvMozPozcD6WmOt6sVvYDNBKhFSz3kjOVQ== } + resolution: {integrity: sha512-e5+q1DjMh62LZAJOnDraSSbDMvGJ8x3cbjygy2qFEi7HCeUT4BDKCvMozPozcD6WmOt6sVvYDNBKhFSz3kjOVQ==} micromark-extension-mdx-md@2.0.0: - resolution: - { integrity: sha512-EpAiszsB3blw4Rpba7xTOUptcFeBFi+6PY8VnJ2hhimH+vCQDirWgsMpz7w1XcZE7LVrSAUGb9VJpG9ghlYvYQ== } + resolution: {integrity: sha512-EpAiszsB3blw4Rpba7xTOUptcFeBFi+6PY8VnJ2hhimH+vCQDirWgsMpz7w1XcZE7LVrSAUGb9VJpG9ghlYvYQ==} micromark-extension-mdxjs-esm@3.0.0: - resolution: - { integrity: sha512-DJFl4ZqkErRpq/dAPyeWp15tGrcrrJho1hKK5uBS70BCtfrIFg81sqcTVu3Ta+KD1Tk5vAtBNElWxtAa+m8K9A== } + resolution: {integrity: sha512-DJFl4ZqkErRpq/dAPyeWp15tGrcrrJho1hKK5uBS70BCtfrIFg81sqcTVu3Ta+KD1Tk5vAtBNElWxtAa+m8K9A==} micromark-extension-mdxjs@3.0.0: - resolution: - { integrity: sha512-A873fJfhnJ2siZyUrJ31l34Uqwy4xIFmvPY1oj+Ean5PHcPBYzEsvqvWGaWcfEIr11O5Dlw3p2y0tZWpKHDejQ== } + resolution: {integrity: sha512-A873fJfhnJ2siZyUrJ31l34Uqwy4xIFmvPY1oj+Ean5PHcPBYzEsvqvWGaWcfEIr11O5Dlw3p2y0tZWpKHDejQ==} micromark-factory-destination@2.0.1: - resolution: - { integrity: sha512-Xe6rDdJlkmbFRExpTOmRj9N3MaWmbAgdpSrBQvCFqhezUn4AHqJHbaEnfbVYYiexVSs//tqOdY/DxhjdCiJnIA== } + resolution: {integrity: sha512-Xe6rDdJlkmbFRExpTOmRj9N3MaWmbAgdpSrBQvCFqhezUn4AHqJHbaEnfbVYYiexVSs//tqOdY/DxhjdCiJnIA==} micromark-factory-label@2.0.1: - resolution: - { integrity: sha512-VFMekyQExqIW7xIChcXn4ok29YE3rnuyveW3wZQWWqF4Nv9Wk5rgJ99KzPvHjkmPXF93FXIbBp6YdW3t71/7Vg== } + resolution: {integrity: sha512-VFMekyQExqIW7xIChcXn4ok29YE3rnuyveW3wZQWWqF4Nv9Wk5rgJ99KzPvHjkmPXF93FXIbBp6YdW3t71/7Vg==} micromark-factory-mdx-expression@2.0.3: - resolution: - { integrity: sha512-kQnEtA3vzucU2BkrIa8/VaSAsP+EJ3CKOvhMuJgOEGg9KDC6OAY6nSnNDVRiVNRqj7Y4SlSzcStaH/5jge8JdQ== } + resolution: {integrity: sha512-kQnEtA3vzucU2BkrIa8/VaSAsP+EJ3CKOvhMuJgOEGg9KDC6OAY6nSnNDVRiVNRqj7Y4SlSzcStaH/5jge8JdQ==} micromark-factory-space@2.0.1: - resolution: - { integrity: sha512-zRkxjtBxxLd2Sc0d+fbnEunsTj46SWXgXciZmHq0kDYGnck/ZSGj9/wULTV95uoeYiK5hRXP2mJ98Uo4cq/LQg== } + resolution: {integrity: sha512-zRkxjtBxxLd2Sc0d+fbnEunsTj46SWXgXciZmHq0kDYGnck/ZSGj9/wULTV95uoeYiK5hRXP2mJ98Uo4cq/LQg==} micromark-factory-title@2.0.1: - resolution: - { integrity: sha512-5bZ+3CjhAd9eChYTHsjy6TGxpOFSKgKKJPJxr293jTbfry2KDoWkhBb6TcPVB4NmzaPhMs1Frm9AZH7OD4Cjzw== } + resolution: {integrity: sha512-5bZ+3CjhAd9eChYTHsjy6TGxpOFSKgKKJPJxr293jTbfry2KDoWkhBb6TcPVB4NmzaPhMs1Frm9AZH7OD4Cjzw==} micromark-factory-whitespace@2.0.1: - resolution: - { integrity: sha512-Ob0nuZ3PKt/n0hORHyvoD9uZhr+Za8sFoP+OnMcnWK5lngSzALgQYKMr9RJVOWLqQYuyn6ulqGWSXdwf6F80lQ== } + resolution: {integrity: sha512-Ob0nuZ3PKt/n0hORHyvoD9uZhr+Za8sFoP+OnMcnWK5lngSzALgQYKMr9RJVOWLqQYuyn6ulqGWSXdwf6F80lQ==} micromark-util-character@2.1.1: - resolution: - { integrity: sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q== } + resolution: {integrity: sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q==} micromark-util-chunked@2.0.1: - resolution: - { integrity: sha512-QUNFEOPELfmvv+4xiNg2sRYeS/P84pTW0TCgP5zc9FpXetHY0ab7SxKyAQCNCc1eK0459uoLI1y5oO5Vc1dbhA== } + resolution: {integrity: sha512-QUNFEOPELfmvv+4xiNg2sRYeS/P84pTW0TCgP5zc9FpXetHY0ab7SxKyAQCNCc1eK0459uoLI1y5oO5Vc1dbhA==} micromark-util-classify-character@2.0.1: - resolution: - { integrity: sha512-K0kHzM6afW/MbeWYWLjoHQv1sgg2Q9EccHEDzSkxiP/EaagNzCm7T/WMKZ3rjMbvIpvBiZgwR3dKMygtA4mG1Q== } + resolution: {integrity: sha512-K0kHzM6afW/MbeWYWLjoHQv1sgg2Q9EccHEDzSkxiP/EaagNzCm7T/WMKZ3rjMbvIpvBiZgwR3dKMygtA4mG1Q==} micromark-util-combine-extensions@2.0.1: - resolution: - { integrity: sha512-OnAnH8Ujmy59JcyZw8JSbK9cGpdVY44NKgSM7E9Eh7DiLS2E9RNQf0dONaGDzEG9yjEl5hcqeIsj4hfRkLH/Bg== } + resolution: {integrity: sha512-OnAnH8Ujmy59JcyZw8JSbK9cGpdVY44NKgSM7E9Eh7DiLS2E9RNQf0dONaGDzEG9yjEl5hcqeIsj4hfRkLH/Bg==} micromark-util-decode-numeric-character-reference@2.0.2: - resolution: - { integrity: sha512-ccUbYk6CwVdkmCQMyr64dXz42EfHGkPQlBj5p7YVGzq8I7CtjXZJrubAYezf7Rp+bjPseiROqe7G6foFd+lEuw== } + resolution: {integrity: sha512-ccUbYk6CwVdkmCQMyr64dXz42EfHGkPQlBj5p7YVGzq8I7CtjXZJrubAYezf7Rp+bjPseiROqe7G6foFd+lEuw==} micromark-util-decode-string@2.0.1: - resolution: - { integrity: sha512-nDV/77Fj6eH1ynwscYTOsbK7rR//Uj0bZXBwJZRfaLEJ1iGBR6kIfNmlNqaqJf649EP0F3NWNdeJi03elllNUQ== } + resolution: {integrity: sha512-nDV/77Fj6eH1ynwscYTOsbK7rR//Uj0bZXBwJZRfaLEJ1iGBR6kIfNmlNqaqJf649EP0F3NWNdeJi03elllNUQ==} micromark-util-encode@2.0.1: - resolution: - { integrity: sha512-c3cVx2y4KqUnwopcO9b/SCdo2O67LwJJ/UyqGfbigahfegL9myoEFoDYZgkT7f36T0bLrM9hZTAaAyH+PCAXjw== } + resolution: {integrity: sha512-c3cVx2y4KqUnwopcO9b/SCdo2O67LwJJ/UyqGfbigahfegL9myoEFoDYZgkT7f36T0bLrM9hZTAaAyH+PCAXjw==} micromark-util-events-to-acorn@2.0.3: - resolution: - { integrity: sha512-jmsiEIiZ1n7X1Rr5k8wVExBQCg5jy4UXVADItHmNk1zkwEVhBuIUKRu3fqv+hs4nxLISi2DQGlqIOGiFxgbfHg== } + resolution: {integrity: sha512-jmsiEIiZ1n7X1Rr5k8wVExBQCg5jy4UXVADItHmNk1zkwEVhBuIUKRu3fqv+hs4nxLISi2DQGlqIOGiFxgbfHg==} micromark-util-html-tag-name@2.0.1: - resolution: - { integrity: sha512-2cNEiYDhCWKI+Gs9T0Tiysk136SnR13hhO8yW6BGNyhOC4qYFnwF1nKfD3HFAIXA5c45RrIG1ub11GiXeYd1xA== } + resolution: {integrity: sha512-2cNEiYDhCWKI+Gs9T0Tiysk136SnR13hhO8yW6BGNyhOC4qYFnwF1nKfD3HFAIXA5c45RrIG1ub11GiXeYd1xA==} micromark-util-normalize-identifier@2.0.1: - resolution: - { integrity: sha512-sxPqmo70LyARJs0w2UclACPUUEqltCkJ6PhKdMIDuJ3gSf/Q+/GIe3WKl0Ijb/GyH9lOpUkRAO2wp0GVkLvS9Q== } + resolution: {integrity: sha512-sxPqmo70LyARJs0w2UclACPUUEqltCkJ6PhKdMIDuJ3gSf/Q+/GIe3WKl0Ijb/GyH9lOpUkRAO2wp0GVkLvS9Q==} micromark-util-resolve-all@2.0.1: - resolution: - { integrity: sha512-VdQyxFWFT2/FGJgwQnJYbe1jjQoNTS4RjglmSjTUlpUMa95Htx9NHeYW4rGDJzbjvCsl9eLjMQwGeElsqmzcHg== } + resolution: {integrity: sha512-VdQyxFWFT2/FGJgwQnJYbe1jjQoNTS4RjglmSjTUlpUMa95Htx9NHeYW4rGDJzbjvCsl9eLjMQwGeElsqmzcHg==} micromark-util-sanitize-uri@2.0.1: - resolution: - { integrity: sha512-9N9IomZ/YuGGZZmQec1MbgxtlgougxTodVwDzzEouPKo3qFWvymFHWcnDi2vzV1ff6kas9ucW+o3yzJK9YB1AQ== } + resolution: {integrity: sha512-9N9IomZ/YuGGZZmQec1MbgxtlgougxTodVwDzzEouPKo3qFWvymFHWcnDi2vzV1ff6kas9ucW+o3yzJK9YB1AQ==} micromark-util-subtokenize@2.1.0: - resolution: - { integrity: sha512-XQLu552iSctvnEcgXw6+Sx75GflAPNED1qx7eBJ+wydBb2KCbRZe+NwvIEEMM83uml1+2WSXpBAcp9IUCgCYWA== } + resolution: {integrity: sha512-XQLu552iSctvnEcgXw6+Sx75GflAPNED1qx7eBJ+wydBb2KCbRZe+NwvIEEMM83uml1+2WSXpBAcp9IUCgCYWA==} micromark-util-symbol@2.0.1: - resolution: - { integrity: sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q== } + resolution: {integrity: sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==} micromark-util-types@2.0.2: - resolution: - { integrity: sha512-Yw0ECSpJoViF1qTU4DC6NwtC4aWGt1EkzaQB8KPPyCRR8z9TWeV0HbEFGTO+ZY1wB22zmxnJqhPyTpOVCpeHTA== } + resolution: {integrity: sha512-Yw0ECSpJoViF1qTU4DC6NwtC4aWGt1EkzaQB8KPPyCRR8z9TWeV0HbEFGTO+ZY1wB22zmxnJqhPyTpOVCpeHTA==} micromark@4.0.2: - resolution: - { integrity: sha512-zpe98Q6kvavpCr1NPVSCMebCKfD7CA2NqZ+rykeNhONIJBpc1tFKt9hucLGwha3jNTNI8lHpctWJWoimVF4PfA== } + resolution: {integrity: sha512-zpe98Q6kvavpCr1NPVSCMebCKfD7CA2NqZ+rykeNhONIJBpc1tFKt9hucLGwha3jNTNI8lHpctWJWoimVF4PfA==} micromatch@4.0.8: - resolution: - { integrity: sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA== } - engines: { node: ">=8.6" } + resolution: {integrity: sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==} + engines: {node: '>=8.6'} minimatch@3.1.2: - resolution: - { integrity: sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw== } + resolution: {integrity: sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==} minimatch@9.0.5: - resolution: - { integrity: sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow== } - engines: { node: ">=16 || 14 >=14.17" } + resolution: {integrity: sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==} + engines: {node: '>=16 || 14 >=14.17'} minimist@1.2.8: - resolution: - { integrity: sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA== } + resolution: {integrity: sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==} minipass@7.1.2: - resolution: - { integrity: sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw== } - engines: { node: ">=16 || 14 >=14.17" } + resolution: {integrity: sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==} + engines: {node: '>=16 || 14 >=14.17'} minizlib@3.0.2: - resolution: - { integrity: sha512-oG62iEk+CYt5Xj2YqI5Xi9xWUeZhDI8jjQmC5oThVH5JGCTgIjr7ciJDzC7MBzYd//WvR1OTmP5Q38Q8ShQtVA== } - engines: { node: ">= 18" } + resolution: {integrity: sha512-oG62iEk+CYt5Xj2YqI5Xi9xWUeZhDI8jjQmC5oThVH5JGCTgIjr7ciJDzC7MBzYd//WvR1OTmP5Q38Q8ShQtVA==} + engines: {node: '>= 18'} mkdirp@3.0.1: - resolution: - { integrity: sha512-+NsyUUAZDmo6YVHzL/stxSu3t9YS1iljliy3BSDrXJ/dkn1KYdmtZODGGjLcc9XLgVVpH4KshHB8XmZgMhaBXg== } - engines: { node: ">=10" } + resolution: {integrity: sha512-+NsyUUAZDmo6YVHzL/stxSu3t9YS1iljliy3BSDrXJ/dkn1KYdmtZODGGjLcc9XLgVVpH4KshHB8XmZgMhaBXg==} + engines: {node: '>=10'} hasBin: true motion-dom@12.22.0: - resolution: - { integrity: sha512-ooH7+/BPw9gOsL9VtPhEJHE2m4ltnhMlcGMhEqA0YGNhKof7jdaszvsyThXI6LVIKshJUZ9/CP6HNqQhJfV7kw== } + resolution: {integrity: sha512-ooH7+/BPw9gOsL9VtPhEJHE2m4ltnhMlcGMhEqA0YGNhKof7jdaszvsyThXI6LVIKshJUZ9/CP6HNqQhJfV7kw==} motion-utils@12.19.0: - resolution: - { integrity: sha512-BuFTHINYmV07pdWs6lj6aI63vr2N4dg0vR+td0rtrdpWOhBzIkEklZyLcvKBoEtwSqx8Jg06vUB5RS0xDiUybw== } + resolution: {integrity: sha512-BuFTHINYmV07pdWs6lj6aI63vr2N4dg0vR+td0rtrdpWOhBzIkEklZyLcvKBoEtwSqx8Jg06vUB5RS0xDiUybw==} motion@12.23.0: - resolution: - { integrity: sha512-PPNwblArRH9GRC4F3KtOTiIaYd+mtp324vYq3HIL+ueseoAVqPRK5TPFTAQBcIprfVd0NWo3DLzZSiyWaYFXXQ== } + resolution: {integrity: sha512-PPNwblArRH9GRC4F3KtOTiIaYd+mtp324vYq3HIL+ueseoAVqPRK5TPFTAQBcIprfVd0NWo3DLzZSiyWaYFXXQ==} peerDependencies: - "@emotion/is-prop-valid": "*" + '@emotion/is-prop-valid': '*' react: ^18.0.0 || ^19.0.0 react-dom: ^18.0.0 || ^19.0.0 peerDependenciesMeta: - "@emotion/is-prop-valid": + '@emotion/is-prop-valid': optional: true react: optional: true @@ -3036,53 +2568,46 @@ packages: optional: true ms@2.1.3: - resolution: - { integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA== } + resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} nanoid@3.3.11: - resolution: - { integrity: sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w== } - engines: { node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1 } + resolution: {integrity: sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==} + engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} hasBin: true napi-postinstall@0.3.0: - resolution: - { integrity: sha512-M7NqKyhODKV1gRLdkwE7pDsZP2/SC2a2vHkOYh9MCpKMbWVfyVfUw5MaH83Fv6XMjxr5jryUp3IDDL9rlxsTeA== } - engines: { node: ^12.20.0 || ^14.18.0 || >=16.0.0 } + resolution: {integrity: sha512-M7NqKyhODKV1gRLdkwE7pDsZP2/SC2a2vHkOYh9MCpKMbWVfyVfUw5MaH83Fv6XMjxr5jryUp3IDDL9rlxsTeA==} + engines: {node: ^12.20.0 || ^14.18.0 || >=16.0.0} hasBin: true natural-compare@1.4.0: - resolution: - { integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw== } + resolution: {integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==} negotiator@1.0.0: - resolution: - { integrity: sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg== } - engines: { node: ">= 0.6" } + resolution: {integrity: sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg==} + engines: {node: '>= 0.6'} next-themes@0.4.6: - resolution: - { integrity: sha512-pZvgD5L0IEvX5/9GWyHMf3m8BKiVQwsCMHfoFosXtXBMnaS0ZnIJ9ST4b4NqLVKDEm8QBxoNNGNaBv2JNF6XNA== } + resolution: {integrity: sha512-pZvgD5L0IEvX5/9GWyHMf3m8BKiVQwsCMHfoFosXtXBMnaS0ZnIJ9ST4b4NqLVKDEm8QBxoNNGNaBv2JNF6XNA==} peerDependencies: react: ^16.8 || ^17 || ^18 || ^19 || ^19.0.0-rc react-dom: ^16.8 || ^17 || ^18 || ^19 || ^19.0.0-rc next@15.3.4: - resolution: - { integrity: sha512-mHKd50C+mCjam/gcnwqL1T1vPx/XQNFlXqFIVdgQdVAFY9iIQtY0IfaVflEYzKiqjeA7B0cYYMaCrmAYFjs4rA== } - engines: { node: ^18.18.0 || ^19.8.0 || >= 20.0.0 } + resolution: {integrity: sha512-mHKd50C+mCjam/gcnwqL1T1vPx/XQNFlXqFIVdgQdVAFY9iIQtY0IfaVflEYzKiqjeA7B0cYYMaCrmAYFjs4rA==} + engines: {node: ^18.18.0 || ^19.8.0 || >= 20.0.0} hasBin: true peerDependencies: - "@opentelemetry/api": ^1.1.0 - "@playwright/test": ^1.41.2 - babel-plugin-react-compiler: "*" + '@opentelemetry/api': ^1.1.0 + '@playwright/test': ^1.41.2 + babel-plugin-react-compiler: '*' react: ^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0 react-dom: ^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0 sass: ^1.3.0 peerDependenciesMeta: - "@opentelemetry/api": + '@opentelemetry/api': optional: true - "@playwright/test": + '@playwright/test': optional: true babel-plugin-react-compiler: optional: true @@ -3090,915 +2615,767 @@ packages: optional: true object-assign@4.1.1: - resolution: - { integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg== } - engines: { node: ">=0.10.0" } + resolution: {integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==} + engines: {node: '>=0.10.0'} object-inspect@1.13.4: - resolution: - { integrity: sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew== } - engines: { node: ">= 0.4" } + resolution: {integrity: sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==} + engines: {node: '>= 0.4'} object-keys@1.1.1: - resolution: - { integrity: sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA== } - engines: { node: ">= 0.4" } + resolution: {integrity: sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==} + engines: {node: '>= 0.4'} object.assign@4.1.7: - resolution: - { integrity: sha512-nK28WOo+QIjBkDduTINE4JkF/UJJKyf2EJxvJKfblDpyg0Q+pkOHNTL0Qwy6NP6FhE/EnzV73BxxqcJaXY9anw== } - engines: { node: ">= 0.4" } + resolution: {integrity: sha512-nK28WOo+QIjBkDduTINE4JkF/UJJKyf2EJxvJKfblDpyg0Q+pkOHNTL0Qwy6NP6FhE/EnzV73BxxqcJaXY9anw==} + engines: {node: '>= 0.4'} object.entries@1.1.9: - resolution: - { integrity: sha512-8u/hfXFRBD1O0hPUjioLhoWFHRmt6tKA4/vZPyckBr18l1KE9uHrFaFaUi8MDRTpi4uak2goyPTSNJLXX2k2Hw== } - engines: { node: ">= 0.4" } + resolution: {integrity: sha512-8u/hfXFRBD1O0hPUjioLhoWFHRmt6tKA4/vZPyckBr18l1KE9uHrFaFaUi8MDRTpi4uak2goyPTSNJLXX2k2Hw==} + engines: {node: '>= 0.4'} object.fromentries@2.0.8: - resolution: - { integrity: sha512-k6E21FzySsSK5a21KRADBd/NGneRegFO5pLHfdQLpRDETUNJueLXs3WCzyQ3tFRDYgbq3KHGXfTbi2bs8WQ6rQ== } - engines: { node: ">= 0.4" } + resolution: {integrity: sha512-k6E21FzySsSK5a21KRADBd/NGneRegFO5pLHfdQLpRDETUNJueLXs3WCzyQ3tFRDYgbq3KHGXfTbi2bs8WQ6rQ==} + engines: {node: '>= 0.4'} object.groupby@1.0.3: - resolution: - { integrity: sha512-+Lhy3TQTuzXI5hevh8sBGqbmurHbbIjAi0Z4S63nthVLmLxfbj4T54a4CfZrXIrt9iP4mVAPYMo/v99taj3wjQ== } - engines: { node: ">= 0.4" } + resolution: {integrity: sha512-+Lhy3TQTuzXI5hevh8sBGqbmurHbbIjAi0Z4S63nthVLmLxfbj4T54a4CfZrXIrt9iP4mVAPYMo/v99taj3wjQ==} + engines: {node: '>= 0.4'} object.values@1.2.1: - resolution: - { integrity: sha512-gXah6aZrcUxjWg2zR2MwouP2eHlCBzdV4pygudehaKXSGW4v2AsRQUK+lwwXhii6KFZcunEnmSUoYp5CXibxtA== } - engines: { node: ">= 0.4" } + resolution: {integrity: sha512-gXah6aZrcUxjWg2zR2MwouP2eHlCBzdV4pygudehaKXSGW4v2AsRQUK+lwwXhii6KFZcunEnmSUoYp5CXibxtA==} + engines: {node: '>= 0.4'} oniguruma-parser@0.12.1: - resolution: - { integrity: sha512-8Unqkvk1RYc6yq2WBYRj4hdnsAxVze8i7iPfQr8e4uSP3tRv0rpZcbGUDvxfQQcdwHt/e9PrMvGCsa8OqG9X3w== } + resolution: {integrity: sha512-8Unqkvk1RYc6yq2WBYRj4hdnsAxVze8i7iPfQr8e4uSP3tRv0rpZcbGUDvxfQQcdwHt/e9PrMvGCsa8OqG9X3w==} oniguruma-to-es@4.3.3: - resolution: - { integrity: sha512-rPiZhzC3wXwE59YQMRDodUwwT9FZ9nNBwQQfsd1wfdtlKEyCdRV0avrTcSZ5xlIvGRVPd/cx6ZN45ECmS39xvg== } + resolution: {integrity: sha512-rPiZhzC3wXwE59YQMRDodUwwT9FZ9nNBwQQfsd1wfdtlKEyCdRV0avrTcSZ5xlIvGRVPd/cx6ZN45ECmS39xvg==} optionator@0.9.4: - resolution: - { integrity: sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g== } - engines: { node: ">= 0.8.0" } + resolution: {integrity: sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==} + engines: {node: '>= 0.8.0'} own-keys@1.0.1: - resolution: - { integrity: sha512-qFOyK5PjiWZd+QQIh+1jhdb9LpxTF0qs7Pm8o5QHYZ0M3vKqSqzsZaEB6oWlxZ+q2sJBMI/Ktgd2N5ZwQoRHfg== } - engines: { node: ">= 0.4" } + resolution: {integrity: sha512-qFOyK5PjiWZd+QQIh+1jhdb9LpxTF0qs7Pm8o5QHYZ0M3vKqSqzsZaEB6oWlxZ+q2sJBMI/Ktgd2N5ZwQoRHfg==} + engines: {node: '>= 0.4'} p-limit@3.1.0: - resolution: - { integrity: sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ== } - engines: { node: ">=10" } + resolution: {integrity: sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==} + engines: {node: '>=10'} p-locate@5.0.0: - resolution: - { integrity: sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw== } - engines: { node: ">=10" } + resolution: {integrity: sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==} + engines: {node: '>=10'} parent-module@1.0.1: - resolution: - { integrity: sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g== } - engines: { node: ">=6" } + resolution: {integrity: sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==} + engines: {node: '>=6'} parse-entities@4.0.2: - resolution: - { integrity: sha512-GG2AQYWoLgL877gQIKeRPGO1xF9+eG1ujIb5soS5gPvLQ1y2o8FL90w2QWNdf9I361Mpp7726c+lj3U0qK1uGw== } + resolution: {integrity: sha512-GG2AQYWoLgL877gQIKeRPGO1xF9+eG1ujIb5soS5gPvLQ1y2o8FL90w2QWNdf9I361Mpp7726c+lj3U0qK1uGw==} path-exists@4.0.0: - resolution: - { integrity: sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w== } - engines: { node: ">=8" } + resolution: {integrity: sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==} + engines: {node: '>=8'} path-key@3.1.1: - resolution: - { integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q== } - engines: { node: ">=8" } + resolution: {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==} + engines: {node: '>=8'} path-parse@1.0.7: - resolution: - { integrity: sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw== } + resolution: {integrity: sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==} picocolors@1.1.1: - resolution: - { integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA== } + resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==} picomatch@2.3.1: - resolution: - { integrity: sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA== } - engines: { node: ">=8.6" } + resolution: {integrity: sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==} + engines: {node: '>=8.6'} picomatch@4.0.2: - resolution: - { integrity: sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg== } - engines: { node: ">=12" } + resolution: {integrity: sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==} + engines: {node: '>=12'} possible-typed-array-names@1.1.0: - resolution: - { integrity: sha512-/+5VFTchJDoVj3bhoqi6UeymcD00DAwb1nJwamzPvHEszJ4FpF6SNNbUbOS8yI56qHzdV8eK0qEfOSiodkTdxg== } - engines: { node: ">= 0.4" } + resolution: {integrity: sha512-/+5VFTchJDoVj3bhoqi6UeymcD00DAwb1nJwamzPvHEszJ4FpF6SNNbUbOS8yI56qHzdV8eK0qEfOSiodkTdxg==} + engines: {node: '>= 0.4'} postcss-selector-parser@7.1.0: - resolution: - { integrity: sha512-8sLjZwK0R+JlxlYcTuVnyT2v+htpdrjDOKuMcOVdYjt52Lh8hWRYpxBPoKx/Zg+bcjc3wx6fmQevMmUztS/ccA== } - engines: { node: ">=4" } + resolution: {integrity: sha512-8sLjZwK0R+JlxlYcTuVnyT2v+htpdrjDOKuMcOVdYjt52Lh8hWRYpxBPoKx/Zg+bcjc3wx6fmQevMmUztS/ccA==} + engines: {node: '>=4'} postcss@8.4.31: - resolution: - { integrity: sha512-PS08Iboia9mts/2ygV3eLpY5ghnUcfLV/EXTOW1E2qYxJKGGBUtNjN76FYHnMs36RmARn41bC0AZmn+rR0OVpQ== } - engines: { node: ^10 || ^12 || >=14 } + resolution: {integrity: sha512-PS08Iboia9mts/2ygV3eLpY5ghnUcfLV/EXTOW1E2qYxJKGGBUtNjN76FYHnMs36RmARn41bC0AZmn+rR0OVpQ==} + engines: {node: ^10 || ^12 || >=14} postcss@8.5.6: - resolution: - { integrity: sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg== } - engines: { node: ^10 || ^12 || >=14 } + resolution: {integrity: sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==} + engines: {node: ^10 || ^12 || >=14} prelude-ls@1.2.1: - resolution: - { integrity: sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g== } - engines: { node: ">= 0.8.0" } + resolution: {integrity: sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==} + engines: {node: '>= 0.8.0'} prettier-linter-helpers@1.0.0: - resolution: - { integrity: sha512-GbK2cP9nraSSUF9N2XwUwqfzlAFlMNYYl+ShE/V+H8a9uNl/oUqB1w2EL54Jh0OlyRSd8RfWYJ3coVS4TROP2w== } - engines: { node: ">=6.0.0" } + resolution: {integrity: sha512-GbK2cP9nraSSUF9N2XwUwqfzlAFlMNYYl+ShE/V+H8a9uNl/oUqB1w2EL54Jh0OlyRSd8RfWYJ3coVS4TROP2w==} + engines: {node: '>=6.0.0'} prettier-plugin-sort-json@4.1.1: - resolution: - { integrity: sha512-uJ49wCzwJ/foKKV4tIPxqi4jFFvwUzw4oACMRG2dcmDhBKrxBv0L2wSKkAqHCmxKCvj0xcCZS4jO2kSJO/tRJw== } - engines: { node: ">=18.0.0" } + resolution: {integrity: sha512-uJ49wCzwJ/foKKV4tIPxqi4jFFvwUzw4oACMRG2dcmDhBKrxBv0L2wSKkAqHCmxKCvj0xcCZS4jO2kSJO/tRJw==} + engines: {node: '>=18.0.0'} peerDependencies: prettier: ^3.0.0 prettier@3.6.2: - resolution: - { integrity: sha512-I7AIg5boAr5R0FFtJ6rCfD+LFsWHp81dolrFD8S79U9tb8Az2nGrJncnMSnys+bpQJfRUzqs9hnA81OAA3hCuQ== } - engines: { node: ">=14" } + resolution: {integrity: sha512-I7AIg5boAr5R0FFtJ6rCfD+LFsWHp81dolrFD8S79U9tb8Az2nGrJncnMSnys+bpQJfRUzqs9hnA81OAA3hCuQ==} + engines: {node: '>=14'} hasBin: true prop-types@15.8.1: - resolution: - { integrity: sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg== } + resolution: {integrity: sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==} property-information@7.1.0: - resolution: - { integrity: sha512-TwEZ+X+yCJmYfL7TPUOcvBZ4QfoT5YenQiJuX//0th53DE6w0xxLEtfK3iyryQFddXuvkIk51EEgrJQ0WJkOmQ== } + resolution: {integrity: sha512-TwEZ+X+yCJmYfL7TPUOcvBZ4QfoT5YenQiJuX//0th53DE6w0xxLEtfK3iyryQFddXuvkIk51EEgrJQ0WJkOmQ==} punycode@2.3.1: - resolution: - { integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg== } - engines: { node: ">=6" } + resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==} + engines: {node: '>=6'} queue-microtask@1.2.3: - resolution: - { integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A== } + resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==} react-dom@19.1.0: - resolution: - { integrity: sha512-Xs1hdnE+DyKgeHJeJznQmYMIBG3TKIHJJT95Q58nHLSrElKlGQqDTR2HQ9fx5CN/Gk6Vh/kupBTDLU11/nDk/g== } + resolution: {integrity: sha512-Xs1hdnE+DyKgeHJeJznQmYMIBG3TKIHJJT95Q58nHLSrElKlGQqDTR2HQ9fx5CN/Gk6Vh/kupBTDLU11/nDk/g==} peerDependencies: react: ^19.1.0 react-is@16.13.1: - resolution: - { integrity: sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ== } + resolution: {integrity: sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==} react-medium-image-zoom@5.2.14: - resolution: - { integrity: sha512-nfTVYcAUnBzXQpPDcZL+cG/e6UceYUIG+zDcnemL7jtAqbJjVVkA85RgneGtJeni12dTyiRPZVM6Szkmwd/o8w== } + resolution: {integrity: sha512-nfTVYcAUnBzXQpPDcZL+cG/e6UceYUIG+zDcnemL7jtAqbJjVVkA85RgneGtJeni12dTyiRPZVM6Szkmwd/o8w==} peerDependencies: react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 react-remove-scroll-bar@2.3.8: - resolution: - { integrity: sha512-9r+yi9+mgU33AKcj6IbT9oRCO78WriSj6t/cF8DWBZJ9aOGPOTEDvdUDz1FwKim7QXWwmHqtdHnRJfhAxEG46Q== } - engines: { node: ">=10" } + resolution: {integrity: sha512-9r+yi9+mgU33AKcj6IbT9oRCO78WriSj6t/cF8DWBZJ9aOGPOTEDvdUDz1FwKim7QXWwmHqtdHnRJfhAxEG46Q==} + engines: {node: '>=10'} peerDependencies: - "@types/react": "*" + '@types/react': '*' react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 peerDependenciesMeta: - "@types/react": + '@types/react': optional: true react-remove-scroll@2.7.1: - resolution: - { integrity: sha512-HpMh8+oahmIdOuS5aFKKY6Pyog+FNaZV/XyJOq7b4YFwsFHe5yYfdbIalI4k3vU2nSDql7YskmUseHsRrJqIPA== } - engines: { node: ">=10" } + resolution: {integrity: sha512-HpMh8+oahmIdOuS5aFKKY6Pyog+FNaZV/XyJOq7b4YFwsFHe5yYfdbIalI4k3vU2nSDql7YskmUseHsRrJqIPA==} + engines: {node: '>=10'} peerDependencies: - "@types/react": "*" + '@types/react': '*' react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc peerDependenciesMeta: - "@types/react": + '@types/react': optional: true react-style-singleton@2.2.3: - resolution: - { integrity: sha512-b6jSvxvVnyptAiLjbkWLE/lOnR4lfTtDAl+eUC7RZy+QQWc6wRzIV2CE6xBuMmDxc2qIihtDCZD5NPOFl7fRBQ== } - engines: { node: ">=10" } + resolution: {integrity: sha512-b6jSvxvVnyptAiLjbkWLE/lOnR4lfTtDAl+eUC7RZy+QQWc6wRzIV2CE6xBuMmDxc2qIihtDCZD5NPOFl7fRBQ==} + engines: {node: '>=10'} peerDependencies: - "@types/react": "*" + '@types/react': '*' react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc peerDependenciesMeta: - "@types/react": + '@types/react': optional: true react@19.1.0: - resolution: - { integrity: sha512-FS+XFBNvn3GTAWq26joslQgWNoFu08F4kl0J4CgdNKADkdSGXQyTCnKteIAJy96Br6YbpEU1LSzV5dYtjMkMDg== } - engines: { node: ">=0.10.0" } + resolution: {integrity: sha512-FS+XFBNvn3GTAWq26joslQgWNoFu08F4kl0J4CgdNKADkdSGXQyTCnKteIAJy96Br6YbpEU1LSzV5dYtjMkMDg==} + engines: {node: '>=0.10.0'} readdirp@4.1.2: - resolution: - { integrity: sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg== } - engines: { node: ">= 14.18.0" } + resolution: {integrity: sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==} + engines: {node: '>= 14.18.0'} recma-build-jsx@1.0.0: - resolution: - { integrity: sha512-8GtdyqaBcDfva+GUKDr3nev3VpKAhup1+RvkMvUxURHpW7QyIvk9F5wz7Vzo06CEMSilw6uArgRqhpiUcWp8ew== } + resolution: {integrity: sha512-8GtdyqaBcDfva+GUKDr3nev3VpKAhup1+RvkMvUxURHpW7QyIvk9F5wz7Vzo06CEMSilw6uArgRqhpiUcWp8ew==} recma-jsx@1.0.0: - resolution: - { integrity: sha512-5vwkv65qWwYxg+Atz95acp8DMu1JDSqdGkA2Of1j6rCreyFUE/gp15fC8MnGEuG1W68UKjM6x6+YTWIh7hZM/Q== } + resolution: {integrity: sha512-5vwkv65qWwYxg+Atz95acp8DMu1JDSqdGkA2Of1j6rCreyFUE/gp15fC8MnGEuG1W68UKjM6x6+YTWIh7hZM/Q==} recma-parse@1.0.0: - resolution: - { integrity: sha512-OYLsIGBB5Y5wjnSnQW6t3Xg7q3fQ7FWbw/vcXtORTnyaSFscOtABg+7Pnz6YZ6c27fG1/aN8CjfwoUEUIdwqWQ== } + resolution: {integrity: sha512-OYLsIGBB5Y5wjnSnQW6t3Xg7q3fQ7FWbw/vcXtORTnyaSFscOtABg+7Pnz6YZ6c27fG1/aN8CjfwoUEUIdwqWQ==} recma-stringify@1.0.0: - resolution: - { integrity: sha512-cjwII1MdIIVloKvC9ErQ+OgAtwHBmcZ0Bg4ciz78FtbT8In39aAYbaA7zvxQ61xVMSPE8WxhLwLbhif4Js2C+g== } + resolution: {integrity: sha512-cjwII1MdIIVloKvC9ErQ+OgAtwHBmcZ0Bg4ciz78FtbT8In39aAYbaA7zvxQ61xVMSPE8WxhLwLbhif4Js2C+g==} reflect.getprototypeof@1.0.10: - resolution: - { integrity: sha512-00o4I+DVrefhv+nX0ulyi3biSHCPDe+yLv5o/p6d/UVlirijB8E16FtfwSAi4g3tcqrQ4lRAqQSoFEZJehYEcw== } - engines: { node: ">= 0.4" } + resolution: {integrity: sha512-00o4I+DVrefhv+nX0ulyi3biSHCPDe+yLv5o/p6d/UVlirijB8E16FtfwSAi4g3tcqrQ4lRAqQSoFEZJehYEcw==} + engines: {node: '>= 0.4'} regex-recursion@6.0.2: - resolution: - { integrity: sha512-0YCaSCq2VRIebiaUviZNs0cBz1kg5kVS2UKUfNIx8YVs1cN3AV7NTctO5FOKBA+UT2BPJIWZauYHPqJODG50cg== } + resolution: {integrity: sha512-0YCaSCq2VRIebiaUviZNs0cBz1kg5kVS2UKUfNIx8YVs1cN3AV7NTctO5FOKBA+UT2BPJIWZauYHPqJODG50cg==} regex-utilities@2.3.0: - resolution: - { integrity: sha512-8VhliFJAWRaUiVvREIiW2NXXTmHs4vMNnSzuJVhscgmGav3g9VDxLrQndI3dZZVVdp0ZO/5v0xmX516/7M9cng== } + resolution: {integrity: sha512-8VhliFJAWRaUiVvREIiW2NXXTmHs4vMNnSzuJVhscgmGav3g9VDxLrQndI3dZZVVdp0ZO/5v0xmX516/7M9cng==} regex@6.0.1: - resolution: - { integrity: sha512-uorlqlzAKjKQZ5P+kTJr3eeJGSVroLKoHmquUj4zHWuR+hEyNqlXsSKlYYF5F4NI6nl7tWCs0apKJ0lmfsXAPA== } + resolution: {integrity: sha512-uorlqlzAKjKQZ5P+kTJr3eeJGSVroLKoHmquUj4zHWuR+hEyNqlXsSKlYYF5F4NI6nl7tWCs0apKJ0lmfsXAPA==} regexp.prototype.flags@1.5.4: - resolution: - { integrity: sha512-dYqgNSZbDwkaJ2ceRd9ojCGjBq+mOm9LmtXnAnEGyHhN/5R7iDW2TRw3h+o/jCFxus3P2LfWIIiwowAjANm7IA== } - engines: { node: ">= 0.4" } + resolution: {integrity: sha512-dYqgNSZbDwkaJ2ceRd9ojCGjBq+mOm9LmtXnAnEGyHhN/5R7iDW2TRw3h+o/jCFxus3P2LfWIIiwowAjANm7IA==} + engines: {node: '>= 0.4'} rehype-recma@1.0.0: - resolution: - { integrity: sha512-lqA4rGUf1JmacCNWWZx0Wv1dHqMwxzsDWYMTowuplHF3xH0N/MmrZ/G3BDZnzAkRmxDadujCjaKM2hqYdCBOGw== } + resolution: {integrity: sha512-lqA4rGUf1JmacCNWWZx0Wv1dHqMwxzsDWYMTowuplHF3xH0N/MmrZ/G3BDZnzAkRmxDadujCjaKM2hqYdCBOGw==} remark-gfm@4.0.1: - resolution: - { integrity: sha512-1quofZ2RQ9EWdeN34S79+KExV1764+wCUGop5CPL1WGdD0ocPpu91lzPGbwWMECpEpd42kJGQwzRfyov9j4yNg== } + resolution: {integrity: sha512-1quofZ2RQ9EWdeN34S79+KExV1764+wCUGop5CPL1WGdD0ocPpu91lzPGbwWMECpEpd42kJGQwzRfyov9j4yNg==} remark-mdx@3.1.0: - resolution: - { integrity: sha512-Ngl/H3YXyBV9RcRNdlYsZujAmhsxwzxpDzpDEhFBVAGthS4GDgnctpDjgFl/ULx5UEDzqtW1cyBSNKqYYrqLBA== } + resolution: {integrity: sha512-Ngl/H3YXyBV9RcRNdlYsZujAmhsxwzxpDzpDEhFBVAGthS4GDgnctpDjgFl/ULx5UEDzqtW1cyBSNKqYYrqLBA==} remark-parse@11.0.0: - resolution: - { integrity: sha512-FCxlKLNGknS5ba/1lmpYijMUzX2esxW5xQqjWxw2eHFfS2MSdaHVINFmhjo+qN1WhZhNimq0dZATN9pH0IDrpA== } + resolution: {integrity: sha512-FCxlKLNGknS5ba/1lmpYijMUzX2esxW5xQqjWxw2eHFfS2MSdaHVINFmhjo+qN1WhZhNimq0dZATN9pH0IDrpA==} remark-rehype@11.1.2: - resolution: - { integrity: sha512-Dh7l57ianaEoIpzbp0PC9UKAdCSVklD8E5Rpw7ETfbTl3FqcOOgq5q2LVDhgGCkaBv7p24JXikPdvhhmHvKMsw== } + resolution: {integrity: sha512-Dh7l57ianaEoIpzbp0PC9UKAdCSVklD8E5Rpw7ETfbTl3FqcOOgq5q2LVDhgGCkaBv7p24JXikPdvhhmHvKMsw==} remark-stringify@11.0.0: - resolution: - { integrity: sha512-1OSmLd3awB/t8qdoEOMazZkNsfVTeY4fTsgzcQFdXNq8ToTN4ZGwrMnlda4K6smTFKD+GRV6O48i6Z4iKgPPpw== } + resolution: {integrity: sha512-1OSmLd3awB/t8qdoEOMazZkNsfVTeY4fTsgzcQFdXNq8ToTN4ZGwrMnlda4K6smTFKD+GRV6O48i6Z4iKgPPpw==} remark@15.0.1: - resolution: - { integrity: sha512-Eht5w30ruCXgFmxVUSlNWQ9iiimq07URKeFS3hNc8cUWy1llX4KDWfyEDZRycMc+znsN9Ux5/tJ/BFdgdOwA3A== } + resolution: {integrity: sha512-Eht5w30ruCXgFmxVUSlNWQ9iiimq07URKeFS3hNc8cUWy1llX4KDWfyEDZRycMc+znsN9Ux5/tJ/BFdgdOwA3A==} resolve-from@4.0.0: - resolution: - { integrity: sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g== } - engines: { node: ">=4" } + resolution: {integrity: sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==} + engines: {node: '>=4'} resolve-pkg-maps@1.0.0: - resolution: - { integrity: sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw== } + resolution: {integrity: sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==} resolve@1.22.10: - resolution: - { integrity: sha512-NPRy+/ncIMeDlTAsuqwKIiferiawhefFJtkNSW0qZJEqMEb+qBt/77B/jGeeek+F0uOeN05CDa6HXbbIgtVX4w== } - engines: { node: ">= 0.4" } + resolution: {integrity: sha512-NPRy+/ncIMeDlTAsuqwKIiferiawhefFJtkNSW0qZJEqMEb+qBt/77B/jGeeek+F0uOeN05CDa6HXbbIgtVX4w==} + engines: {node: '>= 0.4'} hasBin: true resolve@2.0.0-next.5: - resolution: - { integrity: sha512-U7WjGVG9sH8tvjW5SmGbQuui75FiyjAX72HX15DwBBwF9dNiQZRQAg9nnPhYy+TUnE0+VcrttuvNI8oSxZcocA== } + resolution: {integrity: sha512-U7WjGVG9sH8tvjW5SmGbQuui75FiyjAX72HX15DwBBwF9dNiQZRQAg9nnPhYy+TUnE0+VcrttuvNI8oSxZcocA==} hasBin: true reusify@1.1.0: - resolution: - { integrity: sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw== } - engines: { iojs: ">=1.0.0", node: ">=0.10.0" } + resolution: {integrity: sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==} + engines: {iojs: '>=1.0.0', node: '>=0.10.0'} run-parallel@1.2.0: - resolution: - { integrity: sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA== } + resolution: {integrity: sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==} safe-array-concat@1.1.3: - resolution: - { integrity: sha512-AURm5f0jYEOydBj7VQlVvDrjeFgthDdEF5H1dP+6mNpoXOMo1quQqJ4wvJDyRZ9+pO3kGWoOdmV08cSv2aJV6Q== } - engines: { node: ">=0.4" } + resolution: {integrity: sha512-AURm5f0jYEOydBj7VQlVvDrjeFgthDdEF5H1dP+6mNpoXOMo1quQqJ4wvJDyRZ9+pO3kGWoOdmV08cSv2aJV6Q==} + engines: {node: '>=0.4'} safe-push-apply@1.0.0: - resolution: - { integrity: sha512-iKE9w/Z7xCzUMIZqdBsp6pEQvwuEebH4vdpjcDWnyzaI6yl6O9FHvVpmGelvEHNsoY6wGblkxR6Zty/h00WiSA== } - engines: { node: ">= 0.4" } + resolution: {integrity: sha512-iKE9w/Z7xCzUMIZqdBsp6pEQvwuEebH4vdpjcDWnyzaI6yl6O9FHvVpmGelvEHNsoY6wGblkxR6Zty/h00WiSA==} + engines: {node: '>= 0.4'} safe-regex-test@1.1.0: - resolution: - { integrity: sha512-x/+Cz4YrimQxQccJf5mKEbIa1NzeCRNI5Ecl/ekmlYaampdNLPalVyIcCZNNH3MvmqBugV5TMYZXv0ljslUlaw== } - engines: { node: ">= 0.4" } + resolution: {integrity: sha512-x/+Cz4YrimQxQccJf5mKEbIa1NzeCRNI5Ecl/ekmlYaampdNLPalVyIcCZNNH3MvmqBugV5TMYZXv0ljslUlaw==} + engines: {node: '>= 0.4'} scheduler@0.26.0: - resolution: - { integrity: sha512-NlHwttCI/l5gCPR3D1nNXtWABUmBwvZpEQiD4IXSbIDq8BzLIK/7Ir5gTFSGZDUu37K5cMNp0hFtzO38sC7gWA== } + resolution: {integrity: sha512-NlHwttCI/l5gCPR3D1nNXtWABUmBwvZpEQiD4IXSbIDq8BzLIK/7Ir5gTFSGZDUu37K5cMNp0hFtzO38sC7gWA==} scroll-into-view-if-needed@3.1.0: - resolution: - { integrity: sha512-49oNpRjWRvnU8NyGVmUaYG4jtTkNonFZI86MmGRDqBphEK2EXT9gdEUoQPZhuBM8yWHxCWbobltqYO5M4XrUvQ== } + resolution: {integrity: sha512-49oNpRjWRvnU8NyGVmUaYG4jtTkNonFZI86MmGRDqBphEK2EXT9gdEUoQPZhuBM8yWHxCWbobltqYO5M4XrUvQ==} semver@6.3.1: - resolution: - { integrity: sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA== } + resolution: {integrity: sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==} hasBin: true semver@7.7.2: - resolution: - { integrity: sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA== } - engines: { node: ">=10" } + resolution: {integrity: sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==} + engines: {node: '>=10'} hasBin: true set-function-length@1.2.2: - resolution: - { integrity: sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg== } - engines: { node: ">= 0.4" } + resolution: {integrity: sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==} + engines: {node: '>= 0.4'} set-function-name@2.0.2: - resolution: - { integrity: sha512-7PGFlmtwsEADb0WYyvCMa1t+yke6daIG4Wirafur5kcf+MhUnPms1UeR0CKQdTZD81yESwMHbtn+TR+dMviakQ== } - engines: { node: ">= 0.4" } + resolution: {integrity: sha512-7PGFlmtwsEADb0WYyvCMa1t+yke6daIG4Wirafur5kcf+MhUnPms1UeR0CKQdTZD81yESwMHbtn+TR+dMviakQ==} + engines: {node: '>= 0.4'} set-proto@1.0.0: - resolution: - { integrity: sha512-RJRdvCo6IAnPdsvP/7m6bsQqNnn1FCBX5ZNtFL98MmFF/4xAIJTIg1YbHW5DC2W5SKZanrC6i4HsJqlajw/dZw== } - engines: { node: ">= 0.4" } + resolution: {integrity: sha512-RJRdvCo6IAnPdsvP/7m6bsQqNnn1FCBX5ZNtFL98MmFF/4xAIJTIg1YbHW5DC2W5SKZanrC6i4HsJqlajw/dZw==} + engines: {node: '>= 0.4'} sharp@0.34.2: - resolution: - { integrity: sha512-lszvBmB9QURERtyKT2bNmsgxXK0ShJrL/fvqlonCo7e6xBF8nT8xU6pW+PMIbLsz0RxQk3rgH9kd8UmvOzlMJg== } - engines: { node: ^18.17.0 || ^20.3.0 || >=21.0.0 } + resolution: {integrity: sha512-lszvBmB9QURERtyKT2bNmsgxXK0ShJrL/fvqlonCo7e6xBF8nT8xU6pW+PMIbLsz0RxQk3rgH9kd8UmvOzlMJg==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} shebang-command@2.0.0: - resolution: - { integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA== } - engines: { node: ">=8" } + resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==} + engines: {node: '>=8'} shebang-regex@3.0.0: - resolution: - { integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A== } - engines: { node: ">=8" } + resolution: {integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==} + engines: {node: '>=8'} shiki@3.7.0: - resolution: - { integrity: sha512-ZcI4UT9n6N2pDuM2n3Jbk0sR4Swzq43nLPgS/4h0E3B/NrFn2HKElrDtceSf8Zx/OWYOo7G1SAtBLypCp+YXqg== } + resolution: {integrity: sha512-ZcI4UT9n6N2pDuM2n3Jbk0sR4Swzq43nLPgS/4h0E3B/NrFn2HKElrDtceSf8Zx/OWYOo7G1SAtBLypCp+YXqg==} side-channel-list@1.0.0: - resolution: - { integrity: sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA== } - engines: { node: ">= 0.4" } + resolution: {integrity: sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==} + engines: {node: '>= 0.4'} side-channel-map@1.0.1: - resolution: - { integrity: sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA== } - engines: { node: ">= 0.4" } + resolution: {integrity: sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==} + engines: {node: '>= 0.4'} side-channel-weakmap@1.0.2: - resolution: - { integrity: sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A== } - engines: { node: ">= 0.4" } + resolution: {integrity: sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==} + engines: {node: '>= 0.4'} side-channel@1.1.0: - resolution: - { integrity: sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw== } - engines: { node: ">= 0.4" } + resolution: {integrity: sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==} + engines: {node: '>= 0.4'} simple-swizzle@0.2.2: - resolution: - { integrity: sha512-JA//kQgZtbuY83m+xT+tXJkmJncGMTFT+C+g2h2R9uxkYIrE2yy9sgmcLhCnw57/WSD+Eh3J97FPEDFnbXnDUg== } + resolution: {integrity: sha512-JA//kQgZtbuY83m+xT+tXJkmJncGMTFT+C+g2h2R9uxkYIrE2yy9sgmcLhCnw57/WSD+Eh3J97FPEDFnbXnDUg==} source-map-js@1.2.1: - resolution: - { integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA== } - engines: { node: ">=0.10.0" } + resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==} + engines: {node: '>=0.10.0'} source-map@0.7.4: - resolution: - { integrity: sha512-l3BikUxvPOcn5E74dZiq5BGsTb5yEwhaTSzccU6t4sDOH8NWJCstKO5QT2CvtFoK6F0saL7p9xHAqHOlCPJygA== } - engines: { node: ">= 8" } + resolution: {integrity: sha512-l3BikUxvPOcn5E74dZiq5BGsTb5yEwhaTSzccU6t4sDOH8NWJCstKO5QT2CvtFoK6F0saL7p9xHAqHOlCPJygA==} + engines: {node: '>= 8'} space-separated-tokens@2.0.2: - resolution: - { integrity: sha512-PEGlAwrG8yXGXRjW32fGbg66JAlOAwbObuqVoJpv/mRgoWDQfgH1wDPvtzWyUSNAXBGSk8h755YDbbcEy3SH2Q== } + resolution: {integrity: sha512-PEGlAwrG8yXGXRjW32fGbg66JAlOAwbObuqVoJpv/mRgoWDQfgH1wDPvtzWyUSNAXBGSk8h755YDbbcEy3SH2Q==} stable-hash@0.0.5: - resolution: - { integrity: sha512-+L3ccpzibovGXFK+Ap/f8LOS0ahMrHTf3xu7mMLSpEGU0EO9ucaysSylKo9eRDFNhWve/y275iPmIZ4z39a9iA== } + resolution: {integrity: sha512-+L3ccpzibovGXFK+Ap/f8LOS0ahMrHTf3xu7mMLSpEGU0EO9ucaysSylKo9eRDFNhWve/y275iPmIZ4z39a9iA==} stop-iteration-iterator@1.1.0: - resolution: - { integrity: sha512-eLoXW/DHyl62zxY4SCaIgnRhuMr6ri4juEYARS8E6sCEqzKpOiE521Ucofdx+KnDZl5xmvGYaaKCk5FEOxJCoQ== } - engines: { node: ">= 0.4" } + resolution: {integrity: sha512-eLoXW/DHyl62zxY4SCaIgnRhuMr6ri4juEYARS8E6sCEqzKpOiE521Ucofdx+KnDZl5xmvGYaaKCk5FEOxJCoQ==} + engines: {node: '>= 0.4'} streamsearch@1.1.0: - resolution: - { integrity: sha512-Mcc5wHehp9aXz1ax6bZUyY5afg9u2rv5cqQI3mRrYkGC8rW2hM02jWuwjtL++LS5qinSyhj2QfLyNsuc+VsExg== } - engines: { node: ">=10.0.0" } + resolution: {integrity: sha512-Mcc5wHehp9aXz1ax6bZUyY5afg9u2rv5cqQI3mRrYkGC8rW2hM02jWuwjtL++LS5qinSyhj2QfLyNsuc+VsExg==} + engines: {node: '>=10.0.0'} string.prototype.includes@2.0.1: - resolution: - { integrity: sha512-o7+c9bW6zpAdJHTtujeePODAhkuicdAryFsfVKwA+wGw89wJ4GTY484WTucM9hLtDEOpOvI+aHnzqnC5lHp4Rg== } - engines: { node: ">= 0.4" } + resolution: {integrity: sha512-o7+c9bW6zpAdJHTtujeePODAhkuicdAryFsfVKwA+wGw89wJ4GTY484WTucM9hLtDEOpOvI+aHnzqnC5lHp4Rg==} + engines: {node: '>= 0.4'} string.prototype.matchall@4.0.12: - resolution: - { integrity: sha512-6CC9uyBL+/48dYizRf7H7VAYCMCNTBeM78x/VTUe9bFEaxBepPJDa1Ow99LqI/1yF7kuy7Q3cQsYMrcjGUcskA== } - engines: { node: ">= 0.4" } + resolution: {integrity: sha512-6CC9uyBL+/48dYizRf7H7VAYCMCNTBeM78x/VTUe9bFEaxBepPJDa1Ow99LqI/1yF7kuy7Q3cQsYMrcjGUcskA==} + engines: {node: '>= 0.4'} string.prototype.repeat@1.0.0: - resolution: - { integrity: sha512-0u/TldDbKD8bFCQ/4f5+mNRrXwZ8hg2w7ZR8wa16e8z9XpePWl3eGEcUD0OXpEH/VJH/2G3gjUtR3ZOiBe2S/w== } + resolution: {integrity: sha512-0u/TldDbKD8bFCQ/4f5+mNRrXwZ8hg2w7ZR8wa16e8z9XpePWl3eGEcUD0OXpEH/VJH/2G3gjUtR3ZOiBe2S/w==} string.prototype.trim@1.2.10: - resolution: - { integrity: sha512-Rs66F0P/1kedk5lyYyH9uBzuiI/kNRmwJAR9quK6VOtIpZ2G+hMZd+HQbbv25MgCA6gEffoMZYxlTod4WcdrKA== } - engines: { node: ">= 0.4" } + resolution: {integrity: sha512-Rs66F0P/1kedk5lyYyH9uBzuiI/kNRmwJAR9quK6VOtIpZ2G+hMZd+HQbbv25MgCA6gEffoMZYxlTod4WcdrKA==} + engines: {node: '>= 0.4'} string.prototype.trimend@1.0.9: - resolution: - { integrity: sha512-G7Ok5C6E/j4SGfyLCloXTrngQIQU3PWtXGst3yM7Bea9FRURf1S42ZHlZZtsNque2FN2PoUhfZXYLNWwEr4dLQ== } - engines: { node: ">= 0.4" } + resolution: {integrity: sha512-G7Ok5C6E/j4SGfyLCloXTrngQIQU3PWtXGst3yM7Bea9FRURf1S42ZHlZZtsNque2FN2PoUhfZXYLNWwEr4dLQ==} + engines: {node: '>= 0.4'} string.prototype.trimstart@1.0.8: - resolution: - { integrity: sha512-UXSH262CSZY1tfu3G3Secr6uGLCFVPMhIqHjlgCUtCCcgihYc/xKs9djMTMUOb2j1mVSeU8EU6NWc/iQKU6Gfg== } - engines: { node: ">= 0.4" } + resolution: {integrity: sha512-UXSH262CSZY1tfu3G3Secr6uGLCFVPMhIqHjlgCUtCCcgihYc/xKs9djMTMUOb2j1mVSeU8EU6NWc/iQKU6Gfg==} + engines: {node: '>= 0.4'} stringify-entities@4.0.4: - resolution: - { integrity: sha512-IwfBptatlO+QCJUo19AqvrPNqlVMpW9YEL2LIVY+Rpv2qsjCGxaDLNRgeGsQWJhfItebuJhsGSLjaBbNSQ+ieg== } + resolution: {integrity: sha512-IwfBptatlO+QCJUo19AqvrPNqlVMpW9YEL2LIVY+Rpv2qsjCGxaDLNRgeGsQWJhfItebuJhsGSLjaBbNSQ+ieg==} strip-bom@3.0.0: - resolution: - { integrity: sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA== } - engines: { node: ">=4" } + resolution: {integrity: sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==} + engines: {node: '>=4'} strip-json-comments@3.1.1: - resolution: - { integrity: sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig== } - engines: { node: ">=8" } + resolution: {integrity: sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==} + engines: {node: '>=8'} style-to-js@1.1.17: - resolution: - { integrity: sha512-xQcBGDxJb6jjFCTzvQtfiPn6YvvP2O8U1MDIPNfJQlWMYfktPy+iGsHE7cssjs7y84d9fQaK4UF3RIJaAHSoYA== } + resolution: {integrity: sha512-xQcBGDxJb6jjFCTzvQtfiPn6YvvP2O8U1MDIPNfJQlWMYfktPy+iGsHE7cssjs7y84d9fQaK4UF3RIJaAHSoYA==} style-to-object@1.0.9: - resolution: - { integrity: sha512-G4qppLgKu/k6FwRpHiGiKPaPTFcG3g4wNVX/Qsfu+RqQM30E7Tyu/TEgxcL9PNLF5pdRLwQdE3YKKf+KF2Dzlw== } + resolution: {integrity: sha512-G4qppLgKu/k6FwRpHiGiKPaPTFcG3g4wNVX/Qsfu+RqQM30E7Tyu/TEgxcL9PNLF5pdRLwQdE3YKKf+KF2Dzlw==} styled-jsx@5.1.6: - resolution: - { integrity: sha512-qSVyDTeMotdvQYoHWLNGwRFJHC+i+ZvdBRYosOFgC+Wg1vx4frN2/RG/NA7SYqqvKNLf39P2LSRA2pu6n0XYZA== } - engines: { node: ">= 12.0.0" } + resolution: {integrity: sha512-qSVyDTeMotdvQYoHWLNGwRFJHC+i+ZvdBRYosOFgC+Wg1vx4frN2/RG/NA7SYqqvKNLf39P2LSRA2pu6n0XYZA==} + engines: {node: '>= 12.0.0'} peerDependencies: - "@babel/core": "*" - babel-plugin-macros: "*" - react: ">= 16.8.0 || 17.x.x || ^18.0.0-0 || ^19.0.0-0" + '@babel/core': '*' + babel-plugin-macros: '*' + react: '>= 16.8.0 || 17.x.x || ^18.0.0-0 || ^19.0.0-0' peerDependenciesMeta: - "@babel/core": + '@babel/core': optional: true babel-plugin-macros: optional: true supports-color@7.2.0: - resolution: - { integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw== } - engines: { node: ">=8" } + resolution: {integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==} + engines: {node: '>=8'} supports-preserve-symlinks-flag@1.0.0: - resolution: - { integrity: sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w== } - engines: { node: ">= 0.4" } + resolution: {integrity: sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==} + engines: {node: '>= 0.4'} synckit@0.11.8: - resolution: - { integrity: sha512-+XZ+r1XGIJGeQk3VvXhT6xx/VpbHsRzsTkGgF6E5RX9TTXD0118l87puaEBZ566FhqblC6U0d4XnubznJDm30A== } - engines: { node: ^14.18.0 || >=16.0.0 } + resolution: {integrity: sha512-+XZ+r1XGIJGeQk3VvXhT6xx/VpbHsRzsTkGgF6E5RX9TTXD0118l87puaEBZ566FhqblC6U0d4XnubznJDm30A==} + engines: {node: ^14.18.0 || >=16.0.0} tailwind-merge@3.3.1: - resolution: - { integrity: sha512-gBXpgUm/3rp1lMZZrM/w7D8GKqshif0zAymAhbCyIt8KMe+0v9DQ7cdYLR4FHH/cKpdTXb+A/tKKU3eolfsI+g== } + resolution: {integrity: sha512-gBXpgUm/3rp1lMZZrM/w7D8GKqshif0zAymAhbCyIt8KMe+0v9DQ7cdYLR4FHH/cKpdTXb+A/tKKU3eolfsI+g==} tailwindcss@4.1.11: - resolution: - { integrity: sha512-2E9TBm6MDD/xKYe+dvJZAmg3yxIEDNRc0jwlNyDg/4Fil2QcSLjFKGVff0lAf1jjeaArlG/M75Ey/EYr/OJtBA== } + resolution: {integrity: sha512-2E9TBm6MDD/xKYe+dvJZAmg3yxIEDNRc0jwlNyDg/4Fil2QcSLjFKGVff0lAf1jjeaArlG/M75Ey/EYr/OJtBA==} tapable@2.2.2: - resolution: - { integrity: sha512-Re10+NauLTMCudc7T5WLFLAwDhQ0JWdrMK+9B2M8zR5hRExKmsRDCBA7/aV/pNJFltmBFO5BAMlQFi/vq3nKOg== } - engines: { node: ">=6" } + resolution: {integrity: sha512-Re10+NauLTMCudc7T5WLFLAwDhQ0JWdrMK+9B2M8zR5hRExKmsRDCBA7/aV/pNJFltmBFO5BAMlQFi/vq3nKOg==} + engines: {node: '>=6'} tar@7.4.3: - resolution: - { integrity: sha512-5S7Va8hKfV7W5U6g3aYxXmlPoZVAwUMy9AOKyF2fVuZa2UD3qZjg578OrLRt8PcNN1PleVaL/5/yYATNL0ICUw== } - engines: { node: ">=18" } + resolution: {integrity: sha512-5S7Va8hKfV7W5U6g3aYxXmlPoZVAwUMy9AOKyF2fVuZa2UD3qZjg578OrLRt8PcNN1PleVaL/5/yYATNL0ICUw==} + engines: {node: '>=18'} tinyexec@1.0.1: - resolution: - { integrity: sha512-5uC6DDlmeqiOwCPmK9jMSdOuZTh8bU39Ys6yidB+UTt5hfZUPGAypSgFRiEp+jbi9qH40BLDvy85jIU88wKSqw== } + resolution: {integrity: sha512-5uC6DDlmeqiOwCPmK9jMSdOuZTh8bU39Ys6yidB+UTt5hfZUPGAypSgFRiEp+jbi9qH40BLDvy85jIU88wKSqw==} tinyglobby@0.2.14: - resolution: - { integrity: sha512-tX5e7OM1HnYr2+a2C/4V0htOcSQcoSTH9KgJnVvNm5zm/cyEWKJ7j7YutsH9CxMdtOkkLFy2AHrMci9IM8IPZQ== } - engines: { node: ">=12.0.0" } + resolution: {integrity: sha512-tX5e7OM1HnYr2+a2C/4V0htOcSQcoSTH9KgJnVvNm5zm/cyEWKJ7j7YutsH9CxMdtOkkLFy2AHrMci9IM8IPZQ==} + engines: {node: '>=12.0.0'} to-regex-range@5.0.1: - resolution: - { integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ== } - engines: { node: ">=8.0" } + resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==} + engines: {node: '>=8.0'} trim-lines@3.0.1: - resolution: - { integrity: sha512-kRj8B+YHZCc9kQYdWfJB2/oUl9rA99qbowYYBtr4ui4mZyAQ2JpvVBd/6U2YloATfqBhBTSMhTpgBHtU0Mf3Rg== } + resolution: {integrity: sha512-kRj8B+YHZCc9kQYdWfJB2/oUl9rA99qbowYYBtr4ui4mZyAQ2JpvVBd/6U2YloATfqBhBTSMhTpgBHtU0Mf3Rg==} trough@2.2.0: - resolution: - { integrity: sha512-tmMpK00BjZiUyVyvrBK7knerNgmgvcV/KLVyuma/SC+TQN167GrMRciANTz09+k3zW8L8t60jWO1GpfkZdjTaw== } + resolution: {integrity: sha512-tmMpK00BjZiUyVyvrBK7knerNgmgvcV/KLVyuma/SC+TQN167GrMRciANTz09+k3zW8L8t60jWO1GpfkZdjTaw==} ts-api-utils@2.1.0: - resolution: - { integrity: sha512-CUgTZL1irw8u29bzrOD/nH85jqyc74D6SshFgujOIA7osm2Rz7dYH77agkx7H4FBNxDq7Cjf+IjaX/8zwFW+ZQ== } - engines: { node: ">=18.12" } + resolution: {integrity: sha512-CUgTZL1irw8u29bzrOD/nH85jqyc74D6SshFgujOIA7osm2Rz7dYH77agkx7H4FBNxDq7Cjf+IjaX/8zwFW+ZQ==} + engines: {node: '>=18.12'} peerDependencies: - typescript: ">=4.8.4" + typescript: '>=4.8.4' tsconfig-paths@3.15.0: - resolution: - { integrity: sha512-2Ac2RgzDe/cn48GvOe3M+o82pEFewD3UPbyoUHHdKasHwJKjds4fLXWf/Ux5kATBKN20oaFGu+jbElp1pos0mg== } + resolution: {integrity: sha512-2Ac2RgzDe/cn48GvOe3M+o82pEFewD3UPbyoUHHdKasHwJKjds4fLXWf/Ux5kATBKN20oaFGu+jbElp1pos0mg==} tslib@2.8.1: - resolution: - { integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w== } + resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==} tw-animate-css@1.3.5: - resolution: - { integrity: sha512-t3u+0YNoloIhj1mMXs779P6MO9q3p3mvGn4k1n3nJPqJw/glZcuijG2qTSN4z4mgNRfW5ZC3aXJFLwDtiipZXA== } + resolution: {integrity: sha512-t3u+0YNoloIhj1mMXs779P6MO9q3p3mvGn4k1n3nJPqJw/glZcuijG2qTSN4z4mgNRfW5ZC3aXJFLwDtiipZXA==} type-check@0.4.0: - resolution: - { integrity: sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew== } - engines: { node: ">= 0.8.0" } + resolution: {integrity: sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==} + engines: {node: '>= 0.8.0'} typed-array-buffer@1.0.3: - resolution: - { integrity: sha512-nAYYwfY3qnzX30IkA6AQZjVbtK6duGontcQm1WSG1MD94YLqK0515GNApXkoxKOWMusVssAHWLh9SeaoefYFGw== } - engines: { node: ">= 0.4" } + resolution: {integrity: sha512-nAYYwfY3qnzX30IkA6AQZjVbtK6duGontcQm1WSG1MD94YLqK0515GNApXkoxKOWMusVssAHWLh9SeaoefYFGw==} + engines: {node: '>= 0.4'} typed-array-byte-length@1.0.3: - resolution: - { integrity: sha512-BaXgOuIxz8n8pIq3e7Atg/7s+DpiYrxn4vdot3w9KbnBhcRQq6o3xemQdIfynqSeXeDrF32x+WvfzmOjPiY9lg== } - engines: { node: ">= 0.4" } + resolution: {integrity: sha512-BaXgOuIxz8n8pIq3e7Atg/7s+DpiYrxn4vdot3w9KbnBhcRQq6o3xemQdIfynqSeXeDrF32x+WvfzmOjPiY9lg==} + engines: {node: '>= 0.4'} typed-array-byte-offset@1.0.4: - resolution: - { integrity: sha512-bTlAFB/FBYMcuX81gbL4OcpH5PmlFHqlCCpAl8AlEzMz5k53oNDvN8p1PNOWLEmI2x4orp3raOFB51tv9X+MFQ== } - engines: { node: ">= 0.4" } + resolution: {integrity: sha512-bTlAFB/FBYMcuX81gbL4OcpH5PmlFHqlCCpAl8AlEzMz5k53oNDvN8p1PNOWLEmI2x4orp3raOFB51tv9X+MFQ==} + engines: {node: '>= 0.4'} typed-array-length@1.0.7: - resolution: - { integrity: sha512-3KS2b+kL7fsuk/eJZ7EQdnEmQoaho/r6KUef7hxvltNA5DR8NAUM+8wJMbJyZ4G9/7i3v5zPBIMN5aybAh2/Jg== } - engines: { node: ">= 0.4" } + resolution: {integrity: sha512-3KS2b+kL7fsuk/eJZ7EQdnEmQoaho/r6KUef7hxvltNA5DR8NAUM+8wJMbJyZ4G9/7i3v5zPBIMN5aybAh2/Jg==} + engines: {node: '>= 0.4'} typescript@5.8.3: - resolution: - { integrity: sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ== } - engines: { node: ">=14.17" } + resolution: {integrity: sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ==} + engines: {node: '>=14.17'} hasBin: true unbox-primitive@1.1.0: - resolution: - { integrity: sha512-nWJ91DjeOkej/TA8pXQ3myruKpKEYgqvpw9lz4OPHj/NWFNluYrjbz9j01CJ8yKQd2g4jFoOkINCTW2I5LEEyw== } - engines: { node: ">= 0.4" } + resolution: {integrity: sha512-nWJ91DjeOkej/TA8pXQ3myruKpKEYgqvpw9lz4OPHj/NWFNluYrjbz9j01CJ8yKQd2g4jFoOkINCTW2I5LEEyw==} + engines: {node: '>= 0.4'} undici-types@6.21.0: - resolution: - { integrity: sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ== } + resolution: {integrity: sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==} unified@11.0.5: - resolution: - { integrity: sha512-xKvGhPWw3k84Qjh8bI3ZeJjqnyadK+GEFtazSfZv/rKeTkTjOJho6mFqh2SM96iIcZokxiOpg78GazTSg8+KHA== } + resolution: {integrity: sha512-xKvGhPWw3k84Qjh8bI3ZeJjqnyadK+GEFtazSfZv/rKeTkTjOJho6mFqh2SM96iIcZokxiOpg78GazTSg8+KHA==} unist-util-is@6.0.0: - resolution: - { integrity: sha512-2qCTHimwdxLfz+YzdGfkqNlH0tLi9xjTnHddPmJwtIG9MGsdbutfTc4P+haPD7l7Cjxf/WZj+we5qfVPvvxfYw== } + resolution: {integrity: sha512-2qCTHimwdxLfz+YzdGfkqNlH0tLi9xjTnHddPmJwtIG9MGsdbutfTc4P+haPD7l7Cjxf/WZj+we5qfVPvvxfYw==} unist-util-position-from-estree@2.0.0: - resolution: - { integrity: sha512-KaFVRjoqLyF6YXCbVLNad/eS4+OfPQQn2yOd7zF/h5T/CSL2v8NpN6a5TPvtbXthAGw5nG+PuTtq+DdIZr+cRQ== } + resolution: {integrity: sha512-KaFVRjoqLyF6YXCbVLNad/eS4+OfPQQn2yOd7zF/h5T/CSL2v8NpN6a5TPvtbXthAGw5nG+PuTtq+DdIZr+cRQ==} unist-util-position@5.0.0: - resolution: - { integrity: sha512-fucsC7HjXvkB5R3kTCO7kUjRdrS0BJt3M/FPxmHMBOm8JQi2BsHAHFsy27E0EolP8rp0NzXsJ+jNPyDWvOJZPA== } + resolution: {integrity: sha512-fucsC7HjXvkB5R3kTCO7kUjRdrS0BJt3M/FPxmHMBOm8JQi2BsHAHFsy27E0EolP8rp0NzXsJ+jNPyDWvOJZPA==} unist-util-stringify-position@4.0.0: - resolution: - { integrity: sha512-0ASV06AAoKCDkS2+xw5RXJywruurpbC4JZSm7nr7MOt1ojAzvyyaO+UxZf18j8FCF6kmzCZKcAgN/yu2gm2XgQ== } + resolution: {integrity: sha512-0ASV06AAoKCDkS2+xw5RXJywruurpbC4JZSm7nr7MOt1ojAzvyyaO+UxZf18j8FCF6kmzCZKcAgN/yu2gm2XgQ==} unist-util-visit-parents@6.0.1: - resolution: - { integrity: sha512-L/PqWzfTP9lzzEa6CKs0k2nARxTdZduw3zyh8d2NVBnsyvHjSX4TWse388YrrQKbvI8w20fGjGlhgT96WwKykw== } + resolution: {integrity: sha512-L/PqWzfTP9lzzEa6CKs0k2nARxTdZduw3zyh8d2NVBnsyvHjSX4TWse388YrrQKbvI8w20fGjGlhgT96WwKykw==} unist-util-visit@5.0.0: - resolution: - { integrity: sha512-MR04uvD+07cwl/yhVuVWAtw+3GOR/knlL55Nd/wAdblk27GCVt3lqpTivy/tkJcZoNPzTwS1Y+KMojlLDhoTzg== } + resolution: {integrity: sha512-MR04uvD+07cwl/yhVuVWAtw+3GOR/knlL55Nd/wAdblk27GCVt3lqpTivy/tkJcZoNPzTwS1Y+KMojlLDhoTzg==} unrs-resolver@1.11.0: - resolution: - { integrity: sha512-uw3hCGO/RdAEAb4zgJ3C/v6KIAFFOtBoxR86b2Ejc5TnH7HrhTWJR2o0A9ullC3eWMegKQCw/arQ/JivywQzkg== } + resolution: {integrity: sha512-uw3hCGO/RdAEAb4zgJ3C/v6KIAFFOtBoxR86b2Ejc5TnH7HrhTWJR2o0A9ullC3eWMegKQCw/arQ/JivywQzkg==} uri-js@4.4.1: - resolution: - { integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg== } + resolution: {integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==} use-callback-ref@1.3.3: - resolution: - { integrity: sha512-jQL3lRnocaFtu3V00JToYz/4QkNWswxijDaCVNZRiRTO3HQDLsdu1ZtmIUvV4yPp+rvWm5j0y0TG/S61cuijTg== } - engines: { node: ">=10" } + resolution: {integrity: sha512-jQL3lRnocaFtu3V00JToYz/4QkNWswxijDaCVNZRiRTO3HQDLsdu1ZtmIUvV4yPp+rvWm5j0y0TG/S61cuijTg==} + engines: {node: '>=10'} peerDependencies: - "@types/react": "*" + '@types/react': '*' react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc peerDependenciesMeta: - "@types/react": + '@types/react': optional: true use-sidecar@1.1.3: - resolution: - { integrity: sha512-Fedw0aZvkhynoPYlA5WXrMCAMm+nSWdZt6lzJQ7Ok8S6Q+VsHmHpRWndVRJ8Be0ZbkfPc5LRYH+5XrzXcEeLRQ== } - engines: { node: ">=10" } + resolution: {integrity: sha512-Fedw0aZvkhynoPYlA5WXrMCAMm+nSWdZt6lzJQ7Ok8S6Q+VsHmHpRWndVRJ8Be0ZbkfPc5LRYH+5XrzXcEeLRQ==} + engines: {node: '>=10'} peerDependencies: - "@types/react": "*" + '@types/react': '*' react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc peerDependenciesMeta: - "@types/react": + '@types/react': optional: true util-deprecate@1.0.2: - resolution: - { integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw== } + resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==} vfile-message@4.0.2: - resolution: - { integrity: sha512-jRDZ1IMLttGj41KcZvlrYAaI3CfqpLpfpf+Mfig13viT6NKvRzWZ+lXz0Y5D60w6uJIBAOGq9mSHf0gktF0duw== } + resolution: {integrity: sha512-jRDZ1IMLttGj41KcZvlrYAaI3CfqpLpfpf+Mfig13viT6NKvRzWZ+lXz0Y5D60w6uJIBAOGq9mSHf0gktF0duw==} vfile@6.0.3: - resolution: - { integrity: sha512-KzIbH/9tXat2u30jf+smMwFCsno4wHVdNmzFyL+T/L3UGqqk6JKfVqOFOZEpZSHADH1k40ab6NUIXZq422ov3Q== } + resolution: {integrity: sha512-KzIbH/9tXat2u30jf+smMwFCsno4wHVdNmzFyL+T/L3UGqqk6JKfVqOFOZEpZSHADH1k40ab6NUIXZq422ov3Q==} which-boxed-primitive@1.1.1: - resolution: - { integrity: sha512-TbX3mj8n0odCBFVlY8AxkqcHASw3L60jIuF8jFP78az3C2YhmGvqbHBpAjTRH2/xqYunrJ9g1jSyjCjpoWzIAA== } - engines: { node: ">= 0.4" } + resolution: {integrity: sha512-TbX3mj8n0odCBFVlY8AxkqcHASw3L60jIuF8jFP78az3C2YhmGvqbHBpAjTRH2/xqYunrJ9g1jSyjCjpoWzIAA==} + engines: {node: '>= 0.4'} which-builtin-type@1.2.1: - resolution: - { integrity: sha512-6iBczoX+kDQ7a3+YJBnh3T+KZRxM/iYNPXicqk66/Qfm1b93iu+yOImkg0zHbj5LNOcNv1TEADiZ0xa34B4q6Q== } - engines: { node: ">= 0.4" } + resolution: {integrity: sha512-6iBczoX+kDQ7a3+YJBnh3T+KZRxM/iYNPXicqk66/Qfm1b93iu+yOImkg0zHbj5LNOcNv1TEADiZ0xa34B4q6Q==} + engines: {node: '>= 0.4'} which-collection@1.0.2: - resolution: - { integrity: sha512-K4jVyjnBdgvc86Y6BkaLZEN933SwYOuBFkdmBu9ZfkcAbdVbpITnDmjvZ/aQjRXQrv5EPkTnD1s39GiiqbngCw== } - engines: { node: ">= 0.4" } + resolution: {integrity: sha512-K4jVyjnBdgvc86Y6BkaLZEN933SwYOuBFkdmBu9ZfkcAbdVbpITnDmjvZ/aQjRXQrv5EPkTnD1s39GiiqbngCw==} + engines: {node: '>= 0.4'} which-typed-array@1.1.19: - resolution: - { integrity: sha512-rEvr90Bck4WZt9HHFC4DJMsjvu7x+r6bImz0/BrbWb7A2djJ8hnZMrWnHo9F8ssv0OMErasDhftrfROTyqSDrw== } - engines: { node: ">= 0.4" } + resolution: {integrity: sha512-rEvr90Bck4WZt9HHFC4DJMsjvu7x+r6bImz0/BrbWb7A2djJ8hnZMrWnHo9F8ssv0OMErasDhftrfROTyqSDrw==} + engines: {node: '>= 0.4'} which@2.0.2: - resolution: - { integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA== } - engines: { node: ">= 8" } + resolution: {integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==} + engines: {node: '>= 8'} hasBin: true word-wrap@1.2.5: - resolution: - { integrity: sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA== } - engines: { node: ">=0.10.0" } + resolution: {integrity: sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==} + engines: {node: '>=0.10.0'} yallist@5.0.0: - resolution: - { integrity: sha512-YgvUTfwqyc7UXVMrB+SImsVYSmTS8X/tSrtdNZMImM+n7+QTriRXyXim0mBrTXNeqzVF0KWGgHPeiyViFFrNDw== } - engines: { node: ">=18" } + resolution: {integrity: sha512-YgvUTfwqyc7UXVMrB+SImsVYSmTS8X/tSrtdNZMImM+n7+QTriRXyXim0mBrTXNeqzVF0KWGgHPeiyViFFrNDw==} + engines: {node: '>=18'} yocto-queue@0.1.0: - resolution: - { integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q== } - engines: { node: ">=10" } + resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==} + engines: {node: '>=10'} zod@3.25.74: - resolution: - { integrity: sha512-J8poo92VuhKjNknViHRAIuuN6li/EwFbAC8OedzI8uxpEPGiXHGQu9wemIAioIpqgfB4SySaJhdk0mH5Y4ICBg== } + resolution: {integrity: sha512-J8poo92VuhKjNknViHRAIuuN6li/EwFbAC8OedzI8uxpEPGiXHGQu9wemIAioIpqgfB4SySaJhdk0mH5Y4ICBg==} zwitch@2.0.4: - resolution: - { integrity: sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A== } + resolution: {integrity: sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A==} snapshots: - "@alloc/quick-lru@5.2.0": {} - "@ampproject/remapping@2.3.0": + '@alloc/quick-lru@5.2.0': {} + + '@ampproject/remapping@2.3.0': dependencies: - "@jridgewell/gen-mapping": 0.3.12 - "@jridgewell/trace-mapping": 0.3.29 + '@jridgewell/gen-mapping': 0.3.12 + '@jridgewell/trace-mapping': 0.3.29 - "@babel/code-frame@7.27.1": + '@babel/code-frame@7.27.1': dependencies: - "@babel/helper-validator-identifier": 7.27.1 + '@babel/helper-validator-identifier': 7.27.1 js-tokens: 4.0.0 picocolors: 1.1.1 - "@babel/generator@7.28.0": + '@babel/generator@7.28.0': dependencies: - "@babel/parser": 7.28.0 - "@babel/types": 7.28.0 - "@jridgewell/gen-mapping": 0.3.12 - "@jridgewell/trace-mapping": 0.3.29 + '@babel/parser': 7.28.0 + '@babel/types': 7.28.0 + '@jridgewell/gen-mapping': 0.3.12 + '@jridgewell/trace-mapping': 0.3.29 jsesc: 3.1.0 - "@babel/helper-globals@7.28.0": {} + '@babel/helper-globals@7.28.0': {} - "@babel/helper-string-parser@7.27.1": {} + '@babel/helper-string-parser@7.27.1': {} - "@babel/helper-validator-identifier@7.27.1": {} + '@babel/helper-validator-identifier@7.27.1': {} - "@babel/parser@7.28.0": + '@babel/parser@7.28.0': dependencies: - "@babel/types": 7.28.0 + '@babel/types': 7.28.0 - "@babel/template@7.27.2": + '@babel/template@7.27.2': dependencies: - "@babel/code-frame": 7.27.1 - "@babel/parser": 7.28.0 - "@babel/types": 7.28.0 + '@babel/code-frame': 7.27.1 + '@babel/parser': 7.28.0 + '@babel/types': 7.28.0 - "@babel/traverse@7.28.0": + '@babel/traverse@7.28.0': dependencies: - "@babel/code-frame": 7.27.1 - "@babel/generator": 7.28.0 - "@babel/helper-globals": 7.28.0 - "@babel/parser": 7.28.0 - "@babel/template": 7.27.2 - "@babel/types": 7.28.0 + '@babel/code-frame': 7.27.1 + '@babel/generator': 7.28.0 + '@babel/helper-globals': 7.28.0 + '@babel/parser': 7.28.0 + '@babel/template': 7.27.2 + '@babel/types': 7.28.0 debug: 4.4.1 transitivePeerDependencies: - supports-color - "@babel/types@7.28.0": + '@babel/types@7.28.0': dependencies: - "@babel/helper-string-parser": 7.27.1 - "@babel/helper-validator-identifier": 7.27.1 + '@babel/helper-string-parser': 7.27.1 + '@babel/helper-validator-identifier': 7.27.1 - "@emnapi/core@1.4.3": + '@emnapi/core@1.4.3': dependencies: - "@emnapi/wasi-threads": 1.0.2 + '@emnapi/wasi-threads': 1.0.2 tslib: 2.8.1 optional: true - "@emnapi/runtime@1.4.3": + '@emnapi/runtime@1.4.3': dependencies: tslib: 2.8.1 optional: true - "@emnapi/wasi-threads@1.0.2": + '@emnapi/wasi-threads@1.0.2': dependencies: tslib: 2.8.1 optional: true - "@esbuild/aix-ppc64@0.25.5": + '@esbuild/aix-ppc64@0.25.5': optional: true - "@esbuild/android-arm64@0.25.5": + '@esbuild/android-arm64@0.25.5': optional: true - "@esbuild/android-arm@0.25.5": + '@esbuild/android-arm@0.25.5': optional: true - "@esbuild/android-x64@0.25.5": + '@esbuild/android-x64@0.25.5': optional: true - "@esbuild/darwin-arm64@0.25.5": + '@esbuild/darwin-arm64@0.25.5': optional: true - "@esbuild/darwin-x64@0.25.5": + '@esbuild/darwin-x64@0.25.5': optional: true - "@esbuild/freebsd-arm64@0.25.5": + '@esbuild/freebsd-arm64@0.25.5': optional: true - "@esbuild/freebsd-x64@0.25.5": + '@esbuild/freebsd-x64@0.25.5': optional: true - "@esbuild/linux-arm64@0.25.5": + '@esbuild/linux-arm64@0.25.5': optional: true - "@esbuild/linux-arm@0.25.5": + '@esbuild/linux-arm@0.25.5': optional: true - "@esbuild/linux-ia32@0.25.5": + '@esbuild/linux-ia32@0.25.5': optional: true - "@esbuild/linux-loong64@0.25.5": + '@esbuild/linux-loong64@0.25.5': optional: true - "@esbuild/linux-mips64el@0.25.5": + '@esbuild/linux-mips64el@0.25.5': optional: true - "@esbuild/linux-ppc64@0.25.5": + '@esbuild/linux-ppc64@0.25.5': optional: true - "@esbuild/linux-riscv64@0.25.5": + '@esbuild/linux-riscv64@0.25.5': optional: true - "@esbuild/linux-s390x@0.25.5": + '@esbuild/linux-s390x@0.25.5': optional: true - "@esbuild/linux-x64@0.25.5": + '@esbuild/linux-x64@0.25.5': optional: true - "@esbuild/netbsd-arm64@0.25.5": + '@esbuild/netbsd-arm64@0.25.5': optional: true - "@esbuild/netbsd-x64@0.25.5": + '@esbuild/netbsd-x64@0.25.5': optional: true - "@esbuild/openbsd-arm64@0.25.5": + '@esbuild/openbsd-arm64@0.25.5': optional: true - "@esbuild/openbsd-x64@0.25.5": + '@esbuild/openbsd-x64@0.25.5': optional: true - "@esbuild/sunos-x64@0.25.5": + '@esbuild/sunos-x64@0.25.5': optional: true - "@esbuild/win32-arm64@0.25.5": + '@esbuild/win32-arm64@0.25.5': optional: true - "@esbuild/win32-ia32@0.25.5": + '@esbuild/win32-ia32@0.25.5': optional: true - "@esbuild/win32-x64@0.25.5": + '@esbuild/win32-x64@0.25.5': optional: true - "@eslint-community/eslint-utils@4.7.0(eslint@9.30.0(jiti@2.4.2))": + '@eslint-community/eslint-utils@4.7.0(eslint@9.30.0(jiti@2.4.2))': dependencies: eslint: 9.30.0(jiti@2.4.2) eslint-visitor-keys: 3.4.3 - "@eslint-community/regexpp@4.12.1": {} + '@eslint-community/regexpp@4.12.1': {} - "@eslint/config-array@0.21.0": + '@eslint/config-array@0.21.0': dependencies: - "@eslint/object-schema": 2.1.6 + '@eslint/object-schema': 2.1.6 debug: 4.4.1 minimatch: 3.1.2 transitivePeerDependencies: - supports-color - "@eslint/config-helpers@0.3.0": {} + '@eslint/config-helpers@0.3.0': {} - "@eslint/core@0.14.0": + '@eslint/core@0.14.0': dependencies: - "@types/json-schema": 7.0.15 + '@types/json-schema': 7.0.15 - "@eslint/core@0.15.1": + '@eslint/core@0.15.1': dependencies: - "@types/json-schema": 7.0.15 + '@types/json-schema': 7.0.15 - "@eslint/eslintrc@3.3.1": + '@eslint/eslintrc@3.3.1': dependencies: ajv: 6.12.6 debug: 4.4.1 @@ -4012,165 +3389,165 @@ snapshots: transitivePeerDependencies: - supports-color - "@eslint/js@9.30.0": {} + '@eslint/js@9.30.0': {} - "@eslint/object-schema@2.1.6": {} + '@eslint/object-schema@2.1.6': {} - "@eslint/plugin-kit@0.3.3": + '@eslint/plugin-kit@0.3.3': dependencies: - "@eslint/core": 0.15.1 + '@eslint/core': 0.15.1 levn: 0.4.1 - "@floating-ui/core@1.7.2": + '@floating-ui/core@1.7.2': dependencies: - "@floating-ui/utils": 0.2.10 + '@floating-ui/utils': 0.2.10 - "@floating-ui/dom@1.7.2": + '@floating-ui/dom@1.7.2': dependencies: - "@floating-ui/core": 1.7.2 - "@floating-ui/utils": 0.2.10 + '@floating-ui/core': 1.7.2 + '@floating-ui/utils': 0.2.10 - "@floating-ui/react-dom@2.1.4(react-dom@19.1.0(react@19.1.0))(react@19.1.0)": + '@floating-ui/react-dom@2.1.4(react-dom@19.1.0(react@19.1.0))(react@19.1.0)': dependencies: - "@floating-ui/dom": 1.7.2 + '@floating-ui/dom': 1.7.2 react: 19.1.0 react-dom: 19.1.0(react@19.1.0) - "@floating-ui/utils@0.2.10": {} + '@floating-ui/utils@0.2.10': {} - "@formatjs/intl-localematcher@0.6.1": + '@formatjs/intl-localematcher@0.6.1': dependencies: tslib: 2.8.1 - "@humanfs/core@0.19.1": {} + '@humanfs/core@0.19.1': {} - "@humanfs/node@0.16.6": + '@humanfs/node@0.16.6': dependencies: - "@humanfs/core": 0.19.1 - "@humanwhocodes/retry": 0.3.1 + '@humanfs/core': 0.19.1 + '@humanwhocodes/retry': 0.3.1 - "@humanwhocodes/module-importer@1.0.1": {} + '@humanwhocodes/module-importer@1.0.1': {} - "@humanwhocodes/retry@0.3.1": {} + '@humanwhocodes/retry@0.3.1': {} - "@humanwhocodes/retry@0.4.3": {} + '@humanwhocodes/retry@0.4.3': {} - "@ianvs/prettier-plugin-sort-imports@4.4.2(prettier@3.6.2)": + '@ianvs/prettier-plugin-sort-imports@4.4.2(prettier@3.6.2)': dependencies: - "@babel/generator": 7.28.0 - "@babel/parser": 7.28.0 - "@babel/traverse": 7.28.0 - "@babel/types": 7.28.0 + '@babel/generator': 7.28.0 + '@babel/parser': 7.28.0 + '@babel/traverse': 7.28.0 + '@babel/types': 7.28.0 prettier: 3.6.2 semver: 7.7.2 transitivePeerDependencies: - supports-color - "@img/sharp-darwin-arm64@0.34.2": + '@img/sharp-darwin-arm64@0.34.2': optionalDependencies: - "@img/sharp-libvips-darwin-arm64": 1.1.0 + '@img/sharp-libvips-darwin-arm64': 1.1.0 optional: true - "@img/sharp-darwin-x64@0.34.2": + '@img/sharp-darwin-x64@0.34.2': optionalDependencies: - "@img/sharp-libvips-darwin-x64": 1.1.0 + '@img/sharp-libvips-darwin-x64': 1.1.0 optional: true - "@img/sharp-libvips-darwin-arm64@1.1.0": + '@img/sharp-libvips-darwin-arm64@1.1.0': optional: true - "@img/sharp-libvips-darwin-x64@1.1.0": + '@img/sharp-libvips-darwin-x64@1.1.0': optional: true - "@img/sharp-libvips-linux-arm64@1.1.0": + '@img/sharp-libvips-linux-arm64@1.1.0': optional: true - "@img/sharp-libvips-linux-arm@1.1.0": + '@img/sharp-libvips-linux-arm@1.1.0': optional: true - "@img/sharp-libvips-linux-ppc64@1.1.0": + '@img/sharp-libvips-linux-ppc64@1.1.0': optional: true - "@img/sharp-libvips-linux-s390x@1.1.0": + '@img/sharp-libvips-linux-s390x@1.1.0': optional: true - "@img/sharp-libvips-linux-x64@1.1.0": + '@img/sharp-libvips-linux-x64@1.1.0': optional: true - "@img/sharp-libvips-linuxmusl-arm64@1.1.0": + '@img/sharp-libvips-linuxmusl-arm64@1.1.0': optional: true - "@img/sharp-libvips-linuxmusl-x64@1.1.0": + '@img/sharp-libvips-linuxmusl-x64@1.1.0': optional: true - "@img/sharp-linux-arm64@0.34.2": + '@img/sharp-linux-arm64@0.34.2': optionalDependencies: - "@img/sharp-libvips-linux-arm64": 1.1.0 + '@img/sharp-libvips-linux-arm64': 1.1.0 optional: true - "@img/sharp-linux-arm@0.34.2": + '@img/sharp-linux-arm@0.34.2': optionalDependencies: - "@img/sharp-libvips-linux-arm": 1.1.0 + '@img/sharp-libvips-linux-arm': 1.1.0 optional: true - "@img/sharp-linux-s390x@0.34.2": + '@img/sharp-linux-s390x@0.34.2': optionalDependencies: - "@img/sharp-libvips-linux-s390x": 1.1.0 + '@img/sharp-libvips-linux-s390x': 1.1.0 optional: true - "@img/sharp-linux-x64@0.34.2": + '@img/sharp-linux-x64@0.34.2': optionalDependencies: - "@img/sharp-libvips-linux-x64": 1.1.0 + '@img/sharp-libvips-linux-x64': 1.1.0 optional: true - "@img/sharp-linuxmusl-arm64@0.34.2": + '@img/sharp-linuxmusl-arm64@0.34.2': optionalDependencies: - "@img/sharp-libvips-linuxmusl-arm64": 1.1.0 + '@img/sharp-libvips-linuxmusl-arm64': 1.1.0 optional: true - "@img/sharp-linuxmusl-x64@0.34.2": + '@img/sharp-linuxmusl-x64@0.34.2': optionalDependencies: - "@img/sharp-libvips-linuxmusl-x64": 1.1.0 + '@img/sharp-libvips-linuxmusl-x64': 1.1.0 optional: true - "@img/sharp-wasm32@0.34.2": + '@img/sharp-wasm32@0.34.2': dependencies: - "@emnapi/runtime": 1.4.3 + '@emnapi/runtime': 1.4.3 optional: true - "@img/sharp-win32-arm64@0.34.2": + '@img/sharp-win32-arm64@0.34.2': optional: true - "@img/sharp-win32-ia32@0.34.2": + '@img/sharp-win32-ia32@0.34.2': optional: true - "@img/sharp-win32-x64@0.34.2": + '@img/sharp-win32-x64@0.34.2': optional: true - "@isaacs/fs-minipass@4.0.1": + '@isaacs/fs-minipass@4.0.1': dependencies: minipass: 7.1.2 - "@jridgewell/gen-mapping@0.3.12": + '@jridgewell/gen-mapping@0.3.12': dependencies: - "@jridgewell/sourcemap-codec": 1.5.4 - "@jridgewell/trace-mapping": 0.3.29 + '@jridgewell/sourcemap-codec': 1.5.4 + '@jridgewell/trace-mapping': 0.3.29 - "@jridgewell/resolve-uri@3.1.2": {} + '@jridgewell/resolve-uri@3.1.2': {} - "@jridgewell/sourcemap-codec@1.5.4": {} + '@jridgewell/sourcemap-codec@1.5.4': {} - "@jridgewell/trace-mapping@0.3.29": + '@jridgewell/trace-mapping@0.3.29': dependencies: - "@jridgewell/resolve-uri": 3.1.2 - "@jridgewell/sourcemap-codec": 1.5.4 + '@jridgewell/resolve-uri': 3.1.2 + '@jridgewell/sourcemap-codec': 1.5.4 - "@mdx-js/mdx@3.1.0(acorn@8.15.0)": + '@mdx-js/mdx@3.1.0(acorn@8.15.0)': dependencies: - "@types/estree": 1.0.8 - "@types/estree-jsx": 1.0.5 - "@types/hast": 3.0.4 - "@types/mdx": 2.0.13 + '@types/estree': 1.0.8 + '@types/estree-jsx': 1.0.5 + '@types/hast': 3.0.4 + '@types/mdx': 2.0.13 collapse-white-space: 2.1.0 devlop: 1.1.0 estree-util-is-identifier-name: 3.0.0 @@ -4195,472 +3572,503 @@ snapshots: - acorn - supports-color - "@napi-rs/wasm-runtime@0.2.11": + '@napi-rs/wasm-runtime@0.2.11': dependencies: - "@emnapi/core": 1.4.3 - "@emnapi/runtime": 1.4.3 - "@tybys/wasm-util": 0.9.0 + '@emnapi/core': 1.4.3 + '@emnapi/runtime': 1.4.3 + '@tybys/wasm-util': 0.9.0 optional: true - "@next/env@15.3.4": {} + '@next/env@15.3.4': {} - "@next/eslint-plugin-next@15.3.4": + '@next/eslint-plugin-next@15.3.4': dependencies: fast-glob: 3.3.1 - "@next/swc-darwin-arm64@15.3.4": + '@next/swc-darwin-arm64@15.3.4': optional: true - "@next/swc-darwin-x64@15.3.4": + '@next/swc-darwin-x64@15.3.4': optional: true - "@next/swc-linux-arm64-gnu@15.3.4": + '@next/swc-linux-arm64-gnu@15.3.4': optional: true - "@next/swc-linux-arm64-musl@15.3.4": + '@next/swc-linux-arm64-musl@15.3.4': optional: true - "@next/swc-linux-x64-gnu@15.3.4": + '@next/swc-linux-x64-gnu@15.3.4': optional: true - "@next/swc-linux-x64-musl@15.3.4": + '@next/swc-linux-x64-musl@15.3.4': optional: true - "@next/swc-win32-arm64-msvc@15.3.4": + '@next/swc-win32-arm64-msvc@15.3.4': optional: true - "@next/swc-win32-x64-msvc@15.3.4": + '@next/swc-win32-x64-msvc@15.3.4': optional: true - "@nodelib/fs.scandir@2.1.5": + '@nodelib/fs.scandir@2.1.5': dependencies: - "@nodelib/fs.stat": 2.0.5 + '@nodelib/fs.stat': 2.0.5 run-parallel: 1.2.0 - "@nodelib/fs.stat@2.0.5": {} + '@nodelib/fs.stat@2.0.5': {} - "@nodelib/fs.walk@1.2.8": + '@nodelib/fs.walk@1.2.8': dependencies: - "@nodelib/fs.scandir": 2.1.5 + '@nodelib/fs.scandir': 2.1.5 fastq: 1.19.1 - "@nolyfill/is-core-module@1.0.39": {} + '@nolyfill/is-core-module@1.0.39': {} + + '@orama/orama@3.1.10': {} - "@orama/orama@3.1.10": {} + '@pkgr/core@0.2.7': {} - "@pkgr/core@0.2.7": {} + '@radix-ui/number@1.1.1': {} - "@radix-ui/number@1.1.1": {} + '@radix-ui/primitive@1.1.2': {} - "@radix-ui/primitive@1.1.2": {} + '@radix-ui/primitive@1.1.3': {} - "@radix-ui/react-accordion@1.2.11(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)": + '@radix-ui/react-accordion@1.2.11(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)': dependencies: - "@radix-ui/primitive": 1.1.2 - "@radix-ui/react-collapsible": 1.1.11(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) - "@radix-ui/react-collection": 1.1.7(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) - "@radix-ui/react-compose-refs": 1.1.2(@types/react@19.1.8)(react@19.1.0) - "@radix-ui/react-context": 1.1.2(@types/react@19.1.8)(react@19.1.0) - "@radix-ui/react-direction": 1.1.1(@types/react@19.1.8)(react@19.1.0) - "@radix-ui/react-id": 1.1.1(@types/react@19.1.8)(react@19.1.0) - "@radix-ui/react-primitive": 2.1.3(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) - "@radix-ui/react-use-controllable-state": 1.2.2(@types/react@19.1.8)(react@19.1.0) + '@radix-ui/primitive': 1.1.2 + '@radix-ui/react-collapsible': 1.1.11(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + '@radix-ui/react-collection': 1.1.7(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.1.8)(react@19.1.0) + '@radix-ui/react-context': 1.1.2(@types/react@19.1.8)(react@19.1.0) + '@radix-ui/react-direction': 1.1.1(@types/react@19.1.8)(react@19.1.0) + '@radix-ui/react-id': 1.1.1(@types/react@19.1.8)(react@19.1.0) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.1.8)(react@19.1.0) react: 19.1.0 react-dom: 19.1.0(react@19.1.0) optionalDependencies: - "@types/react": 19.1.8 - "@types/react-dom": 19.1.6(@types/react@19.1.8) + '@types/react': 19.1.8 + '@types/react-dom': 19.1.6(@types/react@19.1.8) - "@radix-ui/react-arrow@1.1.7(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)": + '@radix-ui/react-arrow@1.1.7(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)': dependencies: - "@radix-ui/react-primitive": 2.1.3(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) react: 19.1.0 react-dom: 19.1.0(react@19.1.0) optionalDependencies: - "@types/react": 19.1.8 - "@types/react-dom": 19.1.6(@types/react@19.1.8) - - "@radix-ui/react-collapsible@1.1.11(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)": - dependencies: - "@radix-ui/primitive": 1.1.2 - "@radix-ui/react-compose-refs": 1.1.2(@types/react@19.1.8)(react@19.1.0) - "@radix-ui/react-context": 1.1.2(@types/react@19.1.8)(react@19.1.0) - "@radix-ui/react-id": 1.1.1(@types/react@19.1.8)(react@19.1.0) - "@radix-ui/react-presence": 1.1.4(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) - "@radix-ui/react-primitive": 2.1.3(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) - "@radix-ui/react-use-controllable-state": 1.2.2(@types/react@19.1.8)(react@19.1.0) - "@radix-ui/react-use-layout-effect": 1.1.1(@types/react@19.1.8)(react@19.1.0) + '@types/react': 19.1.8 + '@types/react-dom': 19.1.6(@types/react@19.1.8) + + '@radix-ui/react-collapsible@1.1.11(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)': + dependencies: + '@radix-ui/primitive': 1.1.2 + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.1.8)(react@19.1.0) + '@radix-ui/react-context': 1.1.2(@types/react@19.1.8)(react@19.1.0) + '@radix-ui/react-id': 1.1.1(@types/react@19.1.8)(react@19.1.0) + '@radix-ui/react-presence': 1.1.4(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.1.8)(react@19.1.0) + '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.1.8)(react@19.1.0) react: 19.1.0 react-dom: 19.1.0(react@19.1.0) optionalDependencies: - "@types/react": 19.1.8 - "@types/react-dom": 19.1.6(@types/react@19.1.8) + '@types/react': 19.1.8 + '@types/react-dom': 19.1.6(@types/react@19.1.8) - "@radix-ui/react-collection@1.1.7(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)": + '@radix-ui/react-collection@1.1.7(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)': dependencies: - "@radix-ui/react-compose-refs": 1.1.2(@types/react@19.1.8)(react@19.1.0) - "@radix-ui/react-context": 1.1.2(@types/react@19.1.8)(react@19.1.0) - "@radix-ui/react-primitive": 2.1.3(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) - "@radix-ui/react-slot": 1.2.3(@types/react@19.1.8)(react@19.1.0) + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.1.8)(react@19.1.0) + '@radix-ui/react-context': 1.1.2(@types/react@19.1.8)(react@19.1.0) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + '@radix-ui/react-slot': 1.2.3(@types/react@19.1.8)(react@19.1.0) react: 19.1.0 react-dom: 19.1.0(react@19.1.0) optionalDependencies: - "@types/react": 19.1.8 - "@types/react-dom": 19.1.6(@types/react@19.1.8) + '@types/react': 19.1.8 + '@types/react-dom': 19.1.6(@types/react@19.1.8) - "@radix-ui/react-compose-refs@1.1.2(@types/react@19.1.8)(react@19.1.0)": + '@radix-ui/react-compose-refs@1.1.2(@types/react@19.1.8)(react@19.1.0)': dependencies: react: 19.1.0 optionalDependencies: - "@types/react": 19.1.8 + '@types/react': 19.1.8 - "@radix-ui/react-context@1.1.2(@types/react@19.1.8)(react@19.1.0)": + '@radix-ui/react-context@1.1.2(@types/react@19.1.8)(react@19.1.0)': dependencies: react: 19.1.0 optionalDependencies: - "@types/react": 19.1.8 - - "@radix-ui/react-dialog@1.1.14(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)": - dependencies: - "@radix-ui/primitive": 1.1.2 - "@radix-ui/react-compose-refs": 1.1.2(@types/react@19.1.8)(react@19.1.0) - "@radix-ui/react-context": 1.1.2(@types/react@19.1.8)(react@19.1.0) - "@radix-ui/react-dismissable-layer": 1.1.10(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) - "@radix-ui/react-focus-guards": 1.1.2(@types/react@19.1.8)(react@19.1.0) - "@radix-ui/react-focus-scope": 1.1.7(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) - "@radix-ui/react-id": 1.1.1(@types/react@19.1.8)(react@19.1.0) - "@radix-ui/react-portal": 1.1.9(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) - "@radix-ui/react-presence": 1.1.4(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) - "@radix-ui/react-primitive": 2.1.3(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) - "@radix-ui/react-slot": 1.2.3(@types/react@19.1.8)(react@19.1.0) - "@radix-ui/react-use-controllable-state": 1.2.2(@types/react@19.1.8)(react@19.1.0) + '@types/react': 19.1.8 + + '@radix-ui/react-dialog@1.1.15(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)': + dependencies: + '@radix-ui/primitive': 1.1.3 + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.1.8)(react@19.1.0) + '@radix-ui/react-context': 1.1.2(@types/react@19.1.8)(react@19.1.0) + '@radix-ui/react-dismissable-layer': 1.1.11(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + '@radix-ui/react-focus-guards': 1.1.3(@types/react@19.1.8)(react@19.1.0) + '@radix-ui/react-focus-scope': 1.1.7(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + '@radix-ui/react-id': 1.1.1(@types/react@19.1.8)(react@19.1.0) + '@radix-ui/react-portal': 1.1.9(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + '@radix-ui/react-presence': 1.1.5(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + '@radix-ui/react-slot': 1.2.3(@types/react@19.1.8)(react@19.1.0) + '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.1.8)(react@19.1.0) aria-hidden: 1.2.6 react: 19.1.0 react-dom: 19.1.0(react@19.1.0) react-remove-scroll: 2.7.1(@types/react@19.1.8)(react@19.1.0) optionalDependencies: - "@types/react": 19.1.8 - "@types/react-dom": 19.1.6(@types/react@19.1.8) + '@types/react': 19.1.8 + '@types/react-dom': 19.1.6(@types/react@19.1.8) - "@radix-ui/react-direction@1.1.1(@types/react@19.1.8)(react@19.1.0)": + '@radix-ui/react-direction@1.1.1(@types/react@19.1.8)(react@19.1.0)': dependencies: react: 19.1.0 optionalDependencies: - "@types/react": 19.1.8 + '@types/react': 19.1.8 + + '@radix-ui/react-dismissable-layer@1.1.10(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)': + dependencies: + '@radix-ui/primitive': 1.1.2 + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.1.8)(react@19.1.0) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@19.1.8)(react@19.1.0) + '@radix-ui/react-use-escape-keydown': 1.1.1(@types/react@19.1.8)(react@19.1.0) + react: 19.1.0 + react-dom: 19.1.0(react@19.1.0) + optionalDependencies: + '@types/react': 19.1.8 + '@types/react-dom': 19.1.6(@types/react@19.1.8) - "@radix-ui/react-dismissable-layer@1.1.10(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)": + '@radix-ui/react-dismissable-layer@1.1.11(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)': dependencies: - "@radix-ui/primitive": 1.1.2 - "@radix-ui/react-compose-refs": 1.1.2(@types/react@19.1.8)(react@19.1.0) - "@radix-ui/react-primitive": 2.1.3(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) - "@radix-ui/react-use-callback-ref": 1.1.1(@types/react@19.1.8)(react@19.1.0) - "@radix-ui/react-use-escape-keydown": 1.1.1(@types/react@19.1.8)(react@19.1.0) + '@radix-ui/primitive': 1.1.3 + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.1.8)(react@19.1.0) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@19.1.8)(react@19.1.0) + '@radix-ui/react-use-escape-keydown': 1.1.1(@types/react@19.1.8)(react@19.1.0) react: 19.1.0 react-dom: 19.1.0(react@19.1.0) optionalDependencies: - "@types/react": 19.1.8 - "@types/react-dom": 19.1.6(@types/react@19.1.8) + '@types/react': 19.1.8 + '@types/react-dom': 19.1.6(@types/react@19.1.8) - "@radix-ui/react-focus-guards@1.1.2(@types/react@19.1.8)(react@19.1.0)": + '@radix-ui/react-focus-guards@1.1.2(@types/react@19.1.8)(react@19.1.0)': dependencies: react: 19.1.0 optionalDependencies: - "@types/react": 19.1.8 + '@types/react': 19.1.8 - "@radix-ui/react-focus-scope@1.1.7(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)": + '@radix-ui/react-focus-guards@1.1.3(@types/react@19.1.8)(react@19.1.0)': dependencies: - "@radix-ui/react-compose-refs": 1.1.2(@types/react@19.1.8)(react@19.1.0) - "@radix-ui/react-primitive": 2.1.3(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) - "@radix-ui/react-use-callback-ref": 1.1.1(@types/react@19.1.8)(react@19.1.0) + react: 19.1.0 + optionalDependencies: + '@types/react': 19.1.8 + + '@radix-ui/react-focus-scope@1.1.7(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)': + dependencies: + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.1.8)(react@19.1.0) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@19.1.8)(react@19.1.0) react: 19.1.0 react-dom: 19.1.0(react@19.1.0) optionalDependencies: - "@types/react": 19.1.8 - "@types/react-dom": 19.1.6(@types/react@19.1.8) + '@types/react': 19.1.8 + '@types/react-dom': 19.1.6(@types/react@19.1.8) - "@radix-ui/react-id@1.1.1(@types/react@19.1.8)(react@19.1.0)": + '@radix-ui/react-id@1.1.1(@types/react@19.1.8)(react@19.1.0)': dependencies: - "@radix-ui/react-use-layout-effect": 1.1.1(@types/react@19.1.8)(react@19.1.0) + '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.1.8)(react@19.1.0) react: 19.1.0 optionalDependencies: - "@types/react": 19.1.8 - - "@radix-ui/react-navigation-menu@1.2.13(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)": - dependencies: - "@radix-ui/primitive": 1.1.2 - "@radix-ui/react-collection": 1.1.7(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) - "@radix-ui/react-compose-refs": 1.1.2(@types/react@19.1.8)(react@19.1.0) - "@radix-ui/react-context": 1.1.2(@types/react@19.1.8)(react@19.1.0) - "@radix-ui/react-direction": 1.1.1(@types/react@19.1.8)(react@19.1.0) - "@radix-ui/react-dismissable-layer": 1.1.10(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) - "@radix-ui/react-id": 1.1.1(@types/react@19.1.8)(react@19.1.0) - "@radix-ui/react-presence": 1.1.4(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) - "@radix-ui/react-primitive": 2.1.3(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) - "@radix-ui/react-use-callback-ref": 1.1.1(@types/react@19.1.8)(react@19.1.0) - "@radix-ui/react-use-controllable-state": 1.2.2(@types/react@19.1.8)(react@19.1.0) - "@radix-ui/react-use-layout-effect": 1.1.1(@types/react@19.1.8)(react@19.1.0) - "@radix-ui/react-use-previous": 1.1.1(@types/react@19.1.8)(react@19.1.0) - "@radix-ui/react-visually-hidden": 1.2.3(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + '@types/react': 19.1.8 + + '@radix-ui/react-navigation-menu@1.2.13(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)': + dependencies: + '@radix-ui/primitive': 1.1.2 + '@radix-ui/react-collection': 1.1.7(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.1.8)(react@19.1.0) + '@radix-ui/react-context': 1.1.2(@types/react@19.1.8)(react@19.1.0) + '@radix-ui/react-direction': 1.1.1(@types/react@19.1.8)(react@19.1.0) + '@radix-ui/react-dismissable-layer': 1.1.10(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + '@radix-ui/react-id': 1.1.1(@types/react@19.1.8)(react@19.1.0) + '@radix-ui/react-presence': 1.1.4(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@19.1.8)(react@19.1.0) + '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.1.8)(react@19.1.0) + '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.1.8)(react@19.1.0) + '@radix-ui/react-use-previous': 1.1.1(@types/react@19.1.8)(react@19.1.0) + '@radix-ui/react-visually-hidden': 1.2.3(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) react: 19.1.0 react-dom: 19.1.0(react@19.1.0) optionalDependencies: - "@types/react": 19.1.8 - "@types/react-dom": 19.1.6(@types/react@19.1.8) - - "@radix-ui/react-popover@1.1.14(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)": - dependencies: - "@radix-ui/primitive": 1.1.2 - "@radix-ui/react-compose-refs": 1.1.2(@types/react@19.1.8)(react@19.1.0) - "@radix-ui/react-context": 1.1.2(@types/react@19.1.8)(react@19.1.0) - "@radix-ui/react-dismissable-layer": 1.1.10(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) - "@radix-ui/react-focus-guards": 1.1.2(@types/react@19.1.8)(react@19.1.0) - "@radix-ui/react-focus-scope": 1.1.7(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) - "@radix-ui/react-id": 1.1.1(@types/react@19.1.8)(react@19.1.0) - "@radix-ui/react-popper": 1.2.7(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) - "@radix-ui/react-portal": 1.1.9(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) - "@radix-ui/react-presence": 1.1.4(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) - "@radix-ui/react-primitive": 2.1.3(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) - "@radix-ui/react-slot": 1.2.3(@types/react@19.1.8)(react@19.1.0) - "@radix-ui/react-use-controllable-state": 1.2.2(@types/react@19.1.8)(react@19.1.0) + '@types/react': 19.1.8 + '@types/react-dom': 19.1.6(@types/react@19.1.8) + + '@radix-ui/react-popover@1.1.14(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)': + dependencies: + '@radix-ui/primitive': 1.1.2 + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.1.8)(react@19.1.0) + '@radix-ui/react-context': 1.1.2(@types/react@19.1.8)(react@19.1.0) + '@radix-ui/react-dismissable-layer': 1.1.10(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + '@radix-ui/react-focus-guards': 1.1.2(@types/react@19.1.8)(react@19.1.0) + '@radix-ui/react-focus-scope': 1.1.7(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + '@radix-ui/react-id': 1.1.1(@types/react@19.1.8)(react@19.1.0) + '@radix-ui/react-popper': 1.2.7(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + '@radix-ui/react-portal': 1.1.9(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + '@radix-ui/react-presence': 1.1.4(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + '@radix-ui/react-slot': 1.2.3(@types/react@19.1.8)(react@19.1.0) + '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.1.8)(react@19.1.0) aria-hidden: 1.2.6 react: 19.1.0 react-dom: 19.1.0(react@19.1.0) react-remove-scroll: 2.7.1(@types/react@19.1.8)(react@19.1.0) optionalDependencies: - "@types/react": 19.1.8 - "@types/react-dom": 19.1.6(@types/react@19.1.8) - - "@radix-ui/react-popper@1.2.7(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)": - dependencies: - "@floating-ui/react-dom": 2.1.4(react-dom@19.1.0(react@19.1.0))(react@19.1.0) - "@radix-ui/react-arrow": 1.1.7(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) - "@radix-ui/react-compose-refs": 1.1.2(@types/react@19.1.8)(react@19.1.0) - "@radix-ui/react-context": 1.1.2(@types/react@19.1.8)(react@19.1.0) - "@radix-ui/react-primitive": 2.1.3(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) - "@radix-ui/react-use-callback-ref": 1.1.1(@types/react@19.1.8)(react@19.1.0) - "@radix-ui/react-use-layout-effect": 1.1.1(@types/react@19.1.8)(react@19.1.0) - "@radix-ui/react-use-rect": 1.1.1(@types/react@19.1.8)(react@19.1.0) - "@radix-ui/react-use-size": 1.1.1(@types/react@19.1.8)(react@19.1.0) - "@radix-ui/rect": 1.1.1 + '@types/react': 19.1.8 + '@types/react-dom': 19.1.6(@types/react@19.1.8) + + '@radix-ui/react-popper@1.2.7(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)': + dependencies: + '@floating-ui/react-dom': 2.1.4(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + '@radix-ui/react-arrow': 1.1.7(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.1.8)(react@19.1.0) + '@radix-ui/react-context': 1.1.2(@types/react@19.1.8)(react@19.1.0) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@19.1.8)(react@19.1.0) + '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.1.8)(react@19.1.0) + '@radix-ui/react-use-rect': 1.1.1(@types/react@19.1.8)(react@19.1.0) + '@radix-ui/react-use-size': 1.1.1(@types/react@19.1.8)(react@19.1.0) + '@radix-ui/rect': 1.1.1 + react: 19.1.0 + react-dom: 19.1.0(react@19.1.0) + optionalDependencies: + '@types/react': 19.1.8 + '@types/react-dom': 19.1.6(@types/react@19.1.8) + + '@radix-ui/react-portal@1.1.9(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)': + dependencies: + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.1.8)(react@19.1.0) react: 19.1.0 react-dom: 19.1.0(react@19.1.0) optionalDependencies: - "@types/react": 19.1.8 - "@types/react-dom": 19.1.6(@types/react@19.1.8) + '@types/react': 19.1.8 + '@types/react-dom': 19.1.6(@types/react@19.1.8) - "@radix-ui/react-portal@1.1.9(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)": + '@radix-ui/react-presence@1.1.4(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)': dependencies: - "@radix-ui/react-primitive": 2.1.3(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) - "@radix-ui/react-use-layout-effect": 1.1.1(@types/react@19.1.8)(react@19.1.0) + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.1.8)(react@19.1.0) + '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.1.8)(react@19.1.0) react: 19.1.0 react-dom: 19.1.0(react@19.1.0) optionalDependencies: - "@types/react": 19.1.8 - "@types/react-dom": 19.1.6(@types/react@19.1.8) + '@types/react': 19.1.8 + '@types/react-dom': 19.1.6(@types/react@19.1.8) - "@radix-ui/react-presence@1.1.4(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)": + '@radix-ui/react-presence@1.1.5(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)': dependencies: - "@radix-ui/react-compose-refs": 1.1.2(@types/react@19.1.8)(react@19.1.0) - "@radix-ui/react-use-layout-effect": 1.1.1(@types/react@19.1.8)(react@19.1.0) + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.1.8)(react@19.1.0) + '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.1.8)(react@19.1.0) react: 19.1.0 react-dom: 19.1.0(react@19.1.0) optionalDependencies: - "@types/react": 19.1.8 - "@types/react-dom": 19.1.6(@types/react@19.1.8) + '@types/react': 19.1.8 + '@types/react-dom': 19.1.6(@types/react@19.1.8) - "@radix-ui/react-primitive@2.1.3(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)": + '@radix-ui/react-primitive@2.1.3(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)': dependencies: - "@radix-ui/react-slot": 1.2.3(@types/react@19.1.8)(react@19.1.0) + '@radix-ui/react-slot': 1.2.3(@types/react@19.1.8)(react@19.1.0) react: 19.1.0 react-dom: 19.1.0(react@19.1.0) optionalDependencies: - "@types/react": 19.1.8 - "@types/react-dom": 19.1.6(@types/react@19.1.8) - - "@radix-ui/react-roving-focus@1.1.10(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)": - dependencies: - "@radix-ui/primitive": 1.1.2 - "@radix-ui/react-collection": 1.1.7(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) - "@radix-ui/react-compose-refs": 1.1.2(@types/react@19.1.8)(react@19.1.0) - "@radix-ui/react-context": 1.1.2(@types/react@19.1.8)(react@19.1.0) - "@radix-ui/react-direction": 1.1.1(@types/react@19.1.8)(react@19.1.0) - "@radix-ui/react-id": 1.1.1(@types/react@19.1.8)(react@19.1.0) - "@radix-ui/react-primitive": 2.1.3(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) - "@radix-ui/react-use-callback-ref": 1.1.1(@types/react@19.1.8)(react@19.1.0) - "@radix-ui/react-use-controllable-state": 1.2.2(@types/react@19.1.8)(react@19.1.0) + '@types/react': 19.1.8 + '@types/react-dom': 19.1.6(@types/react@19.1.8) + + '@radix-ui/react-roving-focus@1.1.10(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)': + dependencies: + '@radix-ui/primitive': 1.1.2 + '@radix-ui/react-collection': 1.1.7(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.1.8)(react@19.1.0) + '@radix-ui/react-context': 1.1.2(@types/react@19.1.8)(react@19.1.0) + '@radix-ui/react-direction': 1.1.1(@types/react@19.1.8)(react@19.1.0) + '@radix-ui/react-id': 1.1.1(@types/react@19.1.8)(react@19.1.0) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@19.1.8)(react@19.1.0) + '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.1.8)(react@19.1.0) react: 19.1.0 react-dom: 19.1.0(react@19.1.0) optionalDependencies: - "@types/react": 19.1.8 - "@types/react-dom": 19.1.6(@types/react@19.1.8) - - "@radix-ui/react-scroll-area@1.2.9(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)": - dependencies: - "@radix-ui/number": 1.1.1 - "@radix-ui/primitive": 1.1.2 - "@radix-ui/react-compose-refs": 1.1.2(@types/react@19.1.8)(react@19.1.0) - "@radix-ui/react-context": 1.1.2(@types/react@19.1.8)(react@19.1.0) - "@radix-ui/react-direction": 1.1.1(@types/react@19.1.8)(react@19.1.0) - "@radix-ui/react-presence": 1.1.4(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) - "@radix-ui/react-primitive": 2.1.3(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) - "@radix-ui/react-use-callback-ref": 1.1.1(@types/react@19.1.8)(react@19.1.0) - "@radix-ui/react-use-layout-effect": 1.1.1(@types/react@19.1.8)(react@19.1.0) + '@types/react': 19.1.8 + '@types/react-dom': 19.1.6(@types/react@19.1.8) + + '@radix-ui/react-scroll-area@1.2.9(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)': + dependencies: + '@radix-ui/number': 1.1.1 + '@radix-ui/primitive': 1.1.2 + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.1.8)(react@19.1.0) + '@radix-ui/react-context': 1.1.2(@types/react@19.1.8)(react@19.1.0) + '@radix-ui/react-direction': 1.1.1(@types/react@19.1.8)(react@19.1.0) + '@radix-ui/react-presence': 1.1.4(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@19.1.8)(react@19.1.0) + '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.1.8)(react@19.1.0) react: 19.1.0 react-dom: 19.1.0(react@19.1.0) optionalDependencies: - "@types/react": 19.1.8 - "@types/react-dom": 19.1.6(@types/react@19.1.8) + '@types/react': 19.1.8 + '@types/react-dom': 19.1.6(@types/react@19.1.8) - "@radix-ui/react-slot@1.2.3(@types/react@19.1.8)(react@19.1.0)": + '@radix-ui/react-slot@1.2.3(@types/react@19.1.8)(react@19.1.0)': dependencies: - "@radix-ui/react-compose-refs": 1.1.2(@types/react@19.1.8)(react@19.1.0) + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.1.8)(react@19.1.0) react: 19.1.0 optionalDependencies: - "@types/react": 19.1.8 - - "@radix-ui/react-tabs@1.1.12(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)": - dependencies: - "@radix-ui/primitive": 1.1.2 - "@radix-ui/react-context": 1.1.2(@types/react@19.1.8)(react@19.1.0) - "@radix-ui/react-direction": 1.1.1(@types/react@19.1.8)(react@19.1.0) - "@radix-ui/react-id": 1.1.1(@types/react@19.1.8)(react@19.1.0) - "@radix-ui/react-presence": 1.1.4(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) - "@radix-ui/react-primitive": 2.1.3(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) - "@radix-ui/react-roving-focus": 1.1.10(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) - "@radix-ui/react-use-controllable-state": 1.2.2(@types/react@19.1.8)(react@19.1.0) + '@types/react': 19.1.8 + + '@radix-ui/react-tabs@1.1.12(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)': + dependencies: + '@radix-ui/primitive': 1.1.2 + '@radix-ui/react-context': 1.1.2(@types/react@19.1.8)(react@19.1.0) + '@radix-ui/react-direction': 1.1.1(@types/react@19.1.8)(react@19.1.0) + '@radix-ui/react-id': 1.1.1(@types/react@19.1.8)(react@19.1.0) + '@radix-ui/react-presence': 1.1.4(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + '@radix-ui/react-roving-focus': 1.1.10(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.1.8)(react@19.1.0) react: 19.1.0 react-dom: 19.1.0(react@19.1.0) optionalDependencies: - "@types/react": 19.1.8 - "@types/react-dom": 19.1.6(@types/react@19.1.8) + '@types/react': 19.1.8 + '@types/react-dom': 19.1.6(@types/react@19.1.8) - "@radix-ui/react-use-callback-ref@1.1.1(@types/react@19.1.8)(react@19.1.0)": + '@radix-ui/react-use-callback-ref@1.1.1(@types/react@19.1.8)(react@19.1.0)': dependencies: react: 19.1.0 optionalDependencies: - "@types/react": 19.1.8 + '@types/react': 19.1.8 - "@radix-ui/react-use-controllable-state@1.2.2(@types/react@19.1.8)(react@19.1.0)": + '@radix-ui/react-use-controllable-state@1.2.2(@types/react@19.1.8)(react@19.1.0)': dependencies: - "@radix-ui/react-use-effect-event": 0.0.2(@types/react@19.1.8)(react@19.1.0) - "@radix-ui/react-use-layout-effect": 1.1.1(@types/react@19.1.8)(react@19.1.0) + '@radix-ui/react-use-effect-event': 0.0.2(@types/react@19.1.8)(react@19.1.0) + '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.1.8)(react@19.1.0) react: 19.1.0 optionalDependencies: - "@types/react": 19.1.8 + '@types/react': 19.1.8 - "@radix-ui/react-use-effect-event@0.0.2(@types/react@19.1.8)(react@19.1.0)": + '@radix-ui/react-use-effect-event@0.0.2(@types/react@19.1.8)(react@19.1.0)': dependencies: - "@radix-ui/react-use-layout-effect": 1.1.1(@types/react@19.1.8)(react@19.1.0) + '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.1.8)(react@19.1.0) react: 19.1.0 optionalDependencies: - "@types/react": 19.1.8 + '@types/react': 19.1.8 - "@radix-ui/react-use-escape-keydown@1.1.1(@types/react@19.1.8)(react@19.1.0)": + '@radix-ui/react-use-escape-keydown@1.1.1(@types/react@19.1.8)(react@19.1.0)': dependencies: - "@radix-ui/react-use-callback-ref": 1.1.1(@types/react@19.1.8)(react@19.1.0) + '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@19.1.8)(react@19.1.0) react: 19.1.0 optionalDependencies: - "@types/react": 19.1.8 + '@types/react': 19.1.8 - "@radix-ui/react-use-layout-effect@1.1.1(@types/react@19.1.8)(react@19.1.0)": + '@radix-ui/react-use-layout-effect@1.1.1(@types/react@19.1.8)(react@19.1.0)': dependencies: react: 19.1.0 optionalDependencies: - "@types/react": 19.1.8 + '@types/react': 19.1.8 - "@radix-ui/react-use-previous@1.1.1(@types/react@19.1.8)(react@19.1.0)": + '@radix-ui/react-use-previous@1.1.1(@types/react@19.1.8)(react@19.1.0)': dependencies: react: 19.1.0 optionalDependencies: - "@types/react": 19.1.8 + '@types/react': 19.1.8 - "@radix-ui/react-use-rect@1.1.1(@types/react@19.1.8)(react@19.1.0)": + '@radix-ui/react-use-rect@1.1.1(@types/react@19.1.8)(react@19.1.0)': dependencies: - "@radix-ui/rect": 1.1.1 + '@radix-ui/rect': 1.1.1 react: 19.1.0 optionalDependencies: - "@types/react": 19.1.8 + '@types/react': 19.1.8 - "@radix-ui/react-use-size@1.1.1(@types/react@19.1.8)(react@19.1.0)": + '@radix-ui/react-use-size@1.1.1(@types/react@19.1.8)(react@19.1.0)': dependencies: - "@radix-ui/react-use-layout-effect": 1.1.1(@types/react@19.1.8)(react@19.1.0) + '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.1.8)(react@19.1.0) react: 19.1.0 optionalDependencies: - "@types/react": 19.1.8 + '@types/react': 19.1.8 - "@radix-ui/react-visually-hidden@1.2.3(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)": + '@radix-ui/react-visually-hidden@1.2.3(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)': dependencies: - "@radix-ui/react-primitive": 2.1.3(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) react: 19.1.0 react-dom: 19.1.0(react@19.1.0) optionalDependencies: - "@types/react": 19.1.8 - "@types/react-dom": 19.1.6(@types/react@19.1.8) + '@types/react': 19.1.8 + '@types/react-dom': 19.1.6(@types/react@19.1.8) - "@radix-ui/rect@1.1.1": {} + '@radix-ui/rect@1.1.1': {} - "@rtsao/scc@1.1.0": {} + '@rtsao/scc@1.1.0': {} - "@rushstack/eslint-patch@1.12.0": {} + '@rushstack/eslint-patch@1.12.0': {} - "@shikijs/core@3.7.0": + '@shikijs/core@3.7.0': dependencies: - "@shikijs/types": 3.7.0 - "@shikijs/vscode-textmate": 10.0.2 - "@types/hast": 3.0.4 + '@shikijs/types': 3.7.0 + '@shikijs/vscode-textmate': 10.0.2 + '@types/hast': 3.0.4 hast-util-to-html: 9.0.5 - "@shikijs/engine-javascript@3.7.0": + '@shikijs/engine-javascript@3.7.0': dependencies: - "@shikijs/types": 3.7.0 - "@shikijs/vscode-textmate": 10.0.2 + '@shikijs/types': 3.7.0 + '@shikijs/vscode-textmate': 10.0.2 oniguruma-to-es: 4.3.3 - "@shikijs/engine-oniguruma@3.7.0": + '@shikijs/engine-oniguruma@3.7.0': dependencies: - "@shikijs/types": 3.7.0 - "@shikijs/vscode-textmate": 10.0.2 + '@shikijs/types': 3.7.0 + '@shikijs/vscode-textmate': 10.0.2 - "@shikijs/langs@3.7.0": + '@shikijs/langs@3.7.0': dependencies: - "@shikijs/types": 3.7.0 + '@shikijs/types': 3.7.0 - "@shikijs/rehype@3.7.0": + '@shikijs/rehype@3.7.0': dependencies: - "@shikijs/types": 3.7.0 - "@types/hast": 3.0.4 + '@shikijs/types': 3.7.0 + '@types/hast': 3.0.4 hast-util-to-string: 3.0.1 shiki: 3.7.0 unified: 11.0.5 unist-util-visit: 5.0.0 - "@shikijs/themes@3.7.0": + '@shikijs/themes@3.7.0': dependencies: - "@shikijs/types": 3.7.0 + '@shikijs/types': 3.7.0 - "@shikijs/transformers@3.7.0": + '@shikijs/transformers@3.7.0': dependencies: - "@shikijs/core": 3.7.0 - "@shikijs/types": 3.7.0 + '@shikijs/core': 3.7.0 + '@shikijs/types': 3.7.0 - "@shikijs/types@3.7.0": + '@shikijs/types@3.7.0': dependencies: - "@shikijs/vscode-textmate": 10.0.2 - "@types/hast": 3.0.4 + '@shikijs/vscode-textmate': 10.0.2 + '@types/hast': 3.0.4 - "@shikijs/vscode-textmate@10.0.2": {} + '@shikijs/vscode-textmate@10.0.2': {} - "@standard-schema/spec@1.0.0": {} + '@standard-schema/spec@1.0.0': {} - "@swc/counter@0.1.3": {} + '@swc/counter@0.1.3': {} - "@swc/helpers@0.5.15": + '@swc/helpers@0.5.15': dependencies: tslib: 2.8.1 - "@tailwindcss/node@4.1.11": + '@tailwindcss/node@4.1.11': dependencies: - "@ampproject/remapping": 2.3.0 + '@ampproject/remapping': 2.3.0 enhanced-resolve: 5.18.2 jiti: 2.4.2 lightningcss: 1.30.1 @@ -4668,123 +4076,123 @@ snapshots: source-map-js: 1.2.1 tailwindcss: 4.1.11 - "@tailwindcss/oxide-android-arm64@4.1.11": + '@tailwindcss/oxide-android-arm64@4.1.11': optional: true - "@tailwindcss/oxide-darwin-arm64@4.1.11": + '@tailwindcss/oxide-darwin-arm64@4.1.11': optional: true - "@tailwindcss/oxide-darwin-x64@4.1.11": + '@tailwindcss/oxide-darwin-x64@4.1.11': optional: true - "@tailwindcss/oxide-freebsd-x64@4.1.11": + '@tailwindcss/oxide-freebsd-x64@4.1.11': optional: true - "@tailwindcss/oxide-linux-arm-gnueabihf@4.1.11": + '@tailwindcss/oxide-linux-arm-gnueabihf@4.1.11': optional: true - "@tailwindcss/oxide-linux-arm64-gnu@4.1.11": + '@tailwindcss/oxide-linux-arm64-gnu@4.1.11': optional: true - "@tailwindcss/oxide-linux-arm64-musl@4.1.11": + '@tailwindcss/oxide-linux-arm64-musl@4.1.11': optional: true - "@tailwindcss/oxide-linux-x64-gnu@4.1.11": + '@tailwindcss/oxide-linux-x64-gnu@4.1.11': optional: true - "@tailwindcss/oxide-linux-x64-musl@4.1.11": + '@tailwindcss/oxide-linux-x64-musl@4.1.11': optional: true - "@tailwindcss/oxide-wasm32-wasi@4.1.11": + '@tailwindcss/oxide-wasm32-wasi@4.1.11': optional: true - "@tailwindcss/oxide-win32-arm64-msvc@4.1.11": + '@tailwindcss/oxide-win32-arm64-msvc@4.1.11': optional: true - "@tailwindcss/oxide-win32-x64-msvc@4.1.11": + '@tailwindcss/oxide-win32-x64-msvc@4.1.11': optional: true - "@tailwindcss/oxide@4.1.11": + '@tailwindcss/oxide@4.1.11': dependencies: detect-libc: 2.0.4 tar: 7.4.3 optionalDependencies: - "@tailwindcss/oxide-android-arm64": 4.1.11 - "@tailwindcss/oxide-darwin-arm64": 4.1.11 - "@tailwindcss/oxide-darwin-x64": 4.1.11 - "@tailwindcss/oxide-freebsd-x64": 4.1.11 - "@tailwindcss/oxide-linux-arm-gnueabihf": 4.1.11 - "@tailwindcss/oxide-linux-arm64-gnu": 4.1.11 - "@tailwindcss/oxide-linux-arm64-musl": 4.1.11 - "@tailwindcss/oxide-linux-x64-gnu": 4.1.11 - "@tailwindcss/oxide-linux-x64-musl": 4.1.11 - "@tailwindcss/oxide-wasm32-wasi": 4.1.11 - "@tailwindcss/oxide-win32-arm64-msvc": 4.1.11 - "@tailwindcss/oxide-win32-x64-msvc": 4.1.11 - - "@tailwindcss/postcss@4.1.11": - dependencies: - "@alloc/quick-lru": 5.2.0 - "@tailwindcss/node": 4.1.11 - "@tailwindcss/oxide": 4.1.11 + '@tailwindcss/oxide-android-arm64': 4.1.11 + '@tailwindcss/oxide-darwin-arm64': 4.1.11 + '@tailwindcss/oxide-darwin-x64': 4.1.11 + '@tailwindcss/oxide-freebsd-x64': 4.1.11 + '@tailwindcss/oxide-linux-arm-gnueabihf': 4.1.11 + '@tailwindcss/oxide-linux-arm64-gnu': 4.1.11 + '@tailwindcss/oxide-linux-arm64-musl': 4.1.11 + '@tailwindcss/oxide-linux-x64-gnu': 4.1.11 + '@tailwindcss/oxide-linux-x64-musl': 4.1.11 + '@tailwindcss/oxide-wasm32-wasi': 4.1.11 + '@tailwindcss/oxide-win32-arm64-msvc': 4.1.11 + '@tailwindcss/oxide-win32-x64-msvc': 4.1.11 + + '@tailwindcss/postcss@4.1.11': + dependencies: + '@alloc/quick-lru': 5.2.0 + '@tailwindcss/node': 4.1.11 + '@tailwindcss/oxide': 4.1.11 postcss: 8.5.6 tailwindcss: 4.1.11 - "@tybys/wasm-util@0.9.0": + '@tybys/wasm-util@0.9.0': dependencies: tslib: 2.8.1 optional: true - "@types/debug@4.1.12": + '@types/debug@4.1.12': dependencies: - "@types/ms": 2.1.0 + '@types/ms': 2.1.0 - "@types/estree-jsx@1.0.5": + '@types/estree-jsx@1.0.5': dependencies: - "@types/estree": 1.0.8 + '@types/estree': 1.0.8 - "@types/estree@1.0.8": {} + '@types/estree@1.0.8': {} - "@types/hast@3.0.4": + '@types/hast@3.0.4': dependencies: - "@types/unist": 3.0.3 + '@types/unist': 3.0.3 - "@types/json-schema@7.0.15": {} + '@types/json-schema@7.0.15': {} - "@types/json5@0.0.29": {} + '@types/json5@0.0.29': {} - "@types/mdast@4.0.4": + '@types/mdast@4.0.4': dependencies: - "@types/unist": 3.0.3 + '@types/unist': 3.0.3 - "@types/mdx@2.0.13": {} + '@types/mdx@2.0.13': {} - "@types/ms@2.1.0": {} + '@types/ms@2.1.0': {} - "@types/node@22.14.0": + '@types/node@22.14.0': dependencies: undici-types: 6.21.0 - "@types/react-dom@19.1.6(@types/react@19.1.8)": + '@types/react-dom@19.1.6(@types/react@19.1.8)': dependencies: - "@types/react": 19.1.8 + '@types/react': 19.1.8 - "@types/react@19.1.8": + '@types/react@19.1.8': dependencies: csstype: 3.1.3 - "@types/unist@2.0.11": {} + '@types/unist@2.0.11': {} - "@types/unist@3.0.3": {} + '@types/unist@3.0.3': {} - "@typescript-eslint/eslint-plugin@8.35.1(@typescript-eslint/parser@8.35.1(eslint@9.30.0(jiti@2.4.2))(typescript@5.8.3))(eslint@9.30.0(jiti@2.4.2))(typescript@5.8.3)": + '@typescript-eslint/eslint-plugin@8.35.1(@typescript-eslint/parser@8.35.1(eslint@9.30.0(jiti@2.4.2))(typescript@5.8.3))(eslint@9.30.0(jiti@2.4.2))(typescript@5.8.3)': dependencies: - "@eslint-community/regexpp": 4.12.1 - "@typescript-eslint/parser": 8.35.1(eslint@9.30.0(jiti@2.4.2))(typescript@5.8.3) - "@typescript-eslint/scope-manager": 8.35.1 - "@typescript-eslint/type-utils": 8.35.1(eslint@9.30.0(jiti@2.4.2))(typescript@5.8.3) - "@typescript-eslint/utils": 8.35.1(eslint@9.30.0(jiti@2.4.2))(typescript@5.8.3) - "@typescript-eslint/visitor-keys": 8.35.1 + '@eslint-community/regexpp': 4.12.1 + '@typescript-eslint/parser': 8.35.1(eslint@9.30.0(jiti@2.4.2))(typescript@5.8.3) + '@typescript-eslint/scope-manager': 8.35.1 + '@typescript-eslint/type-utils': 8.35.1(eslint@9.30.0(jiti@2.4.2))(typescript@5.8.3) + '@typescript-eslint/utils': 8.35.1(eslint@9.30.0(jiti@2.4.2))(typescript@5.8.3) + '@typescript-eslint/visitor-keys': 8.35.1 eslint: 9.30.0(jiti@2.4.2) graphemer: 1.4.0 ignore: 7.0.5 @@ -4794,40 +4202,40 @@ snapshots: transitivePeerDependencies: - supports-color - "@typescript-eslint/parser@8.35.1(eslint@9.30.0(jiti@2.4.2))(typescript@5.8.3)": + '@typescript-eslint/parser@8.35.1(eslint@9.30.0(jiti@2.4.2))(typescript@5.8.3)': dependencies: - "@typescript-eslint/scope-manager": 8.35.1 - "@typescript-eslint/types": 8.35.1 - "@typescript-eslint/typescript-estree": 8.35.1(typescript@5.8.3) - "@typescript-eslint/visitor-keys": 8.35.1 + '@typescript-eslint/scope-manager': 8.35.1 + '@typescript-eslint/types': 8.35.1 + '@typescript-eslint/typescript-estree': 8.35.1(typescript@5.8.3) + '@typescript-eslint/visitor-keys': 8.35.1 debug: 4.4.1 eslint: 9.30.0(jiti@2.4.2) typescript: 5.8.3 transitivePeerDependencies: - supports-color - "@typescript-eslint/project-service@8.35.1(typescript@5.8.3)": + '@typescript-eslint/project-service@8.35.1(typescript@5.8.3)': dependencies: - "@typescript-eslint/tsconfig-utils": 8.35.1(typescript@5.8.3) - "@typescript-eslint/types": 8.35.1 + '@typescript-eslint/tsconfig-utils': 8.35.1(typescript@5.8.3) + '@typescript-eslint/types': 8.35.1 debug: 4.4.1 typescript: 5.8.3 transitivePeerDependencies: - supports-color - "@typescript-eslint/scope-manager@8.35.1": + '@typescript-eslint/scope-manager@8.35.1': dependencies: - "@typescript-eslint/types": 8.35.1 - "@typescript-eslint/visitor-keys": 8.35.1 + '@typescript-eslint/types': 8.35.1 + '@typescript-eslint/visitor-keys': 8.35.1 - "@typescript-eslint/tsconfig-utils@8.35.1(typescript@5.8.3)": + '@typescript-eslint/tsconfig-utils@8.35.1(typescript@5.8.3)': dependencies: typescript: 5.8.3 - "@typescript-eslint/type-utils@8.35.1(eslint@9.30.0(jiti@2.4.2))(typescript@5.8.3)": + '@typescript-eslint/type-utils@8.35.1(eslint@9.30.0(jiti@2.4.2))(typescript@5.8.3)': dependencies: - "@typescript-eslint/typescript-estree": 8.35.1(typescript@5.8.3) - "@typescript-eslint/utils": 8.35.1(eslint@9.30.0(jiti@2.4.2))(typescript@5.8.3) + '@typescript-eslint/typescript-estree': 8.35.1(typescript@5.8.3) + '@typescript-eslint/utils': 8.35.1(eslint@9.30.0(jiti@2.4.2))(typescript@5.8.3) debug: 4.4.1 eslint: 9.30.0(jiti@2.4.2) ts-api-utils: 2.1.0(typescript@5.8.3) @@ -4835,14 +4243,14 @@ snapshots: transitivePeerDependencies: - supports-color - "@typescript-eslint/types@8.35.1": {} + '@typescript-eslint/types@8.35.1': {} - "@typescript-eslint/typescript-estree@8.35.1(typescript@5.8.3)": + '@typescript-eslint/typescript-estree@8.35.1(typescript@5.8.3)': dependencies: - "@typescript-eslint/project-service": 8.35.1(typescript@5.8.3) - "@typescript-eslint/tsconfig-utils": 8.35.1(typescript@5.8.3) - "@typescript-eslint/types": 8.35.1 - "@typescript-eslint/visitor-keys": 8.35.1 + '@typescript-eslint/project-service': 8.35.1(typescript@5.8.3) + '@typescript-eslint/tsconfig-utils': 8.35.1(typescript@5.8.3) + '@typescript-eslint/types': 8.35.1 + '@typescript-eslint/visitor-keys': 8.35.1 debug: 4.4.1 fast-glob: 3.3.3 is-glob: 4.0.3 @@ -4853,81 +4261,81 @@ snapshots: transitivePeerDependencies: - supports-color - "@typescript-eslint/utils@8.35.1(eslint@9.30.0(jiti@2.4.2))(typescript@5.8.3)": + '@typescript-eslint/utils@8.35.1(eslint@9.30.0(jiti@2.4.2))(typescript@5.8.3)': dependencies: - "@eslint-community/eslint-utils": 4.7.0(eslint@9.30.0(jiti@2.4.2)) - "@typescript-eslint/scope-manager": 8.35.1 - "@typescript-eslint/types": 8.35.1 - "@typescript-eslint/typescript-estree": 8.35.1(typescript@5.8.3) + '@eslint-community/eslint-utils': 4.7.0(eslint@9.30.0(jiti@2.4.2)) + '@typescript-eslint/scope-manager': 8.35.1 + '@typescript-eslint/types': 8.35.1 + '@typescript-eslint/typescript-estree': 8.35.1(typescript@5.8.3) eslint: 9.30.0(jiti@2.4.2) typescript: 5.8.3 transitivePeerDependencies: - supports-color - "@typescript-eslint/visitor-keys@8.35.1": + '@typescript-eslint/visitor-keys@8.35.1': dependencies: - "@typescript-eslint/types": 8.35.1 + '@typescript-eslint/types': 8.35.1 eslint-visitor-keys: 4.2.1 - "@ungap/structured-clone@1.3.0": {} + '@ungap/structured-clone@1.3.0': {} - "@unrs/resolver-binding-android-arm-eabi@1.11.0": + '@unrs/resolver-binding-android-arm-eabi@1.11.0': optional: true - "@unrs/resolver-binding-android-arm64@1.11.0": + '@unrs/resolver-binding-android-arm64@1.11.0': optional: true - "@unrs/resolver-binding-darwin-arm64@1.11.0": + '@unrs/resolver-binding-darwin-arm64@1.11.0': optional: true - "@unrs/resolver-binding-darwin-x64@1.11.0": + '@unrs/resolver-binding-darwin-x64@1.11.0': optional: true - "@unrs/resolver-binding-freebsd-x64@1.11.0": + '@unrs/resolver-binding-freebsd-x64@1.11.0': optional: true - "@unrs/resolver-binding-linux-arm-gnueabihf@1.11.0": + '@unrs/resolver-binding-linux-arm-gnueabihf@1.11.0': optional: true - "@unrs/resolver-binding-linux-arm-musleabihf@1.11.0": + '@unrs/resolver-binding-linux-arm-musleabihf@1.11.0': optional: true - "@unrs/resolver-binding-linux-arm64-gnu@1.11.0": + '@unrs/resolver-binding-linux-arm64-gnu@1.11.0': optional: true - "@unrs/resolver-binding-linux-arm64-musl@1.11.0": + '@unrs/resolver-binding-linux-arm64-musl@1.11.0': optional: true - "@unrs/resolver-binding-linux-ppc64-gnu@1.11.0": + '@unrs/resolver-binding-linux-ppc64-gnu@1.11.0': optional: true - "@unrs/resolver-binding-linux-riscv64-gnu@1.11.0": + '@unrs/resolver-binding-linux-riscv64-gnu@1.11.0': optional: true - "@unrs/resolver-binding-linux-riscv64-musl@1.11.0": + '@unrs/resolver-binding-linux-riscv64-musl@1.11.0': optional: true - "@unrs/resolver-binding-linux-s390x-gnu@1.11.0": + '@unrs/resolver-binding-linux-s390x-gnu@1.11.0': optional: true - "@unrs/resolver-binding-linux-x64-gnu@1.11.0": + '@unrs/resolver-binding-linux-x64-gnu@1.11.0': optional: true - "@unrs/resolver-binding-linux-x64-musl@1.11.0": + '@unrs/resolver-binding-linux-x64-musl@1.11.0': optional: true - "@unrs/resolver-binding-wasm32-wasi@1.11.0": + '@unrs/resolver-binding-wasm32-wasi@1.11.0': dependencies: - "@napi-rs/wasm-runtime": 0.2.11 + '@napi-rs/wasm-runtime': 0.2.11 optional: true - "@unrs/resolver-binding-win32-arm64-msvc@1.11.0": + '@unrs/resolver-binding-win32-arm64-msvc@1.11.0': optional: true - "@unrs/resolver-binding-win32-ia32-msvc@1.11.0": + '@unrs/resolver-binding-win32-ia32-msvc@1.11.0': optional: true - "@unrs/resolver-binding-win32-x64-msvc@1.11.0": + '@unrs/resolver-binding-win32-x64-msvc@1.11.0': optional: true acorn-jsx@5.3.2(acorn@8.15.0): @@ -5319,45 +4727,45 @@ snapshots: esast-util-from-estree@2.0.0: dependencies: - "@types/estree-jsx": 1.0.5 + '@types/estree-jsx': 1.0.5 devlop: 1.1.0 estree-util-visit: 2.0.0 unist-util-position-from-estree: 2.0.0 esast-util-from-js@2.0.1: dependencies: - "@types/estree-jsx": 1.0.5 + '@types/estree-jsx': 1.0.5 acorn: 8.15.0 esast-util-from-estree: 2.0.0 vfile-message: 4.0.2 esbuild@0.25.5: optionalDependencies: - "@esbuild/aix-ppc64": 0.25.5 - "@esbuild/android-arm": 0.25.5 - "@esbuild/android-arm64": 0.25.5 - "@esbuild/android-x64": 0.25.5 - "@esbuild/darwin-arm64": 0.25.5 - "@esbuild/darwin-x64": 0.25.5 - "@esbuild/freebsd-arm64": 0.25.5 - "@esbuild/freebsd-x64": 0.25.5 - "@esbuild/linux-arm": 0.25.5 - "@esbuild/linux-arm64": 0.25.5 - "@esbuild/linux-ia32": 0.25.5 - "@esbuild/linux-loong64": 0.25.5 - "@esbuild/linux-mips64el": 0.25.5 - "@esbuild/linux-ppc64": 0.25.5 - "@esbuild/linux-riscv64": 0.25.5 - "@esbuild/linux-s390x": 0.25.5 - "@esbuild/linux-x64": 0.25.5 - "@esbuild/netbsd-arm64": 0.25.5 - "@esbuild/netbsd-x64": 0.25.5 - "@esbuild/openbsd-arm64": 0.25.5 - "@esbuild/openbsd-x64": 0.25.5 - "@esbuild/sunos-x64": 0.25.5 - "@esbuild/win32-arm64": 0.25.5 - "@esbuild/win32-ia32": 0.25.5 - "@esbuild/win32-x64": 0.25.5 + '@esbuild/aix-ppc64': 0.25.5 + '@esbuild/android-arm': 0.25.5 + '@esbuild/android-arm64': 0.25.5 + '@esbuild/android-x64': 0.25.5 + '@esbuild/darwin-arm64': 0.25.5 + '@esbuild/darwin-x64': 0.25.5 + '@esbuild/freebsd-arm64': 0.25.5 + '@esbuild/freebsd-x64': 0.25.5 + '@esbuild/linux-arm': 0.25.5 + '@esbuild/linux-arm64': 0.25.5 + '@esbuild/linux-ia32': 0.25.5 + '@esbuild/linux-loong64': 0.25.5 + '@esbuild/linux-mips64el': 0.25.5 + '@esbuild/linux-ppc64': 0.25.5 + '@esbuild/linux-riscv64': 0.25.5 + '@esbuild/linux-s390x': 0.25.5 + '@esbuild/linux-x64': 0.25.5 + '@esbuild/netbsd-arm64': 0.25.5 + '@esbuild/netbsd-x64': 0.25.5 + '@esbuild/openbsd-arm64': 0.25.5 + '@esbuild/openbsd-x64': 0.25.5 + '@esbuild/sunos-x64': 0.25.5 + '@esbuild/win32-arm64': 0.25.5 + '@esbuild/win32-ia32': 0.25.5 + '@esbuild/win32-x64': 0.25.5 escape-string-regexp@4.0.0: {} @@ -5365,10 +4773,10 @@ snapshots: eslint-config-next@15.3.4(eslint@9.30.0(jiti@2.4.2))(typescript@5.8.3): dependencies: - "@next/eslint-plugin-next": 15.3.4 - "@rushstack/eslint-patch": 1.12.0 - "@typescript-eslint/eslint-plugin": 8.35.1(@typescript-eslint/parser@8.35.1(eslint@9.30.0(jiti@2.4.2))(typescript@5.8.3))(eslint@9.30.0(jiti@2.4.2))(typescript@5.8.3) - "@typescript-eslint/parser": 8.35.1(eslint@9.30.0(jiti@2.4.2))(typescript@5.8.3) + '@next/eslint-plugin-next': 15.3.4 + '@rushstack/eslint-patch': 1.12.0 + '@typescript-eslint/eslint-plugin': 8.35.1(@typescript-eslint/parser@8.35.1(eslint@9.30.0(jiti@2.4.2))(typescript@5.8.3))(eslint@9.30.0(jiti@2.4.2))(typescript@5.8.3) + '@typescript-eslint/parser': 8.35.1(eslint@9.30.0(jiti@2.4.2))(typescript@5.8.3) eslint: 9.30.0(jiti@2.4.2) eslint-import-resolver-node: 0.3.9 eslint-import-resolver-typescript: 3.10.1(eslint-plugin-import@2.32.0)(eslint@9.30.0(jiti@2.4.2)) @@ -5397,7 +4805,7 @@ snapshots: eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.32.0)(eslint@9.30.0(jiti@2.4.2)): dependencies: - "@nolyfill/is-core-module": 1.0.39 + '@nolyfill/is-core-module': 1.0.39 debug: 4.4.1 eslint: 9.30.0(jiti@2.4.2) get-tsconfig: 4.10.1 @@ -5414,7 +4822,7 @@ snapshots: dependencies: debug: 3.2.7 optionalDependencies: - "@typescript-eslint/parser": 8.35.1(eslint@9.30.0(jiti@2.4.2))(typescript@5.8.3) + '@typescript-eslint/parser': 8.35.1(eslint@9.30.0(jiti@2.4.2))(typescript@5.8.3) eslint: 9.30.0(jiti@2.4.2) eslint-import-resolver-node: 0.3.9 eslint-import-resolver-typescript: 3.10.1(eslint-plugin-import@2.32.0)(eslint@9.30.0(jiti@2.4.2)) @@ -5423,7 +4831,7 @@ snapshots: eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.35.1(eslint@9.30.0(jiti@2.4.2))(typescript@5.8.3))(eslint-import-resolver-typescript@3.10.1)(eslint@9.30.0(jiti@2.4.2)): dependencies: - "@rtsao/scc": 1.1.0 + '@rtsao/scc': 1.1.0 array-includes: 3.1.9 array.prototype.findlastindex: 1.2.6 array.prototype.flat: 1.3.3 @@ -5444,7 +4852,7 @@ snapshots: string.prototype.trimend: 1.0.9 tsconfig-paths: 3.15.0 optionalDependencies: - "@typescript-eslint/parser": 8.35.1(eslint@9.30.0(jiti@2.4.2))(typescript@5.8.3) + '@typescript-eslint/parser': 8.35.1(eslint@9.30.0(jiti@2.4.2))(typescript@5.8.3) transitivePeerDependencies: - eslint-import-resolver-typescript - eslint-import-resolver-webpack @@ -5515,19 +4923,19 @@ snapshots: eslint@9.30.0(jiti@2.4.2): dependencies: - "@eslint-community/eslint-utils": 4.7.0(eslint@9.30.0(jiti@2.4.2)) - "@eslint-community/regexpp": 4.12.1 - "@eslint/config-array": 0.21.0 - "@eslint/config-helpers": 0.3.0 - "@eslint/core": 0.14.0 - "@eslint/eslintrc": 3.3.1 - "@eslint/js": 9.30.0 - "@eslint/plugin-kit": 0.3.3 - "@humanfs/node": 0.16.6 - "@humanwhocodes/module-importer": 1.0.1 - "@humanwhocodes/retry": 0.4.3 - "@types/estree": 1.0.8 - "@types/json-schema": 7.0.15 + '@eslint-community/eslint-utils': 4.7.0(eslint@9.30.0(jiti@2.4.2)) + '@eslint-community/regexpp': 4.12.1 + '@eslint/config-array': 0.21.0 + '@eslint/config-helpers': 0.3.0 + '@eslint/core': 0.14.0 + '@eslint/eslintrc': 3.3.1 + '@eslint/js': 9.30.0 + '@eslint/plugin-kit': 0.3.3 + '@humanfs/node': 0.16.6 + '@humanwhocodes/module-importer': 1.0.1 + '@humanwhocodes/retry': 0.4.3 + '@types/estree': 1.0.8 + '@types/json-schema': 7.0.15 ajv: 6.12.6 chalk: 4.1.2 cross-spawn: 7.0.6 @@ -5573,11 +4981,11 @@ snapshots: estree-util-attach-comments@3.0.0: dependencies: - "@types/estree": 1.0.8 + '@types/estree': 1.0.8 estree-util-build-jsx@3.0.1: dependencies: - "@types/estree-jsx": 1.0.5 + '@types/estree-jsx': 1.0.5 devlop: 1.1.0 estree-util-is-identifier-name: 3.0.0 estree-walker: 3.0.3 @@ -5586,27 +4994,27 @@ snapshots: estree-util-scope@1.0.0: dependencies: - "@types/estree": 1.0.8 + '@types/estree': 1.0.8 devlop: 1.1.0 estree-util-to-js@2.0.0: dependencies: - "@types/estree-jsx": 1.0.5 + '@types/estree-jsx': 1.0.5 astring: 1.9.0 source-map: 0.7.4 estree-util-value-to-estree@3.4.0: dependencies: - "@types/estree": 1.0.8 + '@types/estree': 1.0.8 estree-util-visit@2.0.0: dependencies: - "@types/estree-jsx": 1.0.5 - "@types/unist": 3.0.3 + '@types/estree-jsx': 1.0.5 + '@types/unist': 3.0.3 estree-walker@3.0.3: dependencies: - "@types/estree": 1.0.8 + '@types/estree': 1.0.8 esutils@2.0.3: {} @@ -5618,16 +5026,16 @@ snapshots: fast-glob@3.3.1: dependencies: - "@nodelib/fs.stat": 2.0.5 - "@nodelib/fs.walk": 1.2.8 + '@nodelib/fs.stat': 2.0.5 + '@nodelib/fs.walk': 1.2.8 glob-parent: 5.1.2 merge2: 1.4.1 micromatch: 4.0.8 fast-glob@3.3.3: dependencies: - "@nodelib/fs.stat": 2.0.5 - "@nodelib/fs.walk": 1.2.8 + '@nodelib/fs.stat': 2.0.5 + '@nodelib/fs.walk': 1.2.8 glob-parent: 5.1.2 merge2: 1.4.1 micromatch: 4.0.8 @@ -5679,10 +5087,10 @@ snapshots: fumadocs-core@15.2.7(@types/react@19.1.8)(next@15.3.4(react-dom@19.1.0(react@19.1.0))(react@19.1.0))(react-dom@19.1.0(react@19.1.0))(react@19.1.0): dependencies: - "@formatjs/intl-localematcher": 0.6.1 - "@orama/orama": 3.1.10 - "@shikijs/rehype": 3.7.0 - "@shikijs/transformers": 3.7.0 + '@formatjs/intl-localematcher': 0.6.1 + '@orama/orama': 3.1.10 + '@shikijs/rehype': 3.7.0 + '@shikijs/transformers': 3.7.0 github-slugger: 2.0.0 hast-util-to-estree: 3.1.3 hast-util-to-jsx-runtime: 2.3.6 @@ -5699,13 +5107,13 @@ snapshots: react: 19.1.0 react-dom: 19.1.0(react@19.1.0) transitivePeerDependencies: - - "@types/react" + - '@types/react' - supports-color fumadocs-mdx@11.6.10(acorn@8.15.0)(fumadocs-core@15.2.7(@types/react@19.1.8)(next@15.3.4(react-dom@19.1.0(react@19.1.0))(react@19.1.0))(react-dom@19.1.0(react@19.1.0))(react@19.1.0))(next@15.3.4(react-dom@19.1.0(react@19.1.0))(react@19.1.0)): dependencies: - "@mdx-js/mdx": 3.1.0(acorn@8.15.0) - "@standard-schema/spec": 1.0.0 + '@mdx-js/mdx': 3.1.0(acorn@8.15.0) + '@standard-schema/spec': 1.0.0 chokidar: 4.0.3 esbuild: 0.25.5 estree-util-value-to-estree: 3.4.0 @@ -5725,15 +5133,15 @@ snapshots: fumadocs-ui@15.2.7(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(next@15.3.4(react-dom@19.1.0(react@19.1.0))(react@19.1.0))(react-dom@19.1.0(react@19.1.0))(react@19.1.0)(tailwindcss@4.1.11): dependencies: - "@radix-ui/react-accordion": 1.2.11(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) - "@radix-ui/react-collapsible": 1.1.11(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) - "@radix-ui/react-dialog": 1.1.14(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) - "@radix-ui/react-direction": 1.1.1(@types/react@19.1.8)(react@19.1.0) - "@radix-ui/react-navigation-menu": 1.2.13(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) - "@radix-ui/react-popover": 1.1.14(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) - "@radix-ui/react-scroll-area": 1.2.9(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) - "@radix-ui/react-slot": 1.2.3(@types/react@19.1.8)(react@19.1.0) - "@radix-ui/react-tabs": 1.1.12(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + '@radix-ui/react-accordion': 1.2.11(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + '@radix-ui/react-collapsible': 1.1.11(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + '@radix-ui/react-dialog': 1.1.15(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + '@radix-ui/react-direction': 1.1.1(@types/react@19.1.8)(react@19.1.0) + '@radix-ui/react-navigation-menu': 1.2.13(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + '@radix-ui/react-popover': 1.1.14(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + '@radix-ui/react-scroll-area': 1.2.9(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + '@radix-ui/react-slot': 1.2.3(@types/react@19.1.8)(react@19.1.0) + '@radix-ui/react-tabs': 1.1.12(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) class-variance-authority: 0.7.1 fumadocs-core: 15.2.7(@types/react@19.1.8)(next@15.3.4(react-dom@19.1.0(react@19.1.0))(react@19.1.0))(react-dom@19.1.0(react@19.1.0))(react@19.1.0) lodash.merge: 4.6.2 @@ -5748,9 +5156,9 @@ snapshots: optionalDependencies: tailwindcss: 4.1.11 transitivePeerDependencies: - - "@oramacloud/client" - - "@types/react" - - "@types/react-dom" + - '@oramacloud/client' + - '@types/react' + - '@types/react-dom' - algoliasearch - supports-color @@ -5844,9 +5252,9 @@ snapshots: hast-util-to-estree@3.1.3: dependencies: - "@types/estree": 1.0.8 - "@types/estree-jsx": 1.0.5 - "@types/hast": 3.0.4 + '@types/estree': 1.0.8 + '@types/estree-jsx': 1.0.5 + '@types/hast': 3.0.4 comma-separated-tokens: 2.0.3 devlop: 1.1.0 estree-util-attach-comments: 3.0.0 @@ -5865,8 +5273,8 @@ snapshots: hast-util-to-html@9.0.5: dependencies: - "@types/hast": 3.0.4 - "@types/unist": 3.0.3 + '@types/hast': 3.0.4 + '@types/unist': 3.0.3 ccount: 2.0.1 comma-separated-tokens: 2.0.3 hast-util-whitespace: 3.0.0 @@ -5879,9 +5287,9 @@ snapshots: hast-util-to-jsx-runtime@2.3.6: dependencies: - "@types/estree": 1.0.8 - "@types/hast": 3.0.4 - "@types/unist": 3.0.3 + '@types/estree': 1.0.8 + '@types/hast': 3.0.4 + '@types/unist': 3.0.3 comma-separated-tokens: 2.0.3 devlop: 1.1.0 estree-util-is-identifier-name: 3.0.0 @@ -5899,11 +5307,11 @@ snapshots: hast-util-to-string@3.0.1: dependencies: - "@types/hast": 3.0.4 + '@types/hast': 3.0.4 hast-util-whitespace@3.0.0: dependencies: - "@types/hast": 3.0.4 + '@types/hast': 3.0.4 html-void-elements@3.0.0: {} @@ -6179,7 +5587,7 @@ snapshots: magic-string@0.30.17: dependencies: - "@jridgewell/sourcemap-codec": 1.5.4 + '@jridgewell/sourcemap-codec': 1.5.4 markdown-extensions@2.0.0: {} @@ -6189,15 +5597,15 @@ snapshots: mdast-util-find-and-replace@3.0.2: dependencies: - "@types/mdast": 4.0.4 + '@types/mdast': 4.0.4 escape-string-regexp: 5.0.0 unist-util-is: 6.0.0 unist-util-visit-parents: 6.0.1 mdast-util-from-markdown@2.0.2: dependencies: - "@types/mdast": 4.0.4 - "@types/unist": 3.0.3 + '@types/mdast': 4.0.4 + '@types/unist': 3.0.3 decode-named-character-reference: 1.2.0 devlop: 1.1.0 mdast-util-to-string: 4.0.0 @@ -6213,7 +5621,7 @@ snapshots: mdast-util-gfm-autolink-literal@2.0.1: dependencies: - "@types/mdast": 4.0.4 + '@types/mdast': 4.0.4 ccount: 2.0.1 devlop: 1.1.0 mdast-util-find-and-replace: 3.0.2 @@ -6221,7 +5629,7 @@ snapshots: mdast-util-gfm-footnote@2.1.0: dependencies: - "@types/mdast": 4.0.4 + '@types/mdast': 4.0.4 devlop: 1.1.0 mdast-util-from-markdown: 2.0.2 mdast-util-to-markdown: 2.1.2 @@ -6231,7 +5639,7 @@ snapshots: mdast-util-gfm-strikethrough@2.0.0: dependencies: - "@types/mdast": 4.0.4 + '@types/mdast': 4.0.4 mdast-util-from-markdown: 2.0.2 mdast-util-to-markdown: 2.1.2 transitivePeerDependencies: @@ -6239,7 +5647,7 @@ snapshots: mdast-util-gfm-table@2.0.0: dependencies: - "@types/mdast": 4.0.4 + '@types/mdast': 4.0.4 devlop: 1.1.0 markdown-table: 3.0.4 mdast-util-from-markdown: 2.0.2 @@ -6249,7 +5657,7 @@ snapshots: mdast-util-gfm-task-list-item@2.0.0: dependencies: - "@types/mdast": 4.0.4 + '@types/mdast': 4.0.4 devlop: 1.1.0 mdast-util-from-markdown: 2.0.2 mdast-util-to-markdown: 2.1.2 @@ -6270,9 +5678,9 @@ snapshots: mdast-util-mdx-expression@2.0.1: dependencies: - "@types/estree-jsx": 1.0.5 - "@types/hast": 3.0.4 - "@types/mdast": 4.0.4 + '@types/estree-jsx': 1.0.5 + '@types/hast': 3.0.4 + '@types/mdast': 4.0.4 devlop: 1.1.0 mdast-util-from-markdown: 2.0.2 mdast-util-to-markdown: 2.1.2 @@ -6281,10 +5689,10 @@ snapshots: mdast-util-mdx-jsx@3.2.0: dependencies: - "@types/estree-jsx": 1.0.5 - "@types/hast": 3.0.4 - "@types/mdast": 4.0.4 - "@types/unist": 3.0.3 + '@types/estree-jsx': 1.0.5 + '@types/hast': 3.0.4 + '@types/mdast': 4.0.4 + '@types/unist': 3.0.3 ccount: 2.0.1 devlop: 1.1.0 mdast-util-from-markdown: 2.0.2 @@ -6308,9 +5716,9 @@ snapshots: mdast-util-mdxjs-esm@2.0.1: dependencies: - "@types/estree-jsx": 1.0.5 - "@types/hast": 3.0.4 - "@types/mdast": 4.0.4 + '@types/estree-jsx': 1.0.5 + '@types/hast': 3.0.4 + '@types/mdast': 4.0.4 devlop: 1.1.0 mdast-util-from-markdown: 2.0.2 mdast-util-to-markdown: 2.1.2 @@ -6319,14 +5727,14 @@ snapshots: mdast-util-phrasing@4.1.0: dependencies: - "@types/mdast": 4.0.4 + '@types/mdast': 4.0.4 unist-util-is: 6.0.0 mdast-util-to-hast@13.2.0: dependencies: - "@types/hast": 3.0.4 - "@types/mdast": 4.0.4 - "@ungap/structured-clone": 1.3.0 + '@types/hast': 3.0.4 + '@types/mdast': 4.0.4 + '@ungap/structured-clone': 1.3.0 devlop: 1.1.0 micromark-util-sanitize-uri: 2.0.1 trim-lines: 3.0.1 @@ -6336,8 +5744,8 @@ snapshots: mdast-util-to-markdown@2.1.2: dependencies: - "@types/mdast": 4.0.4 - "@types/unist": 3.0.3 + '@types/mdast': 4.0.4 + '@types/unist': 3.0.3 longest-streak: 3.1.0 mdast-util-phrasing: 4.1.0 mdast-util-to-string: 4.0.0 @@ -6348,7 +5756,7 @@ snapshots: mdast-util-to-string@4.0.0: dependencies: - "@types/mdast": 4.0.4 + '@types/mdast': 4.0.4 merge2@1.4.1: {} @@ -6431,7 +5839,7 @@ snapshots: micromark-extension-mdx-expression@3.0.1: dependencies: - "@types/estree": 1.0.8 + '@types/estree': 1.0.8 devlop: 1.1.0 micromark-factory-mdx-expression: 2.0.3 micromark-factory-space: 2.0.1 @@ -6442,7 +5850,7 @@ snapshots: micromark-extension-mdx-jsx@3.0.2: dependencies: - "@types/estree": 1.0.8 + '@types/estree': 1.0.8 devlop: 1.1.0 estree-util-is-identifier-name: 3.0.0 micromark-factory-mdx-expression: 2.0.3 @@ -6459,7 +5867,7 @@ snapshots: micromark-extension-mdxjs-esm@3.0.0: dependencies: - "@types/estree": 1.0.8 + '@types/estree': 1.0.8 devlop: 1.1.0 micromark-core-commonmark: 2.0.3 micromark-util-character: 2.1.1 @@ -6495,7 +5903,7 @@ snapshots: micromark-factory-mdx-expression@2.0.3: dependencies: - "@types/estree": 1.0.8 + '@types/estree': 1.0.8 devlop: 1.1.0 micromark-factory-space: 2.0.1 micromark-util-character: 2.1.1 @@ -6559,8 +5967,8 @@ snapshots: micromark-util-events-to-acorn@2.0.3: dependencies: - "@types/estree": 1.0.8 - "@types/unist": 3.0.3 + '@types/estree': 1.0.8 + '@types/unist': 3.0.3 devlop: 1.1.0 estree-util-visit: 2.0.0 micromark-util-symbol: 2.0.1 @@ -6596,7 +6004,7 @@ snapshots: micromark@4.0.2: dependencies: - "@types/debug": 4.1.12 + '@types/debug': 4.1.12 debug: 4.4.1 decode-named-character-reference: 1.2.0 devlop: 1.1.0 @@ -6670,9 +6078,9 @@ snapshots: next@15.3.4(react-dom@19.1.0(react@19.1.0))(react@19.1.0): dependencies: - "@next/env": 15.3.4 - "@swc/counter": 0.1.3 - "@swc/helpers": 0.5.15 + '@next/env': 15.3.4 + '@swc/counter': 0.1.3 + '@swc/helpers': 0.5.15 busboy: 1.6.0 caniuse-lite: 1.0.30001727 postcss: 8.4.31 @@ -6680,17 +6088,17 @@ snapshots: react-dom: 19.1.0(react@19.1.0) styled-jsx: 5.1.6(react@19.1.0) optionalDependencies: - "@next/swc-darwin-arm64": 15.3.4 - "@next/swc-darwin-x64": 15.3.4 - "@next/swc-linux-arm64-gnu": 15.3.4 - "@next/swc-linux-arm64-musl": 15.3.4 - "@next/swc-linux-x64-gnu": 15.3.4 - "@next/swc-linux-x64-musl": 15.3.4 - "@next/swc-win32-arm64-msvc": 15.3.4 - "@next/swc-win32-x64-msvc": 15.3.4 + '@next/swc-darwin-arm64': 15.3.4 + '@next/swc-darwin-x64': 15.3.4 + '@next/swc-linux-arm64-gnu': 15.3.4 + '@next/swc-linux-arm64-musl': 15.3.4 + '@next/swc-linux-x64-gnu': 15.3.4 + '@next/swc-linux-x64-musl': 15.3.4 + '@next/swc-win32-arm64-msvc': 15.3.4 + '@next/swc-win32-x64-msvc': 15.3.4 sharp: 0.34.2 transitivePeerDependencies: - - "@babel/core" + - '@babel/core' - babel-plugin-macros object-assign@4.1.1: {} @@ -6772,7 +6180,7 @@ snapshots: parse-entities@4.0.2: dependencies: - "@types/unist": 2.0.11 + '@types/unist': 2.0.11 character-entities-legacy: 3.0.0 character-reference-invalid: 2.0.1 decode-named-character-reference: 1.2.0 @@ -6853,7 +6261,7 @@ snapshots: react-style-singleton: 2.2.3(@types/react@19.1.8)(react@19.1.0) tslib: 2.8.1 optionalDependencies: - "@types/react": 19.1.8 + '@types/react': 19.1.8 react-remove-scroll@2.7.1(@types/react@19.1.8)(react@19.1.0): dependencies: @@ -6864,7 +6272,7 @@ snapshots: use-callback-ref: 1.3.3(@types/react@19.1.8)(react@19.1.0) use-sidecar: 1.1.3(@types/react@19.1.8)(react@19.1.0) optionalDependencies: - "@types/react": 19.1.8 + '@types/react': 19.1.8 react-style-singleton@2.2.3(@types/react@19.1.8)(react@19.1.0): dependencies: @@ -6872,7 +6280,7 @@ snapshots: react: 19.1.0 tslib: 2.8.1 optionalDependencies: - "@types/react": 19.1.8 + '@types/react': 19.1.8 react@19.1.0: {} @@ -6880,7 +6288,7 @@ snapshots: recma-build-jsx@1.0.0: dependencies: - "@types/estree": 1.0.8 + '@types/estree': 1.0.8 estree-util-build-jsx: 3.0.1 vfile: 6.0.3 @@ -6896,14 +6304,14 @@ snapshots: recma-parse@1.0.0: dependencies: - "@types/estree": 1.0.8 + '@types/estree': 1.0.8 esast-util-from-js: 2.0.1 unified: 11.0.5 vfile: 6.0.3 recma-stringify@1.0.0: dependencies: - "@types/estree": 1.0.8 + '@types/estree': 1.0.8 estree-util-to-js: 2.0.0 unified: 11.0.5 vfile: 6.0.3 @@ -6940,15 +6348,15 @@ snapshots: rehype-recma@1.0.0: dependencies: - "@types/estree": 1.0.8 - "@types/hast": 3.0.4 + '@types/estree': 1.0.8 + '@types/hast': 3.0.4 hast-util-to-estree: 3.1.3 transitivePeerDependencies: - supports-color remark-gfm@4.0.1: dependencies: - "@types/mdast": 4.0.4 + '@types/mdast': 4.0.4 mdast-util-gfm: 3.1.0 micromark-extension-gfm: 3.0.0 remark-parse: 11.0.0 @@ -6966,7 +6374,7 @@ snapshots: remark-parse@11.0.0: dependencies: - "@types/mdast": 4.0.4 + '@types/mdast': 4.0.4 mdast-util-from-markdown: 2.0.2 micromark-util-types: 2.0.2 unified: 11.0.5 @@ -6975,21 +6383,21 @@ snapshots: remark-rehype@11.1.2: dependencies: - "@types/hast": 3.0.4 - "@types/mdast": 4.0.4 + '@types/hast': 3.0.4 + '@types/mdast': 4.0.4 mdast-util-to-hast: 13.2.0 unified: 11.0.5 vfile: 6.0.3 remark-stringify@11.0.0: dependencies: - "@types/mdast": 4.0.4 + '@types/mdast': 4.0.4 mdast-util-to-markdown: 2.1.2 unified: 11.0.5 remark@15.0.1: dependencies: - "@types/mdast": 4.0.4 + '@types/mdast': 4.0.4 remark-parse: 11.0.0 remark-stringify: 11.0.0 unified: 11.0.5 @@ -7075,27 +6483,27 @@ snapshots: detect-libc: 2.0.4 semver: 7.7.2 optionalDependencies: - "@img/sharp-darwin-arm64": 0.34.2 - "@img/sharp-darwin-x64": 0.34.2 - "@img/sharp-libvips-darwin-arm64": 1.1.0 - "@img/sharp-libvips-darwin-x64": 1.1.0 - "@img/sharp-libvips-linux-arm": 1.1.0 - "@img/sharp-libvips-linux-arm64": 1.1.0 - "@img/sharp-libvips-linux-ppc64": 1.1.0 - "@img/sharp-libvips-linux-s390x": 1.1.0 - "@img/sharp-libvips-linux-x64": 1.1.0 - "@img/sharp-libvips-linuxmusl-arm64": 1.1.0 - "@img/sharp-libvips-linuxmusl-x64": 1.1.0 - "@img/sharp-linux-arm": 0.34.2 - "@img/sharp-linux-arm64": 0.34.2 - "@img/sharp-linux-s390x": 0.34.2 - "@img/sharp-linux-x64": 0.34.2 - "@img/sharp-linuxmusl-arm64": 0.34.2 - "@img/sharp-linuxmusl-x64": 0.34.2 - "@img/sharp-wasm32": 0.34.2 - "@img/sharp-win32-arm64": 0.34.2 - "@img/sharp-win32-ia32": 0.34.2 - "@img/sharp-win32-x64": 0.34.2 + '@img/sharp-darwin-arm64': 0.34.2 + '@img/sharp-darwin-x64': 0.34.2 + '@img/sharp-libvips-darwin-arm64': 1.1.0 + '@img/sharp-libvips-darwin-x64': 1.1.0 + '@img/sharp-libvips-linux-arm': 1.1.0 + '@img/sharp-libvips-linux-arm64': 1.1.0 + '@img/sharp-libvips-linux-ppc64': 1.1.0 + '@img/sharp-libvips-linux-s390x': 1.1.0 + '@img/sharp-libvips-linux-x64': 1.1.0 + '@img/sharp-libvips-linuxmusl-arm64': 1.1.0 + '@img/sharp-libvips-linuxmusl-x64': 1.1.0 + '@img/sharp-linux-arm': 0.34.2 + '@img/sharp-linux-arm64': 0.34.2 + '@img/sharp-linux-s390x': 0.34.2 + '@img/sharp-linux-x64': 0.34.2 + '@img/sharp-linuxmusl-arm64': 0.34.2 + '@img/sharp-linuxmusl-x64': 0.34.2 + '@img/sharp-wasm32': 0.34.2 + '@img/sharp-win32-arm64': 0.34.2 + '@img/sharp-win32-ia32': 0.34.2 + '@img/sharp-win32-x64': 0.34.2 optional: true shebang-command@2.0.0: @@ -7106,14 +6514,14 @@ snapshots: shiki@3.7.0: dependencies: - "@shikijs/core": 3.7.0 - "@shikijs/engine-javascript": 3.7.0 - "@shikijs/engine-oniguruma": 3.7.0 - "@shikijs/langs": 3.7.0 - "@shikijs/themes": 3.7.0 - "@shikijs/types": 3.7.0 - "@shikijs/vscode-textmate": 10.0.2 - "@types/hast": 3.0.4 + '@shikijs/core': 3.7.0 + '@shikijs/engine-javascript': 3.7.0 + '@shikijs/engine-oniguruma': 3.7.0 + '@shikijs/langs': 3.7.0 + '@shikijs/themes': 3.7.0 + '@shikijs/types': 3.7.0 + '@shikijs/vscode-textmate': 10.0.2 + '@types/hast': 3.0.4 side-channel-list@1.0.0: dependencies: @@ -7243,7 +6651,7 @@ snapshots: synckit@0.11.8: dependencies: - "@pkgr/core": 0.2.7 + '@pkgr/core': 0.2.7 tailwind-merge@3.3.1: {} @@ -7253,7 +6661,7 @@ snapshots: tar@7.4.3: dependencies: - "@isaacs/fs-minipass": 4.0.1 + '@isaacs/fs-minipass': 4.0.1 chownr: 3.0.0 minipass: 7.1.2 minizlib: 3.0.2 @@ -7281,7 +6689,7 @@ snapshots: tsconfig-paths@3.15.0: dependencies: - "@types/json5": 0.0.29 + '@types/json5': 0.0.29 json5: 1.0.2 minimist: 1.2.8 strip-bom: 3.0.0 @@ -7340,7 +6748,7 @@ snapshots: unified@11.0.5: dependencies: - "@types/unist": 3.0.3 + '@types/unist': 3.0.3 bail: 2.0.2 devlop: 1.1.0 extend: 3.0.2 @@ -7350,28 +6758,28 @@ snapshots: unist-util-is@6.0.0: dependencies: - "@types/unist": 3.0.3 + '@types/unist': 3.0.3 unist-util-position-from-estree@2.0.0: dependencies: - "@types/unist": 3.0.3 + '@types/unist': 3.0.3 unist-util-position@5.0.0: dependencies: - "@types/unist": 3.0.3 + '@types/unist': 3.0.3 unist-util-stringify-position@4.0.0: dependencies: - "@types/unist": 3.0.3 + '@types/unist': 3.0.3 unist-util-visit-parents@6.0.1: dependencies: - "@types/unist": 3.0.3 + '@types/unist': 3.0.3 unist-util-is: 6.0.0 unist-util-visit@5.0.0: dependencies: - "@types/unist": 3.0.3 + '@types/unist': 3.0.3 unist-util-is: 6.0.0 unist-util-visit-parents: 6.0.1 @@ -7379,25 +6787,25 @@ snapshots: dependencies: napi-postinstall: 0.3.0 optionalDependencies: - "@unrs/resolver-binding-android-arm-eabi": 1.11.0 - "@unrs/resolver-binding-android-arm64": 1.11.0 - "@unrs/resolver-binding-darwin-arm64": 1.11.0 - "@unrs/resolver-binding-darwin-x64": 1.11.0 - "@unrs/resolver-binding-freebsd-x64": 1.11.0 - "@unrs/resolver-binding-linux-arm-gnueabihf": 1.11.0 - "@unrs/resolver-binding-linux-arm-musleabihf": 1.11.0 - "@unrs/resolver-binding-linux-arm64-gnu": 1.11.0 - "@unrs/resolver-binding-linux-arm64-musl": 1.11.0 - "@unrs/resolver-binding-linux-ppc64-gnu": 1.11.0 - "@unrs/resolver-binding-linux-riscv64-gnu": 1.11.0 - "@unrs/resolver-binding-linux-riscv64-musl": 1.11.0 - "@unrs/resolver-binding-linux-s390x-gnu": 1.11.0 - "@unrs/resolver-binding-linux-x64-gnu": 1.11.0 - "@unrs/resolver-binding-linux-x64-musl": 1.11.0 - "@unrs/resolver-binding-wasm32-wasi": 1.11.0 - "@unrs/resolver-binding-win32-arm64-msvc": 1.11.0 - "@unrs/resolver-binding-win32-ia32-msvc": 1.11.0 - "@unrs/resolver-binding-win32-x64-msvc": 1.11.0 + '@unrs/resolver-binding-android-arm-eabi': 1.11.0 + '@unrs/resolver-binding-android-arm64': 1.11.0 + '@unrs/resolver-binding-darwin-arm64': 1.11.0 + '@unrs/resolver-binding-darwin-x64': 1.11.0 + '@unrs/resolver-binding-freebsd-x64': 1.11.0 + '@unrs/resolver-binding-linux-arm-gnueabihf': 1.11.0 + '@unrs/resolver-binding-linux-arm-musleabihf': 1.11.0 + '@unrs/resolver-binding-linux-arm64-gnu': 1.11.0 + '@unrs/resolver-binding-linux-arm64-musl': 1.11.0 + '@unrs/resolver-binding-linux-ppc64-gnu': 1.11.0 + '@unrs/resolver-binding-linux-riscv64-gnu': 1.11.0 + '@unrs/resolver-binding-linux-riscv64-musl': 1.11.0 + '@unrs/resolver-binding-linux-s390x-gnu': 1.11.0 + '@unrs/resolver-binding-linux-x64-gnu': 1.11.0 + '@unrs/resolver-binding-linux-x64-musl': 1.11.0 + '@unrs/resolver-binding-wasm32-wasi': 1.11.0 + '@unrs/resolver-binding-win32-arm64-msvc': 1.11.0 + '@unrs/resolver-binding-win32-ia32-msvc': 1.11.0 + '@unrs/resolver-binding-win32-x64-msvc': 1.11.0 uri-js@4.4.1: dependencies: @@ -7408,7 +6816,7 @@ snapshots: react: 19.1.0 tslib: 2.8.1 optionalDependencies: - "@types/react": 19.1.8 + '@types/react': 19.1.8 use-sidecar@1.1.3(@types/react@19.1.8)(react@19.1.0): dependencies: @@ -7416,18 +6824,18 @@ snapshots: react: 19.1.0 tslib: 2.8.1 optionalDependencies: - "@types/react": 19.1.8 + '@types/react': 19.1.8 util-deprecate@1.0.2: {} vfile-message@4.0.2: dependencies: - "@types/unist": 3.0.3 + '@types/unist': 3.0.3 unist-util-stringify-position: 4.0.0 vfile@6.0.3: dependencies: - "@types/unist": 3.0.3 + '@types/unist': 3.0.3 vfile-message: 4.0.2 which-boxed-primitive@1.1.1: diff --git a/apps/docs/src/app/(home)/page.tsx b/apps/docs/src/app/(home)/page.tsx index 3883f294..0e10ab3d 100644 --- a/apps/docs/src/app/(home)/page.tsx +++ b/apps/docs/src/app/(home)/page.tsx @@ -59,13 +59,13 @@ const images = [ "https://res.cloudinary.com/technical-intelligence/image/upload/v1745546005/Palmr./profile_mizwvg.png", ]; -const docsLink = "/docs/3.2-beta"; +const docsLink = "/docs/v3-beta"; function Hero() { return (

- Palmr. v3.2-beta + Palmr. v3-beta

Modern & efficient file sharing

diff --git a/apps/docs/src/app/api/search/route.ts b/apps/docs/src/app/api/search/route.ts index 076febc2..39155a91 100644 --- a/apps/docs/src/app/api/search/route.ts +++ b/apps/docs/src/app/api/search/route.ts @@ -9,6 +9,6 @@ export const { GET } = createFromSource(source, (page) => { url: page.url, id: page.url, structuredData: page.data.structuredData, - tag: page.url.startsWith("/docs/3.2-beta") ? "v3.2-beta" : "v2.0.0-beta", + tag: "v3-beta", }; }); diff --git a/apps/docs/src/app/docs/[[...slug]]/page.tsx b/apps/docs/src/app/docs/[[...slug]]/page.tsx index f4057cc5..424e19bd 100644 --- a/apps/docs/src/app/docs/[[...slug]]/page.tsx +++ b/apps/docs/src/app/docs/[[...slug]]/page.tsx @@ -2,7 +2,6 @@ import { redirect } from "next/navigation"; import { createRelativeLink } from "fumadocs-ui/mdx"; import { DocsBody, DocsDescription, DocsPage, DocsTitle } from "fumadocs-ui/page"; -import { VersionWarning } from "@/components/version-warning"; import { source } from "@/lib/source"; import { getMDXComponents } from "@/mdx-components"; import { Footer } from "../components/footer"; @@ -11,12 +10,10 @@ import { Sponsor } from "../components/sponsor"; export default async function Page(props: { params: Promise<{ slug?: string[] }> }) { const params = await props.params; const page = source.getPage(params.slug); - if (!page) redirect("/docs/3.2-beta"); + if (!page) redirect("/docs/v3-beta"); const MDXContent = page.data.body; - const shouldShowWarning = page.url.startsWith("/docs/2.0.0-beta"); - return ( footer: , }} > - {shouldShowWarning && } {page.data.title}

{page.data.description} @@ -49,7 +45,7 @@ export async function generateStaticParams() { export async function generateMetadata(props: { params: Promise<{ slug?: string[] }> }) { const params = await props.params; const page = source.getPage(params.slug); - if (!page) redirect("/docs/3.2-beta"); + if (!page) redirect("/docs/v3-beta"); return { title: page.data.title + " | Palmr. Docs", diff --git a/apps/docs/src/app/docs/layout.tsx b/apps/docs/src/app/docs/layout.tsx index 3eff330c..3e6d453e 100644 --- a/apps/docs/src/app/docs/layout.tsx +++ b/apps/docs/src/app/docs/layout.tsx @@ -2,12 +2,14 @@ import type { ReactNode } from "react"; import { DocsLayout } from "fumadocs-ui/layouts/docs"; import { baseOptions } from "@/app/layout.config"; +import { V3BetaModal } from "@/components/V3BetaModal"; import { source } from "@/lib/source"; export default function Layout({ children }: { children: ReactNode }) { return ( {children} + ); } diff --git a/apps/docs/src/app/layout.tsx b/apps/docs/src/app/layout.tsx index 21ae0351..521061d5 100644 --- a/apps/docs/src/app/layout.tsx +++ b/apps/docs/src/app/layout.tsx @@ -25,30 +25,7 @@ export default function Layout({ children }: { children: ReactNode }) { - - {children} - + {children} ); diff --git a/apps/docs/src/components/OIDCProviderCards.tsx b/apps/docs/src/components/OIDCProviderCards.tsx index c287e035..b5ade65f 100644 --- a/apps/docs/src/components/OIDCProviderCards.tsx +++ b/apps/docs/src/components/OIDCProviderCards.tsx @@ -6,61 +6,61 @@ const providers = [ { name: "Google", description: "Configure authentication using Google OAuth2 services", - href: "/docs/3.2-beta/oidc-authentication/google", + href: "/docs/v3-beta/oidc-authentication/google", icon: , }, { name: "Discord", description: "Set up Discord OAuth2 for community-based authentication", - href: "/docs/3.2-beta/oidc-authentication/discord", + href: "/docs/v3-beta/oidc-authentication/discord", icon: , }, { name: "GitHub", description: "Enable GitHub OAuth for developer-friendly sign-in", - href: "/docs/3.2-beta/oidc-authentication/github", + href: "/docs/v3-beta/oidc-authentication/github", icon: , }, { name: "Zitadel", description: "Enterprise-grade identity and access management", - href: "/docs/3.2-beta/oidc-authentication/zitadel", + href: "/docs/v3-beta/oidc-authentication/zitadel", icon: , }, { name: "Auth0", description: "Flexible identity platform with extensive customization", - href: "/docs/3.2-beta/oidc-authentication/auth0", + href: "/docs/v3-beta/oidc-authentication/auth0", icon: , }, { name: "Authentik", description: "Open-source identity provider with modern features", - href: "/docs/3.2-beta/oidc-authentication/authentik", + href: "/docs/v3-beta/oidc-authentication/authentik", icon: , }, { name: "Frontegg", description: "User management platform for B2B applications", - href: "/docs/3.2-beta/oidc-authentication/frontegg", + href: "/docs/v3-beta/oidc-authentication/frontegg", icon: , }, { name: "Kinde Auth", description: "Developer-first authentication and user management", - href: "/docs/3.2-beta/oidc-authentication/kinde-auth", + href: "/docs/v3-beta/oidc-authentication/kinde-auth", icon: , }, { name: "Pocket ID", description: "Open-source identity provider with OIDC support", - href: "/docs/3.2-beta/oidc-authentication/pocket-id", + href: "/docs/v3-beta/oidc-authentication/pocket-id", icon: , }, { name: "Other", description: "Configure any other OIDC-compliant identity provider", - href: "/docs/3.2-beta/oidc-authentication/other", + href: "/docs/v3-beta/oidc-authentication/other", icon: , }, ]; diff --git a/apps/docs/src/components/V3BetaModal.tsx b/apps/docs/src/components/V3BetaModal.tsx new file mode 100644 index 00000000..7dbf512b --- /dev/null +++ b/apps/docs/src/components/V3BetaModal.tsx @@ -0,0 +1,82 @@ +"use client"; + +import { useEffect, useState } from "react"; +import { usePathname, useRouter } from "next/navigation"; +import { AlertTriangle } from "lucide-react"; + +import { Button } from "@/components/ui/button"; +import { Dialog, DialogContent, DialogDescription, DialogHeader, DialogTitle } from "@/components/ui/dialog"; + +const V3_BETA_MODAL_KEY = "palmr-v3-beta-modal-shown"; + +export function V3BetaModal() { + const [isOpen, setIsOpen] = useState(false); + const router = useRouter(); + const pathname = usePathname(); + + useEffect(() => { + // Verifica se está na rota v3-beta + if (pathname?.includes("/docs/v3-beta")) { + // Verifica se o modal já foi mostrado antes + const hasSeenModal = localStorage.getItem(V3_BETA_MODAL_KEY); + + if (!hasSeenModal) { + setIsOpen(true); + } + } + }, [pathname]); + + const handleClose = () => { + // Marca como visto no localStorage + localStorage.setItem(V3_BETA_MODAL_KEY, "true"); + setIsOpen(false); + }; + + const handleGoToQuickStart = () => { + handleClose(); + router.push("/docs/v3-beta/quick-start"); + }; + + return ( + + + +
+
+ +
+ Important Changes +
+ +
+

+ Note: This alert is only relevant if you are upgrading from a version prior to + v3.0.0-beta. If you're new to Palmr, you can safely follow the documentation normally. +

+
+

+ Major changes have been made starting from version v3.3.0-beta + . +

+

+ This has made it necessary to update the{" "} + + docker-compose.yaml + {" "} + configuration to work perfectly with the new changes. +

+

For more details, please check the updated documentation in the quick start guide.

+
+
+
+ + +
+
+
+ ); +} diff --git a/apps/docs/src/components/ui/dialog.tsx b/apps/docs/src/components/ui/dialog.tsx new file mode 100644 index 00000000..8a86deed --- /dev/null +++ b/apps/docs/src/components/ui/dialog.tsx @@ -0,0 +1,111 @@ +"use client"; + +import * as React from "react"; +import * as DialogPrimitive from "@radix-ui/react-dialog"; +import { XIcon } from "lucide-react"; + +import { cn } from "@/lib/utils"; + +function Dialog({ ...props }: React.ComponentProps) { + return ; +} + +function DialogTrigger({ ...props }: React.ComponentProps) { + return ; +} + +function DialogPortal({ ...props }: React.ComponentProps) { + return ; +} + +function DialogClose({ ...props }: React.ComponentProps) { + return ; +} + +function DialogOverlay({ className, ...props }: React.ComponentProps) { + return ( + + ); +} + +function DialogContent({ className, children, ...props }: React.ComponentProps) { + return ( + + + + {children} + + + Close + + + + ); +} + +function DialogHeader({ className, ...props }: React.ComponentProps<"div">) { + return ( +
+ ); +} + +function DialogFooter({ className, ...props }: React.ComponentProps<"div">) { + return ( +
+ ); +} + +function DialogTitle({ className, ...props }: React.ComponentProps) { + return ( + + ); +} + +function DialogDescription({ className, ...props }: React.ComponentProps) { + return ( + + ); +} + +export { + Dialog, + DialogClose, + DialogContent, + DialogDescription, + DialogFooter, + DialogHeader, + DialogOverlay, + DialogPortal, + DialogTitle, + DialogTrigger, +}; diff --git a/apps/docs/src/components/version-warning.tsx b/apps/docs/src/components/version-warning.tsx deleted file mode 100644 index d978aa41..00000000 --- a/apps/docs/src/components/version-warning.tsx +++ /dev/null @@ -1,128 +0,0 @@ -"use client"; - -import { useEffect, useRef, useState } from "react"; -import Link from "next/link"; -import { AlertTriangle, ArrowRightIcon, X } from "lucide-react"; - -import { LATEST_VERSION, LATEST_VERSION_PATH } from "@/config/constants"; - -function useIntersectionObserver(targetRef: React.RefObject) { - const [isIntersecting, setIsIntersecting] = useState(true); - - useEffect(() => { - const observer = new IntersectionObserver(([entry]) => setIsIntersecting(entry.isIntersecting), { - threshold: 0, - rootMargin: "-10px 0px 0px 0px", - }); - - if (targetRef.current) { - observer.observe(targetRef.current); - } - - return () => observer.disconnect(); - }, [targetRef]); - - return isIntersecting; -} - -function useClickOutside(ref: React.RefObject, callback: () => void) { - useEffect(() => { - const handleClickOutside = (event: MouseEvent) => { - if (ref.current && !ref.current.contains(event.target as Node)) { - callback(); - } - }; - - document.addEventListener("mousedown", handleClickOutside); - return () => document.removeEventListener("mousedown", handleClickOutside); - }, [ref, callback]); -} - -function WarningContent({ onClose }: { onClose: () => void }) { - return ( - <> - -
- -
-

- Deprecated version documentation -

-

- This documentation refers to a previous version of Palmr. It may contain more complex configurations and - bugs that have already been fixed. -

- - View latest documentation ({LATEST_VERSION}) - -
-
- - ); -} - -function FloatingWarning({ onClose }: { onClose: () => void }) { - const [showContent, setShowContent] = useState(false); - const floatingRef = useRef(null); - - useClickOutside(floatingRef, () => setShowContent(false)); - - const toggleContent = () => setShowContent(!showContent); - - return ( -
-
- - - {showContent && ( -
-
- -
-
-
- )} -
-
- ); -} - -export function VersionWarning() { - const [isVisible, setIsVisible] = useState(true); - const warningRef = useRef(null); - - const isIntersecting = useIntersectionObserver(warningRef); - const shouldShowFloating = isVisible && !isIntersecting; - - const handleClose = () => setIsVisible(false); - - if (!isVisible) return null; - - return ( -
-
- -
- - {shouldShowFloating && } -
- ); -} diff --git a/apps/docs/src/config/constants.ts b/apps/docs/src/config/constants.ts index bb8ada7d..9cc6a863 100644 --- a/apps/docs/src/config/constants.ts +++ b/apps/docs/src/config/constants.ts @@ -1,2 +1,2 @@ -export const LATEST_VERSION_PATH = "/docs/3.2-beta"; -export const LATEST_VERSION = "v3.2-beta"; +export const LATEST_VERSION_PATH = "/docs/v3-beta"; +export const LATEST_VERSION = "v3-beta"; diff --git a/apps/server/package.json b/apps/server/package.json index 1cfe3a5e..015346b0 100644 --- a/apps/server/package.json +++ b/apps/server/package.json @@ -1,6 +1,6 @@ { "name": "palmr-api", - "version": "3.2.5-beta", + "version": "3.3.0-beta", "description": "API for Palmr", "private": true, "author": "Daniel Luiz Alves ", diff --git a/apps/server/prisma/schema.prisma b/apps/server/prisma/schema.prisma index 14590c8d..20ee7c54 100644 --- a/apps/server/prisma/schema.prisma +++ b/apps/server/prisma/schema.prisma @@ -316,3 +316,15 @@ model Folder { @@index([parentId]) @@map("folders") } + +model InviteToken { + id String @id @default(cuid()) + token String @unique + expiresAt DateTime + usedAt DateTime? + createdBy String + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt + + @@map("invite_tokens") +} diff --git a/apps/server/src/config/storage.config.ts b/apps/server/src/config/storage.config.ts index 355c2565..2b18eff5 100644 --- a/apps/server/src/config/storage.config.ts +++ b/apps/server/src/config/storage.config.ts @@ -1,10 +1,57 @@ +import * as fs from "fs"; import process from "node:process"; import { S3Client } from "@aws-sdk/client-s3"; import { env } from "../env"; import { StorageConfig } from "../types/storage"; -export const storageConfig: StorageConfig = { +/** + * Load internal storage credentials if they exist + * This provides S3-compatible storage automatically when ENABLE_S3=false + */ +function loadInternalStorageCredentials(): Partial | null { + const credentialsPath = "/app/server/.minio-credentials"; + + try { + if (fs.existsSync(credentialsPath)) { + const content = fs.readFileSync(credentialsPath, "utf-8"); + const credentials: any = {}; + + content.split("\n").forEach((line) => { + const [key, value] = line.split("="); + if (key && value) { + credentials[key.trim()] = value.trim(); + } + }); + + console.log("[STORAGE] Using internal storage system"); + + return { + endpoint: credentials.S3_ENDPOINT || "127.0.0.1", + port: parseInt(credentials.S3_PORT || "9379", 10), + useSSL: credentials.S3_USE_SSL === "true", + accessKey: credentials.S3_ACCESS_KEY, + secretKey: credentials.S3_SECRET_KEY, + region: credentials.S3_REGION || "default", + bucketName: credentials.S3_BUCKET_NAME || "palmr-files", + forcePathStyle: true, + }; + } + } catch (error) { + console.warn("[STORAGE] Could not load internal storage credentials:", error); + } + + return null; +} + +/** + * Storage configuration: + * - Default (ENABLE_S3=false or not set): Internal storage (auto-configured, zero config) + * - ENABLE_S3=true: External S3 (AWS, S3-compatible, etc) using env vars + */ +const internalStorageConfig = env.ENABLE_S3 === "true" ? null : loadInternalStorageCredentials(); + +export const storageConfig: StorageConfig = (internalStorageConfig as StorageConfig) || { endpoint: env.S3_ENDPOINT || "", port: env.S3_PORT ? Number(env.S3_PORT) : undefined, useSSL: env.S3_USE_SSL === "true", @@ -23,21 +70,74 @@ if (storageConfig.useSSL && env.S3_REJECT_UNAUTHORIZED === "false") { } } -export const s3Client = - env.ENABLE_S3 === "true" - ? new S3Client({ - endpoint: storageConfig.useSSL - ? `https://${storageConfig.endpoint}${storageConfig.port ? `:${storageConfig.port}` : ""}` - : `http://${storageConfig.endpoint}${storageConfig.port ? `:${storageConfig.port}` : ""}`, - region: storageConfig.region, - credentials: { - accessKeyId: storageConfig.accessKey, - secretAccessKey: storageConfig.secretKey, - }, - forcePathStyle: storageConfig.forcePathStyle, - }) - : null; +/** + * Storage is ALWAYS S3-compatible: + * - ENABLE_S3=false → Internal storage (automatic) + * - ENABLE_S3=true → External S3 (AWS, S3-compatible, etc) + */ +const hasValidConfig = storageConfig.endpoint && storageConfig.accessKey && storageConfig.secretKey; + +export const s3Client = hasValidConfig + ? new S3Client({ + endpoint: storageConfig.useSSL + ? `https://${storageConfig.endpoint}${storageConfig.port ? `:${storageConfig.port}` : ""}` + : `http://${storageConfig.endpoint}${storageConfig.port ? `:${storageConfig.port}` : ""}`, + region: storageConfig.region, + credentials: { + accessKeyId: storageConfig.accessKey, + secretAccessKey: storageConfig.secretKey, + }, + forcePathStyle: storageConfig.forcePathStyle, + }) + : null; export const bucketName = storageConfig.bucketName; -export const isS3Enabled = env.ENABLE_S3 === "true"; +/** + * Storage is always S3-compatible + * ENABLE_S3=true means EXTERNAL S3, otherwise uses internal storage + */ +export const isS3Enabled = s3Client !== null; +export const isExternalS3 = env.ENABLE_S3 === "true"; +export const isInternalStorage = s3Client !== null && env.ENABLE_S3 !== "true"; + +/** + * Creates a public S3 client for presigned URL generation. + * - Internal storage (ENABLE_S3=false): Uses STORAGE_URL (e.g., https://syrg.palmr.com) + * - External S3 (ENABLE_S3=true): Uses the original S3 endpoint configuration + * + * @returns S3Client configured with public endpoint, or null if S3 is disabled + */ +export function createPublicS3Client(): S3Client | null { + if (!s3Client) { + return null; + } + + let publicEndpoint: string; + + if (isInternalStorage) { + // Internal storage: use STORAGE_URL + if (!env.STORAGE_URL) { + throw new Error( + "[STORAGE] STORAGE_URL environment variable is required when using internal storage (ENABLE_S3=false). " + + "Set STORAGE_URL to your public storage URL with protocol (e.g., https://syrg.palmr.com or http://192.168.1.100:9379)" + ); + } + publicEndpoint = env.STORAGE_URL; + } else { + // External S3: use the original endpoint configuration + publicEndpoint = storageConfig.useSSL + ? `https://${storageConfig.endpoint}${storageConfig.port ? `:${storageConfig.port}` : ""}` + : `http://${storageConfig.endpoint}${storageConfig.port ? `:${storageConfig.port}` : ""}`; + } + + return new S3Client({ + endpoint: publicEndpoint, + region: storageConfig.region, + credentials: { + accessKeyId: storageConfig.accessKey, + secretAccessKey: storageConfig.secretKey, + }, + forcePathStyle: storageConfig.forcePathStyle, + }); +} diff --git a/apps/server/src/env.ts b/apps/server/src/env.ts index ba97b976..4aa9ad00 100644 --- a/apps/server/src/env.ts +++ b/apps/server/src/env.ts @@ -1,9 +1,8 @@ import { z } from "zod"; const envSchema = z.object({ + // Storage configuration ENABLE_S3: z.union([z.literal("true"), z.literal("false")]).default("false"), - ENCRYPTION_KEY: z.string().optional(), - DISABLE_FILESYSTEM_ENCRYPTION: z.union([z.literal("true"), z.literal("false")]).default("true"), S3_ENDPOINT: z.string().optional(), S3_PORT: z.string().optional(), S3_USE_SSL: z.string().optional(), @@ -13,26 +12,16 @@ const envSchema = z.object({ S3_BUCKET_NAME: z.string().optional(), S3_FORCE_PATH_STYLE: z.union([z.literal("true"), z.literal("false")]).default("false"), S3_REJECT_UNAUTHORIZED: z.union([z.literal("true"), z.literal("false")]).default("true"), + + // Legacy encryption vars (kept for backward compatibility but not used with S3/Garage) + ENCRYPTION_KEY: z.string().optional(), + DISABLE_FILESYSTEM_ENCRYPTION: z.union([z.literal("true"), z.literal("false")]).default("true"), + + // Application configuration PRESIGNED_URL_EXPIRATION: z.string().optional().default("3600"), SECURE_SITE: z.union([z.literal("true"), z.literal("false")]).default("false"), + STORAGE_URL: z.string().optional(), // Storage URL for internal storage presigned URLs (required when ENABLE_S3=false, e.g., https://syrg.palmr.com or http://192.168.1.100:9379) DATABASE_URL: z.string().optional().default("file:/app/server/prisma/palmr.db"), - DOWNLOAD_MAX_CONCURRENT: z - .string() - .optional() - .transform((val) => (val ? parseInt(val, 10) : undefined)), - DOWNLOAD_MEMORY_THRESHOLD_MB: z - .string() - .optional() - .transform((val) => (val ? parseInt(val, 10) : undefined)), - DOWNLOAD_QUEUE_SIZE: z - .string() - .optional() - .transform((val) => (val ? parseInt(val, 10) : undefined)), - DOWNLOAD_AUTO_SCALE: z.union([z.literal("true"), z.literal("false")]).default("true"), - DOWNLOAD_MIN_FILE_SIZE_GB: z - .string() - .optional() - .transform((val) => (val ? parseFloat(val) : undefined)), CUSTOM_PATH: z.string().optional(), }); diff --git a/apps/server/src/modules/app/service.ts b/apps/server/src/modules/app/service.ts index f7e9f659..6ca19b9d 100644 --- a/apps/server/src/modules/app/service.ts +++ b/apps/server/src/modules/app/service.ts @@ -1,4 +1,3 @@ -import { isS3Enabled } from "../../config/storage.config"; import { prisma } from "../../shared/prisma"; import { ConfigService } from "../config/service"; @@ -23,8 +22,8 @@ export class AppService { async getSystemInfo() { return { - storageProvider: isS3Enabled ? "s3" : "filesystem", - s3Enabled: isS3Enabled, + storageProvider: "s3", + s3Enabled: true, }; } diff --git a/apps/server/src/modules/file/controller.ts b/apps/server/src/modules/file/controller.ts index e4bccec8..44fca643 100644 --- a/apps/server/src/modules/file/controller.ts +++ b/apps/server/src/modules/file/controller.ts @@ -1,4 +1,3 @@ -import * as fs from "fs"; import bcrypt from "bcryptjs"; import { FastifyReply, FastifyRequest } from "fastify"; @@ -29,31 +28,30 @@ export class FileController { private fileService = new FileService(); private configService = new ConfigService(); - async getPresignedUrl(request: FastifyRequest, reply: FastifyReply) { - try { - const { filename, extension } = request.query as { - filename?: string; - extension?: string; - }; - if (!filename || !extension) { - return reply.status(400).send({ - error: "The 'filename' and 'extension' parameters are required.", - }); - } + async getPresignedUrl(request: FastifyRequest, reply: FastifyReply): Promise { + const { filename, extension } = request.query as { filename: string; extension: string }; + if (!filename || !extension) { + return reply.status(400).send({ error: "filename and extension are required" }); + } + + try { + // JWT already verified by preValidation in routes.ts const userId = (request as any).user?.userId; if (!userId) { - return reply.status(401).send({ error: "Unauthorized: a valid token is required to access this resource." }); + return reply.status(401).send({ error: "Unauthorized" }); } - const objectName = `${userId}/${Date.now()}-${filename}.${extension}`; + // Generate unique object name + const objectName = `${userId}/${Date.now()}-${Math.random().toString(36).substring(7)}-${filename}.${extension}`; const expires = parseInt(env.PRESIGNED_URL_EXPIRATION); const url = await this.fileService.getPresignedPutUrl(objectName, expires); - return reply.send({ url, objectName }); + + return reply.status(200).send({ url, objectName }); } catch (error) { console.error("Error in getPresignedUrl:", error); - return reply.status(500).send({ error: "Internal server error." }); + return reply.status(500).send({ error: "Internal server error" }); } } @@ -219,9 +217,6 @@ export class FileController { let hasAccess = false; - // Don't log raw passwords. Log only whether a password was provided (for debugging access flow). - console.log(`Requested file access for object="${objectName}" passwordProvided=${password ? true : false}`); - const shares = await prisma.share.findMany({ where: { files: { @@ -264,6 +259,8 @@ export class FileController { const fileName = fileRecord.name; const expires = parseInt(env.PRESIGNED_URL_EXPIRATION); + + // Always use presigned URLs (works for both internal and external storage) const url = await this.fileService.getPresignedGetUrl(objectName, expires, fileName); return reply.send({ url, expiresIn: expires }); } catch (error) { @@ -309,16 +306,14 @@ export class FileController { return reply.status(401).send({ error: "Unauthorized access to file." }); } - const storageProvider = (this.fileService as any).storageProvider; - const filePath = storageProvider.getFilePath(objectName); - + // Stream from S3/storage system + const stream = await this.fileService.getObjectStream(objectName); const contentType = getContentType(reverseShareFile.name); const fileName = reverseShareFile.name; reply.header("Content-Type", contentType); reply.header("Content-Disposition", `inline; filename="${encodeURIComponent(fileName)}"`); - const stream = fs.createReadStream(filePath); return reply.send(stream); } @@ -367,16 +362,14 @@ export class FileController { return reply.status(401).send({ error: "Unauthorized access to file." }); } - const storageProvider = (this.fileService as any).storageProvider; - const filePath = storageProvider.getFilePath(objectName); - + // Stream from S3/MinIO + const stream = await this.fileService.getObjectStream(objectName); const contentType = getContentType(fileRecord.name); const fileName = fileRecord.name; reply.header("Content-Type", contentType); reply.header("Content-Disposition", `inline; filename="${encodeURIComponent(fileName)}"`); - const stream = fs.createReadStream(filePath); return reply.send(stream); } catch (error) { console.error("Error in downloadFile:", error); @@ -612,9 +605,8 @@ export class FileController { }); } - const storageProvider = (this.fileService as any).storageProvider; - const filePath = storageProvider.getFilePath(fileRecord.objectName); - + // Stream from S3/MinIO + const stream = await this.fileService.getObjectStream(fileRecord.objectName); const contentType = getContentType(fileRecord.name); const fileName = fileRecord.name; @@ -622,7 +614,6 @@ export class FileController { reply.header("Content-Disposition", `inline; filename="${encodeURIComponent(fileName)}"`); reply.header("Cache-Control", "public, max-age=31536000"); // Cache por 1 ano - const stream = fs.createReadStream(filePath); return reply.send(stream); } catch (error) { console.error("Error in embedFile:", error); @@ -654,4 +645,123 @@ export class FileController { return allFiles; } + + // Multipart upload endpoints + async createMultipartUpload(request: FastifyRequest, reply: FastifyReply): Promise { + try { + const userId = (request as any).user?.userId; + if (!userId) { + return reply.status(401).send({ error: "Unauthorized" }); + } + + const { filename, extension } = request.body as { filename: string; extension: string }; + + if (!filename || !extension) { + return reply.status(400).send({ error: "filename and extension are required" }); + } + + // Generate unique object name (same pattern as simple upload) + const objectName = `${userId}/${Date.now()}-${Math.random().toString(36).substring(7)}-${filename}.${extension}`; + + const uploadId = await this.fileService.createMultipartUpload(objectName); + + return reply.status(200).send({ + uploadId, + objectName, + message: "Multipart upload initialized", + }); + } catch (error) { + console.error("[Multipart] Error creating multipart upload:", error); + return reply.status(500).send({ error: "Failed to create multipart upload" }); + } + } + + async getMultipartPartUrl(request: FastifyRequest, reply: FastifyReply): Promise { + try { + const userId = (request as any).user?.userId; + if (!userId) { + return reply.status(401).send({ error: "Unauthorized" }); + } + + const { uploadId, objectName, partNumber } = request.query as { + uploadId: string; + objectName: string; + partNumber: string; + }; + + if (!uploadId || !objectName || !partNumber) { + return reply.status(400).send({ error: "uploadId, objectName, and partNumber are required" }); + } + + const partNum = parseInt(partNumber); + if (isNaN(partNum) || partNum < 1 || partNum > 10000) { + return reply.status(400).send({ error: "partNumber must be between 1 and 10000" }); + } + + const expires = parseInt(env.PRESIGNED_URL_EXPIRATION); + + const url = await this.fileService.getPresignedPartUrl(objectName, uploadId, partNum, expires); + + return reply.status(200).send({ url }); + } catch (error) { + console.error("[Multipart] Error getting part URL:", error); + return reply.status(500).send({ error: "Failed to get presigned URL for part" }); + } + } + + async completeMultipartUpload(request: FastifyRequest, reply: FastifyReply): Promise { + try { + const userId = (request as any).user?.userId; + if (!userId) { + return reply.status(401).send({ error: "Unauthorized" }); + } + + const { uploadId, objectName, parts } = request.body as { + uploadId: string; + objectName: string; + parts: Array<{ PartNumber: number; ETag: string }>; + }; + + if (!uploadId || !objectName || !parts || !Array.isArray(parts)) { + return reply.status(400).send({ error: "uploadId, objectName, and parts are required" }); + } + + await this.fileService.completeMultipartUpload(objectName, uploadId, parts); + + return reply.status(200).send({ + message: "Multipart upload completed successfully", + objectName, + }); + } catch (error) { + console.error("[Multipart] Error completing multipart upload:", error); + return reply.status(500).send({ error: "Failed to complete multipart upload" }); + } + } + + async abortMultipartUpload(request: FastifyRequest, reply: FastifyReply): Promise { + try { + const userId = (request as any).user?.userId; + if (!userId) { + return reply.status(401).send({ error: "Unauthorized" }); + } + + const { uploadId, objectName } = request.body as { + uploadId: string; + objectName: string; + }; + + if (!uploadId || !objectName) { + return reply.status(400).send({ error: "uploadId and objectName are required" }); + } + + await this.fileService.abortMultipartUpload(objectName, uploadId); + + return reply.status(200).send({ + message: "Multipart upload aborted successfully", + }); + } catch (error) { + console.error("[Multipart] Error aborting multipart upload:", error); + return reply.status(500).send({ error: "Failed to abort multipart upload" }); + } + } } diff --git a/apps/server/src/modules/file/routes.ts b/apps/server/src/modules/file/routes.ts index e6a5eba7..b35c6044 100644 --- a/apps/server/src/modules/file/routes.ts +++ b/apps/server/src/modules/file/routes.ts @@ -309,4 +309,122 @@ export async function fileRoutes(app: FastifyInstance) { }, fileController.deleteFile.bind(fileController) ); + + // Multipart upload routes + app.post( + "/files/multipart/create", + { + preValidation, + schema: { + tags: ["File"], + operationId: "createMultipartUpload", + summary: "Create Multipart Upload", + description: + "Initializes a multipart upload for large files (≥100MB). Returns uploadId for subsequent part uploads.", + body: z.object({ + filename: z.string().min(1).describe("The filename without extension"), + extension: z.string().min(1).describe("The file extension"), + }), + response: { + 200: z.object({ + uploadId: z.string().describe("The upload ID for this multipart upload"), + objectName: z.string().describe("The object name in storage"), + message: z.string().describe("Success message"), + }), + 400: z.object({ error: z.string() }), + 401: z.object({ error: z.string() }), + 500: z.object({ error: z.string() }), + }, + }, + }, + fileController.createMultipartUpload.bind(fileController) + ); + + app.get( + "/files/multipart/part-url", + { + preValidation, + schema: { + tags: ["File"], + operationId: "getMultipartPartUrl", + summary: "Get Presigned URL for Part", + description: "Gets a presigned URL for uploading a specific part of a multipart upload", + querystring: z.object({ + uploadId: z.string().min(1).describe("The multipart upload ID"), + objectName: z.string().min(1).describe("The object name"), + partNumber: z.string().min(1).describe("The part number (1-10000)"), + }), + response: { + 200: z.object({ + url: z.string().describe("The presigned URL for uploading this part"), + }), + 400: z.object({ error: z.string() }), + 401: z.object({ error: z.string() }), + 500: z.object({ error: z.string() }), + }, + }, + }, + fileController.getMultipartPartUrl.bind(fileController) + ); + + app.post( + "/files/multipart/complete", + { + preValidation, + schema: { + tags: ["File"], + operationId: "completeMultipartUpload", + summary: "Complete Multipart Upload", + description: "Completes a multipart upload by combining all uploaded parts", + body: z.object({ + uploadId: z.string().min(1).describe("The multipart upload ID"), + objectName: z.string().min(1).describe("The object name"), + parts: z + .array( + z.object({ + PartNumber: z.number().min(1).max(10000).describe("The part number"), + ETag: z.string().min(1).describe("The ETag returned from uploading the part"), + }) + ) + .describe("Array of uploaded parts"), + }), + response: { + 200: z.object({ + message: z.string().describe("Success message"), + objectName: z.string().describe("The completed object name"), + }), + 400: z.object({ error: z.string() }), + 401: z.object({ error: z.string() }), + 500: z.object({ error: z.string() }), + }, + }, + }, + fileController.completeMultipartUpload.bind(fileController) + ); + + app.post( + "/files/multipart/abort", + { + preValidation, + schema: { + tags: ["File"], + operationId: "abortMultipartUpload", + summary: "Abort Multipart Upload", + description: "Aborts a multipart upload and cleans up all uploaded parts", + body: z.object({ + uploadId: z.string().min(1).describe("The multipart upload ID"), + objectName: z.string().min(1).describe("The object name"), + }), + response: { + 200: z.object({ + message: z.string().describe("Success message"), + }), + 400: z.object({ error: z.string() }), + 401: z.object({ error: z.string() }), + 500: z.object({ error: z.string() }), + }, + }, + }, + fileController.abortMultipartUpload.bind(fileController) + ); } diff --git a/apps/server/src/modules/file/service.ts b/apps/server/src/modules/file/service.ts index ec1f1f6a..7deba2ae 100644 --- a/apps/server/src/modules/file/service.ts +++ b/apps/server/src/modules/file/service.ts @@ -1,5 +1,3 @@ -import { isS3Enabled } from "../../config/storage.config"; -import { FilesystemStorageProvider } from "../../providers/filesystem-storage.provider"; import { S3StorageProvider } from "../../providers/s3-storage.provider"; import { StorageProvider } from "../../types/storage"; @@ -7,41 +5,59 @@ export class FileService { private storageProvider: StorageProvider; constructor() { - if (isS3Enabled) { - this.storageProvider = new S3StorageProvider(); - } else { - this.storageProvider = FilesystemStorageProvider.getInstance(); - } + // Always use S3 (Garage internal or external S3) + this.storageProvider = new S3StorageProvider(); } - async getPresignedPutUrl(objectName: string, expires: number): Promise { - try { - return await this.storageProvider.getPresignedPutUrl(objectName, expires); - } catch (err) { - console.error("Erro no presignedPutObject:", err); - throw err; - } + async getPresignedPutUrl(objectName: string, expires: number = 3600): Promise { + return await this.storageProvider.getPresignedPutUrl(objectName, expires); } - async getPresignedGetUrl(objectName: string, expires: number, fileName?: string): Promise { + async getPresignedGetUrl(objectName: string, expires: number = 3600, fileName?: string): Promise { + return await this.storageProvider.getPresignedGetUrl(objectName, expires, fileName); + } + + async deleteObject(objectName: string): Promise { try { - return await this.storageProvider.getPresignedGetUrl(objectName, expires, fileName); + await this.storageProvider.deleteObject(objectName); } catch (err) { - console.error("Erro no presignedGetObject:", err); + console.error("Erro no removeObject:", err); throw err; } } - async deleteObject(objectName: string): Promise { + async getObjectStream(objectName: string): Promise { try { - await this.storageProvider.deleteObject(objectName); + return await this.storageProvider.getObjectStream(objectName); } catch (err) { - console.error("Erro no removeObject:", err); + console.error("Error getting object stream:", err); throw err; } } - isFilesystemMode(): boolean { - return !isS3Enabled; + // Multipart upload methods + async createMultipartUpload(objectName: string): Promise { + return await this.storageProvider.createMultipartUpload(objectName); + } + + async getPresignedPartUrl( + objectName: string, + uploadId: string, + partNumber: number, + expires: number = 3600 + ): Promise { + return await this.storageProvider.getPresignedPartUrl(objectName, uploadId, partNumber, expires); + } + + async completeMultipartUpload( + objectName: string, + uploadId: string, + parts: Array<{ PartNumber: number; ETag: string }> + ): Promise { + await this.storageProvider.completeMultipartUpload(objectName, uploadId, parts); + } + + async abortMultipartUpload(objectName: string, uploadId: string): Promise { + await this.storageProvider.abortMultipartUpload(objectName, uploadId); } } diff --git a/apps/server/src/modules/filesystem/chunk-manager.ts b/apps/server/src/modules/filesystem/chunk-manager.ts deleted file mode 100644 index a783489a..00000000 --- a/apps/server/src/modules/filesystem/chunk-manager.ts +++ /dev/null @@ -1,345 +0,0 @@ -import * as fs from "fs"; -import * as path from "path"; - -import { getTempFilePath } from "../../config/directories.config"; -import { FilesystemStorageProvider } from "../../providers/filesystem-storage.provider"; - -export interface ChunkMetadata { - fileId: string; - chunkIndex: number; - totalChunks: number; - chunkSize: number; - totalSize: number; - fileName: string; - isLastChunk: boolean; -} - -export interface ChunkInfo { - fileId: string; - fileName: string; - totalSize: number; - totalChunks: number; - uploadedChunks: Set; - tempPath: string; - createdAt: number; -} - -export class ChunkManager { - private static instance: ChunkManager; - private activeUploads = new Map(); - private finalizingUploads = new Set(); // Track uploads currently being finalized - private cleanupInterval: NodeJS.Timeout; - - private constructor() { - // Cleanup expired uploads every 30 minutes - this.cleanupInterval = setInterval( - () => { - this.cleanupExpiredUploads(); - }, - 30 * 60 * 1000 - ); - } - - public static getInstance(): ChunkManager { - if (!ChunkManager.instance) { - ChunkManager.instance = new ChunkManager(); - } - return ChunkManager.instance; - } - - /** - * Process a chunk upload with streaming - */ - async processChunk( - metadata: ChunkMetadata, - inputStream: NodeJS.ReadableStream, - originalObjectName: string - ): Promise<{ isComplete: boolean; finalPath?: string }> { - const startTime = Date.now(); - const { fileId, chunkIndex, totalChunks, fileName, totalSize, isLastChunk } = metadata; - - console.log(`Processing chunk ${chunkIndex + 1}/${totalChunks} for file ${fileName} (${fileId})`); - - let chunkInfo = this.activeUploads.get(fileId); - if (!chunkInfo) { - if (chunkIndex !== 0) { - throw new Error("First chunk must be chunk 0"); - } - - const tempPath = getTempFilePath(fileId); - chunkInfo = { - fileId, - fileName, - totalSize, - totalChunks, - uploadedChunks: new Set(), - tempPath, - createdAt: Date.now(), - }; - this.activeUploads.set(fileId, chunkInfo); - console.log(`Created new upload session for ${fileName} at ${tempPath}`); - } - - console.log( - `Validating chunk ${chunkIndex} (total: ${totalChunks}, uploaded: ${Array.from(chunkInfo.uploadedChunks).join(",")})` - ); - - if (chunkIndex < 0 || chunkIndex >= totalChunks) { - throw new Error(`Invalid chunk index: ${chunkIndex} (must be 0-${totalChunks - 1})`); - } - - if (chunkInfo.uploadedChunks.has(chunkIndex)) { - console.log(`Chunk ${chunkIndex} already uploaded, treating as success`); - - if (isLastChunk && chunkInfo.uploadedChunks.size === totalChunks) { - if (this.finalizingUploads.has(fileId)) { - console.log(`Upload ${fileId} is already being finalized, waiting...`); - return { isComplete: false }; - } - - console.log(`All chunks uploaded, finalizing ${fileName}`); - return await this.finalizeUpload(chunkInfo, metadata, originalObjectName); - } - - return { isComplete: false }; - } - - const tempDir = path.dirname(chunkInfo.tempPath); - await fs.promises.mkdir(tempDir, { recursive: true }); - console.log(`Temp directory ensured: ${tempDir}`); - - await this.writeChunkToFile(chunkInfo.tempPath, inputStream, chunkIndex === 0); - - chunkInfo.uploadedChunks.add(chunkIndex); - - try { - const stats = await fs.promises.stat(chunkInfo.tempPath); - const processingTime = Date.now() - startTime; - console.log( - `Chunk ${chunkIndex + 1}/${totalChunks} uploaded successfully in ${processingTime}ms. Temp file size: ${stats.size} bytes` - ); - } catch (error) { - console.warn(`Could not get temp file stats:`, error); - } - - console.log( - `Checking completion: isLastChunk=${isLastChunk}, uploadedChunks.size=${chunkInfo.uploadedChunks.size}, totalChunks=${totalChunks}` - ); - - if (isLastChunk && chunkInfo.uploadedChunks.size === totalChunks) { - if (this.finalizingUploads.has(fileId)) { - console.log(`Upload ${fileId} is already being finalized, waiting...`); - return { isComplete: false }; - } - - console.log(`All chunks uploaded, finalizing ${fileName}`); - - const uploadedChunksArray = Array.from(chunkInfo.uploadedChunks).sort((a, b) => a - b); - console.log(`Uploaded chunks in order: ${uploadedChunksArray.join(", ")}`); - - const expectedChunks = Array.from({ length: totalChunks }, (_, i) => i); - const missingChunks = expectedChunks.filter((chunk) => !chunkInfo.uploadedChunks.has(chunk)); - - if (missingChunks.length > 0) { - throw new Error(`Missing chunks: ${missingChunks.join(", ")}`); - } - - return await this.finalizeUpload(chunkInfo, metadata, originalObjectName); - } else { - console.log( - `Not ready for finalization: isLastChunk=${isLastChunk}, uploadedChunks.size=${chunkInfo.uploadedChunks.size}, totalChunks=${totalChunks}` - ); - } - - return { isComplete: false }; - } - - /** - * Write chunk to file using streaming - */ - private async writeChunkToFile( - filePath: string, - inputStream: NodeJS.ReadableStream, - isFirstChunk: boolean - ): Promise { - return new Promise((resolve, reject) => { - console.log(`Writing chunk to ${filePath} (first: ${isFirstChunk})`); - - if (isFirstChunk) { - const writeStream = fs.createWriteStream(filePath, { - highWaterMark: 64 * 1024 * 1024, // 64MB buffer for better performance - }); - writeStream.on("error", (error) => { - console.error("Write stream error:", error); - reject(error); - }); - writeStream.on("finish", () => { - console.log("Write stream finished successfully"); - resolve(); - }); - inputStream.pipe(writeStream); - } else { - const writeStream = fs.createWriteStream(filePath, { - flags: "a", - highWaterMark: 64 * 1024 * 1024, // 64MB buffer for better performance - }); - writeStream.on("error", (error) => { - console.error("Write stream error:", error); - reject(error); - }); - writeStream.on("finish", () => { - console.log("Write stream finished successfully"); - resolve(); - }); - inputStream.pipe(writeStream); - } - }); - } - - /** - * Finalize upload by moving temp file to final location and encrypting (if enabled) - */ - private async finalizeUpload( - chunkInfo: ChunkInfo, - metadata: ChunkMetadata, - originalObjectName: string - ): Promise<{ isComplete: boolean; finalPath: string }> { - // Mark as finalizing to prevent race conditions - this.finalizingUploads.add(chunkInfo.fileId); - - try { - console.log(`Finalizing upload for ${chunkInfo.fileName}`); - - const tempStats = await fs.promises.stat(chunkInfo.tempPath); - console.log(`Temp file size: ${tempStats.size} bytes, expected: ${chunkInfo.totalSize} bytes`); - - if (tempStats.size !== chunkInfo.totalSize) { - console.warn(`Size mismatch! Temp: ${tempStats.size}, Expected: ${chunkInfo.totalSize}`); - } - - const provider = FilesystemStorageProvider.getInstance(); - const finalObjectName = originalObjectName; - const filePath = provider.getFilePath(finalObjectName); - const dir = path.dirname(filePath); - - console.log(`Starting finalization: ${finalObjectName}`); - - await fs.promises.mkdir(dir, { recursive: true }); - - const tempReadStream = fs.createReadStream(chunkInfo.tempPath, { - highWaterMark: 64 * 1024 * 1024, // 64MB buffer for better performance - }); - const writeStream = fs.createWriteStream(filePath, { - highWaterMark: 64 * 1024 * 1024, - }); - const encryptStream = provider.createEncryptStream(); - - await new Promise((resolve, reject) => { - const startTime = Date.now(); - - tempReadStream - .pipe(encryptStream) - .pipe(writeStream) - .on("finish", () => { - const duration = Date.now() - startTime; - console.log(`File processed and saved to: ${filePath} in ${duration}ms`); - resolve(); - }) - .on("error", (error) => { - console.error("Error during processing:", error); - reject(error); - }); - }); - - console.log(`File successfully uploaded and processed: ${finalObjectName}`); - - await this.cleanupTempFile(chunkInfo.tempPath); - - this.activeUploads.delete(chunkInfo.fileId); - this.finalizingUploads.delete(chunkInfo.fileId); - - return { isComplete: true, finalPath: finalObjectName }; - } catch (error) { - console.error("Error during finalization:", error); - await this.cleanupTempFile(chunkInfo.tempPath); - this.activeUploads.delete(chunkInfo.fileId); - this.finalizingUploads.delete(chunkInfo.fileId); - throw error; - } - } - - /** - * Cleanup temporary file - */ - private async cleanupTempFile(tempPath: string): Promise { - try { - await fs.promises.access(tempPath); - await fs.promises.unlink(tempPath); - console.log(`Temp file cleaned up: ${tempPath}`); - } catch (error: any) { - if (error.code === "ENOENT") { - console.log(`Temp file already cleaned up: ${tempPath}`); - } else { - console.warn(`Failed to cleanup temp file ${tempPath}:`, error); - } - } - } - - /** - * Cleanup expired uploads (older than 2 hours) - */ - private async cleanupExpiredUploads(): Promise { - const now = Date.now(); - const maxAge = 2 * 60 * 60 * 1000; // 2 hours - - for (const [fileId, chunkInfo] of this.activeUploads.entries()) { - if (now - chunkInfo.createdAt > maxAge) { - console.log(`Cleaning up expired upload: ${fileId}`); - await this.cleanupTempFile(chunkInfo.tempPath); - this.activeUploads.delete(fileId); - this.finalizingUploads.delete(fileId); - } - } - } - - /** - * Get upload progress - */ - getUploadProgress(fileId: string): { uploaded: number; total: number; percentage: number } | null { - const chunkInfo = this.activeUploads.get(fileId); - if (!chunkInfo) return null; - - return { - uploaded: chunkInfo.uploadedChunks.size, - total: chunkInfo.totalChunks, - percentage: Math.round((chunkInfo.uploadedChunks.size / chunkInfo.totalChunks) * 100), - }; - } - - /** - * Cancel upload - */ - async cancelUpload(fileId: string): Promise { - const chunkInfo = this.activeUploads.get(fileId); - if (chunkInfo) { - await this.cleanupTempFile(chunkInfo.tempPath); - this.activeUploads.delete(fileId); - this.finalizingUploads.delete(fileId); - } - } - - /** - * Cleanup on shutdown - */ - destroy(): void { - if (this.cleanupInterval) { - clearInterval(this.cleanupInterval); - } - - for (const [fileId, chunkInfo] of this.activeUploads.entries()) { - this.cleanupTempFile(chunkInfo.tempPath); - } - this.activeUploads.clear(); - this.finalizingUploads.clear(); - } -} diff --git a/apps/server/src/modules/filesystem/controller.ts b/apps/server/src/modules/filesystem/controller.ts deleted file mode 100644 index e736877a..00000000 --- a/apps/server/src/modules/filesystem/controller.ts +++ /dev/null @@ -1,444 +0,0 @@ -import * as fs from "fs"; -import { pipeline } from "stream/promises"; -import { FastifyReply, FastifyRequest } from "fastify"; - -import { FilesystemStorageProvider } from "../../providers/filesystem-storage.provider"; -import { DownloadCancelResponse, QueueClearResponse, QueueStatusResponse } from "../../types/download-queue"; -import { DownloadMemoryManager } from "../../utils/download-memory-manager"; -import { getContentType } from "../../utils/mime-types"; -import { ChunkManager, ChunkMetadata } from "./chunk-manager"; - -export class FilesystemController { - private chunkManager = ChunkManager.getInstance(); - private memoryManager = DownloadMemoryManager.getInstance(); - - /** - * Check if a character is valid in an HTTP token (RFC 2616) - * Tokens can contain: alphanumeric and !#$%&'*+-.^_`|~ - * Must exclude separators: ()<>@,;:\"/[]?={} and space/tab - */ - private isTokenChar(char: string): boolean { - const code = char.charCodeAt(0); - // Basic ASCII range check - if (code < 33 || code > 126) return false; - // Exclude separator characters per RFC 2616 - const separators = '()<>@,;:\\"/[]?={} \t'; - return !separators.includes(char); - } - - private encodeFilenameForHeader(filename: string): string { - if (!filename || filename.trim() === "") { - return 'attachment; filename="download"'; - } - - let sanitized = filename - .replace(/"/g, "'") - .replace(/[\r\n\t\v\f]/g, "") - .replace(/[\\|/]/g, "-") - .replace(/[<>:|*?]/g, ""); - - sanitized = sanitized - .split("") - .filter((char) => { - const code = char.charCodeAt(0); - return code >= 32 && !(code >= 127 && code <= 159); - }) - .join("") - .trim(); - - if (!sanitized) { - return 'attachment; filename="download"'; - } - - // Create ASCII-safe version with only valid token characters - const asciiSafe = sanitized - .split("") - .filter((char) => this.isTokenChar(char)) - .join(""); - - if (asciiSafe && asciiSafe.trim()) { - const encoded = encodeURIComponent(sanitized); - return `attachment; filename="${asciiSafe}"; filename*=UTF-8''${encoded}`; - } else { - const encoded = encodeURIComponent(sanitized); - return `attachment; filename*=UTF-8''${encoded}`; - } - } - - async upload(request: FastifyRequest, reply: FastifyReply) { - try { - const { token } = request.params as { token: string }; - - const provider = FilesystemStorageProvider.getInstance(); - - const tokenData = provider.validateUploadToken(token); - - if (!tokenData) { - return reply.status(400).send({ error: "Invalid or expired upload token" }); - } - - const chunkMetadata = this.extractChunkMetadata(request); - - if (chunkMetadata) { - try { - const result = await this.handleChunkedUpload(request, chunkMetadata, tokenData.objectName); - - if (result.isComplete) { - reply.status(200).send({ - message: "File uploaded successfully", - objectName: result.finalPath, - finalObjectName: result.finalPath, - }); - } else { - reply.status(200).send({ - message: "Chunk uploaded successfully", - progress: this.chunkManager.getUploadProgress(chunkMetadata.fileId), - }); - } - } catch (chunkError: any) { - return reply.status(400).send({ - error: chunkError.message || "Chunked upload failed", - details: chunkError.toString(), - }); - } - } else { - await this.uploadFileStream(request, provider, tokenData.objectName); - reply.status(200).send({ message: "File uploaded successfully" }); - } - } catch (error) { - return reply.status(500).send({ error: "Internal server error" }); - } - } - - private async uploadFileStream(request: FastifyRequest, provider: FilesystemStorageProvider, objectName: string) { - await provider.uploadFileFromStream(objectName, request.raw); - } - - private extractChunkMetadata(request: FastifyRequest): ChunkMetadata | null { - const fileId = request.headers["x-file-id"] as string; - const chunkIndex = request.headers["x-chunk-index"] as string; - const totalChunks = request.headers["x-total-chunks"] as string; - const chunkSize = request.headers["x-chunk-size"] as string; - const totalSize = request.headers["x-total-size"] as string; - const encodedFileName = request.headers["x-file-name"] as string; - const isLastChunk = request.headers["x-is-last-chunk"] as string; - - if (!fileId || !chunkIndex || !totalChunks || !chunkSize || !totalSize || !encodedFileName) { - return null; - } - - // Decode the base64-encoded filename to handle UTF-8 characters - let fileName: string; - try { - fileName = decodeURIComponent(escape(Buffer.from(encodedFileName, "base64").toString("binary"))); - } catch (error) { - // Fallback to the encoded value if decoding fails (for backward compatibility) - fileName = encodedFileName; - } - - const metadata = { - fileId, - chunkIndex: parseInt(chunkIndex, 10), - totalChunks: parseInt(totalChunks, 10), - chunkSize: parseInt(chunkSize, 10), - totalSize: parseInt(totalSize, 10), - fileName, - isLastChunk: isLastChunk === "true", - }; - - return metadata; - } - - private async handleChunkedUpload(request: FastifyRequest, metadata: ChunkMetadata, originalObjectName: string) { - const stream = request.raw; - - stream.on("error", (error) => { - console.error("Request stream error:", error); - }); - - return await this.chunkManager.processChunk(metadata, stream, originalObjectName); - } - - async getUploadProgress(request: FastifyRequest, reply: FastifyReply) { - try { - const { fileId } = request.params as { fileId: string }; - - const progress = this.chunkManager.getUploadProgress(fileId); - - if (!progress) { - return reply.status(404).send({ error: "Upload not found" }); - } - - reply.status(200).send(progress); - } catch (error) { - return reply.status(500).send({ error: "Internal server error" }); - } - } - - async cancelUpload(request: FastifyRequest, reply: FastifyReply) { - try { - const { fileId } = request.params as { fileId: string }; - - await this.chunkManager.cancelUpload(fileId); - - reply.status(200).send({ message: "Upload cancelled successfully" }); - } catch (error) { - return reply.status(500).send({ error: "Internal server error" }); - } - } - - async download(request: FastifyRequest, reply: FastifyReply) { - const downloadId = `${Date.now()}-${Math.random().toString(36).substring(2, 11)}`; - - try { - const { token } = request.params as { token: string }; - const provider = FilesystemStorageProvider.getInstance(); - - const tokenData = provider.validateDownloadToken(token); - - if (!tokenData) { - return reply.status(400).send({ error: "Invalid or expired download token" }); - } - - const filePath = provider.getFilePath(tokenData.objectName); - - const fileExists = await provider.fileExists(tokenData.objectName); - if (!fileExists) { - console.error(`[DOWNLOAD] File not found: ${tokenData.objectName}`); - return reply.status(404).send({ - error: "File not found", - message: - "The requested file does not exist on the server. It may have been deleted or the upload was incomplete.", - }); - } - - const stats = await fs.promises.stat(filePath); - const fileSize = stats.size; - const fileName = tokenData.fileName || "download"; - - const fileSizeMB = fileSize / (1024 * 1024); - console.log(`[DOWNLOAD] Requesting slot for ${downloadId}: ${tokenData.objectName} (${fileSizeMB.toFixed(1)}MB)`); - - try { - await this.memoryManager.requestDownloadSlot(downloadId, { - fileName, - fileSize, - objectName: tokenData.objectName, - }); - } catch (error: any) { - console.warn(`[DOWNLOAD] Queue full for ${downloadId}: ${error.message}`); - return reply.status(503).send({ - error: "Download queue is full", - message: error.message, - retryAfter: 60, - }); - } - - console.log(`[DOWNLOAD] Starting ${downloadId}: ${tokenData.objectName} (${fileSizeMB.toFixed(1)}MB)`); - this.memoryManager.startDownload(downloadId); - - const range = request.headers.range; - - reply.header("Content-Disposition", this.encodeFilenameForHeader(fileName)); - reply.header("Content-Type", getContentType(fileName)); - reply.header("Accept-Ranges", "bytes"); - reply.header("X-Download-ID", downloadId); - - reply.raw.on("close", () => { - this.memoryManager.endDownload(downloadId); - console.log(`[DOWNLOAD] Client disconnected: ${downloadId}`); - }); - - reply.raw.on("error", () => { - this.memoryManager.endDownload(downloadId); - console.log(`[DOWNLOAD] Client error: ${downloadId}`); - }); - - try { - if (range) { - const parts = range.replace(/bytes=/, "").split("-"); - const start = parseInt(parts[0], 10); - const end = parts[1] ? parseInt(parts[1], 10) : fileSize - 1; - - reply.status(206); - reply.header("Content-Range", `bytes ${start}-${end}/${fileSize}`); - reply.header("Content-Length", end - start + 1); - - await this.downloadFileRange(reply, provider, tokenData.objectName, start, end, downloadId); - } else { - reply.header("Content-Length", fileSize); - await this.downloadFileStream(reply, provider, tokenData.objectName, downloadId); - } - } finally { - this.memoryManager.endDownload(downloadId); - } - } catch (error) { - this.memoryManager.endDownload(downloadId); - console.error(`[DOWNLOAD] Error in ${downloadId}:`, error); - return reply.status(500).send({ error: "Internal server error" }); - } - } - - private async downloadFileStream( - reply: FastifyReply, - provider: FilesystemStorageProvider, - objectName: string, - downloadId?: string - ) { - try { - FilesystemStorageProvider.logMemoryUsage(`Download start: ${objectName} (${downloadId})`); - - const downloadStream = provider.createDownloadStream(objectName); - - downloadStream.on("error", (error) => { - console.error("Download stream error:", error); - FilesystemStorageProvider.logMemoryUsage(`Download error: ${objectName} (${downloadId})`); - if (!reply.sent) { - reply.status(500).send({ error: "Download failed" }); - } - }); - - reply.raw.on("close", () => { - if (downloadStream.readable && typeof (downloadStream as any).destroy === "function") { - (downloadStream as any).destroy(); - } - FilesystemStorageProvider.logMemoryUsage(`Download client disconnect: ${objectName} (${downloadId})`); - }); - - if (this.memoryManager.shouldThrottleStream()) { - console.log( - `[MEMORY THROTTLE] ${objectName} - Pausing stream due to high memory usage: ${this.memoryManager.getCurrentMemoryUsageMB().toFixed(0)}MB` - ); - - const { Transform } = require("stream"); - const memoryManager = this.memoryManager; - const throttleStream = new Transform({ - highWaterMark: 256 * 1024, - transform(chunk: Buffer, _encoding: BufferEncoding, callback: (error?: Error | null, data?: any) => void) { - if (memoryManager.shouldThrottleStream()) { - setImmediate(() => { - this.push(chunk); - callback(); - }); - } else { - this.push(chunk); - callback(); - } - }, - }); - - await pipeline(downloadStream, throttleStream, reply.raw); - } else { - await pipeline(downloadStream, reply.raw); - } - - FilesystemStorageProvider.logMemoryUsage(`Download complete: ${objectName} (${downloadId})`); - } catch (error) { - console.error("Download error:", error); - FilesystemStorageProvider.logMemoryUsage(`Download failed: ${objectName} (${downloadId})`); - if (!reply.sent) { - reply.status(500).send({ error: "Download failed" }); - } - } - } - - private async downloadFileRange( - reply: FastifyReply, - provider: FilesystemStorageProvider, - objectName: string, - start: number, - end: number, - downloadId?: string - ) { - try { - FilesystemStorageProvider.logMemoryUsage(`Range download start: ${objectName} (${start}-${end}) (${downloadId})`); - - const rangeStream = await provider.createDownloadRangeStream(objectName, start, end); - - rangeStream.on("error", (error) => { - console.error("Range download stream error:", error); - FilesystemStorageProvider.logMemoryUsage( - `Range download error: ${objectName} (${start}-${end}) (${downloadId})` - ); - if (!reply.sent) { - reply.status(500).send({ error: "Download failed" }); - } - }); - - reply.raw.on("close", () => { - if (rangeStream.readable && typeof (rangeStream as any).destroy === "function") { - (rangeStream as any).destroy(); - } - FilesystemStorageProvider.logMemoryUsage( - `Range download client disconnect: ${objectName} (${start}-${end}) (${downloadId})` - ); - }); - - await pipeline(rangeStream, reply.raw); - - FilesystemStorageProvider.logMemoryUsage( - `Range download complete: ${objectName} (${start}-${end}) (${downloadId})` - ); - } catch (error) { - console.error("Range download error:", error); - FilesystemStorageProvider.logMemoryUsage( - `Range download failed: ${objectName} (${start}-${end}) (${downloadId})` - ); - if (!reply.sent) { - reply.status(500).send({ error: "Download failed" }); - } - } - } - - async getQueueStatus(_request: FastifyRequest, reply: FastifyReply) { - try { - const queueStatus = this.memoryManager.getQueueStatus(); - const response: QueueStatusResponse = { - status: "success", - data: queueStatus, - }; - reply.status(200).send(response); - } catch (error) { - console.error("Error getting queue status:", error); - return reply.status(500).send({ error: "Internal server error" }); - } - } - - async cancelQueuedDownload(request: FastifyRequest, reply: FastifyReply) { - try { - const { downloadId } = request.params as { downloadId: string }; - - const cancelled = this.memoryManager.cancelQueuedDownload(downloadId); - - if (cancelled) { - const response: DownloadCancelResponse = { - message: "Download cancelled successfully", - downloadId, - }; - reply.status(200).send(response); - } else { - reply.status(404).send({ - error: "Download not found in queue", - downloadId, - }); - } - } catch (error) { - console.error("Error cancelling queued download:", error); - return reply.status(500).send({ error: "Internal server error" }); - } - } - - async clearDownloadQueue(_request: FastifyRequest, reply: FastifyReply) { - try { - const clearedCount = this.memoryManager.clearQueue(); - const response: QueueClearResponse = { - message: "Download queue cleared successfully", - clearedCount, - }; - reply.status(200).send(response); - } catch (error) { - console.error("Error clearing download queue:", error); - return reply.status(500).send({ error: "Internal server error" }); - } - } -} diff --git a/apps/server/src/modules/filesystem/download-queue-routes.ts b/apps/server/src/modules/filesystem/download-queue-routes.ts deleted file mode 100644 index c7920f8d..00000000 --- a/apps/server/src/modules/filesystem/download-queue-routes.ts +++ /dev/null @@ -1,95 +0,0 @@ -import { FastifyInstance } from "fastify"; -import { z } from "zod"; - -import { FilesystemController } from "./controller"; - -export async function downloadQueueRoutes(app: FastifyInstance) { - const filesystemController = new FilesystemController(); - - app.get( - "/filesystem/download-queue/status", - { - schema: { - tags: ["Download Queue"], - operationId: "getDownloadQueueStatus", - summary: "Get download queue status", - description: "Get current status of the download queue including active downloads and queue length", - response: { - 200: z.object({ - status: z.string(), - data: z.object({ - queueLength: z.number(), - maxQueueSize: z.number(), - activeDownloads: z.number(), - maxConcurrent: z.number(), - queuedDownloads: z.array( - z.object({ - downloadId: z.string(), - position: z.number(), - waitTime: z.number(), - fileName: z.string().optional(), - fileSize: z.number().optional(), - }) - ), - }), - }), - 500: z.object({ - error: z.string(), - }), - }, - }, - }, - filesystemController.getQueueStatus.bind(filesystemController) - ); - - app.delete( - "/filesystem/download-queue/:downloadId", - { - schema: { - tags: ["Download Queue"], - operationId: "cancelQueuedDownload", - summary: "Cancel a queued download", - description: "Cancel a specific download that is waiting in the queue", - params: z.object({ - downloadId: z.string().describe("Download ID"), - }), - response: { - 200: z.object({ - message: z.string(), - downloadId: z.string(), - }), - 404: z.object({ - error: z.string(), - downloadId: z.string(), - }), - 500: z.object({ - error: z.string(), - }), - }, - }, - }, - filesystemController.cancelQueuedDownload.bind(filesystemController) - ); - - app.delete( - "/filesystem/download-queue", - { - schema: { - tags: ["Download Queue"], - operationId: "clearDownloadQueue", - summary: "Clear entire download queue", - description: "Cancel all downloads waiting in the queue (admin operation)", - response: { - 200: z.object({ - message: z.string(), - clearedCount: z.number(), - }), - 500: z.object({ - error: z.string(), - }), - }, - }, - }, - filesystemController.clearDownloadQueue.bind(filesystemController) - ); -} diff --git a/apps/server/src/modules/filesystem/routes.ts b/apps/server/src/modules/filesystem/routes.ts deleted file mode 100644 index bf23c966..00000000 --- a/apps/server/src/modules/filesystem/routes.ts +++ /dev/null @@ -1,123 +0,0 @@ -import { FastifyInstance, FastifyRequest } from "fastify"; -import { z } from "zod"; - -import { FilesystemController } from "./controller"; - -export async function filesystemRoutes(app: FastifyInstance) { - const filesystemController = new FilesystemController(); - - app.addContentTypeParser("*", async (request: FastifyRequest, payload: any) => { - return payload; - }); - - app.addContentTypeParser("application/json", async (request: FastifyRequest, payload: any) => { - return payload; - }); - - app.put( - "/filesystem/upload/:token", - { - bodyLimit: 1024 * 1024 * 1024 * 1024 * 1024, // 1PB limit - schema: { - tags: ["Filesystem"], - operationId: "uploadToFilesystem", - summary: "Upload file to filesystem storage", - description: "Upload a file directly to the encrypted filesystem storage", - params: z.object({ - token: z.string().describe("Upload token"), - }), - response: { - 200: z.object({ - message: z.string(), - }), - 400: z.object({ - error: z.string(), - }), - 500: z.object({ - error: z.string(), - }), - }, - }, - }, - filesystemController.upload.bind(filesystemController) - ); - - app.get( - "/filesystem/download/:token", - { - bodyLimit: 1024 * 1024 * 1024 * 1024 * 1024, // 1PB limit - schema: { - tags: ["Filesystem"], - operationId: "downloadFromFilesystem", - summary: "Download file from filesystem storage", - description: "Download a file directly from the encrypted filesystem storage", - params: z.object({ - token: z.string().describe("Download token"), - }), - response: { - 200: z.string().describe("File content"), - 400: z.object({ - error: z.string(), - }), - 500: z.object({ - error: z.string(), - }), - }, - }, - }, - filesystemController.download.bind(filesystemController) - ); - - app.get( - "/filesystem/upload-progress/:fileId", - { - schema: { - tags: ["Filesystem"], - operationId: "getUploadProgress", - summary: "Get chunked upload progress", - description: "Get the progress of a chunked upload", - params: z.object({ - fileId: z.string().describe("File ID"), - }), - response: { - 200: z.object({ - uploaded: z.number(), - total: z.number(), - percentage: z.number(), - }), - 404: z.object({ - error: z.string(), - }), - 500: z.object({ - error: z.string(), - }), - }, - }, - }, - filesystemController.getUploadProgress.bind(filesystemController) - ); - - app.delete( - "/filesystem/cancel-upload/:fileId", - { - schema: { - tags: ["Filesystem"], - operationId: "cancelUpload", - summary: "Cancel chunked upload", - description: "Cancel an ongoing chunked upload", - params: z.object({ - fileId: z.string().describe("File ID"), - }), - response: { - 200: z.object({ - message: z.string(), - }), - 500: z.object({ - error: z.string(), - }), - }, - }, - }, - filesystemController.cancelUpload.bind(filesystemController) - ); -} diff --git a/apps/server/src/modules/folder/service.ts b/apps/server/src/modules/folder/service.ts index 31040338..9a548e13 100644 --- a/apps/server/src/modules/folder/service.ts +++ b/apps/server/src/modules/folder/service.ts @@ -1,5 +1,3 @@ -import { isS3Enabled } from "../../config/storage.config"; -import { FilesystemStorageProvider } from "../../providers/filesystem-storage.provider"; import { S3StorageProvider } from "../../providers/s3-storage.provider"; import { prisma } from "../../shared/prisma"; import { StorageProvider } from "../../types/storage"; @@ -8,11 +6,8 @@ export class FolderService { private storageProvider: StorageProvider; constructor() { - if (isS3Enabled) { - this.storageProvider = new S3StorageProvider(); - } else { - this.storageProvider = FilesystemStorageProvider.getInstance(); - } + // Always use S3 (Garage internal or external S3) + this.storageProvider = new S3StorageProvider(); } async getPresignedPutUrl(objectName: string, expires: number): Promise { @@ -42,10 +37,6 @@ export class FolderService { } } - isFilesystemMode(): boolean { - return !isS3Enabled; - } - async getAllFilesInFolder(folderId: string, userId: string, basePath: string = ""): Promise { const files = await prisma.file.findMany({ where: { folderId, userId }, diff --git a/apps/server/src/modules/invite/controller.ts b/apps/server/src/modules/invite/controller.ts new file mode 100644 index 00000000..00bf2214 --- /dev/null +++ b/apps/server/src/modules/invite/controller.ts @@ -0,0 +1,87 @@ +import { FastifyReply, FastifyRequest } from "fastify"; + +import { InviteService } from "./service"; + +export class InviteController { + private inviteService = new InviteService(); + + async generateInviteToken(request: FastifyRequest, reply: FastifyReply) { + try { + const user = request.user as any; + + if (!user || !user.isAdmin) { + return reply.status(403).send({ error: "Forbidden: admin access required" }); + } + + const { token, expiresAt } = await this.inviteService.generateInviteToken(user.userId || user.id); + return reply.send({ token, expiresAt }); + } catch (error) { + console.error("[Invite Controller] Error generating invite token:", error); + return reply.status(500).send({ error: "Failed to generate invite token" }); + } + } + + async validateInviteToken(request: FastifyRequest<{ Params: { token: string } }>, reply: FastifyReply) { + try { + const { token } = request.params; + const validation = await this.inviteService.validateInviteToken(token); + + return reply.send(validation); + } catch (error) { + console.error("Error validating invite token:", error); + return reply.status(500).send({ error: "Failed to validate invite token" }); + } + } + + async registerWithInvite( + request: FastifyRequest<{ + Body: { + token: string; + firstName: string; + lastName: string; + username: string; + email: string; + password: string; + }; + }>, + reply: FastifyReply + ) { + try { + const { token, firstName, lastName, username, email, password } = request.body; + + const user = await this.inviteService.registerWithInvite({ + token, + firstName, + lastName, + username, + email, + password, + }); + + return reply.send({ + message: "User registered successfully", + user, + }); + } catch (error: any) { + console.error("Error registering with invite:", error); + + if (error.message.includes("already been used")) { + return reply.status(400).send({ error: "This invite link has already been used" }); + } + if (error.message.includes("expired")) { + return reply.status(400).send({ error: "This invite link has expired" }); + } + if (error.message.includes("Invalid invite")) { + return reply.status(400).send({ error: "Invalid invite link" }); + } + if (error.message.includes("Username already exists")) { + return reply.status(400).send({ error: "Username already exists" }); + } + if (error.message.includes("Email already exists")) { + return reply.status(400).send({ error: "Email already exists" }); + } + + return reply.status(500).send({ error: "Failed to register user" }); + } + } +} diff --git a/apps/server/src/modules/invite/dto.ts b/apps/server/src/modules/invite/dto.ts new file mode 100644 index 00000000..3cfd6df3 --- /dev/null +++ b/apps/server/src/modules/invite/dto.ts @@ -0,0 +1,30 @@ +import { z } from "zod"; + +export const CreateInviteTokenResponseSchema = z.object({ + token: z.string().describe("Invite token"), + expiresAt: z.coerce.date().describe("Token expiration date"), +}); + +export const ValidateInviteTokenResponseSchema = z.object({ + valid: z.boolean().describe("Whether the token is valid"), + used: z.boolean().optional().describe("Whether the token has been used"), + expired: z.boolean().optional().describe("Whether the token has expired"), +}); + +export const RegisterWithInviteSchema = z.object({ + token: z.string().min(1, "Token is required").describe("Invite token"), + firstName: z.string().min(1, "First name is required").describe("User first name"), + lastName: z.string().min(1, "Last name is required").describe("User last name"), + username: z.string().min(3, "Username must be at least 3 characters").describe("User username"), + email: z.string().email("Invalid email").describe("User email"), + password: z.string().min(8, "Password must be at least 8 characters").describe("User password"), +}); + +export const RegisterWithInviteResponseSchema = z.object({ + message: z.string().describe("Success message"), + user: z.object({ + id: z.string().describe("User ID"), + username: z.string().describe("User username"), + email: z.string().email().describe("User email"), + }), +}); diff --git a/apps/server/src/modules/invite/routes.ts b/apps/server/src/modules/invite/routes.ts new file mode 100644 index 00000000..93e36a18 --- /dev/null +++ b/apps/server/src/modules/invite/routes.ts @@ -0,0 +1,79 @@ +import { FastifyInstance, FastifyReply, FastifyRequest } from "fastify"; +import { z } from "zod"; + +import { InviteController } from "./controller"; +import { + CreateInviteTokenResponseSchema, + RegisterWithInviteResponseSchema, + RegisterWithInviteSchema, + ValidateInviteTokenResponseSchema, +} from "./dto"; + +export async function inviteRoutes(app: FastifyInstance) { + const inviteController = new InviteController(); + + app.post( + "/invite-tokens", + { + schema: { + tags: ["Invite"], + operationId: "generateInviteToken", + summary: "Generate Invite Token", + description: "Generate a one-time use invite token for user registration (admin only)", + response: { + 200: CreateInviteTokenResponseSchema, + 403: z.object({ error: z.string().describe("Error message") }), + 500: z.object({ error: z.string().describe("Error message") }), + }, + }, + preValidation: async (request: FastifyRequest, reply: FastifyReply) => { + try { + await request.jwtVerify(); + } catch (err) { + console.error(err); + reply.status(401).send({ error: "Unauthorized: a valid token is required to access this resource." }); + } + }, + }, + inviteController.generateInviteToken.bind(inviteController) + ); + + app.get( + "/invite-tokens/:token", + { + schema: { + tags: ["Invite"], + operationId: "validateInviteToken", + summary: "Validate Invite Token", + description: "Check if an invite token is valid and can be used", + params: z.object({ + token: z.string().describe("Invite token"), + }), + response: { + 200: ValidateInviteTokenResponseSchema, + 500: z.object({ error: z.string().describe("Error message") }), + }, + }, + }, + inviteController.validateInviteToken.bind(inviteController) + ); + + app.post( + "/register-with-invite", + { + schema: { + tags: ["Invite"], + operationId: "registerWithInvite", + summary: "Register with Invite", + description: "Create a new user account using an invite token", + body: RegisterWithInviteSchema, + response: { + 200: RegisterWithInviteResponseSchema, + 400: z.object({ error: z.string().describe("Error message") }), + 500: z.object({ error: z.string().describe("Error message") }), + }, + }, + }, + inviteController.registerWithInvite.bind(inviteController) + ); +} diff --git a/apps/server/src/modules/invite/service.ts b/apps/server/src/modules/invite/service.ts new file mode 100644 index 00000000..1066704b --- /dev/null +++ b/apps/server/src/modules/invite/service.ts @@ -0,0 +1,107 @@ +import { randomBytes } from "crypto"; +import bcrypt from "bcryptjs"; + +import { prisma } from "../../shared/prisma"; + +export class InviteService { + async generateInviteToken(adminUserId: string): Promise<{ token: string; expiresAt: Date }> { + const token = randomBytes(32).toString("hex"); + const expiresAt = new Date(); + expiresAt.setMinutes(expiresAt.getMinutes() + 15); + + await prisma.inviteToken.create({ + data: { + token, + expiresAt, + createdBy: adminUserId, + }, + }); + + return { token, expiresAt }; + } + + async validateInviteToken(token: string): Promise<{ valid: boolean; used?: boolean; expired?: boolean }> { + const inviteToken = await prisma.inviteToken.findUnique({ + where: { token }, + }); + + if (!inviteToken) { + return { valid: false }; + } + + if (inviteToken.usedAt) { + return { valid: false, used: true }; + } + + if (new Date() > inviteToken.expiresAt) { + return { valid: false, expired: true }; + } + + return { valid: true }; + } + + async registerWithInvite(data: { + token: string; + firstName: string; + lastName: string; + username: string; + email: string; + password: string; + }): Promise<{ id: string; username: string; email: string }> { + const validation = await this.validateInviteToken(data.token); + + if (!validation.valid) { + if (validation.used) { + throw new Error("This invite link has already been used"); + } + if (validation.expired) { + throw new Error("This invite link has expired"); + } + throw new Error("Invalid invite link"); + } + + const existingUser = await prisma.user.findFirst({ + where: { + OR: [{ username: data.username }, { email: data.email }], + }, + }); + + if (existingUser) { + if (existingUser.username === data.username) { + throw new Error("Username already exists"); + } + if (existingUser.email === data.email) { + throw new Error("Email already exists"); + } + } + + const hashedPassword = await bcrypt.hash(data.password, 10); + const result = await prisma.$transaction(async (tx) => { + const user = await tx.user.create({ + data: { + firstName: data.firstName, + lastName: data.lastName, + username: data.username, + email: data.email, + password: hashedPassword, + isAdmin: false, + isActive: true, + }, + select: { + id: true, + username: true, + email: true, + }, + }); + + await tx.inviteToken.update({ + where: { token: data.token }, + data: { usedAt: new Date() }, + }); + + return user; + }); + + return result; + } +} diff --git a/apps/server/src/modules/reverse-share/controller.ts b/apps/server/src/modules/reverse-share/controller.ts index 2a911f13..f0692d1c 100644 --- a/apps/server/src/modules/reverse-share/controller.ts +++ b/apps/server/src/modules/reverse-share/controller.ts @@ -319,59 +319,12 @@ export class ReverseShareController { const { fileId } = request.params as { fileId: string }; - const fileInfo = await this.reverseShareService.getFileInfo(fileId, userId); - const downloadId = `reverse-${Date.now()}-${Math.random().toString(36).substring(2, 11)}`; - - const { DownloadMemoryManager } = await import("../../utils/download-memory-manager.js"); - const memoryManager = DownloadMemoryManager.getInstance(); - - const fileSizeMB = Number(fileInfo.size) / (1024 * 1024); - console.log( - `[REVERSE-DOWNLOAD] Requesting slot for ${downloadId}: ${fileInfo.name} (${fileSizeMB.toFixed(1)}MB)` - ); - - try { - await memoryManager.requestDownloadSlot(downloadId, { - fileName: fileInfo.name, - fileSize: Number(fileInfo.size), - objectName: fileInfo.objectName, - }); - } catch (error: any) { - console.warn(`[REVERSE-DOWNLOAD] Queued ${downloadId}: ${error.message}`); - return reply.status(202).send({ - queued: true, - downloadId: downloadId, - message: "Download queued due to memory constraints", - estimatedWaitTime: error.estimatedWaitTime || 60, - }); - } - - console.log(`[REVERSE-DOWNLOAD] Starting ${downloadId}: ${fileInfo.name} (${fileSizeMB.toFixed(1)}MB)`); - memoryManager.startDownload(downloadId); - - try { - const result = await this.reverseShareService.downloadReverseShareFile(fileId, userId); - - const originalUrl = result.url; - reply.header("X-Download-ID", downloadId); - - reply.raw.on("finish", () => { - memoryManager.endDownload(downloadId); - }); - - reply.raw.on("close", () => { - memoryManager.endDownload(downloadId); - }); - - reply.raw.on("error", () => { - memoryManager.endDownload(downloadId); - }); - - return reply.send(result); - } catch (downloadError) { - memoryManager.endDownload(downloadId); - throw downloadError; - } + // Pass request context for internal storage proxy URLs + const requestContext = { protocol: "https", host: "localhost" }; // Simplified - frontend will handle the real URL + + const result = await this.reverseShareService.downloadReverseShareFile(fileId, userId, requestContext); + + return reply.send(result); } catch (error: any) { if (error.message === "File not found") { return reply.status(404).send({ error: error.message }); @@ -513,12 +466,8 @@ export class ReverseShareController { return reply.status(401).send({ error: "Unauthorized" }); } - console.log(`Copy to my files: User ${userId} copying file ${fileId}`); - const file = await this.reverseShareService.copyReverseShareFileToUserFiles(fileId, userId); - console.log(`Copy to my files: Successfully copied file ${fileId}`); - return reply.send({ file, message: "File copied to your files successfully" }); } catch (error: any) { console.error(`Copy to my files: Error:`, error.message); diff --git a/apps/server/src/modules/reverse-share/service.ts b/apps/server/src/modules/reverse-share/service.ts index 3ac70deb..2de466b0 100644 --- a/apps/server/src/modules/reverse-share/service.ts +++ b/apps/server/src/modules/reverse-share/service.ts @@ -228,9 +228,21 @@ export class ReverseShareService { } const expires = parseInt(env.PRESIGNED_URL_EXPIRATION); - const url = await this.fileService.getPresignedPutUrl(objectName, expires); - return { url, expiresIn: expires }; + // Import storage config to check if using internal or external S3 + const { isInternalStorage } = await import("../../config/storage.config.js"); + + if (isInternalStorage) { + // Internal storage: Use backend proxy for uploads (127.0.0.1 not accessible from client) + // Note: This would need request context, but reverse-shares are typically used by external users + // For now, we'll use presigned URLs and handle the error on the client side + const url = await this.fileService.getPresignedPutUrl(objectName, expires); + return { url, expiresIn: expires }; + } else { + // External S3: Use presigned URLs directly (more efficient) + const url = await this.fileService.getPresignedPutUrl(objectName, expires); + return { url, expiresIn: expires }; + } } async getPresignedUrlByAlias(alias: string, objectName: string, password?: string) { @@ -258,9 +270,21 @@ export class ReverseShareService { } const expires = parseInt(env.PRESIGNED_URL_EXPIRATION); - const url = await this.fileService.getPresignedPutUrl(objectName, expires); - return { url, expiresIn: expires }; + // Import storage config to check if using internal or external S3 + const { isInternalStorage } = await import("../../config/storage.config.js"); + + if (isInternalStorage) { + // Internal storage: Use backend proxy for uploads (127.0.0.1 not accessible from client) + // Note: This would need request context, but reverse-shares are typically used by external users + // For now, we'll use presigned URLs and handle the error on the client side + const url = await this.fileService.getPresignedPutUrl(objectName, expires); + return { url, expiresIn: expires }; + } else { + // External S3: Use presigned URLs directly (more efficient) + const url = await this.fileService.getPresignedPutUrl(objectName, expires); + return { url, expiresIn: expires }; + } } async registerFileUpload(reverseShareId: string, fileData: UploadToReverseShareInput, password?: string) { @@ -386,7 +410,11 @@ export class ReverseShareService { }; } - async downloadReverseShareFile(fileId: string, creatorId: string) { + async downloadReverseShareFile( + fileId: string, + creatorId: string, + requestContext?: { protocol: string; host: string } + ) { const file = await this.reverseShareRepository.findFileById(fileId); if (!file) { throw new Error("File not found"); @@ -398,8 +426,19 @@ export class ReverseShareService { const fileName = file.name; const expires = parseInt(env.PRESIGNED_URL_EXPIRATION); - const url = await this.fileService.getPresignedGetUrl(file.objectName, expires, fileName); - return { url, expiresIn: expires }; + + // Import storage config to check if using internal or external S3 + const { isInternalStorage } = await import("../../config/storage.config.js"); + + if (isInternalStorage) { + // Internal storage: Use frontend proxy (much simpler!) + const url = `/api/files/download?objectName=${encodeURIComponent(file.objectName)}`; + return { url, expiresIn: expires }; + } else { + // External S3: Use presigned URLs directly (more efficient, no backend proxy) + const url = await this.fileService.getPresignedGetUrl(file.objectName, expires, fileName); + return { url, expiresIn: expires }; + } } async deleteReverseShareFile(fileId: string, creatorId: string) { @@ -568,76 +607,59 @@ export class ReverseShareService { const newObjectName = `${creatorId}/${Date.now()}-${file.name}`; - if (this.fileService.isFilesystemMode()) { - const { FilesystemStorageProvider } = await import("../../providers/filesystem-storage.provider.js"); - const provider = FilesystemStorageProvider.getInstance(); - - const sourcePath = provider.getFilePath(file.objectName); - const fs = await import("fs"); - - const targetPath = provider.getFilePath(newObjectName); + // Copy file using S3 presigned URLs + const fileSizeMB = Number(file.size) / (1024 * 1024); + const needsStreaming = fileSizeMB > 100; - const path = await import("path"); - const targetDir = path.dirname(targetPath); - if (!fs.existsSync(targetDir)) { - fs.mkdirSync(targetDir, { recursive: true }); - } - - const { copyFile } = await import("fs/promises"); - await copyFile(sourcePath, targetPath); - } else { - const fileSizeMB = Number(file.size) / (1024 * 1024); - const needsStreaming = fileSizeMB > 100; - - const downloadUrl = await this.fileService.getPresignedGetUrl(file.objectName, 300); - const uploadUrl = await this.fileService.getPresignedPutUrl(newObjectName, 300); + const downloadUrl = await this.fileService.getPresignedGetUrl(file.objectName, 300); + const uploadUrl = await this.fileService.getPresignedPutUrl(newObjectName, 300); - let retries = 0; - const maxRetries = 3; - let success = false; + let retries = 0; + const maxRetries = 3; + let success = false; - while (retries < maxRetries && !success) { - try { - const response = await fetch(downloadUrl, { - signal: AbortSignal.timeout(600000), // 10 minutes timeout - }); - - if (!response.ok) { - throw new Error(`Failed to download file: ${response.statusText}`); - } + while (retries < maxRetries && !success) { + try { + const response = await fetch(downloadUrl, { + signal: AbortSignal.timeout(600000), // 10 minutes timeout + }); - if (!response.body) { - throw new Error("No response body received"); - } + if (!response.ok) { + throw new Error(`Failed to download file: ${response.statusText}`); + } - const uploadOptions: any = { - method: "PUT", - body: response.body, - headers: { - "Content-Type": "application/octet-stream", - "Content-Length": file.size.toString(), - }, - signal: AbortSignal.timeout(600000), // 10 minutes timeout - }; - - const uploadResponse = await fetch(uploadUrl, uploadOptions); - - if (!uploadResponse.ok) { - const errorText = await uploadResponse.text(); - throw new Error(`Failed to upload file: ${uploadResponse.statusText} - ${errorText}`); - } + if (!response.body) { + throw new Error("No response body received"); + } - success = true; - } catch (error: any) { - retries++; + const uploadOptions: any = { + method: "PUT", + body: response.body, + duplex: "half", + headers: { + "Content-Type": "application/octet-stream", + "Content-Length": file.size.toString(), + }, + signal: AbortSignal.timeout(9600000), // 160 minutes timeout + }; + + const uploadResponse = await fetch(uploadUrl, uploadOptions); + + if (!uploadResponse.ok) { + const errorText = await uploadResponse.text(); + throw new Error(`Failed to upload file: ${uploadResponse.statusText} - ${errorText}`); + } - if (retries >= maxRetries) { - throw new Error(`Failed to copy file after ${maxRetries} attempts: ${error.message}`); - } + success = true; + } catch (error: any) { + retries++; - const delay = Math.min(1000 * Math.pow(2, retries - 1), 10000); - await new Promise((resolve) => setTimeout(resolve, delay)); + if (retries >= maxRetries) { + throw new Error(`Failed to copy file after ${maxRetries} attempts: ${error.message}`); } + + const delay = Math.min(1000 * Math.pow(2, retries - 1), 10000); + await new Promise((resolve) => setTimeout(resolve, delay)); } } @@ -781,7 +803,7 @@ export class ReverseShareService { } // Check if reverse share is expired - const isExpired = reverseShare.expiration && new Date(reverseShare.expiration) < new Date(); + const isExpired = reverseShare.expiration ? new Date(reverseShare.expiration) < new Date() : false; // Check if inactive const isInactive = !reverseShare.isActive; diff --git a/apps/server/src/modules/s3-storage/controller.ts b/apps/server/src/modules/s3-storage/controller.ts new file mode 100644 index 00000000..685961e3 --- /dev/null +++ b/apps/server/src/modules/s3-storage/controller.ts @@ -0,0 +1,174 @@ +/** + * S3 Storage Controller (Simplified) + * + * This controller handles uploads/downloads using S3-compatible storage (Garage). + * It's much simpler than the filesystem controller because: + * - Uses S3 multipart uploads (no chunk management needed) + * - Uses presigned URLs (no streaming through Node.js) + * - No memory management needed (Garage handles it) + * - No encryption needed (Garage handles it) + * + * Replaces ~800 lines of complex code with ~100 lines of simple code. + */ + +import { FastifyReply, FastifyRequest } from "fastify"; + +import { S3StorageProvider } from "../../providers/s3-storage.provider"; + +export class S3StorageController { + private storageProvider = new S3StorageProvider(); + + /** + * Generate presigned upload URL + * Client uploads directly to S3 (Garage) + */ + async getUploadUrl(request: FastifyRequest, reply: FastifyReply) { + try { + const { objectName, expires } = request.body as { objectName: string; expires?: number }; + + if (!objectName) { + return reply.status(400).send({ error: "objectName is required" }); + } + + const expiresIn = expires || 3600; // 1 hour default + + // Import storage config to check if using internal or external S3 + const { isInternalStorage } = await import("../../config/storage.config.js"); + + let uploadUrl: string; + + if (isInternalStorage) { + // Internal storage: Use frontend proxy (much simpler!) + uploadUrl = `/api/files/upload?objectName=${encodeURIComponent(objectName)}`; + } else { + // External S3: Use presigned URLs directly (more efficient) + uploadUrl = await this.storageProvider.getPresignedPutUrl(objectName, expiresIn); + } + + return reply.status(200).send({ + uploadUrl, + objectName, + expiresIn, + message: isInternalStorage ? "Upload via backend proxy" : "Upload directly to this URL using PUT request", + }); + } catch (error) { + console.error("[S3] Error generating upload URL:", error); + return reply.status(500).send({ error: "Failed to generate upload URL" }); + } + } + + /** + * Generate presigned download URL + * For internal storage: Uses backend proxy + * For external S3: Uses presigned URLs directly + */ + async getDownloadUrl(request: FastifyRequest, reply: FastifyReply) { + try { + const { objectName, expires, fileName } = request.query as { + objectName: string; + expires?: string; + fileName?: string; + }; + + if (!objectName) { + return reply.status(400).send({ error: "objectName is required" }); + } + + // Check if file exists + const exists = await this.storageProvider.fileExists(objectName); + if (!exists) { + return reply.status(404).send({ error: "File not found" }); + } + + const expiresIn = expires ? parseInt(expires, 10) : 3600; + + // Import storage config to check if using internal or external S3 + const { isInternalStorage } = await import("../../config/storage.config.js"); + + let downloadUrl: string; + + if (isInternalStorage) { + // Internal storage: Use frontend proxy (much simpler!) + downloadUrl = `/api/files/download?objectName=${encodeURIComponent(objectName)}`; + } else { + // External S3: Use presigned URLs directly (more efficient) + downloadUrl = await this.storageProvider.getPresignedGetUrl(objectName, expiresIn, fileName); + } + + return reply.status(200).send({ + downloadUrl, + objectName, + expiresIn, + message: isInternalStorage ? "Download via backend proxy" : "Download directly from this URL", + }); + } catch (error) { + console.error("[S3] Error generating download URL:", error); + return reply.status(500).send({ error: "Failed to generate download URL" }); + } + } + + /** + * Upload directly (for small files) + * Receives file and uploads to S3 + */ + async upload(request: FastifyRequest, reply: FastifyReply) { + try { + // For large files, clients should use presigned URLs + // This is just for backward compatibility or small files + + return reply.status(501).send({ + error: "Not implemented", + message: "Use getUploadUrl endpoint for efficient uploads", + }); + } catch (error) { + console.error("[S3] Error in upload:", error); + return reply.status(500).send({ error: "Upload failed" }); + } + } + + /** + * Delete object from S3 + */ + async deleteObject(request: FastifyRequest, reply: FastifyReply) { + try { + const { objectName } = request.params as { objectName: string }; + + if (!objectName) { + return reply.status(400).send({ error: "objectName is required" }); + } + + await this.storageProvider.deleteObject(objectName); + + return reply.status(200).send({ + message: "Object deleted successfully", + objectName, + }); + } catch (error) { + console.error("[S3] Error deleting object:", error); + return reply.status(500).send({ error: "Failed to delete object" }); + } + } + + /** + * Check if object exists + */ + async checkExists(request: FastifyRequest, reply: FastifyReply) { + try { + const { objectName } = request.query as { objectName: string }; + + if (!objectName) { + return reply.status(400).send({ error: "objectName is required" }); + } + + const exists = await this.storageProvider.fileExists(objectName); + + return reply.status(200).send({ + exists, + objectName, + }); + } catch (error) { + console.error("[S3] Error checking existence:", error); + return reply.status(500).send({ error: "Failed to check existence" }); + } + } +} diff --git a/apps/server/src/modules/s3-storage/routes.ts b/apps/server/src/modules/s3-storage/routes.ts new file mode 100644 index 00000000..006154c7 --- /dev/null +++ b/apps/server/src/modules/s3-storage/routes.ts @@ -0,0 +1,112 @@ +/** + * S3 Storage Routes + * + * Simple routes for S3-based storage using presigned URLs. + * Much simpler than filesystem routes - no chunk management, no streaming. + */ + +import { FastifyInstance } from "fastify"; +import { z } from "zod"; + +import { S3StorageController } from "./controller"; + +export async function s3StorageRoutes(app: FastifyInstance) { + const controller = new S3StorageController(); + + // Get presigned upload URL + app.post( + "/s3/upload-url", + { + schema: { + tags: ["S3 Storage"], + operationId: "getS3UploadUrl", + summary: "Get presigned URL for upload", + description: "Returns a presigned URL that clients can use to upload directly to S3", + body: z.object({ + objectName: z.string().describe("Object name/path in S3"), + expires: z.number().optional().describe("URL expiration in seconds (default: 3600)"), + }), + response: { + 200: z.object({ + uploadUrl: z.string(), + objectName: z.string(), + expiresIn: z.number(), + message: z.string(), + }), + }, + }, + }, + controller.getUploadUrl.bind(controller) + ); + + // Get presigned download URL + app.get( + "/s3/download-url", + { + schema: { + tags: ["S3 Storage"], + operationId: "getS3DownloadUrl", + summary: "Get presigned URL for download", + description: "Returns a presigned URL that clients can use to download directly from S3", + querystring: z.object({ + objectName: z.string().describe("Object name/path in S3"), + expires: z.string().optional().describe("URL expiration in seconds (default: 3600)"), + fileName: z.string().optional().describe("Optional filename for download"), + }), + response: { + 200: z.object({ + downloadUrl: z.string(), + objectName: z.string(), + expiresIn: z.number(), + message: z.string(), + }), + }, + }, + }, + controller.getDownloadUrl.bind(controller) + ); + + // Delete object + app.delete( + "/s3/object/:objectName", + { + schema: { + tags: ["S3 Storage"], + operationId: "deleteS3Object", + summary: "Delete object from S3", + params: z.object({ + objectName: z.string().describe("Object name/path in S3"), + }), + response: { + 200: z.object({ + message: z.string(), + objectName: z.string(), + }), + }, + }, + }, + controller.deleteObject.bind(controller) + ); + + // Check if object exists + app.get( + "/s3/exists", + { + schema: { + tags: ["S3 Storage"], + operationId: "checkS3ObjectExists", + summary: "Check if object exists in S3", + querystring: z.object({ + objectName: z.string().describe("Object name/path in S3"), + }), + response: { + 200: z.object({ + exists: z.boolean(), + objectName: z.string(), + }), + }, + }, + }, + controller.checkExists.bind(controller) + ); +} diff --git a/apps/server/src/modules/share/service.ts b/apps/server/src/modules/share/service.ts index fdd26203..66e6c2ee 100644 --- a/apps/server/src/modules/share/service.ts +++ b/apps/server/src/modules/share/service.ts @@ -448,10 +448,10 @@ export class ShareService { } // Check if share is expired - const isExpired = share.expiration && new Date(share.expiration) < new Date(); + const isExpired = share.expiration ? new Date(share.expiration) < new Date() : false; // Check if max views reached - const isMaxViewsReached = share.security.maxViews !== null && share.views >= share.security.maxViews; + const isMaxViewsReached = share.security.maxViews !== null ? share.views >= share.security.maxViews : false; const totalFiles = share.files?.length || 0; const totalFolders = share.folders?.length || 0; diff --git a/apps/server/src/providers/filesystem-storage.provider.ts b/apps/server/src/providers/filesystem-storage.provider.ts deleted file mode 100644 index d6a47d36..00000000 --- a/apps/server/src/providers/filesystem-storage.provider.ts +++ /dev/null @@ -1,715 +0,0 @@ -import * as crypto from "crypto"; -import * as fsSync from "fs"; -import * as fs from "fs/promises"; -import * as path from "path"; -import { Transform } from "stream"; -import { pipeline } from "stream/promises"; - -import { directoriesConfig, getTempFilePath } from "../config/directories.config"; -import { env } from "../env"; -import { StorageProvider } from "../types/storage"; - -export class FilesystemStorageProvider implements StorageProvider { - private static instance: FilesystemStorageProvider; - private uploadsDir: string; - private encryptionKey = env.ENCRYPTION_KEY; - private isEncryptionDisabled = env.DISABLE_FILESYSTEM_ENCRYPTION === "true"; - private uploadTokens = new Map(); - private downloadTokens = new Map(); - - private constructor() { - this.uploadsDir = directoriesConfig.uploads; - - if (!this.isEncryptionDisabled && !this.encryptionKey) { - throw new Error( - "Encryption is enabled but ENCRYPTION_KEY is not provided. " + - "Please set ENCRYPTION_KEY environment variable or set DISABLE_FILESYSTEM_ENCRYPTION=true to disable encryption." - ); - } - - this.ensureUploadsDir(); - setInterval(() => this.cleanExpiredTokens(), 5 * 60 * 1000); - setInterval(() => this.cleanupEmptyTempDirs(), 10 * 60 * 1000); - } - - public static getInstance(): FilesystemStorageProvider { - if (!FilesystemStorageProvider.instance) { - FilesystemStorageProvider.instance = new FilesystemStorageProvider(); - } - return FilesystemStorageProvider.instance; - } - - private async ensureUploadsDir(): Promise { - try { - await fs.access(this.uploadsDir); - } catch { - await fs.mkdir(this.uploadsDir, { recursive: true }); - } - } - - private cleanExpiredTokens(): void { - const now = Date.now(); - - for (const [token, data] of this.uploadTokens.entries()) { - if (now > data.expiresAt) { - this.uploadTokens.delete(token); - } - } - - for (const [token, data] of this.downloadTokens.entries()) { - if (now > data.expiresAt) { - this.downloadTokens.delete(token); - } - } - } - - public getFilePath(objectName: string): string { - const sanitizedName = objectName.replace(/[^a-zA-Z0-9\-_./]/g, "_"); - return path.join(this.uploadsDir, sanitizedName); - } - - private createEncryptionKey(): Buffer { - if (!this.encryptionKey) { - throw new Error( - "Encryption key is required when encryption is enabled. Please set ENCRYPTION_KEY environment variable." - ); - } - return crypto.scryptSync(this.encryptionKey, "salt", 32); - } - - public createEncryptStream(): Transform { - if (this.isEncryptionDisabled) { - return new Transform({ - highWaterMark: 64 * 1024, - transform(chunk, _encoding, callback) { - this.push(chunk); - callback(); - }, - }); - } - - const key = this.createEncryptionKey(); - const iv = crypto.randomBytes(16); - const cipher = crypto.createCipheriv("aes-256-cbc", key, iv); - - let isFirstChunk = true; - - return new Transform({ - highWaterMark: 64 * 1024, - transform(chunk, _encoding, callback) { - try { - if (isFirstChunk) { - this.push(iv); - isFirstChunk = false; - } - - const encrypted = cipher.update(chunk); - this.push(encrypted); - callback(); - } catch (error) { - callback(error as Error); - } - }, - - flush(callback) { - try { - const final = cipher.final(); - this.push(final); - callback(); - } catch (error) { - callback(error as Error); - } - }, - }); - } - - public createDecryptStream(): Transform { - if (this.isEncryptionDisabled) { - return new Transform({ - highWaterMark: 64 * 1024, - transform(chunk, _encoding, callback) { - this.push(chunk); - callback(); - }, - }); - } - - const key = this.createEncryptionKey(); - let iv: Buffer | null = null; - let decipher: crypto.Decipher | null = null; - let ivBuffer = Buffer.alloc(0); - - return new Transform({ - highWaterMark: 64 * 1024, - transform(chunk, _encoding, callback) { - try { - if (!iv) { - ivBuffer = Buffer.concat([ivBuffer, chunk]); - - if (ivBuffer.length >= 16) { - iv = ivBuffer.subarray(0, 16); - decipher = crypto.createDecipheriv("aes-256-cbc", key, iv); - const remainingData = ivBuffer.subarray(16); - if (remainingData.length > 0) { - const decrypted = decipher.update(remainingData); - this.push(decrypted); - } - } - callback(); - return; - } - - if (decipher) { - const decrypted = decipher.update(chunk); - this.push(decrypted); - } - callback(); - } catch (error) { - callback(error as Error); - } - }, - - flush(callback) { - try { - if (decipher) { - const final = decipher.final(); - this.push(final); - } - callback(); - } catch (error) { - callback(error as Error); - } - }, - }); - } - - async getPresignedPutUrl(objectName: string, expires: number): Promise { - const token = crypto.randomBytes(32).toString("hex"); - const expiresAt = Date.now() + expires * 1000; - - this.uploadTokens.set(token, { objectName, expiresAt }); - - return `/api/filesystem/upload/${token}`; - } - - async getPresignedGetUrl(objectName: string): Promise { - const encodedObjectName = encodeURIComponent(objectName); - return `/api/files/download?objectName=${encodedObjectName}`; - } - - async deleteObject(objectName: string): Promise { - const filePath = this.getFilePath(objectName); - try { - await fs.unlink(filePath); - } catch (error: any) { - if (error.code !== "ENOENT") { - throw error; - } - } - } - - async uploadFile(objectName: string, buffer: Buffer): Promise { - const filePath = this.getFilePath(objectName); - const dir = path.dirname(filePath); - - await fs.mkdir(dir, { recursive: true }); - - const { Readable } = await import("stream"); - const readable = Readable.from(buffer); - - await this.uploadFileFromStream(objectName, readable); - } - - async uploadFileFromStream(objectName: string, inputStream: NodeJS.ReadableStream): Promise { - const filePath = this.getFilePath(objectName); - const dir = path.dirname(filePath); - - await fs.mkdir(dir, { recursive: true }); - - const tempPath = getTempFilePath(objectName); - const tempDir = path.dirname(tempPath); - - await fs.mkdir(tempDir, { recursive: true }); - - const writeStream = fsSync.createWriteStream(tempPath); - const encryptStream = this.createEncryptStream(); - - try { - await pipeline(inputStream, encryptStream, writeStream); - await this.moveFile(tempPath, filePath); - } catch (error) { - await this.cleanupTempFile(tempPath); - throw error; - } - } - - async downloadFile(objectName: string): Promise { - const filePath = this.getFilePath(objectName); - const fileBuffer = await fs.readFile(filePath); - - if (this.isEncryptionDisabled) { - return fileBuffer; - } - - if (fileBuffer.length > 16) { - try { - return this.decryptFileBuffer(fileBuffer); - } catch (error: unknown) { - if (error instanceof Error) { - console.warn("Failed to decrypt with new method, trying legacy format", error.message); - } - return this.decryptFileLegacy(fileBuffer); - } - } - - return this.decryptFileLegacy(fileBuffer); - } - - createDownloadStream(objectName: string): NodeJS.ReadableStream { - const filePath = this.getFilePath(objectName); - - const streamOptions = { - highWaterMark: 64 * 1024, - autoDestroy: true, - emitClose: true, - }; - - const fileStream = fsSync.createReadStream(filePath, streamOptions); - - if (this.isEncryptionDisabled) { - this.setupStreamMemoryManagement(fileStream, objectName); - return fileStream; - } - - const decryptStream = this.createDecryptStream(); - const { PassThrough } = require("stream"); - const outputStream = new PassThrough(streamOptions); - - let isDestroyed = false; - let memoryCheckInterval: NodeJS.Timeout; - - const cleanup = () => { - if (isDestroyed) return; - isDestroyed = true; - - if (memoryCheckInterval) { - clearInterval(memoryCheckInterval); - } - - try { - if (fileStream && !fileStream.destroyed) { - fileStream.destroy(); - } - if (decryptStream && !decryptStream.destroyed) { - decryptStream.destroy(); - } - if (outputStream && !outputStream.destroyed) { - outputStream.destroy(); - } - } catch (error) { - console.warn("Error during download stream cleanup:", error); - } - - setImmediate(() => { - if (global.gc) { - global.gc(); - } - }); - }; - - memoryCheckInterval = setInterval(() => { - const memUsage = process.memoryUsage(); - const memoryUsageMB = memUsage.heapUsed / 1024 / 1024; - - if (memoryUsageMB > 1024) { - if (!fileStream.readableFlowing) return; - - console.warn( - `[MEMORY THROTTLE] ${objectName} - Pausing stream due to high memory usage: ${memoryUsageMB.toFixed(2)}MB` - ); - fileStream.pause(); - - if (global.gc) { - global.gc(); - } - - setTimeout(() => { - if (!isDestroyed && fileStream && !fileStream.destroyed) { - fileStream.resume(); - console.log(`[MEMORY THROTTLE] ${objectName} - Stream resumed`); - } - }, 100); - } - }, 1000); - - fileStream.on("error", (error: any) => { - console.error("File stream error:", error); - cleanup(); - }); - - decryptStream.on("error", (error: any) => { - console.error("Decrypt stream error:", error); - cleanup(); - }); - - outputStream.on("error", (error: any) => { - console.error("Output stream error:", error); - cleanup(); - }); - - outputStream.on("close", cleanup); - outputStream.on("finish", cleanup); - - outputStream.on("pipe", (src: any) => { - if (src && src.on) { - src.on("close", cleanup); - src.on("error", cleanup); - } - }); - - pipeline(fileStream, decryptStream, outputStream) - .then(() => {}) - .catch((error: any) => { - console.error("Pipeline error during download:", error); - cleanup(); - }); - - this.setupStreamMemoryManagement(outputStream, objectName); - return outputStream; - } - - private setupStreamMemoryManagement(stream: NodeJS.ReadableStream, objectName: string): void { - let lastMemoryLog = 0; - - stream.on("data", () => { - const now = Date.now(); - if (now - lastMemoryLog > 30000) { - FilesystemStorageProvider.logMemoryUsage(`Active download: ${objectName}`); - lastMemoryLog = now; - } - }); - - stream.on("end", () => { - FilesystemStorageProvider.logMemoryUsage(`Download completed: ${objectName}`); - setImmediate(() => { - if (global.gc) { - global.gc(); - } - }); - }); - - stream.on("close", () => { - FilesystemStorageProvider.logMemoryUsage(`Download closed: ${objectName}`); - }); - } - - async createDownloadRangeStream(objectName: string, start: number, end: number): Promise { - if (!this.isEncryptionDisabled) { - return this.createRangeStreamFromDecrypted(objectName, start, end); - } - - const filePath = this.getFilePath(objectName); - return fsSync.createReadStream(filePath, { start, end }); - } - - private createRangeStreamFromDecrypted(objectName: string, start: number, end: number): NodeJS.ReadableStream { - const { Transform, PassThrough } = require("stream"); - const filePath = this.getFilePath(objectName); - const fileStream = fsSync.createReadStream(filePath); - const decryptStream = this.createDecryptStream(); - const rangeStream = new PassThrough(); - - let bytesRead = 0; - let rangeEnded = false; - let isDestroyed = false; - - const rangeTransform = new Transform({ - transform(chunk: Buffer, encoding: any, callback: any) { - if (rangeEnded || isDestroyed) { - callback(); - return; - } - - const chunkStart = bytesRead; - const chunkEnd = bytesRead + chunk.length - 1; - bytesRead += chunk.length; - - if (chunkEnd < start) { - callback(); - return; - } - - if (chunkStart > end) { - rangeEnded = true; - this.end(); - callback(); - return; - } - - let sliceStart = 0; - let sliceEnd = chunk.length; - - if (chunkStart < start) { - sliceStart = start - chunkStart; - } - - if (chunkEnd > end) { - sliceEnd = end - chunkStart + 1; - rangeEnded = true; - } - - const slicedChunk = chunk.slice(sliceStart, sliceEnd); - this.push(slicedChunk); - - if (rangeEnded) { - this.end(); - } - - callback(); - }, - - flush(callback: any) { - if (global.gc) { - global.gc(); - } - callback(); - }, - }); - - const cleanup = () => { - if (isDestroyed) return; - isDestroyed = true; - - try { - if (fileStream && !fileStream.destroyed) { - fileStream.destroy(); - } - if (decryptStream && !decryptStream.destroyed) { - decryptStream.destroy(); - } - if (rangeTransform && !rangeTransform.destroyed) { - rangeTransform.destroy(); - } - if (rangeStream && !rangeStream.destroyed) { - rangeStream.destroy(); - } - } catch (error) { - console.warn("Error during stream cleanup:", error); - } - - if (global.gc) { - global.gc(); - } - }; - - fileStream.on("error", cleanup); - decryptStream.on("error", cleanup); - rangeTransform.on("error", cleanup); - rangeStream.on("error", cleanup); - - rangeStream.on("close", cleanup); - rangeStream.on("end", cleanup); - - rangeStream.on("pipe", (src: any) => { - if (src && src.on) { - src.on("close", cleanup); - src.on("error", cleanup); - } - }); - - fileStream.pipe(decryptStream).pipe(rangeTransform).pipe(rangeStream); - - return rangeStream; - } - - private decryptFileBuffer(encryptedBuffer: Buffer): Buffer { - const key = this.createEncryptionKey(); - const iv = encryptedBuffer.slice(0, 16); - const encrypted = encryptedBuffer.slice(16); - - const decipher = crypto.createDecipheriv("aes-256-cbc", key, iv); - - return Buffer.concat([decipher.update(encrypted), decipher.final()]); - } - - private decryptFileLegacy(encryptedBuffer: Buffer): Buffer { - if (!this.encryptionKey) { - throw new Error( - "Encryption key is required when encryption is enabled. Please set ENCRYPTION_KEY environment variable." - ); - } - const CryptoJS = require("crypto-js"); - const decrypted = CryptoJS.AES.decrypt(encryptedBuffer.toString("utf8"), this.encryptionKey); - return Buffer.from(decrypted.toString(CryptoJS.enc.Utf8), "base64"); - } - - static logMemoryUsage(context: string = "Unknown"): void { - const memUsage = process.memoryUsage(); - const formatBytes = (bytes: number) => { - const mb = bytes / 1024 / 1024; - return `${mb.toFixed(2)} MB`; - }; - - const rssInMB = memUsage.rss / 1024 / 1024; - const heapUsedInMB = memUsage.heapUsed / 1024 / 1024; - - if (rssInMB > 1024 || heapUsedInMB > 512) { - console.warn(`[MEMORY WARNING] ${context} - High memory usage detected:`); - console.warn(` RSS: ${formatBytes(memUsage.rss)}`); - console.warn(` Heap Used: ${formatBytes(memUsage.heapUsed)}`); - console.warn(` Heap Total: ${formatBytes(memUsage.heapTotal)}`); - console.warn(` External: ${formatBytes(memUsage.external)}`); - - if (global.gc) { - console.warn(" Forcing garbage collection..."); - global.gc(); - - const afterGC = process.memoryUsage(); - console.warn(` After GC - RSS: ${formatBytes(afterGC.rss)}, Heap: ${formatBytes(afterGC.heapUsed)}`); - } - } else { - console.log( - `[MEMORY INFO] ${context} - RSS: ${formatBytes(memUsage.rss)}, Heap: ${formatBytes(memUsage.heapUsed)}` - ); - } - } - - static forceGarbageCollection(context: string = "Manual"): void { - if (global.gc) { - const beforeGC = process.memoryUsage(); - global.gc(); - const afterGC = process.memoryUsage(); - - const formatBytes = (bytes: number) => `${(bytes / 1024 / 1024).toFixed(2)} MB`; - - console.log(`[GC] ${context} - Before: RSS ${formatBytes(beforeGC.rss)}, Heap ${formatBytes(beforeGC.heapUsed)}`); - console.log(`[GC] ${context} - After: RSS ${formatBytes(afterGC.rss)}, Heap ${formatBytes(afterGC.heapUsed)}`); - - const rssSaved = beforeGC.rss - afterGC.rss; - const heapSaved = beforeGC.heapUsed - afterGC.heapUsed; - - if (rssSaved > 0 || heapSaved > 0) { - console.log(`[GC] ${context} - Freed: RSS ${formatBytes(rssSaved)}, Heap ${formatBytes(heapSaved)}`); - } - } else { - console.warn(`[GC] ${context} - Garbage collection not available. Start Node.js with --expose-gc flag.`); - } - } - - async fileExists(objectName: string): Promise { - const filePath = this.getFilePath(objectName); - try { - await fs.access(filePath); - return true; - } catch { - return false; - } - } - - validateUploadToken(token: string): { objectName: string } | null { - const data = this.uploadTokens.get(token); - if (!data || Date.now() > data.expiresAt) { - this.uploadTokens.delete(token); - return null; - } - return { objectName: data.objectName }; - } - - validateDownloadToken(token: string): { objectName: string; fileName?: string } | null { - const data = this.downloadTokens.get(token); - - if (!data) { - return null; - } - - const now = Date.now(); - - if (now > data.expiresAt) { - this.downloadTokens.delete(token); - return null; - } - - return { objectName: data.objectName, fileName: data.fileName }; - } - - // Tokens are automatically cleaned up by cleanExpiredTokens() every 5 minutes - // No need to manually consume tokens - allows reuse for previews, range requests, etc. - - private async cleanupTempFile(tempPath: string): Promise { - try { - await fs.unlink(tempPath); - - const tempDir = path.dirname(tempPath); - try { - const files = await fs.readdir(tempDir); - if (files.length === 0) { - await fs.rmdir(tempDir); - } - } catch (dirError: any) { - if (dirError.code !== "ENOTEMPTY" && dirError.code !== "ENOENT") { - console.warn("Warning: Could not remove temp directory:", dirError.message); - } - } - } catch (cleanupError: any) { - if (cleanupError.code !== "ENOENT") { - console.error("Error deleting temp file:", cleanupError); - } - } - } - - private async cleanupEmptyTempDirs(): Promise { - try { - const tempUploadsDir = directoriesConfig.tempUploads; - - try { - await fs.access(tempUploadsDir); - } catch { - return; - } - - const items = await fs.readdir(tempUploadsDir); - - for (const item of items) { - const itemPath = path.join(tempUploadsDir, item); - - try { - const stat = await fs.stat(itemPath); - - if (stat.isDirectory()) { - const dirContents = await fs.readdir(itemPath); - if (dirContents.length === 0) { - await fs.rmdir(itemPath); - console.log(`🧹 Cleaned up empty temp directory: ${itemPath}`); - } - } else if (stat.isFile()) { - const oneHourAgo = Date.now() - 60 * 60 * 1000; - if (stat.mtime.getTime() < oneHourAgo) { - await fs.unlink(itemPath); - console.log(`🧹 Cleaned up stale temp file: ${itemPath}`); - } - } - } catch (error: any) { - if (error.code !== "ENOENT") { - console.warn(`Warning: Could not process temp item ${itemPath}:`, error.message); - } - } - } - } catch (error) { - console.error("Error during temp directory cleanup:", error); - } - } - - private async moveFile(src: string, dest: string): Promise { - try { - await fs.rename(src, dest); - } catch (err: any) { - if (err.code === "EXDEV") { - // cross-device: fallback to copy + delete - await fs.copyFile(src, dest); - await fs.unlink(src); - } else { - throw err; - } - } - } -} diff --git a/apps/server/src/providers/s3-storage.provider.ts b/apps/server/src/providers/s3-storage.provider.ts index becb66df..dd36470d 100644 --- a/apps/server/src/providers/s3-storage.provider.ts +++ b/apps/server/src/providers/s3-storage.provider.ts @@ -1,17 +1,25 @@ -import { DeleteObjectCommand, GetObjectCommand, HeadObjectCommand, PutObjectCommand } from "@aws-sdk/client-s3"; +import { + AbortMultipartUploadCommand, + CompleteMultipartUploadCommand, + CreateMultipartUploadCommand, + DeleteObjectCommand, + GetObjectCommand, + HeadObjectCommand, + PutObjectCommand, + UploadPartCommand, +} from "@aws-sdk/client-s3"; import { getSignedUrl } from "@aws-sdk/s3-request-presigner"; -import { bucketName, s3Client } from "../config/storage.config"; +import { bucketName, createPublicS3Client, s3Client } from "../config/storage.config"; import { StorageProvider } from "../types/storage"; import { getContentType } from "../utils/mime-types"; export class S3StorageProvider implements StorageProvider { - constructor() { + private ensureClient() { if (!s3Client) { - throw new Error( - "S3 client is not configured. Make sure ENABLE_S3=true and all S3 environment variables are set." - ); + throw new Error("S3 client is not configured. Storage is initializing, please wait..."); } + return s3Client; } /** @@ -71,8 +79,10 @@ export class S3StorageProvider implements StorageProvider { } async getPresignedPutUrl(objectName: string, expires: number): Promise { - if (!s3Client) { - throw new Error("S3 client is not available"); + // Always use public S3 client for presigned URLs (uses SERVER_IP) + const client = createPublicS3Client(); + if (!client) { + throw new Error("S3 client could not be created"); } const command = new PutObjectCommand({ @@ -80,12 +90,14 @@ export class S3StorageProvider implements StorageProvider { Key: objectName, }); - return await getSignedUrl(s3Client, command, { expiresIn: expires }); + return await getSignedUrl(client, command, { expiresIn: expires }); } async getPresignedGetUrl(objectName: string, expires: number, fileName?: string): Promise { - if (!s3Client) { - throw new Error("S3 client is not available"); + // Always use public S3 client for presigned URLs (uses SERVER_IP) + const client = createPublicS3Client(); + if (!client) { + throw new Error("S3 client could not be created"); } let rcdFileName: string; @@ -107,26 +119,22 @@ export class S3StorageProvider implements StorageProvider { ResponseContentType: getContentType(rcdFileName), }); - return await getSignedUrl(s3Client, command, { expiresIn: expires }); + return await getSignedUrl(client, command, { expiresIn: expires }); } async deleteObject(objectName: string): Promise { - if (!s3Client) { - throw new Error("S3 client is not available"); - } + const client = this.ensureClient(); const command = new DeleteObjectCommand({ Bucket: bucketName, Key: objectName, }); - await s3Client.send(command); + await client.send(command); } async fileExists(objectName: string): Promise { - if (!s3Client) { - throw new Error("S3 client is not available"); - } + const client = this.ensureClient(); try { const command = new HeadObjectCommand({ @@ -134,7 +142,7 @@ export class S3StorageProvider implements StorageProvider { Key: objectName, }); - await s3Client.send(command); + await client.send(command); return true; } catch (error: any) { if (error.name === "NotFound" || error.$metadata?.httpStatusCode === 404) { @@ -143,4 +151,115 @@ export class S3StorageProvider implements StorageProvider { throw error; } } + + /** + * Get a readable stream for downloading an object + * Used for proxying downloads through the backend + */ + async getObjectStream(objectName: string): Promise { + const client = this.ensureClient(); + + const command = new GetObjectCommand({ + Bucket: bucketName, + Key: objectName, + }); + + const response = await client.send(command); + + if (!response.Body) { + throw new Error("No body in S3 response"); + } + + // AWS SDK v3 returns a readable stream + return response.Body as NodeJS.ReadableStream; + } + + /** + * Initialize a multipart upload + * Returns uploadId for subsequent part uploads + */ + async createMultipartUpload(objectName: string): Promise { + const client = createPublicS3Client(); + if (!client) { + throw new Error("S3 client could not be created"); + } + + const command = new CreateMultipartUploadCommand({ + Bucket: bucketName, + Key: objectName, + }); + + const response = await client.send(command); + + if (!response.UploadId) { + throw new Error("Failed to create multipart upload - no UploadId returned"); + } + + return response.UploadId; + } + + /** + * Get presigned URL for uploading a specific part + */ + async getPresignedPartUrl( + objectName: string, + uploadId: string, + partNumber: number, + expires: number + ): Promise { + const client = createPublicS3Client(); + if (!client) { + throw new Error("S3 client could not be created"); + } + + const command = new UploadPartCommand({ + Bucket: bucketName, + Key: objectName, + UploadId: uploadId, + PartNumber: partNumber, + }); + + const url = await getSignedUrl(client, command, { expiresIn: expires }); + return url; + } + + /** + * Complete a multipart upload + */ + async completeMultipartUpload( + objectName: string, + uploadId: string, + parts: Array<{ PartNumber: number; ETag: string }> + ): Promise { + const client = this.ensureClient(); + + const command = new CompleteMultipartUploadCommand({ + Bucket: bucketName, + Key: objectName, + UploadId: uploadId, + MultipartUpload: { + Parts: parts.map((part) => ({ + PartNumber: part.PartNumber, + ETag: part.ETag, + })), + }, + }); + + await client.send(command); + } + + /** + * Abort a multipart upload + */ + async abortMultipartUpload(objectName: string, uploadId: string): Promise { + const client = this.ensureClient(); + + const command = new AbortMultipartUploadCommand({ + Bucket: bucketName, + Key: objectName, + UploadId: uploadId, + }); + + await client.send(command); + } } diff --git a/apps/server/src/scripts/cleanup-orphan-files.ts b/apps/server/src/scripts/cleanup-orphan-files.ts index ee461af5..33907a3d 100644 --- a/apps/server/src/scripts/cleanup-orphan-files.ts +++ b/apps/server/src/scripts/cleanup-orphan-files.ts @@ -1,5 +1,3 @@ -import { isS3Enabled } from "../config/storage.config"; -import { FilesystemStorageProvider } from "../providers/filesystem-storage.provider"; import { S3StorageProvider } from "../providers/s3-storage.provider"; import { prisma } from "../shared/prisma"; import { StorageProvider } from "../types/storage"; @@ -10,14 +8,10 @@ import { StorageProvider } from "../types/storage"; */ async function cleanupOrphanFiles() { console.log("Starting orphan file cleanup..."); - console.log(`Storage mode: ${isS3Enabled ? "S3" : "Filesystem"}`); + console.log(`Storage mode: S3 (Garage or External)`); - let storageProvider: StorageProvider; - if (isS3Enabled) { - storageProvider = new S3StorageProvider(); - } else { - storageProvider = FilesystemStorageProvider.getInstance(); - } + // Always use S3 storage provider + const storageProvider: StorageProvider = new S3StorageProvider(); // Get all files from database const allFiles = await prisma.file.findMany({ diff --git a/apps/server/src/scripts/migrate-filesystem-to-s3.ts b/apps/server/src/scripts/migrate-filesystem-to-s3.ts new file mode 100644 index 00000000..6b52dbdc --- /dev/null +++ b/apps/server/src/scripts/migrate-filesystem-to-s3.ts @@ -0,0 +1,305 @@ +/** + * Automatic Migration Script: Filesystem → S3 (Garage) + * + * This script runs automatically on server start and: + * 1. Detects existing filesystem files + * 2. Migrates them to S3 in background + * 3. Updates database references + * 4. Keeps filesystem as fallback during migration + * 5. Zero downtime, zero user intervention + */ + +import { createReadStream } from "fs"; +import * as fs from "fs/promises"; +import * as path from "path"; +import { PutObjectCommand } from "@aws-sdk/client-s3"; + +import { directoriesConfig } from "../config/directories.config"; +import { bucketName, s3Client } from "../config/storage.config"; +import { prisma } from "../shared/prisma"; + +interface MigrationStats { + totalFiles: number; + migratedFiles: number; + failedFiles: number; + skippedFiles: number; + totalSizeBytes: number; + startTime: number; + endTime?: number; +} + +const MIGRATION_STATE_FILE = path.join(directoriesConfig.uploads, ".migration-state.json"); +const MIGRATION_BATCH_SIZE = 10; // Migrate 10 files at a time +const MIGRATION_DELAY_MS = 100; // Small delay between batches to avoid overwhelming + +export class FilesystemToS3Migrator { + private stats: MigrationStats = { + totalFiles: 0, + migratedFiles: 0, + failedFiles: 0, + skippedFiles: 0, + totalSizeBytes: 0, + startTime: Date.now(), + }; + + /** + * Check if migration is needed and should run + */ + async shouldMigrate(): Promise { + // Only migrate if S3 client is available + if (!s3Client) { + console.log("[MIGRATION] S3 not configured, skipping migration"); + return false; + } + + // Check if migration already completed + try { + const stateExists = await fs + .access(MIGRATION_STATE_FILE) + .then(() => true) + .catch(() => false); + + if (stateExists) { + const state = JSON.parse(await fs.readFile(MIGRATION_STATE_FILE, "utf-8")); + + if (state.completed) { + console.log("[MIGRATION] Migration already completed"); + return false; + } + + console.log("[MIGRATION] Previous migration incomplete, resuming..."); + this.stats = { ...state, startTime: Date.now() }; + return true; + } + } catch (error) { + console.warn("[MIGRATION] Could not read migration state:", error); + } + + // Check if there are files to migrate + try { + const uploadsDir = directoriesConfig.uploads; + const files = await this.scanDirectory(uploadsDir); + + if (files.length === 0) { + console.log("[MIGRATION] No filesystem files found, nothing to migrate"); + await this.markMigrationComplete(); + return false; + } + + console.log(`[MIGRATION] Found ${files.length} files to migrate`); + this.stats.totalFiles = files.length; + return true; + } catch (error) { + console.error("[MIGRATION] Error scanning files:", error); + return false; + } + } + + /** + * Run the migration process + */ + async migrate(): Promise { + console.log("[MIGRATION] Starting automatic filesystem → S3 migration"); + console.log("[MIGRATION] This runs in background, zero downtime"); + + try { + const uploadsDir = directoriesConfig.uploads; + const files = await this.scanDirectory(uploadsDir); + + // Process in batches + for (let i = 0; i < files.length; i += MIGRATION_BATCH_SIZE) { + const batch = files.slice(i, i + MIGRATION_BATCH_SIZE); + + await Promise.all( + batch.map((file) => + this.migrateFile(file).catch((error) => { + console.error(`[MIGRATION] Failed to migrate ${file}:`, error); + this.stats.failedFiles++; + }) + ) + ); + + // Save progress + await this.saveState(); + + // Small delay between batches + if (i + MIGRATION_BATCH_SIZE < files.length) { + await new Promise((resolve) => setTimeout(resolve, MIGRATION_DELAY_MS)); + } + + // Log progress + const progress = Math.round(((i + batch.length) / files.length) * 100); + console.log(`[MIGRATION] Progress: ${progress}% (${this.stats.migratedFiles}/${files.length})`); + } + + this.stats.endTime = Date.now(); + await this.markMigrationComplete(); + + const durationSeconds = Math.round((this.stats.endTime - this.stats.startTime) / 1000); + const sizeMB = Math.round(this.stats.totalSizeBytes / 1024 / 1024); + + console.log("[MIGRATION] ✓✓✓ Migration completed successfully!"); + console.log(`[MIGRATION] Stats:`); + console.log(` - Total files: ${this.stats.totalFiles}`); + console.log(` - Migrated: ${this.stats.migratedFiles}`); + console.log(` - Failed: ${this.stats.failedFiles}`); + console.log(` - Skipped: ${this.stats.skippedFiles}`); + console.log(` - Total size: ${sizeMB}MB`); + console.log(` - Duration: ${durationSeconds}s`); + } catch (error) { + console.error("[MIGRATION] Migration failed:", error); + await this.saveState(); + throw error; + } + } + + /** + * Scan directory recursively for files + */ + private async scanDirectory(dir: string, baseDir: string = dir): Promise { + const files: string[] = []; + + try { + const entries = await fs.readdir(dir, { withFileTypes: true }); + + for (const entry of entries) { + const fullPath = path.join(dir, entry.name); + + // Skip special files and directories + if (entry.name.startsWith(".") || entry.name === "temp-uploads") { + continue; + } + + if (entry.isDirectory()) { + const subFiles = await this.scanDirectory(fullPath, baseDir); + files.push(...subFiles); + } else if (entry.isFile()) { + // Get relative path for S3 key + const relativePath = path.relative(baseDir, fullPath); + files.push(relativePath); + } + } + } catch (error) { + console.warn(`[MIGRATION] Could not scan directory ${dir}:`, error); + } + + return files; + } + + /** + * Migrate a single file to S3 + */ + private async migrateFile(relativeFilePath: string): Promise { + const fullPath = path.join(directoriesConfig.uploads, relativeFilePath); + + try { + // Check if file still exists + const stats = await fs.stat(fullPath); + + if (!stats.isFile()) { + this.stats.skippedFiles++; + return; + } + + // S3 object name (preserve directory structure) + const objectName = relativeFilePath.replace(/\\/g, "/"); + + // Check if already exists in S3 + if (s3Client) { + try { + const { HeadObjectCommand } = await import("@aws-sdk/client-s3"); + await s3Client.send( + new HeadObjectCommand({ + Bucket: bucketName, + Key: objectName, + }) + ); + + // Already exists in S3, skip + console.log(`[MIGRATION] Already in S3: ${objectName}`); + this.stats.skippedFiles++; + return; + } catch (error: any) { + // Not found, proceed with migration + if (error.$metadata?.httpStatusCode !== 404) { + throw error; + } + } + } + + // Upload to S3 + if (s3Client) { + const fileStream = createReadStream(fullPath); + + await s3Client.send( + new PutObjectCommand({ + Bucket: bucketName, + Key: objectName, + Body: fileStream, + }) + ); + + this.stats.migratedFiles++; + this.stats.totalSizeBytes += stats.size; + + console.log(`[MIGRATION] ✓ Migrated: ${objectName} (${Math.round(stats.size / 1024)}KB)`); + + // Delete filesystem file after successful migration to free up space + try { + await fs.unlink(fullPath); + console.log(`[MIGRATION] 🗑️ Deleted from filesystem: ${relativeFilePath}`); + } catch (unlinkError) { + console.warn(`[MIGRATION] Warning: Could not delete ${relativeFilePath}:`, unlinkError); + } + } + } catch (error) { + console.error(`[MIGRATION] Failed to migrate ${relativeFilePath}:`, error); + this.stats.failedFiles++; + throw error; + } + } + + /** + * Save migration state + */ + private async saveState(): Promise { + try { + await fs.writeFile(MIGRATION_STATE_FILE, JSON.stringify({ ...this.stats, completed: false }, null, 2)); + } catch (error) { + console.warn("[MIGRATION] Could not save state:", error); + } + } + + /** + * Mark migration as complete + */ + private async markMigrationComplete(): Promise { + try { + await fs.writeFile(MIGRATION_STATE_FILE, JSON.stringify({ ...this.stats, completed: true }, null, 2)); + console.log("[MIGRATION] Migration marked as complete"); + } catch (error) { + console.warn("[MIGRATION] Could not mark migration complete:", error); + } + } +} + +/** + * Auto-run migration on import (called by server.ts) + */ +export async function runAutoMigration(): Promise { + const migrator = new FilesystemToS3Migrator(); + + if (await migrator.shouldMigrate()) { + // Run in background, don't block server start + setTimeout(async () => { + try { + await migrator.migrate(); + } catch (error) { + console.error("[MIGRATION] Auto-migration failed:", error); + console.log("[MIGRATION] Will retry on next server restart"); + } + }, 5000); // Start after 5 seconds + + console.log("[MIGRATION] Background migration scheduled"); + } +} diff --git a/apps/server/src/server.ts b/apps/server/src/server.ts index 07558f3a..a6367671 100644 --- a/apps/server/src/server.ts +++ b/apps/server/src/server.ts @@ -1,27 +1,22 @@ import * as fs from "fs/promises"; import crypto from "node:crypto"; -import path from "path"; import fastifyMultipart from "@fastify/multipart"; -import fastifyStatic from "@fastify/static"; import { buildApp } from "./app"; import { directoriesConfig } from "./config/directories.config"; -import { env } from "./env"; import { appRoutes } from "./modules/app/routes"; import { authProvidersRoutes } from "./modules/auth-providers/routes"; import { authRoutes } from "./modules/auth/routes"; import { fileRoutes } from "./modules/file/routes"; -import { ChunkManager } from "./modules/filesystem/chunk-manager"; -import { downloadQueueRoutes } from "./modules/filesystem/download-queue-routes"; -import { filesystemRoutes } from "./modules/filesystem/routes"; import { folderRoutes } from "./modules/folder/routes"; import { healthRoutes } from "./modules/health/routes"; +import { inviteRoutes } from "./modules/invite/routes"; import { reverseShareRoutes } from "./modules/reverse-share/routes"; +import { s3StorageRoutes } from "./modules/s3-storage/routes"; import { shareRoutes } from "./modules/share/routes"; import { storageRoutes } from "./modules/storage/routes"; import { twoFactorRoutes } from "./modules/two-factor/routes"; import { userRoutes } from "./modules/user/routes"; -import { IS_RUNNING_IN_CONTAINER } from "./utils/container-detection"; if (typeof globalThis.crypto === "undefined") { globalThis.crypto = crypto.webcrypto as any; @@ -51,6 +46,9 @@ async function startServer() { const app = await buildApp(); await ensureDirectories(); + const { isInternalStorage, isExternalS3 } = await import("./config/storage.config.js"); + const { runAutoMigration } = await import("./scripts/migrate-filesystem-to-s3.js"); + await runAutoMigration(); await app.register(fastifyMultipart, { limits: { @@ -63,29 +61,26 @@ async function startServer() { }, }); - if (env.ENABLE_S3 !== "true") { - await app.register(fastifyStatic, { - root: directoriesConfig.uploads, - prefix: "/uploads/", - decorateReply: false, - }); - } - app.register(authRoutes); app.register(authProvidersRoutes, { prefix: "/auth" }); app.register(twoFactorRoutes, { prefix: "/auth" }); + app.register(inviteRoutes); app.register(userRoutes); - app.register(fileRoutes); app.register(folderRoutes); - app.register(downloadQueueRoutes); + app.register(fileRoutes); app.register(shareRoutes); app.register(reverseShareRoutes); app.register(storageRoutes); app.register(appRoutes); app.register(healthRoutes); - - if (env.ENABLE_S3 !== "true") { - app.register(filesystemRoutes); + app.register(s3StorageRoutes); + + if (isInternalStorage) { + console.log("📦 Using internal storage (auto-configured)"); + } else if (isExternalS3) { + console.log("📦 Using external S3 storage (AWS/S3-compatible)"); + } else { + console.log("⚠️ WARNING: Storage not configured! Storage may not work."); } await app.listen({ @@ -93,36 +88,11 @@ async function startServer() { host: "0.0.0.0", }); - let authProviders = "Disabled"; - try { - const { AuthProvidersService } = await import("./modules/auth-providers/service.js"); - const authService = new AuthProvidersService(); - const enabledProviders = await authService.getEnabledProviders(); - authProviders = enabledProviders.length > 0 ? `Enabled (${enabledProviders.length} providers)` : "Disabled"; - } catch (error) { - console.error("Error getting auth providers status:", error); - } + console.log(`🌴 Palmr server running on port 3333`); - console.log(`🌴 Palmr server running on port 3333 🌴`); - console.log( - `📦 Storage mode: ${env.ENABLE_S3 === "true" ? "S3" : `Local Filesystem ${env.DISABLE_FILESYSTEM_ENCRYPTION === "true" ? "(Unencrypted)" : "(Encrypted)"}`}` - ); - console.log(`🔐 Auth Providers: ${authProviders}`); - - console.log("\n📚 API Documentation:"); - console.log(` - API Reference: http://localhost:3333/docs\n`); - - process.on("SIGINT", async () => { - const chunkManager = ChunkManager.getInstance(); - chunkManager.destroy(); - process.exit(0); - }); - - process.on("SIGTERM", async () => { - const chunkManager = ChunkManager.getInstance(); - chunkManager.destroy(); - process.exit(0); - }); + // Cleanup on shutdown + process.on("SIGINT", () => process.exit(0)); + process.on("SIGTERM", () => process.exit(0)); } startServer().catch((err) => { diff --git a/apps/server/src/types/download-queue.ts b/apps/server/src/types/download-queue.ts deleted file mode 100644 index 608919dc..00000000 --- a/apps/server/src/types/download-queue.ts +++ /dev/null @@ -1,52 +0,0 @@ -/** - * TypeScript interfaces for download queue management - */ - -export interface QueuedDownloadInfo { - downloadId: string; - position: number; - waitTime: number; - fileName?: string; - fileSize?: number; -} - -export interface QueueStatus { - queueLength: number; - maxQueueSize: number; - activeDownloads: number; - maxConcurrent: number; - queuedDownloads: QueuedDownloadInfo[]; -} - -export interface DownloadCancelResponse { - message: string; - downloadId: string; -} - -export interface QueueClearResponse { - message: string; - clearedCount: number; -} - -export interface ApiResponse { - status: "success" | "error"; - data?: T; - error?: string; - message?: string; -} - -export interface QueueStatusResponse extends ApiResponse { - status: "success"; - data: QueueStatus; -} - -export interface DownloadSlotRequest { - fileName?: string; - fileSize?: number; - objectName: string; -} - -export interface ActiveDownloadInfo { - startTime: number; - memoryAtStart: number; -} diff --git a/apps/server/src/types/storage.ts b/apps/server/src/types/storage.ts index 30931856..884a3964 100644 --- a/apps/server/src/types/storage.ts +++ b/apps/server/src/types/storage.ts @@ -3,6 +3,17 @@ export interface StorageProvider { getPresignedGetUrl(objectName: string, expires: number, fileName?: string): Promise; deleteObject(objectName: string): Promise; fileExists(objectName: string): Promise; + getObjectStream(objectName: string): Promise; + + // Multipart upload methods + createMultipartUpload(objectName: string): Promise; + getPresignedPartUrl(objectName: string, uploadId: string, partNumber: number, expires: number): Promise; + completeMultipartUpload( + objectName: string, + uploadId: string, + parts: Array<{ PartNumber: number; ETag: string }> + ): Promise; + abortMultipartUpload(objectName: string, uploadId: string): Promise; } export interface StorageConfig { diff --git a/apps/server/src/utils/download-memory-manager.ts b/apps/server/src/utils/download-memory-manager.ts deleted file mode 100644 index cf4e5ee3..00000000 --- a/apps/server/src/utils/download-memory-manager.ts +++ /dev/null @@ -1,423 +0,0 @@ -import { ActiveDownloadInfo, DownloadSlotRequest, QueuedDownloadInfo, QueueStatus } from "../types/download-queue"; - -interface QueuedDownload { - downloadId: string; - queuedAt: number; - resolve: () => void; - reject: (error: Error) => void; - metadata?: DownloadSlotRequest; -} - -export class DownloadMemoryManager { - private static instance: DownloadMemoryManager; - private activeDownloads = new Map(); - private downloadQueue: QueuedDownload[] = []; - private maxConcurrentDownloads: number; - private memoryThresholdMB: number; - private maxQueueSize: number; - private cleanupInterval: NodeJS.Timeout; - private isAutoScalingEnabled: boolean; - private minFileSizeGB: number; - - private constructor() { - const { env } = require("../env"); - - const totalMemoryGB = require("os").totalmem() / 1024 ** 3; - this.isAutoScalingEnabled = env.DOWNLOAD_AUTO_SCALE === "true"; - - if (env.DOWNLOAD_MAX_CONCURRENT !== undefined) { - this.maxConcurrentDownloads = env.DOWNLOAD_MAX_CONCURRENT; - } else if (this.isAutoScalingEnabled) { - this.maxConcurrentDownloads = this.calculateDefaultConcurrentDownloads(totalMemoryGB); - } else { - this.maxConcurrentDownloads = 3; - } - - if (env.DOWNLOAD_MEMORY_THRESHOLD_MB !== undefined) { - this.memoryThresholdMB = env.DOWNLOAD_MEMORY_THRESHOLD_MB; - } else if (this.isAutoScalingEnabled) { - this.memoryThresholdMB = this.calculateDefaultMemoryThreshold(totalMemoryGB); - } else { - this.memoryThresholdMB = 1024; - } - - if (env.DOWNLOAD_QUEUE_SIZE !== undefined) { - this.maxQueueSize = env.DOWNLOAD_QUEUE_SIZE; - } else if (this.isAutoScalingEnabled) { - this.maxQueueSize = this.calculateDefaultQueueSize(totalMemoryGB); - } else { - this.maxQueueSize = 15; - } - - if (env.DOWNLOAD_MIN_FILE_SIZE_GB !== undefined) { - this.minFileSizeGB = env.DOWNLOAD_MIN_FILE_SIZE_GB; - } else { - this.minFileSizeGB = 3.0; - } - - this.validateConfiguration(); - - console.log(`[DOWNLOAD MANAGER] Configuration loaded:`); - console.log(` System Memory: ${totalMemoryGB.toFixed(1)}GB`); - console.log( - ` Max Concurrent: ${this.maxConcurrentDownloads} ${env.DOWNLOAD_MAX_CONCURRENT !== undefined ? "(ENV)" : "(AUTO)"}` - ); - console.log( - ` Memory Threshold: ${this.memoryThresholdMB}MB ${env.DOWNLOAD_MEMORY_THRESHOLD_MB !== undefined ? "(ENV)" : "(AUTO)"}` - ); - console.log(` Queue Size: ${this.maxQueueSize} ${env.DOWNLOAD_QUEUE_SIZE !== undefined ? "(ENV)" : "(AUTO)"}`); - console.log( - ` Min File Size: ${this.minFileSizeGB}GB ${env.DOWNLOAD_MIN_FILE_SIZE_GB !== undefined ? "(ENV)" : "(DEFAULT)"}` - ); - console.log(` Auto-scaling: ${this.isAutoScalingEnabled ? "enabled" : "disabled"}`); - - this.cleanupInterval = setInterval(() => { - this.cleanupStaleDownloads(); - }, 30000); - } - - public static getInstance(): DownloadMemoryManager { - if (!DownloadMemoryManager.instance) { - DownloadMemoryManager.instance = new DownloadMemoryManager(); - } - return DownloadMemoryManager.instance; - } - - private calculateDefaultConcurrentDownloads(totalMemoryGB: number): number { - if (totalMemoryGB > 16) return 10; - if (totalMemoryGB > 8) return 5; - if (totalMemoryGB > 4) return 3; - if (totalMemoryGB > 2) return 2; - return 1; - } - - private calculateDefaultMemoryThreshold(totalMemoryGB: number): number { - if (totalMemoryGB > 16) return 4096; // 4GB - if (totalMemoryGB > 8) return 2048; // 2GB - if (totalMemoryGB > 4) return 1024; // 1GB - if (totalMemoryGB > 2) return 512; // 512MB - return 256; // 256MB - } - - private calculateDefaultQueueSize(totalMemoryGB: number): number { - if (totalMemoryGB > 16) return 50; // Large queue for powerful servers - if (totalMemoryGB > 8) return 25; // Medium queue - if (totalMemoryGB > 4) return 15; // Small queue - if (totalMemoryGB > 2) return 10; // Very small queue - return 5; // Minimal queue - } - - private validateConfiguration(): void { - const warnings: string[] = []; - const errors: string[] = []; - - if (this.maxConcurrentDownloads < 1) { - errors.push(`DOWNLOAD_MAX_CONCURRENT must be >= 1, got: ${this.maxConcurrentDownloads}`); - } - if (this.maxConcurrentDownloads > 50) { - warnings.push( - `DOWNLOAD_MAX_CONCURRENT is very high (${this.maxConcurrentDownloads}), this may cause performance issues` - ); - } - - if (this.memoryThresholdMB < 128) { - warnings.push( - `DOWNLOAD_MEMORY_THRESHOLD_MB is very low (${this.memoryThresholdMB}MB), downloads may be throttled frequently` - ); - } - if (this.memoryThresholdMB > 16384) { - warnings.push( - `DOWNLOAD_MEMORY_THRESHOLD_MB is very high (${this.memoryThresholdMB}MB), system may run out of memory` - ); - } - - if (this.maxQueueSize < 1) { - errors.push(`DOWNLOAD_QUEUE_SIZE must be >= 1, got: ${this.maxQueueSize}`); - } - if (this.maxQueueSize > 1000) { - warnings.push(`DOWNLOAD_QUEUE_SIZE is very high (${this.maxQueueSize}), this may consume significant memory`); - } - - if (this.minFileSizeGB < 0.1) { - warnings.push( - `DOWNLOAD_MIN_FILE_SIZE_GB is very low (${this.minFileSizeGB}GB), most downloads will use memory management` - ); - } - if (this.minFileSizeGB > 50) { - warnings.push( - `DOWNLOAD_MIN_FILE_SIZE_GB is very high (${this.minFileSizeGB}GB), memory management may rarely activate` - ); - } - - const recommendedQueueSize = this.maxConcurrentDownloads * 5; - if (this.maxQueueSize < this.maxConcurrentDownloads) { - warnings.push( - `DOWNLOAD_QUEUE_SIZE (${this.maxQueueSize}) is smaller than DOWNLOAD_MAX_CONCURRENT (${this.maxConcurrentDownloads})` - ); - } else if (this.maxQueueSize < recommendedQueueSize) { - warnings.push( - `DOWNLOAD_QUEUE_SIZE (${this.maxQueueSize}) might be too small. Recommended: ${recommendedQueueSize} (5x concurrent downloads)` - ); - } - - if (warnings.length > 0) { - console.warn(`[DOWNLOAD MANAGER] Configuration warnings:`); - warnings.forEach((warning) => console.warn(` - ${warning}`)); - } - - if (errors.length > 0) { - console.error(`[DOWNLOAD MANAGER] Configuration errors:`); - errors.forEach((error) => console.error(` - ${error}`)); - throw new Error(`Invalid download manager configuration: ${errors.join(", ")}`); - } - } - - public async requestDownloadSlot(downloadId: string, metadata?: DownloadSlotRequest): Promise { - if (metadata?.fileSize) { - const fileSizeGB = metadata.fileSize / 1024 ** 3; - if (fileSizeGB < this.minFileSizeGB) { - console.log( - `[DOWNLOAD MANAGER] File ${metadata.fileName || "unknown"} (${fileSizeGB.toFixed(2)}GB) below threshold (${this.minFileSizeGB}GB), bypassing queue` - ); - return Promise.resolve(); - } - } - - if (this.canStartImmediately()) { - console.log(`[DOWNLOAD MANAGER] Immediate start: ${downloadId}`); - return Promise.resolve(); - } - - if (this.downloadQueue.length >= this.maxQueueSize) { - const error = new Error(`Download queue is full: ${this.downloadQueue.length}/${this.maxQueueSize}`); - throw error; - } - - return new Promise((resolve, reject) => { - const queuedDownload: QueuedDownload = { - downloadId, - queuedAt: Date.now(), - resolve, - reject, - metadata, - }; - - this.downloadQueue.push(queuedDownload); - - const position = this.downloadQueue.length; - console.log(`[DOWNLOAD MANAGER] Queued: ${downloadId} (Position: ${position}/${this.maxQueueSize})`); - - if (metadata?.fileName && metadata?.fileSize) { - const sizeMB = (metadata.fileSize / (1024 * 1024)).toFixed(1); - console.log(`[DOWNLOAD MANAGER] Queued file: ${metadata.fileName} (${sizeMB}MB)`); - } - }); - } - - private canStartImmediately(): boolean { - const currentMemoryMB = this.getCurrentMemoryUsage(); - - if (currentMemoryMB > this.memoryThresholdMB) { - return false; - } - - if (this.activeDownloads.size >= this.maxConcurrentDownloads) { - return false; - } - - return true; - } - - public canStartDownload(): { allowed: boolean; reason?: string } { - if (this.canStartImmediately()) { - return { allowed: true }; - } - - const currentMemoryMB = this.getCurrentMemoryUsage(); - - if (currentMemoryMB > this.memoryThresholdMB) { - return { - allowed: false, - reason: `Memory usage too high: ${currentMemoryMB.toFixed(0)}MB > ${this.memoryThresholdMB}MB`, - }; - } - - return { - allowed: false, - reason: `Too many concurrent downloads: ${this.activeDownloads.size}/${this.maxConcurrentDownloads}`, - }; - } - - public startDownload(downloadId: string): void { - const memUsage = process.memoryUsage(); - this.activeDownloads.set(downloadId, { - startTime: Date.now(), - memoryAtStart: memUsage.rss + memUsage.external, - }); - - console.log( - `[DOWNLOAD MANAGER] Started: ${downloadId} (${this.activeDownloads.size}/${this.maxConcurrentDownloads} active)` - ); - } - - public endDownload(downloadId: string): void { - const downloadInfo = this.activeDownloads.get(downloadId); - this.activeDownloads.delete(downloadId); - - if (downloadInfo) { - const duration = Date.now() - downloadInfo.startTime; - const memUsage = process.memoryUsage(); - const currentMemory = memUsage.rss + memUsage.external; - const memoryDiff = currentMemory - downloadInfo.memoryAtStart; - - console.log( - `[DOWNLOAD MANAGER] Ended: ${downloadId} (Duration: ${(duration / 1000).toFixed(1)}s, Memory delta: ${(memoryDiff / 1024 / 1024).toFixed(1)}MB)` - ); - - if (memoryDiff > 100 * 1024 * 1024 && global.gc) { - setImmediate(() => { - global.gc!(); - console.log(`[DOWNLOAD MANAGER] Forced GC after download ${downloadId}`); - }); - } - } - - this.processQueue(); - } - - private processQueue(): void { - if (this.downloadQueue.length === 0 || !this.canStartImmediately()) { - return; - } - - const nextDownload = this.downloadQueue.shift(); - if (!nextDownload) { - return; - } - - console.log( - `[DOWNLOAD MANAGER] Processing queue: ${nextDownload.downloadId} (${this.downloadQueue.length} remaining)` - ); - - if (nextDownload.metadata?.fileName && nextDownload.metadata?.fileSize) { - const sizeMB = (nextDownload.metadata.fileSize / (1024 * 1024)).toFixed(1); - console.log(`[DOWNLOAD MANAGER] Starting queued file: ${nextDownload.metadata.fileName} (${sizeMB}MB)`); - } - - nextDownload.resolve(); - } - - public getActiveDownloadsCount(): number { - return this.activeDownloads.size; - } - - private getCurrentMemoryUsage(): number { - const usage = process.memoryUsage(); - return (usage.rss + usage.external) / (1024 * 1024); - } - - public getCurrentMemoryUsageMB(): number { - return this.getCurrentMemoryUsage(); - } - - public getQueueStatus(): QueueStatus { - return { - queueLength: this.downloadQueue.length, - maxQueueSize: this.maxQueueSize, - activeDownloads: this.activeDownloads.size, - maxConcurrent: this.maxConcurrentDownloads, - queuedDownloads: this.downloadQueue.map((download, index) => ({ - downloadId: download.downloadId, - position: index + 1, - waitTime: Date.now() - download.queuedAt, - fileName: download.metadata?.fileName, - fileSize: download.metadata?.fileSize, - })), - }; - } - - public cancelQueuedDownload(downloadId: string): boolean { - const index = this.downloadQueue.findIndex((item) => item.downloadId === downloadId); - - if (index === -1) { - return false; - } - - const canceledDownload = this.downloadQueue.splice(index, 1)[0]; - canceledDownload.reject(new Error(`Download ${downloadId} was cancelled`)); - - console.log(`[DOWNLOAD MANAGER] Cancelled queued download: ${downloadId} (was at position ${index + 1})`); - return true; - } - - private cleanupStaleDownloads(): void { - const now = Date.now(); - const staleThreshold = 10 * 60 * 1000; // 10 minutes - const queueStaleThreshold = 30 * 60 * 1000; - - for (const [downloadId, info] of this.activeDownloads.entries()) { - if (now - info.startTime > staleThreshold) { - console.warn(`[DOWNLOAD MANAGER] Cleaning up stale active download: ${downloadId}`); - this.activeDownloads.delete(downloadId); - } - } - - const initialQueueLength = this.downloadQueue.length; - this.downloadQueue = this.downloadQueue.filter((download) => { - if (now - download.queuedAt > queueStaleThreshold) { - console.warn(`[DOWNLOAD MANAGER] Cleaning up stale queued download: ${download.downloadId}`); - download.reject(new Error(`Download ${download.downloadId} timed out in queue`)); - return false; - } - return true; - }); - - if (this.downloadQueue.length < initialQueueLength) { - console.log( - `[DOWNLOAD MANAGER] Cleaned up ${initialQueueLength - this.downloadQueue.length} stale queued downloads` - ); - } - - this.processQueue(); - } - - public shouldThrottleStream(): boolean { - const currentMemoryMB = this.getCurrentMemoryUsageMB(); - return currentMemoryMB > this.memoryThresholdMB * 0.8; - } - - public getThrottleDelay(): number { - const currentMemoryMB = this.getCurrentMemoryUsageMB(); - const thresholdRatio = currentMemoryMB / this.memoryThresholdMB; - - if (thresholdRatio > 0.9) return 200; - if (thresholdRatio > 0.8) return 100; - return 50; - } - - public destroy(): void { - if (this.cleanupInterval) { - clearInterval(this.cleanupInterval); - } - - this.downloadQueue.forEach((download) => { - download.reject(new Error("Download manager is shutting down")); - }); - - this.activeDownloads.clear(); - this.downloadQueue = []; - console.log("[DOWNLOAD MANAGER] Shutdown completed"); - } - - public clearQueue(): number { - const clearedCount = this.downloadQueue.length; - - this.downloadQueue.forEach((download) => { - download.reject(new Error("Queue was cleared by administrator")); - }); - - this.downloadQueue = []; - console.log(`[DOWNLOAD MANAGER] Cleared queue: ${clearedCount} downloads cancelled`); - return clearedCount; - } -} diff --git a/apps/web/messages/ar-SA.json b/apps/web/messages/ar-SA.json index 0f7a10f6..9d7a661f 100644 --- a/apps/web/messages/ar-SA.json +++ b/apps/web/messages/ar-SA.json @@ -6,7 +6,9 @@ "token_expired": "انتهت صلاحية الرمز المميز. حاول مرة أخرى.", "config_error": "خطأ في التكوين. اتصل بالدعم الفني.", "auth_failed": "فشل في المصادقة. حاول مرة أخرى." - } + }, + "authenticationFailed": "فشلت المصادقة", + "successfullyAuthenticated": "تم المصادقة بنجاح!" }, "authProviders": { "title": "مزودي المصادقة", @@ -174,7 +176,14 @@ "tabs": { "shareDetails": "تفاصيل المشاركة", "selectFiles": "اختيار الملفات" - } + }, + "errors": { + "nameRequired": "اسم المشاركة مطلوب", + "selectItems": "يرجى اختيار ملف أو مجلد واحد على الأقل" + }, + "itemsSelected": "{count} عنصر محدد", + "passwordPlaceholder": "أدخل كلمة المرور", + "selectItemsPrompt": "اختر الملفات والمجلدات للمشاركة" }, "customization": { "breadcrumb": "التخصيص", @@ -340,7 +349,8 @@ "addToShare": "إضافة إلى المشاركة", "removeFromShare": "إزالة من المشاركة", "saveChanges": "حفظ التغييرات", - "editFolder": "تحرير المجلد" + "editFolder": "تحرير المجلد", + "itemsSelected": "{count} عنصر محدد" }, "files": { "title": "جميع الملفات", @@ -376,7 +386,12 @@ "description": "ارفع ملفك الأول أو أنشئ مجلدًا للبدء" }, "files": "ملفات", - "folders": "مجلدات" + "folders": "مجلدات", + "errors": { + "moveItemsFailed": "فشل نقل العناصر. يرجى المحاولة مرة أخرى.", + "cannotMoveHere": "لا يمكن نقل العناصر إلى هذا الموقع" + }, + "openFolder": "فتح المجلد" }, "filesTable": { "ariaLabel": "جدول الملفات", @@ -539,7 +554,10 @@ "movingTo": "النقل إلى:", "title": "نقل {count, plural, =1 {عنصر} other {عناصر}}", "description": "نقل {count, plural, =1 {عنصر} other {عناصر}} إلى موقع جديد", - "success": "تم نقل {count} {count, plural, =1 {عنصر} other {عناصر}} بنجاح" + "success": "تم نقل {count} {count, plural, =1 {عنصر} other {عناصر}} بنجاح", + "errors": { + "moveFailed": "فشل نقل العناصر" + } }, "navbar": { "logoAlt": "شعار التطبيق", @@ -1149,14 +1167,10 @@ "components": { "fileRow": { "addDescription": "إضافة وصف...", - "anonymous": "مجهول", - "deleteError": "خطأ في حذف الملف", - "deleteSuccess": "تم حذف الملف بنجاح" + "anonymous": "مجهول" }, "fileActions": { "edit": "تحرير", - "save": "حفظ", - "cancel": "إلغاء", "preview": "معاينة", "download": "تحميل", "delete": "حذف", @@ -1379,16 +1393,6 @@ "deleteTitle": "حذف المشاركة", "deleteConfirmation": "هل أنت متأكد من أنك تريد حذف هذه المشاركة؟ لا يمكن التراجع عن هذا الإجراء.", "editTitle": "تحرير المشاركة", - "nameLabel": "اسم المشاركة", - "descriptionLabel": "الوصف", - "descriptionPlaceholder": "أدخل وصفاً (اختياري)", - "expirationLabel": "تاريخ انتهاء الصلاحية", - "expirationPlaceholder": "YYYY/MM/DD HH:MM", - "maxViewsLabel": "الحد الأقصى للمشاهدات", - "maxViewsPlaceholder": "اتركه فارغاً للمشاهدات غير المحدودة", - "passwordProtection": "محمي بكلمة مرور", - "passwordLabel": "كلمة المرور", - "passwordPlaceholder": "أدخل كلمة المرور", "newPasswordLabel": "كلمة المرور الجديدة (اتركها فارغة للاحتفاظ بالحالية)", "newPasswordPlaceholder": "أدخل كلمة المرور الجديدة", "manageFilesTitle": "إدارة الملفات", @@ -1407,7 +1411,9 @@ "linkDescriptionFile": "إنشاء رابط مخصص لمشاركة الملف", "linkDescriptionFolder": "إنشاء رابط مخصص لمشاركة المجلد", "linkReady": "رابط المشاركة جاهز:", - "linkTitle": "إنشاء رابط" + "linkTitle": "إنشاء رابط", + "itemsSelected": "{count} عنصر محدد", + "manageFilesDescription": "اختر الملفات والمجلدات لتضمينها في هذه المشاركة" }, "shareDetails": { "title": "تفاصيل المشاركة", @@ -1423,7 +1429,6 @@ "noLink": "لم يتم إنشاء رابط بعد", "copyLink": "نسخ الرابط", "openLink": "فتح في علامة تبويب جديدة", - "linkCopied": "تم نسخ الرابط إلى الحافظة", "views": "المشاهدات", "dates": "التواريخ", "created": "تم الإنشاء", @@ -1471,28 +1476,6 @@ "expires": "تنتهي صلاحيته:", "expirationDate": "تاريخ انتهاء الصلاحية" }, - "shareFile": { - "title": "مشاركة ملف", - "linkTitle": "إنشاء رابط", - "nameLabel": "اسم المشاركة", - "namePlaceholder": "أدخل اسم المشاركة", - "descriptionLabel": "الوصف", - "descriptionPlaceholder": "أدخل وصفاً (اختياري)", - "expirationLabel": "تاريخ انتهاء الصلاحية", - "expirationPlaceholder": "YYYY/MM/DD HH:MM", - "maxViewsLabel": "الحد الأقصى للمشاهدات", - "maxViewsPlaceholder": "اتركه فارغاً للمشاهدات غير المحدودة", - "passwordProtection": "محمي بكلمة مرور", - "passwordLabel": "كلمة المرور", - "passwordPlaceholder": "أدخل كلمة المرور", - "linkDescription": "إنشاء رابط مخصص لمشاركة الملف", - "aliasLabel": "اسم مستعار للرابط", - "aliasPlaceholder": "أدخل اسماً مستعاراً مخصصاً", - "linkReady": "رابط المشاركة جاهز:", - "createShare": "إنشاء مشاركة", - "generateLink": "إنشاء رابط", - "copyLink": "نسخ الرابط" - }, "shareManager": { "deleteSuccess": "تم حذف المشاركة بنجاح", "deleteError": "فشل في حذف المشاركة", @@ -1522,7 +1505,10 @@ "noFilesToDownload": "لا توجد ملفات متاحة للتنزيل", "singleShareZipName": "{ShareName} _files.zip", "zipDownloadError": "فشل في إنشاء ملف مضغوط", - "zipDownloadSuccess": "تم تنزيل ملف zip بنجاح" + "zipDownloadSuccess": "تم تنزيل ملف zip بنجاح", + "errors": { + "multipleDownloadNotSupported": "تنزيل مشاركات متعددة غير مدعوم حالياً - يرجى تنزيل المشاركات بشكل فردي" + } }, "shareMultipleFiles": { "title": "مشاركة ملفات متعددة", @@ -1919,6 +1905,23 @@ "inactive": "غير مفعل", "admin": "مسؤول", "userr": "مستخدم" + }, + "invite": { + "button": "إنشاء رابط دعوة", + "title": "إنشاء رابط دعوة للمستخدم", + "description": "إنشاء رابط يستخدم لمرة واحدة يسمح لشخص ما بإنشاء حسابه الخاص. ينتهي الرابط بعد 15 دقيقة.", + "generating": "جاري الإنشاء...", + "generate": "إنشاء الرابط", + "generated": "تم إنشاء رابط الدعوة بنجاح!", + "linkReady": "رابط الدعوة جاهز", + "linkReadyDescription": "شارك هذا الرابط مع الشخص الذي تريد دعوته. سيتمكن من إنشاء حسابه الخاص كمستخدم عادي.", + "copyLink": "نسخ الرابط", + "linkCopied": "تم نسخ رابط الدعوة إلى الحافظة!", + "expiresIn": "ينتهي بعد 15 دقيقة", + "close": "إغلاق", + "errors": { + "generateFailed": "فشل في إنشاء رابط الدعوة" + } } }, "validation": { @@ -1937,15 +1940,62 @@ "required": "هذا الحقل مطلوب" }, "embedCode": { - "title": "تضمين الصورة", - "description": "استخدم هذه الأكواد لتضمين هذه الصورة في المنتديات أو المواقع الإلكترونية أو المنصات الأخرى", + "title": "تضمين الوسائط", + "description": "استخدم هذه الأكواد لتضمين هذه الوسائط في المنتديات أو المواقع الإلكترونية أو المنصات الأخرى", "tabs": { "directLink": "رابط مباشر", "html": "HTML", "bbcode": "BBCode" }, - "directLinkDescription": "عنوان URL مباشر لملف الصورة", - "htmlDescription": "استخدم هذا الكود لتضمين الصورة في صفحات HTML", - "bbcodeDescription": "استخدم هذا الكود لتضمين الصورة في المنتديات التي تدعم BBCode" + "directLinkDescription": "عنوان URL مباشر لملف الوسائط", + "htmlDescription": "استخدم هذا الكود لتضمين الوسائط في صفحات HTML", + "bbcodeDescription": "استخدم هذا الكود لتضمين الوسائط في المنتديات التي تدعم BBCode" + }, + "contextMenu": { + "newFolder": "مجلد جديد", + "uploadFile": "رفع ملف" + }, + "registerWithInvite": { + "title": "إنشاء حسابك", + "description": "املأ المعلومات أدناه لإنشاء حسابك", + "labels": { + "firstName": "الاسم الأول", + "firstNamePlaceholder": "أدخل اسمك الأول", + "lastName": "اسم العائلة", + "lastNamePlaceholder": "أدخل اسم عائلتك", + "username": "اسم المستخدم", + "usernamePlaceholder": "اختر اسم مستخدم", + "email": "البريد الإلكتروني", + "emailPlaceholder": "أدخل بريدك الإلكتروني", + "password": "كلمة المرور", + "passwordPlaceholder": "اختر كلمة مرور", + "confirmPassword": "تأكيد كلمة المرور", + "confirmPasswordPlaceholder": "أكد كلمة مرورك" + }, + "buttons": { + "creating": "جاري إنشاء الحساب...", + "createAccount": "إنشاء حساب" + }, + "validation": { + "firstNameRequired": "الاسم الأول مطلوب", + "lastNameRequired": "اسم العائلة مطلوب", + "usernameMinLength": "يجب أن يحتوي اسم المستخدم على 3 أحرف على الأقل", + "invalidEmail": "البريد الإلكتروني غير صالح", + "passwordMinLength": "يجب أن تحتوي كلمة المرور على 8 أحرف على الأقل", + "passwordsMatch": "يجب أن تتطابق كلمتا المرور" + }, + "messages": { + "success": "تم إنشاء الحساب بنجاح! جارٍ إعادة التوجيه إلى تسجيل الدخول...", + "redirecting": "جارٍ إعادة التوجيه إلى تسجيل الدخول..." + }, + "errors": { + "invalidToken": "خطأ في رابط الدعوة", + "tokenUsed": "تم استخدام رابط الدعوة هذا بالفعل", + "tokenExpired": "انتهت صلاحية رابط الدعوة هذا", + "usernameExists": "اسم المستخدم موجود بالفعل", + "emailExists": "البريد الإلكتروني موجود بالفعل", + "createFailed": "فشل في إنشاء الحساب. يرجى المحاولة مرة أخرى." + }, + "pageTitle": "إنشاء حساب" } } \ No newline at end of file diff --git a/apps/web/messages/de-DE.json b/apps/web/messages/de-DE.json index 576a7309..9bb4af4c 100644 --- a/apps/web/messages/de-DE.json +++ b/apps/web/messages/de-DE.json @@ -6,7 +6,9 @@ "token_expired": "Token abgelaufen. Versuchen Sie es erneut.", "config_error": "Konfigurationsfehler. Kontaktieren Sie den Support.", "auth_failed": "Authentifizierung fehlgeschlagen. Versuchen Sie es erneut." - } + }, + "authenticationFailed": "Authentifizierung fehlgeschlagen", + "successfullyAuthenticated": "Erfolgreich authentifiziert!" }, "authProviders": { "title": "Authentifizierungsanbieter", @@ -174,7 +176,14 @@ "tabs": { "shareDetails": "Freigabe-Details", "selectFiles": "Dateien auswählen" - } + }, + "errors": { + "nameRequired": "Freigabename ist erforderlich", + "selectItems": "Bitte wählen Sie mindestens eine Datei oder einen Ordner aus" + }, + "itemsSelected": "{count} Elemente ausgewählt", + "passwordPlaceholder": "Passwort eingeben", + "selectItemsPrompt": "Wählen Sie Dateien und Ordner zum Teilen aus" }, "customization": { "breadcrumb": "Anpassung", @@ -340,7 +349,8 @@ "addToShare": "Zur Freigabe hinzufügen", "removeFromShare": "Aus Freigabe entfernen", "saveChanges": "Änderungen Speichern", - "editFolder": "Ordner bearbeiten" + "editFolder": "Ordner bearbeiten", + "itemsSelected": "{count} Elemente ausgewählt" }, "files": { "title": "Alle Dateien", @@ -376,7 +386,12 @@ "description": "Laden Sie Ihre erste Datei hoch oder erstellen Sie einen Ordner, um zu beginnen" }, "files": "Dateien", - "folders": "Ordner" + "folders": "Ordner", + "errors": { + "moveItemsFailed": "Verschieben der Elemente fehlgeschlagen. Bitte erneut versuchen.", + "cannotMoveHere": "Elemente können nicht an diesen Speicherort verschoben werden" + }, + "openFolder": "Ordner öffnen" }, "filesTable": { "ariaLabel": "Dateien-Tabelle", @@ -539,7 +554,10 @@ "movingTo": "Verschieben nach:", "title": "{count, plural, =1 {Element} other {Elemente}} verschieben", "description": "{count, plural, =1 {Element} other {Elemente}} an einen neuen Ort verschieben", - "success": "Erfolgreich {count} {count, plural, =1 {Element} other {Elemente}} verschoben" + "success": "Erfolgreich {count} {count, plural, =1 {Element} other {Elemente}} verschoben", + "errors": { + "moveFailed": "Verschieben der Elemente fehlgeschlagen" + } }, "navbar": { "logoAlt": "Anwendungslogo", @@ -1153,8 +1171,6 @@ }, "fileActions": { "edit": "Bearbeiten", - "save": "Speichern", - "cancel": "Abbrechen", "preview": "Vorschau", "download": "Herunterladen", "delete": "Löschen", @@ -1378,16 +1394,6 @@ "deleteConfirmation": "Sind Sie sicher, dass Sie diese Freigabe löschen möchten? Diese Aktion kann nicht rückgängig gemacht werden.", "addDescriptionPlaceholder": "Beschreibung hinzufügen...", "editTitle": "Freigabe Bearbeiten", - "nameLabel": "Freigabe-Name", - "descriptionLabel": "Beschreibung", - "descriptionPlaceholder": "Geben Sie eine Beschreibung ein (optional)", - "expirationLabel": "Ablaufdatum", - "expirationPlaceholder": "TT.MM.JJJJ HH:MM", - "maxViewsLabel": "Maximale Ansichten", - "maxViewsPlaceholder": "Leer lassen für unbegrenzt", - "passwordProtection": "Passwort-geschützt", - "passwordLabel": "Passwort", - "passwordPlaceholder": "Passwort eingeben", "newPasswordLabel": "Neues Passwort (leer lassen, um das aktuelle zu behalten)", "newPasswordPlaceholder": "Neues Passwort eingeben", "manageFilesTitle": "Dateien Verwalten", @@ -1405,7 +1411,9 @@ "linkDescriptionFile": "Erstellen Sie einen benutzerdefinierten Link zum Teilen der Datei", "linkDescriptionFolder": "Erstellen Sie einen benutzerdefinierten Link zum Teilen des Ordners", "linkReady": "Ihr Freigabe-Link ist bereit:", - "linkTitle": "Link generieren" + "linkTitle": "Link generieren", + "itemsSelected": "{count} Elemente ausgewählt", + "manageFilesDescription": "Wählen Sie Dateien und Ordner aus, die in diese Freigabe aufgenommen werden sollen" }, "shareDetails": { "title": "Freigabe-Details", @@ -1421,7 +1429,6 @@ "noLink": "Noch kein Link generiert", "copyLink": "Link kopieren", "openLink": "In neuem Tab öffnen", - "linkCopied": "Link in Zwischenablage kopiert", "views": "Ansichten", "dates": "Daten", "created": "Erstellt", @@ -1469,28 +1476,6 @@ "expires": "Läuft ab:", "expirationDate": "Ablaufdatum" }, - "shareFile": { - "title": "Datei Freigeben", - "linkTitle": "Link Generieren", - "nameLabel": "Freigabe-Name", - "namePlaceholder": "Freigabe-Namen eingeben", - "descriptionLabel": "Beschreibung", - "descriptionPlaceholder": "Geben Sie eine Beschreibung ein (optional)", - "expirationLabel": "Ablaufdatum", - "expirationPlaceholder": "TT.MM.JJJJ HH:MM", - "maxViewsLabel": "Maximale Ansichten", - "maxViewsPlaceholder": "Leer lassen für unbegrenzt", - "passwordProtection": "Passwort-geschützt", - "passwordLabel": "Passwort", - "passwordPlaceholder": "Passwort eingeben", - "linkDescription": "Einen benutzerdefinierten Link zum Teilen der Datei generieren", - "aliasLabel": "Link-Alias", - "aliasPlaceholder": "Benutzerdefinierten Alias eingeben", - "linkReady": "Ihr Freigabe-Link ist bereit:", - "createShare": "Freigabe Erstellen", - "generateLink": "Link Generieren", - "copyLink": "Link Kopieren" - }, "shareManager": { "deleteSuccess": "Freigabe erfolgreich gelöscht", "deleteError": "Fehler beim Löschen der Freigabe", @@ -1520,7 +1505,10 @@ "noFilesToDownload": "Keine Dateien zum Download zur Verfügung stehen", "singleShareZipName": "{ShareName} _files.zip", "zipDownloadError": "Die ZIP -Datei erstellt nicht", - "zipDownloadSuccess": "ZIP -Datei erfolgreich heruntergeladen" + "zipDownloadSuccess": "ZIP -Datei erfolgreich heruntergeladen", + "errors": { + "multipleDownloadNotSupported": "Download mehrerer Freigaben wird noch nicht unterstützt - bitte laden Sie Freigaben einzeln herunter" + } }, "shareMultipleFiles": { "title": "Mehrere Dateien Teilen", @@ -1917,6 +1905,23 @@ "inactive": "Inaktiv", "admin": "Administrator", "userr": "Benutzer" + }, + "invite": { + "button": "Einladungslink generieren", + "title": "Benutzer-Einladungslink generieren", + "description": "Generieren Sie einen einmaligen Link, der es jemandem ermöglicht, sein eigenes Konto zu erstellen. Der Link läuft in 15 Minuten ab.", + "generating": "Wird generiert...", + "generate": "Link generieren", + "generated": "Einladungslink erfolgreich generiert!", + "linkReady": "Einladungslink bereit", + "linkReadyDescription": "Teilen Sie diesen Link mit der Person, die Sie einladen möchten. Sie können ihr eigenes Konto als normaler Benutzer erstellen.", + "copyLink": "Link kopieren", + "linkCopied": "Einladungslink in die Zwischenablage kopiert!", + "expiresIn": "Läuft in 15 Minuten ab", + "close": "Schließen", + "errors": { + "generateFailed": "Fehler beim Generieren des Einladungslinks" + } } }, "validation": { @@ -1935,15 +1940,62 @@ "required": "Dieses Feld ist erforderlich" }, "embedCode": { - "title": "Bild einbetten", - "description": "Verwenden Sie diese Codes, um dieses Bild in Foren, Websites oder anderen Plattformen einzubetten", + "title": "Medien einbetten", + "description": "Verwenden Sie diese Codes, um diese Medien in Foren, Websites oder anderen Plattformen einzubetten", "tabs": { "directLink": "Direkter Link", "html": "HTML", "bbcode": "BBCode" }, - "directLinkDescription": "Direkte URL zur Bilddatei", - "htmlDescription": "Verwenden Sie diesen Code, um das Bild in HTML-Seiten einzubetten", - "bbcodeDescription": "Verwenden Sie diesen Code, um das Bild in Foren einzubetten, die BBCode unterstützen" + "directLinkDescription": "Direkte URL zur Mediendatei", + "htmlDescription": "Verwenden Sie diesen Code, um die Medien in HTML-Seiten einzubetten", + "bbcodeDescription": "Verwenden Sie diesen Code, um die Medien in Foren einzubetten, die BBCode unterstützen" + }, + "contextMenu": { + "newFolder": "Neuer Ordner", + "uploadFile": "Datei hochladen" + }, + "registerWithInvite": { + "title": "Erstellen Sie Ihr Konto", + "description": "Füllen Sie die unten stehenden Informationen aus, um Ihr Konto zu erstellen", + "labels": { + "firstName": "Vorname", + "firstNamePlaceholder": "Geben Sie Ihren Vornamen ein", + "lastName": "Nachname", + "lastNamePlaceholder": "Geben Sie Ihren Nachnamen ein", + "username": "Benutzername", + "usernamePlaceholder": "Wählen Sie einen Benutzernamen", + "email": "E-Mail", + "emailPlaceholder": "Geben Sie Ihre E-Mail ein", + "password": "Passwort", + "passwordPlaceholder": "Wählen Sie ein Passwort", + "confirmPassword": "Passwort bestätigen", + "confirmPasswordPlaceholder": "Bestätigen Sie Ihr Passwort" + }, + "buttons": { + "creating": "Konto wird erstellt...", + "createAccount": "Konto erstellen" + }, + "validation": { + "firstNameRequired": "Vorname ist erforderlich", + "lastNameRequired": "Nachname ist erforderlich", + "usernameMinLength": "Benutzername muss mindestens 3 Zeichen lang sein", + "invalidEmail": "Ungültige E-Mail", + "passwordMinLength": "Passwort muss mindestens 8 Zeichen lang sein", + "passwordsMatch": "Passwörter müssen übereinstimmen" + }, + "messages": { + "success": "Konto erfolgreich erstellt! Weiterleitung zur Anmeldung...", + "redirecting": "Weiterleitung zur Anmeldung..." + }, + "errors": { + "invalidToken": "Fehler mit Einladungslink", + "tokenUsed": "Dieser Einladungslink wurde bereits verwendet", + "tokenExpired": "Dieser Einladungslink ist abgelaufen", + "usernameExists": "Benutzername existiert bereits", + "emailExists": "E-Mail existiert bereits", + "createFailed": "Konto konnte nicht erstellt werden. Bitte versuchen Sie es erneut." + }, + "pageTitle": "Konto erstellen" } } \ No newline at end of file diff --git a/apps/web/messages/el-GR.json b/apps/web/messages/el-GR.json new file mode 100644 index 00000000..a5ba5f92 --- /dev/null +++ b/apps/web/messages/el-GR.json @@ -0,0 +1,2001 @@ +{ + "auth": { + "successfullyAuthenticated": "Επιτυχής έλεγχος ταυτότητας!", + "authenticationFailed": "Αποτυχία ελέγχου ταυτότητας", + "errors": { + "account_inactive": "Ο λογαριασμός είναι ανενεργός. Επικοινωνήστε με τον διαχειριστή.", + "registration_disabled": "Η εγγραφή μέσω SSO είναι απενεργοποιημένη.", + "token_expired": "Το διακριτικό έχει λήξει. Προσπαθήστε ξανά.", + "config_error": "Σφάλμα ρυθμίσεων. Επικοινωνήστε με την υποστήριξη.", + "auth_failed": "Αποτυχία ελέγχου ταυτότητας. Προσπαθήστε ξανά." + } + }, + "contextMenu": { + "newFolder": "Νέος φάκελος", + "uploadFile": "Μεταφόρτωση αρχείου" + }, + "authProviders": { + "title": "Πάροχοι ελέγχου ταυτότητας", + "description": "Ρυθμίστε εξωτερικούς παρόχους ελέγχου ταυτότητας για SSO", + "enabledCount": "{count} ενεργοποιημένοι", + "loadingProviders": "Φόρτωση παρόχων...", + "providersConfigured": "{count} πάροχοι ρυθμίστηκαν", + "enabledOfTotal": "{enabled} ενεργοποιημένοι από {total} παρόχους", + "hideDisabledProviders": "Απόκρυψη απενεργοποιημένων παρόχων", + "addProvider": "Προσθήκη παρόχου", + "addProviderTitle": "Προσθήκη παρόχου", + "editProvider": "Επεξεργασία παρόχου", + "deleteProvider": "Διαγραφή παρόχου", + "enabled": "Ενεργό", + "disabled": "Ανενεργό", + "officialProvider": "Επίσημος πάροχος", + "dragToReorder": "Σύρετε για αναδιάταξη", + "dragDisabledMessage": "Η μεταφορά/απόθεση είναι απενεργοποιημένη κατά το φιλτράρισμα. Εμφανίστε όλους τους παρόχους για αναδιάταξη.", + "dragEnabledMessage": "Σύρετε τους παρόχους για να τους αναδιατάξετε. Αυτή η σειρά θα εμφανιστεί στη σελίδα σύνδεσης.", + "noProvidersEnabled": "Δεν υπάρχουν ενεργοποιημένοι πάροχοι ελέγχου ταυτότητας", + "noProvidersConfigured": "Δεν έχουν ρυθμιστεί πάροχοι ελέγχου ταυτότητας", + "form": { + "providerName": "Όνομα παρόχου", + "providerNamePlaceholder": "π.χ., mycompany", + "displayName": "Εμφανιζόμενο όνομα", + "displayNamePlaceholder": "π.χ., My Company SSO", + "type": "Τύπος", + "typeOidc": "OIDC (OpenID Connect)", + "typeOauth2": "OAuth 2.0", + "icon": "Εικονίδιο", + "iconPlaceholder": "Επιλέξτε εικονίδιο", + "clientId": "Client ID", + "clientIdPlaceholder": "Το OAuth Client ID σας", + "clientSecret": "Client Secret", + "clientSecretPlaceholder": "Το OAuth Client Secret σας", + "oauthScopes": "Scopes OAuth", + "scopesPlaceholder": "Εισαγάγετε scopes (π.χ., openid, profile, email)", + "scopesHelpOidc": "Τα scopes προτείνονται αυτόματα βάσει του URL παρόχου. Συνήθη OIDC scopes: openid, profile, email, groups", + "scopesHelpOauth2": "Τα scopes προτείνονται αυτόματα βάσει του URL παρόχου. Τα συνήθη OAuth2 scopes εξαρτώνται από τον πάροχο", + "providerUrl": "URL παρόχου", + "providerUrlPlaceholder": "https://auth.example.com", + "providerUrlAutoPlaceholder": "https://your-provider.com (τα endpoints θα εντοπιστούν αυτόματα)", + "providerUrlManualPlaceholder": "https://your-provider.com", + "autoDiscoveryHelp": "Το σύστημα θα εντοπίσει αυτόματα τα endpoints authorization, token και userinfo", + "manualConfigurationHelp": "Βασικό URL του παρόχου σας (τα endpoints θα είναι σχετικά με αυτό)", + "authorizationEndpoint": "Endpoint εξουσιοδότησης", + "authorizationEndpointPlaceholder": "https://auth.example.com/auth", + "tokenEndpoint": "Endpoint token", + "tokenEndpointPlaceholder": "https://auth.example.com/token", + "userInfoEndpoint": "Endpoint user info", + "userInfoEndpointPlaceholder": "https://auth.example.com/userinfo", + "configurationMethod": "Μέθοδος ρύθμισης", + "autoDiscovery": "Αυτόματος εντοπισμός", + "autoDiscoveryDescription": "Αυτόματος εντοπισμός endpoints από το URL παρόχου", + "manualEndpoints": "Χειροκίνητα endpoints (Προτείνεται)", + "manualEndpointsDescription": "Χειροκίνητη ρύθμιση endpoints για authorization, token και user info", + "callbackUrl": "Callback URL", + "callbackUrlDescription": "Χρησιμοποιήστε αυτό το URL στη ρύθμιση του OAuth παρόχου σας", + "copyCallbackUrl": "Αντιγραφή Callback URL", + "callbackUrlCopied": "Το Callback URL αντιγράφηκε στο πρόχειρο!", + "adminEmailDomains": "Domains email διαχειριστή", + "adminEmailDomainsPlaceholder": "Εισαγάγετε domains (π.χ., admin.company.com)", + "adminEmailDomainsHelp": "Οι χρήστες με email από αυτά τα domains θα αποκτούν δικαιώματα διαχειριστή", + "autoRegister": "Αυτόματη εγγραφή νέων χρηστών", + "officialProviderUrlPlaceholder": "Αντικαταστήστε το placeholder με το URL του {displayName}", + "officialProviderHelp": "Αυτός είναι επίσημος πάροχος. Τα endpoints είναι προρυθμισμένα. Μπορείτε να επεξεργαστείτε μόνο αυτό το URL.", + "officialProviderIconHelp": "Μπορείτε να προσαρμόσετε το εικονίδιο για αυτόν τον επίσημο πάροχο." + }, + "buttons": { + "cancel": "Ακύρωση", + "save": "Αποθήκευση", + "saving": "Γίνεται αποθήκευση...", + "adding": "Προσθήκη...", + "updating": "Γίνεται ενημέρωση...", + "saveProvider": "Αποθήκευση παρόχου", + "delete": "Διαγραφή", + "deleting": "Γίνεται διαγραφή...", + "edit": "Επεξεργασία", + "enable": "Ενεργοποίηση", + "disable": "Απενεργοποίηση" + }, + "messages": { + "providerAdded": "Ο πάροχος προστέθηκε με επιτυχία", + "providerUpdated": "Ο πάροχος ενημερώθηκε με επιτυχία", + "providerDeleted": "Ο πάροχος διαγράφηκε με επιτυχία", + "providerOrderUpdated": "Η σειρά παρόχων ενημερώθηκε με επιτυχία", + "fillRequiredFields": "Συμπληρώστε όλα τα απαιτούμενα πεδία (όνομα, εμφανιζόμενο όνομα, Client ID, Client Secret)", + "provideUrlOrEndpoints": "Εισαγάγετε είτε URL παρόχου για αυτόματο εντοπισμό είτε και τα τρία προσαρμοσμένα endpoints", + "chooseDiscoveryOrManual": "Επιλέξτε είτε αυτόματο εντοπισμό (URL παρόχου) είτε χειροκίνητα endpoints, όχι και τα δύο", + "loadFailed": "Αποτυχία φόρτωσης παρόχων", + "addFailed": "Αποτυχία προσθήκης παρόχου", + "updateFailed": "Αποτυχία ενημέρωσης παρόχου", + "deleteFailed": "Αποτυχία διαγραφής παρόχου", + "orderUpdateFailed": "Αποτυχία ενημέρωσης σειράς παρόχων" + }, + "info": { + "title": "Πληροφορίες", + "officialProvidersRecommended": "Για καλύτερη λειτουργικότητα, προτιμήστε επίσημους παρόχους. Αν έχετε προβλήματα με προσαρμοσμένο πάροχο, ανοίξτε ένα θέμα στο", + "github": "GitHub", + "officialProvider": "Επίσημος πάροχος", + "officialProviderDescription": "Αυτός ο πάροχος είναι βελτιστοποιημένος από το Palmr. Μόνο τα διαπιστευτήρια και οι ρυθμίσεις μπορούν να τροποποιηθούν.", + "manualConfigTitle": "Χειροκίνητη ρύθμιση", + "manualConfigDescription": "Παρέχετε όλα τα endpoints χειροκίνητα. Βεβαιωθείτε ότι είναι σωστά για τον πάροχό σας." + }, + "deleteModal": { + "title": "Διαγραφή παρόχου ελέγχου ταυτότητας", + "confirmMessage": "Είστε βέβαιοι ότι θέλετε να διαγράψετε τον πάροχο \"{displayName}\"; Η ενέργεια δεν αναιρείται.", + "providerId": "ID παρόχου: {name}", + "cancel": "Ακύρωση", + "delete": "Διαγραφή παρόχου", + "deleting": "Γίνεται διαγραφή..." + } + }, + "bulkDownload": { + "title": "Bulk Download", + "zipNameLabel": "ZIP file name", + "zipNamePlaceholder": "Enter file name", + "description": "{count, plural, =1 {1 file will be compressed} other {# files will be compressed}}", + "download": "Download ZIP" + }, + "common": { + "loading": "Φόρτωση, παρακαλώ περιμένετε...", + "loadingSimple": "Φόρτωση...", + "cancel": "Ακύρωση", + "save": "Αποθήκευση", + "saving": "Γίνεται αποθήκευση...", + "update": "Ενημέρωση", + "updating": "Γίνεται ενημέρωση...", + "delete": "Διαγραφή", + "deleting": "Γίνεται διαγραφή...", + "close": "Κλείσιμο", + "download": "Λήψη", + "unexpectedError": "Παρουσιάστηκε απρόσμενο σφάλμα. Προσπαθήστε ξανά.", + "yes": "Ναι", + "no": "Όχι", + "dashboard": "Πίνακας ελέγχου", + "back": "Πίσω", + "click": "Κάντε κλικ για", + "creating": "Γίνεται δημιουργία...", + "create": "Δημιουργία", + "rename": "Μετονομασία", + "move": "Μετακίνηση", + "share": "Κοινή χρήση", + "search": "Αναζήτηση", + "copy": "Αντιγραφή", + "copied": "Αντιγράφηκε" + }, + "createShare": { + "title": "Δημιουργία κοινής χρήσης", + "nameLabel": "Όνομα κοινής χρήσης", + "namePlaceholder": "Εισαγάγετε ένα όνομα για την κοινή χρήση", + "descriptionLabel": "Περιγραφή", + "descriptionPlaceholder": "Εισαγάγετε περιγραφή (προαιρετικό)", + "expirationLabel": "Ημερομηνία λήξης", + "expirationPlaceholder": "MM/DD/YYYY HH:MM", + "maxViewsLabel": "Μέγιστες προβολές", + "maxViewsPlaceholder": "Αφήστε κενό για απεριόριστες", + "passwordProtection": "Προστασία με κωδικό", + "passwordLabel": "Κωδικός", + "passwordPlaceholder": "Εισαγάγετε κωδικό", + "create": "Δημιουργία κοινής χρήσης", + "success": "Η κοινή χρήση δημιουργήθηκε με επιτυχία", + "error": "Αποτυχία δημιουργίας κοινής χρήσης", + "errors": { + "nameRequired": "Απαιτείται όνομα κοινής χρήσης", + "selectItems": "Επιλέξτε τουλάχιστον ένα αρχείο ή φάκελο" + }, + "itemsSelected": "{count} στοιχεία επιλέχθηκαν", + "selectItemsPrompt": "Επιλέξτε αρχεία και φακέλους για κοινή χρήση", + "tabs": { + "shareDetails": "Λεπτομέρειες κοινής χρήσης", + "selectFiles": "Επιλογή αρχείων" + }, + "nextSelectFiles": "Επόμενο: Επιλογή αρχείων", + "searchLabel": "Αναζήτηση" + }, + "customization": { + "breadcrumb": "Προσαρμογή", + "colors": { + "title": "Χρώματα θέματος", + "description": "Επιλέξτε το προτιμώμενο βασικό χρώμα θέματος", + "presets": "Διαθέσιμα χρώματα", + "presetsDescription": "Επιλέξτε από τα διαθέσιμα θέματα χρωμάτων", + "reset": "Επαναφορά στις προεπιλογές" + }, + "fonts": { + "title": "Τυπογραφία", + "description": "Επιλέξτε την προτιμώμενη οικογένεια γραμματοσειρών", + "available": "Διαθέσιμες γραμματοσειρές", + "availableDescription": "Επιλέξτε από τις διαθέσιμες οικογένειες γραμματοσειρών", + "reset": "Επαναφορά στις προεπιλογές" + }, + "radius": { + "title": "Καμπυλότητα γωνιών", + "description": "Προσαρμόστε το πόσο στρογγυλεμένα είναι τα στοιχεία", + "available": "Επιλογές στρογγυλοποίησης", + "availableDescription": "Επιλέξτε πόσο στρογγυλεμένες θα είναι οι γωνίες", + "reset": "Επαναφορά στις προεπιλογές" + }, + "background": { + "title": "Χρώματα φόντου", + "description": "Προσαρμόστε τα χρώματα φόντου για φωτεινή και σκοτεινή λειτουργία", + "lightMode": "Φωτεινή λειτουργία", + "darkMode": "Σκοτεινή λειτουργία", + "availableDescription": "Επιλέξτε χρώματα φόντου για φωτεινά και σκοτεινά θέματα", + "reset": "Επαναφορά στις προεπιλογές" + }, + "theme": { + "title": "Λειτουργία θέματος", + "description": "Επιλέξτε μεταξύ φωτεινού, σκοτεινού ή του συστήματος", + "selectTheme": "Προτίμηση θέματος", + "availableDescription": "Επιλέξτε την προτιμώμενη λειτουργία θέματος", + "reset": "Επαναφορά στο σύστημα" + }, + "pageTitle": "Προσαρμογή" + }, + "dashboard": { + "loadError": "Αποτυχία φόρτωσης δεδομένων πίνακα ελέγχου", + "linkCopied": "Ο σύνδεσμος αντιγράφηκε στο πρόχειρο", + "pageTitle": "Πίνακας ελέγχου", + "breadcrumb": "Πίνακας ελέγχου", + "recentFiles": { + "title": "Πρόσφατα αρχεία", + "description": "Τα πιο πρόσφατα μεταφορτωμένα αρχεία σας" + } + }, + "deleteConfirmation": { + "filesToDelete": "Αρχεία προς διαγραφή", + "foldersToDelete": "Φάκελοι προς διαγραφή", + "itemsToDelete": "Στοιχεία προς διαγραφή", + "sharesToDelete": "Κοινές χρήσεις προς διαγραφή" + }, + "downloadQueue": { + "downloadQueued": "Η λήψη μπήκε στην ουρά: {fileName}", + "queuedDescription": "Η λήψη σας θα ξεκινήσει αυτόματα όταν υπάρξει διαθέσιμη θέση", + "queuePosition": "Η λήψη μπήκε στην ουρά στη θέση {position}: {fileName}", + "estimatedWait": "Εκτιμώμενος χρόνος αναμονής: {time}", + "queueFull": "Η ουρά λήψεων είναι πλήρης", + "queueFullDescription": "Δοκιμάστε ξανά σε λίγα λεπτά όταν υπάρξει χώρος στην ουρά", + "cancelSuccess": "Η λήψη ακυρώθηκε με επιτυχία", + "cancelError": "Αποτυχία ακύρωσης λήψης: {error}", + "status": { + "pending": "Γίνεται προετοιμασία...", + "queued": "Στην ουρά", + "downloading": "Γίνεται λήψη", + "completed": "Ολοκληρώθηκε", + "failed": "Απέτυχε" + }, + "waitTime": { + "seconds": "{seconds}δ", + "minutes": "{minutes}λ", + "hoursMinutes": "{hours}ω {minutes}λ" + }, + "indicator": { + "title": "Λήψεις", + "downloads": "Ουρά λήψεων", + "active": "Ενεργές", + "queued": "Στην ουρά", + "position": "Θέση {position}", + "estimatedWait": "Αναμονή: {time}", + "unknownFile": "Άγνωστο αρχείο", + "noDownloads": "Δεν υπάρχουν λήψεις σε εξέλιξη", + "refresh": "Ανανέωση ουράς" + } + }, + "emptyState": { + "noFiles": "Δεν έχουν μεταφορτωθεί ακόμη αρχεία", + "uploadFile": "Μεταφόρτωση αρχείου" + }, + "errors": { + "invalidCredentials": "Μη έγκυρο email ή κωδικός πρόσβασης", + "userNotFound": "Ο χρήστης δεν βρέθηκε", + "accountLocked": "Ο λογαριασμός είναι κλειδωμένος. Προσπαθήστε αργότερα", + "unexpectedError": "Παρουσιάστηκε απρόσμενο σφάλμα. Προσπαθήστε ξανά", + "Invalid verification code": "Μη έγκυρος κωδικός επαλήθευσης", + "Two-factor authentication is already enabled": "Ο έλεγχος ταυτότητας δύο παραγόντων είναι ήδη ενεργός", + "Two-factor authentication is not enabled": "Ο έλεγχος ταυτότητας δύο παραγόντων δεν είναι ενεργός", + "Invalid password": "Μη έγκυρος κωδικός πρόσβασης", + "Password verification required": "Απαιτείται επαλήθευση κωδικού πρόσβασης", + "Invalid two-factor authentication code": "Μη έγκυρος κωδικός ελέγχου ταυτότητας δύο παραγόντων", + "Two-factor authentication required": "Απαιτείται έλεγχος ταυτότητας δύο παραγόντων", + "noUserData": "Δεν υπάρχουν δεδομένα χρήστη" + }, + "fileActions": { + "editFile": "Επεξεργασία αρχείου", + "nameLabel": "Όνομα", + "namePlaceholder": "Εισαγάγετε νέο όνομα", + "extension": "Επέκταση", + "descriptionLabel": "Περιγραφή", + "descriptionPlaceholder": "Εισαγάγετε περιγραφή αρχείου", + "addDescriptionPlaceholder": "Προσθήκη περιγραφής...", + "deleteFile": "Διαγραφή αρχείου", + "deleteConfirmation": "Είστε βέβαιοι ότι θέλετε να διαγράψετε αυτό το αρχείο;", + "deleteWarning": "Αυτή η ενέργεια δεν μπορεί να αναιρεθεί." + }, + "fileManager": { + "downloadError": "Αποτυχία λήψης αρχείου", + "updateSuccess": "Το αρχείο ενημερώθηκε με επιτυχία", + "updateError": "Αποτυχία ενημέρωσης αρχείου", + "deleteSuccess": "Το αρχείο διαγράφηκε με επιτυχία", + "deleteError": "Αποτυχία διαγραφής αρχείου" + }, + "filePreview": { + "title": "Προεπισκόπηση αρχείου", + "description": "Προεπισκόπηση και λήψη αρχείου", + "loading": "Φόρτωση...", + "notAvailable": "Η προεπισκόπηση δεν είναι διαθέσιμη για αυτόν τον τύπο αρχείου", + "downloadToView": "Χρησιμοποιήστε το κουμπί λήψης για να δείτε αυτό το αρχείο", + "loadError": "Σφάλμα φόρτωσης προεπισκόπησης αρχείου", + "downloadError": "Σφάλμα λήψης αρχείου", + "audioNotSupported": "Ο περιηγητής σας δεν υποστηρίζει αναπαραγωγή ήχου", + "videoNotSupported": "Ο περιηγητής σας δεν υποστηρίζει αναπαραγωγή βίντεο", + "pdfPreviewNotAvailable": "Η προεπισκόπηση PDF δεν είναι διαθέσιμη. Δοκιμάστε εναλλακτική προβολή ή κάντε λήψη", + "tryAlternativeView": "Δοκιμάστε εναλλακτική προβολή", + "loadingAlternative": "Φόρτωση εναλλακτικής προβολής...", + "loadingAudio": "Φόρτωση ήχου..." + }, + "fileSelector": { + "availableFiles": "Διαθέσιμα αρχεία ({count})", + "shareFiles": "Αρχεία κοινής χρήσης ({count})", + "shareFilesDescription": "Αρχεία που βρίσκονται σε αυτή την κοινή χρήση", + "availableFilesDescription": "Επιλέξτε αρχεία για να τα προσθέσετε σε αυτή την κοινή χρήση", + "searchPlaceholder": "Αναζήτηση αρχείων...", + "searchSelectedFiles": "Αναζήτηση επιλεγμένων αρχείων...", + "noMatchingFiles": "Δεν βρέθηκαν αντίστοιχα αρχεία", + "noAvailableFiles": "Δεν υπάρχουν διαθέσιμα αρχεία", + "noFilesInShare": "Δεν υπάρχουν αρχεία σε αυτή την κοινή χρήση", + "noFilesFound": "Δεν βρέθηκαν αρχεία", + "noFilesFoundWith": "Δεν βρέθηκαν αρχεία που να ταιριάζουν με \"{query}\"", + "addFilesFromList": "Προσθέστε αρχεία από την παρακάτω λίστα", + "tryDifferentSearch": "Δοκιμάστε διαφορετικούς όρους αναζήτησης", + "allFilesInShare": "Όλα τα αρχεία βρίσκονται ήδη σε αυτή την κοινή χρήση", + "uploadNewFiles": "Μεταφορτώστε νέα αρχεία για να τα προσθέσετε", + "fileCount": "{count, plural, =1 {αρχείο} other {αρχεία}}", + "filesSelected": "{count, plural, =0 {Δεν επιλέχθηκαν αρχεία} =1 {1 αρχείο επιλέχθηκε} other {# αρχεία επιλέχθηκαν}}", + "itemsSelected": "{count} στοιχεία επιλέχθηκαν", + "editFile": "Επεξεργασία αρχείου", + "editFolder": "Επεξεργασία φακέλου", + "previewFile": "Προεπισκόπηση αρχείου", + "addToShare": "Προσθήκη στην κοινή χρήση", + "removeFromShare": "Αφαίρεση από την κοινή χρήση", + "saveChanges": "Αποθήκευση αλλαγών" + }, + "files": { + "title": "Όλα τα αρχεία", + "uploadFile": "Μεταφόρτωση αρχείου", + "loadError": "Αποτυχία φόρτωσης αρχείων", + "pageTitle": "Τα αρχεία μου", + "breadcrumb": "Τα αρχεία μου", + "downloadStart": "Η λήψη ξεκίνησε", + "downloadError": "Αποτυχία λήψης αρχείου", + "updateSuccess": "Το αρχείο ενημερώθηκε με επιτυχία", + "updateError": "Αποτυχία ενημέρωσης αρχείου", + "deleteSuccess": "Το αρχείο διαγράφηκε με επιτυχία", + "deleteError": "Αποτυχία διαγραφής αρχείου", + "bulkDownloadSuccess": "Η λήψη αρχείων ξεκίνησε με επιτυχία", + "bulkDownloadError": "Σφάλμα δημιουργίας αρχείου ZIP", + "bulkDownloadFileError": "Σφάλμα λήψης αρχείου {fileName}", + "bulkDeleteSuccess": "{count, plural, =1 {1 στοιχείο διαγράφηκε με επιτυχία} other {# στοιχεία διαγράφηκαν με επιτυχία}}", + "bulkDeleteError": "Σφάλμα διαγραφής επιλεγμένων στοιχείων", + "bulkDeleteTitle": "Διαγραφή επιλεγμένων στοιχείων", + "bulkDeleteConfirmation": "Είστε βέβαιοι ότι θέλετε να διαγράψετε {count, plural, =1 {1 στοιχείο} other {# στοιχεία}}; Αυτή η ενέργεια δεν μπορεί να αναιρεθεί.", + "totalFiles": "{count, plural, =0 {Κανένα αρχείο} =1 {1 αρχείο} other {# αρχεία}}", + "openFolder": "Άνοιγμα φακέλου", + "errors": { + "moveItemsFailed": "Αποτυχία μετακίνησης στοιχείων. Προσπαθήστε ξανά.", + "cannotMoveHere": "Δεν είναι δυνατή η μετακίνηση στοιχείων σε αυτήν την τοποθεσία" + }, + "empty": { + "title": "Δεν υπάρχουν ακόμη αρχεία ή φάκελοι", + "description": "Μεταφορτώστε το πρώτο σας αρχείο ή δημιουργήστε έναν φάκελο για να ξεκινήσετε" + }, + "files": "αρχεία", + "folders": "φάκελοι", + "actions": { + "open": "Άνοιγμα", + "rename": "Μετονομασία", + "delete": "Διαγραφή" + }, + "viewMode": { + "table": "Πίνακας", + "grid": "Πλέγμα" + } + }, + "filesTable": { + "ariaLabel": "Πίνακας αρχείων", + "selectAll": "Επιλογή όλων", + "selectFile": "Επιλογή αρχείου {fileName}", + "columns": { + "name": "ΟΝΟΜΑ", + "description": "ΠΕΡΙΓΡΑΦΗ", + "size": "ΜΕΓΕΘΟΣ", + "createdAt": "ΗΜ/ΝΙΑ ΔΗΜΙΟΥΡΓΙΑΣ", + "updatedAt": "ΗΜ/ΝΙΑ ΕΝΗΜΕΡΩΣΗΣ", + "actions": "ΕΝΕΡΓΕΙΕΣ" + }, + "actions": { + "menu": "Μενού ενεργειών αρχείου", + "preview": "Προεπισκόπηση", + "edit": "Επεξεργασία", + "share": "Κοινή χρήση", + "download": "Λήψη", + "delete": "Διαγραφή" + }, + "bulkActions": { + "selected": "{count, plural, =1 {1 αρχείο επιλέχθηκε} other {# αρχεία επιλέχθηκαν}}", + "actions": "Ενέργειες", + "download": "Λήψη επιλεγμένων", + "share": "Κοινή χρήση επιλεγμένων", + "delete": "Διαγραφή επιλεγμένων" + } + }, + "folderActions": { + "editFolder": "Επεξεργασία φακέλου", + "folderName": "Όνομα φακέλου", + "folderNamePlaceholder": "Εισαγάγετε όνομα φακέλου", + "folderDescription": "Περιγραφή", + "folderDescriptionPlaceholder": "Εισαγάγετε περιγραφή φακέλου (προαιρετικό)", + "createFolder": "Δημιουργία φακέλου", + "renameFolder": "Μετονομασία φακέλου", + "moveFolder": "Μετακίνηση φακέλου", + "shareFolder": "Κοινή χρήση φακέλου", + "deleteFolder": "Διαγραφή φακέλου", + "moveTo": "Μετακίνηση σε", + "selectDestination": "Επιλέξτε φάκελο προορισμού", + "rootFolder": "Ριζικός", + "folderCreated": "Ο φάκελος δημιουργήθηκε με επιτυχία", + "folderRenamed": "Ο φάκελος μετονομάστηκε με επιτυχία", + "folderMoved": "Ο φάκελος μετακινήθηκε με επιτυχία", + "folderDeleted": "Ο φάκελος διαγράφηκε με επιτυχία", + "folderShared": "Ο φάκελος κοινοποιήθηκε με επιτυχία", + "createFolderError": "Σφάλμα δημιουργίας φακέλου", + "renameFolderError": "Σφάλμα μετονομασίας φακέλου", + "moveFolderError": "Σφάλμα μετακίνησης φακέλου", + "deleteFolderError": "Σφάλμα διαγραφής φακέλου", + "shareFolderError": "Σφάλμα κοινής χρήσης φακέλου", + "deleteConfirmation": "Είστε βέβαιοι ότι θέλετε να διαγράψετε αυτόν τον φάκελο;", + "deleteWarning": "Αυτή η ενέργεια δεν μπορεί να αναιρεθεί." + }, + "footer": { + "poweredBy": "Με την υποστήριξη", + "kyanHomepage": "Αρχική σελίδα Kyantech" + }, + "forgotPassword": { + "emailLabel": "Διεύθυνση email", + "emailPlaceholder": "Εισαγάγετε το email σας", + "sending": "Αποστολή...", + "submit": "Αποστολή οδηγιών επαναφοράς", + "backToLogin": "Επιστροφή στη σύνδεση", + "title": "Ξεχάσατε τον κωδικό;", + "description": "Εισαγάγετε το email σας και θα στείλουμε οδηγίες για επαναφορά κωδικού", + "resetInstructions": "Οι οδηγίες επαναφοράς στάλθηκαν στο email σας", + "pageTitle": "Επαναφορά κωδικού", + "passwordAuthDisabled": "Ο έλεγχος ταυτότητας με κωδικό είναι απενεργοποιημένος. Επικοινωνήστε με τον διαχειριστή σας ή χρησιμοποιήστε εξωτερικό πάροχο." + }, + "generateShareLink": { + "generateTitle": "Δημιουργία συνδέσμου κοινής χρήσης", + "updateTitle": "Ενημέρωση συνδέσμου κοινής χρήσης", + "generateDescription": "Δημιουργήστε προσαρμοσμένο σύνδεσμο για αυτή την κοινή χρήση. Μπορείτε να προσαρμόσετε το URL ώστε να είναι πιο εύκολο να θυμάται.", + "updateDescription": "Ενημερώστε τον προσαρμοσμένο σύνδεσμο για αυτή την κοινή χρήση. Μπορείτε να προσαρμόσετε το URL ώστε να είναι πιο εύκολο να θυμάται.", + "aliasPlaceholder": "Προσαρμοσμένο αναγνωριστικό συνδέσμου", + "linkReady": "Ο σύνδεσμος κοινής χρήσης είναι έτοιμος. Μπορείτε να τον αντιγράψετε τώρα.", + "readyDescription": "Ο σύνδεσμος κοινής χρήσης είναι έτοιμος. Μπορείτε να σαρώσετε τον κωδικό QR, να τον κατεβάσετε για αργότερα ή να αντιγράψετε τον σύνδεσμο παρακάτω.", + "generateButton": "Δημιουργία συνδέσμου", + "updateButton": "Ενημέρωση συνδέσμου", + "copyButton": "Αντιγραφή συνδέσμου", + "success": "Ο σύνδεσμος δημιουργήθηκε με επιτυχία", + "error": "Αποτυχία δημιουργίας συνδέσμου", + "copied": "Ο σύνδεσμος αντιγράφηκε στο πρόχειρο", + "tabs": { + "link": "Σύνδεσμος", + "qrcode": "Κωδικός QR" + } + }, + "home": { + "description": "Η εναλλακτική λύση ανοιχτού κώδικα στο WeTransfer. Μοιραστείτε αρχεία με ασφάλεια, χωρίς παρακολούθηση ή περιορισμούς.", + "documentation": "Τεκμηρίωση", + "starOnGithub": "Αστέρι στο GitHub", + "privacyMessage": "Φτιαγμένο με γνώμονα το απόρρητο. Τα αρχεία σας είναι προσβάσιμα μόνο σε όσους έχουν τον σύνδεσμο κοινής χρήσης πριν τη μεταφόρτωση. Για πάντα δωρεάν και ανοικτού κώδικα.", + "header": { + "fileSharing": "Κοινή χρήση αρχείων", + "tagline": "απλή και δωρεάν" + }, + "pageTitle": "Αρχική" + }, + "iconPicker": { + "title": "Επιλογή εικονιδίου", + "placeholder": "Επιλέξτε ένα εικονίδιο", + "searchPlaceholder": "Αναζήτηση εικονιδίων...", + "loadingMore": "Φόρτωση περισσότερων εικονιδίων...", + "allIconsLoaded": "Φορτώθηκαν όλα τα {count} εικονίδια", + "noIconsFound": "Δεν βρέθηκαν εικονίδια για \"{search}\"", + "tabs": { + "all": "Όλα τα εικονίδια", + "popular": "Δημοφιλή", + "auth": "Πάροχοι ταυτότητας" + }, + "stats": "{iconCount} εικονίδια από {libraryCount} βιβλιοθήκες", + "categoryBadge": "{category} ({count} εικονίδια)" + }, + "imageEdit": { + "title": "Επεξεργασία εικόνας", + "rotate": "Περιστροφή", + "zoom": "Ζουμ", + "cropInstructions": "Σύρετε για επανατοποθέτηση, αλλάξτε μέγεθος από τις γωνίες για προσαρμογή περικοπής" + }, + "login": { + "welcome": "Καλώς ήρθατε στο", + "signInToContinue": "Συνδεθείτε για να συνεχίσετε", + "emailOrUsernameLabel": "Email ή όνομα χρήστη", + "emailOrUsernamePlaceholder": "Εισαγάγετε το email ή το όνομα χρήστη σας", + "emailLabel": "Διεύθυνση email", + "emailPlaceholder": "Εισαγάγετε το email σας", + "passwordLabel": "Κωδικός πρόσβασης", + "passwordPlaceholder": "Εισαγάγετε τον κωδικό πρόσβασής σας", + "signIn": "Σύνδεση", + "signingIn": "Γίνεται σύνδεση...", + "forgotPassword": "Ξεχάσατε τον κωδικό;", + "pageTitle": "Σύνδεση", + "or": "ή", + "continueWithSSO": "Συνέχεια με SSO", + "processing": "Επεξεργασία ελέγχου ταυτότητας..." + }, + "logo": { + "labels": { + "appLogo": "Λογότυπο εφαρμογής" + }, + "buttons": { + "upload": "Μεταφόρτωση λογοτύπου", + "remove": "Αφαίρεση λογοτύπου" + }, + "messages": { + "uploadSuccess": "Το λογότυπο μεταφορτώθηκε με επιτυχία", + "removeSuccess": "Το λογότυπο αφαιρέθηκε με επιτυχία" + }, + "errors": { + "uploadFailed": "Αποτυχία μεταφόρτωσης λογοτύπου", + "removeFailed": "Αποτυχία αφαίρεσης λογοτύπου" + } + }, + "moveItems": { + "itemsToMove": "Στοιχεία για μετακίνηση:", + "movingTo": "Μετακίνηση σε:", + "title": "Μετακίνηση {count, plural, =1 {στοιχείου} other {στοιχείων}}", + "description": "Μετακινήστε {count, plural, =1 {1 στοιχείο} other {# στοιχεία}} σε νέα τοποθεσία", + "success": "Μετακινήθηκαν επιτυχώς {count} {count, plural, =1 {στοιχείο} other {στοιχεία}}", + "errors": { + "moveFailed": "Αποτυχία μετακίνησης στοιχείων" + } + }, + "navbar": { + "logoAlt": "Λογότυπο εφαρμογής", + "profileMenu": "Μενού προφίλ", + "profile": "Προφίλ", + "customization": "Προσαρμογή", + "settings": "Ρυθμίσεις", + "usersManagement": "Διαχείριση χρηστών", + "logout": "Αποσύνδεση" + }, + "navigation": { + "dashboard": "Πίνακας ελέγχου" + }, + "notifications": { + "permissionGranted": "Οι ειδοποιήσεις λήψεων ενεργοποιήθηκαν", + "permissionDenied": "Οι ειδοποιήσεις λήψεων απενεργοποιήθηκαν", + "downloadComplete": { + "title": "Η λήψη ολοκληρώθηκε", + "body": "Το {fileName} ολοκλήρωσε τη λήψη" + }, + "downloadFailed": { + "title": "Αποτυχία λήψης", + "body": "Αποτυχία λήψης {fileName}: {error}", + "unknownError": "Άγνωστο σφάλμα" + }, + "queueProcessing": { + "title": "Έναρξη λήψης", + "body": "Το {fileName} γίνεται λήψη τώρα{position}", + "position": " (ήταν #{position} στην ουρά)" + } + }, + "profile": { + "password": { + "title": "Αλλαγή κωδικού πρόσβασης", + "newPassword": "Νέος κωδικός πρόσβασης", + "confirmPassword": "Επιβεβαίωση νέου κωδικού", + "updateButton": "Ενημέρωση κωδικού" + }, + "form": { + "title": "Πληροφορίες προφίλ", + "firstName": "Όνομα", + "lastName": "Επώνυμο", + "username": "Όνομα χρήστη", + "email": "Email", + "updateButton": "Ενημέρωση προφίλ" + }, + "header": { + "title": "Προφίλ", + "subtitle": "Διαχειριστείτε τα προσωπικά σας στοιχεία και τον κωδικό πρόσβασης" + }, + "picture": { + "title": "Εικόνα προφίλ", + "description": "Κάντε κλικ στο εικονίδιο κάμερας για να αλλάξετε την εικόνα προφίλ", + "uploadPhoto": "Μεταφόρτωση φωτογραφίας", + "removePhoto": "Αφαίρεση φωτογραφίας" + }, + "errors": { + "loadFailed": "Αποτυχία φόρτωσης δεδομένων χρήστη", + "updateFailed": "Αποτυχία ενημέρωσης προφίλ", + "passwordFailed": "Αποτυχία ενημέρωσης κωδικού", + "imageFailed": "Αποτυχία ενημέρωσης εικόνας προφίλ", + "imageRemoveFailed": "Αποτυχία αφαίρεσης εικόνας προφίλ" + }, + "messages": { + "noChanges": "Καμία αλλαγή για αποθήκευση", + "updateSuccess": "Το προφίλ ενημερώθηκε με επιτυχία", + "fillPasswords": "Συμπληρώστε και τα δύο πεδία κωδικού", + "passwordSuccess": "Ο κωδικός ενημερώθηκε με επιτυχία", + "imageSuccess": "Η εικόνα προφίλ ενημερώθηκε με επιτυχία", + "imageRemoved": "Η εικόνα προφίλ αφαιρέθηκε με επιτυχία" + }, + "pageTitle": "Προφίλ" + }, + "qrCodeModal": { + "title": "Κοινοποίηση κωδικού QR", + "description": "Σαρώστε αυτόν τον κωδικό QR για πρόσβαση στον σύνδεσμο.", + "download": "Λήψη κωδικού QR" + }, + "quickAccess": { + "files": { + "title": "Τα αρχεία μου", + "description": "Πρόσβαση και διαχείριση των μεταφορτωμένων αρχείων σας" + }, + "shares": { + "title": "Οι κοινές χρήσεις μου", + "description": "Προβολή και διαχείριση των κοινόχρηστων αρχείων σας" + }, + "reverseShares": { + "title": "Λήψη αρχείων", + "description": "Δημιουργήστε συνδέσμους για να σας στέλνουν αρχεία άλλοι" + } + }, + "recentFiles": { + "title": "Πρόσφατες μεταφορτώσεις", + "viewAll": "Προβολή όλων", + "upload": "Μεταφόρτωση", + "uploadFile": "Μεταφόρτωση αρχείου", + "noFiles": "Δεν έχουν μεταφορτωθεί ακόμη αρχεία" + }, + "recentShares": { + "title": "Πρόσφατες κοινές χρήσεις", + "viewAll": "Προβολή όλων", + "createShare": "Δημιουργία κοινής χρήσης", + "noShares": "Δεν έχουν δημιουργηθεί ακόμη κοινές χρήσεις", + "createFirst": "Δημιουργήστε την πρώτη σας κοινή χρήση" + }, + "recipientSelector": { + "emailPlaceholder": "Εισαγάγετε email παραλήπτη", + "add": "Προσθήκη", + "recipients": "Παραλήπτες ({count})", + "notifyAll": "Ειδοποίηση όλων", + "noRecipients": "Δεν έχουν προστεθεί ακόμη παραλήπτες", + "addSuccess": "Ο παραλήπτης προστέθηκε με επιτυχία", + "addError": "Αποτυχία προσθήκης παραλήπτη", + "removeSuccess": "Ο παραλήπτης αφαιρέθηκε με επιτυχία", + "removeError": "Αποτυχία αφαίρεσης παραλήπτη", + "sendingNotifications": "Αποστολή ειδοποιήσεων...", + "notifySuccess": "Οι παραλήπτες ειδοποιήθηκαν με επιτυχία", + "notifyError": "Αποτυχία ειδοποίησης παραληπτών", + "selectAll": "Επιλογή όλων", + "selectedCount": "{count} επιλεγμένα", + "selectRecipient": "Επιλογή {email}", + "notifySelected": "Ειδοποίηση επιλεγμένων", + "removeSelected": "Αφαίρεση επιλεγμένων", + "notifySingle": "Ειδοποίηση αυτού του παραλήπτη", + "removeSingle": "Αφαίρεση αυτού του παραλήπτη", + "bulkRemoveSuccess": "{count} παραλήπτες αφαιρέθηκαν με επιτυχία", + "bulkRemoveError": "Αποτυχία αφαίρεσης επιλεγμένων παραληπτών", + "bulkNotifySuccess": "Εστάλησαν ειδοποιήσεις σε {count} παραλήπτες", + "bulkNotifyError": "Αποτυχία ειδοποίησης επιλεγμένων παραληπτών", + "singleNotifySuccess": "Εστάλη ειδοποίηση στο {email}", + "singleNotifyError": "Αποτυχία ειδοποίησης παραλήπτη", + "modalDescription": "Προσθέστε και διαχειριστείτε παραλήπτες για αυτή την κοινή χρήση. Μπορείτε να ειδοποιείτε όλους ή συγκεκριμένους παραλήπτες όταν έχει ρυθμιστεί SMTP.", + "addRecipient": "Προσθήκη παραλήπτη", + "invalidEmail": "Παρακαλώ εισαγάγετε έγκυρη διεύθυνση email", + "duplicateEmail": "Αυτός ο παραλήπτης έχει ήδη προστεθεί", + "noRecipientsDescription": "Προσθέστε παραλήπτες για να κοινοποιήσετε αυτό το περιεχόμενο μέσω email" + }, + "register": { + "validation": { + "firstNameRequired": "Απαιτείται όνομα", + "lastNameRequired": "Απαιτείται επώνυμο", + "usernameMinLength": "Το όνομα χρήστη πρέπει να έχει τουλάχιστον 3 χαρακτήρες", + "invalidEmail": "Μη έγκυρο email", + "passwordMinLength": "Ο κωδικός πρέπει να έχει τουλάχιστον 8 χαρακτήρες", + "success": "Ο διαχειριστής δημιουργήθηκε με επιτυχία!", + "error": "Σφάλμα δημιουργίας διαχειριστή" + }, + "labels": { + "firstName": "Όνομα", + "lastName": "Επώνυμο", + "username": "Όνομα χρήστη", + "email": "Email", + "password": "Κωδικός πρόσβασης" + }, + "buttons": { + "creating": "Δημιουργία...", + "createAdmin": "Δημιουργία λογαριασμού διαχειριστή" + } + }, + "resetPassword": { + "pageTitle": "Επαναφορά κωδικού", + "header": { + "title": "Επαναφορά κωδικού", + "description": "Εισαγάγετε τον νέο σας κωδικό παρακάτω" + }, + "form": { + "newPassword": "Νέος κωδικός", + "newPasswordPlaceholder": "Εισαγάγετε τον νέο σας κωδικό", + "confirmPassword": "Επιβεβαίωση νέου κωδικού", + "confirmPasswordPlaceholder": "Επιβεβαιώστε τον νέο σας κωδικό", + "resetting": "Γίνεται επαναφορά κωδικού...", + "submit": "Επαναφορά κωδικού", + "backToLogin": "Επιστροφή στη σύνδεση" + }, + "messages": { + "success": "Ο κωδικός επαναφέρθηκε με επιτυχία" + }, + "errors": { + "serverError": "Αποτυχία επαναφοράς κωδικού. Προσπαθήστε ξανά.", + "invalidToken": "Μη έγκυρο ή ελλιπές διακριτικό επαναφοράς" + } + }, + "reverseShares": { + "pageTitle": "Λήψη αρχείων", + "search": { + "title": "Διαχείριση συνδέσμων λήψης", + "createButton": "Δημιουργία συνδέσμου", + "placeholder": "Αναζήτηση συνδέσμων λήψης...", + "results": "Βρέθηκαν {filtered} από {total} σύνδεσμοι λήψης" + }, + "labels": { + "files": "αρχεία", + "size": "μέγεθος", + "status": "κατάσταση", + "access": "πρόσβαση", + "description": "Περιγραφή", + "pageLayout": "Διάταξη σελίδας", + "security": "Ασφάλεια & κατάσταση", + "limits": "Όρια", + "maxFiles": "Μέγιστος αριθμός αρχείων", + "maxFileSize": "Μέγιστο μέγεθος", + "allowedTypes": "Επιτρεπόμενοι τύποι", + "filesReceived": "Ελήφθησαν αρχεία", + "fileLimit": "Όριο αρχείων", + "noLimit": "Χωρίς όριο", + "noLinkCreated": "Δεν δημιουργήθηκε σύνδεσμος", + "publicAccess": "Δημόσια πρόσβαση", + "protectedByPassword": "Προστασία με κωδικό", + "configureProtection": "Κάντε κλικ για ρύθμιση προστασίας", + "enterPassword": "Εισαγάγετε κωδικό", + "thisLinkProtected": "Ο σύνδεσμος θα προστατεύεται με κωδικό", + "thisLinkPublic": "Ο σύνδεσμος θα είναι δημόσιος", + "configureExpiration": "Ρύθμιση λήξης", + "configureLimits": "Ρύθμιση ορίων αρχείων", + "protectWithPassword": "Προστασία με κωδικό", + "layoutOptions": { + "default": "Προεπιλογή", + "wetransfer": "WeTransfer" + }, + "noFilesLimit": "Χωρίς όριο αρχείων", + "noSizeLimit": "Χωρίς όριο μεγέθους", + "allFileTypes": "Όλοι οι τύποι αρχείων", + "fileTypesHelp": "Εισαγάγετε επεκτάσεις χωρίς τελείες, χωρισμένες με κενό, κόμμα, παύλα ή κάθετο", + "fieldRequirements": "Απαιτήσεις πεδίου", + "nameFieldRequired": "Πεδίο ονόματος", + "emailFieldRequired": "Πεδίο email", + "fieldOptions": { + "hidden": "Κρυφό", + "optional": "Προαιρετικό", + "required": "Υποχρεωτικό" + } + }, + "card": { + "untitled": "Σύνδεσμος χωρίς τίτλο", + "noDescription": "Χωρίς περιγραφή", + "addDescriptionPlaceholder": "Προσθήκη περιγραφής...", + "files": "αρχεία", + "progress": "Πρόοδος", + "created": "Δημιουργήθηκε", + "expired": "Έληξε", + "expires": "Λήγει", + "viewDetails": "Προβολή λεπτομερειών", + "viewQrCode": "Προβολή κωδικού QR", + "copyLink": "Αντιγραφή συνδέσμου", + "openInNewTab": "Άνοιγμα σε νέα καρτέλα", + "editLink": "Επεξεργασία συνδέσμου", + "createLink": "Δημιουργία συνδέσμου", + "delete": "Διαγραφή", + "copyLinkTitle": "Αντιγραφή συνδέσμου", + "createLinkCTA": "Δημιουργία συνδέσμου λήψης" + }, + "status": { + "active": "Ενεργό", + "inactive": "Ανενεργό", + "expired": "Έληξε", + "protected": "Προστατευμένο", + "public": "Δημόσιο" + }, + "actions": { + "copyLink": "Αντιγραφή συνδέσμου", + "editAlias": "Επεξεργασία ψευδωνύμου", + "createAlias": "Δημιουργία ψευδωνύμου", + "viewDetails": "Προβολή λεπτομερειών", + "edit": "Επεξεργασία", + "delete": "Διαγραφή", + "viewFiles": "Εισερχόμενα αρχεία", + "viewQrCode": "Προβολή κωδικού QR" + }, + "empty": { + "title": "Δεν δημιουργήθηκαν σύνδεσμοι λήψης", + "description": "Δημιουργήστε προσαρμοσμένους συνδέσμους για να σας στέλνουν αρχεία με ασφάλεια και οργάνωση.", + "createButton": "Δημιουργία πρώτου συνδέσμου" + }, + "modals": { + "create": { + "title": "Δημιουργία συνδέσμου λήψης", + "description": "Ρυθμίστε έναν προσαρμοσμένο σύνδεσμο για να λαμβάνετε αρχεία από άλλους" + }, + "edit": { + "title": "Επεξεργασία συνδέσμου λήψης", + "description": "Ενημερώστε τις ρυθμίσεις για αυτόν τον σύνδεσμο λήψης", + "updating": "Γίνεται ενημέρωση...", + "saveChanges": "Αποθήκευση αλλαγών" + }, + "details": { + "title": "Λεπτομέρειες συνδέσμου", + "description": "Προβολή και επεξεργασία πληροφοριών συνδέσμου λήψης", + "pageLayout": "Διάταξη σελίδας", + "linkSection": "Σύνδεσμος λήψης", + "noLinkCreated": "Δεν δημιουργήθηκε σύνδεσμος", + "limits": "Όρια", + "maxFiles": "Μέγιστος αριθμός αρχείων", + "maxFileSize": "Μέγιστο μέγεθος", + "allowedTypes": "Επιτρεπόμενοι τύποι", + "noLimit": "Χωρίς όριο", + "security": "Ασφάλεια", + "status": "Κατάσταση", + "password": "Κωδικός", + "files": "Εισερχόμενα αρχεία", + "noFiles": "Δεν έχουν ληφθεί ακόμη αρχεία", + "copyLink": "Αντιγραφή συνδέσμου", + "openLink": "Άνοιγμα συνδέσμου", + "editAlias": "Επεξεργασία ψευδωνύμου", + "createAlias": "Δημιουργία ψευδωνύμου", + "editPassword": "Επεξεργασία προστασίας με κωδικό", + "basicInfo": "Βασικές πληροφορίες", + "securityAndStatus": "Ασφάλεια & κατάσταση", + "protection": "Προστασία", + "protectedByPassword": "Προστατευμένο με κωδικό", + "publicAccess": "Δημόσια πρόσβαση", + "active": "Ενεργό", + "inactive": "Ανενεργό", + "deactivate": "Απενεργοποίηση", + "activate": "Ενεργοποίηση", + "expiration": "Λήξη", + "dates": "Ημερομηνίες", + "createdAt": "Δημιουργήθηκε", + "updatedAt": "Ενημερώθηκε", + "allTypes": "Όλοι οι τύποι", + "placeholderTypes": ".pdf,.jpg,.png (χωρισμένα με κόμμα)", + "downloadSuccess": "Η λήψη ξεκίνησε", + "downloadError": "Σφάλμα λήψης αρχείου", + "editSuccess": "Το αρχείο ενημερώθηκε με επιτυχία", + "editError": "Σφάλμα ενημέρωσης αρχείου", + "previewNotAvailable": "Η προεπισκόπηση δεν είναι διαθέσιμη", + "notAvailable": "Μ/Δ", + "invalidDate": "Μη έγκυρη ημερομηνία" + }, + "alias": { + "editTitle": "Επεξεργασία ψευδωνύμου", + "createTitle": "Δημιουργία ψευδωνύμου", + "editDescription": "Ενημερώστε το ψευδώνυμο για αυτόν τον σύνδεσμο", + "createDescription": "Δημιουργήστε προσαρμοσμένο ψευδώνυμο για αυτόν τον σύνδεσμο", + "aliasLabel": "Ψευδώνυμο συνδέσμου", + "aliasPlaceholder": "my-custom-link", + "preview": "Προεπισκόπηση:", + "currentLink": "Τρέχων σύνδεσμος:", + "copyCurrentLink": "Αντιγραφή τρέχοντος συνδέσμου", + "randomTooltip": "Δημιουργία τυχαίου ψευδωνύμου", + "cancel": "Ακύρωση", + "creating": "Δημιουργία...", + "updating": "Ενημέρωση...", + "create": "Δημιουργία ψευδωνύμου", + "update": "Ενημέρωση ψευδωνύμου", + "validation": { + "required": "Απαιτείται ψευδώνυμο", + "minLength": "Το ψευδώνυμο πρέπει να έχει τουλάχιστον 3 χαρακτήρες", + "maxLength": "Το ψευδώνυμο πρέπει να έχει το πολύ 50 χαρακτήρες", + "pattern": "Το ψευδώνυμο πρέπει να περιέχει μόνο γράμματα, αριθμούς, παύλες και κάτω παύλες" + }, + "help": "3-50 χαρακτήρες. Τα κενά θα μετατραπούν αυτόματα σε παύλες." + }, + "password": { + "title": "Επεξεργασία προστασίας με κωδικό", + "description": "Ρυθμίστε προστασία με κωδικό για αυτόν τον σύνδεσμο", + "hasPassword": "Προστατευμένο με κωδικό", + "password": "Κωδικός", + "cancel": "Ακύρωση", + "save": "Αποθήκευση", + "saving": "Γίνεται αποθήκευση..." + }, + "receivedFiles": { + "title": "Εισερχόμενα αρχεία", + "description": "Προβολή και διαχείριση αρχείων που στάλθηκαν σε αυτόν τον σύνδεσμο", + "noFiles": "Δεν έχουν ληφθεί ακόμη αρχεία", + "noFilesDescription": "Τα αρχεία που αποστέλλονται μέσω αυτού του συνδέσμου θα εμφανίζονται εδώ", + "fileCount": "{count, plural, =0 {Κανένα αρχείο} =1 {1 αρχείο} other {# αρχεία}}", + "invalidDate": "Μη έγκυρη ημερομηνία", + "totalSize": "Συνολικό μέγεθος: {size}", + "columns": { + "file": "Αρχείο", + "size": "Μέγεθος", + "sender": "Απεσταλμένο από", + "date": "Ημερομηνία", + "actions": "Ενέργειες" + }, + "actions": { + "preview": "Προεπισκόπηση", + "download": "Λήψη", + "copyToMyFiles": "Αντιγραφή στα αρχεία μου", + "copying": "Γίνεται αντιγραφή..." + }, + "uploadedBy": "Μεταφορτώθηκε από {name}", + "anonymous": "Ανώνυμος", + "downloadSuccess": "Η λήψη ξεκίνησε", + "downloadError": "Σφάλμα λήψης αρχείου", + "editSuccess": "Το αρχείο ενημερώθηκε με επιτυχία", + "editError": "Σφάλμα ενημέρωσης αρχείου", + "previewNotAvailable": "Η προεπισκόπηση δεν είναι διαθέσιμη", + "copySuccess": "Το αρχείο αντιγράφηκε στα αρχεία σας", + "copyError": "Σφάλμα αντιγραφής αρχείου στα αρχεία σας", + "deleteSuccess": "Το αρχείο διαγράφηκε με επιτυχία", + "deleteError": "Σφάλμα διαγραφής αρχείου", + "bulkCopySuccess": "{count, plural, =1 {1 αρχείο αντιγράφηκε στα αρχεία σας} other {# αρχεία αντιγράφηκαν στα αρχεία σας}}", + "bulkDeleteSuccess": "{count, plural, =1 {1 αρχείο διαγράφηκε με επιτυχία} other {# αρχεία διαγράφηκαν με επιτυχία}}", + "bulkCopyProgress": "Γίνεται αντιγραφή {count, plural, =1 {1 αρχείου} other {# αρχείων}} στα αρχεία σας...", + "bulkDeleteProgress": "Γίνεται διαγραφή {count, plural, =1 {1 αρχείου} other {# αρχείων}}...", + "bulkDeleteConfirmTitle": "Διαγραφή επιλεγμένων αρχείων", + "bulkDeleteConfirmMessage": "Είστε βέβαιοι ότι θέλετε να διαγράψετε {count, plural, =1 {αυτό το αρχείο} other {αυτά τα # αρχεία}}; Η ενέργεια δεν αναιρείται.", + "bulkDeleteConfirmButton": "Διαγραφή {count, plural, =1 {αρχείου} other {αρχείων}}", + "bulkActions": { + "selected": "{count, plural, =1 {1 αρχείο επιλέχθηκε} other {# αρχεία επιλέχθηκαν}}", + "actions": "Ενέργειες", + "download": "Λήψη επιλεγμένων", + "copyToMyFiles": "Αντιγραφή επιλεγμένων στα Αρχεία μου", + "delete": "Διαγραφή επιλεγμένων" + }, + "selectAll": "Επιλογή όλων", + "selectFile": "Επιλογή αρχείου {fileName}", + "copyErrors": { + "timeout": "Η αντιγραφή έληξε λόγω χρονικού ορίου. Δοκιμάστε ξανά με μικρότερο αρχείο ή ελέγξτε τη σύνδεσή σας.", + "failed": "Η αντιγραφή απέτυχε. Προσπαθήστε ξανά.", + "aborted": "Η αντιγραφή ακυρώθηκε λόγω λήξης χρονικού ορίου." + } + } + }, + "form": { + "name": { + "label": "Όνομα συνδέσμου", + "placeholder": "π.χ.: Έγγραφα έργου, Οικογενειακές φωτογραφίες..." + }, + "description": { + "label": "Περιγραφή", + "placeholder": "Περιγράψτε τι αρχεία αναμένετε να λάβετε...", + "description": "Προαιρετικό. Βοηθά τους αποστολείς να καταλάβουν τι να στείλουν." + }, + "status": { + "label": "Κατάσταση συνδέσμου", + "description": "Ενεργοποίηση ή απενεργοποίηση αυτού του συνδέσμου λήψης" + }, + "expiration": { + "label": "Ημερομηνία λήξης", + "description": "Προαιρετικό. Ο σύνδεσμος θα απενεργοποιηθεί μετά από αυτή την ημερομηνία.", + "configure": "Ρύθμιση λήξης" + }, + "fileLimits": { + "configure": "Ρύθμιση ορίων αρχείων" + }, + "maxFiles": { + "label": "Μέγιστος αριθμός αρχείων", + "placeholder": "π.χ.: 10", + "description": "Προαιρετικό. Περιορίστε τον συνολικό αριθμό αρχείων που μπορούν να σταλούν.", + "noLimit": "Χωρίς όριο αρχείων" + }, + "maxFileSize": { + "label": "Μέγιστο μέγεθος αρχείου", + "placeholder": "π.χ.: 100", + "description": "Προαιρετικό. Περιορίστε το μέγεθος κάθε αρχείου.", + "noLimit": "Χωρίς όριο μεγέθους" + }, + "allowedFileTypes": { + "label": "Επιτρεπόμενοι τύποι αρχείων", + "placeholder": "π.χ.: pdf, jpg, png, docx", + "description": "Εισαγάγετε επεκτάσεις χωρίς τελείες, χωρισμένες με κενό, κόμμα, παύλα ή κάθετο", + "allTypes": "Όλοι οι τύποι αρχείων" + }, + "pageLayout": { + "label": "Διάταξη σελίδας", + "placeholder": "Επιλέξτε διάταξη", + "description": "Πώς θα εμφανίζεται η σελίδα μεταφόρτωσης στους χρήστες.", + "options": { + "default": "Προεπιλεγμένη διάταξη", + "wetransfer": "Στιλ WeTransfer" + } + }, + "password": { + "label": "Κωδικός προστασίας", + "placeholder": "Προαιρετικό. Προσθέστε κωδικό για προστασία του συνδέσμου", + "description": "Προαιρετικό. Οι χρήστες θα χρειάζονται αυτόν τον κωδικό για πρόσβαση.", + "configurePassword": "Ρύθμιση κωδικού", + "protectWithPassword": "Προστασία με κωδικό", + "passwordHelp": "Ο κωδικός πρέπει να έχει τουλάχιστον 4 χαρακτήρες", + "passwordPlaceholder": "Εισαγάγετε κωδικό για προστασία του συνδέσμου" + }, + "nameFieldRequired": { + "label": "Απαίτηση πεδίου ονόματος", + "description": "Ρυθμίστε αν θα εμφανίζεται και αν θα είναι υποχρεωτικό το όνομα αποστολέα" + }, + "emailFieldRequired": { + "label": "Απαίτηση πεδίου email", + "description": "Ρυθμίστε αν θα εμφανίζεται και αν θα είναι υποχρεωτικό το email αποστολέα" + }, + "fieldRequirements": { + "title": "Απαιτήσεις πεδίων", + "description": "Ρυθμίστε ποια πεδία θα εμφανίζονται στη φόρμα μεταφόρτωσης" + }, + "submit": "Δημιουργία συνδέσμου λήψης" + }, + "messages": { + "created": "Ο σύνδεσμος λήψης δημιουργήθηκε με επιτυχία!", + "createSuccess": "Ο σύνδεσμος λήψης δημιουργήθηκε με επιτυχία!", + "updateSuccess": "Ο σύνδεσμος λήψης ενημερώθηκε με επιτυχία!", + "linkCopied": "Ο σύνδεσμος αντιγράφηκε στο πρόχειρο!", + "deleteSuccess": "Ο σύνδεσμος λήψης διαγράφηκε με επιτυχία!", + "aliasCreated": "Το ψευδώνυμο δημιουργήθηκε με επιτυχία!", + "activateSuccess": "Ο σύνδεσμος λήψης ενεργοποιήθηκε με επιτυχία!", + "deactivateSuccess": "Ο σύνδεσμος λήψης απενεργοποιήθηκε με επιτυχία!", + "passwordProtectionEnabled": "Η προστασία με κωδικό ενεργοποιήθηκε με επιτυχία!", + "passwordProtectionDisabled": "Η προστασία με κωδικό αφαιρέθηκε με επιτυχία!" + }, + "defaultLinkName": "Εισερχόμενα αρχεία", + "errors": { + "loadFailed": "Αποτυχία φόρτωσης συνδέσμων λήψης", + "createFailed": "Αποτυχία δημιουργίας συνδέσμου λήψης. Προσπαθήστε ξανά.", + "updateFailed": "Αποτυχία ενημέρωσης συνδέσμου λήψης. Προσπαθήστε ξανά.", + "deleteFailed": "Αποτυχία διαγραφής συνδέσμου λήψης. Προσπαθήστε ξανά.", + "aliasCreateFailed": "Αποτυχία δημιουργίας ψευδωνύμου. Προσπαθήστε ξανά.", + "passwordUpdateFailed": "Αποτυχία ενημέρωσης προστασίας με κωδικό" + }, + "delete": { + "title": "Διαγραφή συνδέσμου λήψης", + "description": "Αυτή η ενέργεια δεν αναιρείται. Ο σύνδεσμος θα αφαιρεθεί μόνιμα και δεν θα δέχεται πλέον αρχεία.", + "confirmButton": "Διαγραφή συνδέσμου", + "cancelButton": "Ακύρωση", + "deleting": "Γίνεται διαγραφή..." + }, + "upload": { + "metadata": { + "title": "Αποστολή αρχείων - Palmr", + "description": "Στείλτε αρχεία μέσω του κοινόχρηστου συνδέσμου", + "descriptionWithLimit": "Μεταφόρτωση αρχείων (μέγιστο {limit} αρχεία)" + }, + "layout": { + "defaultTitle": "Αποστολή αρχείων", + "importantInfo": "Σημαντικές πληροφορίες:", + "maxFiles": "Μέγιστο {count} αρχείο(α)", + "maxFileSize": "Μέγιστο μέγεθος αρχείου: {size}MB", + "allowedTypes": "Επιτρεπόμενοι τύποι: {types}", + "loading": "Φόρτωση..." + }, + "password": { + "title": "Προστατευμένος σύνδεσμος", + "description": "Αυτός ο σύνδεσμος προστατεύεται με κωδικό. Εισαγάγετε τον κωδικό για να συνεχίσετε.", + "label": "Κωδικός", + "placeholder": "Εισαγάγετε κωδικό", + "cancel": "Ακύρωση", + "submit": "Συνέχεια", + "verifying": "Γίνεται επαλήθευση..." + }, + "errors": { + "loadFailed": "Αποτυχία φόρτωσης πληροφοριών. Προσπαθήστε ξανά.", + "passwordIncorrect": "Λανθασμένος κωδικός. Προσπαθήστε ξανά.", + "linkNotFound": "Ο σύνδεσμος δεν βρέθηκε ή έχει λήξει.", + "linkInactive": "Αυτός ο σύνδεσμος είναι ανενεργός.", + "linkExpired": "Αυτός ο σύνδεσμος έχει λήξει.", + "uploadFailed": "Σφάλμα μεταφόρτωσης αρχείου", + "retry": "Επανάληψη", + "fileTooLarge": "Πολύ μεγάλο αρχείο. Μέγιστο μέγεθος: {maxSize}", + "fileTypeNotAllowed": "Μη επιτρεπτός τύπος αρχείου. Αποδεκτοί τύποι: {allowedTypes}", + "maxFilesExceeded": "Επιτρέπονται έως {maxFiles} αρχεία", + "selectAtLeastOneFile": "Επιλέξτε τουλάχιστον ένα αρχείο", + "provideNameOrEmail": "Παρακαλώ δώστε το όνομα ή το email σας", + "provideNameRequired": "Απαιτείται όνομα", + "provideEmailRequired": "Απαιτείται email" + }, + "fileDropzone": { + "dragActive": "Αφήστε αρχεία εδώ", + "dragInactive": "Σύρετε αρχεία εδώ ή κάντε κλικ για επιλογή", + "acceptedTypes": "Αποδεκτοί τύποι: {types}", + "maxFileSize": "Μέγιστο μέγεθος: {size}", + "maxFiles": "Μέγιστο {count} αρχεία", + "remainingFiles": "Απομένουν {remaining} από {max} αρχεία" + }, + "fileList": { + "title": "Επιλεγμένα αρχεία:", + "statusUploaded": "Μεταφορτώθηκε", + "statusError": "Σφάλμα", + "retry": "Επανάληψη" + }, + "form": { + "nameLabel": "Όνομα", + "nameLabelOptional": "Όνομα (προαιρετικό)", + "namePlaceholder": "Το όνομά σας", + "emailLabel": "Email", + "emailLabelOptional": "Email (προαιρετικό)", + "emailPlaceholder": "your@email.com", + "descriptionLabel": "Περιγραφή (προαιρετική)", + "descriptionPlaceholder": "Προσθέστε περιγραφή για τα αρχεία...", + "uploadButton": "Αποστολή {count} αρχείου(ων)", + "uploading": "Γίνεται αποστολή..." + }, + "success": { + "title": "Τα αρχεία στάλθηκαν με επιτυχία! 🎉", + "description": "Μπορείτε να κλείσετε αυτή τη σελίδα.", + "countMessage": "{count} αρχείο(α) στάλθηκαν με επιτυχία!" + }, + "maxFilesReached": { + "title": "Έχει επιτευχθεί το όριο αρχείων", + "description": "Αυτός ο σύνδεσμος έχει ήδη λάβει το μέγιστο αριθμό {maxFiles} αρχείων.", + "contactOwner": "Αν υπήρξε σφάλμα ή χρειάζεται να στείλετε περισσότερα αρχεία, επικοινωνήστε με τον ιδιοκτήτη του συνδέσμου." + }, + "linkInactive": { + "title": "Ο σύνδεσμος είναι ανενεργός", + "description": "Αυτός ο σύνδεσμος λήψης είναι προσωρινά ανενεργός.", + "contactOwner": "Επικοινωνήστε με τον ιδιοκτήτη για περισσότερες πληροφορίες." + }, + "linkNotFound": { + "title": "Ο σύνδεσμος δεν βρέθηκε", + "description": "Ο σύνδεσμος ίσως αφαιρέθηκε ή δεν υπήρξε ποτέ." + }, + "linkExpired": { + "title": "Ο σύνδεσμος έχει λήξει", + "description": "Αυτός ο σύνδεσμος λήψης έχει λήξει και δεν δέχεται πλέον αρχεία.", + "contactOwner": "Επικοινωνήστε με τον ιδιοκτήτη αν χρειάζεται να στείλετε αρχεία." + } + }, + "components": { + "fileRow": { + "addDescription": "Προσθήκη περιγραφής...", + "anonymous": "Ανώνυμος" + }, + "fileActions": { + "edit": "Επεξεργασία", + "preview": "Προεπισκόπηση", + "download": "Λήψη", + "delete": "Διαγραφή", + "copyToMyFiles": "Αντιγραφή στα αρχεία μου", + "copying": "Γίνεται αντιγραφή..." + }, + "editField": { + "saveChanges": "Αποθήκευση αλλαγών", + "cancelEdit": "Ακύρωση επεξεργασίας" + } + } + }, + "searchBar": { + "placeholder": "Αναζήτηση αρχείων και φακέλων...", + "placeholderFiles": "Αναζήτηση αρχείων...", + "placeholderFolders": "Αναζήτηση φακέλων...", + "results": "Εμφάνιση {filtered} από {total} στοιχεία", + "noResults": "Δεν βρέθηκαν αποτελέσματα για \"{query}\"" + }, + "settings": { + "groups": { + "defaultDescription": "Επιλογές ρύθμισης", + "general": { + "title": "Γενικά", + "description": "Βασικές ρυθμίσεις εφαρμογής" + }, + "email": { + "title": "Email", + "description": "Ρύθμιση διακομιστή email" + }, + "security": { + "title": "Ασφάλεια", + "description": "Ρυθμίσεις ασφάλειας και ελέγχου ταυτότητας" + }, + "storage": { + "title": "Αποθήκευση", + "description": "Ρύθμιση αποθήκευσης αρχείων" + }, + "oidc": { + "title": "OpenID Connect (SSO)", + "description": "Ρυθμίστε έλεγχο ταυτότητας SSO μέσω OpenID Connect" + } + }, + "fields": { + "noDescription": "Δεν υπάρχει διαθέσιμη περιγραφή", + "appLogo": { + "title": "Λογότυπο εφαρμογής", + "description": "Εικόνα λογοτύπου εφαρμογής" + }, + "appName": { + "title": "Όνομα εφαρμογής", + "description": "Όνομα εφαρμογής που εμφανίζεται στους χρήστες" + }, + "appDescription": { + "title": "Περιγραφή εφαρμογής", + "description": "Σύντομη περιγραφή της εφαρμογής" + }, + "showHomePage": { + "title": "Εμφάνιση αρχικής σελίδας", + "description": "Εμφάνιση αρχικής σελίδας μετά την εγκατάσταση" + }, + "hideVersion": { + "title": "Απόκρυψη έκδοσης", + "description": "Απόκρυψη της έκδοσης Palmr από το υποσέλιδο σε όλες τις σελίδες" + }, + "smtpEnabled": { + "title": "Ενεργοποίηση SMTP", + "description": "Ενεργοποίηση ή απενεργοποίηση λειτουργιών email SMTP" + }, + "smtpHost": { + "title": "Διακομιστής SMTP", + "description": "Διεύθυνση διακομιστή SMTP" + }, + "smtpPort": { + "title": "Θύρα SMTP", + "description": "Θύρα διακομιστή SMTP" + }, + "smtpUser": { + "title": "Όνομα χρήστη SMTP", + "description": "Όνομα χρήστη για έλεγχο ταυτότητας SMTP" + }, + "smtpPass": { + "title": "Κωδικός SMTP", + "description": "Κωδικός για έλεγχο ταυτότητας SMTP" + }, + "smtpFromName": { + "title": "Όνομα αποστολέα", + "description": "Εμφανιζόμενο όνομα για τα εξερχόμενα email" + }, + "smtpFromEmail": { + "title": "Email αποστολέα", + "description": "Διεύθυνση email αποστολέα" + }, + "smtpSecure": { + "title": "Ασφάλεια σύνδεσης", + "description": "Μέθοδος ασφάλειας σύνδεσης SMTP - Αυτόματο (προτείνεται), SSL, STARTTLS ή Καμία (μη ασφαλές)", + "options": { + "auto": "Αυτόματο (προτείνεται)", + "ssl": "SSL (Port 465)", + "tls": "STARTTLS (Port 587)", + "none": "Καμία (Μη ασφαλές)" + } + }, + "smtpNoAuth": { + "title": "Χωρίς έλεγχο ταυτότητας", + "description": "Ενεργοποιήστε το για εσωτερικούς διακομιστές που δεν απαιτούν όνομα χρήστη/κωδικό (κρύβει τα πεδία ταυτότητας)" + }, + "smtpTrustSelfSigned": { + "title": "Εμπιστοσύνη σε αυτοϋπογεγραμμένα πιστοποιητικά", + "description": "Ενεργοποιήστε για εμπιστοσύνη σε αυτοϋπογεγραμμένα πιστοποιητικά SSL/TLS (χρήσιμο για περιβάλλοντα ανάπτυξης)" + }, + "testSmtp": { + "title": "Έλεγχος σύνδεσης SMTP", + "description": "Ελέγξτε αν η ρύθμιση SMTP είναι έγκυρη" + }, + "maxLoginAttempts": { + "title": "Μέγιστες προσπάθειες σύνδεσης", + "description": "Μέγιστος αριθμός προσπαθειών σύνδεσης πριν τον αποκλεισμό" + }, + "loginBlockDuration": { + "title": "Διάρκεια αποκλεισμού", + "description": "Διάρκεια (σε δευτερόλεπτα) αποκλεισμού μετά την υπέρβαση προσπαθειών" + }, + "passwordMinLength": { + "title": "Ελάχιστο μήκος κωδικού", + "description": "Ελάχιστος αριθμός χαρακτήρων για κωδικούς" + }, + "passwordResetTokenExpiration": { + "title": "Λήξη διακριτικού επαναφοράς", + "description": "Χρόνος ισχύος (σε δευτερόλεπτα) για το διακριτικό επαναφοράς κωδικού" + }, + "maxFileSize": { + "title": "Μέγιστο μέγεθος αρχείου", + "description": "Μέγιστο επιτρεπτό μέγεθος αρχείου για μεταφορτώσεις" + }, + "maxTotalStoragePerUser": { + "title": "Μέγιστος χώρος ανά χρήστη", + "description": "Συνολικό όριο αποθήκευσης ανά χρήστη" + }, + "firstUserAccess": { + "title": "Πρώτη πρόσβαση χρήστη", + "description": "Ρυθμίσεις για την πρώτη πρόσβαση νέων χρηστών" + }, + "serverUrl": { + "title": "URL διακομιστή", + "description": "Βασικό URL του διακομιστή Palmr (π.χ.: https://palmr.example.com)" + }, + "passwordAuthEnabled": { + "title": "Έλεγχος με κωδικό πρόσβασης", + "description": "Ενεργοποίηση ή απενεργοποίηση ελέγχου με κωδικό" + } + }, + "buttons": { + "save": "Αποθήκευση {group}", + "testSmtp": "Έλεγχος σύνδεσης", + "testing": "Γίνεται έλεγχος..." + }, + "errors": { + "loadFailed": "Αποτυχία φόρτωσης ρυθμίσεων", + "updateFailed": "Αποτυχία ενημέρωσης ρυθμίσεων", + "passwordAuthRequiresProvider": "Δεν είναι δυνατή η απενεργοποίηση του ελέγχου με κωδικό χωρίς τουλάχιστον έναν ενεργό πάροχο ελέγχου ταυτότητας" + }, + "messages": { + "noChanges": "Καμία αλλαγή για αποθήκευση", + "updateSuccess": "Οι ρυθμίσεις {group} ενημερώθηκαν με επιτυχία", + "smtpTestSuccess": "Επιτυχής σύνδεση SMTP! Η ρύθμιση email λειτουργεί σωστά.", + "smtpTestFailed": "Αποτυχία σύνδεσης SMTP: {error}", + "smtpTestGenericError": "Αποτυχία ελέγχου σύνδεσης SMTP. Ελέγξτε τις ρυθμίσεις και δοκιμάστε ξανά.", + "smtpNotEnabled": "Το SMTP δεν είναι ενεργό. Ενεργοποιήστε το πρώτα.", + "smtpMissingHostPort": "Συμπληρώστε Host και Port του SMTP πριν τον έλεγχο.", + "smtpMissingAuth": "Συμπληρώστε Όνομα χρήστη και Κωδικό SMTP ή ενεργοποιήστε την επιλογή 'Χωρίς έλεγχο ταυτότητας'." + }, + "title": "Ρυθμίσεις", + "breadcrumb": "Ρυθμίσεις", + "pageTitle": "Ρυθμίσεις", + "tooltips": { + "testSmtp": "Δοκιμάζει τη σύνδεση SMTP με τις τρέχουσες τιμές της φόρμας. Για μόνιμες αλλαγές, θυμηθείτε να αποθηκεύσετε μετά τον έλεγχο.", + "defaultPlaceholder": "Πληκτρολογήστε και πατήστε Enter" + }, + "redirectUri": { + "placeholder": "https://mysite.com", + "previewLabel": "Πλήρες URL που θα αποθηκευτεί:" + } + }, + "share": { + "errors": { + "invalidPassword": "Μη έγκυρος κωδικός. Προσπαθήστε ξανά.", + "loadFailed": "Αποτυχία φόρτωσης κοινής χρήσης", + "downloadFailed": "Αποτυχία λήψης αρχείου" + }, + "messages": { + "downloadStarted": "Η λήψη ξεκίνησε" + }, + "password": { + "title": "Κοινή χρήση προστατευμένη με κωδικό", + "protected": "Αυτή η κοινή χρήση προστατεύεται με κωδικό", + "incorrect": "Λανθασμένος κωδικός. Προσπαθήστε ξανά.", + "label": "Κωδικός", + "placeholder": "Εισαγάγετε τον κωδικό κοινής χρήσης", + "submit": "Υποβολή" + }, + "details": { + "untitled": "Κοινή χρήση χωρίς τίτλο", + "created": "Δημιουργία: {date}", + "expires": "Λήξη: {date}" + }, + "downloadAll": "Λήψη όλων", + "notFound": { + "title": "Η κοινή χρήση δεν βρέθηκε", + "description": "Η κοινή χρήση μπορεί να έχει διαγραφεί ή λήξει." + }, + "pageTitle": "Κοινή χρήση", + "metadata": { + "defaultDescription": "Κοινή χρήση αρχείων με ασφάλεια", + "filesShared": "{count, plural, =1 {1 αρχείο σε κοινή χρήση} other {# αρχεία σε κοινή χρήση}}" + } + }, + "shareActions": { + "fileTitle": "Κοινή χρήση αρχείου", + "folderTitle": "Κοινή χρήση φακέλου", + "linkTitle": "Δημιουργία συνδέσμου", + "linkDescriptionFile": "Δημιουργήστε προσαρμοσμένο σύνδεσμο για την κοινή χρήση του αρχείου", + "linkDescriptionFolder": "Δημιουργήστε προσαρμοσμένο σύνδεσμο για την κοινή χρήση του φακέλου", + "aliasLabel": "Ψευδώνυμο συνδέσμου", + "aliasPlaceholder": "Εισαγάγετε προσαρμοσμένο ψευδώνυμο", + "linkReady": "Ο σύνδεσμος κοινής χρήσης είναι έτοιμος:", + "generateLink": "Δημιουργία συνδέσμου", + "copyLink": "Αντιγραφή συνδέσμου", + "deleteTitle": "Διαγραφή κοινής χρήσης", + "deleteConfirmation": "Είστε βέβαιοι ότι θέλετε να διαγράψετε αυτή την κοινή χρήση; Η ενέργεια δεν αναιρείται.", + "addDescriptionPlaceholder": "Προσθήκη περιγραφής...", + "editTitle": "Επεξεργασία κοινής χρήσης", + "newPasswordLabel": "Νέος κωδικός (αφήστε κενό για διατήρηση)", + "newPasswordPlaceholder": "Εισαγάγετε νέο κωδικό", + "manageFilesTitle": "Διαχείριση αρχείων", + "manageFilesDescription": "Επιλέξτε αρχεία και φακέλους που θα συμπεριληφθούν σε αυτή την κοινή χρήση", + "manageRecipientsTitle": "Διαχείριση παραληπτών", + "itemsSelected": "{count} στοιχεία επιλέχθηκαν", + "editSuccess": "Η κοινή χρήση ενημερώθηκε με επιτυχία", + "editError": "Αποτυχία ενημέρωσης κοινής χρήσης", + "bulkDeleteConfirmation": "Είστε βέβαιοι ότι θέλετε να διαγράψετε {count, plural, =1 {1 κοινή χρήση} other {# κοινές χρήσεις}}; Η ενέργεια δεν αναιρείται.", + "bulkDeleteTitle": "Διαγραφή επιλεγμένων κοινών χρήσεων" + }, + "shareDetails": { + "title": "Λεπτομέρειες κοινής χρήσης", + "subtitle": "Προβολή και διαχείριση λεπτομερειών για αυτή την κοινή χρήση", + "basicInfo": "Βασικές πληροφορίες", + "name": "Όνομα", + "description": "Περιγραφή", + "shareLink": "Σύνδεσμος κοινής χρήσης", + "dates": "Ημερομηνίες", + "security": "Ασφάλεια", + "files": "Αρχεία", + "recipients": "Παραλήπτες", + "views": "Προβολές", + "created": "Δημιουργήθηκε", + "expires": "Λήγει", + "never": "Ποτέ", + "untitled": "Κοινή χρήση χωρίς τίτλο", + "noDescription": "Χωρίς περιγραφή", + "notAvailable": "Μ/Δ", + "invalidDate": "Μη έγκυρη ημερομηνία", + "passwordProtected": "Προστατευμένη με κωδικό", + "publicAccess": "Δημόσια πρόσβαση", + "maxViews": "Μέγιστες προβολές:", + "noLink": "Δεν δημιουργήθηκε σύνδεσμος", + "generateLink": "Δημιουργία συνδέσμου", + "editLink": "Επεξεργασία συνδέσμου", + "copyLink": "Αντιγραφή συνδέσμου", + "openLink": "Άνοιγμα συνδέσμου", + "editSecurity": "Επεξεργασία ασφάλειας", + "editExpiration": "Επεξεργασία λήξης", + "qrCode": "Κωδικός QR", + "downloadQrCode": "Λήψη κωδικού QR", + "clickToEnlargeQrCode": "Κάντε κλικ για μεγέθυνση του κωδικού QR", + "loadError": "Αποτυχία φόρτωσης λεπτομερειών κοινής χρήσης" + }, + "shareExpiration": { + "title": "Ρυθμίσεις λήξης κοινής χρήσης", + "subtitle": "Ρυθμίστε πότε θα λήξει αυτή η κοινή χρήση", + "currentStatus": "Τρέχουσα κατάσταση", + "expires": "Λήγει:", + "neverExpires": "Δεν λήγει ποτέ", + "enableExpiration": "Ενεργοποίηση λήξης", + "expirationDate": "Ημερομηνία λήξης", + "validation": { + "dateRequired": "Επιλέξτε ημερομηνία λήξης", + "dateMustBeFuture": "Η ημερομηνία λήξης πρέπει να είναι μελλοντική" + }, + "success": { + "expirationSet": "Η ημερομηνία λήξης ορίστηκε με επιτυχία", + "expirationUpdated": "Η ημερομηνία λήξης ενημερώθηκε με επιτυχία", + "expirationRemoved": "Η λήξη αφαιρέθηκε με επιτυχία - η κοινή χρήση είναι πλέον μόνιμη" + }, + "error": { + "updateFailed": "Αποτυχία ενημέρωσης ρυθμίσεων λήξης" + }, + "info": { + "title": "Σχετικά με τη λήξη:", + "willBeInaccessible": "Η κοινή χρήση θα γίνει μη προσβάσιμη μετά από αυτή την ημερομηνία", + "canBeChanged": "Μπορείτε να αλλάξετε ή να αφαιρέσετε τη λήξη οποιαδήποτε στιγμή", + "noExpiration": "Αυτή η κοινή χρήση δεν θα λήξει ποτέ και θα παραμένει διαθέσιμη επ' αόριστον." + } + }, + "shareManager": { + "deleteSuccess": "Η κοινή χρήση διαγράφηκε με επιτυχία", + "deleteError": "Αποτυχία διαγραφής κοινής χρήσης", + "updateSuccess": "Η κοινή χρήση ενημερώθηκε με επιτυχία", + "updateError": "Αποτυχία ενημέρωσης κοινής χρήσης", + "securityUpdateSuccess": "Οι ρυθμίσεις ασφάλειας ενημερώθηκαν με επιτυχία", + "securityUpdateError": "Αποτυχία ενημέρωσης ρυθμίσεων ασφάλειας", + "expirationUpdateSuccess": "Οι ρυθμίσεις λήξης ενημερώθηκαν με επιτυχία", + "expirationUpdateError": "Αποτυχία ενημέρωσης ρυθμίσεων λήξης", + "filesUpdateSuccess": "Τα αρχεία ενημερώθηκαν με επιτυχία", + "filesUpdateError": "Αποτυχία ενημέρωσης αρχείων", + "recipientsUpdateSuccess": "Οι παραλήπτες ενημερώθηκαν με επιτυχία", + "recipientsUpdateError": "Αποτυχία ενημέρωσης παραληπτών", + "linkGenerateSuccess": "Ο σύνδεσμος κοινής χρήσης δημιουργήθηκε με επιτυχία", + "linkGenerateError": "Αποτυχία δημιουργίας συνδέσμου κοινής χρήσης", + "notifyLoading": "Αποστολή ειδοποιήσεων...", + "notifySuccess": "Οι παραλήπτες ειδοποιήθηκαν με επιτυχία", + "notifyError": "Αποτυχία ειδοποίησης παραληπτών", + "bulkDeleteError": "Αποτυχία διαγραφής κοινών χρήσεων", + "bulkDeleteLoading": "Διαγραφή {count, plural, =1 {1 κοινής χρήσης} other {# κοινών χρήσεων}}...", + "bulkDeleteSuccess": "{count, plural, =1 {1 κοινή χρήση διαγράφηκε με επιτυχία} other {# κοινές χρήσεις διαγράφηκαν με επιτυχία}}", + "downloadSuccess": "Η λήψη ξεκίνησε με επιτυχία", + "downloadError": "Αποτυχία λήψης αρχείων κοινής χρήσης", + "noFilesToDownload": "Δεν υπάρχουν αρχεία για λήψη", + "creatingZip": "Δημιουργία αρχείου ZIP...", + "zipDownloadSuccess": "Το αρχείο ZIP λήφθηκε με επιτυχία", + "zipDownloadError": "Αποτυχία δημιουργίας αρχείου ZIP", + "errors": { + "multipleDownloadNotSupported": "Η λήψη πολλών κοινών χρήσεων δεν υποστηρίζεται ακόμη - κατεβάστε τις ξεχωριστά" + }, + "singleShareZipName": "{shareName}_αρχεία.zip", + "multipleSharesZipName": "{count}_κοινές_χρήσεις_αρχεία.zip", + "defaultShareName": "Κοινή χρήση" + }, + "shareMultipleFiles": { + "title": "Κοινή χρήση πολλών αρχείων", + "shareNameLabel": "Όνομα κοινής χρήσης", + "shareNamePlaceholder": "Εισαγάγετε όνομα κοινής χρήσης", + "descriptionLabel": "Περιγραφή", + "descriptionPlaceholder": "Εισαγάγετε περιγραφή (προαιρετικό)", + "filesToShare": "Αρχεία για κοινή χρήση", + "files": "αρχεία", + "totalSize": "Συνολικό μέγεθος", + "creating": "Δημιουργία κοινής χρήσης...", + "create": "Δημιουργία κοινής χρήσης", + "itemsToShare": "Στοιχεία για κοινή χρήση ({count} {count, plural, =1 {στοιχείο} other {στοιχεία}})" + }, + "shareSecurity": { + "title": "Ρυθμίσεις ασφάλειας κοινής χρήσης", + "subtitle": "Ρυθμίστε προστασία με κωδικό και επιλογές ασφάλειας για αυτή την κοινή χρήση", + "currentStatus": "Τρέχουσα κατάσταση", + "passwordProtection": "Προστασία με κωδικό", + "password": "Κωδικός", + "newPassword": "Νέος κωδικός", + "passwordPlaceholder": "Εισαγάγετε έναν ασφαλή κωδικό", + "existingPasswordMessage": "Αυτή η κοινή χρήση έχει ήδη κωδικό. Για ενημέρωση, εισαγάγετε τον νέο κωδικό στο παρακάτω πεδίο και αποθηκεύστε.", + "passwordRequirements": { + "title": "Απαιτήσεις κωδικού:", + "minLength": "Τουλάχιστον 2 χαρακτήρες" + }, + "info": { + "title": "Πώς λειτουργεί:", + "withPassword": "Οι χρήστες θα πρέπει να εισάγουν τον κωδικό για πρόσβαση στην κοινή χρήση.", + "withoutPassword": "Όποιος έχει τον σύνδεσμο θα έχει πρόσβαση χωρίς κωδικό." + }, + "validation": { + "passwordRequired": "Απαιτείται κωδικός", + "passwordTooShort": "Ο κωδικός πρέπει να έχει τουλάχιστον 2 χαρακτήρες" + }, + "success": { + "passwordSet": "Η προστασία με κωδικό ενεργοποιήθηκε με επιτυχία", + "passwordUpdated": "Ο κωδικός ενημερώθηκε με επιτυχία", + "passwordRemoved": "Η προστασία με κωδικό αφαιρέθηκε με επιτυχία" + }, + "error": { + "updateFailed": "Αποτυχία ενημέρωσης ρυθμίσεων ασφάλειας" + } + }, + "shares": { + "errors": { + "loadFailed": "Αποτυχία φόρτωσης κοινών χρήσεων", + "notifyFailed": "Αποτυχία ειδοποίησης παραληπτών", + "smtpConfigFailed": "Αποτυχία φόρτωσης ρύθμισης SMTP" + }, + "messages": { + "linkCopied": "Ο σύνδεσμος αντιγράφηκε στο πρόχειρο", + "recipientsNotified": "Οι παραλήπτες ειδοποιήθηκαν με επιτυχία" + }, + "empty": { + "message": "Δεν έχουν δημιουργηθεί ακόμη κοινές χρήσεις", + "createButton": "Δημιουργία κοινής χρήσης" + }, + "header": { + "title": "Οι κοινές μου χρήσεις", + "myShares": "Οι κοινές μου χρήσεις" + }, + "search": { + "title": "Όλες οι κοινές χρήσεις", + "createButton": "Δημιουργία κοινής χρήσης", + "placeholder": "Αναζήτηση κοινών χρήσεων...", + "results": "Βρέθηκαν {filtered} από {total} κοινές χρήσεις" + }, + "pageTitle": "Κοινές χρήσεις" + }, + "sharesTable": { + "ariaLabel": "Πίνακας κοινών χρήσεων", + "never": "Ποτέ", + "columns": { + "name": "ΟΝΟΜΑ", + "description": "ΠΕΡΙΓΡΑΦΗ", + "createdAt": "ΗΜ/ΝΙΑ ΔΗΜΙΟΥΡΓΙΑΣ", + "expiresAt": "ΗΜ/ΝΙΑ ΛΗΞΗΣ", + "status": "ΚΑΤΑΣΤΑΣΗ", + "security": "ΑΣΦΑΛΕΙΑ", + "files": "ΑΡΧΕΙΑ", + "recipients": "ΠΑΡΑΛΗΠΤΕΣ", + "actions": "ΕΝΕΡΓΕΙΕΣ" + }, + "status": { + "neverExpires": "Δεν λήγει ποτέ", + "active": "Ενεργή", + "expired": "Έχει λήξει" + }, + "security": { + "protected": "Προστατευμένη", + "public": "Δημόσια" + }, + "filesCount": "αρχεία", + "folderCount": "φάκελοι", + "recipientsCount": "παραλήπτες", + "actions": { + "menu": "Μενού ενεργειών κοινής χρήσης", + "edit": "Επεξεργασία", + "manageFiles": "Διαχείριση αρχείων", + "manageRecipients": "Διαχείριση παραληπτών", + "viewDetails": "Προβολή λεπτομερειών", + "generateLink": "Δημιουργία συνδέσμου", + "editLink": "Επεξεργασία συνδέσμου", + "copyLink": "Αντιγραφή συνδέσμου", + "viewQrCode": "Προβολή κωδικού QR", + "notifyRecipients": "Ειδοποίηση παραληπτών", + "downloadShareFiles": "Λήψη όλων των αρχείων", + "delete": "Διαγραφή" + }, + "bulkActions": { + "actions": "Ενέργειες", + "download": "Λήψη επιλεγμένων", + "delete": "Διαγραφή", + "selected": "{count, plural, =1 {1 κοινή χρήση επιλέχθηκε} other {# κοινές χρήσεις επιλέχθηκαν}}" + }, + "selectAll": "Επιλογή όλων", + "selectShare": "Επιλογή κοινής χρήσης {shareName}" + }, + "storageUsage": { + "title": "Χρήση αποθήκευσης", + "ariaLabel": "Γραμμή προόδου χρήσης αποθήκευσης", + "used": "χρησιμοποιημένο", + "available": "διαθέσιμο", + "total": "Σύνολο", + "loading": "Φόρτωση...", + "retry": "Δοκιμή ξανά", + "errors": { + "title": "Οι πληροφορίες αποθήκευσης δεν είναι διαθέσιμες", + "detectionFailed": "Δεν είναι δυνατός ο εντοπισμός του χώρου δίσκου. Ίσως οφείλεται σε ρυθμίσεις συστήματος ή ανεπαρκή δικαιώματα.", + "serverError": "Προέκυψε σφάλμα διακομιστή κατά την ανάκτηση πληροφοριών αποθήκευσης. Δοκιμάστε αργότερα.", + "unknown": "Προέκυψε απρόσμενο σφάλμα κατά τη φόρτωση πληροφοριών αποθήκευσης." + } + }, + "theme": { + "toggle": "Εναλλαγή θέματος", + "light": "Φωτεινό", + "dark": "Σκοτεινό", + "system": "Σύστημα" + }, + "twoFactor": { + "title": "Έλεγχος ταυτότητας δύο παραγόντων", + "description": "Προσθέστε ένα επιπλέον επίπεδο ασφαλείας στον λογαριασμό σας", + "enabled": "Ο λογαριασμός σας προστατεύεται με έλεγχο δύο παραγόντων", + "disabled": "Ο έλεγχος δύο παραγόντων δεν είναι ενεργός", + "status": { + "label": "Κατάσταση:", + "enabled": "Ενεργό", + "disabled": "Ανενεργό" + }, + "buttons": { + "enable2FA": "Ενεργοποίηση 2FA", + "disable2FA": "Απενεργοποίηση 2FA" + }, + "setup": { + "title": "Ενεργοποίηση ελέγχου δύο παραγόντων", + "description": "Σαρώστε τον κωδικό QR με την εφαρμογή αυθεντικοποίησης και εισάγετε τον κωδικό.", + "qrCode": "Κωδικός QR", + "manualEntryKey": "Κλειδί χειροκίνητης εισαγωγής", + "verificationCode": "Κωδικός επαλήθευσης", + "verificationCodePlaceholder": "Εισαγάγετε 6-ψήφιο κωδικό", + "verificationCodeDescription": "Εισαγάγετε τον 6-ψήφιο κωδικό από την εφαρμογή αυθεντικοποίησης", + "verifyAndEnable": "Επαλήθευση & Ενεργοποίηση", + "cancel": "Ακύρωση" + }, + "disable": { + "title": "Απενεργοποίηση ελέγχου δύο παραγόντων", + "description": "Εισαγάγετε τον κωδικό σας για επιβεβαίωση απενεργοποίησης.", + "password": "Κωδικός πρόσβασης", + "passwordPlaceholder": "Εισαγάγετε τον κωδικό σας", + "confirm": "Επιβεβαίωση απενεργοποίησης", + "cancel": "Ακύρωση" + }, + "backupCodes": { + "title": "Εφεδρικοί κωδικοί", + "description": "Αποθηκεύστε αυτούς τους κωδικούς σε ασφαλές σημείο. Θα σας χρειαστούν αν χάσετε τη συσκευή αυθεντικοποίησης.", + "warning": "Σημαντικό:", + "warningText": "Κάθε εφεδρικός κωδικός χρησιμοποιείται μόνο μία φορά. Κρατήστε τους ασφαλείς και μην τους μοιράζεστε.", + "generateNew": "Δημιουργία νέων εφεδρικών κωδικών", + "download": "Λήψη εφεδρικών κωδικών", + "copyToClipboard": "Αντιγραφή στο πρόχειρο", + "savedMessage": "Αποθήκευσα τους εφεδρικούς κωδικούς μου", + "available": "{count} διαθέσιμοι εφεδρικοί κωδικοί", + "instructions": [ + "• Αποθηκεύστε αυτούς τους κωδικούς σε ασφαλές σημείο", + "• Κάθε εφεδρικός κωδικός χρησιμοποιείται μία φορά", + "• Μπορείτε να δημιουργήσετε νέους κωδικούς οποτεδήποτε" + ] + }, + "verification": { + "title": "Έλεγχος ταυτότητας δύο παραγόντων", + "description": "Εισαγάγετε τον 6-ψήφιο κωδικό από την εφαρμογή", + "backupDescription": "Εισαγάγετε έναν εφεδρικό κωδικό για να συνεχίσετε", + "verificationCode": "Κωδικός επαλήθευσης", + "backupCode": "Εφεδρικός κωδικός", + "verificationCodePlaceholder": "000000", + "backupCodePlaceholder": "XXXX-XXXX", + "verify": "Επαλήθευση", + "verifying": "Γίνεται επαλήθευση...", + "useBackupCode": "Χρήση εφεδρικού κωδικού", + "useAuthenticatorCode": "Χρήση κωδικού εφαρμογής", + "rememberDevice": "Να θυμάσαι αυτή τη συσκευή για 30 ημέρες", + "rememberDeviceDescription": "Δεν θα χρειαστεί να εισάγετε κωδικούς 2FA σε αυτή τη συσκευή για 30 ημέρες" + }, + "trustedDevices": { + "title": "Έμπιστες συσκευές - 2FA", + "description": "Συσκευές που δεν απαιτούν έλεγχο 2FA", + "noDevices": "Δεν υπάρχουν έμπιστες συσκευές", + "deviceName": "Συσκευή", + "addedOn": "Προστέθηκε", + "expiresOn": "Λήγει", + "remove": "Αφαίρεση", + "removeAll": "Αφαίρεση όλων", + "confirmRemove": "Είστε βέβαιοι ότι θέλετε να αφαιρέσετε αυτή την έμπιστη συσκευή;", + "confirmRemoveAll": "Είστε βέβαιοι ότι θέλετε να αφαιρέσετε όλες τις έμπιστες συσκευές;", + "deviceRemoved": "Η έμπιστη συσκευή αφαιρέθηκε με επιτυχία", + "allDevicesRemoved": "Όλες οι έμπιστες συσκευές αφαιρέθηκαν με επιτυχία", + "loadFailed": "Αποτυχία φόρτωσης έμπιστων συσκευών", + "removeFailed": "Αποτυχία αφαίρεσης έμπιστης συσκευής", + "removeAllFailed": "Αποτυχία αφαίρεσης όλων των έμπιστων συσκευών", + "loading": "Φόρτωση έμπιστων συσκευών...", + "noDevicesDescription": "Οι συσκευές θα εμφανιστούν εδώ όταν τις ορίσετε ως έμπιστες κατά τον έλεγχο 2FA", + "tableHeaders": { + "device": "Συσκευή", + "added": "Προστέθηκε", + "expires": "Λήγει", + "lastUsed": "Τελευταία χρήση", + "ipAddress": "Διεύθυνση IP", + "actions": "Ενέργειες" + }, + "status": { + "never": "Ποτέ", + "expired": "Έληξε" + }, + "modals": { + "removeDevice": { + "title": "Αφαίρεση έμπιστης συσκευής", + "added": "Προστέθηκε:", + "ip": "IP:" + }, + "removeAllDevices": { + "title": "Αφαίρεση όλων των έμπιστων συσκευών", + "description": "Θα αφαιρεθούν {count} έμπιστες συσκευές. Θα χρειαστεί να επαληθεύσετε 2FA ξανά σε όλες τις συσκευές." + }, + "buttons": { + "cancel": "Ακύρωση", + "removing": "Γίνεται αφαίρεση...", + "removeDevice": "Αφαίρεση συσκευής", + "removeAllDevices": "Αφαίρεση όλων των συσκευών" + } + } + }, + "messages": { + "enabledSuccess": "Ο έλεγχος δύο παραγόντων ενεργοποιήθηκε με επιτυχία!", + "disabledSuccess": "Ο έλεγχος δύο παραγόντων απενεργοποιήθηκε με επιτυχία", + "backupCodesGenerated": "Οι νέοι εφεδρικοί κωδικοί δημιουργήθηκαν με επιτυχία", + "backupCodesCopied": "Οι εφεδρικοί κωδικοί αντιγράφηκαν στο πρόχειρο", + "setupFailed": "Αποτυχία δημιουργίας ρύθμισης 2FA", + "verificationFailed": "Μη έγκυρος κωδικός επαλήθευσης", + "disableFailed": "Αποτυχία απενεργοποίησης 2FA. Ελέγξτε τον κωδικό σας.", + "backupCodesFailed": "Αποτυχία δημιουργίας εφεδρικών κωδικών", + "backupCodesCopyFailed": "Αποτυχία αντιγραφής εφεδρικών κωδικών", + "statusLoadFailed": "Αποτυχία φόρτωσης κατάστασης 2FA", + "enterVerificationCode": "Εισαγάγετε τον κωδικό επαλήθευσης", + "enterPassword": "Εισαγάγετε τον κωδικό σας", + "deviceTrusted": "Αυτή η συσκευή σημειώθηκε ως έμπιστη για 30 ημέρες" + }, + "errors": { + "invalidVerificationCode": "Μη έγκυρος κωδικός επαλήθευσης", + "invalidTwoFactorCode": "Μη έγκυρος κωδικός δύο παραγόντων", + "twoFactorRequired": "Απαιτείται έλεγχος δύο παραγόντων", + "twoFactorAlreadyEnabled": "Ο έλεγχος δύο παραγόντων είναι ήδη ενεργός", + "twoFactorNotEnabled": "Ο έλεγχος δύο παραγόντων δεν είναι ενεργός", + "passwordVerificationRequired": "Απαιτείται επαλήθευση κωδικού", + "invalidPassword": "Μη έγκυρος κωδικός", + "userNotFound": "Ο χρήστης δεν βρέθηκε" + }, + "deviceNames": { + "unknownDevice": "Άγνωστη συσκευή", + "browsers": { + "chrome": "Chrome", + "firefox": "Firefox", + "safari": "Safari", + "edge": "Edge" + }, + "platforms": { + "windows": " σε Windows", + "macos": " σε macOS", + "linux": " σε Linux", + "iphone": " σε iPhone", + "android": " σε Android" + } + } + }, + "uploadFile": { + "title": "Μεταφόρτωση αρχείου", + "multipleTitle": "Μεταφόρτωση αρχείων", + "selectFile": "Κάντε κλικ για επιλογή αρχείου", + "selectMultipleFiles": "Κάντε κλικ για επιλογή ενός ή πολλών αρχείων", + "dragAndDrop": "ή σύρετε και αφήστε αρχεία εδώ", + "filesQueued": "{count, plural, one {# αρχείο σε ουρά μεταφόρτωσης} other {# αρχεία σε ουρά μεταφόρτωσης}}", + "preview": "Προεπισκόπηση", + "uploadProgress": "Πρόοδος μεταφόρτωσης", + "upload": "Μεταφόρτωση", + "startUploads": "Έναρξη μεταφορτώσεων", + "retry": "Επανάληψη", + "finish": "Τέλος", + "success": "Το αρχείο μεταφορτώθηκε με επιτυχία", + "allSuccess": "{count, plural, =1 {Το αρχείο μεταφορτώθηκε με επιτυχία} other {# αρχεία μεταφορτώθηκαν με επιτυχία}}", + "partialSuccess": "{success} αρχεία μεταφορτώθηκαν, {error} απέτυχαν", + "error": "Αποτυχία μεταφόρτωσης αρχείου", + "fileSizeExceeded": "Το μέγεθος αρχείου υπερβαίνει το όριο {maxsizemb}MB.", + "insufficientStorage": "Ανεπαρκής χώρος αποθήκευσης. Διαθέσιμα {availablespace}MB.", + "unauthorized": "Μη εξουσιοδοτημένο: απαιτείται έγκυρο διακριτικό για πρόσβαση σε αυτόν τον πόρο.", + "globalDrop": { + "title": "Αφήστε αρχεία για μεταφόρτωση", + "description": "Αφήστε για να μεταφορτωθούν τα αρχεία σας" + }, + "confirmCancel": { + "title": "Ακύρωση μεταφορτώσεων", + "messageSingle": "Υπάρχει μία μεταφόρτωση σε εξέλιξη.", + "messageMultiple": "Υπάρχουν {count} μεταφορτώσεις σε εξέλιξη.", + "warning": "Αν κλείσετε τώρα, οι μεταφορτώσεις θα ακυρωθούν και η πρόοδος θα χαθεί.", + "continue": "Συνέχιση μεταφορτώσεων", + "cancel": "Ακύρωση μεταφορτώσεων" + }, + "pasteSuccess": "{count, plural, =1 {Η εικόνα επικολλήθηκε και μεταφορτώθηκε με επιτυχία} other {# εικόνες επικολλήθηκαν και μεταφορτώθηκαν με επιτυχία}}" + }, + "users": { + "modes": { + "create": "δημιουργία", + "edit": "επεξεργασία" + }, + "errors": { + "loadFailed": "Αποτυχία φόρτωσης χρηστών", + "submitFailed": "Αποτυχία {mode} χρήστη", + "deleteFailed": "Αποτυχία διαγραφής χρήστη", + "statusUpdateFailed": "Αποτυχία ενημέρωσης κατάστασης χρήστη" + }, + "messages": { + "createSuccess": "Ο χρήστης δημιουργήθηκε με επιτυχία", + "updateSuccess": "Ο χρήστης ενημερώθηκε με επιτυχία", + "deleteSuccess": "Ο χρήστης διαγράφηκε με επιτυχία", + "activateSuccess": "Ο χρήστης ενεργοποιήθηκε με επιτυχία", + "deactivateSuccess": "Ο χρήστης απενεργοποιήθηκε με επιτυχία" + }, + "actions": { + "edit": "Επεξεργασία", + "activate": "Ενεργοποίηση", + "deactivate": "Απενεργοποίηση", + "delete": "Διαγραφή" + }, + "delete": { + "title": "Επιβεβαίωση διαγραφής χρήστη", + "confirmation": "Είστε βέβαιοι ότι θέλετε να διαγράψετε τον χρήστη {firstName} {lastName}; Η ενέργεια δεν αναιρείται.", + "confirm": "Διαγραφή χρήστη" + }, + "form": { + "titleCreate": "Προσθήκη νέου χρήστη", + "titleEdit": "Επεξεργασία χρήστη", + "firstName": "Όνομα", + "lastName": "Επώνυμο", + "username": "Όνομα χρήστη", + "email": "Email", + "password": "Κωδικός πρόσβασης", + "newPassword": "Νέος κωδικός (προαιρετικό)", + "passwordPlaceholder": "Αφήστε κενό για διατήρηση τρέχοντος κωδικού", + "role": "Ρόλος", + "roleUser": "Χρήστης", + "roleAdmin": "Διαχειριστής", + "create": "Δημιουργία", + "save": "Αποθήκευση" + }, + "status": { + "title": "Επιβεβαίωση αλλαγής κατάστασης", + "confirmation": "Είστε βέβαιοι ότι θέλετε να {action} τον χρήστη {firstName} {lastName};", + "activate": "Ενεργοποίηση", + "deactivate": "Απενεργοποίηση", + "user": "Χρήστης" + }, + "header": { + "title": "Διαχείριση χρηστών", + "addUser": "Προσθήκη χρήστη", + "management": "Διαχείριση χρηστών" + }, + "table": { + "user": "ΧΡΗΣΤΗΣ", + "email": "EMAIL", + "status": "ΚΑΤΑΣΤΑΣΗ", + "role": "ΡΟΛΟΣ", + "actions": "ΕΝΕΡΓΕΙΕΣ", + "active": "Ενεργός", + "inactive": "Ανενεργός", + "admin": "Διαχειριστής", + "userr": "Χρήστης" + }, + "invite": { + "button": "Δημιουργία συνδέσμου πρόσκλησης", + "title": "Δημιουργία συνδέσμου πρόσκλησης χρήστη", + "description": "Δημιουργήστε έναν σύνδεσμο μιας χρήσης που επιτρέπει σε κάποιον να δημιουργήσει τον δικό του λογαριασμό. Ο σύνδεσμος λήγει σε 15 λεπτά.", + "generating": "Γίνεται δημιουργία...", + "generate": "Δημιουργία συνδέσμου", + "generated": "Ο σύνδεσμος πρόσκλησης δημιουργήθηκε με επιτυχία!", + "linkReady": "Ο σύνδεσμος πρόσκλησης είναι έτοιμος", + "linkReadyDescription": "Μοιραστείτε αυτόν τον σύνδεσμο με το άτομο που θέλετε να προσκαλέσετε. Θα μπορούν να δημιουργήσουν τον δικό τους λογαριασμό ως κανονικός χρήστης.", + "copyLink": "Αντιγραφή συνδέσμου", + "linkCopied": "Ο σύνδεσμος πρόσκλησης αντιγράφηκε στο πρόχειρο!", + "expiresIn": "Λήγει σε 15 λεπτά", + "close": "Κλείσιμο", + "errors": { + "generateFailed": "Αποτυχία δημιουργίας συνδέσμου πρόσκλησης" + } + } + }, + "embedCode": { + "title": "Ενσωμάτωση πολυμέσων", + "description": "Χρησιμοποιήστε αυτούς τους κώδικες για ενσωμάτωση πολυμέσων σε φόρουμ, ιστοσελίδες ή άλλες πλατφόρμες", + "tabs": { + "directLink": "Άμεσος σύνδεσμος", + "html": "HTML", + "bbcode": "BBCode" + }, + "directLinkDescription": "Άμεσο URL προς το αρχείο πολυμέσων", + "htmlDescription": "Χρησιμοποιήστε αυτόν τον κώδικα για ενσωμάτωση σε σελίδες HTML", + "bbcodeDescription": "Χρησιμοποιήστε αυτόν τον κώδικα για φόρουμ που υποστηρίζουν BBCode" + }, + "validation": { + "firstNameRequired": "Απαιτείται όνομα", + "lastNameRequired": "Απαιτείται επώνυμο", + "usernameLength": "Το όνομα χρήστη πρέπει να έχει τουλάχιστον 3 χαρακτήρες", + "usernameSpaces": "Το όνομα χρήστη δεν μπορεί να περιέχει κενά", + "invalidEmail": "Παρακαλώ εισαγάγετε έγκυρη διεύθυνση email", + "passwordLength": "Ο κωδικός πρέπει να έχει τουλάχιστον 8 χαρακτήρες", + "passwordsMatch": "Οι κωδικοί πρέπει να ταιριάζουν", + "emailRequired": "Απαιτείται email", + "emailOrUsernameRequired": "Απαιτείται email ή όνομα χρήστη", + "passwordRequired": "Απαιτείται κωδικός", + "passwordMinLength": "Ο κωδικός πρέπει να έχει τουλάχιστον 6 χαρακτήρες", + "nameRequired": "Απαιτείται όνομα", + "required": "Αυτό το πεδίο είναι υποχρεωτικό" + }, + "registerWithInvite": { + "title": "Δημιουργήστε τον Λογαριασμό Σας", + "description": "Συμπληρώστε τις πληροφορίες παρακάτω για να δημιουργήσετε τον λογαριασμό σας", + "labels": { + "firstName": "Όνομα", + "firstNamePlaceholder": "Εισαγάγετε το όνομα σας", + "lastName": "Επώνυμο", + "lastNamePlaceholder": "Εισαγάγετε το επώνυμό σας", + "username": "Όνομα χρήστη", + "usernamePlaceholder": "Επιλέξτε ένα όνομα χρήστη", + "email": "Email", + "emailPlaceholder": "Εισαγάγετε το email σας", + "password": "Κωδικός πρόσβασης", + "passwordPlaceholder": "Επιλέξτε έναν κωδικό πρόσβασης", + "confirmPassword": "Επιβεβαίωση κωδικού πρόσβασης", + "confirmPasswordPlaceholder": "Επιβεβαιώστε τον κωδικό πρόσβασής σας" + }, + "buttons": { + "creating": "Γίνεται δημιουργία λογαριασμού...", + "createAccount": "Δημιουργία λογαριασμού" + }, + "validation": { + "firstNameRequired": "Απαιτείται όνομα", + "lastNameRequired": "Απαιτείται επώνυμο", + "usernameMinLength": "Το όνομα χρήστη πρέπει να έχει τουλάχιστον 3 χαρακτήρες", + "invalidEmail": "Μη έγκυρο email", + "passwordMinLength": "Ο κωδικός πρέπει να έχει τουλάχιστον 8 χαρακτήρες", + "passwordsMatch": "Οι κωδικοί πρέπει να ταιριάζουν" + }, + "messages": { + "success": "Ο λογαριασμός δημιουργήθηκε με επιτυχία! Ανακατεύθυνση στη σύνδεση...", + "redirecting": "Γίνεται ανακατεύθυνση στη σύνδεση..." + }, + "errors": { + "invalidToken": "Σφάλμα με τον σύνδεσμο πρόσκλησης", + "tokenUsed": "Αυτός ο σύνδεσμος πρόσκλησης έχει ήδη χρησιμοποιηθεί", + "tokenExpired": "Αυτός ο σύνδεσμος πρόσκλησης έχει λήξει", + "usernameExists": "Το όνομα χρήστη υπάρχει ήδη", + "emailExists": "Το email υπάρχει ήδη", + "createFailed": "Αποτυχία δημιουργίας λογαριασμού. Παρακαλώ δοκιμάστε ξανά." + }, + "pageTitle": "Δημιουργία λογαριασμού" + } +} \ No newline at end of file diff --git a/apps/web/messages/en-US.json b/apps/web/messages/en-US.json index 087cf762..38798de8 100644 --- a/apps/web/messages/en-US.json +++ b/apps/web/messages/en-US.json @@ -1,5 +1,7 @@ { "auth": { + "successfullyAuthenticated": "Successfully authenticated!", + "authenticationFailed": "Authentication failed", "errors": { "account_inactive": "Account inactive. Please contact the administrator.", "registration_disabled": "SSO registration is disabled.", @@ -8,6 +10,10 @@ "auth_failed": "Authentication failed. Please try again." } }, + "contextMenu": { + "newFolder": "New folder", + "uploadFile": "Upload file" + }, "authProviders": { "title": "Authentication Providers", "description": "Configure external authentication providers for SSO", @@ -166,9 +172,16 @@ "maxViewsPlaceholder": "Leave empty for unlimited", "passwordProtection": "Password Protected", "passwordLabel": "Password", + "passwordPlaceholder": "Enter password", "create": "Create Share", "success": "Share created successfully", "error": "Failed to create share", + "errors": { + "nameRequired": "Share name is required", + "selectItems": "Please select at least one file or folder" + }, + "itemsSelected": "{count} items selected", + "selectItemsPrompt": "Select files and folders to share", "tabs": { "shareDetails": "Share Details", "selectFiles": "Select Files" @@ -335,6 +348,7 @@ "uploadNewFiles": "Upload new files to add them", "fileCount": "{count, plural, =1 {file} other {files}}", "filesSelected": "{count, plural, =0 {No files selected} =1 {1 file selected} other {# files selected}}", + "itemsSelected": "{count} items selected", "editFile": "Edit file", "editFolder": "Edit folder", "previewFile": "Preview file", @@ -362,6 +376,11 @@ "bulkDeleteTitle": "Delete Selected Items", "bulkDeleteConfirmation": "Are you sure you want to delete {count, plural, =1 {1 item} other {# items}}? This action cannot be undone.", "totalFiles": "{count, plural, =0 {No files} =1 {1 file} other {# files}}", + "openFolder": "Open folder", + "errors": { + "moveItemsFailed": "Failed to move items. Please try again.", + "cannotMoveHere": "Cannot move items to this location" + }, "empty": { "title": "No files or folders yet", "description": "Upload your first file or create a folder to get started" @@ -539,7 +558,10 @@ "movingTo": "Moving to:", "title": "Move {count, plural, =1 {Item} other {Items}}", "description": "Move {count, plural, =1 {item} other {items}} to a new location", - "success": "Successfully moved {count} {count, plural, =1 {item} other {items}}" + "success": "Successfully moved {count} {count, plural, =1 {item} other {items}}", + "errors": { + "moveFailed": "Failed to move items" + } }, "navbar": { "logoAlt": "App Logo", @@ -700,6 +722,49 @@ "createAdmin": "Create Admin Account" } }, + "registerWithInvite": { + "title": "Create Your Account", + "description": "Fill in the information below to create your account", + "labels": { + "firstName": "First Name", + "firstNamePlaceholder": "Enter your first name", + "lastName": "Last Name", + "lastNamePlaceholder": "Enter your last name", + "username": "Username", + "usernamePlaceholder": "Choose a username", + "email": "Email", + "emailPlaceholder": "Enter your email", + "password": "Password", + "passwordPlaceholder": "Choose a password", + "confirmPassword": "Confirm Password", + "confirmPasswordPlaceholder": "Confirm your password" + }, + "buttons": { + "creating": "Creating account...", + "createAccount": "Create Account" + }, + "validation": { + "firstNameRequired": "First name is required", + "lastNameRequired": "Last name is required", + "usernameMinLength": "Username must be at least 3 characters", + "invalidEmail": "Invalid email", + "passwordMinLength": "Password must be at least 8 characters", + "passwordsMatch": "Passwords must match" + }, + "messages": { + "success": "Account created successfully! Redirecting to login...", + "redirecting": "Redirecting to login..." + }, + "errors": { + "invalidToken": "Error with invite link", + "tokenUsed": "This invite link has already been used", + "tokenExpired": "This invite link has expired", + "usernameExists": "Username already exists", + "emailExists": "Email already exists", + "createFailed": "Failed to create account. Please try again." + }, + "pageTitle": "Create Account" + }, "resetPassword": { "pageTitle": "Reset Password", "header": { @@ -1389,7 +1454,9 @@ "newPasswordLabel": "New Password (leave empty to keep current)", "newPasswordPlaceholder": "Enter new password", "manageFilesTitle": "Manage Files", + "manageFilesDescription": "Select files and folders to include in this share", "manageRecipientsTitle": "Manage Recipients", + "itemsSelected": "{count} items selected", "editSuccess": "Share updated successfully", "editError": "Failed to update share", "bulkDeleteConfirmation": "Are you sure you want to delete {count, plural, =1 {1 share} other {# shares}}? This action cannot be undone.", @@ -1483,6 +1550,9 @@ "creatingZip": "Creating ZIP file...", "zipDownloadSuccess": "ZIP file downloaded successfully", "zipDownloadError": "Failed to create ZIP file", + "errors": { + "multipleDownloadNotSupported": "Multiple share download not yet supported - please download shares individually" + }, "singleShareZipName": "{shareName}_files.zip", "multipleSharesZipName": "{count}_shares_files.zip", "defaultShareName": "Share" @@ -1882,19 +1952,36 @@ "inactive": "Inactive", "admin": "Admin", "userr": "User" + }, + "invite": { + "button": "Generate Invite Link", + "title": "Generate User Invite Link", + "description": "Generate a one-time use link that allows someone to create their own account. The link expires in 15 minutes.", + "generating": "Generating...", + "generate": "Generate Link", + "generated": "Invite link generated successfully!", + "linkReady": "Invite link ready", + "linkReadyDescription": "Share this link with the person you want to invite. They'll be able to create their own account as a regular user.", + "copyLink": "Copy Link", + "linkCopied": "Invite link copied to clipboard!", + "expiresIn": "Expires in 15 minutes", + "close": "Close", + "errors": { + "generateFailed": "Failed to generate invite link" + } } }, "embedCode": { - "title": "Embed Image", - "description": "Use these codes to embed this image in forums, websites, or other platforms", + "title": "Embed Media", + "description": "Use these codes to embed this media in forums, websites, or other platforms", "tabs": { "directLink": "Direct Link", "html": "HTML", "bbcode": "BBCode" }, - "directLinkDescription": "Direct URL to the image file", - "htmlDescription": "Use this code to embed the image in HTML pages", - "bbcodeDescription": "Use this code to embed the image in forums that support BBCode" + "directLinkDescription": "Direct URL to the media file", + "htmlDescription": "Use this code to embed the media in HTML pages", + "bbcodeDescription": "Use this code to embed the media in forums that support BBCode" }, "validation": { "firstNameRequired": "First name is required", diff --git a/apps/web/messages/es-ES.json b/apps/web/messages/es-ES.json index 5d01b3b4..f0aa0d54 100644 --- a/apps/web/messages/es-ES.json +++ b/apps/web/messages/es-ES.json @@ -6,7 +6,13 @@ "token_expired": "Token expirado. Por favor, inténtelo de nuevo.", "config_error": "Error de configuración. Por favor, contacte al soporte.", "auth_failed": "Error de autenticación. Por favor, inténtelo de nuevo." - } + }, + "authenticationFailed": "Autenticación fallida", + "successfullyAuthenticated": "¡Autenticado exitosamente!" + }, + "contextMenu": { + "newFolder": "Nueva carpeta", + "uploadFile": "Subir archivo" }, "authProviders": { "title": "Proveedores de Autenticación", @@ -174,7 +180,14 @@ "tabs": { "shareDetails": "Detalles del compartido", "selectFiles": "Seleccionar archivos" - } + }, + "errors": { + "nameRequired": "El nombre del compartir es obligatorio", + "selectItems": "Por favor seleccione al menos un archivo o carpeta" + }, + "itemsSelected": "{count} elementos seleccionados", + "passwordPlaceholder": "Ingrese contraseña", + "selectItemsPrompt": "Seleccione archivos y carpetas para compartir" }, "customization": { "breadcrumb": "Personalización", @@ -340,7 +353,8 @@ "addToShare": "Agregar a compartición", "removeFromShare": "Quitar de compartición", "saveChanges": "Guardar Cambios", - "editFolder": "Editar carpeta" + "editFolder": "Editar carpeta", + "itemsSelected": "{count} elementos seleccionados" }, "files": { "title": "Todos los Archivos", @@ -376,7 +390,12 @@ "description": "Suba su primer archivo o cree una carpeta para comenzar" }, "files": "archivos", - "folders": "carpetas" + "folders": "carpetas", + "errors": { + "moveItemsFailed": "Error al mover elementos. Por favor, inténtelo de nuevo.", + "cannotMoveHere": "No se pueden mover elementos a esta ubicación" + }, + "openFolder": "Abrir carpeta" }, "filesTable": { "ariaLabel": "Tabla de archivos", @@ -539,7 +558,10 @@ "movingTo": "Moviendo a:", "title": "Mover {count, plural, =1 {elemento} other {elementos}}", "description": "Mover {count, plural, =1 {elemento} other {elementos}} a una nueva ubicación", - "success": "Se movieron exitosamente {count} {count, plural, =1 {elemento} other {elementos}}" + "success": "Se movieron exitosamente {count} {count, plural, =1 {elemento} other {elementos}}", + "errors": { + "moveFailed": "Error al mover elementos" + } }, "navbar": { "logoAlt": "Logo de la aplicación", @@ -1153,8 +1175,6 @@ }, "fileActions": { "edit": "Editar", - "save": "Guardar", - "cancel": "Cancelar", "preview": "Vista previa", "download": "Descargar", "delete": "Eliminar", @@ -1378,16 +1398,6 @@ "deleteConfirmation": "¿Estás seguro de que deseas eliminar esta compartición? Esta acción no se puede deshacer.", "addDescriptionPlaceholder": "Agregar descripción...", "editTitle": "Editar Compartir", - "nameLabel": "Nombre del Compartir", - "descriptionLabel": "Descripción", - "descriptionPlaceholder": "Ingrese una descripción (opcional)", - "expirationLabel": "Fecha de Expiración", - "expirationPlaceholder": "DD/MM/AAAA HH:MM", - "maxViewsLabel": "Vistas Máximas", - "maxViewsPlaceholder": "Deje vacío para ilimitado", - "passwordProtection": "Protegido por Contraseña", - "passwordLabel": "Contraseña", - "passwordPlaceholder": "Ingrese contraseña", "newPasswordLabel": "Nueva Contraseña (deje vacío para mantener la actual)", "newPasswordPlaceholder": "Ingrese nueva contraseña", "manageFilesTitle": "Administrar Archivos", @@ -1405,7 +1415,9 @@ "linkDescriptionFile": "Genere un enlace personalizado para compartir el archivo", "linkDescriptionFolder": "Genere un enlace personalizado para compartir la carpeta", "linkReady": "Su enlace de compartición está listo:", - "linkTitle": "Generar enlace" + "linkTitle": "Generar enlace", + "itemsSelected": "{count} elementos seleccionados", + "manageFilesDescription": "Selecciona archivos y carpetas para incluir en este compartido" }, "shareDetails": { "title": "Detalles del Compartir", @@ -1421,7 +1433,6 @@ "noLink": "Ningún enlace generado aún", "copyLink": "Copiar enlace", "openLink": "Abrir en nueva pestaña", - "linkCopied": "Enlace copiado al portapapeles", "views": "Visualizaciones", "dates": "Fechas", "created": "Creado", @@ -1469,28 +1480,6 @@ "expires": "Expira:", "expirationDate": "Fecha de Expiración" }, - "shareFile": { - "title": "Compartir Archivo", - "linkTitle": "Generar Enlace", - "nameLabel": "Nombre del Compartir", - "namePlaceholder": "Ingrese nombre del compartir", - "descriptionLabel": "Descripción", - "descriptionPlaceholder": "Ingrese una descripción (opcional)", - "expirationLabel": "Fecha de Expiración", - "expirationPlaceholder": "DD/MM/AAAA HH:MM", - "maxViewsLabel": "Vistas Máximas", - "maxViewsPlaceholder": "Deje vacío para ilimitado", - "passwordProtection": "Protegido por Contraseña", - "passwordLabel": "Contraseña", - "passwordPlaceholder": "Ingrese contraseña", - "linkDescription": "Genere un enlace personalizado para compartir el archivo", - "aliasLabel": "Alias del Enlace", - "aliasPlaceholder": "Ingrese alias personalizado", - "linkReady": "Su enlace de compartir está listo:", - "createShare": "Crear Compartir", - "generateLink": "Generar Enlace", - "copyLink": "Copiar Enlace" - }, "shareManager": { "deleteSuccess": "Compartición eliminada exitosamente", "deleteError": "Error al eliminar la compartición", @@ -1520,7 +1509,10 @@ "noFilesToDownload": "No hay archivos disponibles para descargar", "singleShareZipName": "{Sharename} _files.zip", "zipDownloadError": "No se pudo crear un archivo zip", - "zipDownloadSuccess": "Archivo zip descargado correctamente" + "zipDownloadSuccess": "Archivo zip descargado correctamente", + "errors": { + "multipleDownloadNotSupported": "Descarga múltiple de compartidos aún no soportada - por favor descarga los compartidos individualmente" + } }, "shareMultipleFiles": { "title": "Compartir Múltiples Archivos", @@ -1917,6 +1909,23 @@ "inactive": "Inactivo", "admin": "Administrador", "userr": "Usuario" + }, + "invite": { + "button": "Generar Enlace de Invitación", + "title": "Generar Enlace de Invitación de Usuario", + "description": "Genera un enlace de un solo uso que permite a alguien crear su propia cuenta. El enlace expira en 15 minutos.", + "generating": "Generando...", + "generate": "Generar Enlace", + "generated": "¡Enlace de invitación generado exitosamente!", + "linkReady": "Enlace de invitación listo", + "linkReadyDescription": "Comparte este enlace con la persona que deseas invitar. Podrán crear su propia cuenta como usuario regular.", + "copyLink": "Copiar Enlace", + "linkCopied": "¡Enlace de invitación copiado al portapapeles!", + "expiresIn": "Expira en 15 minutos", + "close": "Cerrar", + "errors": { + "generateFailed": "Error al generar el enlace de invitación" + } } }, "validation": { @@ -1935,15 +1944,58 @@ "required": "Este campo es obligatorio" }, "embedCode": { - "title": "Insertar imagen", - "description": "Utiliza estos códigos para insertar esta imagen en foros, sitios web u otras plataformas", + "title": "Insertar multimedia", + "description": "Utiliza estos códigos para insertar este contenido multimedia en foros, sitios web u otras plataformas", "tabs": { "directLink": "Enlace directo", "html": "HTML", "bbcode": "BBCode" }, - "directLinkDescription": "URL directa al archivo de imagen", - "htmlDescription": "Utiliza este código para insertar la imagen en páginas HTML", - "bbcodeDescription": "Utiliza este código para insertar la imagen en foros que admiten BBCode" + "directLinkDescription": "URL directa al archivo multimedia", + "htmlDescription": "Utiliza este código para insertar el contenido multimedia en páginas HTML", + "bbcodeDescription": "Utiliza este código para insertar el contenido multimedia en foros que admiten BBCode" + }, + "registerWithInvite": { + "title": "Crea Tu Cuenta", + "description": "Completa la información a continuación para crear tu cuenta", + "labels": { + "firstName": "Nombre", + "firstNamePlaceholder": "Ingresa tu nombre", + "lastName": "Apellido", + "lastNamePlaceholder": "Ingresa tu apellido", + "username": "Nombre de Usuario", + "usernamePlaceholder": "Elige un nombre de usuario", + "email": "Correo Electrónico", + "emailPlaceholder": "Ingresa tu correo electrónico", + "password": "Contraseña", + "passwordPlaceholder": "Elige una contraseña", + "confirmPassword": "Confirmar Contraseña", + "confirmPasswordPlaceholder": "Confirma tu contraseña" + }, + "buttons": { + "creating": "Creando cuenta...", + "createAccount": "Crear Cuenta" + }, + "validation": { + "firstNameRequired": "El nombre es obligatorio", + "lastNameRequired": "El apellido es obligatorio", + "usernameMinLength": "El nombre de usuario debe tener al menos 3 caracteres", + "invalidEmail": "Correo electrónico inválido", + "passwordMinLength": "La contraseña debe tener al menos 8 caracteres", + "passwordsMatch": "Las contraseñas deben coincidir" + }, + "messages": { + "success": "¡Cuenta creada exitosamente! Redirigiendo al inicio de sesión...", + "redirecting": "Redirigiendo al inicio de sesión..." + }, + "errors": { + "invalidToken": "Error con el enlace de invitación", + "tokenUsed": "Este enlace de invitación ya ha sido utilizado", + "tokenExpired": "Este enlace de invitación ha expirado", + "usernameExists": "El nombre de usuario ya existe", + "emailExists": "El correo electrónico ya existe", + "createFailed": "Error al crear la cuenta. Por favor, inténtalo de nuevo." + }, + "pageTitle": "Crear Cuenta" } } \ No newline at end of file diff --git a/apps/web/messages/fa-IR.json b/apps/web/messages/fa-IR.json new file mode 100644 index 00000000..4959a290 --- /dev/null +++ b/apps/web/messages/fa-IR.json @@ -0,0 +1,2001 @@ +{ + "auth": { + "successfullyAuthenticated": "با موفقیت وارد شدید!", + "authenticationFailed": "احراز هویت ناموفق بود", + "errors": { + "account_inactive": "حساب کاربری غیرفعال است. لطفاً با مدیر تماس بگیرید.", + "registration_disabled": "ثبت‌نام از طریق SSO غیرفعال است.", + "token_expired": "توکن منقضی شده است. لطفاً دوباره تلاش کنید.", + "config_error": "خطای پیکربندی. لطفاً با پشتیبانی تماس بگیرید.", + "auth_failed": "احراز هویت ناموفق بود. لطفاً دوباره تلاش کنید." + } + }, + "contextMenu": { + "newFolder": "پوشه جدید", + "uploadFile": "بارگذاری فایل" + }, + "authProviders": { + "title": "ارائه‌دهندگان احراز هویت", + "description": "ارائه‌دهندگان احراز هویت خارجی را برای SSO پیکربندی کنید", + "enabledCount": "{count} فعال", + "loadingProviders": "در حال بارگذاری ارائه‌دهندگان...", + "providersConfigured": "{count} ارائه‌دهنده پیکربندی شد", + "enabledOfTotal": "{enabled} فعال از {total} ارائه‌دهنده", + "hideDisabledProviders": "پنهان کردن ارائه‌دهندگان غیرفعال", + "addProvider": "افزودن ارائه‌دهنده", + "addProviderTitle": "افزودن ارائه‌دهنده", + "editProvider": "ویرایش ارائه‌دهنده", + "deleteProvider": "حذف ارائه‌دهنده", + "enabled": "فعال", + "disabled": "غیرفعال", + "officialProvider": "ارائه‌دهنده رسمی", + "dragToReorder": "برای تغییر ترتیب بکشید", + "dragDisabledMessage": "در هنگام فیلتر کردن ارائه‌دهندگان، کشیدن و رها کردن غیرفعال است. برای تغییر ترتیب، همه را نمایش دهید.", + "dragEnabledMessage": "برای تغییر ترتیب ارائه‌دهندگان را بکشید. این ترتیب در صفحه ورود نمایش داده می‌شود.", + "noProvidersEnabled": "هیچ ارائه‌دهنده احراز هویت فعالی وجود ندارد", + "noProvidersConfigured": "هیچ ارائه‌دهنده احراز هویتی پیکربندی نشده است", + "form": { + "providerName": "نام ارائه‌دهنده", + "providerNamePlaceholder": "مثلاً: mycompany", + "displayName": "نام نمایشی", + "displayNamePlaceholder": "مثلاً: My Company SSO", + "type": "نوع", + "typeOidc": "OIDC (OpenID Connect)", + "typeOauth2": "OAuth 2.0", + "icon": "آیکون", + "iconPlaceholder": "یک آیکون انتخاب کنید", + "clientId": "Client ID", + "clientIdPlaceholder": "Client ID مربوط به OAuth شما", + "clientSecret": "Client Secret", + "clientSecretPlaceholder": "Client Secret مربوط به OAuth شما", + "oauthScopes": "Scopes در OAuth", + "scopesPlaceholder": "Scopes را وارد کنید (مثلاً: openid, profile, email)", + "scopesHelpOidc": "Scopes بر اساس URL ارائه‌دهنده به‌صورت خودکار پیشنهاد می‌شوند. Scopes رایج OIDC: openid، profile، email، groups", + "scopesHelpOauth2": "Scopes بر اساس URL ارائه‌دهنده به‌صورت خودکار پیشنهاد می‌شوند. Scopes رایج OAuth2 بسته به ارائه‌دهنده متفاوت است", + "providerUrl": "URL ارائه‌دهنده", + "providerUrlPlaceholder": "https://auth.example.com", + "providerUrlAutoPlaceholder": "https://your-provider.com (نقاط پایانی به‌صورت خودکار کشف می‌شوند)", + "providerUrlManualPlaceholder": "https://your-provider.com", + "autoDiscoveryHelp": "سیستم به‌صورت خودکار نقاط پایانی Authorization، Token و UserInfo را کشف می‌کند", + "manualConfigurationHelp": "URL پایه ارائه‌دهنده شما (نقاط پایانی نسبت به این تنظیم می‌شوند)", + "authorizationEndpoint": "Endpoint مجوز (Authorization)", + "authorizationEndpointPlaceholder": "https://auth.example.com/auth", + "tokenEndpoint": "Endpoint توکن", + "tokenEndpointPlaceholder": "https://auth.example.com/token", + "userInfoEndpoint": "Endpoint اطلاعات کاربر (User Info)", + "userInfoEndpointPlaceholder": "https://auth.example.com/userinfo", + "configurationMethod": "روش پیکربندی", + "autoDiscovery": "کشف خودکار", + "autoDiscoveryDescription": "کشف خودکار نقاط پایانی از طریق URL ارائه‌دهنده", + "manualEndpoints": "نقاط پایانی دستی (پیشنهادی)", + "manualEndpointsDescription": "پیکربندی دستی نقاط پایانی Authorization، Token و User Info", + "callbackUrl": "Callback URL", + "callbackUrlDescription": "این URL را در پیکربندی ارائه‌دهنده OAuth خود استفاده کنید", + "copyCallbackUrl": "کپی Callback URL", + "callbackUrlCopied": "Callback URL در کلیپ‌بورد کپی شد!", + "adminEmailDomains": "دامنه‌های ایمیل مدیر", + "adminEmailDomainsPlaceholder": "دامنه‌ها را وارد کنید (مثلاً: admin.company.com)", + "adminEmailDomainsHelp": "کاربرانی که ایمیل آن‌ها از این دامنه‌ها است، دسترسی مدیر دریافت می‌کنند", + "autoRegister": "ثبت‌نام خودکار کاربران جدید", + "officialProviderUrlPlaceholder": "جای‌نگهدار را با URL مربوط به {displayName} جایگزین کنید", + "officialProviderHelp": "این یک ارائه‌دهنده رسمی است. نقاط پایانی از پیش پیکربندی شده‌اند. فقط می‌توانید این URL را ویرایش کنید.", + "officialProviderIconHelp": "می‌توانید آیکون این ارائه‌دهنده رسمی را شخصی‌سازی کنید." + }, + "buttons": { + "cancel": "انصراف", + "save": "ذخیره", + "saving": "در حال ذخیره...", + "adding": "در حال افزودن...", + "updating": "در حال به‌روزرسانی...", + "saveProvider": "ذخیره ارائه‌دهنده", + "delete": "حذف", + "deleting": "در حال حذف...", + "edit": "ویرایش", + "enable": "فعال‌سازی", + "disable": "غیرفعال‌سازی" + }, + "messages": { + "providerAdded": "ارائه‌دهنده با موفقیت افزوده شد", + "providerUpdated": "ارائه‌دهنده با موفقیت به‌روزرسانی شد", + "providerDeleted": "ارائه‌دهنده با موفقیت حذف شد", + "providerOrderUpdated": "ترتیب ارائه‌دهندگان با موفقیت به‌روزرسانی شد", + "fillRequiredFields": "لطفاً همه فیلدهای ضروری (نام، نام نمایشی، Client ID، Client Secret) را پر کنید", + "provideUrlOrEndpoints": "یا URL ارائه‌دهنده را برای کشف خودکار وارد کنید یا هر سه نقطه پایانی سفارشی را فراهم کنید", + "chooseDiscoveryOrManual": "یا کشف خودکار (URL ارائه‌دهنده) یا نقاط پایانی دستی را انتخاب کنید؛ نه هر دو", + "loadFailed": "بارگذاری ارائه‌دهندگان ناموفق بود", + "addFailed": "افزودن ارائه‌دهنده ناموفق بود", + "updateFailed": "به‌روزرسانی ارائه‌دهنده ناموفق بود", + "deleteFailed": "حذف ارائه‌دهنده ناموفق بود", + "orderUpdateFailed": "به‌روزرسانی ترتیب ارائه‌دهندگان ناموفق بود" + }, + "info": { + "title": "اطلاعات", + "officialProvidersRecommended": "برای عملکرد بهتر، استفاده از ارائه‌دهندگان رسمی را در نظر بگیرید. اگر با ارائه‌دهنده سفارشی مشکلی دارید، یک Issue در", + "github": "GitHub", + "officialProvider": "ارائه‌دهنده رسمی", + "officialProviderDescription": "این ارائه‌دهنده توسط Palmr بهینه شده است. فقط می‌توانید اعتبارنامه‌ها و پیکربندی را تغییر دهید.", + "manualConfigTitle": "پیکربندی دستی", + "manualConfigDescription": "شما همه نقاط پایانی را به‌صورت دستی وارد می‌کنید. مطمئن شوید برای ارائه‌دهنده شما صحیح هستند." + }, + "deleteModal": { + "title": "حذف ارائه‌دهنده احراز هویت", + "confirmMessage": "آیا از حذف ارائه‌دهنده \"{displayName}\" مطمئن هستید؟ این عمل قابل بازگشت نیست.", + "providerId": "شناسه ارائه‌دهنده: {name}", + "cancel": "انصراف", + "delete": "حذف ارائه‌دهنده", + "deleting": "در حال حذف..." + } + }, + "bulkDownload": { + "title": "دانلود دسته‌جمعی", + "zipNameLabel": "نام فایل ZIP", + "zipNamePlaceholder": "نام فایل را وارد کنید", + "description": "{count, plural, =1 {۱ فایل فشرده خواهد شد} other {# فایل فشرده خواهد شد}}", + "download": "دانلود ZIP" + }, + "common": { + "loading": "در حال بارگذاری، لطفاً صبر کنید...", + "loadingSimple": "در حال بارگذاری...", + "cancel": "انصراف", + "save": "ذخیره", + "saving": "در حال ذخیره...", + "update": "به‌روزرسانی", + "updating": "در حال به‌روزرسانی...", + "delete": "حذف", + "deleting": "در حال حذف...", + "close": "بستن", + "download": "دانلود", + "unexpectedError": "خطای غیرمنتظره‌ای رخ داد. لطفاً دوباره تلاش کنید.", + "yes": "بله", + "no": "خیر", + "dashboard": "داشبورد", + "back": "بازگشت", + "click": "برای کلیک کنید", + "creating": "در حال ایجاد...", + "create": "ایجاد", + "rename": "تغییر نام", + "move": "انتقال", + "share": "اشتراک‌گذاری", + "search": "جست‌وجو", + "copy": "کپی", + "copied": "کپی شد" + }, + "createShare": { + "title": "ایجاد اشتراک", + "nameLabel": "نام اشتراک", + "namePlaceholder": "نامی برای اشتراک خود وارد کنید", + "descriptionLabel": "توضیحات", + "descriptionPlaceholder": "توضیحات وارد کنید (اختیاری)", + "expirationLabel": "تاریخ انقضا", + "expirationPlaceholder": "MM/DD/YYYY HH:MM", + "maxViewsLabel": "حداکثر بازدید", + "maxViewsPlaceholder": "برای نامحدود خالی بگذارید", + "passwordProtection": "محافظت با گذرواژه", + "passwordLabel": "گذرواژه", + "passwordPlaceholder": "گذرواژه را وارد کنید", + "create": "ایجاد اشتراک", + "success": "اشتراک با موفقیت ایجاد شد", + "error": "ایجاد اشتراک ناموفق بود", + "errors": { + "nameRequired": "نام اشتراک الزامی است", + "selectItems": "لطفاً حداقل یک فایل یا پوشه انتخاب کنید" + }, + "itemsSelected": "{count} مورد انتخاب شد", + "selectItemsPrompt": "فایل‌ها و پوشه‌ها را برای اشتراک‌گذاری انتخاب کنید", + "tabs": { + "shareDetails": "جزئیات اشتراک", + "selectFiles": "انتخاب فایل‌ها" + }, + "nextSelectFiles": "بعدی: انتخاب فایل‌ها", + "searchLabel": "جست‌وجو" + }, + "customization": { + "breadcrumb": "شخصی‌سازی", + "colors": { + "title": "رنگ‌های تم", + "description": "رنگ اصلی تم ترجیحی خود را انتخاب کنید", + "presets": "رنگ‌های موجود", + "presetsDescription": "از تم‌های رنگی موجود انتخاب کنید", + "reset": "بازنشانی به پیش‌فرض" + }, + "fonts": { + "title": "تایپوگرافی", + "description": "خانواده فونت ترجیحی خود را انتخاب کنید", + "available": "فونت‌های موجود", + "availableDescription": "از خانواده‌های فونت موجود انتخاب کنید", + "reset": "بازنشانی به پیش‌فرض" + }, + "radius": { + "title": "شعاع گوشه", + "description": "میزان گردی عناصر رابط کاربری را تنظیم کنید", + "available": "گزینه‌های گردی", + "availableDescription": "انتخاب کنید گوشه‌ها چقدر گرد باشند", + "reset": "بازنشانی به پیش‌فرض" + }, + "background": { + "title": "رنگ‌های پس‌زمینه", + "description": "رنگ‌های پس‌زمینه برای حالت روشن و تیره را تنظیم کنید", + "lightMode": "حالت روشن", + "darkMode": "حالت تیره", + "availableDescription": "رنگ‌های پس‌زمینه را برای تم‌های روشن و تیره انتخاب کنید", + "reset": "بازنشانی به پیش‌فرض" + }, + "theme": { + "title": "حالت تم", + "description": "بین تم روشن، تیره یا سیستم انتخاب کنید", + "selectTheme": "ترجیح تم", + "availableDescription": "حالت تم ترجیحی خود را انتخاب کنید", + "reset": "بازنشانی به سیستم" + }, + "pageTitle": "شخصی‌سازی" + }, + "dashboard": { + "loadError": "بارگذاری داده‌های داشبورد ناموفق بود", + "linkCopied": "لینک در کلیپ‌بورد کپی شد", + "pageTitle": "داشبورد", + "breadcrumb": "داشبورد", + "recentFiles": { + "title": "فایل‌های اخیر", + "description": "آخرین فایل‌های بارگذاری‌شده شما" + } + }, + "deleteConfirmation": { + "filesToDelete": "فایل‌های قابل حذف", + "foldersToDelete": "پوشه‌های قابل حذف", + "itemsToDelete": "موارد قابل حذف", + "sharesToDelete": "اشتراک‌های قابل حذف" + }, + "downloadQueue": { + "downloadQueued": "دانلود در صف قرار گرفت: {fileName}", + "queuedDescription": "دانلود شما به صورت خودکار شروع خواهد شد وقتی جایی خالی شود", + "queuePosition": "دانلود در موقعیت {position} قرار گرفت: {fileName}", + "estimatedWait": "زمان انتظار تخمینی: {time}", + "queueFull": "صف دانلود پر است", + "queueFullDescription": "لطفاً چند دقیقه دیگر دوباره امتحان کنید وقتی صف جای خالی داشت", + "cancelSuccess": "دانلود با موفقیت لغو شد", + "cancelError": "لغو دانلود ناموفق بود: {error}", + "status": { + "pending": "در حال آماده‌سازی...", + "queued": "در صف", + "downloading": "در حال دانلود", + "completed": "کامل شد", + "failed": "ناموفق" + }, + "waitTime": { + "seconds": "{seconds} ثانیه", + "minutes": "{minutes} دقیقه", + "hoursMinutes": "{hours} ساعت {minutes} دقیقه" + }, + "indicator": { + "title": "دانلودها", + "downloads": "صف دانلود", + "active": "فعال", + "queued": "در صف", + "position": "موقعیت {position}", + "estimatedWait": "انتظار: {time}", + "unknownFile": "فایل ناشناخته", + "noDownloads": "هیچ دانلودی در حال انجام نیست", + "refresh": "بروزرسانی صف" + } + }, + "emptyState": { + "noFiles": "هنوز فایلی بارگذاری نشده است", + "uploadFile": "بارگذاری فایل" + }, + "errors": { + "invalidCredentials": "ایمیل یا گذرواژه نامعتبر است", + "userNotFound": "کاربر یافت نشد", + "accountLocked": "حساب کاربری قفل شده است. لطفاً بعداً دوباره امتحان کنید", + "unexpectedError": "خطای غیرمنتظره‌ای رخ داد. لطفاً دوباره امتحان کنید", + "Invalid verification code": "کد تأیید نامعتبر است", + "Two-factor authentication is already enabled": "احراز هویت دو مرحله‌ای قبلاً فعال شده است", + "Two-factor authentication is not enabled": "احراز هویت دو مرحله‌ای فعال نیست", + "Invalid password": "گذرواژه نامعتبر است", + "Password verification required": "تأیید گذرواژه الزامی است", + "Invalid two-factor authentication code": "کد احراز هویت دو مرحله‌ای نامعتبر است", + "Two-factor authentication required": "احراز هویت دو مرحله‌ای الزامی است", + "noUserData": "داده کاربری وجود ندارد" + }, + "fileActions": { + "editFile": "ویرایش فایل", + "nameLabel": "نام", + "namePlaceholder": "نام جدید را وارد کنید", + "extension": "پسوند", + "descriptionLabel": "توضیحات", + "descriptionPlaceholder": "توضیحات فایل را وارد کنید", + "addDescriptionPlaceholder": "افزودن توضیحات...", + "deleteFile": "حذف فایل", + "deleteConfirmation": "آیا از حذف این فایل اطمینان دارید؟", + "deleteWarning": "این عمل قابل بازگشت نیست." + }, + "fileManager": { + "downloadError": "دانلود فایل ناموفق بود", + "updateSuccess": "فایل با موفقیت به‌روز شد", + "updateError": "به‌روزرسانی فایل ناموفق بود", + "deleteSuccess": "فایل با موفقیت حذف شد", + "deleteError": "حذف فایل ناموفق بود" + }, + "filePreview": { + "title": "پیش‌نمایش فایل", + "description": "پیش‌نمایش و دانلود فایل", + "loading": "در حال بارگذاری...", + "notAvailable": "پیش‌نمایش برای این نوع فایل در دسترس نیست", + "downloadToView": "از دکمه دانلود برای مشاهده این فایل استفاده کنید", + "loadError": "خطا در بارگذاری پیش‌نمایش فایل", + "downloadError": "خطا در دانلود فایل", + "audioNotSupported": "مرورگر شما از پخش صوت پشتیبانی نمی‌کند", + "videoNotSupported": "مرورگر شما از پخش ویدیو پشتیبانی نمی‌کند", + "pdfPreviewNotAvailable": "پیش‌نمایش PDF در دسترس نیست. نمایش جایگزین را امتحان کنید یا دانلود کنید", + "tryAlternativeView": "امتحان نمایش جایگزین", + "loadingAlternative": "در حال بارگذاری نمایش جایگزین...", + "loadingAudio": "در حال بارگذاری صوت..." + }, + "fileSelector": { + "availableFiles": "فایل‌های موجود ({count})", + "shareFiles": "فایل‌های اشتراک‌گذاری شده ({count})", + "shareFilesDescription": "فایل‌های موجود در این اشتراک", + "availableFilesDescription": "فایل‌ها را برای افزودن به این اشتراک انتخاب کنید", + "searchPlaceholder": "جست‌وجوی فایل‌ها...", + "searchSelectedFiles": "جست‌وجوی فایل‌های انتخاب‌شده...", + "noMatchingFiles": "هیچ فایل مطابقی یافت نشد", + "noAvailableFiles": "هیچ فایلی موجود نیست", + "noFilesInShare": "هیچ فایلی در این اشتراک نیست", + "noFilesFound": "هیچ فایلی یافت نشد", + "noFilesFoundWith": "هیچ فایلی با \"{query}\" یافت نشد", + "addFilesFromList": "فایل‌ها را از لیست زیر اضافه کنید", + "tryDifferentSearch": "عبارت‌های جست‌وجوی متفاوتی امتحان کنید", + "allFilesInShare": "همه فایل‌ها از قبل در این اشتراک هستند", + "uploadNewFiles": "فایل‌های جدید را بارگذاری کنید تا اضافه شوند", + "fileCount": "{count, plural, =1 {فایل} other {فایل}}", + "filesSelected": "{count, plural, =0 {هیچ فایلی انتخاب نشده} =1 {۱ فایل انتخاب شده} other {# فایل انتخاب شده}}", + "itemsSelected": "{count} مورد انتخاب شده", + "editFile": "ویرایش فایل", + "editFolder": "ویرایش پوشه", + "previewFile": "پیش‌نمایش فایل", + "addToShare": "افزودن به اشتراک", + "removeFromShare": "حذف از اشتراک", + "saveChanges": "ذخیره تغییرات" + }, + "files": { + "title": "همه فایل‌ها", + "uploadFile": "بارگذاری فایل", + "loadError": "بارگذاری فایل‌ها ناموفق بود", + "pageTitle": "فایل‌های من", + "breadcrumb": "فایل‌های من", + "downloadStart": "دانلود آغاز شد", + "downloadError": "دانلود فایل ناموفق بود", + "updateSuccess": "فایل با موفقیت به‌روز شد", + "updateError": "به‌روزرسانی فایل ناموفق بود", + "deleteSuccess": "فایل با موفقیت حذف شد", + "deleteError": "حذف فایل ناموفق بود", + "bulkDownloadSuccess": "دانلود فایل‌ها با موفقیت آغاز شد", + "bulkDownloadError": "خطا در ایجاد فایل ZIP", + "bulkDownloadFileError": "خطا در دانلود فایل {fileName}", + "bulkDeleteSuccess": "{count, plural, =1 {۱ مورد با موفقیت حذف شد} other {# مورد با موفقیت حذف شد}}", + "bulkDeleteError": "خطا در حذف موارد انتخاب‌شده", + "bulkDeleteTitle": "حذف موارد انتخاب‌شده", + "bulkDeleteConfirmation": "آیا از حذف {count, plural, =1 {۱ مورد} other {# مورد}} اطمینان دارید؟ این عمل قابل بازگشت نیست.", + "totalFiles": "{count, plural, =0 {هیچ فایلی} =1 {۱ فایل} other {# فایل}}", + "openFolder": "باز کردن پوشه", + "errors": { + "moveItemsFailed": "انتقال موارد ناموفق بود. لطفاً دوباره امتحان کنید.", + "cannotMoveHere": "نمی‌توان موارد را به این مکان منتقل کرد" + }, + "empty": { + "title": "هنوز فایل یا پوشه‌ای وجود ندارد", + "description": "اولین فایل خود را بارگذاری کنید یا یک پوشه ایجاد کنید تا شروع کنید" + }, + "files": "فایل‌ها", + "folders": "پوشه‌ها", + "actions": { + "open": "باز کردن", + "rename": "تغییر نام", + "delete": "حذف" + }, + "viewMode": { + "table": "جدول", + "grid": "شبکه" + } + }, + "filesTable": { + "ariaLabel": "جدول فایل‌ها", + "selectAll": "انتخاب همه", + "selectFile": "انتخاب فایل {fileName}", + "columns": { + "name": "نام", + "description": "توضیحات", + "size": "اندازه", + "createdAt": "ایجاد شده در", + "updatedAt": "به‌روز شده در", + "actions": "اقدامات" + }, + "actions": { + "menu": "منوی اقدامات فایل", + "preview": "پیش‌نمایش", + "edit": "ویرایش", + "share": "اشتراک‌گذاری", + "download": "دانلود", + "delete": "حذف" + }, + "bulkActions": { + "selected": "{count, plural, =1 {۱ فایل انتخاب شده} other {# فایل انتخاب شده}}", + "actions": "اقدامات", + "download": "دانلود موارد انتخاب‌شده", + "share": "اشتراک‌گذاری موارد انتخاب‌شده", + "delete": "حذف موارد انتخاب‌شده" + } + }, + "folderActions": { + "editFolder": "ویرایش پوشه", + "folderName": "نام پوشه", + "folderNamePlaceholder": "نام پوشه را وارد کنید", + "folderDescription": "توضیحات", + "folderDescriptionPlaceholder": "توضیحات پوشه را وارد کنید (اختیاری)", + "createFolder": "ایجاد پوشه", + "renameFolder": "تغییر نام پوشه", + "moveFolder": "انتقال پوشه", + "shareFolder": "اشتراک‌گذاری پوشه", + "deleteFolder": "حذف پوشه", + "moveTo": "انتقال به", + "selectDestination": "پوشه مقصد را انتخاب کنید", + "rootFolder": "ریشه", + "folderCreated": "پوشه با موفقیت ایجاد شد", + "folderRenamed": "پوشه با موفقیت تغییر نام یافت", + "folderMoved": "پوشه با موفقیت منتقل شد", + "folderDeleted": "پوشه با موفقیت حذف شد", + "folderShared": "پوشه با موفقیت به اشتراک گذاشته شد", + "createFolderError": "خطا در ایجاد پوشه", + "renameFolderError": "خطا در تغییر نام پوشه", + "moveFolderError": "خطا در انتقال پوشه", + "deleteFolderError": "خطا در حذف پوشه", + "shareFolderError": "خطا در اشتراک‌گذاری پوشه", + "deleteConfirmation": "آیا از حذف این پوشه اطمینان دارید؟", + "deleteWarning": "این عمل قابل بازگشت نیست." + }, + "footer": { + "poweredBy": "قدرت گرفته از", + "kyanHomepage": "صفحه اصلی Kyantech" + }, + "forgotPassword": { + "emailLabel": "آدرس ایمیل", + "emailPlaceholder": "ایمیل خود را وارد کنید", + "sending": "در حال ارسال...", + "submit": "ارسال دستورالعمل‌های بازنشانی", + "backToLogin": "بازگشت به صفحه ورود", + "title": "فراموشی گذرواژه", + "description": "آدرس ایمیل خود را وارد کنید تا دستورالعمل‌های بازنشانی گذرواژه را برای شما ارسال کنیم", + "resetInstructions": "دستورالعمل‌های بازنشانی به ایمیل شما ارسال شد", + "pageTitle": "فراموشی گذرواژه", + "passwordAuthDisabled": "احراز هویت با گذرواژه غیرفعال است. لطفاً با مدیر خود تماس بگیرید یا از یک ارائه‌دهنده احراز هویت خارجی استفاده کنید." + }, + "generateShareLink": { + "generateTitle": "تولید لینک اشتراک", + "updateTitle": "به‌روزرسانی لینک اشتراک", + "generateDescription": "یک لینک سفارشی برای این اشتراک تولید کنید. می‌توانید URL را سفارشی کنید تا به‌یادماندنی‌تر شود.", + "updateDescription": "لینک سفارشی این اشتراک را به‌روز کنید. می‌توانید URL را سفارشی کنید تا به‌یادماندنی‌تر شود.", + "aliasPlaceholder": "شناسه سفارشی برای لینک", + "linkReady": "لینک اشتراک شما آماده است. می‌توانید اکنون آن را کپی کنید.", + "readyDescription": "لینک اشتراک شما آماده است. می‌توانید QR کد را مستقیماً اسکن کنید، آن را برای استفاده بعدی دانلود کنید، یا لینک زیر را کپی کنید.", + "generateButton": "تولید لینک", + "updateButton": "به‌روزرسانی لینک", + "copyButton": "کپی لینک", + "success": "لینک با موفقیت تولید شد", + "error": "تولید لینک ناموفق بود", + "copied": "لینک در کلیپ‌بورد کپی شد", + "tabs": { + "link": "لینک", + "qrcode": "QR کد" + } + }, + "home": { + "description": "جایگزین متن‌باز برای WeTransfer. فایل‌ها را به صورت امن و بدون ردیابی یا محدودیت به اشتراک بگذارید.", + "documentation": "مستندات", + "starOnGithub": "ستاره در GitHub", + "privacyMessage": "با در نظر گرفتن حریم خصوصی ساخته شده. فایل‌های شما فقط قبل از بارگذاری برای کسانی که لینک اشتراک‌گذاری دارند قابل دسترسی هستند. برای همیشه رایگان و متن‌باز.", + "header": { + "fileSharing": "اشتراک‌گذاری فایل", + "tagline": "ساده و رایگان شده" + }, + "pageTitle": "خانه" + }, + "iconPicker": { + "title": "انتخاب آیکون", + "placeholder": "یک آیکون انتخاب کنید", + "searchPlaceholder": "جست‌وجوی آیکون‌ها...", + "loadingMore": "بارگذاری آیکون‌های بیشتر...", + "allIconsLoaded": "همه {count} آیکون بارگذاری شد", + "noIconsFound": "هیچ آیکونی برای \"{search}\" یافت نشد", + "tabs": { + "all": "همه آیکون‌ها", + "popular": "محبوب", + "auth": "ارائه‌دهندگان احراز هویت" + }, + "stats": "{iconCount} آیکون از {libraryCount} کتابخانه", + "categoryBadge": "{category} ({count} آیکون)" + }, + "imageEdit": { + "title": "ویرایش تصویر", + "rotate": "چرخش", + "zoom": "زوم", + "cropInstructions": "برای تغییر موقعیت بکشید، گوشه‌ها را برای تنظیم ناحیه برش تغییر اندازه دهید" + }, + "login": { + "welcome": "خوش آمدید به", + "signInToContinue": "برای ادامه وارد شوید", + "emailOrUsernameLabel": "ایمیل یا نام کاربری", + "emailOrUsernamePlaceholder": "ایمیل یا نام کاربری خود را وارد کنید", + "emailLabel": "آدرس ایمیل", + "emailPlaceholder": "ایمیل خود را وارد کنید", + "passwordLabel": "گذرواژه", + "passwordPlaceholder": "گذرواژه خود را وارد کنید", + "signIn": "ورود", + "signingIn": "در حال ورود...", + "forgotPassword": "گذرواژه را فراموش کردید؟", + "pageTitle": "ورود", + "or": "یا", + "continueWithSSO": "ادامه با SSO", + "processing": "در حال پردازش احراز هویت..." + }, + "logo": { + "labels": { + "appLogo": "لوگوی برنامه" + }, + "buttons": { + "upload": "بارگذاری لوگو", + "remove": "حذف لوگو" + }, + "messages": { + "uploadSuccess": "لوگو با موفقیت بارگذاری شد", + "removeSuccess": "لوگو با موفقیت حذف شد" + }, + "errors": { + "uploadFailed": "بارگذاری لوگو ناموفق بود", + "removeFailed": "حذف لوگو ناموفق بود" + } + }, + "moveItems": { + "itemsToMove": "موارد قابل انتقال:", + "movingTo": "در حال انتقال به:", + "title": "انتقال {count, plural, =1 {مورد} other {موارد}}", + "description": "انتقال {count, plural, =1 {مورد} other {موارد}} به مکان جدید", + "success": "{count} {count, plural, =1 {مورد} other {مورد}} با موفقیت منتقل شد", + "errors": { + "moveFailed": "انتقال موارد ناموفق بود" + } + }, + "navbar": { + "logoAlt": "لوگوی برنامه", + "profileMenu": "منوی پروفایل", + "profile": "پروفایل", + "customization": "شخصی‌سازی", + "settings": "تنظیمات", + "usersManagement": "مدیریت کاربران", + "logout": "خروج" + }, + "navigation": { + "dashboard": "داشبورد" + }, + "notifications": { + "permissionGranted": "اعلان‌های دانلود فعال شد", + "permissionDenied": "اعلان‌های دانلود غیرفعال شد", + "downloadComplete": { + "title": "دانلود کامل شد", + "body": "دانلود {fileName} به پایان رسید" + }, + "downloadFailed": { + "title": "دانلود ناموفق بود", + "body": "دانلود {fileName} ناموفق بود: {error}", + "unknownError": "خطای ناشناخته" + }, + "queueProcessing": { + "title": "شروع دانلود", + "body": "{fileName} اکنون در حال دانلود است{position}", + "position": " (در موقعیت #{position} صف بود)" + } + }, + "profile": { + "password": { + "title": "تغییر گذرواژه", + "newPassword": "گذرواژه جدید", + "confirmPassword": "تأیید گذرواژه جدید", + "updateButton": "به‌روزرسانی گذرواژه" + }, + "form": { + "title": "اطلاعات پروفایل", + "firstName": "نام", + "lastName": "نام خانوادگی", + "username": "نام کاربری", + "email": "ایمیل", + "updateButton": "به‌روزرسانی پروفایل" + }, + "header": { + "title": "پروفایل", + "subtitle": "مدیریت اطلاعات شخصی و گذرواژه خود" + }, + "picture": { + "title": "عکس پروفایل", + "description": "روی آیکون دوربین کلیک کنید تا عکس پروفایل خود را تغییر دهید", + "uploadPhoto": "بارگذاری عکس", + "removePhoto": "حذف عکس" + }, + "errors": { + "loadFailed": "بارگذاری داده‌های کاربر ناموفق بود", + "updateFailed": "به‌روزرسانی پروفایل ناموفق بود", + "passwordFailed": "به‌روزرسانی گذرواژه ناموفق بود", + "imageFailed": "به‌روزرسانی تصویر پروفایل ناموفق بود", + "imageRemoveFailed": "حذف تصویر پروفایل ناموفق بود" + }, + "messages": { + "noChanges": "تغییری برای ذخیره وجود ندارد", + "updateSuccess": "پروفایل با موفقیت به‌روز شد", + "fillPasswords": "لطفاً هر دو فیلد گذرواژه را پر کنید", + "passwordSuccess": "گذرواژه با موفقیت به‌روز شد", + "imageSuccess": "تصویر پروفایل با موفقیت به‌روز شد", + "imageRemoved": "تصویر پروفایل با موفقیت حذف شد" + }, + "pageTitle": "پروفایل" + }, + "qrCodeModal": { + "title": "اشتراک‌گذاری QR کد", + "description": "این QR کد را اسکن کنید تا به لینک دسترسی پیدا کنید.", + "download": "دانلود QR کد" + }, + "quickAccess": { + "files": { + "title": "فایل‌های من", + "description": "دسترسی و مدیریت فایل‌های بارگذاری‌شده شما" + }, + "shares": { + "title": "اشتراک‌های من", + "description": "مشاهده و مدیریت فایل‌های به اشتراک‌گذاری‌شده شما" + }, + "reverseShares": { + "title": "دریافت فایل‌ها", + "description": "ایجاد لینک برای دیگران تا فایل برای شما ارسال کنند" + } + }, + "recentFiles": { + "title": "بارگذاری‌های اخیر", + "viewAll": "مشاهده همه", + "upload": "بارگذاری", + "uploadFile": "بارگذاری فایل", + "noFiles": "هنوز فایلی بارگذاری نشده است" + }, + "recentShares": { + "title": "اشتراک‌های اخیر", + "viewAll": "مشاهده همه", + "createShare": "ایجاد اشتراک", + "noShares": "هنوز اشتراکی ایجاد نشده است", + "createFirst": "اولین اشتراک خود را ایجاد کنید" + }, + "recipientSelector": { + "emailPlaceholder": "ایمیل گیرنده را وارد کنید", + "add": "افزودن", + "recipients": "گیرندگان ({count})", + "notifyAll": "اطلاع‌رسانی به همه", + "noRecipients": "هنوز گیرنده‌ای اضافه نشده است", + "addSuccess": "گیرنده با موفقیت اضافه شد", + "addError": "افزودن گیرنده ناموفق بود", + "removeSuccess": "گیرنده با موفقیت حذف شد", + "removeError": "حذف گیرنده ناموفق بود", + "sendingNotifications": "در حال ارسال اعلان‌ها...", + "notifySuccess": "گیرندگان با موفقیت مطلع شدند", + "notifyError": "اطلاع‌رسانی به گیرندگان ناموفق بود", + "selectAll": "انتخاب همه", + "selectedCount": "{count} انتخاب شده", + "selectRecipient": "انتخاب {email}", + "notifySelected": "اطلاع‌رسانی به موارد انتخاب‌شده", + "removeSelected": "حذف موارد انتخاب‌شده", + "notifySingle": "اطلاع‌رسانی به این گیرنده", + "removeSingle": "حذف این گیرنده", + "bulkRemoveSuccess": "{count} گیرنده با موفقیت حذف شد", + "bulkRemoveError": "حذف گیرندگان انتخاب‌شده ناموفق بود", + "bulkNotifySuccess": "اعلان‌ها به {count} گیرنده ارسال شد", + "bulkNotifyError": "اطلاع‌رسانی به گیرندگان انتخاب‌شده ناموفق بود", + "singleNotifySuccess": "اعلان به {email} ارسال شد", + "singleNotifyError": "اطلاع‌رسانی به گیرنده ناموفق بود", + "modalDescription": "افزودن و مدیریت گیرندگان برای این اشتراک. می‌توانید همه یا گیرندگان خاصی را وقتی SMTP پیکربندی شده مطلع کنید.", + "addRecipient": "افزودن گیرنده", + "invalidEmail": "لطفاً یک آدرس ایمیل معتبر وارد کنید", + "duplicateEmail": "این گیرنده قبلاً اضافه شده است", + "noRecipientsDescription": "گیرندگان را اضافه کنید تا این محتوا را از طریق ایمیل به اشتراک بگذارید" + }, + "register": { + "validation": { + "firstNameRequired": "نام الزامی است", + "lastNameRequired": "نام خانوادگی الزامی است", + "usernameMinLength": "نام کاربری باید حداقل ۳ کاراکتر باشد", + "invalidEmail": "ایمیل نامعتبر است", + "passwordMinLength": "گذرواژه باید حداقل ۸ کاراکتر باشد", + "success": "کاربر مدیر با موفقیت ایجاد شد!", + "error": "خطا در ایجاد کاربر مدیر" + }, + "labels": { + "firstName": "نام", + "lastName": "نام خانوادگی", + "username": "نام کاربری", + "email": "ایمیل", + "password": "گذرواژه" + }, + "buttons": { + "creating": "در حال ایجاد...", + "createAdmin": "ایجاد حساب مدیر" + } + }, + "resetPassword": { + "pageTitle": "بازنشانی گذرواژه", + "header": { + "title": "بازنشانی گذرواژه", + "description": "گذرواژه جدید خود را در زیر وارد کنید" + }, + "form": { + "newPassword": "گذرواژه جدید", + "newPasswordPlaceholder": "گذرواژه جدید خود را وارد کنید", + "confirmPassword": "تأیید گذرواژه جدید", + "confirmPasswordPlaceholder": "گذرواژه جدید خود را تأیید کنید", + "resetting": "در حال بازنشانی گذرواژه...", + "submit": "بازنشانی گذرواژه", + "backToLogin": "بازگشت به صفحه ورود" + }, + "messages": { + "success": "گذرواژه با موفقیت بازنشانی شد" + }, + "errors": { + "serverError": "بازنشانی گذرواژه ناموفق بود. لطفاً دوباره امتحان کنید.", + "invalidToken": "توکن بازنشانی نامعتبر یا وجود ندارد" + } + }, + "reverseShares": { + "pageTitle": "دریافت فایل‌ها", + "search": { + "title": "مدیریت لینک‌های دریافت", + "createButton": "ایجاد لینک", + "placeholder": "جست‌وجوی لینک‌های دریافت...", + "results": "‏{filtered} از {total} لینک دریافت پیدا شد" + }, + "labels": { + "files": "فایل‌ها", + "size": "حجم", + "status": "وضعیت", + "access": "دسترسی", + "description": "توضیحات", + "pageLayout": "چیدمان صفحه", + "security": "امنیت و وضعیت", + "limits": "محدودیت‌ها", + "maxFiles": "حداکثر تعداد فایل", + "maxFileSize": "حداکثر حجم", + "allowedTypes": "انواع مجاز", + "filesReceived": "فایل‌های دریافت‌شده", + "fileLimit": "محدودیت فایل", + "noLimit": "بدون محدودیت", + "noLinkCreated": "هیچ لینکی ایجاد نشده است", + "publicAccess": "دسترسی عمومی", + "protectedByPassword": "محافظت‌شده با گذرواژه", + "configureProtection": "برای پیکربندی حفاظت کلیک کنید", + "enterPassword": "گذرواژه را وارد کنید", + "thisLinkProtected": "این لینک با گذرواژه محافظت خواهد شد", + "thisLinkPublic": "این لینک به‌صورت عمومی در دسترس خواهد بود", + "configureExpiration": "تنظیم تاریخ انقضا", + "configureLimits": "تنظیم محدودیت‌های فایل", + "protectWithPassword": "محافظت با گذرواژه", + "layoutOptions": { + "default": "پیش‌فرض", + "wetransfer": "WeTransfer" + }, + "noFilesLimit": "بدون محدودیت تعداد فایل", + "noSizeLimit": "بدون محدودیت حجم", + "allFileTypes": "همهٔ انواع فایل", + "fileTypesHelp": "پسوندها را بدون نقطه و با فاصله، ویرگول، خط تیره یا خط عمودی جدا کنید", + "fieldRequirements": "الزامات فیلدها", + "nameFieldRequired": "فیلد نام", + "emailFieldRequired": "فیلد ایمیل", + "fieldOptions": { + "hidden": "مخفی", + "optional": "اختیاری", + "required": "ضروری" + } + }, + "card": { + "untitled": "لینک بدون عنوان", + "noDescription": "بدون توضیح", + "addDescriptionPlaceholder": "افزودن توضیح...", + "files": "فایل‌ها", + "progress": "پیشرفت", + "created": "ایجاد شد", + "expired": "منقضی شد", + "expires": "منقضی می‌شود", + "viewDetails": "مشاهده جزئیات", + "viewQrCode": "مشاهده کد QR", + "copyLink": "کپی لینک", + "openInNewTab": "باز کردن در زبانه جدید", + "editLink": "ویرایش لینک", + "createLink": "ایجاد لینک", + "delete": "حذف", + "copyLinkTitle": "کپی لینک", + "createLinkCTA": "ایجاد لینک دریافت" + }, + "status": { + "active": "فعال", + "inactive": "غیرفعال", + "expired": "منقضی شده", + "protected": "محافظت‌شده", + "public": "عمومی" + }, + "actions": { + "copyLink": "کپی لینک", + "editAlias": "ویرایش نام مستعار", + "createAlias": "ایجاد نام مستعار", + "viewDetails": "مشاهده جزئیات", + "edit": "ویرایش", + "delete": "حذف", + "viewFiles": "فایل‌های دریافت‌شده", + "viewQrCode": "مشاهده کد QR" + }, + "empty": { + "title": "هیچ لینک دریافتی ایجاد نشده است", + "description": "برای اینکه دیگران بتوانند به‌صورت امن و منظم مستقیم برای شما فایل بفرستند، لینک‌های سفارشی ایجاد کنید.", + "createButton": "ایجاد اولین لینک" + }, + "modals": { + "create": { + "title": "ایجاد لینک دریافت", + "description": "یک لینک سفارشی برای دریافت فایل از دیگران پیکربندی کنید" + }, + "edit": { + "title": "ویرایش لینک دریافت", + "description": "تنظیمات این لینک دریافت را به‌روزرسانی کنید", + "updating": "در حال به‌روزرسانی...", + "saveChanges": "ذخیره تغییرات" + }, + "details": { + "title": "جزئیات لینک", + "description": "اطلاعات لینک دریافت خود را مشاهده و ویرایش کنید", + "pageLayout": "چیدمان صفحه", + "linkSection": "لینک دریافت", + "noLinkCreated": "هیچ لینکی ایجاد نشده است", + "limits": "محدودیت‌ها", + "maxFiles": "حداکثر تعداد فایل", + "maxFileSize": "حداکثر حجم", + "allowedTypes": "انواع مجاز", + "noLimit": "بدون محدودیت", + "security": "امنیت", + "status": "وضعیت", + "password": "گذرواژه", + "files": "فایل‌های دریافت‌شده", + "noFiles": "هنوز فایلی دریافت نشده است", + "copyLink": "کپی لینک", + "openLink": "باز کردن لینک", + "editAlias": "ویرایش نام مستعار", + "createAlias": "ایجاد نام مستعار", + "editPassword": "ویرایش محافظت با گذرواژه", + "basicInfo": "اطلاعات پایه", + "securityAndStatus": "امنیت و وضعیت", + "protection": "حفاظت", + "protectedByPassword": "محافظت‌شده با گذرواژه", + "publicAccess": "دسترسی عمومی", + "active": "فعال", + "inactive": "غیرفعال", + "deactivate": "غیرفعال کردن", + "activate": "فعال کردن", + "expiration": "انقضا", + "dates": "تاریخ‌ها", + "createdAt": "ایجاد در", + "updatedAt": "به‌روزرسانی در", + "allTypes": "همه انواع", + "placeholderTypes": ".pdf,.jpg,.png (با ویرگول جدا کنید)", + "downloadSuccess": "دانلود شروع شد", + "downloadError": "خطا در دانلود فایل", + "editSuccess": "فایل با موفقیت به‌روزرسانی شد", + "editError": "خطا در به‌روزرسانی فایل", + "previewNotAvailable": "پیش‌نمایش در دسترس نیست", + "notAvailable": "ناموجود", + "invalidDate": "تاریخ نامعتبر" + }, + "alias": { + "editTitle": "ویرایش نام مستعار", + "createTitle": "ایجاد نام مستعار", + "editDescription": "نام مستعار این لینک دریافت را به‌روزرسانی کنید", + "createDescription": "یک نام مستعار سفارشی برای این لینک دریافت ایجاد کنید", + "aliasLabel": "نام مستعار لینک", + "aliasPlaceholder": "my-custom-link", + "preview": "پیش‌نمایش:", + "currentLink": "لینک فعلی:", + "copyCurrentLink": "کپی لینک فعلی", + "randomTooltip": "تولید نام مستعار تصادفی", + "cancel": "انصراف", + "creating": "در حال ایجاد...", + "updating": "در حال به‌روزرسانی...", + "create": "ایجاد نام مستعار", + "update": "به‌روزرسانی نام مستعار", + "validation": { + "required": "نام مستعار الزامی است", + "minLength": "نام مستعار باید حداقل ۳ کاراکتر باشد", + "maxLength": "نام مستعار باید حداکثر ۵۰ کاراکتر باشد", + "pattern": "نام مستعار فقط می‌تواند شامل حروف، اعداد، خط تیره و زیرخط باشد" + }, + "help": "۳ تا ۵۰ کاراکتر. فاصله‌ها به‌صورت خودکار به خط تیره تبدیل می‌شوند." + }, + "password": { + "title": "ویرایش محافظت با گذرواژه", + "description": "محافظت با گذرواژه را برای این لینک پیکربندی کنید", + "hasPassword": "محافظت‌شده با گذرواژه", + "password": "گذرواژه", + "cancel": "انصراف", + "save": "ذخیره", + "saving": "در حال ذخیره..." + }, + "receivedFiles": { + "title": "فایل‌های دریافت‌شده", + "description": "مشاهده و مدیریت فایل‌هایی که به این لینک ارسال شده‌اند", + "noFiles": "هنوز فایلی دریافت نشده است", + "noFilesDescription": "فایل‌هایی که از طریق این لینک ارسال شوند اینجا نمایش داده می‌شوند", + "fileCount": "{count, plural, =0 {بدون فایل} =1 {۱ فایل} other {# فایل}}", + "invalidDate": "تاریخ نامعتبر", + "totalSize": "حجم کل: {size}", + "columns": { + "file": "فایل", + "size": "حجم", + "sender": "ارسال‌شده توسط", + "date": "تاریخ", + "actions": "اقدامات" + }, + "actions": { + "preview": "پیش‌نمایش", + "download": "دانلود", + "copyToMyFiles": "کپی به فایل‌های من", + "copying": "در حال کپی..." + }, + "uploadedBy": "بارگذاری‌شده توسط {name}", + "anonymous": "ناشناس", + "downloadSuccess": "دانلود شروع شد", + "downloadError": "خطا در دانلود فایل", + "editSuccess": "فایل با موفقیت به‌روزرسانی شد", + "editError": "خطا در به‌روزرسانی فایل", + "previewNotAvailable": "پیش‌نمایش در دسترس نیست", + "copySuccess": "فایل با موفقیت به فایل‌های شما کپی شد", + "copyError": "خطا در کپی فایل به فایل‌های شما", + "deleteSuccess": "فایل با موفقیت حذف شد", + "deleteError": "خطا در حذف فایل", + "bulkCopySuccess": "{count, plural, =1 {۱ فایل با موفقیت به فایل‌های شما کپی شد} other {# فایل با موفقیت به فایل‌های شما کپی شد}}", + "bulkDeleteSuccess": "{count, plural, =1 {۱ فایل با موفقیت حذف شد} other {# فایل با موفقیت حذف شد}}", + "bulkCopyProgress": "در حال کپی {count, plural, =1 {۱ فایل} other {# فایل}} به فایل‌های شما...", + "bulkDeleteProgress": "در حال حذف {count, plural, =1 {۱ فایل} other {# فایل}}...", + "bulkDeleteConfirmTitle": "حذف فایل‌های انتخاب‌شده", + "bulkDeleteConfirmMessage": "آیا از حذف {count, plural, =1 {این فایل} other {این # فایل}} مطمئن هستید؟ این عمل قابل بازگشت نیست.", + "bulkDeleteConfirmButton": "حذف {count, plural, =1 {فایل} other {فایل‌ها}}", + "bulkActions": { + "selected": "{count, plural, =1 {۱ فایل انتخاب شد} other {# فایل انتخاب شد}}", + "actions": "اقدامات", + "download": "دانلود موارد انتخاب‌شده", + "copyToMyFiles": "کپی موارد انتخاب‌شده به فایل‌های من", + "delete": "حذف موارد انتخاب‌شده" + }, + "selectAll": "انتخاب همه", + "selectFile": "انتخاب فایل {fileName}", + "copyErrors": { + "timeout": "عملیات کپی به دلیل اتمام مهلت زمانی متوقف شد. لطفاً با فایل کوچک‌تر تلاش کنید یا اتصال خود را بررسی کنید.", + "failed": "عملیات کپی ناموفق بود. لطفاً دوباره تلاش کنید.", + "aborted": "عملیات کپی به دلیل اتمام مهلت زمانی لغو شد." + } + } + }, + "form": { + "name": { + "label": "نام لینک", + "placeholder": "مثلاً: مدارک پروژه، عکس‌های خانوادگی..." + }, + "description": { + "label": "توضیحات", + "placeholder": "توضیح دهید چه نوع فایل‌هایی انتظار دریافت دارید...", + "description": "اختیاری. به افراد کمک می‌کند بدانند چه چیزی ارسال کنند." + }, + "status": { + "label": "وضعیت لینک", + "description": "این لینک دریافت را فعال یا غیرفعال کنید" + }, + "expiration": { + "label": "تاریخ انقضا", + "description": "اختیاری. پس از این تاریخ لینک غیرفعال می‌شود.", + "configure": "تنظیم تاریخ انقضا" + }, + "fileLimits": { + "configure": "تنظیم محدودیت‌های فایل" + }, + "maxFiles": { + "label": "حداکثر تعداد فایل", + "placeholder": "مثلاً: 10", + "description": "اختیاری. محدود کردن تعداد کل فایل‌هایی که می‌توان ارسال کرد.", + "noLimit": "بدون محدودیت تعداد فایل" + }, + "maxFileSize": { + "label": "حداکثر اندازه فایل", + "placeholder": "مثلاً: 100", + "description": "اختیاری. محدود کردن اندازه هر فایل.", + "noLimit": "بدون محدودیت اندازه" + }, + "allowedFileTypes": { + "label": "انواع فایل مجاز", + "placeholder": "مثلاً: pdf, jpg, png, docx", + "description": "پسوندها را بدون نقطه و با فاصله، ویرگول، خط تیره یا خط عمودی جدا کنید", + "allTypes": "همهٔ انواع فایل" + }, + "pageLayout": { + "label": "چیدمان صفحه", + "placeholder": "انتخاب چیدمان", + "description": "چگونه صفحه بارگذاری به کاربران نمایش داده شود.", + "options": { + "default": "چیدمان پیش‌فرض", + "wetransfer": "سبک WeTransfer" + } + }, + "password": { + "label": "گذرواژه محافظتی", + "placeholder": "اختیاری. برای محافظت از لینک گذرواژه اضافه کنید", + "description": "اختیاری. کاربران برای دسترسی به لینک به این گذرواژه نیاز دارند.", + "configurePassword": "تنظیم گذرواژه", + "protectWithPassword": "محافظت با گذرواژه", + "passwordHelp": "گذرواژه باید حداقل ۴ کاراکتر باشد", + "passwordPlaceholder": "یک گذرواژه برای محافظت از لینک وارد کنید" + }, + "nameFieldRequired": { + "label": "الزام فیلد نام", + "description": "تنظیم کنید آیا فیلد نام ارسال‌کننده نمایش داده شود و آیا ضروری باشد" + }, + "emailFieldRequired": { + "label": "الزام فیلد ایمیل", + "description": "تنظیم کنید آیا فیلد ایمیل ارسال‌کننده نمایش داده شود و آیا ضروری باشد" + }, + "fieldRequirements": { + "title": "الزامات فیلدها", + "description": "مشخص کنید کدام فیلدها در فرم بارگذاری نمایش داده شوند" + }, + "submit": "ایجاد لینک دریافت" + }, + "messages": { + "created": "لینک دریافت با موفقیت ایجاد شد!", + "createSuccess": "لینک دریافت با موفقیت ایجاد شد!", + "updateSuccess": "لینک دریافت با موفقیت به‌روزرسانی شد!", + "linkCopied": "لینک در کلیپ‌بورد کپی شد!", + "deleteSuccess": "لینک دریافت با موفقیت حذف شد!", + "aliasCreated": "نام مستعار با موفقیت ایجاد شد!", + "activateSuccess": "لینک دریافت با موفقیت فعال شد!", + "deactivateSuccess": "لینک دریافت با موفقیت غیرفعال شد!", + "passwordProtectionEnabled": "محافظت با گذرواژه با موفقیت فعال شد!", + "passwordProtectionDisabled": "محافظت با گذرواژه با موفقیت حذف شد!" + }, + "defaultLinkName": "فایل‌های دریافت‌شده", + "errors": { + "loadFailed": "بارگذاری لینک‌های دریافت ناموفق بود", + "createFailed": "ایجاد لینک دریافت ناموفق بود. لطفاً دوباره تلاش کنید.", + "updateFailed": "به‌روزرسانی لینک دریافت ناموفق بود. لطفاً دوباره تلاش کنید.", + "deleteFailed": "حذف لینک دریافت ناموفق بود. لطفاً دوباره تلاش کنید.", + "aliasCreateFailed": "ایجاد نام مستعار ناموفق بود. لطفاً دوباره تلاش کنید.", + "passwordUpdateFailed": "به‌روزرسانی محافظت با گذرواژه ناموفق بود" + }, + "delete": { + "title": "حذف لینک دریافت", + "description": "این عمل قابل بازگشت نیست. لینک به‌طور دائم حذف می‌شود و دیگر قادر به دریافت فایل نخواهد بود.", + "confirmButton": "حذف لینک", + "cancelButton": "انصراف", + "deleting": "در حال حذف..." + }, + "upload": { + "metadata": { + "title": "ارسال فایل‌ها - Palmr", + "description": "ارسال فایل از طریق لینک اشتراکی", + "descriptionWithLimit": "بارگذاری فایل‌ها (حداکثر {limit} فایل)" + }, + "layout": { + "defaultTitle": "ارسال فایل‌ها", + "importantInfo": "اطلاعات مهم:", + "maxFiles": "حداکثر {count} فایل", + "maxFileSize": "حداکثر اندازه فایل: {size}MB", + "allowedTypes": "انواع مجاز: {types}", + "loading": "در حال بارگذاری..." + }, + "password": { + "title": "لینک محافظت‌شده", + "description": "این لینک با گذرواژه محافظت می‌شود. برای ادامه، گذرواژه را وارد کنید.", + "label": "گذرواژه", + "placeholder": "گذرواژه را وارد کنید", + "cancel": "انصراف", + "submit": "ادامه", + "verifying": "در حال بررسی..." + }, + "errors": { + "loadFailed": "خطا در بارگذاری اطلاعات. لطفاً دوباره تلاش کنید.", + "passwordIncorrect": "گذرواژه اشتباه است. لطفاً دوباره تلاش کنید.", + "linkNotFound": "لینک پیدا نشد یا منقضی شده است.", + "linkInactive": "این لینک غیرفعال است.", + "linkExpired": "این لینک منقضی شده است.", + "uploadFailed": "خطا در بارگذاری فایل", + "retry": "تلاش مجدد", + "fileTooLarge": "فایل بیش از حد بزرگ است. حداکثر اندازه: {maxSize}", + "fileTypeNotAllowed": "نوع فایل مجاز نیست. انواع قابل قبول: {allowedTypes}", + "maxFilesExceeded": "حداکثر {maxFiles} فایل مجاز است", + "selectAtLeastOneFile": "حداقل یک فایل انتخاب کنید", + "provideNameOrEmail": "لطفاً نام یا ایمیل خود را وارد کنید", + "provideNameRequired": "نام الزامی است", + "provideEmailRequired": "ایمیل الزامی است" + }, + "fileDropzone": { + "dragActive": "فایل‌ها را اینجا رها کنید", + "dragInactive": "فایل‌ها را اینجا بکشید یا برای انتخاب کلیک کنید", + "acceptedTypes": "انواع قابل قبول: {types}", + "maxFileSize": "حداکثر اندازه: {size}", + "maxFiles": "حداکثر {count} فایل", + "remainingFiles": "{remaining} از {max} فایل باقی‌مانده" + }, + "fileList": { + "title": "فایل‌های انتخاب‌شده:", + "statusUploaded": "بارگذاری شد", + "statusError": "خطا", + "retry": "تلاش مجدد" + }, + "form": { + "nameLabel": "نام", + "nameLabelOptional": "نام (اختیاری)", + "namePlaceholder": "نام شما", + "emailLabel": "ایمیل", + "emailLabelOptional": "ایمیل (اختیاری)", + "emailPlaceholder": "your@email.com", + "descriptionLabel": "توضیحات (اختیاری)", + "descriptionPlaceholder": "برای فایل‌ها توضیح اضافه کنید...", + "uploadButton": "ارسال {count} فایل", + "uploading": "در حال ارسال..." + }, + "success": { + "title": "فایل‌ها با موفقیت ارسال شدند! 🎉", + "description": "می‌توانید این صفحه را ببندید.", + "countMessage": "{count} فایل با موفقیت ارسال شد!" + }, + "maxFilesReached": { + "title": "محدودیت تعداد فایل پر شد", + "description": "این لینک قبلاً حداکثر تعداد مجاز {maxFiles} فایل را دریافت کرده است.", + "contactOwner": "اگر خطایی رخ داده یا نیاز به ارسال فایل‌های بیشتری دارید، با صاحب لینک تماس بگیرید." + }, + "linkInactive": { + "title": "لینک غیرفعال است", + "description": "این لینک دریافت به‌طور موقت غیرفعال است.", + "contactOwner": "برای اطلاعات بیشتر با صاحب لینک تماس بگیرید." + }, + "linkNotFound": { + "title": "لینک پیدا نشد", + "description": "ممکن است این لینک حذف شده باشد یا هرگز وجود نداشته است." + }, + "linkExpired": { + "title": "لینک منقضی شده", + "description": "این لینک دریافت منقضی شده و دیگر فایل‌ها را نمی‌پذیرد.", + "contactOwner": "اگر نیاز به ارسال فایل دارید، با صاحب لینک تماس بگیرید." + } + }, + "components": { + "fileRow": { + "addDescription": "افزودن توضیح...", + "anonymous": "ناشناس" + }, + "fileActions": { + "edit": "ویرایش", + "preview": "پیش‌نمایش", + "download": "دانلود", + "delete": "حذف", + "copyToMyFiles": "کپی به فایل‌های من", + "copying": "در حال کپی..." + }, + "editField": { + "saveChanges": "ذخیره تغییرات", + "cancelEdit": "انصراف از ویرایش" + } + } + }, + "searchBar": { + "placeholder": "جست‌وجوی فایل‌ها و پوشه‌ها...", + "placeholderFiles": "جست‌وجوی فایل‌ها...", + "placeholderFolders": "جست‌وجوی پوشه‌ها...", + "results": "نمایش {filtered} از {total} مورد", + "noResults": "هیچ نتیجه‌ای برای \"{query}\" یافت نشد" + }, + "settings": { + "groups": { + "defaultDescription": "گزینه‌های پیکربندی", + "general": { + "title": "عمومی", + "description": "تنظیمات پایه برنامه" + }, + "email": { + "title": "ایمیل", + "description": "پیکربندی سرور ایمیل" + }, + "security": { + "title": "امنیت", + "description": "تنظیمات امنیت و احراز هویت" + }, + "storage": { + "title": "ذخیره‌سازی", + "description": "پیکربندی ذخیره‌سازی فایل" + }, + "oidc": { + "title": "OpenID Connect (SSO)", + "description": "پیکربندی احراز هویت SSO از طریق OpenID Connect" + } + }, + "fields": { + "noDescription": "توضیحی موجود نیست", + "appLogo": { + "title": "لوگوی برنامه", + "description": "تصویر لوگوی برنامه" + }, + "appName": { + "title": "نام برنامه", + "description": "نام برنامه که به کاربران نمایش داده می‌شود" + }, + "appDescription": { + "title": "توضیحات برنامه", + "description": "توضیح مختصری از برنامه" + }, + "showHomePage": { + "title": "نمایش صفحه اصلی", + "description": "نمایش صفحه اصلی پس از نصب" + }, + "hideVersion": { + "title": "مخفی کردن نسخه", + "description": "مخفی کردن نسخه Palmr از فوتر در تمام صفحات" + }, + "smtpEnabled": { + "title": "فعال‌سازی SMTP", + "description": "فعال یا غیرفعال کردن قابلیت ایمیل SMTP" + }, + "smtpHost": { + "title": "سرور SMTP", + "description": "آدرس سرور SMTP" + }, + "smtpPort": { + "title": "پورت SMTP", + "description": "پورت سرور SMTP" + }, + "smtpUser": { + "title": "نام کاربری SMTP", + "description": "نام کاربری برای احراز هویت SMTP" + }, + "smtpPass": { + "title": "گذرواژه SMTP", + "description": "گذرواژه برای احراز هویت SMTP" + }, + "smtpFromName": { + "title": "نام فرستنده", + "description": "نام نمایشی برای ایمیل‌های ارسالی" + }, + "smtpFromEmail": { + "title": "ایمیل فرستنده", + "description": "آدرس ایمیل فرستنده" + }, + "smtpSecure": { + "title": "امنیت اتصال", + "description": "روش امنیت اتصال SMTP - خودکار (توصیه می‌شود)، SSL، STARTTLS یا هیچ (ناامن)", + "options": { + "auto": "خودکار (توصیه می‌شود)", + "ssl": "SSL (پورت ۴۶۵)", + "tls": "STARTTLS (پورت ۵۸۷)", + "none": "هیچ (ناامن)" + } + }, + "smtpNoAuth": { + "title": "بدون احراز هویت", + "description": "این را برای سرورهای داخلی که به نام کاربری/گذرواژه نیاز ندارند فعال کنید (فیلدهای احراز هویت را مخفی می‌کند)" + }, + "smtpTrustSelfSigned": { + "title": "اعتماد به گواهینامه‌های خودامضا", + "description": "این را فعال کنید تا به گواهینامه‌های SSL/TLS خودامضا اعتماد شود (برای محیط‌های توسعه مفید است)" + }, + "testSmtp": { + "title": "آزمایش اتصال SMTP", + "description": "آزمایش معتبر بودن پیکربندی SMTP" + }, + "maxLoginAttempts": { + "title": "حداکثر تلاش‌های ورود", + "description": "حداکثر تعداد تلاش‌های ورود قبل از مسدودسازی" + }, + "loginBlockDuration": { + "title": "مدت زمان مسدودسازی", + "description": "مدت زمان (به ثانیه) برای مسدودسازی پس از تجاوز از تلاش‌ها" + }, + "passwordMinLength": { + "title": "حداقل طول گذرواژه", + "description": "حداقل تعداد کاراکترها برای گذرواژه‌ها" + }, + "passwordResetTokenExpiration": { + "title": "انقضای توکن بازنشانی", + "description": "مدت زمان اعتبار (به ثانیه) برای توکن بازنشانی گذرواژه" + }, + "maxFileSize": { + "title": "حداکثر اندازه فایل", + "description": "حداکثر اندازه مجاز برای بارگذاری فایل" + }, + "maxTotalStoragePerUser": { + "title": "حداکثر ذخیره‌سازی به ازای هر کاربر", + "description": "محدودیت کل ذخیره‌سازی برای هر کاربر" + }, + "firstUserAccess": { + "title": "دسترسی اولین کاربر", + "description": "تنظیمات برای دسترسی اولیه کاربران جدید" + }, + "serverUrl": { + "title": "URL سرور", + "description": "URL پایه سرور Palmr (مثلاً: https://palmr.example.com)" + }, + "passwordAuthEnabled": { + "title": "احراز هویت با گذرواژه", + "description": "فعال یا غیرفعال کردن احراز هویت مبتنی بر گذرواژه" + } + }, + "buttons": { + "save": "ذخیره {group}", + "testSmtp": "آزمایش اتصال", + "testing": "در حال آزمایش..." + }, + "errors": { + "loadFailed": "بارگذاری تنظیمات ناموفق بود", + "updateFailed": "به‌روزرسانی تنظیمات ناموفق بود", + "passwordAuthRequiresProvider": "نمی‌توان احراز هویت با گذرواژه را بدون داشتن حداقل یک ارائه‌دهنده احراز هویت فعال غیرفعال کرد" + }, + "messages": { + "noChanges": "تغییری برای ذخیره وجود ندارد", + "updateSuccess": "تنظیمات {group} با موفقیت به‌روز شد", + "smtpTestSuccess": "اتصال SMTP موفق بود! پیکربندی ایمیل شما به درستی کار می‌کند.", + "smtpTestFailed": "اتصال SMTP ناموفق بود: {error}", + "smtpTestGenericError": "آزمایش اتصال SMTP ناموفق بود. لطفاً تنظیمات خود را بررسی کنید و دوباره امتحان کنید.", + "smtpNotEnabled": "SMTP فعال نیست. لطفاً ابتدا SMTP را فعال کنید.", + "smtpMissingHostPort": "لطفاً قبل از آزمایش، هاست و پورت SMTP را پر کنید.", + "smtpMissingAuth": "لطفاً نام کاربری و گذرواژه SMTP را پر کنید، یا گزینه 'بدون احراز هویت' را فعال کنید." + }, + "title": "تنظیمات", + "breadcrumb": "تنظیمات", + "pageTitle": "تنظیمات", + "tooltips": { + "testSmtp": "اتصال SMTP را با مقادیر وارد شده در فرم فعلی آزمایش می‌کند. برای دائمی کردن تغییرات، به یاد داشته باشید تنظیمات خود را پس از آزمایش ذخیره کنید.", + "defaultPlaceholder": "وارد کنید و Enter را فشار دهید" + }, + "redirectUri": { + "placeholder": "https://mysite.com", + "previewLabel": "URL کامل که ذخیره خواهد شد:" + } + }, + "share": { + "errors": { + "invalidPassword": "گذرواژه نامعتبر است. لطفاً دوباره امتحان کنید.", + "loadFailed": "بارگذاری اشتراک ناموفق بود", + "downloadFailed": "دانلود فایل ناموفق بود" + }, + "messages": { + "downloadStarted": "دانلود آغاز شد" + }, + "password": { + "title": "اشتراک محافظت‌شده با گذرواژه", + "protected": "این اشتراک با گذرواژه محافظت می‌شود", + "incorrect": "گذرواژه اشتباه است. لطفاً دوباره امتحان کنید.", + "label": "گذرواژه", + "placeholder": "گذرواژه اشتراک را وارد کنید", + "submit": "ارسال" + }, + "details": { + "untitled": "اشتراک بدون عنوان", + "created": "ایجاد شده: {date}", + "expires": "منقضی می‌شود: {date}" + }, + "downloadAll": "دانلود همه", + "notFound": { + "title": "اشتراک یافت نشد", + "description": "این اشتراک ممکن است حذف شده یا منقضی شده باشد." + }, + "pageTitle": "اشتراک", + "metadata": { + "defaultDescription": "اشتراک‌گذاری فایل‌ها به صورت امن", + "filesShared": "{count, plural, =1 {۱ فایل به اشتراک گذاشته شد} other {# فایل به اشتراک گذاشته شد}}" + } + }, + "shareActions": { + "fileTitle": "اشتراک‌گذاری فایل", + "folderTitle": "اشتراک‌گذاری پوشه", + "linkTitle": "تولید لینک", + "linkDescriptionFile": "تولید یک لینک سفارشی برای اشتراک‌گذاری فایل", + "linkDescriptionFolder": "تولید یک لینک سفارشی برای اشتراک‌گذاری پوشه", + "aliasLabel": "نام مستعار لینک", + "aliasPlaceholder": "نام مستعار سفارشی وارد کنید", + "linkReady": "لینک اشتراک شما آماده است:", + "generateLink": "تولید لینک", + "copyLink": "کپی لینک", + "deleteTitle": "حذف اشتراک", + "deleteConfirmation": "آیا از حذف این اشتراک اطمینان دارید؟ این عمل قابل بازگشت نیست.", + "addDescriptionPlaceholder": "افزودن توضیح...", + "editTitle": "ویرایش اشتراک", + "newPasswordLabel": "گذرواژه جدید (برای حفظ فعلی خالی بگذارید)", + "newPasswordPlaceholder": "گذرواژه جدید را وارد کنید", + "manageFilesTitle": "مدیریت فایل‌ها", + "manageFilesDescription": "فایل‌ها و پوشه‌هایی را که می‌خواهید در این اشتراک باشند انتخاب کنید", + "manageRecipientsTitle": "مدیریت گیرندگان", + "itemsSelected": "{count} مورد انتخاب شده", + "editSuccess": "اشتراک با موفقیت به‌روز شد", + "editError": "به‌روزرسانی اشتراک ناموفق بود", + "bulkDeleteConfirmation": "آیا از حذف {count, plural, =1 {۱ اشتراک} other {# اشتراک}} اطمینان دارید؟ این عمل قابل بازگشت نیست.", + "bulkDeleteTitle": "حذف اشتراک‌های انتخاب‌شده" + }, + "shareDetails": { + "title": "جزئیات اشتراک", + "subtitle": "مشاهده و مدیریت جزئیات این اشتراک", + "basicInfo": "اطلاعات پایه", + "name": "نام", + "description": "توضیحات", + "shareLink": "لینک اشتراک", + "dates": "تاریخ‌ها", + "security": "امنیت", + "files": "فایل‌ها", + "recipients": "گیرندگان", + "views": "بازدیدها", + "created": "ایجاد شد", + "expires": "منقضی می‌شود", + "never": "هرگز", + "untitled": "اشتراک بدون عنوان", + "noDescription": "بدون توضیح", + "notAvailable": "ناموجود", + "invalidDate": "تاریخ نامعتبر", + "passwordProtected": "محافظت‌شده با گذرواژه", + "publicAccess": "دسترسی عمومی", + "maxViews": "حداکثر بازدیدها:", + "noLink": "لینکی تولید نشده است", + "generateLink": "تولید لینک", + "editLink": "ویرایش لینک", + "copyLink": "کپی لینک", + "openLink": "باز کردن لینک", + "editSecurity": "ویرایش امنیت", + "editExpiration": "ویرایش انقضا", + "qrCode": "QR کد", + "downloadQrCode": "دانلود QR کد", + "clickToEnlargeQrCode": "برای بزرگ‌نمایی QR کد کلیک کنید", + "loadError": "بارگذاری جزئیات اشتراک ناموفق بود" + }, + "shareExpiration": { + "title": "تنظیمات انقضای اشتراک", + "subtitle": "پیکربندی زمان انقضای این اشتراک", + "currentStatus": "وضعیت فعلی", + "expires": "منقضی می‌شود:", + "neverExpires": "هرگز منقضی نمی‌شود", + "enableExpiration": "فعال‌سازی انقضا", + "expirationDate": "تاریخ انقضا", + "validation": { + "dateRequired": "لطفاً یک تاریخ انقضا انتخاب کنید", + "dateMustBeFuture": "تاریخ انقضا باید در آینده باشد" + }, + "success": { + "expirationSet": "تاریخ انقضا با موفقیت تنظیم شد", + "expirationUpdated": "تاریخ انقضا با موفقیت به‌روز شد", + "expirationRemoved": "انقضا با موفقیت حذف شد - اشتراک اکنون دائمی است" + }, + "error": { + "updateFailed": "به‌روزرسانی تنظیمات انقضا ناموفق بود" + }, + "info": { + "title": "درباره انقضا:", + "willBeInaccessible": "اشتراک پس از این تاریخ غیرقابل دسترس خواهد شد", + "canBeChanged": "می‌توانید تاریخ انقضا را هر زمان تغییر دهید یا حذف کنید", + "noExpiration": "این اشتراک هرگز منقضی نخواهد شد و برای همیشه قابل دسترس خواهد ماند." + } + }, + "shareManager": { + "deleteSuccess": "اشتراک با موفقیت حذف شد", + "deleteError": "حذف اشتراک ناموفق بود", + "updateSuccess": "اشتراک با موفقیت به‌روز شد", + "updateError": "به‌روزرسانی اشتراک ناموفق بود", + "securityUpdateSuccess": "تنظیمات امنیتی با موفقیت به‌روز شد", + "securityUpdateError": "به‌روزرسانی تنظیمات امنیتی ناموفق بود", + "expirationUpdateSuccess": "تنظیمات انقضا با موفقیت به‌روز شد", + "expirationUpdateError": "به‌روزرسانی تنظیمات انقضا ناموفق بود", + "filesUpdateSuccess": "فایل‌ها با موفقیت به‌روز شد", + "filesUpdateError": "به‌روزرسانی فایل‌ها ناموفق بود", + "recipientsUpdateSuccess": "گیرندگان با موفقیت به‌روز شدند", + "recipientsUpdateError": "به‌روزرسانی گیرندگان ناموفق بود", + "linkGenerateSuccess": "لینک اشتراک با موفقیت تولید شد", + "linkGenerateError": "تولید لینک اشتراک ناموفق بود", + "notifyLoading": "در حال ارسال اعلان‌ها...", + "notifySuccess": "گیرندگان با موفقیت مطلع شدند", + "notifyError": "اطلاع‌رسانی به گیرندگان ناموفق بود", + "bulkDeleteError": "حذف اشتراک‌ها ناموفق بود", + "bulkDeleteLoading": "در حال حذف {count, plural, =1 {۱ اشتراک} other {# اشتراک}}...", + "bulkDeleteSuccess": "{count, plural, =1 {۱ اشتراک با موفقیت حذف شد} other {# اشتراک با موفقیت حذف شد}}", + "downloadSuccess": "دانلود با موفقیت آغاز شد", + "downloadError": "دانلود فایل‌های اشتراک ناموفق بود", + "noFilesToDownload": "فایلی برای دانلود موجود نیست", + "creatingZip": "در حال ایجاد فایل ZIP...", + "zipDownloadSuccess": "فایل ZIP با موفقیت دانلود شد", + "zipDownloadError": "ایجاد فایل ZIP ناموفق بود", + "errors": { + "multipleDownloadNotSupported": "دانلود چند اشتراک هنوز پشتیبانی نمی‌شود - لطفاً اشتراک‌ها را به صورت جداگانه دانلود کنید" + }, + "singleShareZipName": "{shareName}_files.zip", + "multipleSharesZipName": "{count}_shares_files.zip", + "defaultShareName": "اشتراک" + }, + "shareMultipleFiles": { + "title": "اشتراک‌گذاری چند فایل", + "shareNameLabel": "نام اشتراک", + "shareNamePlaceholder": "نام اشتراک را وارد کنید", + "descriptionLabel": "توضیحات", + "descriptionPlaceholder": "توضیح وارد کنید (اختیاری)", + "filesToShare": "فایل‌ها برای اشتراک‌گذاری", + "files": "فایل‌ها", + "totalSize": "حجم کل", + "creating": "در حال ایجاد اشتراک...", + "create": "ایجاد اشتراک", + "itemsToShare": "موارد برای اشتراک‌گذاری ({count} {count, plural, =1 {مورد} other {مورد}})" + }, + "shareSecurity": { + "title": "تنظیمات امنیتی اشتراک", + "subtitle": "پیکربندی محافظت با گذرواژه و گزینه‌های امنیتی برای این اشتراک", + "currentStatus": "وضعیت فعلی", + "passwordProtection": "محافظت با گذرواژه", + "password": "گذرواژه", + "newPassword": "گذرواژه جدید", + "passwordPlaceholder": "یک گذرواژه امن وارد کنید", + "existingPasswordMessage": "این اشتراک از قبل گذرواژه دارد. اگر می‌خواهید آن را به‌روز کنید، گذرواژه جدید را در فیلد زیر وارد کرده و ذخیره کنید.", + "passwordRequirements": { + "title": "الزامات گذرواژه:", + "minLength": "حداقل ۲ کاراکتر" + }, + "info": { + "title": "نحوه کار:", + "withPassword": "کاربران برای دسترسی به این اشتراک باید گذرواژه را وارد کنند.", + "withoutPassword": "هر کسی که لینک را داشته باشد می‌تواند بدون گذرواژه به این اشتراک دسترسی پیدا کند." + }, + "validation": { + "passwordRequired": "گذرواژه الزامی است", + "passwordTooShort": "گذرواژه باید حداقل ۲ کاراکتر باشد" + }, + "success": { + "passwordSet": "محافظت با گذرواژه با موفقیت فعال شد", + "passwordUpdated": "گذرواژه با موفقیت به‌روز شد", + "passwordRemoved": "محافظت با گذرواژه با موفقیت حذف شد" + }, + "error": { + "updateFailed": "به‌روزرسانی تنظیمات امنیتی ناموفق بود" + } + }, + "shares": { + "errors": { + "loadFailed": "بارگذاری اشتراک‌ها ناموفق بود", + "notifyFailed": "اطلاع‌رسانی به گیرندگان ناموفق بود", + "smtpConfigFailed": "بارگذاری پیکربندی SMTP ناموفق بود" + }, + "messages": { + "linkCopied": "لینک در کلیپ‌بورد کپی شد", + "recipientsNotified": "گیرندگان با موفقیت مطلع شدند" + }, + "empty": { + "message": "هنوز اشتراکی ایجاد نشده است", + "createButton": "ایجاد اشتراک" + }, + "header": { + "title": "اشتراک‌های من", + "myShares": "اشتراک‌های من" + }, + "search": { + "title": "همه اشتراک‌ها", + "createButton": "ایجاد اشتراک", + "placeholder": "جست‌وجوی اشتراک‌ها...", + "results": "{filtered} از {total} اشتراک پیدا شد" + }, + "pageTitle": "اشتراک‌ها" + }, + "sharesTable": { + "ariaLabel": "جدول اشتراک‌ها", + "never": "هرگز", + "columns": { + "name": "نام", + "description": "توضیحات", + "createdAt": "ایجاد شده در", + "expiresAt": "منقضی می‌شود در", + "status": "وضعیت", + "security": "امنیت", + "files": "فایل‌ها", + "recipients": "گیرندگان", + "actions": "اقدامات" + }, + "status": { + "neverExpires": "هرگز منقضی نمی‌شود", + "active": "فعال", + "expired": "منقضی شده" + }, + "security": { + "protected": "محافظت‌شده", + "public": "عمومی" + }, + "filesCount": "فایل‌ها", + "folderCount": "پوشه‌ها", + "recipientsCount": "گیرندگان", + "actions": { + "menu": "منوی اقدامات اشتراک", + "edit": "ویرایش", + "manageFiles": "مدیریت فایل‌ها", + "manageRecipients": "مدیریت گیرندگان", + "viewDetails": "مشاهده جزئیات", + "generateLink": "تولید لینک", + "editLink": "ویرایش لینک", + "copyLink": "کپی لینک", + "viewQrCode": "مشاهده QR کد", + "notifyRecipients": "اطلاع‌رسانی به گیرندگان", + "downloadShareFiles": "دانلود همه فایل‌ها", + "delete": "حذف" + }, + "bulkActions": { + "actions": "اقدامات", + "download": "دانلود موارد انتخاب‌شده", + "delete": "حذف", + "selected": "{count, plural, =1 {۱ اشتراک انتخاب شده} other {# اشتراک انتخاب شده}}" + }, + "selectAll": "انتخاب همه", + "selectShare": "انتخاب اشتراک {shareName}" + }, + "storageUsage": { + "title": "استفاده از ذخیره‌سازی", + "ariaLabel": "نوار پیشرفت استفاده از ذخیره‌سازی", + "used": "استفاده شده", + "available": "موجود", + "total": "کل", + "loading": "در حال بارگذاری...", + "retry": "تلاش مجدد", + "errors": { + "title": "اطلاعات ذخیره‌سازی در دسترس نیست", + "detectionFailed": "تشخیص فضای دیسک ممکن نیست. این ممکن است به دلیل مشکلات پیکربندی سیستم یا مجوزهای ناکافی باشد.", + "serverError": "خطای سرور هنگام دریافت اطلاعات ذخیره‌سازی. لطفاً بعداً دوباره امتحان کنید.", + "unknown": "خطای غیرمنتظره‌ای هنگام بارگذاری اطلاعات ذخیره‌سازی رخ داد." + } + }, + "theme": { + "toggle": "تغییر تم", + "light": "روشن", + "dark": "تیره", + "system": "سیستم" + }, + "twoFactor": { + "title": "احراز هویت دو مرحله‌ای", + "description": "لایه امنیتی اضافی به حساب خود اضافه کنید", + "enabled": "حساب شما با احراز هویت دو مرحله‌ای محافظت می‌شود", + "disabled": "احراز هویت دو مرحله‌ای فعال نیست", + "status": { + "label": "وضعیت:", + "enabled": "فعال", + "disabled": "غیرفعال" + }, + "buttons": { + "enable2FA": "فعال‌سازی 2FA", + "disable2FA": "غیرفعال‌سازی 2FA" + }, + "setup": { + "title": "فعال‌سازی احراز هویت دو مرحله‌ای", + "description": "QR کد را با برنامه احراز هویت خود اسکن کنید، سپس کد تأیید را وارد کنید.", + "qrCode": "QR کد", + "manualEntryKey": "کلید ورود دستی", + "verificationCode": "کد تأیید", + "verificationCodePlaceholder": "کد ۶ رقمی را وارد کنید", + "verificationCodeDescription": "کد ۶ رقمی را از برنامه احراز هویت خود وارد کنید", + "verifyAndEnable": "تأیید و فعال‌سازی", + "cancel": "انصراف" + }, + "disable": { + "title": "غیرفعال‌سازی احراز هویت دو مرحله‌ای", + "description": "برای تأیید غیرفعال‌سازی احراز هویت دو مرحله‌ای، گذرواژه خود را وارد کنید.", + "password": "گذرواژه", + "passwordPlaceholder": "گذرواژه خود را وارد کنید", + "confirm": "تأیید غیرفعال‌سازی", + "cancel": "انصراف" + }, + "backupCodes": { + "title": "کدهای پشتیبان", + "description": "این کدهای پشتیبان را در مکانی امن ذخیره کنید. می‌توانید از آنها برای دسترسی به حساب خود در صورت گم شدن دستگاه احراز هویت استفاده کنید.", + "warning": "مهم:", + "warningText": "هر کد پشتیبان فقط یک بار قابل استفاده است. آنها را امن نگه دارید و با کسی به اشتراک نگذارید.", + "generateNew": "تولید کدهای پشتیبان جدید", + "download": "دانلود کدهای پشتیبان", + "copyToClipboard": "کپی در کلیپ‌بورد", + "savedMessage": "کدهای پشتیبان خود را ذخیره کردم", + "available": "{count} کد پشتیبان موجود", + "instructions": [ + "• این کدها را در مکان امنی ذخیره کنید", + "• هر کد پشتیبان فقط یک بار قابل استفاده است", + "• می‌توانید هر زمان کدهای جدید تولید کنید" + ] + }, + "verification": { + "title": "احراز هویت دو مرحله‌ای", + "description": "کد ۶ رقمی را از برنامه احراز هویت خود وارد کنید", + "backupDescription": "یکی از کدهای پشتیبان خود را برای ادامه وارد کنید", + "verificationCode": "کد تأیید", + "backupCode": "کد پشتیبان", + "verificationCodePlaceholder": "۰۰۰۰۰۰", + "backupCodePlaceholder": "XXXX-XXXX", + "verify": "تأیید", + "verifying": "در حال تأیید...", + "useBackupCode": "استفاده از کد پشتیبان", + "useAuthenticatorCode": "استفاده از کد احراز هویت", + "rememberDevice": "به خاطر سپاری این دستگاه برای ۳۰ روز", + "rememberDeviceDescription": "برای ۳۰ روز نیازی به وارد کردن کدهای 2FA در این دستگاه نخواهید داشت" + }, + "trustedDevices": { + "title": "دستگاه‌های معتمد - 2FA", + "description": "دستگاه‌هایی که به تأیید 2FA نیاز ندارند", + "noDevices": "هیچ دستگاه معتمدی وجود ندارد", + "deviceName": "دستگاه", + "addedOn": "اضافه شده در", + "expiresOn": "منقضی می‌شود در", + "remove": "حذف", + "removeAll": "حذف همه", + "confirmRemove": "آیا از حذف این دستگاه معتمد اطمینان دارید؟", + "confirmRemoveAll": "آیا از حذف همه دستگاه‌های معتمد اطمینان دارید؟", + "deviceRemoved": "دستگاه معتمد با موفقیت حذف شد", + "allDevicesRemoved": "همه دستگاه‌های معتمد با موفقیت حذف شدند", + "loadFailed": "بارگذاری دستگاه‌های معتمد ناموفق بود", + "removeFailed": "حذف دستگاه معتمد ناموفق بود", + "removeAllFailed": "حذف همه دستگاه‌های معتمد ناموفق بود", + "loading": "در حال بارگذاری دستگاه‌های معتمد...", + "noDevicesDescription": "دستگاه‌ها وقتی که در طول تأیید 2FA آنها را معتمد کنید اینجا نمایش داده می‌شوند", + "tableHeaders": { + "device": "دستگاه", + "added": "اضافه شده", + "expires": "منقضی می‌شود", + "lastUsed": "آخرین استفاده", + "ipAddress": "آدرس IP", + "actions": "اقدامات" + }, + "status": { + "never": "هرگز", + "expired": "منقضی شده" + }, + "modals": { + "removeDevice": { + "title": "حذف دستگاه معتمد", + "added": "اضافه شده:", + "ip": "IP:" + }, + "removeAllDevices": { + "title": "حذف همه دستگاه‌های معتمد", + "description": "این {count} دستگاه{count, plural, =1 {} other {}} معتمد را حذف خواهد کرد. باید دوباره 2FA را در همه دستگاه‌ها تأیید کنید." + }, + "buttons": { + "cancel": "انصراف", + "removing": "در حال حذف...", + "removeDevice": "حذف دستگاه", + "removeAllDevices": "حذف همه دستگاه‌ها" + } + } + }, + "messages": { + "enabledSuccess": "احراز هویت دو مرحله‌ای با موفقیت فعال شد!", + "disabledSuccess": "احراز هویت دو مرحله‌ای با موفقیت غیرفعال شد", + "backupCodesGenerated": "کدهای پشتیبان جدید با موفقیت تولید شد", + "backupCodesCopied": "کدهای پشتیبان در کلیپ‌بورد کپی شد", + "setupFailed": "تولید تنظیمات 2FA ناموفق بود", + "verificationFailed": "کد تأیید نامعتبر است", + "disableFailed": "غیرفعال‌سازی 2FA ناموفق بود. لطفاً گذرواژه خود را بررسی کنید.", + "backupCodesFailed": "تولید کدهای پشتیبان ناموفق بود", + "backupCodesCopyFailed": "کپی کدهای پشتیبان ناموفق بود", + "statusLoadFailed": "بارگذاری وضعیت 2FA ناموفق بود", + "enterVerificationCode": "لطفاً کد تأیید را وارد کنید", + "enterPassword": "لطفاً گذرواژه خود را وارد کنید", + "deviceTrusted": "این دستگاه به مدت ۳۰ روز به عنوان معتمد علامت‌گذاری شده است" + }, + "errors": { + "invalidVerificationCode": "کد تأیید نامعتبر است", + "invalidTwoFactorCode": "کد احراز هویت دو مرحله‌ای نامعتبر است", + "twoFactorRequired": "احراز هویت دو مرحله‌ای الزامی است", + "twoFactorAlreadyEnabled": "احراز هویت دو مرحله‌ای قبلاً فعال شده است", + "twoFactorNotEnabled": "احراز هویت دو مرحله‌ای فعال نیست", + "passwordVerificationRequired": "تأیید گذرواژه الزامی است", + "invalidPassword": "گذرواژه نامعتبر است", + "userNotFound": "کاربر یافت نشد" + }, + "deviceNames": { + "unknownDevice": "دستگاه ناشناخته", + "browsers": { + "chrome": "Chrome", + "firefox": "Firefox", + "safari": "Safari", + "edge": "Edge" + }, + "platforms": { + "windows": " در Windows", + "macos": " در macOS", + "linux": " در Linux", + "iphone": " در iPhone", + "android": " در Android" + } + } + }, + "uploadFile": { + "title": "بارگذاری فایل", + "multipleTitle": "بارگذاری فایل‌ها", + "selectFile": "برای انتخاب فایل کلیک کنید", + "selectMultipleFiles": "برای انتخاب یک یا چند فایل کلیک کنید", + "dragAndDrop": "یا فایل‌ها را اینجا بکشید و رها کنید", + "filesQueued": "{count, plural, one {# فایل در صف بارگذاری} other {# فایل در صف بارگذاری}}", + "preview": "پیش‌نمایش", + "uploadProgress": "پیشرفت بارگذاری", + "upload": "بارگذاری", + "startUploads": "شروع بارگذاری", + "retry": "تلاش مجدد", + "finish": "پایان", + "success": "فایل با موفقیت بارگذاری شد", + "allSuccess": "{count, plural, =1 {فایل با موفقیت بارگذاری شد} other {# فایل با موفقیت بارگذاری شد}}", + "partialSuccess": "{success} فایل با موفقیت بارگذاری شد، {error} ناموفق", + "error": "بارگذاری فایل ناموفق بود", + "fileSizeExceeded": "حجم فایل از محدودیت {maxsizemb}MB تجاوز می‌کند.", + "insufficientStorage": "فضای ذخیره‌سازی کافی نیست. شما {availablespace}MB فضای موجود دارید.", + "unauthorized": "غیرمجاز: برای دسترسی به این منبع به یک توکن معتبر نیاز است.", + "globalDrop": { + "title": "فایل‌ها را برای بارگذاری رها کنید", + "description": "برای بارگذاری فایل‌های خود رها کنید" + }, + "confirmCancel": { + "title": "لغو بارگذاری", + "messageSingle": "یک بارگذاری در حال انجام است.", + "messageMultiple": "{count} بارگذاری در حال انجام است.", + "warning": "اگر اکنون ببندید، بارگذاری‌ها لغو خواهند شد و هر گونه پیشرفتی از دست خواهد رفت.", + "continue": "ادامه بارگذاری", + "cancel": "لغو بارگذاری" + }, + "pasteSuccess": "{count, plural, =1 {تصویر چسبانده و با موفقیت بارگذاری شد} other {# تصویر چسبانده و با موفقیت بارگذاری شد}}" + }, + "users": { + "modes": { + "create": "ایجاد", + "edit": "ویرایش" + }, + "errors": { + "loadFailed": "بارگذاری کاربران ناموفق بود", + "submitFailed": "{mode} کاربر ناموفق بود", + "deleteFailed": "حذف کاربر ناموفق بود", + "statusUpdateFailed": "به‌روزرسانی وضعیت کاربر ناموفق بود" + }, + "messages": { + "createSuccess": "کاربر با موفقیت ایجاد شد", + "updateSuccess": "کاربر با موفقیت به‌روز شد", + "deleteSuccess": "کاربر با موفقیت حذف شد", + "activateSuccess": "کاربر با موفقیت فعال شد", + "deactivateSuccess": "کاربر با موفقیت غیرفعال شد" + }, + "actions": { + "edit": "ویرایش", + "activate": "فعال‌سازی", + "deactivate": "غیرفعال‌سازی", + "delete": "حذف" + }, + "delete": { + "title": "تأیید حذف کاربر", + "confirmation": "آیا از حذف کاربر {firstName} {lastName} اطمینان دارید؟ این عمل قابل بازگشت نیست.", + "confirm": "حذف کاربر" + }, + "form": { + "titleCreate": "افزودن کاربر جدید", + "titleEdit": "ویرایش کاربر", + "firstName": "نام", + "lastName": "نام خانوادگی", + "username": "نام کاربری", + "email": "ایمیل", + "password": "گذرواژه", + "newPassword": "گذرواژه جدید (اختیاری)", + "passwordPlaceholder": "برای حفظ گذرواژه فعلی خالی بگذارید", + "role": "نقش", + "roleUser": "کاربر", + "roleAdmin": "مدیر", + "create": "ایجاد", + "save": "ذخیره" + }, + "status": { + "title": "تأیید تغییر وضعیت", + "confirmation": "آیا از {action} کاربر {firstName} {lastName} اطمینان دارید؟", + "activate": "فعال‌سازی", + "deactivate": "غیرفعال‌سازی", + "user": "کاربر" + }, + "header": { + "title": "مدیریت کاربران", + "addUser": "افزودن کاربر", + "management": "مدیریت کاربران" + }, + "table": { + "user": "کاربر", + "email": "ایمیل", + "status": "وضعیت", + "role": "نقش", + "actions": "اقدامات", + "active": "فعال", + "inactive": "غیرفعال", + "admin": "مدیر", + "userr": "کاربر" + }, + "invite": { + "button": "تولید لینک دعوت", + "title": "تولید لینک دعوت کاربر", + "description": "یک لینک یک‌بار مصرف تولید کنید که به کسی اجازه می‌دهد حساب خود را ایجاد کند. لینک پس از ۱۵ دقیقه منقضی می‌شود.", + "generating": "در حال تولید...", + "generate": "تولید لینک", + "generated": "لینک دعوت با موفقیت تولید شد!", + "linkReady": "لینک دعوت آماده است", + "linkReadyDescription": "این لینک را با شخصی که می‌خواهید دعوت کنید به اشتراک بگذارید. آنها می‌توانند حساب خود را به عنوان یک کاربر معمولی ایجاد کنند.", + "copyLink": "کپی لینک", + "linkCopied": "لینک دعوت در کلیپ‌بورد کپی شد!", + "expiresIn": "پس از ۱۵ دقیقه منقضی می‌شود", + "close": "بستن", + "errors": { + "generateFailed": "تولید لینک دعوت ناموفق بود" + } + } + }, + "embedCode": { + "title": "جاسازی رسانه", + "description": "از این کدها برای جاسازی این رسانه در انجمن‌ها، وب‌سایت‌ها یا پلتفرم‌های دیگر استفاده کنید", + "tabs": { + "directLink": "لینک مستقیم", + "html": "HTML", + "bbcode": "BBCode" + }, + "directLinkDescription": "URL مستقیم به فایل رسانه", + "htmlDescription": "از این کد برای جاسازی رسانه در صفحات HTML استفاده کنید", + "bbcodeDescription": "از این کد برای جاسازی رسانه در انجمن‌هایی که از BBCode پشتیبانی می‌کنند استفاده کنید" + }, + "validation": { + "firstNameRequired": "نام الزامی است", + "lastNameRequired": "نام خانوادگی الزامی است", + "usernameLength": "نام کاربری باید حداقل ۳ کاراکتر باشد", + "usernameSpaces": "نام کاربری نمی‌تواند شامل فاصله باشد", + "invalidEmail": "لطفاً یک آدرس ایمیل معتبر وارد کنید", + "passwordLength": "گذرواژه باید حداقل ۸ کاراکتر باشد", + "passwordsMatch": "گذرواژه‌ها باید مطابقت داشته باشند", + "emailRequired": "ایمیل الزامی است", + "emailOrUsernameRequired": "ایمیل یا نام کاربری الزامی است", + "passwordRequired": "گذرواژه الزامی است", + "passwordMinLength": "گذرواژه باید حداقل ۶ کاراکتر باشد", + "nameRequired": "نام الزامی است", + "required": "این فیلد الزامی است" + }, + "registerWithInvite": { + "title": "حساب خود را ایجاد کنید", + "description": "برای ایجاد حساب خود، اطلاعات زیر را پر کنید", + "labels": { + "firstName": "نام", + "firstNamePlaceholder": "نام خود را وارد کنید", + "lastName": "نام خانوادگی", + "lastNamePlaceholder": "نام خانوادگی خود را وارد کنید", + "username": "نام کاربری", + "usernamePlaceholder": "یک نام کاربری انتخاب کنید", + "email": "ایمیل", + "emailPlaceholder": "ایمیل خود را وارد کنید", + "password": "گذرواژه", + "passwordPlaceholder": "یک گذرواژه انتخاب کنید", + "confirmPassword": "تأیید گذرواژه", + "confirmPasswordPlaceholder": "گذرواژه خود را تأیید کنید" + }, + "buttons": { + "creating": "در حال ایجاد حساب...", + "createAccount": "ایجاد حساب" + }, + "validation": { + "firstNameRequired": "نام الزامی است", + "lastNameRequired": "نام خانوادگی الزامی است", + "usernameMinLength": "نام کاربری باید حداقل ۳ کاراکتر باشد", + "invalidEmail": "ایمیل نامعتبر است", + "passwordMinLength": "گذرواژه باید حداقل ۸ کاراکتر باشد", + "passwordsMatch": "گذرواژه‌ها باید مطابقت داشته باشند" + }, + "messages": { + "success": "حساب با موفقیت ایجاد شد! در حال انتقال به صفحه ورود...", + "redirecting": "در حال انتقال به صفحه ورود..." + }, + "errors": { + "invalidToken": "خطا در لینک دعوت", + "tokenUsed": "این لینک دعوت قبلاً استفاده شده است", + "tokenExpired": "این لینک دعوت منقضی شده است", + "usernameExists": "نام کاربری قبلاً وجود دارد", + "emailExists": "ایمیل قبلاً وجود دارد", + "createFailed": "ایجاد حساب ناموفق بود. لطفاً دوباره تلاش کنید." + }, + "pageTitle": "ایجاد حساب" + } +} \ No newline at end of file diff --git a/apps/web/messages/fr-FR.json b/apps/web/messages/fr-FR.json index 1cb22773..933e58f1 100644 --- a/apps/web/messages/fr-FR.json +++ b/apps/web/messages/fr-FR.json @@ -6,7 +6,9 @@ "token_expired": "Jeton expiré. Veuillez réessayer.", "config_error": "Erreur de configuration. Veuillez contacter le support.", "auth_failed": "Échec de l'authentification. Veuillez réessayer." - } + }, + "authenticationFailed": "Échec de l'authentification", + "successfullyAuthenticated": "Authentification réussie !" }, "authProviders": { "title": "Fournisseurs d'authentification", @@ -174,7 +176,14 @@ "tabs": { "shareDetails": "Détails du partage", "selectFiles": "Sélectionner les fichiers" - } + }, + "errors": { + "nameRequired": "Le nom du partage est requis", + "selectItems": "Veuillez sélectionner au moins un fichier ou dossier" + }, + "itemsSelected": "{count, plural, =0 {Aucun élément sélectionné} =1 {1 élément sélectionné} other {# éléments sélectionnés}}", + "passwordPlaceholder": "Entrez le mot de passe", + "selectItemsPrompt": "Sélectionnez les fichiers et dossiers à partager" }, "customization": { "breadcrumb": "Personnalisation", @@ -340,7 +349,8 @@ "addToShare": "Ajouter au partage", "removeFromShare": "Retirer du partage", "saveChanges": "Sauvegarder les Modifications", - "editFolder": "Modifier le dossier" + "editFolder": "Modifier le dossier", + "itemsSelected": "{count, plural, =0 {Aucun élément sélectionné} =1 {1 élément sélectionné} other {# éléments sélectionnés}}" }, "files": { "title": "Tous les Fichiers", @@ -376,7 +386,12 @@ "description": "Téléchargez votre premier fichier ou créez un dossier pour commencer" }, "files": "fichiers", - "folders": "dossiers" + "folders": "dossiers", + "errors": { + "moveItemsFailed": "Échec du déplacement des éléments. Veuillez réessayer.", + "cannotMoveHere": "Impossible de déplacer les éléments vers cet emplacement" + }, + "openFolder": "Ouvrir le dossier" }, "filesTable": { "ariaLabel": "Tableau des fichiers", @@ -539,7 +554,10 @@ "movingTo": "Déplacement vers :", "title": "Déplacer {count, plural, =1 {élément} other {éléments}}", "description": "Déplacer {count, plural, =1 {élément} other {éléments}} vers un nouvel emplacement", - "success": "{count} {count, plural, =1 {élément déplacé} other {éléments déplacés}} avec succès" + "success": "{count} {count, plural, =1 {élément déplacé} other {éléments déplacés}} avec succès", + "errors": { + "moveFailed": "Échec du déplacement des éléments" + } }, "navbar": { "logoAlt": "Logo de l'Application", @@ -1153,8 +1171,6 @@ }, "fileActions": { "edit": "Modifier", - "save": "Enregistrer", - "cancel": "Annuler", "preview": "Aperçu", "download": "Télécharger", "delete": "Supprimer", @@ -1378,16 +1394,6 @@ "deleteConfirmation": "Êtes-vous sûr de vouloir supprimer ce partage ? Cette action ne peut pas être annulée.", "addDescriptionPlaceholder": "Ajouter une description...", "editTitle": "Modifier le Partage", - "nameLabel": "Nom du Partage", - "descriptionLabel": "Description", - "descriptionPlaceholder": "Entrez une description (optionnel)", - "expirationLabel": "Date d'Expiration", - "expirationPlaceholder": "DD/MM/AAAA HH:MM", - "maxViewsLabel": "Vues Maximales", - "maxViewsPlaceholder": "Laissez vide pour illimité", - "passwordProtection": "Protégé par Mot de Passe", - "passwordLabel": "Mot de Passe", - "passwordPlaceholder": "Entrez le mot de passe", "newPasswordLabel": "Nouveau Mot de Passe (laissez vide pour conserver l'actuel)", "newPasswordPlaceholder": "Entrez le nouveau mot de passe", "manageFilesTitle": "Gérer les Fichiers", @@ -1405,7 +1411,9 @@ "linkDescriptionFile": "Générez un lien personnalisé pour partager le fichier", "linkDescriptionFolder": "Générez un lien personnalisé pour partager le dossier", "linkReady": "Votre lien de partage est prêt :", - "linkTitle": "Générer un lien" + "linkTitle": "Générer un lien", + "itemsSelected": "{count, plural, =0 {Aucun élément sélectionné} =1 {1 élément sélectionné} other {# éléments sélectionnés}}", + "manageFilesDescription": "Sélectionnez les fichiers et dossiers à inclure dans ce partage" }, "shareDetails": { "title": "Détails du Partage", @@ -1421,7 +1429,6 @@ "noLink": "Aucun lien généré pour le moment", "copyLink": "Copier le lien", "openLink": "Ouvrir dans un nouvel onglet", - "linkCopied": "Lien copié dans le presse-papiers", "views": "Vues", "dates": "Dates", "created": "Créé", @@ -1469,28 +1476,6 @@ "expires": "Expire:", "expirationDate": "Date d'Expiration" }, - "shareFile": { - "title": "Partager un Fichier", - "linkTitle": "Générer un Lien", - "nameLabel": "Nom du Partage", - "namePlaceholder": "Entrez le nom du partage", - "descriptionLabel": "Description", - "descriptionPlaceholder": "Entrez une description (optionnel)", - "expirationLabel": "Date d'Expiration", - "expirationPlaceholder": "DD/MM/AAAA HH:MM", - "maxViewsLabel": "Vues Maximales", - "maxViewsPlaceholder": "Laissez vide pour illimité", - "passwordProtection": "Protégé par Mot de Passe", - "passwordLabel": "Mot de Passe", - "passwordPlaceholder": "Entrez le mot de passe", - "linkDescription": "Générez un lien personnalisé pour partager le fichier", - "aliasLabel": "Alias du Lien", - "aliasPlaceholder": "Entrez un alias personnalisé", - "linkReady": "Votre lien de partage est prêt :", - "createShare": "Créer un Partage", - "generateLink": "Générer un Lien", - "copyLink": "Copier le Lien" - }, "shareManager": { "deleteSuccess": "Partage supprimé avec succès", "deleteError": "Échec de la suppression du partage", @@ -1520,7 +1505,10 @@ "noFilesToDownload": "Aucun fichier disponible en téléchargement", "singleShareZipName": "{sharename} _files.zip", "zipDownloadError": "Échec de la création du fichier zip", - "zipDownloadSuccess": "Fichier zip téléchargé avec succès" + "zipDownloadSuccess": "Fichier zip téléchargé avec succès", + "errors": { + "multipleDownloadNotSupported": "Le téléchargement de plusieurs partages n'est pas encore pris en charge - veuillez télécharger les partages individuellement" + } }, "shareMultipleFiles": { "title": "Partager Plusieurs Fichiers", @@ -1917,6 +1905,23 @@ "inactive": "Inactif", "admin": "Administrateur", "userr": "Utilisateur" + }, + "invite": { + "button": "Générer un Lien d'Invitation", + "title": "Générer un Lien d'Invitation Utilisateur", + "description": "Générez un lien à usage unique qui permet à quelqu'un de créer son propre compte. Le lien expire dans 15 minutes.", + "generating": "Génération...", + "generate": "Générer le Lien", + "generated": "Lien d'invitation généré avec succès !", + "linkReady": "Lien d'invitation prêt", + "linkReadyDescription": "Partagez ce lien avec la personne que vous souhaitez inviter. Elle pourra créer son propre compte en tant qu'utilisateur régulier.", + "copyLink": "Copier le Lien", + "linkCopied": "Lien d'invitation copié dans le presse-papiers !", + "expiresIn": "Expire dans 15 minutes", + "close": "Fermer", + "errors": { + "generateFailed": "Échec de la génération du lien d'invitation" + } } }, "validation": { @@ -1935,15 +1940,62 @@ "required": "Este campo é obrigatório" }, "embedCode": { - "title": "Intégrer l'image", - "description": "Utilisez ces codes pour intégrer cette image dans des forums, sites web ou autres plateformes", + "title": "Intégrer le média", + "description": "Utilisez ces codes pour intégrer ce média dans des forums, sites web ou autres plateformes", "tabs": { "directLink": "Lien direct", "html": "HTML", "bbcode": "BBCode" }, - "directLinkDescription": "URL directe vers le fichier image", - "htmlDescription": "Utilisez ce code pour intégrer l'image dans des pages HTML", - "bbcodeDescription": "Utilisez ce code pour intégrer l'image dans des forums prenant en charge BBCode" + "directLinkDescription": "URL directe vers le fichier média", + "htmlDescription": "Utilisez ce code pour intégrer le média dans des pages HTML", + "bbcodeDescription": "Utilisez ce code pour intégrer le média dans des forums prenant en charge BBCode" + }, + "contextMenu": { + "newFolder": "Nouveau dossier", + "uploadFile": "Télécharger un fichier" + }, + "registerWithInvite": { + "title": "Créer Votre Compte", + "description": "Remplissez les informations ci-dessous pour créer votre compte", + "labels": { + "firstName": "Prénom", + "firstNamePlaceholder": "Entrez votre prénom", + "lastName": "Nom", + "lastNamePlaceholder": "Entrez votre nom", + "username": "Nom d'Utilisateur", + "usernamePlaceholder": "Choisissez un nom d'utilisateur", + "email": "E-mail", + "emailPlaceholder": "Entrez votre e-mail", + "password": "Mot de Passe", + "passwordPlaceholder": "Choisissez un mot de passe", + "confirmPassword": "Confirmer le Mot de Passe", + "confirmPasswordPlaceholder": "Confirmez votre mot de passe" + }, + "buttons": { + "creating": "Création du compte...", + "createAccount": "Créer un Compte" + }, + "validation": { + "firstNameRequired": "Le prénom est requis", + "lastNameRequired": "Le nom est requis", + "usernameMinLength": "Le nom d'utilisateur doit contenir au moins 3 caractères", + "invalidEmail": "E-mail invalide", + "passwordMinLength": "Le mot de passe doit contenir au moins 8 caractères", + "passwordsMatch": "Les mots de passe doivent correspondre" + }, + "messages": { + "success": "Compte créé avec succès ! Redirection vers la connexion...", + "redirecting": "Redirection vers la connexion..." + }, + "errors": { + "invalidToken": "Erreur avec le lien d'invitation", + "tokenUsed": "Ce lien d'invitation a déjà été utilisé", + "tokenExpired": "Ce lien d'invitation a expiré", + "usernameExists": "Le nom d'utilisateur existe déjà", + "emailExists": "L'e-mail existe déjà", + "createFailed": "Échec de la création du compte. Veuillez réessayer." + }, + "pageTitle": "Créer un Compte" } } \ No newline at end of file diff --git a/apps/web/messages/he-IL.json b/apps/web/messages/he-IL.json new file mode 100644 index 00000000..03782a97 --- /dev/null +++ b/apps/web/messages/he-IL.json @@ -0,0 +1,2001 @@ +{ + "auth": { + "successfullyAuthenticated": "אימות בוצע בהצלחה!", + "authenticationFailed": "האימות נכשל", + "errors": { + "account_inactive": "החשבון לא פעיל. אנא צור קשר עם המנהל.", + "registration_disabled": "הרשמה באמצעות SSO מושבתת.", + "token_expired": "פג תוקף הטוקן. אנא נסה שוב.", + "config_error": "שגיאת תצורה. אנא צור קשר עם התמיכה.", + "auth_failed": "האימות נכשל. אנא נסה שוב." + } + }, + "contextMenu": { + "newFolder": "תיקייה חדשה", + "uploadFile": "העלה קובץ" + }, + "authProviders": { + "title": "ספקי אימות", + "description": "הגדר ספקי אימות חיצוניים ל-SSO", + "enabledCount": "{count} מופעלים", + "loadingProviders": "טוען ספקים...", + "providersConfigured": "{count} ספקים מוגדרים", + "enabledOfTotal": "{enabled} מופעלים מתוך {total} ספקים", + "hideDisabledProviders": "הסתר ספקים מושבתים", + "addProvider": "הוסף ספק", + "addProviderTitle": "הוסף ספק", + "editProvider": "ערוך ספק", + "deleteProvider": "מחק ספק", + "enabled": "מופעל", + "disabled": "מושבת", + "officialProvider": "ספק רשמי", + "dragToReorder": "גרור לסידור מחדש", + "dragDisabledMessage": "גרירה ושחרור מושבתים בעת סינון ספקים. הצג את כל הספקים כדי לסדר אותם מחדש.", + "dragEnabledMessage": "גרור ספקים כדי לסדר אותם מחדש. סדר זה יתבטא בעמוד הכניסה.", + "noProvidersEnabled": "אין ספקי אימות מופעלים", + "noProvidersConfigured": "אין ספקי אימות מוגדרים", + "form": { + "providerName": "שם ספק", + "providerNamePlaceholder": "למשל: mycompany", + "displayName": "שם תצוגה", + "displayNamePlaceholder": "למשל: My Company SSO", + "type": "סוג", + "typeOidc": "OIDC (OpenID Connect)", + "typeOauth2": "OAuth 2.0", + "icon": "אייקון", + "iconPlaceholder": "בחר אייקון", + "clientId": "Client ID", + "clientIdPlaceholder": "OAuth client ID שלך", + "clientSecret": "Client Secret", + "clientSecretPlaceholder": "OAuth client secret שלך", + "oauthScopes": "OAuth Scopes", + "scopesPlaceholder": "הזן scopes (למשל: openid, profile, email)", + "scopesHelpOidc": "Scopes מוצעים אוטומטית על סמך Provider URL. Scopes נפוצים ל-OIDC: openid, profile, email, groups", + "scopesHelpOauth2": "Scopes מוצעים אוטומטית על סמך Provider URL. Scopes נפוצים ל-OAuth2 תלויים בספק", + "providerUrl": "Provider URL", + "providerUrlPlaceholder": "https://auth.example.com", + "providerUrlAutoPlaceholder": "https://your-provider.com (endpoints יגלו אוטומטית)", + "providerUrlManualPlaceholder": "https://your-provider.com", + "autoDiscoveryHelp": "המערכת תגלה אוטומטית authorization, token ו-userinfo endpoints", + "manualConfigurationHelp": "כתובת URL בסיסית של הספק שלך (endpoints יהיו יחסיים לזה)", + "authorizationEndpoint": "Authorization Endpoint", + "authorizationEndpointPlaceholder": "https://auth.example.com/auth", + "tokenEndpoint": "Token Endpoint", + "tokenEndpointPlaceholder": "https://auth.example.com/token", + "userInfoEndpoint": "User Info Endpoint", + "userInfoEndpointPlaceholder": "https://auth.example.com/userinfo", + "configurationMethod": "שיטת הגדרה", + "autoDiscovery": "גילוי אוטומטי", + "autoDiscoveryDescription": "גלה אוטומטית endpoints מה-Provider URL", + "manualEndpoints": "Endpoints ידניים (מומלץ)", + "manualEndpointsDescription": "הגדר ידנית authorization, token ו-user info endpoints", + "callbackUrl": "Callback URL", + "callbackUrlDescription": "השתמש ב-URL זה בהגדרת ספק ה-OAuth שלך", + "copyCallbackUrl": "העתק Callback URL", + "callbackUrlCopied": "Callback URL הועתק ללוח!", + "adminEmailDomains": "דומיינים של דוא\"ל מנהל", + "adminEmailDomainsPlaceholder": "הזן דומיינים (למשל: admin.company.com)", + "adminEmailDomainsHelp": "משתמשים עם דוא\"ל מדומיינים אלה יקבלו הרשאות מנהל", + "autoRegister": "הירשם אוטומטית משתמשים חדשים", + "officialProviderUrlPlaceholder": "החלף placeholder ב-{displayName} URL שלך", + "officialProviderHelp": "זהו ספק רשמי. ה-endpoints מוגדרים מראש. אתה יכול לערוך רק את ה-URL הזה.", + "officialProviderIconHelp": "אתה יכול להתאים את האייקון לספק הרשמי הזה." + }, + "buttons": { + "cancel": "ביטול", + "save": "שמור", + "saving": "שומר...", + "adding": "מוסיף...", + "updating": "מעדכן...", + "saveProvider": "שמור ספק", + "delete": "מחק", + "deleting": "מוחק...", + "edit": "ערוך", + "enable": "הפעל", + "disable": "השבת" + }, + "messages": { + "providerAdded": "הספק נוסף בהצלחה", + "providerUpdated": "הספק עודכן בהצלחה", + "providerDeleted": "הספק נמחק בהצלחה", + "providerOrderUpdated": "סדר הספקים עודכן בהצלחה", + "fillRequiredFields": "אנא מלא את כל השדות הנדרשים (שם, שם תצוגה, client ID, client secret)", + "provideUrlOrEndpoints": "ספק או Provider URL לגילוי אוטומטי או את כל שלושת ה-custom endpoints", + "chooseDiscoveryOrManual": "בחר או גילוי אוטומטי (Provider URL) או endpoints ידניים, לא את שניהם", + "loadFailed": "נכשל בטעינת ספקים", + "addFailed": "נכשל בהוספת ספק", + "updateFailed": "נכשל בעדכון ספק", + "deleteFailed": "נכשל במחיקת ספק", + "orderUpdateFailed": "נכשל בעדכון סדר ספקים" + }, + "info": { + "title": "מידע", + "officialProvidersRecommended": "לתפקוד טוב יותר, שקול שימוש בספקים רשמיים. אם יש לך בעיות עם ספק מותאם אישית, שקול לפתוח issue ב-", + "github": "GitHub", + "officialProvider": "ספק רשמי", + "officialProviderDescription": "הספק הזה מותאם על ידי Palmr. ניתן לערוך רק אישורי כניסה והגדרות.", + "manualConfigTitle": "הגדרה ידנית", + "manualConfigDescription": "אתה מספק את כל ה-endpoints ידנית. ודא שהם נכונים עבור הספק שלך." + }, + "deleteModal": { + "title": "מחק ספק אימות", + "confirmMessage": "האם אתה בטוח שברצונך למחוק את הספק \"{displayName}\"? פעולה זו אינה ניתנת לביטול.", + "providerId": "מזהה ספק: {name}", + "cancel": "ביטול", + "delete": "מחק ספק", + "deleting": "מוחק..." + } + }, + "bulkDownload": { + "title": "הורדה מרובת", + "zipNameLabel": "שם קובץ ZIP", + "zipNamePlaceholder": "הזן שם קובץ", + "description": "{count, plural, =1 {קובץ אחד יעבור דחיסה} other {# קבצים יעברו דחיסה}}", + "download": "הורד ZIP" + }, + "common": { + "loading": "טוען, אנא המתן...", + "loadingSimple": "טוען...", + "cancel": "ביטול", + "save": "שמור", + "saving": "שומר...", + "update": "עדכן", + "updating": "מעדכן...", + "delete": "מחק", + "deleting": "מוחק...", + "close": "סגור", + "download": "הורד", + "unexpectedError": "אירעה שגיאה בלתי צפויה. אנא נסה שוב.", + "yes": "כן", + "no": "לא", + "dashboard": "לוח בקרה", + "back": "חזור", + "click": "לחץ ל-", + "creating": "יוצר...", + "create": "צור", + "rename": "שנה שם", + "move": "העבר", + "share": "שתף", + "search": "חפש", + "copy": "העתק", + "copied": "הועתק" + }, + "createShare": { + "title": "צור שיתוף", + "nameLabel": "שם השיתוף", + "namePlaceholder": "הזן שם לשיתוף שלך", + "descriptionLabel": "תיאור", + "descriptionPlaceholder": "הזן תיאור (אופציונלי)", + "expirationLabel": "תאריך תפוגה", + "expirationPlaceholder": "MM/DD/YYYY HH:MM", + "maxViewsLabel": "מספר צפיות מרבי", + "maxViewsPlaceholder": "השאר ריק ללא הגבלה", + "passwordProtection": "מוגן בסיסמה", + "passwordLabel": "סיסמה", + "passwordPlaceholder": "הזן סיסמה", + "create": "צור שיתוף", + "success": "השיתוף נוצר בהצלחה", + "error": "נכשל ביצירת שיתוף", + "errors": { + "nameRequired": "שם השיתוף נדרש", + "selectItems": "אנא בחר לפחות קובץ או תיקייה אחד" + }, + "itemsSelected": "{count} פריטים נבחרו", + "selectItemsPrompt": "בחר קבצים ותיקיות לשיתוף", + "tabs": { + "shareDetails": "פרטי שיתוף", + "selectFiles": "בחר קבצים" + }, + "nextSelectFiles": "הבא: בחר קבצים", + "searchLabel": "חיפוש" + }, + "customization": { + "breadcrumb": "התאמה אישית", + "colors": { + "title": "צבעי ערכת נושא", + "description": "בחר את צבע הנושא העיקרי המועדף עליך", + "presets": "צבעים זמינים", + "presetsDescription": "בחר מערכות נושא צבעוניות זמינות", + "reset": "אפס לברירת מחדל" + }, + "fonts": { + "title": "טיפוגרפיה", + "description": "בחר את משפחת הגופנים המועדפת עליך", + "available": "גופנים זמינים", + "availableDescription": "בחר ממשפחות גופנים זמינות", + "reset": "אפס לברירת מחדל" + }, + "radius": { + "title": "רדיוס פינות", + "description": "התאם אישית את עגלות רכיבי הממשק", + "available": "אפשרויות עגלות", + "availableDescription": "בחר כיצד הפינות המעוגלות אמורות להיראות", + "reset": "אפס לברירת מחדל" + }, + "background": { + "title": "צבעי רקע", + "description": "התאם אישית צבעי רקע למצבי בהיר וכהה", + "lightMode": "מצב בהיר", + "darkMode": "מצב כהה", + "availableDescription": "בחר צבעי רקע גם לנושא בהיר וגם לנושא כהה", + "reset": "אפס לברירת מחדל" + }, + "theme": { + "title": "מצב נושא", + "description": "בחר בין נושא בהיר, כהה או מערכת", + "selectTheme": "העדפת נושא", + "availableDescription": "בחר את מצב הנושא המועדף עליך", + "reset": "אפס למערכת" + }, + "pageTitle": "התאמה אישית" + }, + "dashboard": { + "loadError": "נכשל בטעינת נתוני לוח הבקרה", + "linkCopied": "הקישור הועתק ללוח", + "pageTitle": "לוח בקרה", + "breadcrumb": "לוח בקרה", + "recentFiles": { + "title": "קבצים אחרונים", + "description": "הקבצים שהועלו לאחרונה" + } + }, + "deleteConfirmation": { + "filesToDelete": "קבצים למחיקה", + "foldersToDelete": "תיקיות למחיקה", + "itemsToDelete": "פריטים למחיקה", + "sharesToDelete": "שיתופים למחיקה" + }, + "downloadQueue": { + "downloadQueued": "הורדה בתור: {fileName}", + "queuedDescription": "ההורדה תתחיל אוטומטית כאשר יהיה מקום פנוי", + "queuePosition": "הורדה בתור במיקום {position}: {fileName}", + "estimatedWait": "זמן המתנה משוער: {time}", + "queueFull": "תור ההורדות מלא", + "queueFullDescription": "אנא נסה שוב בעוד מספר דקות כאשר יהיה מקום בתור", + "cancelSuccess": "ההורדה בוטלה בהצלחה", + "cancelError": "נכשל בביטול ההורדה: {error}", + "status": { + "pending": "מכין...", + "queued": "בתור", + "downloading": "מוריד", + "completed": "הושלם", + "failed": "נכשל" + }, + "waitTime": { + "seconds": "{seconds}ש", + "minutes": "{minutes}ד", + "hoursMinutes": "{hours}ש {minutes}ד" + }, + "indicator": { + "title": "הורדות", + "downloads": "תור הורדות", + "active": "פעיל", + "queued": "בתור", + "position": "מיקום {position}", + "estimatedWait": "המתנה: {time}", + "unknownFile": "קובץ לא ידוע", + "noDownloads": "אין הורדות בתהליך", + "refresh": "רענן תור" + } + }, + "emptyState": { + "noFiles": "אין עדיין קבצים שהועלו", + "uploadFile": "העלה קובץ" + }, + "errors": { + "invalidCredentials": "דוא\"ל או סיסמה לא חוקיים", + "userNotFound": "משתמש לא נמצא", + "accountLocked": "החשבון נעול. אנא נסה שוב מאוחר יותר", + "unexpectedError": "אירעה שגיאה בלתי צפויה. אנא נסה שוב", + "Invalid verification code": "קוד אימות לא חוקי", + "Two-factor authentication is already enabled": "אימות דו-שלבי כבר מופעל", + "Two-factor authentication is not enabled": "אימות דו-שלבי אינו מופעל", + "Invalid password": "סיסמה לא חוקית", + "Password verification required": "נדרש אימות סיסמה", + "Invalid two-factor authentication code": "קוד אימות דו-שלבי לא חוקי", + "Two-factor authentication required": "נדרש אימות דו-שלבי", + "noUserData": "אין נתוני משתמש" + }, + "fileActions": { + "editFile": "ערוך קובץ", + "nameLabel": "שם", + "namePlaceholder": "הזן שם חדש", + "extension": "סיומת", + "descriptionLabel": "תיאור", + "descriptionPlaceholder": "הזן תיאור קובץ", + "addDescriptionPlaceholder": "הוסף תיאור...", + "deleteFile": "מחק קובץ", + "deleteConfirmation": "האם אתה בטוח שברצונך למחוק קובץ זה?", + "deleteWarning": "פעולה זו אינה ניתנת לביטול." + }, + "fileManager": { + "downloadError": "נכשל בהורדת קובץ", + "updateSuccess": "הקובץ עודכן בהצלחה", + "updateError": "נכשל בעדכון קובץ", + "deleteSuccess": "הקובץ נמחק בהצלחה", + "deleteError": "נכשל במחיקת קובץ" + }, + "filePreview": { + "title": "תצוגה מקדימה של קובץ", + "description": "תצוגה מקדימה והורדת קובץ", + "loading": "טוען...", + "notAvailable": "תצוגה מקדימה אינה זמינה עבור סוג קובץ זה", + "downloadToView": "השתמש בכפתור ההורדה כדי להציג קובץ זה", + "loadError": "שגיאה בטעינת תצוגה מקדימה של הקובץ", + "downloadError": "שגיאה בהורדת הקובץ", + "audioNotSupported": "הדפדפן שלך אינו תומך בהשמעת אודיו", + "videoNotSupported": "הדפדפן שלך אינו תומך בהשמעת וידאו", + "pdfPreviewNotAvailable": "תצוגה מקדימה של PDF אינה זמינה. נסה תצוגה חלופית או הורד", + "tryAlternativeView": "נסה תצוגה חלופית", + "loadingAlternative": "טוען תצוגה חלופית...", + "loadingAudio": "טוען אודיו..." + }, + "fileSelector": { + "availableFiles": "קבצים זמינים ({count})", + "shareFiles": "קבצים משותפים ({count})", + "shareFilesDescription": "קבצים כרגע בשיתוף זה", + "availableFilesDescription": "בחר קבצים להוספה לשיתוף זה", + "searchPlaceholder": "חפש קבצים...", + "searchSelectedFiles": "חפש קבצים נבחרים...", + "noMatchingFiles": "לא נמצאו קבצים תואמים", + "noAvailableFiles": "אין קבצים זמינים", + "noFilesInShare": "אין קבצים בשיתוף זה", + "noFilesFound": "לא נמצאו קבצים", + "noFilesFoundWith": "לא נמצאו קבצים התואמים ל-\"{query}\"", + "addFilesFromList": "הוסף קבצים מהרשימה למטה", + "tryDifferentSearch": "נסה מונחי חיפוש שונים", + "allFilesInShare": "כל הקבצים כבר בשיתוף זה", + "uploadNewFiles": "העלה קבצים חדשים כדי להוסיף אותם", + "fileCount": "{count, plural, =1 {קובץ} other {קבצים}}", + "filesSelected": "{count, plural, =0 {לא נבחרו קבצים} =1 {קובץ אחד נבחר} other {# קבצים נבחרו}}", + "itemsSelected": "{count} פריטים נבחרו", + "editFile": "ערוך קובץ", + "editFolder": "ערוך תיקייה", + "previewFile": "תצוגה מקדימה של קובץ", + "addToShare": "הוסף לשיתוף", + "removeFromShare": "הסר משיתוף", + "saveChanges": "שמור שינויים" + }, + "files": { + "title": "כל הקבצים", + "uploadFile": "העלה קובץ", + "loadError": "נכשל בטעינת קבצים", + "pageTitle": "הקבצים שלי", + "breadcrumb": "הקבצים שלי", + "downloadStart": "ההורדה החלה", + "downloadError": "נכשל בהורדת קובץ", + "updateSuccess": "הקובץ עודכן בהצלחה", + "updateError": "נכשל בעדכון קובץ", + "deleteSuccess": "הקובץ נמחק בהצלחה", + "deleteError": "נכשל במחיקת קובץ", + "bulkDownloadSuccess": "הורדת הקבצים החלה בהצלחה", + "bulkDownloadError": "שגיאה ביצירת קובץ ZIP", + "bulkDownloadFileError": "שגיאה בהורדת קובץ {fileName}", + "bulkDeleteSuccess": "{count, plural, =1 {פריט אחד נמחק בהצלחה} other {# פריטים נמחקו בהצלחה}}", + "bulkDeleteError": "שגיאה במחיקת פריטים נבחרים", + "bulkDeleteTitle": "מחק פריטים נבחרים", + "bulkDeleteConfirmation": "האם אתה בטוח שברצונך למחוק {count, plural, =1 {פריט אחד} other {# פריטים}}? פעולה זו אינה ניתנת לביטול.", + "totalFiles": "{count, plural, =0 {אין קבצים} =1 {קובץ אחד} other {# קבצים}}", + "openFolder": "פתח תיקייה", + "errors": { + "moveItemsFailed": "נכשל בהעברת פריטים. אנא נסה שוב.", + "cannotMoveHere": "לא ניתן להעביר פריטים למיקום זה" + }, + "empty": { + "title": "אין עדיין קבצים או תיקיות", + "description": "העלה את הקובץ הראשון שלך או צור תיקייה כדי להתחיל" + }, + "files": "קבצים", + "folders": "תיקיות", + "actions": { + "open": "פתח", + "rename": "שנה שם", + "delete": "מחק" + }, + "viewMode": { + "table": "טבלה", + "grid": "רשת" + } + }, + "filesTable": { + "ariaLabel": "טבלת קבצים", + "selectAll": "בחר הכל", + "selectFile": "בחר קובץ {fileName}", + "columns": { + "name": "שם", + "description": "תיאור", + "size": "גודל", + "createdAt": "נוצר ב", + "updatedAt": "עודכן ב", + "actions": "פעולות" + }, + "actions": { + "menu": "תפריט פעולות קובץ", + "preview": "תצוגה מקדימה", + "edit": "ערוך", + "share": "שתף", + "download": "הורד", + "delete": "מחק" + }, + "bulkActions": { + "selected": "{count, plural, =1 {קובץ אחד נבחר} other {# קבצים נבחרו}}", + "actions": "פעולות", + "download": "הורד נבחרים", + "share": "שתף נבחרים", + "delete": "מחק נבחרים" + } + }, + "folderActions": { + "editFolder": "ערוך תיקייה", + "folderName": "שם תיקייה", + "folderNamePlaceholder": "הזן שם תיקייה", + "folderDescription": "תיאור", + "folderDescriptionPlaceholder": "הזן תיאור תיקייה (אופציונלי)", + "createFolder": "צור תיקייה", + "renameFolder": "שנה שם תיקייה", + "moveFolder": "העבר תיקייה", + "shareFolder": "שתף תיקייה", + "deleteFolder": "מחק תיקייה", + "moveTo": "העבר ל", + "selectDestination": "בחר תיקיית יעד", + "rootFolder": "שורש", + "folderCreated": "התיקייה נוצרה בהצלחה", + "folderRenamed": "התיקייה שונתה בהצלחה", + "folderMoved": "התיקייה הועברה בהצלחה", + "folderDeleted": "התיקייה נמחקה בהצלחה", + "folderShared": "התיקייה שותפה בהצלחה", + "createFolderError": "שגיאה ביצירת תיקייה", + "renameFolderError": "שגיאה בשינוי שם תיקייה", + "moveFolderError": "שגיאה בהעברת תיקייה", + "deleteFolderError": "שגיאה במחיקת תיקייה", + "shareFolderError": "שגיאה בשיתוף תיקייה", + "deleteConfirmation": "האם אתה בטוח שברצונך למחוק תיקייה זו?", + "deleteWarning": "פעולה זו אינה ניתנת לביטול." + }, + "footer": { + "poweredBy": "מופעל על ידי", + "kyanHomepage": "דף הבית של Kyantech" + }, + "forgotPassword": { + "emailLabel": "כתובת דוא\"ל", + "emailPlaceholder": "הזן את הדוא\"ל שלך", + "sending": "שולח...", + "submit": "שלח הוראות איפוס", + "backToLogin": "חזור לכניסה", + "title": "שכחתי סיסמה", + "description": "הזן את כתובת הדוא\"ל שלך ונשלח לך הוראות לאיפוס הסיסמה", + "resetInstructions": "הוראות איפוס נשלחו לדוא\"ל שלך", + "pageTitle": "שכחתי סיסמה", + "passwordAuthDisabled": "אימות סיסמה מושבת. אנא צור קשר עם המנהל או השתמש בספק אימות חיצוני." + }, + "generateShareLink": { + "generateTitle": "צור קישור שיתוף", + "updateTitle": "עדכן קישור שיתוף", + "generateDescription": "צור קישור מותאם אישית לשיתוף זה. ניתן להתאים את כתובת ה-URL כדי שתהיה בלתי נשכחת יותר.", + "updateDescription": "עדכן את הקישור המותאם אישית לשיתוף זה. ניתן להתאים את כתובת ה-URL כדי שתהיה בלתי נשכחת יותר.", + "aliasPlaceholder": "מזהה מותאם אישית לקישור", + "linkReady": "קישור השיתוף שלך מוכן. ניתן להעתיק אותו כעת.", + "readyDescription": "קישור השיתוף שלך מוכן. ניתן לסרוק את קוד ה-QR ישירות, להוריד אותו לשימוש מאוחר יותר, או להעתיק את הקישור למטה.", + "generateButton": "צור קישור", + "updateButton": "עדכן קישור", + "copyButton": "העתק קישור", + "success": "הקישור נוצר בהצלחה", + "error": "נכשל ביצירת קישור", + "copied": "הקישור הועתק ללוח", + "tabs": { + "link": "קישור", + "qrcode": "קוד QR" + } + }, + "home": { + "description": "האלטרנטיבה בקוד פתוח ל-WeTransfer. שתף קבצים בצורה מאובטחת, ללא מעקב או הגבלות.", + "documentation": "תיעוד", + "starOnGithub": "תן כוכב ב-GitHub", + "privacyMessage": "בנוי עם פרטיות בראש. הקבצים שלך נגישים רק לאלו עם קישור השיתוף לפני ההעלאה. חינמי וקוד פתוח לנצח.", + "header": { + "fileSharing": "שיתוף קבצים", + "tagline": "נעשה פשוט וחינם" + }, + "pageTitle": "בית" + }, + "iconPicker": { + "title": "בחר אייקון", + "placeholder": "בחר אייקון", + "searchPlaceholder": "חפש אייקונים...", + "loadingMore": "טוען עוד אייקונים...", + "allIconsLoaded": "כל {count} האייקונים נטענו", + "noIconsFound": "לא נמצאו אייקונים עבור \"{search}\"", + "tabs": { + "all": "כל האייקונים", + "popular": "פופולרי", + "auth": "ספקי אימות" + }, + "stats": "{iconCount} אייקונים מ-{libraryCount} ספריות", + "categoryBadge": "{category} ({count} אייקונים)" + }, + "imageEdit": { + "title": "ערוך תמונה", + "rotate": "סובב", + "zoom": "זום", + "cropInstructions": "גרור כדי למקם מחדש, שנה גודל פינות כדי להתאים אזור חיתוך" + }, + "login": { + "welcome": "ברוכים הבאים ל", + "signInToContinue": "היכנס כדי להמשיך", + "emailOrUsernameLabel": "דוא\"ל או שם משתמש", + "emailOrUsernamePlaceholder": "הזן את הדוא\"ל או שם המשתמש שלך", + "emailLabel": "כתובת דוא\"ל", + "emailPlaceholder": "הזן את הדוא\"ל שלך", + "passwordLabel": "סיסמה", + "passwordPlaceholder": "הזן את הסיסמה שלך", + "signIn": "היכנס", + "signingIn": "נכנס...", + "forgotPassword": "שכחת סיסמה?", + "pageTitle": "כניסה", + "or": "או", + "continueWithSSO": "המשך עם SSO", + "processing": "מעבד אימות..." + }, + "logo": { + "labels": { + "appLogo": "לוגו אפליקציה" + }, + "buttons": { + "upload": "העלה לוגו", + "remove": "הסר לוגו" + }, + "messages": { + "uploadSuccess": "הלוגו הועלה בהצלחה", + "removeSuccess": "הלוגו הוסר בהצלחה" + }, + "errors": { + "uploadFailed": "נכשל בהעלאת לוגו", + "removeFailed": "נכשל בהסרת לוגו" + } + }, + "moveItems": { + "itemsToMove": "פריטים להעברה:", + "movingTo": "מעביר ל:", + "title": "העבר {count, plural, =1 {פריט} other {פריטים}}", + "description": "העבר {count, plural, =1 {פריט} other {פריטים}} למיקום חדש", + "success": "הועברו בהצלחה {count} {count, plural, =1 {פריט} other {פריטים}}", + "errors": { + "moveFailed": "נכשל בהעברת פריטים" + } + }, + "navbar": { + "logoAlt": "לוגו אפליקציה", + "profileMenu": "תפריט פרופיל", + "profile": "פרופיל", + "customization": "התאמה אישית", + "settings": "הגדרות", + "usersManagement": "ניהול משתמשים", + "logout": "התנתק" + }, + "navigation": { + "dashboard": "לוח בקרה" + }, + "notifications": { + "permissionGranted": "התראות הורדה מופעלות", + "permissionDenied": "התראות הורדה מושבתות", + "downloadComplete": { + "title": "ההורדה הושלמה", + "body": "{fileName} סיים להוריד" + }, + "downloadFailed": { + "title": "ההורדה נכשלה", + "body": "נכשל בהורדת {fileName}: {error}", + "unknownError": "שגיאה לא ידועה" + }, + "queueProcessing": { + "title": "ההורדה מתחילה", + "body": "{fileName} מוריד כעת{position}", + "position": " (היה #{position} בתור)" + } + }, + "profile": { + "password": { + "title": "שנה סיסמה", + "newPassword": "סיסמה חדשה", + "confirmPassword": "אשר סיסמה חדשה", + "updateButton": "עדכן סיסמה" + }, + "form": { + "title": "מידע פרופיל", + "firstName": "שם פרטי", + "lastName": "שם משפחה", + "username": "שם משתמש", + "email": "דוא\"ל", + "updateButton": "עדכן פרופיל" + }, + "header": { + "title": "פרופיל", + "subtitle": "נהל את המידע האישי והסיסמה שלך" + }, + "picture": { + "title": "תמונת פרופיל", + "description": "לחץ על אייקון המצלמה כדי לשנות את תמונת הפרופיל שלך", + "uploadPhoto": "העלה תמונה", + "removePhoto": "הסר תמונה" + }, + "errors": { + "loadFailed": "נכשל בטעינת נתוני משתמש", + "updateFailed": "נכשל בעדכון פרופיל", + "passwordFailed": "נכשל בעדכון סיסמה", + "imageFailed": "נכשל בעדכון תמונת פרופיל", + "imageRemoveFailed": "נכשל בהסרת תמונת פרופיל" + }, + "messages": { + "noChanges": "אין שינויים לשמור", + "updateSuccess": "הפרופיל עודכן בהצלחה", + "fillPasswords": "אנא מלא את שני שדות הסיסמה", + "passwordSuccess": "הסיסמה עודכנה בהצלחה", + "imageSuccess": "תמונת הפרופיל עודכנה בהצלחה", + "imageRemoved": "תמונת הפרופיל הוסרה בהצלחה" + }, + "pageTitle": "פרופיל" + }, + "qrCodeModal": { + "title": "קוד QR לשיתוף", + "description": "סרוק קוד QR זה כדי לגשת לקישור.", + "download": "הורד קוד QR" + }, + "quickAccess": { + "files": { + "title": "הקבצים שלי", + "description": "גש ונהל את הקבצים שהעלית" + }, + "shares": { + "title": "השיתופים שלי", + "description": "צפה ונהל את הקבצים המשותפים שלך" + }, + "reverseShares": { + "title": "קבל קבצים", + "description": "צור קישורים לאחרים לשלוח לך קבצים" + } + }, + "recentFiles": { + "title": "העלאות אחרונות", + "viewAll": "צפה בהכל", + "upload": "העלה", + "uploadFile": "העלה קובץ", + "noFiles": "אין עדיין קבצים שהועלו" + }, + "recentShares": { + "title": "שיתופים אחרונים", + "viewAll": "צפה בהכל", + "createShare": "צור שיתוף", + "noShares": "אין עדיין שיתופים שנוצרו", + "createFirst": "צור את השיתוף הראשון שלך" + }, + "recipientSelector": { + "emailPlaceholder": "הזן דוא\"ל נמען", + "add": "הוסף", + "recipients": "נמענים ({count})", + "notifyAll": "הודע לכולם", + "noRecipients": "אין עדיין נמענים שנוספו", + "addSuccess": "הנמען נוסף בהצלחה", + "addError": "נכשל בהוספת נמען", + "removeSuccess": "הנמען הוסר בהצלחה", + "removeError": "נכשל בהסרת נמען", + "sendingNotifications": "שולח התראות...", + "notifySuccess": "הנמענים קיבלו הודעה בהצלחה", + "notifyError": "נכשל בהודעה לנמענים", + "selectAll": "בחר הכל", + "selectedCount": "{count} נבחרו", + "selectRecipient": "בחר {email}", + "notifySelected": "הודע לנבחרים", + "removeSelected": "הסר נבחרים", + "notifySingle": "הודע לנמען זה", + "removeSingle": "הסר נמען זה", + "bulkRemoveSuccess": "{count} נמענים הוסרו בהצלחה", + "bulkRemoveError": "נכשל בהסרת נמענים נבחרים", + "bulkNotifySuccess": "התראות נשלחו ל-{count} נמענים", + "bulkNotifyError": "נכשל בהודעה לנמענים נבחרים", + "singleNotifySuccess": "התראה נשלחה ל-{email}", + "singleNotifyError": "נכשל בהודעה לנמען", + "modalDescription": "הוסף ונהל נמענים לשיתוף זה. ניתן להודיע לכולם או לנמענים ספציפיים כאשר SMTP מוגדר.", + "addRecipient": "הוסף נמען", + "invalidEmail": "אנא הזן כתובת דוא\"ל חוקית", + "duplicateEmail": "נמען זה כבר נוסף", + "noRecipientsDescription": "הוסף נמענים כדי לשתף תוכן זה באמצעות דוא\"ל" + }, + "register": { + "validation": { + "firstNameRequired": "שם פרטי נדרש", + "lastNameRequired": "שם משפחה נדרש", + "usernameMinLength": "שם משתמש חייב להכיל לפחות 3 תווים", + "invalidEmail": "דוא\"ל לא חוקי", + "passwordMinLength": "סיסמה חייבת להכיל לפחות 8 תווים", + "success": "משתמש מנהל נוצר בהצלחה!", + "error": "שגיאה ביצירת משתמש מנהל" + }, + "labels": { + "firstName": "שם פרטי", + "lastName": "שם משפחה", + "username": "שם משתמש", + "email": "דוא\"ל", + "password": "סיסמה" + }, + "buttons": { + "creating": "יוצר...", + "createAdmin": "צור חשבון מנהל" + } + }, + "resetPassword": { + "pageTitle": "אפס סיסמה", + "header": { + "title": "אפס סיסמה", + "description": "הזן את הסיסמה החדשה שלך למטה" + }, + "form": { + "newPassword": "סיסמה חדשה", + "newPasswordPlaceholder": "הזן את הסיסמה החדשה שלך", + "confirmPassword": "אשר סיסמה חדשה", + "confirmPasswordPlaceholder": "אשר את הסיסמה החדשה שלך", + "resetting": "מאפס סיסמה...", + "submit": "אפס סיסמה", + "backToLogin": "חזור לכניסה" + }, + "messages": { + "success": "הסיסמה אופסה בהצלחה" + }, + "errors": { + "serverError": "נכשל באיפוס סיסמה. אנא נסה שוב.", + "invalidToken": "טוקן איפוס לא חוקי או חסר" + } + }, + "reverseShares": { + "pageTitle": "קבל קבצים", + "search": { + "title": "נהל קישורי קבלה", + "createButton": "צור קישור", + "placeholder": "חפש קישורי קבלה...", + "results": "נמצאו {filtered} מתוך {total} קישורי קבלה" + }, + "labels": { + "files": "קבצים", + "size": "גודל", + "status": "סטטוס", + "access": "גישה", + "description": "תיאור", + "pageLayout": "פריסת עמוד", + "security": "אבטחה וסטטוס", + "limits": "הגבלות", + "maxFiles": "מקסימום קבצים", + "maxFileSize": "גודל מרבי", + "allowedTypes": "סוגים מותרים", + "filesReceived": "קבצים שהתקבלו", + "fileLimit": "הגבלת קבצים", + "noLimit": "ללא הגבלה", + "noLinkCreated": "לא נוצר קישור", + "publicAccess": "גישה ציבורית", + "protectedByPassword": "מוגן בסיסמה", + "configureProtection": "לחץ להגדרת הגנה", + "enterPassword": "הזן סיסמה", + "thisLinkProtected": "קישור זה יוגן בסיסמה", + "thisLinkPublic": "קישור זה יהיה זמין לציבור", + "configureExpiration": "הגדר תפוגה", + "configureLimits": "הגדר הגבלות קבצים", + "protectWithPassword": "הגן בסיסמה", + "layoutOptions": { + "default": "ברירת מחדל", + "wetransfer": "WeTransfer" + }, + "noFilesLimit": "ללא הגבלת קבצים", + "noSizeLimit": "ללא הגבלת גודל", + "allFileTypes": "כל סוגי הקבצים", + "fileTypesHelp": "הזן סיומות ללא נקודות, מופרדות ברווח, פסיק, מקף או קו אנכי", + "fieldRequirements": "דרישות שדות", + "nameFieldRequired": "שדה שם", + "emailFieldRequired": "שדה דוא\"ל", + "fieldOptions": { + "hidden": "מוסתר", + "optional": "אופציונלי", + "required": "נדרש" + } + }, + "card": { + "untitled": "קישור ללא כותרת", + "noDescription": "אין תיאור", + "addDescriptionPlaceholder": "הוסף תיאור...", + "files": "קבצים", + "progress": "התקדמות", + "created": "נוצר", + "expired": "פג תוקף", + "expires": "תפוגה", + "viewDetails": "צפה בפרטים", + "viewQrCode": "צפה בקוד QR", + "copyLink": "העתק קישור", + "openInNewTab": "פתח בכרטיסייה חדשה", + "editLink": "ערוך קישור", + "createLink": "צור קישור", + "delete": "מחק", + "copyLinkTitle": "העתק קישור", + "createLinkCTA": "צור קישור קבלה" + }, + "status": { + "active": "פעיל", + "inactive": "לא פעיל", + "expired": "פג תוקף", + "protected": "מוגן", + "public": "ציבורי" + }, + "actions": { + "copyLink": "העתק קישור", + "editAlias": "ערוך כינוי", + "createAlias": "צור כינוי", + "viewDetails": "צפה בפרטים", + "edit": "ערוך", + "delete": "מחק", + "viewFiles": "קבצים שהתקבלו", + "viewQrCode": "צפה בקוד QR" + }, + "empty": { + "title": "לא נוצרו קישורי קבלה", + "description": "צור קישורים מותאמים אישית לאחרים לשלוח קבצים ישירות אליך בצורה מאובטחת ומאורגנת.", + "createButton": "צור קישור ראשון" + }, + "modals": { + "create": { + "title": "צור קישור קבלה", + "description": "הגדר קישור מותאם אישית לקבלת קבצים מאחרים" + }, + "edit": { + "title": "ערוך קישור קבלה", + "description": "עדכן את ההגדרות לקישור קבלה זה", + "updating": "מעדכן...", + "saveChanges": "שמור שינויים" + }, + "details": { + "title": "פרטי קישור", + "description": "צפה וערוך את מידע קישור הקבלה שלך", + "pageLayout": "פריסת עמוד", + "linkSection": "קישור קבלה", + "noLinkCreated": "לא נוצר קישור", + "limits": "הגבלות", + "maxFiles": "מקסימום קבצים", + "maxFileSize": "גודל מרבי", + "allowedTypes": "סוגים מותרים", + "noLimit": "ללא הגבלה", + "security": "אבטחה", + "status": "סטטוס", + "password": "סיסמה", + "files": "קבצים שהתקבלו", + "noFiles": "טרם התקבלו קבצים", + "copyLink": "העתק קישור", + "openLink": "פתח קישור", + "editAlias": "ערוך כינוי", + "createAlias": "צור כינוי", + "editPassword": "ערוך הגנת סיסמה", + "basicInfo": "מידע בסיסי", + "securityAndStatus": "אבטחה וסטטוס", + "protection": "הגנה", + "protectedByPassword": "מוגן בסיסמה", + "publicAccess": "גישה ציבורית", + "active": "פעיל", + "inactive": "לא פעיל", + "deactivate": "השבת", + "activate": "הפעל", + "expiration": "תפוגה", + "dates": "תאריכים", + "createdAt": "נוצר ב", + "updatedAt": "עודכן ב", + "allTypes": "כל הסוגים", + "placeholderTypes": ".pdf,.jpg,.png (מופרד בפסיקים)", + "downloadSuccess": "ההורדה החלה", + "downloadError": "שגיאה בהורדת קובץ", + "editSuccess": "הקובץ עודכן בהצלחה", + "editError": "שגיאה בעדכון קובץ", + "previewNotAvailable": "תצוגה מקדימה אינה זמינה", + "notAvailable": "לא זמין", + "invalidDate": "תאריך לא חוקי" + }, + "alias": { + "editTitle": "ערוך כינוי", + "createTitle": "צור כינוי", + "editDescription": "עדכן את הכינוי לקישור קבלה זה", + "createDescription": "צור כינוי מותאם אישית לקישור קבלה זה", + "aliasLabel": "כינוי קישור", + "aliasPlaceholder": "my-custom-link", + "preview": "תצוגה מקדימה:", + "currentLink": "קישור נוכחי:", + "copyCurrentLink": "העתק קישור נוכחי", + "randomTooltip": "צור כינוי אקראי", + "cancel": "ביטול", + "creating": "יוצר...", + "updating": "מעדכן...", + "create": "צור כינוי", + "update": "עדכן כינוי", + "validation": { + "required": "כינוי נדרש", + "minLength": "כינוי חייב להכיל לפחות 3 תווים", + "maxLength": "כינוי יכול להכיל עד 50 תווים", + "pattern": "כינוי חייב להכיל רק אותיות, מספרים, מקפים וקווים תחתונים" + }, + "help": "3-50 תווים. רווחים יומרו אוטומטית למקפים." + }, + "password": { + "title": "ערוך הגנת סיסמה", + "description": "הגדר הגנת סיסמה לקישור זה", + "hasPassword": "מוגן בסיסמה", + "password": "סיסמה", + "cancel": "ביטול", + "save": "שמור", + "saving": "שומר..." + }, + "receivedFiles": { + "title": "קבצים שהתקבלו", + "description": "צפה ונהל קבצים שנשלחו לקישור זה", + "noFiles": "טרם התקבלו קבצים", + "noFilesDescription": "קבצים שנשלחו דרך קישור זה יופיעו כאן", + "fileCount": "{count, plural, =0 {אין קבצים} =1 {קובץ אחד} other {# קבצים}}", + "invalidDate": "תאריך לא חוקי", + "totalSize": "גודל כולל: {size}", + "columns": { + "file": "קובץ", + "size": "גודל", + "sender": "נשלח על ידי", + "date": "תאריך", + "actions": "פעולות" + }, + "actions": { + "preview": "תצוגה מקדימה", + "download": "הורד", + "copyToMyFiles": "העתק לקבצים שלי", + "copying": "מעתיק..." + }, + "uploadedBy": "הועלה על ידי {name}", + "anonymous": "אנונימי", + "downloadSuccess": "ההורדה החלה", + "downloadError": "שגיאה בהורדת קובץ", + "editSuccess": "הקובץ עודכן בהצלחה", + "editError": "שגיאה בעדכון קובץ", + "previewNotAvailable": "תצוגה מקדימה אינה זמינה", + "copySuccess": "הקובץ הועתק לקבצים שלך בהצלחה", + "copyError": "שגיאה בהעתקת קובץ לקבצים שלך", + "deleteSuccess": "הקובץ נמחק בהצלחה", + "deleteError": "שגיאה במחיקת קובץ", + "bulkCopySuccess": "{count, plural, =1 {קובץ אחד הועתק לקבצים שלך בהצלחה} other {# קבצים הועתקו לקבצים שלך בהצלחה}}", + "bulkDeleteSuccess": "{count, plural, =1 {קובץ אחד נמחק בהצלחה} other {# קבצים נמחקו בהצלחה}}", + "bulkCopyProgress": "מעתיק {count, plural, =1 {קובץ אחד} other {# קבצים}} לקבצים שלך...", + "bulkDeleteProgress": "מוחק {count, plural, =1 {קובץ אחד} other {# קבצים}}...", + "bulkDeleteConfirmTitle": "מחק קבצים נבחרים", + "bulkDeleteConfirmMessage": "האם אתה בטוח שברצונך למחוק {count, plural, =1 {קובץ זה} other {# קבצים אלה}}? פעולה זו אינה ניתנת לביטול.", + "bulkDeleteConfirmButton": "מחק {count, plural, =1 {קובץ} other {קבצים}}", + "bulkActions": { + "selected": "{count, plural, =1 {קובץ אחד נבחר} other {# קבצים נבחרו}}", + "actions": "פעולות", + "download": "הורד נבחרים", + "copyToMyFiles": "העתק נבחרים לקבצים שלי", + "delete": "מחק נבחרים" + }, + "selectAll": "בחר הכל", + "selectFile": "בחר קובץ {fileName}", + "copyErrors": { + "timeout": "פעולת העתקה פג זמנה. אנא נסה שוב עם קובץ קטן יותר או בדוק את החיבור שלך.", + "failed": "פעולת העתקה נכשלה. אנא נסה שוב.", + "aborted": "פעולת העתקה בוטלה עקב פג זמן." + } + } + }, + "form": { + "name": { + "label": "שם קישור", + "placeholder": "למשל: מסמכי פרויקט, תמונות משפחה..." + }, + "description": { + "label": "תיאור", + "placeholder": "תאר איזה סוג קבצים אתה מצפה לקבל...", + "description": "אופציונלי. עוזר לאנשים להבין מה לשלוח." + }, + "status": { + "label": "סטטוס קישור", + "description": "הפעל או השבת קישור קבלה זה" + }, + "expiration": { + "label": "תאריך תפוגה", + "description": "אופציונלי. הקישור יושבת לאחר תאריך זה.", + "configure": "הגדר תפוגה" + }, + "fileLimits": { + "configure": "הגדר הגבלות קבצים" + }, + "maxFiles": { + "label": "מקסימום קבצים", + "placeholder": "למשל: 10", + "description": "אופציונלי. הגבל את המספר הכולל של קבצים שניתן לשלוח.", + "noLimit": "ללא הגבלת קבצים" + }, + "maxFileSize": { + "label": "גודל קובץ מרבי", + "placeholder": "למשל: 100", + "description": "אופציונלי. הגבל את הגודל האינדיבידואלי של כל קובץ.", + "noLimit": "ללא הגבלת גודל" + }, + "allowedFileTypes": { + "label": "סוגי קבצים מותרים", + "placeholder": "למשל: pdf, jpg, png, docx", + "description": "הזן סיומות ללא נקודות, מופרדות ברווח, פסיק, מקף או קו אנכי", + "allTypes": "כל סוגי הקבצים" + }, + "pageLayout": { + "label": "פריסת עמוד", + "placeholder": "בחר פריסה", + "description": "כיצד עמוד ההעלאה יופיע למשתמשים.", + "options": { + "default": "פריסת ברירת מחדל", + "wetransfer": "סגנון WeTransfer" + } + }, + "password": { + "label": "סיסמת הגנה", + "placeholder": "אופציונלי. הוסף סיסמה להגנה על הקישור", + "description": "אופציונלי. משתמשים יצטרכו סיסמה זו כדי לגשת לקישור.", + "configurePassword": "הגדר סיסמה", + "protectWithPassword": "הגן בסיסמה", + "passwordHelp": "סיסמה חייבת להכיל לפחות 4 תווים", + "passwordPlaceholder": "הזן סיסמה להגנה על הקישור" + }, + "nameFieldRequired": { + "label": "דרישת שדה שם", + "description": "הגדר אם שדה שם המעלה צריך להיות מוצג ואם הוא נדרש" + }, + "emailFieldRequired": { + "label": "דרישת שדה דוא\"ל", + "description": "הגדר אם שדה דוא\"ל המעלה צריך להיות מוצג ואם הוא נדרש" + }, + "fieldRequirements": { + "title": "דרישות שדות", + "description": "הגדר אילו שדות מוצגים בטופס ההעלאה" + }, + "submit": "צור קישור קבלה" + }, + "messages": { + "created": "קישור קבלה נוצר בהצלחה!", + "createSuccess": "קישור קבלה נוצר בהצלחה!", + "updateSuccess": "קישור קבלה עודכן בהצלחה!", + "linkCopied": "הקישור הועתק ללוח!", + "deleteSuccess": "קישור קבלה נמחק בהצלחה!", + "aliasCreated": "הכינוי נוצר בהצלחה!", + "activateSuccess": "קישור קבלה הופעל בהצלחה!", + "deactivateSuccess": "קישור קבלה הושבת בהצלחה!", + "passwordProtectionEnabled": "הגנת סיסמה הופעלה בהצלחה!", + "passwordProtectionDisabled": "הגנת סיסמה הוסרה בהצלחה!" + }, + "defaultLinkName": "קבצים שהתקבלו", + "errors": { + "loadFailed": "נכשל בטעינת קישורי קבלה", + "createFailed": "נכשל ביצירת קישור קבלה. אנא נסה שוב.", + "updateFailed": "נכשל בעדכון קישור קבלה. אנא נסה שוב.", + "deleteFailed": "נכשל במחיקת קישור קבלה. אנא נסה שוב.", + "aliasCreateFailed": "נכשל ביצירת כינוי. אנא נסה שוב.", + "passwordUpdateFailed": "נכשל בעדכון הגנת סיסמה" + }, + "delete": { + "title": "מחק קישור קבלה", + "description": "פעולה זו אינה ניתנת לביטול. הקישור יוסר לצמיתות ולא יוכל לקבל קבצים יותר.", + "confirmButton": "מחק קישור", + "cancelButton": "ביטול", + "deleting": "מוחק..." + }, + "upload": { + "metadata": { + "title": "שלח קבצים - Palmr", + "description": "שלח קבצים דרך הקישור המשותף", + "descriptionWithLimit": "העלה קבצים (מקסימום {limit} קבצים)" + }, + "layout": { + "defaultTitle": "שלח קבצים", + "importantInfo": "מידע חשוב:", + "maxFiles": "מקסימום {count} קבצים", + "maxFileSize": "גודל קובץ מרבי: {size}MB", + "allowedTypes": "סוגים מותרים: {types}", + "loading": "טוען..." + }, + "password": { + "title": "קישור מוגן", + "description": "קישור זה מוגן בסיסמה. הזן את הסיסמה כדי להמשיך.", + "label": "סיסמה", + "placeholder": "הזן סיסמה", + "cancel": "ביטול", + "submit": "המשך", + "verifying": "מאמת..." + }, + "errors": { + "loadFailed": "נכשל בטעינת מידע. אנא נסה שוב.", + "passwordIncorrect": "סיסמה שגויה. אנא נסה שוב.", + "linkNotFound": "קישור לא נמצא או פג תוקפו.", + "linkInactive": "קישור זה אינו פעיל.", + "linkExpired": "קישור זה פג תוקפו.", + "uploadFailed": "שגיאה בהעלאת קובץ", + "retry": "נסה שוב", + "fileTooLarge": "קובץ גדול מדי. גודל מרבי: {maxSize}", + "fileTypeNotAllowed": "סוג קובץ לא מותר. סוגים מקובלים: {allowedTypes}", + "maxFilesExceeded": "מותרים מקסימום {maxFiles} קבצים", + "selectAtLeastOneFile": "בחר לפחות קובץ אחד", + "provideNameOrEmail": "אנא ספק את שמך או דוא\"ל", + "provideNameRequired": "שם נדרש", + "provideEmailRequired": "דוא\"ל נדרש" + }, + "fileDropzone": { + "dragActive": "שחרר קבצים כאן", + "dragInactive": "גרור קבצים לכאן או לחץ לבחירה", + "acceptedTypes": "סוגים מקובלים: {types}", + "maxFileSize": "גודל מרבי: {size}", + "maxFiles": "מקסימום {count} קבצים", + "remainingFiles": "{remaining} מתוך {max} קבצים נותרו" + }, + "fileList": { + "title": "קבצים נבחרים:", + "statusUploaded": "הועלה", + "statusError": "שגיאה", + "retry": "נסה שוב" + }, + "form": { + "nameLabel": "שם", + "nameLabelOptional": "שם (אופציונלי)", + "namePlaceholder": "השם שלך", + "emailLabel": "דוא\"ל", + "emailLabelOptional": "דוא\"ל (אופציונלי)", + "emailPlaceholder": "your@email.com", + "descriptionLabel": "תיאור (אופציונלי)", + "descriptionPlaceholder": "הוסף תיאור לקבצים...", + "uploadButton": "שלח {count} קבצים", + "uploading": "שולח..." + }, + "success": { + "title": "קבצים נשלחו בהצלחה! 🎉", + "description": "ניתן לסגור דף זה.", + "countMessage": "{count} קבצים נשלחו בהצלחה!" + }, + "maxFilesReached": { + "title": "הגעת למקסימום הקבצים", + "description": "קישור זה כבר קיבל את מספר הקבצים המרבי של {maxFiles} הקבצים המותרים.", + "contactOwner": "אם הייתה שגיאה או שאתה צריך לשלוח עוד קבצים, צור קשר עם בעל הקישור." + }, + "linkInactive": { + "title": "קישור לא פעיל", + "description": "קישור קבלה זה אינו פעיל זמנית.", + "contactOwner": "צור קשר עם בעל הקישור למידע נוסף." + }, + "linkNotFound": { + "title": "קישור לא נמצא", + "description": "קישור זה אולי הוסר או מעולם לא היה קיים." + }, + "linkExpired": { + "title": "קישור פג תוקף", + "description": "קישור קבלה זה פג תוקפו ואינו מקבל קבצים יותר.", + "contactOwner": "צור קשר עם בעל הקישור אם אתה צריך לשלוח קבצים." + } + }, + "components": { + "fileRow": { + "addDescription": "הוסף תיאור...", + "anonymous": "אנונימי" + }, + "fileActions": { + "edit": "ערוך", + "preview": "תצוגה מקדימה", + "download": "הורד", + "delete": "מחק", + "copyToMyFiles": "העתק לקבצים שלי", + "copying": "מעתיק..." + }, + "editField": { + "saveChanges": "שמור שינויים", + "cancelEdit": "ביטול עריכה" + } + } + }, + "searchBar": { + "placeholder": "חפש קבצים ותיקיות...", + "placeholderFiles": "חפש קבצים...", + "placeholderFolders": "חפש תיקיות...", + "results": "מציג {filtered} מתוך {total} פריטים", + "noResults": "לא נמצאו תוצאות עבור \"{query}\"" + }, + "settings": { + "groups": { + "defaultDescription": "אפשרויות תצורה", + "general": { + "title": "כללי", + "description": "הגדרות אפליקציה בסיסיות" + }, + "email": { + "title": "דוא\"ל", + "description": "תצורת שרת דוא\"ל" + }, + "security": { + "title": "אבטחה", + "description": "הגדרות אבטחה ואימות" + }, + "storage": { + "title": "אחסון", + "description": "תצורת אחסון קבצים" + }, + "oidc": { + "title": "OpenID Connect (ספקי אימות)", + "description": "הגדר אימות SSO באמצעות OpenID Connect" + } + }, + "fields": { + "noDescription": "אין תיאור זמין", + "appLogo": { + "title": "לוגו אפליקציה", + "description": "תמונת לוגו האפליקציה" + }, + "appName": { + "title": "שם אפליקציה", + "description": "שם האפליקציה המוצג למשתמשים" + }, + "appDescription": { + "title": "תיאור אפליקציה", + "description": "תיאור קצר של האפליקציה" + }, + "showHomePage": { + "title": "הצג דף בית", + "description": "הצג דף בית לאחר ההתקנה" + }, + "hideVersion": { + "title": "הסתר גרסה", + "description": "הסתר את גרסת Palmr מהכותרת התחתונה בכל העמודים" + }, + "smtpEnabled": { + "title": "SMTP מופעל", + "description": "הפעל או השבת פונקציונליות דוא\"ל SMTP" + }, + "smtpHost": { + "title": "שרת SMTP", + "description": "כתובת שרת SMTP" + }, + "smtpPort": { + "title": "פורט SMTP", + "description": "פורט שרת SMTP" + }, + "smtpUser": { + "title": "שם משתמש SMTP", + "description": "שם משתמש לאימות SMTP" + }, + "smtpPass": { + "title": "סיסמת SMTP", + "description": "סיסמה לאימות SMTP" + }, + "smtpFromName": { + "title": "שם שולח", + "description": "שם תצוגה לדוא\"ל נשלח" + }, + "smtpFromEmail": { + "title": "דוא\"ל שולח", + "description": "כתובת דוא\"ל שולח" + }, + "smtpSecure": { + "title": "אבטחת חיבור", + "description": "שיטת אבטחת חיבור SMTP - אוטומטי (מומלץ), SSL, STARTTLS, או ללא (לא מאובטח)", + "options": { + "auto": "אוטומטי (מומלץ)", + "ssl": "SSL (פורט 465)", + "tls": "STARTTLS (פורט 587)", + "none": "ללא (לא מאובטח)" + } + }, + "smtpNoAuth": { + "title": "ללא אימות", + "description": "הפעל זאת עבור שרתים פנימיים שאינם דורשים שם משתמש/סיסמה (מסתיר שדות אימות)" + }, + "smtpTrustSelfSigned": { + "title": "אמון בתעודות חתומות עצמית", + "description": "הפעל זאת כדי לסמוך על תעודות SSL/TLS חתומות עצמית (שימושי לסביבות פיתוח)" + }, + "testSmtp": { + "title": "בדוק חיבור SMTP", + "description": "בדוק אם תצורת ה-SMTP תקינה" + }, + "maxLoginAttempts": { + "title": "ניסיונות התחברות מקסימליים", + "description": "מספר ניסיונות התחברות מרביים לפני חסימה" + }, + "loginBlockDuration": { + "title": "משך חסימה", + "description": "משך (בשניות) לחסימה לאחר חריגה מניסיונות" + }, + "passwordMinLength": { + "title": "אורך סיסמה מינימלי", + "description": "מספר תווים מינימלי לסיסמאות" + }, + "passwordResetTokenExpiration": { + "title": "תפוגת טוקן איפוס", + "description": "זמן תוקף (בשניות) לטוקן איפוס סיסמה" + }, + "maxFileSize": { + "title": "גודל קובץ מרבי", + "description": "גודל קובץ מרבי מותר להעלאות" + }, + "maxTotalStoragePerUser": { + "title": "אחסון מרבי למשתמש", + "description": "מגבלת אחסון כוללת למשתמש" + }, + "firstUserAccess": { + "title": "גישת משתמש ראשון", + "description": "הגדרות לגישה ראשונה של משתמשים חדשים" + }, + "serverUrl": { + "title": "כתובת URL של שרת", + "description": "כתובת URL בסיסית של שרת Palmr (למשל: https://palmr.example.com)" + }, + "passwordAuthEnabled": { + "title": "אימות סיסמה", + "description": "הפעל או השבת אימות מבוסס סיסמה" + } + }, + "buttons": { + "save": "שמור {group}", + "testSmtp": "בדוק חיבור", + "testing": "בודק..." + }, + "errors": { + "loadFailed": "נכשל בטעינת הגדרות", + "updateFailed": "נכשל בעדכון הגדרות", + "passwordAuthRequiresProvider": "לא ניתן להשבית אימות סיסמה ללא ספק אימות פעיל אחד לפחות" + }, + "messages": { + "noChanges": "אין שינויים לשמור", + "updateSuccess": "הגדרות {group} עודכנו בהצלחה", + "smtpTestSuccess": "חיבור SMTP הצליח! תצורת הדוא\"ל שלך פועלת כראוי.", + "smtpTestFailed": "חיבור SMTP נכשל: {error}", + "smtpTestGenericError": "נכשל בבדיקת חיבור SMTP. אנא בדוק את ההגדרות ונסה שוב.", + "smtpNotEnabled": "SMTP אינו מופעל. אנא הפעל SMTP תחילה.", + "smtpMissingHostPort": "אנא מלא את שרת SMTP ופורט לפני הבדיקה.", + "smtpMissingAuth": "אנא מלא שם משתמש וסיסמת SMTP, או הפעל אפשרות 'ללא אימות'." + }, + "title": "הגדרות", + "breadcrumb": "הגדרות", + "pageTitle": "הגדרות", + "tooltips": { + "testSmtp": "בודק את חיבור ה-SMTP עם הערכים שהוזנו כעת בטופס. כדי להפוך שינויים לקבועים, זכור לשמור את ההגדרות לאחר הבדיקה.", + "defaultPlaceholder": "הזן ולחץ Enter" + }, + "redirectUri": { + "placeholder": "https://mysite.com", + "previewLabel": "כתובת URL מלאה שתישמר:" + } + }, + "share": { + "errors": { + "invalidPassword": "סיסמה לא חוקית. אנא נסה שוב.", + "loadFailed": "נכשל בטעינת שיתוף", + "downloadFailed": "נכשל בהורדת קובץ" + }, + "messages": { + "downloadStarted": "ההורדה החלה" + }, + "password": { + "title": "שיתוף מוגן בסיסמה", + "protected": "שיתוף זה מוגן בסיסמה", + "incorrect": "סיסמה שגויה. אנא נסה שוב.", + "label": "סיסמה", + "placeholder": "הזן סיסמת שיתוף", + "submit": "שלח" + }, + "details": { + "untitled": "שיתוף ללא כותרת", + "created": "נוצר: {date}", + "expires": "תפוגה: {date}" + }, + "downloadAll": "הורד הכל", + "notFound": { + "title": "שיתוף לא נמצא", + "description": "שיתוף זה אולי נמחק או פג תוקפו." + }, + "pageTitle": "שיתוף", + "metadata": { + "defaultDescription": "שתף קבצים בצורה מאובטחת", + "filesShared": "{count, plural, =1 {קובץ אחד משותף} other {# קבצים משותפים}}" + } + }, + "shareActions": { + "fileTitle": "שתף קובץ", + "folderTitle": "שתף תיקייה", + "linkTitle": "צור קישור", + "linkDescriptionFile": "צור קישור מותאם אישית לשיתוף הקובץ", + "linkDescriptionFolder": "צור קישור מותאם אישית לשיתוף התיקייה", + "aliasLabel": "כינוי קישור", + "aliasPlaceholder": "הזן כינוי מותאם אישית", + "linkReady": "קישור השיתוף שלך מוכן:", + "generateLink": "צור קישור", + "copyLink": "העתק קישור", + "deleteTitle": "מחק שיתוף", + "deleteConfirmation": "האם אתה בטוח שברצונך למחוק שיתוף זה? פעולה זו אינה ניתנת לביטול.", + "addDescriptionPlaceholder": "הוסף תיאור...", + "editTitle": "ערוך שיתוף", + "newPasswordLabel": "סיסמה חדשה (השאר ריק לשמירת הנוכחית)", + "newPasswordPlaceholder": "הזן סיסמה חדשה", + "manageFilesTitle": "נהל קבצים", + "manageFilesDescription": "בחר קבצים ותיקיות לכלול בשיתוף זה", + "manageRecipientsTitle": "נהל נמענים", + "itemsSelected": "{count} פריטים נבחרו", + "editSuccess": "השיתוף עודכן בהצלחה", + "editError": "נכשל בעדכון שיתוף", + "bulkDeleteConfirmation": "האם אתה בטוח שברצונך למחוק {count, plural, =1 {שיתוף אחד} other {# שיתופים}}? פעולה זו אינה ניתנת לביטול.", + "bulkDeleteTitle": "מחק שיתופים נבחרים" + }, + "shareDetails": { + "title": "פרטי שיתוף", + "subtitle": "צפה ונהל פרטים לשיתוף זה", + "basicInfo": "מידע בסיסי", + "name": "שם", + "description": "תיאור", + "shareLink": "קישור שיתוף", + "dates": "תאריכים", + "security": "אבטחה", + "files": "קבצים", + "recipients": "נמענים", + "views": "צפיות", + "created": "נוצר", + "expires": "תפוגה", + "never": "אף פעם", + "untitled": "שיתוף ללא כותרת", + "noDescription": "אין תיאור", + "notAvailable": "לא זמין", + "invalidDate": "תאריך לא חוקי", + "passwordProtected": "מוגן בסיסמה", + "publicAccess": "גישה ציבורית", + "maxViews": "צפיות מקסימליות:", + "noLink": "לא נוצר קישור", + "generateLink": "צור קישור", + "editLink": "ערוך קישור", + "copyLink": "העתק קישור", + "openLink": "פתח קישור", + "editSecurity": "ערוך אבטחה", + "editExpiration": "ערוך תפוגה", + "qrCode": "קוד QR", + "downloadQrCode": "הורד קוד QR", + "clickToEnlargeQrCode": "לחץ להגדלת קוד QR", + "loadError": "נכשל בטעינת פרטי שיתוף" + }, + "shareExpiration": { + "title": "הגדרות תפוגת שיתוף", + "subtitle": "הגדר מתי השיתוף הזה יפוג", + "currentStatus": "סטטוס נוכחי", + "expires": "תפוגה:", + "neverExpires": "לא פג תוקף", + "enableExpiration": "הפעל תפוגה", + "expirationDate": "תאריך תפוגה", + "validation": { + "dateRequired": "אנא בחר תאריך תפוגה", + "dateMustBeFuture": "תאריך תפוגה חייב להיות בעתיד" + }, + "success": { + "expirationSet": "תאריך תפוגה הוגדר בהצלחה", + "expirationUpdated": "תאריך תפוגה עודכן בהצלחה", + "expirationRemoved": "תפוגה הוסרה בהצלחה - השיתוף כעת קבוע" + }, + "error": { + "updateFailed": "נכשל בעדכון הגדרות תפוגה" + }, + "info": { + "title": "אודות תפוגה:", + "willBeInaccessible": "השיתוף יהיה בלתי נגיש לאחר תאריך זה", + "canBeChanged": "אתה יכול לשנות או להסיר את תאריך התפוגה בכל עת", + "noExpiration": "השיתוף הזה לעולם לא יפוג וישאר נגיש לצמיתות." + } + }, + "shareManager": { + "deleteSuccess": "השיתוף נמחק בהצלחה", + "deleteError": "נכשל במחיקת שיתוף", + "updateSuccess": "השיתוף עודכן בהצלחה", + "updateError": "נכשל בעדכון שיתוף", + "securityUpdateSuccess": "הגדרות אבטחה עודכנו בהצלחה", + "securityUpdateError": "נכשל בעדכון הגדרות אבטחה", + "expirationUpdateSuccess": "הגדרות תפוגה עודכנו בהצלחה", + "expirationUpdateError": "נכשל בעדכון הגדרות תפוגה", + "filesUpdateSuccess": "קבצים עודכנו בהצלחה", + "filesUpdateError": "נכשל בעדכון קבצים", + "recipientsUpdateSuccess": "נמענים עודכנו בהצלחה", + "recipientsUpdateError": "נכשל בעדכון נמענים", + "linkGenerateSuccess": "קישור שיתוף נוצר בהצלחה", + "linkGenerateError": "נכשל ביצירת קישור שיתוף", + "notifyLoading": "שולח התראות...", + "notifySuccess": "נמענים קיבלו הודעה בהצלחה", + "notifyError": "נכשל בהודעה לנמענים", + "bulkDeleteError": "נכשל במחיקת שיתופים", + "bulkDeleteLoading": "מוחק {count, plural, =1 {שיתוף אחד} other {# שיתופים}}...", + "bulkDeleteSuccess": "{count, plural, =1 {שיתוף אחד נמחק בהצלחה} other {# שיתופים נמחקו בהצלחה}}", + "downloadSuccess": "ההורדה החלה בהצלחה", + "downloadError": "נכשל בהורדת קבצי שיתוף", + "noFilesToDownload": "אין קבצים זמינים להורדה", + "creatingZip": "יוצר קובץ ZIP...", + "zipDownloadSuccess": "קובץ ZIP הורד בהצלחה", + "zipDownloadError": "נכשל ביצירת קובץ ZIP", + "errors": { + "multipleDownloadNotSupported": "הורדה מרובת של שיתופים עדיין לא נתמכת - אנא הורד שיתופים בנפרד" + }, + "singleShareZipName": "{shareName}_files.zip", + "multipleSharesZipName": "{count}_shares_files.zip", + "defaultShareName": "שיתוף" + }, + "shareMultipleFiles": { + "title": "שתף מספר קבצים", + "shareNameLabel": "שם שיתוף", + "shareNamePlaceholder": "הזן שם שיתוף", + "descriptionLabel": "תיאור", + "descriptionPlaceholder": "הזן תיאור (אופציונלי)", + "filesToShare": "קבצים לשיתוף", + "files": "קבצים", + "totalSize": "גודל כולל", + "creating": "יוצר שיתוף...", + "create": "צור שיתוף", + "itemsToShare": "פריטים לשיתוף ({count} {count, plural, =1 {פריט} other {פריטים}})" + }, + "shareSecurity": { + "title": "הגדרות אבטחת שיתוף", + "subtitle": "הגדר הגנת סיסמה ואפשרויות אבטחה לשיתוף זה", + "currentStatus": "סטטוס נוכחי", + "passwordProtection": "הגנת סיסמה", + "password": "סיסמה", + "newPassword": "סיסמה חדשה", + "passwordPlaceholder": "הזן סיסמה מאובטחת", + "existingPasswordMessage": "לשיתוף הזה כבר יש סיסמה. אם אתה רוצה לעדכן אותה, הזן את הסיסמה החדשה בשדה למטה ושמור.", + "passwordRequirements": { + "title": "דרישות סיסמה:", + "minLength": "לפחות 2 תווים" + }, + "info": { + "title": "איך זה עובד:", + "withPassword": "משתמשים יצטרכו להזין את הסיסמה כדי לגשת לשיתוף הזה.", + "withoutPassword": "כל מי שיש לו את הקישור יכול לגשת לשיתוף הזה ללא סיסמה." + }, + "validation": { + "passwordRequired": "סיסמה נדרשת", + "passwordTooShort": "סיסמה חייבת להכיל לפחות 2 תווים" + }, + "success": { + "passwordSet": "הגנת סיסמה הופעלה בהצלחה", + "passwordUpdated": "סיסמה עודכנה בהצלחה", + "passwordRemoved": "הגנת סיסמה הוסרה בהצלחה" + }, + "error": { + "updateFailed": "נכשל בעדכון הגדרות אבטחה" + } + }, + "shares": { + "errors": { + "loadFailed": "נכשל בטעינת שיתופים", + "notifyFailed": "נכשל בהודעה לנמענים", + "smtpConfigFailed": "נכשל בטעינת תצורת SMTP" + }, + "messages": { + "linkCopied": "הקישור הועתק ללוח", + "recipientsNotified": "נמענים קיבלו הודעה בהצלחה" + }, + "empty": { + "message": "עדיין לא נוצרו שיתופים", + "createButton": "צור שיתוף" + }, + "header": { + "title": "השיתופים שלי", + "myShares": "השיתופים שלי" + }, + "search": { + "title": "כל השיתופים", + "createButton": "צור שיתוף", + "placeholder": "חפש שיתופים...", + "results": "נמצאו {filtered} מתוך {total} שיתופים" + }, + "pageTitle": "שיתופים" + }, + "sharesTable": { + "ariaLabel": "טבלת שיתופים", + "never": "אף פעם", + "columns": { + "name": "שם", + "description": "תיאור", + "createdAt": "נוצר ב", + "expiresAt": "תפוגה ב", + "status": "סטטוס", + "security": "אבטחה", + "files": "קבצים", + "recipients": "נמענים", + "actions": "פעולות" + }, + "status": { + "neverExpires": "לא פג תוקף", + "active": "פעיל", + "expired": "פג תוקף" + }, + "security": { + "protected": "מוגן", + "public": "ציבורי" + }, + "filesCount": "קבצים", + "folderCount": "תיקיות", + "recipientsCount": "נמענים", + "actions": { + "menu": "תפריט פעולות שיתוף", + "edit": "ערוך", + "manageFiles": "נהל קבצים", + "manageRecipients": "נהל נמענים", + "viewDetails": "צפה בפרטים", + "generateLink": "צור קישור", + "editLink": "ערוך קישור", + "copyLink": "העתק קישור", + "viewQrCode": "צפה בקוד QR", + "notifyRecipients": "הודע לנמענים", + "downloadShareFiles": "הורד כל הקבצים", + "delete": "מחק" + }, + "bulkActions": { + "actions": "פעולות", + "download": "הורד נבחרים", + "delete": "מחק", + "selected": "{count, plural, =1 {שיתוף אחד נבחר} other {# שיתופים נבחרו}}" + }, + "selectAll": "בחר הכל", + "selectShare": "בחר שיתוף {shareName}" + }, + "storageUsage": { + "title": "שימוש באחסון", + "ariaLabel": "פס התקדמות שימוש באחסון", + "used": "משומש", + "available": "זמין", + "total": "סה\"כ", + "loading": "טוען...", + "retry": "נסה שוב", + "errors": { + "title": "מידע אחסון לא זמין", + "detectionFailed": "לא ניתן לזהות שטח דיסק. זה יכול להיות בגלל בעיות תצורת מערכת או הרשאות לא מספקות.", + "serverError": "שגיאת שרת אירעה בעת קבלת מידע אחסון. אנא נסה שוב מאוחר יותר.", + "unknown": "אירעה שגיאה בלתי צפויה בעת טעינת מידע אחסון." + } + }, + "theme": { + "toggle": "החלף נושא", + "light": "בהיר", + "dark": "כהה", + "system": "מערכת" + }, + "twoFactor": { + "title": "אימות דו-שלבי", + "description": "הוסף שכבת אבטחה נוספת לחשבון שלך", + "enabled": "החשבון שלך מוגן באימות דו-שלבי", + "disabled": "אימות דו-שלבי אינו מופעל", + "status": { + "label": "סטטוס:", + "enabled": "מופעל", + "disabled": "מושבת" + }, + "buttons": { + "enable2FA": "הפעל 2FA", + "disable2FA": "השבת 2FA" + }, + "setup": { + "title": "הפעל אימות דו-שלבי", + "description": "סרוק את קוד ה-QR עם אפליקציית האימות שלך, ואז הזן את קוד האימות.", + "qrCode": "קוד QR", + "manualEntryKey": "מפתח הזנה ידני", + "verificationCode": "קוד אימות", + "verificationCodePlaceholder": "הזן קוד בן 6 ספרות", + "verificationCodeDescription": "הזן את קוד האימות בן 6 הספרות מאפליקציית האימות שלך", + "verifyAndEnable": "אמת והפעל", + "cancel": "ביטול" + }, + "disable": { + "title": "השבת אימות דו-שלבי", + "description": "הזן את הסיסמה שלך כדי לאשר השבתת אימות דו-שלבי.", + "password": "סיסמה", + "passwordPlaceholder": "הזן את הסיסמה שלך", + "confirm": "אשר השבתה", + "cancel": "ביטול" + }, + "backupCodes": { + "title": "קודי גיבוי", + "description": "שמור את קודי הגיבוי האלה במקום בטוח. אתה יכול להשתמש בהם כדי לגשת לחשבון שלך אם תאבד את מכשיר האימות שלך.", + "warning": "חשוב:", + "warningText": "כל קוד גיבוי יכול לשמש רק פעם אחת. שמור עליהם מאובטחים ואל תשתף אותם עם אף אחד.", + "generateNew": "צור קודי גיבוי חדשים", + "download": "הורד קודי גיבוי", + "copyToClipboard": "העתק ללוח", + "savedMessage": "שמרתי את קודי הגיבוי שלי", + "available": "{count} קודי גיבוי זמינים", + "instructions": [ + "• שמור את הקודים האלה במיקום מאובטח", + "• כל קוד גיבוי יכול לשמש רק פעם אחת", + "• אתה יכול ליצור קודים חדשים בכל עת" + ] + }, + "verification": { + "title": "אימות דו-שלבי", + "description": "הזן את קוד האימות בן 6 הספרות מאפליקציית האימות שלך", + "backupDescription": "הזן אחד מקודי הגיבוי שלך כדי להמשיך", + "verificationCode": "קוד אימות", + "backupCode": "קוד גיבוי", + "verificationCodePlaceholder": "000000", + "backupCodePlaceholder": "XXXX-XXXX", + "verify": "אמת", + "verifying": "מאמת...", + "useBackupCode": "השתמש בקוד גיבוי במקום", + "useAuthenticatorCode": "השתמש בקוד אימות במקום", + "rememberDevice": "זכור מכשיר זה למשך 30 יום", + "rememberDeviceDescription": "לא תצטרך להזין קודי 2FA במכשיר זה למשך 30 יום" + }, + "trustedDevices": { + "title": "מכשירים מהימנים - 2FA", + "description": "מכשירים שאינם דורשים אימות 2FA", + "noDevices": "אין מכשירים מהימנים", + "deviceName": "מכשיר", + "addedOn": "נוסף ב", + "expiresOn": "תפוגה ב", + "remove": "הסר", + "removeAll": "הסר הכל", + "confirmRemove": "האם אתה בטוח שברצונך להסיר את המכשיר המהימן הזה?", + "confirmRemoveAll": "האם אתה בטוח שברצונך להסיר את כל המכשירים המהימנים?", + "deviceRemoved": "המכשיר המהימן הוסר בהצלחה", + "allDevicesRemoved": "כל המכשירים המהימנים הוסרו בהצלחה", + "loadFailed": "נכשל בטעינת מכשירים מהימנים", + "removeFailed": "נכשל בהסרת מכשיר מהימן", + "removeAllFailed": "נכשל בהסרת כל המכשירים המהימנים", + "loading": "טוען מכשירים מהימנים...", + "noDevicesDescription": "מכשירים יופיעו כאן כשתבחר לאשר אותם במהלך אימות 2FA", + "tableHeaders": { + "device": "מכשיר", + "added": "נוסף", + "expires": "תפוגה", + "lastUsed": "שימוש אחרון", + "ipAddress": "כתובת IP", + "actions": "פעולות" + }, + "status": { + "never": "אף פעם", + "expired": "פג תוקף" + }, + "modals": { + "removeDevice": { + "title": "הסר מכשיר מהימן", + "added": "נוסף:", + "ip": "IP:" + }, + "removeAllDevices": { + "title": "הסר את כל המכשירים המהימנים", + "description": "זה יסיר {count} מכשיר{count, plural, =1 {} other {ים}} מהימן{count, plural, =1 {} other {ים}}. תצטרך לאמת 2FA בכל המכשירים שוב." + }, + "buttons": { + "cancel": "ביטול", + "removing": "מסיר...", + "removeDevice": "הסר מכשיר", + "removeAllDevices": "הסר את כל המכשירים" + } + } + }, + "messages": { + "enabledSuccess": "אימות דו-שלבי הופעל בהצלחה!", + "disabledSuccess": "אימות דו-שלבי הושבת בהצלחה", + "backupCodesGenerated": "קודי גיבוי חדשים נוצרו בהצלחה", + "backupCodesCopied": "קודי גיבוי הועתקו ללוח", + "setupFailed": "נכשל ביצירת הגדרת 2FA", + "verificationFailed": "קוד אימות לא חוקי", + "disableFailed": "נכשל בהשבתת 2FA. אנא בדוק את הסיסמה שלך.", + "backupCodesFailed": "נכשל ביצירת קודי גיבוי", + "backupCodesCopyFailed": "נכשל בהעתקת קודי גיבוי", + "statusLoadFailed": "נכשל בטעינת סטטוס 2FA", + "enterVerificationCode": "אנא הזן את קוד האימות", + "enterPassword": "אנא הזן את הסיסמה שלך", + "deviceTrusted": "המכשיר הזה סומן כמאמון למשך 30 יום" + }, + "errors": { + "invalidVerificationCode": "קוד אימות לא חוקי", + "invalidTwoFactorCode": "קוד אימות דו-שלבי לא חוקי", + "twoFactorRequired": "נדרש אימות דו-שלבי", + "twoFactorAlreadyEnabled": "אימות דו-שלבי כבר מופעל", + "twoFactorNotEnabled": "אימות דו-שלבי אינו מופעל", + "passwordVerificationRequired": "נדרש אימות סיסמה", + "invalidPassword": "סיסמה לא חוקית", + "userNotFound": "משתמש לא נמצא" + }, + "deviceNames": { + "unknownDevice": "מכשיר לא ידוע", + "browsers": { + "chrome": "Chrome", + "firefox": "Firefox", + "safari": "Safari", + "edge": "Edge" + }, + "platforms": { + "windows": " ב-Windows", + "macos": " ב-macOS", + "linux": " ב-Linux", + "iphone": " ב-iPhone", + "android": " ב-Android" + } + } + }, + "uploadFile": { + "title": "העלה קובץ", + "multipleTitle": "העלה קבצים", + "selectFile": "לחץ כדי לבחור קובץ", + "selectMultipleFiles": "לחץ כדי לבחור קובץ אחד או מספר קבצים", + "dragAndDrop": "או גרור ושחרר קבצים כאן", + "filesQueued": "{count, plural, one {# קובץ בתור להעלאה} other {# קבצים בתור להעלאה}}", + "preview": "תצוגה מקדימה", + "uploadProgress": "התקדמות העלאה", + "upload": "העלה", + "startUploads": "התחל העלאות", + "retry": "נסה שוב", + "finish": "סיים", + "success": "הקובץ הועלה בהצלחה", + "allSuccess": "{count, plural, =1 {הקובץ הועלה בהצלחה} other {# קבצים הועלו בהצלחה}}", + "partialSuccess": "{success} קבצים הועלו בהצלחה, {error} נכשלו", + "error": "נכשל בהעלאת קובץ", + "fileSizeExceeded": "גודל הקובץ חורג מהגבול של {maxsizemb}MB.", + "insufficientStorage": "אין מספיק שטח אחסון. יש לך {availablespace}MB זמינים.", + "unauthorized": "לא מורשה: נדרש טוקן חוקי כדי לגשת למשאב הזה.", + "globalDrop": { + "title": "שחרר קבצים להעלאה", + "description": "שחרר כדי להעלות את הקבצים שלך" + }, + "confirmCancel": { + "title": "בטל העלאות", + "messageSingle": "יש העלאה אחת בתהליך.", + "messageMultiple": "יש {count} העלאות בתהליך.", + "warning": "אם תסגור עכשיו, ההעלאות יבוטלו וכל ההתקדמות תאבד.", + "continue": "המשך העלאות", + "cancel": "בטל העלאות" + }, + "pasteSuccess": "{count, plural, =1 {תמונה הודבקה והועלתה בהצלחה} other {# תמונות הודבקו והועלו בהצלחה}}" + }, + "users": { + "modes": { + "create": "יצירה", + "edit": "עריכה" + }, + "errors": { + "loadFailed": "נכשל בטעינת משתמשים", + "submitFailed": "נכשל ב-{mode} משתמש", + "deleteFailed": "נכשל במחיקת משתמש", + "statusUpdateFailed": "נכשל בעדכון סטטוס משתמש" + }, + "messages": { + "createSuccess": "משתמש נוצר בהצלחה", + "updateSuccess": "משתמש עודכן בהצלחה", + "deleteSuccess": "משתמש נמחק בהצלחה", + "activateSuccess": "משתמש הופעל בהצלחה", + "deactivateSuccess": "משתמש הושבת בהצלחה" + }, + "actions": { + "edit": "ערוך", + "activate": "הפעל", + "deactivate": "השבת", + "delete": "מחק" + }, + "delete": { + "title": "אשר מחיקת משתמש", + "confirmation": "האם אתה בטוח שברצונך למחוק משתמש {firstName} {lastName}? פעולה זו אינה ניתנת לביטול.", + "confirm": "מחק משתמש" + }, + "form": { + "titleCreate": "הוסף משתמש חדש", + "titleEdit": "ערוך משתמש", + "firstName": "שם פרטי", + "lastName": "שם משפחה", + "username": "שם משתמש", + "email": "דוא\"ל", + "password": "סיסמה", + "newPassword": "סיסמה חדשה (אופציונלי)", + "passwordPlaceholder": "השאר ריק כדי לשמור על הסיסמה הנוכחית", + "role": "תפקיד", + "roleUser": "משתמש", + "roleAdmin": "מנהל", + "create": "צור", + "save": "שמור" + }, + "status": { + "title": "אשר שינוי סטטוס", + "confirmation": "האם אתה בטוח שברצונך ל-{action} משתמש {firstName} {lastName}?", + "activate": "להפעיל", + "deactivate": "להשבת", + "user": "משתמש" + }, + "header": { + "title": "ניהול משתמשים", + "addUser": "הוסף משתמש", + "management": "ניהול משתמשים" + }, + "table": { + "user": "משתמש", + "email": "דוא\"ל", + "status": "סטטוס", + "role": "תפקיד", + "actions": "פעולות", + "active": "פעיל", + "inactive": "לא פעיל", + "admin": "מנהל", + "userr": "משתמש" + }, + "invite": { + "button": "צור קישור הזמנה", + "title": "צור קישור הזמנת משתמש", + "description": "צור קישור חד-פעמי המאפשר למישהו ליצור חשבון משלו. הקישור פג תוקף בעוד 15 דקות.", + "generating": "יוצר...", + "generate": "צור קישור", + "generated": "קישור ההזמנה נוצר בהצלחה!", + "linkReady": "קישור ההזמנה מוכן", + "linkReadyDescription": "שתף קישור זה עם האדם שאתה רוצה להזמין. הם יוכלו ליצור חשבון משלהם כמשתמש רגיל.", + "copyLink": "העתק קישור", + "linkCopied": "קישור ההזמנה הועתק ללוח!", + "expiresIn": "פג תוקף בעוד 15 דקות", + "close": "סגור", + "errors": { + "generateFailed": "נכשל ביצירת קישור הזמנה" + } + } + }, + "embedCode": { + "title": "הטמע מדיה", + "description": "השתמש בקודים האלה כדי להטמיע מדיה זו בפורומים, אתרים או פלטפורמות אחרות", + "tabs": { + "directLink": "קישור ישיר", + "html": "HTML", + "bbcode": "BBCode" + }, + "directLinkDescription": "כתובת URL ישירה לקובץ המדיה", + "htmlDescription": "השתמש בקוד הזה כדי להטמיע את המדיה בעמודי HTML", + "bbcodeDescription": "השתמש בקוד הזה כדי להטמיע את המדיה בפורומים שתומכים ב-BBCode" + }, + "validation": { + "firstNameRequired": "שם פרטי נדרש", + "lastNameRequired": "שם משפחה נדרש", + "usernameLength": "שם משתמש חייב להכיל לפחות 3 תווים", + "usernameSpaces": "שם משתמש אינו יכול להכיל רווחים", + "invalidEmail": "אנא הזן כתובת דוא\"ל חוקית", + "passwordLength": "סיסמה חייבת להכיל לפחות 8 תווים", + "passwordsMatch": "סיסמאות חייבות להתאים", + "emailRequired": "דוא\"ל נדרש", + "emailOrUsernameRequired": "דוא\"ל או שם משתמש נדרש", + "passwordRequired": "סיסמה נדרשת", + "passwordMinLength": "סיסמה חייבת להכיל לפחות 6 תווים", + "nameRequired": "שם נדרש", + "required": "שדה זה נדרש" + }, + "registerWithInvite": { + "title": "צור את החשבון שלך", + "description": "מלא את הפרטים למטה כדי ליצור את החשבון שלך", + "labels": { + "firstName": "שם פרטי", + "firstNamePlaceholder": "הזן את השם הפרטי שלך", + "lastName": "שם משפחה", + "lastNamePlaceholder": "הזן את השם המשפחה שלך", + "username": "שם משתמש", + "usernamePlaceholder": "בחר שם משתמש", + "email": "דוא\"ל", + "emailPlaceholder": "הזן את הדוא\"ל שלך", + "password": "סיסמה", + "passwordPlaceholder": "בחר סיסמה", + "confirmPassword": "אשר סיסמה", + "confirmPasswordPlaceholder": "אשר את הסיסמה שלך" + }, + "buttons": { + "creating": "יוצר חשבון...", + "createAccount": "צור חשבון" + }, + "validation": { + "firstNameRequired": "שם פרטי נדרש", + "lastNameRequired": "שם משפחה נדרש", + "usernameMinLength": "שם משתמש חייב להכיל לפחות 3 תווים", + "invalidEmail": "דוא\"ל לא חוקי", + "passwordMinLength": "סיסמה חייבת להכיל לפחות 8 תווים", + "passwordsMatch": "סיסמאות חייבות להתאים" + }, + "messages": { + "success": "החשבון נוצר בהצלחה! מעביר לכניסה...", + "redirecting": "מעביר לכניסה..." + }, + "errors": { + "invalidToken": "שגיאה בקישור ההזמנה", + "tokenUsed": "קישור ההזמנה הזה כבר נוצל", + "tokenExpired": "קישור ההזמנה הזה פג תוקפו", + "usernameExists": "שם המשתמש כבר קיים", + "emailExists": "הדוא\"ל כבר קיים", + "createFailed": "נכשל ביצירת חשבון. אנא נסה שוב." + }, + "pageTitle": "צור חשבון" + } +} \ No newline at end of file diff --git a/apps/web/messages/hi-IN.json b/apps/web/messages/hi-IN.json index fa16cc40..aa6fd4cd 100644 --- a/apps/web/messages/hi-IN.json +++ b/apps/web/messages/hi-IN.json @@ -6,7 +6,9 @@ "token_expired": "टोकन समाप्त हो गया है। कृपया पुनः प्रयास करें।", "config_error": "कॉन्फ़िगरेशन त्रुटि। कृपया सहायता से संपर्क करें।", "auth_failed": "प्रमाणीकरण विफल। कृपया पुनः प्रयास करें।" - } + }, + "authenticationFailed": "प्रमाणीकरण विफल", + "successfullyAuthenticated": "सफलतापूर्वक प्रमाणित!" }, "authProviders": { "title": "प्रमाणीकरण प्रदाता", @@ -174,7 +176,14 @@ "tabs": { "shareDetails": "साझाकरण विवरण", "selectFiles": "फ़ाइलें चुनें" - } + }, + "errors": { + "nameRequired": "शेयर का नाम आवश्यक है", + "selectItems": "कृपया कम से कम एक फ़ाइल या फ़ोल्डर चुनें" + }, + "itemsSelected": "{count, plural, =0 {कोई आइटम चयनित नहीं} =1 {1 आइटम चयनित} other {# आइटम चयनित}}", + "passwordPlaceholder": "पासवर्ड दर्ज करें", + "selectItemsPrompt": "साझा करने के लिए फ़ाइलें और फ़ोल्डर चुनें" }, "customization": { "breadcrumb": "अनुकूलन", @@ -340,7 +349,8 @@ "addToShare": "साझाकरण में जोड़ें", "removeFromShare": "साझाकरण से हटाएं", "saveChanges": "परिवर्तन सहेजें", - "editFolder": "फ़ोल्डर संपादित करें" + "editFolder": "फ़ोल्डर संपादित करें", + "itemsSelected": "{count, plural, =0 {कोई आइटम चयनित नहीं} =1 {1 आइटम चयनित} other {# आइटम चयनित}}" }, "files": { "title": "सभी फाइलें", @@ -376,7 +386,12 @@ "description": "आरंभ करने के लिए अपनी पहली फ़ाइल अपलोड करें या फ़ोल्डर बनाएं" }, "files": "फ़ाइलें", - "folders": "फ़ोल्डर" + "folders": "फ़ोल्डर", + "errors": { + "moveItemsFailed": "आइटम स्थानांतरित करने में विफल। कृपया पुनः प्रयास करें।", + "cannotMoveHere": "इस स्थान पर आइटम स्थानांतरित नहीं कर सकते" + }, + "openFolder": "फ़ोल्डर खोलें" }, "filesTable": { "ariaLabel": "फाइल तालिका", @@ -539,7 +554,10 @@ "movingTo": "यहाँ स्थानांतरित कर रहे हैं:", "title": "आइटम स्थानांतरित करें", "description": "आइटम को नए स्थान पर स्थानांतरित करें", - "success": "{count} आइटम सफलतापूर्वक स्थानांतरित किए गए" + "success": "{count} आइटम सफलतापूर्वक स्थानांतरित किए गए", + "errors": { + "moveFailed": "आइटम स्थानांतरित करने में विफल" + } }, "navbar": { "logoAlt": "एप्लिकेशन लोगो", @@ -1153,8 +1171,6 @@ }, "fileActions": { "edit": "संपादित करें", - "save": "सहेजें", - "cancel": "रद्द करें", "preview": "पूर्वावलोकन", "download": "डाउनलोड", "delete": "हटाएं", @@ -1377,16 +1393,6 @@ "deleteTitle": "साझाकरण हटाएं", "deleteConfirmation": "क्या आप वाकई इस साझाकरण को हटाना चाहते हैं? यह क्रिया पूर्ववत नहीं की जा सकती।", "editTitle": "साझाकरण संपादित करें", - "nameLabel": "साझाकरण नाम", - "descriptionLabel": "विवरण", - "descriptionPlaceholder": "विवरण दर्ज करें (वैकल्पिक)", - "expirationLabel": "समाप्ति तिथि", - "expirationPlaceholder": "DD/MM/YYYY HH:MM", - "maxViewsLabel": "अधिकतम दृश्य", - "maxViewsPlaceholder": "असीमित के लिए खाली छोड़ें", - "passwordProtection": "पासवर्ड संरक्षित", - "passwordLabel": "पासवर्ड", - "passwordPlaceholder": "पासवर्ड दर्ज करें", "newPasswordLabel": "नया पासवर्ड (वर्तमान रखने के लिए खाली छोड़ें)", "newPasswordPlaceholder": "नया पासवर्ड दर्ज करें", "manageFilesTitle": "फाइलें प्रबंधित करें", @@ -1405,7 +1411,9 @@ "linkDescriptionFile": "फ़ाइल साझा करने के लिए कस्टम लिंक जेनरेट करें", "linkDescriptionFolder": "फ़ोल्डर साझा करने के लिए कस्टम लिंक जेनरेट करें", "linkReady": "आपका साझाकरण लिंक तैयार है:", - "linkTitle": "लिंक जेनरेट करें" + "linkTitle": "लिंक जेनरेट करें", + "itemsSelected": "{count, plural, =0 {कोई आइटम चयनित नहीं} =1 {1 आइटम चयनित} other {# आइटम चयनित}}", + "manageFilesDescription": "इस साझाकरण में शामिल करने के लिए फ़ाइलें और फ़ोल्डर चुनें" }, "shareDetails": { "title": "साझाकरण विवरण", @@ -1421,7 +1429,6 @@ "noLink": "अभी तक कोई लिंक जेनरेट नहीं किया गया", "copyLink": "लिंक कॉपी करें", "openLink": "नए टैब में खोलें", - "linkCopied": "लिंक क्लिपबोर्ड में कॉपी कर दिया गया", "views": "दृश्य", "dates": "तिथियां", "created": "बनाया गया", @@ -1469,28 +1476,6 @@ "expires": "समाप्त होता है:", "expirationDate": "समाप्ति तिथि" }, - "shareFile": { - "title": "फाइल साझा करें", - "linkTitle": "लिंक जेनरेट करें", - "nameLabel": "साझाकरण नाम", - "namePlaceholder": "साझाकरण नाम दर्ज करें", - "descriptionLabel": "विवरण", - "descriptionPlaceholder": "विवरण दर्ज करें (वैकल्पिक)", - "expirationLabel": "समाप्ति तिथि", - "expirationPlaceholder": "DD/MM/YYYY HH:MM", - "maxViewsLabel": "अधिकतम दृश्य", - "maxViewsPlaceholder": "असीमित के लिए खाली छोड़ें", - "passwordProtection": "पासवर्ड संरक्षित", - "passwordLabel": "पासवर्ड", - "passwordPlaceholder": "पासवर्ड दर्ज करें", - "linkDescription": "फाइल साझा करने के लिए कस्टम लिंक जेनरेट करें", - "aliasLabel": "लिंक उपनाम", - "aliasPlaceholder": "कस्टम उपनाम दर्ज करें", - "linkReady": "आपका साझाकरण लिंक तैयार है:", - "createShare": "साझाकरण बनाएं", - "generateLink": "लिंक जेनरेट करें", - "copyLink": "लिंक कॉपी करें" - }, "shareManager": { "deleteSuccess": "साझाकरण सफलतापूर्वक हटाया गया", "deleteError": "साझाकरण हटाने में त्रुटि", @@ -1520,7 +1505,10 @@ "noFilesToDownload": "डाउनलोड करने के लिए कोई फाइलें उपलब्ध नहीं हैं", "singleShareZipName": "{Sharename} _files.zip", "zipDownloadError": "ज़िप फ़ाइल बनाने में विफल", - "zipDownloadSuccess": "ज़िप फ़ाइल सफलतापूर्वक डाउनलोड की गई" + "zipDownloadSuccess": "ज़िप फ़ाइल सफलतापूर्वक डाउनलोड की गई", + "errors": { + "multipleDownloadNotSupported": "कई साझाकरण डाउनलोड अभी तक समर्थित नहीं है - कृपया साझाकरण को अलग-अलग डाउनलोड करें" + } }, "shareMultipleFiles": { "title": "कई फाइलें साझा करें", @@ -1917,6 +1905,23 @@ "inactive": "निष्क्रिय", "admin": "व्यवस्थापक", "userr": "उपयोगकर्ता" + }, + "invite": { + "button": "आमंत्रण लिंक जनरेट करें", + "title": "उपयोगकर्ता आमंत्रण लिंक जनरेट करें", + "description": "एक एक-बार-उपयोग लिंक जनरेट करें जो किसी को अपना खाता बनाने की अनुमति देता है। लिंक 15 मिनट में समाप्त हो जाता है।", + "generating": "जनरेट हो रहा है...", + "generate": "लिंक जनरेट करें", + "generated": "आमंत्रण लिंक सफलतापूर्वक जनरेट हुआ!", + "linkReady": "आमंत्रण लिंक तैयार", + "linkReadyDescription": "इस लिंक को उस व्यक्ति के साथ साझा करें जिसे आप आमंत्रित करना चाहते हैं। वे एक नियमित उपयोगकर्ता के रूप में अपना खाता बना सकेंगे।", + "copyLink": "लिंक कॉपी करें", + "linkCopied": "आमंत्रण लिंक क्लिपबोर्ड पर कॉपी किया गया!", + "expiresIn": "15 मिनट में समाप्त होता है", + "close": "बंद करें", + "errors": { + "generateFailed": "आमंत्रण लिंक जनरेट करने में विफल" + } } }, "validation": { @@ -1935,15 +1940,62 @@ "required": "यह फ़ील्ड आवश्यक है" }, "embedCode": { - "title": "छवि एम्बेड करें", - "description": "इस छवि को मंचों, वेबसाइटों या अन्य प्लेटफार्मों में एम्बेड करने के लिए इन कोड का उपयोग करें", + "title": "मीडिया एम्बेड करें", + "description": "इस मीडिया को मंचों, वेबसाइटों या अन्य प्लेटफार्मों में एम्बेड करने के लिए इन कोड का उपयोग करें", "tabs": { "directLink": "सीधा लिंक", "html": "HTML", "bbcode": "BBCode" }, - "directLinkDescription": "छवि फ़ाइल का सीधा URL", - "htmlDescription": "HTML पेजों में छवि एम्बेड करने के लिए इस कोड का उपयोग करें", - "bbcodeDescription": "BBCode का समर्थन करने वाले मंचों में छवि एम्बेड करने के लिए इस कोड का उपयोग करें" + "directLinkDescription": "मीडिया फ़ाइल का सीधा URL", + "htmlDescription": "HTML पेजों में मीडिया एम्बेड करने के लिए इस कोड का उपयोग करें", + "bbcodeDescription": "BBCode का समर्थन करने वाले मंचों में मीडिया एम्बेड करने के लिए इस कोड का उपयोग करें" + }, + "contextMenu": { + "newFolder": "नया फ़ोल्डर", + "uploadFile": "फ़ाइल अपलोड करें" + }, + "registerWithInvite": { + "title": "अपना खाता बनाएं", + "description": "अपना खाता बनाने के लिए नीचे दी गई जानकारी भरें", + "labels": { + "firstName": "पहला नाम", + "firstNamePlaceholder": "अपना पहला नाम दर्ज करें", + "lastName": "अंतिम नाम", + "lastNamePlaceholder": "अपना अंतिम नाम दर्ज करें", + "username": "उपयोगकर्ता नाम", + "usernamePlaceholder": "एक उपयोगकर्ता नाम चुनें", + "email": "ईमेल", + "emailPlaceholder": "अपना ईमेल दर्ज करें", + "password": "पासवर्ड", + "passwordPlaceholder": "एक पासवर्ड चुनें", + "confirmPassword": "पासवर्ड की पुष्टि करें", + "confirmPasswordPlaceholder": "अपना पासवर्ड पुष्टि करें" + }, + "buttons": { + "creating": "खाता बनाया जा रहा है...", + "createAccount": "खाता बनाएं" + }, + "validation": { + "firstNameRequired": "पहला नाम आवश्यक है", + "lastNameRequired": "अंतिम नाम आवश्यक है", + "usernameMinLength": "उपयोगकर्ता नाम कम से कम 3 अक्षर का होना चाहिए", + "invalidEmail": "अमान्य ईमेल", + "passwordMinLength": "पासवर्ड कम से कम 8 अक्षर का होना चाहिए", + "passwordsMatch": "पासवर्ड मेल खाना चाहिए" + }, + "messages": { + "success": "खाता सफलतापूर्वक बनाया गया! लॉगिन पर पुनर्निर्देशित किया जा रहा है...", + "redirecting": "लॉगिन पर पुनर्निर्देशित किया जा रहा है..." + }, + "errors": { + "invalidToken": "आमंत्रण लिंक में त्रुटि", + "tokenUsed": "यह आमंत्रण लिंक पहले ही उपयोग किया जा चुका है", + "tokenExpired": "यह आमंत्रण लिंक समाप्त हो गया है", + "usernameExists": "उपयोगकर्ता नाम पहले से मौजूद है", + "emailExists": "ईमेल पहले से मौजूद है", + "createFailed": "खाता बनाने में विफल। कृपया पुनः प्रयास करें।" + }, + "pageTitle": "खाता बनाएं" } } \ No newline at end of file diff --git a/apps/web/messages/id-ID.json b/apps/web/messages/id-ID.json new file mode 100644 index 00000000..d484bc7d --- /dev/null +++ b/apps/web/messages/id-ID.json @@ -0,0 +1,2001 @@ +{ + "auth": { + "successfullyAuthenticated": "Berhasil diautentikasi!", + "authenticationFailed": "Autentikasi gagal", + "errors": { + "account_inactive": "Akun tidak aktif. Silakan hubungi administrator.", + "registration_disabled": "Pendaftaran SSO dinonaktifkan.", + "token_expired": "Token kedaluwarsa. Silakan coba lagi.", + "config_error": "Kesalahan konfigurasi. Silakan hubungi dukungan.", + "auth_failed": "Autentikasi gagal. Silakan coba lagi." + } + }, + "contextMenu": { + "newFolder": "Folder baru", + "uploadFile": "Unggah file" + }, + "authProviders": { + "title": "Penyedia Otentikasi", + "description": "Konfigurasikan penyedia otentikasi eksternal untuk SSO", + "enabledCount": "{count} diaktifkan", + "loadingProviders": "Memuat penyedia...", + "providersConfigured": "{count} penyedia dikonfigurasi", + "enabledOfTotal": "{enabled} diaktifkan dari {total} penyedia", + "hideDisabledProviders": "Sembunyikan penyedia yang nonaktif", + "addProvider": "Tambah Penyedia", + "addProviderTitle": "Tambah Penyedia", + "editProvider": "Edit Penyedia", + "deleteProvider": "Hapus Penyedia", + "enabled": "Diaktifkan", + "disabled": "Nonaktif", + "officialProvider": "Penyedia Resmi", + "dragToReorder": "Seret untuk mengurutkan ulang", + "dragDisabledMessage": "Seret dan lepas dinonaktifkan saat memfilter penyedia. Tampilkan semua penyedia untuk mengurutkan ulang.", + "dragEnabledMessage": "Seret penyedia untuk mengurutkan ulang. Urutan ini akan ditampilkan di halaman login.", + "noProvidersEnabled": "Tidak ada penyedia otentikasi yang diaktifkan", + "noProvidersConfigured": "Tidak ada penyedia otentikasi yang dikonfigurasi", + "form": { + "providerName": "Nama Penyedia", + "providerNamePlaceholder": "misal: mycompany", + "displayName": "Nama Tampilan", + "displayNamePlaceholder": "misal: My Company SSO", + "type": "Tipe", + "typeOidc": "OIDC (OpenID Connect)", + "typeOauth2": "OAuth 2.0", + "icon": "Ikon", + "iconPlaceholder": "Pilih ikon", + "clientId": "Client ID", + "clientIdPlaceholder": "Client ID OAuth Anda", + "clientSecret": "Client Secret", + "clientSecretPlaceholder": "Client Secret OAuth Anda", + "oauthScopes": "Ruang Lingkup OAuth", + "scopesPlaceholder": "Masukkan scopes (misal: openid, profile, email)", + "scopesHelpOidc": "Scopes disarankan otomatis berdasarkan URL Penyedia. OIDC umum: openid, profile, email, groups", + "scopesHelpOauth2": "Scopes disarankan otomatis berdasarkan URL Penyedia. Scopes OAuth2 umum bergantung pada penyedia", + "providerUrl": "URL Penyedia", + "providerUrlPlaceholder": "https://auth.example.com", + "providerUrlAutoPlaceholder": "https://your-provider.com (endpoint akan dideteksi otomatis)", + "providerUrlManualPlaceholder": "https://your-provider.com", + "autoDiscoveryHelp": "Sistem akan secara otomatis mendeteksi endpoint otorisasi, token, dan userinfo", + "manualConfigurationHelp": "Base URL penyedia Anda (endpoint relatif terhadap ini)", + "authorizationEndpoint": "Endpoint Otorisasi", + "authorizationEndpointPlaceholder": "https://auth.example.com/auth", + "tokenEndpoint": "Endpoint Token", + "tokenEndpointPlaceholder": "https://auth.example.com/token", + "userInfoEndpoint": "Endpoint Info Pengguna", + "userInfoEndpointPlaceholder": "https://auth.example.com/userinfo", + "configurationMethod": "Metode Konfigurasi", + "autoDiscovery": "Deteksi Otomatis", + "autoDiscoveryDescription": "Otomatis mendeteksi endpoint dari URL Penyedia", + "manualEndpoints": "Endpoint Manual (Disarankan)", + "manualEndpointsDescription": "Konfigurasikan endpoint otorisasi, token, dan info pengguna manual", + "callbackUrl": "Callback URL", + "callbackUrlDescription": "Gunakan URL ini dalam konfigurasi OAuth penyedia Anda", + "copyCallbackUrl": "Salin Callback URL", + "callbackUrlCopied": "Callback URL berhasil disalin ke papan klip!", + "adminEmailDomains": "Domain Email Admin", + "adminEmailDomainsPlaceholder": "Masukkan domain (misal: admin.company.com)", + "adminEmailDomainsHelp": "Pengguna dengan email dari domain ini akan memiliki hak admin", + "autoRegister": "Daftarkan pengguna baru secara otomatis", + "officialProviderUrlPlaceholder": "Ganti placeholder dengan URL {displayName} Anda", + "officialProviderHelp": "Ini adalah penyedia resmi. Endpoint telah dikonfigurasi. Anda hanya bisa edit URL ini.", + "officialProviderIconHelp": "Anda bisa mengganti ikon untuk penyedia resmi ini." + }, + "buttons": { + "cancel": "Batal", + "save": "Simpan", + "saving": "Menyimpan...", + "adding": "Menambah...", + "updating": "Memperbarui...", + "saveProvider": "Simpan Penyedia", + "delete": "Hapus", + "deleting": "Menghapus...", + "edit": "Edit", + "enable": "Aktifkan", + "disable": "Nonaktifkan" + }, + "messages": { + "providerAdded": "Penyedia berhasil ditambahkan", + "providerUpdated": "Penyedia berhasil diperbarui", + "providerDeleted": "Penyedia berhasil dihapus", + "providerOrderUpdated": "Urutan penyedia berhasil diperbarui", + "fillRequiredFields": "Silakan lengkapi semua kolom wajib (nama, nama tampilan, client ID, client secret)", + "provideUrlOrEndpoints": "Berikan URL Penyedia untuk deteksi otomatis ATAU semua ketiga endpoint kustom", + "chooseDiscoveryOrManual": "Pilih deteksi otomatis (URL Penyedia) ATAU endpoint manual, bukan keduanya", + "loadFailed": "Gagal memuat penyedia", + "addFailed": "Gagal menambah penyedia", + "updateFailed": "Gagal memperbarui penyedia", + "deleteFailed": "Gagal menghapus penyedia", + "orderUpdateFailed": "Gagal memperbarui urutan penyedia" + }, + "info": { + "title": "Informasi", + "officialProvidersRecommended": "Untuk fungsionalitas lebih baik, disarankan menggunakan penyedia resmi. Jika Anda mengalami masalah dengan penyedia kustom, pertimbangkan untuk membuka isu di", + "github": "GitHub", + "officialProvider": "Penyedia Resmi", + "officialProviderDescription": "Penyedia ini dioptimalkan oleh Palmr. Hanya kredensial dan konfigurasi yang dapat diubah.", + "manualConfigTitle": "Konfigurasi Manual", + "manualConfigDescription": "Anda memasukkan semua endpoint secara manual. Pastikan semua sudah benar untuk penyedia Anda." + }, + "deleteModal": { + "title": "Hapus Penyedia Otentikasi", + "confirmMessage": "Apakah Anda yakin ingin menghapus penyedia \"{displayName}\"? Tindakan ini tidak dapat dibatalkan.", + "providerId": "ID Penyedia: {name}", + "cancel": "Batal", + "delete": "Hapus Penyedia", + "deleting": "Menghapus..." + } + }, + "bulkDownload": { + "title": "Unduh Massal", + "zipNameLabel": "Nama file ZIP", + "zipNamePlaceholder": "Masukkan nama file", + "description": "{count, plural, =1 {1 file akan dikompres} other {# file akan dikompres}}", + "download": "Unduh ZIP" + }, + "common": { + "loading": "Memuat, mohon tunggu...", + "loadingSimple": "Memuat...", + "cancel": "Batal", + "save": "Simpan", + "saving": "Menyimpan...", + "update": "Perbarui", + "updating": "Memperbarui...", + "delete": "Hapus", + "deleting": "Menghapus...", + "close": "Tutup", + "download": "Unduh", + "unexpectedError": "Terjadi kesalahan tak terduga. Silakan coba lagi.", + "yes": "Ya", + "no": "Tidak", + "dashboard": "Dasbor", + "back": "Kembali", + "click": "Klik untuk", + "creating": "Membuat...", + "create": "Buat", + "rename": "Ubah Nama", + "move": "Pindahkan", + "share": "Bagikan", + "search": "Cari", + "copy": "Salin", + "copied": "Disalin" + }, + "createShare": { + "title": "Buat Berbagi", + "nameLabel": "Nama Berbagi", + "namePlaceholder": "Masukkan nama untuk berbagi Anda", + "descriptionLabel": "Deskripsi", + "descriptionPlaceholder": "Masukkan deskripsi (opsional)", + "expirationLabel": "Tanggal Kedaluwarsa", + "expirationPlaceholder": "MM/DD/YYYY HH:MM", + "maxViewsLabel": "Tampilan Maksimum", + "maxViewsPlaceholder": "Biarkan kosong untuk tak terbatas", + "passwordProtection": "Dilindungi Kata Sandi", + "passwordLabel": "Kata Sandi", + "passwordPlaceholder": "Masukkan kata sandi", + "create": "Buat Berbagi", + "success": "Berbagi berhasil dibuat", + "error": "Gagal membuat berbagi", + "errors": { + "nameRequired": "Nama berbagi wajib diisi", + "selectItems": "Silakan pilih minimal satu file atau folder" + }, + "itemsSelected": "{count} item dipilih", + "selectItemsPrompt": "Pilih file dan folder untuk dibagikan", + "tabs": { + "shareDetails": "Detail Berbagi", + "selectFiles": "Pilih File" + }, + "nextSelectFiles": "Berikutnya: Pilih File", + "searchLabel": "Cari" + }, + "customization": { + "breadcrumb": "Kustomisasi", + "colors": { + "title": "Warna Tema", + "description": "Pilih tema warna utama yang Anda sukai", + "presets": "Warna Tersedia", + "presetsDescription": "Pilih dari tema warna yang tersedia", + "reset": "Atur Ulang ke Default" + }, + "fonts": { + "title": "Tipografi", + "description": "Pilih keluarga font yang Anda sukai", + "available": "Font Tersedia", + "availableDescription": "Pilih dari keluarga font yang tersedia", + "reset": "Atur Ulang ke Default" + }, + "radius": { + "title": "Radius Sudut", + "description": "Sesuaikan tingkat kebulatan elemen antarmuka", + "available": "Opsi Sudut Bulat", + "availableDescription": "Pilih seberapa bulat sudut tampilan", + "reset": "Atur Ulang ke Default" + }, + "background": { + "title": "Warna Latar Belakang", + "description": "Sesuaikan warna latar untuk mode terang dan gelap", + "lightMode": "Mode Terang", + "darkMode": "Mode Gelap", + "availableDescription": "Pilih warna latar belakang untuk tema terang dan gelap", + "reset": "Atur Ulang ke Default" + }, + "theme": { + "title": "Mode Tema", + "description": "Pilih antara tema terang, gelap, atau sistem", + "selectTheme": "Preferensi Tema", + "availableDescription": "Pilih mode tema favorit Anda", + "reset": "Atur Ulang ke Sistem" + }, + "pageTitle": "Kustomisasi" + }, + "dashboard": { + "loadError": "Gagal memuat data dasbor", + "linkCopied": "Tautan disalin ke clipboard", + "pageTitle": "Dasbor", + "breadcrumb": "Dasbor", + "recentFiles": { + "title": "File Terbaru", + "description": "File yang baru saja Anda unggah" + } + }, + "deleteConfirmation": { + "filesToDelete": "File yang akan dihapus", + "foldersToDelete": "Folder yang akan dihapus", + "itemsToDelete": "Item yang akan dihapus", + "sharesToDelete": "Berbagi yang akan dihapus" + }, + "downloadQueue": { + "downloadQueued": "Unduhan dalam antrean: {fileName}", + "queuedDescription": "Unduhan Anda akan mulai secara otomatis saat slot tersedia", + "queuePosition": "Unduhan dalam antrean pada posisi {position}: {fileName}", + "estimatedWait": "Perkiraan waktu tunggu: {time}", + "queueFull": "Antrean unduhan penuh", + "queueFullDescription": "Silakan coba lagi dalam beberapa menit saat antrean tersedia", + "cancelSuccess": "Unduhan berhasil dibatalkan", + "cancelError": "Gagal membatalkan unduhan: {error}", + "status": { + "pending": "Menyiapkan...", + "queued": "Dalam antrean", + "downloading": "Mengunduh", + "completed": "Selesai", + "failed": "Gagal" + }, + "waitTime": { + "seconds": "{seconds}d", + "minutes": "{minutes}m", + "hoursMinutes": "{hours}j {minutes}m" + }, + "indicator": { + "title": "Unduhan", + "downloads": "Antrean Unduhan", + "active": "Aktif", + "queued": "Dalam antrean", + "position": "Posisi {position}", + "estimatedWait": "Tunggu: {time}", + "unknownFile": "File tidak diketahui", + "noDownloads": "Tidak ada unduhan yang sedang berlangsung", + "refresh": "Muat Ulang Antrean" + } + }, + "emptyState": { + "noFiles": "Belum ada file yang diunggah", + "uploadFile": "Unggah File" + }, + "errors": { + "invalidCredentials": "Email atau kata sandi salah", + "userNotFound": "Pengguna tidak ditemukan", + "accountLocked": "Akun terkunci. Silakan coba lagi nanti", + "unexpectedError": "Terjadi kesalahan tak terduga. Silakan coba lagi", + "Invalid verification code": "Kode verifikasi tidak valid", + "Two-factor authentication is already enabled": "Otentikasi dua faktor sudah diaktifkan", + "Two-factor authentication is not enabled": "Otentikasi dua faktor belum diaktifkan", + "Invalid password": "Kata sandi tidak valid", + "Password verification required": "Verifikasi kata sandi diperlukan", + "Invalid two-factor authentication code": "Kode otentikasi dua faktor tidak valid", + "Two-factor authentication required": "Otentikasi dua faktor diperlukan", + "noUserData": "Tidak ada data pengguna" + }, + "fileActions": { + "editFile": "Edit Berkas", + "nameLabel": "Nama", + "namePlaceholder": "Masukkan nama baru", + "extension": "Ekstensi", + "descriptionLabel": "Deskripsi", + "descriptionPlaceholder": "Masukkan deskripsi file", + "addDescriptionPlaceholder": "Tambah deskripsi...", + "deleteFile": "Hapus File", + "deleteConfirmation": "Apakah Anda yakin ingin menghapus file ini?", + "deleteWarning": "Tindakan ini tidak dapat dibatalkan." + }, + "fileManager": { + "downloadError": "Gagal mengunduh file", + "updateSuccess": "File berhasil diperbarui", + "updateError": "Gagal memperbarui file", + "deleteSuccess": "File berhasil dihapus", + "deleteError": "Gagal menghapus file" + }, + "filePreview": { + "title": "Pratinjau File", + "description": "Pratinjau dan unduh file", + "loading": "Memuat...", + "notAvailable": "Pratinjau tidak tersedia untuk tipe file ini", + "downloadToView": "Gunakan tombol unduh untuk melihat file ini", + "loadError": "Kesalahan memuat pratinjau file", + "downloadError": "Kesalahan saat mengunduh file", + "audioNotSupported": "Browser Anda tidak mendukung pemutaran audio", + "videoNotSupported": "Browser Anda tidak mendukung pemutaran video", + "pdfPreviewNotAvailable": "Pratinjau PDF tidak tersedia. Coba tampilan alternatif atau unduh", + "tryAlternativeView": "Coba Tampilan Alternatif", + "loadingAlternative": "Memuat tampilan alternatif...", + "loadingAudio": "Memuat audio..." + }, + "fileSelector": { + "availableFiles": "File Tersedia ({count})", + "shareFiles": "File yang Dibagikan ({count})", + "shareFilesDescription": "File yang saat ini ada di bagikan ini", + "availableFilesDescription": "Pilih file untuk ditambahkan ke bagikan ini", + "searchPlaceholder": "Cari file...", + "searchSelectedFiles": "Cari file yang dipilih...", + "noMatchingFiles": "Tidak ditemukan file yang cocok", + "noAvailableFiles": "Tidak ada file tersedia", + "noFilesInShare": "Tidak ada file dalam bagikan ini", + "noFilesFound": "Tidak ada file ditemukan", + "noFilesFoundWith": "Tidak ada file ditemukan yang cocok dengan \"{query}\"", + "addFilesFromList": "Tambahkan file dari daftar di bawah ini", + "tryDifferentSearch": "Coba kata kunci pencarian lain", + "allFilesInShare": "Semua file sudah ada di bagikan ini", + "uploadNewFiles": "Unggah file baru untuk menambahkannya", + "fileCount": "{count, plural, =1 {file} other {file}}", + "filesSelected": "{count, plural, =0 {Tidak ada file yang dipilih} =1 {1 file dipilih} other {# file dipilih}}", + "itemsSelected": "{count} item dipilih", + "editFile": "Edit file", + "editFolder": "Edit folder", + "previewFile": "Pratinjau file", + "addToShare": "Tambahkan ke bagikan", + "removeFromShare": "Hapus dari bagikan", + "saveChanges": "Simpan Perubahan" + }, + "files": { + "title": "Semua File", + "uploadFile": "Unggah File", + "loadError": "Gagal memuat file", + "pageTitle": "File Saya", + "breadcrumb": "File Saya", + "downloadStart": "Unduhan dimulai", + "downloadError": "Gagal mengunduh file", + "updateSuccess": "File berhasil diperbarui", + "updateError": "Gagal memperbarui file", + "deleteSuccess": "File berhasil dihapus", + "deleteError": "Gagal menghapus file", + "bulkDownloadSuccess": "Unduhan file berhasil dimulai", + "bulkDownloadError": "Kesalahan membuat file ZIP", + "bulkDownloadFileError": "Kesalahan mengunduh file {fileName}", + "bulkDeleteSuccess": "{count, plural, =1 {1 item berhasil dihapus} other {# item berhasil dihapus}}", + "bulkDeleteError": "Kesalahan menghapus item yang dipilih", + "bulkDeleteTitle": "Hapus Item Terpilih", + "bulkDeleteConfirmation": "Apakah Anda yakin ingin menghapus {count, plural, =1 {1 item} other {# item}}? Tindakan ini tidak dapat dibatalkan.", + "totalFiles": "{count, plural, =0 {Tidak ada file} =1 {1 file} other {# file}}", + "openFolder": "Buka folder", + "errors": { + "moveItemsFailed": "Gagal memindahkan item. Silakan coba lagi.", + "cannotMoveHere": "Tidak dapat memindahkan item ke lokasi ini" + }, + "empty": { + "title": "Belum ada file atau folder", + "description": "Unggah file pertama Anda atau buat folder untuk memulai" + }, + "files": "file", + "folders": "folder", + "actions": { + "open": "Buka", + "rename": "Ganti Nama", + "delete": "Hapus" + }, + "viewMode": { + "table": "Tabel", + "grid": "Grid" + } + }, + "filesTable": { + "ariaLabel": "Tabel file", + "selectAll": "Pilih semua", + "selectFile": "Pilih file {fileName}", + "columns": { + "name": "NAMA", + "description": "DESKRIPSI", + "size": "UKURAN", + "createdAt": "DIBUAT PADA", + "updatedAt": "DIUBAH PADA", + "actions": "TINDAKAN" + }, + "actions": { + "menu": "Menu aksi file", + "preview": "Pratinjau", + "edit": "Edit", + "share": "Bagikan", + "download": "Unduh", + "delete": "Hapus" + }, + "bulkActions": { + "selected": "{count, plural, =1 {1 file dipilih} other {# file dipilih}}", + "actions": "Tindakan", + "download": "Unduh Terpilih", + "share": "Bagikan Terpilih", + "delete": "Hapus Terpilih" + } + }, + "folderActions": { + "editFolder": "Edit Folder", + "folderName": "Nama Folder", + "folderNamePlaceholder": "Masukkan nama folder", + "folderDescription": "Deskripsi", + "folderDescriptionPlaceholder": "Masukkan deskripsi folder (opsional)", + "createFolder": "Buat Folder", + "renameFolder": "Ganti Nama Folder", + "moveFolder": "Pindahkan Folder", + "shareFolder": "Bagikan Folder", + "deleteFolder": "Hapus Folder", + "moveTo": "Pindahkan ke", + "selectDestination": "Pilih folder tujuan", + "rootFolder": "Root", + "folderCreated": "Folder berhasil dibuat", + "folderRenamed": "Folder berhasil diubah nama", + "folderMoved": "Folder berhasil dipindahkan", + "folderDeleted": "Folder berhasil dihapus", + "folderShared": "Folder berhasil dibagikan", + "createFolderError": "Kesalahan membuat folder", + "renameFolderError": "Kesalahan mengubah nama folder", + "moveFolderError": "Kesalahan memindahkan folder", + "deleteFolderError": "Kesalahan menghapus folder", + "shareFolderError": "Kesalahan membagikan folder", + "deleteConfirmation": "Apakah Anda yakin ingin menghapus folder ini?", + "deleteWarning": "Tindakan ini tidak dapat dibatalkan." + }, + "footer": { + "poweredBy": "Ditenagai oleh", + "kyanHomepage": "Beranda Kyantech" + }, + "forgotPassword": { + "emailLabel": "Alamat Email", + "emailPlaceholder": "Masukkan email Anda", + "sending": "Mengirim...", + "submit": "Kirim Instruksi Reset", + "backToLogin": "Kembali ke Masuk", + "title": "Lupa Kata Sandi", + "description": "Masukkan alamat email Anda dan kami akan mengirimkan instruksi untuk mengatur ulang kata sandi Anda", + "resetInstructions": "Instruksi reset telah dikirim ke email Anda", + "pageTitle": "Lupa Kata Sandi", + "passwordAuthDisabled": "Otentikasi kata sandi dinonaktifkan. Silakan hubungi administrator Anda atau gunakan penyedia otentikasi eksternal." + }, + "generateShareLink": { + "generateTitle": "Buat Tautan Berbagi", + "updateTitle": "Perbarui Tautan Berbagi", + "generateDescription": "Buat tautan khusus untuk berbagi ini. Anda dapat menyesuaikan URL agar lebih mudah diingat.", + "updateDescription": "Perbarui tautan khusus untuk berbagi ini. Anda dapat menyesuaikan URL agar lebih mudah diingat.", + "aliasPlaceholder": "ID kustom untuk tautan", + "linkReady": "Tautan berbagi Anda sudah siap. Anda dapat menyalinnya sekarang.", + "readyDescription": "Tautan berbagi Anda sudah siap. Anda dapat memindai kode QR secara langsung, mengunduhnya untuk digunakan nanti, atau menyalin tautan di bawah.", + "generateButton": "Buat Tautan", + "updateButton": "Perbarui Tautan", + "copyButton": "Salin Tautan", + "success": "Tautan berhasil dibuat", + "error": "Gagal membuat tautan", + "copied": "Tautan disalin ke clipboard", + "tabs": { + "link": "Tautan", + "qrcode": "Kode QR" + } + }, + "home": { + "description": "Alternatif open-source untuk WeTransfer. Bagikan file secara aman, tanpa pelacakan atau batasan.", + "documentation": "Dokumentasi", + "starOnGithub": "Berikan Bintang di GitHub", + "privacyMessage": "Dibangun dengan mengutamakan privasi. File Anda hanya dapat diakses oleh mereka yang memiliki tautan berbagi sebelum unggah. Selamanya gratis dan open source.", + "header": { + "fileSharing": "Berbagi file", + "tagline": "dibuat sederhana dan gratis" + }, + "pageTitle": "Beranda" + }, + "iconPicker": { + "title": "Pilih Ikon", + "placeholder": "Pilih ikon", + "searchPlaceholder": "Cari ikon...", + "loadingMore": "Memuat lebih banyak ikon...", + "allIconsLoaded": "Semua {count} ikon dimuat", + "noIconsFound": "Tidak ada ikon yang ditemukan untuk \"{search}\"", + "tabs": { + "all": "Semua Ikon", + "popular": "Populer", + "auth": "Penyedia Autentikasi" + }, + "stats": "{iconCount} ikon dari {libraryCount} perpustakaan", + "categoryBadge": "{category} ({count} ikon)" + }, + "imageEdit": { + "title": "Edit Gambar", + "rotate": "Putar", + "zoom": "Perbesar", + "cropInstructions": "Seret untuk memposisikan ulang, ubah ukuran sudut untuk menyesuaikan area potong" + }, + "login": { + "welcome": "Selamat datang di", + "signInToContinue": "Masuk untuk melanjutkan", + "emailOrUsernameLabel": "Email atau Nama Pengguna", + "emailOrUsernamePlaceholder": "Masukkan email atau nama pengguna Anda", + "emailLabel": "Alamat Email", + "emailPlaceholder": "Masukkan email Anda", + "passwordLabel": "Kata Sandi", + "passwordPlaceholder": "Masukkan kata sandi Anda", + "signIn": "Masuk", + "signingIn": "Masuk...", + "forgotPassword": "Lupa kata sandi?", + "pageTitle": "Masuk", + "or": "atau", + "continueWithSSO": "Lanjutkan dengan SSO", + "processing": "Memproses autentikasi..." + }, + "logo": { + "labels": { + "appLogo": "Logo Aplikasi" + }, + "buttons": { + "upload": "Unggah Logo", + "remove": "Hapus Logo" + }, + "messages": { + "uploadSuccess": "Logo berhasil diunggah", + "removeSuccess": "Logo berhasil dihapus" + }, + "errors": { + "uploadFailed": "Gagal mengunggah logo", + "removeFailed": "Gagal menghapus logo" + } + }, + "moveItems": { + "itemsToMove": "Item yang akan dipindahkan:", + "movingTo": "Memindahkan ke:", + "title": "Pindahkan {count, plural, =1 {Item} other {Item}}", + "description": "Pindahkan {count, plural, =1 {item} other {item}} ke lokasi baru", + "success": "Berhasil memindahkan {count} {count, plural, =1 {item} other {item}}", + "errors": { + "moveFailed": "Gagal memindahkan item" + } + }, + "navbar": { + "logoAlt": "Logo Aplikasi", + "profileMenu": "Menu Profil", + "profile": "Profil", + "customization": "Kustomisasi", + "settings": "Pengaturan", + "usersManagement": "Manajemen Pengguna", + "logout": "Keluar" + }, + "navigation": { + "dashboard": "Dasbor" + }, + "notifications": { + "permissionGranted": "Notifikasi unduhan diaktifkan", + "permissionDenied": "Notifikasi unduhan dinonaktifkan", + "downloadComplete": { + "title": "Unduhan Selesai", + "body": "{fileName} telah selesai diunduh" + }, + "downloadFailed": { + "title": "Unduhan Gagal", + "body": "Gagal mengunduh {fileName}: {error}", + "unknownError": "Kesalahan tidak dikenal" + }, + "queueProcessing": { + "title": "Unduhan Dimulai", + "body": "{fileName} sekarang sedang mengunduh{position}", + "position": " (sebelumnya # posisi {position} dalam antrean)" + } + }, + "profile": { + "password": { + "title": "Ubah Kata Sandi", + "newPassword": "Kata Sandi Baru", + "confirmPassword": "Konfirmasi Kata Sandi Baru", + "updateButton": "Perbarui Kata Sandi" + }, + "form": { + "title": "Informasi Profil", + "firstName": "Nama Depan", + "lastName": "Nama Belakang", + "username": "Nama Pengguna", + "email": "Email", + "updateButton": "Perbarui Profil" + }, + "header": { + "title": "Profil", + "subtitle": "Kelola informasi pribadi dan kata sandi Anda" + }, + "picture": { + "title": "Foto Profil", + "description": "Klik ikon kamera untuk mengubah foto profil Anda", + "uploadPhoto": "Unggah Foto", + "removePhoto": "Hapus Foto" + }, + "errors": { + "loadFailed": "Gagal memuat data pengguna", + "updateFailed": "Gagal memperbarui profil", + "passwordFailed": "Gagal memperbarui kata sandi", + "imageFailed": "Gagal memperbarui gambar profil", + "imageRemoveFailed": "Gagal menghapus gambar profil" + }, + "messages": { + "noChanges": "Tidak ada perubahan untuk disimpan", + "updateSuccess": "Profil berhasil diperbarui", + "fillPasswords": "Mohon isi kedua kolom kata sandi", + "passwordSuccess": "Kata sandi berhasil diperbarui", + "imageSuccess": "Gambar profil berhasil diperbarui", + "imageRemoved": "Gambar profil berhasil dihapus" + }, + "pageTitle": "Profil" + }, + "qrCodeModal": { + "title": "Bagikan Kode QR", + "description": "Pindai kode QR ini untuk mengakses tautan.", + "download": "Unduh Kode QR" + }, + "quickAccess": { + "files": { + "title": "File Saya", + "description": "Akses dan kelola file yang telah Anda unggah" + }, + "shares": { + "title": "Berbagi Saya", + "description": "Lihat dan kelola file yang telah Anda bagikan" + }, + "reverseShares": { + "title": "Terima File", + "description": "Buat tautan untuk orang lain mengirim file kepada Anda" + } + }, + "recentFiles": { + "title": "Unggahan Terbaru", + "viewAll": "Lihat Semua", + "upload": "Unggah", + "uploadFile": "Unggah File", + "noFiles": "Belum ada file yang diunggah" + }, + "recentShares": { + "title": "Berbagi Terbaru", + "viewAll": "Lihat Semua", + "createShare": "Buat Berbagi", + "noShares": "Belum ada berbagi yang dibuat", + "createFirst": "Buat berbagi pertama Anda" + }, + "recipientSelector": { + "emailPlaceholder": "Masukkan email penerima", + "add": "Tambah", + "recipients": "Penerima ({count})", + "notifyAll": "Beritahu Semua", + "noRecipients": "Belum ada penerima yang ditambahkan", + "addSuccess": "Penerima berhasil ditambahkan", + "addError": "Gagal menambahkan penerima", + "removeSuccess": "Penerima berhasil dihapus", + "removeError": "Gagal menghapus penerima", + "sendingNotifications": "Mengirim notifikasi...", + "notifySuccess": "Penerima berhasil diberitahu", + "notifyError": "Gagal memberitahu penerima", + "selectAll": "Pilih semua", + "selectedCount": "{count} dipilih", + "selectRecipient": "Pilih {email}", + "notifySelected": "Beritahu yang Dipilih", + "removeSelected": "Hapus yang Dipilih", + "notifySingle": "Beritahu penerima ini", + "removeSingle": "Hapus penerima ini", + "bulkRemoveSuccess": "{count} penerima berhasil dihapus", + "bulkRemoveError": "Gagal menghapus penerima yang dipilih", + "bulkNotifySuccess": "Notifikasi dikirim ke {count} penerima", + "bulkNotifyError": "Gagal memberitahu penerima yang dipilih", + "singleNotifySuccess": "Notifikasi dikirim ke {email}", + "singleNotifyError": "Gagal memberitahu penerima", + "modalDescription": "Tambahkan dan kelola penerima untuk berbagi ini. Anda dapat memberitahu semua atau penerima tertentu saat SMTP dikonfigurasi.", + "addRecipient": "Tambah Penerima", + "invalidEmail": "Mohon masukkan alamat email yang valid", + "duplicateEmail": "Penerima ini sudah ditambahkan", + "noRecipientsDescription": "Tambahkan penerima untuk membagikan konten ini melalui email" + }, + "register": { + "validation": { + "firstNameRequired": "Nama depan wajib diisi", + "lastNameRequired": "Nama belakang wajib diisi", + "usernameMinLength": "Nama pengguna harus minimal 3 karakter", + "invalidEmail": "Email tidak valid", + "passwordMinLength": "Kata sandi harus minimal 8 karakter", + "success": "Pengguna administrator berhasil dibuat!", + "error": "Kesalahan membuat pengguna administrator" + }, + "labels": { + "firstName": "Nama Depan", + "lastName": "Nama Belakang", + "username": "Nama Pengguna", + "email": "Email", + "password": "Kata Sandi" + }, + "buttons": { + "creating": "Membuat...", + "createAdmin": "Buat Akun Admin" + } + }, + "resetPassword": { + "pageTitle": "Atur Ulang Kata Sandi", + "header": { + "title": "Atur Ulang Kata Sandi", + "description": "Masukkan kata sandi baru Anda di bawah" + }, + "form": { + "newPassword": "Kata Sandi Baru", + "newPasswordPlaceholder": "Masukkan kata sandi baru Anda", + "confirmPassword": "Konfirmasi Kata Sandi Baru", + "confirmPasswordPlaceholder": "Konfirmasi kata sandi baru Anda", + "resetting": "Mengatur Ulang Kata Sandi...", + "submit": "Atur Ulang Kata Sandi", + "backToLogin": "Kembali ke Masuk" + }, + "messages": { + "success": "Kata sandi berhasil diatur ulang" + }, + "errors": { + "serverError": "Gagal mengatur ulang kata sandi. Silakan coba lagi.", + "invalidToken": "Token reset tidak valid atau hilang" + } + }, + "reverseShares": { + "pageTitle": "Terima File", + "search": { + "title": "Kelola Tautan Terima", + "createButton": "Buat Tautan", + "placeholder": "Cari tautan terima...", + "results": "Ditemukan {filtered} dari {total} tautan terima" + }, + "labels": { + "files": "file", + "size": "ukuran", + "status": "status", + "access": "akses", + "description": "Deskripsi", + "pageLayout": "Tata Letak Halaman", + "security": "Keamanan & Status", + "limits": "Batas", + "maxFiles": "File Maksimum", + "maxFileSize": "Ukuran Maksimum", + "allowedTypes": "Jenis yang Diizinkan", + "filesReceived": "File Diterima", + "fileLimit": "Batas File", + "noLimit": "Tidak ada batas", + "noLinkCreated": "Tidak ada tautan dibuat", + "publicAccess": "Akses Publik", + "protectedByPassword": "Dilindungi Kata Sandi", + "configureProtection": "Klik untuk mengkonfigurasi perlindungan", + "enterPassword": "Masukkan kata sandi", + "thisLinkProtected": "Tautan ini akan dilindungi kata sandi", + "thisLinkPublic": "Tautan ini akan tersedia untuk publik", + "configureExpiration": "Konfigurasi Kedaluwarsa", + "configureLimits": "Konfigurasi Batas File", + "protectWithPassword": "Lindungi dengan Kata Sandi", + "layoutOptions": { + "default": "Default", + "wetransfer": "WeTransfer" + }, + "noFilesLimit": "Tidak ada batas file", + "noSizeLimit": "Tidak ada batas ukuran", + "allFileTypes": "Semua jenis file", + "fileTypesHelp": "Masukkan ekstensi tanpa titik, dipisahkan dengan spasi, koma, tanda hubung atau pipa", + "fieldRequirements": "Persyaratan Bidang", + "nameFieldRequired": "Bidang Nama", + "emailFieldRequired": "Bidang Email", + "fieldOptions": { + "hidden": "Sembunyi", + "optional": "Opsional", + "required": "Wajib" + } + }, + "card": { + "untitled": "Tautan Tanpa Judul", + "noDescription": "Tidak ada deskripsi", + "addDescriptionPlaceholder": "Tambah deskripsi...", + "files": "file", + "progress": "Progres", + "created": "Dibuat", + "expired": "Kedaluwarsa", + "expires": "Kedaluwarsa", + "viewDetails": "Lihat detail", + "viewQrCode": "Lihat Kode QR", + "copyLink": "Salin Tautan", + "openInNewTab": "Buka di Tab Baru", + "editLink": "Edit Tautan", + "createLink": "Buat Tautan", + "delete": "Hapus", + "copyLinkTitle": "Salin tautan", + "createLinkCTA": "Buat Tautan Terima" + }, + "status": { + "active": "Aktif", + "inactive": "Tidak Aktif", + "expired": "Kedaluwarsa", + "protected": "Terlindungi", + "public": "Publik" + }, + "actions": { + "copyLink": "Salin Tautan", + "editAlias": "Edit Alias", + "createAlias": "Buat Alias", + "viewDetails": "Lihat Detail", + "edit": "Edit", + "delete": "Hapus", + "viewFiles": "File Diterima", + "viewQrCode": "Lihat Kode QR" + }, + "empty": { + "title": "Belum ada tautan terima dibuat", + "description": "Buat tautan khusus untuk orang lain mengirim file langsung kepada Anda secara aman dan terorganisir.", + "createButton": "Buat Tautan Pertama" + }, + "modals": { + "create": { + "title": "Buat Tautan Terima", + "description": "Konfigurasi tautan khusus untuk menerima file dari orang lain" + }, + "edit": { + "title": "Edit Tautan Terima", + "description": "Perbarui pengaturan untuk tautan terima ini", + "updating": "Memperbarui...", + "saveChanges": "Simpan Perubahan" + }, + "details": { + "title": "Detail Tautan", + "description": "Lihat dan edit informasi tautan terima Anda", + "pageLayout": "Tata Letak Halaman", + "linkSection": "Tautan Terima", + "noLinkCreated": "Tidak ada tautan dibuat", + "limits": "Batas", + "maxFiles": "File Maksimum", + "maxFileSize": "Ukuran Maksimum", + "allowedTypes": "Jenis yang Diizinkan", + "noLimit": "Tidak ada batas", + "security": "Keamanan", + "status": "Status", + "password": "Kata Sandi", + "files": "File Diterima", + "noFiles": "Belum ada file diterima", + "copyLink": "Salin Tautan", + "openLink": "Buka Tautan", + "editAlias": "Edit Alias", + "createAlias": "Buat Alias", + "editPassword": "Edit Perlindungan Kata Sandi", + "basicInfo": "Informasi Dasar", + "securityAndStatus": "Keamanan & Status", + "protection": "Perlindungan", + "protectedByPassword": "Dilindungi Kata Sandi", + "publicAccess": "Akses Publik", + "active": "Aktif", + "inactive": "Tidak Aktif", + "deactivate": "Nonaktifkan", + "activate": "Aktifkan", + "expiration": "Kedaluwarsa", + "dates": "Tanggal", + "createdAt": "Dibuat pada", + "updatedAt": "Diperbarui pada", + "allTypes": "Semua jenis", + "placeholderTypes": ".pdf,.jpg,.png (dipisahkan koma)", + "downloadSuccess": "Unduhan dimulai", + "downloadError": "Kesalahan mengunduh file", + "editSuccess": "File berhasil diperbarui", + "editError": "Kesalahan memperbarui file", + "previewNotAvailable": "Pratinjau tidak tersedia", + "notAvailable": "Tidak tersedia", + "invalidDate": "Tanggal tidak valid" + }, + "alias": { + "editTitle": "Edit Alias", + "createTitle": "Buat Alias", + "editDescription": "Perbarui alias untuk tautan terima ini", + "createDescription": "Buat alias khusus untuk tautan terima ini", + "aliasLabel": "Alias Tautan", + "aliasPlaceholder": "tautan-kustom-saya", + "preview": "Pratinjau:", + "currentLink": "Tautan Saat Ini:", + "copyCurrentLink": "Salin tautan saat ini", + "randomTooltip": "Buat alias acak", + "cancel": "Batal", + "creating": "Membuat...", + "updating": "Memperbarui...", + "create": "Buat Alias", + "update": "Perbarui Alias", + "validation": { + "required": "Alias wajib diisi", + "minLength": "Alias harus minimal 3 karakter", + "maxLength": "Alias harus maksimal 50 karakter", + "pattern": "Alias hanya boleh berisi huruf, angka, tanda hubung dan garis bawah" + }, + "help": "3-50 karakter. Spasi akan otomatis diubah menjadi tanda hubung." + }, + "password": { + "title": "Edit Perlindungan Kata Sandi", + "description": "Konfigurasi perlindungan kata sandi untuk tautan ini", + "hasPassword": "Dilindungi Kata Sandi", + "password": "Kata Sandi", + "cancel": "Batal", + "save": "Simpan", + "saving": "Menyimpan..." + }, + "receivedFiles": { + "title": "File Diterima", + "description": "Lihat dan kelola file yang dikirim ke tautan ini", + "noFiles": "Belum ada file diterima", + "noFilesDescription": "File yang dikirim melalui tautan ini akan muncul di sini", + "fileCount": "{count, plural, =0 {Tidak ada file} =1 {1 file} other {# file}}", + "invalidDate": "Tanggal tidak valid", + "totalSize": "Total ukuran: {size}", + "columns": { + "file": "File", + "size": "Ukuran", + "sender": "Dikirim oleh", + "date": "Tanggal", + "actions": "Tindakan" + }, + "actions": { + "preview": "Pratinjau", + "download": "Unduh", + "copyToMyFiles": "Salin ke file saya", + "copying": "Menyalin..." + }, + "uploadedBy": "Diunggah oleh {name}", + "anonymous": "Anonim", + "downloadSuccess": "Unduhan dimulai", + "downloadError": "Kesalahan mengunduh file", + "editSuccess": "File berhasil diperbarui", + "editError": "Kesalahan memperbarui file", + "previewNotAvailable": "Pratinjau tidak tersedia", + "copySuccess": "File berhasil disalin ke file Anda", + "copyError": "Kesalahan menyalin file ke file Anda", + "deleteSuccess": "File berhasil dihapus", + "deleteError": "Kesalahan menghapus file", + "bulkCopySuccess": "{count, plural, =1 {1 file berhasil disalin ke file Anda} other {# file berhasil disalin ke file Anda}}", + "bulkDeleteSuccess": "{count, plural, =1 {1 file berhasil dihapus} other {# file berhasil dihapus}}", + "bulkCopyProgress": "Menyalin {count, plural, =1 {1 file} other {# file}} ke file Anda...", + "bulkDeleteProgress": "Menghapus {count, plural, =1 {1 file} other {# file}}...", + "bulkDeleteConfirmTitle": "Hapus File Terpilih", + "bulkDeleteConfirmMessage": "Apakah Anda yakin ingin menghapus {count, plural, =1 {file ini} other {# file ini}}? Tindakan ini tidak dapat dibatalkan.", + "bulkDeleteConfirmButton": "Hapus {count, plural, =1 {File} other {File}}", + "bulkActions": { + "selected": "{count, plural, =1 {1 file dipilih} other {# file dipilih}}", + "actions": "Tindakan", + "download": "Unduh Terpilih", + "copyToMyFiles": "Salin Terpilih ke File Saya", + "delete": "Hapus Terpilih" + }, + "selectAll": "Pilih semua", + "selectFile": "Pilih file {fileName}", + "copyErrors": { + "timeout": "Operasi penyalinan habis waktu. Silakan coba lagi dengan file yang lebih kecil atau periksa koneksi Anda.", + "failed": "Operasi penyalinan gagal. Silakan coba lagi.", + "aborted": "Operasi penyalinan dibatalkan karena habis waktu." + } + } + }, + "form": { + "name": { + "label": "Nama Tautan", + "placeholder": "misal: Dokumen Proyek, Foto Keluarga..." + }, + "description": { + "label": "Deskripsi", + "placeholder": "Jelaskan jenis file yang Anda harapkan untuk diterima...", + "description": "Opsional. Membantu orang memahami apa yang harus dikirim." + }, + "status": { + "label": "Status Tautan", + "description": "Aktifkan atau nonaktifkan tautan terima ini" + }, + "expiration": { + "label": "Tanggal Kedaluwarsa", + "description": "Opsional. Tautan akan dinonaktifkan setelah tanggal ini.", + "configure": "Konfigurasi Kedaluwarsa" + }, + "fileLimits": { + "configure": "Konfigurasi Batas File" + }, + "maxFiles": { + "label": "File Maksimum", + "placeholder": "misal: 10", + "description": "Opsional. Batasi jumlah total file yang dapat dikirim.", + "noLimit": "Tidak ada batas file" + }, + "maxFileSize": { + "label": "Ukuran File Maksimum", + "placeholder": "misal: 100", + "description": "Opsional. Batasi ukuran individual setiap file.", + "noLimit": "Tidak ada batas ukuran" + }, + "allowedFileTypes": { + "label": "Jenis File yang Diizinkan", + "placeholder": "misal: pdf, jpg, png, docx", + "description": "Masukkan ekstensi tanpa titik, dipisahkan dengan spasi, koma, tanda hubung atau pipa", + "allTypes": "Semua jenis file" + }, + "pageLayout": { + "label": "Tata Letak Halaman", + "placeholder": "Pilih tata letak", + "description": "Bagaimana halaman unggah akan muncul untuk pengguna.", + "options": { + "default": "Tata Letak Default", + "wetransfer": "Gaya WeTransfer" + } + }, + "password": { + "label": "Kata Sandi Perlindungan", + "placeholder": "Opsional. Tambahkan kata sandi untuk melindungi tautan", + "description": "Opsional. Pengguna akan memerlukan kata sandi ini untuk mengakses tautan.", + "configurePassword": "Konfigurasi Kata Sandi", + "protectWithPassword": "Lindungi dengan Kata Sandi", + "passwordHelp": "Kata sandi harus minimal 4 karakter", + "passwordPlaceholder": "Masukkan kata sandi untuk melindungi tautan" + }, + "nameFieldRequired": { + "label": "Persyaratan Bidang Nama", + "description": "Konfigurasi apakah bidang nama pengunggah harus ditampilkan dan apakah wajib" + }, + "emailFieldRequired": { + "label": "Persyaratan Bidang Email", + "description": "Konfigurasi apakah bidang email pengunggah harus ditampilkan dan apakah wajib" + }, + "fieldRequirements": { + "title": "Persyaratan Bidang", + "description": "Konfigurasi bidang mana yang ditampilkan dalam formulir unggah" + }, + "submit": "Buat Tautan Terima" + }, + "messages": { + "created": "Tautan terima berhasil dibuat!", + "createSuccess": "Tautan terima berhasil dibuat!", + "updateSuccess": "Tautan terima berhasil diperbarui!", + "linkCopied": "Tautan disalin ke clipboard!", + "deleteSuccess": "Tautan terima berhasil dihapus!", + "aliasCreated": "Alias berhasil dibuat!", + "activateSuccess": "Tautan terima berhasil diaktifkan!", + "deactivateSuccess": "Tautan terima berhasil dinonaktifkan!", + "passwordProtectionEnabled": "Perlindungan kata sandi berhasil diaktifkan!", + "passwordProtectionDisabled": "Perlindungan kata sandi berhasil dihapus!" + }, + "defaultLinkName": "File Diterima", + "errors": { + "loadFailed": "Gagal memuat tautan terima", + "createFailed": "Gagal membuat tautan terima. Silakan coba lagi.", + "updateFailed": "Gagal memperbarui tautan terima. Silakan coba lagi.", + "deleteFailed": "Gagal menghapus tautan terima. Silakan coba lagi.", + "aliasCreateFailed": "Gagal membuat alias. Silakan coba lagi.", + "passwordUpdateFailed": "Gagal memperbarui perlindungan kata sandi" + }, + "delete": { + "title": "Hapus tautan terima", + "description": "Tindakan ini tidak dapat dibatalkan. Tautan akan dihapus secara permanen dan tidak akan lagi dapat menerima file.", + "confirmButton": "Hapus Tautan", + "cancelButton": "Batal", + "deleting": "Menghapus..." + }, + "upload": { + "metadata": { + "title": "Kirim File - Palmr", + "description": "Kirim file melalui tautan yang dibagikan", + "descriptionWithLimit": "Unggah file (maks {limit} file)" + }, + "layout": { + "defaultTitle": "Kirim File", + "importantInfo": "Informasi penting:", + "maxFiles": "Maksimum {count} file", + "maxFileSize": "Ukuran file maksimum: {size}MB", + "allowedTypes": "Jenis yang diizinkan: {types}", + "loading": "Memuat..." + }, + "password": { + "title": "Tautan Terlindungi", + "description": "Tautan ini dilindungi kata sandi. Masukkan kata sandi untuk melanjutkan.", + "label": "Kata Sandi", + "placeholder": "Masukkan kata sandi", + "cancel": "Batal", + "submit": "Lanjutkan", + "verifying": "Memverifikasi..." + }, + "errors": { + "loadFailed": "Gagal memuat informasi. Silakan coba lagi.", + "passwordIncorrect": "Kata sandi salah. Silakan coba lagi.", + "linkNotFound": "Tautan tidak ditemukan atau kedaluwarsa.", + "linkInactive": "Tautan ini tidak aktif.", + "linkExpired": "Tautan ini telah kedaluwarsa.", + "uploadFailed": "Kesalahan mengunggah file", + "retry": "Coba Lagi", + "fileTooLarge": "File terlalu besar. Ukuran maksimum: {maxSize}", + "fileTypeNotAllowed": "Jenis file tidak diizinkan. Jenis yang diterima: {allowedTypes}", + "maxFilesExceeded": "Maksimum {maxFiles} file diizinkan", + "selectAtLeastOneFile": "Pilih setidaknya satu file", + "provideNameOrEmail": "Mohon berikan nama atau email Anda", + "provideNameRequired": "Nama wajib diisi", + "provideEmailRequired": "Email wajib diisi" + }, + "fileDropzone": { + "dragActive": "Jatuhkan file di sini", + "dragInactive": "Seret file di sini atau klik untuk memilih", + "acceptedTypes": "Jenis yang diterima: {types}", + "maxFileSize": "Ukuran maksimum: {size}", + "maxFiles": "Maksimum {count} file", + "remainingFiles": "{remaining} dari {max} file tersisa" + }, + "fileList": { + "title": "File yang dipilih:", + "statusUploaded": "Diunggah", + "statusError": "Kesalahan", + "retry": "Coba Lagi" + }, + "form": { + "nameLabel": "Nama", + "nameLabelOptional": "Nama (opsional)", + "namePlaceholder": "Nama Anda", + "emailLabel": "Email", + "emailLabelOptional": "Email (opsional)", + "emailPlaceholder": "email@anda.com", + "descriptionLabel": "Deskripsi (opsional)", + "descriptionPlaceholder": "Tambahkan deskripsi ke file...", + "uploadButton": "Kirim {count} file", + "uploading": "Mengirim..." + }, + "success": { + "title": "File berhasil dikirim! 🎉", + "description": "Anda dapat menutup halaman ini.", + "countMessage": "{count} file berhasil dikirim!" + }, + "maxFilesReached": { + "title": "Batas file tercapai", + "description": "Tautan ini telah menerima jumlah maksimum {maxFiles} file yang diizinkan.", + "contactOwner": "Jika ada kesalahan atau Anda perlu mengirim lebih banyak file, hubungi pemilik tautan." + }, + "linkInactive": { + "title": "Tautan tidak aktif", + "description": "Tautan terima ini sementara tidak aktif.", + "contactOwner": "Hubungi pemilik tautan untuk informasi lebih lanjut." + }, + "linkNotFound": { + "title": "Tautan tidak ditemukan", + "description": "Tautan ini mungkin telah dihapus atau tidak pernah ada." + }, + "linkExpired": { + "title": "Tautan kedaluwarsa", + "description": "Tautan terima ini telah kedaluwarsa dan tidak lagi menerima file.", + "contactOwner": "Hubungi pemilik tautan jika Anda perlu mengirim file." + } + }, + "components": { + "fileRow": { + "addDescription": "Tambah deskripsi...", + "anonymous": "Anonim" + }, + "fileActions": { + "edit": "Edit", + "preview": "Pratinjau", + "download": "Unduh", + "delete": "Hapus", + "copyToMyFiles": "Salin ke file saya", + "copying": "Menyalin..." + }, + "editField": { + "saveChanges": "Simpan perubahan", + "cancelEdit": "Batal edit" + } + } + }, + "searchBar": { + "placeholder": "Cari file dan folder...", + "placeholderFiles": "Cari file...", + "placeholderFolders": "Cari folder...", + "results": "Menampilkan {filtered} dari {total} item", + "noResults": "Tidak ada hasil ditemukan untuk \"{query}\"" + }, + "settings": { + "groups": { + "defaultDescription": "Opsi konfigurasi", + "general": { + "title": "Umum", + "description": "Pengaturan aplikasi dasar" + }, + "email": { + "title": "Email", + "description": "Konfigurasi server email" + }, + "security": { + "title": "Keamanan", + "description": "Pengaturan keamanan dan autentikasi" + }, + "storage": { + "title": "Penyimpanan", + "description": "Konfigurasi penyimpanan file" + }, + "oidc": { + "title": "OpenID Connect (SSO)", + "description": "Konfigurasi autentikasi SSO melalui OpenID Connect" + } + }, + "fields": { + "noDescription": "Tidak ada deskripsi tersedia", + "appLogo": { + "title": "Logo Aplikasi", + "description": "Gambar logo aplikasi" + }, + "appName": { + "title": "Nama Aplikasi", + "description": "Nama aplikasi yang ditampilkan kepada pengguna" + }, + "appDescription": { + "title": "Deskripsi Aplikasi", + "description": "Deskripsi singkat tentang aplikasi" + }, + "showHomePage": { + "title": "Tampilkan Halaman Beranda", + "description": "Tampilkan Halaman Beranda setelah instalasi" + }, + "hideVersion": { + "title": "Sembunyikan Versi", + "description": "Sembunyikan versi Palmr dari footer di semua halaman" + }, + "smtpEnabled": { + "title": "SMTP Diaktifkan", + "description": "Aktifkan atau nonaktifkan fungsionalitas email SMTP" + }, + "smtpHost": { + "title": "Server SMTP", + "description": "Alamat server SMTP" + }, + "smtpPort": { + "title": "Port SMTP", + "description": "Port server SMTP" + }, + "smtpUser": { + "title": "Username SMTP", + "description": "Username untuk autentikasi SMTP" + }, + "smtpPass": { + "title": "Password SMTP", + "description": "Password untuk autentikasi SMTP" + }, + "smtpFromName": { + "title": "Nama Pengirim", + "description": "Nama tampilan untuk email yang dikirim" + }, + "smtpFromEmail": { + "title": "Email Pengirim", + "description": "Alamat email pengirim" + }, + "smtpSecure": { + "title": "Keamanan Koneksi", + "description": "Metode keamanan koneksi SMTP - Auto (disarankan), SSL, STARTTLS, atau None (tidak aman)", + "options": { + "auto": "Auto (Disarankan)", + "ssl": "SSL (Port 465)", + "tls": "STARTTLS (Port 587)", + "none": "None (Tidak Aman)" + } + }, + "smtpNoAuth": { + "title": "Tanpa Autentikasi", + "description": "Aktifkan ini untuk server internal yang tidak memerlukan username/password (menyembunyikan bidang auth)" + }, + "smtpTrustSelfSigned": { + "title": "Percayai Sertifikat Self-Signed", + "description": "Aktifkan ini untuk mempercayai sertifikat SSL/TLS self-signed (berguna untuk lingkungan pengembangan)" + }, + "testSmtp": { + "title": "Uji Koneksi SMTP", + "description": "Uji apakah konfigurasi SMTP valid" + }, + "maxLoginAttempts": { + "title": "Percobaan Login Maksimum", + "description": "Jumlah maksimum percobaan login sebelum pemblokiran" + }, + "loginBlockDuration": { + "title": "Durasi Pemblokiran", + "description": "Durasi (dalam detik) untuk memblokir setelah melebihi percobaan" + }, + "passwordMinLength": { + "title": "Panjang Kata Sandi Minimum", + "description": "Jumlah minimum karakter untuk kata sandi" + }, + "passwordResetTokenExpiration": { + "title": "Kedaluwarsa Token Reset", + "description": "Waktu validitas (dalam detik) untuk token reset kata sandi" + }, + "maxFileSize": { + "title": "Ukuran File Maksimum", + "description": "Ukuran file maksimum yang diizinkan untuk unggah" + }, + "maxTotalStoragePerUser": { + "title": "Penyimpanan Maksimum Per Pengguna", + "description": "Batas penyimpanan total per pengguna" + }, + "firstUserAccess": { + "title": "Akses Pengguna Pertama", + "description": "Pengaturan untuk akses pertama pengguna baru" + }, + "serverUrl": { + "title": "URL Server", + "description": "URL dasar server Palmr (misal: https://palmr.example.com)" + }, + "passwordAuthEnabled": { + "title": "Autentikasi Kata Sandi", + "description": "Aktifkan atau nonaktifkan autentikasi berbasis kata sandi" + } + }, + "buttons": { + "save": "Simpan {group}", + "testSmtp": "Uji Koneksi", + "testing": "Menguji..." + }, + "errors": { + "loadFailed": "Gagal memuat pengaturan", + "updateFailed": "Gagal memperbarui pengaturan", + "passwordAuthRequiresProvider": "Tidak dapat menonaktifkan autentikasi kata sandi tanpa memiliki setidaknya satu penyedia autentikasi aktif" + }, + "messages": { + "noChanges": "Tidak ada perubahan untuk disimpan", + "updateSuccess": "Pengaturan {group} berhasil diperbarui", + "smtpTestSuccess": "Koneksi SMTP berhasil! Konfigurasi email Anda berfungsi dengan benar.", + "smtpTestFailed": "Koneksi SMTP gagal: {error}", + "smtpTestGenericError": "Gagal menguji koneksi SMTP. Silakan periksa pengaturan Anda dan coba lagi.", + "smtpNotEnabled": "SMTP tidak diaktifkan. Silakan aktifkan SMTP terlebih dahulu.", + "smtpMissingHostPort": "Silakan isi Host dan Port SMTP sebelum menguji.", + "smtpMissingAuth": "Silakan isi Username dan Password SMTP, atau aktifkan opsi 'Tanpa Autentikasi'." + }, + "title": "Pengaturan", + "breadcrumb": "Pengaturan", + "pageTitle": "Pengaturan", + "tooltips": { + "testSmtp": "Menguji koneksi SMTP dengan nilai yang saat ini dimasukkan dalam formulir. Untuk membuat perubahan permanen, ingat untuk menyimpan pengaturan Anda setelah menguji.", + "defaultPlaceholder": "Masukkan dan tekan Enter" + }, + "redirectUri": { + "placeholder": "https://mysite.com", + "previewLabel": "URL lengkap yang akan disimpan:" + } + }, + "share": { + "errors": { + "invalidPassword": "Kata sandi tidak valid. Silakan coba lagi.", + "loadFailed": "Gagal memuat berbagi", + "downloadFailed": "Gagal mengunduh file" + }, + "messages": { + "downloadStarted": "Unduhan dimulai" + }, + "password": { + "title": "Berbagi Dilindungi Kata Sandi", + "protected": "Berbagi ini dilindungi kata sandi", + "incorrect": "Kata sandi salah. Silakan coba lagi.", + "label": "Kata Sandi", + "placeholder": "Masukkan kata sandi berbagi", + "submit": "Kirim" + }, + "details": { + "untitled": "Berbagi Tanpa Judul", + "created": "Dibuat: {date}", + "expires": "Kedaluwarsa: {date}" + }, + "downloadAll": "Unduh Semua", + "notFound": { + "title": "Berbagi Tidak Ditemukan", + "description": "Berbagi ini mungkin telah dihapus atau kedaluwarsa." + }, + "pageTitle": "Berbagi", + "metadata": { + "defaultDescription": "Bagikan file secara aman", + "filesShared": "{count, plural, =1 {1 file dibagikan} other {# file dibagikan}}" + } + }, + "shareActions": { + "fileTitle": "Bagikan File", + "folderTitle": "Bagikan Folder", + "linkTitle": "Buat Tautan", + "linkDescriptionFile": "Buat tautan khusus untuk membagikan file", + "linkDescriptionFolder": "Buat tautan khusus untuk membagikan folder", + "aliasLabel": "Alias Tautan", + "aliasPlaceholder": "Masukkan alias khusus", + "linkReady": "Tautan berbagi Anda siap:", + "generateLink": "Buat Tautan", + "copyLink": "Salin Tautan", + "deleteTitle": "Hapus Berbagi", + "deleteConfirmation": "Apakah Anda yakin ingin menghapus berbagi ini? Tindakan ini tidak dapat dibatalkan.", + "addDescriptionPlaceholder": "Tambah deskripsi...", + "editTitle": "Edit Berbagi", + "newPasswordLabel": "Kata Sandi Baru (biarkan kosong untuk mempertahankan yang sekarang)", + "newPasswordPlaceholder": "Masukkan kata sandi baru", + "manageFilesTitle": "Kelola File", + "manageFilesDescription": "Pilih file dan folder untuk disertakan dalam berbagi ini", + "manageRecipientsTitle": "Kelola Penerima", + "itemsSelected": "{count} item dipilih", + "editSuccess": "Berbagi berhasil diperbarui", + "editError": "Gagal memperbarui berbagi", + "bulkDeleteConfirmation": "Apakah Anda yakin ingin menghapus {count, plural, =1 {1 berbagi} other {# berbagi}}? Tindakan ini tidak dapat dibatalkan.", + "bulkDeleteTitle": "Hapus Berbagi Terpilih" + }, + "shareDetails": { + "title": "Detail Berbagi", + "subtitle": "Lihat dan kelola detail untuk berbagi ini", + "basicInfo": "Informasi Dasar", + "name": "Nama", + "description": "Deskripsi", + "shareLink": "Tautan Berbagi", + "dates": "Tanggal", + "security": "Keamanan", + "files": "File", + "recipients": "Penerima", + "views": "Tampilan", + "created": "Dibuat", + "expires": "Kedaluwarsa", + "never": "Tidak pernah", + "untitled": "Berbagi Tanpa Judul", + "noDescription": "Tidak ada deskripsi", + "notAvailable": "T/A", + "invalidDate": "Tanggal tidak valid", + "passwordProtected": "Dilindungi Kata Sandi", + "publicAccess": "Akses Publik", + "maxViews": "Tampilan Maks:", + "noLink": "Tidak ada tautan dibuat", + "generateLink": "Buat Tautan", + "editLink": "Edit Tautan", + "copyLink": "Salin Tautan", + "openLink": "Buka Tautan", + "editSecurity": "Edit Keamanan", + "editExpiration": "Edit Kedaluwarsa", + "qrCode": "Kode QR", + "downloadQrCode": "Unduh Kode QR", + "clickToEnlargeQrCode": "Klik untuk memperbesar Kode QR", + "loadError": "Gagal memuat detail berbagi" + }, + "shareExpiration": { + "title": "Pengaturan Kedaluwarsa Berbagi", + "subtitle": "Konfigurasi kapan berbagi ini akan kedaluwarsa", + "currentStatus": "Status Saat Ini", + "expires": "Kedaluwarsa:", + "neverExpires": "Tidak pernah kedaluwarsa", + "enableExpiration": "Aktifkan Kedaluwarsa", + "expirationDate": "Tanggal Kedaluwarsa", + "validation": { + "dateRequired": "Mohon pilih tanggal kedaluwarsa", + "dateMustBeFuture": "Tanggal kedaluwarsa harus di masa depan" + }, + "success": { + "expirationSet": "Tanggal kedaluwarsa berhasil diatur", + "expirationUpdated": "Tanggal kedaluwarsa berhasil diperbarui", + "expirationRemoved": "Kedaluwarsa berhasil dihapus - berbagi sekarang permanen" + }, + "error": { + "updateFailed": "Gagal memperbarui pengaturan kedaluwarsa" + }, + "info": { + "title": "Tentang kedaluwarsa:", + "willBeInaccessible": "Berbagi akan menjadi tidak dapat diakses setelah tanggal ini", + "canBeChanged": "Anda dapat mengubah atau menghapus tanggal kedaluwarsa kapan saja", + "noExpiration": "Berbagi ini tidak akan pernah kedaluwarsa dan akan tetap dapat diakses tanpa batas." + } + }, + "shareManager": { + "deleteSuccess": "Berbagi berhasil dihapus", + "deleteError": "Gagal menghapus berbagi", + "updateSuccess": "Berbagi berhasil diperbarui", + "updateError": "Gagal memperbarui berbagi", + "securityUpdateSuccess": "Pengaturan keamanan berhasil diperbarui", + "securityUpdateError": "Gagal memperbarui pengaturan keamanan", + "expirationUpdateSuccess": "Pengaturan kedaluwarsa berhasil diperbarui", + "expirationUpdateError": "Gagal memperbarui pengaturan kedaluwarsa", + "filesUpdateSuccess": "File berhasil diperbarui", + "filesUpdateError": "Gagal memperbarui file", + "recipientsUpdateSuccess": "Penerima berhasil diperbarui", + "recipientsUpdateError": "Gagal memperbarui penerima", + "linkGenerateSuccess": "Tautan berbagi berhasil dibuat", + "linkGenerateError": "Gagal membuat tautan berbagi", + "notifyLoading": "Mengirim notifikasi...", + "notifySuccess": "Penerima berhasil diberitahu", + "notifyError": "Gagal memberitahu penerima", + "bulkDeleteError": "Gagal menghapus berbagi", + "bulkDeleteLoading": "Menghapus {count, plural, =1 {1 berbagi} other {# berbagi}}...", + "bulkDeleteSuccess": "{count, plural, =1 {1 berbagi berhasil dihapus} other {# berbagi berhasil dihapus}}", + "downloadSuccess": "Unduhan berhasil dimulai", + "downloadError": "Gagal mengunduh file berbagi", + "noFilesToDownload": "Tidak ada file yang tersedia untuk diunduh", + "creatingZip": "Membuat file ZIP...", + "zipDownloadSuccess": "File ZIP berhasil diunduh", + "zipDownloadError": "Gagal membuat file ZIP", + "errors": { + "multipleDownloadNotSupported": "Unduhan berbagi ganda belum didukung - silakan unduh berbagi satu per satu" + }, + "singleShareZipName": "{shareName}_files.zip", + "multipleSharesZipName": "{count}_shares_files.zip", + "defaultShareName": "Berbagi" + }, + "shareMultipleFiles": { + "title": "Bagikan Beberapa File", + "shareNameLabel": "Nama Berbagi", + "shareNamePlaceholder": "Masukkan nama berbagi", + "descriptionLabel": "Deskripsi", + "descriptionPlaceholder": "Masukkan deskripsi (opsional)", + "filesToShare": "File untuk Dibagikan", + "files": "file", + "totalSize": "Total ukuran", + "creating": "Membuat berbagi...", + "create": "Buat Berbagi", + "itemsToShare": "Item untuk dibagikan ({count} {count, plural, =1 {item} other {item}})" + }, + "shareSecurity": { + "title": "Pengaturan Keamanan Berbagi", + "subtitle": "Konfigurasi perlindungan kata sandi dan opsi keamanan untuk berbagi ini", + "currentStatus": "Status Saat Ini", + "passwordProtection": "Perlindungan Kata Sandi", + "password": "Kata Sandi", + "newPassword": "Kata Sandi Baru", + "passwordPlaceholder": "Masukkan kata sandi yang aman", + "existingPasswordMessage": "Berbagi ini sudah memiliki kata sandi. Jika Anda ingin memperbaruinya, masukkan kata sandi baru di kolom di bawah ini dan simpan.", + "passwordRequirements": { + "title": "Persyaratan kata sandi:", + "minLength": "Minimal 2 karakter" + }, + "info": { + "title": "Cara kerjanya:", + "withPassword": "Pengguna perlu memasukkan kata sandi untuk mengakses berbagi ini.", + "withoutPassword": "Siapa pun dengan tautan dapat mengakses berbagi ini tanpa kata sandi." + }, + "validation": { + "passwordRequired": "Kata sandi wajib diisi", + "passwordTooShort": "Kata sandi harus minimal 2 karakter" + }, + "success": { + "passwordSet": "Perlindungan kata sandi berhasil diaktifkan", + "passwordUpdated": "Kata sandi berhasil diperbarui", + "passwordRemoved": "Perlindungan kata sandi berhasil dihapus" + }, + "error": { + "updateFailed": "Gagal memperbarui pengaturan keamanan" + } + }, + "shares": { + "errors": { + "loadFailed": "Gagal memuat berbagi", + "notifyFailed": "Gagal memberitahu penerima", + "smtpConfigFailed": "Gagal memuat konfigurasi SMTP" + }, + "messages": { + "linkCopied": "Tautan disalin ke clipboard", + "recipientsNotified": "Penerima berhasil diberitahu" + }, + "empty": { + "message": "Belum ada berbagi yang dibuat", + "createButton": "Buat Berbagi" + }, + "header": { + "title": "Berbagi Saya", + "myShares": "Berbagi Saya" + }, + "search": { + "title": "Semua Berbagi", + "createButton": "Buat Berbagi", + "placeholder": "Cari berbagi...", + "results": "Ditemukan {filtered} dari {total} berbagi" + }, + "pageTitle": "Berbagi" + }, + "sharesTable": { + "ariaLabel": "Tabel berbagi", + "never": "Tidak pernah", + "columns": { + "name": "NAMA", + "description": "DESKRIPSI", + "createdAt": "DIBUAT PADA", + "expiresAt": "KEDALUWARSA PADA", + "status": "STATUS", + "security": "KEAMANAN", + "files": "FILE", + "recipients": "PENERIMA", + "actions": "TINDAKAN" + }, + "status": { + "neverExpires": "Tidak pernah kedaluwarsa", + "active": "Aktif", + "expired": "Kedaluwarsa" + }, + "security": { + "protected": "Terlindungi", + "public": "Publik" + }, + "filesCount": "file", + "folderCount": "folder", + "recipientsCount": "penerima", + "actions": { + "menu": "Menu aksi berbagi", + "edit": "Edit", + "manageFiles": "Kelola File", + "manageRecipients": "Kelola Penerima", + "viewDetails": "Lihat Detail", + "generateLink": "Buat Tautan", + "editLink": "Edit Tautan", + "copyLink": "Salin Tautan", + "viewQrCode": "Lihat Kode QR", + "notifyRecipients": "Beritahu Penerima", + "downloadShareFiles": "Unduh Semua File", + "delete": "Hapus" + }, + "bulkActions": { + "actions": "Tindakan", + "download": "Unduh Terpilih", + "delete": "Hapus", + "selected": "{count, plural, =1 {1 berbagi dipilih} other {# berbagi dipilih}}" + }, + "selectAll": "Pilih semua", + "selectShare": "Pilih berbagi {shareName}" + }, + "storageUsage": { + "title": "Penggunaan Penyimpanan", + "ariaLabel": "Bar progres penggunaan penyimpanan", + "used": "digunakan", + "available": "tersedia", + "total": "Total", + "loading": "Memuat...", + "retry": "Coba Lagi", + "errors": { + "title": "Informasi penyimpanan tidak tersedia", + "detectionFailed": "Tidak dapat mendeteksi ruang disk. Ini mungkin disebabkan oleh masalah konfigurasi sistem atau izin yang tidak cukup.", + "serverError": "Terjadi kesalahan server saat mengambil informasi penyimpanan. Silakan coba lagi nanti.", + "unknown": "Terjadi kesalahan tak terduga saat memuat informasi penyimpanan." + } + }, + "theme": { + "toggle": "Alih tema", + "light": "Terang", + "dark": "Gelap", + "system": "Sistem" + }, + "twoFactor": { + "title": "Otentikasi Dua Faktor", + "description": "Tambahkan lapisan keamanan ekstra ke akun Anda", + "enabled": "Akun Anda dilindungi dengan otentikasi dua faktor", + "disabled": "Otentikasi dua faktor tidak diaktifkan", + "status": { + "label": "Status:", + "enabled": "Diaktifkan", + "disabled": "Dinonaktifkan" + }, + "buttons": { + "enable2FA": "Aktifkan 2FA", + "disable2FA": "Nonaktifkan 2FA" + }, + "setup": { + "title": "Aktifkan Otentikasi Dua Faktor", + "description": "Pindai kode QR dengan aplikasi autentikator Anda, lalu masukkan kode verifikasi.", + "qrCode": "Kode QR", + "manualEntryKey": "Kunci Masuk Manual", + "verificationCode": "Kode Verifikasi", + "verificationCodePlaceholder": "Masukkan kode 6 digit", + "verificationCodeDescription": "Masukkan kode 6 digit dari aplikasi autentikator Anda", + "verifyAndEnable": "Verifikasi & Aktifkan", + "cancel": "Batal" + }, + "disable": { + "title": "Nonaktifkan Otentikasi Dua Faktor", + "description": "Masukkan kata sandi Anda untuk mengkonfirmasi menonaktifkan otentikasi dua faktor.", + "password": "Kata Sandi", + "passwordPlaceholder": "Masukkan kata sandi Anda", + "confirm": "Konfirmasi Nonaktifkan", + "cancel": "Batal" + }, + "backupCodes": { + "title": "Kode Cadangan", + "description": "Simpan kode cadangan ini di tempat yang aman. Anda dapat menggunakannya untuk mengakses akun Anda jika kehilangan perangkat autentikator.", + "warning": "Penting:", + "warningText": "Setiap kode cadangan hanya dapat digunakan sekali. Jaga keamanannya dan jangan bagikan kepada siapa pun.", + "generateNew": "Buat Kode Cadangan Baru", + "download": "Unduh Kode Cadangan", + "copyToClipboard": "Salin ke Clipboard", + "savedMessage": "Saya Sudah Menyimpan Kode Cadangan", + "available": "{count} kode cadangan tersedia", + "instructions": [ + "• Simpan kode-kode ini di lokasi yang aman", + "• Setiap kode cadangan hanya dapat digunakan sekali", + "• Anda dapat membuat kode baru kapan saja" + ] + }, + "verification": { + "title": "Otentikasi Dua Faktor", + "description": "Masukkan kode 6 digit dari aplikasi autentikator Anda", + "backupDescription": "Masukkan salah satu kode cadangan Anda untuk melanjutkan", + "verificationCode": "Kode Verifikasi", + "backupCode": "Kode Cadangan", + "verificationCodePlaceholder": "000000", + "backupCodePlaceholder": "XXXX-XXXX", + "verify": "Verifikasi", + "verifying": "Memverifikasi...", + "useBackupCode": "Gunakan kode cadangan", + "useAuthenticatorCode": "Gunakan kode autentikator", + "rememberDevice": "Ingat perangkat ini selama 30 hari", + "rememberDeviceDescription": "Anda tidak perlu memasukkan kode 2FA di perangkat ini selama 30 hari" + }, + "trustedDevices": { + "title": "Perangkat Terpercaya - 2FA", + "description": "Perangkat yang tidak memerlukan verifikasi 2FA", + "noDevices": "Tidak ada perangkat terpercaya", + "deviceName": "Perangkat", + "addedOn": "Ditambahkan pada", + "expiresOn": "Kedaluwarsa pada", + "remove": "Hapus", + "removeAll": "Hapus Semua", + "confirmRemove": "Apakah Anda yakin ingin menghapus perangkat terpercaya ini?", + "confirmRemoveAll": "Apakah Anda yakin ingin menghapus semua perangkat terpercaya?", + "deviceRemoved": "Perangkat terpercaya berhasil dihapus", + "allDevicesRemoved": "Semua perangkat terpercaya berhasil dihapus", + "loadFailed": "Gagal memuat perangkat terpercaya", + "removeFailed": "Gagal menghapus perangkat terpercaya", + "removeAllFailed": "Gagal menghapus semua perangkat terpercaya", + "loading": "Memuat perangkat terpercaya...", + "noDevicesDescription": "Perangkat akan muncul di sini ketika Anda memilih untuk mempercayainya selama verifikasi 2FA", + "tableHeaders": { + "device": "Perangkat", + "added": "Ditambahkan", + "expires": "Kedaluwarsa", + "lastUsed": "Terakhir Digunakan", + "ipAddress": "Alamat IP", + "actions": "Tindakan" + }, + "status": { + "never": "Tidak pernah", + "expired": "Kedaluwarsa" + }, + "modals": { + "removeDevice": { + "title": "Hapus Perangkat Terpercaya", + "added": "Ditambahkan:", + "ip": "IP:" + }, + "removeAllDevices": { + "title": "Hapus Semua Perangkat Terpercaya", + "description": "Ini akan menghapus {count} perangkat terpercaya. Anda perlu memverifikasi 2FA di semua perangkat lagi." + }, + "buttons": { + "cancel": "Batal", + "removing": "Menghapus...", + "removeDevice": "Hapus Perangkat", + "removeAllDevices": "Hapus Semua Perangkat" + } + } + }, + "messages": { + "enabledSuccess": "Otentikasi dua faktor berhasil diaktifkan!", + "disabledSuccess": "Otentikasi dua faktor berhasil dinonaktifkan", + "backupCodesGenerated": "Kode cadangan baru berhasil dibuat", + "backupCodesCopied": "Kode cadangan disalin ke clipboard", + "setupFailed": "Gagal membuat pengaturan 2FA", + "verificationFailed": "Kode verifikasi tidak valid", + "disableFailed": "Gagal menonaktifkan 2FA. Silakan periksa kata sandi Anda.", + "backupCodesFailed": "Gagal membuat kode cadangan", + "backupCodesCopyFailed": "Gagal menyalin kode cadangan", + "statusLoadFailed": "Gagal memuat status 2FA", + "enterVerificationCode": "Mohon masukkan kode verifikasi", + "enterPassword": "Mohon masukkan kata sandi Anda", + "deviceTrusted": "Perangkat ini telah ditandai sebagai terpercaya selama 30 hari" + }, + "errors": { + "invalidVerificationCode": "Kode verifikasi tidak valid", + "invalidTwoFactorCode": "Kode otentikasi dua faktor tidak valid", + "twoFactorRequired": "Otentikasi dua faktor diperlukan", + "twoFactorAlreadyEnabled": "Otentikasi dua faktor sudah diaktifkan", + "twoFactorNotEnabled": "Otentikasi dua faktor belum diaktifkan", + "passwordVerificationRequired": "Verifikasi kata sandi diperlukan", + "invalidPassword": "Kata sandi tidak valid", + "userNotFound": "Pengguna tidak ditemukan" + }, + "deviceNames": { + "unknownDevice": "Perangkat Tidak Dikenal", + "browsers": { + "chrome": "Chrome", + "firefox": "Firefox", + "safari": "Safari", + "edge": "Edge" + }, + "platforms": { + "windows": " di Windows", + "macos": " di macOS", + "linux": " di Linux", + "iphone": " di iPhone", + "android": " di Android" + } + } + }, + "uploadFile": { + "title": "Unggah File", + "multipleTitle": "Unggah File", + "selectFile": "Klik untuk memilih file", + "selectMultipleFiles": "Klik untuk memilih satu atau beberapa file", + "dragAndDrop": "atau seret dan lepas file di sini", + "filesQueued": "{count, plural, one {# file dalam antrean untuk unggah} other {# file dalam antrean untuk unggah}}", + "preview": "Pratinjau", + "uploadProgress": "Progres unggah", + "upload": "Unggah", + "startUploads": "Mulai Unggah", + "retry": "Coba Lagi", + "finish": "Selesai", + "success": "File berhasil diunggah", + "allSuccess": "{count, plural, =1 {File berhasil diunggah} other {# file berhasil diunggah}}", + "partialSuccess": "{success} file berhasil diunggah, {error} gagal", + "error": "Gagal mengunggah file", + "fileSizeExceeded": "Ukuran file melebihi batas {maxsizemb}MB.", + "insufficientStorage": "Ruang penyimpanan tidak mencukupi. Anda memiliki {availablespace}MB yang tersedia.", + "unauthorized": "Tidak berwenang: token yang valid diperlukan untuk mengakses sumber daya ini.", + "globalDrop": { + "title": "Lepaskan file untuk mengunggah", + "description": "Lepaskan untuk mengunggah file Anda" + }, + "confirmCancel": { + "title": "Batalkan Unggah", + "messageSingle": "Ada satu unggah yang sedang berlangsung.", + "messageMultiple": "Ada {count} unggah yang sedang berlangsung.", + "warning": "Jika Anda menutup sekarang, unggah akan dibatalkan dan semua progres akan hilang.", + "continue": "Lanjutkan Unggah", + "cancel": "Batalkan Unggah" + }, + "pasteSuccess": "{count, plural, =1 {Gambar berhasil ditempel dan diunggah} other {# gambar berhasil ditempel dan diunggah}}" + }, + "users": { + "modes": { + "create": "buat", + "edit": "edit" + }, + "errors": { + "loadFailed": "Gagal memuat pengguna", + "submitFailed": "Gagal {mode} pengguna", + "deleteFailed": "Gagal menghapus pengguna", + "statusUpdateFailed": "Gagal memperbarui status pengguna" + }, + "messages": { + "createSuccess": "Pengguna berhasil dibuat", + "updateSuccess": "Pengguna berhasil diperbarui", + "deleteSuccess": "Pengguna berhasil dihapus", + "activateSuccess": "Pengguna berhasil diaktifkan", + "deactivateSuccess": "Pengguna berhasil dinonaktifkan" + }, + "actions": { + "edit": "Edit", + "activate": "Aktifkan", + "deactivate": "Nonaktifkan", + "delete": "Hapus" + }, + "delete": { + "title": "Konfirmasi Hapus Pengguna", + "confirmation": "Apakah Anda yakin ingin menghapus pengguna {firstName} {lastName}? Tindakan ini tidak dapat dibatalkan.", + "confirm": "Hapus Pengguna" + }, + "form": { + "titleCreate": "Tambah Pengguna Baru", + "titleEdit": "Edit Pengguna", + "firstName": "Nama Depan", + "lastName": "Nama Belakang", + "username": "Nama Pengguna", + "email": "Email", + "password": "Kata Sandi", + "newPassword": "Kata Sandi Baru (opsional)", + "passwordPlaceholder": "Biarkan kosong untuk mempertahankan kata sandi saat ini", + "role": "Peran", + "roleUser": "Pengguna", + "roleAdmin": "Admin", + "create": "Buat", + "save": "Simpan" + }, + "status": { + "title": "Konfirmasi Perubahan Status", + "confirmation": "Apakah Anda yakin ingin {action} pengguna {firstName} {lastName}?", + "activate": "Aktifkan", + "deactivate": "Nonaktifkan", + "user": "Pengguna" + }, + "header": { + "title": "Manajemen Pengguna", + "addUser": "Tambah Pengguna", + "management": "Manajemen Pengguna" + }, + "table": { + "user": "PENGGUNA", + "email": "EMAIL", + "status": "STATUS", + "role": "PERAN", + "actions": "TINDAKAN", + "active": "Aktif", + "inactive": "Tidak Aktif", + "admin": "Admin", + "userr": "Pengguna" + }, + "invite": { + "button": "Buat Tautan Undangan", + "title": "Buat Tautan Undangan Pengguna", + "description": "Buat tautan sekali pakai yang memungkinkan seseorang membuat akun mereka sendiri. Tautan kedaluwarsa dalam 15 menit.", + "generating": "Membuat...", + "generate": "Buat Tautan", + "generated": "Tautan undangan berhasil dibuat!", + "linkReady": "Tautan undangan siap", + "linkReadyDescription": "Bagikan tautan ini dengan orang yang ingin Anda undang. Mereka akan dapat membuat akun mereka sendiri sebagai pengguna biasa.", + "copyLink": "Salin Tautan", + "linkCopied": "Tautan undangan disalin ke clipboard!", + "expiresIn": "Kedaluwarsa dalam 15 menit", + "close": "Tutup", + "errors": { + "generateFailed": "Gagal membuat tautan undangan" + } + } + }, + "embedCode": { + "title": "Sematkan Media", + "description": "Gunakan kode-kode ini untuk menyematkan media ini di forum, situs web, atau platform lainnya", + "tabs": { + "directLink": "Tautan Langsung", + "html": "HTML", + "bbcode": "BBCode" + }, + "directLinkDescription": "URL langsung ke file media", + "htmlDescription": "Gunakan kode ini untuk menyematkan media di halaman HTML", + "bbcodeDescription": "Gunakan kode ini untuk menyematkan media di forum yang mendukung BBCode" + }, + "validation": { + "firstNameRequired": "Nama depan wajib diisi", + "lastNameRequired": "Nama belakang wajib diisi", + "usernameLength": "Nama pengguna harus minimal 3 karakter", + "usernameSpaces": "Nama pengguna tidak boleh mengandung spasi", + "invalidEmail": "Mohon masukkan alamat email yang valid", + "passwordLength": "Kata sandi harus minimal 8 karakter", + "passwordsMatch": "Kata sandi harus sama", + "emailRequired": "Email wajib diisi", + "emailOrUsernameRequired": "Email atau nama pengguna wajib diisi", + "passwordRequired": "Kata sandi wajib diisi", + "passwordMinLength": "Kata sandi harus minimal 6 karakter", + "nameRequired": "Nama wajib diisi", + "required": "Bidang ini wajib diisi" + }, + "registerWithInvite": { + "title": "Buat Akun Anda", + "description": "Isi informasi di bawah ini untuk membuat akun Anda", + "labels": { + "firstName": "Nama Depan", + "firstNamePlaceholder": "Masukkan nama depan Anda", + "lastName": "Nama Belakang", + "lastNamePlaceholder": "Masukkan nama belakang Anda", + "username": "Nama Pengguna", + "usernamePlaceholder": "Pilih nama pengguna", + "email": "Email", + "emailPlaceholder": "Masukkan email Anda", + "password": "Kata Sandi", + "passwordPlaceholder": "Pilih kata sandi", + "confirmPassword": "Konfirmasi Kata Sandi", + "confirmPasswordPlaceholder": "Konfirmasi kata sandi Anda" + }, + "buttons": { + "creating": "Membuat akun...", + "createAccount": "Buat Akun" + }, + "validation": { + "firstNameRequired": "Nama depan wajib diisi", + "lastNameRequired": "Nama belakang wajib diisi", + "usernameMinLength": "Nama pengguna harus minimal 3 karakter", + "invalidEmail": "Email tidak valid", + "passwordMinLength": "Kata sandi harus minimal 8 karakter", + "passwordsMatch": "Kata sandi harus sama" + }, + "messages": { + "success": "Akun berhasil dibuat! Mengalihkan ke login...", + "redirecting": "Mengalihkan ke login..." + }, + "errors": { + "invalidToken": "Kesalahan dengan tautan undangan", + "tokenUsed": "Tautan undangan ini sudah digunakan", + "tokenExpired": "Tautan undangan ini telah kedaluwarsa", + "usernameExists": "Nama pengguna sudah ada", + "emailExists": "Email sudah ada", + "createFailed": "Gagal membuat akun. Silakan coba lagi." + }, + "pageTitle": "Buat Akun" + } +} \ No newline at end of file diff --git a/apps/web/messages/it-IT.json b/apps/web/messages/it-IT.json index 7056fef4..c42a1e9e 100644 --- a/apps/web/messages/it-IT.json +++ b/apps/web/messages/it-IT.json @@ -6,7 +6,9 @@ "token_expired": "Token scaduto. Riprova.", "config_error": "Errore di configurazione. Contatta il supporto.", "auth_failed": "Autenticazione fallita. Riprova." - } + }, + "authenticationFailed": "Autenticazione fallita", + "successfullyAuthenticated": "Autenticazione completata con successo!" }, "authProviders": { "title": "Provider di Autenticazione", @@ -174,7 +176,14 @@ "tabs": { "shareDetails": "Dettagli condivisione", "selectFiles": "Seleziona file" - } + }, + "errors": { + "nameRequired": "Il nome della condivisione è obbligatorio", + "selectItems": "Seleziona almeno un file o una cartella" + }, + "itemsSelected": "{count, plural, =0 {Nessun elemento selezionato} =1 {1 elemento selezionato} other {# elementi selezionati}}", + "passwordPlaceholder": "Inserisci password", + "selectItemsPrompt": "Seleziona file e cartelle da condividere" }, "customization": { "breadcrumb": "Personalizzazione", @@ -340,7 +349,8 @@ "addToShare": "Aggiungi alla condivisione", "removeFromShare": "Rimuovi dalla condivisione", "saveChanges": "Salva Modifiche", - "editFolder": "Modifica cartella" + "editFolder": "Modifica cartella", + "itemsSelected": "{count, plural, =0 {Nessun elemento selezionato} =1 {1 elemento selezionato} other {# elementi selezionati}}" }, "files": { "title": "Tutti i File", @@ -376,7 +386,12 @@ "description": "Carica il tuo primo file o crea una cartella per iniziare" }, "files": "file", - "folders": "cartelle" + "folders": "cartelle", + "errors": { + "moveItemsFailed": "Impossibile spostare gli elementi. Riprova.", + "cannotMoveHere": "Impossibile spostare gli elementi in questa posizione" + }, + "openFolder": "Apri cartella" }, "filesTable": { "ariaLabel": "Tabella dei file", @@ -539,7 +554,10 @@ "movingTo": "Spostamento in:", "title": "Sposta {count, plural, =1 {elemento} other {elementi}}", "description": "Sposta {count, plural, =1 {elemento} other {elementi}} in una nuova posizione", - "success": "Spostati con successo {count} {count, plural, =1 {elemento} other {elementi}}" + "success": "Spostati con successo {count} {count, plural, =1 {elemento} other {elementi}}", + "errors": { + "moveFailed": "Impossibile spostare gli elementi" + } }, "navbar": { "logoAlt": "Logo dell'App", @@ -1153,8 +1171,6 @@ }, "fileActions": { "edit": "Modifica", - "save": "Salva", - "cancel": "Annulla", "preview": "Anteprima", "download": "Scarica", "delete": "Elimina", @@ -1378,16 +1394,6 @@ "deleteConfirmation": "Sei sicuro di voler eliminare questa condivisione? Questa azione non può essere annullata.", "addDescriptionPlaceholder": "Aggiungi descrizione...", "editTitle": "Modifica Condivisione", - "nameLabel": "Nome Condivisione", - "descriptionLabel": "Descrizione", - "descriptionPlaceholder": "Inserisci una descrizione (opzionale)", - "expirationLabel": "Data di Scadenza", - "expirationPlaceholder": "GG/MM/AAAA HH:MM", - "maxViewsLabel": "Visualizzazioni Massime", - "maxViewsPlaceholder": "Lasciare vuoto per illimitato", - "passwordProtection": "Protetto da Password", - "passwordLabel": "Password", - "passwordPlaceholder": "Inserisci password", "newPasswordLabel": "Nuova Password (lasciare vuoto per mantenere quella attuale)", "newPasswordPlaceholder": "Inserisci nuova password", "manageFilesTitle": "Gestisci File", @@ -1405,7 +1411,9 @@ "linkDescriptionFile": "Genera un collegamento personalizzato per condividere il file", "linkDescriptionFolder": "Genera un collegamento personalizzato per condividere la cartella", "linkReady": "Il tuo collegamento di condivisione è pronto:", - "linkTitle": "Genera collegamento" + "linkTitle": "Genera collegamento", + "itemsSelected": "{count, plural, =0 {Nessun elemento selezionato} =1 {1 elemento selezionato} other {# elementi selezionati}}", + "manageFilesDescription": "Seleziona file e cartelle da includere in questa condivisione" }, "shareDetails": { "title": "Dettagli Condivisione", @@ -1421,7 +1429,6 @@ "noLink": "Nessun link generato ancora", "copyLink": "Copia link", "openLink": "Apri in nuova scheda", - "linkCopied": "Link copiato negli appunti", "views": "Visualizzazioni", "dates": "Date", "created": "Creato", @@ -1469,28 +1476,6 @@ "expires": "Scade:", "expirationDate": "Data di Scadenza" }, - "shareFile": { - "title": "Condividi File", - "linkTitle": "Genera Link", - "nameLabel": "Nome Condivisione", - "namePlaceholder": "Inserisci nome condivisione", - "descriptionLabel": "Descrizione", - "descriptionPlaceholder": "Inserisci una descrizione (opzionale)", - "expirationLabel": "Data di Scadenza", - "expirationPlaceholder": "GG/MM/AAAA HH:MM", - "maxViewsLabel": "Visualizzazioni Massime", - "maxViewsPlaceholder": "Lasciare vuoto per illimitato", - "passwordProtection": "Protetto da Password", - "passwordLabel": "Password", - "passwordPlaceholder": "Inserisci password", - "linkDescription": "Genera un link personalizzato per condividere il file", - "aliasLabel": "Alias Link", - "aliasPlaceholder": "Inserisci alias personalizzato", - "linkReady": "Il tuo link di condivisione è pronto:", - "createShare": "Crea Condivisione", - "generateLink": "Genera Link", - "copyLink": "Copia Link" - }, "shareManager": { "deleteSuccess": "Condivisione eliminata con successo", "deleteError": "Errore durante l'eliminazione della condivisione", @@ -1520,7 +1505,10 @@ "noFilesToDownload": "Nessun file disponibile per il download", "singleShareZipName": "{Sharename} _files.zip", "zipDownloadError": "Impossibile creare un file zip", - "zipDownloadSuccess": "File zip scaricato correttamente" + "zipDownloadSuccess": "File zip scaricato correttamente", + "errors": { + "multipleDownloadNotSupported": "Il download di più condivisioni non è ancora supportato - scarica le condivisioni singolarmente" + } }, "shareMultipleFiles": { "title": "Condividi File Multipli", @@ -1917,6 +1905,23 @@ "inactive": "Inattivo", "admin": "Amministratore", "userr": "Utente" + }, + "invite": { + "button": "Genera Link di Invito", + "title": "Genera Link di Invito Utente", + "description": "Genera un link di utilizzo una tantum che permette a qualcuno di creare il proprio account. Il link scade in 15 minuti.", + "generating": "Generazione in corso...", + "generate": "Genera Link", + "generated": "Link di invito generato con successo!", + "linkReady": "Link di invito pronto", + "linkReadyDescription": "Condividi questo link con la persona che vuoi invitare. Potranno creare il proprio account come utente regolare.", + "copyLink": "Copia Link", + "linkCopied": "Link di invito copiato negli appunti!", + "expiresIn": "Scade in 15 minuti", + "close": "Chiudi", + "errors": { + "generateFailed": "Impossibile generare il link di invito" + } } }, "validation": { @@ -1935,15 +1940,62 @@ "required": "Questo campo è obbligatorio" }, "embedCode": { - "title": "Incorpora immagine", - "description": "Usa questi codici per incorporare questa immagine in forum, siti web o altre piattaforme", + "title": "Incorpora contenuto multimediale", + "description": "Usa questi codici per incorporare questo contenuto multimediale in forum, siti web o altre piattaforme", "tabs": { "directLink": "Link diretto", "html": "HTML", "bbcode": "BBCode" }, - "directLinkDescription": "URL diretto al file immagine", - "htmlDescription": "Usa questo codice per incorporare l'immagine nelle pagine HTML", - "bbcodeDescription": "Usa questo codice per incorporare l'immagine nei forum che supportano BBCode" + "directLinkDescription": "URL diretto al file multimediale", + "htmlDescription": "Usa questo codice per incorporare il contenuto multimediale nelle pagine HTML", + "bbcodeDescription": "Usa questo codice per incorporare il contenuto multimediale nei forum che supportano BBCode" + }, + "contextMenu": { + "newFolder": "Nuova cartella", + "uploadFile": "Carica file" + }, + "registerWithInvite": { + "title": "Crea il Tuo Account", + "description": "Compila le informazioni sottostanti per creare il tuo account", + "labels": { + "firstName": "Nome", + "firstNamePlaceholder": "Inserisci il tuo nome", + "lastName": "Cognome", + "lastNamePlaceholder": "Inserisci il tuo cognome", + "username": "Nome Utente", + "usernamePlaceholder": "Scegli un nome utente", + "email": "Email", + "emailPlaceholder": "Inserisci la tua email", + "password": "Password", + "passwordPlaceholder": "Scegli una password", + "confirmPassword": "Conferma Password", + "confirmPasswordPlaceholder": "Conferma la tua password" + }, + "buttons": { + "creating": "Creazione account in corso...", + "createAccount": "Crea Account" + }, + "validation": { + "firstNameRequired": "Il nome è obbligatorio", + "lastNameRequired": "Il cognome è obbligatorio", + "usernameMinLength": "Il nome utente deve essere di almeno 3 caratteri", + "invalidEmail": "Email non valida", + "passwordMinLength": "La password deve essere di almeno 8 caratteri", + "passwordsMatch": "Le password devono corrispondere" + }, + "messages": { + "success": "Account creato con successo! Reindirizzamento al login...", + "redirecting": "Reindirizzamento al login..." + }, + "errors": { + "invalidToken": "Errore con il link di invito", + "tokenUsed": "Questo link di invito è già stato utilizzato", + "tokenExpired": "Questo link di invito è scaduto", + "usernameExists": "Il nome utente esiste già", + "emailExists": "L'email esiste già", + "createFailed": "Impossibile creare l'account. Riprova." + }, + "pageTitle": "Crea Account" } } \ No newline at end of file diff --git a/apps/web/messages/ja-JP.json b/apps/web/messages/ja-JP.json index 096c4256..a626f2fe 100644 --- a/apps/web/messages/ja-JP.json +++ b/apps/web/messages/ja-JP.json @@ -6,7 +6,9 @@ "token_expired": "トークンの有効期限が切れました。もう一度お試しください。", "config_error": "設定エラー。サポートにお問い合わせください。", "auth_failed": "認証に失敗しました。もう一度お試しください。" - } + }, + "authenticationFailed": "認証に失敗しました", + "successfullyAuthenticated": "認証に成功しました!" }, "authProviders": { "title": "認証プロバイダー", @@ -174,7 +176,14 @@ "tabs": { "shareDetails": "共有の詳細", "selectFiles": "ファイルを選択" - } + }, + "errors": { + "nameRequired": "共有名は必須です", + "selectItems": "少なくとも1つのファイルまたはフォルダを選択してください" + }, + "itemsSelected": "{count, plural, =0 {アイテムが選択されていません} =1 {1つのアイテムが選択されています} other {#つのアイテムが選択されています}}", + "passwordPlaceholder": "パスワードを入力してください", + "selectItemsPrompt": "共有するファイルとフォルダを選択してください" }, "customization": { "breadcrumb": "カスタマイズ", @@ -340,7 +349,8 @@ "addToShare": "共有に追加", "removeFromShare": "共有から削除", "saveChanges": "変更を保存", - "editFolder": "フォルダを編集" + "editFolder": "フォルダを編集", + "itemsSelected": "{count, plural, =0 {アイテムが選択されていません} =1 {1つのアイテムが選択されています} other {#つのアイテムが選択されています}}" }, "files": { "title": "すべてのファイル", @@ -376,7 +386,12 @@ "description": "最初のファイルをアップロードするか、フォルダを作成して始めましょう" }, "files": "ファイル", - "folders": "フォルダ" + "folders": "フォルダ", + "errors": { + "moveItemsFailed": "アイテムの移動に失敗しました。もう一度お試しください。", + "cannotMoveHere": "この場所にアイテムを移動できません" + }, + "openFolder": "フォルダを開く" }, "filesTable": { "ariaLabel": "ファイルテーブル", @@ -539,7 +554,10 @@ "movingTo": "移動先:", "title": "アイテムを移動", "description": "アイテムを新しい場所に移動", - "success": "{count}個のアイテムが正常に移動されました" + "success": "{count}個のアイテムが正常に移動されました", + "errors": { + "moveFailed": "アイテムの移動に失敗しました" + } }, "navbar": { "logoAlt": "アプリケーションロゴ", @@ -1153,8 +1171,6 @@ }, "fileActions": { "edit": "編集", - "save": "保存", - "cancel": "キャンセル", "preview": "プレビュー", "download": "ダウンロード", "delete": "削除", @@ -1377,16 +1393,6 @@ "deleteTitle": "共有を削除", "deleteConfirmation": "この共有を削除しますか?この操作は元に戻すことができません。", "editTitle": "共有を編集", - "nameLabel": "共有名", - "descriptionLabel": "説明", - "descriptionPlaceholder": "説明を入力してください(任意)", - "expirationLabel": "有効期限", - "expirationPlaceholder": "YYYY/MM/DD HH:MM", - "maxViewsLabel": "最大表示回数", - "maxViewsPlaceholder": "無制限の場合は空白にしてください", - "passwordProtection": "パスワード保護", - "passwordLabel": "パスワード", - "passwordPlaceholder": "パスワードを入力してください", "newPasswordLabel": "新しいパスワード(現在のパスワードを保持する場合は空白)", "newPasswordPlaceholder": "新しいパスワードを入力してください", "manageFilesTitle": "ファイル管理", @@ -1405,7 +1411,9 @@ "linkDescriptionFile": "ファイルを共有するためのカスタムリンクを生成", "linkDescriptionFolder": "フォルダを共有するためのカスタムリンクを生成", "linkReady": "共有リンクの準備ができました:", - "linkTitle": "リンクを生成" + "linkTitle": "リンクを生成", + "itemsSelected": "{count, plural, =0 {アイテムが選択されていません} =1 {1つのアイテムが選択されています} other {#つのアイテムが選択されています}}", + "manageFilesDescription": "この共有に含めるファイルとフォルダを選択してください" }, "shareDetails": { "title": "共有詳細", @@ -1421,7 +1429,6 @@ "noLink": "まだリンクが生成されていません", "copyLink": "リンクをコピー", "openLink": "新しいタブで開く", - "linkCopied": "リンクがクリップボードにコピーされました", "views": "表示回数", "dates": "日付", "created": "作成日", @@ -1469,28 +1476,6 @@ "expires": "期限:", "expirationDate": "有効期限" }, - "shareFile": { - "title": "ファイル共有", - "linkTitle": "リンク生成", - "nameLabel": "共有名", - "namePlaceholder": "共有名を入力してください", - "descriptionLabel": "説明", - "descriptionPlaceholder": "説明を入力してください(任意)", - "expirationLabel": "有効期限", - "expirationPlaceholder": "YYYY/MM/DD HH:MM", - "maxViewsLabel": "最大表示回数", - "maxViewsPlaceholder": "無制限の場合は空白にしてください", - "passwordProtection": "パスワード保護", - "passwordLabel": "パスワード", - "passwordPlaceholder": "パスワードを入力してください", - "linkDescription": "ファイルを共有するためのカスタムリンクを生成する", - "aliasLabel": "リンクエイリアス", - "aliasPlaceholder": "カスタムエイリアスを入力してください", - "linkReady": "共有リンクの準備ができました:", - "createShare": "共有を作成", - "generateLink": "リンク生成", - "copyLink": "リンクコピー" - }, "shareManager": { "deleteSuccess": "共有が正常に削除されました", "deleteError": "共有の削除に失敗しました", @@ -1520,7 +1505,10 @@ "noFilesToDownload": "ダウンロードできるファイルはありません", "singleShareZipName": "{sharename} _files.zip", "zipDownloadError": "zipファイルの作成に失敗しました", - "zipDownloadSuccess": "zipファイルは正常にダウンロードされました" + "zipDownloadSuccess": "zipファイルは正常にダウンロードされました", + "errors": { + "multipleDownloadNotSupported": "複数の共有のダウンロードはまだサポートされていません - 共有を個別にダウンロードしてください" + } }, "shareMultipleFiles": { "title": "複数ファイルを共有", @@ -1917,6 +1905,23 @@ "inactive": "非アクティブ", "admin": "管理者", "userr": "ユーザー" + }, + "invite": { + "button": "招待リンクを生成", + "title": "ユーザー招待リンクを生成", + "description": "誰かが自分のアカウントを作成できるワンタイム使用リンクを生成します。リンクは15分で期限切れになります。", + "generating": "生成中...", + "generate": "リンクを生成", + "generated": "招待リンクが正常に生成されました!", + "linkReady": "招待リンクの準備完了", + "linkReadyDescription": "招待したい人にこのリンクを共有してください。彼らは通常ユーザーとして自分のアカウントを作成できます。", + "copyLink": "リンクをコピー", + "linkCopied": "招待リンクがクリップボードにコピーされました!", + "expiresIn": "15分で期限切れ", + "close": "閉じる", + "errors": { + "generateFailed": "招待リンクの生成に失敗しました" + } } }, "validation": { @@ -1935,15 +1940,62 @@ "required": "このフィールドは必須です" }, "embedCode": { - "title": "画像を埋め込む", - "description": "これらのコードを使用して、この画像をフォーラム、ウェブサイト、またはその他のプラットフォームに埋め込みます", + "title": "メディアを埋め込む", + "description": "これらのコードを使用して、このメディアをフォーラム、ウェブサイト、またはその他のプラットフォームに埋め込みます", "tabs": { "directLink": "直接リンク", "html": "HTML", "bbcode": "BBCode" }, - "directLinkDescription": "画像ファイルへの直接URL", - "htmlDescription": "このコードを使用してHTMLページに画像を埋め込みます", - "bbcodeDescription": "BBCodeをサポートするフォーラムに画像を埋め込むには、このコードを使用します" + "directLinkDescription": "メディアファイルへの直接URL", + "htmlDescription": "このコードを使用してHTMLページにメディアを埋め込みます", + "bbcodeDescription": "BBCodeをサポートするフォーラムにメディアを埋め込むには、このコードを使用します" + }, + "contextMenu": { + "newFolder": "新規フォルダ", + "uploadFile": "ファイルをアップロード" + }, + "registerWithInvite": { + "title": "アカウントを作成", + "description": "アカウントを作成するには、以下の情報を入力してください", + "labels": { + "firstName": "名", + "firstNamePlaceholder": "名を入力してください", + "lastName": "姓", + "lastNamePlaceholder": "姓を入力してください", + "username": "ユーザー名", + "usernamePlaceholder": "ユーザー名を選択してください", + "email": "メールアドレス", + "emailPlaceholder": "メールアドレスを入力してください", + "password": "パスワード", + "passwordPlaceholder": "パスワードを選択してください", + "confirmPassword": "パスワードの確認", + "confirmPasswordPlaceholder": "パスワードを確認してください" + }, + "buttons": { + "creating": "アカウントを作成中...", + "createAccount": "アカウントを作成" + }, + "validation": { + "firstNameRequired": "名は必須です", + "lastNameRequired": "姓は必須です", + "usernameMinLength": "ユーザー名は3文字以上である必要があります", + "invalidEmail": "無効なメールアドレスです", + "passwordMinLength": "パスワードは8文字以上である必要があります", + "passwordsMatch": "パスワードが一致する必要があります" + }, + "messages": { + "success": "アカウントが正常に作成されました!ログインにリダイレクト中...", + "redirecting": "ログインにリダイレクト中..." + }, + "errors": { + "invalidToken": "招待リンクにエラーがあります", + "tokenUsed": "この招待リンクは既に使用されています", + "tokenExpired": "この招待リンクは期限切れです", + "usernameExists": "ユーザー名は既に存在します", + "emailExists": "メールアドレスは既に存在します", + "createFailed": "アカウントの作成に失敗しました。もう一度お試しください。" + }, + "pageTitle": "アカウントを作成" } } \ No newline at end of file diff --git a/apps/web/messages/ko-KR.json b/apps/web/messages/ko-KR.json index a2749478..3909a4a8 100644 --- a/apps/web/messages/ko-KR.json +++ b/apps/web/messages/ko-KR.json @@ -6,7 +6,9 @@ "token_expired": "토큰이 만료되었습니다. 다시 시도하세요.", "config_error": "구성 오류. 지원팀에 문의하세요.", "auth_failed": "인증에 실패했습니다. 다시 시도하세요." - } + }, + "authenticationFailed": "인증에 실패했습니다", + "successfullyAuthenticated": "인증에 성공했습니다!" }, "authProviders": { "title": "인증 제공자", @@ -174,7 +176,14 @@ "tabs": { "shareDetails": "공유 세부사항", "selectFiles": "파일 선택" - } + }, + "errors": { + "nameRequired": "공유 이름은 필수입니다", + "selectItems": "최소 하나의 파일 또는 폴더를 선택해주세요" + }, + "itemsSelected": "{count, plural, =0 {선택된 항목 없음} =1 {1개 항목 선택됨} other {#개 항목 선택됨}}", + "passwordPlaceholder": "비밀번호를 입력하세요", + "selectItemsPrompt": "공유할 파일과 폴더를 선택하세요" }, "customization": { "breadcrumb": "커스터마이징", @@ -340,7 +349,8 @@ "addToShare": "공유에 추가", "removeFromShare": "공유에서 제거", "saveChanges": "변경사항 저장", - "editFolder": "폴더 편집" + "editFolder": "폴더 편집", + "itemsSelected": "{count, plural, =0 {선택된 항목 없음} =1 {1개 항목 선택됨} other {#개 항목 선택됨}}" }, "files": { "title": "모든 파일", @@ -376,7 +386,12 @@ "description": "첫 번째 파일을 업로드하거나 폴더를 만들어 시작하세요" }, "files": "파일", - "folders": "폴더" + "folders": "폴더", + "errors": { + "moveItemsFailed": "항목 이동에 실패했습니다. 다시 시도해주세요.", + "cannotMoveHere": "이 위치로 항목을 이동할 수 없습니다" + }, + "openFolder": "폴더 열기" }, "filesTable": { "ariaLabel": "파일 테이블", @@ -539,7 +554,10 @@ "movingTo": "이동 위치:", "title": "항목 이동", "description": "항목을 새 위치로 이동", - "success": "{count}개 항목이 성공적으로 이동되었습니다" + "success": "{count}개 항목이 성공적으로 이동되었습니다", + "errors": { + "moveFailed": "항목 이동에 실패했습니다" + } }, "navbar": { "logoAlt": "애플리케이션 로고", @@ -1153,8 +1171,6 @@ }, "fileActions": { "edit": "편집", - "save": "저장", - "cancel": "취소", "preview": "미리보기", "download": "다운로드", "delete": "삭제", @@ -1377,16 +1393,6 @@ "deleteTitle": "공유 삭제", "deleteConfirmation": "이 공유를 삭제하시겠습니까? 이 작업은 되돌릴 수 없습니다.", "editTitle": "공유 편집", - "nameLabel": "공유 이름", - "descriptionLabel": "설명", - "descriptionPlaceholder": "설명을 입력하세요 (선택사항)", - "expirationLabel": "만료 날짜", - "expirationPlaceholder": "YYYY/MM/DD HH:MM", - "maxViewsLabel": "최대 조회수", - "maxViewsPlaceholder": "무제한은 공백으로 두세요", - "passwordProtection": "비밀번호 보호", - "passwordLabel": "비밀번호", - "passwordPlaceholder": "비밀번호를 입력하세요", "newPasswordLabel": "새 비밀번호 (현재 비밀번호를 유지하려면 공백으로 두세요)", "newPasswordPlaceholder": "새 비밀번호를 입력하세요", "manageFilesTitle": "파일 관리", @@ -1405,7 +1411,9 @@ "linkDescriptionFile": "파일을 공유할 사용자 정의 링크 생성", "linkDescriptionFolder": "폴더를 공유할 사용자 정의 링크 생성", "linkReady": "공유 링크가 준비되었습니다:", - "linkTitle": "링크 생성" + "linkTitle": "링크 생성", + "itemsSelected": "{count}개 항목 선택됨", + "manageFilesDescription": "이 공유에 포함할 파일 및 폴더 선택" }, "shareDetails": { "title": "공유 세부 정보", @@ -1421,7 +1429,6 @@ "noLink": "아직 링크가 생성되지 않았습니다", "copyLink": "링크 복사", "openLink": "새 탭에서 열기", - "linkCopied": "링크가 클립보드에 복사되었습니다", "views": "조회수", "dates": "날짜", "created": "생성됨", @@ -1469,28 +1476,6 @@ "expires": "만료:", "expirationDate": "만료 날짜" }, - "shareFile": { - "title": "파일 공유", - "linkTitle": "링크 생성", - "nameLabel": "공유 이름", - "namePlaceholder": "공유 이름을 입력하세요", - "descriptionLabel": "설명", - "descriptionPlaceholder": "설명을 입력하세요 (선택사항)", - "expirationLabel": "만료 날짜", - "expirationPlaceholder": "YYYY/MM/DD HH:MM", - "maxViewsLabel": "최대 조회수", - "maxViewsPlaceholder": "무제한은 공백으로 두세요", - "passwordProtection": "비밀번호 보호", - "passwordLabel": "비밀번호", - "passwordPlaceholder": "비밀번호를 입력하세요", - "linkDescription": "파일을 공유할 맞춤 링크를 생성하세요", - "aliasLabel": "링크 별칭", - "aliasPlaceholder": "맞춤 별칭을 입력하세요", - "linkReady": "공유 링크가 준비되었습니다:", - "createShare": "공유 생성", - "generateLink": "링크 생성", - "copyLink": "링크 복사" - }, "shareManager": { "deleteSuccess": "공유가 성공적으로 삭제되었습니다", "deleteError": "공유 삭제에 실패했습니다", @@ -1520,7 +1505,10 @@ "noFilesToDownload": "다운로드 할 수있는 파일이 없습니다", "singleShareZipName": "{sharename} _files.zip", "zipDownloadError": "zip 파일을 만들지 못했습니다", - "zipDownloadSuccess": "zip 파일이 성공적으로 다운로드되었습니다" + "zipDownloadSuccess": "zip 파일이 성공적으로 다운로드되었습니다", + "errors": { + "multipleDownloadNotSupported": "여러 공유 다운로드는 아직 지원되지 않습니다 - 공유를 개별적으로 다운로드해주세요" + } }, "shareMultipleFiles": { "title": "여러 파일 공유", @@ -1917,6 +1905,23 @@ "inactive": "비활성", "admin": "관리자", "userr": "사용자" + }, + "invite": { + "button": "초대 링크 생성", + "title": "사용자 초대 링크 생성", + "description": "한 번 사용할 수 있는 링크를 생성하여 누군가가 자신의 계정을 만들 수 있도록 합니다. 링크는 15분 후에 만료됩니다.", + "generating": "생성 중...", + "generate": "링크 생성", + "generated": "초대 링크가 성공적으로 생성되었습니다!", + "linkReady": "초대 링크 준비됨", + "linkReadyDescription": "이 링크를 초대하려는 사람과 공유하세요. 그들은 일반 사용자로 자신의 계정을 만들 수 있습니다.", + "copyLink": "링크 복사", + "linkCopied": "초대 링크가 클립보드에 복사되었습니다!", + "expiresIn": "15분 후 만료", + "close": "닫기", + "errors": { + "generateFailed": "초대 링크 생성 실패" + } } }, "validation": { @@ -1935,15 +1940,62 @@ "required": "이 필드는 필수입니다" }, "embedCode": { - "title": "이미지 삽입", - "description": "이 코드를 사용하여 포럼, 웹사이트 또는 기타 플랫폼에 이 이미지를 삽입하세요", + "title": "미디어 삽입", + "description": "이 코드를 사용하여 포럼, 웹사이트 또는 기타 플랫폼에 이 미디어를 삽입하세요", "tabs": { "directLink": "직접 링크", "html": "HTML", "bbcode": "BBCode" }, - "directLinkDescription": "이미지 파일에 대한 직접 URL", - "htmlDescription": "이 코드를 사용하여 HTML 페이지에 이미지를 삽입하세요", - "bbcodeDescription": "BBCode를 지원하는 포럼에 이미지를 삽입하려면 이 코드를 사용하세요" + "directLinkDescription": "미디어 파일에 대한 직접 URL", + "htmlDescription": "이 코드를 사용하여 HTML 페이지에 미디어를 삽입하세요", + "bbcodeDescription": "BBCode를 지원하는 포럼에 미디어를 삽입하려면 이 코드를 사용하세요" + }, + "contextMenu": { + "newFolder": "새 폴더", + "uploadFile": "파일 업로드" + }, + "registerWithInvite": { + "title": "계정 만들기", + "description": "아래 정보를 입력하여 계정을 만드세요", + "labels": { + "firstName": "이름", + "firstNamePlaceholder": "이름을 입력하세요", + "lastName": "성", + "lastNamePlaceholder": "성을 입력하세요", + "username": "사용자 이름", + "usernamePlaceholder": "사용자 이름을 선택하세요", + "email": "이메일", + "emailPlaceholder": "이메일을 입력하세요", + "password": "비밀번호", + "passwordPlaceholder": "비밀번호를 선택하세요", + "confirmPassword": "비밀번호 확인", + "confirmPasswordPlaceholder": "비밀번호를 확인하세요" + }, + "buttons": { + "creating": "계정 생성 중...", + "createAccount": "계정 만들기" + }, + "validation": { + "firstNameRequired": "이름은 필수입니다", + "lastNameRequired": "성은 필수입니다", + "usernameMinLength": "사용자 이름은 최소 3자 이상이어야 합니다", + "invalidEmail": "유효하지 않은 이메일입니다", + "passwordMinLength": "비밀번호는 최소 8자 이상이어야 합니다", + "passwordsMatch": "비밀번호가 일치해야 합니다" + }, + "messages": { + "success": "계정이 성공적으로 생성되었습니다! 로그인 페이지로 이동합니다...", + "redirecting": "로그인 페이지로 이동합니다..." + }, + "errors": { + "invalidToken": "초대 링크에 오류가 있습니다", + "tokenUsed": "이 초대 링크는 이미 사용되었습니다", + "tokenExpired": "이 초대 링크는 만료되었습니다", + "usernameExists": "사용자 이름이 이미 존재합니다", + "emailExists": "이메일이 이미 존재합니다", + "createFailed": "계정 생성에 실패했습니다. 다시 시도해주세요." + }, + "pageTitle": "계정 만들기" } } \ No newline at end of file diff --git a/apps/web/messages/nl-NL.json b/apps/web/messages/nl-NL.json index 5e2faabe..1024260c 100644 --- a/apps/web/messages/nl-NL.json +++ b/apps/web/messages/nl-NL.json @@ -6,7 +6,9 @@ "token_expired": "Token verlopen. Probeer het opnieuw.", "config_error": "Configuratiefout. Neem contact op met support.", "auth_failed": "Authenticatie mislukt. Probeer het opnieuw." - } + }, + "authenticationFailed": "Authenticatie mislukt", + "successfullyAuthenticated": "Succesvol geauthenticeerd!" }, "authProviders": { "title": "Authenticatie Providers", @@ -174,7 +176,14 @@ "tabs": { "shareDetails": "Deel details", "selectFiles": "Bestanden selecteren" - } + }, + "errors": { + "nameRequired": "Deelnaam is verplicht", + "selectItems": "Selecteer ten minste één bestand of map" + }, + "itemsSelected": "{count} items geselecteerd", + "passwordPlaceholder": "Voer wachtwoord in", + "selectItemsPrompt": "Selecteer bestanden en mappen om te delen" }, "customization": { "breadcrumb": "Aanpassing", @@ -340,7 +349,8 @@ "addToShare": "Toevoegen aan share", "removeFromShare": "Verwijderen uit share", "saveChanges": "Wijzigingen Opslaan", - "editFolder": "Map bewerken" + "editFolder": "Map bewerken", + "itemsSelected": "{count} items geselecteerd" }, "files": { "title": "Alle Bestanden", @@ -376,7 +386,12 @@ "description": "Upload uw eerste bestand of maak een map om te beginnen" }, "files": "bestanden", - "folders": "mappen" + "folders": "mappen", + "errors": { + "moveItemsFailed": "Verplaatsen van items mislukt. Probeer het opnieuw.", + "cannotMoveHere": "Kan items niet naar deze locatie verplaatsen" + }, + "openFolder": "Map openen" }, "filesTable": { "ariaLabel": "Bestanden tabel", @@ -539,7 +554,10 @@ "movingTo": "Verplaatsen naar:", "title": "{count, plural, =1 {Item} other {Items}} verplaatsen", "description": "{count, plural, =1 {Item} other {Items}} naar een nieuwe locatie verplaatsen", - "success": "{count} {count, plural, =1 {item} other {items}} succesvol verplaatst" + "success": "{count} {count, plural, =1 {item} other {items}} succesvol verplaatst", + "errors": { + "moveFailed": "Verplaatsen van items mislukt" + } }, "navbar": { "logoAlt": "Applicatie Logo", @@ -1153,8 +1171,6 @@ }, "fileActions": { "edit": "Bewerken", - "save": "Opslaan", - "cancel": "Annuleren", "preview": "Voorvertoning", "download": "Downloaden", "delete": "Verwijderen", @@ -1377,16 +1393,6 @@ "deleteTitle": "Delen Verwijderen", "deleteConfirmation": "Weet je zeker dat je dit delen wilt verwijderen? Deze actie kan niet ongedaan worden gemaakt.", "editTitle": "Delen Bewerken", - "nameLabel": "Delen Naam", - "descriptionLabel": "Beschrijving", - "descriptionPlaceholder": "Voer een beschrijving in (optioneel)", - "expirationLabel": "Vervaldatum", - "expirationPlaceholder": "DD/MM/JJJJ UU:MM", - "maxViewsLabel": "Maximale Weergaven", - "maxViewsPlaceholder": "Laat leeg voor onbeperkt", - "passwordProtection": "Wachtwoord Beveiligd", - "passwordLabel": "Wachtwoord", - "passwordPlaceholder": "Voer wachtwoord in", "newPasswordLabel": "Nieuw Wachtwoord (laat leeg om huidig te behouden)", "newPasswordPlaceholder": "Voer nieuw wachtwoord in", "manageFilesTitle": "Bestanden Beheren", @@ -1405,7 +1411,9 @@ "linkDescriptionFile": "Genereer een aangepaste link om het bestand te delen", "linkDescriptionFolder": "Genereer een aangepaste link om de map te delen", "linkReady": "Uw deel-link is klaar:", - "linkTitle": "Link genereren" + "linkTitle": "Link genereren", + "itemsSelected": "{count} items geselecteerd", + "manageFilesDescription": "Selecteer bestanden en mappen om in dit deel op te nemen" }, "shareDetails": { "title": "Delen Details", @@ -1421,7 +1429,6 @@ "noLink": "Nog geen link gegenereerd", "copyLink": "Link kopiëren", "openLink": "Openen in nieuw tabblad", - "linkCopied": "Link gekopieerd naar klembord", "views": "Weergaven", "dates": "Data", "created": "Aangemaakt", @@ -1469,28 +1476,6 @@ "expires": "Verloopt:", "expirationDate": "Vervaldatum" }, - "shareFile": { - "title": "Bestand Delen", - "linkTitle": "Link Genereren", - "nameLabel": "Delen Naam", - "namePlaceholder": "Voer delen naam in", - "descriptionLabel": "Beschrijving", - "descriptionPlaceholder": "Voer een beschrijving in (optioneel)", - "expirationLabel": "Vervaldatum", - "expirationPlaceholder": "DD/MM/JJJJ UU:MM", - "maxViewsLabel": "Maximale Weergaven", - "maxViewsPlaceholder": "Laat leeg voor onbeperkt", - "passwordProtection": "Wachtwoord Beveiligd", - "passwordLabel": "Wachtwoord", - "passwordPlaceholder": "Voer wachtwoord in", - "linkDescription": "Genereer een aangepaste link om het bestand te delen", - "aliasLabel": "Link Alias", - "aliasPlaceholder": "Voer aangepaste alias in", - "linkReady": "Jouw delen link is klaar:", - "createShare": "Delen Aanmaken", - "generateLink": "Link Genereren", - "copyLink": "Link Kopiëren" - }, "shareManager": { "deleteSuccess": "Delen succesvol verwijderd", "deleteError": "Fout bij het verwijderen van delen", @@ -1520,7 +1505,10 @@ "noFilesToDownload": "Geen bestanden beschikbaar om te downloaden", "singleShareZipName": "{Sharename} _files.zip", "zipDownloadError": "Kan zip -bestand niet maken", - "zipDownloadSuccess": "Zipbestand met succes gedownload" + "zipDownloadSuccess": "Zipbestand met succes gedownload", + "errors": { + "multipleDownloadNotSupported": "Download van meerdere delen wordt nog niet ondersteund - download delen afzonderlijk" + } }, "shareMultipleFiles": { "title": "Meerdere Bestanden Delen", @@ -1917,6 +1905,23 @@ "inactive": "Inactief", "admin": "Beheerder", "userr": "Gebruiker" + }, + "invite": { + "button": "Genereer Uitnodigingslink", + "title": "Genereer Gebruikersuitnodigingslink", + "description": "Genereer een eenmalige link waarmee iemand zijn eigen account kan aanmaken. De link verloopt over 15 minuten.", + "generating": "Genereren...", + "generate": "Link Genereren", + "generated": "Uitnodigingslink succesvol gegenereerd!", + "linkReady": "Uitnodigingslink klaar", + "linkReadyDescription": "Deel deze link met de persoon die je wilt uitnodigen. Ze kunnen hun eigen account aanmaken als gewone gebruiker.", + "copyLink": "Link Kopiëren", + "linkCopied": "Uitnodigingslink gekopieerd naar klembord!", + "expiresIn": "Verloopt over 15 minuten", + "close": "Sluiten", + "errors": { + "generateFailed": "Genereren van uitnodigingslink mislukt" + } } }, "validation": { @@ -1935,15 +1940,62 @@ "required": "Dit veld is verplicht" }, "embedCode": { - "title": "Afbeelding insluiten", - "description": "Gebruik deze codes om deze afbeelding in te sluiten in forums, websites of andere platforms", + "title": "Media insluiten", + "description": "Gebruik deze codes om deze media in te sluiten in forums, websites of andere platforms", "tabs": { "directLink": "Directe link", "html": "HTML", "bbcode": "BBCode" }, - "directLinkDescription": "Directe URL naar het afbeeldingsbestand", - "htmlDescription": "Gebruik deze code om de afbeelding in te sluiten in HTML-pagina's", - "bbcodeDescription": "Gebruik deze code om de afbeelding in te sluiten in forums die BBCode ondersteunen" + "directLinkDescription": "Directe URL naar het mediabestand", + "htmlDescription": "Gebruik deze code om de media in te sluiten in HTML-pagina's", + "bbcodeDescription": "Gebruik deze code om de media in te sluiten in forums die BBCode ondersteunen" + }, + "contextMenu": { + "newFolder": "Nieuwe map", + "uploadFile": "Bestand uploaden" + }, + "registerWithInvite": { + "title": "Maak Uw Account Aan", + "description": "Vul de onderstaande informatie in om uw account aan te maken", + "labels": { + "firstName": "Voornaam", + "firstNamePlaceholder": "Voer uw voornaam in", + "lastName": "Achternaam", + "lastNamePlaceholder": "Voer uw achternaam in", + "username": "Gebruikersnaam", + "usernamePlaceholder": "Kies een gebruikersnaam", + "email": "E-mail", + "emailPlaceholder": "Voer uw e-mail in", + "password": "Wachtwoord", + "passwordPlaceholder": "Kies een wachtwoord", + "confirmPassword": "Bevestig Wachtwoord", + "confirmPasswordPlaceholder": "Bevestig uw wachtwoord" + }, + "buttons": { + "creating": "Account aanmaken...", + "createAccount": "Account Aanmaken" + }, + "validation": { + "firstNameRequired": "Voornaam is verplicht", + "lastNameRequired": "Achternaam is verplicht", + "usernameMinLength": "Gebruikersnaam moet minimaal 3 tekens bevatten", + "invalidEmail": "Ongeldig e-mailadres", + "passwordMinLength": "Wachtwoord moet minimaal 8 tekens bevatten", + "passwordsMatch": "Wachtwoorden moeten overeenkomen" + }, + "messages": { + "success": "Account succesvol aangemaakt! Doorverwijzen naar inloggen...", + "redirecting": "Doorverwijzen naar inloggen..." + }, + "errors": { + "invalidToken": "Fout met uitnodigingslink", + "tokenUsed": "Deze uitnodigingslink is al gebruikt", + "tokenExpired": "Deze uitnodigingslink is verlopen", + "usernameExists": "Gebruikersnaam bestaat al", + "emailExists": "E-mailadres bestaat al", + "createFailed": "Aanmaken van account mislukt. Probeer het opnieuw." + }, + "pageTitle": "Account Aanmaken" } } \ No newline at end of file diff --git a/apps/web/messages/pl-PL.json b/apps/web/messages/pl-PL.json index 5fc4c14e..abe2c1e2 100644 --- a/apps/web/messages/pl-PL.json +++ b/apps/web/messages/pl-PL.json @@ -6,7 +6,9 @@ "token_expired": "Token wygasł. Spróbuj ponownie.", "config_error": "Błąd konfiguracji. Skontaktuj się z pomocą techniczną.", "auth_failed": "Uwierzytelnienie nie powiodło się. Spróbuj ponownie." - } + }, + "authenticationFailed": "Uwierzytelnienie nie powiodło się", + "successfullyAuthenticated": "Pomyślnie uwierzytelniono!" }, "authProviders": { "title": "Dostawcy uwierzytelniania", @@ -174,7 +176,14 @@ "tabs": { "shareDetails": "Szczegóły udostępniania", "selectFiles": "Wybierz pliki" - } + }, + "errors": { + "nameRequired": "Nazwa udostępnienia jest wymagana", + "selectItems": "Wybierz co najmniej jeden plik lub folder" + }, + "itemsSelected": "Wybrano {count} elementów", + "passwordPlaceholder": "Wprowadź hasło", + "selectItemsPrompt": "Wybierz pliki i foldery do udostępnienia" }, "customization": { "breadcrumb": "Personalizacja", @@ -340,7 +349,8 @@ "addToShare": "Dodaj do udostępnienia", "removeFromShare": "Usuń z udostępnienia", "saveChanges": "Zapisz zmiany", - "editFolder": "Edytuj folder" + "editFolder": "Edytuj folder", + "itemsSelected": "Wybrano {count} elementów" }, "files": { "title": "Wszystkie pliki", @@ -376,7 +386,12 @@ "description": "Prześlij swój pierwszy plik lub utwórz folder, aby rozpocząć" }, "files": "pliki", - "folders": "foldery" + "folders": "foldery", + "errors": { + "moveItemsFailed": "Nie udało się przenieść elementów. Spróbuj ponownie.", + "cannotMoveHere": "Nie można przenieść elementów do tej lokalizacji" + }, + "openFolder": "Otwórz folder" }, "filesTable": { "ariaLabel": "Tabela plików", @@ -539,7 +554,10 @@ "movingTo": "Przenoszenie do:", "title": "Przenieś {count, plural, =1 {element} other {elementy}}", "description": "Przenieś {count, plural, =1 {element} other {elementy}} do nowej lokalizacji", - "success": "Pomyślnie przeniesiono {count} {count, plural, =1 {element} other {elementów}}" + "success": "Pomyślnie przeniesiono {count} {count, plural, =1 {element} other {elementów}}", + "errors": { + "moveFailed": "Nie udało się przenieść elementów" + } }, "navbar": { "logoAlt": "Logo aplikacji", @@ -1153,8 +1171,6 @@ }, "fileActions": { "edit": "Edytuj", - "save": "Zapisz", - "cancel": "Anuluj", "preview": "Podgląd", "download": "Pobierz", "delete": "Usuń", @@ -1377,16 +1393,6 @@ "deleteTitle": "Usuń udostępnienie", "deleteConfirmation": "Czy na pewno chcesz usunąć to udostępnienie? Tej operacji nie można cofnąć.", "editTitle": "Edytuj udostępnienie", - "nameLabel": "Nazwa udostępnienia", - "descriptionLabel": "Opis", - "descriptionPlaceholder": "Wpisz opis (opcjonalnie)", - "expirationLabel": "Data wygaśnięcia", - "expirationPlaceholder": "MM/DD/RRRR GG:MM", - "maxViewsLabel": "Maksymalna liczba wyświetleń", - "maxViewsPlaceholder": "Pozostaw puste dla nieograniczonej liczby", - "passwordProtection": "Chronione hasłem", - "passwordLabel": "Hasło", - "passwordPlaceholder": "Wprowadź hasło", "newPasswordLabel": "Nowe hasło (pozostaw puste, aby zachować bieżące)", "newPasswordPlaceholder": "Wprowadź nowe hasło", "manageFilesTitle": "Zarządzaj plikami", @@ -1405,7 +1411,9 @@ "linkDescriptionFile": "Wygeneruj niestandardowy link do udostępnienia pliku", "linkDescriptionFolder": "Wygeneruj niestandardowy link do udostępnienia folderu", "linkReady": "Twój link udostępniania jest gotowy:", - "linkTitle": "Generuj link" + "linkTitle": "Generuj link", + "itemsSelected": "Wybrano {count} elementów", + "manageFilesDescription": "Wybierz pliki i foldery do uwzględnienia w tym udostępnieniu" }, "shareDetails": { "title": "Szczegóły udostępnienia", @@ -1421,7 +1429,6 @@ "noLink": "Brak wygenerowanego linku", "copyLink": "Skopiuj link", "openLink": "Otwórz w nowej karcie", - "linkCopied": "Link skopiowany do schowka", "views": "Wyświetlenia", "dates": "Daty", "created": "Utworzono", @@ -1469,28 +1476,6 @@ "noExpiration": "To udostępnienie nigdy nie wygaśnie i pozostanie dostępne bezterminowo." } }, - "shareFile": { - "title": "Udostępnij plik", - "linkTitle": "Generuj link", - "nameLabel": "Nazwa udostępnienia", - "namePlaceholder": "Wprowadź nazwę udostępnienia", - "descriptionLabel": "Opis", - "descriptionPlaceholder": "Wprowadź opis (opcjonalnie)", - "expirationLabel": "Data wygaśnięcia", - "expirationPlaceholder": "MM/DD/RRRR GG:MM", - "maxViewsLabel": "Maksymalna liczba wyświetleń", - "maxViewsPlaceholder": "Pozostaw puste dla nieograniczonej liczby", - "passwordProtection": "Chronione hasłem", - "passwordLabel": "Hasło", - "passwordPlaceholder": "Wprowadź hasło", - "linkDescription": "Wygeneruj niestandardowy link do udostępniania pliku", - "aliasLabel": "Alias linku", - "aliasPlaceholder": "Wprowadź własny alias", - "linkReady": "Twój link do udostępniania jest gotowy:", - "createShare": "Utwórz udostępnienie", - "generateLink": "Generuj link", - "copyLink": "Skopiuj link" - }, "shareManager": { "deleteSuccess": "Udostępnienie usunięte pomyślnie", "deleteError": "Nie udało się usunąć udostępnienia", @@ -1520,7 +1505,10 @@ "noFilesToDownload": "Brak plików do pobrania", "singleShareZipName": "{ShaRename} _files.zip", "zipDownloadError": "Nie udało się utworzyć pliku zip", - "zipDownloadSuccess": "Plik zip pobrany pomyślnie" + "zipDownloadSuccess": "Plik zip pobrany pomyślnie", + "errors": { + "multipleDownloadNotSupported": "Pobieranie wielu udostępnień nie jest jeszcze obsługiwane - pobierz udostępnienia pojedynczo" + } }, "shareMultipleFiles": { "title": "Udostępnij wiele plików", @@ -1917,6 +1905,23 @@ "inactive": "Nieaktywny", "admin": "Administrator", "userr": "Użytkownik" + }, + "invite": { + "button": "Wygeneruj link zaproszenia", + "title": "Wygeneruj link zaproszenia użytkownika", + "description": "Wygeneruj link jednorazowego użytku, który pozwala komuś utworzyć własne konto. Link wygasa za 15 minut.", + "generating": "Generowanie...", + "generate": "Wygeneruj link", + "generated": "Link zaproszenia wygenerowany pomyślnie!", + "linkReady": "Link zaproszenia gotowy", + "linkReadyDescription": "Udostępnij ten link osobie, którą chcesz zaprosić. Będzie mogła utworzyć własne konto jako zwykły użytkownik.", + "copyLink": "Kopiuj link", + "linkCopied": "Link zaproszenia skopiowany do schowka!", + "expiresIn": "Wygasa za 15 minut", + "close": "Zamknij", + "errors": { + "generateFailed": "Nie udało się wygenerować linku zaproszenia" + } } }, "validation": { @@ -1935,15 +1940,62 @@ "required": "To pole jest wymagane" }, "embedCode": { - "title": "Osadź obraz", - "description": "Użyj tych kodów, aby osadzić ten obraz na forach, stronach internetowych lub innych platformach", + "title": "Osadź multimedia", + "description": "Użyj tych kodów, aby osadzić te multimedia na forach, stronach internetowych lub innych platformach", "tabs": { "directLink": "Link bezpośredni", "html": "HTML", "bbcode": "BBCode" }, - "directLinkDescription": "Bezpośredni adres URL pliku obrazu", - "htmlDescription": "Użyj tego kodu, aby osadzić obraz na stronach HTML", - "bbcodeDescription": "Użyj tego kodu, aby osadzić obraz na forach obsługujących BBCode" + "directLinkDescription": "Bezpośredni adres URL pliku multimedialnego", + "htmlDescription": "Użyj tego kodu, aby osadzić multimedia na stronach HTML", + "bbcodeDescription": "Użyj tego kodu, aby osadzić multimedia na forach obsługujących BBCode" + }, + "contextMenu": { + "newFolder": "Nowy folder", + "uploadFile": "Prześlij plik" + }, + "registerWithInvite": { + "title": "Utwórz swoje konto", + "description": "Wypełnij poniższe informacje, aby utworzyć swoje konto", + "labels": { + "firstName": "Imię", + "firstNamePlaceholder": "Wprowadź swoje imię", + "lastName": "Nazwisko", + "lastNamePlaceholder": "Wprowadź swoje nazwisko", + "username": "Nazwa użytkownika", + "usernamePlaceholder": "Wybierz nazwę użytkownika", + "email": "E-mail", + "emailPlaceholder": "Wprowadź swój adres e-mail", + "password": "Hasło", + "passwordPlaceholder": "Wybierz hasło", + "confirmPassword": "Potwierdź hasło", + "confirmPasswordPlaceholder": "Potwierdź swoje hasło" + }, + "buttons": { + "creating": "Tworzenie konta...", + "createAccount": "Utwórz konto" + }, + "validation": { + "firstNameRequired": "Imię jest wymagane", + "lastNameRequired": "Nazwisko jest wymagane", + "usernameMinLength": "Nazwa użytkownika musi mieć co najmniej 3 znaki", + "invalidEmail": "Nieprawidłowy adres e-mail", + "passwordMinLength": "Hasło musi mieć co najmniej 8 znaków", + "passwordsMatch": "Hasła muszą być zgodne" + }, + "messages": { + "success": "Konto utworzone pomyślnie! Przekierowywanie do logowania...", + "redirecting": "Przekierowywanie do logowania..." + }, + "errors": { + "invalidToken": "Błąd z linkiem zaproszenia", + "tokenUsed": "Ten link zaproszenia został już użyty", + "tokenExpired": "Ten link zaproszenia wygasł", + "usernameExists": "Nazwa użytkownika już istnieje", + "emailExists": "Adres e-mail już istnieje", + "createFailed": "Nie udało się utworzyć konta. Spróbuj ponownie." + }, + "pageTitle": "Utwórz konto" } } \ No newline at end of file diff --git a/apps/web/messages/pt-BR.json b/apps/web/messages/pt-BR.json index 732bb730..884b88d0 100644 --- a/apps/web/messages/pt-BR.json +++ b/apps/web/messages/pt-BR.json @@ -6,7 +6,13 @@ "token_expired": "Token expirado. Tente novamente.", "config_error": "Erro de configuração. Contate o suporte.", "auth_failed": "Falha na autenticação. Tente novamente." - } + }, + "authenticationFailed": "Falha na autenticação", + "successfullyAuthenticated": "Autenticado com sucesso!" + }, + "contextMenu": { + "newFolder": "Nova pasta", + "uploadFile": "Enviar arquivo" }, "authProviders": { "title": "Provedores de autenticação", @@ -174,7 +180,14 @@ "tabs": { "shareDetails": "Detalhes do compartilhamento", "selectFiles": "Selecionar arquivos" - } + }, + "errors": { + "nameRequired": "O nome do compartilhamento é obrigatório", + "selectItems": "Por favor, selecione pelo menos um arquivo ou pasta" + }, + "itemsSelected": "{count} itens selecionados", + "passwordPlaceholder": "Digite a senha", + "selectItemsPrompt": "Selecione arquivos e pastas para compartilhar" }, "customization": { "breadcrumb": "Personalização", @@ -340,7 +353,8 @@ "addToShare": "Adicionar ao compartilhamento", "removeFromShare": "Remover do compartilhamento", "saveChanges": "Salvar Alterações", - "editFolder": "Editar pasta" + "editFolder": "Editar pasta", + "itemsSelected": "{count} itens selecionados" }, "files": { "title": "Todos os Arquivos", @@ -376,7 +390,12 @@ "description": "Carregue seu primeiro arquivo ou crie uma pasta para começar" }, "files": "arquivos", - "folders": "pastas" + "folders": "pastas", + "errors": { + "moveItemsFailed": "Falha ao mover itens. Por favor, tente novamente.", + "cannotMoveHere": "Não é possível mover itens para este local" + }, + "openFolder": "Abrir pasta" }, "filesTable": { "ariaLabel": "Tabela de arquivos", @@ -539,7 +558,10 @@ "movingTo": "Movendo para:", "title": "Mover {count, plural, =1 {item} other {itens}}", "description": "Mover {count, plural, =1 {item} other {itens}} para um novo local", - "success": "Movidos com sucesso {count} {count, plural, =1 {item} other {itens}}" + "success": "Movidos com sucesso {count} {count, plural, =1 {item} other {itens}}", + "errors": { + "moveFailed": "Falha ao mover itens" + } }, "navbar": { "logoAlt": "Logo do aplicativo", @@ -914,7 +936,6 @@ "size": "Tamanho", "sender": "Enviado por", "date": "Data", - "invalidDate": "Data inválida", "actions": "Ações" }, "actions": { @@ -1154,8 +1175,6 @@ }, "fileActions": { "edit": "Editar", - "save": "Salvar", - "cancel": "Cancelar", "preview": "Visualizar", "download": "Baixar", "delete": "Excluir", @@ -1381,16 +1400,6 @@ "bulkDeleteTitle": "Excluir Compartilhamentos Selecionados", "bulkDeleteConfirmation": "Tem certeza que deseja excluir {count, plural, =1 {1 compartilhamento} other {# compartilhamentos}} selecionado(s)? Esta ação não pode ser desfeita.", "editTitle": "Editar Compartilhamento", - "nameLabel": "Nome do Compartilhamento", - "descriptionLabel": "Descrição", - "descriptionPlaceholder": "Digite uma descrição (opcional)", - "expirationLabel": "Data de Expiração", - "expirationPlaceholder": "DD/MM/AAAA HH:MM", - "maxViewsLabel": "Máximo de Visualizações", - "maxViewsPlaceholder": "Deixe vazio para ilimitado", - "passwordProtection": "Protegido por Senha", - "passwordLabel": "Senha", - "passwordPlaceholder": "Digite a senha", "newPasswordLabel": "Nova Senha (deixe vazio para manter a atual)", "newPasswordPlaceholder": "Digite a nova senha", "manageFilesTitle": "Gerenciar Arquivos", @@ -1406,7 +1415,9 @@ "linkDescriptionFile": "Gere um link personalizado para compartilhar o arquivo", "linkDescriptionFolder": "Gere um link personalizado para compartilhar a pasta", "linkReady": "Seu link de compartilhamento está pronto:", - "linkTitle": "Gerar link" + "linkTitle": "Gerar link", + "itemsSelected": "{count} itens selecionados", + "manageFilesDescription": "Selecione arquivos e pastas para incluir neste compartilhamento" }, "shareDetails": { "title": "Detalhes do Compartilhamento", @@ -1422,7 +1433,6 @@ "noLink": "Nenhum link gerado ainda", "copyLink": "Copiar link", "openLink": "Abrir em nova guia", - "linkCopied": "Link copiado para a área de transferência", "views": "Visualizações", "dates": "Datas", "created": "Criado", @@ -1470,28 +1480,6 @@ "expires": "Expira:", "expirationDate": "Data de expiração" }, - "shareFile": { - "title": "Compartilhar arquivo", - "linkTitle": "Gerar link", - "nameLabel": "Nome do compartilhamento", - "namePlaceholder": "Digite o nome do compartilhamento", - "descriptionLabel": "Descrição", - "descriptionPlaceholder": "Digite uma descrição (opcional)", - "expirationLabel": "Data de Expiração", - "expirationPlaceholder": "DD/MM/AAAA HH:MM", - "maxViewsLabel": "Máximo de Visualizações", - "maxViewsPlaceholder": "Deixe vazio para ilimitado", - "passwordProtection": "Protegido por senha", - "passwordLabel": "Senha", - "passwordPlaceholder": "Digite a senha", - "linkDescription": "Gere um link personalizado para compartilhar o arquivo", - "aliasLabel": "Alias do link", - "aliasPlaceholder": "Digite um alias personalizado", - "linkReady": "Seu link de compartilhamento está pronto:", - "createShare": "Criar compartilhamento", - "generateLink": "Gerar link", - "copyLink": "Copiar link" - }, "shareManager": { "deleteSuccess": "Compartilhamento excluído com sucesso", "deleteError": "Falha ao excluir compartilhamento", @@ -1521,7 +1509,10 @@ "noFilesToDownload": "Nenhum arquivo disponível para download", "singleShareZipName": "{sharename}.zip", "zipDownloadError": "Falha ao criar o arquivo zip", - "zipDownloadSuccess": "FILE DE ZIP FILHADO COMBONHADO com sucesso" + "zipDownloadSuccess": "Arquivo ZIP baixado com sucesso", + "errors": { + "multipleDownloadNotSupported": "Download de múltiplos compartilhamentos ainda não é suportado - por favor, baixe os compartilhamentos individualmente" + } }, "shareMultipleFiles": { "title": "Compartilhar Múltiplos Arquivos", @@ -1918,6 +1909,66 @@ "inactive": "Inativo", "admin": "Administrador", "userr": "Usuário" + }, + "invite": { + "button": "Gerar Link de Convite", + "title": "Gerar Link de Convite de Usuário", + "description": "Gere um link de uso único que permite que alguém crie sua própria conta. O link expira em 15 minutos.", + "generating": "Gerando...", + "generate": "Gerar Link", + "generated": "Link gerado com sucesso!", + "linkReady": "Link de convite pronto", + "linkReadyDescription": "Compartilhe este link com a pessoa que você deseja convidar. Ela poderá criar sua própria conta como usuário regular.", + "copyLink": "Copiar Link", + "linkCopied": "Link de convite copiado para a área de transferência!", + "expiresIn": "Expira em 15 minutos", + "close": "Fechar", + "errors": { + "generateFailed": "Falha ao gerar link de convite" + } + } + }, + "registerWithInvite": { + "pageTitle": "Criar Conta", + "title": "Crie Sua Conta", + "description": "Preencha as informações abaixo para criar sua conta", + "labels": { + "firstName": "Nome", + "firstNamePlaceholder": "Digite seu nome", + "lastName": "Sobrenome", + "lastNamePlaceholder": "Digite seu sobrenome", + "username": "Nome de Usuário", + "usernamePlaceholder": "Escolha um nome de usuário", + "email": "E-mail", + "emailPlaceholder": "Digite seu e-mail", + "password": "Senha", + "passwordPlaceholder": "Escolha uma senha", + "confirmPassword": "Confirmar Senha", + "confirmPasswordPlaceholder": "Confirme sua senha" + }, + "buttons": { + "creating": "Criando conta...", + "createAccount": "Criar Conta" + }, + "validation": { + "firstNameRequired": "Nome é obrigatório", + "lastNameRequired": "Sobrenome é obrigatório", + "usernameMinLength": "Nome de usuário deve ter pelo menos 3 caracteres", + "invalidEmail": "E-mail inválido", + "passwordMinLength": "Senha deve ter pelo menos 8 caracteres", + "passwordsMatch": "As senhas devem coincidir" + }, + "messages": { + "success": "Conta criada com sucesso! Redirecionando para o login...", + "redirecting": "Redirecionando para o login..." + }, + "errors": { + "invalidToken": "Link de convite inválido ou expirado", + "tokenUsed": "Este link de convite já foi usado", + "tokenExpired": "Este link de convite expirou", + "usernameExists": "Nome de usuário já existe", + "emailExists": "E-mail já existe", + "createFailed": "Falha ao criar conta. Por favor, tente novamente." } }, "validation": { @@ -1936,15 +1987,15 @@ "usernameSpaces": "O nome de usuário não pode conter espaços" }, "embedCode": { - "title": "Incorporar imagem", - "description": "Use estes códigos para incorporar esta imagem em fóruns, sites ou outras plataformas", + "title": "Incorporar mídia", + "description": "Use estes códigos para incorporar esta mídia em fóruns, sites ou outras plataformas", "tabs": { "directLink": "Link direto", "html": "HTML", "bbcode": "BBCode" }, - "directLinkDescription": "URL direto para o arquivo de imagem", - "htmlDescription": "Use este código para incorporar a imagem em páginas HTML", - "bbcodeDescription": "Use este código para incorporar a imagem em fóruns que suportam BBCode" + "directLinkDescription": "URL direto para o arquivo de mídia", + "htmlDescription": "Use este código para incorporar a mídia em páginas HTML", + "bbcodeDescription": "Use este código para incorporar a mídia em fóruns que suportam BBCode" } } \ No newline at end of file diff --git a/apps/web/messages/ru-RU.json b/apps/web/messages/ru-RU.json index 6e9503f7..4090cd2d 100644 --- a/apps/web/messages/ru-RU.json +++ b/apps/web/messages/ru-RU.json @@ -6,7 +6,9 @@ "token_expired": "Token expirado. Tente novamente.", "config_error": "Erro de configuração. Contate o suporte.", "auth_failed": "Falha na autenticação. Tente novamente." - } + }, + "authenticationFailed": "Ошибка аутентификации", + "successfullyAuthenticated": "Успешно аутентифицирован!" }, "authProviders": { "title": "Провайдеры аутентификации", @@ -174,7 +176,14 @@ "tabs": { "shareDetails": "Детали общего доступа", "selectFiles": "Выбрать файлы" - } + }, + "errors": { + "nameRequired": "Требуется имя общего ресурса", + "selectItems": "Выберите хотя бы один файл или папку" + }, + "itemsSelected": "Выбрано элементов: {count}", + "passwordPlaceholder": "Введите пароль", + "selectItemsPrompt": "Выберите файлы и папки для общего доступа" }, "customization": { "breadcrumb": "Настройка", @@ -340,7 +349,8 @@ "addToShare": "Добавить в общий доступ", "removeFromShare": "Удалить из общего доступа", "saveChanges": "Сохранить Изменения", - "editFolder": "Редактировать папку" + "editFolder": "Редактировать папку", + "itemsSelected": "Выбрано элементов: {count}" }, "files": { "title": "Все файлы", @@ -376,7 +386,12 @@ "description": "Загрузите свой первый файл или создайте папку для начала работы" }, "files": "файлы", - "folders": "папки" + "folders": "папки", + "errors": { + "moveItemsFailed": "Не удалось переместить элементы. Попробуйте еще раз.", + "cannotMoveHere": "Невозможно переместить элементы в это место" + }, + "openFolder": "Открыть папку" }, "filesTable": { "ariaLabel": "Таблица файлов", @@ -539,7 +554,10 @@ "movingTo": "Перемещение в:", "title": "Переместить {count, plural, =1 {элемент} other {элементы}}", "description": "Переместить {count, plural, =1 {элемент} other {элементы}} в новое место", - "success": "Успешно перемещено {count} {count, plural, =1 {элемент} other {элементов}}" + "success": "Успешно перемещено {count} {count, plural, =1 {элемент} other {элементов}}", + "errors": { + "moveFailed": "Не удалось переместить элементы" + } }, "navbar": { "logoAlt": "Логотип приложения", @@ -1153,8 +1171,6 @@ }, "fileActions": { "edit": "Редактировать", - "save": "Сохранить", - "cancel": "Отмена", "preview": "Предпросмотр", "download": "Скачать", "delete": "Удалить", @@ -1377,16 +1393,6 @@ "deleteTitle": "Удалить Общий Доступ", "deleteConfirmation": "Вы уверены, что хотите удалить этот общий доступ? Это действие нельзя отменить.", "editTitle": "Редактировать Общий Доступ", - "nameLabel": "Название Общего Доступа", - "descriptionLabel": "Описание", - "descriptionPlaceholder": "Введите описание (опционально)", - "expirationLabel": "Дата Истечения", - "expirationPlaceholder": "ДД.ММ.ГГГГ ЧЧ:ММ", - "maxViewsLabel": "Максимальные Просмотры", - "maxViewsPlaceholder": "Оставьте пустым для неограниченного", - "passwordProtection": "Защищено Паролем", - "passwordLabel": "Пароль", - "passwordPlaceholder": "Введите пароль", "newPasswordLabel": "Новый Пароль (оставьте пустым, чтобы сохранить текущий)", "newPasswordPlaceholder": "Введите новый пароль", "manageFilesTitle": "Управление Файлами", @@ -1405,7 +1411,9 @@ "linkDescriptionFile": "Создайте пользовательскую ссылку для обмена файлом", "linkDescriptionFolder": "Создайте пользовательскую ссылку для обмена папкой", "linkReady": "Ваша ссылка для обмена готова:", - "linkTitle": "Создать ссылку" + "linkTitle": "Создать ссылку", + "itemsSelected": "Выбрано элементов: {count}", + "manageFilesDescription": "Выберите файлы и папки для включения в этот общий доступ" }, "shareDetails": { "title": "Детали Общего Доступа", @@ -1421,7 +1429,6 @@ "noLink": "Ссылка еще не сгенерирована", "copyLink": "Скопировать ссылку", "openLink": "Открыть в новой вкладке", - "linkCopied": "Ссылка скопирована в буфер обмена", "views": "Просмотры", "dates": "Даты", "created": "Создано", @@ -1469,28 +1476,6 @@ "expires": "Истекает:", "expirationDate": "Дата истечения" }, - "shareFile": { - "title": "Поделиться Файлом", - "linkTitle": "Сгенерировать Ссылку", - "nameLabel": "Название Общего Доступа", - "namePlaceholder": "Введите название общего доступа", - "descriptionLabel": "Описание", - "descriptionPlaceholder": "Введите описание (опционально)", - "expirationLabel": "Дата Истечения", - "expirationPlaceholder": "ДД.ММ.ГГГГ ЧЧ:ММ", - "maxViewsLabel": "Максимальные Просмотры", - "maxViewsPlaceholder": "Оставьте пустым для неограниченного", - "passwordProtection": "Защищено Паролем", - "passwordLabel": "Пароль", - "passwordPlaceholder": "Введите пароль", - "linkDescription": "Сгенерируйте пользовательскую ссылку для отправки файла", - "aliasLabel": "Псевдоним Ссылки", - "aliasPlaceholder": "Введите пользовательский псевдоним", - "linkReady": "Ваша ссылка для общего доступа готова:", - "createShare": "Создать Общий Доступ", - "generateLink": "Сгенерировать Ссылку", - "copyLink": "Скопировать Ссылку" - }, "shareManager": { "deleteSuccess": "Общий доступ успешно удален", "deleteError": "Ошибка при удалении общего доступа", @@ -1520,7 +1505,10 @@ "noFilesToDownload": "Нет файлов для скачивания", "singleShareZipName": "{shareme} _files.zip", "zipDownloadError": "Не удалось создать zip -файл", - "zipDownloadSuccess": "Zip -файл успешно загружен" + "zipDownloadSuccess": "Zip -файл успешно загружен", + "errors": { + "multipleDownloadNotSupported": "Множественная загрузка общих ресурсов пока не поддерживается - загружайте общие ресурсы по отдельности" + } }, "shareMultipleFiles": { "title": "Поделиться Несколькими Файлами", @@ -1917,6 +1905,23 @@ "inactive": "Неактивен", "admin": "Администратор", "userr": "Пользователь" + }, + "invite": { + "button": "Создать ссылку приглашения", + "title": "Создать ссылку приглашения пользователя", + "description": "Создайте одноразовую ссылку, которая позволит кому-то создать свою учетную запись. Ссылка истекает через 15 минут.", + "generating": "Генерация...", + "generate": "Создать ссылку", + "generated": "Ссылка приглашения успешно создана!", + "linkReady": "Ссылка приглашения готова", + "linkReadyDescription": "Поделитесь этой ссылкой с человеком, которого хотите пригласить. Они смогут создать свою учетную запись как обычный пользователь.", + "copyLink": "Копировать ссылку", + "linkCopied": "Ссылка приглашения скопирована в буфер обмена!", + "expiresIn": "Истекает через 15 минут", + "close": "Закрыть", + "errors": { + "generateFailed": "Не удалось создать ссылку приглашения" + } } }, "validation": { @@ -1935,15 +1940,62 @@ "required": "Это поле обязательно" }, "embedCode": { - "title": "Встроить изображение", - "description": "Используйте эти коды для встраивания этого изображения на форумах, веб-сайтах или других платформах", + "title": "Встроить медиа", + "description": "Используйте эти коды для встраивания этого медиа на форумах, веб-сайтах или других платформах", "tabs": { "directLink": "Прямая ссылка", "html": "HTML", "bbcode": "BBCode" }, - "directLinkDescription": "Прямой URL-адрес файла изображения", - "htmlDescription": "Используйте этот код для встраивания изображения в HTML-страницы", - "bbcodeDescription": "Используйте этот код для встраивания изображения на форумах, поддерживающих BBCode" + "directLinkDescription": "Прямой URL-адрес медиафайла", + "htmlDescription": "Используйте этот код для встраивания медиа в HTML-страницы", + "bbcodeDescription": "Используйте этот код для встраивания медиа на форумах, поддерживающих BBCode" + }, + "contextMenu": { + "newFolder": "Новая папка", + "uploadFile": "Загрузить файл" + }, + "registerWithInvite": { + "title": "Создайте свою учетную запись", + "description": "Заполните информацию ниже, чтобы создать свою учетную запись", + "labels": { + "firstName": "Имя", + "firstNamePlaceholder": "Введите ваше имя", + "lastName": "Фамилия", + "lastNamePlaceholder": "Введите вашу фамилию", + "username": "Имя пользователя", + "usernamePlaceholder": "Выберите имя пользователя", + "email": "Электронная почта", + "emailPlaceholder": "Введите вашу электронную почту", + "password": "Пароль", + "passwordPlaceholder": "Выберите пароль", + "confirmPassword": "Подтвердите пароль", + "confirmPasswordPlaceholder": "Подтвердите ваш пароль" + }, + "buttons": { + "creating": "Создание учетной записи...", + "createAccount": "Создать учетную запись" + }, + "validation": { + "firstNameRequired": "Имя обязательно", + "lastNameRequired": "Фамилия обязательна", + "usernameMinLength": "Имя пользователя должно содержать не менее 3 символов", + "invalidEmail": "Неверный адрес электронной почты", + "passwordMinLength": "Пароль должен содержать не менее 8 символов", + "passwordsMatch": "Пароли должны совпадать" + }, + "messages": { + "success": "Учетная запись успешно создана! Перенаправление на вход...", + "redirecting": "Перенаправление на вход..." + }, + "errors": { + "invalidToken": "Ошибка с ссылкой приглашения", + "tokenUsed": "Эта ссылка приглашения уже была использована", + "tokenExpired": "Срок действия этой ссылки приглашения истек", + "usernameExists": "Имя пользователя уже существует", + "emailExists": "Электронная почта уже существует", + "createFailed": "Не удалось создать учетную запись. Пожалуйста, попробуйте еще раз." + }, + "pageTitle": "Создать учетную запись" } } \ No newline at end of file diff --git a/apps/web/messages/sv-SE.json b/apps/web/messages/sv-SE.json new file mode 100644 index 00000000..91929dfa --- /dev/null +++ b/apps/web/messages/sv-SE.json @@ -0,0 +1,2001 @@ +{ + "auth": { + "successfullyAuthenticated": "Autentiserats framgångsrikt!", + "authenticationFailed": "Autentiseringen misslyckades", + "errors": { + "account_inactive": "Kontot är inaktivt. Kontakta administratören.", + "registration_disabled": "SSO-registrering är inaktiverad.", + "token_expired": "Token har löpt ut. Försök igen.", + "config_error": "Konfigurationsfel. Kontakta supporten.", + "auth_failed": "Autentiseringen misslyckades. Försök igen." + } + }, + "contextMenu": { + "newFolder": "Ny mapp", + "uploadFile": "Ladda upp fil" + }, + "authProviders": { + "title": "Autentiseringsleverantörer", + "description": "Konfigurera externa autentiseringsleverantörer för SSO", + "enabledCount": "{count} aktiverade", + "loadingProviders": "Laddar leverantörer...", + "providersConfigured": "{count} leverantörer konfigurerade", + "enabledOfTotal": "{enabled} aktiverade av {total} leverantörer", + "hideDisabledProviders": "Dölj funktionshindrade leverantörer", + "addProvider": "Lägg till leverantör", + "addProviderTitle": "Lägg till leverantör", + "editProvider": "Redigera leverantör", + "deleteProvider": "Ta bort leverantör", + "enabled": "Aktiverad", + "disabled": "Inaktiverad", + "officialProvider": "Officiell leverantör", + "dragToReorder": "Dra för att ändra ordning", + "dragDisabledMessage": "Dra och släpp är inaktiverat när du filtrerar leverantörer. Visa alla leverantörer för att ändra ordning på dem.", + "dragEnabledMessage": "Dra leverantörer för att ordna om dem. Denna beställning kommer att återspeglas på inloggningssidan.", + "noProvidersEnabled": "Inga aktiverade autentiseringsleverantörer", + "noProvidersConfigured": "Inga autentiseringsleverantörer har konfigurerats", + "form": { + "providerName": "Leverantörens namn", + "providerNamePlaceholder": "t.ex. mitt företag", + "displayName": "Visningsnamn", + "displayNamePlaceholder": "t.ex. Mitt företag SSO", + "type": "Typ", + "typeOidc": "OIDC (OpenID Connect)", + "typeOauth2": "OAuth 2.0", + "icon": "Ikon", + "iconPlaceholder": "Välj en ikon", + "clientId": "Klient-ID", + "clientIdPlaceholder": "Ditt OAuth-klient-ID", + "clientSecret": "Klienthemlighet", + "clientSecretPlaceholder": "Din OAuth-klienthemlighet", + "oauthScopes": "OAuth omfattningar", + "scopesPlaceholder": "Ange omfång (t.ex. openid, profil, e-post)", + "scopesHelpOidc": "Omfattningar automatiskt föreslagna baserat på leverantörens URL. Vanliga OIDC-omfång: openid, profil, e-post, grupper", + "scopesHelpOauth2": "Omfattningar automatiskt föreslagna baserat på leverantörens URL. Vanliga OAuth2-omfång beror på leverantören", + "providerUrl": "Leverantörs URL", + "providerUrlPlaceholder": "https://auth.example.com", + "providerUrlAutoPlaceholder": "https://din-leverantör.com (slutpunkter kommer att upptäckas automatiskt)", + "providerUrlManualPlaceholder": "https://din-leverantör.com", + "autoDiscoveryHelp": "Systemet kommer automatiskt att upptäcka auktoriserings-, token- och användarinfoslutpunkter", + "manualConfigurationHelp": "Bas-URL för din leverantör (slutpunkter kommer att vara relativa till detta)", + "authorizationEndpoint": "Auktoriseringsslutpunkt", + "authorizationEndpointPlaceholder": "https://auth.example.com/auth", + "tokenEndpoint": "Token Endpoint", + "tokenEndpointPlaceholder": "https://auth.example.com/token", + "userInfoEndpoint": "Användarinfo Endpoint", + "userInfoEndpointPlaceholder": "https://auth.example.com/userinfo", + "configurationMethod": "Konfigurationsmetod", + "autoDiscovery": "Automatisk upptäckt", + "autoDiscoveryDescription": "Upptäck automatiskt slutpunkter från leverantörens URL", + "manualEndpoints": "Manuella slutpunkter (rekommenderas)", + "manualEndpointsDescription": "Konfigurera auktoriserings-, token- och användarinformationsslutpunkter manuellt", + "callbackUrl": "Callback URL", + "callbackUrlDescription": "Använd den här webbadressen i din OAuth-leverantörskonfiguration", + "copyCallbackUrl": "Kopiera Callback URL", + "callbackUrlCopied": "Återuppringnings-URL kopierad till urklipp!", + "adminEmailDomains": "Admin e-postdomäner", + "adminEmailDomainsPlaceholder": "Ange domäner (t.ex. admin.company.com)", + "adminEmailDomainsHelp": "Användare med e-postmeddelanden från dessa domäner kommer att tilldelas administratörsbehörighet", + "autoRegister": "Registrera nya användare automatiskt", + "officialProviderUrlPlaceholder": "Ersätt platshållare med din {displayName} URL", + "officialProviderHelp": "Detta är en officiell leverantör. Slutpunkter är förkonfigurerade. Du kan bara redigera denna URL.", + "officialProviderIconHelp": "Du kan anpassa ikonen för denna officiella leverantör." + }, + "buttons": { + "cancel": "Avboka", + "save": "Spara", + "saving": "Sparande...", + "adding": "Lägger till...", + "updating": "Uppdaterar...", + "saveProvider": "Spara leverantör", + "delete": "Radera", + "deleting": "Tar bort...", + "edit": "Redigera", + "enable": "Aktivera", + "disable": "Inaktivera" + }, + "messages": { + "providerAdded": "Leverantören har lagts till", + "providerUpdated": "Leverantören har uppdaterats", + "providerDeleted": "Provider har tagits bort", + "providerOrderUpdated": "Leverantörens beställning har uppdaterats", + "fillRequiredFields": "Vänligen fyll i alla obligatoriska fält (namn, visningsnamn, klient-ID, klienthemlighet)", + "provideUrlOrEndpoints": "Ange antingen en leverantörs-URL för automatisk upptäckt ELLER alla tre anpassade slutpunkter", + "chooseDiscoveryOrManual": "Välj antingen automatisk upptäckt (Provider URL) ELLER manuella slutpunkter, inte båda", + "loadFailed": "Det gick inte att läsa in leverantörer", + "addFailed": "Det gick inte att lägga till leverantör", + "updateFailed": "Det gick inte att uppdatera leverantören", + "deleteFailed": "Det gick inte att ta bort leverantören", + "orderUpdateFailed": "Det gick inte att uppdatera leverantörsbeställningen" + }, + "info": { + "title": "Information", + "officialProvidersRecommended": "För bättre funktionalitet, överväg att använda officiella leverantörer. Om du har problem med en anpassad leverantör, överväg att öppna ett problem på", + "github": "GitHub", + "officialProvider": "Officiell leverantör", + "officialProviderDescription": "Denna leverantör är optimerad av Palmr. Endast inloggningsuppgifter och konfiguration kan ändras.", + "manualConfigTitle": "Manuell konfiguration", + "manualConfigDescription": "Du tillhandahåller alla slutpunkter manuellt. Se till att de är korrekta för din leverantör." + }, + "deleteModal": { + "title": "Ta bort autentiseringsleverantör", + "confirmMessage": "Är du säker på att du vill ta bort leverantören \"{displayName}\"? Denna åtgärd kan inte ångras.", + "providerId": "Provider ID: {name}", + "cancel": "Avboka", + "delete": "Ta bort leverantör", + "deleting": "Tar bort..." + } + }, + "bulkDownload": { + "title": "Massnedladdning", + "zipNameLabel": "ZIP-filnamn", + "zipNamePlaceholder": "Ange filnamn", + "description": "{count, plural, =1 {1 fil kommer att komprimeras} other {# filer kommer att komprimeras}}", + "download": "Ladda ner ZIP" + }, + "common": { + "loading": "Laddar, vänta...", + "loadingSimple": "Belastning...", + "cancel": "Avboka", + "save": "Spara", + "saving": "Sparande...", + "update": "Uppdatera", + "updating": "Uppdaterar...", + "delete": "Radera", + "deleting": "Tar bort...", + "close": "Nära", + "download": "Ladda ner", + "unexpectedError": "Ett oväntat fel inträffade. Försök igen.", + "yes": "Ja", + "no": "Inga", + "dashboard": "Instrumentbräda", + "back": "Tillbaka", + "click": "Klicka för att", + "creating": "Skapande...", + "create": "Skapa", + "rename": "Döpa om", + "move": "Flytta", + "share": "Dela", + "search": "Söka", + "copy": "Kopiera", + "copied": "Kopierade" + }, + "createShare": { + "title": "Skapa Share", + "nameLabel": "Dela namn", + "namePlaceholder": "Ange ett namn för din del", + "descriptionLabel": "Beskrivning", + "descriptionPlaceholder": "Ange en beskrivning (valfritt)", + "expirationLabel": "Utgångsdatum", + "expirationPlaceholder": "MM/DD/ÅÅÅÅ TT:MM", + "maxViewsLabel": "Max visningar", + "maxViewsPlaceholder": "Lämna tomt för obegränsat", + "passwordProtection": "Lösenordsskyddad", + "passwordLabel": "Lösenord", + "passwordPlaceholder": "Ange lösenord", + "create": "Skapa Share", + "success": "Delningen har skapats", + "error": "Det gick inte att skapa delning", + "errors": { + "nameRequired": "Delningsnamn krävs", + "selectItems": "Välj minst en fil eller mapp" + }, + "itemsSelected": "{count} objekt valda", + "selectItemsPrompt": "Välj filer och mappar att dela", + "tabs": { + "shareDetails": "Delningsdetaljer", + "selectFiles": "Välj Filer" + }, + "nextSelectFiles": "Nästa: Välj Filer", + "searchLabel": "Söka" + }, + "customization": { + "breadcrumb": "Anpassning", + "colors": { + "title": "Tema färger", + "description": "Välj ditt föredragna primära färgtema", + "presets": "Tillgängliga färger", + "presetsDescription": "Välj bland tillgängliga färgteman", + "reset": "Återställ till standard" + }, + "fonts": { + "title": "Typografi", + "description": "Välj önskad typsnittsfamilj", + "available": "Tillgängliga teckensnitt", + "availableDescription": "Välj bland tillgängliga teckensnittsfamiljer", + "reset": "Återställ till standard" + }, + "radius": { + "title": "Gränsradie", + "description": "Anpassa rundheten hos gränssnittselement", + "available": "Avrundningsalternativ", + "availableDescription": "Välj hur rundade hörn ska se ut", + "reset": "Återställ till standard" + }, + "background": { + "title": "Bakgrundsfärger", + "description": "Anpassa bakgrundsfärger för ljusa och mörka lägen", + "lightMode": "Ljusläge", + "darkMode": "Mörkt läge", + "availableDescription": "Välj bakgrundsfärger för både ljusa och mörka teman", + "reset": "Återställ till standard" + }, + "theme": { + "title": "Temaläge", + "description": "Välj mellan ljust, mörkt eller systemtema", + "selectTheme": "Temapreferens", + "availableDescription": "Välj ditt föredragna temaläge", + "reset": "Återställ till system" + }, + "pageTitle": "Anpassning" + }, + "dashboard": { + "loadError": "Det gick inte att läsa in instrumentpanelsdata", + "linkCopied": "Länken har kopierats till urklipp", + "pageTitle": "Instrumentbräda", + "breadcrumb": "Instrumentbräda", + "recentFiles": { + "title": "Senaste filer", + "description": "Dina senast uppladdade filer" + } + }, + "deleteConfirmation": { + "filesToDelete": "Filer som ska raderas", + "foldersToDelete": "Mappar som ska raderas", + "itemsToDelete": "Objekt som ska raderas", + "sharesToDelete": "Aktier som ska raderas" + }, + "downloadQueue": { + "downloadQueued": "Nedladdning köad: {fileName}", + "queuedDescription": "Din nedladdning startar automatiskt när en slot blir tillgänglig", + "queuePosition": "Nedladdning köad på position {position}: {fileName}", + "estimatedWait": "Uppskattad väntetid: {time}", + "queueFull": "Nedladdningskön är full", + "queueFullDescription": "Försök igen om några minuter när kön har plats", + "cancelSuccess": "Nedladdningen avbröts", + "cancelError": "Misslyckades att avbryta nedladdning: {error}", + "status": { + "pending": "Förbereder...", + "queued": "I kö", + "downloading": "Laddar ner", + "completed": "Avslutad", + "failed": "Misslyckades" + }, + "waitTime": { + "seconds": "{seconds}s", + "minutes": "{minutes}m", + "hoursMinutes": "{hours}h {minutes}m" + }, + "indicator": { + "title": "Nedladdningar", + "downloads": "Ladda ner kö", + "active": "Aktiv", + "queued": "I kö", + "position": "Position {position}", + "estimatedWait": "Wait: {time}", + "unknownFile": "Okänd fil", + "noDownloads": "Inga nedladdningar pågår", + "refresh": "Uppdatera kö" + } + }, + "emptyState": { + "noFiles": "Inga filer har laddats upp ännu", + "uploadFile": "Ladda upp fil" + }, + "errors": { + "invalidCredentials": "Ogiltig e-postadress eller lösenord", + "userNotFound": "Användaren hittades inte", + "accountLocked": "Konto låst. Försök igen senare", + "unexpectedError": "Ett oväntat fel inträffade. Försök igen", + "Invalid verification code": "Ogiltig verifieringskod", + "Two-factor authentication is already enabled": "Tvåfaktorsautentisering är redan aktiverad", + "Two-factor authentication is not enabled": "Tvåfaktorsautentisering är inte aktiverad", + "Invalid password": "Ogiltigt lösenord", + "Password verification required": "Lösenordsverifiering krävs", + "Invalid two-factor authentication code": "Ogiltig tvåfaktorsautentiseringskod", + "Two-factor authentication required": "Tvåfaktorsautentisering krävs", + "noUserData": "Inga användardata" + }, + "fileActions": { + "editFile": "Redigera fil", + "nameLabel": "Namn", + "namePlaceholder": "Ange nytt namn", + "extension": "Förlängning", + "descriptionLabel": "Beskrivning", + "descriptionPlaceholder": "Ange filbeskrivning", + "addDescriptionPlaceholder": "Lägg till beskrivning...", + "deleteFile": "Ta bort fil", + "deleteConfirmation": "Är du säker på att du vill ta bort den här filen?", + "deleteWarning": "Denna åtgärd kan inte ångras." + }, + "fileManager": { + "downloadError": "Det gick inte att ladda ned filen", + "updateSuccess": "Filen har uppdaterats", + "updateError": "Det gick inte att uppdatera filen", + "deleteSuccess": "Filen har raderats", + "deleteError": "Det gick inte att ta bort filen" + }, + "filePreview": { + "title": "Förhandsgranska fil", + "description": "Förhandsgranska och ladda ner fil", + "loading": "Belastning...", + "notAvailable": "Förhandsgranskning är inte tillgänglig för den här filtypen", + "downloadToView": "Använd nedladdningsknappen för att se den här filen", + "loadError": "Det gick inte att läsa in förhandsgranskningen av filen", + "downloadError": "Fel vid nedladdning av filen", + "audioNotSupported": "Din webbläsare stöder inte ljuduppspelning", + "videoNotSupported": "Din webbläsare stöder inte videouppspelning", + "pdfPreviewNotAvailable": "PDF-förhandsgranskning är inte tillgänglig. Prova alternativ vy eller ladda ner", + "tryAlternativeView": "Prova Alternativ vy", + "loadingAlternative": "Laddar alternativ vy...", + "loadingAudio": "Laddar ljud..." + }, + "fileSelector": { + "availableFiles": "Tillgängliga filer ({count})", + "shareFiles": "Delade filer ({count})", + "shareFilesDescription": "Filer för närvarande i denna del", + "availableFilesDescription": "Välj filer att lägga till i den här resursen", + "searchPlaceholder": "Sök efter filer...", + "searchSelectedFiles": "Sök efter valda filer...", + "noMatchingFiles": "Inga matchande filer hittades", + "noAvailableFiles": "Inga filer tillgängliga", + "noFilesInShare": "Inga filer i den här resursen", + "noFilesFound": "Inga filer hittades", + "noFilesFoundWith": "Inga filer hittades som matchar \"{query}\"", + "addFilesFromList": "Lägg till filer från listan nedan", + "tryDifferentSearch": "Prova olika söktermer", + "allFilesInShare": "Alla filer finns redan i den här resursen", + "uploadNewFiles": "Ladda upp nya filer för att lägga till dem", + "fileCount": "{count, plural, =1 {fil} other {filer}}", + "filesSelected": "{count, plural, =0 {Inga filer valda} =1 {1 fil vald} other {# filer valda}}", + "itemsSelected": "{count} objekt valda", + "editFile": "Redigera fil", + "editFolder": "Redigera mapp", + "previewFile": "Förhandsgranska filen", + "addToShare": "Lägg till för att dela", + "removeFromShare": "Ta bort från delningen", + "saveChanges": "Spara ändringar" + }, + "files": { + "title": "Alla filer", + "uploadFile": "Ladda upp fil", + "loadError": "Det gick inte att ladda filer", + "pageTitle": "Mina filer", + "breadcrumb": "Mina filer", + "downloadStart": "Nedladdningen startade", + "downloadError": "Det gick inte att ladda ned filen", + "updateSuccess": "Filen har uppdaterats", + "updateError": "Det gick inte att uppdatera filen", + "deleteSuccess": "Filen har raderats", + "deleteError": "Det gick inte att ta bort filen", + "bulkDownloadSuccess": "Filnedladdningen startade", + "bulkDownloadError": "Det gick inte att skapa ZIP-fil", + "bulkDownloadFileError": "Fel vid nedladdning av fil {fileName}", + "bulkDeleteSuccess": "{count, plural, =1 {1 objekt borttaget framgångsrikt} other {# objekt borttagna framgångsrikt}}", + "bulkDeleteError": "Det gick inte att ta bort markerade objekt", + "bulkDeleteTitle": "Ta bort markerade objekt", + "bulkDeleteConfirmation": "Är du säker på att du vill ta bort {count, plural, =1 {1 objekt} other {# objekt}}? Denna åtgärd kan inte ångras.", + "totalFiles": "{count, plural, =0 {Inga filer} =1 {1 fil} other {# filer}}", + "openFolder": "Öppna mappen", + "errors": { + "moveItemsFailed": "Det gick inte att flytta objekt. Försök igen.", + "cannotMoveHere": "Det går inte att flytta objekt till den här platsen" + }, + "empty": { + "title": "Inga filer eller mappar ännu", + "description": "Ladda upp din första fil eller skapa en mapp för att komma igång" + }, + "files": "filer", + "folders": "mappar", + "actions": { + "open": "Öppna", + "rename": "Döpa om", + "delete": "Radera" + }, + "viewMode": { + "table": "Tabell", + "grid": "Rutnät" + } + }, + "filesTable": { + "ariaLabel": "Filer tabell", + "selectAll": "Välj alla", + "selectFile": "Välj fil {fileName}", + "columns": { + "name": "NAMN", + "description": "BESKRIVNING", + "size": "STORLEK", + "createdAt": "SKAPAD KL", + "updatedAt": "UPPDATERAD KL", + "actions": "ÅTGÄRDER" + }, + "actions": { + "menu": "Menyn Arkivåtgärder", + "preview": "Förhandsvisning", + "edit": "Redigera", + "share": "Dela", + "download": "Ladda ner", + "delete": "Radera" + }, + "bulkActions": { + "selected": "{count, plural, =1 {1 fil vald} other {# filer valda}}", + "actions": "Åtgärder", + "download": "Ladda ner valt", + "share": "Dela valt", + "delete": "Ta bort markerade" + } + }, + "folderActions": { + "editFolder": "Redigera mapp", + "folderName": "Mappnamn", + "folderNamePlaceholder": "Ange mappnamn", + "folderDescription": "Beskrivning", + "folderDescriptionPlaceholder": "Ange mappbeskrivning (valfritt)", + "createFolder": "Skapa mapp", + "renameFolder": "Byt namn på mapp", + "moveFolder": "Flytta mapp", + "shareFolder": "Dela mapp", + "deleteFolder": "Ta bort mapp", + "moveTo": "Flytta till", + "selectDestination": "Välj målmapp", + "rootFolder": "Rot", + "folderCreated": "Mappen har skapats", + "folderRenamed": "Mappen har bytt namn", + "folderMoved": "Mappen har flyttats", + "folderDeleted": "Mappen har raderats", + "folderShared": "Mappen har delats", + "createFolderError": "Det gick inte att skapa mapp", + "renameFolderError": "Det gick inte att byta namn på mappen", + "moveFolderError": "Det gick inte att flytta mappen", + "deleteFolderError": "Det gick inte att ta bort mappen", + "shareFolderError": "Fel vid delning av mapp", + "deleteConfirmation": "Är du säker på att du vill ta bort den här mappen?", + "deleteWarning": "Denna åtgärd kan inte ångras." + }, + "footer": { + "poweredBy": "Drivs av", + "kyanHomepage": "Kyantechs hemsida" + }, + "forgotPassword": { + "emailLabel": "E-postadress", + "emailPlaceholder": "Ange din e-postadress", + "sending": "Sändning...", + "submit": "Skicka återställningsinstruktioner", + "backToLogin": "Tillbaka till inloggning", + "title": "Glömt lösenord", + "description": "Ange din e-postadress så skickar vi instruktioner för att återställa ditt lösenord", + "resetInstructions": "Återställ instruktioner som skickats till din e-post", + "pageTitle": "Glömt lösenord", + "passwordAuthDisabled": "Lösenordsautentisering är inaktiverad. Kontakta din administratör eller använd en extern autentiseringsleverantör." + }, + "generateShareLink": { + "generateTitle": "Skapa delningslänk", + "updateTitle": "Uppdatera delningslänk", + "generateDescription": "Skapa en anpassad länk för den här andelen. Du kan anpassa webbadressen för att göra den mer minnesvärd.", + "updateDescription": "Uppdatera den anpassade länken för denna del. Du kan anpassa webbadressen för att göra den mer minnesvärd.", + "aliasPlaceholder": "Anpassat ID för länk", + "linkReady": "Din delningslänk är klar. Du kan kopiera det nu.", + "readyDescription": "Din delningslänk är klar. Du kan skanna QR-koden direkt, ladda ner den för senare användning eller kopiera länken nedan.", + "generateButton": "Skapa länk", + "updateButton": "Uppdatera länk", + "copyButton": "Kopiera länk", + "success": "Länken har skapats", + "error": "Det gick inte att skapa länk", + "copied": "Länken har kopierats till urklipp", + "tabs": { + "link": "Länk", + "qrcode": "QR-kod" + } + }, + "home": { + "description": "Alternativet med öppen källkod till WeTransfer. Dela filer säkert, utan spårning eller begränsningar.", + "documentation": "Dokumentation", + "starOnGithub": "Stjärna på GitHub", + "privacyMessage": "Byggd med integritet i åtanke. Dina filer är endast tillgängliga för de med delningslänken före uppladdning. För alltid gratis och öppen källkod.", + "header": { + "fileSharing": "Fildelning", + "tagline": "gjort enkelt och gratis" + }, + "pageTitle": "Hem" + }, + "iconPicker": { + "title": "Välj ikon", + "placeholder": "Välj en ikon", + "searchPlaceholder": "Sökikoner...", + "loadingMore": "Läser in fler ikoner...", + "allIconsLoaded": "Alla {count} ikoner laddade", + "noIconsFound": "Inga ikoner hittades för \"{search}\"", + "tabs": { + "all": "Alla ikoner", + "popular": "Populär", + "auth": "Auth-leverantörer" + }, + "stats": "{iconCount} ikoner från {libraryCount} bibliotek", + "categoryBadge": "{category} ({count} ikoner)" + }, + "imageEdit": { + "title": "Redigera bild", + "rotate": "Rotera", + "zoom": "Zoom", + "cropInstructions": "Dra för att flytta om, ändra storlek på hörn för att justera beskärningsområdet" + }, + "login": { + "welcome": "Välkommen till", + "signInToContinue": "Logga in för att fortsätta", + "emailOrUsernameLabel": "E-post eller användarnamn", + "emailOrUsernamePlaceholder": "Ange din e-postadress eller ditt användarnamn", + "emailLabel": "E-postadress", + "emailPlaceholder": "Ange din e-postadress", + "passwordLabel": "Lösenord", + "passwordPlaceholder": "Ange ditt lösenord", + "signIn": "Logga in", + "signingIn": "Loggar in...", + "forgotPassword": "Glömt lösenordet?", + "pageTitle": "Inloggning", + "or": "eller", + "continueWithSSO": "Fortsätt med SSO", + "processing": "Bearbetar autentisering..." + }, + "logo": { + "labels": { + "appLogo": "Applogotyp" + }, + "buttons": { + "upload": "Ladda upp logotyp", + "remove": "Ta bort logotyp" + }, + "messages": { + "uploadSuccess": "Logotypen har laddats upp", + "removeSuccess": "Logotypen har tagits bort" + }, + "errors": { + "uploadFailed": "Det gick inte att ladda upp logotypen", + "removeFailed": "Det gick inte att ta bort logotypen" + } + }, + "moveItems": { + "itemsToMove": "Föremål att flytta:", + "movingTo": "Flytta till:", + "title": "Flytta {count, plural, =1 {objekt} other {objekt}}", + "description": "Flytta {count, plural, =1 {objekt} other {objekt}} till en ny plats", + "success": "Flyttade {count} {count, plural, =1 {objekt} other {objekt}} framgångsrikt", + "errors": { + "moveFailed": "Det gick inte att flytta objekt" + } + }, + "navbar": { + "logoAlt": "Applogotyp", + "profileMenu": "Profilmeny", + "profile": "Profil", + "customization": "Anpassning", + "settings": "Inställningar", + "usersManagement": "Användarhantering", + "logout": "Logga ut" + }, + "navigation": { + "dashboard": "Instrumentbräda" + }, + "notifications": { + "permissionGranted": "Nedladdningsaviseringar aktiverade", + "permissionDenied": "Nedladdningsaviseringar inaktiverade", + "downloadComplete": { + "title": "Nedladdningen är klar", + "body": "{fileName} har slutfört nedladdningen" + }, + "downloadFailed": { + "title": "Nedladdningen misslyckades", + "body": "Misslyckades att ladda ner {fileName}: {error}", + "unknownError": "Okänt fel" + }, + "queueProcessing": { + "title": "Ladda ner startar", + "body": "{fileName} laddas nu ner{position}", + "position": " (var #{position} i kön)" + } + }, + "profile": { + "password": { + "title": "Ändra lösenord", + "newPassword": "Nytt lösenord", + "confirmPassword": "Bekräfta nytt lösenord", + "updateButton": "Uppdatera lösenord" + }, + "form": { + "title": "Profilinformation", + "firstName": "Förnamn", + "lastName": "Efternamn", + "username": "Användarnamn", + "email": "E-post", + "updateButton": "Uppdatera profil" + }, + "header": { + "title": "Profil", + "subtitle": "Hantera din personliga information och lösenord" + }, + "picture": { + "title": "Profilbild", + "description": "Klicka på kameraikonen för att ändra din profilbild", + "uploadPhoto": "Ladda upp foto", + "removePhoto": "Ta bort foto" + }, + "errors": { + "loadFailed": "Det gick inte att läsa in användardata", + "updateFailed": "Det gick inte att uppdatera profilen", + "passwordFailed": "Det gick inte att uppdatera lösenordet", + "imageFailed": "Det gick inte att uppdatera profilbilden", + "imageRemoveFailed": "Det gick inte att ta bort profilbilden" + }, + "messages": { + "noChanges": "Inga ändringar att spara", + "updateSuccess": "Profilen har uppdaterats", + "fillPasswords": "Vänligen fyll i båda lösenordsfälten", + "passwordSuccess": "Lösenordet har uppdaterats", + "imageSuccess": "Profilbilden har uppdaterats", + "imageRemoved": "Profilbilden har tagits bort" + }, + "pageTitle": "Profil" + }, + "qrCodeModal": { + "title": "Dela QR-kod", + "description": "Skanna den här QR-koden för att komma åt länken.", + "download": "Ladda ner QR-kod" + }, + "quickAccess": { + "files": { + "title": "Mina filer", + "description": "Få åtkomst till och hantera dina uppladdade filer" + }, + "shares": { + "title": "Mina aktier", + "description": "Visa och hantera dina delade filer" + }, + "reverseShares": { + "title": "Ta emot filer", + "description": "Skapa länkar så att andra kan skicka filer till dig" + } + }, + "recentFiles": { + "title": "Senaste uppladdningar", + "viewAll": "Visa alla", + "upload": "Ladda upp", + "uploadFile": "Ladda upp fil", + "noFiles": "Inga filer har laddats upp ännu" + }, + "recentShares": { + "title": "Senaste aktier", + "viewAll": "Visa alla", + "createShare": "Skapa Share", + "noShares": "Inga delningar har skapats ännu", + "createFirst": "Skapa din första delning" + }, + "recipientSelector": { + "emailPlaceholder": "Ange mottagarens e-postadress", + "add": "Tillägga", + "recipients": "Recipients ({count})", + "notifyAll": "Meddela alla", + "noRecipients": "Inga mottagare har lagts till ännu", + "addSuccess": "Mottagare har lagts till", + "addError": "Det gick inte att lägga till mottagare", + "removeSuccess": "Mottagaren har tagits bort", + "removeError": "Det gick inte att ta bort mottagaren", + "sendingNotifications": "Skickar aviseringar...", + "notifySuccess": "Mottagarna har meddelats", + "notifyError": "Det gick inte att meddela mottagarna", + "selectAll": "Välj alla", + "selectedCount": "{count} valda", + "selectRecipient": "Välj {email}", + "notifySelected": "Meddela valda", + "removeSelected": "Ta bort markerade", + "notifySingle": "Meddela denna mottagare", + "removeSingle": "Ta bort den här mottagaren", + "bulkRemoveSuccess": "{count} mottagare borttagna framgångsrikt", + "bulkRemoveError": "Det gick inte att ta bort valda mottagare", + "bulkNotifySuccess": "Aviseringar skickade till {count} mottagare", + "bulkNotifyError": "Det gick inte att meddela valda mottagare", + "singleNotifySuccess": "Avisering skickad till {email}", + "singleNotifyError": "Det gick inte att meddela mottagaren", + "modalDescription": "Lägg till och hantera mottagare för den här andelen. Du kan meddela alla eller specifika mottagare när SMTP är konfigurerat.", + "addRecipient": "Lägg till mottagare", + "invalidEmail": "Vänligen ange en giltig e-postadress", + "duplicateEmail": "Den här mottagaren har redan lagts till", + "noRecipientsDescription": "Lägg till mottagare för att dela detta innehåll via e-post" + }, + "register": { + "validation": { + "firstNameRequired": "Förnamn krävs", + "lastNameRequired": "Efternamn krävs", + "usernameMinLength": "Användarnamnet måste bestå av minst 3 tecken", + "invalidEmail": "Ogiltig e-postadress", + "passwordMinLength": "Lösenordet måste vara minst 8 tecken", + "success": "Administratörsanvändare skapades framgångsrikt!", + "error": "Det gick inte att skapa administratörsanvändare" + }, + "labels": { + "firstName": "Förnamn", + "lastName": "Efternamn", + "username": "Användarnamn", + "email": "E-post", + "password": "Lösenord" + }, + "buttons": { + "creating": "Skapande...", + "createAdmin": "Skapa ett administratörskonto" + } + }, + "resetPassword": { + "pageTitle": "Återställ lösenord", + "header": { + "title": "Återställ lösenord", + "description": "Ange ditt nya lösenord nedan" + }, + "form": { + "newPassword": "Nytt lösenord", + "newPasswordPlaceholder": "Ange ditt nya lösenord", + "confirmPassword": "Bekräfta nytt lösenord", + "confirmPasswordPlaceholder": "Bekräfta ditt nya lösenord", + "resetting": "Återställer lösenord...", + "submit": "Återställ lösenord", + "backToLogin": "Tillbaka till inloggning" + }, + "messages": { + "success": "Lösenordsåterställning lyckades" + }, + "errors": { + "serverError": "Det gick inte att återställa lösenordet. Försök igen.", + "invalidToken": "Ogiltig eller saknad återställningstoken" + } + }, + "reverseShares": { + "pageTitle": "Ta emot filer", + "search": { + "title": "Hantera mottagningslänkar", + "createButton": "Skapa länk", + "placeholder": "Sök ta emot länkar...", + "results": "Hittade {filtered} av {total} mottagningslänkar" + }, + "labels": { + "files": "filer", + "size": "storlek", + "status": "status", + "access": "tillträde", + "description": "Beskrivning", + "pageLayout": "Sidlayout", + "security": "Säkerhet och status", + "limits": "Gränser", + "maxFiles": "Maximalt antal filer", + "maxFileSize": "Maximal storlek", + "allowedTypes": "Tillåtna typer", + "filesReceived": "Filer mottagna", + "fileLimit": "Filgräns", + "noLimit": "Ingen gräns", + "noLinkCreated": "Ingen länk skapad", + "publicAccess": "Allmän tillgång", + "protectedByPassword": "Lösenordsskyddad", + "configureProtection": "Klicka för att konfigurera skydd", + "enterPassword": "Ange lösenord", + "thisLinkProtected": "Denna länk kommer att vara lösenordsskyddad", + "thisLinkPublic": "Denna länk kommer att vara allmänt tillgänglig", + "configureExpiration": "Konfigurera utgångsdatum", + "configureLimits": "Konfigurera filgränser", + "protectWithPassword": "Skydda med lösenord", + "layoutOptions": { + "default": "Standard", + "wetransfer": "WeTransfer" + }, + "noFilesLimit": "Ingen filgräns", + "noSizeLimit": "Ingen storleksbegränsning", + "allFileTypes": "Alla filtyper", + "fileTypesHelp": "Ange tillägg utan prickar, åtskilda av mellanslag, komma, streck eller rör", + "fieldRequirements": "Fältkrav", + "nameFieldRequired": "Namnfält", + "emailFieldRequired": "E-postfält", + "fieldOptions": { + "hidden": "Dold", + "optional": "Frivillig", + "required": "Nödvändig" + } + }, + "card": { + "untitled": "Namnlös länk", + "noDescription": "Ingen beskrivning", + "addDescriptionPlaceholder": "Lägg till beskrivning...", + "files": "filer", + "progress": "Framsteg", + "created": "Skapad", + "expired": "Utgått", + "expires": "Upphör att gälla", + "viewDetails": "Visa detaljer", + "viewQrCode": "Se QR-koden", + "copyLink": "Kopiera länk", + "openInNewTab": "Öppna i Ny flik", + "editLink": "Redigera länk", + "createLink": "Skapa länk", + "delete": "Radera", + "copyLinkTitle": "Kopiera länk", + "createLinkCTA": "Skapa mottagningslänk" + }, + "status": { + "active": "Aktiv", + "inactive": "Inaktiv", + "expired": "Utgått", + "protected": "Skyddad", + "public": "Offentlig" + }, + "actions": { + "copyLink": "Kopiera länk", + "editAlias": "Redigera Alias", + "createAlias": "Skapa Alias", + "viewDetails": "Visa detaljer", + "edit": "Redigera", + "delete": "Radera", + "viewFiles": "Mottagna filer", + "viewQrCode": "Se QR-koden" + }, + "empty": { + "title": "Inga mottagningslänkar skapade", + "description": "Skapa anpassade länkar så att andra kan skicka filer direkt till dig säkert och organiserat.", + "createButton": "Skapa första länken" + }, + "modals": { + "create": { + "title": "Skapa mottagningslänk", + "description": "Konfigurera en anpassad länk för att ta emot filer från andra" + }, + "edit": { + "title": "Redigera ta emot länk", + "description": "Uppdatera inställningarna för denna mottagningslänk", + "updating": "Uppdaterar...", + "saveChanges": "Spara ändringar" + }, + "details": { + "title": "Länkdetaljer", + "description": "Visa och redigera din mottagna länkinformation", + "pageLayout": "Sidlayout", + "linkSection": "Ta emot länk", + "noLinkCreated": "Ingen länk skapad", + "limits": "Gränser", + "maxFiles": "Maximalt antal filer", + "maxFileSize": "Maximal storlek", + "allowedTypes": "Tillåtna typer", + "noLimit": "Ingen gräns", + "security": "Säkerhet", + "status": "Status", + "password": "Lösenord", + "files": "Mottagna filer", + "noFiles": "Inga filer har tagits emot ännu", + "copyLink": "Kopiera länk", + "openLink": "Öppna länken", + "editAlias": "Redigera Alias", + "createAlias": "Skapa Alias", + "editPassword": "Redigera lösenordsskydd", + "basicInfo": "Grundläggande information", + "securityAndStatus": "Säkerhet och status", + "protection": "Skydd", + "protectedByPassword": "Lösenordsskyddad", + "publicAccess": "Allmän tillgång", + "active": "Aktiv", + "inactive": "Inaktiv", + "deactivate": "Avaktivera", + "activate": "Aktivera", + "expiration": "Utgång", + "dates": "Datum", + "createdAt": "Skapad på", + "updatedAt": "Uppdaterad kl", + "allTypes": "Alla typer", + "placeholderTypes": ".pdf,.jpg,.png (kommaseparerad)", + "downloadSuccess": "Nedladdningen startade", + "downloadError": "Fel vid nedladdning av filen", + "editSuccess": "Filen har uppdaterats", + "editError": "Fel vid uppdatering av filen", + "previewNotAvailable": "Förhandsgranskning inte tillgänglig", + "notAvailable": "Ej tillgängligt", + "invalidDate": "Ogiltigt datum" + }, + "alias": { + "editTitle": "Redigera Alias", + "createTitle": "Skapa Alias", + "editDescription": "Uppdatera aliaset för denna mottagningslänk", + "createDescription": "Skapa ett anpassat alias för denna mottagningslänk", + "aliasLabel": "Länkalias", + "aliasPlaceholder": "min-anpassade-länk", + "preview": "Förhandsvisning:", + "currentLink": "Aktuell länk:", + "copyCurrentLink": "Kopiera aktuell länk", + "randomTooltip": "Generera slumpmässigt alias", + "cancel": "Avboka", + "creating": "Skapande...", + "updating": "Uppdaterar...", + "create": "Skapa Alias", + "update": "Uppdatera Alias", + "validation": { + "required": "Alias ​​krävs", + "minLength": "Alias ​​måste bestå av minst 3 tecken", + "maxLength": "Alias ​​får vara högst 50 tecken", + "pattern": "Alias ​​får endast innehålla bokstäver, siffror, bindestreck och understreck" + }, + "help": "3-50 tecken. Mellanslag konverteras automatiskt till bindestreck." + }, + "password": { + "title": "Redigera lösenordsskydd", + "description": "Konfigurera lösenordsskydd för denna länk", + "hasPassword": "Lösenordsskyddad", + "password": "Lösenord", + "cancel": "Avboka", + "save": "Spara", + "saving": "Sparande..." + }, + "receivedFiles": { + "title": "Mottagna filer", + "description": "Visa och hantera filer som skickas till den här länken", + "noFiles": "Inga filer har tagits emot ännu", + "noFilesDescription": "Filer som skickas via denna länk kommer att visas här", + "fileCount": "{count, plural, =0 {Inga filer} =1 {1 fil} other {# filer}}", + "invalidDate": "Ogiltigt datum", + "totalSize": "Total storlek: {size}", + "columns": { + "file": "Fil", + "size": "Storlek", + "sender": "Skickat av", + "date": "Datum", + "actions": "Åtgärder" + }, + "actions": { + "preview": "Förhandsvisning", + "download": "Ladda ner", + "copyToMyFiles": "Kopiera till mina filer", + "copying": "Kopierar..." + }, + "uploadedBy": "Uppladdad av {name}", + "anonymous": "Anonym", + "downloadSuccess": "Nedladdningen startade", + "downloadError": "Fel vid nedladdning av filen", + "editSuccess": "Filen har uppdaterats", + "editError": "Fel vid uppdatering av filen", + "previewNotAvailable": "Förhandsgranskning inte tillgänglig", + "copySuccess": "Filen har kopierats till dina filer", + "copyError": "Det gick inte att kopiera filen till dina filer", + "deleteSuccess": "Filen har raderats", + "deleteError": "Det gick inte att ta bort filen", + "bulkCopySuccess": "{count, plural, =1 {1 fil kopierad till dina filer framgångsrikt} other {# filer kopierade till dina filer framgångsrikt}}", + "bulkDeleteSuccess": "{count, plural, =1 {1 fil raderad framgångsrikt} other {# filer raderade framgångsrikt}}", + "bulkCopyProgress": "Kopierar {count, plural, =1 {1 fil} other {# filer}} till dina filer...", + "bulkDeleteProgress": "Raderar {count, plural, =1 {1 fil} other {# filer}}...", + "bulkDeleteConfirmTitle": "Ta bort valda filer", + "bulkDeleteConfirmMessage": "Är du säker på att du vill ta bort {count, plural, =1 {denna fil} other {dessa # filer}}? Denna åtgärd kan inte ångras.", + "bulkDeleteConfirmButton": "Ta bort {count, plural, =1 {fil} other {filer}}", + "bulkActions": { + "selected": "{count, plural, =1 {1 fil vald} other {# filer valda}}", + "actions": "Åtgärder", + "download": "Ladda ner valt", + "copyToMyFiles": "Kopiera valda till Mina filer", + "delete": "Ta bort markerade" + }, + "selectAll": "Välj alla", + "selectFile": "Välj fil {fileName}", + "copyErrors": { + "timeout": "Kopieringsåtgärden tog timeout. Försök igen med en mindre fil eller kontrollera din anslutning.", + "failed": "Kopieringsåtgärden misslyckades. Försök igen.", + "aborted": "Kopieringsåtgärden avbröts på grund av timeout." + } + } + }, + "form": { + "name": { + "label": "Länknamn", + "placeholder": "t.ex.: projektdokument, familjefoton..." + }, + "description": { + "label": "Beskrivning", + "placeholder": "Beskriv vilken typ av filer du förväntar dig att få...", + "description": "Frivillig. Hjälper människor att förstå vad de ska skicka." + }, + "status": { + "label": "Länkstatus", + "description": "Aktivera eller avaktivera denna mottagningslänk" + }, + "expiration": { + "label": "Utgångsdatum", + "description": "Frivillig. Länken kommer att avaktiveras efter detta datum.", + "configure": "Konfigurera utgångsdatum" + }, + "fileLimits": { + "configure": "Konfigurera filgränser" + }, + "maxFiles": { + "label": "Maximalt antal filer", + "placeholder": "t.ex.: 10", + "description": "Frivillig. Begränsa det totala antalet filer som kan skickas.", + "noLimit": "Ingen filgräns" + }, + "maxFileSize": { + "label": "Maximal filstorlek", + "placeholder": "t.ex.: 100", + "description": "Frivillig. Begränsa den individuella storleken på varje fil.", + "noLimit": "Ingen storleksbegränsning" + }, + "allowedFileTypes": { + "label": "Tillåtna filtyper", + "placeholder": "t.ex.: pdf, jpg, png, docx", + "description": "Ange tillägg utan prickar, åtskilda av mellanslag, komma, streck eller rör", + "allTypes": "Alla filtyper" + }, + "pageLayout": { + "label": "Sidlayout", + "placeholder": "Välj layout", + "description": "Hur uppladdningssidan kommer att se ut för användarna.", + "options": { + "default": "Standardlayout", + "wetransfer": "WeTransfer-stil" + } + }, + "password": { + "label": "Skyddslösenord", + "placeholder": "Frivillig. Lägg till ett lösenord för att skydda länken", + "description": "Frivillig. Användare kommer att behöva detta lösenord för att komma åt länken.", + "configurePassword": "Konfigurera lösenord", + "protectWithPassword": "Skydda med lösenord", + "passwordHelp": "Lösenordet måste bestå av minst 4 tecken", + "passwordPlaceholder": "Ange ett lösenord för att skydda länken" + }, + "nameFieldRequired": { + "label": "Namnfältskrav", + "description": "Konfigurera om fältet för uppladdarens namn ska visas och om det krävs" + }, + "emailFieldRequired": { + "label": "E-postfältskrav", + "description": "Konfigurera om uppladdarens e-postfält ska visas och om det krävs" + }, + "fieldRequirements": { + "title": "Fältkrav", + "description": "Konfigurera vilka fält som ska visas i uppladdningsformuläret" + }, + "submit": "Skapa mottagningslänk" + }, + "messages": { + "created": "Ta emot länk skapad framgångsrikt!", + "createSuccess": "Ta emot länk skapad framgångsrikt!", + "updateSuccess": "Mottagningslänken har uppdaterats framgångsrikt!", + "linkCopied": "Länken har kopierats till urklipp!", + "deleteSuccess": "Mottagningslänken har raderats!", + "aliasCreated": "Alias ​​skapades framgångsrikt!", + "activateSuccess": "Mottagningslänken har aktiverats!", + "deactivateSuccess": "Mottagningslänken har avaktiverats!", + "passwordProtectionEnabled": "Lösenordsskydd har aktiverats!", + "passwordProtectionDisabled": "Lösenordsskyddet har tagits bort!" + }, + "defaultLinkName": "Mottagna filer", + "errors": { + "loadFailed": "Det gick inte att läsa in mottagarlänkar", + "createFailed": "Det gick inte att skapa mottagningslänk. Försök igen.", + "updateFailed": "Det gick inte att uppdatera mottagningslänken. Försök igen.", + "deleteFailed": "Det gick inte att ta bort mottagningslänken. Försök igen.", + "aliasCreateFailed": "Det gick inte att skapa alias. Försök igen.", + "passwordUpdateFailed": "Det gick inte att uppdatera lösenordsskyddet" + }, + "delete": { + "title": "Ta bort mottagningslänk", + "description": "Denna åtgärd kan inte ångras. Länken kommer att tas bort permanent och kommer inte längre att kunna ta emot filer.", + "confirmButton": "Ta bort länk", + "cancelButton": "Avboka", + "deleting": "Tar bort..." + }, + "upload": { + "metadata": { + "title": "Skicka filer - Palmr", + "description": "Skicka filer via den delade länken", + "descriptionWithLimit": "Ladda upp filer (max {limit} filer)" + }, + "layout": { + "defaultTitle": "Skicka filer", + "importantInfo": "Viktig information:", + "maxFiles": "Maximalt {count} fil(er)", + "maxFileSize": "Maximal filstorlek: {size}MB", + "allowedTypes": "Tillåtna typer: {types}", + "loading": "Laddar..." + }, + "password": { + "title": "Skyddad länk", + "description": "Denna länk är lösenordsskyddad. Ange lösenordet för att fortsätta.", + "label": "Lösenord", + "placeholder": "Ange lösenord", + "cancel": "Avboka", + "submit": "Fortsätta", + "verifying": "Verifierar..." + }, + "errors": { + "loadFailed": "Det gick inte att läsa in information. Försök igen.", + "passwordIncorrect": "Felaktigt lösenord. Försök igen.", + "linkNotFound": "Länken hittades inte eller har löpt ut.", + "linkInactive": "Denna länk är inaktiv.", + "linkExpired": "Denna länk har upphört att gälla.", + "uploadFailed": "Det gick inte att ladda upp filen", + "retry": "Försöka igen", + "fileTooLarge": "Filen är för stor. Maximal storlek: {maxSize}", + "fileTypeNotAllowed": "Filtypen är inte tillåten. Godkända typer: {allowedTypes}", + "maxFilesExceeded": "Maximalt {maxFiles} filer tillåtna", + "selectAtLeastOneFile": "Välj minst en fil", + "provideNameOrEmail": "Ange ditt namn eller din e-postadress", + "provideNameRequired": "Namn krävs", + "provideEmailRequired": "E-post krävs" + }, + "fileDropzone": { + "dragActive": "Släpp filer här", + "dragInactive": "Dra filerna hit eller klicka för att välja", + "acceptedTypes": "Godkända typer: {types}", + "maxFileSize": "Maximal storlek: {size}", + "maxFiles": "Maximalt {count} filer", + "remainingFiles": "{remaining} av {max} filer kvar" + }, + "fileList": { + "title": "Valda filer:", + "statusUploaded": "Uppladdad", + "statusError": "Fel", + "retry": "Försöka igen" + }, + "form": { + "nameLabel": "Namn", + "nameLabelOptional": "Namn (valfritt)", + "namePlaceholder": "Ditt namn", + "emailLabel": "E-post", + "emailLabelOptional": "E-post (valfritt)", + "emailPlaceholder": "your@email.com", + "descriptionLabel": "Beskrivning (valfritt)", + "descriptionPlaceholder": "Lägg till en beskrivning till filerna...", + "uploadButton": "Skicka {count} fil(er)", + "uploading": "Sändning..." + }, + "success": { + "title": "Filer har skickats! 🎉", + "description": "Du kan stänga den här sidan.", + "countMessage": "{count, plural, =1 {Fil skickad framgångsrikt!} other {# filer skickade framgångsrikt!}}" + }, + "maxFilesReached": { + "title": "Filgränsen har nåtts", + "description": "Denna länk har redan tagit emot det maximala antalet {maxFiles, plural, =1 {fil} other {filer}} som är tillåtna.", + "contactOwner": "Om det uppstod ett fel eller om du behöver skicka fler filer, kontakta länkägaren." + }, + "linkInactive": { + "title": "Länk inaktiv", + "description": "Denna mottagningslänk är tillfälligt inaktiv.", + "contactOwner": "Kontakta länkägaren för mer information." + }, + "linkNotFound": { + "title": "Länken hittades inte", + "description": "Denna länk kan ha tagits bort eller aldrig funnits." + }, + "linkExpired": { + "title": "Länken har löpt ut", + "description": "Denna mottagningslänk har upphört att gälla och accepterar inte längre filer.", + "contactOwner": "Kontakta länkägaren om du behöver skicka filer." + } + }, + "components": { + "fileRow": { + "addDescription": "Lägg till beskrivning...", + "anonymous": "Anonym" + }, + "fileActions": { + "edit": "Redigera", + "preview": "Förhandsvisning", + "download": "Ladda ner", + "delete": "Radera", + "copyToMyFiles": "Kopiera till mina filer", + "copying": "Kopierar..." + }, + "editField": { + "saveChanges": "Spara ändringar", + "cancelEdit": "Avbryt redigering" + } + } + }, + "searchBar": { + "placeholder": "Sök efter filer och mappar...", + "placeholderFiles": "Sök efter filer...", + "placeholderFolders": "Sök i mappar...", + "results": "Visar {filtered} av {total} objekt", + "noResults": "Inga resultat hittades för \"{query}\"" + }, + "settings": { + "groups": { + "defaultDescription": "Konfigurationsalternativ", + "general": { + "title": "Allmän", + "description": "Grundläggande programinställningar" + }, + "email": { + "title": "E-post", + "description": "E-postserverkonfiguration" + }, + "security": { + "title": "Säkerhet", + "description": "Säkerhets- och autentiseringsinställningar" + }, + "storage": { + "title": "Lagring", + "description": "File storage configuration" + }, + "oidc": { + "title": "OpenID Connect (SSO)", + "description": "Konfigurera SSO-autentisering via OpenID Connect" + } + }, + "fields": { + "noDescription": "Ingen beskrivning tillgänglig", + "appLogo": { + "title": "Applikationslogotyp", + "description": "Bild för applikationslogotyp" + }, + "appName": { + "title": "Applikationsnamn", + "description": "Applikationsnamnet visas för användarna" + }, + "appDescription": { + "title": "Applikationsbeskrivning", + "description": "Kort beskrivning av ansökan" + }, + "showHomePage": { + "title": "Visa startsida", + "description": "Visa startsidan efter installationen" + }, + "hideVersion": { + "title": "Dölj version", + "description": "Dölj Palmr-versionen från sidfoten på alla sidor" + }, + "smtpEnabled": { + "title": "SMTP aktiverad", + "description": "Aktivera eller inaktivera SMTP-e-postfunktioner" + }, + "smtpHost": { + "title": "SMTP-server", + "description": "SMTP-serveradress" + }, + "smtpPort": { + "title": "SMTP-port", + "description": "SMTP-serverport" + }, + "smtpUser": { + "title": "SMTP-användarnamn", + "description": "Användarnamn för SMTP-autentisering" + }, + "smtpPass": { + "title": "SMTP-lösenord", + "description": "Lösenord för SMTP-autentisering" + }, + "smtpFromName": { + "title": "Avsändarens namn", + "description": "Visningsnamn för skickade e-postmeddelanden" + }, + "smtpFromEmail": { + "title": "Avsändarens e-post", + "description": "Avsändarens e-postadress" + }, + "smtpSecure": { + "title": "Anslutningssäkerhet", + "description": "Säkerhetsmetod för SMTP-anslutning - Auto (rekommenderas), SSL, STARTTLS eller Ingen (osäker)", + "options": { + "auto": "Auto (rekommenderas)", + "ssl": "SSL (Port 465)", + "tls": "STARTTLS (Port 587)", + "none": "Ingen (osäker)" + } + }, + "smtpNoAuth": { + "title": "Ingen autentisering", + "description": "Aktivera detta för interna servrar som inte kräver användarnamn/lösenord (döljer auth-fält)" + }, + "smtpTrustSelfSigned": { + "title": "Lita på självsignerade certifikat", + "description": "Aktivera detta för att lita på självsignerade SSL/TLS-certifikat (användbart för utvecklingsmiljöer)" + }, + "testSmtp": { + "title": "Testa SMTP-anslutning", + "description": "Testa om SMTP-konfigurationen är giltig" + }, + "maxLoginAttempts": { + "title": "Maximalt antal inloggningsförsök", + "description": "Maximalt antal inloggningsförsök före blockering" + }, + "loginBlockDuration": { + "title": "Blockeringstid", + "description": "Varaktighet (i sekunder) att blockera efter fler försök" + }, + "passwordMinLength": { + "title": "Minsta lösenordslängd", + "description": "Minsta antal tecken för lösenord" + }, + "passwordResetTokenExpiration": { + "title": "Återställ tokens utgång", + "description": "Giltighetstid (i sekunder) för lösenordsåterställningstoken" + }, + "maxFileSize": { + "title": "Maximal filstorlek", + "description": "Högsta tillåtna filstorlek för uppladdningar" + }, + "maxTotalStoragePerUser": { + "title": "Maximalt lagringsutrymme per användare", + "description": "Total lagringsgräns per användare" + }, + "firstUserAccess": { + "title": "Första användaråtkomst", + "description": "Inställningar för första åtkomst för nya användare" + }, + "serverUrl": { + "title": "Server URL", + "description": "Bas-URL för Palmr-servern (t.ex.: https://palmr.example.com)" + }, + "passwordAuthEnabled": { + "title": "Lösenordsautentisering", + "description": "Aktivera eller inaktivera lösenordsbaserad autentisering" + } + }, + "buttons": { + "save": "Spara {group}", + "testSmtp": "Testa anslutningen", + "testing": "Testning..." + }, + "errors": { + "loadFailed": "Det gick inte att läsa in inställningarna", + "updateFailed": "Det gick inte att uppdatera inställningarna", + "passwordAuthRequiresProvider": "Det går inte att inaktivera lösenordsautentisering utan att ha minst en aktiv autentiseringsleverantör" + }, + "messages": { + "noChanges": "Inga ändringar att spara", + "updateSuccess": "{group} inställningar uppdaterade framgångsrikt", + "smtpTestSuccess": "SMTP-anslutningen lyckades! Din e-postkonfiguration fungerar korrekt.", + "smtpTestFailed": "SMTP-anslutningen misslyckades: {error}", + "smtpTestGenericError": "Det gick inte att testa SMTP-anslutningen. Kontrollera dina inställningar och försök igen.", + "smtpNotEnabled": "SMTP är inte aktiverat. Vänligen aktivera SMTP först.", + "smtpMissingHostPort": "Vänligen fyll i SMTP-värd och port innan du testar.", + "smtpMissingAuth": "Vänligen fyll i SMTP-användarnamn och lösenord, eller aktivera alternativet \"Ingen autentisering\"." + }, + "title": "Inställningar", + "breadcrumb": "Inställningar", + "pageTitle": "Inställningar", + "tooltips": { + "testSmtp": "Testar SMTP-anslutningen med de värden som för närvarande anges i formuläret. För att göra ändringar permanenta, kom ihåg att spara dina inställningar efter testet.", + "defaultPlaceholder": "Ange och tryck på Enter" + }, + "redirectUri": { + "placeholder": "https://mysite.com", + "previewLabel": "Komplett URL som kommer att sparas:" + } + }, + "share": { + "errors": { + "invalidPassword": "Ogiltigt lösenord. Försök igen.", + "loadFailed": "Det gick inte att läsa in delningen", + "downloadFailed": "Det gick inte att ladda ned filen" + }, + "messages": { + "downloadStarted": "Nedladdningen startade" + }, + "password": { + "title": "Lösenordsskyddad del", + "protected": "Denna del är lösenordsskyddad", + "incorrect": "Felaktigt lösenord. Försök igen.", + "label": "Lösenord", + "placeholder": "Ange delningslösenord", + "submit": "Överlämna" + }, + "details": { + "untitled": "Namnlös dela", + "created": "Skapad: {date}", + "expires": "Upphör: {date}" + }, + "downloadAll": "Ladda ner alla", + "notFound": { + "title": "Dela ej hittades", + "description": "Den här andelen kan ha tagits bort eller upphört att gälla." + }, + "pageTitle": "Dela", + "metadata": { + "defaultDescription": "Dela filer säkert", + "filesShared": "{count, plural, =1 {1 fil delad} other {# filer delade}}" + } + }, + "shareActions": { + "fileTitle": "Dela fil", + "folderTitle": "Dela mapp", + "linkTitle": "Skapa länk", + "linkDescriptionFile": "Skapa en anpassad länk för att dela filen", + "linkDescriptionFolder": "Skapa en anpassad länk för att dela mappen", + "aliasLabel": "Länkalias", + "aliasPlaceholder": "Ange anpassat alias", + "linkReady": "Din delningslänk är klar:", + "generateLink": "Skapa länk", + "copyLink": "Kopiera länk", + "deleteTitle": "Ta bort Share", + "deleteConfirmation": "Är du säker på att du vill ta bort den här delningen? Denna åtgärd kan inte ångras.", + "addDescriptionPlaceholder": "Lägg till beskrivning...", + "editTitle": "Redigera Dela", + "newPasswordLabel": "Nytt lösenord (låt vara tomt för att hålla det aktuellt)", + "newPasswordPlaceholder": "Ange nytt lösenord", + "manageFilesTitle": "Hantera filer", + "manageFilesDescription": "Välj filer och mappar som ska inkluderas i den här resursen", + "manageRecipientsTitle": "Hantera mottagare", + "itemsSelected": "{count} objekt valda", + "editSuccess": "Delningen har uppdaterats", + "editError": "Det gick inte att uppdatera delningen", + "bulkDeleteConfirmation": "Är du säker på att du vill ta bort {count, plural, =1 {1 delning} other {# delningar}}? Denna åtgärd kan inte ångras.", + "bulkDeleteTitle": "Ta bort valda andelar" + }, + "shareDetails": { + "title": "Dela detaljer", + "subtitle": "Visa och hantera information för den här andelen", + "basicInfo": "Grundläggande information", + "name": "Namn", + "description": "Beskrivning", + "shareLink": "Dela länk", + "dates": "Datum", + "security": "Säkerhet", + "files": "Filer", + "recipients": "Mottagare", + "views": "Visningar", + "created": "Skapad", + "expires": "Upphör att gälla", + "never": "Aldrig", + "untitled": "Namnlös dela", + "noDescription": "Ingen beskrivning", + "notAvailable": "N/A", + "invalidDate": "Ogiltigt datum", + "passwordProtected": "Lösenordsskyddad", + "publicAccess": "Allmän tillgång", + "maxViews": "Max antal visningar:", + "noLink": "Ingen länk genererad", + "generateLink": "Skapa länk", + "editLink": "Redigera länk", + "copyLink": "Kopiera länk", + "openLink": "Öppna länken", + "editSecurity": "Redigera säkerhet", + "editExpiration": "Redigera utgångsdatum", + "qrCode": "QR-kod", + "downloadQrCode": "Ladda ner QR-kod", + "clickToEnlargeQrCode": "Klicka för att förstora QR-koden", + "loadError": "Det gick inte att läsa in delningsinformation" + }, + "shareExpiration": { + "title": "Dela utgångsinställningar", + "subtitle": "Konfigurera när den här andelen upphör", + "currentStatus": "Aktuell status", + "expires": "Upphör att gälla:", + "neverExpires": "Går aldrig ut", + "enableExpiration": "Aktivera utgångsdatum", + "expirationDate": "Utgångsdatum", + "validation": { + "dateRequired": "Välj ett utgångsdatum", + "dateMustBeFuture": "Sista utgångsdatum måste ligga i framtiden" + }, + "success": { + "expirationSet": "Sista utgångsdatum har ställts in", + "expirationUpdated": "Sista utgångsdatum har uppdaterats", + "expirationRemoved": "Förfallodatumet har tagits bort - delningen är nu permanent" + }, + "error": { + "updateFailed": "Det gick inte att uppdatera utgångsinställningarna" + }, + "info": { + "title": "Om utgångsdatum:", + "willBeInaccessible": "Aktien blir otillgänglig efter detta datum", + "canBeChanged": "Du kan ändra eller ta bort utgångsdatumet när som helst", + "noExpiration": "Den här andelen kommer aldrig att upphöra att gälla och kommer att vara tillgänglig på obestämd tid." + } + }, + "shareManager": { + "deleteSuccess": "Delningen har tagits bort", + "deleteError": "Det gick inte att ta bort delningen", + "updateSuccess": "Delningen har uppdaterats", + "updateError": "Det gick inte att uppdatera delningen", + "securityUpdateSuccess": "Säkerhetsinställningarna har uppdaterats", + "securityUpdateError": "Det gick inte att uppdatera säkerhetsinställningarna", + "expirationUpdateSuccess": "Inställningarna för utgångsdatum har uppdaterats", + "expirationUpdateError": "Det gick inte att uppdatera utgångsinställningarna", + "filesUpdateSuccess": "Filerna har uppdaterats", + "filesUpdateError": "Det gick inte att uppdatera filer", + "recipientsUpdateSuccess": "Mottagarna har uppdaterats", + "recipientsUpdateError": "Det gick inte att uppdatera mottagarna", + "linkGenerateSuccess": "Delningslänken har skapats", + "linkGenerateError": "Det gick inte att skapa delningslänk", + "notifyLoading": "Skickar aviseringar...", + "notifySuccess": "Mottagarna har meddelats", + "notifyError": "Det gick inte att meddela mottagarna", + "bulkDeleteError": "Det gick inte att ta bort delningar", + "bulkDeleteLoading": "Tar bort {count, plural, =1 {1 delning} other {# delningar}}...", + "bulkDeleteSuccess": "{count, plural, =1 {1 delning borttagen framgångsrikt} other {# delningar borttagna framgångsrikt}}", + "downloadSuccess": "Nedladdningen startade framgångsrikt", + "downloadError": "Det gick inte att ladda ned dela filer", + "noFilesToDownload": "Inga filer tillgängliga att ladda ner", + "creatingZip": "Skapar ZIP-fil...", + "zipDownloadSuccess": "ZIP-filen har laddats ned", + "zipDownloadError": "Det gick inte att skapa ZIP-fil", + "errors": { + "multipleDownloadNotSupported": "Nedladdning av flera delningar stöds inte ännu - ladda ner delningar individuellt" + }, + "singleShareZipName": "{shareName}_files.zip", + "multipleSharesZipName": "{count}_shares_files.zip", + "defaultShareName": "Dela" + }, + "shareMultipleFiles": { + "title": "Dela flera filer", + "shareNameLabel": "Dela namn", + "shareNamePlaceholder": "Ange delningsnamn", + "descriptionLabel": "Beskrivning", + "descriptionPlaceholder": "Ange en beskrivning (valfritt)", + "filesToShare": "Filer att dela", + "files": "filer", + "totalSize": "Total storlek", + "creating": "Skapar delning...", + "create": "Skapa Share", + "itemsToShare": "Objekt att dela ({count} {count, plural, =1 {objekt} other {objekt}})" + }, + "shareSecurity": { + "title": "Dela säkerhetsinställningar", + "subtitle": "Konfigurera lösenordsskydd och säkerhetsalternativ för den här resursen", + "currentStatus": "Aktuell status", + "passwordProtection": "Lösenordsskydd", + "password": "Lösenord", + "newPassword": "Nytt lösenord", + "passwordPlaceholder": "Ange ett säkert lösenord", + "existingPasswordMessage": "Den här resursen har redan ett lösenord. Om du vill uppdatera det, skriv in det nya lösenordet i fältet nedan och spara.", + "passwordRequirements": { + "title": "Lösenordskrav:", + "minLength": "Minst 2 tecken" + }, + "info": { + "title": "Hur det fungerar:", + "withPassword": "Användare måste ange lösenordet för att komma åt den här resursen.", + "withoutPassword": "Alla med länken kan komma åt denna del utan lösenord." + }, + "validation": { + "passwordRequired": "Lösenord krävs", + "passwordTooShort": "Lösenordet måste bestå av minst 2 tecken" + }, + "success": { + "passwordSet": "Lösenordsskydd har aktiverats", + "passwordUpdated": "Lösenordet har uppdaterats", + "passwordRemoved": "Lösenordsskyddet har tagits bort" + }, + "error": { + "updateFailed": "Det gick inte att uppdatera säkerhetsinställningarna" + } + }, + "shares": { + "errors": { + "loadFailed": "Det gick inte att läsa in delningar", + "notifyFailed": "Det gick inte att meddela mottagarna", + "smtpConfigFailed": "Det gick inte att läsa in SMTP-konfigurationen" + }, + "messages": { + "linkCopied": "Länken har kopierats till urklipp", + "recipientsNotified": "Mottagarna har meddelats" + }, + "empty": { + "message": "Inga delningar har skapats ännu", + "createButton": "Skapa Share" + }, + "header": { + "title": "Mina aktier", + "myShares": "Mina aktier" + }, + "search": { + "title": "Alla aktier", + "createButton": "Skapa Share", + "placeholder": "Sök andelar...", + "results": "Hittade {filtered} av {total} delningar" + }, + "pageTitle": "Aktier" + }, + "sharesTable": { + "ariaLabel": "Delar tabell", + "never": "Aldrig", + "columns": { + "name": "NAMN", + "description": "BESKRIVNING", + "createdAt": "SKAPAD KL", + "expiresAt": "UTGÖR KL", + "status": "STATUS", + "security": "SÄKERHET", + "files": "FILER", + "recipients": "MOTTAGARE", + "actions": "ÅTGÄRDER" + }, + "status": { + "neverExpires": "Går aldrig ut", + "active": "Aktiv", + "expired": "Utgått" + }, + "security": { + "protected": "Skyddad", + "public": "Offentlig" + }, + "filesCount": "filer", + "folderCount": "mappar", + "recipientsCount": "mottagare", + "actions": { + "menu": "Menyn Dela åtgärder", + "edit": "Redigera", + "manageFiles": "Hantera filer", + "manageRecipients": "Hantera mottagare", + "viewDetails": "Visa detaljer", + "generateLink": "Skapa länk", + "editLink": "Redigera länk", + "copyLink": "Kopiera länk", + "viewQrCode": "Se QR-koden", + "notifyRecipients": "Meddela mottagare", + "downloadShareFiles": "Ladda ner alla filer", + "delete": "Radera" + }, + "bulkActions": { + "actions": "Åtgärder", + "download": "Ladda ner valt", + "delete": "Radera", + "selected": "{count, plural, =1 {1 delning vald} other {# delningar valda}}" + }, + "selectAll": "Välj alla", + "selectShare": "Välj delning {shareName}" + }, + "storageUsage": { + "title": "Användning av lagring", + "ariaLabel": "Förloppsindikator för lagringsanvändning", + "used": "begagnad", + "available": "tillgänglig", + "total": "Total", + "loading": "Belastning...", + "retry": "Försöka igen", + "errors": { + "title": "Lagringsinformation är inte tillgänglig", + "detectionFailed": "Det gick inte att upptäcka diskutrymme. Detta kan bero på systemkonfigurationsproblem eller otillräckliga behörigheter.", + "serverError": "Serverfel uppstod vid hämtning av lagringsinformation. Försök igen senare.", + "unknown": "Ett oväntat fel inträffade när lagringsinformation laddades." + } + }, + "theme": { + "toggle": "Växla tema", + "light": "Ljus", + "dark": "Mörk", + "system": "System" + }, + "twoFactor": { + "title": "Tvåfaktorsautentisering", + "description": "Lägg till ett extra lager av säkerhet till ditt konto", + "enabled": "Ditt konto är skyddat med tvåfaktorsautentisering", + "disabled": "Tvåfaktorsautentisering är inte aktiverad", + "status": { + "label": "Status:", + "enabled": "Aktiverad", + "disabled": "Inaktiverad" + }, + "buttons": { + "enable2FA": "Aktivera 2FA", + "disable2FA": "Inaktivera 2FA" + }, + "setup": { + "title": "Aktivera tvåfaktorsautentisering", + "description": "Skanna QR-koden med din autentiseringsapp och ange sedan verifieringskoden.", + "qrCode": "QR-kod", + "manualEntryKey": "Manuell inmatningsnyckel", + "verificationCode": "Verifieringskod", + "verificationCodePlaceholder": "Ange 6-siffrig kod", + "verificationCodeDescription": "Ange den sexsiffriga koden från din autentiseringsapp", + "verifyAndEnable": "Verifiera och aktivera", + "cancel": "Avboka" + }, + "disable": { + "title": "Inaktivera tvåfaktorsautentisering", + "description": "Ange ditt lösenord för att bekräfta att du inaktiverar tvåfaktorsautentisering.", + "password": "Lösenord", + "passwordPlaceholder": "Ange ditt lösenord", + "confirm": "Bekräfta inaktivera", + "cancel": "Avboka" + }, + "backupCodes": { + "title": "Säkerhetskopieringskoder", + "description": "Spara dessa reservkoder på ett säkert ställe. Du kan använda dem för att komma åt ditt konto om du tappar din autentiseringsenhet.", + "warning": "Viktig:", + "warningText": "Varje reservkod kan endast användas en gång. Håll dem säkra och dela dem inte med någon.", + "generateNew": "Generera nya säkerhetskopieringskoder", + "download": "Ladda ner säkerhetskopieringskoder", + "copyToClipboard": "Kopiera till Urklipp", + "savedMessage": "Jag har sparat mina säkerhetskopieringskoder", + "available": "{count} säkerhetskopieringskoder tillgängliga", + "instructions": [ + "• Spara dessa koder på en säker plats", + "• Varje reservkod kan endast användas en gång", + "• Du kan generera nya koder när som helst" + ] + }, + "verification": { + "title": "Tvåfaktorsautentisering", + "description": "Ange den sexsiffriga koden från din autentiseringsapp", + "backupDescription": "Ange en av dina reservkoder för att fortsätta", + "verificationCode": "Verifieringskod", + "backupCode": "Säkerhetskopieringskod", + "verificationCodePlaceholder": "000 000", + "backupCodePlaceholder": "XXXX-XXXX", + "verify": "Kontrollera", + "verifying": "Verifierar...", + "useBackupCode": "Använd reservkod istället", + "useAuthenticatorCode": "Använd autentiseringskod istället", + "rememberDevice": "Kom ihåg den här enheten i 30 dagar", + "rememberDeviceDescription": "Du behöver inte ange 2FA-koder på den här enheten på 30 dagar" + }, + "trustedDevices": { + "title": "Betrodda enheter - 2FA", + "description": "Enheter som inte kräver 2FA-verifiering", + "noDevices": "Inga betrodda enheter", + "deviceName": "Anordning", + "addedOn": "Tillagd på", + "expiresOn": "Upphör att gälla", + "remove": "Ta bort", + "removeAll": "Ta bort alla", + "confirmRemove": "Är du säker på att du vill ta bort den här betrodda enheten?", + "confirmRemoveAll": "Är du säker på att du vill ta bort alla betrodda enheter?", + "deviceRemoved": "Betrodd enhet har tagits bort", + "allDevicesRemoved": "Alla betrodda enheter har tagits bort", + "loadFailed": "Det gick inte att läsa in betrodda enheter", + "removeFailed": "Det gick inte att ta bort betrodd enhet", + "removeAllFailed": "Det gick inte att ta bort alla betrodda enheter", + "loading": "Läser in betrodda enheter...", + "noDevicesDescription": "Enheter kommer att visas här när du väljer att lita på dem under 2FA-verifiering", + "tableHeaders": { + "device": "Anordning", + "added": "Tillagd", + "expires": "Upphör att gälla", + "lastUsed": "Senast använd", + "ipAddress": "IP-adress", + "actions": "Åtgärder" + }, + "status": { + "never": "Aldrig", + "expired": "Utgått" + }, + "modals": { + "removeDevice": { + "title": "Ta bort betrodd enhet", + "added": "Lade till:", + "ip": "IP:" + }, + "removeAllDevices": { + "title": "Ta bort alla betrodda enheter", + "description": "Detta kommer att ta bort {count} betrodd{count, plural, =1 { enhet} other {a enheter}}. Du måste verifiera 2FA på alla enheter igen." + }, + "buttons": { + "cancel": "Avboka", + "removing": "Tar bort...", + "removeDevice": "Ta bort enhet", + "removeAllDevices": "Ta bort alla enheter" + } + } + }, + "messages": { + "enabledSuccess": "Tvåfaktorsautentisering har aktiverats!", + "disabledSuccess": "Tvåfaktorsautentisering har inaktiverats", + "backupCodesGenerated": "Nya reservkoder har genererats", + "backupCodesCopied": "Säkerhetskopieringskoder har kopierats till urklipp", + "setupFailed": "Det gick inte att generera 2FA-inställning", + "verificationFailed": "Ogiltig verifieringskod", + "disableFailed": "Det gick inte att inaktivera 2FA. Kontrollera ditt lösenord.", + "backupCodesFailed": "Det gick inte att generera reservkoder", + "backupCodesCopyFailed": "Det gick inte att kopiera reservkoder", + "statusLoadFailed": "Det gick inte att läsa in 2FA-status", + "enterVerificationCode": "Vänligen ange verifieringskoden", + "enterPassword": "Vänligen ange ditt lösenord", + "deviceTrusted": "Den här enheten har markerats som betrodd i 30 dagar" + }, + "errors": { + "invalidVerificationCode": "Ogiltig verifieringskod", + "invalidTwoFactorCode": "Ogiltig tvåfaktorsautentiseringskod", + "twoFactorRequired": "Tvåfaktorsautentisering krävs", + "twoFactorAlreadyEnabled": "Tvåfaktorsautentisering är redan aktiverad", + "twoFactorNotEnabled": "Tvåfaktorsautentisering är inte aktiverad", + "passwordVerificationRequired": "Lösenordsverifiering krävs", + "invalidPassword": "Ogiltigt lösenord", + "userNotFound": "Användaren hittades inte" + }, + "deviceNames": { + "unknownDevice": "Okänd enhet", + "browsers": { + "chrome": "Krom", + "firefox": "Firefox", + "safari": "Safari", + "edge": "Kant" + }, + "platforms": { + "windows": "på Windows", + "macos": "på macOS", + "linux": "på Linux", + "iphone": "på iPhone", + "android": "på Android" + } + } + }, + "uploadFile": { + "title": "Ladda upp fil", + "multipleTitle": "Ladda upp filer", + "selectFile": "Klicka för att välja en fil", + "selectMultipleFiles": "Klicka för att välja en eller flera filer", + "dragAndDrop": "eller dra och släpp filer här", + "filesQueued": "{count, plural, one {# fil köad för uppladdning} other {# filer köade för uppladdning}}", + "preview": "Förhandsvisning", + "uploadProgress": "Uppladdningsförlopp", + "upload": "Ladda upp", + "startUploads": "Starta uppladdningar", + "retry": "Försöka igen", + "finish": "Avsluta", + "success": "Filen har laddats upp", + "allSuccess": "{count, plural, =1 {Fil uppladdad framgångsrikt} other {# filer uppladdade framgångsrikt}}", + "partialSuccess": "{success} filer uppladdade framgångsrikt, {error} misslyckades", + "error": "Det gick inte att ladda upp filen", + "fileSizeExceeded": "File size exceeds the limit of {maxsizemb}MB.", + "insufficientStorage": "Insufficient storage space. You have {availablespace}MB available.", + "unauthorized": "Obehörig: en giltig token krävs för att komma åt den här resursen.", + "globalDrop": { + "title": "Släpp filer för att ladda upp", + "description": "Släpp för att ladda upp dina filer" + }, + "confirmCancel": { + "title": "Avbryt uppladdningar", + "messageSingle": "Det pågår en uppladdning.", + "messageMultiple": "There are {count} uploads in progress.", + "warning": "Om du stänger nu kommer uppladdningar att avbrytas och alla framsteg kommer att gå förlorade.", + "continue": "Fortsätt uppladdningar", + "cancel": "Avbryt uppladdningar" + }, + "pasteSuccess": "{count, plural, =1 {Bild klistrad och uppladdad framgångsrikt} other {# bilder klistrade och uppladdade framgångsrikt}}" + }, + "users": { + "modes": { + "create": "skapa", + "edit": "redigera" + }, + "errors": { + "loadFailed": "Det gick inte att läsa in användare", + "submitFailed": "Det gick inte att {mode} användare", + "deleteFailed": "Det gick inte att ta bort användaren", + "statusUpdateFailed": "Det gick inte att uppdatera användarstatus" + }, + "messages": { + "createSuccess": "Användaren skapades", + "updateSuccess": "Användaren har uppdaterats", + "deleteSuccess": "Användaren har raderats", + "activateSuccess": "Användaren har aktiverats", + "deactivateSuccess": "Användaren avaktiverades framgångsrikt" + }, + "actions": { + "edit": "Redigera", + "activate": "Aktivera", + "deactivate": "Avaktivera", + "delete": "Radera" + }, + "delete": { + "title": "Bekräfta Ta bort användare", + "confirmation": "Är du säker på att du vill ta bort användaren {firstName} {lastName}? Denna åtgärd kan inte ångras.", + "confirm": "Ta bort användare" + }, + "form": { + "titleCreate": "Lägg till ny användare", + "titleEdit": "Redigera användare", + "firstName": "Förnamn", + "lastName": "Efternamn", + "username": "Användarnamn", + "email": "E-post", + "password": "Lösenord", + "newPassword": "Nytt lösenord (valfritt)", + "passwordPlaceholder": "Lämna tomt för att behålla nuvarande lösenord", + "role": "Roll", + "roleUser": "Användare", + "roleAdmin": "Administration", + "create": "Skapa", + "save": "Spara" + }, + "status": { + "title": "Bekräfta statusändring", + "confirmation": "Är du säker på att du vill {action} användaren {firstName} {lastName}?", + "activate": "Aktivera", + "deactivate": "Avaktivera", + "user": "Användare" + }, + "header": { + "title": "Användarhantering", + "addUser": "Lägg till användare", + "management": "Användarhantering" + }, + "table": { + "user": "ANVÄNDARE", + "email": "E-POST", + "status": "STATUS", + "role": "ROLL", + "actions": "ÅTGÄRDER", + "active": "Aktiv", + "inactive": "Inaktiv", + "admin": "Administration", + "userr": "Användare" + }, + "invite": { + "button": "Generera inbjudningslänk", + "title": "Generera användarinbjudningslänk", + "description": "Generera en engångslänk som låter någon skapa sitt eget konto. Länken upphör att gälla om 15 minuter.", + "generating": "Genererar...", + "generate": "Generera länk", + "generated": "Inbjudningslänken genererades framgångsrikt!", + "linkReady": "Inbjudningslänk klar", + "linkReadyDescription": "Dela denna länk med personen du vill bjuda in. De kommer att kunna skapa sitt eget konto som en vanlig användare.", + "copyLink": "Kopiera länk", + "linkCopied": "Inbjudningslänken kopierades till urklipp!", + "expiresIn": "Upphör om 15 minuter", + "close": "Stäng", + "errors": { + "generateFailed": "Det gick inte att generera inbjudningslänk" + } + } + }, + "embedCode": { + "title": "Bädda in media", + "description": "Använd dessa koder för att bädda in detta media i forum, webbplatser eller andra plattformar", + "tabs": { + "directLink": "Direktlänk", + "html": "HTML", + "bbcode": "BBCode" + }, + "directLinkDescription": "Direkt URL till mediafilen", + "htmlDescription": "Använd den här koden för att bädda in media i HTML-sidor", + "bbcodeDescription": "Använd den här koden för att bädda in media i forum som stöder BBCode" + }, + "validation": { + "firstNameRequired": "Förnamn krävs", + "lastNameRequired": "Efternamn krävs", + "usernameLength": "Användarnamnet måste vara minst 3 tecken långt", + "usernameSpaces": "Användarnamnet får inte innehålla mellanslag", + "invalidEmail": "Vänligen ange en giltig e-postadress", + "passwordLength": "Lösenordet måste vara minst 8 tecken långt", + "passwordsMatch": "Lösenord måste matcha", + "emailRequired": "E-post krävs", + "emailOrUsernameRequired": "E-post eller användarnamn krävs", + "passwordRequired": "Lösenord krävs", + "passwordMinLength": "Lösenordet måste bestå av minst 6 tecken", + "nameRequired": "Namn krävs", + "required": "Detta fält är obligatoriskt" + }, + "registerWithInvite": { + "title": "Skapa ditt konto", + "description": "Fyll i informationen nedan för att skapa ditt konto", + "labels": { + "firstName": "Förnamn", + "firstNamePlaceholder": "Ange ditt förnamn", + "lastName": "Efternamn", + "lastNamePlaceholder": "Ange ditt efternamn", + "username": "Användarnamn", + "usernamePlaceholder": "Välj ett användarnamn", + "email": "E-post", + "emailPlaceholder": "Ange din e-post", + "password": "Lösenord", + "passwordPlaceholder": "Välj ett lösenord", + "confirmPassword": "Bekräfta lösenord", + "confirmPasswordPlaceholder": "Bekräfta ditt lösenord" + }, + "buttons": { + "creating": "Skapar konto...", + "createAccount": "Skapa konto" + }, + "validation": { + "firstNameRequired": "Förnamn krävs", + "lastNameRequired": "Efternamn krävs", + "usernameMinLength": "Användarnamnet måste vara minst 3 tecken", + "invalidEmail": "Ogiltig e-post", + "passwordMinLength": "Lösenordet måste vara minst 8 tecken", + "passwordsMatch": "Lösenorden måste matcha" + }, + "messages": { + "success": "Kontot skapades framgångsrikt! Omdirigerar till inloggning...", + "redirecting": "Omdirigerar till inloggning..." + }, + "errors": { + "invalidToken": "Fel med inbjudningslänk", + "tokenUsed": "Denna inbjudningslänk har redan använts", + "tokenExpired": "Denna inbjudningslänk har gått ut", + "usernameExists": "Användarnamnet finns redan", + "emailExists": "E-postadressen finns redan", + "createFailed": "Det gick inte att skapa konto. Försök igen." + }, + "pageTitle": "Skapa konto" + } +} \ No newline at end of file diff --git a/apps/web/messages/th-TH.json b/apps/web/messages/th-TH.json new file mode 100644 index 00000000..2548bdc1 --- /dev/null +++ b/apps/web/messages/th-TH.json @@ -0,0 +1,2001 @@ +{ + "auth": { + "successfullyAuthenticated": "ยืนยันตัวตนสำเร็จ!", + "authenticationFailed": "การยืนยันตัวตนล้มเหลว", + "errors": { + "account_inactive": "บัญชีถูกปิดใช้งาน กรุณาติดต่อผู้ดูแลระบบ", + "registration_disabled": "ปิดการลงทะเบียน SSO", + "token_expired": "โทเค็นหมดอายุ กรุณาลองใหม่อีกครั้ง", + "config_error": "เกิดข้อผิดพลาดในการตั้งค่า กรุณาติดต่อฝ่ายสนับสนุน", + "auth_failed": "การยืนยันตัวตนล้มเหลว กรุณาลองใหม่อีกครั้ง" + } + }, + "contextMenu": { + "newFolder": "โฟลเดอร์ใหม่", + "uploadFile": "อัปโหลดไฟล์" + }, + "authProviders": { + "title": "ผู้ให้บริการยืนยันตัวตน", + "description": "กำหนดค่าผู้ให้บริการยืนยันตัวตนภายนอกสำหรับ SSO", + "enabledCount": "{count} เปิดใช้งาน", + "loadingProviders": "กำลังโหลดผู้ให้บริการ...", + "providersConfigured": "{count} ผู้ให้บริการที่กำหนดค่า", + "enabledOfTotal": "{enabled} เปิดใช้งาน จาก {total} ผู้ให้บริการ", + "hideDisabledProviders": "ซ่อนผู้ให้บริการที่ปิดใช้งาน", + "addProvider": "เพิ่มผู้ให้บริการ", + "addProviderTitle": "เพิ่มผู้ให้บริการ", + "editProvider": "แก้ไขผู้ให้บริการ", + "deleteProvider": "ลบผู้ให้บริการ", + "enabled": "เปิดใช้งาน", + "disabled": "ปิดใช้งาน", + "officialProvider": "ผู้ให้บริการอย่างเป็นทางการ", + "dragToReorder": "ลากเพื่อเรียงลำดับใหม่", + "dragDisabledMessage": "การลากและวางถูกปิดใช้งานเมื่อกำหนดตัวกรองผู้ให้บริการ แสดงผู้ให้บริการทั้งหมดเพื่อเรียงลำดับใหม่", + "dragEnabledMessage": "ลากผู้ให้บริการเพื่อเรียงลำดับใหม่ ลำดับนี้จะแสดงในหน้าเข้าสู่ระบบ", + "noProvidersEnabled": "ไม่มีผู้ให้บริการยืนยันตัวตนที่เปิดใช้งาน", + "noProvidersConfigured": "ไม่มีผู้ให้บริการยืนยันตัวตนที่กำหนดค่า", + "form": { + "providerName": "ชื่อผู้ให้บริการ", + "providerNamePlaceholder": "เช่น บริษัทของฉัน", + "displayName": "ชื่อที่แสดง", + "displayNamePlaceholder": "เช่น SSO ของบริษัทของฉัน", + "type": "ประเภท", + "typeOidc": "OIDC (OpenID Connect)", + "typeOauth2": "OAuth 2.0", + "icon": "ไอคอน", + "iconPlaceholder": "เลือกไอคอน", + "clientId": "ID ไคลเอนต์", + "clientIdPlaceholder": "ID ไคลเอนต์ OAuth ของคุณ", + "clientSecret": "ลับของไคลเอนต์", + "clientSecretPlaceholder": "ลับของไคลเอนต์ OAuth ของคุณ", + "oauthScopes": "OAuth Scopes", + "scopesPlaceholder": "ป้อน scopes (เช่น openid, profile, email)", + "scopesHelpOidc": "Scopes จะแนะนำโดยอัตโนมัติตามURL ผู้ให้บริการ OIDC scopes ทั่วไป: openid, profile, email, groups", + "scopesHelpOauth2": "Scopes จะแนะนำโดยอัตโนมัติตามURL ผู้ให้บริการ OAuth2 scopes ทั่วไปขึ้นอยู่กับผู้ให้บริการ", + "providerUrl": "URL ผู้ให้บริการ", + "providerUrlPlaceholder": "https://auth.example.com", + "providerUrlAutoPlaceholder": "https://your-provider.com (endpoints จะถูกค้นพบโดยอัตโนมัติ)", + "providerUrlManualPlaceholder": "https://your-provider.com", + "autoDiscoveryHelp": "ระบบจะค้นพบ authorization, token และ userinfo endpoints โดยอัตโนมัติ", + "manualConfigurationHelp": "URL ฐานของผู้ให้บริการของคุณ (endpoints จะเป็นแบบสัมพัทธ์กับสิ่งนี้)", + "authorizationEndpoint": "Authorization Endpoint", + "authorizationEndpointPlaceholder": "https://auth.example.com/auth", + "tokenEndpoint": "Token Endpoint", + "tokenEndpointPlaceholder": "https://auth.example.com/token", + "userInfoEndpoint": "User Info Endpoint", + "userInfoEndpointPlaceholder": "https://auth.example.com/userinfo", + "configurationMethod": "วิธีการกำหนดค่า", + "autoDiscovery": "ค้นพบโดยอัตโนมัติ", + "autoDiscoveryDescription": "ค้นพบ endpoints โดยอัตโนมัติจาก URL ผู้ให้บริการ", + "manualEndpoints": "Manual Endpoints (แนะนำ)", + "manualEndpointsDescription": "กำหนด authorization, token และ user info endpoints ด้วยตนเอง", + "callbackUrl": "Callback URL", + "callbackUrlDescription": "ใช้ URL นี้ในการกำหนดค่าผู้ให้บริการ OAuth ของคุณ", + "copyCallbackUrl": "คัดลอก Callback URL", + "callbackUrlCopied": "Callback URL คัดลอกไปยังคลิปบอร์ดแล้ว!", + "adminEmailDomains": "โดเมนอีเมลผู้ดูแลระบบ", + "adminEmailDomainsPlaceholder": "ป้อนโดเมน (เช่น admin.company.com)", + "adminEmailDomainsHelp": "ผู้ใช้ที่มีอีเมลจากโดเมนเหล่านี้จะได้รับสิทธิ์ผู้ดูแลระบบ", + "autoRegister": "ลงทะเบียนผู้ใช้ใหม่โดยอัตโนมัติ", + "officialProviderUrlPlaceholder": "แทนที่เพลสโฮลเดอร์ด้วย {displayName} URL ของคุณ", + "officialProviderHelp": "นี่คือผู้ให้บริการอย่างเป็นทางการ Endpoints มีการตั้งค่าล่วงหน้า คุณสามารถแก้ไข URL นี้เท่านั้น", + "officialProviderIconHelp": "คุณสามารถปรับแต่งไอคอนสำหรับผู้ให้บริการอย่างเป็นทางการนี้" + }, + "buttons": { + "cancel": "ยกเลิก", + "save": "บันทึก", + "saving": "กำลังบันทึก...", + "adding": "กำลังเพิ่ม...", + "updating": "กำลังอัปเดต...", + "saveProvider": "บันทึกผู้ให้บริการ", + "delete": "ลบ", + "deleting": "กำลังลบ...", + "edit": "แก้ไข", + "enable": "เปิดใช้งาน", + "disable": "ปิดใช้งาน" + }, + "messages": { + "providerAdded": "เพิ่มผู้ให้บริการเรียบร้อยแล้ว", + "providerUpdated": "อัปเดตผู้ให้บริการเรียบร้อยแล้ว", + "providerDeleted": "ลบผู้ให้บริการเรียบร้อยแล้ว", + "providerOrderUpdated": "อัปเดตลำดับผู้ให้บริการเรียบร้อยแล้ว", + "fillRequiredFields": "กรุณากรอกฟิลด์ที่จำเป็นทั้งหมด (ชื่อ ชื่อที่แสดง ID ไคลเอนต์ ลับของไคลเอนต์)", + "provideUrlOrEndpoints": "ให้ URL ผู้ให้บริการสำหรับค้นพบโดยอัตโนมัติ หรือ endpoints ที่กำหนดเองทั้งสามรายการ", + "chooseDiscoveryOrManual": "เลือกค้นพบโดยอัตโนมัติ (URL ผู้ให้บริการ) หรือ endpoints ที่กำหนดเอง ไม่ใช่ทั้งสองอย่าง", + "loadFailed": "ไม่สามารถโหลดผู้ให้บริการ", + "addFailed": "ไม่สามารถเพิ่มผู้ให้บริการ", + "updateFailed": "ไม่สามารถอัปเดตผู้ให้บริการ", + "deleteFailed": "ไม่สามารถลบผู้ให้บริการ", + "orderUpdateFailed": "ไม่สามารถอัปเดตลำดับผู้ให้บริการ" + }, + "info": { + "title": "ข้อมูล", + "officialProvidersRecommended": "เพื่อการทำงานที่ดีขึ้น ให้พิจารณาใช้ผู้ให้บริการอย่างเป็นทางการ หากคุณมีปัญหากับผู้ให้บริการที่กำหนดเอง ให้พิจารณาเปิด issue บน", + "github": "GitHub", + "officialProvider": "ผู้ให้บริการอย่างเป็นทางการ", + "officialProviderDescription": "ผู้ให้บริการนี้ได้รับการปรับปรุงโดย Palmr สามารถแก้ไขได้เฉพาะข้อมูลประจำตัวและการกำหนดค่า", + "manualConfigTitle": "การกำหนดค่าด้วยตนเอง", + "manualConfigDescription": "คุณกำลังให้ endpoints ทั้งหมดด้วยตนเอง ตรวจสอบให้แน่ใจว่าถูกต้องสำหรับผู้ให้บริการของคุณ" + }, + "deleteModal": { + "title": "ลบผู้ให้บริการยืนยันตัวตน", + "confirmMessage": "คุณแน่ใจหรือไม่ว่าต้องการลบผู้ให้บริการ \"{displayName}\"? ไม่สามารถเลิกทำการกระทำนี้ได้", + "providerId": "ID ผู้ให้บริการ: {name}", + "cancel": "ยกเลิก", + "delete": "ลบผู้ให้บริการ", + "deleting": "กำลังลบ..." + } + }, + "bulkDownload": { + "title": "ดาวน์โหลดแบบรวม", + "zipNameLabel": "ชื่อไฟล์ ZIP", + "zipNamePlaceholder": "ป้อนชื่อไฟล์", + "description": "{count, plural, =1 {1 ไฟล์จะถูกบีบอัด} other {# ไฟล์จะถูกบีบอัด}}", + "download": "ดาวน์โหลด ZIP" + }, + "common": { + "loading": "กำลังโหลด โปรดรอ...", + "loadingSimple": "กำลังโหลด...", + "cancel": "ยกเลิก", + "save": "บันทึก", + "saving": "กำลังบันทึก...", + "update": "อัปเดต", + "updating": "กำลังอัปเดต...", + "delete": "ลบ", + "deleting": "กำลังลบ...", + "close": "ปิด", + "download": "ดาวน์โหลด", + "unexpectedError": "เกิดข้อผิดพลาดที่ไม่คาดคิด โปรดลองใหม่", + "yes": "ใช่", + "no": "ไม่", + "dashboard": "แดชบอร์ด", + "back": "กลับ", + "click": "คลิกเพื่อ", + "creating": "กำลังสร้าง...", + "create": "สร้าง", + "rename": "เปลี่ยนชื่อ", + "move": "ย้าย", + "share": "แชร์", + "search": "ค้นหา", + "copy": "คัดลอก", + "copied": "คัดลอกแล้ว" + }, + "createShare": { + "title": "สร้างแชร์", + "nameLabel": "ชื่อแชร์", + "namePlaceholder": "ป้อนชื่อสำหรับแชร์ของคุณ", + "descriptionLabel": "คำอธิบาย", + "descriptionPlaceholder": "ป้อนคำอธิบาย (ไม่บังคับ)", + "expirationLabel": "วันที่หมดอายุ", + "expirationPlaceholder": "MM/DD/YYYY HH:MM", + "maxViewsLabel": "มุมมองสูงสุด", + "maxViewsPlaceholder": "ปล่อยว่างไว้เพื่ออนุญาตไม่จำกัด", + "passwordProtection": "ป้องกันด้วยรหัสผ่าน", + "passwordLabel": "รหัสผ่าน", + "passwordPlaceholder": "ป้อนรหัสผ่าน", + "create": "สร้างแชร์", + "success": "สร้างแชร์เรียบร้อยแล้ว", + "error": "ไม่สามารถสร้างแชร์", + "errors": { + "nameRequired": "ต้องระบุชื่อแชร์", + "selectItems": "โปรดเลือกไฟล์หรือโฟลเดอร์อย่างน้อยหนึ่งรายการ" + }, + "itemsSelected": "{count} รายการที่เลือก", + "selectItemsPrompt": "เลือกไฟล์และโฟลเดอร์เพื่อแชร์", + "tabs": { + "shareDetails": "รายละเอียดแชร์", + "selectFiles": "เลือกไฟล์" + }, + "nextSelectFiles": "ถัดไป: เลือกไฟล์", + "searchLabel": "ค้นหา" + }, + "customization": { + "breadcrumb": "การปรับแต่ง", + "colors": { + "title": "สีธีม", + "description": "เลือกสีธีมหลักที่คุณชื่นชอบ", + "presets": "สีที่พร้อมใช้งาน", + "presetsDescription": "เลือกจากธีมสีที่พร้อมใช้งาน", + "reset": "รีเซ็ตเป็นค่าเริ่มต้น" + }, + "fonts": { + "title": "ลักษณะการพิมพ์", + "description": "เลือกตระกูลฟอนต์ที่คุณชื่นชอบ", + "available": "ฟอนต์ที่พร้อมใช้งาน", + "availableDescription": "เลือกจากตระกูลฟอนต์ที่พร้อมใช้งาน", + "reset": "รีเซ็ตเป็นค่าเริ่มต้น" + }, + "radius": { + "title": "ขอบเขตมุม", + "description": "ปรับแต่งความโค้งขององค์ประกอบอินเทอร์เฟซ", + "available": "ตัวเลือกความมน", + "availableDescription": "เลือกว่ามุมควรปรากฏอย่างไร", + "reset": "รีเซ็ตเป็นค่าเริ่มต้น" + }, + "background": { + "title": "สีพื้นหลัง", + "description": "ปรับแต่งสีพื้นหลังสำหรับโหมดสว่างและมืด", + "lightMode": "โหมดสว่าง", + "darkMode": "โหมดมืด", + "availableDescription": "เลือกสีพื้นหลังสำหรับธีมทั้งสวางและมืด", + "reset": "รีเซ็ตเป็นค่าเริ่มต้น" + }, + "theme": { + "title": "โหมดธีม", + "description": "เลือกระหว่างธีมสว่าง มืด หรือธีมระบบ", + "selectTheme": "ความชอบธีม", + "availableDescription": "เลือกโหมดธีมที่คุณชื่นชอบ", + "reset": "รีเซ็ตเป็นระบบ" + }, + "pageTitle": "การปรับแต่ง" + }, + "dashboard": { + "loadError": "ไม่สามารถโหลดข้อมูลแดชบอร์ด", + "linkCopied": "คัดลอกลิงค์ไปยังคลิปบอร์ดแล้ว", + "pageTitle": "แดชบอร์ด", + "breadcrumb": "แดชบอร์ด", + "recentFiles": { + "title": "ไฟล์ล่าสุด", + "description": "ไฟล์ที่อัปโหลดล่าสุดของคุณ" + } + }, + "deleteConfirmation": { + "filesToDelete": "ไฟล์ที่จะลบ", + "foldersToDelete": "โฟลเดอร์ที่จะลบ", + "itemsToDelete": "รายการที่จะลบ", + "sharesToDelete": "แชร์ที่จะลบ" + }, + "downloadQueue": { + "downloadQueued": "ดาวน์โหลดในคิว: {fileName}", + "queuedDescription": "ดาวน์โหลดของคุณจะเริ่มต้นโดยอัตโนมัติเมื่อมีช่องว่างพร้อม", + "queuePosition": "ดาวน์โหลดในคิวที่ตำแหน่ง {position}: {fileName}", + "estimatedWait": "เวลารอโดยประมาณ: {time}", + "queueFull": "คิวดาวน์โหลดเต็มแล้ว", + "queueFullDescription": "โปรดลองอีกครั้งในอีกไม่กี่นาทีเมื่อคิวว่างลง", + "cancelSuccess": "ยกเลิกดาวน์โหลดเรียบร้อยแล้ว", + "cancelError": "ไม่สามารถยกเลิกดาวน์โหลด: {error}", + "status": { + "pending": "กำลังเตรียม...", + "queued": "ในคิว", + "downloading": "กำลังดาวน์โหลด", + "completed": "เสร็จสิ้น", + "failed": "ล้มเหลว" + }, + "waitTime": { + "seconds": "{seconds}วิ", + "minutes": "{minutes}น", + "hoursMinutes": "{hours}ช {minutes}น" + }, + "indicator": { + "title": "ดาวน์โหลด", + "downloads": "คิวดาวน์โหลด", + "active": "ใช้งานอยู่", + "queued": "ในคิว", + "position": "ตำแหน่ง {position}", + "estimatedWait": "รอ: {time}", + "unknownFile": "ไฟล์ที่ไม่ทราบ", + "noDownloads": "ไม่มีดาวน์โหลดที่กำลังดำเนิน", + "refresh": "รีเฟรชคิว" + } + }, + "emptyState": { + "noFiles": "ยังไม่มีไฟล์ที่อัปโหลด", + "uploadFile": "อัปโหลดไฟล์" + }, + "errors": { + "invalidCredentials": "อีเมลหรือรหัสผ่านไม่ถูกต้อง", + "userNotFound": "ไม่พบผู้ใช้", + "accountLocked": "บัญชีถูกล็อค โปรดลองใหม่ในภายหลัง", + "unexpectedError": "เกิดข้อผิดพลาดที่ไม่คาดคิด โปรดลองใหม่", + "Invalid verification code": "รหัสการยืนยันไม่ถูกต้อง", + "Two-factor authentication is already enabled": "เปิดใช้งานการยืนยันตัวตนสองปัจจัยแล้ว", + "Two-factor authentication is not enabled": "ไม่ได้เปิดใช้งานการยืนยันตัวตนสองปัจจัย", + "Invalid password": "รหัสผ่านไม่ถูกต้อง", + "Password verification required": "ต้องใช้การยืนยันรหัสผ่าน", + "Invalid two-factor authentication code": "รหัสการยืนยันตัวตนสองปัจจัยไม่ถูกต้อง", + "Two-factor authentication required": "ต้องใช้การยืนยันตัวตนสองปัจจัย", + "noUserData": "ไม่มีข้อมูลผู้ใช้" + }, + "fileActions": { + "editFile": "แก้ไขไฟล์", + "nameLabel": "ชื่อ", + "namePlaceholder": "ป้อนชื่อใหม่", + "extension": "นามสกุล", + "descriptionLabel": "คำอธิบาย", + "descriptionPlaceholder": "ป้อนคำอธิบายไฟล์", + "addDescriptionPlaceholder": "เพิ่มคำอธิบาย...", + "deleteFile": "ลบไฟล์", + "deleteConfirmation": "คุณแน่ใจหรือไม่ว่าต้องการลบไฟล์นี้?", + "deleteWarning": "ไม่สามารถเลิกทำการกระทำนี้ได้" + }, + "fileManager": { + "downloadError": "ไม่สามารถดาวน์โหลดไฟล์", + "updateSuccess": "อัปเดตไฟล์เรียบร้อยแล้ว", + "updateError": "ไม่สามารถอัปเดตไฟล์", + "deleteSuccess": "ลบไฟล์เรียบร้อยแล้ว", + "deleteError": "ไม่สามารถลบไฟล์" + }, + "filePreview": { + "title": "ตัวอย่างไฟล์", + "description": "ตัวอย่างและดาวน์โหลดไฟล์", + "loading": "กำลังโหลด...", + "notAvailable": "ตัวอย่างไม่พร้อมใช้งานสำหรับประเภทไฟล์นี้", + "downloadToView": "ใช้ปุ่มดาวน์โหลดเพื่อดูไฟล์นี้", + "loadError": "เกิดข้อผิดพลาดในการโหลดตัวอย่างไฟล์", + "downloadError": "เกิดข้อผิดพลาดในการดาวน์โหลดไฟล์", + "audioNotSupported": "เบราว์เซอร์ของคุณไม่รองรับการเล่นเสียง", + "videoNotSupported": "เบราว์เซอร์ของคุณไม่รองรับการเล่นวิดีโอ", + "pdfPreviewNotAvailable": "ตัวอย่าง PDF ไม่พร้อมใช้งาน ลองมุมมองอื่นหรือดาวน์โหลด", + "tryAlternativeView": "ลองมุมมองอื่น", + "loadingAlternative": "กำลังโหลดมุมมองอื่น...", + "loadingAudio": "กำลังโหลดเสียง..." + }, + "fileSelector": { + "availableFiles": "ไฟล์ที่พร้อมใช้งาน ({count})", + "shareFiles": "ไฟล์ที่แชร์ ({count})", + "shareFilesDescription": "ไฟล์ที่อยู่ในแชร์นี้", + "availableFilesDescription": "เลือกไฟล์เพื่อเพิ่มไปยังแชร์นี้", + "searchPlaceholder": "ค้นหาไฟล์...", + "searchSelectedFiles": "ค้นหาไฟล์ที่เลือก...", + "noMatchingFiles": "ไม่พบไฟล์ที่ตรงกัน", + "noAvailableFiles": "ไม่มีไฟล์ที่พร้อมใช้งาน", + "noFilesInShare": "ไม่มีไฟล์ในแชร์นี้", + "noFilesFound": "ไม่พบไฟล์", + "noFilesFoundWith": "ไม่พบไฟล์ที่ตรงกับ \"{query}\"", + "addFilesFromList": "เพิ่มไฟล์จากรายการด้านล่าง", + "tryDifferentSearch": "ลองใช้คำค้นหาที่แตกต่างกัน", + "allFilesInShare": "ไฟล์ทั้งหมดอยู่ในแชร์นี้แล้ว", + "uploadNewFiles": "อัปโหลดไฟล์ใหม่เพื่อเพิ่มไฟล์", + "fileCount": "{count, plural, =1 {ไฟล์} other {ไฟล์}}", + "filesSelected": "{count, plural, =0 {ไม่ได้เลือกไฟล์} =1 {เลือก 1 ไฟล์} other {เลือก # ไฟล์}}", + "itemsSelected": "{count} รายการที่เลือก", + "editFile": "แก้ไขไฟล์", + "editFolder": "แก้ไขโฟลเดอร์", + "previewFile": "ตัวอย่างไฟล์", + "addToShare": "เพิ่มไปยังแชร์", + "removeFromShare": "นำออกจากแชร์", + "saveChanges": "บันทึกการเปลี่ยนแปลง" + }, + "files": { + "title": "ไฟล์ทั้งหมด", + "uploadFile": "อัปโหลดไฟล์", + "loadError": "ไม่สามารถโหลดไฟล์", + "pageTitle": "ไฟล์ของฉัน", + "breadcrumb": "ไฟล์ของฉัน", + "downloadStart": "เริ่มดาวน์โหลด", + "downloadError": "ไม่สามารถดาวน์โหลดไฟล์", + "updateSuccess": "อัปเดตไฟล์เรียบร้อยแล้ว", + "updateError": "ไม่สามารถอัปเดตไฟล์", + "deleteSuccess": "ลบไฟล์เรียบร้อยแล้ว", + "deleteError": "ไม่สามารถลบไฟล์", + "bulkDownloadSuccess": "เริ่มดาวน์โหลดไฟล์เรียบร้อยแล้ว", + "bulkDownloadError": "เกิดข้อผิดพลาดในการสร้างไฟล์ ZIP", + "bulkDownloadFileError": "เกิดข้อผิดพลาดในการดาวน์โหลดไฟล์ {fileName}", + "bulkDeleteSuccess": "{count, plural, =1 {ลบ 1 รายการเรียบร้อยแล้ว} other {ลบ # รายการเรียบร้อยแล้ว}}", + "bulkDeleteError": "เกิดข้อผิดพลาดในการลบรายการที่เลือก", + "bulkDeleteTitle": "ลบรายการที่เลือก", + "bulkDeleteConfirmation": "คุณแน่ใจหรือไม่ว่าต้องการลบ {count, plural, =1 {1 รายการ} other {# รายการ}}? ไม่สามารถเลิกทำการกระทำนี้ได้", + "totalFiles": "{count, plural, =0 {ไม่มีไฟล์} =1 {1 ไฟล์} other {# ไฟล์}}", + "openFolder": "เปิดโฟลเดอร์", + "errors": { + "moveItemsFailed": "ไม่สามารถย้ายรายการ โปรดลองใหม่", + "cannotMoveHere": "ไม่สามารถย้ายรายการไปยังตำแหน่งนี้" + }, + "empty": { + "title": "ยังไม่มีไฟล์หรือโฟลเดอร์", + "description": "อัปโหลดไฟล์แรกของคุณหรือสร้างโฟลเดอร์เพื่อเริ่มต้น" + }, + "files": "ไฟล์", + "folders": "โฟลเดอร์", + "actions": { + "open": "เปิด", + "rename": "เปลี่ยนชื่อ", + "delete": "ลบ" + }, + "viewMode": { + "table": "ตาราง", + "grid": "กริด" + } + }, + "filesTable": { + "ariaLabel": "ตารางไฟล์", + "selectAll": "เลือกทั้งหมด", + "selectFile": "เลือกไฟล์ {fileName}", + "columns": { + "name": "ชื่อ", + "description": "คำอธิบาย", + "size": "ขนาด", + "createdAt": "สร้างเมื่อ", + "updatedAt": "อัปเดตเมื่อ", + "actions": "การกระทำ" + }, + "actions": { + "menu": "เมนูการกระทำไฟล์", + "preview": "ตัวอย่าง", + "edit": "แก้ไข", + "share": "แชร์", + "download": "ดาวน์โหลด", + "delete": "ลบ" + }, + "bulkActions": { + "selected": "{count, plural, =1 {เลือก 1 ไฟล์} other {เลือก # ไฟล์}}", + "actions": "การกระทำ", + "download": "ดาวน์โหลดที่เลือก", + "share": "แชร์ที่เลือก", + "delete": "ลบที่เลือก" + } + }, + "folderActions": { + "editFolder": "แก้ไขโฟลเดอร์", + "folderName": "ชื่อโฟลเดอร์", + "folderNamePlaceholder": "ป้อนชื่อโฟลเดอร์", + "folderDescription": "คำอธิบาย", + "folderDescriptionPlaceholder": "ป้อนคำอธิบายโฟลเดอร์ (ไม่บังคับ)", + "createFolder": "สร้างโฟลเดอร์", + "renameFolder": "เปลี่ยนชื่อโฟลเดอร์", + "moveFolder": "ย้ายโฟลเดอร์", + "shareFolder": "แชร์โฟลเดอร์", + "deleteFolder": "ลบโฟลเดอร์", + "moveTo": "ย้ายไปยัง", + "selectDestination": "เลือกโฟลเดอร์ปลายทาง", + "rootFolder": "รูท", + "folderCreated": "สร้างโฟลเดอร์เรียบร้อยแล้ว", + "folderRenamed": "เปลี่ยนชื่อโฟลเดอร์เรียบร้อยแล้ว", + "folderMoved": "ย้ายโฟลเดอร์เรียบร้อยแล้ว", + "folderDeleted": "ลบโฟลเดอร์เรียบร้อยแล้ว", + "folderShared": "แชร์โฟลเดอร์เรียบร้อยแล้ว", + "createFolderError": "เกิดข้อผิดพลาดในการสร้างโฟลเดอร์", + "renameFolderError": "เกิดข้อผิดพลาดในการเปลี่ยนชื่อโฟลเดอร์", + "moveFolderError": "เกิดข้อผิดพลาดในการย้ายโฟลเดอร์", + "deleteFolderError": "เกิดข้อผิดพลาดในการลบโฟลเดอร์", + "shareFolderError": "เกิดข้อผิดพลาดในการแชร์โฟลเดอร์", + "deleteConfirmation": "คุณแน่ใจหรือไม่ว่าต้องการลบโฟลเดอร์นี้?", + "deleteWarning": "ไม่สามารถเลิกทำการกระทำนี้ได้" + }, + "footer": { + "poweredBy": "ขับเคลื่อนโดย", + "kyanHomepage": "หน้าแรก Kyantech" + }, + "forgotPassword": { + "emailLabel": "ที่อยู่อีเมล", + "emailPlaceholder": "ป้อนอีเมลของคุณ", + "sending": "กำลังส่ง...", + "submit": "ส่งคำแนะนำการรีเซ็ต", + "backToLogin": "กลับไปที่หน้าเข้าสู่ระบบ", + "title": "ลืมรหัสผ่าน", + "description": "ป้อนที่อยู่อีเมลของคุณและเราจะส่งคำแนะนำในการรีเซ็ตรหัสผ่านให้คุณ", + "resetInstructions": "ส่งคำแนะนำการรีเซ็ตไปยังอีเมลของคุณแล้ว", + "pageTitle": "ลืมรหัสผ่าน", + "passwordAuthDisabled": "ปิดใช้งานการยืนยันตัวตนด้วยรหัสผ่าน โปรดติดต่อผู้ดูแลระบบหรือใช้ผู้ให้บริการยืนยันตัวตนภายนอก" + }, + "generateShareLink": { + "generateTitle": "สร้างลิงค์แชร์", + "updateTitle": "อัปเดตลิงค์แชร์", + "generateDescription": "สร้างลิงค์ที่กำหนดเองสำหรับแชร์นี้ คุณสามารถปรับแต่ง URL เพื่อให้จดจำได้ง่ายขึ้น", + "updateDescription": "อัปเดตลิงค์ที่กำหนดเองสำหรับแชร์นี้ คุณสามารถปรับแต่ง URL เพื่อให้จดจำได้ง่ายขึ้น", + "aliasPlaceholder": "ID ที่กำหนดเองสำหรับลิงค์", + "linkReady": "ลิงค์แชร์ของคุณพร้อมแล้ว คุณสามารถคัดลอกได้เลย", + "readyDescription": "ลิงค์แชร์ของคุณพร้อมแล้ว คุณสามารถสแกนรหัส QR โดยตรง ดาวน์โหลดสำหรับใช้ภายหลัง หรือคัดลอกลิงค์ด้านล่าง", + "generateButton": "สร้างลิงค์", + "updateButton": "อัปเดตลิงค์", + "copyButton": "คัดลอกลิงค์", + "success": "สร้างลิงค์เรียบร้อยแล้ว", + "error": "ไม่สามารถสร้างลิงค์", + "copied": "คัดลอกลิงค์ไปยังคลิปบอร์ดแล้ว", + "tabs": { + "link": "ลิงค์", + "qrcode": "รหัส QR" + } + }, + "home": { + "description": "ทางเลือกโอเพนซอร์สแทน WeTransfer แชร์ไฟล์อย่างปลอดภัยโดยไม่มีการติดตามหรือข้อจำกัด", + "documentation": "เอกสารประกอบ", + "starOnGithub": "ให้ดาว บน GitHub", + "privacyMessage": "สร้างขึ้นด้วยการคำนึงถึงความเป็นส่วนตัว ไฟล์ของคุณสามารถเข้าถึงได้เฉพาะผู้ที่มีลิงค์แชร์เท่านั้นก่อนการอัปโหลด ฟรีและโอเพนซอร์สตลอดไป", + "header": { + "fileSharing": "แชร์ไฟล์", + "tagline": "ทำให้ง่ายและฟรี" + }, + "pageTitle": "หน้าแรก" + }, + "iconPicker": { + "title": "เลือกไอคอน", + "placeholder": "เลือกไอคอน", + "searchPlaceholder": "ค้นหาไอคอน...", + "loadingMore": "กำลังโหลดไอคอนเพิ่มเติม...", + "allIconsLoaded": "โหลดไอคอนทั้งหมด {count} แล้ว", + "noIconsFound": "ไม่พบไอคอนสำหรับ \"{search}\"", + "tabs": { + "all": "ไอคอนทั้งหมด", + "popular": "ยอดนิยม", + "auth": "ผู้ให้บริการยืนยันตัวตน" + }, + "stats": "{iconCount} ไอคอนจาก {libraryCount} ไลบรารี", + "categoryBadge": "{category} ({count} ไอคอน)" + }, + "imageEdit": { + "title": "แก้ไขรูปภาพ", + "rotate": "หมุน", + "zoom": "ซูม", + "cropInstructions": "ลากเพื่อเปลี่ยนตำแหน่ง ปรับขนาดมุมเพื่อปรับพื้นที่ครอป" + }, + "login": { + "welcome": "ยินดีต้อนรับสู่", + "signInToContinue": "เข้าสู่ระบบเพื่อดำเนิน", + "emailOrUsernameLabel": "อีเมลหรือชื่อผู้ใช้", + "emailOrUsernamePlaceholder": "ป้อนอีเมลหรือชื่อผู้ใช้ของคุณ", + "emailLabel": "ที่อยู่อีเมล", + "emailPlaceholder": "ป้อนอีเมลของคุณ", + "passwordLabel": "รหัสผ่าน", + "passwordPlaceholder": "ป้อนรหัสผ่านของคุณ", + "signIn": "เข้าสู่ระบบ", + "signingIn": "กำลังเข้าสู่ระบบ...", + "forgotPassword": "ลืมรหัสผ่าน?", + "pageTitle": "เข้าสู่ระบบ", + "or": "หรือ", + "continueWithSSO": "ดำเนินการต่อด้วย SSO", + "processing": "กำลังประมวลผลการยืนยันตัวตน..." + }, + "logo": { + "labels": { + "appLogo": "โลโก้แอปพลิเคชัน" + }, + "buttons": { + "upload": "อัปโหลดโลโก้", + "remove": "นำออกโลโก้" + }, + "messages": { + "uploadSuccess": "อัปโหลดโลโก้เรียบร้อยแล้ว", + "removeSuccess": "นำออกโลโก้เรียบร้อยแล้ว" + }, + "errors": { + "uploadFailed": "ไม่สามารถอัปโหลดโลโก้", + "removeFailed": "ไม่สามารถนำออกโลโก้" + } + }, + "moveItems": { + "itemsToMove": "รายการเพื่อย้าย:", + "movingTo": "ย้ายไปยัง:", + "title": "ย้าย {count, plural, =1 {รายการ} other {รายการ}}", + "description": "ย้าย {count, plural, =1 {รายการ} other {# รายการ}} ไปยังตำแหน่งใหม่", + "success": "ย้าย {count} {count, plural, =1 {รายการ} other {รายการ}} เรียบร้อยแล้ว", + "errors": { + "moveFailed": "ไม่สามารถย้ายรายการ" + } + }, + "navbar": { + "logoAlt": "โลโก้แอปพลิเคชัน", + "profileMenu": "เมนูโปรไฟล์", + "profile": "โปรไฟล์", + "customization": "การปรับแต่ง", + "settings": "การตั้งค่า", + "usersManagement": "การจัดการผู้ใช้", + "logout": "ออกจากระบบ" + }, + "navigation": { + "dashboard": "แดชบอร์ด" + }, + "notifications": { + "permissionGranted": "เปิดใช้งานการแจ้งเตือนการดาวน์โหลด", + "permissionDenied": "ปิดใช้งานการแจ้งเตือนการดาวน์โหลด", + "downloadComplete": { + "title": "ดาวน์โหลดเสร็จสิ้น", + "body": "{fileName} ดาวน์โหลดเสร็จสิ้น" + }, + "downloadFailed": { + "title": "ดาวน์โหลดล้มเหลว", + "body": "ไม่สามารถดาวน์โหลด {fileName}: {error}", + "unknownError": "ข้อผิดพลาดที่ไม่ทราบ" + }, + "queueProcessing": { + "title": "เริ่มดาวน์โหลด", + "body": "{fileName} กำลังดาวน์โหลด{position}", + "position": " (ก่อนหน้านี้ #{position} ในคิว)" + } + }, + "profile": { + "password": { + "title": "เปลี่ยนรหัสผ่าน", + "newPassword": "รหัสผ่านใหม่", + "confirmPassword": "ยืนยันรหัสผ่านใหม่", + "updateButton": "อัปเดตรหัสผ่าน" + }, + "form": { + "title": "ข้อมูลโปรไฟล์", + "firstName": "ชื่อจริง", + "lastName": "นามสกุล", + "username": "ชื่อผู้ใช้", + "email": "อีเมล", + "updateButton": "อัปเดตโปรไฟล์" + }, + "header": { + "title": "โปรไฟล์", + "subtitle": "จัดการข้อมูลส่วนบุคคลและรหัสผ่าน" + }, + "picture": { + "title": "รูปโปรไฟล์", + "description": "คลิกไอคอนกล้องเพื่อเปลี่ยนรูปโปรไฟล์ของคุณ", + "uploadPhoto": "อัปโหลดรูป", + "removePhoto": "นำออกรูป" + }, + "errors": { + "loadFailed": "ไม่สามารถโหลดข้อมูลผู้ใช้", + "updateFailed": "ไม่สามารถอัปเดตโปรไฟล์", + "passwordFailed": "ไม่สามารถอัปเดตรหัสผ่าน", + "imageFailed": "ไม่สามารถอัปเดตรูปโปรไฟล์", + "imageRemoveFailed": "ไม่สามารถนำออกรูปโปรไฟล์" + }, + "messages": { + "noChanges": "ไม่มีการเปลี่ยนแปลงเพื่อบันทึก", + "updateSuccess": "อัปเดตโปรไฟล์เรียบร้อยแล้ว", + "fillPasswords": "กรุณากรอกฟิลด์รหัสผ่านทั้งสองช่อง", + "passwordSuccess": "อัปเดตรหัสผ่านเรียบร้อยแล้ว", + "imageSuccess": "อัปเดตรูปโปรไฟล์เรียบร้อยแล้ว", + "imageRemoved": "นำออกรูปโปรไฟล์เรียบร้อยแล้ว" + }, + "pageTitle": "โปรไฟล์" + }, + "qrCodeModal": { + "title": "แชร์รหัส QR", + "description": "สแกนรหัส QR นี้เพื่อเข้าถึงลิงค์", + "download": "ดาวน์โหลดรหัส QR" + }, + "quickAccess": { + "files": { + "title": "ไฟล์ของฉัน", + "description": "เข้าถึงและจัดการไฟล์ที่อัปโหลด" + }, + "shares": { + "title": "แชร์ของฉัน", + "description": "ดูและจัดการไฟล์ที่แชร์" + }, + "reverseShares": { + "title": "รับไฟล์", + "description": "สร้างลิงค์เพื่อให้ผู้อื่นส่งไฟล์ให้คุณ" + } + }, + "recentFiles": { + "title": "การอัปโหลดล่าสุด", + "viewAll": "ดูทั้งหมด", + "upload": "อัปโหลด", + "uploadFile": "อัปโหลดไฟล์", + "noFiles": "ยังไม่มีไฟล์ที่อัปโหลด" + }, + "recentShares": { + "title": "แชร์ล่าสุด", + "viewAll": "ดูทั้งหมด", + "createShare": "สร้างแชร์", + "noShares": "ยังไม่มีแชร์ที่สร้าง", + "createFirst": "สร้างแชร์แรกของคุณ" + }, + "recipientSelector": { + "emailPlaceholder": "ป้อนอีเมลผู้รับ", + "add": "เพิ่ม", + "recipients": "ผู้รับ ({count})", + "notifyAll": "แจ้งเตือนทั้งหมด", + "noRecipients": "ยังไม่ได้เพิ่มผู้รับ", + "addSuccess": "เพิ่มผู้รับเรียบร้อยแล้ว", + "addError": "ไม่สามารถเพิ่มผู้รับ", + "removeSuccess": "นำออกผู้รับเรียบร้อยแล้ว", + "removeError": "ไม่สามารถนำออกผู้รับ", + "sendingNotifications": "กำลังส่งการแจ้งเตือน...", + "notifySuccess": "แจ้งเตือนผู้รับเรียบร้อยแล้ว", + "notifyError": "ไม่สามารถแจ้งเตือนผู้รับ", + "selectAll": "เลือกทั้งหมด", + "selectedCount": "{count} ที่เลือก", + "selectRecipient": "เลือก {email}", + "notifySelected": "แจ้งเตือนที่เลือก", + "removeSelected": "นำออกที่เลือก", + "notifySingle": "แจ้งเตือนผู้รับนี้", + "removeSingle": "นำออกผู้รับนี้", + "bulkRemoveSuccess": "นำออก {count} ผู้รับเรียบร้อยแล้ว", + "bulkRemoveError": "ไม่สามารถนำออกผู้รับที่เลือก", + "bulkNotifySuccess": "ส่งการแจ้งเตือนไปยัง {count} ผู้รับแล้ว", + "bulkNotifyError": "ไม่สามารถแจ้งเตือนผู้รับที่เลือก", + "singleNotifySuccess": "ส่งการแจ้งเตือนไปยัง {email}เรียบร้อยแล้ว", + "singleNotifyError": "ไม่สามารถแจ้งเตือนผู้รับ", + "modalDescription": "เพิ่มและจัดการผู้รับสำหรับแชร์นี้ คุณสามารถแจ้งเตือนทั้งหมดหรือผู้รับเฉพาะเมื่อมีการตั้งค่า SMTP", + "addRecipient": "เพิ่มผู้รับ", + "invalidEmail": "กรุณากรอกที่อยู่อีเมลที่ถูกต้อง", + "duplicateEmail": "ผู้รับนี้ได้รับการเพิ่มแล้ว", + "noRecipientsDescription": "เพิ่มผู้รับเพื่อแชร์เนื้อหาผ่านทางอีเมล" + }, + "register": { + "validation": { + "firstNameRequired": "ต้องระบุชื่อจริง", + "lastNameRequired": "ต้องระบุนามสกุล", + "usernameMinLength": "ชื่อผู้ใช้ต้องมีอย่างน้อย 3 ตัวอักษร", + "invalidEmail": "อีเมลไม่ถูกต้อง", + "passwordMinLength": "รหัสผ่านต้องมีอย่างน้อย 8 ตัวอักษร", + "success": "สร้างผู้ใช้ผู้ดูแลระบบเรียบร้อยแล้ว!", + "error": "เกิดข้อผิดพลาดในการสร้างผู้ใช้ผู้ดูแลระบบ" + }, + "labels": { + "firstName": "ชื่อจริง", + "lastName": "นามสกุล", + "username": "ชื่อผู้ใช้", + "email": "อีเมล", + "password": "รหัสผ่าน" + }, + "buttons": { + "creating": "กำลังสร้าง...", + "createAdmin": "สร้างบัญชีผู้ดูแลระบบ" + } + }, + "resetPassword": { + "pageTitle": "รีเซ็ตรหัสผ่าน", + "header": { + "title": "รีเซ็ตรหัสผ่าน", + "description": "ป้อนรหัสผ่านใหม่ของคุณด้านล่าง" + }, + "form": { + "newPassword": "รหัสผ่านใหม่", + "newPasswordPlaceholder": "ป้อนรหัสผ่านใหม่ของคุณ", + "confirmPassword": "ยืนยันรหัสผ่านใหม่", + "confirmPasswordPlaceholder": "ยืนยันรหัสผ่านใหม่ของคุณ", + "resetting": "กำลังรีเซ็ตรหัสผ่าน...", + "submit": "รีเซ็ตรหัสผ่าน", + "backToLogin": "กลับไปที่หน้าเข้าสู่ระบบ" + }, + "messages": { + "success": "รีเซ็ตรหัสผ่านเรียบร้อยแล้ว" + }, + "errors": { + "serverError": "ไม่สามารถรีเซ็ตรหัสผ่าน โปรดลองใหม่", + "invalidToken": "โทเค็นรีเซ็ตไม่ถูกต้องหรือหายไป" + } + }, + "reverseShares": { + "pageTitle": "รับไฟล์", + "search": { + "title": "จัดการลิงค์การรับ", + "createButton": "สร้างลิงค์", + "placeholder": "ค้นหาลิงค์การรับ...", + "results": "พบ {filtered} จาก {total} ลิงค์การรับ" + }, + "labels": { + "files": "ไฟล์", + "size": "ขนาด", + "status": "สถานะ", + "access": "การเข้าถึง", + "description": "คำอธิบาย", + "pageLayout": "เค้าโครงหน้า", + "security": "ความปลอดภัยและสถานะ", + "limits": "ข้อจำกัด", + "maxFiles": "ไฟล์สูงสุด", + "maxFileSize": "ขนาดสูงสุด", + "allowedTypes": "ประเภทที่อนุญาต", + "filesReceived": "ไฟล์ที่ได้รับ", + "fileLimit": "ขีดจำกัดไฟล์", + "noLimit": "ไม่มีขีดจำกัด", + "noLinkCreated": "ยังไม่มีลิงค์ที่สร้าง", + "publicAccess": "การเข้าถึงแบบสาธารณะ", + "protectedByPassword": "ป้องกันด้วยรหัสผ่าน", + "configureProtection": "คลิกเพื่อตั้งค่าการป้องกัน", + "enterPassword": "ป้อนรหัสผ่าน", + "thisLinkProtected": "ลิงค์นี้จะปกป้องด้วยรหัสผ่าน", + "thisLinkPublic": "ลิงค์นี้จะเป็นสาธารณะ", + "configureExpiration": "ตั้งค่าการหมดอายุ", + "configureLimits": "ตั้งค่าขีดจำกัดไฟล์", + "protectWithPassword": "ปกป้องด้วยรหัสผ่าน", + "layoutOptions": { + "default": "ค่าเริ่มต้น", + "wetransfer": "WeTransfer" + }, + "noFilesLimit": "ไม่มีขีดจำกัดไฟล์", + "noSizeLimit": "ไม่มีขีดจำกัดขนาด", + "allFileTypes": "ไฟล์ทุกประเภท", + "fileTypesHelp": "ป้อนนามสกุลโดยไม่มีจุด คั่นด้วยช่องว่าง ลูกน้ำ ขีดกลาง หรือท่อ", + "fieldRequirements": "ข้อกำหนดฟิลด์", + "nameFieldRequired": "ฟิลด์ชื่อ", + "emailFieldRequired": "ฟิลด์อีเมล", + "fieldOptions": { + "hidden": "ซ่อน", + "optional": "ไม่บังคับ", + "required": "ต้องระบุ" + } + }, + "card": { + "untitled": "ลิงค์ที่ไม่มีชื่อ", + "noDescription": "ไม่มีคำอธิบาย", + "addDescriptionPlaceholder": "เพิ่มคำอธิบาย...", + "files": "ไฟล์", + "progress": "ความคืบหน้า", + "created": "สร้างแล้ว", + "expired": "หมดอายุ", + "expires": "หมดอายุ", + "viewDetails": "ดูรายละเอียด", + "viewQrCode": "ดูรหัส QR", + "copyLink": "คัดลอกลิงค์", + "openInNewTab": "เปิดในแท็บใหม่", + "editLink": "แก้ไขลิงค์", + "createLink": "สร้างลิงค์", + "delete": "ลบ", + "copyLinkTitle": "คัดลอกลิงค์", + "createLinkCTA": "สร้างลิงค์การรับ" + }, + "status": { + "active": "ใช้งานอยู่", + "inactive": "ไม่ใช้งาน", + "expired": "หมดอายุ", + "protected": "ปกป้อง", + "public": "สาธารณะ" + }, + "actions": { + "copyLink": "คัดลอกลิงค์", + "editAlias": "แก้ไขนามแฝง", + "createAlias": "สร้างนามแฝง", + "viewDetails": "ดูรายละเอียด", + "edit": "แก้ไข", + "delete": "ลบ", + "viewFiles": "ไฟล์ที่ได้รับ", + "viewQrCode": "ดูรหัส QR" + }, + "empty": { + "title": "ยังไม่มีลิงค์การรับที่สร้าง", + "description": "สร้างลิงค์ที่กำหนดเองเพื่อให้ผู้อื่นส่งไฟล์โดยตรงให้คุณอย่างปลอดภัยและเป็นระเบียบ", + "createButton": "สร้างลิงค์แรก" + }, + "modals": { + "create": { + "title": "สร้างลิงค์การรับ", + "description": "ตั้งค่าลิงค์ที่กำหนดเองเพื่อรับไฟล์จากผู้อื่น" + }, + "edit": { + "title": "แก้ไขลิงค์การรับ", + "description": "อัปเดตการตั้งค่าสำหรับลิงค์การรับนี้", + "updating": "กำลังอัปเดต...", + "saveChanges": "บันทึกการเปลี่ยนแปลง" + }, + "details": { + "title": "รายละเอียดลิงค์", + "description": "ดูและแก้ไขข้อมูลลิงค์การรับของคุณ", + "pageLayout": "เค้าโครงหน้า", + "linkSection": "ลิงค์การรับ", + "noLinkCreated": "ยังไม่มีลิงค์ที่สร้าง", + "limits": "ข้อจำกัด", + "maxFiles": "ไฟล์สูงสุด", + "maxFileSize": "ขนาดสูงสุด", + "allowedTypes": "ประเภทที่อนุญาต", + "noLimit": "ไม่มีขีดจำกัด", + "security": "ความปลอดภัย", + "status": "สถานะ", + "password": "รหัสผ่าน", + "files": "ไฟล์ที่ได้รับ", + "noFiles": "ยังไม่มีไฟล์ที่ได้รับ", + "copyLink": "คัดลอกลิงค์", + "openLink": "เปิดลิงค์", + "editAlias": "แก้ไขนามแฝง", + "createAlias": "สร้างนามแฝง", + "editPassword": "แก้ไขการป้องกันด้วยรหัสผ่าน", + "basicInfo": "ข้อมูลพื้นฐาน", + "securityAndStatus": "ความปลอดภัยและสถานะ", + "protection": "การป้องกัน", + "protectedByPassword": "ปกป้องด้วยรหัสผ่าน", + "publicAccess": "การเข้าถึงแบบสาธารณะ", + "active": "ใช้งานอยู่", + "inactive": "ไม่ใช้งาน", + "deactivate": "ปิดใช้งาน", + "activate": "เปิดใช้งาน", + "expiration": "การหมดอายุ", + "dates": "วันที่", + "createdAt": "สร้างเมื่อ", + "updatedAt": "อัปเดตเมื่อ", + "allTypes": "ทั้งหมด", + "placeholderTypes": ".pdf,.jpg,.png (คั่นด้วยลูกน้ำ)", + "downloadSuccess": "เริ่มดาวน์โหลดแล้ว", + "downloadError": "เกิดข้อผิดพลาดในการดาวน์โหลดไฟล์", + "editSuccess": "อัปเดตไฟล์เรียบร้อยแล้ว", + "editError": "เกิดข้อผิดพลาดในการอัปเดตไฟล์", + "previewNotAvailable": "ไม่มีตัวอย่างพร้อมใช้งาน", + "notAvailable": "ไม่พร้อมใช้งาน", + "invalidDate": "วันที่ไม่ถูกต้อง" + }, + "alias": { + "editTitle": "แก้ไขนามแฝง", + "createTitle": "สร้างนามแฝง", + "editDescription": "อัปเดตนามแฝงสำหรับลิงค์การรับนี้", + "createDescription": "สร้างนามแฝงที่กำหนดเองสำหรับลิงค์การรับนี้", + "aliasLabel": "นามแฝงลิงค์", + "aliasPlaceholder": "my-custom-link", + "preview": "ตัวอย่าง:", + "currentLink": "ลิงค์ปัจจุบัน:", + "copyCurrentLink": "คัดลอกลิงค์ปัจจุบัน", + "randomTooltip": "สร้างนามแฝงแบบสุ่ม", + "cancel": "ยกเลิก", + "creating": "กำลังสร้าง...", + "updating": "กำลังอัปเดต...", + "create": "สร้างนามแฝง", + "update": "อัปเดตนามแฝง", + "validation": { + "required": "ต้องระบุนามแฝง", + "minLength": "นามแฝงต้องมีอย่างน้อย 3 ตัวอักษร", + "maxLength": "นามแฝงต้องมีไม่เกิน 50 ตัวอักษร", + "pattern": "นามแฝงต้องมีเฉพาะตัวอักษร ตัวเลข ขีดและเครื่องหมายขีดใต้เท่านั้น" + }, + "help": "3-50 ตัวอักษร ช่องว่างจะถูกแปลงเป็นขีดอัตโนมัติ" + }, + "password": { + "title": "แก้ไขการป้องกันด้วยรหัสผ่าน", + "description": "ตั้งค่าการป้องกันด้วยรหัสผ่านสำหรับลิงค์นี้", + "hasPassword": "ปกป้องด้วยรหัสผ่าน", + "password": "รหัสผ่าน", + "cancel": "ยกเลิก", + "save": "บันทึก", + "saving": "กำลังบันทึก..." + }, + "receivedFiles": { + "title": "ไฟล์ที่ได้รับ", + "description": "ดูและจัดการไฟล์ที่ส่งไปยังลิงค์นี้", + "noFiles": "ยังไม่มีไฟล์ที่ได้รับ", + "noFilesDescription": "ไฟล์ที่ส่งผ่านลิงค์นี้จะปรากฏที่นี่", + "fileCount": "{count, plural, =0 {ไม่มีไฟล์} =1 {1 ไฟล์} other {# ไฟล์}}", + "invalidDate": "วันที่ไม่ถูกต้อง", + "totalSize": "ขนาดรวม: {size}", + "columns": { + "file": "ไฟล์", + "size": "ขนาด", + "sender": "ส่งโดย", + "date": "วันที่", + "actions": "การกระทำ" + }, + "actions": { + "preview": "ตัวอย่าง", + "download": "ดาวน์โหลด", + "copyToMyFiles": "คัดลอกไปยังไฟล์ของฉัน", + "copying": "กำลังคัดลอก..." + }, + "uploadedBy": "อัปโหลดโดย {name}", + "anonymous": "ไม่ระบุตัวตน", + "downloadSuccess": "เริ่มดาวน์โหลดแล้ว", + "downloadError": "เกิดข้อผิดพลาดในการดาวน์โหลดไฟล์", + "editSuccess": "อัปเดตไฟล์เรียบร้อยแล้ว", + "editError": "เกิดข้อผิดพลาดในการอัปเดตไฟล์", + "previewNotAvailable": "ไม่มีตัวอย่างพร้อมใช้งาน", + "copySuccess": "คัดลอกไฟล์ไปยังไฟล์ของคุณเรียบร้อยแล้ว", + "copyError": "เกิดข้อผิดพลาดในการคัดลอกไฟล์ไปยังไฟล์ของคุณ", + "deleteSuccess": "ลบไฟล์เรียบร้อยแล้ว", + "deleteError": "เกิดข้อผิดพลาดในการลบไฟล์", + "bulkCopySuccess": "{count, plural, =1 {คัดลอก 1 ไฟล์ไปยังไฟล์ของคุณเรียบร้อยแล้ว} other {คัดลอก # ไฟล์ไปยังไฟล์ของคุณเรียบร้อยแล้ว}}", + "bulkDeleteSuccess": "{count, plural, =1 {ลบ 1 ไฟล์เรียบร้อยแล้ว} other {ลบ # ไฟล์เรียบร้อยแล้ว}}", + "bulkCopyProgress": "กำลังคัดลอก {count, plural, =1 {1 ไฟล์} other {# ไฟล์}} ไปยังไฟล์ของคุณ...", + "bulkDeleteProgress": "กำลังลบ {count, plural, =1 {1 ไฟล์} other {# ไฟล์}}...", + "bulkDeleteConfirmTitle": "ลบไฟล์ที่เลือก", + "bulkDeleteConfirmMessage": "คุณแน่ใจหรือไม่ว่าต้องการลบ {count, plural, =1 {ไฟล์นี้} other {ไฟล์ # นี้}}? ไม่สามารถเลิกทำการกระทำนี้ได้", + "bulkDeleteConfirmButton": "ลบ {count, plural, =1 {ไฟล์} other {ไฟล์}}", + "bulkActions": { + "selected": "{count, plural, =1 {เลือก 1 ไฟล์} other {เลือก # ไฟล์}}", + "actions": "การกระทำ", + "download": "ดาวน์โหลดที่เลือก", + "copyToMyFiles": "คัดลอกที่เลือกไปยังไฟล์ของฉัน", + "delete": "ลบที่เลือก" + }, + "selectAll": "เลือกทั้งหมด", + "selectFile": "เลือกไฟล์ {fileName}", + "copyErrors": { + "timeout": "หมดเวลาการคัดลอก โปรดลองใหม่ด้วยไฟล์ที่เล็กกว่าหรือตรวจสอบการเชื่อมต่อของคุณ", + "failed": "การคัดลอกล้มเหลว โปรดลองใหม่", + "aborted": "การคัดลอกถูกยกเลิกเนื่องจากหมดเวลา" + } + } + }, + "form": { + "name": { + "label": "ชื่อลิงค์", + "placeholder": "เช่น เอกสารโครงการ ภาพครอบครัว..." + }, + "description": { + "label": "คำอธิบาย", + "placeholder": "อธิบายว่าคุณคาดหวังให้ส่งไฟล์ประเภทใด...", + "description": "ไม่บังคับ ช่วยให้คนเข้าใจว่าจะส่งอะไร" + }, + "status": { + "label": "สถานะลิงค์", + "description": "เปิดใช้งานหรือปิดใช้งานลิงค์การรับนี้" + }, + "expiration": { + "label": "วันที่หมดอายุ", + "description": "ไม่บังคับ ลิงค์จะปิดใช้งานหลังจากวันที่นี้", + "configure": "ตั้งค่าการหมดอายุ" + }, + "fileLimits": { + "configure": "ตั้งค่าขีดจำกัดไฟล์" + }, + "maxFiles": { + "label": "ไฟล์สูงสุด", + "placeholder": "เช่น 10", + "description": "ไม่บังคับ จำกัดจำนวนไฟล์ทั้งหมดที่สามารถส่งได้", + "noLimit": "ไม่มีขีดจำกัดไฟล์" + }, + "maxFileSize": { + "label": "ขนาดไฟล์สูงสุด", + "placeholder": "เช่น 100", + "description": "ไม่บังคับ จำกัดขนาดของแต่ละไฟล์", + "noLimit": "ไม่มีขีดจำกัดขนาด" + }, + "allowedFileTypes": { + "label": "ประเภทไฟล์ที่อนุญาต", + "placeholder": "เช่น pdf, jpg, png, docx", + "description": "ป้อนนามสกุลโดยไม่มีจุด คั่นด้วยช่องว่าง ลูกน้ำ ขีดกลาง หรือท่อ", + "allTypes": "ไฟล์ทุกประเภท" + }, + "pageLayout": { + "label": "เค้าโครงหน้า", + "placeholder": "เลือกเค้าโครง", + "description": "วิธีการปรากฏของหน้าการอัปโหลดต่อผู้ใช้", + "options": { + "default": "เค้าโครงค่าเริ่มต้น", + "wetransfer": "สไตล์ WeTransfer" + } + }, + "password": { + "label": "รหัสผ่านการป้องกัน", + "placeholder": "ไม่บังคับ เพิ่มรหัสผ่านเพื่อปกป้องลิงค์", + "description": "ไม่บังคับ ผู้ใช้จะต้องมีรหัสผ่านนี้เพื่อเข้าถึงลิงค์", + "configurePassword": "ตั้งค่ารหัสผ่าน", + "protectWithPassword": "ปกป้องด้วยรหัสผ่าน", + "passwordHelp": "รหัสผ่านต้องมีอย่างน้อย 4 ตัวอักษร", + "passwordPlaceholder": "ป้อนรหัสผ่านเพื่อปกป้องลิงค์" + }, + "nameFieldRequired": { + "label": "ข้อกำหนดของฟิลด์ชื่อ", + "description": "ตั้งค่าว่าควรแสดงฟิลด์ชื่อผู้อัปโหลดหรือไม่และจำเป็นหรือไม่" + }, + "emailFieldRequired": { + "label": "ข้อกำหนดของฟิลด์อีเมล", + "description": "ตั้งค่าว่าควรแสดงฟิลด์อีเมลผู้อัปโหลดหรือไม่และจำเป็นหรือไม่" + }, + "fieldRequirements": { + "title": "ข้อกำหนดฟิลด์", + "description": "ตั้งค่าฟิลด์ใดที่จะแสดงในแบบฟอร์มการอัปโหลด" + }, + "submit": "สร้างลิงค์การรับ" + }, + "messages": { + "created": "สร้างลิงค์การรับเรียบร้อยแล้ว!", + "createSuccess": "สร้างลิงค์การรับเรียบร้อยแล้ว!", + "updateSuccess": "อัปเดตลิงค์การรับเรียบร้อยแล้ว!", + "linkCopied": "คัดลอกลิงค์ไปยังคลิปบอร์ดแล้ว!", + "deleteSuccess": "ลบลิงค์การรับเรียบร้อยแล้ว!", + "aliasCreated": "สร้างนามแฝงเรียบร้อยแล้ว!", + "activateSuccess": "เปิดใช้งานลิงค์การรับเรียบร้อยแล้ว!", + "deactivateSuccess": "ปิดใช้งานลิงค์การรับเรียบร้อยแล้ว!", + "passwordProtectionEnabled": "เปิดใช้งานการป้องกันด้วยรหัสผ่านเรียบร้อยแล้ว!", + "passwordProtectionDisabled": "นำออกการป้องกันด้วยรหัสผ่านเรียบร้อยแล้ว!" + }, + "defaultLinkName": "ไฟล์ที่ได้รับ", + "errors": { + "loadFailed": "ไม่สามารถโหลดลิงค์การรับ", + "createFailed": "ไม่สามารถสร้างลิงค์การรับ โปรดลองใหม่", + "updateFailed": "ไม่สามารถอัปเดตลิงค์การรับ โปรดลองใหม่", + "deleteFailed": "ไม่สามารถลบลิงค์การรับ โปรดลองใหม่", + "aliasCreateFailed": "ไม่สามารถสร้างนามแฝง โปรดลองใหม่", + "passwordUpdateFailed": "ไม่สามารถอัปเดตการป้องกันด้วยรหัสผ่าน" + }, + "delete": { + "title": "ลบลิงค์การรับ", + "description": "ไม่สามารถเลิกทำการกระทำนี้ได้ ลิงค์จะถูกลบอย่างถาวรและจะไม่สามารถรับไฟล์ได้อีกต่อไป", + "confirmButton": "ลบลิงค์", + "cancelButton": "ยกเลิก", + "deleting": "กำลังลบ..." + }, + "upload": { + "metadata": { + "title": "ส่งไฟล์ - Palmr", + "description": "ส่งไฟล์ผ่านลิงค์ที่แชร์", + "descriptionWithLimit": "อัปโหลดไฟล์ (ไฟล์สูงสุด {limit} ไฟล์)" + }, + "layout": { + "defaultTitle": "ส่งไฟล์", + "importantInfo": "ข้อมูลสำคัญ:", + "maxFiles": "ไฟล์สูงสุด {count} ไฟล์", + "maxFileSize": "ขนาดไฟล์สูงสุด: {size}MB", + "allowedTypes": "ประเภทที่อนุญาต: {types}", + "loading": "กำลังโหลด..." + }, + "password": { + "title": "ลิงค์ป้องกัน", + "description": "ลิงค์นี้ปกป้องด้วยรหัสผ่าน ป้อนรหัสผ่านเพื่อดำเนิน", + "label": "รหัสผ่าน", + "placeholder": "ป้อนรหัสผ่าน", + "cancel": "ยกเลิก", + "submit": "ดำเนิน", + "verifying": "กำลังตรวจสอบ..." + }, + "errors": { + "loadFailed": "ไม่สามารถโหลดข้อมูล โปรดลองใหม่", + "passwordIncorrect": "รหัสผ่านไม่ถูกต้อง โปรดลองใหม่", + "linkNotFound": "ไม่พบลิงค์หรือหมดอายุ", + "linkInactive": "ลิงค์นี้ไม่ใช้งาน", + "linkExpired": "ลิงค์นี้หมดอายุ", + "uploadFailed": "เกิดข้อผิดพลาดในการอัปโหลดไฟล์", + "retry": "ลองใหม่", + "fileTooLarge": "ไฟล์ขนาดใหญ่เกินไป ขนาดสูงสุด: {maxSize}", + "fileTypeNotAllowed": "ประเภทไฟล์ไม่ได้รับอนุญาต ประเภทที่ยอมรับ: {allowedTypes}", + "maxFilesExceeded": "อนุญาตได้ไม่เกิน {maxFiles} ไฟล์", + "selectAtLeastOneFile": "เลือกไฟล์อย่างน้อยหนึ่งไฟล์", + "provideNameOrEmail": "โปรดระบุชื่อหรืออีเมลของคุณ", + "provideNameRequired": "ต้องระบุชื่อ", + "provideEmailRequired": "ต้องระบุอีเมล" + }, + "fileDropzone": { + "dragActive": "วางไฟล์ที่นี่", + "dragInactive": "ลากไฟล์ที่นี่หรือคลิกเพื่อเลือก", + "acceptedTypes": "ประเภทที่ยอมรับ: {types}", + "maxFileSize": "ขนาดสูงสุด: {size}", + "maxFiles": "ไฟล์สูงสุด {count} ไฟล์", + "remainingFiles": "{remaining} จาก {max} ไฟล์ที่เหลือ" + }, + "fileList": { + "title": "ไฟล์ที่เลือก:", + "statusUploaded": "อัปโหลดแล้ว", + "statusError": "ข้อผิดพลาด", + "retry": "ลองใหม่" + }, + "form": { + "nameLabel": "ชื่อ", + "nameLabelOptional": "ชื่อ (ไม่บังคับ)", + "namePlaceholder": "ชื่อของคุณ", + "emailLabel": "อีเมล", + "emailLabelOptional": "อีเมล (ไม่บังคับ)", + "emailPlaceholder": "your@email.com", + "descriptionLabel": "คำอธิบาย (ไม่บังคับ)", + "descriptionPlaceholder": "เพิ่มคำอธิบายสำหรับไฟล์...", + "uploadButton": "ส่ง {count} ไฟล์", + "uploading": "กำลังส่ง..." + }, + "success": { + "title": "ส่งไฟล์เรียบร้อยแล้ว! 🎉", + "description": "คุณสามารถปิดหน้านี้ได้", + "countMessage": "{count} ไฟล์ส่งเรียบร้อยแล้ว!" + }, + "maxFilesReached": { + "title": "ถึงขีดจำกัดไฟล์", + "description": "ลิงค์นี้ได้รับไฟล์ทั้งหมด {maxFiles} ไฟล์ที่อนุญาต", + "contactOwner": "หากมีข้อผิดพลาดหรือต้องการส่งไฟล์เพิ่มเติม โปรดติดต่อเจ้าของลิงค์" + }, + "linkInactive": { + "title": "ลิงค์ไม่ใช้งาน", + "description": "ลิงค์การรับนี้ปิดใช้งานชั่วคราว", + "contactOwner": "โปรดติดต่อเจ้าของลิงค์เพื่อข้อมูลเพิ่มเติม" + }, + "linkNotFound": { + "title": "ไม่พบลิงค์", + "description": "ลิงค์นี้อาจถูกลบหรือไม่เคยมีอยู่" + }, + "linkExpired": { + "title": "ลิงค์หมดอายุ", + "description": "ลิงค์การรับนี้หมดอายุและไม่ยอมรับไฟล์อีกต่อไป", + "contactOwner": "โปรดติดต่อเจ้าของลิงค์หากคุณต้องการส่งไฟล์" + } + }, + "components": { + "fileRow": { + "addDescription": "เพิ่มคำอธิบาย...", + "anonymous": "ไม่ระบุตัวตน" + }, + "fileActions": { + "edit": "แก้ไข", + "preview": "ตัวอย่าง", + "download": "ดาวน์โหลด", + "delete": "ลบ", + "copyToMyFiles": "คัดลอกไปยังไฟล์ของฉัน", + "copying": "กำลังคัดลอก..." + }, + "editField": { + "saveChanges": "บันทึกการเปลี่ยนแปลง", + "cancelEdit": "ยกเลิกการแก้ไข" + } + } + }, + "searchBar": { + "placeholder": "ค้นหาไฟล์และโฟลเดอร์...", + "placeholderFiles": "ค้นหาไฟล์...", + "placeholderFolders": "ค้นหาโฟลเดอร์...", + "results": "แสดง {filtered} จาก {total} รายการ", + "noResults": "ไม่พบผลลัพธ์สำหรับ \"{query}\"" + }, + "settings": { + "groups": { + "defaultDescription": "ตัวเลือกการตั้งค่า", + "general": { + "title": "ทั่วไป", + "description": "การตั้งค่าแอปพลิเคชันขั้นพื้นฐาน" + }, + "email": { + "title": "อีเมล", + "description": "การตั้งค่าเซิร์ฟเวอร์อีเมล" + }, + "security": { + "title": "ความปลอดภัย", + "description": "การตั้งค่าความปลอดภัยและการยืนยันตัวตน" + }, + "storage": { + "title": "จัดเก็บข้อมูล", + "description": "การตั้งค่าการจัดเก็บไฟล์" + }, + "oidc": { + "title": "OpenID Connect (SSO)", + "description": "ตั้งค่า SSO ผ่าน OpenID Connect" + } + }, + "fields": { + "noDescription": "ไม่มีคำอธิบายพร้อมใช้งาน", + "appLogo": { + "title": "โลโก้แอปพลิเคชัน", + "description": "รูปภาพโลโก้แอปพลิเคชัน" + }, + "appName": { + "title": "ชื่อแอปพลิเคชัน", + "description": "ชื่อแอปพลิเคชันที่แสดงให้ผู้ใช้" + }, + "appDescription": { + "title": "คำอธิบายแอปพลิเคชัน", + "description": "คำอธิบายสั้นของแอปพลิเคชัน" + }, + "showHomePage": { + "title": "แสดงหน้าแรก", + "description": "แสดงหน้าแรกหลังการติดตั้ง" + }, + "hideVersion": { + "title": "ซ่อนเวอร์ชัน", + "description": "ซ่อนเวอร์ชัน Palmr จากท้ายเพจ" + }, + "smtpEnabled": { + "title": "เปิดใช้งาน SMTP", + "description": "เปิดหรือปิดใช้งานการทำงาน SMTP" + }, + "smtpHost": { + "title": "เซิร์ฟเวอร์ SMTP", + "description": "ที่อยู่เซิร์ฟเวอร์ SMTP" + }, + "smtpPort": { + "title": "พอร์ต SMTP", + "description": "พอร์ตเซิร์ฟเวอร์ SMTP" + }, + "smtpUser": { + "title": "ชื่อผู้ใช้ SMTP", + "description": "ชื่อผู้ใช้สำหรับการยืนยันตัวตน SMTP" + }, + "smtpPass": { + "title": "รหัสผ่าน SMTP", + "description": "รหัสผ่านสำหรับการยืนยันตัวตน SMTP" + }, + "smtpFromName": { + "title": "ชื่อผู้ส่ง", + "description": "ชื่อที่แสดงสำหรับอีเมลที่ส่ง" + }, + "smtpFromEmail": { + "title": "อีเมลผู้ส่ง", + "description": "ที่อยู่อีเมลผู้ส่ง" + }, + "smtpSecure": { + "title": "ความปลอดภัยการเชื่อมต่อ", + "description": "วิธีการความปลอดภัยการเชื่อมต่อ SMTP - Auto (แนะนำ) SSL STARTTLS หรือ None (ไม่ปลอดภัย)", + "options": { + "auto": "Auto (แนะนำ)", + "ssl": "SSL (พอร์ต 465)", + "tls": "STARTTLS (พอร์ต 587)", + "none": "ไม่มี (ไม่ปลอดภัย)" + } + }, + "smtpNoAuth": { + "title": "ไม่มีการยืนยันตัวตน", + "description": "เปิดใช้งานสำหรับเซิร์ฟเวอร์ภายในที่ไม่ต้องชื่อผู้ใช้/รหัสผ่าน (ซ่อนฟิลด์การยืนยันตัวตน)" + }, + "smtpTrustSelfSigned": { + "title": "เชื่อถือใบรับรอง Self-Signed", + "description": "เปิดใช้งานเพื่อเชื่อถือใบรับรอง SSL/TLS แบบ self-signed (มีประโยชน์สำหรับสภาวะการพัฒนา)" + }, + "testSmtp": { + "title": "ทดสอบการเชื่อมต่อ SMTP", + "description": "ทดสอบว่าการตั้งค่า SMTP ถูกต้องหรือไม่" + }, + "maxLoginAttempts": { + "title": "การพยายามเข้าสู่ระบบสูงสุด", + "description": "จำนวนการพยายามเข้าสู่ระบบสูงสุดก่อนที่จะถูกบล็อก" + }, + "loginBlockDuration": { + "title": "ระยะเวลาบล็อก", + "description": "ระยะเวลา (เป็นวินาที) เพื่อบล็อกหลังจากเกินการพยายาม" + }, + "passwordMinLength": { + "title": "ความยาวรหัสผ่านขั้นต่ำ", + "description": "จำนวนตัวอักษรขั้นต่ำสำหรับรหัสผ่าน" + }, + "passwordResetTokenExpiration": { + "title": "การหมดอายุของโทเค็นรีเซ็ต", + "description": "เวลาที่ใช้ได้ (เป็นวินาที) สำหรับโทเค็นรีเซ็ตรหัสผ่าน" + }, + "maxFileSize": { + "title": "ขนาดไฟล์สูงสุด", + "description": "ขนาดไฟล์สูงสุดที่อนุญาตสำหรับการอัปโหลด" + }, + "maxTotalStoragePerUser": { + "title": "จัดเก็บสูงสุดต่อผู้ใช้", + "description": "ขีดจำกัดการจัดเก็บทั้งหมดต่อผู้ใช้" + }, + "firstUserAccess": { + "title": "การเข้าถึงผู้ใช้คนแรก", + "description": "การตั้งค่าสำหรับการเข้าถึงครั้งแรกของผู้ใช้ใหม่" + }, + "serverUrl": { + "title": "URL เซิร์ฟเวอร์", + "description": "URL ฐานของเซิร์ฟเวอร์ Palmr (เช่น https://palmr.example.com)" + }, + "passwordAuthEnabled": { + "title": "การยืนยันตัวตนด้วยรหัสผ่าน", + "description": "เปิดหรือปิดใช้งานการยืนยันตัวตนโดยใช้รหัสผ่าน" + } + }, + "buttons": { + "save": "บันทึก {group}", + "testSmtp": "ทดสอบการเชื่อมต่อ", + "testing": "กำลังทดสอบ..." + }, + "errors": { + "loadFailed": "ไม่สามารถโหลดการตั้งค่า", + "updateFailed": "ไม่สามารถอัปเดตการตั้งค่า", + "passwordAuthRequiresProvider": "ไม่สามารถปิดใช้งานการยืนยันตัวตนด้วยรหัสผ่านโดยไม่มีผู้ให้บริการยืนยันตัวตนที่ใช้งานอยู่" + }, + "messages": { + "noChanges": "ไม่มีการเปลี่ยนแปลง", + "updateSuccess": "อัปเดตการตั้งค่า {group} เรียบร้อยแล้ว", + "smtpTestSuccess": "การเชื่อมต่อ SMTP สำเร็จ! การตั้งค่าอีเมลของคุณใช้งานได้ถูกต้อง", + "smtpTestFailed": "การเชื่อมต่อ SMTP ล้มเหลว: {error}", + "smtpTestGenericError": "ไม่สามารถทดสอบการเชื่อมต่อ SMTP โปรดตรวจสอบการตั้งค่าของคุณแล้วลองใหม่", + "smtpNotEnabled": "ปิดใช้งาน SMTP แล้ว โปรดเปิดใช้งาน SMTP ก่อน", + "smtpMissingHostPort": "โปรดกรอกพอร์ต SMTP Host และ SMTP ก่อนทดสอบ", + "smtpMissingAuth": "โปรดกรอกชื่อผู้ใช้ SMTP และรหัสผ่าน หรือเปิดใช้งานตัวเลือก 'ไม่มีการยืนยันตัวตน'" + }, + "title": "การตั้งค่า", + "breadcrumb": "การตั้งค่า", + "pageTitle": "การตั้งค่า", + "tooltips": { + "testSmtp": "ทดสอบการเชื่อมต่อ SMTP ด้วยค่าที่ป้อนในแบบฟอร์ม หากต้องการให้การเปลี่ยนแปลงถาวร อย่าลืมบันทึกการตั้งค่าหลังจากการทดสอบ", + "defaultPlaceholder": "ป้อนและกด Enter" + }, + "redirectUri": { + "placeholder": "https://mysite.com", + "previewLabel": "URL ที่สมบูรณ์ที่จะบันทึก:" + } + }, + "share": { + "errors": { + "invalidPassword": "รหัสผ่านไม่ถูกต้อง โปรดลองอีกครั้ง", + "loadFailed": "ไม่สามารถโหลดแชร์", + "downloadFailed": "ไม่สามารถดาวน์โหลดไฟล์" + }, + "messages": { + "downloadStarted": "เริ่มดาวน์โหลดแล้ว" + }, + "password": { + "title": "แชร์ที่ป้องกันด้วยรหัสผ่าน", + "protected": "แชร์นี้ป้องกันด้วยรหัสผ่าน", + "incorrect": "รหัสผ่านไม่ถูกต้อง โปรดลองอีกครั้ง", + "label": "รหัสผ่าน", + "placeholder": "ป้อนรหัสผ่านของแชร์", + "submit": "ส่ง" + }, + "details": { + "untitled": "แชร์ที่ไม่มีชื่อ", + "created": "สร้างเมื่อ: {date}", + "expires": "หมดอายุ: {date}" + }, + "downloadAll": "ดาวน์โหลดทั้งหมด", + "notFound": { + "title": "ไม่พบแชร์", + "description": "แชร์นี้อาจถูกลบหรือหมดอายุแล้ว" + }, + "pageTitle": "แชร์", + "metadata": { + "defaultDescription": "แชร์ไฟล์อย่างปลอดภัย", + "filesShared": "{count, plural, =1 {แชร์ 1 ไฟล์} other {แชร์ # ไฟล์}}" + } + }, + "shareActions": { + "fileTitle": "แชร์ไฟล์", + "folderTitle": "แชร์โฟลเดอร์", + "linkTitle": "สร้างลิงก์", + "linkDescriptionFile": "สร้างลิงก์ที่เป็นเอกลักษณ์เพื่อแชร์ไฟล์", + "linkDescriptionFolder": "สร้างลิงก์ที่เป็นเอกลักษณ์เพื่อแชร์โฟลเดอร์", + "aliasLabel": "นามแฝงลิงก์", + "aliasPlaceholder": "ป้อนนามแฝงที่เป็นเอกลักษณ์", + "linkReady": "ลิงก์แชร์ของคุณพร้อมแล้ว:", + "generateLink": "สร้างลิงก์", + "copyLink": "คัดลอกลิงก์", + "deleteTitle": "ลบแชร์", + "deleteConfirmation": "คุณแน่ใจว่าต้องการลบแชร์นี้หรือไม่? ไม่สามารถเลิกทำการกระทำนี้ได้", + "addDescriptionPlaceholder": "เพิ่มคำอธิบาย...", + "editTitle": "แก้ไขแชร์", + "newPasswordLabel": "รหัสผ่านใหม่ (ปล่อยว่างไว้เพื่อเก็บรหัสผ่านปัจจุบัน)", + "newPasswordPlaceholder": "ป้อนรหัสผ่านใหม่", + "manageFilesTitle": "จัดการไฟล์", + "manageFilesDescription": "เลือกไฟล์และโฟลเดอร์ที่จะรวมในแชร์นี้", + "manageRecipientsTitle": "จัดการผู้รับ", + "itemsSelected": "เลือก {count} รายการแล้ว", + "editSuccess": "อัปเดตแชร์เรียบร้อยแล้ว", + "editError": "ไม่สามารถอัปเดตแชร์", + "bulkDeleteConfirmation": "คุณแน่ใจว่าต้องการลบ {count, plural, =1 {1 แชร์} other {# แชร์}} หรือไม่? ไม่สามารถเลิกทำการกระทำนี้ได้", + "bulkDeleteTitle": "ลบแชร์ที่เลือก" + }, + "shareDetails": { + "title": "รายละเอียดแชร์", + "subtitle": "ดูและจัดการรายละเอียดแชร์นี้", + "basicInfo": "ข้อมูลพื้นฐาน", + "name": "ชื่อ", + "description": "คำอธิบาย", + "shareLink": "ลิงก์แชร์", + "dates": "วันที่", + "security": "ความปลอดภัย", + "files": "ไฟล์", + "recipients": "ผู้รับ", + "views": "มุมมอง", + "created": "สร้างเมื่อ", + "expires": "หมดอายุ", + "never": "ไม่เคย", + "untitled": "แชร์ที่ไม่มีชื่อ", + "noDescription": "ไม่มีคำอธิบาย", + "notAvailable": "ไม่พร้อมใช้งาน", + "invalidDate": "วันที่ไม่ถูกต้อง", + "passwordProtected": "ป้องกันด้วยรหัสผ่าน", + "publicAccess": "การเข้าถึงสาธารณะ", + "maxViews": "มุมมองสูงสุด:", + "noLink": "ไม่มีลิงก์ที่สร้าง", + "generateLink": "สร้างลิงก์", + "editLink": "แก้ไขลิงก์", + "copyLink": "คัดลอกลิงก์", + "openLink": "เปิดลิงก์", + "editSecurity": "แก้ไขความปลอดภัย", + "editExpiration": "แก้ไขการหมดอายุ", + "qrCode": "รหัส QR", + "downloadQrCode": "ดาวน์โหลดรหัส QR", + "clickToEnlargeQrCode": "คลิกเพื่อขยายรหัส QR", + "loadError": "ไม่สามารถโหลดรายละเอียดแชร์" + }, + "shareExpiration": { + "title": "การตั้งค่าการหมดอายุของแชร์", + "subtitle": "กำหนดเวลาที่แชร์นี้จะหมดอายุ", + "currentStatus": "สถานะปัจจุบัน", + "expires": "หมดอายุ:", + "neverExpires": "ไม่เคยหมดอายุ", + "enableExpiration": "เปิดใช้งานการหมดอายุ", + "expirationDate": "วันที่หมดอายุ", + "validation": { + "dateRequired": "โปรดเลือกวันที่หมดอายุ", + "dateMustBeFuture": "วันที่หมดอายุต้องเป็นวันข้างหน้า" + }, + "success": { + "expirationSet": "ตั้งวันที่หมดอายุเรียบร้อยแล้ว", + "expirationUpdated": "อัปเดตวันที่หมดอายุเรียบร้อยแล้ว", + "expirationRemoved": "ลบการหมดอายุเรียบร้อยแล้ว - แชร์นี้เป็นแบบถาวรแล้ว" + }, + "error": { + "updateFailed": "ไม่สามารถอัปเดตการตั้งค่าการหมดอายุ" + }, + "info": { + "title": "เกี่ยวกับการหมดอายุ:", + "willBeInaccessible": "แชร์จะไม่สามารถเข้าถึงได้หลังจากวันที่นี้", + "canBeChanged": "คุณสามารถเปลี่ยนแปลงหรือลบวันที่หมดอายุได้ตลอดเวลา", + "noExpiration": "แชร์นี้จะไม่เคยหมดอายุและจะยังคงสามารถเข้าถึงได้อย่างไม่มีกำหนด" + } + }, + "shareManager": { + "deleteSuccess": "ลบแชร์เรียบร้อยแล้ว", + "deleteError": "ไม่สามารถลบแชร์", + "updateSuccess": "อัปเดตแชร์เรียบร้อยแล้ว", + "updateError": "ไม่สามารถอัปเดตแชร์", + "securityUpdateSuccess": "อัปเดตการตั้งค่าความปลอดภัยเรียบร้อยแล้ว", + "securityUpdateError": "ไม่สามารถอัปเดตการตั้งค่าความปลอดภัย", + "expirationUpdateSuccess": "อัปเดตการตั้งค่าการหมดอายุเรียบร้อยแล้ว", + "expirationUpdateError": "ไม่สามารถอัปเดตการตั้งค่าการหมดอายุ", + "filesUpdateSuccess": "อัปเดตไฟล์เรียบร้อยแล้ว", + "filesUpdateError": "ไม่สามารถอัปเดตไฟล์", + "recipientsUpdateSuccess": "อัปเดตผู้รับเรียบร้อยแล้ว", + "recipientsUpdateError": "ไม่สามารถอัปเดตผู้รับ", + "linkGenerateSuccess": "สร้างลิงก์แชร์เรียบร้อยแล้ว", + "linkGenerateError": "ไม่สามารถสร้างลิงก์แชร์", + "notifyLoading": "กำลังส่งการแจ้งเตือน...", + "notifySuccess": "แจ้งให้ผู้รับทราบเรียบร้อยแล้ว", + "notifyError": "ไม่สามารถแจ้งให้ผู้รับทราบ", + "bulkDeleteError": "ไม่สามารถลบแชร์", + "bulkDeleteLoading": "กำลังลบ {count, plural, =1 {1 แชร์} other {# แชร์}}...", + "bulkDeleteSuccess": "{count, plural, =1 {ลบ 1 แชร์เรียบร้อยแล้ว} other {ลบ # แชร์เรียบร้อยแล้ว}}", + "downloadSuccess": "เริ่มการดาวน์โหลดเรียบร้อยแล้ว", + "downloadError": "ไม่สามารถดาวน์โหลดไฟล์แชร์", + "noFilesToDownload": "ไม่มีไฟล์ที่พร้อมใช้งานสำหรับการดาวน์โหลด", + "creatingZip": "กำลังสร้างไฟล์ ZIP...", + "zipDownloadSuccess": "ดาวน์โหลดไฟล์ ZIP เรียบร้อยแล้ว", + "zipDownloadError": "ไม่สามารถสร้างไฟล์ ZIP", + "errors": { + "multipleDownloadNotSupported": "ยังไม่รองรับการดาวน์โหลดแชร์หลายรายการ - โปรดดาวน์โหลดแชร์เป็นรายบุคคล" + }, + "singleShareZipName": "{shareName}_ไฟล์.zip", + "multipleSharesZipName": "{count}_แชร์_ไฟล์.zip", + "defaultShareName": "แชร์" + }, + "shareMultipleFiles": { + "title": "แชร์ไฟล์หลายรายการ", + "shareNameLabel": "ชื่อแชร์", + "shareNamePlaceholder": "ป้อนชื่อแชร์", + "descriptionLabel": "คำอธิบาย", + "descriptionPlaceholder": "ป้อนคำอธิบาย (เสริม)", + "filesToShare": "ไฟล์ที่จะแชร์", + "files": "ไฟล์", + "totalSize": "ขนาดทั้งหมด", + "creating": "กำลังสร้างแชร์...", + "create": "สร้างแชร์", + "itemsToShare": "รายการที่จะแชร์ ({count} {count, plural, =1 {รายการ} other {รายการ}})" + }, + "shareSecurity": { + "title": "การตั้งค่าความปลอดภัยแชร์", + "subtitle": "กำหนดค่าการป้องกันด้วยรหัสผ่านและตัวเลือกความปลอดภัยสำหรับแชร์นี้", + "currentStatus": "สถานะปัจจุบัน", + "passwordProtection": "การป้องกันด้วยรหัสผ่าน", + "password": "รหัสผ่าน", + "newPassword": "รหัสผ่านใหม่", + "passwordPlaceholder": "ป้อนรหัสผ่านที่ปลอดภัย", + "existingPasswordMessage": "แชร์นี้มีรหัสผ่านอยู่แล้ว หากต้องการอัปเดต ให้ป้อนรหัสผ่านใหม่ในช่องด้านล่างและบันทึก", + "passwordRequirements": { + "title": "ข้อกำหนดรหัสผ่าน:", + "minLength": "อย่างน้อย 2 ตัวอักษร" + }, + "info": { + "title": "วิธีการทำงาน:", + "withPassword": "ผู้ใช้จะต้องป้อนรหัสผ่านเพื่อเข้าถึงแชร์นี้", + "withoutPassword": "ทุกคนที่มีลิงก์สามารถเข้าถึงแชร์นี้ได้โดยไม่ต้องใช้รหัสผ่าน" + }, + "validation": { + "passwordRequired": "จำเป็นต้องมีรหัสผ่าน", + "passwordTooShort": "รหัสผ่านต้องมีความยาวอย่างน้อย 2 ตัวอักษร" + }, + "success": { + "passwordSet": "เปิดใช้งานการป้องกันด้วยรหัสผ่านเรียบร้อยแล้ว", + "passwordUpdated": "อัปเดตรหัสผ่านเรียบร้อยแล้ว", + "passwordRemoved": "ลบการป้องกันด้วยรหัสผ่านเรียบร้อยแล้ว" + }, + "error": { + "updateFailed": "ไม่สามารถอัปเดตการตั้งค่าความปลอดภัย" + } + }, + "shares": { + "errors": { + "loadFailed": "ไม่สามารถโหลดแชร์", + "notifyFailed": "ไม่สามารถแจ้งให้ผู้รับทราบ", + "smtpConfigFailed": "ไม่สามารถโหลดการตั้งค่า SMTP" + }, + "messages": { + "linkCopied": "คัดลอกลิงก์ลงในคลิปบอร์ดแล้ว", + "recipientsNotified": "แจ้งให้ผู้รับทราบเรียบร้อยแล้ว" + }, + "empty": { + "message": "ยังไม่ได้สร้างแชร์ใด ๆ", + "createButton": "สร้างแชร์" + }, + "header": { + "title": "แชร์ของฉัน", + "myShares": "แชร์ของฉัน" + }, + "search": { + "title": "แชร์ทั้งหมด", + "createButton": "สร้างแชร์", + "placeholder": "ค้นหาแชร์...", + "results": "พบ {filtered} จาก {total} แชร์" + }, + "pageTitle": "แชร์" + }, + "sharesTable": { + "ariaLabel": "ตารางแชร์", + "never": "ไม่เคย", + "columns": { + "name": "ชื่อ", + "description": "คำอธิบาย", + "createdAt": "สร้างเมื่อ", + "expiresAt": "หมดอายุ", + "status": "สถานะ", + "security": "ความปลอดภัย", + "files": "ไฟล์", + "recipients": "ผู้รับ", + "actions": "การกระทำ" + }, + "status": { + "neverExpires": "ไม่เคยหมดอายุ", + "active": "ใช้งานอยู่", + "expired": "หมดอายุแล้ว" + }, + "security": { + "protected": "ป้องกัน", + "public": "สาธารณะ" + }, + "filesCount": "ไฟล์", + "folderCount": "โฟลเดอร์", + "recipientsCount": "ผู้รับ", + "actions": { + "menu": "เมนูการกระทำแชร์", + "edit": "แก้ไข", + "manageFiles": "จัดการไฟล์", + "manageRecipients": "จัดการผู้รับ", + "viewDetails": "ดูรายละเอียด", + "generateLink": "สร้างลิงก์", + "editLink": "แก้ไขลิงก์", + "copyLink": "คัดลอกลิงก์", + "viewQrCode": "ดูรหัส QR", + "notifyRecipients": "แจ้งให้ผู้รับทราบ", + "downloadShareFiles": "ดาวน์โหลดไฟล์ทั้งหมด", + "delete": "ลบ" + }, + "bulkActions": { + "actions": "การกระทำ", + "download": "ดาวน์โหลดที่เลือก", + "delete": "ลบ", + "selected": "{count, plural, =1 {เลือก 1 แชร์} other {เลือก # แชร์}}" + }, + "selectAll": "เลือกทั้งหมด", + "selectShare": "เลือกแชร์ {shareName}" + }, + "storageUsage": { + "title": "การใช้ที่จัดเก็บ", + "ariaLabel": "แถบความคืบหน้าการใช้ที่จัดเก็บ", + "used": "ใช้แล้ว", + "available": "พร้อมใช้งาน", + "total": "ทั้งหมด", + "loading": "กำลังโหลด...", + "retry": "ลองใหม่", + "errors": { + "title": "ข้อมูลการจัดเก็บไม่พร้อมใช้งาน", + "detectionFailed": "ไม่สามารถตรวจหาพื้นที่ดิสก์ อาจเนื่องจากปัญหาการตั้งค่าระบบหรือสิทธิ์ไม่เพียงพอ", + "serverError": "เกิดข้อผิดพลาดของเซิร์ฟเวอร์ขณะดึงข้อมูลการจัดเก็บ โปรดลองใหม่ภายหลัง", + "unknown": "เกิดข้อผิดพลาดที่ไม่คาดคิดขณะโหลดข้อมูลการจัดเก็บ" + } + }, + "theme": { + "toggle": "สลับธีม", + "light": "สว่าง", + "dark": "มืด", + "system": "ระบบ" + }, + "twoFactor": { + "title": "การยืนยันตัวตนแบบสองปัจจัย", + "description": "เพิ่มชั้นความปลอดภัยพิเศษให้กับบัญชีของคุณ", + "enabled": "บัญชีของคุณป้องกันด้วยการยืนยันตัวตนแบบสองปัจจัย", + "disabled": "ไม่เปิดใช้งานการยืนยันตัวตนแบบสองปัจจัย", + "status": { + "label": "สถานะ:", + "enabled": "เปิดใช้งาน", + "disabled": "ปิดใช้งาน" + }, + "buttons": { + "enable2FA": "เปิดใช้งาน 2FA", + "disable2FA": "ปิดใช้งาน 2FA" + }, + "setup": { + "title": "เปิดใช้งานการยืนยันตัวตนแบบสองปัจจัย", + "description": "สแกนรหัส QR ด้วยแอปผู้ยืนยันตัวตนของคุณ จากนั้นป้อนรหัสการยืนยันตัวตน", + "qrCode": "รหัส QR", + "manualEntryKey": "คีย์สำหรับป้อนด้วยตนเอง", + "verificationCode": "รหัสการยืนยันตัวตน", + "verificationCodePlaceholder": "ป้อนรหัส 6 หลัก", + "verificationCodeDescription": "ป้อนรหัส 6 หลักจากแอปผู้ยืนยันตัวตนของคุณ", + "verifyAndEnable": "ยืนยันตัวตนและเปิดใช้งาน", + "cancel": "ยกเลิก" + }, + "disable": { + "title": "ปิดใช้งานการยืนยันตัวตนแบบสองปัจจัย", + "description": "ป้อนรหัสผ่านของคุณเพื่อยืนยันการปิดใช้งานการยืนยันตัวตนแบบสองปัจจัย", + "password": "รหัสผ่าน", + "passwordPlaceholder": "ป้อนรหัสผ่านของคุณ", + "confirm": "ยืนยันการปิดใช้งาน", + "cancel": "ยกเลิก" + }, + "backupCodes": { + "title": "รหัสสำรอง", + "description": "บันทึกรหัสสำรองเหล่านี้ไว้ในที่ปลอดภัย คุณสามารถใช้รหัสเหล่านี้เพื่อเข้าถึงบัญชีของคุณหากคุณทำหายอุปกรณ์ผู้ยืนยันตัวตนของคุณ", + "warning": "สำคัญ:", + "warningText": "สามารถใช้รหัสสำรองแต่ละรหัสได้เพียงครั้งเดียวเท่านั้น เก็บรักษาไว้อย่างปลอดภัยและอย่าแชร์กับใครเลย", + "generateNew": "สร้างรหัสสำรองใหม่", + "download": "ดาวน์โหลดรหัสสำรอง", + "copyToClipboard": "คัดลอกไปยังคลิปบอร์ด", + "savedMessage": "ฉันได้บันทึกรหัสสำรองของฉันแล้ว", + "available": "มีรหัสสำรอง {count} รหัสพร้อมใช้งาน", + "instructions": [ + "• บันทึกรหัสเหล่านี้ไว้ในสถานที่ปลอดภัย", + "• สามารถใช้รหัสสำรองแต่ละรหัสได้เพียงครั้งเดียวเท่านั้น", + "• คุณสามารถสร้างรหัสใหม่ได้ตลอดเวลา" + ] + }, + "verification": { + "title": "การยืนยันตัวตนแบบสองปัจจัย", + "description": "ป้อนรหัส 6 หลักจากแอปผู้ยืนยันตัวตนของคุณ", + "backupDescription": "ป้อนรหัสสำรองรหัสใดรหัสหนึ่งของคุณเพื่อดำเนินการต่อ", + "verificationCode": "รหัสการยืนยันตัวตน", + "backupCode": "รหัสสำรอง", + "verificationCodePlaceholder": "000000", + "backupCodePlaceholder": "XXXX-XXXX", + "verify": "ยืนยันตัวตน", + "verifying": "กำลังยืนยันตัวตน...", + "useBackupCode": "ใช้รหัสสำรองแทน", + "useAuthenticatorCode": "ใช้รหัสผู้ยืนยันตัวตนแทน", + "rememberDevice": "จำอุปกรณ์นี้เป็นเวลา 30 วัน", + "rememberDeviceDescription": "คุณจะไม่ต้องป้อนรหัส 2FA บนอุปกรณ์นี้เป็นเวลา 30 วัน" + }, + "trustedDevices": { + "title": "อุปกรณ์ที่เชื่อถือ - 2FA", + "description": "อุปกรณ์ที่ไม่ต้องการการยืนยันตัวตน 2FA", + "noDevices": "ไม่มีอุปกรณ์ที่เชื่อถือ", + "deviceName": "อุปกรณ์", + "addedOn": "เพิ่มเมื่อ", + "expiresOn": "หมดอายุ", + "remove": "ลบ", + "removeAll": "ลบทั้งหมด", + "confirmRemove": "คุณแน่ใจว่าต้องการลบอุปกรณ์ที่เชื่อถือนี้หรือไม่?", + "confirmRemoveAll": "คุณแน่ใจว่าต้องการลบอุปกรณ์ที่เชื่อถือทั้งหมดหรือไม่?", + "deviceRemoved": "ลบอุปกรณ์ที่เชื่อถือเรียบร้อยแล้ว", + "allDevicesRemoved": "ลบอุปกรณ์ที่เชื่อถือทั้งหมดเรียบร้อยแล้ว", + "loadFailed": "ไม่สามารถโหลดอุปกรณ์ที่เชื่อถือ", + "removeFailed": "ไม่สามารถลบอุปกรณ์ที่เชื่อถือ", + "removeAllFailed": "ไม่สามารถลบอุปกรณ์ที่เชื่อถือทั้งหมด", + "loading": "กำลังโหลดอุปกรณ์ที่เชื่อถือ...", + "noDevicesDescription": "อุปกรณ์จะปรากฏที่นี่เมื่อคุณเลือกที่จะเชื่อถือระหว่างการยืนยันตัวตน 2FA", + "tableHeaders": { + "device": "อุปกรณ์", + "added": "เพิ่มเมื่อ", + "expires": "หมดอายุ", + "lastUsed": "ใช้ครั้งล่าสุด", + "ipAddress": "ที่อยู่ IP", + "actions": "การกระทำ" + }, + "status": { + "never": "ไม่เคย", + "expired": "หมดอายุ" + }, + "modals": { + "removeDevice": { + "title": "ลบอุปกรณ์ที่เชื่อถือ", + "added": "เพิ่มเมื่อ:", + "ip": "IP:" + }, + "removeAllDevices": { + "title": "ลบอุปกรณ์ที่เชื่อถือทั้งหมด", + "description": "นี่จะลบอุปกรณ์ที่เชื่อถือ {count} รายการ{count, plural, =1 {} other {}}คุณจะต้องยืนยัน 2FA บนอุปกรณ์ทั้งหมดอีกครั้ง" + }, + "buttons": { + "cancel": "ยกเลิก", + "removing": "กำลังลบ...", + "removeDevice": "ลบอุปกรณ์", + "removeAllDevices": "ลบอุปกรณ์ทั้งหมด" + } + } + }, + "messages": { + "enabledSuccess": "เปิดใช้งานการยืนยันตัวตนแบบสองปัจจัยเรียบร้อยแล้ว!", + "disabledSuccess": "ปิดใช้งานการยืนยันตัวตนแบบสองปัจจัยเรียบร้อยแล้ว", + "backupCodesGenerated": "สร้างรหัสสำรองใหม่เรียบร้อยแล้ว", + "backupCodesCopied": "คัดลอกรหัสสำรองไปยังคลิปบอร์ดเรียบร้อยแล้ว", + "setupFailed": "ไม่สามารถสร้างการตั้งค่า 2FA", + "verificationFailed": "รหัสการยืนยันตัวตนไม่ถูกต้อง", + "disableFailed": "ไม่สามารถปิดใช้งาน 2FA โปรดตรวจสอบรหัสผ่านของคุณ", + "backupCodesFailed": "ไม่สามารถสร้างรหัสสำรอง", + "backupCodesCopyFailed": "ไม่สามารถคัดลอกรหัสสำรอง", + "statusLoadFailed": "ไม่สามารถโหลดสถานะ 2FA", + "enterVerificationCode": "โปรดป้อนรหัสการยืนยันตัวตน", + "enterPassword": "โปรดป้อนรหัสผ่านของคุณ", + "deviceTrusted": "อุปกรณ์นี้ถูกทำเครื่องหมายว่าเชื่อถือเป็นเวลา 30 วัน" + }, + "errors": { + "invalidVerificationCode": "รหัสการยืนยันตัวตนไม่ถูกต้อง", + "invalidTwoFactorCode": "รหัสการยืนยันตัวตนแบบสองปัจจัยไม่ถูกต้อง", + "twoFactorRequired": "ต้องใช้การยืนยันตัวตนแบบสองปัจจัย", + "twoFactorAlreadyEnabled": "เปิดใช้งานการยืนยันตัวตนแบบสองปัจจัยแล้ว", + "twoFactorNotEnabled": "ไม่เปิดใช้งานการยืนยันตัวตนแบบสองปัจจัย", + "passwordVerificationRequired": "จำเป็นต้องยืนยันรหัสผ่าน", + "invalidPassword": "รหัสผ่านไม่ถูกต้อง", + "userNotFound": "ไม่พบผู้ใช้" + }, + "deviceNames": { + "unknownDevice": "อุปกรณ์ที่ไม่รู้จัก", + "browsers": { + "chrome": "Chrome", + "firefox": "Firefox", + "safari": "Safari", + "edge": "Edge" + }, + "platforms": { + "windows": " บน Windows", + "macos": " บน macOS", + "linux": " บน Linux", + "iphone": " บน iPhone", + "android": " บน Android" + } + } + }, + "uploadFile": { + "title": "อัปโหลดไฟล์", + "multipleTitle": "อัปโหลดไฟล์", + "selectFile": "คลิกเพื่อเลือกไฟล์", + "selectMultipleFiles": "คลิกเพื่อเลือกไฟล์หนึ่งไฟล์ขึ้นไป", + "dragAndDrop": "หรือลากวางไฟล์ที่นี่", + "filesQueued": "{count, plural, one {อัปโหลด # ไฟล์ที่จัดคิว} other {อัปโหลด # ไฟล์ที่จัดคิว}}", + "preview": "ตัวอย่าง", + "uploadProgress": "ความคืบหน้าการอัปโหลด", + "upload": "อัปโหลด", + "startUploads": "เริ่มการอัปโหลด", + "retry": "ลองใหม่", + "finish": "เสร็จสิ้น", + "success": "อัปโหลดไฟล์เรียบร้อยแล้ว", + "allSuccess": "{count, plural, =1 {อัปโหลดไฟล์เรียบร้อยแล้ว} other {อัปโหลด # ไฟล์เรียบร้อยแล้ว}}", + "partialSuccess": "อัปโหลดไฟล์ {success} ไฟล์เรียบร้อยแล้ว {error} ล้มเหลว", + "error": "ไม่สามารถอัปโหลดไฟล์", + "fileSizeExceeded": "ขนาดไฟล์เกินขีดจำกัด {maxsizemb}MB", + "insufficientStorage": "พื้นที่จัดเก็บไม่เพียงพอ คุณมีพื้นที่ว่าง {availablespace}MB", + "unauthorized": "ไม่ได้รับอนุญาต: ต้องมีโทเค็นที่ถูกต้องเพื่อเข้าถึงทรัพยากรนี้", + "globalDrop": { + "title": "วางไฟล์เพื่ออัปโหลด", + "description": "ปล่อยเพื่ออัปโหลดไฟล์ของคุณ" + }, + "confirmCancel": { + "title": "ยกเลิกการอัปโหลด", + "messageSingle": "กำลังอัปโหลดหนึ่งไฟล์", + "messageMultiple": "กำลังอัปโหลด {count} ไฟล์", + "warning": "หากคุณปิดตอนนี้ การอัปโหลดจะถูกยกเลิกและการลงชื่อใด ๆ จะหาย", + "continue": "ดำเนินการอัปโหลดต่อ", + "cancel": "ยกเลิกการอัปโหลด" + }, + "pasteSuccess": "{count, plural, =1 {วางและอัปโหลดรูปภาพเรียบร้อยแล้ว} other {วางและอัปโหลด # รูปภาพเรียบร้อยแล้ว}}" + }, + "users": { + "modes": { + "create": "สร้าง", + "edit": "แก้ไข" + }, + "errors": { + "loadFailed": "ไม่สามารถโหลดผู้ใช้", + "submitFailed": "ไม่สามารถ{mode}ผู้ใช้", + "deleteFailed": "ไม่สามารถลบผู้ใช้", + "statusUpdateFailed": "ไม่สามารถอัปเดตสถานะผู้ใช้" + }, + "messages": { + "createSuccess": "สร้างผู้ใช้เรียบร้อยแล้ว", + "updateSuccess": "อัปเดตผู้ใช้เรียบร้อยแล้ว", + "deleteSuccess": "ลบผู้ใช้เรียบร้อยแล้ว", + "activateSuccess": "เปิดใช้งานผู้ใช้เรียบร้อยแล้ว", + "deactivateSuccess": "ปิดใช้งานผู้ใช้เรียบร้อยแล้ว" + }, + "actions": { + "edit": "แก้ไข", + "activate": "เปิดใช้งาน", + "deactivate": "ปิดใช้งาน", + "delete": "ลบ" + }, + "delete": { + "title": "ยืนยันการลบผู้ใช้", + "confirmation": "คุณแน่ใจว่าต้องการลบผู้ใช้ {firstName} {lastName} หรือไม่? ไม่สามารถเลิกทำการกระทำนี้ได้", + "confirm": "ลบผู้ใช้" + }, + "form": { + "titleCreate": "เพิ่มผู้ใช้ใหม่", + "titleEdit": "แก้ไขผู้ใช้", + "firstName": "ชื่อ", + "lastName": "นามสกุล", + "username": "ชื่อผู้ใช้", + "email": "อีเมล", + "password": "รหัสผ่าน", + "newPassword": "รหัสผ่านใหม่ (เสริม)", + "passwordPlaceholder": "ปล่อยว่างไว้เพื่อเก็บรหัสผ่านปัจจุบัน", + "role": "บทบาท", + "roleUser": "ผู้ใช้", + "roleAdmin": "ผู้ดูแลระบบ", + "create": "สร้าง", + "save": "บันทึก" + }, + "status": { + "title": "ยืนยันการเปลี่ยนสถานะ", + "confirmation": "คุณแน่ใจว่าต้องการ {action} ผู้ใช้ {firstName} {lastName} หรือไม่?", + "activate": "เปิดใช้งาน", + "deactivate": "ปิดใช้งาน", + "user": "ผู้ใช้" + }, + "header": { + "title": "การจัดการผู้ใช้", + "addUser": "เพิ่มผู้ใช้", + "management": "การจัดการผู้ใช้" + }, + "table": { + "user": "ผู้ใช้", + "email": "อีเมล", + "status": "สถานะ", + "role": "บทบาท", + "actions": "การกระทำ", + "active": "ใช้งานอยู่", + "inactive": "ไม่ใช้งาน", + "admin": "ผู้ดูแลระบบ", + "userr": "ผู้ใช้" + }, + "invite": { + "button": "สร้างลิงค์เชิญ", + "title": "สร้างลิงค์เชิญผู้ใช้", + "description": "สร้างลิงค์ที่ใช้ได้ครั้งเดียวเพื่อให้ใครบางคนสร้างบัญชีของตนเอง ลิงค์จะหมดอายุใน 15 นาที", + "generating": "กำลังสร้าง...", + "generate": "สร้างลิงค์", + "generated": "สร้างลิงค์เชิญเรียบร้อยแล้ว!", + "linkReady": "ลิงค์เชิญพร้อมแล้ว", + "linkReadyDescription": "แชร์ลิงค์นี้กับคนที่คุณต้องการเชิญ พวกเขาจะสามารถสร้างบัญชีของตนเองเป็นผู้ใช้ทั่วไป", + "copyLink": "คัดลอกลิงค์", + "linkCopied": "คัดลอกลิงค์เชิญไปยังคลิปบอร์ดแล้ว!", + "expiresIn": "หมดอายุใน 15 นาที", + "close": "ปิด", + "errors": { + "generateFailed": "ไม่สามารถสร้างลิงค์เชิญ" + } + } + }, + "embedCode": { + "title": "ฝังสื่อ", + "description": "ใช้รหัสเหล่านี้เพื่อฝังสื่อนี้ในฟอรัม เว็บไซต์ หรือแพลตฟอร์มอื่น ๆ", + "tabs": { + "directLink": "ลิงก์โดยตรง", + "html": "HTML", + "bbcode": "BBCode" + }, + "directLinkDescription": "URL โดยตรงไปยังไฟล์สื่อ", + "htmlDescription": "ใช้รหัสนี้เพื่อฝังสื่อในหน้า HTML", + "bbcodeDescription": "ใช้รหัสนี้เพื่อฝังสื่อในฟอรัมที่รองรับ BBCode" + }, + "validation": { + "firstNameRequired": "จำเป็นต้องมีชื่อ", + "lastNameRequired": "จำเป็นต้องมีนามสกุล", + "usernameLength": "ชื่อผู้ใช้ต้องมีความยาวอย่างน้อย 3 ตัวอักษร", + "usernameSpaces": "ชื่อผู้ใช้ไม่สามารถมีช่องว่าง", + "invalidEmail": "โปรดป้อนที่อยู่อีเมลที่ถูกต้อง", + "passwordLength": "รหัสผ่านต้องมีความยาวอย่างน้อย 8 ตัวอักษร", + "passwordsMatch": "รหัสผ่านต้องตรงกัน", + "emailRequired": "จำเป็นต้องมีอีเมล", + "emailOrUsernameRequired": "จำเป็นต้องมีอีเมลหรือชื่อผู้ใช้", + "passwordRequired": "จำเป็นต้องมีรหัสผ่าน", + "passwordMinLength": "รหัสผ่านต้องมีความยาวอย่างน้อย 6 ตัวอักษร", + "nameRequired": "จำเป็นต้องมีชื่อ", + "required": "จำเป็นต้องกรอกข้อมูลช่องนี้" + }, + "registerWithInvite": { + "title": "สร้างบัญชีของคุณ", + "description": "กรอกข้อมูลด้านล่างเพื่อสร้างบัญชีของคุณ", + "labels": { + "firstName": "ชื่อจริง", + "firstNamePlaceholder": "ป้อนชื่อจริงของคุณ", + "lastName": "นามสกุล", + "lastNamePlaceholder": "ป้อนนามสกุลของคุณ", + "username": "ชื่อผู้ใช้", + "usernamePlaceholder": "เลือกชื่อผู้ใช้", + "email": "อีเมล", + "emailPlaceholder": "ป้อนอีเมลของคุณ", + "password": "รหัสผ่าน", + "passwordPlaceholder": "เลือกรหัสผ่าน", + "confirmPassword": "ยืนยันรหัสผ่าน", + "confirmPasswordPlaceholder": "ยืนยันรหัสผ่านของคุณ" + }, + "buttons": { + "creating": "กำลังสร้างบัญชี...", + "createAccount": "สร้างบัญชี" + }, + "validation": { + "firstNameRequired": "ต้องระบุชื่อจริง", + "lastNameRequired": "ต้องระบุนามสกุล", + "usernameMinLength": "ชื่อผู้ใช้ต้องมีอย่างน้อย 3 ตัวอักษร", + "invalidEmail": "อีเมลไม่ถูกต้อง", + "passwordMinLength": "รหัสผ่านต้องมีอย่างน้อย 8 ตัวอักษร", + "passwordsMatch": "รหัสผ่านต้องตรงกัน" + }, + "messages": { + "success": "สร้างบัญชีเรียบร้อยแล้ว! กำลังเปลี่ยนเส้นทางไปยังหน้าเข้าสู่ระบบ...", + "redirecting": "กำลังเปลี่ยนเส้นทางไปยังหน้าเข้าสู่ระบบ..." + }, + "errors": { + "invalidToken": "ข้อผิดพลาดกับลิงค์เชิญ", + "tokenUsed": "ลิงค์เชิญนี้ถูกใช้ไปแล้ว", + "tokenExpired": "ลิงค์เชิญนี้หมดอายุแล้ว", + "usernameExists": "ชื่อผู้ใช้มีอยู่แล้ว", + "emailExists": "อีเมลมีอยู่แล้ว", + "createFailed": "ไม่สามารถสร้างบัญชี โปรดลองอีกครั้ง" + }, + "pageTitle": "สร้างบัญชี" + } +} \ No newline at end of file diff --git a/apps/web/messages/tr-TR.json b/apps/web/messages/tr-TR.json index 234f93b2..bb217ec6 100644 --- a/apps/web/messages/tr-TR.json +++ b/apps/web/messages/tr-TR.json @@ -6,7 +6,9 @@ "token_expired": "Token süresi doldu. Lütfen tekrar deneyin.", "config_error": "Yapılandırma hatası. Lütfen destek ile iletişime geçin.", "auth_failed": "Kimlik doğrulama başarısız. Lütfen tekrar deneyin." - } + }, + "authenticationFailed": "Kimlik doğrulama başarısız", + "successfullyAuthenticated": "Başarıyla kimlik doğrulandı!" }, "authProviders": { "title": "Kimlik Doğrulama Sağlayıcıları", @@ -174,7 +176,14 @@ "tabs": { "shareDetails": "Paylaşım Detayları", "selectFiles": "Dosyaları Seç" - } + }, + "errors": { + "nameRequired": "Paylaşım adı gerekli", + "selectItems": "Lütfen en az bir dosya veya klasör seçin" + }, + "itemsSelected": "{count} öğe seçildi", + "passwordPlaceholder": "Şifre girin", + "selectItemsPrompt": "Paylaşmak için dosya ve klasörleri seçin" }, "customization": { "breadcrumb": "Özelleştirme", @@ -340,7 +349,8 @@ "addToShare": "Paylaşıma ekle", "removeFromShare": "Paylaşımdan kaldır", "saveChanges": "Değişiklikleri Kaydet", - "editFolder": "Klasörü düzenle" + "editFolder": "Klasörü düzenle", + "itemsSelected": "{count} öğe seçildi" }, "files": { "title": "Tüm Dosyalar", @@ -376,7 +386,12 @@ "description": "Başlamak için ilk dosyanızı yükleyin veya bir klasör oluşturun" }, "files": "dosyalar", - "folders": "klasörler" + "folders": "klasörler", + "errors": { + "moveItemsFailed": "Öğeler taşınamadı. Lütfen tekrar deneyin.", + "cannotMoveHere": "Öğeler bu konuma taşınamaz" + }, + "openFolder": "Klasörü aç" }, "filesTable": { "ariaLabel": "Dosya Tablosu", @@ -539,7 +554,10 @@ "movingTo": "Taşınıyor:", "title": "{count, plural, =1 {Öğe} other {Öğeler}} Taşı", "description": "{count, plural, =1 {Öğeyi} other {Öğeleri}} yeni konuma taşı", - "success": "{count} {count, plural, =1 {öğe} other {öğe}} başarıyla taşındı" + "success": "{count} {count, plural, =1 {öğe} other {öğe}} başarıyla taşındı", + "errors": { + "moveFailed": "Öğeler taşınamadı" + } }, "navbar": { "logoAlt": "Uygulama Logosu", @@ -1153,8 +1171,6 @@ }, "fileActions": { "edit": "Düzenle", - "save": "Kaydet", - "cancel": "İptal", "preview": "Önizle", "download": "İndir", "delete": "Sil", @@ -1377,16 +1393,6 @@ "deleteTitle": "Paylaşımı Sil", "deleteConfirmation": "Bu paylaşımı silmek istediğinizden emin misiniz? Bu işlem geri alınamaz.", "editTitle": "Paylaşımı Düzenle", - "nameLabel": "Paylaşım Adı", - "descriptionLabel": "Açıklama", - "descriptionPlaceholder": "Açıklama girin (isteğe bağlı)", - "expirationLabel": "Son Kullanma Tarihi", - "expirationPlaceholder": "DD/MM/YYYY HH:MM", - "maxViewsLabel": "Maksimum Görüntüleme", - "maxViewsPlaceholder": "Sınırsız için boş bırakın", - "passwordProtection": "Şifre Korumalı", - "passwordLabel": "Şifre", - "passwordPlaceholder": "Şifre girin", "newPasswordLabel": "Yeni Şifre (mevcut şifreyi korumak için boş bırakın)", "newPasswordPlaceholder": "Yeni şifre girin", "manageFilesTitle": "Dosyaları Yönet", @@ -1405,7 +1411,9 @@ "linkDescriptionFile": "Dosyayı paylaşmak için özel bağlantı oluşturun", "linkDescriptionFolder": "Klasörü paylaşmak için özel bağlantı oluşturun", "linkReady": "Paylaşım bağlantınız hazır:", - "linkTitle": "Bağlantı Oluştur" + "linkTitle": "Bağlantı Oluştur", + "itemsSelected": "{count} öğe seçildi", + "manageFilesDescription": "Bu paylaşıma dahil edilecek dosya ve klasörleri seçin" }, "shareDetails": { "title": "Paylaşım Detayları", @@ -1421,7 +1429,6 @@ "noLink": "Henüz bağlantı oluşturulmadı", "copyLink": "Bağlantıyı kopyala", "openLink": "Yeni sekmede aç", - "linkCopied": "Bağlantı panoya kopyalandı", "views": "Görüntüleme", "dates": "Tarihler", "created": "Oluşturuldu", @@ -1469,28 +1476,6 @@ "expires": "Sona erer:", "expirationDate": "Son Kullanma Tarihi" }, - "shareFile": { - "title": "Dosya Paylaş", - "linkTitle": "Bağlantı Oluştur", - "nameLabel": "Paylaşım Adı", - "namePlaceholder": "Paylaşım adı girin", - "descriptionLabel": "Açıklama", - "descriptionPlaceholder": "Açıklama girin (isteğe bağlı)", - "expirationLabel": "Son Kullanma Tarihi", - "expirationPlaceholder": "DD/MM/YYYY HH:MM", - "maxViewsLabel": "Maksimum Görüntüleme", - "maxViewsPlaceholder": "Sınırsız için boş bırakın", - "passwordProtection": "Şifre Korumalı", - "passwordLabel": "Şifre", - "passwordPlaceholder": "Şifre girin", - "linkDescription": "Dosyayı paylaşmak için özel bağlantı oluşturun", - "aliasLabel": "Bağlantı Takma Adı", - "aliasPlaceholder": "Özel takma ad girin", - "linkReady": "Paylaşım bağlantınız hazır:", - "createShare": "Paylaşım Oluştur", - "generateLink": "Bağlantı Oluştur", - "copyLink": "Bağlantıyı Kopyala" - }, "shareManager": { "deleteSuccess": "Paylaşım başarıyla silindi", "deleteError": "Paylaşım silinemedi", @@ -1520,7 +1505,10 @@ "noFilesToDownload": "İndirilebilecek dosya yok", "singleShareZipName": "{Sharename} _files.zip", "zipDownloadError": "Zip dosyası oluşturulamadı", - "zipDownloadSuccess": "Zip dosyası başarıyla indirildi" + "zipDownloadSuccess": "Zip dosyası başarıyla indirildi", + "errors": { + "multipleDownloadNotSupported": "Çoklu paylaşım indirme henüz desteklenmiyor - lütfen paylaşımları ayrı ayrı indirin" + } }, "shareMultipleFiles": { "title": "Çoklu Dosya Paylaş", @@ -1917,6 +1905,23 @@ "inactive": "Devre Dışı", "admin": "Yönetici", "userr": "Kullanıcı" + }, + "invite": { + "button": "Davet Bağlantısı Oluştur", + "title": "Kullanıcı Davet Bağlantısı Oluştur", + "description": "Birinin kendi hesabını oluşturmasına izin veren tek kullanımlık bir bağlantı oluşturun. Bağlantı 15 dakika içinde sona erer.", + "generating": "Oluşturuluyor...", + "generate": "Bağlantı Oluştur", + "generated": "Davet bağlantısı başarıyla oluşturuldu!", + "linkReady": "Davet bağlantısı hazır", + "linkReadyDescription": "Bu bağlantıyı davet etmek istediğiniz kişiyle paylaşın. Normal bir kullanıcı olarak kendi hesaplarını oluşturabilecekler.", + "copyLink": "Bağlantıyı Kopyala", + "linkCopied": "Davet bağlantısı panoya kopyalandı!", + "expiresIn": "15 dakika içinde sona erer", + "close": "Kapat", + "errors": { + "generateFailed": "Davet bağlantısı oluşturulamadı" + } } }, "validation": { @@ -1935,15 +1940,62 @@ "required": "Bu alan zorunludur" }, "embedCode": { - "title": "Resmi Yerleştir", - "description": "Bu görüntüyü forumlara, web sitelerine veya diğer platformlara yerleştirmek için bu kodları kullanın", + "title": "Medyayı Yerleştir", + "description": "Bu medyayı forumlara, web sitelerine veya diğer platformlara yerleştirmek için bu kodları kullanın", "tabs": { "directLink": "Doğrudan Bağlantı", "html": "HTML", "bbcode": "BBCode" }, - "directLinkDescription": "Resim dosyasının doğrudan URL'si", - "htmlDescription": "Resmi HTML sayfalarına yerleştirmek için bu kodu kullanın", - "bbcodeDescription": "BBCode destekleyen forumlara resmi yerleştirmek için bu kodu kullanın" + "directLinkDescription": "Medya dosyasının doğrudan URL'si", + "htmlDescription": "Medyayı HTML sayfalarına yerleştirmek için bu kodu kullanın", + "bbcodeDescription": "BBCode destekleyen forumlara medyayı yerleştirmek için bu kodu kullanın" + }, + "contextMenu": { + "newFolder": "Yeni klasör", + "uploadFile": "Dosya yükle" + }, + "registerWithInvite": { + "title": "Hesabınızı Oluşturun", + "description": "Hesabınızı oluşturmak için aşağıdaki bilgileri doldurun", + "labels": { + "firstName": "Ad", + "firstNamePlaceholder": "Adınızı girin", + "lastName": "Soyad", + "lastNamePlaceholder": "Soyadınızı girin", + "username": "Kullanıcı Adı", + "usernamePlaceholder": "Bir kullanıcı adı seçin", + "email": "E-posta", + "emailPlaceholder": "E-postanızı girin", + "password": "Şifre", + "passwordPlaceholder": "Bir şifre seçin", + "confirmPassword": "Şifreyi Onayla", + "confirmPasswordPlaceholder": "Şifrenizi onaylayın" + }, + "buttons": { + "creating": "Hesap oluşturuluyor...", + "createAccount": "Hesap Oluştur" + }, + "validation": { + "firstNameRequired": "Ad gerekli", + "lastNameRequired": "Soyad gerekli", + "usernameMinLength": "Kullanıcı adı en az 3 karakter olmalıdır", + "invalidEmail": "Geçersiz e-posta", + "passwordMinLength": "Şifre en az 8 karakter olmalıdır", + "passwordsMatch": "Şifreler eşleşmelidir" + }, + "messages": { + "success": "Hesap başarıyla oluşturuldu! Girişe yönlendiriliyor...", + "redirecting": "Girişe yönlendiriliyor..." + }, + "errors": { + "invalidToken": "Davet bağlantısında hata", + "tokenUsed": "Bu davet bağlantısı zaten kullanılmış", + "tokenExpired": "Bu davet bağlantısının süresi dolmuş", + "usernameExists": "Kullanıcı adı zaten mevcut", + "emailExists": "E-posta zaten mevcut", + "createFailed": "Hesap oluşturulamadı. Lütfen tekrar deneyin." + }, + "pageTitle": "Hesap Oluştur" } } \ No newline at end of file diff --git a/apps/web/messages/uk-UA.json b/apps/web/messages/uk-UA.json new file mode 100644 index 00000000..ebd74a22 --- /dev/null +++ b/apps/web/messages/uk-UA.json @@ -0,0 +1,2001 @@ +{ + "auth": { + "successfullyAuthenticated": "Успішно автентифіковано!", + "authenticationFailed": "Помилка автентифікації", + "errors": { + "account_inactive": "Акаунт неактивний. Будь ласка, зв'яжіться з адміністратором.", + "registration_disabled": "Реєстрація через SSO відключена.", + "token_expired": "Токен прострочений. Спробуйте ще раз.", + "config_error": "Помилка конфігурації. Будь ласка, зв'яжіться з підтримкою.", + "auth_failed": "Помилка автентифікації. Спробуйте ще раз." + } + }, + "contextMenu": { + "newFolder": "Нова папка", + "uploadFile": "Завантажити файл" + }, + "authProviders": { + "title": "Постачальники автентифікації", + "description": "Налаштувати зовнішніх постачальників автентифікації для SSO", + "enabledCount": "{count} увімкнено", + "loadingProviders": "Завантаження постачальників...", + "providersConfigured": "{count} постачальників налаштовано", + "enabledOfTotal": "{enabled} увімкнено з {total} постачальників", + "hideDisabledProviders": "Приховати відключених постачальників", + "addProvider": "Додати постачальника", + "addProviderTitle": "Додати постачальника", + "editProvider": "Редагувати постачальника", + "deleteProvider": "Видалити постачальника", + "enabled": "Увімкнено", + "disabled": "Відключено", + "officialProvider": "Офіційний постачальник", + "dragToReorder": "Перетягніть для зміни порядку", + "dragDisabledMessage": "Перетягування відключено при фільтрації постачальників. Покажіть усіх постачальників для зміни порядку.", + "dragEnabledMessage": "Перетягніть постачальників для зміни порядку. Цей порядок буде відображено на сторінці входу.", + "noProvidersEnabled": "Немає увімкнених постачальників автентифікації", + "noProvidersConfigured": "Немає налаштованих постачальників автентифікації", + "form": { + "providerName": "Назва постачальника", + "providerNamePlaceholder": "наприклад, mycompany", + "displayName": "Відображувана назва", + "displayNamePlaceholder": "наприклад, My Company SSO", + "type": "Тип", + "typeOidc": "OIDC (OpenID Connect)", + "typeOauth2": "OAuth 2.0", + "icon": "Іконка", + "iconPlaceholder": "Виберіть іконку", + "clientId": "ID клієнта", + "clientIdPlaceholder": "Ваш OAuth ID клієнта", + "clientSecret": "Секрет клієнта", + "clientSecretPlaceholder": "Ваш OAuth секрет клієнта", + "oauthScopes": "OAuth області", + "scopesPlaceholder": "Введіть області (наприклад, openid, profile, email)", + "scopesHelpOidc": "Області автоматично пропонуються на основі URL постачальника. Загальні OIDC області: openid, profile, email, groups", + "scopesHelpOauth2": "Області автоматично пропонуються на основі URL постачальника. Загальні OAuth2 області залежать від постачальника", + "providerUrl": "URL постачальника", + "providerUrlPlaceholder": "https://auth.example.com", + "providerUrlAutoPlaceholder": "https://your-provider.com (кінцеві точки будуть виявлені автоматично)", + "providerUrlManualPlaceholder": "https://your-provider.com", + "autoDiscoveryHelp": "Система автоматично виявить кінцеві точки авторизації, токена та інформації користувача", + "manualConfigurationHelp": "Базовий URL вашого постачальника (кінцеві точки будуть відносно цього)", + "authorizationEndpoint": "Кінцева точка авторизації", + "authorizationEndpointPlaceholder": "https://auth.example.com/auth", + "tokenEndpoint": "Кінцева точка токена", + "tokenEndpointPlaceholder": "https://auth.example.com/token", + "userInfoEndpoint": "Кінцева точка інформації користувача", + "userInfoEndpointPlaceholder": "https://auth.example.com/userinfo", + "configurationMethod": "Метод конфігурації", + "autoDiscovery": "Автовиявлення", + "autoDiscoveryDescription": "Автоматично виявити кінцеві точки з URL постачальника", + "manualEndpoints": "Ручні кінцеві точки (Рекомендовано)", + "manualEndpointsDescription": "Ручно налаштувати авторизацію, токен та кінцеві точки інформації користувача", + "callbackUrl": "URL зворотного виклику", + "callbackUrlDescription": "Використовуйте цей URL у конфігурації вашого OAuth постачальника", + "copyCallbackUrl": "Копіювати URL зворотного виклику", + "callbackUrlCopied": "URL зворотного виклику скопійовано в буфер обміну!", + "adminEmailDomains": "Адміністративні домени електронної пошти", + "adminEmailDomainsPlaceholder": "Введіть домени (наприклад, admin.company.com)", + "adminEmailDomainsHelp": "Користувачі з електронною поштою з цих доменів отримають права адміністратора", + "autoRegister": "Автоматично реєструвати нових користувачів", + "officialProviderUrlPlaceholder": "Замініть заповнювач на ваш {displayName} URL", + "officialProviderHelp": "Це офіційний постачальник. Кінцеві точки попередньо налаштовані. Ви можете редагувати лише цей URL.", + "officialProviderIconHelp": "Ви можете налаштувати іконку для цього офіційного постачальника." + }, + "buttons": { + "cancel": "Скасувати", + "save": "Зберегти", + "saving": "Збереження...", + "adding": "Додавання...", + "updating": "Оновлення...", + "saveProvider": "Зберегти постачальника", + "delete": "Видалити", + "deleting": "Видалення...", + "edit": "Редагувати", + "enable": "Увімкнути", + "disable": "Відключити" + }, + "messages": { + "providerAdded": "Постачальника додано успішно", + "providerUpdated": "Постачальника оновлено успішно", + "providerDeleted": "Постачальника видалено успішно", + "providerOrderUpdated": "Порядок постачальників оновлено успішно", + "fillRequiredFields": "Будь ласка, заповніть усі обов'язкові поля (назва, відображувана назва, ID клієнта, секрет клієнта)", + "provideUrlOrEndpoints": "Надайте URL постачальника для автоматичного виявлення АБО всі три користувацькі кінцеві точки", + "chooseDiscoveryOrManual": "Виберіть або автоматичне виявлення (URL постачальника) АБО ручні кінцеві точки, не обидва", + "loadFailed": "Не вдалося завантажити постачальників", + "addFailed": "Не вдалося додати постачальника", + "updateFailed": "Не вдалося оновити постачальника", + "deleteFailed": "Не вдалося видалити постачальника", + "orderUpdateFailed": "Не вдалося оновити порядок постачальників" + }, + "info": { + "title": "Інформація", + "officialProvidersRecommended": "Для кращої функціональності розгляньте використання офіційних постачальників. Якщо у вас є проблеми з користувацьким постачальником, розгляньте відкриття питання на", + "github": "GitHub", + "officialProvider": "Офіційний постачальник", + "officialProviderDescription": "Цей постачальник оптимізований Palmr. Можна змінювати лише облікові дані та конфігурацію.", + "manualConfigTitle": "Ручна конфігурація", + "manualConfigDescription": "Ви надаєте всі кінцеві точки вручну. Переконайтеся, що вони правильні для вашого постачальника." + }, + "deleteModal": { + "title": "Видалити постачальника автентифікації", + "confirmMessage": "Ви впевнені, що хочете видалити постачальника \"{displayName}\"? Цю дію неможливо скасувати.", + "providerId": "ID постачальника: {name}", + "cancel": "Скасувати", + "delete": "Видалити постачальника", + "deleting": "Видалення..." + } + }, + "bulkDownload": { + "title": "Масове завантаження", + "zipNameLabel": "Назва ZIP файлу", + "zipNamePlaceholder": "Введіть назву файлу", + "description": "{count, plural, =1 {1 файл буде стиснуто} other {# файлів буде стиснуто}}", + "download": "Завантажити ZIP" + }, + "common": { + "loading": "Завантаження, будь ласка, зачекайте...", + "loadingSimple": "Завантаження...", + "cancel": "Скасувати", + "save": "Зберегти", + "saving": "Збереження...", + "update": "Оновити", + "updating": "Оновлення...", + "delete": "Видалити", + "deleting": "Видалення...", + "close": "Закрити", + "download": "Завантажити", + "unexpectedError": "Сталася неочікувана помилка. Спробуйте ще раз.", + "yes": "Так", + "no": "Ні", + "dashboard": "Панель управління", + "back": "Назад", + "click": "Натисніть, щоб", + "creating": "Створення...", + "create": "Створити", + "rename": "Перейменувати", + "move": "Перемістити", + "share": "Поділитися", + "search": "Пошук", + "copy": "Копіювати", + "copied": "Скопійовано" + }, + "createShare": { + "title": "Створити спільний доступ", + "nameLabel": "Назва спільного доступу", + "namePlaceholder": "Введіть назву для вашого спільного доступу", + "descriptionLabel": "Опис", + "descriptionPlaceholder": "Введіть опис (необов'язково)", + "expirationLabel": "Дата закінчення терміну дії", + "expirationPlaceholder": "ММ/ДД/РРРР ГГ:ХХ", + "maxViewsLabel": "Максимальна кількість переглядів", + "maxViewsPlaceholder": "Залишіть порожнім для необмеженої кількості", + "passwordProtection": "Захищено паролем", + "passwordLabel": "Пароль", + "passwordPlaceholder": "Введіть пароль", + "create": "Створити спільний доступ", + "success": "Спільний доступ створено успішно", + "error": "Не вдалося створити спільний доступ", + "errors": { + "nameRequired": "Назва спільного доступу обов'язкова", + "selectItems": "Будь ласка, виберіть принаймні один файл або папку" + }, + "itemsSelected": "{count} елементів вибрано", + "selectItemsPrompt": "Виберіть файли та папки для спільного доступу", + "tabs": { + "shareDetails": "Деталі спільного доступу", + "selectFiles": "Вибрати файли" + }, + "nextSelectFiles": "Далі: Вибрати файли", + "searchLabel": "Пошук" + }, + "customization": { + "breadcrumb": "Налаштування", + "colors": { + "title": "Кольори теми", + "description": "Виберіть бажану основну кольорову тему", + "presets": "Доступні кольори", + "presetsDescription": "Виберіть з доступних кольорових тем", + "reset": "Скинути до стандартного" + }, + "fonts": { + "title": "Типографіка", + "description": "Виберіть бажане сімейство шрифтів", + "available": "Доступні шрифти", + "availableDescription": "Виберіть з доступних сімейств шрифтів", + "reset": "Скинути до стандартного" + }, + "radius": { + "title": "Радіус межі", + "description": "Налаштуйте округлість елементів інтерфейсу", + "available": "Опції округлості", + "availableDescription": "Виберіть, як повинні виглядати округлі кути", + "reset": "Скинути до стандартного" + }, + "background": { + "title": "Кольори фону", + "description": "Налаштуйте кольори фону для світлого та темного режимів", + "lightMode": "Світлий режим", + "darkMode": "Темний режим", + "availableDescription": "Виберіть кольори фону для світлої та темної тем", + "reset": "Скинути до стандартного" + }, + "theme": { + "title": "Режим теми", + "description": "Виберіть між світлою, темною або системною темою", + "selectTheme": "Налаштування теми", + "availableDescription": "Виберіть бажаний режим теми", + "reset": "Скинути до системного" + }, + "pageTitle": "Налаштування" + }, + "dashboard": { + "loadError": "Не вдалося завантажити дані панелі управління", + "linkCopied": "Посилання скопійовано в буфер обміну", + "pageTitle": "Панель управління", + "breadcrumb": "Панель управління", + "recentFiles": { + "title": "Останні файли", + "description": "Ваші найнедавніше завантажені файли" + } + }, + "deleteConfirmation": { + "filesToDelete": "Файли для видалення", + "foldersToDelete": "Папки для видалення", + "itemsToDelete": "Елементи для видалення", + "sharesToDelete": "Спільні доступи для видалення" + }, + "downloadQueue": { + "downloadQueued": "Завантаження в черзі: {fileName}", + "queuedDescription": "Ваше завантаження почнеться автоматично, коли з'явиться вільне місце", + "queuePosition": "Завантаження в черзі на позиції {position}: {fileName}", + "estimatedWait": "Орієнтовний час очікування: {time}", + "queueFull": "Черга завантажень заповнена", + "queueFullDescription": "Будь ласка, спробуйте ще раз через кілька хвилин, коли в черзі з'явиться місце", + "cancelSuccess": "Завантаження скасовано успішно", + "cancelError": "Не вдалося скасувати завантаження: {error}", + "status": { + "pending": "Підготовка...", + "queued": "В черзі", + "downloading": "Завантажується", + "completed": "Завершено", + "failed": "Не вдалося" + }, + "waitTime": { + "seconds": "{seconds}с", + "minutes": "{minutes}хв", + "hoursMinutes": "{hours}г {minutes}хв" + }, + "indicator": { + "title": "Завантаження", + "downloads": "Черга завантажень", + "active": "Активні", + "queued": "В черзі", + "position": "Позиція {position}", + "estimatedWait": "Очікування: {time}", + "unknownFile": "Невідомий файл", + "noDownloads": "Немає завантажень у процесі", + "refresh": "Оновити чергу" + } + }, + "emptyState": { + "noFiles": "Ще немає завантажених файлів", + "uploadFile": "Завантажити файл" + }, + "errors": { + "invalidCredentials": "Неправильна електронна пошта або пароль", + "userNotFound": "Користувача не знайдено", + "accountLocked": "Акаунт заблоковано. Спробуйте ще раз пізніше", + "unexpectedError": "Сталася неочікувана помилка. Спробуйте ще раз", + "Invalid verification code": "Неправильний код підтвердження", + "Two-factor authentication is already enabled": "Двофакторна автентифікація вже увімкнена", + "Two-factor authentication is not enabled": "Двофакторна автентифікація не увімкнена", + "Invalid password": "Неправильний пароль", + "Password verification required": "Потрібна перевірка пароля", + "Invalid two-factor authentication code": "Неправильний код двофакторної автентифікації", + "Two-factor authentication required": "Потрібна двофакторна автентифікація", + "noUserData": "Немає даних користувача" + }, + "fileActions": { + "editFile": "Редагувати файл", + "nameLabel": "Назва", + "namePlaceholder": "Введіть нову назву", + "extension": "Розширення", + "descriptionLabel": "Опис", + "descriptionPlaceholder": "Введіть опис файлу", + "addDescriptionPlaceholder": "Додати опис...", + "deleteFile": "Видалити файл", + "deleteConfirmation": "Ви впевнені, що хочете видалити цей файл?", + "deleteWarning": "Цю дію неможливо скасувати." + }, + "fileManager": { + "downloadError": "Не вдалося завантажити файл", + "updateSuccess": "Файл оновлено успішно", + "updateError": "Не вдалося оновити файл", + "deleteSuccess": "Файл видалено успішно", + "deleteError": "Не вдалося видалити файл" + }, + "filePreview": { + "title": "Попередній перегляд файлу", + "description": "Попередній перегляд та завантаження файлу", + "loading": "Завантаження...", + "notAvailable": "Попередній перегляд недоступний для цього типу файлу", + "downloadToView": "Використовуйте кнопку завантаження для перегляду цього файлу", + "loadError": "Помилка завантаження попереднього перегляду файлу", + "downloadError": "Помилка завантаження файлу", + "audioNotSupported": "Ваш браузер не підтримує відтворення аудіо", + "videoNotSupported": "Ваш браузер не підтримує відтворення відео", + "pdfPreviewNotAvailable": "Попередній перегляд PDF недоступний. Спробуйте альтернативний перегляд або завантаження", + "tryAlternativeView": "Спробувати альтернативний перегляд", + "loadingAlternative": "Завантаження альтернативного перегляду...", + "loadingAudio": "Завантаження аудіо..." + }, + "fileSelector": { + "availableFiles": "Доступні файли ({count})", + "shareFiles": "Спільні файли ({count})", + "shareFilesDescription": "Файли, які зараз в цьому спільному доступі", + "availableFilesDescription": "Виберіть файли для додавання до цього спільного доступу", + "searchPlaceholder": "Пошук файлів...", + "searchSelectedFiles": "Пошук вибраних файлів...", + "noMatchingFiles": "Не знайдено відповідних файлів", + "noAvailableFiles": "Немає доступних файлів", + "noFilesInShare": "Немає файлів у цьому спільному доступі", + "noFilesFound": "Файли не знайдено", + "noFilesFoundWith": "Не знайдено файлів, що відповідають \"{query}\"", + "addFilesFromList": "Додайте файли зі списку нижче", + "tryDifferentSearch": "Спробуйте інші пошукові терміни", + "allFilesInShare": "Усі файли вже в цьому спільному доступі", + "uploadNewFiles": "Завантажте нові файли, щоб додати їх", + "fileCount": "{count, plural, =1 {файл} other {файлів}}", + "filesSelected": "{count, plural, =0 {Файли не вибрано} =1 {1 файл вибрано} other {# файлів вибрано}}", + "itemsSelected": "{count} елементів вибрано", + "editFile": "Редагувати файл", + "editFolder": "Редагувати папку", + "previewFile": "Попередній перегляд файлу", + "addToShare": "Додати до спільного доступу", + "removeFromShare": "Видалити зі спільного доступу", + "saveChanges": "Зберегти зміни" + }, + "files": { + "title": "Усі файли", + "uploadFile": "Завантажити файл", + "loadError": "Не вдалося завантажити файли", + "pageTitle": "Мої файли", + "breadcrumb": "Мої файли", + "downloadStart": "Завантаження розпочато", + "downloadError": "Не вдалося завантажити файл", + "updateSuccess": "Файл оновлено успішно", + "updateError": "Не вдалося оновити файл", + "deleteSuccess": "Файл видалено успішно", + "deleteError": "Не вдалося видалити файл", + "bulkDownloadSuccess": "Завантаження файлів розпочато успішно", + "bulkDownloadError": "Помилка створення ZIP файлу", + "bulkDownloadFileError": "Помилка завантаження файлу {fileName}", + "bulkDeleteSuccess": "{count, plural, =1 {1 елемент видалено успішно} other {# елементів видалено успішно}}", + "bulkDeleteError": "Помилка видалення вибраних елементів", + "bulkDeleteTitle": "Видалити вибрані елементи", + "bulkDeleteConfirmation": "Ви впевнені, що хочете видалити {count, plural, =1 {1 елемент} other {# елементів}}? Цю дію неможливо скасувати.", + "totalFiles": "{count, plural, =0 {Немає файлів} =1 {1 файл} other {# файлів}}", + "openFolder": "Відкрити папку", + "errors": { + "moveItemsFailed": "Не вдалося перемістити елементи. Спробуйте ще раз.", + "cannotMoveHere": "Неможливо перемістити елементи в це місце" + }, + "empty": { + "title": "Ще немає файлів або папок", + "description": "Завантажте ваш перший файл або створіть папку, щоб почати" + }, + "files": "файли", + "folders": "папки", + "actions": { + "open": "Відкрити", + "rename": "Перейменувати", + "delete": "Видалити" + }, + "viewMode": { + "table": "Таблиця", + "grid": "Сітка" + } + }, + "filesTable": { + "ariaLabel": "Таблиця файлів", + "selectAll": "Вибрати все", + "selectFile": "Вибрати файл {fileName}", + "columns": { + "name": "НАЗВА", + "description": "ОПИС", + "size": "РОЗМІР", + "createdAt": "СТВОРЕНО", + "updatedAt": "ОНОВЛЕНО", + "actions": "ДІЇ" + }, + "actions": { + "menu": "Меню дій з файлом", + "preview": "Попередній перегляд", + "edit": "Редагувати", + "share": "Поділитися", + "download": "Завантажити", + "delete": "Видалити" + }, + "bulkActions": { + "selected": "{count, plural, =1 {1 файл вибрано} other {# файлів вибрано}}", + "actions": "Дії", + "download": "Завантажити вибрані", + "share": "Поділитися вибраними", + "delete": "Видалити вибрані" + } + }, + "folderActions": { + "editFolder": "Редагувати папку", + "folderName": "Назва папки", + "folderNamePlaceholder": "Введіть назву папки", + "folderDescription": "Опис", + "folderDescriptionPlaceholder": "Введіть опис папки (необов'язково)", + "createFolder": "Створити папку", + "renameFolder": "Перейменувати папку", + "moveFolder": "Перемістити папку", + "shareFolder": "Поділитися папкою", + "deleteFolder": "Видалити папку", + "moveTo": "Перемістити до", + "selectDestination": "Виберіть папку призначення", + "rootFolder": "Корінь", + "folderCreated": "Папку створено успішно", + "folderRenamed": "Папку перейменовано успішно", + "folderMoved": "Папку переміщено успішно", + "folderDeleted": "Папку видалено успішно", + "folderShared": "Папкою поділилися успішно", + "createFolderError": "Помилка створення папки", + "renameFolderError": "Помилка перейменування папки", + "moveFolderError": "Помилка переміщення папки", + "deleteFolderError": "Помилка видалення папки", + "shareFolderError": "Помилка спільного доступу до папки", + "deleteConfirmation": "Ви впевнені, що хочете видалити цю папку?", + "deleteWarning": "Цю дію неможливо скасувати." + }, + "footer": { + "poweredBy": "Працює на", + "kyanHomepage": "Домашня сторінка Kyantech" + }, + "forgotPassword": { + "emailLabel": "Адреса електронної пошти", + "emailPlaceholder": "Введіть вашу електронну пошту", + "sending": "Надсилання...", + "submit": "Надіслати інструкції для скидання", + "backToLogin": "Повернутися до входу", + "title": "Забули пароль", + "description": "Введіть вашу адресу електронної пошти, і ми надішлемо вам інструкції для скидання пароля", + "resetInstructions": "Інструкції для скидання надіслано на вашу електронну пошту", + "pageTitle": "Забули пароль", + "passwordAuthDisabled": "Автентифікація за паролем відключена. Будь ласка, зв'яжіться з адміністратором або використовуйте зовнішнього постачальника автентифікації." + }, + "generateShareLink": { + "generateTitle": "Створити посилання для спільного доступу", + "updateTitle": "Оновити посилання для спільного доступу", + "generateDescription": "Створіть користувацьке посилання для цього спільного доступу. Ви можете налаштувати URL, щоб зробити його більш запам'ятовуваним.", + "updateDescription": "Оновіть користувацьке посилання для цього спільного доступу. Ви можете налаштувати URL, щоб зробити його більш запам'ятовуваним.", + "aliasPlaceholder": "Користувацький ID для посилання", + "linkReady": "Ваше посилання для спільного доступу готове. Ви можете скопіювати його зараз.", + "readyDescription": "Ваше посилання для спільного доступу готове. Ви можете сканувати QR-код безпосередньо, завантажити його для подальшого використання або скопіювати посилання нижче.", + "generateButton": "Створити посилання", + "updateButton": "Оновити посилання", + "copyButton": "Копіювати посилання", + "success": "Посилання створено успішно", + "error": "Не вдалося створити посилання", + "copied": "Посилання скопійовано в буфер обміну", + "tabs": { + "link": "Посилання", + "qrcode": "QR-код" + } + }, + "home": { + "description": "Альтернатива WeTransfer з відкритим кодом. Діліться файлами безпечно, без відстеження або обмежень.", + "documentation": "Документація", + "starOnGithub": "Поставити зірочку на GitHub", + "privacyMessage": "Створено з думкою про приватність. Ваші файли доступні лише тим, у кого є посилання для спільного доступу перед завантаженням. Назавжди безкоштовно та з відкритим кодом.", + "header": { + "fileSharing": "Обмін файлами", + "tagline": "зроблено простим і безкоштовним" + }, + "pageTitle": "Головна" + }, + "iconPicker": { + "title": "Вибрати іконку", + "placeholder": "Вибрати іконку", + "searchPlaceholder": "Пошук іконок...", + "loadingMore": "Завантаження більше іконок...", + "allIconsLoaded": "Усі {count} іконки завантажено", + "noIconsFound": "Не знайдено іконок для \"{search}\"", + "tabs": { + "all": "Усі іконки", + "popular": "Популярні", + "auth": "Постачальники автентифікації" + }, + "stats": "{iconCount} іконок з {libraryCount} бібліотек", + "categoryBadge": "{category} ({count} іконок)" + }, + "imageEdit": { + "title": "Редагувати зображення", + "rotate": "Повернути", + "zoom": "Масштабувати", + "cropInstructions": "Перетягніть для переміщення, змініть розмір кутів для регулювання області обрізання" + }, + "login": { + "welcome": "Ласкаво просимо до", + "signInToContinue": "Увійдіть, щоб продовжити", + "emailOrUsernameLabel": "Електронна пошта або ім'я користувача", + "emailOrUsernamePlaceholder": "Введіть вашу електронну пошту або ім'я користувача", + "emailLabel": "Адреса електронної пошти", + "emailPlaceholder": "Введіть вашу електронну пошту", + "passwordLabel": "Пароль", + "passwordPlaceholder": "Введіть ваш пароль", + "signIn": "Увійти", + "signingIn": "Вхід...", + "forgotPassword": "Забули пароль?", + "pageTitle": "Вхід", + "or": "або", + "continueWithSSO": "Продовжити з SSO", + "processing": "Обробка автентифікації..." + }, + "logo": { + "labels": { + "appLogo": "Логотип додатку" + }, + "buttons": { + "upload": "Завантажити логотип", + "remove": "Видалити логотип" + }, + "messages": { + "uploadSuccess": "Логотип завантажено успішно", + "removeSuccess": "Логотип видалено успішно" + }, + "errors": { + "uploadFailed": "Не вдалося завантажити логотип", + "removeFailed": "Не вдалося видалити логотип" + } + }, + "moveItems": { + "itemsToMove": "Елементи для переміщення:", + "movingTo": "Переміщення до:", + "title": "Перемістити {count, plural, =1 {елемент} other {елементи}}", + "description": "Перемістити {count, plural, =1 {елемент} other {елементи}} в нове місце", + "success": "Успішно переміщено {count} {count, plural, =1 {елемент} other {елементів}}", + "errors": { + "moveFailed": "Не вдалося перемістити елементи" + } + }, + "navbar": { + "logoAlt": "Логотип додатку", + "profileMenu": "Меню профілю", + "profile": "Профіль", + "customization": "Налаштування", + "settings": "Налаштування", + "usersManagement": "Управління користувачами", + "logout": "Вийти" + }, + "navigation": { + "dashboard": "Панель управління" + }, + "notifications": { + "permissionGranted": "Сповіщення про завантаження увімкнено", + "permissionDenied": "Сповіщення про завантаження відключено", + "downloadComplete": { + "title": "Завантаження завершено", + "body": "{fileName} завершив завантаження" + }, + "downloadFailed": { + "title": "Завантаження не вдалося", + "body": "Не вдалося завантажити {fileName}: {error}", + "unknownError": "Невідома помилка" + }, + "queueProcessing": { + "title": "Завантаження розпочинається", + "body": "{fileName} зараз завантажується{position}", + "position": " (був #{position} в черзі)" + } + }, + "profile": { + "password": { + "title": "Змінити пароль", + "newPassword": "Новий пароль", + "confirmPassword": "Підтвердити новий пароль", + "updateButton": "Оновити пароль" + }, + "form": { + "title": "Інформація профілю", + "firstName": "Ім'я", + "lastName": "Прізвище", + "username": "Ім'я користувача", + "email": "Електронна пошта", + "updateButton": "Оновити профіль" + }, + "header": { + "title": "Профіль", + "subtitle": "Керуйте вашою особистою інформацією та паролем" + }, + "picture": { + "title": "Фото профілю", + "description": "Натисніть на іконку камери, щоб змінити ваше фото профілю", + "uploadPhoto": "Завантажити фото", + "removePhoto": "Видалити фото" + }, + "errors": { + "loadFailed": "Не вдалося завантажити дані користувача", + "updateFailed": "Не вдалося оновити профіль", + "passwordFailed": "Не вдалося оновити пароль", + "imageFailed": "Не вдалося оновити зображення профілю", + "imageRemoveFailed": "Не вдалося видалити зображення профілю" + }, + "messages": { + "noChanges": "Немає змін для збереження", + "updateSuccess": "Профіль оновлено успішно", + "fillPasswords": "Будь ласка, заповніть обидва поля паролю", + "passwordSuccess": "Пароль оновлено успішно", + "imageSuccess": "Зображення профілю оновлено успішно", + "imageRemoved": "Зображення профілю видалено успішно" + }, + "pageTitle": "Профіль" + }, + "qrCodeModal": { + "title": "QR-код спільного доступу", + "description": "Відскануйте цей QR-код для доступу до посилання.", + "download": "Завантажити QR-код" + }, + "quickAccess": { + "files": { + "title": "Мої файли", + "description": "Доступ та управління вашими завантаженими файлами" + }, + "shares": { + "title": "Мої спільні доступи", + "description": "Переглядайте та керуйте вашими спільними файлами" + }, + "reverseShares": { + "title": "Отримувати файли", + "description": "Створюйте посилання для інших, щоб надсилати файли вам" + } + }, + "recentFiles": { + "title": "Останні завантаження", + "viewAll": "Переглянути все", + "upload": "Завантажити", + "uploadFile": "Завантажити файл", + "noFiles": "Ще немає завантажених файлів" + }, + "recentShares": { + "title": "Останні спільні доступи", + "viewAll": "Переглянути все", + "createShare": "Створити спільний доступ", + "noShares": "Ще немає створених спільних доступів", + "createFirst": "Створіть ваш перший спільний доступ" + }, + "recipientSelector": { + "emailPlaceholder": "Введіть електронну пошту отримувача", + "add": "Додати", + "recipients": "Отримувачі ({count})", + "notifyAll": "Сповістити всіх", + "noRecipients": "Ще не додано отримувачів", + "addSuccess": "Отримувача додано успішно", + "addError": "Не вдалося додати отримувача", + "removeSuccess": "Отримувача видалено успішно", + "removeError": "Не вдалося видалити отримувача", + "sendingNotifications": "Надсилання сповіщень...", + "notifySuccess": "Отримувачів сповіщено успішно", + "notifyError": "Не вдалося сповістити отримувачів", + "selectAll": "Вибрати все", + "selectedCount": "{count} вибрано", + "selectRecipient": "Вибрати {email}", + "notifySelected": "Сповістити вибраних", + "removeSelected": "Видалити вибраних", + "notifySingle": "Сповістити цього отримувача", + "removeSingle": "Видалити цього отримувача", + "bulkRemoveSuccess": "{count} отримувачів видалено успішно", + "bulkRemoveError": "Не вдалося видалити вибраних отримувачів", + "bulkNotifySuccess": "Сповіщення надіслано {count} отримувачам", + "bulkNotifyError": "Не вдалося сповістити вибраних отримувачів", + "singleNotifySuccess": "Сповіщення надіслано {email}", + "singleNotifyError": "Не вдалося сповістити отримувача", + "modalDescription": "Додайте та керуйте отримувачами для цього спільного доступу. Ви можете сповістити всіх або конкретних отримувачів, коли SMTP налаштовано.", + "addRecipient": "Додати отримувача", + "invalidEmail": "Будь ласка, введіть дійсну адресу електронної пошти", + "duplicateEmail": "Цього отримувача вже додано", + "noRecipientsDescription": "Додайте отримувачів, щоб поділитися цим контентом через електронну пошту" + }, + "register": { + "validation": { + "firstNameRequired": "Ім'я обов'язкове", + "lastNameRequired": "Прізвище обов'язкове", + "usernameMinLength": "Ім'я користувача має бути щонайменше 3 символи", + "invalidEmail": "Неправильна електронна пошта", + "passwordMinLength": "Пароль має бути щонайменше 8 символів", + "success": "Користувача-адміністратора створено успішно!", + "error": "Помилка створення користувача-адміністратора" + }, + "labels": { + "firstName": "Ім'я", + "lastName": "Прізвище", + "username": "Ім'я користувача", + "email": "Електронна пошта", + "password": "Пароль" + }, + "buttons": { + "creating": "Створення...", + "createAdmin": "Створити акаунт адміністратора" + } + }, + "resetPassword": { + "pageTitle": "Скинути пароль", + "header": { + "title": "Скинути пароль", + "description": "Введіть ваш новий пароль нижче" + }, + "form": { + "newPassword": "Новий пароль", + "newPasswordPlaceholder": "Введіть ваш новий пароль", + "confirmPassword": "Підтвердити новий пароль", + "confirmPasswordPlaceholder": "Підтвердіть ваш новий пароль", + "resetting": "Скидання пароля...", + "submit": "Скинути пароль", + "backToLogin": "Повернутися до входу" + }, + "messages": { + "success": "Пароль скинуто успішно" + }, + "errors": { + "serverError": "Не вдалося скинути пароль. Спробуйте ще раз.", + "invalidToken": "Неправильний або відсутній токен скидання" + } + }, + "reverseShares": { + "pageTitle": "Отримувати файли", + "search": { + "title": "Керувати посиланнями для отримання", + "createButton": "Створити посилання", + "placeholder": "Пошук посилань для отримання...", + "results": "Знайдено {filtered} з {total} посилань для отримання" + }, + "labels": { + "files": "файли", + "size": "розмір", + "status": "статус", + "access": "доступ", + "description": "Опис", + "pageLayout": "Макет сторінки", + "security": "Безпека та статус", + "limits": "Обмеження", + "maxFiles": "Максимум файлів", + "maxFileSize": "Максимальний розмір", + "allowedTypes": "Дозволені типи", + "filesReceived": "Отримані файли", + "fileLimit": "Обмеження файлів", + "noLimit": "Без обмежень", + "noLinkCreated": "Посилання не створено", + "publicAccess": "Публічний доступ", + "protectedByPassword": "Захищено паролем", + "configureProtection": "Натисніть для налаштування захисту", + "enterPassword": "Введіть пароль", + "thisLinkProtected": "Це посилання буде захищено паролем", + "thisLinkPublic": "Це посилання буде публічно доступним", + "configureExpiration": "Налаштувати закінчення терміну дії", + "configureLimits": "Налаштувати обмеження файлів", + "protectWithPassword": "Захистити паролем", + "layoutOptions": { + "default": "Стандартний", + "wetransfer": "WeTransfer" + }, + "noFilesLimit": "Без обмеження файлів", + "noSizeLimit": "Без обмеження розміру", + "allFileTypes": "Усі типи файлів", + "fileTypesHelp": "Введіть розширення без крапок, розділені пробілом, комою, тире або вертикальною рискою", + "fieldRequirements": "Вимоги до полів", + "nameFieldRequired": "Поле імені", + "emailFieldRequired": "Поле електронної пошти", + "fieldOptions": { + "hidden": "Приховано", + "optional": "Необов'язково", + "required": "Обов'язково" + } + }, + "card": { + "untitled": "Посилання без назви", + "noDescription": "Без опису", + "addDescriptionPlaceholder": "Додати опис...", + "files": "файли", + "progress": "Прогрес", + "created": "Створено", + "expired": "Прострочено", + "expires": "Закінчується", + "viewDetails": "Переглянути деталі", + "viewQrCode": "Переглянути QR-код", + "copyLink": "Копіювати посилання", + "openInNewTab": "Відкрити в новій вкладці", + "editLink": "Редагувати посилання", + "createLink": "Створити посилання", + "delete": "Видалити", + "copyLinkTitle": "Копіювати посилання", + "createLinkCTA": "Створити посилання для отримання" + }, + "status": { + "active": "Активний", + "inactive": "Неактивний", + "expired": "Прострочений", + "protected": "Захищений", + "public": "Публічний" + }, + "actions": { + "copyLink": "Копіювати посилання", + "editAlias": "Редагувати псевдонім", + "createAlias": "Створити псевдонім", + "viewDetails": "Переглянути деталі", + "edit": "Редагувати", + "delete": "Видалити", + "viewFiles": "Отримані файли", + "viewQrCode": "Переглянути QR-код" + }, + "empty": { + "title": "Не створено посилань для отримання", + "description": "Створюйте користувацькі посилання для інших, щоб надсилати файли безпосередньо вам безпечно та організовано.", + "createButton": "Створити перше посилання" + }, + "modals": { + "create": { + "title": "Створити посилання для отримання", + "description": "Налаштуйте користувацьке посилання для отримання файлів від інших" + }, + "edit": { + "title": "Редагувати посилання для отримання", + "description": "Оновіть налаштування для цього посилання отримання", + "updating": "Оновлення...", + "saveChanges": "Зберегти зміни" + }, + "details": { + "title": "Деталі посилання", + "description": "Переглядайте та редагуйте інформацію вашого посилання для отримання", + "pageLayout": "Макет сторінки", + "linkSection": "Посилання для отримання", + "noLinkCreated": "Посилання не створено", + "limits": "Обмеження", + "maxFiles": "Максимум файлів", + "maxFileSize": "Максимальний розмір", + "allowedTypes": "Дозволені типи", + "noLimit": "Без обмежень", + "security": "Безпека", + "status": "Статус", + "password": "Пароль", + "files": "Отримані файли", + "noFiles": "Ще не отримано файлів", + "copyLink": "Копіювати посилання", + "openLink": "Відкрити посилання", + "editAlias": "Редагувати псевдонім", + "createAlias": "Створити псевдонім", + "editPassword": "Редагувати захист паролем", + "basicInfo": "Основна інформація", + "securityAndStatus": "Безпека та статус", + "protection": "Захист", + "protectedByPassword": "Захищено паролем", + "publicAccess": "Публічний доступ", + "active": "Активний", + "inactive": "Неактивний", + "deactivate": "Деактивувати", + "activate": "Активувати", + "expiration": "Закінчення терміну дії", + "dates": "Дати", + "createdAt": "Створено о", + "updatedAt": "Оновлено о", + "allTypes": "Усі типи", + "placeholderTypes": ".pdf,.jpg,.png (розділені комами)", + "downloadSuccess": "Завантаження розпочато", + "downloadError": "Помилка завантаження файлу", + "editSuccess": "Файл оновлено успішно", + "editError": "Помилка оновлення файлу", + "previewNotAvailable": "Попередній перегляд недоступний", + "notAvailable": "Недоступно", + "invalidDate": "Неправильна дата" + }, + "alias": { + "editTitle": "Редагувати псевдонім", + "createTitle": "Створити псевдонім", + "editDescription": "Оновіть псевдонім для цього посилання отримання", + "createDescription": "Створіть користувацький псевдонім для цього посилання отримання", + "aliasLabel": "Псевдонім посилання", + "aliasPlaceholder": "my-custom-link", + "preview": "Попередній перегляд:", + "currentLink": "Поточне посилання:", + "copyCurrentLink": "Копіювати поточне посилання", + "randomTooltip": "Створити випадковий псевдонім", + "cancel": "Скасувати", + "creating": "Створення...", + "updating": "Оновлення...", + "create": "Створити псевдонім", + "update": "Оновити псевдонім", + "validation": { + "required": "Псевдонім обов'язковий", + "minLength": "Псевдонім має бути щонайменше 3 символи", + "maxLength": "Псевдонім має бути не більше 50 символів", + "pattern": "Псевдонім може містити лише літери, цифри, дефіси та підкреслення" + }, + "help": "3-50 символів. Пробіли будуть автоматично перетворені на дефіси." + }, + "password": { + "title": "Редагувати захист паролем", + "description": "Налаштуйте захист паролем для цього посилання", + "hasPassword": "Захищено паролем", + "password": "Пароль", + "cancel": "Скасувати", + "save": "Зберегти", + "saving": "Збереження..." + }, + "receivedFiles": { + "title": "Отримані файли", + "description": "Переглядайте та керуйте файлами, надісланими через це посилання", + "noFiles": "Ще не отримано файлів", + "noFilesDescription": "Файли, надіслані через це посилання, з'являться тут", + "fileCount": "{count, plural, =0 {Немає файлів} =1 {1 файл} other {# файлів}}", + "invalidDate": "Неправильна дата", + "totalSize": "Загальний розмір: {size}", + "columns": { + "file": "Файл", + "size": "Розмір", + "sender": "Надіслав", + "date": "Дата", + "actions": "Дії" + }, + "actions": { + "preview": "Попередній перегляд", + "download": "Завантажити", + "copyToMyFiles": "Копіювати в мої файли", + "copying": "Копіювання..." + }, + "uploadedBy": "Завантажено {name}", + "anonymous": "Анонімно", + "downloadSuccess": "Завантаження розпочато", + "downloadError": "Помилка завантаження файлу", + "editSuccess": "Файл оновлено успішно", + "editError": "Помилка оновлення файлу", + "previewNotAvailable": "Попередній перегляд недоступний", + "copySuccess": "Файл успішно скопійовано в ваші файли", + "copyError": "Помилка копіювання файлу в ваші файли", + "deleteSuccess": "Файл видалено успішно", + "deleteError": "Помилка видалення файлу", + "bulkCopySuccess": "{count, plural, =1 {1 файл успішно скопійовано в ваші файли} other {# файлів успішно скопійовано в ваші файли}}", + "bulkDeleteSuccess": "{count, plural, =1 {1 файл видалено успішно} other {# файлів видалено успішно}}", + "bulkCopyProgress": "Копіювання {count, plural, =1 {1 файлу} other {# файлів}} в ваші файли...", + "bulkDeleteProgress": "Видалення {count, plural, =1 {1 файлу} other {# файлів}}...", + "bulkDeleteConfirmTitle": "Видалити вибрані файли", + "bulkDeleteConfirmMessage": "Ви впевнені, що хочете видалити {count, plural, =1 {цей файл} other {ці # файлів}}? Цю дію неможливо скасувати.", + "bulkDeleteConfirmButton": "Видалити {count, plural, =1 {файл} other {файли}}", + "bulkActions": { + "selected": "{count, plural, =1 {1 файл вибрано} other {# файлів вибрано}}", + "actions": "Дії", + "download": "Завантажити вибрані", + "copyToMyFiles": "Копіювати вибрані в мої файли", + "delete": "Видалити вибрані" + }, + "selectAll": "Вибрати все", + "selectFile": "Вибрати файл {fileName}", + "copyErrors": { + "timeout": "Операція копіювання перевищила час очікування. Спробуйте ще раз з меншим файлом або перевірте з'єднання.", + "failed": "Операція копіювання не вдалася. Спробуйте ще раз.", + "aborted": "Операція копіювання була скасована через перевищення часу очікування." + } + } + }, + "form": { + "name": { + "label": "Назва посилання", + "placeholder": "наприклад: Проектні документи, Сімейні фото..." + }, + "description": { + "label": "Опис", + "placeholder": "Опишіть, які файли ви очікуєте отримати...", + "description": "Необов'язково. Допомагає людям зрозуміти, що надсилати." + }, + "status": { + "label": "Статус посилання", + "description": "Активувати або деактивувати це посилання для отримання" + }, + "expiration": { + "label": "Дата закінчення терміну дії", + "description": "Необов'язково. Посилання буде деактивовано після цієї дати.", + "configure": "Налаштувати закінчення терміну дії" + }, + "fileLimits": { + "configure": "Налаштувати обмеження файлів" + }, + "maxFiles": { + "label": "Максимум файлів", + "placeholder": "наприклад: 10", + "description": "Необов'язково. Обмежити загальну кількість файлів, які можна надіслати.", + "noLimit": "Без обмеження файлів" + }, + "maxFileSize": { + "label": "Максимальний розмір файлу", + "placeholder": "наприклад: 100", + "description": "Необов'язково. Обмежити індивідуальний розмір кожного файлу.", + "noLimit": "Без обмеження розміру" + }, + "allowedFileTypes": { + "label": "Дозволені типи файлів", + "placeholder": "наприклад: pdf, jpg, png, docx", + "description": "Введіть розширення без крапок, розділені пробілом, комою, тире або вертикальною рискою", + "allTypes": "Усі типи файлів" + }, + "pageLayout": { + "label": "Макет сторінки", + "placeholder": "Виберіть макет", + "description": "Як сторінка завантаження буде виглядати для користувачів.", + "options": { + "default": "Стандартний макет", + "wetransfer": "Стиль WeTransfer" + } + }, + "password": { + "label": "Пароль захисту", + "placeholder": "Необов'язково. Додайте пароль для захисту посилання", + "description": "Необов'язково. Користувачам буде потрібен цей пароль для доступу до посилання.", + "configurePassword": "Налаштувати пароль", + "protectWithPassword": "Захистити паролем", + "passwordHelp": "Пароль має бути щонайменше 4 символи", + "passwordPlaceholder": "Введіть пароль для захисту посилання" + }, + "nameFieldRequired": { + "label": "Вимога до поля імені", + "description": "Налаштуйте, чи повинно показуватися поле імені завантажувача і чи є воно обов'язковим" + }, + "emailFieldRequired": { + "label": "Вимога до поля електронної пошти", + "description": "Налаштуйте, чи повинно показуватися поле електронної пошти завантажувача і чи є воно обов'язковим" + }, + "fieldRequirements": { + "title": "Вимоги до полів", + "description": "Налаштуйте, які поля показуються в формі завантаження" + }, + "submit": "Створити посилання для отримання" + }, + "messages": { + "created": "Посилання для отримання створено успішно!", + "createSuccess": "Посилання для отримання створено успішно!", + "updateSuccess": "Посилання для отримання оновлено успішно!", + "linkCopied": "Посилання скопійовано в буфер обміну!", + "deleteSuccess": "Посилання для отримання видалено успішно!", + "aliasCreated": "Псевдонім створено успішно!", + "activateSuccess": "Посилання для отримання активовано успішно!", + "deactivateSuccess": "Посилання для отримання деактивовано успішно!", + "passwordProtectionEnabled": "Захист паролем увімкнено успішно!", + "passwordProtectionDisabled": "Захист паролем видалено успішно!" + }, + "defaultLinkName": "Отримані файли", + "errors": { + "loadFailed": "Не вдалося завантажити посилання для отримання", + "createFailed": "Не вдалося створити посилання для отримання. Спробуйте ще раз.", + "updateFailed": "Не вдалося оновити посилання для отримання. Спробуйте ще раз.", + "deleteFailed": "Не вдалося видалити посилання для отримання. Спробуйте ще раз.", + "aliasCreateFailed": "Не вдалося створити псевдонім. Спробуйте ще раз.", + "passwordUpdateFailed": "Не вдалося оновити захист паролем" + }, + "delete": { + "title": "Видалити посилання для отримання", + "description": "Цю дію неможливо скасувати. Посилання буде повністю видалено і більше не зможе отримувати файли.", + "confirmButton": "Видалити посилання", + "cancelButton": "Скасувати", + "deleting": "Видалення..." + }, + "upload": { + "metadata": { + "title": "Надіслати файли - Palmr", + "description": "Надішліть файли через спільне посилання", + "descriptionWithLimit": "Завантажити файли (макс {limit} файлів)" + }, + "layout": { + "defaultTitle": "Надіслати файли", + "importantInfo": "Важлива інформація:", + "maxFiles": "Максимум {count} файл(ів)", + "maxFileSize": "Максимальний розмір файлу: {size}МБ", + "allowedTypes": "Дозволені типи: {types}", + "loading": "Завантаження..." + }, + "password": { + "title": "Захищене посилання", + "description": "Це посилання захищено паролем. Введіть пароль для продовження.", + "label": "Пароль", + "placeholder": "Введіть пароль", + "cancel": "Скасувати", + "submit": "Продовжити", + "verifying": "Перевірка..." + }, + "errors": { + "loadFailed": "Не вдалося завантажити інформацію. Спробуйте ще раз.", + "passwordIncorrect": "Неправильний пароль. Спробуйте ще раз.", + "linkNotFound": "Посилання не знайдено або прострочене.", + "linkInactive": "Це посилання неактивне.", + "linkExpired": "Термін дії цього посилання закінчився.", + "uploadFailed": "Помилка завантаження файлу", + "retry": "Повторити", + "fileTooLarge": "Файл занадто великий. Максимальний розмір: {maxSize}", + "fileTypeNotAllowed": "Тип файлу не дозволений. Прийняті типи: {allowedTypes}", + "maxFilesExceeded": "Дозволено максимум {maxFiles} файлів", + "selectAtLeastOneFile": "Виберіть принаймні один файл", + "provideNameOrEmail": "Будь ласка, вкажіть ваше ім'я або електронну пошту", + "provideNameRequired": "Ім'я обов'язкове", + "provideEmailRequired": "Електронна пошта обов'язкова" + }, + "fileDropzone": { + "dragActive": "Перетягніть файли сюди", + "dragInactive": "Перетягніть файли сюди або натисніть для вибору", + "acceptedTypes": "Прийняті типи: {types}", + "maxFileSize": "Максимальний розмір: {size}", + "maxFiles": "Максимум {count} файлів", + "remainingFiles": "{remaining} з {max} файлів залишилось" + }, + "fileList": { + "title": "Вибрані файли:", + "statusUploaded": "Завантажено", + "statusError": "Помилка", + "retry": "Повторити" + }, + "form": { + "nameLabel": "Ім'я", + "nameLabelOptional": "Ім'я (необов'язково)", + "namePlaceholder": "Ваше ім'я", + "emailLabel": "Електронна пошта", + "emailLabelOptional": "Електронна пошта (необов'язково)", + "emailPlaceholder": "your@email.com", + "descriptionLabel": "Опис (необов'язково)", + "descriptionPlaceholder": "Додайте опис до файлів...", + "uploadButton": "Надіслати {count} файл(ів)", + "uploading": "Надсилання..." + }, + "success": { + "title": "Файли надіслано успішно! 🎉", + "description": "Ви можете закрити цю сторінку.", + "countMessage": "{count} файл(ів) надіслано успішно!" + }, + "maxFilesReached": { + "title": "Досягнуто ліміт файлів", + "description": "Це посилання вже отримало максимальну кількість {maxFiles} файл(ів), що дозволена.", + "contactOwner": "Якщо сталася помилка або вам потрібно надіслати більше файлів, зв'яжіться з власником посилання." + }, + "linkInactive": { + "title": "Посилання неактивне", + "description": "Це посилання для отримання тимчасово неактивне.", + "contactOwner": "Зв'яжіться з власником посилання для отримання додаткової інформації." + }, + "linkNotFound": { + "title": "Посилання не знайдено", + "description": "Це посилання могло бути видалено або ніколи не існувало." + }, + "linkExpired": { + "title": "Термін дії посилання закінчився", + "description": "Термін дії цього посилання для отримання закінчився і воно більше не приймає файли.", + "contactOwner": "Зв'яжіться з власником посилання, якщо вам потрібно надіслати файли." + } + }, + "components": { + "fileRow": { + "addDescription": "Додати опис...", + "anonymous": "Анонімно" + }, + "fileActions": { + "edit": "Редагувати", + "preview": "Попередній перегляд", + "download": "Завантажити", + "delete": "Видалити", + "copyToMyFiles": "Копіювати в мої файли", + "copying": "Копіювання..." + }, + "editField": { + "saveChanges": "Зберегти зміни", + "cancelEdit": "Скасувати редагування" + } + } + }, + "searchBar": { + "placeholder": "Пошук файлів і папок...", + "placeholderFiles": "Пошук файлів...", + "placeholderFolders": "Пошук папок...", + "results": "Показано {filtered} з {total} елементів", + "noResults": "Не знайдено результатів для \"{query}\"" + }, + "settings": { + "groups": { + "defaultDescription": "Опції конфігурації", + "general": { + "title": "Загальні", + "description": "Основні налаштування програми" + }, + "email": { + "title": "Електронна пошта", + "description": "Конфігурація поштового сервера" + }, + "security": { + "title": "Безпека", + "description": "Налаштування безпеки та автентифікації" + }, + "storage": { + "title": "Сховище", + "description": "Конфігурація сховища файлів" + }, + "oidc": { + "title": "OpenID Connect (SSO)", + "description": "Налаштування автентифікації SSO через OpenID Connect" + } + }, + "fields": { + "noDescription": "Опис недоступний", + "appLogo": { + "title": "Логотип програми", + "description": "Зображення логотипу програми" + }, + "appName": { + "title": "Назва програми", + "description": "Назва програми, яка відображається користувачам" + }, + "appDescription": { + "title": "Опис програми", + "description": "Короткий опис програми" + }, + "showHomePage": { + "title": "Показати головну сторінку", + "description": "Показати головну сторінку після встановлення" + }, + "hideVersion": { + "title": "Сховати версію", + "description": "Сховати версію Palmr з підвалу на всіх сторінках" + }, + "smtpEnabled": { + "title": "SMTP увімкнено", + "description": "Увімкнути або вимкнути функціональність електронної пошти SMTP" + }, + "smtpHost": { + "title": "SMTP сервер", + "description": "Адреса SMTP сервера" + }, + "smtpPort": { + "title": "SMTP порт", + "description": "Порт SMTP сервера" + }, + "smtpUser": { + "title": "SMTP користувач", + "description": "Ім'я користувача для автентифікації SMTP" + }, + "smtpPass": { + "title": "SMTP пароль", + "description": "Пароль для автентифікації SMTP" + }, + "smtpFromName": { + "title": "Ім'я відправника", + "description": "Відображуване ім'я для надісланих листів" + }, + "smtpFromEmail": { + "title": "Email відправника", + "description": "Адреса електронної пошти відправника" + }, + "smtpSecure": { + "title": "Безпека з'єднання", + "description": "Метод безпеки SMTP з'єднання - Авто (рекомендується), SSL, STARTTLS або Немає (небезпечно)", + "options": { + "auto": "Авто (рекомендується)", + "ssl": "SSL (порт 465)", + "tls": "STARTTLS (порт 587)", + "none": "Немає (небезпечно)" + } + }, + "smtpNoAuth": { + "title": "Без автентифікації", + "description": "Увімкніть це для внутрішніх серверів, які не потребують користувача/пароля (ховає поля автентифікації)" + }, + "smtpTrustSelfSigned": { + "title": "Довіряти самопідписаним сертифікатам", + "description": "Увімкніть це, щоб довіряти самопідписаним SSL/TLS сертифікатам (корисно для середовищ розробки)" + }, + "testSmtp": { + "title": "Тестувати SMTP з'єднання", + "description": "Перевірити, чи дійсна конфігурація SMTP" + }, + "maxLoginAttempts": { + "title": "Максимальна кількість спроб входу", + "description": "Максимальна кількість спроб входу перед блокуванням" + }, + "loginBlockDuration": { + "title": "Тривалість блокування", + "description": "Тривалість (у секундах) блокування після перевищення спроб" + }, + "passwordMinLength": { + "title": "Мінімальна довжина пароля", + "description": "Мінімальна кількість символів для паролів" + }, + "passwordResetTokenExpiration": { + "title": "Закінчення токена скидання", + "description": "Час дії (у секундах) для токена скидання пароля" + }, + "maxFileSize": { + "title": "Максимальний розмір файлу", + "description": "Максимально дозволений розмір файлу для завантаження" + }, + "maxTotalStoragePerUser": { + "title": "Максимальне сховище на користувача", + "description": "Загальний ліміт сховища на користувача" + }, + "firstUserAccess": { + "title": "Доступ першого користувача", + "description": "Налаштування для першого доступу нових користувачів" + }, + "serverUrl": { + "title": "URL сервера", + "description": "Базовий URL сервера Palmr (наприклад: https://palmr.example.com)" + }, + "passwordAuthEnabled": { + "title": "Автентифікація за паролем", + "description": "Увімкнути або вимкнути автентифікацію на основі пароля" + } + }, + "buttons": { + "save": "Зберегти {group}", + "testSmtp": "Тестувати з'єднання", + "testing": "Тестування..." + }, + "errors": { + "loadFailed": "Не вдалося завантажити налаштування", + "updateFailed": "Не вдалося оновити налаштування", + "passwordAuthRequiresProvider": "Неможливо вимкнути автентифікацію паролем без принаймні одного активного провайдера автентифікації" + }, + "messages": { + "noChanges": "Немає змін для збереження", + "updateSuccess": "Налаштування {group} успішно оновлено", + "smtpTestSuccess": "SMTP з'єднання успішне! Ваша конфігурація електронної пошти працює правильно.", + "smtpTestFailed": "SMTP з'єднання не вдалося: {error}", + "smtpTestGenericError": "Не вдалося протестувати SMTP з'єднання. Будь ласка, перевірте ваші налаштування та спробуйте знову.", + "smtpNotEnabled": "SMTP не увімкнено. Будь ласка, спочатку увімкніть SMTP.", + "smtpMissingHostPort": "Будь ласка, заповніть SMTP хост і порт перед тестуванням.", + "smtpMissingAuth": "Будь ласка, заповніть SMTP користувача та пароль, або увімкніть опцію 'Без автентифікації'." + }, + "title": "Налаштування", + "breadcrumb": "Налаштування", + "pageTitle": "Налаштування", + "tooltips": { + "testSmtp": "Тестує SMTP з'єднання з значеннями, які наразі введені у формі. Щоб зміни стали постійними, не забудьте зберегти налаштування після тестування.", + "defaultPlaceholder": "Введіть і натисніть Enter" + }, + "redirectUri": { + "placeholder": "https://mysite.com", + "previewLabel": "Повний URL, який буде збережено:" + } + }, + "share": { + "errors": { + "invalidPassword": "Неправильний пароль. Будь ласка, спробуйте ще раз.", + "loadFailed": "Не вдалося завантажити спільне використання", + "downloadFailed": "Не вдалося завантажити файл" + }, + "messages": { + "downloadStarted": "Завантаження розпочато" + }, + "password": { + "title": "Захищене паролем спільне використання", + "protected": "Це спільне використання захищене паролем", + "incorrect": "Неправильний пароль. Будь ласка, спробуйте ще раз.", + "label": "Пароль", + "placeholder": "Введіть пароль спільного використання", + "submit": "Надіслати" + }, + "details": { + "untitled": "Спільне використання без назви", + "created": "Створено: {date}", + "expires": "Закінчується: {date}" + }, + "downloadAll": "Завантажити все", + "notFound": { + "title": "Спільне використання не знайдено", + "description": "Це спільне використання могло бути видалене або закінчилося." + }, + "pageTitle": "Спільне використання", + "metadata": { + "defaultDescription": "Безпечно ділитися файлами", + "filesShared": "{count, plural, =1 {1 файл спільно використовується} few {# файли спільно використовуються} many {# файлів спільно використовується} other {# файлів спільно використовується}}" + } + }, + "shareActions": { + "fileTitle": "Поділитися файлом", + "folderTitle": "Поділитися папкою", + "linkTitle": "Створити посилання", + "linkDescriptionFile": "Створити індивідуальне посилання для спільного використання файлу", + "linkDescriptionFolder": "Створити індивідуальне посилання для спільного використання папки", + "aliasLabel": "Псевдонім посилання", + "aliasPlaceholder": "Введіть індивідуальний псевдонім", + "linkReady": "Ваше посилання для спільного використання готове:", + "generateLink": "Створити посилання", + "copyLink": "Копіювати посилання", + "deleteTitle": "Видалити спільне використання", + "deleteConfirmation": "Ви впевнені, що хочете видалити це спільне використання? Цю дію неможливо скасувати.", + "addDescriptionPlaceholder": "Додати опис...", + "editTitle": "Редагувати спільне використання", + "newPasswordLabel": "Новий пароль (залиште порожнім, щоб зберегти поточний)", + "newPasswordPlaceholder": "Введіть новий пароль", + "manageFilesTitle": "Керувати файлами", + "manageFilesDescription": "Виберіть файли та папки для включення в це спільне використання", + "manageRecipientsTitle": "Керувати одержувачами", + "itemsSelected": "Вибрано елементів: {count}", + "editSuccess": "Спільне використання успішно оновлено", + "editError": "Не вдалося оновити спільне використання", + "bulkDeleteConfirmation": "Ви впевнені, що хочете видалити {count, plural, =1 {1 спільне використання} few {# спільних використання} many {# спільних використань} other {# спільних використань}}? Цю дію неможливо скасувати.", + "bulkDeleteTitle": "Видалити вибрані спільні використання" + }, + "shareDetails": { + "title": "Деталі спільного використання", + "subtitle": "Переглянути та керувати деталями цього спільного використання", + "basicInfo": "Основна інформація", + "name": "Назва", + "description": "Опис", + "shareLink": "Посилання для спільного використання", + "dates": "Дати", + "security": "Безпека", + "files": "Файли", + "recipients": "Одержувачі", + "views": "Перегляди", + "created": "Створено", + "expires": "Закінчується", + "never": "Ніколи", + "untitled": "Спільне використання без назви", + "noDescription": "Немає опису", + "notAvailable": "Н/Д", + "invalidDate": "Неправильна дата", + "passwordProtected": "Захищено паролем", + "publicAccess": "Публічний доступ", + "maxViews": "Макс. переглядів:", + "noLink": "Посилання не створено", + "generateLink": "Створити посилання", + "editLink": "Редагувати посилання", + "copyLink": "Копіювати посилання", + "openLink": "Відкрити посилання", + "editSecurity": "Редагувати безпеку", + "editExpiration": "Редагувати закінчення", + "qrCode": "QR код", + "downloadQrCode": "Завантажити QR код", + "clickToEnlargeQrCode": "Клікніть, щоб збільшити QR код", + "loadError": "Не вдалося завантажити деталі спільного використання" + }, + "shareExpiration": { + "title": "Share Expiration Settings", + "subtitle": "Configure when this share will expire", + "currentStatus": "Current Status", + "expires": "Закінчується:", + "neverExpires": "Ніколи не закінчується", + "enableExpiration": "Увімкнути закінчення", + "expirationDate": "Дата закінчення", + "validation": { + "dateRequired": "Будь ласка, виберіть дату закінчення", + "dateMustBeFuture": "Дата закінчення повинна бути в майбутньому" + }, + "success": { + "expirationSet": "Дата закінчення встановлена успішно", + "expirationUpdated": "Дата закінчення оновлена успішно", + "expirationRemoved": "Закінчення видалено успішно - спільне використання тепер постійне" + }, + "error": { + "updateFailed": "Не вдалося оновити налаштування закінчення" + }, + "info": { + "title": "Про закінчення:", + "willBeInaccessible": "Спільне використання стане недоступним після цієї дати", + "canBeChanged": "Ви можете змінити або видалити дату закінчення в будь-який час", + "noExpiration": "Це спільне використання ніколи не закінчиться і залишатиметься доступним безстроково." + } + }, + "shareManager": { + "deleteSuccess": "Спільне використання видалено успішно", + "deleteError": "Не вдалося видалити спільне використання", + "updateSuccess": "Спільне використання оновлено успішно", + "updateError": "Не вдалося оновити спільне використання", + "securityUpdateSuccess": "Налаштування безпеки оновлено успішно", + "securityUpdateError": "Не вдалося оновити налаштування безпеки", + "expirationUpdateSuccess": "Налаштування закінчення оновлено успішно", + "expirationUpdateError": "Не вдалося оновити налаштування закінчення", + "filesUpdateSuccess": "Файли оновлено успішно", + "filesUpdateError": "Не вдалося оновити файли", + "recipientsUpdateSuccess": "Одержувачі оновлено успішно", + "recipientsUpdateError": "Не вдалося оновити одержувачів", + "linkGenerateSuccess": "Посилання для спільного використання створено успішно", + "linkGenerateError": "Не вдалося створити посилання для спільного використання", + "notifyLoading": "Надсилання сповіщень...", + "notifySuccess": "Одержувачі успішно сповіщені", + "notifyError": "Не вдалося сповістити одержувачів", + "bulkDeleteError": "Не вдалося видалити спільні використання", + "bulkDeleteLoading": "Видалення {count, plural, =1 {1 спільного використання} few {# спільних використань} many {# спільних використань} other {# спільних використань}}...", + "bulkDeleteSuccess": "{count, plural, =1 {1 спільне використання видалено успішно} few {# спільних використання видалено успішно} many {# спільних використань видалено успішно} other {# спільних використань видалено успішно}}", + "downloadSuccess": "Завантаження розпочато успішно", + "downloadError": "Не вдалося завантажити файли спільного використання", + "noFilesToDownload": "Немає файлів для завантаження", + "creatingZip": "Створення ZIP файлу...", + "zipDownloadSuccess": "ZIP файл завантажено успішно", + "zipDownloadError": "Не вдалося створити ZIP файл", + "errors": { + "multipleDownloadNotSupported": "Завантаження кількох спільних використань ще не підтримується - будь ласка, завантажуйте спільні використання окремо" + }, + "singleShareZipName": "{shareName}_files.zip", + "multipleSharesZipName": "{count}_shares_files.zip", + "defaultShareName": "Спільне використання" + }, + "shareMultipleFiles": { + "title": "Поділитися кількома файлами", + "shareNameLabel": "Назва спільного використання", + "shareNamePlaceholder": "Введіть назву спільного використання", + "descriptionLabel": "Опис", + "descriptionPlaceholder": "Введіть опис (необов'язково)", + "filesToShare": "Файли для спільного використання", + "files": "файли", + "totalSize": "Загальний розмір", + "creating": "Створення спільного використання...", + "create": "Створити спільне використання", + "itemsToShare": "Елементи для спільного використання ({count} {count, plural, =1 {елемент} few {елементи} many {елементів} other {елементів}})" + }, + "shareSecurity": { + "title": "Налаштування безпеки спільного використання", + "subtitle": "Налаштуйте захист паролем та опції безпеки для цього спільного використання", + "currentStatus": "Поточний статус", + "passwordProtection": "Захист паролем", + "password": "Пароль", + "newPassword": "Новий пароль", + "passwordPlaceholder": "Введіть безпечний пароль", + "existingPasswordMessage": "Це спільне використання вже має пароль. Якщо ви хочете його оновити, введіть новий пароль у поле нижче та збережіть.", + "passwordRequirements": { + "title": "Вимоги до пароля:", + "minLength": "Принаймні 2 символи" + }, + "info": { + "title": "Як це працює:", + "withPassword": "Користувачам потрібно буде ввести пароль для доступу до цього спільного використання.", + "withoutPassword": "Будь-хто з посиланням може отримати доступ до цього спільного використання без пароля." + }, + "validation": { + "passwordRequired": "Пароль обов'язковий", + "passwordTooShort": "Пароль повинен містити принаймні 2 символи" + }, + "success": { + "passwordSet": "Захист паролем увімкнено успішно", + "passwordUpdated": "Пароль оновлено успішно", + "passwordRemoved": "Захист паролем видалено успішно" + }, + "error": { + "updateFailed": "Не вдалося оновити налаштування безпеки" + } + }, + "shares": { + "errors": { + "loadFailed": "Не вдалося завантажити спільні використання", + "notifyFailed": "Не вдалося сповістити одержувачів", + "smtpConfigFailed": "Не вдалося завантажити конфігурацію SMTP" + }, + "messages": { + "linkCopied": "Посилання скопійовано в буфер обміну", + "recipientsNotified": "Одержувачі успішно сповіщені" + }, + "empty": { + "message": "Ще не створено жодного спільного використання", + "createButton": "Створити спільне використання" + }, + "header": { + "title": "Мої спільні використання", + "myShares": "Мої спільні використання" + }, + "search": { + "title": "Всі спільні використання", + "createButton": "Створити спільне використання", + "placeholder": "Пошук спільних використань...", + "results": "Знайдено {filtered} з {total} спільних використань" + }, + "pageTitle": "Спільні використання" + }, + "sharesTable": { + "ariaLabel": "Таблиця спільних використань", + "never": "Ніколи", + "columns": { + "name": "НАЗВА", + "description": "ОПИС", + "createdAt": "СТВОРЕНО", + "expiresAt": "ЗАКІНЧУЄТЬСЯ", + "status": "СТАТУС", + "security": "БЕЗПЕКА", + "files": "ФАЙЛИ", + "recipients": "ОДЕРЖУВАЧІ", + "actions": "ДІЇ" + }, + "status": { + "neverExpires": "Ніколи не закінчується", + "active": "Активне", + "expired": "Закінчилося" + }, + "security": { + "protected": "Захищене", + "public": "Публічне" + }, + "filesCount": "файли", + "folderCount": "папки", + "recipientsCount": "одержувачі", + "actions": { + "menu": "Меню дій спільного використання", + "edit": "Редагувати", + "manageFiles": "Керувати файлами", + "manageRecipients": "Керувати одержувачами", + "viewDetails": "Переглянути деталі", + "generateLink": "Створити посилання", + "editLink": "Редагувати посилання", + "copyLink": "Копіювати посилання", + "viewQrCode": "Переглянути QR код", + "notifyRecipients": "Сповістити одержувачів", + "downloadShareFiles": "Завантажити всі файли", + "delete": "Видалити" + }, + "bulkActions": { + "actions": "Дії", + "download": "Завантажити вибрані", + "delete": "Видалити", + "selected": "{count, plural, =1 {1 спільне використання вибрано} few {# спільних використання вибрано} many {# спільних використань вибрано} other {# спільних використань вибрано}}" + }, + "selectAll": "Вибрати все", + "selectShare": "Вибрати спільне використання {shareName}" + }, + "storageUsage": { + "title": "Використання сховища", + "ariaLabel": "Індикатор використання сховища", + "used": "використано", + "available": "доступно", + "total": "Загалом", + "loading": "Завантаження...", + "retry": "Повторити", + "errors": { + "title": "Інформація про сховище недоступна", + "detectionFailed": "Неможливо визначити дисковий простір. Це може бути через проблеми конфігурації системи або недостатні дозволи.", + "serverError": "Сталася помилка сервера під час отримання інформації про сховище. Будь ласка, спробуйте знову пізніше.", + "unknown": "Сталася неочікувана помилка під час завантаження інформації про сховище." + } + }, + "theme": { + "toggle": "Переключити тему", + "light": "Світла", + "dark": "Темна", + "system": "Системна" + }, + "twoFactor": { + "title": "Двофакторна автентифікація", + "description": "Додайте додатковий рівень безпеки до вашого облікового запису", + "enabled": "Ваш обліковий запис захищений двофакторною автентифікацією", + "disabled": "Двофакторна автентифікація не увімкнена", + "status": { + "label": "Статус:", + "enabled": "Увімкнено", + "disabled": "Вимкнено" + }, + "buttons": { + "enable2FA": "Увімкнути 2FA", + "disable2FA": "Вимкнути 2FA" + }, + "setup": { + "title": "Увімкнути двофакторну автентифікацію", + "description": "Відскануйте QR код за допомогою вашого додатку автентифікатора, потім введіть код підтвердження.", + "qrCode": "QR код", + "manualEntryKey": "Ключ для ручного введення", + "verificationCode": "Код підтвердження", + "verificationCodePlaceholder": "Введіть 6-значний код", + "verificationCodeDescription": "Введіть 6-значний код з вашого додатку автентифікатора", + "verifyAndEnable": "Підтвердити та увімкнути", + "cancel": "Скасувати" + }, + "disable": { + "title": "Вимкнути двофакторну автентифікацію", + "description": "Введіть ваш пароль для підтвердження вимкнення двофакторної автентифікації.", + "password": "Пароль", + "passwordPlaceholder": "Введіть ваш пароль", + "confirm": "Підтвердити вимкнення", + "cancel": "Скасувати" + }, + "backupCodes": { + "title": "Резервні коди", + "description": "Збережіть ці резервні коди в безпечному місці. Ви можете використовувати їх для доступу до вашого облікового запису, якщо втратите пристрій автентифікатора.", + "warning": "Важливо:", + "warningText": "Кожен резервний код можна використати лише один раз. Тримайте їх в безпеці і не діліться ними з ніким.", + "generateNew": "Створити нові резервні коди", + "download": "Завантажити резервні коди", + "copyToClipboard": "Копіювати в буфер обміну", + "savedMessage": "Я зберіг мої резервні коди", + "available": "Доступно резервних кодів: {count}", + "instructions": [ + "• Збережіть ці коди в безпечному місці", + "• Кожен резервний код можна використати лише один раз", + "• Ви можете створити нові коди в будь-який час" + ] + }, + "verification": { + "title": "Двофакторна автентифікація", + "description": "Введіть 6-значний код з вашого додатку автентифікатора", + "backupDescription": "Введіть один з ваших резервних кодів для продовження", + "verificationCode": "Код підтвердження", + "backupCode": "Резервний код", + "verificationCodePlaceholder": "000000", + "backupCodePlaceholder": "XXXX-XXXX", + "verify": "Підтвердити", + "verifying": "Підтвердження...", + "useBackupCode": "Використати резервний код замість цього", + "useAuthenticatorCode": "Використати код автентифікатора замість цього", + "rememberDevice": "Запам'ятати цей пристрій на 30 днів", + "rememberDeviceDescription": "Вам не потрібно буде вводити коди 2FA на цьому пристрої протягом 30 днів" + }, + "trustedDevices": { + "title": "Довірені пристрої - 2FA", + "description": "Пристрої, які не потребують підтвердження 2FA", + "noDevices": "Немає довірених пристроїв", + "deviceName": "Пристрій", + "addedOn": "Додано", + "expiresOn": "Закінчується", + "remove": "Видалити", + "removeAll": "Видалити все", + "confirmRemove": "Ви впевнені, що хочете видалити цей довірений пристрій?", + "confirmRemoveAll": "Ви впевнені, що хочете видалити всі довірені пристрої?", + "deviceRemoved": "Довірений пристрій успішно видалено", + "allDevicesRemoved": "Всі довірені пристрої успішно видалено", + "loadFailed": "Не вдалося завантажити довірені пристрої", + "removeFailed": "Не вдалося видалити довірений пристрій", + "removeAllFailed": "Не вдалося видалити всі довірені пристрої", + "loading": "Завантаження довірених пристроїв...", + "noDevicesDescription": "Пристрої з'являться тут, коли ви оберете довіряти їм під час підтвердження 2FA", + "tableHeaders": { + "device": "Пристрій", + "added": "Додано", + "expires": "Закінчується", + "lastUsed": "Останнє використання", + "ipAddress": "IP адреса", + "actions": "Дії" + }, + "status": { + "never": "Ніколи", + "expired": "Закінчився" + }, + "modals": { + "removeDevice": { + "title": "Remove Trusted Device", + "added": "Added:", + "ip": "IP:" + }, + "removeAllDevices": { + "title": "Remove All Trusted Devices", + "description": "This will remove {count} trusted device{count, plural, =1 {} other {s}}. You will need to verify 2FA on all devices again." + }, + "buttons": { + "cancel": "Скасувати", + "removing": "Видалення...", + "removeDevice": "Видалити пристрій", + "removeAllDevices": "Видалити всі пристрої" + } + } + }, + "messages": { + "enabledSuccess": "Двофакторна автентифікація успішно увімкнена!", + "disabledSuccess": "Двофакторна автентифікація успішно вимкнена", + "backupCodesGenerated": "Нові резервні коди успішно створено", + "backupCodesCopied": "Резервні коди скопійовано в буфер обміну", + "setupFailed": "Не вдалося створити налаштування 2FA", + "verificationFailed": "Неправильний код підтвердження", + "disableFailed": "Не вдалося вимкнути 2FA. Будь ласка, перевірте ваш пароль.", + "backupCodesFailed": "Не вдалося створити резервні коди", + "backupCodesCopyFailed": "Не вдалося скопіювати резервні коди", + "statusLoadFailed": "Не вдалося завантажити статус 2FA", + "enterVerificationCode": "Будь ласка, введіть код підтвердження", + "enterPassword": "Будь ласка, введіть ваш пароль", + "deviceTrusted": "Цей пристрій позначено як довірений на 30 днів" + }, + "errors": { + "invalidVerificationCode": "Неправильний код підтвердження", + "invalidTwoFactorCode": "Неправильний код двофакторної автентифікації", + "twoFactorRequired": "Потрібна двофакторна автентифікація", + "twoFactorAlreadyEnabled": "Двофакторна автентифікація вже увімкнена", + "twoFactorNotEnabled": "Двофакторна автентифікація не увімкнена", + "passwordVerificationRequired": "Потрібне підтвердження пароля", + "invalidPassword": "Неправильний пароль", + "userNotFound": "Користувача не знайдено" + }, + "deviceNames": { + "unknownDevice": "Невідомий пристрій", + "browsers": { + "chrome": "Chrome", + "firefox": "Firefox", + "safari": "Safari", + "edge": "Edge" + }, + "platforms": { + "windows": " на Windows", + "macos": " на macOS", + "linux": " на Linux", + "iphone": " на iPhone", + "android": " на Android" + } + } + }, + "uploadFile": { + "title": "Завантажити файл", + "multipleTitle": "Завантажити файли", + "selectFile": "Клікніть, щоб вибрати файл", + "selectMultipleFiles": "Клікніть, щоб вибрати один або кілька файлів", + "dragAndDrop": "або перетягніть файли сюди", + "filesQueued": "{count, plural, =1 {# файл у черзі на завантаження} few {# файли у черзі на завантаження} many {# файлів у черзі на завантаження} other {# файлів у черзі на завантаження}}", + "preview": "Попередній перегляд", + "uploadProgress": "Прогрес завантаження", + "upload": "Завантажити", + "startUploads": "Почати завантаження", + "retry": "Повторити", + "finish": "Завершити", + "success": "Файл успішно завантажено", + "allSuccess": "{count, plural, =1 {Файл успішно завантажено} few {# файли успішно завантажено} many {# файлів успішно завантажено} other {# файлів успішно завантажено}}", + "partialSuccess": "{success} файлів завантажено успішно, {error} не вдалося", + "error": "Не вдалося завантажити файл", + "fileSizeExceeded": "Розмір файлу перевищує ліміт {maxsizemb}МБ.", + "insufficientStorage": "Недостатньо місця для зберігання. У вас доступно {availablespace}МБ.", + "unauthorized": "Неавторизовано: для доступу до цього ресурсу потрібний дійсний токен.", + "globalDrop": { + "title": "Відпустіть файли для завантаження", + "description": "Відпустіть, щоб завантажити ваші файли" + }, + "confirmCancel": { + "title": "Скасувати завантаження", + "messageSingle": "Триває одне завантаження.", + "messageMultiple": "Триває {count} завантажень.", + "warning": "Якщо ви закриєте зараз, завантаження будуть скасовані і весь прогрес буде втрачено.", + "continue": "Продовжити завантаження", + "cancel": "Скасувати завантаження" + }, + "pasteSuccess": "{count, plural, =1 {Зображення вставлено і завантажено успішно} few {# зображення вставлено і завантажено успішно} many {# зображень вставлено і завантажено успішно} other {# зображень вставлено і завантажено успішно}}" + }, + "users": { + "modes": { + "create": "створити", + "edit": "редагувати" + }, + "errors": { + "loadFailed": "Не вдалося завантажити користувачів", + "submitFailed": "Не вдалося {mode} користувача", + "deleteFailed": "Не вдалося видалити користувача", + "statusUpdateFailed": "Не вдалося оновити статус користувача" + }, + "messages": { + "createSuccess": "Користувача успішно створено", + "updateSuccess": "Користувача успішно оновлено", + "deleteSuccess": "Користувача успішно видалено", + "activateSuccess": "Користувача успішно активовано", + "deactivateSuccess": "Користувача успішно деактивовано" + }, + "actions": { + "edit": "Редагувати", + "activate": "Активувати", + "deactivate": "Деактивувати", + "delete": "Видалити" + }, + "delete": { + "title": "Підтвердити видалення користувача", + "confirmation": "Ви впевнені, що хочете видалити користувача {firstName} {lastName}? Цю дію неможливо скасувати.", + "confirm": "Видалити користувача" + }, + "form": { + "titleCreate": "Додати нового користувача", + "titleEdit": "Редагувати користувача", + "firstName": "Ім'я", + "lastName": "Прізвище", + "username": "Ім'я користувача", + "email": "Електронна пошта", + "password": "Пароль", + "newPassword": "Новий пароль (необов'язково)", + "passwordPlaceholder": "Залиште порожнім, щоб зберегти поточний пароль", + "role": "Роль", + "roleUser": "Користувач", + "roleAdmin": "Адміністратор", + "create": "Створити", + "save": "Зберегти" + }, + "status": { + "title": "Підтвердити зміну статусу", + "confirmation": "Ви впевнені, що хочете {action} користувача {firstName} {lastName}?", + "activate": "Активувати", + "deactivate": "Деактивувати", + "user": "Користувач" + }, + "header": { + "title": "Керування користувачами", + "addUser": "Додати користувача", + "management": "Керування користувачами" + }, + "table": { + "user": "КОРИСТУВАЧ", + "email": "ЕЛЕКТРОННА ПОШТА", + "status": "СТАТУС", + "role": "РОЛЬ", + "actions": "ДІЇ", + "active": "Активний", + "inactive": "Неактивний", + "admin": "Адміністратор", + "userr": "Користувач" + }, + "invite": { + "button": "Створити посилання для запрошення", + "title": "Створити посилання для запрошення користувача", + "description": "Створіть одноразове посилання, яке дозволяє комусь створити власний обліковий запис. Посилання закінчується через 15 хвилин.", + "generating": "Створення...", + "generate": "Створити посилання", + "generated": "Посилання для запрошення створено успішно!", + "linkReady": "Посилання для запрошення готове", + "linkReadyDescription": "Поділіться цим посиланням з особою, яку хочете запросити. Вони зможуть створити власний обліковий запис як звичайний користувач.", + "copyLink": "Копіювати посилання", + "linkCopied": "Посилання для запрошення скопійовано в буфер обміну!", + "expiresIn": "Закінчується через 15 хвилин", + "close": "Закрити", + "errors": { + "generateFailed": "Не вдалося створити посилання для запрошення" + } + } + }, + "embedCode": { + "title": "Вбудувати медіа", + "description": "Використовуйте ці коди для вбудовування цього медіа на форумах, веб-сайтах або інших платформах", + "tabs": { + "directLink": "Пряме посилання", + "html": "HTML", + "bbcode": "BBCode" + }, + "directLinkDescription": "Прямий URL до медіа файлу", + "htmlDescription": "Використовуйте цей код для вбудовування медіа в HTML сторінки", + "bbcodeDescription": "Використовуйте цей код для вбудовування медіа на форумах, які підтримують BBCode" + }, + "validation": { + "firstNameRequired": "Ім'я обов'язкове", + "lastNameRequired": "Прізвище обов'язкове", + "usernameLength": "Ім'я користувача повинно містити принаймні 3 символи", + "usernameSpaces": "Ім'я користувача не може містити пробіли", + "invalidEmail": "Будь ласка, введіть дійсну адресу електронної пошти", + "passwordLength": "Пароль повинен містити принаймні 8 символів", + "passwordsMatch": "Паролі повинні збігатися", + "emailRequired": "Електронна пошта обов'язкова", + "emailOrUsernameRequired": "Електронна пошта або ім'я користувача обов'язкові", + "passwordRequired": "Пароль обов'язковий", + "passwordMinLength": "Пароль повинен містити принаймні 6 символів", + "nameRequired": "Назва обов'язкова", + "required": "Це поле обов'язкове" + }, + "registerWithInvite": { + "title": "Створіть свій обліковий запис", + "description": "Заповніть інформацію нижче, щоб створити свій обліковий запис", + "labels": { + "firstName": "Ім'я", + "firstNamePlaceholder": "Введіть ваше ім'я", + "lastName": "Прізвище", + "lastNamePlaceholder": "Введіть ваше прізвище", + "username": "Ім'я користувача", + "usernamePlaceholder": "Виберіть ім'я користувача", + "email": "Електронна пошта", + "emailPlaceholder": "Введіть вашу електронну пошту", + "password": "Пароль", + "passwordPlaceholder": "Виберіть пароль", + "confirmPassword": "Підтвердити пароль", + "confirmPasswordPlaceholder": "Підтвердіть ваш пароль" + }, + "buttons": { + "creating": "Створення облікового запису...", + "createAccount": "Створити обліковий запис" + }, + "validation": { + "firstNameRequired": "Ім'я обов'язкове", + "lastNameRequired": "Прізвище обов'язкове", + "usernameMinLength": "Ім'я користувача має бути щонайменше 3 символи", + "invalidEmail": "Неправильна електронна пошта", + "passwordMinLength": "Пароль має бути щонайменше 8 символів", + "passwordsMatch": "Паролі повинні збігатися" + }, + "messages": { + "success": "Обліковий запис створено успішно! Перенаправлення на вхід...", + "redirecting": "Перенаправлення на вхід..." + }, + "errors": { + "invalidToken": "Помилка з посиланням для запрошення", + "tokenUsed": "Це посилання для запрошення вже використано", + "tokenExpired": "Термін дії цього посилання для запрошення закінчився", + "usernameExists": "Ім'я користувача вже існує", + "emailExists": "Електронна пошта вже існує", + "createFailed": "Не вдалося створити обліковий запис. Спробуйте ще раз." + }, + "pageTitle": "Створити обліковий запис" + } +} \ No newline at end of file diff --git a/apps/web/messages/vi-VN.json b/apps/web/messages/vi-VN.json new file mode 100644 index 00000000..4ddfcf76 --- /dev/null +++ b/apps/web/messages/vi-VN.json @@ -0,0 +1,2001 @@ +{ + "auth": { + "successfullyAuthenticated": "Đã xác thực thành công!", + "authenticationFailed": "Xác thực không thành công", + "errors": { + "account_inactive": "Tài khoản không hoạt động. Vui lòng liên hệ với quản trị viên.", + "registration_disabled": "Đăng ký SSO đã bị tắt.", + "token_expired": "Mã thông báo đã hết hạn. Vui lòng thử lại.", + "config_error": "Lỗi cấu hình. Vui lòng liên hệ với bộ phận hỗ trợ.", + "auth_failed": "Xác thực không thành công. Vui lòng thử lại." + } + }, + "contextMenu": { + "newFolder": "Thư mục mới", + "uploadFile": "Tải lên tệp" + }, + "authProviders": { + "title": "Nhà cung cấp xác thực", + "description": "Định cấu hình nhà cung cấp xác thực bên ngoài cho SSO", + "enabledCount": "{count} đã bật", + "loadingProviders": "Đang tải nhà cung cấp...", + "providersConfigured": "{count} nhà cung cấp đã được định cấu hình", + "enabledOfTotal": "{enabled} đã bật trong tổng số {total} nhà cung cấp", + "hideDisabledProviders": "Ẩn các nhà cung cấp đã tắt", + "addProvider": "Thêm nhà cung cấp", + "addProviderTitle": "Thêm nhà cung cấp", + "editProvider": "Chỉnh sửa nhà cung cấp", + "deleteProvider": "Xóa nhà cung cấp", + "enabled": "Đã bật", + "disabled": "Đã tắt", + "officialProvider": "Nhà cung cấp chính thức", + "dragToReorder": "Kéo để sắp xếp lại", + "dragDisabledMessage": "Kéo và thả bị tắt khi lọc nhà cung cấp. Hiển thị tất cả các nhà cung cấp để sắp xếp lại chúng.", + "dragEnabledMessage": "Kéo nhà cung cấp để sắp xếp lại chúng. Thứ tự này sẽ được phản ánh trên trang đăng nhập.", + "noProvidersEnabled": "Không có nhà cung cấp xác thực nào được bật", + "noProvidersConfigured": "Chưa có nhà cung cấp xác thực nào được định cấu hình", + "form": { + "providerName": "Tên nhà cung cấp", + "providerNamePlaceholder": "ví dụ: mycompany", + "displayName": "Tên hiển thị", + "displayNamePlaceholder": "ví dụ: My Company SSO", + "type": "Loại", + "typeOidc": "OIDC (OpenID Connect)", + "typeOauth2": "OAuth 2.0", + "icon": "Biểu tượng", + "iconPlaceholder": "Chọn một biểu tượng", + "clientId": "ID khách hàng", + "clientIdPlaceholder": "ID khách hàng OAuth của bạn", + "clientSecret": "Bí mật khách hàng", + "clientSecretPlaceholder": "Bí mật khách hàng OAuth của bạn", + "oauthScopes": "Phạm vi OAuth", + "scopesPlaceholder": "Nhập phạm vi (ví dụ: openid, profile, email)", + "scopesHelpOidc": "Phạm vi được đề xuất tự động dựa trên URL của nhà cung cấp. Các phạm vi OIDC phổ biến: openid, profile, email, groups", + "scopesHelpOauth2": "Phạm vi được đề xuất tự động dựa trên URL của nhà cung cấp. Các phạm vi OAuth2 phổ biến phụ thuộc vào nhà cung cấp", + "providerUrl": "URL nhà cung cấp", + "providerUrlPlaceholder": "https://auth.example.com", + "providerUrlAutoPlaceholder": "https://your-provider.com (các điểm cuối sẽ được tự động phát hiện)", + "providerUrlManualPlaceholder": "https://your-provider.com", + "autoDiscoveryHelp": "Hệ thống sẽ tự động phát hiện các điểm cuối ủy quyền, mã thông báo và thông tin người dùng", + "manualConfigurationHelp": "URL cơ sở của nhà cung cấp của bạn (các điểm cuối sẽ tương đối với URL này)", + "authorizationEndpoint": "Điểm cuối ủy quyền", + "authorizationEndpointPlaceholder": "https://auth.example.com/auth", + "tokenEndpoint": "Điểm cuối mã thông báo", + "tokenEndpointPlaceholder": "https://auth.example.com/token", + "userInfoEndpoint": "Điểm cuối thông tin người dùng", + "userInfoEndpointPlaceholder": "https://auth.example.com/userinfo", + "configurationMethod": "Phương pháp cấu hình", + "autoDiscovery": "Tự động phát hiện", + "autoDiscoveryDescription": "Tự động phát hiện các điểm cuối từ URL của nhà cung cấp", + "manualEndpoints": "Điểm cuối thủ công (Được khuyến nghị)", + "manualEndpointsDescription": "Định cấu hình thủ công các điểm cuối ủy quyền, mã thông báo và thông tin người dùng", + "callbackUrl": "URL gọi lại", + "callbackUrlDescription": "Sử dụng URL này trong cấu hình nhà cung cấp OAuth của bạn", + "copyCallbackUrl": "Sao chép URL gọi lại", + "callbackUrlCopied": "URL gọi lại đã được sao chép vào khay nhớ tạm!", + "adminEmailDomains": "Miền email quản trị", + "adminEmailDomainsPlaceholder": "Nhập miền (ví dụ: admin.company.com)", + "adminEmailDomainsHelp": "Người dùng có email từ các miền này sẽ được cấp đặc quyền quản trị", + "autoRegister": "Tự động đăng ký người dùng mới", + "officialProviderUrlPlaceholder": "Thay thế trình giữ chỗ bằng URL {displayName} của bạn", + "officialProviderHelp": "Đây là nhà cung cấp chính thức. Các điểm cuối đã được định cấu hình sẵn. Bạn chỉ có thể chỉnh sửa URL này.", + "officialProviderIconHelp": "Bạn có thể tùy chỉnh biểu tượng cho nhà cung cấp chính thức này." + }, + "buttons": { + "cancel": "Hủy", + "save": "Lưu", + "saving": "Đang lưu...", + "adding": "Đang thêm...", + "updating": "Đang cập nhật...", + "saveProvider": "Lưu nhà cung cấp", + "delete": "Xóa", + "deleting": "Đang xóa...", + "edit": "Chỉnh sửa", + "enable": "Bật", + "disable": "Tắt" + }, + "messages": { + "providerAdded": "Đã thêm nhà cung cấp thành công", + "providerUpdated": "Đã cập nhật nhà cung cấp thành công", + "providerDeleted": "Đã xóa nhà cung cấp thành công", + "providerOrderUpdated": "Đã cập nhật thứ tự nhà cung cấp thành công", + "fillRequiredFields": "Vui lòng điền vào tất cả các trường bắt buộc (tên, tên hiển thị, ID khách hàng, bí mật khách hàng)", + "provideUrlOrEndpoints": "Cung cấp URL nhà cung cấp để tự động phát hiện HOẶC cả ba điểm cuối tùy chỉnh", + "chooseDiscoveryOrManual": "Chọn tự động phát hiện (URL nhà cung cấp) HOẶC điểm cuối thủ công, không phải cả hai", + "loadFailed": "Không thể tải nhà cung cấp", + "addFailed": "Không thể thêm nhà cung cấp", + "updateFailed": "Không thể cập nhật nhà cung cấp", + "deleteFailed": "Không thể xóa nhà cung cấp", + "orderUpdateFailed": "Không thể cập nhật thứ tự nhà cung cấp" + }, + "info": { + "title": "Thông tin", + "officialProvidersRecommended": "Để có chức năng tốt hơn, hãy cân nhắc sử dụng các nhà cung cấp chính thức. Nếu bạn gặp sự cố với nhà cung cấp tùy chỉnh, hãy cân nhắc mở một vấn đề trên", + "github": "GitHub", + "officialProvider": "Nhà cung cấp chính thức", + "officialProviderDescription": "Nhà cung cấp này được tối ưu hóa bởi Palmr. Chỉ có thể sửa đổi thông tin đăng nhập và cấu hình.", + "manualConfigTitle": "Cấu hình thủ công", + "manualConfigDescription": "Bạn đang cung cấp tất cả các điểm cuối theo cách thủ công. Đảm bảo chúng chính xác cho nhà cung cấp của bạn." + }, + "deleteModal": { + "title": "Xóa nhà cung cấp xác thực", + "confirmMessage": "Bạn có chắc chắn muốn xóa nhà cung cấp \"{displayName}\"? Hành động này không thể hoàn tác.", + "providerId": "ID nhà cung cấp: {name}", + "cancel": "Hủy", + "delete": "Xóa nhà cung cấp", + "deleting": "Đang xóa..." + } + }, + "bulkDownload": { + "title": "Tải xuống hàng loạt", + "zipNameLabel": "Tên tệp ZIP", + "zipNamePlaceholder": "Nhập tên tệp", + "description": "{count, plural, =1 {1 tệp sẽ được nén} other {# tệp sẽ được nén}}", + "download": "Tải xuống ZIP" + }, + "common": { + "loading": "Đang tải, vui lòng đợi...", + "loadingSimple": "Đang tải...", + "cancel": "Hủy", + "save": "Lưu", + "saving": "Đang lưu...", + "update": "Cập nhật", + "updating": "Đang cập nhật...", + "delete": "Xóa", + "deleting": "Đang xóa...", + "close": "Đóng", + "download": "Tải xuống", + "unexpectedError": "Đã xảy ra lỗi không mong muốn. Vui lòng thử lại.", + "yes": "Có", + "no": "Không", + "dashboard": "Bảng điều khiển", + "back": "Quay lại", + "click": "Nhấp để", + "creating": "Đang tạo...", + "create": "Tạo", + "rename": "Đổi tên", + "move": "Di chuyển", + "share": "Chia sẻ", + "search": "Tìm kiếm", + "copy": "Sao chép", + "copied": "Đã sao chép" + }, + "createShare": { + "title": "Tạo chia sẻ", + "nameLabel": "Tên chia sẻ", + "namePlaceholder": "Nhập tên cho chia sẻ của bạn", + "descriptionLabel": "Mô tả", + "descriptionPlaceholder": "Nhập mô tả (tùy chọn)", + "expirationLabel": "Ngày hết hạn", + "expirationPlaceholder": "MM/DD/YYYY HH:MM", + "maxViewsLabel": "Lượt xem tối đa", + "maxViewsPlaceholder": "Để trống để không giới hạn", + "passwordProtection": "Được bảo vệ bằng mật khẩu", + "passwordLabel": "Mật khẩu", + "passwordPlaceholder": "Nhập mật khẩu", + "create": "Tạo chia sẻ", + "success": "Đã tạo chia sẻ thành công", + "error": "Không thể tạo chia sẻ", + "errors": { + "nameRequired": "Tên chia sẻ là bắt buộc", + "selectItems": "Vui lòng chọn ít nhất một tệp hoặc thư mục" + }, + "itemsSelected": "{count} mục đã chọn", + "selectItemsPrompt": "Chọn tệp và thư mục để chia sẻ", + "tabs": { + "shareDetails": "Chi tiết chia sẻ", + "selectFiles": "Chọn tệp" + }, + "nextSelectFiles": "Tiếp theo: Chọn tệp", + "searchLabel": "Tìm kiếm" + }, + "customization": { + "breadcrumb": "Tùy chỉnh", + "colors": { + "title": "Màu chủ đề", + "description": "Chọn chủ đề màu chính ưa thích của bạn", + "presets": "Màu có sẵn", + "presetsDescription": "Chọn từ các chủ đề màu có sẵn", + "reset": "Đặt lại về mặc định" + }, + "fonts": { + "title": "Kiểu chữ", + "description": "Chọn họ phông chữ ưa thích của bạn", + "available": "Phông chữ có sẵn", + "availableDescription": "Chọn từ các họ phông chữ có sẵn", + "reset": "Đặt lại về mặc định" + }, + "radius": { + "title": "Bán kính đường viền", + "description": "Tùy chỉnh độ tròn của các phần tử giao diện", + "available": "Tùy chọn độ tròn", + "availableDescription": "Chọn cách các góc tròn sẽ xuất hiện", + "reset": "Đặt lại về mặc định" + }, + "background": { + "title": "Màu nền", + "description": "Tùy chỉnh màu nền cho chế độ sáng và tối", + "lightMode": "Chế độ sáng", + "darkMode": "Chế độ tối", + "availableDescription": "Chọn màu nền cho cả chủ đề sáng và tối", + "reset": "Đặt lại về mặc định" + }, + "theme": { + "title": "Chế độ chủ đề", + "description": "Chọn giữa chủ đề sáng, tối hoặc hệ thống", + "selectTheme": "Tùy chọn chủ đề", + "availableDescription": "Chọn chế độ chủ đề ưa thích của bạn", + "reset": "Đặt lại về hệ thống" + }, + "pageTitle": "Tùy chỉnh" + }, + "dashboard": { + "loadError": "Không thể tải dữ liệu bảng điều khiển", + "linkCopied": "Đã sao chép liên kết vào khay nhớ tạm", + "pageTitle": "Bảng điều khiển", + "breadcrumb": "Bảng điều khiển", + "recentFiles": { + "title": "Tệp gần đây", + "description": "Các tệp được tải lên gần đây nhất của bạn" + } + }, + "deleteConfirmation": { + "filesToDelete": "Các tệp sẽ bị xóa", + "foldersToDelete": "Các thư mục sẽ bị xóa", + "itemsToDelete": "Các mục sẽ bị xóa", + "sharesToDelete": "Các chia sẻ sẽ bị xóa" + }, + "downloadQueue": { + "downloadQueued": "Đã xếp hàng tải xuống: {fileName}", + "queuedDescription": "Quá trình tải xuống của bạn sẽ tự động bắt đầu khi có một vị trí trống", + "queuePosition": "Đã xếp hàng tải xuống ở vị trí {position}: {fileName}", + "estimatedWait": "Thời gian chờ ước tính: {time}", + "queueFull": "Hàng đợi tải xuống đã đầy", + "queueFullDescription": "Vui lòng thử lại sau vài phút khi hàng đợi có dung lượng trống", + "cancelSuccess": "Đã hủy tải xuống thành công", + "cancelError": "Không thể hủy tải xuống: {error}", + "status": { + "pending": "Đang chuẩn bị...", + "queued": "Trong hàng đợi", + "downloading": "Đang tải xuống", + "completed": "Đã hoàn tất", + "failed": "Không thành công" + }, + "waitTime": { + "seconds": "{seconds} giây", + "minutes": "{minutes} phút", + "hoursMinutes": "{hours} giờ {minutes} phút" + }, + "indicator": { + "title": "Tải xuống", + "downloads": "Hàng đợi tải xuống", + "active": "Đang hoạt động", + "queued": "Đã xếp hàng", + "position": "Vị trí {position}", + "estimatedWait": "Chờ: {time}", + "unknownFile": "Tệp không xác định", + "noDownloads": "Không có bản tải xuống nào đang diễn ra", + "refresh": "Làm mới hàng đợi" + } + }, + "emptyState": { + "noFiles": "Chưa có tệp nào được tải lên", + "uploadFile": "Tải lên tệp" + }, + "errors": { + "invalidCredentials": "Email hoặc mật khẩu không hợp lệ", + "userNotFound": "Không tìm thấy người dùng", + "accountLocked": "Tài khoản bị khóa. Vui lòng thử lại sau", + "unexpectedError": "Đã xảy ra lỗi không mong muốn. Vui lòng thử lại", + "Invalid verification code": "Mã xác minh không hợp lệ", + "Two-factor authentication is already enabled": "Xác thực hai yếu tố đã được bật", + "Two-factor authentication is not enabled": "Xác thực hai yếu tố chưa được bật", + "Invalid password": "Mật khẩu không hợp lệ", + "Password verification required": "Yêu cầu xác minh mật khẩu", + "Invalid two-factor authentication code": "Mã xác thực hai yếu tố không hợp lệ", + "Two-factor authentication required": "Yêu cầu xác thực hai yếu tố", + "noUserData": "Không có dữ liệu người dùng" + }, + "fileActions": { + "editFile": "Chỉnh sửa tệp", + "nameLabel": "Tên", + "namePlaceholder": "Nhập tên mới", + "extension": "Phần mở rộng", + "descriptionLabel": "Mô tả", + "descriptionPlaceholder": "Nhập mô tả tệp", + "addDescriptionPlaceholder": "Thêm mô tả...", + "deleteFile": "Xóa tệp", + "deleteConfirmation": "Bạn có chắc chắn muốn xóa tệp này không?", + "deleteWarning": "Hành động này không thể hoàn tác." + }, + "fileManager": { + "downloadError": "Không thể tải xuống tệp", + "updateSuccess": "Đã cập nhật tệp thành công", + "updateError": "Không thể cập nhật tệp", + "deleteSuccess": "Đã xóa tệp thành công", + "deleteError": "Không thể xóa tệp" + }, + "filePreview": { + "title": "Xem trước tệp", + "description": "Xem trước và tải xuống tệp", + "loading": "Đang tải...", + "notAvailable": "Không có bản xem trước cho loại tệp này", + "downloadToView": "Sử dụng nút tải xuống để xem tệp này", + "loadError": "Lỗi khi tải bản xem trước tệp", + "downloadError": "Lỗi khi tải xuống tệp", + "audioNotSupported": "Trình duyệt của bạn không hỗ trợ phát lại âm thanh", + "videoNotSupported": "Trình duyệt của bạn không hỗ trợ phát lại video", + "pdfPreviewNotAvailable": "Không có bản xem trước PDF. Hãy thử chế độ xem thay thế hoặc tải xuống", + "tryAlternativeView": "Thử chế độ xem thay thế", + "loadingAlternative": "Đang tải chế độ xem thay thế...", + "loadingAudio": "Đang tải âm thanh..." + }, + "fileSelector": { + "availableFiles": "Tệp có sẵn ({count})", + "shareFiles": "Tệp được chia sẻ ({count})", + "shareFilesDescription": "Các tệp hiện có trong chia sẻ này", + "availableFilesDescription": "Chọn tệp để thêm vào chia sẻ này", + "searchPlaceholder": "Tìm kiếm tệp...", + "searchSelectedFiles": "Tìm kiếm các tệp đã chọn...", + "noMatchingFiles": "Không tìm thấy tệp phù hợp", + "noAvailableFiles": "Không có tệp nào", + "noFilesInShare": "Không có tệp nào trong chia sẻ này", + "noFilesFound": "Không tìm thấy tệp nào", + "noFilesFoundWith": "Không tìm thấy tệp nào khớp với \"{query}\"", + "addFilesFromList": "Thêm tệp từ danh sách bên dưới", + "tryDifferentSearch": "Thử các cụm từ tìm kiếm khác", + "allFilesInShare": "Tất cả các tệp đã có trong chia sẻ này", + "uploadNewFiles": "Tải lên tệp mới để thêm chúng", + "fileCount": "{count, plural, =1 {tệp} other {tệp}}", + "filesSelected": "{count, plural, =0 {Không có tệp nào được chọn} =1 {1 tệp đã được chọn} other {# tệp đã được chọn}}", + "itemsSelected": "{count} mục đã chọn", + "editFile": "Chỉnh sửa tệp", + "editFolder": "Chỉnh sửa thư mục", + "previewFile": "Xem trước tệp", + "addToShare": "Thêm vào chia sẻ", + "removeFromShare": "Xóa khỏi chia sẻ", + "saveChanges": "Lưu thay đổi" + }, + "files": { + "title": "Tất cả các tệp", + "uploadFile": "Tải lên tệp", + "loadError": "Không thể tải tệp", + "pageTitle": "Tệp của tôi", + "breadcrumb": "Tệp của tôi", + "downloadStart": "Đã bắt đầu tải xuống", + "downloadError": "Không thể tải xuống tệp", + "updateSuccess": "Đã cập nhật tệp thành công", + "updateError": "Không thể cập nhật tệp", + "deleteSuccess": "Đã xóa tệp thành công", + "deleteError": "Không thể xóa tệp", + "bulkDownloadSuccess": "Đã bắt đầu tải xuống tệp thành công", + "bulkDownloadError": "Lỗi khi tạo tệp ZIP", + "bulkDownloadFileError": "Lỗi khi tải xuống tệp {fileName}", + "bulkDeleteSuccess": "{count, plural, =1 {1 mục đã được xóa thành công} other {# mục đã được xóa thành công}}", + "bulkDeleteError": "Lỗi khi xóa các mục đã chọn", + "bulkDeleteTitle": "Xóa các mục đã chọn", + "bulkDeleteConfirmation": "Bạn có chắc chắn muốn xóa {count, plural, =1 {1 mục} other {# mục}} không? Hành động này không thể hoàn tác.", + "totalFiles": "{count, plural, =0 {Không có tệp nào} =1 {1 tệp} other {# tệp}}", + "openFolder": "Mở thư mục", + "errors": { + "moveItemsFailed": "Không thể di chuyển các mục. Vui lòng thử lại.", + "cannotMoveHere": "Không thể di chuyển các mục đến vị trí này" + }, + "empty": { + "title": "Chưa có tệp hoặc thư mục nào", + "description": "Tải lên tệp đầu tiên của bạn hoặc tạo một thư mục để bắt đầu" + }, + "files": "tệp", + "folders": "thư mục", + "actions": { + "open": "Mở", + "rename": "Đổi tên", + "delete": "Xóa" + }, + "viewMode": { + "table": "Bảng", + "grid": "Lưới" + } + }, + "filesTable": { + "ariaLabel": "Bảng tệp", + "selectAll": "Chọn tất cả", + "selectFile": "Chọn tệp {fileName}", + "columns": { + "name": "TÊN", + "description": "MÔ TẢ", + "size": "KÍCH THƯỚC", + "createdAt": "NGÀY TẠO", + "updatedAt": "NGÀY CẬP NHẬT", + "actions": "HÀNH ĐỘNG" + }, + "actions": { + "menu": "Menu hành động tệp", + "preview": "Xem trước", + "edit": "Chỉnh sửa", + "share": "Chia sẻ", + "download": "Tải xuống", + "delete": "Xóa" + }, + "bulkActions": { + "selected": "{count, plural, =1 {1 tệp đã được chọn} other {# tệp đã được chọn}}", + "actions": "Hành động", + "download": "Tải xuống các mục đã chọn", + "share": "Chia sẻ các mục đã chọn", + "delete": "Xóa các mục đã chọn" + } + }, + "folderActions": { + "editFolder": "Chỉnh sửa thư mục", + "folderName": "Tên thư mục", + "folderNamePlaceholder": "Nhập tên thư mục", + "folderDescription": "Mô tả", + "folderDescriptionPlaceholder": "Nhập mô tả thư mục (tùy chọn)", + "createFolder": "Tạo thư mục", + "renameFolder": "Đổi tên thư mục", + "moveFolder": "Di chuyển thư mục", + "shareFolder": "Chia sẻ thư mục", + "deleteFolder": "Xóa thư mục", + "moveTo": "Di chuyển đến", + "selectDestination": "Chọn thư mục đích", + "rootFolder": "Gốc", + "folderCreated": "Đã tạo thư mục thành công", + "folderRenamed": "Đã đổi tên thư mục thành công", + "folderMoved": "Đã di chuyển thư mục thành công", + "folderDeleted": "Đã xóa thư mục thành công", + "folderShared": "Đã chia sẻ thư mục thành công", + "createFolderError": "Lỗi khi tạo thư mục", + "renameFolderError": "Lỗi khi đổi tên thư mục", + "moveFolderError": "Lỗi khi di chuyển thư mục", + "deleteFolderError": "Lỗi khi xóa thư mục", + "shareFolderError": "Lỗi khi chia sẻ thư mục", + "deleteConfirmation": "Bạn có chắc chắn muốn xóa thư mục này không?", + "deleteWarning": "Hành động này không thể hoàn tác." + }, + "footer": { + "poweredBy": "Cung cấp bởi", + "kyanHomepage": "Trang chủ Kyantech" + }, + "forgotPassword": { + "emailLabel": "Địa chỉ email", + "emailPlaceholder": "Nhập email của bạn", + "sending": "Đang gửi...", + "submit": "Gửi hướng dẫn đặt lại", + "backToLogin": "Quay lại đăng nhập", + "title": "Quên mật khẩu", + "description": "Nhập địa chỉ email của bạn và chúng tôi sẽ gửi cho bạn hướng dẫn để đặt lại mật khẩu của bạn", + "resetInstructions": "Hướng dẫn đặt lại đã được gửi đến email của bạn", + "pageTitle": "Quên mật khẩu", + "passwordAuthDisabled": "Xác thực bằng mật khẩu đã bị tắt. Vui lòng liên hệ với quản trị viên của bạn hoặc sử dụng nhà cung cấp xác thực bên ngoài." + }, + "generateShareLink": { + "generateTitle": "Tạo liên kết chia sẻ", + "updateTitle": "Cập nhật liên kết chia sẻ", + "generateDescription": "Tạo một liên kết tùy chỉnh cho chia sẻ này. Bạn có thể tùy chỉnh URL để dễ nhớ hơn.", + "updateDescription": "Cập nhật liên kết tùy chỉnh cho chia sẻ này. Bạn có thể tùy chỉnh URL để dễ nhớ hơn.", + "aliasPlaceholder": "ID tùy chỉnh cho liên kết", + "linkReady": "Liên kết chia sẻ của bạn đã sẵn sàng. Bạn có thể sao chép nó ngay bây giờ.", + "readyDescription": "Liên kết chia sẻ của bạn đã sẵn sàng. Bạn có thể quét mã QR trực tiếp, tải xuống để sử dụng sau hoặc sao chép liên kết bên dưới.", + "generateButton": "Tạo liên kết", + "updateButton": "Cập nhật liên kết", + "copyButton": "Sao chép liên kết", + "success": "Đã tạo liên kết thành công", + "error": "Không thể tạo liên kết", + "copied": "Đã sao chép liên kết vào khay nhớ tạm", + "tabs": { + "link": "Liên kết", + "qrcode": "Mã QR" + } + }, + "home": { + "description": "Giải pháp thay thế mã nguồn mở cho WeTransfer. Chia sẻ tệp một cách an toàn, không theo dõi hoặc giới hạn.", + "documentation": "Tài liệu", + "starOnGithub": "Gắn sao trên GitHub", + "privacyMessage": "Được xây dựng với sự riêng tư. Các tệp của bạn chỉ có thể được truy cập bởi những người có liên kết chia sẻ trước khi tải lên. Mãi mãi miễn phí và mã nguồn mở.", + "header": { + "fileSharing": "Chia sẻ tệp", + "tagline": "đơn giản và miễn phí" + }, + "pageTitle": "Trang chủ" + }, + "iconPicker": { + "title": "Chọn biểu tượng", + "placeholder": "Chọn một biểu tượng", + "searchPlaceholder": "Tìm kiếm biểu tượng...", + "loadingMore": "Đang tải thêm biểu tượng...", + "allIconsLoaded": "Đã tải tất cả {count} biểu tượng", + "noIconsFound": "Không tìm thấy biểu tượng nào cho \"{search}\"", + "tabs": { + "all": "Tất cả các biểu tượng", + "popular": "Phổ biến", + "auth": "Nhà cung cấp xác thực" + }, + "stats": "{iconCount} biểu tượng từ {libraryCount} thư viện", + "categoryBadge": "{category} ({count} biểu tượng)" + }, + "imageEdit": { + "title": "Chỉnh sửa hình ảnh", + "rotate": "Xoay", + "zoom": "Phóng to", + "cropInstructions": "Kéo để định vị lại, thay đổi kích thước các góc để điều chỉnh vùng cắt" + }, + "login": { + "welcome": "Chào mừng đến với", + "signInToContinue": "Đăng nhập để tiếp tục", + "emailOrUsernameLabel": "Email hoặc Tên người dùng", + "emailOrUsernamePlaceholder": "Nhập email hoặc tên người dùng của bạn", + "emailLabel": "Địa chỉ email", + "emailPlaceholder": "Nhập email của bạn", + "passwordLabel": "Mật khẩu", + "passwordPlaceholder": "Nhập mật khẩu của bạn", + "signIn": "Đăng nhập", + "signingIn": "Đang đăng nhập...", + "forgotPassword": "Quên mật khẩu?", + "pageTitle": "Đăng nhập", + "or": "hoặc", + "continueWithSSO": "Tiếp tục với SSO", + "processing": "Đang xử lý xác thực..." + }, + "logo": { + "labels": { + "appLogo": "Logo ứng dụng" + }, + "buttons": { + "upload": "Tải lên logo", + "remove": "Xóa logo" + }, + "messages": { + "uploadSuccess": "Đã tải lên logo thành công", + "removeSuccess": "Đã xóa logo thành công" + }, + "errors": { + "uploadFailed": "Không thể tải lên logo", + "removeFailed": "Không thể xóa logo" + } + }, + "moveItems": { + "itemsToMove": "Các mục cần di chuyển:", + "movingTo": "Đang di chuyển đến:", + "title": "Di chuyển {count, plural, =1 {mục} other {mục}}", + "description": "Di chuyển {count, plural, =1 {mục} other {mục}} đến một vị trí mới", + "success": "Đã di chuyển thành công {count} {count, plural, =1 {mục} other {mục}}", + "errors": { + "moveFailed": "Không thể di chuyển các mục" + } + }, + "navbar": { + "logoAlt": "Logo ứng dụng", + "profileMenu": "Menu hồ sơ", + "profile": "Hồ sơ", + "customization": "Tùy chỉnh", + "settings": "Cài đặt", + "usersManagement": "Quản lý người dùng", + "logout": "Đăng xuất" + }, + "navigation": { + "dashboard": "Bảng điều khiển" + }, + "notifications": { + "permissionGranted": "Đã bật thông báo tải xuống", + "permissionDenied": "Đã tắt thông báo tải xuống", + "downloadComplete": { + "title": "Tải xuống hoàn tất", + "body": "{fileName} đã tải xuống xong" + }, + "downloadFailed": { + "title": "Tải xuống không thành công", + "body": "Không thể tải xuống {fileName}: {error}", + "unknownError": "Lỗi không xác định" + }, + "queueProcessing": { + "title": "Bắt đầu tải xuống", + "body": "{fileName} hiện đang được tải xuống{position}", + "position": " (đã ở vị trí #{position} trong hàng đợi)" + } + }, + "profile": { + "password": { + "title": "Thay đổi mật khẩu", + "newPassword": "Mật khẩu mới", + "confirmPassword": "Xác nhận mật khẩu mới", + "updateButton": "Cập nhật mật khẩu" + }, + "form": { + "title": "Thông tin hồ sơ", + "firstName": "Tên", + "lastName": "Họ", + "username": "Tên người dùng", + "email": "Email", + "updateButton": "Cập nhật hồ sơ" + }, + "header": { + "title": "Hồ sơ", + "subtitle": "Quản lý thông tin cá nhân và mật khẩu của bạn" + }, + "picture": { + "title": "Ảnh hồ sơ", + "description": "Nhấp vào biểu tượng máy ảnh để thay đổi ảnh hồ sơ của bạn", + "uploadPhoto": "Tải lên ảnh", + "removePhoto": "Xóa ảnh" + }, + "errors": { + "loadFailed": "Không thể tải dữ liệu người dùng", + "updateFailed": "Không thể cập nhật hồ sơ", + "passwordFailed": "Không thể cập nhật mật khẩu", + "imageFailed": "Không thể cập nhật ảnh hồ sơ", + "imageRemoveFailed": "Không thể xóa ảnh hồ sơ" + }, + "messages": { + "noChanges": "Không có thay đổi nào để lưu", + "updateSuccess": "Đã cập nhật hồ sơ thành công", + "fillPasswords": "Vui lòng điền vào cả hai trường mật khẩu", + "passwordSuccess": "Đã cập nhật mật khẩu thành công", + "imageSuccess": "Đã cập nhật ảnh hồ sơ thành công", + "imageRemoved": "Đã xóa ảnh hồ sơ thành công" + }, + "pageTitle": "Hồ sơ" + }, + "qrCodeModal": { + "title": "Chia sẻ mã QR", + "description": "Quét mã QR này để truy cập liên kết.", + "download": "Tải xuống mã QR" + }, + "quickAccess": { + "files": { + "title": "Tệp của tôi", + "description": "Truy cập và quản lý các tệp đã tải lên của bạn" + }, + "shares": { + "title": "Chia sẻ của tôi", + "description": "Xem và quản lý các tệp được chia sẻ của bạn" + }, + "reverseShares": { + "title": "Nhận tệp", + "description": "Tạo liên kết để người khác gửi tệp cho bạn" + } + }, + "recentFiles": { + "title": "Tải lên gần đây", + "viewAll": "Xem tất cả", + "upload": "Tải lên", + "uploadFile": "Tải lên tệp", + "noFiles": "Chưa có tệp nào được tải lên" + }, + "recentShares": { + "title": "Chia sẻ gần đây", + "viewAll": "Xem tất cả", + "createShare": "Tạo chia sẻ", + "noShares": "Chưa có chia sẻ nào được tạo", + "createFirst": "Tạo chia sẻ đầu tiên của bạn" + }, + "recipientSelector": { + "emailPlaceholder": "Nhập email người nhận", + "add": "Thêm", + "recipients": "Người nhận ({count})", + "notifyAll": "Thông báo cho tất cả", + "noRecipients": "Chưa có người nhận nào được thêm", + "addSuccess": "Đã thêm người nhận thành công", + "addError": "Không thể thêm người nhận", + "removeSuccess": "Đã xóa người nhận thành công", + "removeError": "Không thể xóa người nhận", + "sendingNotifications": "Đang gửi thông báo...", + "notifySuccess": "Đã thông báo cho người nhận thành công", + "notifyError": "Không thể thông báo cho người nhận", + "selectAll": "Chọn tất cả", + "selectedCount": "{count} đã chọn", + "selectRecipient": "Chọn {email}", + "notifySelected": "Thông báo cho các mục đã chọn", + "removeSelected": "Xóa các mục đã chọn", + "notifySingle": "Thông báo cho người nhận này", + "removeSingle": "Xóa người nhận này", + "bulkRemoveSuccess": "Đã xóa thành công {count} người nhận", + "bulkRemoveError": "Không thể xóa các người nhận đã chọn", + "bulkNotifySuccess": "Đã gửi thông báo đến {count} người nhận", + "bulkNotifyError": "Không thể thông báo cho các người nhận đã chọn", + "singleNotifySuccess": "Đã gửi thông báo đến {email}", + "singleNotifyError": "Không thể thông báo cho người nhận", + "modalDescription": "Thêm và quản lý người nhận cho chia sẻ này. Bạn có thể thông báo cho tất cả hoặc các người nhận cụ thể khi SMTP được định cấu hình.", + "addRecipient": "Thêm người nhận", + "invalidEmail": "Vui lòng nhập một địa chỉ email hợp lệ", + "duplicateEmail": "Người nhận này đã được thêm", + "noRecipientsDescription": "Thêm người nhận để chia sẻ nội dung này qua email" + }, + "register": { + "validation": { + "firstNameRequired": "Tên là bắt buộc", + "lastNameRequired": "Họ là bắt buộc", + "usernameMinLength": "Tên người dùng phải có ít nhất 3 ký tự", + "invalidEmail": "Email không hợp lệ", + "passwordMinLength": "Mật khẩu phải có ít nhất 8 ký tự", + "success": "Người dùng quản trị đã được tạo thành công!", + "error": "Lỗi khi tạo người dùng quản trị" + }, + "labels": { + "firstName": "Tên", + "lastName": "Họ", + "username": "Tên người dùng", + "email": "Email", + "password": "Mật khẩu" + }, + "buttons": { + "creating": "Đang tạo...", + "createAdmin": "Tạo tài khoản quản trị" + } + }, + "resetPassword": { + "pageTitle": "Đặt lại mật khẩu", + "header": { + "title": "Đặt lại mật khẩu", + "description": "Nhập mật khẩu mới của bạn bên dưới" + }, + "form": { + "newPassword": "Mật khẩu mới", + "newPasswordPlaceholder": "Nhập mật khẩu mới của bạn", + "confirmPassword": "Xác nhận mật khẩu mới", + "confirmPasswordPlaceholder": "Xác nhận mật khẩu mới của bạn", + "resetting": "Đang đặt lại mật khẩu...", + "submit": "Đặt lại mật khẩu", + "backToLogin": "Quay lại đăng nhập" + }, + "messages": { + "success": "Đã đặt lại mật khẩu thành công" + }, + "errors": { + "serverError": "Không thể đặt lại mật khẩu. Vui lòng thử lại.", + "invalidToken": "Mã thông báo đặt lại không hợp lệ hoặc bị thiếu" + } + }, + "reverseShares": { + "pageTitle": "Nhận tệp", + "search": { + "title": "Quản lý liên kết nhận", + "createButton": "Tạo liên kết", + "placeholder": "Tìm kiếm liên kết nhận...", + "results": "Đã tìm thấy {filtered} trong số {total} liên kết nhận" + }, + "labels": { + "files": "tệp", + "size": "kích thước", + "status": "trạng thái", + "access": "quyền truy cập", + "description": "Mô tả", + "pageLayout": "Bố cục trang", + "security": "Bảo mật & Trạng thái", + "limits": "Giới hạn", + "maxFiles": "Tệp tối đa", + "maxFileSize": "Kích thước tối đa", + "allowedTypes": "Loại được phép", + "filesReceived": "Tệp đã nhận", + "fileLimit": "Giới hạn tệp", + "noLimit": "Không giới hạn", + "noLinkCreated": "Chưa tạo liên kết", + "publicAccess": "Truy cập công khai", + "protectedByPassword": "Được bảo vệ bằng mật khẩu", + "configureProtection": "Nhấp để định cấu hình bảo vệ", + "enterPassword": "Nhập mật khẩu", + "thisLinkProtected": "Liên kết này sẽ được bảo vệ bằng mật khẩu", + "thisLinkPublic": "Liên kết này sẽ được cung cấp công khai", + "configureExpiration": "Định cấu hình hết hạn", + "configureLimits": "Định cấu hình giới hạn tệp", + "protectWithPassword": "Bảo vệ bằng mật khẩu", + "layoutOptions": { + "default": "Mặc định", + "wetransfer": "Kiểu WeTransfer" + }, + "noFilesLimit": "Không giới hạn tệp", + "noSizeLimit": "Không giới hạn kích thước", + "allFileTypes": "Tất cả các loại tệp", + "fileTypesHelp": "Nhập các phần mở rộng không có dấu chấm, được phân tách bằng dấu cách, dấu phẩy, dấu gạch ngang hoặc dấu gạch đứng", + "fieldRequirements": "Yêu cầu trường", + "nameFieldRequired": "Trường tên", + "emailFieldRequired": "Trường email", + "fieldOptions": { + "hidden": "Ẩn", + "optional": "Tùy chọn", + "required": "Bắt buộc" + } + }, + "card": { + "untitled": "Liên kết không có tiêu đề", + "noDescription": "Không có mô tả", + "addDescriptionPlaceholder": "Thêm mô tả...", + "files": "tệp", + "progress": "Tiến trình", + "created": "Đã tạo", + "expired": "Đã hết hạn", + "expires": "Hết hạn", + "viewDetails": "Xem chi tiết", + "viewQrCode": "Xem mã QR", + "copyLink": "Sao chép liên kết", + "openInNewTab": "Mở trong tab mới", + "editLink": "Chỉnh sửa liên kết", + "createLink": "Tạo liên kết", + "delete": "Xóa", + "copyLinkTitle": "Sao chép liên kết", + "createLinkCTA": "Tạo liên kết nhận" + }, + "status": { + "active": "Đang hoạt động", + "inactive": "Không hoạt động", + "expired": "Đã hết hạn", + "protected": "Được bảo vệ", + "public": "Công khai" + }, + "actions": { + "copyLink": "Sao chép liên kết", + "editAlias": "Chỉnh sửa bí danh", + "createAlias": "Tạo bí danh", + "viewDetails": "Xem chi tiết", + "edit": "Chỉnh sửa", + "delete": "Xóa", + "viewFiles": "Tệp đã nhận", + "viewQrCode": "Xem mã QR" + }, + "empty": { + "title": "Chưa tạo liên kết nhận", + "description": "Tạo liên kết tùy chỉnh để người khác gửi tệp trực tiếp cho bạn một cách an toàn và có tổ chức.", + "createButton": "Tạo liên kết đầu tiên" + }, + "modals": { + "create": { + "title": "Tạo liên kết nhận", + "description": "Định cấu hình một liên kết tùy chỉnh để nhận tệp từ người khác" + }, + "edit": { + "title": "Chỉnh sửa liên kết nhận", + "description": "Cập nhật cài đặt cho liên kết nhận này", + "updating": "Đang cập nhật...", + "saveChanges": "Lưu thay đổi" + }, + "details": { + "title": "Chi tiết liên kết", + "description": "Xem và chỉnh sửa thông tin liên kết nhận của bạn", + "pageLayout": "Bố cục trang", + "linkSection": "Liên kết nhận", + "noLinkCreated": "Chưa tạo liên kết", + "limits": "Giới hạn", + "maxFiles": "Tệp tối đa", + "maxFileSize": "Kích thước tối đa", + "allowedTypes": "Loại được phép", + "noLimit": "Không giới hạn", + "security": "Bảo mật", + "status": "Trạng thái", + "password": "Mật khẩu", + "files": "Tệp đã nhận", + "noFiles": "Chưa nhận được tệp nào", + "copyLink": "Sao chép liên kết", + "openLink": "Mở liên kết", + "editAlias": "Chỉnh sửa bí danh", + "createAlias": "Tạo bí danh", + "editPassword": "Chỉnh sửa bảo vệ bằng mật khẩu", + "basicInfo": "Thông tin cơ bản", + "securityAndStatus": "Bảo mật & Trạng thái", + "protection": "Bảo vệ", + "protectedByPassword": "Được bảo vệ bằng mật khẩu", + "publicAccess": "Truy cập công khai", + "active": "Đang hoạt động", + "inactive": "Không hoạt động", + "deactivate": "Hủy kích hoạt", + "activate": "Kích hoạt", + "expiration": "Hết hạn", + "dates": "Ngày", + "createdAt": "Được tạo vào", + "updatedAt": "Được cập nhật vào", + "allTypes": "Tất cả các loại", + "placeholderTypes": ".pdf,.jpg,.png (phân tách bằng dấu phẩy)", + "downloadSuccess": "Đã bắt đầu tải xuống", + "downloadError": "Lỗi khi tải xuống tệp", + "editSuccess": "Đã cập nhật tệp thành công", + "editError": "Lỗi khi cập nhật tệp", + "previewNotAvailable": "Không có bản xem trước", + "notAvailable": "Không có sẵn", + "invalidDate": "Ngày không hợp lệ" + }, + "alias": { + "editTitle": "Chỉnh sửa bí danh", + "createTitle": "Tạo bí danh", + "editDescription": "Cập nhật bí danh cho liên kết nhận này", + "createDescription": "Tạo một bí danh tùy chỉnh cho liên kết nhận này", + "aliasLabel": "Bí danh liên kết", + "aliasPlaceholder": "my-custom-link", + "preview": "Xem trước:", + "currentLink": "Liên kết hiện tại:", + "copyCurrentLink": "Sao chép liên kết hiện tại", + "randomTooltip": "Tạo bí danh ngẫu nhiên", + "cancel": "Hủy", + "creating": "Đang tạo...", + "updating": "Đang cập nhật...", + "create": "Tạo bí danh", + "update": "Cập nhật bí danh", + "validation": { + "required": "Bí danh là bắt buộc", + "minLength": "Bí danh phải có ít nhất 3 ký tự", + "maxLength": "Bí danh phải có tối đa 50 ký tự", + "pattern": "Bí danh chỉ được chứa chữ cái, số, dấu gạch ngang và dấu gạch dưới" + }, + "help": "3-50 ký tự. Dấu cách sẽ tự động được chuyển đổi thành dấu gạch ngang." + }, + "password": { + "title": "Chỉnh sửa bảo vệ bằng mật khẩu", + "description": "Định cấu hình bảo vệ bằng mật khẩu cho liên kết này", + "hasPassword": "Được bảo vệ bằng mật khẩu", + "password": "Mật khẩu", + "cancel": "Hủy", + "save": "Lưu", + "saving": "Đang lưu..." + }, + "receivedFiles": { + "title": "Tệp đã nhận", + "description": "Xem và quản lý các tệp được gửi đến liên kết này", + "noFiles": "Chưa nhận được tệp nào", + "noFilesDescription": "Các tệp được gửi qua liên kết này sẽ xuất hiện ở đây", + "fileCount": "{count, plural, =0 {Không có tệp nào} =1 {1 tệp} other {# tệp}}", + "invalidDate": "Ngày không hợp lệ", + "totalSize": "Tổng kích thước: {size}", + "columns": { + "file": "Tệp", + "size": "Kích thước", + "sender": "Được gửi bởi", + "date": "Ngày", + "actions": "Hành động" + }, + "actions": { + "preview": "Xem trước", + "download": "Tải xuống", + "copyToMyFiles": "Sao chép vào các tệp của tôi", + "copying": "Đang sao chép..." + }, + "uploadedBy": "Được tải lên bởi {name}", + "anonymous": "Ẩn danh", + "downloadSuccess": "Đã bắt đầu tải xuống", + "downloadError": "Lỗi khi tải xuống tệp", + "editSuccess": "Đã cập nhật tệp thành công", + "editError": "Lỗi khi cập nhật tệp", + "previewNotAvailable": "Không có bản xem trước", + "copySuccess": "Đã sao chép tệp vào tệp của bạn thành công", + "copyError": "Lỗi khi sao chép tệp vào tệp của bạn", + "deleteSuccess": "Đã xóa tệp thành công", + "deleteError": "Lỗi khi xóa tệp", + "bulkCopySuccess": "{count, plural, =1 {1 tệp đã được sao chép thành công vào tệp của bạn} other {# tệp đã được sao chép thành công vào tệp của bạn}}", + "bulkDeleteSuccess": "{count, plural, =1 {1 tệp đã được xóa thành công} other {# tệp đã được xóa thành công}}", + "bulkCopyProgress": "Đang sao chép {count, plural, =1 {1 tệp} other {# tệp}} vào tệp của bạn...", + "bulkDeleteProgress": "Đang xóa {count, plural, =1 {1 tệp} other {# tệp}}...", + "bulkDeleteConfirmTitle": "Xóa các tệp đã chọn", + "bulkDeleteConfirmMessage": "Bạn có chắc chắn muốn xóa {count, plural, =1 {tệp này} other {# tệp này}} không? Hành động này không thể hoàn tác.", + "bulkDeleteConfirmButton": "Xóa {count, plural, =1 {tệp} other {tệp}}", + "bulkActions": { + "selected": "{count, plural, =1 {1 tệp đã được chọn} other {# tệp đã được chọn}}", + "actions": "Hành động", + "download": "Tải xuống các mục đã chọn", + "copyToMyFiles": "Sao chép các mục đã chọn vào tệp của tôi", + "delete": "Xóa các mục đã chọn" + }, + "selectAll": "Chọn tất cả", + "selectFile": "Chọn tệp {fileName}", + "copyErrors": { + "timeout": "Thao tác sao chép đã hết thời gian chờ. Vui lòng thử lại với tệp nhỏ hơn hoặc kiểm tra kết nối của bạn.", + "failed": "Thao tác sao chép không thành công. Vui lòng thử lại.", + "aborted": "Thao tác sao chép đã bị hủy do hết thời gian chờ." + } + } + }, + "form": { + "name": { + "label": "Tên liên kết", + "placeholder": "ví dụ: Tài liệu dự án, Ảnh gia đình..." + }, + "description": { + "label": "Mô tả", + "placeholder": "Mô tả loại tệp bạn mong đợi nhận được...", + "description": "Tùy chọn. Giúp mọi người hiểu những gì cần gửi." + }, + "status": { + "label": "Trạng thái liên kết", + "description": "Kích hoạt hoặc hủy kích hoạt liên kết nhận này" + }, + "expiration": { + "label": "Ngày hết hạn", + "description": "Tùy chọn. Liên kết sẽ bị hủy kích hoạt sau ngày này.", + "configure": "Định cấu hình hết hạn" + }, + "fileLimits": { + "configure": "Định cấu hình giới hạn tệp" + }, + "maxFiles": { + "label": "Tệp tối đa", + "placeholder": "ví dụ: 10", + "description": "Tùy chọn. Giới hạn tổng số tệp có thể được gửi.", + "noLimit": "Không giới hạn tệp" + }, + "maxFileSize": { + "label": "Kích thước tệp tối đa", + "placeholder": "ví dụ: 100", + "description": "Tùy chọn. Giới hạn kích thước riêng của mỗi tệp.", + "noLimit": "Không giới hạn kích thước" + }, + "allowedFileTypes": { + "label": "Loại tệp được phép", + "placeholder": "ví dụ: pdf, jpg, png, docx", + "description": "Nhập các phần mở rộng không có dấu chấm, được phân tách bằng dấu cách, dấu phẩy, dấu gạch ngang hoặc dấu gạch đứng", + "allTypes": "Tất cả các loại tệp" + }, + "pageLayout": { + "label": "Bố cục trang", + "placeholder": "Chọn bố cục", + "description": "Trang tải lên sẽ xuất hiện như thế nào với người dùng.", + "options": { + "default": "Bố cục mặc định", + "wetransfer": "Kiểu WeTransfer" + } + }, + "password": { + "label": "Mật khẩu bảo vệ", + "placeholder": "Tùy chọn. Thêm mật khẩu để bảo vệ liên kết", + "description": "Tùy chọn. Người dùng sẽ cần mật khẩu này để truy cập liên kết.", + "configurePassword": "Định cấu hình mật khẩu", + "protectWithPassword": "Bảo vệ bằng mật khẩu", + "passwordHelp": "Mật khẩu phải có ít nhất 4 ký tự", + "passwordPlaceholder": "Nhập mật khẩu để bảo vệ liên kết" + }, + "nameFieldRequired": { + "label": "Yêu cầu trường tên", + "description": "Định cấu hình xem trường tên người tải lên có được hiển thị hay không và có bắt buộc hay không" + }, + "emailFieldRequired": { + "label": "Yêu cầu trường email", + "description": "Định cấu hình xem trường email người tải lên có được hiển thị hay không và có bắt buộc hay không" + }, + "fieldRequirements": { + "title": "Yêu cầu trường", + "description": "Định cấu hình các trường được hiển thị trong biểu mẫu tải lên" + }, + "submit": "Tạo liên kết nhận" + }, + "messages": { + "created": "Đã tạo liên kết nhận thành công!", + "createSuccess": "Đã tạo liên kết nhận thành công!", + "updateSuccess": "Đã cập nhật liên kết nhận thành công!", + "linkCopied": "Đã sao chép liên kết vào khay nhớ tạm!", + "deleteSuccess": "Đã xóa liên kết nhận thành công!", + "aliasCreated": "Đã tạo bí danh thành công!", + "activateSuccess": "Đã kích hoạt liên kết nhận thành công!", + "deactivateSuccess": "Đã hủy kích hoạt liên kết nhận thành công!", + "passwordProtectionEnabled": "Đã bật bảo vệ bằng mật khẩu thành công!", + "passwordProtectionDisabled": "Đã xóa bảo vệ bằng mật khẩu thành công!" + }, + "defaultLinkName": "Tệp đã nhận", + "errors": { + "loadFailed": "Không thể tải liên kết nhận", + "createFailed": "Không thể tạo liên kết nhận. Vui lòng thử lại.", + "updateFailed": "Không thể cập nhật liên kết nhận. Vui lòng thử lại.", + "deleteFailed": "Không thể xóa liên kết nhận. Vui lòng thử lại.", + "aliasCreateFailed": "Không thể tạo bí danh. Vui lòng thử lại.", + "passwordUpdateFailed": "Không thể cập nhật bảo vệ bằng mật khẩu" + }, + "delete": { + "title": "Xóa liên kết nhận", + "description": "Hành động này không thể hoàn tác. Liên kết sẽ bị xóa vĩnh viễn và sẽ không thể nhận tệp nữa.", + "confirmButton": "Xóa liên kết", + "cancelButton": "Hủy", + "deleting": "Đang xóa..." + }, + "upload": { + "metadata": { + "title": "Gửi tệp - Palmr", + "description": "Gửi tệp qua liên kết được chia sẻ", + "descriptionWithLimit": "Tải lên tệp (tối đa {limit} tệp)" + }, + "layout": { + "defaultTitle": "Gửi tệp", + "importantInfo": "Thông tin quan trọng:", + "maxFiles": "Tối đa {count} tệp", + "maxFileSize": "Kích thước tệp tối đa: {size}MB", + "allowedTypes": "Loại được phép: {types}", + "loading": "Đang tải..." + }, + "password": { + "title": "Liên kết được bảo vệ", + "description": "Liên kết này được bảo vệ bằng mật khẩu. Nhập mật khẩu để tiếp tục.", + "label": "Mật khẩu", + "placeholder": "Nhập mật khẩu", + "cancel": "Hủy", + "submit": "Tiếp tục", + "verifying": "Đang xác minh..." + }, + "errors": { + "loadFailed": "Không thể tải thông tin. Vui lòng thử lại.", + "passwordIncorrect": "Mật khẩu không chính xác. Vui lòng thử lại.", + "linkNotFound": "Không tìm thấy liên kết hoặc đã hết hạn.", + "linkInactive": "Liên kết này không hoạt động.", + "linkExpired": "Liên kết này đã hết hạn.", + "uploadFailed": "Lỗi khi tải lên tệp", + "retry": "Thử lại", + "fileTooLarge": "Tệp quá lớn. Kích thước tối đa: {maxSize}", + "fileTypeNotAllowed": "Loại tệp không được phép. Các loại được chấp nhận: {allowedTypes}", + "maxFilesExceeded": "Chỉ được phép tối đa {maxFiles} tệp", + "selectAtLeastOneFile": "Chọn ít nhất một tệp", + "provideNameOrEmail": "Vui lòng cung cấp tên hoặc email của bạn", + "provideNameRequired": "Tên là bắt buộc", + "provideEmailRequired": "Email là bắt buộc" + }, + "fileDropzone": { + "dragActive": "Thả tệp vào đây", + "dragInactive": "Kéo tệp vào đây hoặc nhấp để chọn", + "acceptedTypes": "Loại được chấp nhận: {types}", + "maxFileSize": "Kích thước tối đa: {size}", + "maxFiles": "Tối đa {count} tệp", + "remainingFiles": "Còn lại {remaining} trong số {max} tệp" + }, + "fileList": { + "title": "Các tệp đã chọn:", + "statusUploaded": "Đã tải lên", + "statusError": "Lỗi", + "retry": "Thử lại" + }, + "form": { + "nameLabel": "Tên", + "nameLabelOptional": "Tên (tùy chọn)", + "namePlaceholder": "Tên của bạn", + "emailLabel": "Email", + "emailLabelOptional": "Email (tùy chọn)", + "emailPlaceholder": "your@email.com", + "descriptionLabel": "Mô tả (tùy chọn)", + "descriptionPlaceholder": "Thêm mô tả cho các tệp...", + "uploadButton": "Gửi {count} tệp", + "uploading": "Đang gửi..." + }, + "success": { + "title": "Đã gửi tệp thành công! 🎉", + "description": "Bạn có thể đóng trang này.", + "countMessage": "Đã gửi thành công {count} tệp!" + }, + "maxFilesReached": { + "title": "Đã đạt đến giới hạn tệp", + "description": "Liên kết này đã nhận được số lượng tệp tối đa cho phép là {maxFiles}.", + "contactOwner": "Nếu có lỗi hoặc bạn cần gửi thêm tệp, hãy liên hệ với chủ sở hữu liên kết." + }, + "linkInactive": { + "title": "Liên kết không hoạt động", + "description": "Liên kết nhận này tạm thời không hoạt động.", + "contactOwner": "Liên hệ với chủ sở hữu liên kết để biết thêm thông tin." + }, + "linkNotFound": { + "title": "Không tìm thấy liên kết", + "description": "Liên kết này có thể đã bị xóa hoặc chưa từng tồn tại." + }, + "linkExpired": { + "title": "Liên kết đã hết hạn", + "description": "Liên kết nhận này đã hết hạn và không còn chấp nhận tệp nữa.", + "contactOwner": "Liên hệ với chủ sở hữu liên kết nếu bạn cần gửi tệp." + } + }, + "components": { + "fileRow": { + "addDescription": "Thêm mô tả...", + "anonymous": "Ẩn danh" + }, + "fileActions": { + "edit": "Chỉnh sửa", + "preview": "Xem trước", + "download": "Tải xuống", + "delete": "Xóa", + "copyToMyFiles": "Sao chép vào các tệp của tôi", + "copying": "Đang sao chép..." + }, + "editField": { + "saveChanges": "Lưu thay đổi", + "cancelEdit": "Hủy chỉnh sửa" + } + } + }, + "searchBar": { + "placeholder": "Tìm kiếm tệp và thư mục...", + "placeholderFiles": "Tìm kiếm tệp...", + "placeholderFolders": "Tìm kiếm thư mục...", + "results": "Hiển thị {filtered} trong số {total} mục", + "noResults": "Không tìm thấy kết quả nào cho \"{query}\"" + }, + "settings": { + "groups": { + "defaultDescription": "Tùy chọn cấu hình", + "general": { + "title": "Chung", + "description": "Cài đặt ứng dụng cơ bản" + }, + "email": { + "title": "Email", + "description": "Cấu hình máy chủ email" + }, + "security": { + "title": "Bảo mật", + "description": "Cài đặt bảo mật và xác thực" + }, + "storage": { + "title": "Lưu trữ", + "description": "Cấu hình lưu trữ tệp" + }, + "oidc": { + "title": "OpenID Connect (SSO)", + "description": "Định cấu hình xác thực SSO qua OpenID Connect" + } + }, + "fields": { + "noDescription": "Không có mô tả nào", + "appLogo": { + "title": "Logo ứng dụng", + "description": "Hình ảnh logo ứng dụng" + }, + "appName": { + "title": "Tên ứng dụng", + "description": "Tên ứng dụng được hiển thị cho người dùng" + }, + "appDescription": { + "title": "Mô tả ứng dụng", + "description": "Mô tả ngắn gọn về ứng dụng" + }, + "showHomePage": { + "title": "Hiển thị trang chủ", + "description": "Hiển thị trang chủ sau khi cài đặt" + }, + "hideVersion": { + "title": "Ẩn phiên bản", + "description": "Ẩn phiên bản Palmr khỏi chân trang trên tất cả các trang" + }, + "smtpEnabled": { + "title": "Đã bật SMTP", + "description": "Bật hoặc tắt chức năng email SMTP" + }, + "smtpHost": { + "title": "Máy chủ SMTP", + "description": "Địa chỉ máy chủ SMTP" + }, + "smtpPort": { + "title": "Cổng SMTP", + "description": "Cổng máy chủ SMTP" + }, + "smtpUser": { + "title": "Tên người dùng SMTP", + "description": "Tên người dùng để xác thực SMTP" + }, + "smtpPass": { + "title": "Mật khẩu SMTP", + "description": "Mật khẩu để xác thực SMTP" + }, + "smtpFromName": { + "title": "Tên người gửi", + "description": "Tên hiển thị cho các email đã gửi" + }, + "smtpFromEmail": { + "title": "Email người gửi", + "description": "Địa chỉ email của người gửi" + }, + "smtpSecure": { + "title": "Bảo mật kết nối", + "description": "Phương thức bảo mật kết nối SMTP - Tự động (được khuyến nghị), SSL, STARTTLS hoặc Không có (không an toàn)", + "options": { + "auto": "Tự động (Được khuyến nghị)", + "ssl": "SSL (Cổng 465)", + "tls": "STARTTLS (Cổng 587)", + "none": "Không có (Không an toàn)" + } + }, + "smtpNoAuth": { + "title": "Không xác thực", + "description": "Bật tính năng này cho các máy chủ nội bộ không yêu cầu tên người dùng/mật khẩu (ẩn các trường xác thực)" + }, + "smtpTrustSelfSigned": { + "title": "Tin cậy chứng chỉ tự ký", + "description": "Bật tính năng này để tin cậy các chứng chỉ SSL/TLS tự ký (hữu ích cho môi trường phát triển)" + }, + "testSmtp": { + "title": "Kiểm tra kết nối SMTP", + "description": "Kiểm tra xem cấu hình SMTP có hợp lệ không" + }, + "maxLoginAttempts": { + "title": "Số lần đăng nhập tối đa", + "description": "Số lần đăng nhập tối đa trước khi bị chặn" + }, + "loginBlockDuration": { + "title": "Thời gian chặn", + "description": "Thời gian (tính bằng giây) để chặn sau khi vượt quá số lần thử" + }, + "passwordMinLength": { + "title": "Độ dài mật khẩu tối thiểu", + "description": "Số ký tự tối thiểu cho mật khẩu" + }, + "passwordResetTokenExpiration": { + "title": "Hết hạn mã thông báo đặt lại", + "description": "Thời gian hiệu lực (tính bằng giây) cho mã thông báo đặt lại mật khẩu" + }, + "maxFileSize": { + "title": "Kích thước tệp tối đa", + "description": "Kích thước tệp tối đa được phép tải lên" + }, + "maxTotalStoragePerUser": { + "title": "Dung lượng lưu trữ tối đa cho mỗi người dùng", + "description": "Tổng giới hạn dung lượng lưu trữ cho mỗi người dùng" + }, + "firstUserAccess": { + "title": "Quyền truy cập của người dùng đầu tiên", + "description": "Cài đặt cho lần truy cập đầu tiên của người dùng mới" + }, + "serverUrl": { + "title": "URL máy chủ", + "description": "URL cơ sở của máy chủ Palmr (ví dụ: https://palmr.example.com)" + }, + "passwordAuthEnabled": { + "title": "Xác thực bằng mật khẩu", + "description": "Bật hoặc tắt xác thực dựa trên mật khẩu" + } + }, + "buttons": { + "save": "Lưu {group}", + "testSmtp": "Kiểm tra kết nối", + "testing": "Đang kiểm tra..." + }, + "errors": { + "loadFailed": "Không thể tải cài đặt", + "updateFailed": "Không thể cập nhật cài đặt", + "passwordAuthRequiresProvider": "Không thể tắt xác thực bằng mật khẩu mà không có ít nhất một nhà cung cấp xác thực đang hoạt động" + }, + "messages": { + "noChanges": "Không có thay đổi nào để lưu", + "updateSuccess": "Đã cập nhật cài đặt {group} thành công", + "smtpTestSuccess": "Kết nối SMTP thành công! Cấu hình email của bạn đang hoạt động chính xác.", + "smtpTestFailed": "Kết nối SMTP không thành công: {error}", + "smtpTestGenericError": "Không thể kiểm tra kết nối SMTP. Vui lòng kiểm tra cài đặt của bạn và thử lại.", + "smtpNotEnabled": "SMTP chưa được bật. Vui lòng bật SMTP trước.", + "smtpMissingHostPort": "Vui lòng điền vào Máy chủ và Cổng SMTP trước khi kiểm tra.", + "smtpMissingAuth": "Vui lòng điền vào Tên người dùng và Mật khẩu SMTP hoặc bật tùy chọn 'Không xác thực'." + }, + "title": "Cài đặt", + "breadcrumb": "Cài đặt", + "pageTitle": "Cài đặt", + "tooltips": { + "testSmtp": "Kiểm tra kết nối SMTP với các giá trị hiện được nhập trong biểu mẫu. Để thực hiện các thay đổi vĩnh viễn, hãy nhớ lưu cài đặt của bạn sau khi kiểm tra.", + "defaultPlaceholder": "Nhập và nhấn Enter" + }, + "redirectUri": { + "placeholder": "https://mysite.com", + "previewLabel": "URL hoàn chỉnh sẽ được lưu:" + } + }, + "share": { + "errors": { + "invalidPassword": "Mật khẩu không hợp lệ. Vui lòng thử lại.", + "loadFailed": "Không thể tải chia sẻ", + "downloadFailed": "Không thể tải xuống tệp" + }, + "messages": { + "downloadStarted": "Đã bắt đầu tải xuống" + }, + "password": { + "title": "Chia sẻ được bảo vệ bằng mật khẩu", + "protected": "Chia sẻ này được bảo vệ bằng mật khẩu", + "incorrect": "Mật khẩu không chính xác. Vui lòng thử lại.", + "label": "Mật khẩu", + "placeholder": "Nhập mật khẩu chia sẻ", + "submit": "Gửi" + }, + "details": { + "untitled": "Chia sẻ không có tiêu đề", + "created": "Đã tạo: {date}", + "expires": "Hết hạn: {date}" + }, + "downloadAll": "Tải xuống tất cả", + "notFound": { + "title": "Không tìm thấy chia sẻ", + "description": "Chia sẻ này có thể đã bị xóa hoặc đã hết hạn." + }, + "pageTitle": "Chia sẻ", + "metadata": { + "defaultDescription": "Chia sẻ tệp một cách an toàn", + "filesShared": "{count, plural, =1 {1 tệp đã được chia sẻ} other {# tệp đã được chia sẻ}}" + } + }, + "shareActions": { + "fileTitle": "Chia sẻ tệp", + "folderTitle": "Chia sẻ thư mục", + "linkTitle": "Tạo liên kết", + "linkDescriptionFile": "Tạo một liên kết tùy chỉnh để chia sẻ tệp", + "linkDescriptionFolder": "Tạo một liên kết tùy chỉnh để chia sẻ thư mục", + "aliasLabel": "Bí danh liên kết", + "aliasPlaceholder": "Nhập bí danh tùy chỉnh", + "linkReady": "Liên kết chia sẻ của bạn đã sẵn sàng:", + "generateLink": "Tạo liên kết", + "copyLink": "Sao chép liên kết", + "deleteTitle": "Xóa chia sẻ", + "deleteConfirmation": "Bạn có chắc chắn muốn xóa chia sẻ này không? Hành động này không thể hoàn tác.", + "addDescriptionPlaceholder": "Thêm mô tả...", + "editTitle": "Chỉnh sửa chia sẻ", + "newPasswordLabel": "Mật khẩu mới (để trống để giữ mật khẩu hiện tại)", + "newPasswordPlaceholder": "Nhập mật khẩu mới", + "manageFilesTitle": "Quản lý tệp", + "manageFilesDescription": "Chọn tệp và thư mục để đưa vào chia sẻ này", + "manageRecipientsTitle": "Quản lý người nhận", + "itemsSelected": "{count} mục đã chọn", + "editSuccess": "Đã cập nhật chia sẻ thành công", + "editError": "Không thể cập nhật chia sẻ", + "bulkDeleteConfirmation": "Bạn có chắc chắn muốn xóa {count, plural, =1 {1 chia sẻ} other {# chia sẻ}} không? Hành động này không thể hoàn tác.", + "bulkDeleteTitle": "Xóa các chia sẻ đã chọn" + }, + "shareDetails": { + "title": "Chi tiết chia sẻ", + "subtitle": "Xem và quản lý chi tiết cho chia sẻ này", + "basicInfo": "Thông tin cơ bản", + "name": "Tên", + "description": "Mô tả", + "shareLink": "Liên kết chia sẻ", + "dates": "Ngày", + "security": "Bảo mật", + "files": "Tệp", + "recipients": "Người nhận", + "views": "Lượt xem", + "created": "Đã tạo", + "expires": "Hết hạn", + "never": "Không bao giờ", + "untitled": "Chia sẻ không có tiêu đề", + "noDescription": "Không có mô tả", + "notAvailable": "Không có sẵn", + "invalidDate": "Ngày không hợp lệ", + "passwordProtected": "Được bảo vệ bằng mật khẩu", + "publicAccess": "Truy cập công khai", + "maxViews": "Lượt xem tối đa:", + "noLink": "Chưa tạo liên kết", + "generateLink": "Tạo liên kết", + "editLink": "Chỉnh sửa liên kết", + "copyLink": "Sao chép liên kết", + "openLink": "Mở liên kết", + "editSecurity": "Chỉnh sửa bảo mật", + "editExpiration": "Chỉnh sửa hết hạn", + "qrCode": "Mã QR", + "downloadQrCode": "Tải xuống mã QR", + "clickToEnlargeQrCode": "Nhấp để phóng to mã QR", + "loadError": "Không thể tải chi tiết chia sẻ" + }, + "shareExpiration": { + "title": "Cài đặt hết hạn chia sẻ", + "subtitle": "Định cấu hình khi nào chia sẻ này sẽ hết hạn", + "currentStatus": "Trạng thái hiện tại", + "expires": "Hết hạn:", + "neverExpires": "Không bao giờ hết hạn", + "enableExpiration": "Bật hết hạn", + "expirationDate": "Ngày hết hạn", + "validation": { + "dateRequired": "Vui lòng chọn ngày hết hạn", + "dateMustBeFuture": "Ngày hết hạn phải trong tương lai" + }, + "success": { + "expirationSet": "Đã đặt ngày hết hạn thành công", + "expirationUpdated": "Đã cập nhật ngày hết hạn thành công", + "expirationRemoved": "Đã xóa hết hạn thành công - chia sẻ hiện là vĩnh viễn" + }, + "error": { + "updateFailed": "Không thể cập nhật cài đặt hết hạn" + }, + "info": { + "title": "Về việc hết hạn:", + "willBeInaccessible": "Chia sẻ sẽ không thể truy cập được sau ngày này", + "canBeChanged": "Bạn có thể thay đổi hoặc xóa ngày hết hạn bất cứ lúc nào", + "noExpiration": "Chia sẻ này sẽ không bao giờ hết hạn và sẽ vẫn có thể truy cập vô thời hạn." + } + }, + "shareManager": { + "deleteSuccess": "Đã xóa chia sẻ thành công", + "deleteError": "Không thể xóa chia sẻ", + "updateSuccess": "Đã cập nhật chia sẻ thành công", + "updateError": "Không thể cập nhật chia sẻ", + "securityUpdateSuccess": "Đã cập nhật cài đặt bảo mật thành công", + "securityUpdateError": "Không thể cập nhật cài đặt bảo mật", + "expirationUpdateSuccess": "Đã cập nhật cài đặt hết hạn thành công", + "expirationUpdateError": "Không thể cập nhật cài đặt hết hạn", + "filesUpdateSuccess": "Đã cập nhật tệp thành công", + "filesUpdateError": "Không thể cập nhật tệp", + "recipientsUpdateSuccess": "Đã cập nhật người nhận thành công", + "recipientsUpdateError": "Không thể cập nhật người nhận", + "linkGenerateSuccess": "Đã tạo liên kết chia sẻ thành công", + "linkGenerateError": "Không thể tạo liên kết chia sẻ", + "notifyLoading": "Đang gửi thông báo...", + "notifySuccess": "Đã thông báo cho người nhận thành công", + "notifyError": "Không thể thông báo cho người nhận", + "bulkDeleteError": "Không thể xóa chia sẻ", + "bulkDeleteLoading": "Đang xóa {count, plural, =1 {1 chia sẻ} other {# chia sẻ}}...", + "bulkDeleteSuccess": "{count, plural, =1 {1 chia sẻ đã được xóa thành công} other {# chia sẻ đã được xóa thành công}}", + "downloadSuccess": "Đã bắt đầu tải xuống thành công", + "downloadError": "Không thể tải xuống tệp chia sẻ", + "noFilesToDownload": "Không có tệp nào để tải xuống", + "creatingZip": "Đang tạo tệp ZIP...", + "zipDownloadSuccess": "Đã tải xuống tệp ZIP thành công", + "zipDownloadError": "Không thể tạo tệp ZIP", + "errors": { + "multipleDownloadNotSupported": "Chưa hỗ trợ tải xuống nhiều chia sẻ - vui lòng tải xuống từng chia sẻ riêng lẻ" + }, + "singleShareZipName": "{shareName}_files.zip", + "multipleSharesZipName": "{count}_shares_files.zip", + "defaultShareName": "Chia sẻ" + }, + "shareMultipleFiles": { + "title": "Chia sẻ nhiều tệp", + "shareNameLabel": "Tên chia sẻ", + "shareNamePlaceholder": "Nhập tên chia sẻ", + "descriptionLabel": "Mô tả", + "descriptionPlaceholder": "Nhập mô tả (tùy chọn)", + "filesToShare": "Tệp để chia sẻ", + "files": "tệp", + "totalSize": "Tổng kích thước", + "creating": "Đang tạo chia sẻ...", + "create": "Tạo chia sẻ", + "itemsToShare": "Các mục để chia sẻ ({count} {count, plural, =1 {mục} other {mục}})" + }, + "shareSecurity": { + "title": "Cài đặt bảo mật chia sẻ", + "subtitle": "Định cấu hình bảo vệ bằng mật khẩu và các tùy chọn bảo mật cho chia sẻ này", + "currentStatus": "Trạng thái hiện tại", + "passwordProtection": "Bảo vệ bằng mật khẩu", + "password": "Mật khẩu", + "newPassword": "Mật khẩu mới", + "passwordPlaceholder": "Nhập mật khẩu an toàn", + "existingPasswordMessage": "Chia sẻ này đã có mật khẩu. Nếu bạn muốn cập nhật nó, hãy nhập mật khẩu mới vào trường bên dưới và lưu.", + "passwordRequirements": { + "title": "Yêu cầu mật khẩu:", + "minLength": "Ít nhất 2 ký tự" + }, + "info": { + "title": "Cách hoạt động:", + "withPassword": "Người dùng sẽ cần nhập mật khẩu để truy cập chia sẻ này.", + "withoutPassword": "Bất kỳ ai có liên kết đều có thể truy cập chia sẻ này mà không cần mật khẩu." + }, + "validation": { + "passwordRequired": "Mật khẩu là bắt buộc", + "passwordTooShort": "Mật khẩu phải có ít nhất 2 ký tự" + }, + "success": { + "passwordSet": "Đã bật bảo vệ bằng mật khẩu thành công", + "passwordUpdated": "Đã cập nhật mật khẩu thành công", + "passwordRemoved": "Đã xóa bảo vệ bằng mật khẩu thành công" + }, + "error": { + "updateFailed": "Không thể cập nhật cài đặt bảo mật" + } + }, + "shares": { + "errors": { + "loadFailed": "Không thể tải chia sẻ", + "notifyFailed": "Không thể thông báo cho người nhận", + "smtpConfigFailed": "Không thể tải cấu hình SMTP" + }, + "messages": { + "linkCopied": "Đã sao chép liên kết vào khay nhớ tạm", + "recipientsNotified": "Đã thông báo cho người nhận thành công" + }, + "empty": { + "message": "Chưa có chia sẻ nào được tạo", + "createButton": "Tạo chia sẻ" + }, + "header": { + "title": "Chia sẻ của tôi", + "myShares": "Chia sẻ của tôi" + }, + "search": { + "title": "Tất cả các chia sẻ", + "createButton": "Tạo chia sẻ", + "placeholder": "Tìm kiếm chia sẻ...", + "results": "Đã tìm thấy {filtered} trong số {total} chia sẻ" + }, + "pageTitle": "Chia sẻ" + }, + "sharesTable": { + "ariaLabel": "Bảng chia sẻ", + "never": "Không bao giờ", + "columns": { + "name": "TÊN", + "description": "MÔ TẢ", + "createdAt": "NGÀY TẠO", + "expiresAt": "NGÀY HẾT HẠN", + "status": "TRẠNG THÁI", + "security": "BẢO MẬT", + "files": "TỆP", + "recipients": "NGƯỜI NHẬN", + "actions": "HÀNH ĐỘNG" + }, + "status": { + "neverExpires": "Không bao giờ hết hạn", + "active": "Đang hoạt động", + "expired": "Đã hết hạn" + }, + "security": { + "protected": "Được bảo vệ", + "public": "Công khai" + }, + "filesCount": "tệp", + "folderCount": "thư mục", + "recipientsCount": "người nhận", + "actions": { + "menu": "Menu hành động chia sẻ", + "edit": "Chỉnh sửa", + "manageFiles": "Quản lý tệp", + "manageRecipients": "Quản lý người nhận", + "viewDetails": "Xem chi tiết", + "generateLink": "Tạo liên kết", + "editLink": "Chỉnh sửa liên kết", + "copyLink": "Sao chép liên kết", + "viewQrCode": "Xem mã QR", + "notifyRecipients": "Thông báo cho người nhận", + "downloadShareFiles": "Tải xuống tất cả các tệp", + "delete": "Xóa" + }, + "bulkActions": { + "actions": "Hành động", + "download": "Tải xuống các mục đã chọn", + "delete": "Xóa", + "selected": "{count, plural, =1 {1 chia sẻ đã được chọn} other {# chia sẻ đã được chọn}}" + }, + "selectAll": "Chọn tất cả", + "selectShare": "Chọn chia sẻ {shareName}" + }, + "storageUsage": { + "title": "Sử dụng bộ nhớ", + "ariaLabel": "Thanh tiến trình sử dụng bộ nhớ", + "used": "đã sử dụng", + "available": "có sẵn", + "total": "Tổng", + "loading": "Đang tải...", + "retry": "Thử lại", + "errors": { + "title": "Thông tin bộ nhớ không có sẵn", + "detectionFailed": "Không thể phát hiện dung lượng đĩa. Điều này có thể do sự cố cấu hình hệ thống hoặc quyền không đủ.", + "serverError": "Đã xảy ra lỗi máy chủ khi truy xuất thông tin bộ nhớ. Vui lòng thử lại sau.", + "unknown": "Đã xảy ra lỗi không mong muốn khi tải thông tin bộ nhớ." + } + }, + "theme": { + "toggle": "Chuyển đổi chủ đề", + "light": "Sáng", + "dark": "Tối", + "system": "Hệ thống" + }, + "twoFactor": { + "title": "Xác thực hai yếu tố", + "description": "Thêm một lớp bảo mật bổ sung vào tài khoản của bạn", + "enabled": "Tài khoản của bạn được bảo vệ bằng xác thực hai yếu tố", + "disabled": "Xác thực hai yếu tố chưa được bật", + "status": { + "label": "Trạng thái:", + "enabled": "Đã bật", + "disabled": "Đã tắt" + }, + "buttons": { + "enable2FA": "Bật 2FA", + "disable2FA": "Tắt 2FA" + }, + "setup": { + "title": "Bật xác thực hai yếu tố", + "description": "Quét mã QR bằng ứng dụng xác thực của bạn, sau đó nhập mã xác minh.", + "qrCode": "Mã QR", + "manualEntryKey": "Khóa nhập thủ công", + "verificationCode": "Mã xác minh", + "verificationCodePlaceholder": "Nhập mã 6 chữ số", + "verificationCodeDescription": "Nhập mã 6 chữ số từ ứng dụng xác thực của bạn", + "verifyAndEnable": "Xác minh & Bật", + "cancel": "Hủy" + }, + "disable": { + "title": "Tắt xác thực hai yếu tố", + "description": "Nhập mật khẩu của bạn để xác nhận tắt xác thực hai yếu tố.", + "password": "Mật khẩu", + "passwordPlaceholder": "Nhập mật khẩu của bạn", + "confirm": "Xác nhận tắt", + "cancel": "Hủy" + }, + "backupCodes": { + "title": "Mã dự phòng", + "description": "Lưu các mã dự phòng này ở nơi an toàn. Bạn có thể sử dụng chúng để truy cập tài khoản của mình nếu mất thiết bị xác thực.", + "warning": "Quan trọng:", + "warningText": "Mỗi mã dự phòng chỉ có thể được sử dụng một lần. Giữ chúng an toàn và không chia sẻ với bất kỳ ai.", + "generateNew": "Tạo mã dự phòng mới", + "download": "Tải xuống mã dự phòng", + "copyToClipboard": "Sao chép vào khay nhớ tạm", + "savedMessage": "Tôi đã lưu mã dự phòng của mình", + "available": "{count} mã dự phòng có sẵn", + "instructions": [ + "• Lưu các mã này ở một vị trí an toàn", + "• Mỗi mã dự phòng chỉ có thể được sử dụng một lần", + "• Bạn có thể tạo mã mới bất cứ lúc nào" + ] + }, + "verification": { + "title": "Xác thực hai yếu tố", + "description": "Nhập mã 6 chữ số từ ứng dụng xác thực của bạn", + "backupDescription": "Nhập một trong các mã dự phòng của bạn để tiếp tục", + "verificationCode": "Mã xác minh", + "backupCode": "Mã dự phòng", + "verificationCodePlaceholder": "000000", + "backupCodePlaceholder": "XXXX-XXXX", + "verify": "Xác minh", + "verifying": "Đang xác minh...", + "useBackupCode": "Sử dụng mã dự phòng thay thế", + "useAuthenticatorCode": "Sử dụng mã xác thực thay thế", + "rememberDevice": "Ghi nhớ thiết bị này trong 30 ngày", + "rememberDeviceDescription": "Bạn sẽ không cần nhập mã 2FA trên thiết bị này trong 30 ngày" + }, + "trustedDevices": { + "title": "Thiết bị đáng tin cậy - 2FA", + "description": "Các thiết bị không yêu cầu xác minh 2FA", + "noDevices": "Không có thiết bị đáng tin cậy", + "deviceName": "Thiết bị", + "addedOn": "Đã thêm vào", + "expiresOn": "Hết hạn vào", + "remove": "Xóa", + "removeAll": "Xóa tất cả", + "confirmRemove": "Bạn có chắc chắn muốn xóa thiết bị đáng tin cậy này không?", + "confirmRemoveAll": "Bạn có chắc chắn muốn xóa tất cả các thiết bị đáng tin cậy không?", + "deviceRemoved": "Đã xóa thiết bị đáng tin cậy thành công", + "allDevicesRemoved": "Đã xóa tất cả các thiết bị đáng tin cậy thành công", + "loadFailed": "Không thể tải thiết bị đáng tin cậy", + "removeFailed": "Không thể xóa thiết bị đáng tin cậy", + "removeAllFailed": "Không thể xóa tất cả các thiết bị đáng tin cậy", + "loading": "Đang tải thiết bị đáng tin cậy...", + "noDevicesDescription": "Các thiết bị sẽ xuất hiện ở đây khi bạn chọn tin cậy chúng trong quá trình xác minh 2FA", + "tableHeaders": { + "device": "Thiết bị", + "added": "Đã thêm", + "expires": "Hết hạn", + "lastUsed": "Sử dụng lần cuối", + "ipAddress": "Địa chỉ IP", + "actions": "Hành động" + }, + "status": { + "never": "Không bao giờ", + "expired": "Đã hết hạn" + }, + "modals": { + "removeDevice": { + "title": "Xóa thiết bị đáng tin cậy", + "added": "Đã thêm:", + "ip": "IP:" + }, + "removeAllDevices": { + "title": "Xóa tất cả các thiết bị đáng tin cậy", + "description": "Thao tác này sẽ xóa {count} thiết bị đáng tin cậy. Bạn sẽ cần xác minh lại 2FA trên tất cả các thiết bị." + }, + "buttons": { + "cancel": "Hủy", + "removing": "Đang xóa...", + "removeDevice": "Xóa thiết bị", + "removeAllDevices": "Xóa tất cả các thiết bị" + } + } + }, + "messages": { + "enabledSuccess": "Đã bật xác thực hai yếu tố thành công!", + "disabledSuccess": "Đã tắt xác thực hai yếu tố thành công", + "backupCodesGenerated": "Đã tạo mã dự phòng mới thành công", + "backupCodesCopied": "Đã sao chép mã dự phòng vào khay nhớ tạm", + "setupFailed": "Không thể tạo thiết lập 2FA", + "verificationFailed": "Mã xác minh không hợp lệ", + "disableFailed": "Không thể tắt 2FA. Vui lòng kiểm tra mật khẩu của bạn.", + "backupCodesFailed": "Không thể tạo mã dự phòng", + "backupCodesCopyFailed": "Không thể sao chép mã dự phòng", + "statusLoadFailed": "Không thể tải trạng thái 2FA", + "enterVerificationCode": "Vui lòng nhập mã xác minh", + "enterPassword": "Vui lòng nhập mật khẩu của bạn", + "deviceTrusted": "Thiết bị này đã được đánh dấu là đáng tin cậy trong 30 ngày" + }, + "errors": { + "invalidVerificationCode": "Mã xác minh không hợp lệ", + "invalidTwoFactorCode": "Mã xác thực hai yếu tố không hợp lệ", + "twoFactorRequired": "Yêu cầu xác thực hai yếu tố", + "twoFactorAlreadyEnabled": "Xác thực hai yếu tố đã được bật", + "twoFactorNotEnabled": "Xác thực hai yếu tố chưa được bật", + "passwordVerificationRequired": "Yêu cầu xác minh mật khẩu", + "invalidPassword": "Mật khẩu không hợp lệ", + "userNotFound": "Không tìm thấy người dùng" + }, + "deviceNames": { + "unknownDevice": "Thiết bị không xác định", + "browsers": { + "chrome": "Chrome", + "firefox": "Firefox", + "safari": "Safari", + "edge": "Edge" + }, + "platforms": { + "windows": " trên Windows", + "macos": " trên macOS", + "linux": " trên Linux", + "iphone": " trên iPhone", + "android": " trên Android" + } + } + }, + "uploadFile": { + "title": "Tải lên tệp", + "multipleTitle": "Tải lên tệp", + "selectFile": "Nhấp để chọn tệp", + "selectMultipleFiles": "Nhấp để chọn một hoặc nhiều tệp", + "dragAndDrop": "hoặc kéo và thả tệp vào đây", + "filesQueued": "{count, plural, one {# tệp đã được xếp hàng để tải lên} other {# tệp đã được xếp hàng để tải lên}}", + "preview": "Xem trước", + "uploadProgress": "Tiến trình tải lên", + "upload": "Tải lên", + "startUploads": "Bắt đầu tải lên", + "retry": "Thử lại", + "finish": "Hoàn thành", + "success": "Đã tải lên tệp thành công", + "allSuccess": "{count, plural, =1 {Đã tải lên tệp thành công} other {# tệp đã tải lên thành công}}", + "partialSuccess": "{success} tệp đã được tải lên thành công, {error} không thành công", + "error": "Không thể tải lên tệp", + "fileSizeExceeded": "Kích thước tệp vượt quá giới hạn {maxsizemb}MB.", + "insufficientStorage": "Không đủ dung lượng lưu trữ. Bạn còn {availablespace}MB.", + "unauthorized": "Không được phép: cần có mã thông báo hợp lệ để truy cập tài nguyên này.", + "globalDrop": { + "title": "Thả tệp để tải lên", + "description": "Thả để tải lên tệp của bạn" + }, + "confirmCancel": { + "title": "Hủy tải lên", + "messageSingle": "Có một quá trình tải lên đang diễn ra.", + "messageMultiple": "Có {count} quá trình tải lên đang diễn ra.", + "warning": "Nếu bạn đóng ngay bây giờ, quá trình tải lên sẽ bị hủy và mọi tiến trình sẽ bị mất.", + "continue": "Tiếp tục tải lên", + "cancel": "Hủy tải lên" + }, + "pasteSuccess": "{count, plural, =1 {Hình ảnh đã được dán và tải lên thành công} other {# hình ảnh đã được dán và tải lên thành công}}" + }, + "users": { + "modes": { + "create": "tạo", + "edit": "chỉnh sửa" + }, + "errors": { + "loadFailed": "Không thể tải người dùng", + "submitFailed": "Không thể {mode} người dùng", + "deleteFailed": "Không thể xóa người dùng", + "statusUpdateFailed": "Không thể cập nhật trạng thái người dùng" + }, + "messages": { + "createSuccess": "Đã tạo người dùng thành công", + "updateSuccess": "Đã cập nhật người dùng thành công", + "deleteSuccess": "Đã xóa người dùng thành công", + "activateSuccess": "Đã kích hoạt người dùng thành công", + "deactivateSuccess": "Đã hủy kích hoạt người dùng thành công" + }, + "actions": { + "edit": "Chỉnh sửa", + "activate": "Kích hoạt", + "deactivate": "Hủy kích hoạt", + "delete": "Xóa" + }, + "delete": { + "title": "Xác nhận xóa người dùng", + "confirmation": "Bạn có chắc chắn muốn xóa người dùng {firstName} {lastName} không? Hành động này không thể hoàn tác.", + "confirm": "Xóa người dùng" + }, + "form": { + "titleCreate": "Thêm người dùng mới", + "titleEdit": "Chỉnh sửa người dùng", + "firstName": "Tên", + "lastName": "Họ", + "username": "Tên người dùng", + "email": "Email", + "password": "Mật khẩu", + "newPassword": "Mật khẩu mới (tùy chọn)", + "passwordPlaceholder": "Để trống để giữ mật khẩu hiện tại", + "role": "Vai trò", + "roleUser": "Người dùng", + "roleAdmin": "Quản trị viên", + "create": "Tạo", + "save": "Lưu" + }, + "status": { + "title": "Xác nhận thay đổi trạng thái", + "confirmation": "Bạn có chắc chắn muốn {action} người dùng {firstName} {lastName} không?", + "activate": "kích hoạt", + "deactivate": "hủy kích hoạt", + "user": "Người dùng" + }, + "header": { + "title": "Quản lý người dùng", + "addUser": "Thêm người dùng", + "management": "Quản lý người dùng" + }, + "table": { + "user": "NGƯỜI DÙNG", + "email": "EMAIL", + "status": "TRẠNG THÁI", + "role": "VAI TRÒ", + "actions": "HÀNH ĐỘNG", + "active": "Đang hoạt động", + "inactive": "Không hoạt động", + "admin": "Quản trị viên", + "userr": "Người dùng" + }, + "invite": { + "button": "Tạo liên kết mời", + "title": "Tạo liên kết mời người dùng", + "description": "Tạo một liên kết sử dụng một lần cho phép ai đó tạo tài khoản của riêng họ. Liên kết hết hạn sau 15 phút.", + "generating": "Đang tạo...", + "generate": "Tạo liên kết", + "generated": "Đã tạo liên kết mời thành công!", + "linkReady": "Liên kết mời đã sẵn sàng", + "linkReadyDescription": "Chia sẻ liên kết này với người bạn muốn mời. Họ sẽ có thể tạo tài khoản của riêng mình với tư cách là người dùng thông thường.", + "copyLink": "Sao chép liên kết", + "linkCopied": "Đã sao chép liên kết mời vào khay nhớ tạm!", + "expiresIn": "Hết hạn sau 15 phút", + "close": "Đóng", + "errors": { + "generateFailed": "Không thể tạo liên kết mời" + } + } + }, + "embedCode": { + "title": "Nhúng phương tiện", + "description": "Sử dụng các mã này để nhúng phương tiện này vào diễn đàn, trang web hoặc các nền tảng khác", + "tabs": { + "directLink": "Liên kết trực tiếp", + "html": "HTML", + "bbcode": "BBCode" + }, + "directLinkDescription": "URL trực tiếp đến tệp phương tiện", + "htmlDescription": "Sử dụng mã này để nhúng phương tiện vào các trang HTML", + "bbcodeDescription": "Sử dụng mã này để nhúng phương tiện vào các diễn đàn hỗ trợ BBCode" + }, + "validation": { + "firstNameRequired": "Tên là bắt buộc", + "lastNameRequired": "Họ là bắt buộc", + "usernameLength": "Tên người dùng phải có ít nhất 3 ký tự", + "usernameSpaces": "Tên người dùng không được chứa dấu cách", + "invalidEmail": "Vui lòng nhập một địa chỉ email hợp lệ", + "passwordLength": "Mật khẩu phải có ít nhất 8 ký tự", + "passwordsMatch": "Mật khẩu phải khớp", + "emailRequired": "Email là bắt buộc", + "emailOrUsernameRequired": "Email hoặc tên người dùng là bắt buộc", + "passwordRequired": "Mật khẩu là bắt buộc", + "passwordMinLength": "Mật khẩu phải có ít nhất 6 ký tự", + "nameRequired": "Tên là bắt buộc", + "required": "Trường này là bắt buộc" + }, + "registerWithInvite": { + "title": "Tạo Tài Khoản Của Bạn", + "description": "Điền thông tin bên dưới để tạo tài khoản của bạn", + "labels": { + "firstName": "Tên", + "firstNamePlaceholder": "Nhập tên của bạn", + "lastName": "Họ", + "lastNamePlaceholder": "Nhập họ của bạn", + "username": "Tên người dùng", + "usernamePlaceholder": "Chọn tên người dùng", + "email": "Email", + "emailPlaceholder": "Nhập email của bạn", + "password": "Mật khẩu", + "passwordPlaceholder": "Chọn mật khẩu", + "confirmPassword": "Xác nhận mật khẩu", + "confirmPasswordPlaceholder": "Xác nhận mật khẩu của bạn" + }, + "buttons": { + "creating": "Đang tạo tài khoản...", + "createAccount": "Tạo Tài Khoản" + }, + "validation": { + "firstNameRequired": "Tên là bắt buộc", + "lastNameRequired": "Họ là bắt buộc", + "usernameMinLength": "Tên người dùng phải có ít nhất 3 ký tự", + "invalidEmail": "Email không hợp lệ", + "passwordMinLength": "Mật khẩu phải có ít nhất 8 ký tự", + "passwordsMatch": "Mật khẩu phải khớp" + }, + "messages": { + "success": "Đã tạo tài khoản thành công! Đang chuyển hướng đến đăng nhập...", + "redirecting": "Đang chuyển hướng đến đăng nhập..." + }, + "errors": { + "invalidToken": "Lỗi với liên kết mời", + "tokenUsed": "Liên kết mời này đã được sử dụng", + "tokenExpired": "Liên kết mời này đã hết hạn", + "usernameExists": "Tên người dùng đã tồn tại", + "emailExists": "Email đã tồn tại", + "createFailed": "Không thể tạo tài khoản. Vui lòng thử lại." + }, + "pageTitle": "Tạo Tài Khoản" + } +} \ No newline at end of file diff --git a/apps/web/messages/zh-CN.json b/apps/web/messages/zh-CN.json index 1dc439d0..8b32fbbd 100644 --- a/apps/web/messages/zh-CN.json +++ b/apps/web/messages/zh-CN.json @@ -1,12 +1,14 @@ { "auth": { "errors": { - "account_inactive": "Conta inativa. Entre em contato com o administrador.", - "registration_disabled": "Registro via SSO está desabilitado.", - "token_expired": "Token expirado. Tente novamente.", - "config_error": "Erro de configuração. Contate o suporte.", - "auth_failed": "Falha na autenticação. Tente novamente." - } + "account_inactive": "账户未激活。请联系管理员。", + "registration_disabled": "通过SSO注册已被禁用。", + "token_expired": "令牌已过期。请重试。", + "config_error": "配置错误。请联系支持人员。", + "auth_failed": "认证失败。请重试。" + }, + "authenticationFailed": "身份验证失败", + "successfullyAuthenticated": "身份验证成功!" }, "authProviders": { "title": "身份验证提供商", @@ -174,7 +176,14 @@ "tabs": { "shareDetails": "分享详情", "selectFiles": "选择文件" - } + }, + "errors": { + "nameRequired": "分享名称为必填项", + "selectItems": "请至少选择一个文件或文件夹" + }, + "itemsSelected": "已选择 {count} 项", + "passwordPlaceholder": "输入密码", + "selectItemsPrompt": "选择要分享的文件和文件夹" }, "customization": { "breadcrumb": "自定义", @@ -340,7 +349,8 @@ "addToShare": "添加到共享", "removeFromShare": "从共享中移除", "saveChanges": "保存更改", - "editFolder": "编辑文件夹" + "editFolder": "编辑文件夹", + "itemsSelected": "已选择 {count} 项" }, "files": { "title": "所有文件", @@ -376,7 +386,12 @@ "description": "上传您的第一个文件或创建文件夹以开始使用" }, "files": "文件", - "folders": "文件夹" + "folders": "文件夹", + "errors": { + "moveItemsFailed": "移动项目失败,请重试。", + "cannotMoveHere": "无法将项目移动到此位置" + }, + "openFolder": "打开文件夹" }, "filesTable": { "ariaLabel": "文件表格", @@ -539,7 +554,10 @@ "movingTo": "移动到:", "title": "移动 {count, plural, =1 {项目} other {项目}}", "description": "将 {count, plural, =1 {项目} other {项目}} 移动到新位置", - "success": "成功移动了 {count} 个项目" + "success": "成功移动了 {count} 个项目", + "errors": { + "moveFailed": "移动项目失败" + } }, "navbar": { "logoAlt": "应用Logo", @@ -1153,8 +1171,6 @@ }, "fileActions": { "edit": "编辑", - "save": "保存", - "cancel": "取消", "preview": "预览", "download": "下载", "delete": "删除", @@ -1377,22 +1393,12 @@ "deleteTitle": "删除共享", "deleteConfirmation": "您确定要删除此共享吗?此操作不可撤销。", "editTitle": "编辑共享", - "nameLabel": "共享名称", - "expirationLabel": "过期日期", - "expirationPlaceholder": "MM/DD/YYYY HH:MM", - "maxViewsLabel": "最大查看次数", - "maxViewsPlaceholder": "留空表示无限", - "passwordProtection": "密码保护", - "passwordLabel": "密码", - "passwordPlaceholder": "请输入密码", "newPasswordLabel": "新密码(留空表示保持当前密码)", "newPasswordPlaceholder": "请输入新密码", "manageFilesTitle": "管理文件", "manageRecipientsTitle": "管理收件人", "editSuccess": "共享更新成功", "editError": "更新共享失败", - "descriptionPlaceholder": "输入描述(可选)", - "descriptionLabel": "描述", "bulkDeleteConfirmation": "您确定要删除{count, plural, =1 {1个共享} other {#个共享}}吗?此操作无法撤销。", "bulkDeleteTitle": "删除选中的共享", "addDescriptionPlaceholder": "添加描述...", @@ -1405,7 +1411,9 @@ "linkDescriptionFile": "生成自定义链接以分享文件", "linkDescriptionFolder": "生成自定义链接以分享文件夹", "linkReady": "您的分享链接已准备好:", - "linkTitle": "生成链接" + "linkTitle": "生成链接", + "itemsSelected": "已选择 {count} 项", + "manageFilesDescription": "选择要包含在此分享中的文件和文件夹" }, "shareDetails": { "title": "共享详情", @@ -1435,7 +1443,6 @@ "generateLink": "生成链接", "noLink": "尚未生成链接", "description": "描述", - "linkCopied": "链接已复制到剪贴板", "editSecurity": "编辑安全", "editExpiration": "编辑过期", "clickToEnlargeQrCode": "点击放大QR Code", @@ -1469,28 +1476,6 @@ "expires": "过期:", "expirationDate": "过期日期" }, - "shareFile": { - "title": "分享文件", - "linkTitle": "生成链接", - "nameLabel": "分享名称", - "namePlaceholder": "输入分享名称", - "expirationLabel": "过期日期", - "expirationPlaceholder": "MM/DD/YYYY HH:MM", - "maxViewsLabel": "最大查看次数", - "maxViewsPlaceholder": "留空为无限制", - "passwordProtection": "密码保护", - "passwordLabel": "密码", - "passwordPlaceholder": "输入密码", - "linkDescription": "生成自定义链接来分享文件", - "aliasLabel": "链接别名", - "aliasPlaceholder": "输入自定义别名", - "linkReady": "您的分享链接已准备就绪:", - "createShare": "创建分享", - "generateLink": "生成链接", - "copyLink": "复制链接", - "descriptionLabel": "描述", - "descriptionPlaceholder": "输入描述(可选)" - }, "shareManager": { "deleteSuccess": "共享删除成功", "deleteError": "共享删除失败", @@ -1520,7 +1505,10 @@ "noFilesToDownload": "无需下载文件", "singleShareZipName": "{sharename} _files.zip", "zipDownloadError": "无法创建zip文件", - "zipDownloadSuccess": "zip文件成功下载了" + "zipDownloadSuccess": "zip文件成功下载了", + "errors": { + "multipleDownloadNotSupported": "暂不支持多个分享下载 - 请分别下载各个分享" + } }, "shareMultipleFiles": { "title": "分享多个文件", @@ -1917,6 +1905,23 @@ "inactive": "停用", "admin": "管理员", "userr": "用户" + }, + "invite": { + "button": "生成邀请链接", + "title": "生成用户邀请链接", + "description": "生成一个一次性使用的链接,允许某人创建自己的账户。该链接在15分钟后过期。", + "generating": "生成中...", + "generate": "生成链接", + "generated": "邀请链接生成成功!", + "linkReady": "邀请链接准备就绪", + "linkReadyDescription": "与您想要邀请的人分享此链接。他们将能够作为普通用户创建自己的账户。", + "copyLink": "复制链接", + "linkCopied": "邀请链接已复制到剪贴板!", + "expiresIn": "15分钟后过期", + "close": "关闭", + "errors": { + "generateFailed": "生成邀请链接失败" + } } }, "validation": { @@ -1935,15 +1940,62 @@ "required": "此字段为必填项" }, "embedCode": { - "title": "嵌入图片", - "description": "使用这些代码将此图片嵌入到论坛、网站或其他平台中", + "title": "嵌入媒体", + "description": "使用这些代码将此媒体嵌入到论坛、网站或其他平台中", "tabs": { "directLink": "直接链接", "html": "HTML", "bbcode": "BBCode" }, - "directLinkDescription": "图片文件的直接URL", - "htmlDescription": "使用此代码将图片嵌入HTML页面", - "bbcodeDescription": "使用此代码将图片嵌入支持BBCode的论坛" + "directLinkDescription": "媒体文件的直接URL", + "htmlDescription": "使用此代码将媒体嵌入HTML页面", + "bbcodeDescription": "使用此代码将媒体嵌入支持BBCode的论坛" + }, + "contextMenu": { + "newFolder": "新建文件夹", + "uploadFile": "上传文件" + }, + "registerWithInvite": { + "title": "创建您的账户", + "description": "填写以下信息以创建您的账户", + "labels": { + "firstName": "名字", + "firstNamePlaceholder": "输入您的名字", + "lastName": "姓氏", + "lastNamePlaceholder": "输入您的姓氏", + "username": "用户名", + "usernamePlaceholder": "选择一个用户名", + "email": "电子邮件", + "emailPlaceholder": "输入您的电子邮件", + "password": "密码", + "passwordPlaceholder": "选择一个密码", + "confirmPassword": "确认密码", + "confirmPasswordPlaceholder": "确认您的密码" + }, + "buttons": { + "creating": "正在创建账户...", + "createAccount": "创建账户" + }, + "validation": { + "firstNameRequired": "名字为必填项", + "lastNameRequired": "姓氏为必填项", + "usernameMinLength": "用户名至少需要3个字符", + "invalidEmail": "无效的电子邮件", + "passwordMinLength": "密码至少需要8个字符", + "passwordsMatch": "密码必须匹配" + }, + "messages": { + "success": "账户创建成功!正在重定向到登录...", + "redirecting": "正在重定向到登录..." + }, + "errors": { + "invalidToken": "邀请链接错误", + "tokenUsed": "此邀请链接已被使用", + "tokenExpired": "此邀请链接已过期", + "usernameExists": "用户名已存在", + "emailExists": "电子邮件已存在", + "createFailed": "创建账户失败。请重试。" + }, + "pageTitle": "创建账户" } } \ No newline at end of file diff --git a/apps/web/package.json b/apps/web/package.json index 438299f0..340d2fbd 100644 --- a/apps/web/package.json +++ b/apps/web/package.json @@ -1,6 +1,6 @@ { "name": "palmr-web", - "version": "3.2.5-beta", + "version": "3.3.0-beta", "description": "Frontend for Palmr", "private": true, "author": "Daniel Luiz Alves ", @@ -27,7 +27,8 @@ "translations:check": "python3 scripts/run_translations.py check", "translations:sync": "python3 scripts/run_translations.py sync", "translations:dry-run": "python3 scripts/run_translations.py all --dry-run", - "translations:help": "python3 scripts/run_translations.py help" + "translations:help": "python3 scripts/run_translations.py help", + "translations:prune": "python3 scripts/prune_translations.py" }, "dependencies": { "@hello-pangea/dnd": "^18.0.1", @@ -36,6 +37,7 @@ "@radix-ui/react-avatar": "^1.1.4", "@radix-ui/react-checkbox": "^1.3.2", "@radix-ui/react-collapsible": "^1.1.12", + "@radix-ui/react-context-menu": "^2.2.16", "@radix-ui/react-dialog": "^1.1.6", "@radix-ui/react-dropdown-menu": "^2.1.6", "@radix-ui/react-label": "^2.1.2", @@ -49,6 +51,8 @@ "@radix-ui/react-tabs": "^1.1.12", "@tabler/icons-react": "^3.34.0", "@types/react-dropzone": "^5.1.0", + "@uppy/aws-s3": "^4.3.2", + "@uppy/core": "^4.5.2", "axios": "^1.10.0", "class-variance-authority": "^0.7.1", "clsx": "^2.1.1", diff --git a/apps/web/pnpm-lock.yaml b/apps/web/pnpm-lock.yaml index 1d8b4dc8..e9718c5e 100644 --- a/apps/web/pnpm-lock.yaml +++ b/apps/web/pnpm-lock.yaml @@ -1,69 +1,79 @@ -lockfileVersion: "9.0" +lockfileVersion: '9.0' settings: autoInstallPeers: true excludeLinksFromLockfile: false importers: + .: dependencies: - "@hello-pangea/dnd": + '@hello-pangea/dnd': specifier: ^18.0.1 version: 18.0.1(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) - "@hookform/resolvers": + '@hookform/resolvers': specifier: ^5.0.1 version: 5.1.1(react-hook-form@7.60.0(react@19.1.0)) - "@radix-ui/react-aspect-ratio": + '@radix-ui/react-aspect-ratio': specifier: ^1.1.3 version: 1.1.7(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) - "@radix-ui/react-avatar": + '@radix-ui/react-avatar': specifier: ^1.1.4 version: 1.1.10(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) - "@radix-ui/react-checkbox": + '@radix-ui/react-checkbox': specifier: ^1.3.2 version: 1.3.2(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) - "@radix-ui/react-collapsible": + '@radix-ui/react-collapsible': specifier: ^1.1.12 version: 1.1.12(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) - "@radix-ui/react-dialog": + '@radix-ui/react-context-menu': + specifier: ^2.2.16 + version: 2.2.16(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + '@radix-ui/react-dialog': specifier: ^1.1.6 version: 1.1.14(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) - "@radix-ui/react-dropdown-menu": + '@radix-ui/react-dropdown-menu': specifier: ^2.1.6 version: 2.1.15(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) - "@radix-ui/react-label": + '@radix-ui/react-label': specifier: ^2.1.2 version: 2.1.7(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) - "@radix-ui/react-progress": + '@radix-ui/react-progress': specifier: ^1.1.3 version: 1.1.7(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) - "@radix-ui/react-scroll-area": + '@radix-ui/react-scroll-area': specifier: ^1.2.4 version: 1.2.9(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) - "@radix-ui/react-select": + '@radix-ui/react-select': specifier: ^2.1.7 version: 2.2.5(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) - "@radix-ui/react-separator": + '@radix-ui/react-separator': specifier: ^1.1.3 version: 1.1.7(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) - "@radix-ui/react-slider": + '@radix-ui/react-slider': specifier: ^1.3.5 version: 1.3.5(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) - "@radix-ui/react-slot": + '@radix-ui/react-slot': specifier: ^1.1.2 version: 1.2.3(@types/react@19.1.8)(react@19.1.0) - "@radix-ui/react-switch": + '@radix-ui/react-switch': specifier: ^1.1.4 version: 1.2.5(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) - "@radix-ui/react-tabs": + '@radix-ui/react-tabs': specifier: ^1.1.12 version: 1.1.12(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) - "@tabler/icons-react": + '@tabler/icons-react': specifier: ^3.34.0 version: 3.34.0(react@19.1.0) - "@types/react-dropzone": + '@types/react-dropzone': specifier: ^5.1.0 version: 5.1.0(react@19.1.0) + '@uppy/aws-s3': + specifier: ^4.3.2 + version: 4.3.2(@uppy/core@4.5.3) + '@uppy/core': + specifier: ^4.5.2 + version: 4.5.3 axios: specifier: ^1.10.0 version: 1.10.0 @@ -152,37 +162,37 @@ importers: specifier: ^5.0.6 version: 5.0.6(@types/react@19.1.8)(react@19.1.0)(use-sync-external-store@1.5.0(react@19.1.0)) devDependencies: - "@eslint/eslintrc": + '@eslint/eslintrc': specifier: 3.3.1 version: 3.3.1 - "@eslint/js": + '@eslint/js': specifier: 9.30.0 version: 9.30.0 - "@ianvs/prettier-plugin-sort-imports": + '@ianvs/prettier-plugin-sort-imports': specifier: 4.4.2 version: 4.4.2(prettier@3.6.2) - "@tailwindcss/postcss": + '@tailwindcss/postcss': specifier: 4.1.11 version: 4.1.11 - "@types/js-cookie": + '@types/js-cookie': specifier: ^3.0.6 version: 3.0.6 - "@types/node": + '@types/node': specifier: 22.14.0 version: 22.14.0 - "@types/qrcode": + '@types/qrcode': specifier: ^1.5.5 version: 1.5.5 - "@types/react": + '@types/react': specifier: 19.1.8 version: 19.1.8 - "@types/react-dom": + '@types/react-dom': specifier: 19.1.6 version: 19.1.6(@types/react@19.1.8) - "@typescript-eslint/eslint-plugin": + '@typescript-eslint/eslint-plugin': specifier: 8.35.1 version: 8.35.1(@typescript-eslint/parser@8.35.1(eslint@9.30.0(jiti@2.4.2))(typescript@5.8.3))(eslint@9.30.0(jiti@2.4.2))(typescript@5.8.3) - "@typescript-eslint/parser": + '@typescript-eslint/parser': specifier: 8.35.1 version: 8.35.1(eslint@9.30.0(jiti@2.4.2))(typescript@5.8.3) eslint: @@ -211,1810 +221,1628 @@ importers: version: 5.8.3 packages: - "@alloc/quick-lru@5.2.0": - resolution: - { integrity: sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw== } - engines: { node: ">=10" } - - "@ampproject/remapping@2.3.0": - resolution: - { integrity: sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw== } - engines: { node: ">=6.0.0" } - - "@babel/code-frame@7.27.1": - resolution: - { integrity: sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg== } - engines: { node: ">=6.9.0" } - - "@babel/generator@7.28.0": - resolution: - { integrity: sha512-lJjzvrbEeWrhB4P3QBsH7tey117PjLZnDbLiQEKjQ/fNJTjuq4HSqgFA+UNSwZT8D7dxxbnuSBMsa1lrWzKlQg== } - engines: { node: ">=6.9.0" } - - "@babel/helper-globals@7.28.0": - resolution: - { integrity: sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw== } - engines: { node: ">=6.9.0" } - - "@babel/helper-string-parser@7.27.1": - resolution: - { integrity: sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA== } - engines: { node: ">=6.9.0" } - - "@babel/helper-validator-identifier@7.27.1": - resolution: - { integrity: sha512-D2hP9eA+Sqx1kBZgzxZh0y1trbuU+JoDkiEwqhQ36nodYqJwyEIhPSdMNd7lOm/4io72luTPWH20Yda0xOuUow== } - engines: { node: ">=6.9.0" } - - "@babel/parser@7.28.0": - resolution: - { integrity: sha512-jVZGvOxOuNSsuQuLRTh13nU0AogFlw32w/MT+LV6D3sP5WdbW61E77RnkbaO2dUvmPAYrBDJXGn5gGS6tH4j8g== } - engines: { node: ">=6.0.0" } + + '@alloc/quick-lru@5.2.0': + resolution: {integrity: sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw==} + engines: {node: '>=10'} + + '@ampproject/remapping@2.3.0': + resolution: {integrity: sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==} + engines: {node: '>=6.0.0'} + + '@babel/code-frame@7.27.1': + resolution: {integrity: sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg==} + engines: {node: '>=6.9.0'} + + '@babel/generator@7.28.0': + resolution: {integrity: sha512-lJjzvrbEeWrhB4P3QBsH7tey117PjLZnDbLiQEKjQ/fNJTjuq4HSqgFA+UNSwZT8D7dxxbnuSBMsa1lrWzKlQg==} + engines: {node: '>=6.9.0'} + + '@babel/helper-globals@7.28.0': + resolution: {integrity: sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw==} + engines: {node: '>=6.9.0'} + + '@babel/helper-string-parser@7.27.1': + resolution: {integrity: sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==} + engines: {node: '>=6.9.0'} + + '@babel/helper-validator-identifier@7.27.1': + resolution: {integrity: sha512-D2hP9eA+Sqx1kBZgzxZh0y1trbuU+JoDkiEwqhQ36nodYqJwyEIhPSdMNd7lOm/4io72luTPWH20Yda0xOuUow==} + engines: {node: '>=6.9.0'} + + '@babel/parser@7.28.0': + resolution: {integrity: sha512-jVZGvOxOuNSsuQuLRTh13nU0AogFlw32w/MT+LV6D3sP5WdbW61E77RnkbaO2dUvmPAYrBDJXGn5gGS6tH4j8g==} + engines: {node: '>=6.0.0'} hasBin: true - "@babel/runtime@7.27.6": - resolution: - { integrity: sha512-vbavdySgbTTrmFE+EsiqUTzlOr5bzlnJtUv9PynGCAKvfQqjIXbvFdumPM/GxMDfyuGMJaJAU6TO4zc1Jf1i8Q== } - engines: { node: ">=6.9.0" } - - "@babel/template@7.27.2": - resolution: - { integrity: sha512-LPDZ85aEJyYSd18/DkjNh4/y1ntkE5KwUHWTiqgRxruuZL2F1yuHligVHLvcHY2vMHXttKFpJn6LwfI7cw7ODw== } - engines: { node: ">=6.9.0" } - - "@babel/traverse@7.28.0": - resolution: - { integrity: sha512-mGe7UK5wWyh0bKRfupsUchrQGqvDbZDbKJw+kcRGSmdHVYrv+ltd0pnpDTVpiTqnaBru9iEvA8pz8W46v0Amwg== } - engines: { node: ">=6.9.0" } - - "@babel/types@7.28.0": - resolution: - { integrity: sha512-jYnje+JyZG5YThjHiF28oT4SIZLnYOcSBb6+SDaFIyzDVSkXQmQQYclJ2R+YxcdmK0AX6x1E5OQNtuh3jHDrUg== } - engines: { node: ">=6.9.0" } - - "@emnapi/core@1.4.3": - resolution: - { integrity: sha512-4m62DuCE07lw01soJwPiBGC0nAww0Q+RY70VZ+n49yDIO13yyinhbWCeNnaob0lakDtWQzSdtNWzJeOJt2ma+g== } - - "@emnapi/runtime@1.4.3": - resolution: - { integrity: sha512-pBPWdu6MLKROBX05wSNKcNb++m5Er+KQ9QkB+WVM+pW2Kx9hoSrVTnu3BdkI5eBLZoKu/J6mW/B6i6bJB2ytXQ== } - - "@emnapi/wasi-threads@1.0.2": - resolution: - { integrity: sha512-5n3nTJblwRi8LlXkJ9eBzu+kZR8Yxcc7ubakyQTFzPMtIhFpUBRbsnc2Dv88IZDIbCDlBiWrknhB4Lsz7mg6BA== } - - "@eslint-community/eslint-utils@4.7.0": - resolution: - { integrity: sha512-dyybb3AcajC7uha6CvhdVRJqaKyn7w2YKqKyAN37NKYgZT36w+iRb0Dymmc5qEJ549c/S31cMMSFd75bteCpCw== } - engines: { node: ^12.22.0 || ^14.17.0 || >=16.0.0 } + '@babel/runtime@7.27.6': + resolution: {integrity: sha512-vbavdySgbTTrmFE+EsiqUTzlOr5bzlnJtUv9PynGCAKvfQqjIXbvFdumPM/GxMDfyuGMJaJAU6TO4zc1Jf1i8Q==} + engines: {node: '>=6.9.0'} + + '@babel/template@7.27.2': + resolution: {integrity: sha512-LPDZ85aEJyYSd18/DkjNh4/y1ntkE5KwUHWTiqgRxruuZL2F1yuHligVHLvcHY2vMHXttKFpJn6LwfI7cw7ODw==} + engines: {node: '>=6.9.0'} + + '@babel/traverse@7.28.0': + resolution: {integrity: sha512-mGe7UK5wWyh0bKRfupsUchrQGqvDbZDbKJw+kcRGSmdHVYrv+ltd0pnpDTVpiTqnaBru9iEvA8pz8W46v0Amwg==} + engines: {node: '>=6.9.0'} + + '@babel/types@7.28.0': + resolution: {integrity: sha512-jYnje+JyZG5YThjHiF28oT4SIZLnYOcSBb6+SDaFIyzDVSkXQmQQYclJ2R+YxcdmK0AX6x1E5OQNtuh3jHDrUg==} + engines: {node: '>=6.9.0'} + + '@emnapi/core@1.4.3': + resolution: {integrity: sha512-4m62DuCE07lw01soJwPiBGC0nAww0Q+RY70VZ+n49yDIO13yyinhbWCeNnaob0lakDtWQzSdtNWzJeOJt2ma+g==} + + '@emnapi/runtime@1.4.3': + resolution: {integrity: sha512-pBPWdu6MLKROBX05wSNKcNb++m5Er+KQ9QkB+WVM+pW2Kx9hoSrVTnu3BdkI5eBLZoKu/J6mW/B6i6bJB2ytXQ==} + + '@emnapi/wasi-threads@1.0.2': + resolution: {integrity: sha512-5n3nTJblwRi8LlXkJ9eBzu+kZR8Yxcc7ubakyQTFzPMtIhFpUBRbsnc2Dv88IZDIbCDlBiWrknhB4Lsz7mg6BA==} + + '@eslint-community/eslint-utils@4.7.0': + resolution: {integrity: sha512-dyybb3AcajC7uha6CvhdVRJqaKyn7w2YKqKyAN37NKYgZT36w+iRb0Dymmc5qEJ549c/S31cMMSFd75bteCpCw==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} peerDependencies: eslint: ^6.0.0 || ^7.0.0 || >=8.0.0 - "@eslint-community/regexpp@4.12.1": - resolution: - { integrity: sha512-CCZCDJuduB9OUkFkY2IgppNZMi2lBQgD2qzwXkEia16cge2pijY/aXi96CJMquDMn3nJdlPV1A5KrJEXwfLNzQ== } - engines: { node: ^12.0.0 || ^14.0.0 || >=16.0.0 } - - "@eslint/config-array@0.21.0": - resolution: - { integrity: sha512-ENIdc4iLu0d93HeYirvKmrzshzofPw6VkZRKQGe9Nv46ZnWUzcF1xV01dcvEg/1wXUR61OmmlSfyeyO7EvjLxQ== } - engines: { node: ^18.18.0 || ^20.9.0 || >=21.1.0 } - - "@eslint/config-helpers@0.3.0": - resolution: - { integrity: sha512-ViuymvFmcJi04qdZeDc2whTHryouGcDlaxPqarTD0ZE10ISpxGUVZGZDx4w01upyIynL3iu6IXH2bS1NhclQMw== } - engines: { node: ^18.18.0 || ^20.9.0 || >=21.1.0 } - - "@eslint/core@0.14.0": - resolution: - { integrity: sha512-qIbV0/JZr7iSDjqAc60IqbLdsj9GDt16xQtWD+B78d/HAlvysGdZZ6rpJHGAc2T0FQx1X6thsSPdnoiGKdNtdg== } - engines: { node: ^18.18.0 || ^20.9.0 || >=21.1.0 } - - "@eslint/core@0.15.1": - resolution: - { integrity: sha512-bkOp+iumZCCbt1K1CmWf0R9pM5yKpDv+ZXtvSyQpudrI9kuFLp+bM2WOPXImuD/ceQuaa8f5pj93Y7zyECIGNA== } - engines: { node: ^18.18.0 || ^20.9.0 || >=21.1.0 } - - "@eslint/eslintrc@3.3.1": - resolution: - { integrity: sha512-gtF186CXhIl1p4pJNGZw8Yc6RlshoePRvE0X91oPGb3vZ8pM3qOS9W9NGPat9LziaBV7XrJWGylNQXkGcnM3IQ== } - engines: { node: ^18.18.0 || ^20.9.0 || >=21.1.0 } - - "@eslint/js@9.30.0": - resolution: - { integrity: sha512-Wzw3wQwPvc9sHM+NjakWTcPx11mbZyiYHuwWa/QfZ7cIRX7WK54PSk7bdyXDaoaopUcMatv1zaQvOAAO8hCdww== } - engines: { node: ^18.18.0 || ^20.9.0 || >=21.1.0 } - - "@eslint/object-schema@2.1.6": - resolution: - { integrity: sha512-RBMg5FRL0I0gs51M/guSAj5/e14VQ4tpZnQNWwuDT66P14I43ItmPfIZRhO9fUVIPOAQXU47atlywZ/czoqFPA== } - engines: { node: ^18.18.0 || ^20.9.0 || >=21.1.0 } - - "@eslint/plugin-kit@0.3.3": - resolution: - { integrity: sha512-1+WqvgNMhmlAambTvT3KPtCl/Ibr68VldY2XY40SL1CE0ZXiakFR/cbTspaF5HsnpDMvcYYoJHfl4980NBjGag== } - engines: { node: ^18.18.0 || ^20.9.0 || >=21.1.0 } - - "@floating-ui/core@1.7.2": - resolution: - { integrity: sha512-wNB5ooIKHQc+Kui96jE/n69rHFWAVoxn5CAzL1Xdd8FG03cgY3MLO+GF9U3W737fYDSgPWA6MReKhBQBop6Pcw== } - - "@floating-ui/dom@1.7.2": - resolution: - { integrity: sha512-7cfaOQuCS27HD7DX+6ib2OrnW+b4ZBwDNnCcT0uTyidcmyWb03FnQqJybDBoCnpdxwBSfA94UAYlRCt7mV+TbA== } - - "@floating-ui/react-dom@2.1.4": - resolution: - { integrity: sha512-JbbpPhp38UmXDDAu60RJmbeme37Jbgsm7NrHGgzYYFKmblzRUh6Pa641dII6LsjwF4XlScDrde2UAzDo/b9KPw== } + '@eslint-community/regexpp@4.12.1': + resolution: {integrity: sha512-CCZCDJuduB9OUkFkY2IgppNZMi2lBQgD2qzwXkEia16cge2pijY/aXi96CJMquDMn3nJdlPV1A5KrJEXwfLNzQ==} + engines: {node: ^12.0.0 || ^14.0.0 || >=16.0.0} + + '@eslint/config-array@0.21.0': + resolution: {integrity: sha512-ENIdc4iLu0d93HeYirvKmrzshzofPw6VkZRKQGe9Nv46ZnWUzcF1xV01dcvEg/1wXUR61OmmlSfyeyO7EvjLxQ==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@eslint/config-helpers@0.3.0': + resolution: {integrity: sha512-ViuymvFmcJi04qdZeDc2whTHryouGcDlaxPqarTD0ZE10ISpxGUVZGZDx4w01upyIynL3iu6IXH2bS1NhclQMw==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@eslint/core@0.14.0': + resolution: {integrity: sha512-qIbV0/JZr7iSDjqAc60IqbLdsj9GDt16xQtWD+B78d/HAlvysGdZZ6rpJHGAc2T0FQx1X6thsSPdnoiGKdNtdg==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@eslint/core@0.15.1': + resolution: {integrity: sha512-bkOp+iumZCCbt1K1CmWf0R9pM5yKpDv+ZXtvSyQpudrI9kuFLp+bM2WOPXImuD/ceQuaa8f5pj93Y7zyECIGNA==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@eslint/eslintrc@3.3.1': + resolution: {integrity: sha512-gtF186CXhIl1p4pJNGZw8Yc6RlshoePRvE0X91oPGb3vZ8pM3qOS9W9NGPat9LziaBV7XrJWGylNQXkGcnM3IQ==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@eslint/js@9.30.0': + resolution: {integrity: sha512-Wzw3wQwPvc9sHM+NjakWTcPx11mbZyiYHuwWa/QfZ7cIRX7WK54PSk7bdyXDaoaopUcMatv1zaQvOAAO8hCdww==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@eslint/object-schema@2.1.6': + resolution: {integrity: sha512-RBMg5FRL0I0gs51M/guSAj5/e14VQ4tpZnQNWwuDT66P14I43ItmPfIZRhO9fUVIPOAQXU47atlywZ/czoqFPA==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@eslint/plugin-kit@0.3.3': + resolution: {integrity: sha512-1+WqvgNMhmlAambTvT3KPtCl/Ibr68VldY2XY40SL1CE0ZXiakFR/cbTspaF5HsnpDMvcYYoJHfl4980NBjGag==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@floating-ui/core@1.7.2': + resolution: {integrity: sha512-wNB5ooIKHQc+Kui96jE/n69rHFWAVoxn5CAzL1Xdd8FG03cgY3MLO+GF9U3W737fYDSgPWA6MReKhBQBop6Pcw==} + + '@floating-ui/dom@1.7.2': + resolution: {integrity: sha512-7cfaOQuCS27HD7DX+6ib2OrnW+b4ZBwDNnCcT0uTyidcmyWb03FnQqJybDBoCnpdxwBSfA94UAYlRCt7mV+TbA==} + + '@floating-ui/react-dom@2.1.4': + resolution: {integrity: sha512-JbbpPhp38UmXDDAu60RJmbeme37Jbgsm7NrHGgzYYFKmblzRUh6Pa641dII6LsjwF4XlScDrde2UAzDo/b9KPw==} peerDependencies: - react: ">=16.8.0" - react-dom: ">=16.8.0" + react: '>=16.8.0' + react-dom: '>=16.8.0' - "@floating-ui/utils@0.2.10": - resolution: - { integrity: sha512-aGTxbpbg8/b5JfU1HXSrbH3wXZuLPJcNEcZQFMxLs3oSzgtVu6nFPkbbGGUvBcUjKV2YyB9Wxxabo+HEH9tcRQ== } + '@floating-ui/utils@0.2.10': + resolution: {integrity: sha512-aGTxbpbg8/b5JfU1HXSrbH3wXZuLPJcNEcZQFMxLs3oSzgtVu6nFPkbbGGUvBcUjKV2YyB9Wxxabo+HEH9tcRQ==} - "@formatjs/ecma402-abstract@2.3.4": - resolution: - { integrity: sha512-qrycXDeaORzIqNhBOx0btnhpD1c+/qFIHAN9znofuMJX6QBwtbrmlpWfD4oiUUD2vJUOIYFA/gYtg2KAMGG7sA== } + '@formatjs/ecma402-abstract@2.3.4': + resolution: {integrity: sha512-qrycXDeaORzIqNhBOx0btnhpD1c+/qFIHAN9znofuMJX6QBwtbrmlpWfD4oiUUD2vJUOIYFA/gYtg2KAMGG7sA==} - "@formatjs/fast-memoize@2.2.7": - resolution: - { integrity: sha512-Yabmi9nSvyOMrlSeGGWDiH7rf3a7sIwplbvo/dlz9WCIjzIQAfy1RMf4S0X3yG724n5Ghu2GmEl5NJIV6O9sZQ== } + '@formatjs/fast-memoize@2.2.7': + resolution: {integrity: sha512-Yabmi9nSvyOMrlSeGGWDiH7rf3a7sIwplbvo/dlz9WCIjzIQAfy1RMf4S0X3yG724n5Ghu2GmEl5NJIV6O9sZQ==} - "@formatjs/icu-messageformat-parser@2.11.2": - resolution: - { integrity: sha512-AfiMi5NOSo2TQImsYAg8UYddsNJ/vUEv/HaNqiFjnI3ZFfWihUtD5QtuX6kHl8+H+d3qvnE/3HZrfzgdWpsLNA== } + '@formatjs/icu-messageformat-parser@2.11.2': + resolution: {integrity: sha512-AfiMi5NOSo2TQImsYAg8UYddsNJ/vUEv/HaNqiFjnI3ZFfWihUtD5QtuX6kHl8+H+d3qvnE/3HZrfzgdWpsLNA==} - "@formatjs/icu-skeleton-parser@1.8.14": - resolution: - { integrity: sha512-i4q4V4qslThK4Ig8SxyD76cp3+QJ3sAqr7f6q9VVfeGtxG9OhiAk3y9XF6Q41OymsKzsGQ6OQQoJNY4/lI8TcQ== } + '@formatjs/icu-skeleton-parser@1.8.14': + resolution: {integrity: sha512-i4q4V4qslThK4Ig8SxyD76cp3+QJ3sAqr7f6q9VVfeGtxG9OhiAk3y9XF6Q41OymsKzsGQ6OQQoJNY4/lI8TcQ==} - "@formatjs/intl-localematcher@0.5.10": - resolution: - { integrity: sha512-af3qATX+m4Rnd9+wHcjJ4w2ijq+rAVP3CCinJQvFv1kgSu1W6jypUmvleJxcewdxmutM8dmIRZFxO/IQBZmP2Q== } + '@formatjs/intl-localematcher@0.5.10': + resolution: {integrity: sha512-af3qATX+m4Rnd9+wHcjJ4w2ijq+rAVP3CCinJQvFv1kgSu1W6jypUmvleJxcewdxmutM8dmIRZFxO/IQBZmP2Q==} - "@formatjs/intl-localematcher@0.6.1": - resolution: - { integrity: sha512-ePEgLgVCqi2BBFnTMWPfIghu6FkbZnnBVhO2sSxvLfrdFw7wCHAHiDoM2h4NRgjbaY7+B7HgOLZGkK187pZTZg== } + '@formatjs/intl-localematcher@0.6.1': + resolution: {integrity: sha512-ePEgLgVCqi2BBFnTMWPfIghu6FkbZnnBVhO2sSxvLfrdFw7wCHAHiDoM2h4NRgjbaY7+B7HgOLZGkK187pZTZg==} - "@hello-pangea/dnd@18.0.1": - resolution: - { integrity: sha512-xojVWG8s/TGrKT1fC8K2tIWeejJYTAeJuj36zM//yEm/ZrnZUSFGS15BpO+jGZT1ybWvyXmeDJwPYb4dhWlbZQ== } + '@hello-pangea/dnd@18.0.1': + resolution: {integrity: sha512-xojVWG8s/TGrKT1fC8K2tIWeejJYTAeJuj36zM//yEm/ZrnZUSFGS15BpO+jGZT1ybWvyXmeDJwPYb4dhWlbZQ==} peerDependencies: react: ^18.0.0 || ^19.0.0 react-dom: ^18.0.0 || ^19.0.0 - "@hookform/resolvers@5.1.1": - resolution: - { integrity: sha512-J/NVING3LMAEvexJkyTLjruSm7aOFx7QX21pzkiJfMoNG0wl5aFEjLTl7ay7IQb9EWY6AkrBy7tHL2Alijpdcg== } + '@hookform/resolvers@5.1.1': + resolution: {integrity: sha512-J/NVING3LMAEvexJkyTLjruSm7aOFx7QX21pzkiJfMoNG0wl5aFEjLTl7ay7IQb9EWY6AkrBy7tHL2Alijpdcg==} peerDependencies: react-hook-form: ^7.55.0 - "@humanfs/core@0.19.1": - resolution: - { integrity: sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA== } - engines: { node: ">=18.18.0" } - - "@humanfs/node@0.16.6": - resolution: - { integrity: sha512-YuI2ZHQL78Q5HbhDiBA1X4LmYdXCKCMQIfw0pw7piHJwyREFebJUvrQN4cMssyES6x+vfUbx1CIpaQUKYdQZOw== } - engines: { node: ">=18.18.0" } - - "@humanwhocodes/module-importer@1.0.1": - resolution: - { integrity: sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA== } - engines: { node: ">=12.22" } - - "@humanwhocodes/retry@0.3.1": - resolution: - { integrity: sha512-JBxkERygn7Bv/GbN5Rv8Ul6LVknS+5Bp6RgDC/O8gEBU/yeH5Ui5C/OlWrTb6qct7LjjfT6Re2NxB0ln0yYybA== } - engines: { node: ">=18.18" } - - "@humanwhocodes/retry@0.4.3": - resolution: - { integrity: sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ== } - engines: { node: ">=18.18" } - - "@ianvs/prettier-plugin-sort-imports@4.4.2": - resolution: - { integrity: sha512-KkVFy3TLh0OFzimbZglMmORi+vL/i2OFhEs5M07R9w0IwWAGpsNNyE4CY/2u0YoMF5bawKC2+8/fUH60nnNtjw== } + '@humanfs/core@0.19.1': + resolution: {integrity: sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==} + engines: {node: '>=18.18.0'} + + '@humanfs/node@0.16.6': + resolution: {integrity: sha512-YuI2ZHQL78Q5HbhDiBA1X4LmYdXCKCMQIfw0pw7piHJwyREFebJUvrQN4cMssyES6x+vfUbx1CIpaQUKYdQZOw==} + engines: {node: '>=18.18.0'} + + '@humanwhocodes/module-importer@1.0.1': + resolution: {integrity: sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==} + engines: {node: '>=12.22'} + + '@humanwhocodes/retry@0.3.1': + resolution: {integrity: sha512-JBxkERygn7Bv/GbN5Rv8Ul6LVknS+5Bp6RgDC/O8gEBU/yeH5Ui5C/OlWrTb6qct7LjjfT6Re2NxB0ln0yYybA==} + engines: {node: '>=18.18'} + + '@humanwhocodes/retry@0.4.3': + resolution: {integrity: sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==} + engines: {node: '>=18.18'} + + '@ianvs/prettier-plugin-sort-imports@4.4.2': + resolution: {integrity: sha512-KkVFy3TLh0OFzimbZglMmORi+vL/i2OFhEs5M07R9w0IwWAGpsNNyE4CY/2u0YoMF5bawKC2+8/fUH60nnNtjw==} peerDependencies: - "@vue/compiler-sfc": 2.7.x || 3.x + '@vue/compiler-sfc': 2.7.x || 3.x prettier: 2 || 3 || ^4.0.0-0 peerDependenciesMeta: - "@vue/compiler-sfc": + '@vue/compiler-sfc': optional: true - "@img/sharp-darwin-arm64@0.34.2": - resolution: - { integrity: sha512-OfXHZPppddivUJnqyKoi5YVeHRkkNE2zUFT2gbpKxp/JZCFYEYubnMg+gOp6lWfasPrTS+KPosKqdI+ELYVDtg== } - engines: { node: ^18.17.0 || ^20.3.0 || >=21.0.0 } + '@img/sharp-darwin-arm64@0.34.2': + resolution: {integrity: sha512-OfXHZPppddivUJnqyKoi5YVeHRkkNE2zUFT2gbpKxp/JZCFYEYubnMg+gOp6lWfasPrTS+KPosKqdI+ELYVDtg==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} cpu: [arm64] os: [darwin] - "@img/sharp-darwin-x64@0.34.2": - resolution: - { integrity: sha512-dYvWqmjU9VxqXmjEtjmvHnGqF8GrVjM2Epj9rJ6BUIXvk8slvNDJbhGFvIoXzkDhrJC2jUxNLz/GUjjvSzfw+g== } - engines: { node: ^18.17.0 || ^20.3.0 || >=21.0.0 } + '@img/sharp-darwin-x64@0.34.2': + resolution: {integrity: sha512-dYvWqmjU9VxqXmjEtjmvHnGqF8GrVjM2Epj9rJ6BUIXvk8slvNDJbhGFvIoXzkDhrJC2jUxNLz/GUjjvSzfw+g==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} cpu: [x64] os: [darwin] - "@img/sharp-libvips-darwin-arm64@1.1.0": - resolution: - { integrity: sha512-HZ/JUmPwrJSoM4DIQPv/BfNh9yrOA8tlBbqbLz4JZ5uew2+o22Ik+tHQJcih7QJuSa0zo5coHTfD5J8inqj9DA== } + '@img/sharp-libvips-darwin-arm64@1.1.0': + resolution: {integrity: sha512-HZ/JUmPwrJSoM4DIQPv/BfNh9yrOA8tlBbqbLz4JZ5uew2+o22Ik+tHQJcih7QJuSa0zo5coHTfD5J8inqj9DA==} cpu: [arm64] os: [darwin] - "@img/sharp-libvips-darwin-x64@1.1.0": - resolution: - { integrity: sha512-Xzc2ToEmHN+hfvsl9wja0RlnXEgpKNmftriQp6XzY/RaSfwD9th+MSh0WQKzUreLKKINb3afirxW7A0fz2YWuQ== } + '@img/sharp-libvips-darwin-x64@1.1.0': + resolution: {integrity: sha512-Xzc2ToEmHN+hfvsl9wja0RlnXEgpKNmftriQp6XzY/RaSfwD9th+MSh0WQKzUreLKKINb3afirxW7A0fz2YWuQ==} cpu: [x64] os: [darwin] - "@img/sharp-libvips-linux-arm64@1.1.0": - resolution: - { integrity: sha512-IVfGJa7gjChDET1dK9SekxFFdflarnUB8PwW8aGwEoF3oAsSDuNUTYS+SKDOyOJxQyDC1aPFMuRYLoDInyV9Ew== } + '@img/sharp-libvips-linux-arm64@1.1.0': + resolution: {integrity: sha512-IVfGJa7gjChDET1dK9SekxFFdflarnUB8PwW8aGwEoF3oAsSDuNUTYS+SKDOyOJxQyDC1aPFMuRYLoDInyV9Ew==} cpu: [arm64] os: [linux] - "@img/sharp-libvips-linux-arm@1.1.0": - resolution: - { integrity: sha512-s8BAd0lwUIvYCJyRdFqvsj+BJIpDBSxs6ivrOPm/R7piTs5UIwY5OjXrP2bqXC9/moGsyRa37eYWYCOGVXxVrA== } + '@img/sharp-libvips-linux-arm@1.1.0': + resolution: {integrity: sha512-s8BAd0lwUIvYCJyRdFqvsj+BJIpDBSxs6ivrOPm/R7piTs5UIwY5OjXrP2bqXC9/moGsyRa37eYWYCOGVXxVrA==} cpu: [arm] os: [linux] - "@img/sharp-libvips-linux-ppc64@1.1.0": - resolution: - { integrity: sha512-tiXxFZFbhnkWE2LA8oQj7KYR+bWBkiV2nilRldT7bqoEZ4HiDOcePr9wVDAZPi/Id5fT1oY9iGnDq20cwUz8lQ== } + '@img/sharp-libvips-linux-ppc64@1.1.0': + resolution: {integrity: sha512-tiXxFZFbhnkWE2LA8oQj7KYR+bWBkiV2nilRldT7bqoEZ4HiDOcePr9wVDAZPi/Id5fT1oY9iGnDq20cwUz8lQ==} cpu: [ppc64] os: [linux] - "@img/sharp-libvips-linux-s390x@1.1.0": - resolution: - { integrity: sha512-xukSwvhguw7COyzvmjydRb3x/09+21HykyapcZchiCUkTThEQEOMtBj9UhkaBRLuBrgLFzQ2wbxdeCCJW/jgJA== } + '@img/sharp-libvips-linux-s390x@1.1.0': + resolution: {integrity: sha512-xukSwvhguw7COyzvmjydRb3x/09+21HykyapcZchiCUkTThEQEOMtBj9UhkaBRLuBrgLFzQ2wbxdeCCJW/jgJA==} cpu: [s390x] os: [linux] - "@img/sharp-libvips-linux-x64@1.1.0": - resolution: - { integrity: sha512-yRj2+reB8iMg9W5sULM3S74jVS7zqSzHG3Ol/twnAAkAhnGQnpjj6e4ayUz7V+FpKypwgs82xbRdYtchTTUB+Q== } + '@img/sharp-libvips-linux-x64@1.1.0': + resolution: {integrity: sha512-yRj2+reB8iMg9W5sULM3S74jVS7zqSzHG3Ol/twnAAkAhnGQnpjj6e4ayUz7V+FpKypwgs82xbRdYtchTTUB+Q==} cpu: [x64] os: [linux] - "@img/sharp-libvips-linuxmusl-arm64@1.1.0": - resolution: - { integrity: sha512-jYZdG+whg0MDK+q2COKbYidaqW/WTz0cc1E+tMAusiDygrM4ypmSCjOJPmFTvHHJ8j/6cAGyeDWZOsK06tP33w== } + '@img/sharp-libvips-linuxmusl-arm64@1.1.0': + resolution: {integrity: sha512-jYZdG+whg0MDK+q2COKbYidaqW/WTz0cc1E+tMAusiDygrM4ypmSCjOJPmFTvHHJ8j/6cAGyeDWZOsK06tP33w==} cpu: [arm64] os: [linux] - "@img/sharp-libvips-linuxmusl-x64@1.1.0": - resolution: - { integrity: sha512-wK7SBdwrAiycjXdkPnGCPLjYb9lD4l6Ze2gSdAGVZrEL05AOUJESWU2lhlC+Ffn5/G+VKuSm6zzbQSzFX/P65A== } + '@img/sharp-libvips-linuxmusl-x64@1.1.0': + resolution: {integrity: sha512-wK7SBdwrAiycjXdkPnGCPLjYb9lD4l6Ze2gSdAGVZrEL05AOUJESWU2lhlC+Ffn5/G+VKuSm6zzbQSzFX/P65A==} cpu: [x64] os: [linux] - "@img/sharp-linux-arm64@0.34.2": - resolution: - { integrity: sha512-D8n8wgWmPDakc83LORcfJepdOSN6MvWNzzz2ux0MnIbOqdieRZwVYY32zxVx+IFUT8er5KPcyU3XXsn+GzG/0Q== } - engines: { node: ^18.17.0 || ^20.3.0 || >=21.0.0 } + '@img/sharp-linux-arm64@0.34.2': + resolution: {integrity: sha512-D8n8wgWmPDakc83LORcfJepdOSN6MvWNzzz2ux0MnIbOqdieRZwVYY32zxVx+IFUT8er5KPcyU3XXsn+GzG/0Q==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} cpu: [arm64] os: [linux] - "@img/sharp-linux-arm@0.34.2": - resolution: - { integrity: sha512-0DZzkvuEOqQUP9mo2kjjKNok5AmnOr1jB2XYjkaoNRwpAYMDzRmAqUIa1nRi58S2WswqSfPOWLNOr0FDT3H5RQ== } - engines: { node: ^18.17.0 || ^20.3.0 || >=21.0.0 } + '@img/sharp-linux-arm@0.34.2': + resolution: {integrity: sha512-0DZzkvuEOqQUP9mo2kjjKNok5AmnOr1jB2XYjkaoNRwpAYMDzRmAqUIa1nRi58S2WswqSfPOWLNOr0FDT3H5RQ==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} cpu: [arm] os: [linux] - "@img/sharp-linux-s390x@0.34.2": - resolution: - { integrity: sha512-EGZ1xwhBI7dNISwxjChqBGELCWMGDvmxZXKjQRuqMrakhO8QoMgqCrdjnAqJq/CScxfRn+Bb7suXBElKQpPDiw== } - engines: { node: ^18.17.0 || ^20.3.0 || >=21.0.0 } + '@img/sharp-linux-s390x@0.34.2': + resolution: {integrity: sha512-EGZ1xwhBI7dNISwxjChqBGELCWMGDvmxZXKjQRuqMrakhO8QoMgqCrdjnAqJq/CScxfRn+Bb7suXBElKQpPDiw==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} cpu: [s390x] os: [linux] - "@img/sharp-linux-x64@0.34.2": - resolution: - { integrity: sha512-sD7J+h5nFLMMmOXYH4DD9UtSNBD05tWSSdWAcEyzqW8Cn5UxXvsHAxmxSesYUsTOBmUnjtxghKDl15EvfqLFbQ== } - engines: { node: ^18.17.0 || ^20.3.0 || >=21.0.0 } + '@img/sharp-linux-x64@0.34.2': + resolution: {integrity: sha512-sD7J+h5nFLMMmOXYH4DD9UtSNBD05tWSSdWAcEyzqW8Cn5UxXvsHAxmxSesYUsTOBmUnjtxghKDl15EvfqLFbQ==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} cpu: [x64] os: [linux] - "@img/sharp-linuxmusl-arm64@0.34.2": - resolution: - { integrity: sha512-NEE2vQ6wcxYav1/A22OOxoSOGiKnNmDzCYFOZ949xFmrWZOVII1Bp3NqVVpvj+3UeHMFyN5eP/V5hzViQ5CZNA== } - engines: { node: ^18.17.0 || ^20.3.0 || >=21.0.0 } + '@img/sharp-linuxmusl-arm64@0.34.2': + resolution: {integrity: sha512-NEE2vQ6wcxYav1/A22OOxoSOGiKnNmDzCYFOZ949xFmrWZOVII1Bp3NqVVpvj+3UeHMFyN5eP/V5hzViQ5CZNA==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} cpu: [arm64] os: [linux] - "@img/sharp-linuxmusl-x64@0.34.2": - resolution: - { integrity: sha512-DOYMrDm5E6/8bm/yQLCWyuDJwUnlevR8xtF8bs+gjZ7cyUNYXiSf/E8Kp0Ss5xasIaXSHzb888V1BE4i1hFhAA== } - engines: { node: ^18.17.0 || ^20.3.0 || >=21.0.0 } + '@img/sharp-linuxmusl-x64@0.34.2': + resolution: {integrity: sha512-DOYMrDm5E6/8bm/yQLCWyuDJwUnlevR8xtF8bs+gjZ7cyUNYXiSf/E8Kp0Ss5xasIaXSHzb888V1BE4i1hFhAA==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} cpu: [x64] os: [linux] - "@img/sharp-wasm32@0.34.2": - resolution: - { integrity: sha512-/VI4mdlJ9zkaq53MbIG6rZY+QRN3MLbR6usYlgITEzi4Rpx5S6LFKsycOQjkOGmqTNmkIdLjEvooFKwww6OpdQ== } - engines: { node: ^18.17.0 || ^20.3.0 || >=21.0.0 } + '@img/sharp-wasm32@0.34.2': + resolution: {integrity: sha512-/VI4mdlJ9zkaq53MbIG6rZY+QRN3MLbR6usYlgITEzi4Rpx5S6LFKsycOQjkOGmqTNmkIdLjEvooFKwww6OpdQ==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} cpu: [wasm32] - "@img/sharp-win32-arm64@0.34.2": - resolution: - { integrity: sha512-cfP/r9FdS63VA5k0xiqaNaEoGxBg9k7uE+RQGzuK9fHt7jib4zAVVseR9LsE4gJcNWgT6APKMNnCcnyOtmSEUQ== } - engines: { node: ^18.17.0 || ^20.3.0 || >=21.0.0 } + '@img/sharp-win32-arm64@0.34.2': + resolution: {integrity: sha512-cfP/r9FdS63VA5k0xiqaNaEoGxBg9k7uE+RQGzuK9fHt7jib4zAVVseR9LsE4gJcNWgT6APKMNnCcnyOtmSEUQ==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} cpu: [arm64] os: [win32] - "@img/sharp-win32-ia32@0.34.2": - resolution: - { integrity: sha512-QLjGGvAbj0X/FXl8n1WbtQ6iVBpWU7JO94u/P2M4a8CFYsvQi4GW2mRy/JqkRx0qpBzaOdKJKw8uc930EX2AHw== } - engines: { node: ^18.17.0 || ^20.3.0 || >=21.0.0 } + '@img/sharp-win32-ia32@0.34.2': + resolution: {integrity: sha512-QLjGGvAbj0X/FXl8n1WbtQ6iVBpWU7JO94u/P2M4a8CFYsvQi4GW2mRy/JqkRx0qpBzaOdKJKw8uc930EX2AHw==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} cpu: [ia32] os: [win32] - "@img/sharp-win32-x64@0.34.2": - resolution: - { integrity: sha512-aUdT6zEYtDKCaxkofmmJDJYGCf0+pJg3eU9/oBuqvEeoB9dKI6ZLc/1iLJCTuJQDO4ptntAlkUmHgGjyuobZbw== } - engines: { node: ^18.17.0 || ^20.3.0 || >=21.0.0 } + '@img/sharp-win32-x64@0.34.2': + resolution: {integrity: sha512-aUdT6zEYtDKCaxkofmmJDJYGCf0+pJg3eU9/oBuqvEeoB9dKI6ZLc/1iLJCTuJQDO4ptntAlkUmHgGjyuobZbw==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} cpu: [x64] os: [win32] - "@isaacs/fs-minipass@4.0.1": - resolution: - { integrity: sha512-wgm9Ehl2jpeqP3zw/7mo3kRHFp5MEDhqAdwy1fTGkHAwnkGOVsgpvQhL8B5n1qlb01jV3n/bI0ZfZp5lWA1k4w== } - engines: { node: ">=18.0.0" } + '@isaacs/fs-minipass@4.0.1': + resolution: {integrity: sha512-wgm9Ehl2jpeqP3zw/7mo3kRHFp5MEDhqAdwy1fTGkHAwnkGOVsgpvQhL8B5n1qlb01jV3n/bI0ZfZp5lWA1k4w==} + engines: {node: '>=18.0.0'} - "@jridgewell/gen-mapping@0.3.12": - resolution: - { integrity: sha512-OuLGC46TjB5BbN1dH8JULVVZY4WTdkF7tV9Ys6wLL1rubZnCMstOhNHueU5bLCrnRuDhKPDM4g6sw4Bel5Gzqg== } + '@jridgewell/gen-mapping@0.3.12': + resolution: {integrity: sha512-OuLGC46TjB5BbN1dH8JULVVZY4WTdkF7tV9Ys6wLL1rubZnCMstOhNHueU5bLCrnRuDhKPDM4g6sw4Bel5Gzqg==} - "@jridgewell/resolve-uri@3.1.2": - resolution: - { integrity: sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw== } - engines: { node: ">=6.0.0" } + '@jridgewell/resolve-uri@3.1.2': + resolution: {integrity: sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==} + engines: {node: '>=6.0.0'} - "@jridgewell/sourcemap-codec@1.5.4": - resolution: - { integrity: sha512-VT2+G1VQs/9oz078bLrYbecdZKs912zQlkelYpuf+SXF+QvZDYJlbx/LSx+meSAwdDFnF8FVXW92AVjjkVmgFw== } + '@jridgewell/sourcemap-codec@1.5.4': + resolution: {integrity: sha512-VT2+G1VQs/9oz078bLrYbecdZKs912zQlkelYpuf+SXF+QvZDYJlbx/LSx+meSAwdDFnF8FVXW92AVjjkVmgFw==} - "@jridgewell/trace-mapping@0.3.29": - resolution: - { integrity: sha512-uw6guiW/gcAGPDhLmd77/6lW8QLeiV5RUTsAX46Db6oLhGaVj4lhnPwb184s1bkc8kdVg/+h988dro8GRDpmYQ== } + '@jridgewell/trace-mapping@0.3.29': + resolution: {integrity: sha512-uw6guiW/gcAGPDhLmd77/6lW8QLeiV5RUTsAX46Db6oLhGaVj4lhnPwb184s1bkc8kdVg/+h988dro8GRDpmYQ==} - "@napi-rs/wasm-runtime@0.2.11": - resolution: - { integrity: sha512-9DPkXtvHydrcOsopiYpUgPHpmj0HWZKMUnL2dZqpvC42lsratuBG06V5ipyno0fUek5VlFsNQ+AcFATSrJXgMA== } + '@napi-rs/wasm-runtime@0.2.11': + resolution: {integrity: sha512-9DPkXtvHydrcOsopiYpUgPHpmj0HWZKMUnL2dZqpvC42lsratuBG06V5ipyno0fUek5VlFsNQ+AcFATSrJXgMA==} - "@next/env@15.3.4": - resolution: - { integrity: sha512-ZkdYzBseS6UjYzz6ylVKPOK+//zLWvD6Ta+vpoye8cW11AjiQjGYVibF0xuvT4L0iJfAPfZLFidaEzAOywyOAQ== } + '@next/env@15.3.4': + resolution: {integrity: sha512-ZkdYzBseS6UjYzz6ylVKPOK+//zLWvD6Ta+vpoye8cW11AjiQjGYVibF0xuvT4L0iJfAPfZLFidaEzAOywyOAQ==} - "@next/eslint-plugin-next@15.3.4": - resolution: - { integrity: sha512-lBxYdj7TI8phbJcLSAqDt57nIcobEign5NYIKCiy0hXQhrUbTqLqOaSDi568U6vFg4hJfBdZYsG4iP/uKhCqgg== } + '@next/eslint-plugin-next@15.3.4': + resolution: {integrity: sha512-lBxYdj7TI8phbJcLSAqDt57nIcobEign5NYIKCiy0hXQhrUbTqLqOaSDi568U6vFg4hJfBdZYsG4iP/uKhCqgg==} - "@next/swc-darwin-arm64@15.3.4": - resolution: - { integrity: sha512-z0qIYTONmPRbwHWvpyrFXJd5F9YWLCsw3Sjrzj2ZvMYy9NPQMPZ1NjOJh4ojr4oQzcGYwgJKfidzehaNa1BpEg== } - engines: { node: ">= 10" } + '@next/swc-darwin-arm64@15.3.4': + resolution: {integrity: sha512-z0qIYTONmPRbwHWvpyrFXJd5F9YWLCsw3Sjrzj2ZvMYy9NPQMPZ1NjOJh4ojr4oQzcGYwgJKfidzehaNa1BpEg==} + engines: {node: '>= 10'} cpu: [arm64] os: [darwin] - "@next/swc-darwin-x64@15.3.4": - resolution: - { integrity: sha512-Z0FYJM8lritw5Wq+vpHYuCIzIlEMjewG2aRkc3Hi2rcbULknYL/xqfpBL23jQnCSrDUGAo/AEv0Z+s2bff9Zkw== } - engines: { node: ">= 10" } + '@next/swc-darwin-x64@15.3.4': + resolution: {integrity: sha512-Z0FYJM8lritw5Wq+vpHYuCIzIlEMjewG2aRkc3Hi2rcbULknYL/xqfpBL23jQnCSrDUGAo/AEv0Z+s2bff9Zkw==} + engines: {node: '>= 10'} cpu: [x64] os: [darwin] - "@next/swc-linux-arm64-gnu@15.3.4": - resolution: - { integrity: sha512-l8ZQOCCg7adwmsnFm8m5q9eIPAHdaB2F3cxhufYtVo84pymwKuWfpYTKcUiFcutJdp9xGHC+F1Uq3xnFU1B/7g== } - engines: { node: ">= 10" } + '@next/swc-linux-arm64-gnu@15.3.4': + resolution: {integrity: sha512-l8ZQOCCg7adwmsnFm8m5q9eIPAHdaB2F3cxhufYtVo84pymwKuWfpYTKcUiFcutJdp9xGHC+F1Uq3xnFU1B/7g==} + engines: {node: '>= 10'} cpu: [arm64] os: [linux] - "@next/swc-linux-arm64-musl@15.3.4": - resolution: - { integrity: sha512-wFyZ7X470YJQtpKot4xCY3gpdn8lE9nTlldG07/kJYexCUpX1piX+MBfZdvulo+t1yADFVEuzFfVHfklfEx8kw== } - engines: { node: ">= 10" } + '@next/swc-linux-arm64-musl@15.3.4': + resolution: {integrity: sha512-wFyZ7X470YJQtpKot4xCY3gpdn8lE9nTlldG07/kJYexCUpX1piX+MBfZdvulo+t1yADFVEuzFfVHfklfEx8kw==} + engines: {node: '>= 10'} cpu: [arm64] os: [linux] - "@next/swc-linux-x64-gnu@15.3.4": - resolution: - { integrity: sha512-gEbH9rv9o7I12qPyvZNVTyP/PWKqOp8clvnoYZQiX800KkqsaJZuOXkWgMa7ANCCh/oEN2ZQheh3yH8/kWPSEg== } - engines: { node: ">= 10" } + '@next/swc-linux-x64-gnu@15.3.4': + resolution: {integrity: sha512-gEbH9rv9o7I12qPyvZNVTyP/PWKqOp8clvnoYZQiX800KkqsaJZuOXkWgMa7ANCCh/oEN2ZQheh3yH8/kWPSEg==} + engines: {node: '>= 10'} cpu: [x64] os: [linux] - "@next/swc-linux-x64-musl@15.3.4": - resolution: - { integrity: sha512-Cf8sr0ufuC/nu/yQ76AnarbSAXcwG/wj+1xFPNbyNo8ltA6kw5d5YqO8kQuwVIxk13SBdtgXrNyom3ZosHAy4A== } - engines: { node: ">= 10" } + '@next/swc-linux-x64-musl@15.3.4': + resolution: {integrity: sha512-Cf8sr0ufuC/nu/yQ76AnarbSAXcwG/wj+1xFPNbyNo8ltA6kw5d5YqO8kQuwVIxk13SBdtgXrNyom3ZosHAy4A==} + engines: {node: '>= 10'} cpu: [x64] os: [linux] - "@next/swc-win32-arm64-msvc@15.3.4": - resolution: - { integrity: sha512-ay5+qADDN3rwRbRpEhTOreOn1OyJIXS60tg9WMYTWCy3fB6rGoyjLVxc4dR9PYjEdR2iDYsaF5h03NA+XuYPQQ== } - engines: { node: ">= 10" } + '@next/swc-win32-arm64-msvc@15.3.4': + resolution: {integrity: sha512-ay5+qADDN3rwRbRpEhTOreOn1OyJIXS60tg9WMYTWCy3fB6rGoyjLVxc4dR9PYjEdR2iDYsaF5h03NA+XuYPQQ==} + engines: {node: '>= 10'} cpu: [arm64] os: [win32] - "@next/swc-win32-x64-msvc@15.3.4": - resolution: - { integrity: sha512-4kDt31Bc9DGyYs41FTL1/kNpDeHyha2TC0j5sRRoKCyrhNcfZ/nRQkAUlF27mETwm8QyHqIjHJitfcza2Iykfg== } - engines: { node: ">= 10" } + '@next/swc-win32-x64-msvc@15.3.4': + resolution: {integrity: sha512-4kDt31Bc9DGyYs41FTL1/kNpDeHyha2TC0j5sRRoKCyrhNcfZ/nRQkAUlF27mETwm8QyHqIjHJitfcza2Iykfg==} + engines: {node: '>= 10'} cpu: [x64] os: [win32] - "@nodelib/fs.scandir@2.1.5": - resolution: - { integrity: sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g== } - engines: { node: ">= 8" } - - "@nodelib/fs.stat@2.0.5": - resolution: - { integrity: sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A== } - engines: { node: ">= 8" } - - "@nodelib/fs.walk@1.2.8": - resolution: - { integrity: sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg== } - engines: { node: ">= 8" } - - "@nolyfill/is-core-module@1.0.39": - resolution: - { integrity: sha512-nn5ozdjYQpUCZlWGuxcJY/KpxkWQs4DcbMCmKojjyrYDEAGy4Ce19NN4v5MduafTwJlbKc99UA8YhSVqq9yPZA== } - engines: { node: ">=12.4.0" } - - "@pkgr/core@0.2.7": - resolution: - { integrity: sha512-YLT9Zo3oNPJoBjBc4q8G2mjU4tqIbf5CEOORbUUr48dCD9q3umJ3IPlVqOqDakPfd2HuwccBaqlGhN4Gmr5OWg== } - engines: { node: ^12.20.0 || ^14.18.0 || >=16.0.0 } - - "@radix-ui/number@1.1.1": - resolution: - { integrity: sha512-MkKCwxlXTgz6CFoJx3pCwn07GKp36+aZyu/u2Ln2VrA5DcdyCZkASEDBTd8x5whTQQL5CiYf4prXKLcgQdv29g== } - - "@radix-ui/primitive@1.1.2": - resolution: - { integrity: sha512-XnbHrrprsNqZKQhStrSwgRUQzoCI1glLzdw79xiZPoofhGICeZRSQ3dIxAKH1gb3OHfNf4d6f+vAv3kil2eggA== } - - "@radix-ui/primitive@1.1.3": - resolution: - { integrity: sha512-JTF99U/6XIjCBo0wqkU5sK10glYe27MRRsfwoiq5zzOEZLHU3A3KCMa5X/azekYRCJ0HlwI0crAXS/5dEHTzDg== } - - "@radix-ui/react-arrow@1.1.7": - resolution: - { integrity: sha512-F+M1tLhO+mlQaOWspE8Wstg+z6PwxwRd8oQ8IXceWz92kfAmalTRf0EjrouQeo7QssEPfCn05B4Ihs1K9WQ/7w== } + '@nodelib/fs.scandir@2.1.5': + resolution: {integrity: sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==} + engines: {node: '>= 8'} + + '@nodelib/fs.stat@2.0.5': + resolution: {integrity: sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==} + engines: {node: '>= 8'} + + '@nodelib/fs.walk@1.2.8': + resolution: {integrity: sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==} + engines: {node: '>= 8'} + + '@nolyfill/is-core-module@1.0.39': + resolution: {integrity: sha512-nn5ozdjYQpUCZlWGuxcJY/KpxkWQs4DcbMCmKojjyrYDEAGy4Ce19NN4v5MduafTwJlbKc99UA8YhSVqq9yPZA==} + engines: {node: '>=12.4.0'} + + '@pkgr/core@0.2.7': + resolution: {integrity: sha512-YLT9Zo3oNPJoBjBc4q8G2mjU4tqIbf5CEOORbUUr48dCD9q3umJ3IPlVqOqDakPfd2HuwccBaqlGhN4Gmr5OWg==} + engines: {node: ^12.20.0 || ^14.18.0 || >=16.0.0} + + '@radix-ui/number@1.1.1': + resolution: {integrity: sha512-MkKCwxlXTgz6CFoJx3pCwn07GKp36+aZyu/u2Ln2VrA5DcdyCZkASEDBTd8x5whTQQL5CiYf4prXKLcgQdv29g==} + + '@radix-ui/primitive@1.1.2': + resolution: {integrity: sha512-XnbHrrprsNqZKQhStrSwgRUQzoCI1glLzdw79xiZPoofhGICeZRSQ3dIxAKH1gb3OHfNf4d6f+vAv3kil2eggA==} + + '@radix-ui/primitive@1.1.3': + resolution: {integrity: sha512-JTF99U/6XIjCBo0wqkU5sK10glYe27MRRsfwoiq5zzOEZLHU3A3KCMa5X/azekYRCJ0HlwI0crAXS/5dEHTzDg==} + + '@radix-ui/react-arrow@1.1.7': + resolution: {integrity: sha512-F+M1tLhO+mlQaOWspE8Wstg+z6PwxwRd8oQ8IXceWz92kfAmalTRf0EjrouQeo7QssEPfCn05B4Ihs1K9WQ/7w==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-aspect-ratio@1.1.7': + resolution: {integrity: sha512-Yq6lvO9HQyPwev1onK1daHCHqXVLzPhSVjmsNjCa2Zcxy2f7uJD2itDtxknv6FzAKCwD1qQkeVDmX/cev13n/g==} peerDependencies: - "@types/react": "*" - "@types/react-dom": "*" + '@types/react': '*' + '@types/react-dom': '*' react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc peerDependenciesMeta: - "@types/react": + '@types/react': optional: true - "@types/react-dom": + '@types/react-dom': optional: true - "@radix-ui/react-aspect-ratio@1.1.7": - resolution: - { integrity: sha512-Yq6lvO9HQyPwev1onK1daHCHqXVLzPhSVjmsNjCa2Zcxy2f7uJD2itDtxknv6FzAKCwD1qQkeVDmX/cev13n/g== } + '@radix-ui/react-avatar@1.1.10': + resolution: {integrity: sha512-V8piFfWapM5OmNCXTzVQY+E1rDa53zY+MQ4Y7356v4fFz6vqCyUtIz2rUD44ZEdwg78/jKmMJHj07+C/Z/rcog==} peerDependencies: - "@types/react": "*" - "@types/react-dom": "*" + '@types/react': '*' + '@types/react-dom': '*' react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc peerDependenciesMeta: - "@types/react": + '@types/react': optional: true - "@types/react-dom": + '@types/react-dom': optional: true - "@radix-ui/react-avatar@1.1.10": - resolution: - { integrity: sha512-V8piFfWapM5OmNCXTzVQY+E1rDa53zY+MQ4Y7356v4fFz6vqCyUtIz2rUD44ZEdwg78/jKmMJHj07+C/Z/rcog== } + '@radix-ui/react-checkbox@1.3.2': + resolution: {integrity: sha512-yd+dI56KZqawxKZrJ31eENUwqc1QSqg4OZ15rybGjF2ZNwMO+wCyHzAVLRp9qoYJf7kYy0YpZ2b0JCzJ42HZpA==} peerDependencies: - "@types/react": "*" - "@types/react-dom": "*" + '@types/react': '*' + '@types/react-dom': '*' react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc peerDependenciesMeta: - "@types/react": + '@types/react': optional: true - "@types/react-dom": + '@types/react-dom': optional: true - "@radix-ui/react-checkbox@1.3.2": - resolution: - { integrity: sha512-yd+dI56KZqawxKZrJ31eENUwqc1QSqg4OZ15rybGjF2ZNwMO+wCyHzAVLRp9qoYJf7kYy0YpZ2b0JCzJ42HZpA== } + '@radix-ui/react-collapsible@1.1.12': + resolution: {integrity: sha512-Uu+mSh4agx2ib1uIGPP4/CKNULyajb3p92LsVXmH2EHVMTfZWpll88XJ0j4W0z3f8NK1eYl1+Mf/szHPmcHzyA==} peerDependencies: - "@types/react": "*" - "@types/react-dom": "*" + '@types/react': '*' + '@types/react-dom': '*' react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc peerDependenciesMeta: - "@types/react": + '@types/react': optional: true - "@types/react-dom": + '@types/react-dom': optional: true - "@radix-ui/react-collapsible@1.1.12": - resolution: - { integrity: sha512-Uu+mSh4agx2ib1uIGPP4/CKNULyajb3p92LsVXmH2EHVMTfZWpll88XJ0j4W0z3f8NK1eYl1+Mf/szHPmcHzyA== } + '@radix-ui/react-collection@1.1.7': + resolution: {integrity: sha512-Fh9rGN0MoI4ZFUNyfFVNU4y9LUz93u9/0K+yLgA2bwRojxM8JU1DyvvMBabnZPBgMWREAJvU2jjVzq+LrFUglw==} peerDependencies: - "@types/react": "*" - "@types/react-dom": "*" + '@types/react': '*' + '@types/react-dom': '*' react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc peerDependenciesMeta: - "@types/react": + '@types/react': optional: true - "@types/react-dom": + '@types/react-dom': optional: true - "@radix-ui/react-collection@1.1.7": - resolution: - { integrity: sha512-Fh9rGN0MoI4ZFUNyfFVNU4y9LUz93u9/0K+yLgA2bwRojxM8JU1DyvvMBabnZPBgMWREAJvU2jjVzq+LrFUglw== } + '@radix-ui/react-compose-refs@1.1.2': + resolution: {integrity: sha512-z4eqJvfiNnFMHIIvXP3CY57y2WJs5g2v3X0zm9mEJkrkNv4rDxu+sg9Jh8EkXyeqBkB7SOcboo9dMVqhyrACIg==} peerDependencies: - "@types/react": "*" - "@types/react-dom": "*" + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + '@radix-ui/react-context-menu@2.2.16': + resolution: {integrity: sha512-O8morBEW+HsVG28gYDZPTrT9UUovQUlJue5YO836tiTJhuIWBm/zQHc7j388sHWtdH/xUZurK9olD2+pcqx5ww==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc peerDependenciesMeta: - "@types/react": + '@types/react': optional: true - "@types/react-dom": + '@types/react-dom': + optional: true + + '@radix-ui/react-context@1.1.2': + resolution: {integrity: sha512-jCi/QKUM2r1Ju5a3J64TH2A5SpKAgh0LpknyqdQ4m6DCV0xJ2HG1xARRwNGPQfi1SLdLWZ1OJz6F4OMBBNiGJA==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': optional: true - "@radix-ui/react-compose-refs@1.1.2": - resolution: - { integrity: sha512-z4eqJvfiNnFMHIIvXP3CY57y2WJs5g2v3X0zm9mEJkrkNv4rDxu+sg9Jh8EkXyeqBkB7SOcboo9dMVqhyrACIg== } + '@radix-ui/react-dialog@1.1.14': + resolution: {integrity: sha512-+CpweKjqpzTmwRwcYECQcNYbI8V9VSQt0SNFKeEBLgfucbsLssU6Ppq7wUdNXEGb573bMjFhVjKVll8rmV6zMw==} peerDependencies: - "@types/react": "*" + '@types/react': '*' + '@types/react-dom': '*' react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc peerDependenciesMeta: - "@types/react": + '@types/react': + optional: true + '@types/react-dom': optional: true - "@radix-ui/react-context@1.1.2": - resolution: - { integrity: sha512-jCi/QKUM2r1Ju5a3J64TH2A5SpKAgh0LpknyqdQ4m6DCV0xJ2HG1xARRwNGPQfi1SLdLWZ1OJz6F4OMBBNiGJA== } + '@radix-ui/react-direction@1.1.1': + resolution: {integrity: sha512-1UEWRX6jnOA2y4H5WczZ44gOOjTEmlqv1uNW4GAJEO5+bauCBhv8snY65Iw5/VOS/ghKN9gr2KjnLKxrsvoMVw==} peerDependencies: - "@types/react": "*" + '@types/react': '*' react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc peerDependenciesMeta: - "@types/react": + '@types/react': optional: true - "@radix-ui/react-dialog@1.1.14": - resolution: - { integrity: sha512-+CpweKjqpzTmwRwcYECQcNYbI8V9VSQt0SNFKeEBLgfucbsLssU6Ppq7wUdNXEGb573bMjFhVjKVll8rmV6zMw== } + '@radix-ui/react-dismissable-layer@1.1.10': + resolution: {integrity: sha512-IM1zzRV4W3HtVgftdQiiOmA0AdJlCtMLe00FXaHwgt3rAnNsIyDqshvkIW3hj/iu5hu8ERP7KIYki6NkqDxAwQ==} peerDependencies: - "@types/react": "*" - "@types/react-dom": "*" + '@types/react': '*' + '@types/react-dom': '*' react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc peerDependenciesMeta: - "@types/react": + '@types/react': optional: true - "@types/react-dom": + '@types/react-dom': optional: true - "@radix-ui/react-direction@1.1.1": - resolution: - { integrity: sha512-1UEWRX6jnOA2y4H5WczZ44gOOjTEmlqv1uNW4GAJEO5+bauCBhv8snY65Iw5/VOS/ghKN9gr2KjnLKxrsvoMVw== } + '@radix-ui/react-dismissable-layer@1.1.11': + resolution: {integrity: sha512-Nqcp+t5cTB8BinFkZgXiMJniQH0PsUt2k51FUhbdfeKvc4ACcG2uQniY/8+h1Yv6Kza4Q7lD7PQV0z0oicE0Mg==} peerDependencies: - "@types/react": "*" + '@types/react': '*' + '@types/react-dom': '*' react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc peerDependenciesMeta: - "@types/react": + '@types/react': + optional: true + '@types/react-dom': optional: true - "@radix-ui/react-dismissable-layer@1.1.10": - resolution: - { integrity: sha512-IM1zzRV4W3HtVgftdQiiOmA0AdJlCtMLe00FXaHwgt3rAnNsIyDqshvkIW3hj/iu5hu8ERP7KIYki6NkqDxAwQ== } + '@radix-ui/react-dropdown-menu@2.1.15': + resolution: {integrity: sha512-mIBnOjgwo9AH3FyKaSWoSu/dYj6VdhJ7frEPiGTeXCdUFHjl9h3mFh2wwhEtINOmYXWhdpf1rY2minFsmaNgVQ==} peerDependencies: - "@types/react": "*" - "@types/react-dom": "*" + '@types/react': '*' + '@types/react-dom': '*' react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc peerDependenciesMeta: - "@types/react": + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-focus-guards@1.1.2': + resolution: {integrity: sha512-fyjAACV62oPV925xFCrH8DR5xWhg9KYtJT4s3u54jxp+L/hbpTY2kIeEFFbFe+a/HCE94zGQMZLIpVTPVZDhaA==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': optional: true - "@types/react-dom": + + '@radix-ui/react-focus-guards@1.1.3': + resolution: {integrity: sha512-0rFg/Rj2Q62NCm62jZw0QX7a3sz6QCQU0LpZdNrJX8byRGaGVTqbrW9jAoIAHyMQqsNpeZ81YgSizOt5WXq0Pw==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': optional: true - "@radix-ui/react-dropdown-menu@2.1.15": - resolution: - { integrity: sha512-mIBnOjgwo9AH3FyKaSWoSu/dYj6VdhJ7frEPiGTeXCdUFHjl9h3mFh2wwhEtINOmYXWhdpf1rY2minFsmaNgVQ== } + '@radix-ui/react-focus-scope@1.1.7': + resolution: {integrity: sha512-t2ODlkXBQyn7jkl6TNaw/MtVEVvIGelJDCG41Okq/KwUsJBwQ4XVZsHAVUkK4mBv3ewiAS3PGuUWuY2BoK4ZUw==} peerDependencies: - "@types/react": "*" - "@types/react-dom": "*" + '@types/react': '*' + '@types/react-dom': '*' react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc peerDependenciesMeta: - "@types/react": + '@types/react': + optional: true + '@types/react-dom': optional: true - "@types/react-dom": + + '@radix-ui/react-id@1.1.1': + resolution: {integrity: sha512-kGkGegYIdQsOb4XjsfM97rXsiHaBwco+hFI66oO4s9LU+PLAC5oJ7khdOVFxkhsmlbpUqDAvXw11CluXP+jkHg==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': optional: true - "@radix-ui/react-focus-guards@1.1.2": - resolution: - { integrity: sha512-fyjAACV62oPV925xFCrH8DR5xWhg9KYtJT4s3u54jxp+L/hbpTY2kIeEFFbFe+a/HCE94zGQMZLIpVTPVZDhaA== } + '@radix-ui/react-label@2.1.7': + resolution: {integrity: sha512-YT1GqPSL8kJn20djelMX7/cTRp/Y9w5IZHvfxQTVHrOqa2yMl7i/UfMqKRU5V7mEyKTrUVgJXhNQPVCG8PBLoQ==} peerDependencies: - "@types/react": "*" + '@types/react': '*' + '@types/react-dom': '*' react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc peerDependenciesMeta: - "@types/react": + '@types/react': + optional: true + '@types/react-dom': optional: true - "@radix-ui/react-focus-scope@1.1.7": - resolution: - { integrity: sha512-t2ODlkXBQyn7jkl6TNaw/MtVEVvIGelJDCG41Okq/KwUsJBwQ4XVZsHAVUkK4mBv3ewiAS3PGuUWuY2BoK4ZUw== } + '@radix-ui/react-menu@2.1.15': + resolution: {integrity: sha512-tVlmA3Vb9n8SZSd+YSbuFR66l87Wiy4du+YE+0hzKQEANA+7cWKH1WgqcEX4pXqxUFQKrWQGHdvEfw00TjFiew==} peerDependencies: - "@types/react": "*" - "@types/react-dom": "*" + '@types/react': '*' + '@types/react-dom': '*' react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc peerDependenciesMeta: - "@types/react": + '@types/react': optional: true - "@types/react-dom": + '@types/react-dom': optional: true - "@radix-ui/react-id@1.1.1": - resolution: - { integrity: sha512-kGkGegYIdQsOb4XjsfM97rXsiHaBwco+hFI66oO4s9LU+PLAC5oJ7khdOVFxkhsmlbpUqDAvXw11CluXP+jkHg== } + '@radix-ui/react-menu@2.1.16': + resolution: {integrity: sha512-72F2T+PLlphrqLcAotYPp0uJMr5SjP5SL01wfEspJbru5Zs5vQaSHb4VB3ZMJPimgHHCHG7gMOeOB9H3Hdmtxg==} peerDependencies: - "@types/react": "*" + '@types/react': '*' + '@types/react-dom': '*' react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc peerDependenciesMeta: - "@types/react": + '@types/react': + optional: true + '@types/react-dom': optional: true - "@radix-ui/react-label@2.1.7": - resolution: - { integrity: sha512-YT1GqPSL8kJn20djelMX7/cTRp/Y9w5IZHvfxQTVHrOqa2yMl7i/UfMqKRU5V7mEyKTrUVgJXhNQPVCG8PBLoQ== } + '@radix-ui/react-popper@1.2.7': + resolution: {integrity: sha512-IUFAccz1JyKcf/RjB552PlWwxjeCJB8/4KxT7EhBHOJM+mN7LdW+B3kacJXILm32xawcMMjb2i0cIZpo+f9kiQ==} peerDependencies: - "@types/react": "*" - "@types/react-dom": "*" + '@types/react': '*' + '@types/react-dom': '*' react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc peerDependenciesMeta: - "@types/react": + '@types/react': optional: true - "@types/react-dom": + '@types/react-dom': optional: true - "@radix-ui/react-menu@2.1.15": - resolution: - { integrity: sha512-tVlmA3Vb9n8SZSd+YSbuFR66l87Wiy4du+YE+0hzKQEANA+7cWKH1WgqcEX4pXqxUFQKrWQGHdvEfw00TjFiew== } + '@radix-ui/react-popper@1.2.8': + resolution: {integrity: sha512-0NJQ4LFFUuWkE7Oxf0htBKS6zLkkjBH+hM1uk7Ng705ReR8m/uelduy1DBo0PyBXPKVnBA6YBlU94MBGXrSBCw==} peerDependencies: - "@types/react": "*" - "@types/react-dom": "*" + '@types/react': '*' + '@types/react-dom': '*' react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc peerDependenciesMeta: - "@types/react": + '@types/react': optional: true - "@types/react-dom": + '@types/react-dom': optional: true - "@radix-ui/react-popper@1.2.7": - resolution: - { integrity: sha512-IUFAccz1JyKcf/RjB552PlWwxjeCJB8/4KxT7EhBHOJM+mN7LdW+B3kacJXILm32xawcMMjb2i0cIZpo+f9kiQ== } + '@radix-ui/react-portal@1.1.9': + resolution: {integrity: sha512-bpIxvq03if6UNwXZ+HTK71JLh4APvnXntDc6XOX8UVq4XQOVl7lwok0AvIl+b8zgCw3fSaVTZMpAPPagXbKmHQ==} peerDependencies: - "@types/react": "*" - "@types/react-dom": "*" + '@types/react': '*' + '@types/react-dom': '*' react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc peerDependenciesMeta: - "@types/react": + '@types/react': optional: true - "@types/react-dom": + '@types/react-dom': optional: true - "@radix-ui/react-portal@1.1.9": - resolution: - { integrity: sha512-bpIxvq03if6UNwXZ+HTK71JLh4APvnXntDc6XOX8UVq4XQOVl7lwok0AvIl+b8zgCw3fSaVTZMpAPPagXbKmHQ== } + '@radix-ui/react-presence@1.1.4': + resolution: {integrity: sha512-ueDqRbdc4/bkaQT3GIpLQssRlFgWaL/U2z/S31qRwwLWoxHLgry3SIfCwhxeQNbirEUXFa+lq3RL3oBYXtcmIA==} peerDependencies: - "@types/react": "*" - "@types/react-dom": "*" + '@types/react': '*' + '@types/react-dom': '*' react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc peerDependenciesMeta: - "@types/react": + '@types/react': optional: true - "@types/react-dom": + '@types/react-dom': optional: true - "@radix-ui/react-presence@1.1.4": - resolution: - { integrity: sha512-ueDqRbdc4/bkaQT3GIpLQssRlFgWaL/U2z/S31qRwwLWoxHLgry3SIfCwhxeQNbirEUXFa+lq3RL3oBYXtcmIA== } + '@radix-ui/react-presence@1.1.5': + resolution: {integrity: sha512-/jfEwNDdQVBCNvjkGit4h6pMOzq8bHkopq458dPt2lMjx+eBQUohZNG9A7DtO/O5ukSbxuaNGXMjHicgwy6rQQ==} peerDependencies: - "@types/react": "*" - "@types/react-dom": "*" + '@types/react': '*' + '@types/react-dom': '*' react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc peerDependenciesMeta: - "@types/react": + '@types/react': optional: true - "@types/react-dom": + '@types/react-dom': optional: true - "@radix-ui/react-presence@1.1.5": - resolution: - { integrity: sha512-/jfEwNDdQVBCNvjkGit4h6pMOzq8bHkopq458dPt2lMjx+eBQUohZNG9A7DtO/O5ukSbxuaNGXMjHicgwy6rQQ== } + '@radix-ui/react-primitive@2.1.3': + resolution: {integrity: sha512-m9gTwRkhy2lvCPe6QJp4d3G1TYEUHn/FzJUtq9MjH46an1wJU+GdoGC5VLof8RX8Ft/DlpshApkhswDLZzHIcQ==} peerDependencies: - "@types/react": "*" - "@types/react-dom": "*" + '@types/react': '*' + '@types/react-dom': '*' react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc peerDependenciesMeta: - "@types/react": + '@types/react': optional: true - "@types/react-dom": + '@types/react-dom': optional: true - "@radix-ui/react-primitive@2.1.3": - resolution: - { integrity: sha512-m9gTwRkhy2lvCPe6QJp4d3G1TYEUHn/FzJUtq9MjH46an1wJU+GdoGC5VLof8RX8Ft/DlpshApkhswDLZzHIcQ== } + '@radix-ui/react-progress@1.1.7': + resolution: {integrity: sha512-vPdg/tF6YC/ynuBIJlk1mm7Le0VgW6ub6J2UWnTQ7/D23KXcPI1qy+0vBkgKgd38RCMJavBXpB83HPNFMTb0Fg==} peerDependencies: - "@types/react": "*" - "@types/react-dom": "*" + '@types/react': '*' + '@types/react-dom': '*' react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc peerDependenciesMeta: - "@types/react": + '@types/react': optional: true - "@types/react-dom": + '@types/react-dom': optional: true - "@radix-ui/react-progress@1.1.7": - resolution: - { integrity: sha512-vPdg/tF6YC/ynuBIJlk1mm7Le0VgW6ub6J2UWnTQ7/D23KXcPI1qy+0vBkgKgd38RCMJavBXpB83HPNFMTb0Fg== } + '@radix-ui/react-roving-focus@1.1.10': + resolution: {integrity: sha512-dT9aOXUen9JSsxnMPv/0VqySQf5eDQ6LCk5Sw28kamz8wSOW2bJdlX2Bg5VUIIcV+6XlHpWTIuTPCf/UNIyq8Q==} peerDependencies: - "@types/react": "*" - "@types/react-dom": "*" + '@types/react': '*' + '@types/react-dom': '*' react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc peerDependenciesMeta: - "@types/react": + '@types/react': optional: true - "@types/react-dom": + '@types/react-dom': optional: true - "@radix-ui/react-roving-focus@1.1.10": - resolution: - { integrity: sha512-dT9aOXUen9JSsxnMPv/0VqySQf5eDQ6LCk5Sw28kamz8wSOW2bJdlX2Bg5VUIIcV+6XlHpWTIuTPCf/UNIyq8Q== } + '@radix-ui/react-roving-focus@1.1.11': + resolution: {integrity: sha512-7A6S9jSgm/S+7MdtNDSb+IU859vQqJ/QAtcYQcfFC6W8RS4IxIZDldLR0xqCFZ6DCyrQLjLPsxtTNch5jVA4lA==} peerDependencies: - "@types/react": "*" - "@types/react-dom": "*" + '@types/react': '*' + '@types/react-dom': '*' react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc peerDependenciesMeta: - "@types/react": + '@types/react': optional: true - "@types/react-dom": + '@types/react-dom': optional: true - "@radix-ui/react-scroll-area@1.2.9": - resolution: - { integrity: sha512-YSjEfBXnhUELsO2VzjdtYYD4CfQjvao+lhhrX5XsHD7/cyUNzljF1FHEbgTPN7LH2MClfwRMIsYlqTYpKTTe2A== } + '@radix-ui/react-scroll-area@1.2.9': + resolution: {integrity: sha512-YSjEfBXnhUELsO2VzjdtYYD4CfQjvao+lhhrX5XsHD7/cyUNzljF1FHEbgTPN7LH2MClfwRMIsYlqTYpKTTe2A==} peerDependencies: - "@types/react": "*" - "@types/react-dom": "*" + '@types/react': '*' + '@types/react-dom': '*' react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc peerDependenciesMeta: - "@types/react": + '@types/react': optional: true - "@types/react-dom": + '@types/react-dom': optional: true - "@radix-ui/react-select@2.2.5": - resolution: - { integrity: sha512-HnMTdXEVuuyzx63ME0ut4+sEMYW6oouHWNGUZc7ddvUWIcfCva/AMoqEW/3wnEllriMWBa0RHspCYnfCWJQYmA== } + '@radix-ui/react-select@2.2.5': + resolution: {integrity: sha512-HnMTdXEVuuyzx63ME0ut4+sEMYW6oouHWNGUZc7ddvUWIcfCva/AMoqEW/3wnEllriMWBa0RHspCYnfCWJQYmA==} peerDependencies: - "@types/react": "*" - "@types/react-dom": "*" + '@types/react': '*' + '@types/react-dom': '*' react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc peerDependenciesMeta: - "@types/react": + '@types/react': optional: true - "@types/react-dom": + '@types/react-dom': optional: true - "@radix-ui/react-separator@1.1.7": - resolution: - { integrity: sha512-0HEb8R9E8A+jZjvmFCy/J4xhbXy3TV+9XSnGJ3KvTtjlIUy/YQ/p6UYZvi7YbeoeXdyU9+Y3scizK6hkY37baA== } + '@radix-ui/react-separator@1.1.7': + resolution: {integrity: sha512-0HEb8R9E8A+jZjvmFCy/J4xhbXy3TV+9XSnGJ3KvTtjlIUy/YQ/p6UYZvi7YbeoeXdyU9+Y3scizK6hkY37baA==} peerDependencies: - "@types/react": "*" - "@types/react-dom": "*" + '@types/react': '*' + '@types/react-dom': '*' react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc peerDependenciesMeta: - "@types/react": + '@types/react': optional: true - "@types/react-dom": + '@types/react-dom': optional: true - "@radix-ui/react-slider@1.3.5": - resolution: - { integrity: sha512-rkfe2pU2NBAYfGaxa3Mqosi7VZEWX5CxKaanRv0vZd4Zhl9fvQrg0VM93dv3xGLGfrHuoTRF3JXH8nb9g+B3fw== } + '@radix-ui/react-slider@1.3.5': + resolution: {integrity: sha512-rkfe2pU2NBAYfGaxa3Mqosi7VZEWX5CxKaanRv0vZd4Zhl9fvQrg0VM93dv3xGLGfrHuoTRF3JXH8nb9g+B3fw==} peerDependencies: - "@types/react": "*" - "@types/react-dom": "*" + '@types/react': '*' + '@types/react-dom': '*' react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc peerDependenciesMeta: - "@types/react": + '@types/react': optional: true - "@types/react-dom": + '@types/react-dom': optional: true - "@radix-ui/react-slot@1.2.3": - resolution: - { integrity: sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A== } + '@radix-ui/react-slot@1.2.3': + resolution: {integrity: sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A==} peerDependencies: - "@types/react": "*" + '@types/react': '*' react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc peerDependenciesMeta: - "@types/react": + '@types/react': optional: true - "@radix-ui/react-switch@1.2.5": - resolution: - { integrity: sha512-5ijLkak6ZMylXsaImpZ8u4Rlf5grRmoc0p0QeX9VJtlrM4f5m3nCTX8tWga/zOA8PZYIR/t0p2Mnvd7InrJ6yQ== } + '@radix-ui/react-switch@1.2.5': + resolution: {integrity: sha512-5ijLkak6ZMylXsaImpZ8u4Rlf5grRmoc0p0QeX9VJtlrM4f5m3nCTX8tWga/zOA8PZYIR/t0p2Mnvd7InrJ6yQ==} peerDependencies: - "@types/react": "*" - "@types/react-dom": "*" + '@types/react': '*' + '@types/react-dom': '*' react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc peerDependenciesMeta: - "@types/react": + '@types/react': optional: true - "@types/react-dom": + '@types/react-dom': optional: true - "@radix-ui/react-tabs@1.1.12": - resolution: - { integrity: sha512-GTVAlRVrQrSw3cEARM0nAx73ixrWDPNZAruETn3oHCNP6SbZ/hNxdxp+u7VkIEv3/sFoLq1PfcHrl7Pnp0CDpw== } + '@radix-ui/react-tabs@1.1.12': + resolution: {integrity: sha512-GTVAlRVrQrSw3cEARM0nAx73ixrWDPNZAruETn3oHCNP6SbZ/hNxdxp+u7VkIEv3/sFoLq1PfcHrl7Pnp0CDpw==} peerDependencies: - "@types/react": "*" - "@types/react-dom": "*" + '@types/react': '*' + '@types/react-dom': '*' react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc peerDependenciesMeta: - "@types/react": + '@types/react': optional: true - "@types/react-dom": + '@types/react-dom': optional: true - "@radix-ui/react-use-callback-ref@1.1.1": - resolution: - { integrity: sha512-FkBMwD+qbGQeMu1cOHnuGB6x4yzPjho8ap5WtbEJ26umhgqVXbhekKUQO+hZEL1vU92a3wHwdp0HAcqAUF5iDg== } + '@radix-ui/react-use-callback-ref@1.1.1': + resolution: {integrity: sha512-FkBMwD+qbGQeMu1cOHnuGB6x4yzPjho8ap5WtbEJ26umhgqVXbhekKUQO+hZEL1vU92a3wHwdp0HAcqAUF5iDg==} peerDependencies: - "@types/react": "*" + '@types/react': '*' react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc peerDependenciesMeta: - "@types/react": + '@types/react': optional: true - "@radix-ui/react-use-controllable-state@1.2.2": - resolution: - { integrity: sha512-BjasUjixPFdS+NKkypcyyN5Pmg83Olst0+c6vGov0diwTEo6mgdqVR6hxcEgFuh4QrAs7Rc+9KuGJ9TVCj0Zzg== } + '@radix-ui/react-use-controllable-state@1.2.2': + resolution: {integrity: sha512-BjasUjixPFdS+NKkypcyyN5Pmg83Olst0+c6vGov0diwTEo6mgdqVR6hxcEgFuh4QrAs7Rc+9KuGJ9TVCj0Zzg==} peerDependencies: - "@types/react": "*" + '@types/react': '*' react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc peerDependenciesMeta: - "@types/react": + '@types/react': optional: true - "@radix-ui/react-use-effect-event@0.0.2": - resolution: - { integrity: sha512-Qp8WbZOBe+blgpuUT+lw2xheLP8q0oatc9UpmiemEICxGvFLYmHm9QowVZGHtJlGbS6A6yJ3iViad/2cVjnOiA== } + '@radix-ui/react-use-effect-event@0.0.2': + resolution: {integrity: sha512-Qp8WbZOBe+blgpuUT+lw2xheLP8q0oatc9UpmiemEICxGvFLYmHm9QowVZGHtJlGbS6A6yJ3iViad/2cVjnOiA==} peerDependencies: - "@types/react": "*" + '@types/react': '*' react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc peerDependenciesMeta: - "@types/react": + '@types/react': optional: true - "@radix-ui/react-use-escape-keydown@1.1.1": - resolution: - { integrity: sha512-Il0+boE7w/XebUHyBjroE+DbByORGR9KKmITzbR7MyQ4akpORYP/ZmbhAr0DG7RmmBqoOnZdy2QlvajJ2QA59g== } + '@radix-ui/react-use-escape-keydown@1.1.1': + resolution: {integrity: sha512-Il0+boE7w/XebUHyBjroE+DbByORGR9KKmITzbR7MyQ4akpORYP/ZmbhAr0DG7RmmBqoOnZdy2QlvajJ2QA59g==} peerDependencies: - "@types/react": "*" + '@types/react': '*' react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc peerDependenciesMeta: - "@types/react": + '@types/react': optional: true - "@radix-ui/react-use-is-hydrated@0.1.0": - resolution: - { integrity: sha512-U+UORVEq+cTnRIaostJv9AGdV3G6Y+zbVd+12e18jQ5A3c0xL03IhnHuiU4UV69wolOQp5GfR58NW/EgdQhwOA== } + '@radix-ui/react-use-is-hydrated@0.1.0': + resolution: {integrity: sha512-U+UORVEq+cTnRIaostJv9AGdV3G6Y+zbVd+12e18jQ5A3c0xL03IhnHuiU4UV69wolOQp5GfR58NW/EgdQhwOA==} peerDependencies: - "@types/react": "*" + '@types/react': '*' react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc peerDependenciesMeta: - "@types/react": + '@types/react': optional: true - "@radix-ui/react-use-layout-effect@1.1.1": - resolution: - { integrity: sha512-RbJRS4UWQFkzHTTwVymMTUv8EqYhOp8dOOviLj2ugtTiXRaRQS7GLGxZTLL1jWhMeoSCf5zmcZkqTl9IiYfXcQ== } + '@radix-ui/react-use-layout-effect@1.1.1': + resolution: {integrity: sha512-RbJRS4UWQFkzHTTwVymMTUv8EqYhOp8dOOviLj2ugtTiXRaRQS7GLGxZTLL1jWhMeoSCf5zmcZkqTl9IiYfXcQ==} peerDependencies: - "@types/react": "*" + '@types/react': '*' react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc peerDependenciesMeta: - "@types/react": + '@types/react': optional: true - "@radix-ui/react-use-previous@1.1.1": - resolution: - { integrity: sha512-2dHfToCj/pzca2Ck724OZ5L0EVrr3eHRNsG/b3xQJLA2hZpVCS99bLAX+hm1IHXDEnzU6by5z/5MIY794/a8NQ== } + '@radix-ui/react-use-previous@1.1.1': + resolution: {integrity: sha512-2dHfToCj/pzca2Ck724OZ5L0EVrr3eHRNsG/b3xQJLA2hZpVCS99bLAX+hm1IHXDEnzU6by5z/5MIY794/a8NQ==} peerDependencies: - "@types/react": "*" + '@types/react': '*' react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc peerDependenciesMeta: - "@types/react": + '@types/react': optional: true - "@radix-ui/react-use-rect@1.1.1": - resolution: - { integrity: sha512-QTYuDesS0VtuHNNvMh+CjlKJ4LJickCMUAqjlE3+j8w+RlRpwyX3apEQKGFzbZGdo7XNG1tXa+bQqIE7HIXT2w== } + '@radix-ui/react-use-rect@1.1.1': + resolution: {integrity: sha512-QTYuDesS0VtuHNNvMh+CjlKJ4LJickCMUAqjlE3+j8w+RlRpwyX3apEQKGFzbZGdo7XNG1tXa+bQqIE7HIXT2w==} peerDependencies: - "@types/react": "*" + '@types/react': '*' react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc peerDependenciesMeta: - "@types/react": + '@types/react': optional: true - "@radix-ui/react-use-size@1.1.1": - resolution: - { integrity: sha512-ewrXRDTAqAXlkl6t/fkXWNAhFX9I+CkKlw6zjEwk86RSPKwZr3xpBRso655aqYafwtnbpHLj6toFzmd6xdVptQ== } + '@radix-ui/react-use-size@1.1.1': + resolution: {integrity: sha512-ewrXRDTAqAXlkl6t/fkXWNAhFX9I+CkKlw6zjEwk86RSPKwZr3xpBRso655aqYafwtnbpHLj6toFzmd6xdVptQ==} peerDependencies: - "@types/react": "*" + '@types/react': '*' react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc peerDependenciesMeta: - "@types/react": + '@types/react': optional: true - "@radix-ui/react-visually-hidden@1.2.3": - resolution: - { integrity: sha512-pzJq12tEaaIhqjbzpCuv/OypJY/BPavOofm+dbab+MHLajy277+1lLm6JFcGgF5eskJ6mquGirhXY2GD/8u8Ug== } + '@radix-ui/react-visually-hidden@1.2.3': + resolution: {integrity: sha512-pzJq12tEaaIhqjbzpCuv/OypJY/BPavOofm+dbab+MHLajy277+1lLm6JFcGgF5eskJ6mquGirhXY2GD/8u8Ug==} peerDependencies: - "@types/react": "*" - "@types/react-dom": "*" + '@types/react': '*' + '@types/react-dom': '*' react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc peerDependenciesMeta: - "@types/react": + '@types/react': optional: true - "@types/react-dom": + '@types/react-dom': optional: true - "@radix-ui/rect@1.1.1": - resolution: - { integrity: sha512-HPwpGIzkl28mWyZqG52jiqDJ12waP11Pa1lGoiyUkIEuMLBP0oeK/C89esbXrxsky5we7dfd8U58nm0SgAWpVw== } + '@radix-ui/rect@1.1.1': + resolution: {integrity: sha512-HPwpGIzkl28mWyZqG52jiqDJ12waP11Pa1lGoiyUkIEuMLBP0oeK/C89esbXrxsky5we7dfd8U58nm0SgAWpVw==} - "@rtsao/scc@1.1.0": - resolution: - { integrity: sha512-zt6OdqaDoOnJ1ZYsCYGt9YmWzDXl4vQdKTyJev62gFhRGKdx7mcT54V9KIjg+d2wi9EXsPvAPKe7i7WjfVWB8g== } + '@rtsao/scc@1.1.0': + resolution: {integrity: sha512-zt6OdqaDoOnJ1ZYsCYGt9YmWzDXl4vQdKTyJev62gFhRGKdx7mcT54V9KIjg+d2wi9EXsPvAPKe7i7WjfVWB8g==} - "@rushstack/eslint-patch@1.12.0": - resolution: - { integrity: sha512-5EwMtOqvJMMa3HbmxLlF74e+3/HhwBTMcvt3nqVJgGCozO6hzIPOBlwm8mGVNR9SN2IJpxSnlxczyDjcn7qIyw== } + '@rushstack/eslint-patch@1.12.0': + resolution: {integrity: sha512-5EwMtOqvJMMa3HbmxLlF74e+3/HhwBTMcvt3nqVJgGCozO6hzIPOBlwm8mGVNR9SN2IJpxSnlxczyDjcn7qIyw==} - "@schummar/icu-type-parser@1.21.5": - resolution: - { integrity: sha512-bXHSaW5jRTmke9Vd0h5P7BtWZG9Znqb8gSDxZnxaGSJnGwPLDPfS+3g0BKzeWqzgZPsIVZkM7m2tbo18cm5HBw== } + '@schummar/icu-type-parser@1.21.5': + resolution: {integrity: sha512-bXHSaW5jRTmke9Vd0h5P7BtWZG9Znqb8gSDxZnxaGSJnGwPLDPfS+3g0BKzeWqzgZPsIVZkM7m2tbo18cm5HBw==} - "@standard-schema/utils@0.3.0": - resolution: - { integrity: sha512-e7Mew686owMaPJVNNLs55PUvgz371nKgwsc4vxE49zsODpJEnxgxRo2y/OKrqueavXgZNMDVj3DdHFlaSAeU8g== } + '@standard-schema/utils@0.3.0': + resolution: {integrity: sha512-e7Mew686owMaPJVNNLs55PUvgz371nKgwsc4vxE49zsODpJEnxgxRo2y/OKrqueavXgZNMDVj3DdHFlaSAeU8g==} - "@swc/counter@0.1.3": - resolution: - { integrity: sha512-e2BR4lsJkkRlKZ/qCHPw9ZaSxc0MVUd7gtbtaB7aMvHeJVYe8sOB8DBZkP2DtISHGSku9sCK6T6cnY0CtXrOCQ== } + '@swc/counter@0.1.3': + resolution: {integrity: sha512-e2BR4lsJkkRlKZ/qCHPw9ZaSxc0MVUd7gtbtaB7aMvHeJVYe8sOB8DBZkP2DtISHGSku9sCK6T6cnY0CtXrOCQ==} - "@swc/helpers@0.5.15": - resolution: - { integrity: sha512-JQ5TuMi45Owi4/BIMAJBoSQoOJu12oOk/gADqlcUL9JEdHB8vyjUSsxqeNXnmXHjYKMi2WcYtezGEEhqUI/E2g== } + '@swc/helpers@0.5.15': + resolution: {integrity: sha512-JQ5TuMi45Owi4/BIMAJBoSQoOJu12oOk/gADqlcUL9JEdHB8vyjUSsxqeNXnmXHjYKMi2WcYtezGEEhqUI/E2g==} - "@tabler/icons-react@3.34.0": - resolution: - { integrity: sha512-OpEIR2iZsIXECtAIMbn1zfKfQ3zKJjXyIZlkgOGUL9UkMCFycEiF2Y8AVfEQsyre/3FnBdlWJvGr0NU47n2TbQ== } + '@tabler/icons-react@3.34.0': + resolution: {integrity: sha512-OpEIR2iZsIXECtAIMbn1zfKfQ3zKJjXyIZlkgOGUL9UkMCFycEiF2Y8AVfEQsyre/3FnBdlWJvGr0NU47n2TbQ==} peerDependencies: - react: ">= 16" + react: '>= 16' - "@tabler/icons@3.34.0": - resolution: - { integrity: sha512-jtVqv0JC1WU2TTEBN32D9+R6mc1iEBuPwLnBsWaR02SIEciu9aq5806AWkCHuObhQ4ERhhXErLEK7Fs+tEZxiA== } + '@tabler/icons@3.34.0': + resolution: {integrity: sha512-jtVqv0JC1WU2TTEBN32D9+R6mc1iEBuPwLnBsWaR02SIEciu9aq5806AWkCHuObhQ4ERhhXErLEK7Fs+tEZxiA==} - "@tailwindcss/node@4.1.11": - resolution: - { integrity: sha512-yzhzuGRmv5QyU9qLNg4GTlYI6STedBWRE7NjxP45CsFYYq9taI0zJXZBMqIC/c8fViNLhmrbpSFS57EoxUmD6Q== } + '@tailwindcss/node@4.1.11': + resolution: {integrity: sha512-yzhzuGRmv5QyU9qLNg4GTlYI6STedBWRE7NjxP45CsFYYq9taI0zJXZBMqIC/c8fViNLhmrbpSFS57EoxUmD6Q==} - "@tailwindcss/oxide-android-arm64@4.1.11": - resolution: - { integrity: sha512-3IfFuATVRUMZZprEIx9OGDjG3Ou3jG4xQzNTvjDoKmU9JdmoCohQJ83MYd0GPnQIu89YoJqvMM0G3uqLRFtetg== } - engines: { node: ">= 10" } + '@tailwindcss/oxide-android-arm64@4.1.11': + resolution: {integrity: sha512-3IfFuATVRUMZZprEIx9OGDjG3Ou3jG4xQzNTvjDoKmU9JdmoCohQJ83MYd0GPnQIu89YoJqvMM0G3uqLRFtetg==} + engines: {node: '>= 10'} cpu: [arm64] os: [android] - "@tailwindcss/oxide-darwin-arm64@4.1.11": - resolution: - { integrity: sha512-ESgStEOEsyg8J5YcMb1xl8WFOXfeBmrhAwGsFxxB2CxY9evy63+AtpbDLAyRkJnxLy2WsD1qF13E97uQyP1lfQ== } - engines: { node: ">= 10" } + '@tailwindcss/oxide-darwin-arm64@4.1.11': + resolution: {integrity: sha512-ESgStEOEsyg8J5YcMb1xl8WFOXfeBmrhAwGsFxxB2CxY9evy63+AtpbDLAyRkJnxLy2WsD1qF13E97uQyP1lfQ==} + engines: {node: '>= 10'} cpu: [arm64] os: [darwin] - "@tailwindcss/oxide-darwin-x64@4.1.11": - resolution: - { integrity: sha512-EgnK8kRchgmgzG6jE10UQNaH9Mwi2n+yw1jWmof9Vyg2lpKNX2ioe7CJdf9M5f8V9uaQxInenZkOxnTVL3fhAw== } - engines: { node: ">= 10" } + '@tailwindcss/oxide-darwin-x64@4.1.11': + resolution: {integrity: sha512-EgnK8kRchgmgzG6jE10UQNaH9Mwi2n+yw1jWmof9Vyg2lpKNX2ioe7CJdf9M5f8V9uaQxInenZkOxnTVL3fhAw==} + engines: {node: '>= 10'} cpu: [x64] os: [darwin] - "@tailwindcss/oxide-freebsd-x64@4.1.11": - resolution: - { integrity: sha512-xdqKtbpHs7pQhIKmqVpxStnY1skuNh4CtbcyOHeX1YBE0hArj2romsFGb6yUmzkq/6M24nkxDqU8GYrKrz+UcA== } - engines: { node: ">= 10" } + '@tailwindcss/oxide-freebsd-x64@4.1.11': + resolution: {integrity: sha512-xdqKtbpHs7pQhIKmqVpxStnY1skuNh4CtbcyOHeX1YBE0hArj2romsFGb6yUmzkq/6M24nkxDqU8GYrKrz+UcA==} + engines: {node: '>= 10'} cpu: [x64] os: [freebsd] - "@tailwindcss/oxide-linux-arm-gnueabihf@4.1.11": - resolution: - { integrity: sha512-ryHQK2eyDYYMwB5wZL46uoxz2zzDZsFBwfjssgB7pzytAeCCa6glsiJGjhTEddq/4OsIjsLNMAiMlHNYnkEEeg== } - engines: { node: ">= 10" } + '@tailwindcss/oxide-linux-arm-gnueabihf@4.1.11': + resolution: {integrity: sha512-ryHQK2eyDYYMwB5wZL46uoxz2zzDZsFBwfjssgB7pzytAeCCa6glsiJGjhTEddq/4OsIjsLNMAiMlHNYnkEEeg==} + engines: {node: '>= 10'} cpu: [arm] os: [linux] - "@tailwindcss/oxide-linux-arm64-gnu@4.1.11": - resolution: - { integrity: sha512-mYwqheq4BXF83j/w75ewkPJmPZIqqP1nhoghS9D57CLjsh3Nfq0m4ftTotRYtGnZd3eCztgbSPJ9QhfC91gDZQ== } - engines: { node: ">= 10" } + '@tailwindcss/oxide-linux-arm64-gnu@4.1.11': + resolution: {integrity: sha512-mYwqheq4BXF83j/w75ewkPJmPZIqqP1nhoghS9D57CLjsh3Nfq0m4ftTotRYtGnZd3eCztgbSPJ9QhfC91gDZQ==} + engines: {node: '>= 10'} cpu: [arm64] os: [linux] - "@tailwindcss/oxide-linux-arm64-musl@4.1.11": - resolution: - { integrity: sha512-m/NVRFNGlEHJrNVk3O6I9ggVuNjXHIPoD6bqay/pubtYC9QIdAMpS+cswZQPBLvVvEF6GtSNONbDkZrjWZXYNQ== } - engines: { node: ">= 10" } + '@tailwindcss/oxide-linux-arm64-musl@4.1.11': + resolution: {integrity: sha512-m/NVRFNGlEHJrNVk3O6I9ggVuNjXHIPoD6bqay/pubtYC9QIdAMpS+cswZQPBLvVvEF6GtSNONbDkZrjWZXYNQ==} + engines: {node: '>= 10'} cpu: [arm64] os: [linux] - "@tailwindcss/oxide-linux-x64-gnu@4.1.11": - resolution: - { integrity: sha512-YW6sblI7xukSD2TdbbaeQVDysIm/UPJtObHJHKxDEcW2exAtY47j52f8jZXkqE1krdnkhCMGqP3dbniu1Te2Fg== } - engines: { node: ">= 10" } + '@tailwindcss/oxide-linux-x64-gnu@4.1.11': + resolution: {integrity: sha512-YW6sblI7xukSD2TdbbaeQVDysIm/UPJtObHJHKxDEcW2exAtY47j52f8jZXkqE1krdnkhCMGqP3dbniu1Te2Fg==} + engines: {node: '>= 10'} cpu: [x64] os: [linux] - "@tailwindcss/oxide-linux-x64-musl@4.1.11": - resolution: - { integrity: sha512-e3C/RRhGunWYNC3aSF7exsQkdXzQ/M+aYuZHKnw4U7KQwTJotnWsGOIVih0s2qQzmEzOFIJ3+xt7iq67K/p56Q== } - engines: { node: ">= 10" } + '@tailwindcss/oxide-linux-x64-musl@4.1.11': + resolution: {integrity: sha512-e3C/RRhGunWYNC3aSF7exsQkdXzQ/M+aYuZHKnw4U7KQwTJotnWsGOIVih0s2qQzmEzOFIJ3+xt7iq67K/p56Q==} + engines: {node: '>= 10'} cpu: [x64] os: [linux] - "@tailwindcss/oxide-wasm32-wasi@4.1.11": - resolution: - { integrity: sha512-Xo1+/GU0JEN/C/dvcammKHzeM6NqKovG+6921MR6oadee5XPBaKOumrJCXvopJ/Qb5TH7LX/UAywbqrP4lax0g== } - engines: { node: ">=14.0.0" } + '@tailwindcss/oxide-wasm32-wasi@4.1.11': + resolution: {integrity: sha512-Xo1+/GU0JEN/C/dvcammKHzeM6NqKovG+6921MR6oadee5XPBaKOumrJCXvopJ/Qb5TH7LX/UAywbqrP4lax0g==} + engines: {node: '>=14.0.0'} cpu: [wasm32] bundledDependencies: - - "@napi-rs/wasm-runtime" - - "@emnapi/core" - - "@emnapi/runtime" - - "@tybys/wasm-util" - - "@emnapi/wasi-threads" + - '@napi-rs/wasm-runtime' + - '@emnapi/core' + - '@emnapi/runtime' + - '@tybys/wasm-util' + - '@emnapi/wasi-threads' - tslib - "@tailwindcss/oxide-win32-arm64-msvc@4.1.11": - resolution: - { integrity: sha512-UgKYx5PwEKrac3GPNPf6HVMNhUIGuUh4wlDFR2jYYdkX6pL/rn73zTq/4pzUm8fOjAn5L8zDeHp9iXmUGOXZ+w== } - engines: { node: ">= 10" } + '@tailwindcss/oxide-win32-arm64-msvc@4.1.11': + resolution: {integrity: sha512-UgKYx5PwEKrac3GPNPf6HVMNhUIGuUh4wlDFR2jYYdkX6pL/rn73zTq/4pzUm8fOjAn5L8zDeHp9iXmUGOXZ+w==} + engines: {node: '>= 10'} cpu: [arm64] os: [win32] - "@tailwindcss/oxide-win32-x64-msvc@4.1.11": - resolution: - { integrity: sha512-YfHoggn1j0LK7wR82TOucWc5LDCguHnoS879idHekmmiR7g9HUtMw9MI0NHatS28u/Xlkfi9w5RJWgz2Dl+5Qg== } - engines: { node: ">= 10" } + '@tailwindcss/oxide-win32-x64-msvc@4.1.11': + resolution: {integrity: sha512-YfHoggn1j0LK7wR82TOucWc5LDCguHnoS879idHekmmiR7g9HUtMw9MI0NHatS28u/Xlkfi9w5RJWgz2Dl+5Qg==} + engines: {node: '>= 10'} cpu: [x64] os: [win32] - "@tailwindcss/oxide@4.1.11": - resolution: - { integrity: sha512-Q69XzrtAhuyfHo+5/HMgr1lAiPP/G40OMFAnws7xcFEYqcypZmdW8eGXaOUIeOl1dzPJBPENXgbjsOyhg2nkrg== } - engines: { node: ">= 10" } + '@tailwindcss/oxide@4.1.11': + resolution: {integrity: sha512-Q69XzrtAhuyfHo+5/HMgr1lAiPP/G40OMFAnws7xcFEYqcypZmdW8eGXaOUIeOl1dzPJBPENXgbjsOyhg2nkrg==} + engines: {node: '>= 10'} + + '@tailwindcss/postcss@4.1.11': + resolution: {integrity: sha512-q/EAIIpF6WpLhKEuQSEVMZNMIY8KhWoAemZ9eylNAih9jxMGAYPPWBn3I9QL/2jZ+e7OEz/tZkX5HwbBR4HohA==} - "@tailwindcss/postcss@4.1.11": - resolution: - { integrity: sha512-q/EAIIpF6WpLhKEuQSEVMZNMIY8KhWoAemZ9eylNAih9jxMGAYPPWBn3I9QL/2jZ+e7OEz/tZkX5HwbBR4HohA== } + '@transloadit/prettier-bytes@0.3.5': + resolution: {integrity: sha512-xF4A3d/ZyX2LJWeQZREZQw+qFX4TGQ8bGVP97OLRt6sPO6T0TNHBFTuRHOJh7RNmYOBmQ9MHxpolD9bXihpuVA==} - "@tybys/wasm-util@0.9.0": - resolution: - { integrity: sha512-6+7nlbMVX/PVDCwaIQ8nTOPveOcFLSt8GcXdx8hD0bt39uWxYT88uXzqTd4fTvqta7oeUJqudepapKNt2DYJFw== } + '@tybys/wasm-util@0.9.0': + resolution: {integrity: sha512-6+7nlbMVX/PVDCwaIQ8nTOPveOcFLSt8GcXdx8hD0bt39uWxYT88uXzqTd4fTvqta7oeUJqudepapKNt2DYJFw==} - "@types/estree@1.0.8": - resolution: - { integrity: sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w== } + '@types/estree@1.0.8': + resolution: {integrity: sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==} - "@types/js-cookie@3.0.6": - resolution: - { integrity: sha512-wkw9yd1kEXOPnvEeEV1Go1MmxtBJL0RR79aOTAApecWFVu7w0NNXNqhcWgvw2YgZDYadliXkl14pa3WXw5jlCQ== } + '@types/js-cookie@3.0.6': + resolution: {integrity: sha512-wkw9yd1kEXOPnvEeEV1Go1MmxtBJL0RR79aOTAApecWFVu7w0NNXNqhcWgvw2YgZDYadliXkl14pa3WXw5jlCQ==} - "@types/json-schema@7.0.15": - resolution: - { integrity: sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA== } + '@types/json-schema@7.0.15': + resolution: {integrity: sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==} - "@types/json5@0.0.29": - resolution: - { integrity: sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ== } + '@types/json5@0.0.29': + resolution: {integrity: sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==} - "@types/node@22.14.0": - resolution: - { integrity: sha512-Kmpl+z84ILoG+3T/zQFyAJsU6EPTmOCj8/2+83fSN6djd6I4o7uOuGIH6vq3PrjY5BGitSbFuMN18j3iknubbA== } + '@types/node@22.14.0': + resolution: {integrity: sha512-Kmpl+z84ILoG+3T/zQFyAJsU6EPTmOCj8/2+83fSN6djd6I4o7uOuGIH6vq3PrjY5BGitSbFuMN18j3iknubbA==} - "@types/qrcode@1.5.5": - resolution: - { integrity: sha512-CdfBi/e3Qk+3Z/fXYShipBT13OJ2fDO2Q2w5CIP5anLTLIndQG9z6P1cnm+8zCWSpm5dnxMFd/uREtb0EXuQzg== } + '@types/qrcode@1.5.5': + resolution: {integrity: sha512-CdfBi/e3Qk+3Z/fXYShipBT13OJ2fDO2Q2w5CIP5anLTLIndQG9z6P1cnm+8zCWSpm5dnxMFd/uREtb0EXuQzg==} - "@types/react-dom@19.1.6": - resolution: - { integrity: sha512-4hOiT/dwO8Ko0gV1m/TJZYk3y0KBnY9vzDh7W+DH17b2HFSOGgdj33dhihPeuy3l0q23+4e+hoXHV6hCC4dCXw== } + '@types/react-dom@19.1.6': + resolution: {integrity: sha512-4hOiT/dwO8Ko0gV1m/TJZYk3y0KBnY9vzDh7W+DH17b2HFSOGgdj33dhihPeuy3l0q23+4e+hoXHV6hCC4dCXw==} peerDependencies: - "@types/react": ^19.0.0 + '@types/react': ^19.0.0 - "@types/react-dropzone@5.1.0": - resolution: - { integrity: sha512-VCdDCwSsr1MT2frsVl5p8qH+LWwUGzsaNtGkEQekHviZqK0dmTbiIp2Pzfb8lTkH4oTE2JtBbWnbuM6B4FH80A== } + '@types/react-dropzone@5.1.0': + resolution: {integrity: sha512-VCdDCwSsr1MT2frsVl5p8qH+LWwUGzsaNtGkEQekHviZqK0dmTbiIp2Pzfb8lTkH4oTE2JtBbWnbuM6B4FH80A==} deprecated: This is a stub types definition. react-dropzone provides its own type definitions, so you do not need this installed. - "@types/react@19.1.8": - resolution: - { integrity: sha512-AwAfQ2Wa5bCx9WP8nZL2uMZWod7J7/JSplxbTmBQ5ms6QpqNYm672H0Vu9ZVKVngQ+ii4R/byguVEUZQyeg44g== } + '@types/react@19.1.8': + resolution: {integrity: sha512-AwAfQ2Wa5bCx9WP8nZL2uMZWod7J7/JSplxbTmBQ5ms6QpqNYm672H0Vu9ZVKVngQ+ii4R/byguVEUZQyeg44g==} - "@types/use-sync-external-store@0.0.6": - resolution: - { integrity: sha512-zFDAD+tlpf2r4asuHEj0XH6pY6i0g5NeAHPn+15wk3BV6JA69eERFXC1gyGThDkVa1zCyKr5jox1+2LbV/AMLg== } + '@types/retry@0.12.2': + resolution: {integrity: sha512-XISRgDJ2Tc5q4TRqvgJtzsRkFYNJzZrhTdtMoGVBttwzzQJkPnS3WWTFc7kuDRoPtPakl+T+OfdEUjYJj7Jbow==} - "@typescript-eslint/eslint-plugin@8.35.1": - resolution: - { integrity: sha512-9XNTlo7P7RJxbVeICaIIIEipqxLKguyh+3UbXuT2XQuFp6d8VOeDEGuz5IiX0dgZo8CiI6aOFLg4e8cF71SFVg== } - engines: { node: ^18.18.0 || ^20.9.0 || >=21.1.0 } + '@types/use-sync-external-store@0.0.6': + resolution: {integrity: sha512-zFDAD+tlpf2r4asuHEj0XH6pY6i0g5NeAHPn+15wk3BV6JA69eERFXC1gyGThDkVa1zCyKr5jox1+2LbV/AMLg==} + + '@typescript-eslint/eslint-plugin@8.35.1': + resolution: {integrity: sha512-9XNTlo7P7RJxbVeICaIIIEipqxLKguyh+3UbXuT2XQuFp6d8VOeDEGuz5IiX0dgZo8CiI6aOFLg4e8cF71SFVg==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: - "@typescript-eslint/parser": ^8.35.1 + '@typescript-eslint/parser': ^8.35.1 eslint: ^8.57.0 || ^9.0.0 - typescript: ">=4.8.4 <5.9.0" + typescript: '>=4.8.4 <5.9.0' - "@typescript-eslint/parser@8.35.1": - resolution: - { integrity: sha512-3MyiDfrfLeK06bi/g9DqJxP5pV74LNv4rFTyvGDmT3x2p1yp1lOd+qYZfiRPIOf/oON+WRZR5wxxuF85qOar+w== } - engines: { node: ^18.18.0 || ^20.9.0 || >=21.1.0 } + '@typescript-eslint/parser@8.35.1': + resolution: {integrity: sha512-3MyiDfrfLeK06bi/g9DqJxP5pV74LNv4rFTyvGDmT3x2p1yp1lOd+qYZfiRPIOf/oON+WRZR5wxxuF85qOar+w==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: eslint: ^8.57.0 || ^9.0.0 - typescript: ">=4.8.4 <5.9.0" + typescript: '>=4.8.4 <5.9.0' - "@typescript-eslint/project-service@8.35.1": - resolution: - { integrity: sha512-VYxn/5LOpVxADAuP3NrnxxHYfzVtQzLKeldIhDhzC8UHaiQvYlXvKuVho1qLduFbJjjy5U5bkGwa3rUGUb1Q6Q== } - engines: { node: ^18.18.0 || ^20.9.0 || >=21.1.0 } + '@typescript-eslint/project-service@8.35.1': + resolution: {integrity: sha512-VYxn/5LOpVxADAuP3NrnxxHYfzVtQzLKeldIhDhzC8UHaiQvYlXvKuVho1qLduFbJjjy5U5bkGwa3rUGUb1Q6Q==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: - typescript: ">=4.8.4 <5.9.0" + typescript: '>=4.8.4 <5.9.0' - "@typescript-eslint/scope-manager@8.35.1": - resolution: - { integrity: sha512-s/Bpd4i7ht2934nG+UoSPlYXd08KYz3bmjLEb7Ye1UVob0d1ENiT3lY8bsCmik4RqfSbPw9xJJHbugpPpP5JUg== } - engines: { node: ^18.18.0 || ^20.9.0 || >=21.1.0 } + '@typescript-eslint/scope-manager@8.35.1': + resolution: {integrity: sha512-s/Bpd4i7ht2934nG+UoSPlYXd08KYz3bmjLEb7Ye1UVob0d1ENiT3lY8bsCmik4RqfSbPw9xJJHbugpPpP5JUg==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - "@typescript-eslint/tsconfig-utils@8.35.1": - resolution: - { integrity: sha512-K5/U9VmT9dTHoNowWZpz+/TObS3xqC5h0xAIjXPw+MNcKV9qg6eSatEnmeAwkjHijhACH0/N7bkhKvbt1+DXWQ== } - engines: { node: ^18.18.0 || ^20.9.0 || >=21.1.0 } + '@typescript-eslint/tsconfig-utils@8.35.1': + resolution: {integrity: sha512-K5/U9VmT9dTHoNowWZpz+/TObS3xqC5h0xAIjXPw+MNcKV9qg6eSatEnmeAwkjHijhACH0/N7bkhKvbt1+DXWQ==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: - typescript: ">=4.8.4 <5.9.0" + typescript: '>=4.8.4 <5.9.0' - "@typescript-eslint/type-utils@8.35.1": - resolution: - { integrity: sha512-HOrUBlfVRz5W2LIKpXzZoy6VTZzMu2n8q9C2V/cFngIC5U1nStJgv0tMV4sZPzdf4wQm9/ToWUFPMN9Vq9VJQQ== } - engines: { node: ^18.18.0 || ^20.9.0 || >=21.1.0 } + '@typescript-eslint/type-utils@8.35.1': + resolution: {integrity: sha512-HOrUBlfVRz5W2LIKpXzZoy6VTZzMu2n8q9C2V/cFngIC5U1nStJgv0tMV4sZPzdf4wQm9/ToWUFPMN9Vq9VJQQ==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: eslint: ^8.57.0 || ^9.0.0 - typescript: ">=4.8.4 <5.9.0" + typescript: '>=4.8.4 <5.9.0' - "@typescript-eslint/types@8.35.1": - resolution: - { integrity: sha512-q/O04vVnKHfrrhNAscndAn1tuQhIkwqnaW+eu5waD5IPts2eX1dgJxgqcPx5BX109/qAz7IG6VrEPTOYKCNfRQ== } - engines: { node: ^18.18.0 || ^20.9.0 || >=21.1.0 } + '@typescript-eslint/types@8.35.1': + resolution: {integrity: sha512-q/O04vVnKHfrrhNAscndAn1tuQhIkwqnaW+eu5waD5IPts2eX1dgJxgqcPx5BX109/qAz7IG6VrEPTOYKCNfRQ==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - "@typescript-eslint/typescript-estree@8.35.1": - resolution: - { integrity: sha512-Vvpuvj4tBxIka7cPs6Y1uvM7gJgdF5Uu9F+mBJBPY4MhvjrjWGK4H0lVgLJd/8PWZ23FTqsaJaLEkBCFUk8Y9g== } - engines: { node: ^18.18.0 || ^20.9.0 || >=21.1.0 } + '@typescript-eslint/typescript-estree@8.35.1': + resolution: {integrity: sha512-Vvpuvj4tBxIka7cPs6Y1uvM7gJgdF5Uu9F+mBJBPY4MhvjrjWGK4H0lVgLJd/8PWZ23FTqsaJaLEkBCFUk8Y9g==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: - typescript: ">=4.8.4 <5.9.0" + typescript: '>=4.8.4 <5.9.0' - "@typescript-eslint/utils@8.35.1": - resolution: - { integrity: sha512-lhnwatFmOFcazAsUm3ZnZFpXSxiwoa1Lj50HphnDe1Et01NF4+hrdXONSUHIcbVu2eFb1bAf+5yjXkGVkXBKAQ== } - engines: { node: ^18.18.0 || ^20.9.0 || >=21.1.0 } + '@typescript-eslint/utils@8.35.1': + resolution: {integrity: sha512-lhnwatFmOFcazAsUm3ZnZFpXSxiwoa1Lj50HphnDe1Et01NF4+hrdXONSUHIcbVu2eFb1bAf+5yjXkGVkXBKAQ==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: eslint: ^8.57.0 || ^9.0.0 - typescript: ">=4.8.4 <5.9.0" + typescript: '>=4.8.4 <5.9.0' - "@typescript-eslint/visitor-keys@8.35.1": - resolution: - { integrity: sha512-VRwixir4zBWCSTP/ljEo091lbpypz57PoeAQ9imjG+vbeof9LplljsL1mos4ccG6H9IjfrVGM359RozUnuFhpw== } - engines: { node: ^18.18.0 || ^20.9.0 || >=21.1.0 } + '@typescript-eslint/visitor-keys@8.35.1': + resolution: {integrity: sha512-VRwixir4zBWCSTP/ljEo091lbpypz57PoeAQ9imjG+vbeof9LplljsL1mos4ccG6H9IjfrVGM359RozUnuFhpw==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - "@unrs/resolver-binding-android-arm-eabi@1.11.0": - resolution: - { integrity: sha512-LRw5BW29sYj9NsQC6QoqeLVQhEa+BwVINYyMlcve+6stwdBsSt5UB7zw4UZB4+4PNqIVilHoMaPWCb/KhABHQw== } + '@unrs/resolver-binding-android-arm-eabi@1.11.0': + resolution: {integrity: sha512-LRw5BW29sYj9NsQC6QoqeLVQhEa+BwVINYyMlcve+6stwdBsSt5UB7zw4UZB4+4PNqIVilHoMaPWCb/KhABHQw==} cpu: [arm] os: [android] - "@unrs/resolver-binding-android-arm64@1.11.0": - resolution: - { integrity: sha512-zYX8D2zcWCAHqghA8tPjbp7LwjVXbIZP++mpU/Mrf5jUVlk3BWIxkeB8yYzZi5GpFSlqMcRZQxQqbMI0c2lASQ== } + '@unrs/resolver-binding-android-arm64@1.11.0': + resolution: {integrity: sha512-zYX8D2zcWCAHqghA8tPjbp7LwjVXbIZP++mpU/Mrf5jUVlk3BWIxkeB8yYzZi5GpFSlqMcRZQxQqbMI0c2lASQ==} cpu: [arm64] os: [android] - "@unrs/resolver-binding-darwin-arm64@1.11.0": - resolution: - { integrity: sha512-YsYOT049hevAY/lTYD77GhRs885EXPeAfExG5KenqMJ417nYLS2N/kpRpYbABhFZBVQn+2uRPasTe4ypmYoo3w== } + '@unrs/resolver-binding-darwin-arm64@1.11.0': + resolution: {integrity: sha512-YsYOT049hevAY/lTYD77GhRs885EXPeAfExG5KenqMJ417nYLS2N/kpRpYbABhFZBVQn+2uRPasTe4ypmYoo3w==} cpu: [arm64] os: [darwin] - "@unrs/resolver-binding-darwin-x64@1.11.0": - resolution: - { integrity: sha512-PSjvk3OZf1aZImdGY5xj9ClFG3bC4gnSSYWrt+id0UAv+GwwVldhpMFjAga8SpMo2T1GjV9UKwM+QCsQCQmtdA== } + '@unrs/resolver-binding-darwin-x64@1.11.0': + resolution: {integrity: sha512-PSjvk3OZf1aZImdGY5xj9ClFG3bC4gnSSYWrt+id0UAv+GwwVldhpMFjAga8SpMo2T1GjV9UKwM+QCsQCQmtdA==} cpu: [x64] os: [darwin] - "@unrs/resolver-binding-freebsd-x64@1.11.0": - resolution: - { integrity: sha512-KC/iFaEN/wsTVYnHClyHh5RSYA9PpuGfqkFua45r4sweXpC0KHZ+BYY7ikfcGPt5w1lMpR1gneFzuqWLQxsRKg== } + '@unrs/resolver-binding-freebsd-x64@1.11.0': + resolution: {integrity: sha512-KC/iFaEN/wsTVYnHClyHh5RSYA9PpuGfqkFua45r4sweXpC0KHZ+BYY7ikfcGPt5w1lMpR1gneFzuqWLQxsRKg==} cpu: [x64] os: [freebsd] - "@unrs/resolver-binding-linux-arm-gnueabihf@1.11.0": - resolution: - { integrity: sha512-CDh/0v8uot43cB4yKtDL9CVY8pbPnMV0dHyQCE4lFz6PW/+9tS0i9eqP5a91PAqEBVMqH1ycu+k8rP6wQU846w== } + '@unrs/resolver-binding-linux-arm-gnueabihf@1.11.0': + resolution: {integrity: sha512-CDh/0v8uot43cB4yKtDL9CVY8pbPnMV0dHyQCE4lFz6PW/+9tS0i9eqP5a91PAqEBVMqH1ycu+k8rP6wQU846w==} cpu: [arm] os: [linux] - "@unrs/resolver-binding-linux-arm-musleabihf@1.11.0": - resolution: - { integrity: sha512-+TE7epATDSnvwr3L/hNHX3wQ8KQYB+jSDTdywycg3qDqvavRP8/HX9qdq/rMcnaRDn4EOtallb3vL/5wCWGCkw== } + '@unrs/resolver-binding-linux-arm-musleabihf@1.11.0': + resolution: {integrity: sha512-+TE7epATDSnvwr3L/hNHX3wQ8KQYB+jSDTdywycg3qDqvavRP8/HX9qdq/rMcnaRDn4EOtallb3vL/5wCWGCkw==} cpu: [arm] os: [linux] - "@unrs/resolver-binding-linux-arm64-gnu@1.11.0": - resolution: - { integrity: sha512-VBAYGg3VahofpQ+L4k/ZO8TSICIbUKKTaMYOWHWfuYBFqPbSkArZZLezw3xd27fQkxX4BaLGb/RKnW0dH9Y/UA== } + '@unrs/resolver-binding-linux-arm64-gnu@1.11.0': + resolution: {integrity: sha512-VBAYGg3VahofpQ+L4k/ZO8TSICIbUKKTaMYOWHWfuYBFqPbSkArZZLezw3xd27fQkxX4BaLGb/RKnW0dH9Y/UA==} cpu: [arm64] os: [linux] - "@unrs/resolver-binding-linux-arm64-musl@1.11.0": - resolution: - { integrity: sha512-9IgGFUUb02J1hqdRAHXpZHIeUHRrbnGo6vrRbz0fREH7g+rzQy53/IBSyadZ/LG5iqMxukriNPu4hEMUn+uWEg== } + '@unrs/resolver-binding-linux-arm64-musl@1.11.0': + resolution: {integrity: sha512-9IgGFUUb02J1hqdRAHXpZHIeUHRrbnGo6vrRbz0fREH7g+rzQy53/IBSyadZ/LG5iqMxukriNPu4hEMUn+uWEg==} cpu: [arm64] os: [linux] - "@unrs/resolver-binding-linux-ppc64-gnu@1.11.0": - resolution: - { integrity: sha512-LR4iQ/LPjMfivpL2bQ9kmm3UnTas3U+umcCnq/CV7HAkukVdHxrDD1wwx74MIWbbgzQTLPYY7Ur2MnnvkYJCBQ== } + '@unrs/resolver-binding-linux-ppc64-gnu@1.11.0': + resolution: {integrity: sha512-LR4iQ/LPjMfivpL2bQ9kmm3UnTas3U+umcCnq/CV7HAkukVdHxrDD1wwx74MIWbbgzQTLPYY7Ur2MnnvkYJCBQ==} cpu: [ppc64] os: [linux] - "@unrs/resolver-binding-linux-riscv64-gnu@1.11.0": - resolution: - { integrity: sha512-HCupFQwMrRhrOg7YHrobbB5ADg0Q8RNiuefqMHVsdhEy9lLyXm/CxsCXeLJdrg27NAPsCaMDtdlm8Z2X8x91Tg== } + '@unrs/resolver-binding-linux-riscv64-gnu@1.11.0': + resolution: {integrity: sha512-HCupFQwMrRhrOg7YHrobbB5ADg0Q8RNiuefqMHVsdhEy9lLyXm/CxsCXeLJdrg27NAPsCaMDtdlm8Z2X8x91Tg==} cpu: [riscv64] os: [linux] - "@unrs/resolver-binding-linux-riscv64-musl@1.11.0": - resolution: - { integrity: sha512-Ckxy76A5xgjWa4FNrzcKul5qFMWgP5JSQ5YKd0XakmWOddPLSkQT+uAvUpQNnFGNbgKzv90DyQlxPDYPQ4nd6A== } + '@unrs/resolver-binding-linux-riscv64-musl@1.11.0': + resolution: {integrity: sha512-Ckxy76A5xgjWa4FNrzcKul5qFMWgP5JSQ5YKd0XakmWOddPLSkQT+uAvUpQNnFGNbgKzv90DyQlxPDYPQ4nd6A==} cpu: [riscv64] os: [linux] - "@unrs/resolver-binding-linux-s390x-gnu@1.11.0": - resolution: - { integrity: sha512-HfO0PUCCRte2pMJmVyxPI+eqT7KuV3Fnvn2RPvMe5mOzb2BJKf4/Vth8sSt9cerQboMaTVpbxyYjjLBWIuI5BQ== } + '@unrs/resolver-binding-linux-s390x-gnu@1.11.0': + resolution: {integrity: sha512-HfO0PUCCRte2pMJmVyxPI+eqT7KuV3Fnvn2RPvMe5mOzb2BJKf4/Vth8sSt9cerQboMaTVpbxyYjjLBWIuI5BQ==} cpu: [s390x] os: [linux] - "@unrs/resolver-binding-linux-x64-gnu@1.11.0": - resolution: - { integrity: sha512-9PZdjP7tLOEjpXHS6+B/RNqtfVUyDEmaViPOuSqcbomLdkJnalt5RKQ1tr2m16+qAufV0aDkfhXtoO7DQos/jg== } + '@unrs/resolver-binding-linux-x64-gnu@1.11.0': + resolution: {integrity: sha512-9PZdjP7tLOEjpXHS6+B/RNqtfVUyDEmaViPOuSqcbomLdkJnalt5RKQ1tr2m16+qAufV0aDkfhXtoO7DQos/jg==} cpu: [x64] os: [linux] - "@unrs/resolver-binding-linux-x64-musl@1.11.0": - resolution: - { integrity: sha512-qkE99ieiSKMnFJY/EfyGKVtNra52/k+lVF/PbO4EL5nU6AdvG4XhtJ+WHojAJP7ID9BNIra/yd75EHndewNRfA== } + '@unrs/resolver-binding-linux-x64-musl@1.11.0': + resolution: {integrity: sha512-qkE99ieiSKMnFJY/EfyGKVtNra52/k+lVF/PbO4EL5nU6AdvG4XhtJ+WHojAJP7ID9BNIra/yd75EHndewNRfA==} cpu: [x64] os: [linux] - "@unrs/resolver-binding-wasm32-wasi@1.11.0": - resolution: - { integrity: sha512-MjXek8UL9tIX34gymvQLecz2hMaQzOlaqYJJBomwm1gsvK2F7hF+YqJJ2tRyBDTv9EZJGMt4KlKkSD/gZWCOiw== } - engines: { node: ">=14.0.0" } + '@unrs/resolver-binding-wasm32-wasi@1.11.0': + resolution: {integrity: sha512-MjXek8UL9tIX34gymvQLecz2hMaQzOlaqYJJBomwm1gsvK2F7hF+YqJJ2tRyBDTv9EZJGMt4KlKkSD/gZWCOiw==} + engines: {node: '>=14.0.0'} cpu: [wasm32] - "@unrs/resolver-binding-win32-arm64-msvc@1.11.0": - resolution: - { integrity: sha512-9LT6zIGO7CHybiQSh7DnQGwFMZvVr0kUjah6qQfkH2ghucxPV6e71sUXJdSM4Ba0MaGE6DC/NwWf7mJmc3DAng== } + '@unrs/resolver-binding-win32-arm64-msvc@1.11.0': + resolution: {integrity: sha512-9LT6zIGO7CHybiQSh7DnQGwFMZvVr0kUjah6qQfkH2ghucxPV6e71sUXJdSM4Ba0MaGE6DC/NwWf7mJmc3DAng==} cpu: [arm64] os: [win32] - "@unrs/resolver-binding-win32-ia32-msvc@1.11.0": - resolution: - { integrity: sha512-HYchBYOZ7WN266VjoGm20xFv5EonG/ODURRgwl9EZT7Bq1nLEs6VKJddzfFdXEAho0wfFlt8L/xIiE29Pmy1RA== } + '@unrs/resolver-binding-win32-ia32-msvc@1.11.0': + resolution: {integrity: sha512-HYchBYOZ7WN266VjoGm20xFv5EonG/ODURRgwl9EZT7Bq1nLEs6VKJddzfFdXEAho0wfFlt8L/xIiE29Pmy1RA==} cpu: [ia32] os: [win32] - "@unrs/resolver-binding-win32-x64-msvc@1.11.0": - resolution: - { integrity: sha512-+oLKLHw3I1UQo4MeHfoLYF+e6YBa8p5vYUw3Rgt7IDzCs+57vIZqQlIo62NDpYM0VG6BjWOwnzBczMvbtH8hag== } + '@unrs/resolver-binding-win32-x64-msvc@1.11.0': + resolution: {integrity: sha512-+oLKLHw3I1UQo4MeHfoLYF+e6YBa8p5vYUw3Rgt7IDzCs+57vIZqQlIo62NDpYM0VG6BjWOwnzBczMvbtH8hag==} cpu: [x64] os: [win32] - "@zxing/browser@0.0.7": - resolution: - { integrity: sha512-AepzMgDnD6EjxewqmXpHJsi4S3Gw9ilZJLIbTf6fWuWySEcHBodnGu3p7FWlgq1Sd5QyfPhTum5z3CBkkhMVng== } + '@uppy/aws-s3@4.3.2': + resolution: {integrity: sha512-w3LrMUEI9pXVcn5LZ5oNL92hyocMu8mxfDSVVQIAQKxR+TPi57FfusDBqBB5T33UvxJH8EUGt9a04tuU/LpKqw==} + peerDependencies: + '@uppy/core': ^4.5.2 + + '@uppy/companion-client@4.5.2': + resolution: {integrity: sha512-hfUsReHM5COhn+5d7CdZgZaG8BtDvtwj7vjXzg8qmgKI901mYUm/Zh420iOKT7eHiofKVTNoa7oijeGrqUEnyg==} peerDependencies: - "@zxing/library": ^0.18.3 + '@uppy/core': ^4.5.2 - "@zxing/library@0.18.6": - resolution: - { integrity: sha512-bulZ9JHoLFd9W36pi+7e7DnEYNJhljYjZ1UTsKPOoLMU3qtC+REHITeCRNx40zTRJZx18W5TBRXt5pq2Uopjsw== } - engines: { node: ">= 10.4.0" } + '@uppy/core@4.5.3': + resolution: {integrity: sha512-52VLeBUY/j904h48lpPGykuWikkOOS4Lz/qkmalDiBQfNALb6iB1MOZs079IM3o/uMLYxzZRL80C3sKpkBUYcw==} - "@zxing/text-encoding@0.9.0": - resolution: - { integrity: sha512-U/4aVJ2mxI0aDNI8Uq0wEhMgY+u4CNtEb0om3+y3+niDAsoTCOB33UF0sxpzqzdqXLqmvc+vZyAt4O8pPdfkwA== } + '@uppy/store-default@4.3.2': + resolution: {integrity: sha512-dnY9R2o8fwmO1bF89D0b5jijD7DGED2qVST5hI/j18JreLWzLKH7u6HuNmOvzok8msrQ/qWzQd5Gx4LDQKhBbw==} + + '@uppy/utils@6.2.2': + resolution: {integrity: sha512-9mYJtbcngv2HOJIECkyfmdXTI5dW/ObCyvWP1Iti3E5bKtsa4sMmbx5Yh/tGCj8k/lBNhfvWyZuYnvnjmzNLSQ==} + + '@zxing/browser@0.0.7': + resolution: {integrity: sha512-AepzMgDnD6EjxewqmXpHJsi4S3Gw9ilZJLIbTf6fWuWySEcHBodnGu3p7FWlgq1Sd5QyfPhTum5z3CBkkhMVng==} + peerDependencies: + '@zxing/library': ^0.18.3 + + '@zxing/library@0.18.6': + resolution: {integrity: sha512-bulZ9JHoLFd9W36pi+7e7DnEYNJhljYjZ1UTsKPOoLMU3qtC+REHITeCRNx40zTRJZx18W5TBRXt5pq2Uopjsw==} + engines: {node: '>= 10.4.0'} + + '@zxing/text-encoding@0.9.0': + resolution: {integrity: sha512-U/4aVJ2mxI0aDNI8Uq0wEhMgY+u4CNtEb0om3+y3+niDAsoTCOB33UF0sxpzqzdqXLqmvc+vZyAt4O8pPdfkwA==} acorn-jsx@5.3.2: - resolution: - { integrity: sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ== } + resolution: {integrity: sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==} peerDependencies: acorn: ^6.0.0 || ^7.0.0 || ^8.0.0 acorn@8.15.0: - resolution: - { integrity: sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg== } - engines: { node: ">=0.4.0" } + resolution: {integrity: sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==} + engines: {node: '>=0.4.0'} hasBin: true ajv@6.12.6: - resolution: - { integrity: sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g== } + resolution: {integrity: sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==} ansi-regex@5.0.1: - resolution: - { integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ== } - engines: { node: ">=8" } + resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==} + engines: {node: '>=8'} ansi-styles@4.3.0: - resolution: - { integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg== } - engines: { node: ">=8" } + resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==} + engines: {node: '>=8'} argparse@2.0.1: - resolution: - { integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q== } + resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==} aria-hidden@1.2.6: - resolution: - { integrity: sha512-ik3ZgC9dY/lYVVM++OISsaYDeg1tb0VtP5uL3ouh1koGOaUMDPpbFIei4JkFimWUFPn90sbMNMXQAIVOlnYKJA== } - engines: { node: ">=10" } + resolution: {integrity: sha512-ik3ZgC9dY/lYVVM++OISsaYDeg1tb0VtP5uL3ouh1koGOaUMDPpbFIei4JkFimWUFPn90sbMNMXQAIVOlnYKJA==} + engines: {node: '>=10'} aria-query@5.3.2: - resolution: - { integrity: sha512-COROpnaoap1E2F000S62r6A60uHZnmlvomhfyT2DlTcrY1OrBKn2UhH7qn5wTC9zMvD0AY7csdPSNwKP+7WiQw== } - engines: { node: ">= 0.4" } + resolution: {integrity: sha512-COROpnaoap1E2F000S62r6A60uHZnmlvomhfyT2DlTcrY1OrBKn2UhH7qn5wTC9zMvD0AY7csdPSNwKP+7WiQw==} + engines: {node: '>= 0.4'} array-buffer-byte-length@1.0.2: - resolution: - { integrity: sha512-LHE+8BuR7RYGDKvnrmcuSq3tDcKv9OFEXQt/HpbZhY7V6h0zlUXutnAD82GiFx9rdieCMjkvtcsPqBwgUl1Iiw== } - engines: { node: ">= 0.4" } + resolution: {integrity: sha512-LHE+8BuR7RYGDKvnrmcuSq3tDcKv9OFEXQt/HpbZhY7V6h0zlUXutnAD82GiFx9rdieCMjkvtcsPqBwgUl1Iiw==} + engines: {node: '>= 0.4'} array-includes@3.1.9: - resolution: - { integrity: sha512-FmeCCAenzH0KH381SPT5FZmiA/TmpndpcaShhfgEN9eCVjnFBqq3l1xrI42y8+PPLI6hypzou4GXw00WHmPBLQ== } - engines: { node: ">= 0.4" } + resolution: {integrity: sha512-FmeCCAenzH0KH381SPT5FZmiA/TmpndpcaShhfgEN9eCVjnFBqq3l1xrI42y8+PPLI6hypzou4GXw00WHmPBLQ==} + engines: {node: '>= 0.4'} array.prototype.findlast@1.2.5: - resolution: - { integrity: sha512-CVvd6FHg1Z3POpBLxO6E6zr+rSKEQ9L6rZHAaY7lLfhKsWYUBBOuMs0e9o24oopj6H+geRCX0YJ+TJLBK2eHyQ== } - engines: { node: ">= 0.4" } + resolution: {integrity: sha512-CVvd6FHg1Z3POpBLxO6E6zr+rSKEQ9L6rZHAaY7lLfhKsWYUBBOuMs0e9o24oopj6H+geRCX0YJ+TJLBK2eHyQ==} + engines: {node: '>= 0.4'} array.prototype.findlastindex@1.2.6: - resolution: - { integrity: sha512-F/TKATkzseUExPlfvmwQKGITM3DGTK+vkAsCZoDc5daVygbJBnjEUCbgkAvVFsgfXfX4YIqZ/27G3k3tdXrTxQ== } - engines: { node: ">= 0.4" } + resolution: {integrity: sha512-F/TKATkzseUExPlfvmwQKGITM3DGTK+vkAsCZoDc5daVygbJBnjEUCbgkAvVFsgfXfX4YIqZ/27G3k3tdXrTxQ==} + engines: {node: '>= 0.4'} array.prototype.flat@1.3.3: - resolution: - { integrity: sha512-rwG/ja1neyLqCuGZ5YYrznA62D4mZXg0i1cIskIUKSiqF3Cje9/wXAls9B9s1Wa2fomMsIv8czB8jZcPmxCXFg== } - engines: { node: ">= 0.4" } + resolution: {integrity: sha512-rwG/ja1neyLqCuGZ5YYrznA62D4mZXg0i1cIskIUKSiqF3Cje9/wXAls9B9s1Wa2fomMsIv8czB8jZcPmxCXFg==} + engines: {node: '>= 0.4'} array.prototype.flatmap@1.3.3: - resolution: - { integrity: sha512-Y7Wt51eKJSyi80hFrJCePGGNo5ktJCslFuboqJsbf57CCPcm5zztluPlc4/aD8sWsKvlwatezpV4U1efk8kpjg== } - engines: { node: ">= 0.4" } + resolution: {integrity: sha512-Y7Wt51eKJSyi80hFrJCePGGNo5ktJCslFuboqJsbf57CCPcm5zztluPlc4/aD8sWsKvlwatezpV4U1efk8kpjg==} + engines: {node: '>= 0.4'} array.prototype.tosorted@1.1.4: - resolution: - { integrity: sha512-p6Fx8B7b7ZhL/gmUsAy0D15WhvDccw3mnGNbZpi3pmeJdxtWsj2jEaI4Y6oo3XiHfzuSgPwKc04MYt6KgvC/wA== } - engines: { node: ">= 0.4" } + resolution: {integrity: sha512-p6Fx8B7b7ZhL/gmUsAy0D15WhvDccw3mnGNbZpi3pmeJdxtWsj2jEaI4Y6oo3XiHfzuSgPwKc04MYt6KgvC/wA==} + engines: {node: '>= 0.4'} arraybuffer.prototype.slice@1.0.4: - resolution: - { integrity: sha512-BNoCY6SXXPQ7gF2opIP4GBE+Xw7U+pHMYKuzjgCN3GwiaIR09UUeKfheyIry77QtrCBlC0KK0q5/TER/tYh3PQ== } - engines: { node: ">= 0.4" } + resolution: {integrity: sha512-BNoCY6SXXPQ7gF2opIP4GBE+Xw7U+pHMYKuzjgCN3GwiaIR09UUeKfheyIry77QtrCBlC0KK0q5/TER/tYh3PQ==} + engines: {node: '>= 0.4'} ast-types-flow@0.0.8: - resolution: - { integrity: sha512-OH/2E5Fg20h2aPrbe+QL8JZQFko0YZaF+j4mnQ7BGhfavO7OpSLa8a0y9sBwomHdSbkhTS8TQNayBfnW5DwbvQ== } + resolution: {integrity: sha512-OH/2E5Fg20h2aPrbe+QL8JZQFko0YZaF+j4mnQ7BGhfavO7OpSLa8a0y9sBwomHdSbkhTS8TQNayBfnW5DwbvQ==} async-function@1.0.0: - resolution: - { integrity: sha512-hsU18Ae8CDTR6Kgu9DYf0EbCr/a5iGL0rytQDobUcdpYOKokk8LEjVphnXkDkgpi0wYVsqrXuP0bZxJaTqdgoA== } - engines: { node: ">= 0.4" } + resolution: {integrity: sha512-hsU18Ae8CDTR6Kgu9DYf0EbCr/a5iGL0rytQDobUcdpYOKokk8LEjVphnXkDkgpi0wYVsqrXuP0bZxJaTqdgoA==} + engines: {node: '>= 0.4'} asynckit@0.4.0: - resolution: - { integrity: sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q== } + resolution: {integrity: sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==} attr-accept@2.2.5: - resolution: - { integrity: sha512-0bDNnY/u6pPwHDMoF0FieU354oBi0a8rD9FcsLwzcGWbc8KS8KPIi7y+s13OlVY+gMWc/9xEMUgNE6Qm8ZllYQ== } - engines: { node: ">=4" } + resolution: {integrity: sha512-0bDNnY/u6pPwHDMoF0FieU354oBi0a8rD9FcsLwzcGWbc8KS8KPIi7y+s13OlVY+gMWc/9xEMUgNE6Qm8ZllYQ==} + engines: {node: '>=4'} available-typed-arrays@1.0.7: - resolution: - { integrity: sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ== } - engines: { node: ">= 0.4" } + resolution: {integrity: sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ==} + engines: {node: '>= 0.4'} axe-core@4.10.3: - resolution: - { integrity: sha512-Xm7bpRXnDSX2YE2YFfBk2FnF0ep6tmG7xPh8iHee8MIcrgq762Nkce856dYtJYLkuIoYZvGfTs/PbZhideTcEg== } - engines: { node: ">=4" } + resolution: {integrity: sha512-Xm7bpRXnDSX2YE2YFfBk2FnF0ep6tmG7xPh8iHee8MIcrgq762Nkce856dYtJYLkuIoYZvGfTs/PbZhideTcEg==} + engines: {node: '>=4'} axios@1.10.0: - resolution: - { integrity: sha512-/1xYAC4MP/HEG+3duIhFr4ZQXR4sQXOIe+o6sdqzeykGLx6Upp/1p8MHqhINOvGeP7xyNHe7tsiJByc4SSVUxw== } + resolution: {integrity: sha512-/1xYAC4MP/HEG+3duIhFr4ZQXR4sQXOIe+o6sdqzeykGLx6Upp/1p8MHqhINOvGeP7xyNHe7tsiJByc4SSVUxw==} axobject-query@4.1.0: - resolution: - { integrity: sha512-qIj0G9wZbMGNLjLmg1PT6v2mE9AH2zlnADJD/2tC6E00hgmhUOfEB6greHPAfLRSufHqROIUTkw6E+M3lH0PTQ== } - engines: { node: ">= 0.4" } + resolution: {integrity: sha512-qIj0G9wZbMGNLjLmg1PT6v2mE9AH2zlnADJD/2tC6E00hgmhUOfEB6greHPAfLRSufHqROIUTkw6E+M3lH0PTQ==} + engines: {node: '>= 0.4'} balanced-match@1.0.2: - resolution: - { integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw== } + resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} brace-expansion@1.1.12: - resolution: - { integrity: sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg== } + resolution: {integrity: sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==} brace-expansion@2.0.2: - resolution: - { integrity: sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ== } + resolution: {integrity: sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==} braces@3.0.3: - resolution: - { integrity: sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA== } - engines: { node: ">=8" } + resolution: {integrity: sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==} + engines: {node: '>=8'} busboy@1.6.0: - resolution: - { integrity: sha512-8SFQbg/0hQ9xy3UNTB0YEnsNBbWfhf7RtnzpL7TkBiTBRfrQ9Fxcnz7VJsleJpyp6rVLvXiuORqjlHi5q+PYuA== } - engines: { node: ">=10.16.0" } + resolution: {integrity: sha512-8SFQbg/0hQ9xy3UNTB0YEnsNBbWfhf7RtnzpL7TkBiTBRfrQ9Fxcnz7VJsleJpyp6rVLvXiuORqjlHi5q+PYuA==} + engines: {node: '>=10.16.0'} call-bind-apply-helpers@1.0.2: - resolution: - { integrity: sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ== } - engines: { node: ">= 0.4" } + resolution: {integrity: sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==} + engines: {node: '>= 0.4'} call-bind@1.0.8: - resolution: - { integrity: sha512-oKlSFMcMwpUg2ednkhQ454wfWiU/ul3CkJe/PEHcTKuiX6RpbehUiFMXu13HalGZxfUwCQzZG747YXBn1im9ww== } - engines: { node: ">= 0.4" } + resolution: {integrity: sha512-oKlSFMcMwpUg2ednkhQ454wfWiU/ul3CkJe/PEHcTKuiX6RpbehUiFMXu13HalGZxfUwCQzZG747YXBn1im9ww==} + engines: {node: '>= 0.4'} call-bound@1.0.4: - resolution: - { integrity: sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg== } - engines: { node: ">= 0.4" } + resolution: {integrity: sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==} + engines: {node: '>= 0.4'} callsites@3.1.0: - resolution: - { integrity: sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ== } - engines: { node: ">=6" } + resolution: {integrity: sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==} + engines: {node: '>=6'} camelcase@5.3.1: - resolution: - { integrity: sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg== } - engines: { node: ">=6" } + resolution: {integrity: sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==} + engines: {node: '>=6'} caniuse-lite@1.0.30001727: - resolution: - { integrity: sha512-pB68nIHmbN6L/4C6MH1DokyR3bYqFwjaSs/sWDHGj4CTcFtQUQMuJftVwWkXq7mNWOybD3KhUv3oWHoGxgP14Q== } + resolution: {integrity: sha512-pB68nIHmbN6L/4C6MH1DokyR3bYqFwjaSs/sWDHGj4CTcFtQUQMuJftVwWkXq7mNWOybD3KhUv3oWHoGxgP14Q==} chalk@4.1.2: - resolution: - { integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA== } - engines: { node: ">=10" } + resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==} + engines: {node: '>=10'} chownr@3.0.0: - resolution: - { integrity: sha512-+IxzY9BZOQd/XuYPRmrvEVjF/nqj5kgT4kEq7VofrDoM1MxoRjEWkrCC3EtLi59TVawxTAn+orJwFQcrqEN1+g== } - engines: { node: ">=18" } + resolution: {integrity: sha512-+IxzY9BZOQd/XuYPRmrvEVjF/nqj5kgT4kEq7VofrDoM1MxoRjEWkrCC3EtLi59TVawxTAn+orJwFQcrqEN1+g==} + engines: {node: '>=18'} class-variance-authority@0.7.1: - resolution: - { integrity: sha512-Ka+9Trutv7G8M6WT6SeiRWz792K5qEqIGEGzXKhAE6xOWAY6pPH8U+9IY3oCMv6kqTmLsv7Xh/2w2RigkePMsg== } + resolution: {integrity: sha512-Ka+9Trutv7G8M6WT6SeiRWz792K5qEqIGEGzXKhAE6xOWAY6pPH8U+9IY3oCMv6kqTmLsv7Xh/2w2RigkePMsg==} client-only@0.0.1: - resolution: - { integrity: sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA== } + resolution: {integrity: sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA==} cliui@6.0.0: - resolution: - { integrity: sha512-t6wbgtoCXvAzst7QgXxJYqPt0usEfbgQdftEPbLL/cvv6HPE5VgvqCuAIDR0NgU52ds6rFwqrgakNLrHEjCbrQ== } + resolution: {integrity: sha512-t6wbgtoCXvAzst7QgXxJYqPt0usEfbgQdftEPbLL/cvv6HPE5VgvqCuAIDR0NgU52ds6rFwqrgakNLrHEjCbrQ==} clsx@2.1.1: - resolution: - { integrity: sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA== } - engines: { node: ">=6" } + resolution: {integrity: sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==} + engines: {node: '>=6'} color-convert@2.0.1: - resolution: - { integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ== } - engines: { node: ">=7.0.0" } + resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==} + engines: {node: '>=7.0.0'} color-name@1.1.4: - resolution: - { integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA== } + resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==} color-string@1.9.1: - resolution: - { integrity: sha512-shrVawQFojnZv6xM40anx4CkoDP+fZsw/ZerEMsW/pyzsRbElpsL/DBVW7q3ExxwusdNXI3lXpuhEZkzs8p5Eg== } + resolution: {integrity: sha512-shrVawQFojnZv6xM40anx4CkoDP+fZsw/ZerEMsW/pyzsRbElpsL/DBVW7q3ExxwusdNXI3lXpuhEZkzs8p5Eg==} color@4.2.3: - resolution: - { integrity: sha512-1rXeuUUiGGrykh+CeBdu5Ie7OJwinCgQY0bc7GCRxy5xVHy+moaqkpL/jqQq0MtQOeYcrqEz4abc5f0KtU7W4A== } - engines: { node: ">=12.5.0" } + resolution: {integrity: sha512-1rXeuUUiGGrykh+CeBdu5Ie7OJwinCgQY0bc7GCRxy5xVHy+moaqkpL/jqQq0MtQOeYcrqEz4abc5f0KtU7W4A==} + engines: {node: '>=12.5.0'} combined-stream@1.0.8: - resolution: - { integrity: sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg== } - engines: { node: ">= 0.8" } + resolution: {integrity: sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==} + engines: {node: '>= 0.8'} concat-map@0.0.1: - resolution: - { integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg== } + resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==} cookie@0.4.2: - resolution: - { integrity: sha512-aSWTXFzaKWkvHO1Ny/s+ePFpvKsPnjc551iI41v3ny/ow6tBG5Vd+FuqGNhh1LxOmVzOlGUriIlOaokOvhaStA== } - engines: { node: ">= 0.6" } + resolution: {integrity: sha512-aSWTXFzaKWkvHO1Ny/s+ePFpvKsPnjc551iI41v3ny/ow6tBG5Vd+FuqGNhh1LxOmVzOlGUriIlOaokOvhaStA==} + engines: {node: '>= 0.6'} core-util-is@1.0.3: - resolution: - { integrity: sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ== } + resolution: {integrity: sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==} cross-spawn@7.0.6: - resolution: - { integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA== } - engines: { node: ">= 8" } + resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==} + engines: {node: '>= 8'} css-box-model@1.2.1: - resolution: - { integrity: sha512-a7Vr4Q/kd/aw96bnJG332W9V9LkJO69JRcaCYDUqjp6/z0w6VcZjgAcTbgFxEPfBgdnAwlh3iwu+hLopa+flJw== } + resolution: {integrity: sha512-a7Vr4Q/kd/aw96bnJG332W9V9LkJO69JRcaCYDUqjp6/z0w6VcZjgAcTbgFxEPfBgdnAwlh3iwu+hLopa+flJw==} csstype@3.1.3: - resolution: - { integrity: sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw== } + resolution: {integrity: sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==} damerau-levenshtein@1.0.8: - resolution: - { integrity: sha512-sdQSFB7+llfUcQHUQO3+B8ERRj0Oa4w9POWMI/puGtuf7gFywGmkaLCElnudfTiKZV+NvHqL0ifzdrI8Ro7ESA== } + resolution: {integrity: sha512-sdQSFB7+llfUcQHUQO3+B8ERRj0Oa4w9POWMI/puGtuf7gFywGmkaLCElnudfTiKZV+NvHqL0ifzdrI8Ro7ESA==} data-view-buffer@1.0.2: - resolution: - { integrity: sha512-EmKO5V3OLXh1rtK2wgXRansaK1/mtVdTUEiEI0W8RkvgT05kfxaH29PliLnpLP73yYO6142Q72QNa8Wx/A5CqQ== } - engines: { node: ">= 0.4" } + resolution: {integrity: sha512-EmKO5V3OLXh1rtK2wgXRansaK1/mtVdTUEiEI0W8RkvgT05kfxaH29PliLnpLP73yYO6142Q72QNa8Wx/A5CqQ==} + engines: {node: '>= 0.4'} data-view-byte-length@1.0.2: - resolution: - { integrity: sha512-tuhGbE6CfTM9+5ANGf+oQb72Ky/0+s3xKUpHvShfiz2RxMFgFPjsXuRLBVMtvMs15awe45SRb83D6wH4ew6wlQ== } - engines: { node: ">= 0.4" } + resolution: {integrity: sha512-tuhGbE6CfTM9+5ANGf+oQb72Ky/0+s3xKUpHvShfiz2RxMFgFPjsXuRLBVMtvMs15awe45SRb83D6wH4ew6wlQ==} + engines: {node: '>= 0.4'} data-view-byte-offset@1.0.1: - resolution: - { integrity: sha512-BS8PfmtDGnrgYdOonGZQdLZslWIeCGFP9tpan0hi1Co2Zr2NKADsvGYA8XxuG/4UWgJ6Cjtv+YJnB6MM69QGlQ== } - engines: { node: ">= 0.4" } + resolution: {integrity: sha512-BS8PfmtDGnrgYdOonGZQdLZslWIeCGFP9tpan0hi1Co2Zr2NKADsvGYA8XxuG/4UWgJ6Cjtv+YJnB6MM69QGlQ==} + engines: {node: '>= 0.4'} date-fns@4.1.0: - resolution: - { integrity: sha512-Ukq0owbQXxa/U3EGtsdVBkR1w7KOQ5gIBqdH2hkvknzZPYvBxb/aa6E8L7tmjFtkwZBu3UXBbjIgPo/Ez4xaNg== } + resolution: {integrity: sha512-Ukq0owbQXxa/U3EGtsdVBkR1w7KOQ5gIBqdH2hkvknzZPYvBxb/aa6E8L7tmjFtkwZBu3UXBbjIgPo/Ez4xaNg==} debug@3.2.7: - resolution: - { integrity: sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ== } + resolution: {integrity: sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==} peerDependencies: - supports-color: "*" + supports-color: '*' peerDependenciesMeta: supports-color: optional: true debug@4.4.1: - resolution: - { integrity: sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ== } - engines: { node: ">=6.0" } + resolution: {integrity: sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==} + engines: {node: '>=6.0'} peerDependencies: - supports-color: "*" + supports-color: '*' peerDependenciesMeta: supports-color: optional: true decamelize@1.2.0: - resolution: - { integrity: sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA== } - engines: { node: ">=0.10.0" } + resolution: {integrity: sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA==} + engines: {node: '>=0.10.0'} decimal.js@10.6.0: - resolution: - { integrity: sha512-YpgQiITW3JXGntzdUmyUR1V812Hn8T1YVXhCu+wO3OpS4eU9l4YdD3qjyiKdV6mvV29zapkMeD390UVEf2lkUg== } + resolution: {integrity: sha512-YpgQiITW3JXGntzdUmyUR1V812Hn8T1YVXhCu+wO3OpS4eU9l4YdD3qjyiKdV6mvV29zapkMeD390UVEf2lkUg==} deep-is@0.1.4: - resolution: - { integrity: sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ== } + resolution: {integrity: sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==} define-data-property@1.1.4: - resolution: - { integrity: sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A== } - engines: { node: ">= 0.4" } + resolution: {integrity: sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==} + engines: {node: '>= 0.4'} define-properties@1.2.1: - resolution: - { integrity: sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg== } - engines: { node: ">= 0.4" } + resolution: {integrity: sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==} + engines: {node: '>= 0.4'} delayed-stream@1.0.0: - resolution: - { integrity: sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ== } - engines: { node: ">=0.4.0" } + resolution: {integrity: sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==} + engines: {node: '>=0.4.0'} detect-libc@2.0.4: - resolution: - { integrity: sha512-3UDv+G9CsCKO1WKMGw9fwq/SWJYbI0c5Y7LU1AXYoDdbhE2AHQ6N6Nb34sG8Fj7T5APy8qXDCKuuIHd1BR0tVA== } - engines: { node: ">=8" } + resolution: {integrity: sha512-3UDv+G9CsCKO1WKMGw9fwq/SWJYbI0c5Y7LU1AXYoDdbhE2AHQ6N6Nb34sG8Fj7T5APy8qXDCKuuIHd1BR0tVA==} + engines: {node: '>=8'} detect-node-es@1.1.0: - resolution: - { integrity: sha512-ypdmJU/TbBby2Dxibuv7ZLW3Bs1QEmM7nHjEANfohJLvE0XVujisn1qPJcZxg+qDucsr+bP6fLD1rPS3AhJ7EQ== } + resolution: {integrity: sha512-ypdmJU/TbBby2Dxibuv7ZLW3Bs1QEmM7nHjEANfohJLvE0XVujisn1qPJcZxg+qDucsr+bP6fLD1rPS3AhJ7EQ==} dijkstrajs@1.0.3: - resolution: - { integrity: sha512-qiSlmBq9+BCdCA/L46dw8Uy93mloxsPSbwnm5yrKn2vMPiy8KyAskTF6zuV/j5BMsmOGZDPs7KjU+mjb670kfA== } + resolution: {integrity: sha512-qiSlmBq9+BCdCA/L46dw8Uy93mloxsPSbwnm5yrKn2vMPiy8KyAskTF6zuV/j5BMsmOGZDPs7KjU+mjb670kfA==} doctrine@2.1.0: - resolution: - { integrity: sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw== } - engines: { node: ">=0.10.0" } + resolution: {integrity: sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==} + engines: {node: '>=0.10.0'} dunder-proto@1.0.1: - resolution: - { integrity: sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A== } - engines: { node: ">= 0.4" } + resolution: {integrity: sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==} + engines: {node: '>= 0.4'} emoji-regex@8.0.0: - resolution: - { integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A== } + resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==} emoji-regex@9.2.2: - resolution: - { integrity: sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg== } + resolution: {integrity: sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==} enhanced-resolve@5.18.2: - resolution: - { integrity: sha512-6Jw4sE1maoRJo3q8MsSIn2onJFbLTOjY9hlx4DZXmOKvLRd1Ok2kXmAGXaafL2+ijsJZ1ClYbl/pmqr9+k4iUQ== } - engines: { node: ">=10.13.0" } + resolution: {integrity: sha512-6Jw4sE1maoRJo3q8MsSIn2onJFbLTOjY9hlx4DZXmOKvLRd1Ok2kXmAGXaafL2+ijsJZ1ClYbl/pmqr9+k4iUQ==} + engines: {node: '>=10.13.0'} es-abstract@1.24.0: - resolution: - { integrity: sha512-WSzPgsdLtTcQwm4CROfS5ju2Wa1QQcVeT37jFjYzdFz1r9ahadC8B8/a4qxJxM+09F18iumCdRmlr96ZYkQvEg== } - engines: { node: ">= 0.4" } + resolution: {integrity: sha512-WSzPgsdLtTcQwm4CROfS5ju2Wa1QQcVeT37jFjYzdFz1r9ahadC8B8/a4qxJxM+09F18iumCdRmlr96ZYkQvEg==} + engines: {node: '>= 0.4'} es-define-property@1.0.1: - resolution: - { integrity: sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g== } - engines: { node: ">= 0.4" } + resolution: {integrity: sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==} + engines: {node: '>= 0.4'} es-errors@1.3.0: - resolution: - { integrity: sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw== } - engines: { node: ">= 0.4" } + resolution: {integrity: sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==} + engines: {node: '>= 0.4'} es-iterator-helpers@1.2.1: - resolution: - { integrity: sha512-uDn+FE1yrDzyC0pCo961B2IHbdM8y/ACZsKD4dG6WqrjV53BADjwa7D+1aom2rsNVfLyDgU/eigvlJGJ08OQ4w== } - engines: { node: ">= 0.4" } + resolution: {integrity: sha512-uDn+FE1yrDzyC0pCo961B2IHbdM8y/ACZsKD4dG6WqrjV53BADjwa7D+1aom2rsNVfLyDgU/eigvlJGJ08OQ4w==} + engines: {node: '>= 0.4'} es-object-atoms@1.1.1: - resolution: - { integrity: sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA== } - engines: { node: ">= 0.4" } + resolution: {integrity: sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==} + engines: {node: '>= 0.4'} es-set-tostringtag@2.1.0: - resolution: - { integrity: sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA== } - engines: { node: ">= 0.4" } + resolution: {integrity: sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==} + engines: {node: '>= 0.4'} es-shim-unscopables@1.1.0: - resolution: - { integrity: sha512-d9T8ucsEhh8Bi1woXCf+TIKDIROLG5WCkxg8geBCbvk22kzwC5G2OnXVMO6FUsvQlgUUXQ2itephWDLqDzbeCw== } - engines: { node: ">= 0.4" } + resolution: {integrity: sha512-d9T8ucsEhh8Bi1woXCf+TIKDIROLG5WCkxg8geBCbvk22kzwC5G2OnXVMO6FUsvQlgUUXQ2itephWDLqDzbeCw==} + engines: {node: '>= 0.4'} es-to-primitive@1.3.0: - resolution: - { integrity: sha512-w+5mJ3GuFL+NjVtJlvydShqE1eN3h3PbI7/5LAsYJP/2qtuMXjfL2LpHSRqo4b4eSF5K/DH1JXKUAHSB2UW50g== } - engines: { node: ">= 0.4" } + resolution: {integrity: sha512-w+5mJ3GuFL+NjVtJlvydShqE1eN3h3PbI7/5LAsYJP/2qtuMXjfL2LpHSRqo4b4eSF5K/DH1JXKUAHSB2UW50g==} + engines: {node: '>= 0.4'} escape-string-regexp@4.0.0: - resolution: - { integrity: sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA== } - engines: { node: ">=10" } + resolution: {integrity: sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==} + engines: {node: '>=10'} eslint-config-next@15.3.4: - resolution: - { integrity: sha512-WqeumCq57QcTP2lYlV6BRUySfGiBYEXlQ1L0mQ+u4N4X4ZhUVSSQ52WtjqHv60pJ6dD7jn+YZc0d1/ZSsxccvg== } + resolution: {integrity: sha512-WqeumCq57QcTP2lYlV6BRUySfGiBYEXlQ1L0mQ+u4N4X4ZhUVSSQ52WtjqHv60pJ6dD7jn+YZc0d1/ZSsxccvg==} peerDependencies: eslint: ^7.23.0 || ^8.0.0 || ^9.0.0 - typescript: ">=3.3.1" + typescript: '>=3.3.1' peerDependenciesMeta: typescript: optional: true eslint-config-prettier@9.1.0: - resolution: - { integrity: sha512-NSWl5BFQWEPi1j4TjVNItzYV7dZXZ+wP6I6ZhrBGpChQhZRUaElihE9uRRkcbRnNb76UMKDF3r+WTmNcGPKsqw== } + resolution: {integrity: sha512-NSWl5BFQWEPi1j4TjVNItzYV7dZXZ+wP6I6ZhrBGpChQhZRUaElihE9uRRkcbRnNb76UMKDF3r+WTmNcGPKsqw==} hasBin: true peerDependencies: - eslint: ">=7.0.0" + eslint: '>=7.0.0' eslint-import-resolver-node@0.3.9: - resolution: - { integrity: sha512-WFj2isz22JahUv+B788TlO3N6zL3nNJGU8CcZbPZvVEkBPaJdCV4vy5wyghty5ROFbCRnm132v8BScu5/1BQ8g== } + resolution: {integrity: sha512-WFj2isz22JahUv+B788TlO3N6zL3nNJGU8CcZbPZvVEkBPaJdCV4vy5wyghty5ROFbCRnm132v8BScu5/1BQ8g==} eslint-import-resolver-typescript@3.10.1: - resolution: - { integrity: sha512-A1rHYb06zjMGAxdLSkN2fXPBwuSaQ0iO5M/hdyS0Ajj1VBaRp0sPD3dn1FhME3c/JluGFbwSxyCfqdSbtQLAHQ== } - engines: { node: ^14.18.0 || >=16.0.0 } + resolution: {integrity: sha512-A1rHYb06zjMGAxdLSkN2fXPBwuSaQ0iO5M/hdyS0Ajj1VBaRp0sPD3dn1FhME3c/JluGFbwSxyCfqdSbtQLAHQ==} + engines: {node: ^14.18.0 || >=16.0.0} peerDependencies: - eslint: "*" - eslint-plugin-import: "*" - eslint-plugin-import-x: "*" + eslint: '*' + eslint-plugin-import: '*' + eslint-plugin-import-x: '*' peerDependenciesMeta: eslint-plugin-import: optional: true @@ -2022,17 +1850,16 @@ packages: optional: true eslint-module-utils@2.12.1: - resolution: - { integrity: sha512-L8jSWTze7K2mTg0vos/RuLRS5soomksDPoJLXIslC7c8Wmut3bx7CPpJijDcBZtxQ5lrbUdM+s0OlNbz0DCDNw== } - engines: { node: ">=4" } + resolution: {integrity: sha512-L8jSWTze7K2mTg0vos/RuLRS5soomksDPoJLXIslC7c8Wmut3bx7CPpJijDcBZtxQ5lrbUdM+s0OlNbz0DCDNw==} + engines: {node: '>=4'} peerDependencies: - "@typescript-eslint/parser": "*" - eslint: "*" - eslint-import-resolver-node: "*" - eslint-import-resolver-typescript: "*" - eslint-import-resolver-webpack: "*" + '@typescript-eslint/parser': '*' + eslint: '*' + eslint-import-resolver-node: '*' + eslint-import-resolver-typescript: '*' + eslint-import-resolver-webpack: '*' peerDependenciesMeta: - "@typescript-eslint/parser": + '@typescript-eslint/parser': optional: true eslint: optional: true @@ -2044,136 +1871,114 @@ packages: optional: true eslint-plugin-import@2.32.0: - resolution: - { integrity: sha512-whOE1HFo/qJDyX4SnXzP4N6zOWn79WhnCUY/iDR0mPfQZO8wcYE4JClzI2oZrhBnnMUCBCHZhO6VQyoBU95mZA== } - engines: { node: ">=4" } + resolution: {integrity: sha512-whOE1HFo/qJDyX4SnXzP4N6zOWn79WhnCUY/iDR0mPfQZO8wcYE4JClzI2oZrhBnnMUCBCHZhO6VQyoBU95mZA==} + engines: {node: '>=4'} peerDependencies: - "@typescript-eslint/parser": "*" + '@typescript-eslint/parser': '*' eslint: ^2 || ^3 || ^4 || ^5 || ^6 || ^7.2.0 || ^8 || ^9 peerDependenciesMeta: - "@typescript-eslint/parser": + '@typescript-eslint/parser': optional: true eslint-plugin-jsx-a11y@6.10.2: - resolution: - { integrity: sha512-scB3nz4WmG75pV8+3eRUQOHZlNSUhFNq37xnpgRkCCELU3XMvXAxLk1eqWWyE22Ki4Q01Fnsw9BA3cJHDPgn2Q== } - engines: { node: ">=4.0" } + resolution: {integrity: sha512-scB3nz4WmG75pV8+3eRUQOHZlNSUhFNq37xnpgRkCCELU3XMvXAxLk1eqWWyE22Ki4Q01Fnsw9BA3cJHDPgn2Q==} + engines: {node: '>=4.0'} peerDependencies: eslint: ^3 || ^4 || ^5 || ^6 || ^7 || ^8 || ^9 eslint-plugin-prettier@5.5.1: - resolution: - { integrity: sha512-dobTkHT6XaEVOo8IO90Q4DOSxnm3Y151QxPJlM/vKC0bVy+d6cVWQZLlFiuZPP0wS6vZwSKeJgKkcS+KfMBlRw== } - engines: { node: ^14.18.0 || >=16.0.0 } + resolution: {integrity: sha512-dobTkHT6XaEVOo8IO90Q4DOSxnm3Y151QxPJlM/vKC0bVy+d6cVWQZLlFiuZPP0wS6vZwSKeJgKkcS+KfMBlRw==} + engines: {node: ^14.18.0 || >=16.0.0} peerDependencies: - "@types/eslint": ">=8.0.0" - eslint: ">=8.0.0" - eslint-config-prettier: ">= 7.0.0 <10.0.0 || >=10.1.0" - prettier: ">=3.0.0" + '@types/eslint': '>=8.0.0' + eslint: '>=8.0.0' + eslint-config-prettier: '>= 7.0.0 <10.0.0 || >=10.1.0' + prettier: '>=3.0.0' peerDependenciesMeta: - "@types/eslint": + '@types/eslint': optional: true eslint-config-prettier: optional: true eslint-plugin-react-hooks@5.2.0: - resolution: - { integrity: sha512-+f15FfK64YQwZdJNELETdn5ibXEUQmW1DZL6KXhNnc2heoy/sg9VJJeT7n8TlMWouzWqSWavFkIhHyIbIAEapg== } - engines: { node: ">=10" } + resolution: {integrity: sha512-+f15FfK64YQwZdJNELETdn5ibXEUQmW1DZL6KXhNnc2heoy/sg9VJJeT7n8TlMWouzWqSWavFkIhHyIbIAEapg==} + engines: {node: '>=10'} peerDependencies: eslint: ^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0 || ^9.0.0 eslint-plugin-react@7.37.5: - resolution: - { integrity: sha512-Qteup0SqU15kdocexFNAJMvCJEfa2xUKNV4CC1xsVMrIIqEy3SQ/rqyxCWNzfrd3/ldy6HMlD2e0JDVpDg2qIA== } - engines: { node: ">=4" } + resolution: {integrity: sha512-Qteup0SqU15kdocexFNAJMvCJEfa2xUKNV4CC1xsVMrIIqEy3SQ/rqyxCWNzfrd3/ldy6HMlD2e0JDVpDg2qIA==} + engines: {node: '>=4'} peerDependencies: eslint: ^3 || ^4 || ^5 || ^6 || ^7 || ^8 || ^9.7 eslint-scope@8.4.0: - resolution: - { integrity: sha512-sNXOfKCn74rt8RICKMvJS7XKV/Xk9kA7DyJr8mJik3S7Cwgy3qlkkmyS2uQB3jiJg6VNdZd/pDBJu0nvG2NlTg== } - engines: { node: ^18.18.0 || ^20.9.0 || >=21.1.0 } + resolution: {integrity: sha512-sNXOfKCn74rt8RICKMvJS7XKV/Xk9kA7DyJr8mJik3S7Cwgy3qlkkmyS2uQB3jiJg6VNdZd/pDBJu0nvG2NlTg==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} eslint-visitor-keys@3.4.3: - resolution: - { integrity: sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag== } - engines: { node: ^12.22.0 || ^14.17.0 || >=16.0.0 } + resolution: {integrity: sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} eslint-visitor-keys@4.2.1: - resolution: - { integrity: sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ== } - engines: { node: ^18.18.0 || ^20.9.0 || >=21.1.0 } + resolution: {integrity: sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} eslint@9.30.0: - resolution: - { integrity: sha512-iN/SiPxmQu6EVkf+m1qpBxzUhE12YqFLOSySuOyVLJLEF9nzTf+h/1AJYc1JWzCnktggeNrjvQGLngDzXirU6g== } - engines: { node: ^18.18.0 || ^20.9.0 || >=21.1.0 } + resolution: {integrity: sha512-iN/SiPxmQu6EVkf+m1qpBxzUhE12YqFLOSySuOyVLJLEF9nzTf+h/1AJYc1JWzCnktggeNrjvQGLngDzXirU6g==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} hasBin: true peerDependencies: - jiti: "*" + jiti: '*' peerDependenciesMeta: jiti: optional: true espree@10.4.0: - resolution: - { integrity: sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ== } - engines: { node: ^18.18.0 || ^20.9.0 || >=21.1.0 } + resolution: {integrity: sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} esquery@1.6.0: - resolution: - { integrity: sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg== } - engines: { node: ">=0.10" } + resolution: {integrity: sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg==} + engines: {node: '>=0.10'} esrecurse@4.3.0: - resolution: - { integrity: sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag== } - engines: { node: ">=4.0" } + resolution: {integrity: sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==} + engines: {node: '>=4.0'} estraverse@5.3.0: - resolution: - { integrity: sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA== } - engines: { node: ">=4.0" } + resolution: {integrity: sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==} + engines: {node: '>=4.0'} esutils@2.0.3: - resolution: - { integrity: sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g== } - engines: { node: ">=0.10.0" } + resolution: {integrity: sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==} + engines: {node: '>=0.10.0'} fast-deep-equal@3.1.3: - resolution: - { integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q== } + resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==} fast-diff@1.3.0: - resolution: - { integrity: sha512-VxPP4NqbUjj6MaAOafWeUn2cXWLcCtljklUtZf0Ind4XQ+QPtmA0b18zZy0jIQx+ExRVCR/ZQpBmik5lXshNsw== } + resolution: {integrity: sha512-VxPP4NqbUjj6MaAOafWeUn2cXWLcCtljklUtZf0Ind4XQ+QPtmA0b18zZy0jIQx+ExRVCR/ZQpBmik5lXshNsw==} fast-glob@3.3.1: - resolution: - { integrity: sha512-kNFPyjhh5cKjrUltxs+wFx+ZkbRaxxmZ+X0ZU31SOsxCEtP9VPgtq2teZw1DebupL5GmDaNQ6yKMMVcM41iqDg== } - engines: { node: ">=8.6.0" } + resolution: {integrity: sha512-kNFPyjhh5cKjrUltxs+wFx+ZkbRaxxmZ+X0ZU31SOsxCEtP9VPgtq2teZw1DebupL5GmDaNQ6yKMMVcM41iqDg==} + engines: {node: '>=8.6.0'} fast-glob@3.3.3: - resolution: - { integrity: sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg== } - engines: { node: ">=8.6.0" } + resolution: {integrity: sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==} + engines: {node: '>=8.6.0'} fast-json-stable-stringify@2.1.0: - resolution: - { integrity: sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw== } + resolution: {integrity: sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==} fast-levenshtein@2.0.6: - resolution: - { integrity: sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw== } + resolution: {integrity: sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==} fastq@1.19.1: - resolution: - { integrity: sha512-GwLTyxkCXjXbxqIhTsMI2Nui8huMPtnxg7krajPJAjnEG/iiOS7i+zCtWGZR9G0NBKbXKh6X9m9UIsYX/N6vvQ== } + resolution: {integrity: sha512-GwLTyxkCXjXbxqIhTsMI2Nui8huMPtnxg7krajPJAjnEG/iiOS7i+zCtWGZR9G0NBKbXKh6X9m9UIsYX/N6vvQ==} fdir@6.4.6: - resolution: - { integrity: sha512-hiFoqpyZcfNm1yc4u8oWCf9A2c4D3QjCrks3zmoVKVxpQRzmPNar1hUJcBG2RQHvEVGDN+Jm81ZheVLAQMK6+w== } + resolution: {integrity: sha512-hiFoqpyZcfNm1yc4u8oWCf9A2c4D3QjCrks3zmoVKVxpQRzmPNar1hUJcBG2RQHvEVGDN+Jm81ZheVLAQMK6+w==} peerDependencies: picomatch: ^3 || ^4 peerDependenciesMeta: @@ -2181,68 +1986,57 @@ packages: optional: true file-entry-cache@8.0.0: - resolution: - { integrity: sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ== } - engines: { node: ">=16.0.0" } + resolution: {integrity: sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==} + engines: {node: '>=16.0.0'} file-selector@2.1.2: - resolution: - { integrity: sha512-QgXo+mXTe8ljeqUFaX3QVHc5osSItJ/Km+xpocx0aSqWGMSCf6qYs/VnzZgS864Pjn5iceMRFigeAV7AfTlaig== } - engines: { node: ">= 12" } + resolution: {integrity: sha512-QgXo+mXTe8ljeqUFaX3QVHc5osSItJ/Km+xpocx0aSqWGMSCf6qYs/VnzZgS864Pjn5iceMRFigeAV7AfTlaig==} + engines: {node: '>= 12'} fill-range@7.1.1: - resolution: - { integrity: sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg== } - engines: { node: ">=8" } + resolution: {integrity: sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==} + engines: {node: '>=8'} find-up@4.1.0: - resolution: - { integrity: sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw== } - engines: { node: ">=8" } + resolution: {integrity: sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==} + engines: {node: '>=8'} find-up@5.0.0: - resolution: - { integrity: sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng== } - engines: { node: ">=10" } + resolution: {integrity: sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==} + engines: {node: '>=10'} flat-cache@4.0.1: - resolution: - { integrity: sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw== } - engines: { node: ">=16" } + resolution: {integrity: sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==} + engines: {node: '>=16'} flatted@3.3.3: - resolution: - { integrity: sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg== } + resolution: {integrity: sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg==} follow-redirects@1.15.9: - resolution: - { integrity: sha512-gew4GsXizNgdoRyqmyfMHyAmXsZDk6mHkSxZFCzW9gwlbtOW44CDtYavM+y+72qD/Vq2l550kMF52DT8fOLJqQ== } - engines: { node: ">=4.0" } + resolution: {integrity: sha512-gew4GsXizNgdoRyqmyfMHyAmXsZDk6mHkSxZFCzW9gwlbtOW44CDtYavM+y+72qD/Vq2l550kMF52DT8fOLJqQ==} + engines: {node: '>=4.0'} peerDependencies: - debug: "*" + debug: '*' peerDependenciesMeta: debug: optional: true for-each@0.3.5: - resolution: - { integrity: sha512-dKx12eRCVIzqCxFGplyFKJMPvLEWgmNtUrpTiJIR5u97zEhRG8ySrtboPHZXx7daLxQVrl643cTzbab2tkQjxg== } - engines: { node: ">= 0.4" } + resolution: {integrity: sha512-dKx12eRCVIzqCxFGplyFKJMPvLEWgmNtUrpTiJIR5u97zEhRG8ySrtboPHZXx7daLxQVrl643cTzbab2tkQjxg==} + engines: {node: '>= 0.4'} form-data@4.0.3: - resolution: - { integrity: sha512-qsITQPfmvMOSAdeyZ+12I1c+CKSstAFAwu+97zrnWAbIr5u8wfsExUzCesVLC8NgHuRUqNN4Zy6UPWUTRGslcA== } - engines: { node: ">= 6" } + resolution: {integrity: sha512-qsITQPfmvMOSAdeyZ+12I1c+CKSstAFAwu+97zrnWAbIr5u8wfsExUzCesVLC8NgHuRUqNN4Zy6UPWUTRGslcA==} + engines: {node: '>= 6'} framer-motion@12.23.0: - resolution: - { integrity: sha512-xf6NxTGAyf7zR4r2KlnhFmsRfKIbjqeBupEDBAaEtVIBJX96sAon00kMlsKButSIRwPSHjbRrAPnYdJJ9kyhbA== } + resolution: {integrity: sha512-xf6NxTGAyf7zR4r2KlnhFmsRfKIbjqeBupEDBAaEtVIBJX96sAon00kMlsKButSIRwPSHjbRrAPnYdJJ9kyhbA==} peerDependencies: - "@emotion/is-prop-valid": "*" + '@emotion/is-prop-valid': '*' react: ^18.0.0 || ^19.0.0 react-dom: ^18.0.0 || ^19.0.0 peerDependenciesMeta: - "@emotion/is-prop-valid": + '@emotion/is-prop-valid': optional: true react: optional: true @@ -2250,592 +2044,487 @@ packages: optional: true fsevents@2.3.3: - resolution: - { integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw== } - engines: { node: ^8.16.0 || ^10.6.0 || >=11.0.0 } + resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==} + engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} os: [darwin] function-bind@1.1.2: - resolution: - { integrity: sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA== } + resolution: {integrity: sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==} function.prototype.name@1.1.8: - resolution: - { integrity: sha512-e5iwyodOHhbMr/yNrc7fDYG4qlbIvI5gajyzPnb5TCwyhjApznQh1BMFou9b30SevY43gCJKXycoCBjMbsuW0Q== } - engines: { node: ">= 0.4" } + resolution: {integrity: sha512-e5iwyodOHhbMr/yNrc7fDYG4qlbIvI5gajyzPnb5TCwyhjApznQh1BMFou9b30SevY43gCJKXycoCBjMbsuW0Q==} + engines: {node: '>= 0.4'} functions-have-names@1.2.3: - resolution: - { integrity: sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ== } + resolution: {integrity: sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==} get-caller-file@2.0.5: - resolution: - { integrity: sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg== } - engines: { node: 6.* || 8.* || >= 10.* } + resolution: {integrity: sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==} + engines: {node: 6.* || 8.* || >= 10.*} get-intrinsic@1.3.0: - resolution: - { integrity: sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ== } - engines: { node: ">= 0.4" } + resolution: {integrity: sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==} + engines: {node: '>= 0.4'} get-nonce@1.0.1: - resolution: - { integrity: sha512-FJhYRoDaiatfEkUK8HKlicmu/3SGFD51q3itKDGoSTysQJBnfOcxU5GxnhE1E6soB76MbT0MBtnKJuXyAx+96Q== } - engines: { node: ">=6" } + resolution: {integrity: sha512-FJhYRoDaiatfEkUK8HKlicmu/3SGFD51q3itKDGoSTysQJBnfOcxU5GxnhE1E6soB76MbT0MBtnKJuXyAx+96Q==} + engines: {node: '>=6'} get-proto@1.0.1: - resolution: - { integrity: sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g== } - engines: { node: ">= 0.4" } + resolution: {integrity: sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==} + engines: {node: '>= 0.4'} get-symbol-description@1.1.0: - resolution: - { integrity: sha512-w9UMqWwJxHNOvoNzSJ2oPF5wvYcvP7jUvYzhp67yEhTi17ZDBBC1z9pTdGuzjD+EFIqLSYRweZjqfiPzQ06Ebg== } - engines: { node: ">= 0.4" } + resolution: {integrity: sha512-w9UMqWwJxHNOvoNzSJ2oPF5wvYcvP7jUvYzhp67yEhTi17ZDBBC1z9pTdGuzjD+EFIqLSYRweZjqfiPzQ06Ebg==} + engines: {node: '>= 0.4'} get-tsconfig@4.10.1: - resolution: - { integrity: sha512-auHyJ4AgMz7vgS8Hp3N6HXSmlMdUyhSUrfBF16w153rxtLIEOE+HGqaBppczZvnHLqQJfiHotCYpNhl0lUROFQ== } + resolution: {integrity: sha512-auHyJ4AgMz7vgS8Hp3N6HXSmlMdUyhSUrfBF16w153rxtLIEOE+HGqaBppczZvnHLqQJfiHotCYpNhl0lUROFQ==} glob-parent@5.1.2: - resolution: - { integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow== } - engines: { node: ">= 6" } + resolution: {integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==} + engines: {node: '>= 6'} glob-parent@6.0.2: - resolution: - { integrity: sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A== } - engines: { node: ">=10.13.0" } + resolution: {integrity: sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==} + engines: {node: '>=10.13.0'} globals@14.0.0: - resolution: - { integrity: sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ== } - engines: { node: ">=18" } + resolution: {integrity: sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==} + engines: {node: '>=18'} globalthis@1.0.4: - resolution: - { integrity: sha512-DpLKbNU4WylpxJykQujfCcwYWiV/Jhm50Goo0wrVILAv5jOr9d+H+UR3PhSCD2rCCEIg0uc+G+muBTwD54JhDQ== } - engines: { node: ">= 0.4" } + resolution: {integrity: sha512-DpLKbNU4WylpxJykQujfCcwYWiV/Jhm50Goo0wrVILAv5jOr9d+H+UR3PhSCD2rCCEIg0uc+G+muBTwD54JhDQ==} + engines: {node: '>= 0.4'} gopd@1.2.0: - resolution: - { integrity: sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg== } - engines: { node: ">= 0.4" } + resolution: {integrity: sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==} + engines: {node: '>= 0.4'} graceful-fs@4.2.11: - resolution: - { integrity: sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ== } + resolution: {integrity: sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==} graphemer@1.4.0: - resolution: - { integrity: sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag== } + resolution: {integrity: sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==} has-bigints@1.1.0: - resolution: - { integrity: sha512-R3pbpkcIqv2Pm3dUwgjclDRVmWpTJW2DcMzcIhEXEx1oh/CEMObMm3KLmRJOdvhM7o4uQBnwr8pzRK2sJWIqfg== } - engines: { node: ">= 0.4" } + resolution: {integrity: sha512-R3pbpkcIqv2Pm3dUwgjclDRVmWpTJW2DcMzcIhEXEx1oh/CEMObMm3KLmRJOdvhM7o4uQBnwr8pzRK2sJWIqfg==} + engines: {node: '>= 0.4'} has-flag@4.0.0: - resolution: - { integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ== } - engines: { node: ">=8" } + resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==} + engines: {node: '>=8'} has-property-descriptors@1.0.2: - resolution: - { integrity: sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg== } + resolution: {integrity: sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==} has-proto@1.2.0: - resolution: - { integrity: sha512-KIL7eQPfHQRC8+XluaIw7BHUwwqL19bQn4hzNgdr+1wXoU0KKj6rufu47lhY7KbJR2C6T6+PfyN0Ea7wkSS+qQ== } - engines: { node: ">= 0.4" } + resolution: {integrity: sha512-KIL7eQPfHQRC8+XluaIw7BHUwwqL19bQn4hzNgdr+1wXoU0KKj6rufu47lhY7KbJR2C6T6+PfyN0Ea7wkSS+qQ==} + engines: {node: '>= 0.4'} has-symbols@1.1.0: - resolution: - { integrity: sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ== } - engines: { node: ">= 0.4" } + resolution: {integrity: sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==} + engines: {node: '>= 0.4'} has-tostringtag@1.0.2: - resolution: - { integrity: sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw== } - engines: { node: ">= 0.4" } + resolution: {integrity: sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==} + engines: {node: '>= 0.4'} hasown@2.0.2: - resolution: - { integrity: sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ== } - engines: { node: ">= 0.4" } + resolution: {integrity: sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==} + engines: {node: '>= 0.4'} ignore@5.3.2: - resolution: - { integrity: sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g== } - engines: { node: ">= 4" } + resolution: {integrity: sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==} + engines: {node: '>= 4'} ignore@7.0.5: - resolution: - { integrity: sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg== } - engines: { node: ">= 4" } + resolution: {integrity: sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg==} + engines: {node: '>= 4'} immediate@3.0.6: - resolution: - { integrity: sha512-XXOFtyqDjNDAQxVfYxuF7g9Il/IbWmmlQg2MYKOH8ExIT1qg6xc4zyS3HaEEATgs1btfzxq15ciUiY7gjSXRGQ== } + resolution: {integrity: sha512-XXOFtyqDjNDAQxVfYxuF7g9Il/IbWmmlQg2MYKOH8ExIT1qg6xc4zyS3HaEEATgs1btfzxq15ciUiY7gjSXRGQ==} import-fresh@3.3.1: - resolution: - { integrity: sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ== } - engines: { node: ">=6" } + resolution: {integrity: sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==} + engines: {node: '>=6'} imurmurhash@0.1.4: - resolution: - { integrity: sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA== } - engines: { node: ">=0.8.19" } + resolution: {integrity: sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==} + engines: {node: '>=0.8.19'} inherits@2.0.4: - resolution: - { integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== } + resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==} input-otp@1.4.2: - resolution: - { integrity: sha512-l3jWwYNvrEa6NTCt7BECfCm48GvwuZzkoeG3gBL2w4CHeOXW3eKFmf9UNYkNfYc3mxMrthMnxjIE07MT0zLBQA== } + resolution: {integrity: sha512-l3jWwYNvrEa6NTCt7BECfCm48GvwuZzkoeG3gBL2w4CHeOXW3eKFmf9UNYkNfYc3mxMrthMnxjIE07MT0zLBQA==} peerDependencies: react: ^16.8 || ^17.0 || ^18.0 || ^19.0.0 || ^19.0.0-rc react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0.0 || ^19.0.0-rc internal-slot@1.1.0: - resolution: - { integrity: sha512-4gd7VpWNQNB4UKKCFFVcp1AVv+FMOgs9NKzjHKusc8jTMhd5eL1NqQqOpE0KzMds804/yHlglp3uxgluOqAPLw== } - engines: { node: ">= 0.4" } + resolution: {integrity: sha512-4gd7VpWNQNB4UKKCFFVcp1AVv+FMOgs9NKzjHKusc8jTMhd5eL1NqQqOpE0KzMds804/yHlglp3uxgluOqAPLw==} + engines: {node: '>= 0.4'} intl-messageformat@10.7.16: - resolution: - { integrity: sha512-UmdmHUmp5CIKKjSoE10la5yfU+AYJAaiYLsodbjL4lji83JNvgOQUjGaGhGrpFCb0Uh7sl7qfP1IyILa8Z40ug== } + resolution: {integrity: sha512-UmdmHUmp5CIKKjSoE10la5yfU+AYJAaiYLsodbjL4lji83JNvgOQUjGaGhGrpFCb0Uh7sl7qfP1IyILa8Z40ug==} is-array-buffer@3.0.5: - resolution: - { integrity: sha512-DDfANUiiG2wC1qawP66qlTugJeL5HyzMpfr8lLK+jMQirGzNod0B12cFB/9q838Ru27sBwfw78/rdoU7RERz6A== } - engines: { node: ">= 0.4" } + resolution: {integrity: sha512-DDfANUiiG2wC1qawP66qlTugJeL5HyzMpfr8lLK+jMQirGzNod0B12cFB/9q838Ru27sBwfw78/rdoU7RERz6A==} + engines: {node: '>= 0.4'} is-arrayish@0.3.2: - resolution: - { integrity: sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ== } + resolution: {integrity: sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ==} is-async-function@2.1.1: - resolution: - { integrity: sha512-9dgM/cZBnNvjzaMYHVoxxfPj2QXt22Ev7SuuPrs+xav0ukGB0S6d4ydZdEiM48kLx5kDV+QBPrpVnFyefL8kkQ== } - engines: { node: ">= 0.4" } + resolution: {integrity: sha512-9dgM/cZBnNvjzaMYHVoxxfPj2QXt22Ev7SuuPrs+xav0ukGB0S6d4ydZdEiM48kLx5kDV+QBPrpVnFyefL8kkQ==} + engines: {node: '>= 0.4'} is-bigint@1.1.0: - resolution: - { integrity: sha512-n4ZT37wG78iz03xPRKJrHTdZbe3IicyucEtdRsV5yglwc3GyUfbAfpSeD0FJ41NbUNSt5wbhqfp1fS+BgnvDFQ== } - engines: { node: ">= 0.4" } + resolution: {integrity: sha512-n4ZT37wG78iz03xPRKJrHTdZbe3IicyucEtdRsV5yglwc3GyUfbAfpSeD0FJ41NbUNSt5wbhqfp1fS+BgnvDFQ==} + engines: {node: '>= 0.4'} is-boolean-object@1.2.2: - resolution: - { integrity: sha512-wa56o2/ElJMYqjCjGkXri7it5FbebW5usLw/nPmCMs5DeZ7eziSYZhSmPRn0txqeW4LnAmQQU7FgqLpsEFKM4A== } - engines: { node: ">= 0.4" } + resolution: {integrity: sha512-wa56o2/ElJMYqjCjGkXri7it5FbebW5usLw/nPmCMs5DeZ7eziSYZhSmPRn0txqeW4LnAmQQU7FgqLpsEFKM4A==} + engines: {node: '>= 0.4'} is-bun-module@2.0.0: - resolution: - { integrity: sha512-gNCGbnnnnFAUGKeZ9PdbyeGYJqewpmc2aKHUEMO5nQPWU9lOmv7jcmQIv+qHD8fXW6W7qfuCwX4rY9LNRjXrkQ== } + resolution: {integrity: sha512-gNCGbnnnnFAUGKeZ9PdbyeGYJqewpmc2aKHUEMO5nQPWU9lOmv7jcmQIv+qHD8fXW6W7qfuCwX4rY9LNRjXrkQ==} is-callable@1.2.7: - resolution: - { integrity: sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA== } - engines: { node: ">= 0.4" } + resolution: {integrity: sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==} + engines: {node: '>= 0.4'} is-core-module@2.16.1: - resolution: - { integrity: sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w== } - engines: { node: ">= 0.4" } + resolution: {integrity: sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==} + engines: {node: '>= 0.4'} is-data-view@1.0.2: - resolution: - { integrity: sha512-RKtWF8pGmS87i2D6gqQu/l7EYRlVdfzemCJN/P3UOs//x1QE7mfhvzHIApBTRf7axvT6DMGwSwBXYCT0nfB9xw== } - engines: { node: ">= 0.4" } + resolution: {integrity: sha512-RKtWF8pGmS87i2D6gqQu/l7EYRlVdfzemCJN/P3UOs//x1QE7mfhvzHIApBTRf7axvT6DMGwSwBXYCT0nfB9xw==} + engines: {node: '>= 0.4'} is-date-object@1.1.0: - resolution: - { integrity: sha512-PwwhEakHVKTdRNVOw+/Gyh0+MzlCl4R6qKvkhuvLtPMggI1WAHt9sOwZxQLSGpUaDnrdyDsomoRgNnCfKNSXXg== } - engines: { node: ">= 0.4" } + resolution: {integrity: sha512-PwwhEakHVKTdRNVOw+/Gyh0+MzlCl4R6qKvkhuvLtPMggI1WAHt9sOwZxQLSGpUaDnrdyDsomoRgNnCfKNSXXg==} + engines: {node: '>= 0.4'} is-extglob@2.1.1: - resolution: - { integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ== } - engines: { node: ">=0.10.0" } + resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==} + engines: {node: '>=0.10.0'} is-finalizationregistry@1.1.1: - resolution: - { integrity: sha512-1pC6N8qWJbWoPtEjgcL2xyhQOP491EQjeUo3qTKcmV8YSDDJrOepfG8pcC7h/QgnQHYSv0mJ3Z/ZWxmatVrysg== } - engines: { node: ">= 0.4" } + resolution: {integrity: sha512-1pC6N8qWJbWoPtEjgcL2xyhQOP491EQjeUo3qTKcmV8YSDDJrOepfG8pcC7h/QgnQHYSv0mJ3Z/ZWxmatVrysg==} + engines: {node: '>= 0.4'} is-fullwidth-code-point@3.0.0: - resolution: - { integrity: sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg== } - engines: { node: ">=8" } + resolution: {integrity: sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==} + engines: {node: '>=8'} is-generator-function@1.1.0: - resolution: - { integrity: sha512-nPUB5km40q9e8UfN/Zc24eLlzdSf9OfKByBw9CIdw4H1giPMeA0OIJvbchsCu4npfI2QcMVBsGEBHKZ7wLTWmQ== } - engines: { node: ">= 0.4" } + resolution: {integrity: sha512-nPUB5km40q9e8UfN/Zc24eLlzdSf9OfKByBw9CIdw4H1giPMeA0OIJvbchsCu4npfI2QcMVBsGEBHKZ7wLTWmQ==} + engines: {node: '>= 0.4'} is-glob@4.0.3: - resolution: - { integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg== } - engines: { node: ">=0.10.0" } + resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==} + engines: {node: '>=0.10.0'} is-map@2.0.3: - resolution: - { integrity: sha512-1Qed0/Hr2m+YqxnM09CjA2d/i6YZNfF6R2oRAOj36eUdS6qIV/huPJNSEpKbupewFs+ZsJlxsjjPbc0/afW6Lw== } - engines: { node: ">= 0.4" } + resolution: {integrity: sha512-1Qed0/Hr2m+YqxnM09CjA2d/i6YZNfF6R2oRAOj36eUdS6qIV/huPJNSEpKbupewFs+ZsJlxsjjPbc0/afW6Lw==} + engines: {node: '>= 0.4'} is-negative-zero@2.0.3: - resolution: - { integrity: sha512-5KoIu2Ngpyek75jXodFvnafB6DJgr3u8uuK0LEZJjrU19DrMD3EVERaR8sjz8CCGgpZvxPl9SuE1GMVPFHx1mw== } - engines: { node: ">= 0.4" } + resolution: {integrity: sha512-5KoIu2Ngpyek75jXodFvnafB6DJgr3u8uuK0LEZJjrU19DrMD3EVERaR8sjz8CCGgpZvxPl9SuE1GMVPFHx1mw==} + engines: {node: '>= 0.4'} + + is-network-error@1.3.0: + resolution: {integrity: sha512-6oIwpsgRfnDiyEDLMay/GqCl3HoAtH5+RUKW29gYkL0QA+ipzpDLA16yQs7/RHCSu+BwgbJaOUqa4A99qNVQVw==} + engines: {node: '>=16'} is-number-object@1.1.1: - resolution: - { integrity: sha512-lZhclumE1G6VYD8VHe35wFaIif+CTy5SJIi5+3y4psDgWu4wPDoBhF8NxUOinEc7pHgiTsT6MaBb92rKhhD+Xw== } - engines: { node: ">= 0.4" } + resolution: {integrity: sha512-lZhclumE1G6VYD8VHe35wFaIif+CTy5SJIi5+3y4psDgWu4wPDoBhF8NxUOinEc7pHgiTsT6MaBb92rKhhD+Xw==} + engines: {node: '>= 0.4'} is-number@7.0.0: - resolution: - { integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng== } - engines: { node: ">=0.12.0" } + resolution: {integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==} + engines: {node: '>=0.12.0'} is-regex@1.2.1: - resolution: - { integrity: sha512-MjYsKHO5O7mCsmRGxWcLWheFqN9DJ/2TmngvjKXihe6efViPqc274+Fx/4fYj/r03+ESvBdTXK0V6tA3rgez1g== } - engines: { node: ">= 0.4" } + resolution: {integrity: sha512-MjYsKHO5O7mCsmRGxWcLWheFqN9DJ/2TmngvjKXihe6efViPqc274+Fx/4fYj/r03+ESvBdTXK0V6tA3rgez1g==} + engines: {node: '>= 0.4'} is-set@2.0.3: - resolution: - { integrity: sha512-iPAjerrse27/ygGLxw+EBR9agv9Y6uLeYVJMu+QNCoouJ1/1ri0mGrcWpfCqFZuzzx3WjtwxG098X+n4OuRkPg== } - engines: { node: ">= 0.4" } + resolution: {integrity: sha512-iPAjerrse27/ygGLxw+EBR9agv9Y6uLeYVJMu+QNCoouJ1/1ri0mGrcWpfCqFZuzzx3WjtwxG098X+n4OuRkPg==} + engines: {node: '>= 0.4'} is-shared-array-buffer@1.0.4: - resolution: - { integrity: sha512-ISWac8drv4ZGfwKl5slpHG9OwPNty4jOWPRIhBpxOoD+hqITiwuipOQ2bNthAzwA3B4fIjO4Nln74N0S9byq8A== } - engines: { node: ">= 0.4" } + resolution: {integrity: sha512-ISWac8drv4ZGfwKl5slpHG9OwPNty4jOWPRIhBpxOoD+hqITiwuipOQ2bNthAzwA3B4fIjO4Nln74N0S9byq8A==} + engines: {node: '>= 0.4'} is-string@1.1.1: - resolution: - { integrity: sha512-BtEeSsoaQjlSPBemMQIrY1MY0uM6vnS1g5fmufYOtnxLGUZM2178PKbhsk7Ffv58IX+ZtcvoGwccYsh0PglkAA== } - engines: { node: ">= 0.4" } + resolution: {integrity: sha512-BtEeSsoaQjlSPBemMQIrY1MY0uM6vnS1g5fmufYOtnxLGUZM2178PKbhsk7Ffv58IX+ZtcvoGwccYsh0PglkAA==} + engines: {node: '>= 0.4'} is-symbol@1.1.1: - resolution: - { integrity: sha512-9gGx6GTtCQM73BgmHQXfDmLtfjjTUDSyoxTCbp5WtoixAhfgsDirWIcVQ/IHpvI5Vgd5i/J5F7B9cN/WlVbC/w== } - engines: { node: ">= 0.4" } + resolution: {integrity: sha512-9gGx6GTtCQM73BgmHQXfDmLtfjjTUDSyoxTCbp5WtoixAhfgsDirWIcVQ/IHpvI5Vgd5i/J5F7B9cN/WlVbC/w==} + engines: {node: '>= 0.4'} is-typed-array@1.1.15: - resolution: - { integrity: sha512-p3EcsicXjit7SaskXHs1hA91QxgTw46Fv6EFKKGS5DRFLD8yKnohjF3hxoju94b/OcMZoQukzpPpBE9uLVKzgQ== } - engines: { node: ">= 0.4" } + resolution: {integrity: sha512-p3EcsicXjit7SaskXHs1hA91QxgTw46Fv6EFKKGS5DRFLD8yKnohjF3hxoju94b/OcMZoQukzpPpBE9uLVKzgQ==} + engines: {node: '>= 0.4'} is-weakmap@2.0.2: - resolution: - { integrity: sha512-K5pXYOm9wqY1RgjpL3YTkF39tni1XajUIkawTLUo9EZEVUFga5gSQJF8nNS7ZwJQ02y+1YCNYcMh+HIf1ZqE+w== } - engines: { node: ">= 0.4" } + resolution: {integrity: sha512-K5pXYOm9wqY1RgjpL3YTkF39tni1XajUIkawTLUo9EZEVUFga5gSQJF8nNS7ZwJQ02y+1YCNYcMh+HIf1ZqE+w==} + engines: {node: '>= 0.4'} is-weakref@1.1.1: - resolution: - { integrity: sha512-6i9mGWSlqzNMEqpCp93KwRS1uUOodk2OJ6b+sq7ZPDSy2WuI5NFIxp/254TytR8ftefexkWn5xNiHUNpPOfSew== } - engines: { node: ">= 0.4" } + resolution: {integrity: sha512-6i9mGWSlqzNMEqpCp93KwRS1uUOodk2OJ6b+sq7ZPDSy2WuI5NFIxp/254TytR8ftefexkWn5xNiHUNpPOfSew==} + engines: {node: '>= 0.4'} is-weakset@2.0.4: - resolution: - { integrity: sha512-mfcwb6IzQyOKTs84CQMrOwW4gQcaTOAWJ0zzJCl2WSPDrWk/OzDaImWFH3djXhb24g4eudZfLRozAvPGw4d9hQ== } - engines: { node: ">= 0.4" } + resolution: {integrity: sha512-mfcwb6IzQyOKTs84CQMrOwW4gQcaTOAWJ0zzJCl2WSPDrWk/OzDaImWFH3djXhb24g4eudZfLRozAvPGw4d9hQ==} + engines: {node: '>= 0.4'} isarray@1.0.0: - resolution: - { integrity: sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ== } + resolution: {integrity: sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==} isarray@2.0.5: - resolution: - { integrity: sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw== } + resolution: {integrity: sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==} isexe@2.0.0: - resolution: - { integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw== } + resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==} iterator.prototype@1.1.5: - resolution: - { integrity: sha512-H0dkQoCa3b2VEeKQBOxFph+JAbcrQdE7KC0UkqwpLmv2EC4P41QXP+rqo9wYodACiG5/WM5s9oDApTU8utwj9g== } - engines: { node: ">= 0.4" } + resolution: {integrity: sha512-H0dkQoCa3b2VEeKQBOxFph+JAbcrQdE7KC0UkqwpLmv2EC4P41QXP+rqo9wYodACiG5/WM5s9oDApTU8utwj9g==} + engines: {node: '>= 0.4'} jiti@2.4.2: - resolution: - { integrity: sha512-rg9zJN+G4n2nfJl5MW3BMygZX56zKPNVEYYqq7adpmMh4Jn2QNEwhvQlFy6jPVdcod7txZtKHWnyZiA3a0zP7A== } + resolution: {integrity: sha512-rg9zJN+G4n2nfJl5MW3BMygZX56zKPNVEYYqq7adpmMh4Jn2QNEwhvQlFy6jPVdcod7txZtKHWnyZiA3a0zP7A==} hasBin: true js-cookie@3.0.5: - resolution: - { integrity: sha512-cEiJEAEoIbWfCZYKWhVwFuvPX1gETRYPw6LlaTKoxD3s2AkXzkCjnp6h0V77ozyqj0jakteJ4YqDJT830+lVGw== } - engines: { node: ">=14" } + resolution: {integrity: sha512-cEiJEAEoIbWfCZYKWhVwFuvPX1gETRYPw6LlaTKoxD3s2AkXzkCjnp6h0V77ozyqj0jakteJ4YqDJT830+lVGw==} + engines: {node: '>=14'} js-tokens@4.0.0: - resolution: - { integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ== } + resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==} js-yaml@4.1.0: - resolution: - { integrity: sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA== } + resolution: {integrity: sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==} hasBin: true jsesc@3.1.0: - resolution: - { integrity: sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA== } - engines: { node: ">=6" } + resolution: {integrity: sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==} + engines: {node: '>=6'} hasBin: true json-buffer@3.0.1: - resolution: - { integrity: sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ== } + resolution: {integrity: sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==} json-schema-traverse@0.4.1: - resolution: - { integrity: sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg== } + resolution: {integrity: sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==} json-stable-stringify-without-jsonify@1.0.1: - resolution: - { integrity: sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw== } + resolution: {integrity: sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==} json5@1.0.2: - resolution: - { integrity: sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA== } + resolution: {integrity: sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA==} hasBin: true jsx-ast-utils@3.3.5: - resolution: - { integrity: sha512-ZZow9HBI5O6EPgSJLUb8n2NKgmVWTwCvHGwFuJlMjvLFqlGG6pjirPhtdsseaLZjSibD8eegzmYpUZwoIlj2cQ== } - engines: { node: ">=4.0" } + resolution: {integrity: sha512-ZZow9HBI5O6EPgSJLUb8n2NKgmVWTwCvHGwFuJlMjvLFqlGG6pjirPhtdsseaLZjSibD8eegzmYpUZwoIlj2cQ==} + engines: {node: '>=4.0'} jszip@3.10.1: - resolution: - { integrity: sha512-xXDvecyTpGLrqFrvkrUSoxxfJI5AH7U8zxxtVclpsUtMCq4JQ290LY8AW5c7Ggnr/Y/oK+bQMbqK2qmtk3pN4g== } + resolution: {integrity: sha512-xXDvecyTpGLrqFrvkrUSoxxfJI5AH7U8zxxtVclpsUtMCq4JQ290LY8AW5c7Ggnr/Y/oK+bQMbqK2qmtk3pN4g==} keyv@4.5.4: - resolution: - { integrity: sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw== } + resolution: {integrity: sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==} language-subtag-registry@0.3.23: - resolution: - { integrity: sha512-0K65Lea881pHotoGEa5gDlMxt3pctLi2RplBb7Ezh4rRdLEOtgi7n4EwK9lamnUCkKBqaeKRVebTq6BAxSkpXQ== } + resolution: {integrity: sha512-0K65Lea881pHotoGEa5gDlMxt3pctLi2RplBb7Ezh4rRdLEOtgi7n4EwK9lamnUCkKBqaeKRVebTq6BAxSkpXQ==} language-tags@1.0.9: - resolution: - { integrity: sha512-MbjN408fEndfiQXbFQ1vnd+1NoLDsnQW41410oQBXiyXDMYH5z505juWa4KUE1LqxRC7DgOgZDbKLxHIwm27hA== } - engines: { node: ">=0.10" } + resolution: {integrity: sha512-MbjN408fEndfiQXbFQ1vnd+1NoLDsnQW41410oQBXiyXDMYH5z505juWa4KUE1LqxRC7DgOgZDbKLxHIwm27hA==} + engines: {node: '>=0.10'} levn@0.4.1: - resolution: - { integrity: sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ== } - engines: { node: ">= 0.8.0" } + resolution: {integrity: sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==} + engines: {node: '>= 0.8.0'} lie@3.3.0: - resolution: - { integrity: sha512-UaiMJzeWRlEujzAuw5LokY1L5ecNQYZKfmyZ9L7wDHb/p5etKaxXhohBcrw0EYby+G/NA52vRSN4N39dxHAIwQ== } + resolution: {integrity: sha512-UaiMJzeWRlEujzAuw5LokY1L5ecNQYZKfmyZ9L7wDHb/p5etKaxXhohBcrw0EYby+G/NA52vRSN4N39dxHAIwQ==} lightningcss-darwin-arm64@1.30.1: - resolution: - { integrity: sha512-c8JK7hyE65X1MHMN+Viq9n11RRC7hgin3HhYKhrMyaXflk5GVplZ60IxyoVtzILeKr+xAJwg6zK6sjTBJ0FKYQ== } - engines: { node: ">= 12.0.0" } + resolution: {integrity: sha512-c8JK7hyE65X1MHMN+Viq9n11RRC7hgin3HhYKhrMyaXflk5GVplZ60IxyoVtzILeKr+xAJwg6zK6sjTBJ0FKYQ==} + engines: {node: '>= 12.0.0'} cpu: [arm64] os: [darwin] lightningcss-darwin-x64@1.30.1: - resolution: - { integrity: sha512-k1EvjakfumAQoTfcXUcHQZhSpLlkAuEkdMBsI/ivWw9hL+7FtilQc0Cy3hrx0AAQrVtQAbMI7YjCgYgvn37PzA== } - engines: { node: ">= 12.0.0" } + resolution: {integrity: sha512-k1EvjakfumAQoTfcXUcHQZhSpLlkAuEkdMBsI/ivWw9hL+7FtilQc0Cy3hrx0AAQrVtQAbMI7YjCgYgvn37PzA==} + engines: {node: '>= 12.0.0'} cpu: [x64] os: [darwin] lightningcss-freebsd-x64@1.30.1: - resolution: - { integrity: sha512-kmW6UGCGg2PcyUE59K5r0kWfKPAVy4SltVeut+umLCFoJ53RdCUWxcRDzO1eTaxf/7Q2H7LTquFHPL5R+Gjyig== } - engines: { node: ">= 12.0.0" } + resolution: {integrity: sha512-kmW6UGCGg2PcyUE59K5r0kWfKPAVy4SltVeut+umLCFoJ53RdCUWxcRDzO1eTaxf/7Q2H7LTquFHPL5R+Gjyig==} + engines: {node: '>= 12.0.0'} cpu: [x64] os: [freebsd] lightningcss-linux-arm-gnueabihf@1.30.1: - resolution: - { integrity: sha512-MjxUShl1v8pit+6D/zSPq9S9dQ2NPFSQwGvxBCYaBYLPlCWuPh9/t1MRS8iUaR8i+a6w7aps+B4N0S1TYP/R+Q== } - engines: { node: ">= 12.0.0" } + resolution: {integrity: sha512-MjxUShl1v8pit+6D/zSPq9S9dQ2NPFSQwGvxBCYaBYLPlCWuPh9/t1MRS8iUaR8i+a6w7aps+B4N0S1TYP/R+Q==} + engines: {node: '>= 12.0.0'} cpu: [arm] os: [linux] lightningcss-linux-arm64-gnu@1.30.1: - resolution: - { integrity: sha512-gB72maP8rmrKsnKYy8XUuXi/4OctJiuQjcuqWNlJQ6jZiWqtPvqFziskH3hnajfvKB27ynbVCucKSm2rkQp4Bw== } - engines: { node: ">= 12.0.0" } + resolution: {integrity: sha512-gB72maP8rmrKsnKYy8XUuXi/4OctJiuQjcuqWNlJQ6jZiWqtPvqFziskH3hnajfvKB27ynbVCucKSm2rkQp4Bw==} + engines: {node: '>= 12.0.0'} cpu: [arm64] os: [linux] lightningcss-linux-arm64-musl@1.30.1: - resolution: - { integrity: sha512-jmUQVx4331m6LIX+0wUhBbmMX7TCfjF5FoOH6SD1CttzuYlGNVpA7QnrmLxrsub43ClTINfGSYyHe2HWeLl5CQ== } - engines: { node: ">= 12.0.0" } + resolution: {integrity: sha512-jmUQVx4331m6LIX+0wUhBbmMX7TCfjF5FoOH6SD1CttzuYlGNVpA7QnrmLxrsub43ClTINfGSYyHe2HWeLl5CQ==} + engines: {node: '>= 12.0.0'} cpu: [arm64] os: [linux] lightningcss-linux-x64-gnu@1.30.1: - resolution: - { integrity: sha512-piWx3z4wN8J8z3+O5kO74+yr6ze/dKmPnI7vLqfSqI8bccaTGY5xiSGVIJBDd5K5BHlvVLpUB3S2YCfelyJ1bw== } - engines: { node: ">= 12.0.0" } + resolution: {integrity: sha512-piWx3z4wN8J8z3+O5kO74+yr6ze/dKmPnI7vLqfSqI8bccaTGY5xiSGVIJBDd5K5BHlvVLpUB3S2YCfelyJ1bw==} + engines: {node: '>= 12.0.0'} cpu: [x64] os: [linux] lightningcss-linux-x64-musl@1.30.1: - resolution: - { integrity: sha512-rRomAK7eIkL+tHY0YPxbc5Dra2gXlI63HL+v1Pdi1a3sC+tJTcFrHX+E86sulgAXeI7rSzDYhPSeHHjqFhqfeQ== } - engines: { node: ">= 12.0.0" } + resolution: {integrity: sha512-rRomAK7eIkL+tHY0YPxbc5Dra2gXlI63HL+v1Pdi1a3sC+tJTcFrHX+E86sulgAXeI7rSzDYhPSeHHjqFhqfeQ==} + engines: {node: '>= 12.0.0'} cpu: [x64] os: [linux] lightningcss-win32-arm64-msvc@1.30.1: - resolution: - { integrity: sha512-mSL4rqPi4iXq5YVqzSsJgMVFENoa4nGTT/GjO2c0Yl9OuQfPsIfncvLrEW6RbbB24WtZ3xP/2CCmI3tNkNV4oA== } - engines: { node: ">= 12.0.0" } + resolution: {integrity: sha512-mSL4rqPi4iXq5YVqzSsJgMVFENoa4nGTT/GjO2c0Yl9OuQfPsIfncvLrEW6RbbB24WtZ3xP/2CCmI3tNkNV4oA==} + engines: {node: '>= 12.0.0'} cpu: [arm64] os: [win32] lightningcss-win32-x64-msvc@1.30.1: - resolution: - { integrity: sha512-PVqXh48wh4T53F/1CCu8PIPCxLzWyCnn/9T5W1Jpmdy5h9Cwd+0YQS6/LwhHXSafuc61/xg9Lv5OrCby6a++jg== } - engines: { node: ">= 12.0.0" } + resolution: {integrity: sha512-PVqXh48wh4T53F/1CCu8PIPCxLzWyCnn/9T5W1Jpmdy5h9Cwd+0YQS6/LwhHXSafuc61/xg9Lv5OrCby6a++jg==} + engines: {node: '>= 12.0.0'} cpu: [x64] os: [win32] lightningcss@1.30.1: - resolution: - { integrity: sha512-xi6IyHML+c9+Q3W0S4fCQJOym42pyurFiJUHEcEyHS0CeKzia4yZDEsLlqOFykxOdHpNy0NmvVO31vcSqAxJCg== } - engines: { node: ">= 12.0.0" } + resolution: {integrity: sha512-xi6IyHML+c9+Q3W0S4fCQJOym42pyurFiJUHEcEyHS0CeKzia4yZDEsLlqOFykxOdHpNy0NmvVO31vcSqAxJCg==} + engines: {node: '>= 12.0.0'} locate-path@5.0.0: - resolution: - { integrity: sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g== } - engines: { node: ">=8" } + resolution: {integrity: sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==} + engines: {node: '>=8'} locate-path@6.0.0: - resolution: - { integrity: sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw== } - engines: { node: ">=10" } + resolution: {integrity: sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==} + engines: {node: '>=10'} lodash.merge@4.6.2: - resolution: - { integrity: sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ== } + resolution: {integrity: sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==} + + lodash@4.17.21: + resolution: {integrity: sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==} loose-envify@1.4.0: - resolution: - { integrity: sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q== } + resolution: {integrity: sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==} hasBin: true lucide-react@0.525.0: - resolution: - { integrity: sha512-Tm1txJ2OkymCGkvwoHt33Y2JpN5xucVq1slHcgE6Lk0WjDfjgKWor5CdVER8U6DvcfMwh4M8XxmpTiyzfmfDYQ== } + resolution: {integrity: sha512-Tm1txJ2OkymCGkvwoHt33Y2JpN5xucVq1slHcgE6Lk0WjDfjgKWor5CdVER8U6DvcfMwh4M8XxmpTiyzfmfDYQ==} peerDependencies: react: ^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0 magic-string@0.30.17: - resolution: - { integrity: sha512-sNPKHvyjVf7gyjwS4xGTaW/mCnF8wnjtifKBEhxfZ7E/S8tQ0rssrwGNn6q8JH/ohItJfSQp9mBtQYuTlH5QnA== } + resolution: {integrity: sha512-sNPKHvyjVf7gyjwS4xGTaW/mCnF8wnjtifKBEhxfZ7E/S8tQ0rssrwGNn6q8JH/ohItJfSQp9mBtQYuTlH5QnA==} math-intrinsics@1.1.0: - resolution: - { integrity: sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g== } - engines: { node: ">= 0.4" } + resolution: {integrity: sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==} + engines: {node: '>= 0.4'} merge2@1.4.1: - resolution: - { integrity: sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg== } - engines: { node: ">= 8" } + resolution: {integrity: sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==} + engines: {node: '>= 8'} micromatch@4.0.8: - resolution: - { integrity: sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA== } - engines: { node: ">=8.6" } + resolution: {integrity: sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==} + engines: {node: '>=8.6'} mime-db@1.52.0: - resolution: - { integrity: sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg== } - engines: { node: ">= 0.6" } + resolution: {integrity: sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==} + engines: {node: '>= 0.6'} + + mime-match@1.0.2: + resolution: {integrity: sha512-VXp/ugGDVh3eCLOBCiHZMYWQaTNUHv2IJrut+yXA6+JbLPXHglHwfS/5A5L0ll+jkCY7fIzRJcH6OIunF+c6Cg==} mime-types@2.1.35: - resolution: - { integrity: sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw== } - engines: { node: ">= 0.6" } + resolution: {integrity: sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==} + engines: {node: '>= 0.6'} minimatch@3.1.2: - resolution: - { integrity: sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw== } + resolution: {integrity: sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==} minimatch@9.0.5: - resolution: - { integrity: sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow== } - engines: { node: ">=16 || 14 >=14.17" } + resolution: {integrity: sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==} + engines: {node: '>=16 || 14 >=14.17'} minimist@1.2.8: - resolution: - { integrity: sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA== } + resolution: {integrity: sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==} minipass@7.1.2: - resolution: - { integrity: sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw== } - engines: { node: ">=16 || 14 >=14.17" } + resolution: {integrity: sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==} + engines: {node: '>=16 || 14 >=14.17'} minizlib@3.0.2: - resolution: - { integrity: sha512-oG62iEk+CYt5Xj2YqI5Xi9xWUeZhDI8jjQmC5oThVH5JGCTgIjr7ciJDzC7MBzYd//WvR1OTmP5Q38Q8ShQtVA== } - engines: { node: ">= 18" } + resolution: {integrity: sha512-oG62iEk+CYt5Xj2YqI5Xi9xWUeZhDI8jjQmC5oThVH5JGCTgIjr7ciJDzC7MBzYd//WvR1OTmP5Q38Q8ShQtVA==} + engines: {node: '>= 18'} mkdirp@3.0.1: - resolution: - { integrity: sha512-+NsyUUAZDmo6YVHzL/stxSu3t9YS1iljliy3BSDrXJ/dkn1KYdmtZODGGjLcc9XLgVVpH4KshHB8XmZgMhaBXg== } - engines: { node: ">=10" } + resolution: {integrity: sha512-+NsyUUAZDmo6YVHzL/stxSu3t9YS1iljliy3BSDrXJ/dkn1KYdmtZODGGjLcc9XLgVVpH4KshHB8XmZgMhaBXg==} + engines: {node: '>=10'} hasBin: true motion-dom@12.22.0: - resolution: - { integrity: sha512-ooH7+/BPw9gOsL9VtPhEJHE2m4ltnhMlcGMhEqA0YGNhKof7jdaszvsyThXI6LVIKshJUZ9/CP6HNqQhJfV7kw== } + resolution: {integrity: sha512-ooH7+/BPw9gOsL9VtPhEJHE2m4ltnhMlcGMhEqA0YGNhKof7jdaszvsyThXI6LVIKshJUZ9/CP6HNqQhJfV7kw==} motion-utils@12.19.0: - resolution: - { integrity: sha512-BuFTHINYmV07pdWs6lj6aI63vr2N4dg0vR+td0rtrdpWOhBzIkEklZyLcvKBoEtwSqx8Jg06vUB5RS0xDiUybw== } + resolution: {integrity: sha512-BuFTHINYmV07pdWs6lj6aI63vr2N4dg0vR+td0rtrdpWOhBzIkEklZyLcvKBoEtwSqx8Jg06vUB5RS0xDiUybw==} ms@2.1.3: - resolution: - { integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA== } + resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} + + namespace-emitter@2.0.1: + resolution: {integrity: sha512-N/sMKHniSDJBjfrkbS/tpkPj4RAbvW3mr8UAzvlMHyun93XEm83IAvhWtJVHo+RHn/oO8Job5YN4b+wRjSVp5g==} nanoid@3.3.11: - resolution: - { integrity: sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w== } - engines: { node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1 } + resolution: {integrity: sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==} + engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} hasBin: true nanoid@5.1.5: - resolution: - { integrity: sha512-Ir/+ZpE9fDsNH0hQ3C68uyThDXzYcim2EqcZ8zn8Chtt1iylPT9xXJB0kPCnqzgcEGikO9RxSrh63MsmVCU7Fw== } - engines: { node: ^18 || >=20 } + resolution: {integrity: sha512-Ir/+ZpE9fDsNH0hQ3C68uyThDXzYcim2EqcZ8zn8Chtt1iylPT9xXJB0kPCnqzgcEGikO9RxSrh63MsmVCU7Fw==} + engines: {node: ^18 || >=20} hasBin: true napi-postinstall@0.3.0: - resolution: - { integrity: sha512-M7NqKyhODKV1gRLdkwE7pDsZP2/SC2a2vHkOYh9MCpKMbWVfyVfUw5MaH83Fv6XMjxr5jryUp3IDDL9rlxsTeA== } - engines: { node: ^12.20.0 || ^14.18.0 || >=16.0.0 } + resolution: {integrity: sha512-M7NqKyhODKV1gRLdkwE7pDsZP2/SC2a2vHkOYh9MCpKMbWVfyVfUw5MaH83Fv6XMjxr5jryUp3IDDL9rlxsTeA==} + engines: {node: ^12.20.0 || ^14.18.0 || >=16.0.0} hasBin: true natural-compare@1.4.0: - resolution: - { integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw== } + resolution: {integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==} negotiator@1.0.0: - resolution: - { integrity: sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg== } - engines: { node: ">= 0.6" } + resolution: {integrity: sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg==} + engines: {node: '>= 0.6'} next-intl@4.3.4: - resolution: - { integrity: sha512-VWLIDlGbnL/o4LnveJTJD1NOYN8lh3ZAGTWw2krhfgg53as3VsS4jzUVnArJdqvwtlpU/2BIDbWTZ7V4o1jFEw== } + resolution: {integrity: sha512-VWLIDlGbnL/o4LnveJTJD1NOYN8lh3ZAGTWw2krhfgg53as3VsS4jzUVnArJdqvwtlpU/2BIDbWTZ7V4o1jFEw==} peerDependencies: next: ^12.0.0 || ^13.0.0 || ^14.0.0 || ^15.0.0 react: ^16.8.0 || ^17.0.0 || ^18.0.0 || >=19.0.0-rc <19.0.0 || ^19.0.0 @@ -2845,28 +2534,26 @@ packages: optional: true next-themes@0.4.6: - resolution: - { integrity: sha512-pZvgD5L0IEvX5/9GWyHMf3m8BKiVQwsCMHfoFosXtXBMnaS0ZnIJ9ST4b4NqLVKDEm8QBxoNNGNaBv2JNF6XNA== } + resolution: {integrity: sha512-pZvgD5L0IEvX5/9GWyHMf3m8BKiVQwsCMHfoFosXtXBMnaS0ZnIJ9ST4b4NqLVKDEm8QBxoNNGNaBv2JNF6XNA==} peerDependencies: react: ^16.8 || ^17 || ^18 || ^19 || ^19.0.0-rc react-dom: ^16.8 || ^17 || ^18 || ^19 || ^19.0.0-rc next@15.3.4: - resolution: - { integrity: sha512-mHKd50C+mCjam/gcnwqL1T1vPx/XQNFlXqFIVdgQdVAFY9iIQtY0IfaVflEYzKiqjeA7B0cYYMaCrmAYFjs4rA== } - engines: { node: ^18.18.0 || ^19.8.0 || >= 20.0.0 } + resolution: {integrity: sha512-mHKd50C+mCjam/gcnwqL1T1vPx/XQNFlXqFIVdgQdVAFY9iIQtY0IfaVflEYzKiqjeA7B0cYYMaCrmAYFjs4rA==} + engines: {node: ^18.18.0 || ^19.8.0 || >= 20.0.0} hasBin: true peerDependencies: - "@opentelemetry/api": ^1.1.0 - "@playwright/test": ^1.41.2 - babel-plugin-react-compiler: "*" + '@opentelemetry/api': ^1.1.0 + '@playwright/test': ^1.41.2 + babel-plugin-react-compiler: '*' react: ^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0 react-dom: ^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0 sass: ^1.3.0 peerDependenciesMeta: - "@opentelemetry/api": + '@opentelemetry/api': optional: true - "@playwright/test": + '@playwright/test': optional: true babel-plugin-react-compiler: optional: true @@ -2874,797 +2561,661 @@ packages: optional: true nookies@2.5.2: - resolution: - { integrity: sha512-x0TRSaosAEonNKyCrShoUaJ5rrT5KHRNZ5DwPCuizjgrnkpE5DRf3VL7AyyQin4htict92X1EQ7ejDbaHDVdYA== } + resolution: {integrity: sha512-x0TRSaosAEonNKyCrShoUaJ5rrT5KHRNZ5DwPCuizjgrnkpE5DRf3VL7AyyQin4htict92X1EQ7ejDbaHDVdYA==} object-assign@4.1.1: - resolution: - { integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg== } - engines: { node: ">=0.10.0" } + resolution: {integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==} + engines: {node: '>=0.10.0'} object-inspect@1.13.4: - resolution: - { integrity: sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew== } - engines: { node: ">= 0.4" } + resolution: {integrity: sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==} + engines: {node: '>= 0.4'} object-keys@1.1.1: - resolution: - { integrity: sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA== } - engines: { node: ">= 0.4" } + resolution: {integrity: sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==} + engines: {node: '>= 0.4'} object.assign@4.1.7: - resolution: - { integrity: sha512-nK28WOo+QIjBkDduTINE4JkF/UJJKyf2EJxvJKfblDpyg0Q+pkOHNTL0Qwy6NP6FhE/EnzV73BxxqcJaXY9anw== } - engines: { node: ">= 0.4" } + resolution: {integrity: sha512-nK28WOo+QIjBkDduTINE4JkF/UJJKyf2EJxvJKfblDpyg0Q+pkOHNTL0Qwy6NP6FhE/EnzV73BxxqcJaXY9anw==} + engines: {node: '>= 0.4'} object.entries@1.1.9: - resolution: - { integrity: sha512-8u/hfXFRBD1O0hPUjioLhoWFHRmt6tKA4/vZPyckBr18l1KE9uHrFaFaUi8MDRTpi4uak2goyPTSNJLXX2k2Hw== } - engines: { node: ">= 0.4" } + resolution: {integrity: sha512-8u/hfXFRBD1O0hPUjioLhoWFHRmt6tKA4/vZPyckBr18l1KE9uHrFaFaUi8MDRTpi4uak2goyPTSNJLXX2k2Hw==} + engines: {node: '>= 0.4'} object.fromentries@2.0.8: - resolution: - { integrity: sha512-k6E21FzySsSK5a21KRADBd/NGneRegFO5pLHfdQLpRDETUNJueLXs3WCzyQ3tFRDYgbq3KHGXfTbi2bs8WQ6rQ== } - engines: { node: ">= 0.4" } + resolution: {integrity: sha512-k6E21FzySsSK5a21KRADBd/NGneRegFO5pLHfdQLpRDETUNJueLXs3WCzyQ3tFRDYgbq3KHGXfTbi2bs8WQ6rQ==} + engines: {node: '>= 0.4'} object.groupby@1.0.3: - resolution: - { integrity: sha512-+Lhy3TQTuzXI5hevh8sBGqbmurHbbIjAi0Z4S63nthVLmLxfbj4T54a4CfZrXIrt9iP4mVAPYMo/v99taj3wjQ== } - engines: { node: ">= 0.4" } + resolution: {integrity: sha512-+Lhy3TQTuzXI5hevh8sBGqbmurHbbIjAi0Z4S63nthVLmLxfbj4T54a4CfZrXIrt9iP4mVAPYMo/v99taj3wjQ==} + engines: {node: '>= 0.4'} object.values@1.2.1: - resolution: - { integrity: sha512-gXah6aZrcUxjWg2zR2MwouP2eHlCBzdV4pygudehaKXSGW4v2AsRQUK+lwwXhii6KFZcunEnmSUoYp5CXibxtA== } - engines: { node: ">= 0.4" } + resolution: {integrity: sha512-gXah6aZrcUxjWg2zR2MwouP2eHlCBzdV4pygudehaKXSGW4v2AsRQUK+lwwXhii6KFZcunEnmSUoYp5CXibxtA==} + engines: {node: '>= 0.4'} optionator@0.9.4: - resolution: - { integrity: sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g== } - engines: { node: ">= 0.8.0" } + resolution: {integrity: sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==} + engines: {node: '>= 0.8.0'} own-keys@1.0.1: - resolution: - { integrity: sha512-qFOyK5PjiWZd+QQIh+1jhdb9LpxTF0qs7Pm8o5QHYZ0M3vKqSqzsZaEB6oWlxZ+q2sJBMI/Ktgd2N5ZwQoRHfg== } - engines: { node: ">= 0.4" } + resolution: {integrity: sha512-qFOyK5PjiWZd+QQIh+1jhdb9LpxTF0qs7Pm8o5QHYZ0M3vKqSqzsZaEB6oWlxZ+q2sJBMI/Ktgd2N5ZwQoRHfg==} + engines: {node: '>= 0.4'} p-limit@2.3.0: - resolution: - { integrity: sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w== } - engines: { node: ">=6" } + resolution: {integrity: sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==} + engines: {node: '>=6'} p-limit@3.1.0: - resolution: - { integrity: sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ== } - engines: { node: ">=10" } + resolution: {integrity: sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==} + engines: {node: '>=10'} p-locate@4.1.0: - resolution: - { integrity: sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A== } - engines: { node: ">=8" } + resolution: {integrity: sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==} + engines: {node: '>=8'} p-locate@5.0.0: - resolution: - { integrity: sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw== } - engines: { node: ">=10" } + resolution: {integrity: sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==} + engines: {node: '>=10'} + + p-retry@6.2.1: + resolution: {integrity: sha512-hEt02O4hUct5wtwg4H4KcWgDdm+l1bOaEy/hWzd8xtXB9BqxTWBBhb+2ImAtH4Cv4rPjV76xN3Zumqk3k3AhhQ==} + engines: {node: '>=16.17'} p-try@2.2.0: - resolution: - { integrity: sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ== } - engines: { node: ">=6" } + resolution: {integrity: sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==} + engines: {node: '>=6'} pako@1.0.11: - resolution: - { integrity: sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw== } + resolution: {integrity: sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==} parent-module@1.0.1: - resolution: - { integrity: sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g== } - engines: { node: ">=6" } + resolution: {integrity: sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==} + engines: {node: '>=6'} path-exists@4.0.0: - resolution: - { integrity: sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w== } - engines: { node: ">=8" } + resolution: {integrity: sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==} + engines: {node: '>=8'} path-key@3.1.1: - resolution: - { integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q== } - engines: { node: ">=8" } + resolution: {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==} + engines: {node: '>=8'} path-parse@1.0.7: - resolution: - { integrity: sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw== } + resolution: {integrity: sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==} picocolors@1.1.1: - resolution: - { integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA== } + resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==} picomatch@2.3.1: - resolution: - { integrity: sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA== } - engines: { node: ">=8.6" } + resolution: {integrity: sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==} + engines: {node: '>=8.6'} picomatch@4.0.2: - resolution: - { integrity: sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg== } - engines: { node: ">=12" } + resolution: {integrity: sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==} + engines: {node: '>=12'} pngjs@5.0.0: - resolution: - { integrity: sha512-40QW5YalBNfQo5yRYmiw7Yz6TKKVr3h6970B2YE+3fQpsWcrbj1PzJgxeJ19DRQjhMbKPIuMY8rFaXc8moolVw== } - engines: { node: ">=10.13.0" } + resolution: {integrity: sha512-40QW5YalBNfQo5yRYmiw7Yz6TKKVr3h6970B2YE+3fQpsWcrbj1PzJgxeJ19DRQjhMbKPIuMY8rFaXc8moolVw==} + engines: {node: '>=10.13.0'} possible-typed-array-names@1.1.0: - resolution: - { integrity: sha512-/+5VFTchJDoVj3bhoqi6UeymcD00DAwb1nJwamzPvHEszJ4FpF6SNNbUbOS8yI56qHzdV8eK0qEfOSiodkTdxg== } - engines: { node: ">= 0.4" } + resolution: {integrity: sha512-/+5VFTchJDoVj3bhoqi6UeymcD00DAwb1nJwamzPvHEszJ4FpF6SNNbUbOS8yI56qHzdV8eK0qEfOSiodkTdxg==} + engines: {node: '>= 0.4'} postcss@8.4.31: - resolution: - { integrity: sha512-PS08Iboia9mts/2ygV3eLpY5ghnUcfLV/EXTOW1E2qYxJKGGBUtNjN76FYHnMs36RmARn41bC0AZmn+rR0OVpQ== } - engines: { node: ^10 || ^12 || >=14 } + resolution: {integrity: sha512-PS08Iboia9mts/2ygV3eLpY5ghnUcfLV/EXTOW1E2qYxJKGGBUtNjN76FYHnMs36RmARn41bC0AZmn+rR0OVpQ==} + engines: {node: ^10 || ^12 || >=14} postcss@8.5.6: - resolution: - { integrity: sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg== } - engines: { node: ^10 || ^12 || >=14 } + resolution: {integrity: sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==} + engines: {node: ^10 || ^12 || >=14} + + preact@10.27.2: + resolution: {integrity: sha512-5SYSgFKSyhCbk6SrXyMpqjb5+MQBgfvEKE/OC+PujcY34sOpqtr+0AZQtPYx5IA6VxynQ7rUPCtKzyovpj9Bpg==} prelude-ls@1.2.1: - resolution: - { integrity: sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g== } - engines: { node: ">= 0.8.0" } + resolution: {integrity: sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==} + engines: {node: '>= 0.8.0'} prettier-linter-helpers@1.0.0: - resolution: - { integrity: sha512-GbK2cP9nraSSUF9N2XwUwqfzlAFlMNYYl+ShE/V+H8a9uNl/oUqB1w2EL54Jh0OlyRSd8RfWYJ3coVS4TROP2w== } - engines: { node: ">=6.0.0" } + resolution: {integrity: sha512-GbK2cP9nraSSUF9N2XwUwqfzlAFlMNYYl+ShE/V+H8a9uNl/oUqB1w2EL54Jh0OlyRSd8RfWYJ3coVS4TROP2w==} + engines: {node: '>=6.0.0'} prettier-plugin-sort-json@4.1.1: - resolution: - { integrity: sha512-uJ49wCzwJ/foKKV4tIPxqi4jFFvwUzw4oACMRG2dcmDhBKrxBv0L2wSKkAqHCmxKCvj0xcCZS4jO2kSJO/tRJw== } - engines: { node: ">=18.0.0" } + resolution: {integrity: sha512-uJ49wCzwJ/foKKV4tIPxqi4jFFvwUzw4oACMRG2dcmDhBKrxBv0L2wSKkAqHCmxKCvj0xcCZS4jO2kSJO/tRJw==} + engines: {node: '>=18.0.0'} peerDependencies: prettier: ^3.0.0 prettier@3.6.2: - resolution: - { integrity: sha512-I7AIg5boAr5R0FFtJ6rCfD+LFsWHp81dolrFD8S79U9tb8Az2nGrJncnMSnys+bpQJfRUzqs9hnA81OAA3hCuQ== } - engines: { node: ">=14" } + resolution: {integrity: sha512-I7AIg5boAr5R0FFtJ6rCfD+LFsWHp81dolrFD8S79U9tb8Az2nGrJncnMSnys+bpQJfRUzqs9hnA81OAA3hCuQ==} + engines: {node: '>=14'} hasBin: true process-nextick-args@2.0.1: - resolution: - { integrity: sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag== } + resolution: {integrity: sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==} prop-types@15.8.1: - resolution: - { integrity: sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg== } + resolution: {integrity: sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==} proxy-from-env@1.1.0: - resolution: - { integrity: sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg== } + resolution: {integrity: sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==} punycode@2.3.1: - resolution: - { integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg== } - engines: { node: ">=6" } + resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==} + engines: {node: '>=6'} qr.js@0.0.0: - resolution: - { integrity: sha512-c4iYnWb+k2E+vYpRimHqSu575b1/wKl4XFeJGpFmrJQz5I88v9aY2czh7s0w36srfCM1sXgC/xpoJz5dJfq+OQ== } + resolution: {integrity: sha512-c4iYnWb+k2E+vYpRimHqSu575b1/wKl4XFeJGpFmrJQz5I88v9aY2czh7s0w36srfCM1sXgC/xpoJz5dJfq+OQ==} qrcode@1.5.4: - resolution: - { integrity: sha512-1ca71Zgiu6ORjHqFBDpnSMTR2ReToX4l1Au1VFLyVeBTFavzQnv5JxMFr3ukHVKpSrSA2MCk0lNJSykjUfz7Zg== } - engines: { node: ">=10.13.0" } + resolution: {integrity: sha512-1ca71Zgiu6ORjHqFBDpnSMTR2ReToX4l1Au1VFLyVeBTFavzQnv5JxMFr3ukHVKpSrSA2MCk0lNJSykjUfz7Zg==} + engines: {node: '>=10.13.0'} hasBin: true queue-microtask@1.2.3: - resolution: - { integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A== } + resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==} raf-schd@4.0.3: - resolution: - { integrity: sha512-tQkJl2GRWh83ui2DiPTJz9wEiMN20syf+5oKfB03yYP7ioZcJwsIK8FjrtLwH1m7C7e+Tt2yYBlrOpdT+dyeIQ== } + resolution: {integrity: sha512-tQkJl2GRWh83ui2DiPTJz9wEiMN20syf+5oKfB03yYP7ioZcJwsIK8FjrtLwH1m7C7e+Tt2yYBlrOpdT+dyeIQ==} react-country-flag@3.1.0: - resolution: - { integrity: sha512-JWQFw1efdv9sTC+TGQvTKXQg1NKbDU2mBiAiRWcKM9F1sK+/zjhP2yGmm8YDddWyZdXVkR8Md47rPMJmo4YO5g== } - engines: { node: ">=12" } + resolution: {integrity: sha512-JWQFw1efdv9sTC+TGQvTKXQg1NKbDU2mBiAiRWcKM9F1sK+/zjhP2yGmm8YDddWyZdXVkR8Md47rPMJmo4YO5g==} + engines: {node: '>=12'} peerDependencies: - react: ">=16" + react: '>=16' react-dom@19.1.0: - resolution: - { integrity: sha512-Xs1hdnE+DyKgeHJeJznQmYMIBG3TKIHJJT95Q58nHLSrElKlGQqDTR2HQ9fx5CN/Gk6Vh/kupBTDLU11/nDk/g== } + resolution: {integrity: sha512-Xs1hdnE+DyKgeHJeJznQmYMIBG3TKIHJJT95Q58nHLSrElKlGQqDTR2HQ9fx5CN/Gk6Vh/kupBTDLU11/nDk/g==} peerDependencies: react: ^19.1.0 react-dropzone@14.3.8: - resolution: - { integrity: sha512-sBgODnq+lcA4P296DY4wacOZz3JFpD99fp+hb//iBO2HHnyeZU3FwWyXJ6salNpqQdsZrgMrotuko/BdJMV8Ug== } - engines: { node: ">= 10.13" } + resolution: {integrity: sha512-sBgODnq+lcA4P296DY4wacOZz3JFpD99fp+hb//iBO2HHnyeZU3FwWyXJ6salNpqQdsZrgMrotuko/BdJMV8Ug==} + engines: {node: '>= 10.13'} peerDependencies: - react: ">= 16.8 || 18.0.0" + react: '>= 16.8 || 18.0.0' react-hook-form@7.60.0: - resolution: - { integrity: sha512-SBrYOvMbDB7cV8ZfNpaiLcgjH/a1c7aK0lK+aNigpf4xWLO8q+o4tcvVurv3c4EOyzn/3dCsYt4GKD42VvJ/+A== } - engines: { node: ">=18.0.0" } + resolution: {integrity: sha512-SBrYOvMbDB7cV8ZfNpaiLcgjH/a1c7aK0lK+aNigpf4xWLO8q+o4tcvVurv3c4EOyzn/3dCsYt4GKD42VvJ/+A==} + engines: {node: '>=18.0.0'} peerDependencies: react: ^16.8.0 || ^17 || ^18 || ^19 react-icons@5.5.0: - resolution: - { integrity: sha512-MEFcXdkP3dLo8uumGI5xN3lDFNsRtrjbOEKDLD7yv76v4wpnEq2Lt2qeHaQOr34I/wPN3s3+N08WkQ+CW37Xiw== } + resolution: {integrity: sha512-MEFcXdkP3dLo8uumGI5xN3lDFNsRtrjbOEKDLD7yv76v4wpnEq2Lt2qeHaQOr34I/wPN3s3+N08WkQ+CW37Xiw==} peerDependencies: - react: "*" + react: '*' react-image-crop@11.0.10: - resolution: - { integrity: sha512-+5FfDXUgYLLqBh1Y/uQhIycpHCbXkI50a+nbfkB1C0xXXUTwkisHDo2QCB1SQJyHCqIuia4FeyReqXuMDKWQTQ== } + resolution: {integrity: sha512-+5FfDXUgYLLqBh1Y/uQhIycpHCbXkI50a+nbfkB1C0xXXUTwkisHDo2QCB1SQJyHCqIuia4FeyReqXuMDKWQTQ==} peerDependencies: - react: ">=16.13.1" + react: '>=16.13.1' react-is@16.13.1: - resolution: - { integrity: sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ== } + resolution: {integrity: sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==} react-qr-code@2.0.18: - resolution: - { integrity: sha512-v1Jqz7urLMhkO6jkgJuBYhnqvXagzceg3qJUWayuCK/c6LTIonpWbwxR1f1APGd4xrW/QcQEovNrAojbUz65Tg== } + resolution: {integrity: sha512-v1Jqz7urLMhkO6jkgJuBYhnqvXagzceg3qJUWayuCK/c6LTIonpWbwxR1f1APGd4xrW/QcQEovNrAojbUz65Tg==} peerDependencies: - react: "*" + react: '*' react-qr-reader@3.0.0-beta-1: - resolution: - { integrity: sha512-5HeFH9x/BlziRYQYGK2AeWS9WiKYZtGGMs9DXy3bcySTX3C9UJL9EwcPnWw8vlf7JP4FcrAlr1SnZ5nsWLQGyw== } + resolution: {integrity: sha512-5HeFH9x/BlziRYQYGK2AeWS9WiKYZtGGMs9DXy3bcySTX3C9UJL9EwcPnWw8vlf7JP4FcrAlr1SnZ5nsWLQGyw==} peerDependencies: react: ^16.8.0 || ^17.0.0 react-dom: ^16.8.0 || ^17.0.0 react-redux@9.2.0: - resolution: - { integrity: sha512-ROY9fvHhwOD9ySfrF0wmvu//bKCQ6AeZZq1nJNtbDC+kk5DuSuNX/n6YWYF/SYy7bSba4D4FSz8DJeKY/S/r+g== } + resolution: {integrity: sha512-ROY9fvHhwOD9ySfrF0wmvu//bKCQ6AeZZq1nJNtbDC+kk5DuSuNX/n6YWYF/SYy7bSba4D4FSz8DJeKY/S/r+g==} peerDependencies: - "@types/react": ^18.2.25 || ^19 + '@types/react': ^18.2.25 || ^19 react: ^18.0 || ^19 redux: ^5.0.0 peerDependenciesMeta: - "@types/react": + '@types/react': optional: true redux: optional: true react-remove-scroll-bar@2.3.8: - resolution: - { integrity: sha512-9r+yi9+mgU33AKcj6IbT9oRCO78WriSj6t/cF8DWBZJ9aOGPOTEDvdUDz1FwKim7QXWwmHqtdHnRJfhAxEG46Q== } - engines: { node: ">=10" } + resolution: {integrity: sha512-9r+yi9+mgU33AKcj6IbT9oRCO78WriSj6t/cF8DWBZJ9aOGPOTEDvdUDz1FwKim7QXWwmHqtdHnRJfhAxEG46Q==} + engines: {node: '>=10'} peerDependencies: - "@types/react": "*" + '@types/react': '*' react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 peerDependenciesMeta: - "@types/react": + '@types/react': optional: true react-remove-scroll@2.7.1: - resolution: - { integrity: sha512-HpMh8+oahmIdOuS5aFKKY6Pyog+FNaZV/XyJOq7b4YFwsFHe5yYfdbIalI4k3vU2nSDql7YskmUseHsRrJqIPA== } - engines: { node: ">=10" } + resolution: {integrity: sha512-HpMh8+oahmIdOuS5aFKKY6Pyog+FNaZV/XyJOq7b4YFwsFHe5yYfdbIalI4k3vU2nSDql7YskmUseHsRrJqIPA==} + engines: {node: '>=10'} peerDependencies: - "@types/react": "*" + '@types/react': '*' react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc peerDependenciesMeta: - "@types/react": + '@types/react': optional: true react-style-singleton@2.2.3: - resolution: - { integrity: sha512-b6jSvxvVnyptAiLjbkWLE/lOnR4lfTtDAl+eUC7RZy+QQWc6wRzIV2CE6xBuMmDxc2qIihtDCZD5NPOFl7fRBQ== } - engines: { node: ">=10" } + resolution: {integrity: sha512-b6jSvxvVnyptAiLjbkWLE/lOnR4lfTtDAl+eUC7RZy+QQWc6wRzIV2CE6xBuMmDxc2qIihtDCZD5NPOFl7fRBQ==} + engines: {node: '>=10'} peerDependencies: - "@types/react": "*" + '@types/react': '*' react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc peerDependenciesMeta: - "@types/react": + '@types/react': optional: true react@19.1.0: - resolution: - { integrity: sha512-FS+XFBNvn3GTAWq26joslQgWNoFu08F4kl0J4CgdNKADkdSGXQyTCnKteIAJy96Br6YbpEU1LSzV5dYtjMkMDg== } - engines: { node: ">=0.10.0" } + resolution: {integrity: sha512-FS+XFBNvn3GTAWq26joslQgWNoFu08F4kl0J4CgdNKADkdSGXQyTCnKteIAJy96Br6YbpEU1LSzV5dYtjMkMDg==} + engines: {node: '>=0.10.0'} readable-stream@2.3.8: - resolution: - { integrity: sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA== } + resolution: {integrity: sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==} redux@5.0.1: - resolution: - { integrity: sha512-M9/ELqF6fy8FwmkpnF0S3YKOqMyoWJ4+CS5Efg2ct3oY9daQvd/Pc71FpGZsVsbl3Cpb+IIcjBDUnnyBdQbq4w== } + resolution: {integrity: sha512-M9/ELqF6fy8FwmkpnF0S3YKOqMyoWJ4+CS5Efg2ct3oY9daQvd/Pc71FpGZsVsbl3Cpb+IIcjBDUnnyBdQbq4w==} reflect.getprototypeof@1.0.10: - resolution: - { integrity: sha512-00o4I+DVrefhv+nX0ulyi3biSHCPDe+yLv5o/p6d/UVlirijB8E16FtfwSAi4g3tcqrQ4lRAqQSoFEZJehYEcw== } - engines: { node: ">= 0.4" } + resolution: {integrity: sha512-00o4I+DVrefhv+nX0ulyi3biSHCPDe+yLv5o/p6d/UVlirijB8E16FtfwSAi4g3tcqrQ4lRAqQSoFEZJehYEcw==} + engines: {node: '>= 0.4'} regexp.prototype.flags@1.5.4: - resolution: - { integrity: sha512-dYqgNSZbDwkaJ2ceRd9ojCGjBq+mOm9LmtXnAnEGyHhN/5R7iDW2TRw3h+o/jCFxus3P2LfWIIiwowAjANm7IA== } - engines: { node: ">= 0.4" } + resolution: {integrity: sha512-dYqgNSZbDwkaJ2ceRd9ojCGjBq+mOm9LmtXnAnEGyHhN/5R7iDW2TRw3h+o/jCFxus3P2LfWIIiwowAjANm7IA==} + engines: {node: '>= 0.4'} require-directory@2.1.1: - resolution: - { integrity: sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q== } - engines: { node: ">=0.10.0" } + resolution: {integrity: sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==} + engines: {node: '>=0.10.0'} require-main-filename@2.0.0: - resolution: - { integrity: sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg== } + resolution: {integrity: sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==} resolve-from@4.0.0: - resolution: - { integrity: sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g== } - engines: { node: ">=4" } + resolution: {integrity: sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==} + engines: {node: '>=4'} resolve-pkg-maps@1.0.0: - resolution: - { integrity: sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw== } + resolution: {integrity: sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==} resolve@1.22.10: - resolution: - { integrity: sha512-NPRy+/ncIMeDlTAsuqwKIiferiawhefFJtkNSW0qZJEqMEb+qBt/77B/jGeeek+F0uOeN05CDa6HXbbIgtVX4w== } - engines: { node: ">= 0.4" } + resolution: {integrity: sha512-NPRy+/ncIMeDlTAsuqwKIiferiawhefFJtkNSW0qZJEqMEb+qBt/77B/jGeeek+F0uOeN05CDa6HXbbIgtVX4w==} + engines: {node: '>= 0.4'} hasBin: true resolve@2.0.0-next.5: - resolution: - { integrity: sha512-U7WjGVG9sH8tvjW5SmGbQuui75FiyjAX72HX15DwBBwF9dNiQZRQAg9nnPhYy+TUnE0+VcrttuvNI8oSxZcocA== } + resolution: {integrity: sha512-U7WjGVG9sH8tvjW5SmGbQuui75FiyjAX72HX15DwBBwF9dNiQZRQAg9nnPhYy+TUnE0+VcrttuvNI8oSxZcocA==} hasBin: true + retry@0.13.1: + resolution: {integrity: sha512-XQBQ3I8W1Cge0Seh+6gjj03LbmRFWuoszgK9ooCpwYIrhhoO80pfq4cUkU5DkknwfOfFteRwlZ56PYOGYyFWdg==} + engines: {node: '>= 4'} + reusify@1.1.0: - resolution: - { integrity: sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw== } - engines: { iojs: ">=1.0.0", node: ">=0.10.0" } + resolution: {integrity: sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==} + engines: {iojs: '>=1.0.0', node: '>=0.10.0'} rollup@2.79.2: - resolution: - { integrity: sha512-fS6iqSPZDs3dr/y7Od6y5nha8dW1YnbgtsyotCVvoFGKbERG++CVRFv1meyGDE1SNItQA8BrnCw7ScdAhRJ3XQ== } - engines: { node: ">=10.0.0" } + resolution: {integrity: sha512-fS6iqSPZDs3dr/y7Od6y5nha8dW1YnbgtsyotCVvoFGKbERG++CVRFv1meyGDE1SNItQA8BrnCw7ScdAhRJ3XQ==} + engines: {node: '>=10.0.0'} hasBin: true run-parallel@1.2.0: - resolution: - { integrity: sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA== } + resolution: {integrity: sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==} safe-array-concat@1.1.3: - resolution: - { integrity: sha512-AURm5f0jYEOydBj7VQlVvDrjeFgthDdEF5H1dP+6mNpoXOMo1quQqJ4wvJDyRZ9+pO3kGWoOdmV08cSv2aJV6Q== } - engines: { node: ">=0.4" } + resolution: {integrity: sha512-AURm5f0jYEOydBj7VQlVvDrjeFgthDdEF5H1dP+6mNpoXOMo1quQqJ4wvJDyRZ9+pO3kGWoOdmV08cSv2aJV6Q==} + engines: {node: '>=0.4'} safe-buffer@5.1.2: - resolution: - { integrity: sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g== } + resolution: {integrity: sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==} safe-push-apply@1.0.0: - resolution: - { integrity: sha512-iKE9w/Z7xCzUMIZqdBsp6pEQvwuEebH4vdpjcDWnyzaI6yl6O9FHvVpmGelvEHNsoY6wGblkxR6Zty/h00WiSA== } - engines: { node: ">= 0.4" } + resolution: {integrity: sha512-iKE9w/Z7xCzUMIZqdBsp6pEQvwuEebH4vdpjcDWnyzaI6yl6O9FHvVpmGelvEHNsoY6wGblkxR6Zty/h00WiSA==} + engines: {node: '>= 0.4'} safe-regex-test@1.1.0: - resolution: - { integrity: sha512-x/+Cz4YrimQxQccJf5mKEbIa1NzeCRNI5Ecl/ekmlYaampdNLPalVyIcCZNNH3MvmqBugV5TMYZXv0ljslUlaw== } - engines: { node: ">= 0.4" } + resolution: {integrity: sha512-x/+Cz4YrimQxQccJf5mKEbIa1NzeCRNI5Ecl/ekmlYaampdNLPalVyIcCZNNH3MvmqBugV5TMYZXv0ljslUlaw==} + engines: {node: '>= 0.4'} scheduler@0.26.0: - resolution: - { integrity: sha512-NlHwttCI/l5gCPR3D1nNXtWABUmBwvZpEQiD4IXSbIDq8BzLIK/7Ir5gTFSGZDUu37K5cMNp0hFtzO38sC7gWA== } + resolution: {integrity: sha512-NlHwttCI/l5gCPR3D1nNXtWABUmBwvZpEQiD4IXSbIDq8BzLIK/7Ir5gTFSGZDUu37K5cMNp0hFtzO38sC7gWA==} semver@6.3.1: - resolution: - { integrity: sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA== } + resolution: {integrity: sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==} hasBin: true semver@7.7.2: - resolution: - { integrity: sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA== } - engines: { node: ">=10" } + resolution: {integrity: sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==} + engines: {node: '>=10'} hasBin: true set-blocking@2.0.0: - resolution: - { integrity: sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw== } + resolution: {integrity: sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==} set-cookie-parser@2.7.1: - resolution: - { integrity: sha512-IOc8uWeOZgnb3ptbCURJWNjWUPcO3ZnTTdzsurqERrP6nPyv+paC55vJM0LpOlT2ne+Ix+9+CRG1MNLlyZ4GjQ== } + resolution: {integrity: sha512-IOc8uWeOZgnb3ptbCURJWNjWUPcO3ZnTTdzsurqERrP6nPyv+paC55vJM0LpOlT2ne+Ix+9+CRG1MNLlyZ4GjQ==} set-function-length@1.2.2: - resolution: - { integrity: sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg== } - engines: { node: ">= 0.4" } + resolution: {integrity: sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==} + engines: {node: '>= 0.4'} set-function-name@2.0.2: - resolution: - { integrity: sha512-7PGFlmtwsEADb0WYyvCMa1t+yke6daIG4Wirafur5kcf+MhUnPms1UeR0CKQdTZD81yESwMHbtn+TR+dMviakQ== } - engines: { node: ">= 0.4" } + resolution: {integrity: sha512-7PGFlmtwsEADb0WYyvCMa1t+yke6daIG4Wirafur5kcf+MhUnPms1UeR0CKQdTZD81yESwMHbtn+TR+dMviakQ==} + engines: {node: '>= 0.4'} set-proto@1.0.0: - resolution: - { integrity: sha512-RJRdvCo6IAnPdsvP/7m6bsQqNnn1FCBX5ZNtFL98MmFF/4xAIJTIg1YbHW5DC2W5SKZanrC6i4HsJqlajw/dZw== } - engines: { node: ">= 0.4" } + resolution: {integrity: sha512-RJRdvCo6IAnPdsvP/7m6bsQqNnn1FCBX5ZNtFL98MmFF/4xAIJTIg1YbHW5DC2W5SKZanrC6i4HsJqlajw/dZw==} + engines: {node: '>= 0.4'} setimmediate@1.0.5: - resolution: - { integrity: sha512-MATJdZp8sLqDl/68LfQmbP8zKPLQNV6BIZoIgrscFDQ+RsvK/BxeDQOgyxKKoh0y/8h3BqVFnCqQ/gd+reiIXA== } + resolution: {integrity: sha512-MATJdZp8sLqDl/68LfQmbP8zKPLQNV6BIZoIgrscFDQ+RsvK/BxeDQOgyxKKoh0y/8h3BqVFnCqQ/gd+reiIXA==} sharp@0.34.2: - resolution: - { integrity: sha512-lszvBmB9QURERtyKT2bNmsgxXK0ShJrL/fvqlonCo7e6xBF8nT8xU6pW+PMIbLsz0RxQk3rgH9kd8UmvOzlMJg== } - engines: { node: ^18.17.0 || ^20.3.0 || >=21.0.0 } + resolution: {integrity: sha512-lszvBmB9QURERtyKT2bNmsgxXK0ShJrL/fvqlonCo7e6xBF8nT8xU6pW+PMIbLsz0RxQk3rgH9kd8UmvOzlMJg==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} shebang-command@2.0.0: - resolution: - { integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA== } - engines: { node: ">=8" } + resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==} + engines: {node: '>=8'} shebang-regex@3.0.0: - resolution: - { integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A== } - engines: { node: ">=8" } + resolution: {integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==} + engines: {node: '>=8'} side-channel-list@1.0.0: - resolution: - { integrity: sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA== } - engines: { node: ">= 0.4" } + resolution: {integrity: sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==} + engines: {node: '>= 0.4'} side-channel-map@1.0.1: - resolution: - { integrity: sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA== } - engines: { node: ">= 0.4" } + resolution: {integrity: sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==} + engines: {node: '>= 0.4'} side-channel-weakmap@1.0.2: - resolution: - { integrity: sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A== } - engines: { node: ">= 0.4" } + resolution: {integrity: sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==} + engines: {node: '>= 0.4'} side-channel@1.1.0: - resolution: - { integrity: sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw== } - engines: { node: ">= 0.4" } + resolution: {integrity: sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==} + engines: {node: '>= 0.4'} simple-swizzle@0.2.2: - resolution: - { integrity: sha512-JA//kQgZtbuY83m+xT+tXJkmJncGMTFT+C+g2h2R9uxkYIrE2yy9sgmcLhCnw57/WSD+Eh3J97FPEDFnbXnDUg== } + resolution: {integrity: sha512-JA//kQgZtbuY83m+xT+tXJkmJncGMTFT+C+g2h2R9uxkYIrE2yy9sgmcLhCnw57/WSD+Eh3J97FPEDFnbXnDUg==} sonner@2.0.6: - resolution: - { integrity: sha512-yHFhk8T/DK3YxjFQXIrcHT1rGEeTLliVzWbO0xN8GberVun2RiBnxAjXAYpZrqwEVHBG9asI/Li8TAAhN9m59Q== } + resolution: {integrity: sha512-yHFhk8T/DK3YxjFQXIrcHT1rGEeTLliVzWbO0xN8GberVun2RiBnxAjXAYpZrqwEVHBG9asI/Li8TAAhN9m59Q==} peerDependencies: react: ^18.0.0 || ^19.0.0 || ^19.0.0-rc react-dom: ^18.0.0 || ^19.0.0 || ^19.0.0-rc source-map-js@1.2.1: - resolution: - { integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA== } - engines: { node: ">=0.10.0" } + resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==} + engines: {node: '>=0.10.0'} stable-hash@0.0.5: - resolution: - { integrity: sha512-+L3ccpzibovGXFK+Ap/f8LOS0ahMrHTf3xu7mMLSpEGU0EO9ucaysSylKo9eRDFNhWve/y275iPmIZ4z39a9iA== } + resolution: {integrity: sha512-+L3ccpzibovGXFK+Ap/f8LOS0ahMrHTf3xu7mMLSpEGU0EO9ucaysSylKo9eRDFNhWve/y275iPmIZ4z39a9iA==} stop-iteration-iterator@1.1.0: - resolution: - { integrity: sha512-eLoXW/DHyl62zxY4SCaIgnRhuMr6ri4juEYARS8E6sCEqzKpOiE521Ucofdx+KnDZl5xmvGYaaKCk5FEOxJCoQ== } - engines: { node: ">= 0.4" } + resolution: {integrity: sha512-eLoXW/DHyl62zxY4SCaIgnRhuMr6ri4juEYARS8E6sCEqzKpOiE521Ucofdx+KnDZl5xmvGYaaKCk5FEOxJCoQ==} + engines: {node: '>= 0.4'} streamsearch@1.1.0: - resolution: - { integrity: sha512-Mcc5wHehp9aXz1ax6bZUyY5afg9u2rv5cqQI3mRrYkGC8rW2hM02jWuwjtL++LS5qinSyhj2QfLyNsuc+VsExg== } - engines: { node: ">=10.0.0" } + resolution: {integrity: sha512-Mcc5wHehp9aXz1ax6bZUyY5afg9u2rv5cqQI3mRrYkGC8rW2hM02jWuwjtL++LS5qinSyhj2QfLyNsuc+VsExg==} + engines: {node: '>=10.0.0'} string-width@4.2.3: - resolution: - { integrity: sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== } - engines: { node: ">=8" } + resolution: {integrity: sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==} + engines: {node: '>=8'} string.prototype.includes@2.0.1: - resolution: - { integrity: sha512-o7+c9bW6zpAdJHTtujeePODAhkuicdAryFsfVKwA+wGw89wJ4GTY484WTucM9hLtDEOpOvI+aHnzqnC5lHp4Rg== } - engines: { node: ">= 0.4" } + resolution: {integrity: sha512-o7+c9bW6zpAdJHTtujeePODAhkuicdAryFsfVKwA+wGw89wJ4GTY484WTucM9hLtDEOpOvI+aHnzqnC5lHp4Rg==} + engines: {node: '>= 0.4'} string.prototype.matchall@4.0.12: - resolution: - { integrity: sha512-6CC9uyBL+/48dYizRf7H7VAYCMCNTBeM78x/VTUe9bFEaxBepPJDa1Ow99LqI/1yF7kuy7Q3cQsYMrcjGUcskA== } - engines: { node: ">= 0.4" } + resolution: {integrity: sha512-6CC9uyBL+/48dYizRf7H7VAYCMCNTBeM78x/VTUe9bFEaxBepPJDa1Ow99LqI/1yF7kuy7Q3cQsYMrcjGUcskA==} + engines: {node: '>= 0.4'} string.prototype.repeat@1.0.0: - resolution: - { integrity: sha512-0u/TldDbKD8bFCQ/4f5+mNRrXwZ8hg2w7ZR8wa16e8z9XpePWl3eGEcUD0OXpEH/VJH/2G3gjUtR3ZOiBe2S/w== } + resolution: {integrity: sha512-0u/TldDbKD8bFCQ/4f5+mNRrXwZ8hg2w7ZR8wa16e8z9XpePWl3eGEcUD0OXpEH/VJH/2G3gjUtR3ZOiBe2S/w==} string.prototype.trim@1.2.10: - resolution: - { integrity: sha512-Rs66F0P/1kedk5lyYyH9uBzuiI/kNRmwJAR9quK6VOtIpZ2G+hMZd+HQbbv25MgCA6gEffoMZYxlTod4WcdrKA== } - engines: { node: ">= 0.4" } + resolution: {integrity: sha512-Rs66F0P/1kedk5lyYyH9uBzuiI/kNRmwJAR9quK6VOtIpZ2G+hMZd+HQbbv25MgCA6gEffoMZYxlTod4WcdrKA==} + engines: {node: '>= 0.4'} string.prototype.trimend@1.0.9: - resolution: - { integrity: sha512-G7Ok5C6E/j4SGfyLCloXTrngQIQU3PWtXGst3yM7Bea9FRURf1S42ZHlZZtsNque2FN2PoUhfZXYLNWwEr4dLQ== } - engines: { node: ">= 0.4" } + resolution: {integrity: sha512-G7Ok5C6E/j4SGfyLCloXTrngQIQU3PWtXGst3yM7Bea9FRURf1S42ZHlZZtsNque2FN2PoUhfZXYLNWwEr4dLQ==} + engines: {node: '>= 0.4'} string.prototype.trimstart@1.0.8: - resolution: - { integrity: sha512-UXSH262CSZY1tfu3G3Secr6uGLCFVPMhIqHjlgCUtCCcgihYc/xKs9djMTMUOb2j1mVSeU8EU6NWc/iQKU6Gfg== } - engines: { node: ">= 0.4" } + resolution: {integrity: sha512-UXSH262CSZY1tfu3G3Secr6uGLCFVPMhIqHjlgCUtCCcgihYc/xKs9djMTMUOb2j1mVSeU8EU6NWc/iQKU6Gfg==} + engines: {node: '>= 0.4'} string_decoder@1.1.1: - resolution: - { integrity: sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg== } + resolution: {integrity: sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==} strip-ansi@6.0.1: - resolution: - { integrity: sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== } - engines: { node: ">=8" } + resolution: {integrity: sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==} + engines: {node: '>=8'} strip-bom@3.0.0: - resolution: - { integrity: sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA== } - engines: { node: ">=4" } + resolution: {integrity: sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==} + engines: {node: '>=4'} strip-json-comments@3.1.1: - resolution: - { integrity: sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig== } - engines: { node: ">=8" } + resolution: {integrity: sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==} + engines: {node: '>=8'} styled-jsx@5.1.6: - resolution: - { integrity: sha512-qSVyDTeMotdvQYoHWLNGwRFJHC+i+ZvdBRYosOFgC+Wg1vx4frN2/RG/NA7SYqqvKNLf39P2LSRA2pu6n0XYZA== } - engines: { node: ">= 12.0.0" } + resolution: {integrity: sha512-qSVyDTeMotdvQYoHWLNGwRFJHC+i+ZvdBRYosOFgC+Wg1vx4frN2/RG/NA7SYqqvKNLf39P2LSRA2pu6n0XYZA==} + engines: {node: '>= 12.0.0'} peerDependencies: - "@babel/core": "*" - babel-plugin-macros: "*" - react: ">= 16.8.0 || 17.x.x || ^18.0.0-0 || ^19.0.0-0" + '@babel/core': '*' + babel-plugin-macros: '*' + react: '>= 16.8.0 || 17.x.x || ^18.0.0-0 || ^19.0.0-0' peerDependenciesMeta: - "@babel/core": + '@babel/core': optional: true babel-plugin-macros: optional: true supports-color@7.2.0: - resolution: - { integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw== } - engines: { node: ">=8" } + resolution: {integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==} + engines: {node: '>=8'} supports-preserve-symlinks-flag@1.0.0: - resolution: - { integrity: sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w== } - engines: { node: ">= 0.4" } + resolution: {integrity: sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==} + engines: {node: '>= 0.4'} synckit@0.11.8: - resolution: - { integrity: sha512-+XZ+r1XGIJGeQk3VvXhT6xx/VpbHsRzsTkGgF6E5RX9TTXD0118l87puaEBZ566FhqblC6U0d4XnubznJDm30A== } - engines: { node: ^14.18.0 || >=16.0.0 } + resolution: {integrity: sha512-+XZ+r1XGIJGeQk3VvXhT6xx/VpbHsRzsTkGgF6E5RX9TTXD0118l87puaEBZ566FhqblC6U0d4XnubznJDm30A==} + engines: {node: ^14.18.0 || >=16.0.0} tailwind-merge@3.3.1: - resolution: - { integrity: sha512-gBXpgUm/3rp1lMZZrM/w7D8GKqshif0zAymAhbCyIt8KMe+0v9DQ7cdYLR4FHH/cKpdTXb+A/tKKU3eolfsI+g== } + resolution: {integrity: sha512-gBXpgUm/3rp1lMZZrM/w7D8GKqshif0zAymAhbCyIt8KMe+0v9DQ7cdYLR4FHH/cKpdTXb+A/tKKU3eolfsI+g==} tailwindcss@4.1.11: - resolution: - { integrity: sha512-2E9TBm6MDD/xKYe+dvJZAmg3yxIEDNRc0jwlNyDg/4Fil2QcSLjFKGVff0lAf1jjeaArlG/M75Ey/EYr/OJtBA== } + resolution: {integrity: sha512-2E9TBm6MDD/xKYe+dvJZAmg3yxIEDNRc0jwlNyDg/4Fil2QcSLjFKGVff0lAf1jjeaArlG/M75Ey/EYr/OJtBA==} tapable@2.2.2: - resolution: - { integrity: sha512-Re10+NauLTMCudc7T5WLFLAwDhQ0JWdrMK+9B2M8zR5hRExKmsRDCBA7/aV/pNJFltmBFO5BAMlQFi/vq3nKOg== } - engines: { node: ">=6" } + resolution: {integrity: sha512-Re10+NauLTMCudc7T5WLFLAwDhQ0JWdrMK+9B2M8zR5hRExKmsRDCBA7/aV/pNJFltmBFO5BAMlQFi/vq3nKOg==} + engines: {node: '>=6'} tar@7.4.3: - resolution: - { integrity: sha512-5S7Va8hKfV7W5U6g3aYxXmlPoZVAwUMy9AOKyF2fVuZa2UD3qZjg578OrLRt8PcNN1PleVaL/5/yYATNL0ICUw== } - engines: { node: ">=18" } + resolution: {integrity: sha512-5S7Va8hKfV7W5U6g3aYxXmlPoZVAwUMy9AOKyF2fVuZa2UD3qZjg578OrLRt8PcNN1PleVaL/5/yYATNL0ICUw==} + engines: {node: '>=18'} tiny-invariant@1.3.3: - resolution: - { integrity: sha512-+FbBPE1o9QAYvviau/qC5SE3caw21q3xkvWKBtja5vgqOWIHHJ3ioaq1VPfn/Szqctz2bU/oYeKd9/z5BL+PVg== } + resolution: {integrity: sha512-+FbBPE1o9QAYvviau/qC5SE3caw21q3xkvWKBtja5vgqOWIHHJ3ioaq1VPfn/Szqctz2bU/oYeKd9/z5BL+PVg==} tinyglobby@0.2.14: - resolution: - { integrity: sha512-tX5e7OM1HnYr2+a2C/4V0htOcSQcoSTH9KgJnVvNm5zm/cyEWKJ7j7YutsH9CxMdtOkkLFy2AHrMci9IM8IPZQ== } - engines: { node: ">=12.0.0" } + resolution: {integrity: sha512-tX5e7OM1HnYr2+a2C/4V0htOcSQcoSTH9KgJnVvNm5zm/cyEWKJ7j7YutsH9CxMdtOkkLFy2AHrMci9IM8IPZQ==} + engines: {node: '>=12.0.0'} to-regex-range@5.0.1: - resolution: - { integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ== } - engines: { node: ">=8.0" } + resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==} + engines: {node: '>=8.0'} ts-api-utils@2.1.0: - resolution: - { integrity: sha512-CUgTZL1irw8u29bzrOD/nH85jqyc74D6SshFgujOIA7osm2Rz7dYH77agkx7H4FBNxDq7Cjf+IjaX/8zwFW+ZQ== } - engines: { node: ">=18.12" } + resolution: {integrity: sha512-CUgTZL1irw8u29bzrOD/nH85jqyc74D6SshFgujOIA7osm2Rz7dYH77agkx7H4FBNxDq7Cjf+IjaX/8zwFW+ZQ==} + engines: {node: '>=18.12'} peerDependencies: - typescript: ">=4.8.4" + typescript: '>=4.8.4' ts-custom-error@3.3.1: - resolution: - { integrity: sha512-5OX1tzOjxWEgsr/YEUWSuPrQ00deKLh6D7OTWcvNHm12/7QPyRh8SYpyWvA4IZv8H/+GQWQEh/kwo95Q9OVW1A== } - engines: { node: ">=14.0.0" } + resolution: {integrity: sha512-5OX1tzOjxWEgsr/YEUWSuPrQ00deKLh6D7OTWcvNHm12/7QPyRh8SYpyWvA4IZv8H/+GQWQEh/kwo95Q9OVW1A==} + engines: {node: '>=14.0.0'} tsconfig-paths@3.15.0: - resolution: - { integrity: sha512-2Ac2RgzDe/cn48GvOe3M+o82pEFewD3UPbyoUHHdKasHwJKjds4fLXWf/Ux5kATBKN20oaFGu+jbElp1pos0mg== } + resolution: {integrity: sha512-2Ac2RgzDe/cn48GvOe3M+o82pEFewD3UPbyoUHHdKasHwJKjds4fLXWf/Ux5kATBKN20oaFGu+jbElp1pos0mg==} tslib@2.8.1: - resolution: - { integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w== } + resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==} tw-animate-css@1.3.5: - resolution: - { integrity: sha512-t3u+0YNoloIhj1mMXs779P6MO9q3p3mvGn4k1n3nJPqJw/glZcuijG2qTSN4z4mgNRfW5ZC3aXJFLwDtiipZXA== } + resolution: {integrity: sha512-t3u+0YNoloIhj1mMXs779P6MO9q3p3mvGn4k1n3nJPqJw/glZcuijG2qTSN4z4mgNRfW5ZC3aXJFLwDtiipZXA==} type-check@0.4.0: - resolution: - { integrity: sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew== } - engines: { node: ">= 0.8.0" } + resolution: {integrity: sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==} + engines: {node: '>= 0.8.0'} typed-array-buffer@1.0.3: - resolution: - { integrity: sha512-nAYYwfY3qnzX30IkA6AQZjVbtK6duGontcQm1WSG1MD94YLqK0515GNApXkoxKOWMusVssAHWLh9SeaoefYFGw== } - engines: { node: ">= 0.4" } + resolution: {integrity: sha512-nAYYwfY3qnzX30IkA6AQZjVbtK6duGontcQm1WSG1MD94YLqK0515GNApXkoxKOWMusVssAHWLh9SeaoefYFGw==} + engines: {node: '>= 0.4'} typed-array-byte-length@1.0.3: - resolution: - { integrity: sha512-BaXgOuIxz8n8pIq3e7Atg/7s+DpiYrxn4vdot3w9KbnBhcRQq6o3xemQdIfynqSeXeDrF32x+WvfzmOjPiY9lg== } - engines: { node: ">= 0.4" } + resolution: {integrity: sha512-BaXgOuIxz8n8pIq3e7Atg/7s+DpiYrxn4vdot3w9KbnBhcRQq6o3xemQdIfynqSeXeDrF32x+WvfzmOjPiY9lg==} + engines: {node: '>= 0.4'} typed-array-byte-offset@1.0.4: - resolution: - { integrity: sha512-bTlAFB/FBYMcuX81gbL4OcpH5PmlFHqlCCpAl8AlEzMz5k53oNDvN8p1PNOWLEmI2x4orp3raOFB51tv9X+MFQ== } - engines: { node: ">= 0.4" } + resolution: {integrity: sha512-bTlAFB/FBYMcuX81gbL4OcpH5PmlFHqlCCpAl8AlEzMz5k53oNDvN8p1PNOWLEmI2x4orp3raOFB51tv9X+MFQ==} + engines: {node: '>= 0.4'} typed-array-length@1.0.7: - resolution: - { integrity: sha512-3KS2b+kL7fsuk/eJZ7EQdnEmQoaho/r6KUef7hxvltNA5DR8NAUM+8wJMbJyZ4G9/7i3v5zPBIMN5aybAh2/Jg== } - engines: { node: ">= 0.4" } + resolution: {integrity: sha512-3KS2b+kL7fsuk/eJZ7EQdnEmQoaho/r6KUef7hxvltNA5DR8NAUM+8wJMbJyZ4G9/7i3v5zPBIMN5aybAh2/Jg==} + engines: {node: '>= 0.4'} typescript@5.8.3: - resolution: - { integrity: sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ== } - engines: { node: ">=14.17" } + resolution: {integrity: sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ==} + engines: {node: '>=14.17'} hasBin: true unbox-primitive@1.1.0: - resolution: - { integrity: sha512-nWJ91DjeOkej/TA8pXQ3myruKpKEYgqvpw9lz4OPHj/NWFNluYrjbz9j01CJ8yKQd2g4jFoOkINCTW2I5LEEyw== } - engines: { node: ">= 0.4" } + resolution: {integrity: sha512-nWJ91DjeOkej/TA8pXQ3myruKpKEYgqvpw9lz4OPHj/NWFNluYrjbz9j01CJ8yKQd2g4jFoOkINCTW2I5LEEyw==} + engines: {node: '>= 0.4'} undici-types@6.21.0: - resolution: - { integrity: sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ== } + resolution: {integrity: sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==} unrs-resolver@1.11.0: - resolution: - { integrity: sha512-uw3hCGO/RdAEAb4zgJ3C/v6KIAFFOtBoxR86b2Ejc5TnH7HrhTWJR2o0A9ullC3eWMegKQCw/arQ/JivywQzkg== } + resolution: {integrity: sha512-uw3hCGO/RdAEAb4zgJ3C/v6KIAFFOtBoxR86b2Ejc5TnH7HrhTWJR2o0A9ullC3eWMegKQCw/arQ/JivywQzkg==} uri-js@4.4.1: - resolution: - { integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg== } + resolution: {integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==} use-callback-ref@1.3.3: - resolution: - { integrity: sha512-jQL3lRnocaFtu3V00JToYz/4QkNWswxijDaCVNZRiRTO3HQDLsdu1ZtmIUvV4yPp+rvWm5j0y0TG/S61cuijTg== } - engines: { node: ">=10" } + resolution: {integrity: sha512-jQL3lRnocaFtu3V00JToYz/4QkNWswxijDaCVNZRiRTO3HQDLsdu1ZtmIUvV4yPp+rvWm5j0y0TG/S61cuijTg==} + engines: {node: '>=10'} peerDependencies: - "@types/react": "*" + '@types/react': '*' react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc peerDependenciesMeta: - "@types/react": + '@types/react': optional: true use-intl@4.3.4: - resolution: - { integrity: sha512-sHfiU0QeJ1rirNWRxvCyvlSh9+NczcOzRnPyMeo2rtHXhVnBsvMRjE+UG4eh3lRhCxrvcqei/I0lBxsc59on1w== } + resolution: {integrity: sha512-sHfiU0QeJ1rirNWRxvCyvlSh9+NczcOzRnPyMeo2rtHXhVnBsvMRjE+UG4eh3lRhCxrvcqei/I0lBxsc59on1w==} peerDependencies: react: ^17.0.0 || ^18.0.0 || >=19.0.0-rc <19.0.0 || ^19.0.0 use-sidecar@1.1.3: - resolution: - { integrity: sha512-Fedw0aZvkhynoPYlA5WXrMCAMm+nSWdZt6lzJQ7Ok8S6Q+VsHmHpRWndVRJ8Be0ZbkfPc5LRYH+5XrzXcEeLRQ== } - engines: { node: ">=10" } + resolution: {integrity: sha512-Fedw0aZvkhynoPYlA5WXrMCAMm+nSWdZt6lzJQ7Ok8S6Q+VsHmHpRWndVRJ8Be0ZbkfPc5LRYH+5XrzXcEeLRQ==} + engines: {node: '>=10'} peerDependencies: - "@types/react": "*" + '@types/react': '*' react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc peerDependenciesMeta: - "@types/react": + '@types/react': optional: true use-sync-external-store@1.5.0: - resolution: - { integrity: sha512-Rb46I4cGGVBmjamjphe8L/UnvJD+uPPtTkNvX5mZgqdbavhI4EbgIWJiIHXJ8bc/i9EQGPRh4DwEURJ552Do0A== } + resolution: {integrity: sha512-Rb46I4cGGVBmjamjphe8L/UnvJD+uPPtTkNvX5mZgqdbavhI4EbgIWJiIHXJ8bc/i9EQGPRh4DwEURJ552Do0A==} peerDependencies: react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 util-deprecate@1.0.2: - resolution: - { integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw== } + resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==} which-boxed-primitive@1.1.1: - resolution: - { integrity: sha512-TbX3mj8n0odCBFVlY8AxkqcHASw3L60jIuF8jFP78az3C2YhmGvqbHBpAjTRH2/xqYunrJ9g1jSyjCjpoWzIAA== } - engines: { node: ">= 0.4" } + resolution: {integrity: sha512-TbX3mj8n0odCBFVlY8AxkqcHASw3L60jIuF8jFP78az3C2YhmGvqbHBpAjTRH2/xqYunrJ9g1jSyjCjpoWzIAA==} + engines: {node: '>= 0.4'} which-builtin-type@1.2.1: - resolution: - { integrity: sha512-6iBczoX+kDQ7a3+YJBnh3T+KZRxM/iYNPXicqk66/Qfm1b93iu+yOImkg0zHbj5LNOcNv1TEADiZ0xa34B4q6Q== } - engines: { node: ">= 0.4" } + resolution: {integrity: sha512-6iBczoX+kDQ7a3+YJBnh3T+KZRxM/iYNPXicqk66/Qfm1b93iu+yOImkg0zHbj5LNOcNv1TEADiZ0xa34B4q6Q==} + engines: {node: '>= 0.4'} which-collection@1.0.2: - resolution: - { integrity: sha512-K4jVyjnBdgvc86Y6BkaLZEN933SwYOuBFkdmBu9ZfkcAbdVbpITnDmjvZ/aQjRXQrv5EPkTnD1s39GiiqbngCw== } - engines: { node: ">= 0.4" } + resolution: {integrity: sha512-K4jVyjnBdgvc86Y6BkaLZEN933SwYOuBFkdmBu9ZfkcAbdVbpITnDmjvZ/aQjRXQrv5EPkTnD1s39GiiqbngCw==} + engines: {node: '>= 0.4'} which-module@2.0.1: - resolution: - { integrity: sha512-iBdZ57RDvnOR9AGBhML2vFZf7h8vmBjhoaZqODJBFWHVtKkDmKuHai3cx5PgVMrX5YDNp27AofYbAwctSS+vhQ== } + resolution: {integrity: sha512-iBdZ57RDvnOR9AGBhML2vFZf7h8vmBjhoaZqODJBFWHVtKkDmKuHai3cx5PgVMrX5YDNp27AofYbAwctSS+vhQ==} which-typed-array@1.1.19: - resolution: - { integrity: sha512-rEvr90Bck4WZt9HHFC4DJMsjvu7x+r6bImz0/BrbWb7A2djJ8hnZMrWnHo9F8ssv0OMErasDhftrfROTyqSDrw== } - engines: { node: ">= 0.4" } + resolution: {integrity: sha512-rEvr90Bck4WZt9HHFC4DJMsjvu7x+r6bImz0/BrbWb7A2djJ8hnZMrWnHo9F8ssv0OMErasDhftrfROTyqSDrw==} + engines: {node: '>= 0.4'} which@2.0.2: - resolution: - { integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA== } - engines: { node: ">= 8" } + resolution: {integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==} + engines: {node: '>= 8'} hasBin: true + wildcard@1.1.2: + resolution: {integrity: sha512-DXukZJxpHA8LuotRwL0pP1+rS6CS7FF2qStDDE1C7DDg2rLud2PXRMuEDYIPhgEezwnlHNL4c+N6MfMTjCGTng==} + word-wrap@1.2.5: - resolution: - { integrity: sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA== } - engines: { node: ">=0.10.0" } + resolution: {integrity: sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==} + engines: {node: '>=0.10.0'} wrap-ansi@6.2.0: - resolution: - { integrity: sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA== } - engines: { node: ">=8" } + resolution: {integrity: sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==} + engines: {node: '>=8'} y18n@4.0.3: - resolution: - { integrity: sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ== } + resolution: {integrity: sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ==} yallist@5.0.0: - resolution: - { integrity: sha512-YgvUTfwqyc7UXVMrB+SImsVYSmTS8X/tSrtdNZMImM+n7+QTriRXyXim0mBrTXNeqzVF0KWGgHPeiyViFFrNDw== } - engines: { node: ">=18" } + resolution: {integrity: sha512-YgvUTfwqyc7UXVMrB+SImsVYSmTS8X/tSrtdNZMImM+n7+QTriRXyXim0mBrTXNeqzVF0KWGgHPeiyViFFrNDw==} + engines: {node: '>=18'} yargs-parser@18.1.3: - resolution: - { integrity: sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ== } - engines: { node: ">=6" } + resolution: {integrity: sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ==} + engines: {node: '>=6'} yargs@15.4.1: - resolution: - { integrity: sha512-aePbxDmcYW++PaqBsJ+HYUFwCdv4LVvdnhBy78E57PIor8/OVvhMrADFFEDh8DHDFRv/O9i3lPhsENjO7QX0+A== } - engines: { node: ">=8" } + resolution: {integrity: sha512-aePbxDmcYW++PaqBsJ+HYUFwCdv4LVvdnhBy78E57PIor8/OVvhMrADFFEDh8DHDFRv/O9i3lPhsENjO7QX0+A==} + engines: {node: '>=8'} yocto-queue@0.1.0: - resolution: - { integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q== } - engines: { node: ">=10" } + resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==} + engines: {node: '>=10'} zod@3.25.74: - resolution: - { integrity: sha512-J8poo92VuhKjNknViHRAIuuN6li/EwFbAC8OedzI8uxpEPGiXHGQu9wemIAioIpqgfB4SySaJhdk0mH5Y4ICBg== } + resolution: {integrity: sha512-J8poo92VuhKjNknViHRAIuuN6li/EwFbAC8OedzI8uxpEPGiXHGQu9wemIAioIpqgfB4SySaJhdk0mH5Y4ICBg==} zustand@5.0.6: - resolution: - { integrity: sha512-ihAqNeUVhe0MAD+X8M5UzqyZ9k3FFZLBTtqo6JLPwV53cbRB/mJwBI0PxcIgqhBBHlEs8G45OTDTMq3gNcLq3A== } - engines: { node: ">=12.20.0" } + resolution: {integrity: sha512-ihAqNeUVhe0MAD+X8M5UzqyZ9k3FFZLBTtqo6JLPwV53cbRB/mJwBI0PxcIgqhBBHlEs8G45OTDTMq3gNcLq3A==} + engines: {node: '>=12.20.0'} peerDependencies: - "@types/react": ">=18.0.0" - immer: ">=9.0.6" - react: ">=18.0.0" - use-sync-external-store: ">=1.2.0" + '@types/react': '>=18.0.0' + immer: '>=9.0.6' + react: '>=18.0.0' + use-sync-external-store: '>=1.2.0' peerDependenciesMeta: - "@types/react": + '@types/react': optional: true immer: optional: true @@ -3674,104 +3225,105 @@ packages: optional: true snapshots: - "@alloc/quick-lru@5.2.0": {} - "@ampproject/remapping@2.3.0": + '@alloc/quick-lru@5.2.0': {} + + '@ampproject/remapping@2.3.0': dependencies: - "@jridgewell/gen-mapping": 0.3.12 - "@jridgewell/trace-mapping": 0.3.29 + '@jridgewell/gen-mapping': 0.3.12 + '@jridgewell/trace-mapping': 0.3.29 - "@babel/code-frame@7.27.1": + '@babel/code-frame@7.27.1': dependencies: - "@babel/helper-validator-identifier": 7.27.1 + '@babel/helper-validator-identifier': 7.27.1 js-tokens: 4.0.0 picocolors: 1.1.1 - "@babel/generator@7.28.0": + '@babel/generator@7.28.0': dependencies: - "@babel/parser": 7.28.0 - "@babel/types": 7.28.0 - "@jridgewell/gen-mapping": 0.3.12 - "@jridgewell/trace-mapping": 0.3.29 + '@babel/parser': 7.28.0 + '@babel/types': 7.28.0 + '@jridgewell/gen-mapping': 0.3.12 + '@jridgewell/trace-mapping': 0.3.29 jsesc: 3.1.0 - "@babel/helper-globals@7.28.0": {} + '@babel/helper-globals@7.28.0': {} - "@babel/helper-string-parser@7.27.1": {} + '@babel/helper-string-parser@7.27.1': {} - "@babel/helper-validator-identifier@7.27.1": {} + '@babel/helper-validator-identifier@7.27.1': {} - "@babel/parser@7.28.0": + '@babel/parser@7.28.0': dependencies: - "@babel/types": 7.28.0 + '@babel/types': 7.28.0 - "@babel/runtime@7.27.6": {} + '@babel/runtime@7.27.6': {} - "@babel/template@7.27.2": + '@babel/template@7.27.2': dependencies: - "@babel/code-frame": 7.27.1 - "@babel/parser": 7.28.0 - "@babel/types": 7.28.0 + '@babel/code-frame': 7.27.1 + '@babel/parser': 7.28.0 + '@babel/types': 7.28.0 - "@babel/traverse@7.28.0": + '@babel/traverse@7.28.0': dependencies: - "@babel/code-frame": 7.27.1 - "@babel/generator": 7.28.0 - "@babel/helper-globals": 7.28.0 - "@babel/parser": 7.28.0 - "@babel/template": 7.27.2 - "@babel/types": 7.28.0 + '@babel/code-frame': 7.27.1 + '@babel/generator': 7.28.0 + '@babel/helper-globals': 7.28.0 + '@babel/parser': 7.28.0 + '@babel/template': 7.27.2 + '@babel/types': 7.28.0 debug: 4.4.1 transitivePeerDependencies: - supports-color - "@babel/types@7.28.0": + '@babel/types@7.28.0': dependencies: - "@babel/helper-string-parser": 7.27.1 - "@babel/helper-validator-identifier": 7.27.1 + '@babel/helper-string-parser': 7.27.1 + '@babel/helper-validator-identifier': 7.27.1 - "@emnapi/core@1.4.3": + '@emnapi/core@1.4.3': dependencies: - "@emnapi/wasi-threads": 1.0.2 + '@emnapi/wasi-threads': 1.0.2 tslib: 2.8.1 optional: true - "@emnapi/runtime@1.4.3": + '@emnapi/runtime@1.4.3': dependencies: tslib: 2.8.1 optional: true - "@emnapi/wasi-threads@1.0.2": + '@emnapi/wasi-threads@1.0.2': dependencies: tslib: 2.8.1 optional: true - "@eslint-community/eslint-utils@4.7.0(eslint@9.30.0(jiti@2.4.2))": + '@eslint-community/eslint-utils@4.7.0(eslint@9.30.0(jiti@2.4.2))': dependencies: eslint: 9.30.0(jiti@2.4.2) eslint-visitor-keys: 3.4.3 - "@eslint-community/regexpp@4.12.1": {} + '@eslint-community/regexpp@4.12.1': {} - "@eslint/config-array@0.21.0": + '@eslint/config-array@0.21.0': dependencies: - "@eslint/object-schema": 2.1.6 + '@eslint/object-schema': 2.1.6 debug: 4.4.1 minimatch: 3.1.2 transitivePeerDependencies: - supports-color - "@eslint/config-helpers@0.3.0": {} + '@eslint/config-helpers@0.3.0': {} - "@eslint/core@0.14.0": + '@eslint/core@0.14.0': dependencies: - "@types/json-schema": 7.0.15 + '@types/json-schema': 7.0.15 - "@eslint/core@0.15.1": + '@eslint/core@0.15.1': dependencies: - "@types/json-schema": 7.0.15 + '@types/json-schema': 7.0.15 - "@eslint/eslintrc@3.3.1": + '@eslint/eslintrc@3.3.1': dependencies: ajv: 6.12.6 debug: 4.4.1 @@ -3785,65 +3337,65 @@ snapshots: transitivePeerDependencies: - supports-color - "@eslint/js@9.30.0": {} + '@eslint/js@9.30.0': {} - "@eslint/object-schema@2.1.6": {} + '@eslint/object-schema@2.1.6': {} - "@eslint/plugin-kit@0.3.3": + '@eslint/plugin-kit@0.3.3': dependencies: - "@eslint/core": 0.15.1 + '@eslint/core': 0.15.1 levn: 0.4.1 - "@floating-ui/core@1.7.2": + '@floating-ui/core@1.7.2': dependencies: - "@floating-ui/utils": 0.2.10 + '@floating-ui/utils': 0.2.10 - "@floating-ui/dom@1.7.2": + '@floating-ui/dom@1.7.2': dependencies: - "@floating-ui/core": 1.7.2 - "@floating-ui/utils": 0.2.10 + '@floating-ui/core': 1.7.2 + '@floating-ui/utils': 0.2.10 - "@floating-ui/react-dom@2.1.4(react-dom@19.1.0(react@19.1.0))(react@19.1.0)": + '@floating-ui/react-dom@2.1.4(react-dom@19.1.0(react@19.1.0))(react@19.1.0)': dependencies: - "@floating-ui/dom": 1.7.2 + '@floating-ui/dom': 1.7.2 react: 19.1.0 react-dom: 19.1.0(react@19.1.0) - "@floating-ui/utils@0.2.10": {} + '@floating-ui/utils@0.2.10': {} - "@formatjs/ecma402-abstract@2.3.4": + '@formatjs/ecma402-abstract@2.3.4': dependencies: - "@formatjs/fast-memoize": 2.2.7 - "@formatjs/intl-localematcher": 0.6.1 + '@formatjs/fast-memoize': 2.2.7 + '@formatjs/intl-localematcher': 0.6.1 decimal.js: 10.6.0 tslib: 2.8.1 - "@formatjs/fast-memoize@2.2.7": + '@formatjs/fast-memoize@2.2.7': dependencies: tslib: 2.8.1 - "@formatjs/icu-messageformat-parser@2.11.2": + '@formatjs/icu-messageformat-parser@2.11.2': dependencies: - "@formatjs/ecma402-abstract": 2.3.4 - "@formatjs/icu-skeleton-parser": 1.8.14 + '@formatjs/ecma402-abstract': 2.3.4 + '@formatjs/icu-skeleton-parser': 1.8.14 tslib: 2.8.1 - "@formatjs/icu-skeleton-parser@1.8.14": + '@formatjs/icu-skeleton-parser@1.8.14': dependencies: - "@formatjs/ecma402-abstract": 2.3.4 + '@formatjs/ecma402-abstract': 2.3.4 tslib: 2.8.1 - "@formatjs/intl-localematcher@0.5.10": + '@formatjs/intl-localematcher@0.5.10': dependencies: tslib: 2.8.1 - "@formatjs/intl-localematcher@0.6.1": + '@formatjs/intl-localematcher@0.6.1': dependencies: tslib: 2.8.1 - "@hello-pangea/dnd@18.0.1(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)": + '@hello-pangea/dnd@18.0.1(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)': dependencies: - "@babel/runtime": 7.27.6 + '@babel/runtime': 7.27.6 css-box-model: 1.2.1 raf-schd: 4.0.3 react: 19.1.0 @@ -3851,689 +3403,783 @@ snapshots: react-redux: 9.2.0(@types/react@19.1.8)(react@19.1.0)(redux@5.0.1) redux: 5.0.1 transitivePeerDependencies: - - "@types/react" + - '@types/react' - "@hookform/resolvers@5.1.1(react-hook-form@7.60.0(react@19.1.0))": + '@hookform/resolvers@5.1.1(react-hook-form@7.60.0(react@19.1.0))': dependencies: - "@standard-schema/utils": 0.3.0 + '@standard-schema/utils': 0.3.0 react-hook-form: 7.60.0(react@19.1.0) - "@humanfs/core@0.19.1": {} + '@humanfs/core@0.19.1': {} - "@humanfs/node@0.16.6": + '@humanfs/node@0.16.6': dependencies: - "@humanfs/core": 0.19.1 - "@humanwhocodes/retry": 0.3.1 + '@humanfs/core': 0.19.1 + '@humanwhocodes/retry': 0.3.1 - "@humanwhocodes/module-importer@1.0.1": {} + '@humanwhocodes/module-importer@1.0.1': {} - "@humanwhocodes/retry@0.3.1": {} + '@humanwhocodes/retry@0.3.1': {} - "@humanwhocodes/retry@0.4.3": {} + '@humanwhocodes/retry@0.4.3': {} - "@ianvs/prettier-plugin-sort-imports@4.4.2(prettier@3.6.2)": + '@ianvs/prettier-plugin-sort-imports@4.4.2(prettier@3.6.2)': dependencies: - "@babel/generator": 7.28.0 - "@babel/parser": 7.28.0 - "@babel/traverse": 7.28.0 - "@babel/types": 7.28.0 + '@babel/generator': 7.28.0 + '@babel/parser': 7.28.0 + '@babel/traverse': 7.28.0 + '@babel/types': 7.28.0 prettier: 3.6.2 semver: 7.7.2 transitivePeerDependencies: - supports-color - "@img/sharp-darwin-arm64@0.34.2": + '@img/sharp-darwin-arm64@0.34.2': optionalDependencies: - "@img/sharp-libvips-darwin-arm64": 1.1.0 + '@img/sharp-libvips-darwin-arm64': 1.1.0 optional: true - "@img/sharp-darwin-x64@0.34.2": + '@img/sharp-darwin-x64@0.34.2': optionalDependencies: - "@img/sharp-libvips-darwin-x64": 1.1.0 + '@img/sharp-libvips-darwin-x64': 1.1.0 optional: true - "@img/sharp-libvips-darwin-arm64@1.1.0": + '@img/sharp-libvips-darwin-arm64@1.1.0': optional: true - "@img/sharp-libvips-darwin-x64@1.1.0": + '@img/sharp-libvips-darwin-x64@1.1.0': optional: true - "@img/sharp-libvips-linux-arm64@1.1.0": + '@img/sharp-libvips-linux-arm64@1.1.0': optional: true - "@img/sharp-libvips-linux-arm@1.1.0": + '@img/sharp-libvips-linux-arm@1.1.0': optional: true - "@img/sharp-libvips-linux-ppc64@1.1.0": + '@img/sharp-libvips-linux-ppc64@1.1.0': optional: true - "@img/sharp-libvips-linux-s390x@1.1.0": + '@img/sharp-libvips-linux-s390x@1.1.0': optional: true - "@img/sharp-libvips-linux-x64@1.1.0": + '@img/sharp-libvips-linux-x64@1.1.0': optional: true - "@img/sharp-libvips-linuxmusl-arm64@1.1.0": + '@img/sharp-libvips-linuxmusl-arm64@1.1.0': optional: true - "@img/sharp-libvips-linuxmusl-x64@1.1.0": + '@img/sharp-libvips-linuxmusl-x64@1.1.0': optional: true - "@img/sharp-linux-arm64@0.34.2": + '@img/sharp-linux-arm64@0.34.2': optionalDependencies: - "@img/sharp-libvips-linux-arm64": 1.1.0 + '@img/sharp-libvips-linux-arm64': 1.1.0 optional: true - "@img/sharp-linux-arm@0.34.2": + '@img/sharp-linux-arm@0.34.2': optionalDependencies: - "@img/sharp-libvips-linux-arm": 1.1.0 + '@img/sharp-libvips-linux-arm': 1.1.0 optional: true - "@img/sharp-linux-s390x@0.34.2": + '@img/sharp-linux-s390x@0.34.2': optionalDependencies: - "@img/sharp-libvips-linux-s390x": 1.1.0 + '@img/sharp-libvips-linux-s390x': 1.1.0 optional: true - "@img/sharp-linux-x64@0.34.2": + '@img/sharp-linux-x64@0.34.2': optionalDependencies: - "@img/sharp-libvips-linux-x64": 1.1.0 + '@img/sharp-libvips-linux-x64': 1.1.0 optional: true - "@img/sharp-linuxmusl-arm64@0.34.2": + '@img/sharp-linuxmusl-arm64@0.34.2': optionalDependencies: - "@img/sharp-libvips-linuxmusl-arm64": 1.1.0 + '@img/sharp-libvips-linuxmusl-arm64': 1.1.0 optional: true - "@img/sharp-linuxmusl-x64@0.34.2": + '@img/sharp-linuxmusl-x64@0.34.2': optionalDependencies: - "@img/sharp-libvips-linuxmusl-x64": 1.1.0 + '@img/sharp-libvips-linuxmusl-x64': 1.1.0 optional: true - "@img/sharp-wasm32@0.34.2": + '@img/sharp-wasm32@0.34.2': dependencies: - "@emnapi/runtime": 1.4.3 + '@emnapi/runtime': 1.4.3 optional: true - "@img/sharp-win32-arm64@0.34.2": + '@img/sharp-win32-arm64@0.34.2': optional: true - "@img/sharp-win32-ia32@0.34.2": + '@img/sharp-win32-ia32@0.34.2': optional: true - "@img/sharp-win32-x64@0.34.2": + '@img/sharp-win32-x64@0.34.2': optional: true - "@isaacs/fs-minipass@4.0.1": + '@isaacs/fs-minipass@4.0.1': dependencies: minipass: 7.1.2 - "@jridgewell/gen-mapping@0.3.12": + '@jridgewell/gen-mapping@0.3.12': dependencies: - "@jridgewell/sourcemap-codec": 1.5.4 - "@jridgewell/trace-mapping": 0.3.29 + '@jridgewell/sourcemap-codec': 1.5.4 + '@jridgewell/trace-mapping': 0.3.29 - "@jridgewell/resolve-uri@3.1.2": {} + '@jridgewell/resolve-uri@3.1.2': {} - "@jridgewell/sourcemap-codec@1.5.4": {} + '@jridgewell/sourcemap-codec@1.5.4': {} - "@jridgewell/trace-mapping@0.3.29": + '@jridgewell/trace-mapping@0.3.29': dependencies: - "@jridgewell/resolve-uri": 3.1.2 - "@jridgewell/sourcemap-codec": 1.5.4 + '@jridgewell/resolve-uri': 3.1.2 + '@jridgewell/sourcemap-codec': 1.5.4 - "@napi-rs/wasm-runtime@0.2.11": + '@napi-rs/wasm-runtime@0.2.11': dependencies: - "@emnapi/core": 1.4.3 - "@emnapi/runtime": 1.4.3 - "@tybys/wasm-util": 0.9.0 + '@emnapi/core': 1.4.3 + '@emnapi/runtime': 1.4.3 + '@tybys/wasm-util': 0.9.0 optional: true - "@next/env@15.3.4": {} + '@next/env@15.3.4': {} - "@next/eslint-plugin-next@15.3.4": + '@next/eslint-plugin-next@15.3.4': dependencies: fast-glob: 3.3.1 - "@next/swc-darwin-arm64@15.3.4": + '@next/swc-darwin-arm64@15.3.4': optional: true - "@next/swc-darwin-x64@15.3.4": + '@next/swc-darwin-x64@15.3.4': optional: true - "@next/swc-linux-arm64-gnu@15.3.4": + '@next/swc-linux-arm64-gnu@15.3.4': optional: true - "@next/swc-linux-arm64-musl@15.3.4": + '@next/swc-linux-arm64-musl@15.3.4': optional: true - "@next/swc-linux-x64-gnu@15.3.4": + '@next/swc-linux-x64-gnu@15.3.4': optional: true - "@next/swc-linux-x64-musl@15.3.4": + '@next/swc-linux-x64-musl@15.3.4': optional: true - "@next/swc-win32-arm64-msvc@15.3.4": + '@next/swc-win32-arm64-msvc@15.3.4': optional: true - "@next/swc-win32-x64-msvc@15.3.4": + '@next/swc-win32-x64-msvc@15.3.4': optional: true - "@nodelib/fs.scandir@2.1.5": + '@nodelib/fs.scandir@2.1.5': dependencies: - "@nodelib/fs.stat": 2.0.5 + '@nodelib/fs.stat': 2.0.5 run-parallel: 1.2.0 - "@nodelib/fs.stat@2.0.5": {} + '@nodelib/fs.stat@2.0.5': {} - "@nodelib/fs.walk@1.2.8": + '@nodelib/fs.walk@1.2.8': dependencies: - "@nodelib/fs.scandir": 2.1.5 + '@nodelib/fs.scandir': 2.1.5 fastq: 1.19.1 - "@nolyfill/is-core-module@1.0.39": {} + '@nolyfill/is-core-module@1.0.39': {} - "@pkgr/core@0.2.7": {} + '@pkgr/core@0.2.7': {} - "@radix-ui/number@1.1.1": {} + '@radix-ui/number@1.1.1': {} - "@radix-ui/primitive@1.1.2": {} + '@radix-ui/primitive@1.1.2': {} - "@radix-ui/primitive@1.1.3": {} + '@radix-ui/primitive@1.1.3': {} - "@radix-ui/react-arrow@1.1.7(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)": + '@radix-ui/react-arrow@1.1.7(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)': dependencies: - "@radix-ui/react-primitive": 2.1.3(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) react: 19.1.0 react-dom: 19.1.0(react@19.1.0) optionalDependencies: - "@types/react": 19.1.8 - "@types/react-dom": 19.1.6(@types/react@19.1.8) + '@types/react': 19.1.8 + '@types/react-dom': 19.1.6(@types/react@19.1.8) - "@radix-ui/react-aspect-ratio@1.1.7(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)": + '@radix-ui/react-aspect-ratio@1.1.7(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)': dependencies: - "@radix-ui/react-primitive": 2.1.3(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) react: 19.1.0 react-dom: 19.1.0(react@19.1.0) optionalDependencies: - "@types/react": 19.1.8 - "@types/react-dom": 19.1.6(@types/react@19.1.8) + '@types/react': 19.1.8 + '@types/react-dom': 19.1.6(@types/react@19.1.8) - "@radix-ui/react-avatar@1.1.10(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)": + '@radix-ui/react-avatar@1.1.10(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)': dependencies: - "@radix-ui/react-context": 1.1.2(@types/react@19.1.8)(react@19.1.0) - "@radix-ui/react-primitive": 2.1.3(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) - "@radix-ui/react-use-callback-ref": 1.1.1(@types/react@19.1.8)(react@19.1.0) - "@radix-ui/react-use-is-hydrated": 0.1.0(@types/react@19.1.8)(react@19.1.0) - "@radix-ui/react-use-layout-effect": 1.1.1(@types/react@19.1.8)(react@19.1.0) + '@radix-ui/react-context': 1.1.2(@types/react@19.1.8)(react@19.1.0) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@19.1.8)(react@19.1.0) + '@radix-ui/react-use-is-hydrated': 0.1.0(@types/react@19.1.8)(react@19.1.0) + '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.1.8)(react@19.1.0) react: 19.1.0 react-dom: 19.1.0(react@19.1.0) optionalDependencies: - "@types/react": 19.1.8 - "@types/react-dom": 19.1.6(@types/react@19.1.8) - - "@radix-ui/react-checkbox@1.3.2(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)": - dependencies: - "@radix-ui/primitive": 1.1.2 - "@radix-ui/react-compose-refs": 1.1.2(@types/react@19.1.8)(react@19.1.0) - "@radix-ui/react-context": 1.1.2(@types/react@19.1.8)(react@19.1.0) - "@radix-ui/react-presence": 1.1.4(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) - "@radix-ui/react-primitive": 2.1.3(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) - "@radix-ui/react-use-controllable-state": 1.2.2(@types/react@19.1.8)(react@19.1.0) - "@radix-ui/react-use-previous": 1.1.1(@types/react@19.1.8)(react@19.1.0) - "@radix-ui/react-use-size": 1.1.1(@types/react@19.1.8)(react@19.1.0) + '@types/react': 19.1.8 + '@types/react-dom': 19.1.6(@types/react@19.1.8) + + '@radix-ui/react-checkbox@1.3.2(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)': + dependencies: + '@radix-ui/primitive': 1.1.2 + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.1.8)(react@19.1.0) + '@radix-ui/react-context': 1.1.2(@types/react@19.1.8)(react@19.1.0) + '@radix-ui/react-presence': 1.1.4(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.1.8)(react@19.1.0) + '@radix-ui/react-use-previous': 1.1.1(@types/react@19.1.8)(react@19.1.0) + '@radix-ui/react-use-size': 1.1.1(@types/react@19.1.8)(react@19.1.0) react: 19.1.0 react-dom: 19.1.0(react@19.1.0) optionalDependencies: - "@types/react": 19.1.8 - "@types/react-dom": 19.1.6(@types/react@19.1.8) - - "@radix-ui/react-collapsible@1.1.12(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)": - dependencies: - "@radix-ui/primitive": 1.1.3 - "@radix-ui/react-compose-refs": 1.1.2(@types/react@19.1.8)(react@19.1.0) - "@radix-ui/react-context": 1.1.2(@types/react@19.1.8)(react@19.1.0) - "@radix-ui/react-id": 1.1.1(@types/react@19.1.8)(react@19.1.0) - "@radix-ui/react-presence": 1.1.5(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) - "@radix-ui/react-primitive": 2.1.3(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) - "@radix-ui/react-use-controllable-state": 1.2.2(@types/react@19.1.8)(react@19.1.0) - "@radix-ui/react-use-layout-effect": 1.1.1(@types/react@19.1.8)(react@19.1.0) + '@types/react': 19.1.8 + '@types/react-dom': 19.1.6(@types/react@19.1.8) + + '@radix-ui/react-collapsible@1.1.12(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)': + dependencies: + '@radix-ui/primitive': 1.1.3 + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.1.8)(react@19.1.0) + '@radix-ui/react-context': 1.1.2(@types/react@19.1.8)(react@19.1.0) + '@radix-ui/react-id': 1.1.1(@types/react@19.1.8)(react@19.1.0) + '@radix-ui/react-presence': 1.1.5(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.1.8)(react@19.1.0) + '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.1.8)(react@19.1.0) react: 19.1.0 react-dom: 19.1.0(react@19.1.0) optionalDependencies: - "@types/react": 19.1.8 - "@types/react-dom": 19.1.6(@types/react@19.1.8) + '@types/react': 19.1.8 + '@types/react-dom': 19.1.6(@types/react@19.1.8) - "@radix-ui/react-collection@1.1.7(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)": + '@radix-ui/react-collection@1.1.7(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)': dependencies: - "@radix-ui/react-compose-refs": 1.1.2(@types/react@19.1.8)(react@19.1.0) - "@radix-ui/react-context": 1.1.2(@types/react@19.1.8)(react@19.1.0) - "@radix-ui/react-primitive": 2.1.3(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) - "@radix-ui/react-slot": 1.2.3(@types/react@19.1.8)(react@19.1.0) + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.1.8)(react@19.1.0) + '@radix-ui/react-context': 1.1.2(@types/react@19.1.8)(react@19.1.0) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + '@radix-ui/react-slot': 1.2.3(@types/react@19.1.8)(react@19.1.0) react: 19.1.0 react-dom: 19.1.0(react@19.1.0) optionalDependencies: - "@types/react": 19.1.8 - "@types/react-dom": 19.1.6(@types/react@19.1.8) + '@types/react': 19.1.8 + '@types/react-dom': 19.1.6(@types/react@19.1.8) + + '@radix-ui/react-compose-refs@1.1.2(@types/react@19.1.8)(react@19.1.0)': + dependencies: + react: 19.1.0 + optionalDependencies: + '@types/react': 19.1.8 - "@radix-ui/react-compose-refs@1.1.2(@types/react@19.1.8)(react@19.1.0)": + '@radix-ui/react-context-menu@2.2.16(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)': dependencies: + '@radix-ui/primitive': 1.1.3 + '@radix-ui/react-context': 1.1.2(@types/react@19.1.8)(react@19.1.0) + '@radix-ui/react-menu': 2.1.16(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@19.1.8)(react@19.1.0) + '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.1.8)(react@19.1.0) react: 19.1.0 + react-dom: 19.1.0(react@19.1.0) optionalDependencies: - "@types/react": 19.1.8 + '@types/react': 19.1.8 + '@types/react-dom': 19.1.6(@types/react@19.1.8) - "@radix-ui/react-context@1.1.2(@types/react@19.1.8)(react@19.1.0)": + '@radix-ui/react-context@1.1.2(@types/react@19.1.8)(react@19.1.0)': dependencies: react: 19.1.0 optionalDependencies: - "@types/react": 19.1.8 - - "@radix-ui/react-dialog@1.1.14(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)": - dependencies: - "@radix-ui/primitive": 1.1.2 - "@radix-ui/react-compose-refs": 1.1.2(@types/react@19.1.8)(react@19.1.0) - "@radix-ui/react-context": 1.1.2(@types/react@19.1.8)(react@19.1.0) - "@radix-ui/react-dismissable-layer": 1.1.10(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) - "@radix-ui/react-focus-guards": 1.1.2(@types/react@19.1.8)(react@19.1.0) - "@radix-ui/react-focus-scope": 1.1.7(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) - "@radix-ui/react-id": 1.1.1(@types/react@19.1.8)(react@19.1.0) - "@radix-ui/react-portal": 1.1.9(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) - "@radix-ui/react-presence": 1.1.4(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) - "@radix-ui/react-primitive": 2.1.3(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) - "@radix-ui/react-slot": 1.2.3(@types/react@19.1.8)(react@19.1.0) - "@radix-ui/react-use-controllable-state": 1.2.2(@types/react@19.1.8)(react@19.1.0) + '@types/react': 19.1.8 + + '@radix-ui/react-dialog@1.1.14(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)': + dependencies: + '@radix-ui/primitive': 1.1.2 + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.1.8)(react@19.1.0) + '@radix-ui/react-context': 1.1.2(@types/react@19.1.8)(react@19.1.0) + '@radix-ui/react-dismissable-layer': 1.1.10(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + '@radix-ui/react-focus-guards': 1.1.2(@types/react@19.1.8)(react@19.1.0) + '@radix-ui/react-focus-scope': 1.1.7(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + '@radix-ui/react-id': 1.1.1(@types/react@19.1.8)(react@19.1.0) + '@radix-ui/react-portal': 1.1.9(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + '@radix-ui/react-presence': 1.1.4(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + '@radix-ui/react-slot': 1.2.3(@types/react@19.1.8)(react@19.1.0) + '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.1.8)(react@19.1.0) aria-hidden: 1.2.6 react: 19.1.0 react-dom: 19.1.0(react@19.1.0) react-remove-scroll: 2.7.1(@types/react@19.1.8)(react@19.1.0) optionalDependencies: - "@types/react": 19.1.8 - "@types/react-dom": 19.1.6(@types/react@19.1.8) + '@types/react': 19.1.8 + '@types/react-dom': 19.1.6(@types/react@19.1.8) - "@radix-ui/react-direction@1.1.1(@types/react@19.1.8)(react@19.1.0)": + '@radix-ui/react-direction@1.1.1(@types/react@19.1.8)(react@19.1.0)': dependencies: react: 19.1.0 optionalDependencies: - "@types/react": 19.1.8 + '@types/react': 19.1.8 + + '@radix-ui/react-dismissable-layer@1.1.10(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)': + dependencies: + '@radix-ui/primitive': 1.1.2 + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.1.8)(react@19.1.0) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@19.1.8)(react@19.1.0) + '@radix-ui/react-use-escape-keydown': 1.1.1(@types/react@19.1.8)(react@19.1.0) + react: 19.1.0 + react-dom: 19.1.0(react@19.1.0) + optionalDependencies: + '@types/react': 19.1.8 + '@types/react-dom': 19.1.6(@types/react@19.1.8) - "@radix-ui/react-dismissable-layer@1.1.10(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)": + '@radix-ui/react-dismissable-layer@1.1.11(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)': dependencies: - "@radix-ui/primitive": 1.1.2 - "@radix-ui/react-compose-refs": 1.1.2(@types/react@19.1.8)(react@19.1.0) - "@radix-ui/react-primitive": 2.1.3(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) - "@radix-ui/react-use-callback-ref": 1.1.1(@types/react@19.1.8)(react@19.1.0) - "@radix-ui/react-use-escape-keydown": 1.1.1(@types/react@19.1.8)(react@19.1.0) + '@radix-ui/primitive': 1.1.3 + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.1.8)(react@19.1.0) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@19.1.8)(react@19.1.0) + '@radix-ui/react-use-escape-keydown': 1.1.1(@types/react@19.1.8)(react@19.1.0) react: 19.1.0 react-dom: 19.1.0(react@19.1.0) optionalDependencies: - "@types/react": 19.1.8 - "@types/react-dom": 19.1.6(@types/react@19.1.8) - - "@radix-ui/react-dropdown-menu@2.1.15(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)": - dependencies: - "@radix-ui/primitive": 1.1.2 - "@radix-ui/react-compose-refs": 1.1.2(@types/react@19.1.8)(react@19.1.0) - "@radix-ui/react-context": 1.1.2(@types/react@19.1.8)(react@19.1.0) - "@radix-ui/react-id": 1.1.1(@types/react@19.1.8)(react@19.1.0) - "@radix-ui/react-menu": 2.1.15(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) - "@radix-ui/react-primitive": 2.1.3(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) - "@radix-ui/react-use-controllable-state": 1.2.2(@types/react@19.1.8)(react@19.1.0) + '@types/react': 19.1.8 + '@types/react-dom': 19.1.6(@types/react@19.1.8) + + '@radix-ui/react-dropdown-menu@2.1.15(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)': + dependencies: + '@radix-ui/primitive': 1.1.2 + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.1.8)(react@19.1.0) + '@radix-ui/react-context': 1.1.2(@types/react@19.1.8)(react@19.1.0) + '@radix-ui/react-id': 1.1.1(@types/react@19.1.8)(react@19.1.0) + '@radix-ui/react-menu': 2.1.15(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.1.8)(react@19.1.0) react: 19.1.0 react-dom: 19.1.0(react@19.1.0) optionalDependencies: - "@types/react": 19.1.8 - "@types/react-dom": 19.1.6(@types/react@19.1.8) + '@types/react': 19.1.8 + '@types/react-dom': 19.1.6(@types/react@19.1.8) - "@radix-ui/react-focus-guards@1.1.2(@types/react@19.1.8)(react@19.1.0)": + '@radix-ui/react-focus-guards@1.1.2(@types/react@19.1.8)(react@19.1.0)': dependencies: react: 19.1.0 optionalDependencies: - "@types/react": 19.1.8 + '@types/react': 19.1.8 - "@radix-ui/react-focus-scope@1.1.7(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)": + '@radix-ui/react-focus-guards@1.1.3(@types/react@19.1.8)(react@19.1.0)': dependencies: - "@radix-ui/react-compose-refs": 1.1.2(@types/react@19.1.8)(react@19.1.0) - "@radix-ui/react-primitive": 2.1.3(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) - "@radix-ui/react-use-callback-ref": 1.1.1(@types/react@19.1.8)(react@19.1.0) + react: 19.1.0 + optionalDependencies: + '@types/react': 19.1.8 + + '@radix-ui/react-focus-scope@1.1.7(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)': + dependencies: + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.1.8)(react@19.1.0) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@19.1.8)(react@19.1.0) react: 19.1.0 react-dom: 19.1.0(react@19.1.0) optionalDependencies: - "@types/react": 19.1.8 - "@types/react-dom": 19.1.6(@types/react@19.1.8) + '@types/react': 19.1.8 + '@types/react-dom': 19.1.6(@types/react@19.1.8) - "@radix-ui/react-id@1.1.1(@types/react@19.1.8)(react@19.1.0)": + '@radix-ui/react-id@1.1.1(@types/react@19.1.8)(react@19.1.0)': dependencies: - "@radix-ui/react-use-layout-effect": 1.1.1(@types/react@19.1.8)(react@19.1.0) + '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.1.8)(react@19.1.0) react: 19.1.0 optionalDependencies: - "@types/react": 19.1.8 + '@types/react': 19.1.8 - "@radix-ui/react-label@2.1.7(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)": + '@radix-ui/react-label@2.1.7(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)': dependencies: - "@radix-ui/react-primitive": 2.1.3(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) react: 19.1.0 react-dom: 19.1.0(react@19.1.0) optionalDependencies: - "@types/react": 19.1.8 - "@types/react-dom": 19.1.6(@types/react@19.1.8) - - "@radix-ui/react-menu@2.1.15(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)": - dependencies: - "@radix-ui/primitive": 1.1.2 - "@radix-ui/react-collection": 1.1.7(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) - "@radix-ui/react-compose-refs": 1.1.2(@types/react@19.1.8)(react@19.1.0) - "@radix-ui/react-context": 1.1.2(@types/react@19.1.8)(react@19.1.0) - "@radix-ui/react-direction": 1.1.1(@types/react@19.1.8)(react@19.1.0) - "@radix-ui/react-dismissable-layer": 1.1.10(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) - "@radix-ui/react-focus-guards": 1.1.2(@types/react@19.1.8)(react@19.1.0) - "@radix-ui/react-focus-scope": 1.1.7(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) - "@radix-ui/react-id": 1.1.1(@types/react@19.1.8)(react@19.1.0) - "@radix-ui/react-popper": 1.2.7(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) - "@radix-ui/react-portal": 1.1.9(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) - "@radix-ui/react-presence": 1.1.4(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) - "@radix-ui/react-primitive": 2.1.3(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) - "@radix-ui/react-roving-focus": 1.1.10(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) - "@radix-ui/react-slot": 1.2.3(@types/react@19.1.8)(react@19.1.0) - "@radix-ui/react-use-callback-ref": 1.1.1(@types/react@19.1.8)(react@19.1.0) + '@types/react': 19.1.8 + '@types/react-dom': 19.1.6(@types/react@19.1.8) + + '@radix-ui/react-menu@2.1.15(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)': + dependencies: + '@radix-ui/primitive': 1.1.2 + '@radix-ui/react-collection': 1.1.7(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.1.8)(react@19.1.0) + '@radix-ui/react-context': 1.1.2(@types/react@19.1.8)(react@19.1.0) + '@radix-ui/react-direction': 1.1.1(@types/react@19.1.8)(react@19.1.0) + '@radix-ui/react-dismissable-layer': 1.1.10(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + '@radix-ui/react-focus-guards': 1.1.2(@types/react@19.1.8)(react@19.1.0) + '@radix-ui/react-focus-scope': 1.1.7(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + '@radix-ui/react-id': 1.1.1(@types/react@19.1.8)(react@19.1.0) + '@radix-ui/react-popper': 1.2.7(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + '@radix-ui/react-portal': 1.1.9(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + '@radix-ui/react-presence': 1.1.4(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + '@radix-ui/react-roving-focus': 1.1.10(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + '@radix-ui/react-slot': 1.2.3(@types/react@19.1.8)(react@19.1.0) + '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@19.1.8)(react@19.1.0) + aria-hidden: 1.2.6 + react: 19.1.0 + react-dom: 19.1.0(react@19.1.0) + react-remove-scroll: 2.7.1(@types/react@19.1.8)(react@19.1.0) + optionalDependencies: + '@types/react': 19.1.8 + '@types/react-dom': 19.1.6(@types/react@19.1.8) + + '@radix-ui/react-menu@2.1.16(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)': + dependencies: + '@radix-ui/primitive': 1.1.3 + '@radix-ui/react-collection': 1.1.7(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.1.8)(react@19.1.0) + '@radix-ui/react-context': 1.1.2(@types/react@19.1.8)(react@19.1.0) + '@radix-ui/react-direction': 1.1.1(@types/react@19.1.8)(react@19.1.0) + '@radix-ui/react-dismissable-layer': 1.1.11(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + '@radix-ui/react-focus-guards': 1.1.3(@types/react@19.1.8)(react@19.1.0) + '@radix-ui/react-focus-scope': 1.1.7(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + '@radix-ui/react-id': 1.1.1(@types/react@19.1.8)(react@19.1.0) + '@radix-ui/react-popper': 1.2.8(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + '@radix-ui/react-portal': 1.1.9(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + '@radix-ui/react-presence': 1.1.5(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + '@radix-ui/react-roving-focus': 1.1.11(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + '@radix-ui/react-slot': 1.2.3(@types/react@19.1.8)(react@19.1.0) + '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@19.1.8)(react@19.1.0) aria-hidden: 1.2.6 react: 19.1.0 react-dom: 19.1.0(react@19.1.0) react-remove-scroll: 2.7.1(@types/react@19.1.8)(react@19.1.0) optionalDependencies: - "@types/react": 19.1.8 - "@types/react-dom": 19.1.6(@types/react@19.1.8) - - "@radix-ui/react-popper@1.2.7(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)": - dependencies: - "@floating-ui/react-dom": 2.1.4(react-dom@19.1.0(react@19.1.0))(react@19.1.0) - "@radix-ui/react-arrow": 1.1.7(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) - "@radix-ui/react-compose-refs": 1.1.2(@types/react@19.1.8)(react@19.1.0) - "@radix-ui/react-context": 1.1.2(@types/react@19.1.8)(react@19.1.0) - "@radix-ui/react-primitive": 2.1.3(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) - "@radix-ui/react-use-callback-ref": 1.1.1(@types/react@19.1.8)(react@19.1.0) - "@radix-ui/react-use-layout-effect": 1.1.1(@types/react@19.1.8)(react@19.1.0) - "@radix-ui/react-use-rect": 1.1.1(@types/react@19.1.8)(react@19.1.0) - "@radix-ui/react-use-size": 1.1.1(@types/react@19.1.8)(react@19.1.0) - "@radix-ui/rect": 1.1.1 + '@types/react': 19.1.8 + '@types/react-dom': 19.1.6(@types/react@19.1.8) + + '@radix-ui/react-popper@1.2.7(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)': + dependencies: + '@floating-ui/react-dom': 2.1.4(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + '@radix-ui/react-arrow': 1.1.7(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.1.8)(react@19.1.0) + '@radix-ui/react-context': 1.1.2(@types/react@19.1.8)(react@19.1.0) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@19.1.8)(react@19.1.0) + '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.1.8)(react@19.1.0) + '@radix-ui/react-use-rect': 1.1.1(@types/react@19.1.8)(react@19.1.0) + '@radix-ui/react-use-size': 1.1.1(@types/react@19.1.8)(react@19.1.0) + '@radix-ui/rect': 1.1.1 react: 19.1.0 react-dom: 19.1.0(react@19.1.0) optionalDependencies: - "@types/react": 19.1.8 - "@types/react-dom": 19.1.6(@types/react@19.1.8) + '@types/react': 19.1.8 + '@types/react-dom': 19.1.6(@types/react@19.1.8) + + '@radix-ui/react-popper@1.2.8(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)': + dependencies: + '@floating-ui/react-dom': 2.1.4(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + '@radix-ui/react-arrow': 1.1.7(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.1.8)(react@19.1.0) + '@radix-ui/react-context': 1.1.2(@types/react@19.1.8)(react@19.1.0) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@19.1.8)(react@19.1.0) + '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.1.8)(react@19.1.0) + '@radix-ui/react-use-rect': 1.1.1(@types/react@19.1.8)(react@19.1.0) + '@radix-ui/react-use-size': 1.1.1(@types/react@19.1.8)(react@19.1.0) + '@radix-ui/rect': 1.1.1 + react: 19.1.0 + react-dom: 19.1.0(react@19.1.0) + optionalDependencies: + '@types/react': 19.1.8 + '@types/react-dom': 19.1.6(@types/react@19.1.8) - "@radix-ui/react-portal@1.1.9(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)": + '@radix-ui/react-portal@1.1.9(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)': dependencies: - "@radix-ui/react-primitive": 2.1.3(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) - "@radix-ui/react-use-layout-effect": 1.1.1(@types/react@19.1.8)(react@19.1.0) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.1.8)(react@19.1.0) react: 19.1.0 react-dom: 19.1.0(react@19.1.0) optionalDependencies: - "@types/react": 19.1.8 - "@types/react-dom": 19.1.6(@types/react@19.1.8) + '@types/react': 19.1.8 + '@types/react-dom': 19.1.6(@types/react@19.1.8) - "@radix-ui/react-presence@1.1.4(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)": + '@radix-ui/react-presence@1.1.4(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)': dependencies: - "@radix-ui/react-compose-refs": 1.1.2(@types/react@19.1.8)(react@19.1.0) - "@radix-ui/react-use-layout-effect": 1.1.1(@types/react@19.1.8)(react@19.1.0) + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.1.8)(react@19.1.0) + '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.1.8)(react@19.1.0) react: 19.1.0 react-dom: 19.1.0(react@19.1.0) optionalDependencies: - "@types/react": 19.1.8 - "@types/react-dom": 19.1.6(@types/react@19.1.8) + '@types/react': 19.1.8 + '@types/react-dom': 19.1.6(@types/react@19.1.8) - "@radix-ui/react-presence@1.1.5(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)": + '@radix-ui/react-presence@1.1.5(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)': dependencies: - "@radix-ui/react-compose-refs": 1.1.2(@types/react@19.1.8)(react@19.1.0) - "@radix-ui/react-use-layout-effect": 1.1.1(@types/react@19.1.8)(react@19.1.0) + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.1.8)(react@19.1.0) + '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.1.8)(react@19.1.0) react: 19.1.0 react-dom: 19.1.0(react@19.1.0) optionalDependencies: - "@types/react": 19.1.8 - "@types/react-dom": 19.1.6(@types/react@19.1.8) + '@types/react': 19.1.8 + '@types/react-dom': 19.1.6(@types/react@19.1.8) - "@radix-ui/react-primitive@2.1.3(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)": + '@radix-ui/react-primitive@2.1.3(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)': dependencies: - "@radix-ui/react-slot": 1.2.3(@types/react@19.1.8)(react@19.1.0) + '@radix-ui/react-slot': 1.2.3(@types/react@19.1.8)(react@19.1.0) react: 19.1.0 react-dom: 19.1.0(react@19.1.0) optionalDependencies: - "@types/react": 19.1.8 - "@types/react-dom": 19.1.6(@types/react@19.1.8) + '@types/react': 19.1.8 + '@types/react-dom': 19.1.6(@types/react@19.1.8) - "@radix-ui/react-progress@1.1.7(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)": + '@radix-ui/react-progress@1.1.7(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)': dependencies: - "@radix-ui/react-context": 1.1.2(@types/react@19.1.8)(react@19.1.0) - "@radix-ui/react-primitive": 2.1.3(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + '@radix-ui/react-context': 1.1.2(@types/react@19.1.8)(react@19.1.0) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) react: 19.1.0 react-dom: 19.1.0(react@19.1.0) optionalDependencies: - "@types/react": 19.1.8 - "@types/react-dom": 19.1.6(@types/react@19.1.8) - - "@radix-ui/react-roving-focus@1.1.10(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)": - dependencies: - "@radix-ui/primitive": 1.1.2 - "@radix-ui/react-collection": 1.1.7(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) - "@radix-ui/react-compose-refs": 1.1.2(@types/react@19.1.8)(react@19.1.0) - "@radix-ui/react-context": 1.1.2(@types/react@19.1.8)(react@19.1.0) - "@radix-ui/react-direction": 1.1.1(@types/react@19.1.8)(react@19.1.0) - "@radix-ui/react-id": 1.1.1(@types/react@19.1.8)(react@19.1.0) - "@radix-ui/react-primitive": 2.1.3(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) - "@radix-ui/react-use-callback-ref": 1.1.1(@types/react@19.1.8)(react@19.1.0) - "@radix-ui/react-use-controllable-state": 1.2.2(@types/react@19.1.8)(react@19.1.0) + '@types/react': 19.1.8 + '@types/react-dom': 19.1.6(@types/react@19.1.8) + + '@radix-ui/react-roving-focus@1.1.10(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)': + dependencies: + '@radix-ui/primitive': 1.1.2 + '@radix-ui/react-collection': 1.1.7(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.1.8)(react@19.1.0) + '@radix-ui/react-context': 1.1.2(@types/react@19.1.8)(react@19.1.0) + '@radix-ui/react-direction': 1.1.1(@types/react@19.1.8)(react@19.1.0) + '@radix-ui/react-id': 1.1.1(@types/react@19.1.8)(react@19.1.0) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@19.1.8)(react@19.1.0) + '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.1.8)(react@19.1.0) react: 19.1.0 react-dom: 19.1.0(react@19.1.0) optionalDependencies: - "@types/react": 19.1.8 - "@types/react-dom": 19.1.6(@types/react@19.1.8) - - "@radix-ui/react-scroll-area@1.2.9(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)": - dependencies: - "@radix-ui/number": 1.1.1 - "@radix-ui/primitive": 1.1.2 - "@radix-ui/react-compose-refs": 1.1.2(@types/react@19.1.8)(react@19.1.0) - "@radix-ui/react-context": 1.1.2(@types/react@19.1.8)(react@19.1.0) - "@radix-ui/react-direction": 1.1.1(@types/react@19.1.8)(react@19.1.0) - "@radix-ui/react-presence": 1.1.4(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) - "@radix-ui/react-primitive": 2.1.3(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) - "@radix-ui/react-use-callback-ref": 1.1.1(@types/react@19.1.8)(react@19.1.0) - "@radix-ui/react-use-layout-effect": 1.1.1(@types/react@19.1.8)(react@19.1.0) + '@types/react': 19.1.8 + '@types/react-dom': 19.1.6(@types/react@19.1.8) + + '@radix-ui/react-roving-focus@1.1.11(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)': + dependencies: + '@radix-ui/primitive': 1.1.3 + '@radix-ui/react-collection': 1.1.7(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.1.8)(react@19.1.0) + '@radix-ui/react-context': 1.1.2(@types/react@19.1.8)(react@19.1.0) + '@radix-ui/react-direction': 1.1.1(@types/react@19.1.8)(react@19.1.0) + '@radix-ui/react-id': 1.1.1(@types/react@19.1.8)(react@19.1.0) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@19.1.8)(react@19.1.0) + '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.1.8)(react@19.1.0) react: 19.1.0 react-dom: 19.1.0(react@19.1.0) optionalDependencies: - "@types/react": 19.1.8 - "@types/react-dom": 19.1.6(@types/react@19.1.8) - - "@radix-ui/react-select@2.2.5(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)": - dependencies: - "@radix-ui/number": 1.1.1 - "@radix-ui/primitive": 1.1.2 - "@radix-ui/react-collection": 1.1.7(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) - "@radix-ui/react-compose-refs": 1.1.2(@types/react@19.1.8)(react@19.1.0) - "@radix-ui/react-context": 1.1.2(@types/react@19.1.8)(react@19.1.0) - "@radix-ui/react-direction": 1.1.1(@types/react@19.1.8)(react@19.1.0) - "@radix-ui/react-dismissable-layer": 1.1.10(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) - "@radix-ui/react-focus-guards": 1.1.2(@types/react@19.1.8)(react@19.1.0) - "@radix-ui/react-focus-scope": 1.1.7(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) - "@radix-ui/react-id": 1.1.1(@types/react@19.1.8)(react@19.1.0) - "@radix-ui/react-popper": 1.2.7(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) - "@radix-ui/react-portal": 1.1.9(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) - "@radix-ui/react-primitive": 2.1.3(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) - "@radix-ui/react-slot": 1.2.3(@types/react@19.1.8)(react@19.1.0) - "@radix-ui/react-use-callback-ref": 1.1.1(@types/react@19.1.8)(react@19.1.0) - "@radix-ui/react-use-controllable-state": 1.2.2(@types/react@19.1.8)(react@19.1.0) - "@radix-ui/react-use-layout-effect": 1.1.1(@types/react@19.1.8)(react@19.1.0) - "@radix-ui/react-use-previous": 1.1.1(@types/react@19.1.8)(react@19.1.0) - "@radix-ui/react-visually-hidden": 1.2.3(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + '@types/react': 19.1.8 + '@types/react-dom': 19.1.6(@types/react@19.1.8) + + '@radix-ui/react-scroll-area@1.2.9(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)': + dependencies: + '@radix-ui/number': 1.1.1 + '@radix-ui/primitive': 1.1.2 + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.1.8)(react@19.1.0) + '@radix-ui/react-context': 1.1.2(@types/react@19.1.8)(react@19.1.0) + '@radix-ui/react-direction': 1.1.1(@types/react@19.1.8)(react@19.1.0) + '@radix-ui/react-presence': 1.1.4(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@19.1.8)(react@19.1.0) + '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.1.8)(react@19.1.0) + react: 19.1.0 + react-dom: 19.1.0(react@19.1.0) + optionalDependencies: + '@types/react': 19.1.8 + '@types/react-dom': 19.1.6(@types/react@19.1.8) + + '@radix-ui/react-select@2.2.5(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)': + dependencies: + '@radix-ui/number': 1.1.1 + '@radix-ui/primitive': 1.1.2 + '@radix-ui/react-collection': 1.1.7(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.1.8)(react@19.1.0) + '@radix-ui/react-context': 1.1.2(@types/react@19.1.8)(react@19.1.0) + '@radix-ui/react-direction': 1.1.1(@types/react@19.1.8)(react@19.1.0) + '@radix-ui/react-dismissable-layer': 1.1.10(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + '@radix-ui/react-focus-guards': 1.1.2(@types/react@19.1.8)(react@19.1.0) + '@radix-ui/react-focus-scope': 1.1.7(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + '@radix-ui/react-id': 1.1.1(@types/react@19.1.8)(react@19.1.0) + '@radix-ui/react-popper': 1.2.7(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + '@radix-ui/react-portal': 1.1.9(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + '@radix-ui/react-slot': 1.2.3(@types/react@19.1.8)(react@19.1.0) + '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@19.1.8)(react@19.1.0) + '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.1.8)(react@19.1.0) + '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.1.8)(react@19.1.0) + '@radix-ui/react-use-previous': 1.1.1(@types/react@19.1.8)(react@19.1.0) + '@radix-ui/react-visually-hidden': 1.2.3(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) aria-hidden: 1.2.6 react: 19.1.0 react-dom: 19.1.0(react@19.1.0) react-remove-scroll: 2.7.1(@types/react@19.1.8)(react@19.1.0) optionalDependencies: - "@types/react": 19.1.8 - "@types/react-dom": 19.1.6(@types/react@19.1.8) + '@types/react': 19.1.8 + '@types/react-dom': 19.1.6(@types/react@19.1.8) - "@radix-ui/react-separator@1.1.7(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)": + '@radix-ui/react-separator@1.1.7(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)': dependencies: - "@radix-ui/react-primitive": 2.1.3(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) react: 19.1.0 react-dom: 19.1.0(react@19.1.0) optionalDependencies: - "@types/react": 19.1.8 - "@types/react-dom": 19.1.6(@types/react@19.1.8) - - "@radix-ui/react-slider@1.3.5(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)": - dependencies: - "@radix-ui/number": 1.1.1 - "@radix-ui/primitive": 1.1.2 - "@radix-ui/react-collection": 1.1.7(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) - "@radix-ui/react-compose-refs": 1.1.2(@types/react@19.1.8)(react@19.1.0) - "@radix-ui/react-context": 1.1.2(@types/react@19.1.8)(react@19.1.0) - "@radix-ui/react-direction": 1.1.1(@types/react@19.1.8)(react@19.1.0) - "@radix-ui/react-primitive": 2.1.3(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) - "@radix-ui/react-use-controllable-state": 1.2.2(@types/react@19.1.8)(react@19.1.0) - "@radix-ui/react-use-layout-effect": 1.1.1(@types/react@19.1.8)(react@19.1.0) - "@radix-ui/react-use-previous": 1.1.1(@types/react@19.1.8)(react@19.1.0) - "@radix-ui/react-use-size": 1.1.1(@types/react@19.1.8)(react@19.1.0) + '@types/react': 19.1.8 + '@types/react-dom': 19.1.6(@types/react@19.1.8) + + '@radix-ui/react-slider@1.3.5(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)': + dependencies: + '@radix-ui/number': 1.1.1 + '@radix-ui/primitive': 1.1.2 + '@radix-ui/react-collection': 1.1.7(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.1.8)(react@19.1.0) + '@radix-ui/react-context': 1.1.2(@types/react@19.1.8)(react@19.1.0) + '@radix-ui/react-direction': 1.1.1(@types/react@19.1.8)(react@19.1.0) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.1.8)(react@19.1.0) + '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.1.8)(react@19.1.0) + '@radix-ui/react-use-previous': 1.1.1(@types/react@19.1.8)(react@19.1.0) + '@radix-ui/react-use-size': 1.1.1(@types/react@19.1.8)(react@19.1.0) react: 19.1.0 react-dom: 19.1.0(react@19.1.0) optionalDependencies: - "@types/react": 19.1.8 - "@types/react-dom": 19.1.6(@types/react@19.1.8) + '@types/react': 19.1.8 + '@types/react-dom': 19.1.6(@types/react@19.1.8) - "@radix-ui/react-slot@1.2.3(@types/react@19.1.8)(react@19.1.0)": + '@radix-ui/react-slot@1.2.3(@types/react@19.1.8)(react@19.1.0)': dependencies: - "@radix-ui/react-compose-refs": 1.1.2(@types/react@19.1.8)(react@19.1.0) + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.1.8)(react@19.1.0) react: 19.1.0 optionalDependencies: - "@types/react": 19.1.8 + '@types/react': 19.1.8 - "@radix-ui/react-switch@1.2.5(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)": + '@radix-ui/react-switch@1.2.5(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)': dependencies: - "@radix-ui/primitive": 1.1.2 - "@radix-ui/react-compose-refs": 1.1.2(@types/react@19.1.8)(react@19.1.0) - "@radix-ui/react-context": 1.1.2(@types/react@19.1.8)(react@19.1.0) - "@radix-ui/react-primitive": 2.1.3(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) - "@radix-ui/react-use-controllable-state": 1.2.2(@types/react@19.1.8)(react@19.1.0) - "@radix-ui/react-use-previous": 1.1.1(@types/react@19.1.8)(react@19.1.0) - "@radix-ui/react-use-size": 1.1.1(@types/react@19.1.8)(react@19.1.0) + '@radix-ui/primitive': 1.1.2 + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.1.8)(react@19.1.0) + '@radix-ui/react-context': 1.1.2(@types/react@19.1.8)(react@19.1.0) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.1.8)(react@19.1.0) + '@radix-ui/react-use-previous': 1.1.1(@types/react@19.1.8)(react@19.1.0) + '@radix-ui/react-use-size': 1.1.1(@types/react@19.1.8)(react@19.1.0) react: 19.1.0 react-dom: 19.1.0(react@19.1.0) optionalDependencies: - "@types/react": 19.1.8 - "@types/react-dom": 19.1.6(@types/react@19.1.8) - - "@radix-ui/react-tabs@1.1.12(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)": - dependencies: - "@radix-ui/primitive": 1.1.2 - "@radix-ui/react-context": 1.1.2(@types/react@19.1.8)(react@19.1.0) - "@radix-ui/react-direction": 1.1.1(@types/react@19.1.8)(react@19.1.0) - "@radix-ui/react-id": 1.1.1(@types/react@19.1.8)(react@19.1.0) - "@radix-ui/react-presence": 1.1.4(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) - "@radix-ui/react-primitive": 2.1.3(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) - "@radix-ui/react-roving-focus": 1.1.10(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) - "@radix-ui/react-use-controllable-state": 1.2.2(@types/react@19.1.8)(react@19.1.0) + '@types/react': 19.1.8 + '@types/react-dom': 19.1.6(@types/react@19.1.8) + + '@radix-ui/react-tabs@1.1.12(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)': + dependencies: + '@radix-ui/primitive': 1.1.2 + '@radix-ui/react-context': 1.1.2(@types/react@19.1.8)(react@19.1.0) + '@radix-ui/react-direction': 1.1.1(@types/react@19.1.8)(react@19.1.0) + '@radix-ui/react-id': 1.1.1(@types/react@19.1.8)(react@19.1.0) + '@radix-ui/react-presence': 1.1.4(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + '@radix-ui/react-roving-focus': 1.1.10(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.1.8)(react@19.1.0) react: 19.1.0 react-dom: 19.1.0(react@19.1.0) optionalDependencies: - "@types/react": 19.1.8 - "@types/react-dom": 19.1.6(@types/react@19.1.8) + '@types/react': 19.1.8 + '@types/react-dom': 19.1.6(@types/react@19.1.8) - "@radix-ui/react-use-callback-ref@1.1.1(@types/react@19.1.8)(react@19.1.0)": + '@radix-ui/react-use-callback-ref@1.1.1(@types/react@19.1.8)(react@19.1.0)': dependencies: react: 19.1.0 optionalDependencies: - "@types/react": 19.1.8 + '@types/react': 19.1.8 - "@radix-ui/react-use-controllable-state@1.2.2(@types/react@19.1.8)(react@19.1.0)": + '@radix-ui/react-use-controllable-state@1.2.2(@types/react@19.1.8)(react@19.1.0)': dependencies: - "@radix-ui/react-use-effect-event": 0.0.2(@types/react@19.1.8)(react@19.1.0) - "@radix-ui/react-use-layout-effect": 1.1.1(@types/react@19.1.8)(react@19.1.0) + '@radix-ui/react-use-effect-event': 0.0.2(@types/react@19.1.8)(react@19.1.0) + '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.1.8)(react@19.1.0) react: 19.1.0 optionalDependencies: - "@types/react": 19.1.8 + '@types/react': 19.1.8 - "@radix-ui/react-use-effect-event@0.0.2(@types/react@19.1.8)(react@19.1.0)": + '@radix-ui/react-use-effect-event@0.0.2(@types/react@19.1.8)(react@19.1.0)': dependencies: - "@radix-ui/react-use-layout-effect": 1.1.1(@types/react@19.1.8)(react@19.1.0) + '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.1.8)(react@19.1.0) react: 19.1.0 optionalDependencies: - "@types/react": 19.1.8 + '@types/react': 19.1.8 - "@radix-ui/react-use-escape-keydown@1.1.1(@types/react@19.1.8)(react@19.1.0)": + '@radix-ui/react-use-escape-keydown@1.1.1(@types/react@19.1.8)(react@19.1.0)': dependencies: - "@radix-ui/react-use-callback-ref": 1.1.1(@types/react@19.1.8)(react@19.1.0) + '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@19.1.8)(react@19.1.0) react: 19.1.0 optionalDependencies: - "@types/react": 19.1.8 + '@types/react': 19.1.8 - "@radix-ui/react-use-is-hydrated@0.1.0(@types/react@19.1.8)(react@19.1.0)": + '@radix-ui/react-use-is-hydrated@0.1.0(@types/react@19.1.8)(react@19.1.0)': dependencies: react: 19.1.0 use-sync-external-store: 1.5.0(react@19.1.0) optionalDependencies: - "@types/react": 19.1.8 + '@types/react': 19.1.8 - "@radix-ui/react-use-layout-effect@1.1.1(@types/react@19.1.8)(react@19.1.0)": + '@radix-ui/react-use-layout-effect@1.1.1(@types/react@19.1.8)(react@19.1.0)': dependencies: react: 19.1.0 optionalDependencies: - "@types/react": 19.1.8 + '@types/react': 19.1.8 - "@radix-ui/react-use-previous@1.1.1(@types/react@19.1.8)(react@19.1.0)": + '@radix-ui/react-use-previous@1.1.1(@types/react@19.1.8)(react@19.1.0)': dependencies: react: 19.1.0 optionalDependencies: - "@types/react": 19.1.8 + '@types/react': 19.1.8 - "@radix-ui/react-use-rect@1.1.1(@types/react@19.1.8)(react@19.1.0)": + '@radix-ui/react-use-rect@1.1.1(@types/react@19.1.8)(react@19.1.0)': dependencies: - "@radix-ui/rect": 1.1.1 + '@radix-ui/rect': 1.1.1 react: 19.1.0 optionalDependencies: - "@types/react": 19.1.8 + '@types/react': 19.1.8 - "@radix-ui/react-use-size@1.1.1(@types/react@19.1.8)(react@19.1.0)": + '@radix-ui/react-use-size@1.1.1(@types/react@19.1.8)(react@19.1.0)': dependencies: - "@radix-ui/react-use-layout-effect": 1.1.1(@types/react@19.1.8)(react@19.1.0) + '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.1.8)(react@19.1.0) react: 19.1.0 optionalDependencies: - "@types/react": 19.1.8 + '@types/react': 19.1.8 - "@radix-ui/react-visually-hidden@1.2.3(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)": + '@radix-ui/react-visually-hidden@1.2.3(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)': dependencies: - "@radix-ui/react-primitive": 2.1.3(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) react: 19.1.0 react-dom: 19.1.0(react@19.1.0) optionalDependencies: - "@types/react": 19.1.8 - "@types/react-dom": 19.1.6(@types/react@19.1.8) + '@types/react': 19.1.8 + '@types/react-dom': 19.1.6(@types/react@19.1.8) - "@radix-ui/rect@1.1.1": {} + '@radix-ui/rect@1.1.1': {} - "@rtsao/scc@1.1.0": {} + '@rtsao/scc@1.1.0': {} - "@rushstack/eslint-patch@1.12.0": {} + '@rushstack/eslint-patch@1.12.0': {} - "@schummar/icu-type-parser@1.21.5": {} + '@schummar/icu-type-parser@1.21.5': {} - "@standard-schema/utils@0.3.0": {} + '@standard-schema/utils@0.3.0': {} - "@swc/counter@0.1.3": {} + '@swc/counter@0.1.3': {} - "@swc/helpers@0.5.15": + '@swc/helpers@0.5.15': dependencies: tslib: 2.8.1 - "@tabler/icons-react@3.34.0(react@19.1.0)": + '@tabler/icons-react@3.34.0(react@19.1.0)': dependencies: - "@tabler/icons": 3.34.0 + '@tabler/icons': 3.34.0 react: 19.1.0 - "@tabler/icons@3.34.0": {} + '@tabler/icons@3.34.0': {} - "@tailwindcss/node@4.1.11": + '@tailwindcss/node@4.1.11': dependencies: - "@ampproject/remapping": 2.3.0 + '@ampproject/remapping': 2.3.0 enhanced-resolve: 5.18.2 jiti: 2.4.2 lightningcss: 1.30.1 @@ -4541,113 +4187,117 @@ snapshots: source-map-js: 1.2.1 tailwindcss: 4.1.11 - "@tailwindcss/oxide-android-arm64@4.1.11": + '@tailwindcss/oxide-android-arm64@4.1.11': optional: true - "@tailwindcss/oxide-darwin-arm64@4.1.11": + '@tailwindcss/oxide-darwin-arm64@4.1.11': optional: true - "@tailwindcss/oxide-darwin-x64@4.1.11": + '@tailwindcss/oxide-darwin-x64@4.1.11': optional: true - "@tailwindcss/oxide-freebsd-x64@4.1.11": + '@tailwindcss/oxide-freebsd-x64@4.1.11': optional: true - "@tailwindcss/oxide-linux-arm-gnueabihf@4.1.11": + '@tailwindcss/oxide-linux-arm-gnueabihf@4.1.11': optional: true - "@tailwindcss/oxide-linux-arm64-gnu@4.1.11": + '@tailwindcss/oxide-linux-arm64-gnu@4.1.11': optional: true - "@tailwindcss/oxide-linux-arm64-musl@4.1.11": + '@tailwindcss/oxide-linux-arm64-musl@4.1.11': optional: true - "@tailwindcss/oxide-linux-x64-gnu@4.1.11": + '@tailwindcss/oxide-linux-x64-gnu@4.1.11': optional: true - "@tailwindcss/oxide-linux-x64-musl@4.1.11": + '@tailwindcss/oxide-linux-x64-musl@4.1.11': optional: true - "@tailwindcss/oxide-wasm32-wasi@4.1.11": + '@tailwindcss/oxide-wasm32-wasi@4.1.11': optional: true - "@tailwindcss/oxide-win32-arm64-msvc@4.1.11": + '@tailwindcss/oxide-win32-arm64-msvc@4.1.11': optional: true - "@tailwindcss/oxide-win32-x64-msvc@4.1.11": + '@tailwindcss/oxide-win32-x64-msvc@4.1.11': optional: true - "@tailwindcss/oxide@4.1.11": + '@tailwindcss/oxide@4.1.11': dependencies: detect-libc: 2.0.4 tar: 7.4.3 optionalDependencies: - "@tailwindcss/oxide-android-arm64": 4.1.11 - "@tailwindcss/oxide-darwin-arm64": 4.1.11 - "@tailwindcss/oxide-darwin-x64": 4.1.11 - "@tailwindcss/oxide-freebsd-x64": 4.1.11 - "@tailwindcss/oxide-linux-arm-gnueabihf": 4.1.11 - "@tailwindcss/oxide-linux-arm64-gnu": 4.1.11 - "@tailwindcss/oxide-linux-arm64-musl": 4.1.11 - "@tailwindcss/oxide-linux-x64-gnu": 4.1.11 - "@tailwindcss/oxide-linux-x64-musl": 4.1.11 - "@tailwindcss/oxide-wasm32-wasi": 4.1.11 - "@tailwindcss/oxide-win32-arm64-msvc": 4.1.11 - "@tailwindcss/oxide-win32-x64-msvc": 4.1.11 - - "@tailwindcss/postcss@4.1.11": - dependencies: - "@alloc/quick-lru": 5.2.0 - "@tailwindcss/node": 4.1.11 - "@tailwindcss/oxide": 4.1.11 + '@tailwindcss/oxide-android-arm64': 4.1.11 + '@tailwindcss/oxide-darwin-arm64': 4.1.11 + '@tailwindcss/oxide-darwin-x64': 4.1.11 + '@tailwindcss/oxide-freebsd-x64': 4.1.11 + '@tailwindcss/oxide-linux-arm-gnueabihf': 4.1.11 + '@tailwindcss/oxide-linux-arm64-gnu': 4.1.11 + '@tailwindcss/oxide-linux-arm64-musl': 4.1.11 + '@tailwindcss/oxide-linux-x64-gnu': 4.1.11 + '@tailwindcss/oxide-linux-x64-musl': 4.1.11 + '@tailwindcss/oxide-wasm32-wasi': 4.1.11 + '@tailwindcss/oxide-win32-arm64-msvc': 4.1.11 + '@tailwindcss/oxide-win32-x64-msvc': 4.1.11 + + '@tailwindcss/postcss@4.1.11': + dependencies: + '@alloc/quick-lru': 5.2.0 + '@tailwindcss/node': 4.1.11 + '@tailwindcss/oxide': 4.1.11 postcss: 8.5.6 tailwindcss: 4.1.11 - "@tybys/wasm-util@0.9.0": + '@transloadit/prettier-bytes@0.3.5': {} + + '@tybys/wasm-util@0.9.0': dependencies: tslib: 2.8.1 optional: true - "@types/estree@1.0.8": {} + '@types/estree@1.0.8': {} - "@types/js-cookie@3.0.6": {} + '@types/js-cookie@3.0.6': {} - "@types/json-schema@7.0.15": {} + '@types/json-schema@7.0.15': {} - "@types/json5@0.0.29": {} + '@types/json5@0.0.29': {} - "@types/node@22.14.0": + '@types/node@22.14.0': dependencies: undici-types: 6.21.0 - "@types/qrcode@1.5.5": + '@types/qrcode@1.5.5': dependencies: - "@types/node": 22.14.0 + '@types/node': 22.14.0 - "@types/react-dom@19.1.6(@types/react@19.1.8)": + '@types/react-dom@19.1.6(@types/react@19.1.8)': dependencies: - "@types/react": 19.1.8 + '@types/react': 19.1.8 - "@types/react-dropzone@5.1.0(react@19.1.0)": + '@types/react-dropzone@5.1.0(react@19.1.0)': dependencies: react-dropzone: 14.3.8(react@19.1.0) transitivePeerDependencies: - react - "@types/react@19.1.8": + '@types/react@19.1.8': dependencies: csstype: 3.1.3 - "@types/use-sync-external-store@0.0.6": {} + '@types/retry@0.12.2': {} - "@typescript-eslint/eslint-plugin@8.35.1(@typescript-eslint/parser@8.35.1(eslint@9.30.0(jiti@2.4.2))(typescript@5.8.3))(eslint@9.30.0(jiti@2.4.2))(typescript@5.8.3)": + '@types/use-sync-external-store@0.0.6': {} + + '@typescript-eslint/eslint-plugin@8.35.1(@typescript-eslint/parser@8.35.1(eslint@9.30.0(jiti@2.4.2))(typescript@5.8.3))(eslint@9.30.0(jiti@2.4.2))(typescript@5.8.3)': dependencies: - "@eslint-community/regexpp": 4.12.1 - "@typescript-eslint/parser": 8.35.1(eslint@9.30.0(jiti@2.4.2))(typescript@5.8.3) - "@typescript-eslint/scope-manager": 8.35.1 - "@typescript-eslint/type-utils": 8.35.1(eslint@9.30.0(jiti@2.4.2))(typescript@5.8.3) - "@typescript-eslint/utils": 8.35.1(eslint@9.30.0(jiti@2.4.2))(typescript@5.8.3) - "@typescript-eslint/visitor-keys": 8.35.1 + '@eslint-community/regexpp': 4.12.1 + '@typescript-eslint/parser': 8.35.1(eslint@9.30.0(jiti@2.4.2))(typescript@5.8.3) + '@typescript-eslint/scope-manager': 8.35.1 + '@typescript-eslint/type-utils': 8.35.1(eslint@9.30.0(jiti@2.4.2))(typescript@5.8.3) + '@typescript-eslint/utils': 8.35.1(eslint@9.30.0(jiti@2.4.2))(typescript@5.8.3) + '@typescript-eslint/visitor-keys': 8.35.1 eslint: 9.30.0(jiti@2.4.2) graphemer: 1.4.0 ignore: 7.0.5 @@ -4657,40 +4307,40 @@ snapshots: transitivePeerDependencies: - supports-color - "@typescript-eslint/parser@8.35.1(eslint@9.30.0(jiti@2.4.2))(typescript@5.8.3)": + '@typescript-eslint/parser@8.35.1(eslint@9.30.0(jiti@2.4.2))(typescript@5.8.3)': dependencies: - "@typescript-eslint/scope-manager": 8.35.1 - "@typescript-eslint/types": 8.35.1 - "@typescript-eslint/typescript-estree": 8.35.1(typescript@5.8.3) - "@typescript-eslint/visitor-keys": 8.35.1 + '@typescript-eslint/scope-manager': 8.35.1 + '@typescript-eslint/types': 8.35.1 + '@typescript-eslint/typescript-estree': 8.35.1(typescript@5.8.3) + '@typescript-eslint/visitor-keys': 8.35.1 debug: 4.4.1 eslint: 9.30.0(jiti@2.4.2) typescript: 5.8.3 transitivePeerDependencies: - supports-color - "@typescript-eslint/project-service@8.35.1(typescript@5.8.3)": + '@typescript-eslint/project-service@8.35.1(typescript@5.8.3)': dependencies: - "@typescript-eslint/tsconfig-utils": 8.35.1(typescript@5.8.3) - "@typescript-eslint/types": 8.35.1 + '@typescript-eslint/tsconfig-utils': 8.35.1(typescript@5.8.3) + '@typescript-eslint/types': 8.35.1 debug: 4.4.1 typescript: 5.8.3 transitivePeerDependencies: - supports-color - "@typescript-eslint/scope-manager@8.35.1": + '@typescript-eslint/scope-manager@8.35.1': dependencies: - "@typescript-eslint/types": 8.35.1 - "@typescript-eslint/visitor-keys": 8.35.1 + '@typescript-eslint/types': 8.35.1 + '@typescript-eslint/visitor-keys': 8.35.1 - "@typescript-eslint/tsconfig-utils@8.35.1(typescript@5.8.3)": + '@typescript-eslint/tsconfig-utils@8.35.1(typescript@5.8.3)': dependencies: typescript: 5.8.3 - "@typescript-eslint/type-utils@8.35.1(eslint@9.30.0(jiti@2.4.2))(typescript@5.8.3)": + '@typescript-eslint/type-utils@8.35.1(eslint@9.30.0(jiti@2.4.2))(typescript@5.8.3)': dependencies: - "@typescript-eslint/typescript-estree": 8.35.1(typescript@5.8.3) - "@typescript-eslint/utils": 8.35.1(eslint@9.30.0(jiti@2.4.2))(typescript@5.8.3) + '@typescript-eslint/typescript-estree': 8.35.1(typescript@5.8.3) + '@typescript-eslint/utils': 8.35.1(eslint@9.30.0(jiti@2.4.2))(typescript@5.8.3) debug: 4.4.1 eslint: 9.30.0(jiti@2.4.2) ts-api-utils: 2.1.0(typescript@5.8.3) @@ -4698,14 +4348,14 @@ snapshots: transitivePeerDependencies: - supports-color - "@typescript-eslint/types@8.35.1": {} + '@typescript-eslint/types@8.35.1': {} - "@typescript-eslint/typescript-estree@8.35.1(typescript@5.8.3)": + '@typescript-eslint/typescript-estree@8.35.1(typescript@5.8.3)': dependencies: - "@typescript-eslint/project-service": 8.35.1(typescript@5.8.3) - "@typescript-eslint/tsconfig-utils": 8.35.1(typescript@5.8.3) - "@typescript-eslint/types": 8.35.1 - "@typescript-eslint/visitor-keys": 8.35.1 + '@typescript-eslint/project-service': 8.35.1(typescript@5.8.3) + '@typescript-eslint/tsconfig-utils': 8.35.1(typescript@5.8.3) + '@typescript-eslint/types': 8.35.1 + '@typescript-eslint/visitor-keys': 8.35.1 debug: 4.4.1 fast-glob: 3.3.3 is-glob: 4.0.3 @@ -4716,94 +4366,125 @@ snapshots: transitivePeerDependencies: - supports-color - "@typescript-eslint/utils@8.35.1(eslint@9.30.0(jiti@2.4.2))(typescript@5.8.3)": + '@typescript-eslint/utils@8.35.1(eslint@9.30.0(jiti@2.4.2))(typescript@5.8.3)': dependencies: - "@eslint-community/eslint-utils": 4.7.0(eslint@9.30.0(jiti@2.4.2)) - "@typescript-eslint/scope-manager": 8.35.1 - "@typescript-eslint/types": 8.35.1 - "@typescript-eslint/typescript-estree": 8.35.1(typescript@5.8.3) + '@eslint-community/eslint-utils': 4.7.0(eslint@9.30.0(jiti@2.4.2)) + '@typescript-eslint/scope-manager': 8.35.1 + '@typescript-eslint/types': 8.35.1 + '@typescript-eslint/typescript-estree': 8.35.1(typescript@5.8.3) eslint: 9.30.0(jiti@2.4.2) typescript: 5.8.3 transitivePeerDependencies: - supports-color - "@typescript-eslint/visitor-keys@8.35.1": + '@typescript-eslint/visitor-keys@8.35.1': dependencies: - "@typescript-eslint/types": 8.35.1 + '@typescript-eslint/types': 8.35.1 eslint-visitor-keys: 4.2.1 - "@unrs/resolver-binding-android-arm-eabi@1.11.0": + '@unrs/resolver-binding-android-arm-eabi@1.11.0': optional: true - "@unrs/resolver-binding-android-arm64@1.11.0": + '@unrs/resolver-binding-android-arm64@1.11.0': optional: true - "@unrs/resolver-binding-darwin-arm64@1.11.0": + '@unrs/resolver-binding-darwin-arm64@1.11.0': optional: true - "@unrs/resolver-binding-darwin-x64@1.11.0": + '@unrs/resolver-binding-darwin-x64@1.11.0': optional: true - "@unrs/resolver-binding-freebsd-x64@1.11.0": + '@unrs/resolver-binding-freebsd-x64@1.11.0': optional: true - "@unrs/resolver-binding-linux-arm-gnueabihf@1.11.0": + '@unrs/resolver-binding-linux-arm-gnueabihf@1.11.0': optional: true - "@unrs/resolver-binding-linux-arm-musleabihf@1.11.0": + '@unrs/resolver-binding-linux-arm-musleabihf@1.11.0': optional: true - "@unrs/resolver-binding-linux-arm64-gnu@1.11.0": + '@unrs/resolver-binding-linux-arm64-gnu@1.11.0': optional: true - "@unrs/resolver-binding-linux-arm64-musl@1.11.0": + '@unrs/resolver-binding-linux-arm64-musl@1.11.0': optional: true - "@unrs/resolver-binding-linux-ppc64-gnu@1.11.0": + '@unrs/resolver-binding-linux-ppc64-gnu@1.11.0': optional: true - "@unrs/resolver-binding-linux-riscv64-gnu@1.11.0": + '@unrs/resolver-binding-linux-riscv64-gnu@1.11.0': optional: true - "@unrs/resolver-binding-linux-riscv64-musl@1.11.0": + '@unrs/resolver-binding-linux-riscv64-musl@1.11.0': optional: true - "@unrs/resolver-binding-linux-s390x-gnu@1.11.0": + '@unrs/resolver-binding-linux-s390x-gnu@1.11.0': optional: true - "@unrs/resolver-binding-linux-x64-gnu@1.11.0": + '@unrs/resolver-binding-linux-x64-gnu@1.11.0': optional: true - "@unrs/resolver-binding-linux-x64-musl@1.11.0": + '@unrs/resolver-binding-linux-x64-musl@1.11.0': optional: true - "@unrs/resolver-binding-wasm32-wasi@1.11.0": + '@unrs/resolver-binding-wasm32-wasi@1.11.0': dependencies: - "@napi-rs/wasm-runtime": 0.2.11 + '@napi-rs/wasm-runtime': 0.2.11 optional: true - "@unrs/resolver-binding-win32-arm64-msvc@1.11.0": + '@unrs/resolver-binding-win32-arm64-msvc@1.11.0': optional: true - "@unrs/resolver-binding-win32-ia32-msvc@1.11.0": + '@unrs/resolver-binding-win32-ia32-msvc@1.11.0': optional: true - "@unrs/resolver-binding-win32-x64-msvc@1.11.0": + '@unrs/resolver-binding-win32-x64-msvc@1.11.0': optional: true - "@zxing/browser@0.0.7(@zxing/library@0.18.6)": + '@uppy/aws-s3@4.3.2(@uppy/core@4.5.3)': + dependencies: + '@uppy/companion-client': 4.5.2(@uppy/core@4.5.3) + '@uppy/core': 4.5.3 + '@uppy/utils': 6.2.2 + + '@uppy/companion-client@4.5.2(@uppy/core@4.5.3)': + dependencies: + '@uppy/core': 4.5.3 + '@uppy/utils': 6.2.2 + namespace-emitter: 2.0.1 + p-retry: 6.2.1 + + '@uppy/core@4.5.3': dependencies: - "@zxing/library": 0.18.6 + '@transloadit/prettier-bytes': 0.3.5 + '@uppy/store-default': 4.3.2 + '@uppy/utils': 6.2.2 + lodash: 4.17.21 + mime-match: 1.0.2 + namespace-emitter: 2.0.1 + nanoid: 5.1.5 + preact: 10.27.2 + + '@uppy/store-default@4.3.2': {} + + '@uppy/utils@6.2.2': + dependencies: + lodash: 4.17.21 + preact: 10.27.2 + + '@zxing/browser@0.0.7(@zxing/library@0.18.6)': + dependencies: + '@zxing/library': 0.18.6 optionalDependencies: - "@zxing/text-encoding": 0.9.0 + '@zxing/text-encoding': 0.9.0 - "@zxing/library@0.18.6": + '@zxing/library@0.18.6': dependencies: ts-custom-error: 3.3.1 optionalDependencies: - "@zxing/text-encoding": 0.9.0 + '@zxing/text-encoding': 0.9.0 - "@zxing/text-encoding@0.9.0": + '@zxing/text-encoding@0.9.0': optional: true acorn-jsx@5.3.2(acorn@8.15.0): @@ -5207,10 +4888,10 @@ snapshots: eslint-config-next@15.3.4(eslint@9.30.0(jiti@2.4.2))(typescript@5.8.3): dependencies: - "@next/eslint-plugin-next": 15.3.4 - "@rushstack/eslint-patch": 1.12.0 - "@typescript-eslint/eslint-plugin": 8.35.1(@typescript-eslint/parser@8.35.1(eslint@9.30.0(jiti@2.4.2))(typescript@5.8.3))(eslint@9.30.0(jiti@2.4.2))(typescript@5.8.3) - "@typescript-eslint/parser": 8.35.1(eslint@9.30.0(jiti@2.4.2))(typescript@5.8.3) + '@next/eslint-plugin-next': 15.3.4 + '@rushstack/eslint-patch': 1.12.0 + '@typescript-eslint/eslint-plugin': 8.35.1(@typescript-eslint/parser@8.35.1(eslint@9.30.0(jiti@2.4.2))(typescript@5.8.3))(eslint@9.30.0(jiti@2.4.2))(typescript@5.8.3) + '@typescript-eslint/parser': 8.35.1(eslint@9.30.0(jiti@2.4.2))(typescript@5.8.3) eslint: 9.30.0(jiti@2.4.2) eslint-import-resolver-node: 0.3.9 eslint-import-resolver-typescript: 3.10.1(eslint-plugin-import@2.32.0)(eslint@9.30.0(jiti@2.4.2)) @@ -5239,7 +4920,7 @@ snapshots: eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.32.0)(eslint@9.30.0(jiti@2.4.2)): dependencies: - "@nolyfill/is-core-module": 1.0.39 + '@nolyfill/is-core-module': 1.0.39 debug: 4.4.1 eslint: 9.30.0(jiti@2.4.2) get-tsconfig: 4.10.1 @@ -5256,7 +4937,7 @@ snapshots: dependencies: debug: 3.2.7 optionalDependencies: - "@typescript-eslint/parser": 8.35.1(eslint@9.30.0(jiti@2.4.2))(typescript@5.8.3) + '@typescript-eslint/parser': 8.35.1(eslint@9.30.0(jiti@2.4.2))(typescript@5.8.3) eslint: 9.30.0(jiti@2.4.2) eslint-import-resolver-node: 0.3.9 eslint-import-resolver-typescript: 3.10.1(eslint-plugin-import@2.32.0)(eslint@9.30.0(jiti@2.4.2)) @@ -5265,7 +4946,7 @@ snapshots: eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.35.1(eslint@9.30.0(jiti@2.4.2))(typescript@5.8.3))(eslint-import-resolver-typescript@3.10.1)(eslint@9.30.0(jiti@2.4.2)): dependencies: - "@rtsao/scc": 1.1.0 + '@rtsao/scc': 1.1.0 array-includes: 3.1.9 array.prototype.findlastindex: 1.2.6 array.prototype.flat: 1.3.3 @@ -5286,7 +4967,7 @@ snapshots: string.prototype.trimend: 1.0.9 tsconfig-paths: 3.15.0 optionalDependencies: - "@typescript-eslint/parser": 8.35.1(eslint@9.30.0(jiti@2.4.2))(typescript@5.8.3) + '@typescript-eslint/parser': 8.35.1(eslint@9.30.0(jiti@2.4.2))(typescript@5.8.3) transitivePeerDependencies: - eslint-import-resolver-typescript - eslint-import-resolver-webpack @@ -5357,19 +5038,19 @@ snapshots: eslint@9.30.0(jiti@2.4.2): dependencies: - "@eslint-community/eslint-utils": 4.7.0(eslint@9.30.0(jiti@2.4.2)) - "@eslint-community/regexpp": 4.12.1 - "@eslint/config-array": 0.21.0 - "@eslint/config-helpers": 0.3.0 - "@eslint/core": 0.14.0 - "@eslint/eslintrc": 3.3.1 - "@eslint/js": 9.30.0 - "@eslint/plugin-kit": 0.3.3 - "@humanfs/node": 0.16.6 - "@humanwhocodes/module-importer": 1.0.1 - "@humanwhocodes/retry": 0.4.3 - "@types/estree": 1.0.8 - "@types/json-schema": 7.0.15 + '@eslint-community/eslint-utils': 4.7.0(eslint@9.30.0(jiti@2.4.2)) + '@eslint-community/regexpp': 4.12.1 + '@eslint/config-array': 0.21.0 + '@eslint/config-helpers': 0.3.0 + '@eslint/core': 0.14.0 + '@eslint/eslintrc': 3.3.1 + '@eslint/js': 9.30.0 + '@eslint/plugin-kit': 0.3.3 + '@humanfs/node': 0.16.6 + '@humanwhocodes/module-importer': 1.0.1 + '@humanwhocodes/retry': 0.4.3 + '@types/estree': 1.0.8 + '@types/json-schema': 7.0.15 ajv: 6.12.6 chalk: 4.1.2 cross-spawn: 7.0.6 @@ -5421,16 +5102,16 @@ snapshots: fast-glob@3.3.1: dependencies: - "@nodelib/fs.stat": 2.0.5 - "@nodelib/fs.walk": 1.2.8 + '@nodelib/fs.stat': 2.0.5 + '@nodelib/fs.walk': 1.2.8 glob-parent: 5.1.2 merge2: 1.4.1 micromatch: 4.0.8 fast-glob@3.3.3: dependencies: - "@nodelib/fs.stat": 2.0.5 - "@nodelib/fs.walk": 1.2.8 + '@nodelib/fs.stat': 2.0.5 + '@nodelib/fs.walk': 1.2.8 glob-parent: 5.1.2 merge2: 1.4.1 micromatch: 4.0.8 @@ -5618,9 +5299,9 @@ snapshots: intl-messageformat@10.7.16: dependencies: - "@formatjs/ecma402-abstract": 2.3.4 - "@formatjs/fast-memoize": 2.2.7 - "@formatjs/icu-messageformat-parser": 2.11.2 + '@formatjs/ecma402-abstract': 2.3.4 + '@formatjs/fast-memoize': 2.2.7 + '@formatjs/icu-messageformat-parser': 2.11.2 tslib: 2.8.1 is-array-buffer@3.0.5: @@ -5693,6 +5374,8 @@ snapshots: is-negative-zero@2.0.3: {} + is-network-error@1.3.0: {} + is-number-object@1.1.1: dependencies: call-bound: 1.0.4 @@ -5864,6 +5547,8 @@ snapshots: lodash.merge@4.6.2: {} + lodash@4.17.21: {} + loose-envify@1.4.0: dependencies: js-tokens: 4.0.0 @@ -5874,7 +5559,7 @@ snapshots: magic-string@0.30.17: dependencies: - "@jridgewell/sourcemap-codec": 1.5.4 + '@jridgewell/sourcemap-codec': 1.5.4 math-intrinsics@1.1.0: {} @@ -5887,6 +5572,10 @@ snapshots: mime-db@1.52.0: {} + mime-match@1.0.2: + dependencies: + wildcard: 1.1.2 + mime-types@2.1.35: dependencies: mime-db: 1.52.0 @@ -5917,6 +5606,8 @@ snapshots: ms@2.1.3: {} + namespace-emitter@2.0.1: {} + nanoid@3.3.11: {} nanoid@5.1.5: {} @@ -5929,7 +5620,7 @@ snapshots: next-intl@4.3.4(next@15.3.4(react-dom@19.1.0(react@19.1.0))(react@19.1.0))(react@19.1.0)(typescript@5.8.3): dependencies: - "@formatjs/intl-localematcher": 0.5.10 + '@formatjs/intl-localematcher': 0.5.10 negotiator: 1.0.0 next: 15.3.4(react-dom@19.1.0(react@19.1.0))(react@19.1.0) react: 19.1.0 @@ -5944,9 +5635,9 @@ snapshots: next@15.3.4(react-dom@19.1.0(react@19.1.0))(react@19.1.0): dependencies: - "@next/env": 15.3.4 - "@swc/counter": 0.1.3 - "@swc/helpers": 0.5.15 + '@next/env': 15.3.4 + '@swc/counter': 0.1.3 + '@swc/helpers': 0.5.15 busboy: 1.6.0 caniuse-lite: 1.0.30001727 postcss: 8.4.31 @@ -5954,17 +5645,17 @@ snapshots: react-dom: 19.1.0(react@19.1.0) styled-jsx: 5.1.6(react@19.1.0) optionalDependencies: - "@next/swc-darwin-arm64": 15.3.4 - "@next/swc-darwin-x64": 15.3.4 - "@next/swc-linux-arm64-gnu": 15.3.4 - "@next/swc-linux-arm64-musl": 15.3.4 - "@next/swc-linux-x64-gnu": 15.3.4 - "@next/swc-linux-x64-musl": 15.3.4 - "@next/swc-win32-arm64-msvc": 15.3.4 - "@next/swc-win32-x64-msvc": 15.3.4 + '@next/swc-darwin-arm64': 15.3.4 + '@next/swc-darwin-x64': 15.3.4 + '@next/swc-linux-arm64-gnu': 15.3.4 + '@next/swc-linux-arm64-musl': 15.3.4 + '@next/swc-linux-x64-gnu': 15.3.4 + '@next/swc-linux-x64-musl': 15.3.4 + '@next/swc-win32-arm64-msvc': 15.3.4 + '@next/swc-win32-x64-msvc': 15.3.4 sharp: 0.34.2 transitivePeerDependencies: - - "@babel/core" + - '@babel/core' - babel-plugin-macros nookies@2.5.2: @@ -6045,6 +5736,12 @@ snapshots: dependencies: p-limit: 3.1.0 + p-retry@6.2.1: + dependencies: + '@types/retry': 0.12.2 + is-network-error: 1.3.0 + retry: 0.13.1 + p-try@2.2.0: {} pako@1.0.11: {} @@ -6081,6 +5778,8 @@ snapshots: picocolors: 1.1.1 source-map-js: 1.2.1 + preact@10.27.2: {} + prelude-ls@1.2.1: {} prettier-linter-helpers@1.0.0: @@ -6155,19 +5854,19 @@ snapshots: react-qr-reader@3.0.0-beta-1(react-dom@19.1.0(react@19.1.0))(react@19.1.0): dependencies: - "@zxing/browser": 0.0.7(@zxing/library@0.18.6) - "@zxing/library": 0.18.6 + '@zxing/browser': 0.0.7(@zxing/library@0.18.6) + '@zxing/library': 0.18.6 react: 19.1.0 react-dom: 19.1.0(react@19.1.0) rollup: 2.79.2 react-redux@9.2.0(@types/react@19.1.8)(react@19.1.0)(redux@5.0.1): dependencies: - "@types/use-sync-external-store": 0.0.6 + '@types/use-sync-external-store': 0.0.6 react: 19.1.0 use-sync-external-store: 1.5.0(react@19.1.0) optionalDependencies: - "@types/react": 19.1.8 + '@types/react': 19.1.8 redux: 5.0.1 react-remove-scroll-bar@2.3.8(@types/react@19.1.8)(react@19.1.0): @@ -6176,7 +5875,7 @@ snapshots: react-style-singleton: 2.2.3(@types/react@19.1.8)(react@19.1.0) tslib: 2.8.1 optionalDependencies: - "@types/react": 19.1.8 + '@types/react': 19.1.8 react-remove-scroll@2.7.1(@types/react@19.1.8)(react@19.1.0): dependencies: @@ -6187,7 +5886,7 @@ snapshots: use-callback-ref: 1.3.3(@types/react@19.1.8)(react@19.1.0) use-sidecar: 1.1.3(@types/react@19.1.8)(react@19.1.0) optionalDependencies: - "@types/react": 19.1.8 + '@types/react': 19.1.8 react-style-singleton@2.2.3(@types/react@19.1.8)(react@19.1.0): dependencies: @@ -6195,7 +5894,7 @@ snapshots: react: 19.1.0 tslib: 2.8.1 optionalDependencies: - "@types/react": 19.1.8 + '@types/react': 19.1.8 react@19.1.0: {} @@ -6251,6 +5950,8 @@ snapshots: path-parse: 1.0.7 supports-preserve-symlinks-flag: 1.0.0 + retry@0.13.1: {} + reusify@1.1.0: {} rollup@2.79.2: @@ -6322,27 +6023,27 @@ snapshots: detect-libc: 2.0.4 semver: 7.7.2 optionalDependencies: - "@img/sharp-darwin-arm64": 0.34.2 - "@img/sharp-darwin-x64": 0.34.2 - "@img/sharp-libvips-darwin-arm64": 1.1.0 - "@img/sharp-libvips-darwin-x64": 1.1.0 - "@img/sharp-libvips-linux-arm": 1.1.0 - "@img/sharp-libvips-linux-arm64": 1.1.0 - "@img/sharp-libvips-linux-ppc64": 1.1.0 - "@img/sharp-libvips-linux-s390x": 1.1.0 - "@img/sharp-libvips-linux-x64": 1.1.0 - "@img/sharp-libvips-linuxmusl-arm64": 1.1.0 - "@img/sharp-libvips-linuxmusl-x64": 1.1.0 - "@img/sharp-linux-arm": 0.34.2 - "@img/sharp-linux-arm64": 0.34.2 - "@img/sharp-linux-s390x": 0.34.2 - "@img/sharp-linux-x64": 0.34.2 - "@img/sharp-linuxmusl-arm64": 0.34.2 - "@img/sharp-linuxmusl-x64": 0.34.2 - "@img/sharp-wasm32": 0.34.2 - "@img/sharp-win32-arm64": 0.34.2 - "@img/sharp-win32-ia32": 0.34.2 - "@img/sharp-win32-x64": 0.34.2 + '@img/sharp-darwin-arm64': 0.34.2 + '@img/sharp-darwin-x64': 0.34.2 + '@img/sharp-libvips-darwin-arm64': 1.1.0 + '@img/sharp-libvips-darwin-x64': 1.1.0 + '@img/sharp-libvips-linux-arm': 1.1.0 + '@img/sharp-libvips-linux-arm64': 1.1.0 + '@img/sharp-libvips-linux-ppc64': 1.1.0 + '@img/sharp-libvips-linux-s390x': 1.1.0 + '@img/sharp-libvips-linux-x64': 1.1.0 + '@img/sharp-libvips-linuxmusl-arm64': 1.1.0 + '@img/sharp-libvips-linuxmusl-x64': 1.1.0 + '@img/sharp-linux-arm': 0.34.2 + '@img/sharp-linux-arm64': 0.34.2 + '@img/sharp-linux-s390x': 0.34.2 + '@img/sharp-linux-x64': 0.34.2 + '@img/sharp-linuxmusl-arm64': 0.34.2 + '@img/sharp-linuxmusl-x64': 0.34.2 + '@img/sharp-wasm32': 0.34.2 + '@img/sharp-win32-arm64': 0.34.2 + '@img/sharp-win32-ia32': 0.34.2 + '@img/sharp-win32-x64': 0.34.2 optional: true shebang-command@2.0.0: @@ -6481,7 +6182,7 @@ snapshots: synckit@0.11.8: dependencies: - "@pkgr/core": 0.2.7 + '@pkgr/core': 0.2.7 tailwind-merge@3.3.1: {} @@ -6491,7 +6192,7 @@ snapshots: tar@7.4.3: dependencies: - "@isaacs/fs-minipass": 4.0.1 + '@isaacs/fs-minipass': 4.0.1 chownr: 3.0.0 minipass: 7.1.2 minizlib: 3.0.2 @@ -6517,7 +6218,7 @@ snapshots: tsconfig-paths@3.15.0: dependencies: - "@types/json5": 0.0.29 + '@types/json5': 0.0.29 json5: 1.0.2 minimist: 1.2.8 strip-bom: 3.0.0 @@ -6578,25 +6279,25 @@ snapshots: dependencies: napi-postinstall: 0.3.0 optionalDependencies: - "@unrs/resolver-binding-android-arm-eabi": 1.11.0 - "@unrs/resolver-binding-android-arm64": 1.11.0 - "@unrs/resolver-binding-darwin-arm64": 1.11.0 - "@unrs/resolver-binding-darwin-x64": 1.11.0 - "@unrs/resolver-binding-freebsd-x64": 1.11.0 - "@unrs/resolver-binding-linux-arm-gnueabihf": 1.11.0 - "@unrs/resolver-binding-linux-arm-musleabihf": 1.11.0 - "@unrs/resolver-binding-linux-arm64-gnu": 1.11.0 - "@unrs/resolver-binding-linux-arm64-musl": 1.11.0 - "@unrs/resolver-binding-linux-ppc64-gnu": 1.11.0 - "@unrs/resolver-binding-linux-riscv64-gnu": 1.11.0 - "@unrs/resolver-binding-linux-riscv64-musl": 1.11.0 - "@unrs/resolver-binding-linux-s390x-gnu": 1.11.0 - "@unrs/resolver-binding-linux-x64-gnu": 1.11.0 - "@unrs/resolver-binding-linux-x64-musl": 1.11.0 - "@unrs/resolver-binding-wasm32-wasi": 1.11.0 - "@unrs/resolver-binding-win32-arm64-msvc": 1.11.0 - "@unrs/resolver-binding-win32-ia32-msvc": 1.11.0 - "@unrs/resolver-binding-win32-x64-msvc": 1.11.0 + '@unrs/resolver-binding-android-arm-eabi': 1.11.0 + '@unrs/resolver-binding-android-arm64': 1.11.0 + '@unrs/resolver-binding-darwin-arm64': 1.11.0 + '@unrs/resolver-binding-darwin-x64': 1.11.0 + '@unrs/resolver-binding-freebsd-x64': 1.11.0 + '@unrs/resolver-binding-linux-arm-gnueabihf': 1.11.0 + '@unrs/resolver-binding-linux-arm-musleabihf': 1.11.0 + '@unrs/resolver-binding-linux-arm64-gnu': 1.11.0 + '@unrs/resolver-binding-linux-arm64-musl': 1.11.0 + '@unrs/resolver-binding-linux-ppc64-gnu': 1.11.0 + '@unrs/resolver-binding-linux-riscv64-gnu': 1.11.0 + '@unrs/resolver-binding-linux-riscv64-musl': 1.11.0 + '@unrs/resolver-binding-linux-s390x-gnu': 1.11.0 + '@unrs/resolver-binding-linux-x64-gnu': 1.11.0 + '@unrs/resolver-binding-linux-x64-musl': 1.11.0 + '@unrs/resolver-binding-wasm32-wasi': 1.11.0 + '@unrs/resolver-binding-win32-arm64-msvc': 1.11.0 + '@unrs/resolver-binding-win32-ia32-msvc': 1.11.0 + '@unrs/resolver-binding-win32-x64-msvc': 1.11.0 uri-js@4.4.1: dependencies: @@ -6607,12 +6308,12 @@ snapshots: react: 19.1.0 tslib: 2.8.1 optionalDependencies: - "@types/react": 19.1.8 + '@types/react': 19.1.8 use-intl@4.3.4(react@19.1.0): dependencies: - "@formatjs/fast-memoize": 2.2.7 - "@schummar/icu-type-parser": 1.21.5 + '@formatjs/fast-memoize': 2.2.7 + '@schummar/icu-type-parser': 1.21.5 intl-messageformat: 10.7.16 react: 19.1.0 @@ -6622,7 +6323,7 @@ snapshots: react: 19.1.0 tslib: 2.8.1 optionalDependencies: - "@types/react": 19.1.8 + '@types/react': 19.1.8 use-sync-external-store@1.5.0(react@19.1.0): dependencies: @@ -6677,6 +6378,8 @@ snapshots: dependencies: isexe: 2.0.0 + wildcard@1.1.2: {} + word-wrap@1.2.5: {} wrap-ansi@6.2.0: @@ -6714,6 +6417,6 @@ snapshots: zustand@5.0.6(@types/react@19.1.8)(react@19.1.0)(use-sync-external-store@1.5.0(react@19.1.0)): optionalDependencies: - "@types/react": 19.1.8 + '@types/react': 19.1.8 react: 19.1.0 use-sync-external-store: 1.5.0(react@19.1.0) diff --git a/apps/web/scripts/prune_translations.py b/apps/web/scripts/prune_translations.py new file mode 100644 index 00000000..10d1592f --- /dev/null +++ b/apps/web/scripts/prune_translations.py @@ -0,0 +1,157 @@ +#!/usr/bin/env python3 +""" +Prune extra translation keys using en-US.json as the reference. +Removes keys that exist in target language files but not in the reference. + +Usage examples: + python3 prune_translations.py --dry-run + python3 prune_translations.py --messages-dir ../messages + python3 prune_translations.py --reference en-US.json +""" + +import json +from pathlib import Path +from typing import Dict, Any, Set, List, Tuple +import argparse + + +def load_json_file(file_path: Path) -> Dict[str, Any]: + try: + with open(file_path, 'r', encoding='utf-8') as f: + return json.load(f) + except Exception as e: + print(f"Error loading {file_path}: {e}") + return {} + + +def save_json_file(file_path: Path, data: Dict[str, Any], indent: int = 2) -> bool: + try: + with open(file_path, 'w', encoding='utf-8') as f: + json.dump(data, f, ensure_ascii=False, indent=indent, separators=(',', ': ')) + f.write('\n') + return True + except Exception as e: + print(f"Error saving {file_path}: {e}") + return False + + +def get_all_keys(data: Dict[str, Any], prefix: str = '') -> Set[str]: + keys: Set[str] = set() + for key, value in data.items(): + current_key = f"{prefix}.{key}" if prefix else key + keys.add(current_key) + if isinstance(value, dict): + keys.update(get_all_keys(value, current_key)) + return keys + + +def delete_nested_key(data: Dict[str, Any], key_path: str) -> bool: + """Delete a nested key using a dotted path. Returns True if deleted.""" + keys = key_path.split('.') + current: Any = data + for key in keys[:-1]: + if not isinstance(current, dict) or key not in current: + return False + current = current[key] + last = keys[-1] + if isinstance(current, dict) and last in current: + del current[last] + return True + return False + + +def prune_file(reference: Dict[str, Any], target: Dict[str, Any]) -> Tuple[Dict[str, Any], List[str]]: + ref_keys = get_all_keys(reference) + tgt_keys = get_all_keys(target) + extras = sorted(list(tgt_keys - ref_keys)) + + if not extras: + return target, [] + + # Work on a copy + pruned = json.loads(json.dumps(target)) + for key in extras: + delete_nested_key(pruned, key) + return pruned, extras + + +def prune_translations(messages_dir: Path, reference_file: str = 'en-US.json', dry_run: bool = False) -> int: + reference_path = messages_dir / reference_file + if not reference_path.exists(): + print(f"Reference file not found: {reference_path}") + return 1 + + print(f"Loading reference: {reference_file}") + ref = load_json_file(reference_path) + if not ref: + print("Error loading reference file") + return 1 + + files = [p for p in messages_dir.glob('*.json') if p.name != reference_file] + if not files: + print("No translation files found") + return 0 + + ref_count = len(get_all_keys(ref)) + print(f"Reference keys: {ref_count}") + print(f"Processing {len(files)} files...\n") + + total_removed = 0 + changed_files = 0 + + for p in sorted(files): + data = load_json_file(p) + if not data: + print(f"{p.name}: ❌ load error") + continue + before = len(get_all_keys(data)) + pruned, extras = prune_file(ref, data) + after = len(get_all_keys(pruned)) + if not extras: + print(f"{p.name}: ✅ no extras ({before}/{ref_count})") + continue + + print(f"{p.name}: 🧹 removing {len(extras)} extra key(s)") + # Optionally show first few extras + for k in extras[:5]: + print(f" - {k}") + if len(extras) > 5: + print(f" ... and {len(extras) - 5} more") + + total_removed += len(extras) + changed_files += 1 + if not dry_run: + if save_json_file(p, pruned): + print(f" ✅ saved ({after}/{ref_count})") + else: + print(f" ❌ save error") + else: + print(f" 📝 [DRY RUN] not saved") + print() + + print("=" * 60) + print("SUMMARY") + print("=" * 60) + print(f"Files changed: {changed_files}") + print(f"Extra keys removed: {total_removed}") + if dry_run: + print("Mode: DRY RUN") + return 0 + + +def main(): + parser = argparse.ArgumentParser(description='Prune extra translation keys using reference file') + parser.add_argument('--messages-dir', type=Path, default=Path(__file__).parent.parent / 'messages', help='Messages directory') + parser.add_argument('--reference', default='en-US.json', help='Reference filename') + parser.add_argument('--dry-run', action='store_true', help='Only show what would be removed') + args = parser.parse_args() + + if not args.messages_dir.exists(): + print(f"Directory not found: {args.messages_dir}") + return 1 + + return prune_translations(args.messages_dir, args.reference, args.dry_run) + + +if __name__ == '__main__': + exit(main()) diff --git a/apps/web/src/app/(shares)/r/[alias]/components/file-upload-section.tsx b/apps/web/src/app/(shares)/r/[alias]/components/file-upload-section.tsx index c44998fa..c47ee1a9 100644 --- a/apps/web/src/app/(shares)/r/[alias]/components/file-upload-section.tsx +++ b/apps/web/src/app/(shares)/r/[alias]/components/file-upload-section.tsx @@ -1,8 +1,7 @@ "use client"; -import { useCallback, useEffect, useState } from "react"; +import { useCallback, useState } from "react"; import { IconCheck, IconFile, IconMail, IconUpload, IconUser, IconX } from "@tabler/icons-react"; -import axios from "axios"; import { useTranslations } from "next-intl"; import { useDropzone } from "react-dropzone"; import { toast } from "sonner"; @@ -13,118 +12,103 @@ import { Input } from "@/components/ui/input"; import { Label } from "@/components/ui/label"; import { Progress } from "@/components/ui/progress"; import { Textarea } from "@/components/ui/textarea"; +import { useUppyUpload } from "@/hooks/useUppyUpload"; import { getPresignedUrlForUploadByAlias, registerFileUploadByAlias } from "@/http/endpoints"; -import { getSystemInfo } from "@/http/endpoints/app"; -import { ChunkedUploader } from "@/utils/chunked-upload"; import { formatFileSize } from "@/utils/format-file-size"; -import { FILE_STATUS, UPLOAD_CONFIG, UPLOAD_PROGRESS } from "../constants"; -import { FileUploadSectionProps, FileWithProgress } from "../types"; +import { UPLOAD_CONFIG } from "../constants"; +import { FileUploadSectionProps } from "../types"; export function FileUploadSection({ reverseShare, password, alias, onUploadSuccess }: FileUploadSectionProps) { - const [files, setFiles] = useState([]); const [uploaderName, setUploaderName] = useState(""); const [uploaderEmail, setUploaderEmail] = useState(""); const [description, setDescription] = useState(""); - const [isUploading, setIsUploading] = useState(false); - const [isS3Enabled, setIsS3Enabled] = useState(null); const t = useTranslations(); - useEffect(() => { - const fetchSystemInfo = async () => { - try { - const response = await getSystemInfo(); - setIsS3Enabled(response.data.s3Enabled); - } catch (error) { - console.warn("Failed to fetch system info, defaulting to filesystem mode:", error); - setIsS3Enabled(false); - } - }; - - fetchSystemInfo(); - }, []); - - const validateFileSize = useCallback( - (file: File): string | null => { - if (!reverseShare.maxFileSize) return null; - - if (file.size > reverseShare.maxFileSize) { - return t("reverseShares.upload.errors.fileTooLarge", { + const { addFiles, startUpload, removeFile, retryUpload, fileUploads, isUploading } = useUppyUpload({ + onValidate: async (file) => { + // Client-side validations + if (reverseShare.maxFileSize && file.size > reverseShare.maxFileSize) { + const error = t("reverseShares.upload.errors.fileTooLarge", { maxSize: formatFileSize(reverseShare.maxFileSize), }); + toast.error(error); + throw new Error(error); } - return null; - }, - [reverseShare.maxFileSize, t] - ); - - const validateFileType = useCallback( - (file: File): string | null => { - if (!reverseShare.allowedFileTypes) return null; - - const allowedTypes = reverseShare.allowedFileTypes.split(",").map((type) => type.trim().toLowerCase()); - const fileExtension = file.name.split(".").pop()?.toLowerCase(); + if (reverseShare.allowedFileTypes) { + const extension = file.name.split(".").pop()?.toLowerCase(); + const allowed = reverseShare.allowedFileTypes.split(",").map((t) => t.trim().toLowerCase()); + if (extension && !allowed.includes(extension)) { + const error = t("reverseShares.upload.errors.fileTypeNotAllowed", { + allowedTypes: reverseShare.allowedFileTypes, + }); + toast.error(error); + throw new Error(error); + } + } - if (fileExtension && !allowedTypes.includes(fileExtension)) { - return t("reverseShares.upload.errors.fileTypeNotAllowed", { - allowedTypes: reverseShare.allowedFileTypes, - }); + if (reverseShare.maxFiles) { + const totalFiles = fileUploads.length + 1 + reverseShare.currentFileCount; + if (totalFiles > reverseShare.maxFiles) { + const error = t("reverseShares.upload.errors.maxFilesExceeded", { + maxFiles: reverseShare.maxFiles, + }); + toast.error(error); + throw new Error(error); + } } - return null; }, - [reverseShare.allowedFileTypes, t] - ); - - const validateFileCount = useCallback((): string | null => { - if (!reverseShare.maxFiles) return null; - - const totalFiles = files.length + 1 + reverseShare.currentFileCount; - if (totalFiles > reverseShare.maxFiles) { - return t("reverseShares.upload.errors.maxFilesExceeded", { - maxFiles: reverseShare.maxFiles, - }); - } - return null; - }, [reverseShare.maxFiles, reverseShare.currentFileCount, files.length, t]); - - const validateFile = useCallback( - (file: File): string | null => { - return validateFileSize(file) || validateFileType(file) || validateFileCount(); + onBeforeUpload: async (file) => { + const timestamp = Date.now(); + const sanitizedFileName = file.name.replace(/[^a-zA-Z0-9.-]/g, "_"); + return `reverse-shares/${alias}/${timestamp}-${sanitizedFileName}`; }, - [validateFileSize, validateFileType, validateFileCount] - ); + getPresignedUrl: async (objectName) => { + const response = await getPresignedUrlForUploadByAlias( + alias, + { objectName }, + password ? { password } : undefined + ); + return { url: response.data.url, method: "PUT" }; + }, + onAfterUpload: async (fileId, file, objectName) => { + const fileExtension = file.name.split(".").pop() || ""; - const createFileWithProgress = (file: File): FileWithProgress => ({ - file, - progress: UPLOAD_PROGRESS.INITIAL, - status: FILE_STATUS.PENDING, - }); + await registerFileUploadByAlias( + alias, + { + name: file.name, + description: description || undefined, + extension: fileExtension, + size: file.size, + objectName, + uploaderEmail: uploaderEmail || undefined, + uploaderName: uploaderName || undefined, + }, + password ? { password } : undefined + ); + }, + onSuccess: () => { + const successCount = fileUploads.filter((u) => u.status === "success").length; - const processAcceptedFiles = useCallback( - (acceptedFiles: File[]): FileWithProgress[] => { - const validFiles: FileWithProgress[] = []; + if (successCount > 0) { + toast.success( + t("reverseShares.upload.success.countMessage", { + count: successCount, + }) + ); - for (const file of acceptedFiles) { - const validationError = validateFile(file); - if (validationError) { - toast.error(validationError); - continue; - } - validFiles.push(createFileWithProgress(file)); + onUploadSuccess?.(); } - - return validFiles; }, - [validateFile] - ); + }); const onDrop = useCallback( (acceptedFiles: File[]) => { - const newFiles = processAcceptedFiles(acceptedFiles); - setFiles((previousFiles) => [...previousFiles, ...newFiles]); + addFiles(acceptedFiles); }, - [processAcceptedFiles] + [addFiles] ); const { getRootProps, getInputProps, isDragActive } = useDropzone({ @@ -133,132 +117,8 @@ export function FileUploadSection({ reverseShare, password, alias, onUploadSucce disabled: isUploading, }); - const removeFile = (index: number) => { - setFiles((previousFiles) => previousFiles.filter((_, i) => i !== index)); - }; - - const updateFileStatus = (index: number, updates: Partial) => { - setFiles((previousFiles) => previousFiles.map((file, i) => (i === index ? { ...file, ...updates } : file))); - }; - - const generateObjectName = (fileName: string): string => { - const timestamp = Date.now(); - return `reverse-shares/${alias}/${timestamp}-${fileName}`; - }; - - const getFileExtension = (fileName: string): string => { - return fileName.split(".").pop() || ""; - }; - - const calculateUploadTimeout = (fileSize: number): number => { - const baseTimeout = 300000; - const fileSizeMB = fileSize / (1024 * 1024); - if (fileSizeMB > 500) { - const extraMB = fileSizeMB - 500; - const extraMinutes = Math.ceil(extraMB / 100); - return baseTimeout + extraMinutes * 60000; - } - - return baseTimeout; - }; - - const uploadFileToStorage = async ( - file: File, - presignedUrl: string, - onProgress?: (progress: number) => void - ): Promise => { - const shouldUseChunked = ChunkedUploader.shouldUseChunkedUpload(file.size, isS3Enabled ?? undefined); - - if (shouldUseChunked) { - const chunkSize = ChunkedUploader.calculateOptimalChunkSize(file.size); - - const result = await ChunkedUploader.uploadFile({ - file, - url: presignedUrl, - chunkSize, - isS3Enabled: isS3Enabled ?? undefined, - onProgress, - }); - - if (!result.success) { - throw new Error(result.error || "Chunked upload failed"); - } - } else { - const uploadTimeout = calculateUploadTimeout(file.size); - await axios.put(presignedUrl, file, { - headers: { - "Content-Type": file.type, - }, - timeout: uploadTimeout, - maxContentLength: Infinity, - maxBodyLength: Infinity, - onUploadProgress: (progressEvent) => { - if (onProgress && progressEvent.total) { - const progress = (progressEvent.loaded / progressEvent.total) * 100; - onProgress(Math.round(progress)); - } - }, - }); - } - }; - - const registerUploadedFile = async (file: File, objectName: string): Promise => { - const fileExtension = getFileExtension(file.name); - - await registerFileUploadByAlias( - alias, - { - name: file.name, - description: description || undefined, - extension: fileExtension, - size: file.size, - objectName, - uploaderEmail: uploaderEmail || undefined, - uploaderName: uploaderName || undefined, - }, - password ? { password } : undefined - ); - }; - - const uploadFile = async (fileWithProgress: FileWithProgress, index: number): Promise => { - const { file } = fileWithProgress; - - try { - updateFileStatus(index, { - status: FILE_STATUS.UPLOADING, - progress: UPLOAD_PROGRESS.INITIAL, - }); - - const objectName = generateObjectName(file.name); - const presignedResponse = await getPresignedUrlForUploadByAlias( - alias, - { objectName }, - password ? { password } : undefined - ); - - await uploadFileToStorage(file, presignedResponse.data.url, (progress) => { - updateFileStatus(index, { progress }); - }); - - updateFileStatus(index, { progress: UPLOAD_PROGRESS.COMPLETE }); - - await registerUploadedFile(file, objectName); - - updateFileStatus(index, { status: FILE_STATUS.SUCCESS }); - } catch (error: any) { - const errorMessage = error.response?.data?.error || t("reverseShares.upload.errors.uploadFailed"); - - updateFileStatus(index, { - status: FILE_STATUS.ERROR, - error: errorMessage, - }); - - toast.error(errorMessage); - } - }; - const validateUploadRequirements = (): boolean => { - if (files.length === 0) { + if (fileUploads.length === 0) { toast.error(t("reverseShares.upload.errors.selectAtLeastOneFile")); return false; } @@ -279,36 +139,13 @@ export function FileUploadSection({ reverseShare, password, alias, onUploadSucce return true; }; - const processAllUploads = async (): Promise => { - const uploadPromises = files.map((fileWithProgress, index) => uploadFile(fileWithProgress, index)); - - await Promise.all(uploadPromises); - - const successfulUploads = files.filter((file) => file.status === FILE_STATUS.SUCCESS); - if (successfulUploads.length > 0) { - toast.success( - t("reverseShares.upload.success.countMessage", { - count: successfulUploads.length, - }) - ); - } - }; - const handleUpload = async () => { if (!validateUploadRequirements()) return; - - setIsUploading(true); - - try { - await processAllUploads(); - } catch { - } finally { - setIsUploading(false); - } + startUpload(); }; const getCanUpload = (): boolean => { - if (files.length === 0 || isUploading) return false; + if (fileUploads.length === 0 || isUploading) return false; const nameRequired = reverseShare.nameFieldRequired === "REQUIRED"; const emailRequired = reverseShare.emailFieldRequired === "REQUIRED"; @@ -325,16 +162,8 @@ export function FileUploadSection({ reverseShare, password, alias, onUploadSucce }; const canUpload = getCanUpload(); - const allFilesProcessed = files.every( - (file) => file.status === FILE_STATUS.SUCCESS || file.status === FILE_STATUS.ERROR - ); - const hasSuccessfulUploads = files.some((file) => file.status === FILE_STATUS.SUCCESS); - - useEffect(() => { - if (allFilesProcessed && hasSuccessfulUploads && files.length > 0) { - onUploadSuccess?.(); - } - }, [allFilesProcessed, hasSuccessfulUploads, files.length, onUploadSuccess]); + const allFilesProcessed = fileUploads.every((file) => file.status === "success" || file.status === "error"); + const hasSuccessfulUploads = fileUploads.some((file) => file.status === "success"); const getDragActiveStyles = () => { if (isDragActive) { @@ -354,7 +183,7 @@ export function FileUploadSection({ reverseShare, password, alias, onUploadSucce const renderFileRestrictions = () => { const calculateRemainingFiles = (): number => { if (!reverseShare.maxFiles) return 0; - const currentTotal = reverseShare.currentFileCount + files.length; + const currentTotal = reverseShare.currentFileCount + fileUploads.length; const remaining = reverseShare.maxFiles - currentTotal; return Math.max(0, remaining); }; @@ -387,8 +216,8 @@ export function FileUploadSection({ reverseShare, password, alias, onUploadSucce ); }; - const renderFileStatusBadge = (fileWithProgress: FileWithProgress) => { - if (fileWithProgress.status === FILE_STATUS.SUCCESS) { + const renderFileStatusBadge = (fileStatus: string) => { + if (fileStatus === "success") { return ( @@ -397,51 +226,41 @@ export function FileUploadSection({ reverseShare, password, alias, onUploadSucce ); } - if (fileWithProgress.status === FILE_STATUS.ERROR) { + if (fileStatus === "error") { return {t("reverseShares.upload.fileList.statusError")}; } return null; }; - const renderFileItem = (fileWithProgress: FileWithProgress, index: number) => ( -
+ const renderFileItem = (upload: any) => ( +
-

{fileWithProgress.file.name}

-

{formatFileSize(fileWithProgress.file.size)}

- {fileWithProgress.status === FILE_STATUS.UPLOADING && ( - - )} - {fileWithProgress.status === FILE_STATUS.ERROR && ( -

{fileWithProgress.error}

- )} +

{upload.file.name}

+

{formatFileSize(upload.file.size)}

+ {upload.status === "uploading" && } + {upload.status === "error" && upload.error &&

{upload.error}

}
- {renderFileStatusBadge(fileWithProgress)} - {fileWithProgress.status === FILE_STATUS.PENDING && ( - )} - {fileWithProgress.status === FILE_STATUS.ERROR && ( + {upload.status === "error" && (
-
@@ -463,10 +282,10 @@ export function FileUploadSection({ reverseShare, password, alias, onUploadSucce {renderFileRestrictions()}
- {files.length > 0 && ( + {fileUploads.length > 0 && (

{t("reverseShares.upload.fileList.title")}

- {files.map(renderFileItem)} + {fileUploads.map(renderFileItem)}
)} @@ -528,7 +347,7 @@ export function FileUploadSection({ reverseShare, password, alias, onUploadSucce {allFilesProcessed && hasSuccessfulUploads && ( diff --git a/apps/web/src/app/(shares)/reverse-shares/components/received-files-modal.tsx b/apps/web/src/app/(shares)/reverse-shares/components/received-files-modal.tsx index 4eea8ebb..eafdc1e9 100644 --- a/apps/web/src/app/(shares)/reverse-shares/components/received-files-modal.tsx +++ b/apps/web/src/app/(shares)/reverse-shares/components/received-files-modal.tsx @@ -41,10 +41,10 @@ import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@ import { copyReverseShareFileToUserFiles, deleteReverseShareFile, + downloadReverseShareFile, updateReverseShareFile, } from "@/http/endpoints/reverse-shares"; import type { ReverseShareFile } from "@/http/endpoints/reverse-shares/types"; -import { bulkDownloadWithQueue, downloadReverseShareWithQueue } from "@/utils/download-queue-utils"; import { getFileIcon } from "@/utils/file-icons"; import { truncateFileName } from "@/utils/file-utils"; import { ReverseShare } from "../hooks/use-reverse-shares"; @@ -471,13 +471,21 @@ export function ReceivedFilesModal({ const handleDownload = async (file: ReverseShareFile) => { try { - await downloadReverseShareWithQueue(file.id, file.name, { - onComplete: () => toast.success(t("reverseShares.modals.receivedFiles.downloadSuccess")), - onFail: () => toast.error(t("reverseShares.modals.receivedFiles.downloadError")), - }); + const loadingToast = toast.loading(t("reverseShares.modals.receivedFiles.downloading") || "Downloading..."); + const response = await downloadReverseShareFile(file.id); + + const link = document.createElement("a"); + link.href = response.data.url; + link.download = file.name; + document.body.appendChild(link); + link.click(); + document.body.removeChild(link); + + toast.dismiss(loadingToast); + toast.success(t("reverseShares.modals.receivedFiles.downloadSuccess")); } catch (error) { console.error("Download error:", error); - // Error already handled in downloadReverseShareWithQueue + toast.error(t("reverseShares.modals.receivedFiles.downloadError")); } }; @@ -600,25 +608,28 @@ export function ReceivedFilesModal({ if (selectedFileObjects.length === 0) return; try { - const zipName = `${reverseShare.name || t("reverseShares.defaultLinkName")}_files.zip`; - - toast.promise( - bulkDownloadWithQueue( - selectedFileObjects.map((file) => ({ - name: file.name, - id: file.id, - isReverseShare: true, - })), - zipName - ).then(() => { - setSelectedFiles(new Set()); - }), - { - loading: t("shareManager.creatingZip"), - success: t("shareManager.zipDownloadSuccess"), - error: t("shareManager.zipDownloadError"), + const loadingToast = toast.loading(t("shareManager.creatingZip")); + + try { + // Download files individually + for (const file of selectedFileObjects) { + const response = await downloadReverseShareFile(file.id); + const link = document.createElement("a"); + link.href = response.data.url; + link.download = file.name; + document.body.appendChild(link); + link.click(); + document.body.removeChild(link); } - ); + + toast.dismiss(loadingToast); + toast.success(t("shareManager.zipDownloadSuccess")); + setSelectedFiles(new Set()); + } catch (error) { + toast.dismiss(loadingToast); + toast.error(t("shareManager.zipDownloadError")); + throw error; + } } catch (error) { console.error("Error creating ZIP:", error); } diff --git a/apps/web/src/app/(shares)/reverse-shares/components/received-files-section.tsx b/apps/web/src/app/(shares)/reverse-shares/components/received-files-section.tsx index ad5d4940..f1bcd7fa 100644 --- a/apps/web/src/app/(shares)/reverse-shares/components/received-files-section.tsx +++ b/apps/web/src/app/(shares)/reverse-shares/components/received-files-section.tsx @@ -6,9 +6,8 @@ import { useTranslations } from "next-intl"; import { toast } from "sonner"; import { Button } from "@/components/ui/button"; -import { deleteReverseShareFile } from "@/http/endpoints/reverse-shares"; +import { deleteReverseShareFile, downloadReverseShareFile } from "@/http/endpoints/reverse-shares"; import type { ReverseShareFile } from "@/http/endpoints/reverse-shares/types"; -import { downloadReverseShareWithQueue } from "@/utils/download-queue-utils"; import { getFileIcon } from "@/utils/file-icons"; import { ReverseShareFilePreviewModal } from "./reverse-share-file-preview-modal"; @@ -56,13 +55,21 @@ export function ReceivedFilesSection({ files, onFileDeleted }: ReceivedFilesSect const handleDownload = async (file: ReverseShareFile) => { try { - await downloadReverseShareWithQueue(file.id, file.name, { - onComplete: () => toast.success(t("reverseShares.modals.details.downloadSuccess")), - onFail: () => toast.error(t("reverseShares.modals.details.downloadError")), - }); + const loadingToast = toast.loading(t("reverseShares.modals.details.downloading") || "Downloading..."); + const response = await downloadReverseShareFile(file.id); + + const link = document.createElement("a"); + link.href = response.data.url; + link.download = file.name; + document.body.appendChild(link); + link.click(); + document.body.removeChild(link); + + toast.dismiss(loadingToast); + toast.success(t("reverseShares.modals.details.downloadSuccess")); } catch (error) { console.error("Download error:", error); - // Error already handled in downloadReverseShareWithQueue + toast.error(t("reverseShares.modals.details.downloadError")); } }; diff --git a/apps/web/src/app/(shares)/s/[alias]/components/files-table.tsx b/apps/web/src/app/(shares)/s/[alias]/components/files-table.tsx index aab128a2..35c41238 100644 --- a/apps/web/src/app/(shares)/s/[alias]/components/files-table.tsx +++ b/apps/web/src/app/(shares)/s/[alias]/components/files-table.tsx @@ -152,10 +152,10 @@ export function ShareFilesTable({ variant="ghost" className="h-8 w-8 hover:bg-muted" onClick={() => handleFolderClick(item.id)} - title="Open folder" + title={t("files.openFolder")} > - Open folder + {t("files.openFolder")} )} +
+ +
+
+ +
+ ); + } + + return ( +
+
+ +
+ +
+ +
+ +
+

{t("registerWithInvite.title")}

+

{t("registerWithInvite.description")}

+
+ +
+
+
+ + + {errors.firstName &&

{errors.firstName.message}

} +
+ +
+ + + {errors.lastName &&

{errors.lastName.message}

} +
+
+ +
+ + + {errors.username &&

{errors.username.message}

} +
+ +
+ + + {errors.email &&

{errors.email.message}

} +
+ +
+ +
+ + +
+ {errors.password &&

{errors.password.message}

} +
+ +
+ +
+ value === password || t("registerWithInvite.validation.passwordsMatch"), + })} + /> + +
+ {errors.confirmPassword &&

{errors.confirmPassword.message}

} +
+ + +
+
+
+
+ +
+ ); +} diff --git a/apps/web/src/app/users-management/components/generate-invite-link-modal.tsx b/apps/web/src/app/users-management/components/generate-invite-link-modal.tsx new file mode 100644 index 00000000..8136161a --- /dev/null +++ b/apps/web/src/app/users-management/components/generate-invite-link-modal.tsx @@ -0,0 +1,106 @@ +"use client"; + +import { useState } from "react"; +import { IconCheck, IconCopy, IconLink } from "@tabler/icons-react"; +import { useTranslations } from "next-intl"; +import { toast } from "sonner"; + +import { Button } from "@/components/ui/button"; +import { Dialog, DialogContent, DialogDescription, DialogHeader, DialogTitle } from "@/components/ui/dialog"; +import { Input } from "@/components/ui/input"; +import { Label } from "@/components/ui/label"; +import { generateInviteToken } from "@/http/endpoints/invite"; + +interface GenerateInviteLinkModalProps { + isOpen: boolean; + onClose: () => void; +} + +export function GenerateInviteLinkModal({ isOpen, onClose }: GenerateInviteLinkModalProps) { + const t = useTranslations(); + const [isGenerating, setIsGenerating] = useState(false); + const [inviteUrl, setInviteUrl] = useState(null); + const [copied, setCopied] = useState(false); + + const handleGenerate = async () => { + setIsGenerating(true); + try { + const response = await generateInviteToken(); + + const inviteUrl = `${window.location.origin}/register-with-invite/${response.token}`; + + setInviteUrl(inviteUrl); + toast.success(t("users.invite.generated")); + } catch (error) { + console.error("Failed to generate invite token:", error); + toast.error(t("users.invite.errors.generateFailed")); + } finally { + setIsGenerating(false); + } + }; + + const handleCopy = async () => { + if (!inviteUrl) return; + + try { + await navigator.clipboard.writeText(inviteUrl); + setCopied(true); + toast.success(t("users.invite.linkCopied")); + setTimeout(() => setCopied(false), 2000); + } catch (error) { + console.error("Failed to copy:", error); + toast.error("Failed to copy link"); + } + }; + + const handleClose = () => { + setInviteUrl(null); + setCopied(false); + onClose(); + }; + + return ( + + + + + + {t("users.invite.title")} + + {t("users.invite.description")} + + +
+ {!inviteUrl ? ( + + ) : ( +
+
+

{t("users.invite.linkReady")}

+

{t("users.invite.linkReadyDescription")}

+
+ +
+ + +
+

{t("users.invite.expiresIn")}

+
+
+ +
+ +
+
+ )} +
+
+
+ ); +} diff --git a/apps/web/src/app/users-management/components/users-header.tsx b/apps/web/src/app/users-management/components/users-header.tsx index 4b537f3a..ee254fe7 100644 --- a/apps/web/src/app/users-management/components/users-header.tsx +++ b/apps/web/src/app/users-management/components/users-header.tsx @@ -1,5 +1,5 @@ import Link from "next/link"; -import { IconLayoutDashboard, IconUserPlus, IconUsers } from "@tabler/icons-react"; +import { IconLayoutDashboard, IconLink, IconUserPlus, IconUsers } from "@tabler/icons-react"; import { useTranslations } from "next-intl"; import { @@ -13,7 +13,7 @@ import { Button } from "@/components/ui/button"; import { Separator } from "@/components/ui/separator"; import { UsersHeaderProps } from "../types"; -export function UsersHeader({ onCreateUser }: UsersHeaderProps) { +export function UsersHeader({ onCreateUser, onGenerateInvite }: UsersHeaderProps) { const t = useTranslations(); return ( @@ -23,10 +23,16 @@ export function UsersHeader({ onCreateUser }: UsersHeaderProps) {

{t("users.header.title")}

- +
+ + +
diff --git a/apps/web/src/app/users-management/page.tsx b/apps/web/src/app/users-management/page.tsx index 3346e507..6d65a118 100644 --- a/apps/web/src/app/users-management/page.tsx +++ b/apps/web/src/app/users-management/page.tsx @@ -1,9 +1,12 @@ "use client"; +import { useState } from "react"; + import { ProtectedRoute } from "@/components/auth/protected-route"; import { LoadingScreen } from "@/components/layout/loading-screen"; import { Navbar } from "@/components/layout/navbar"; import { DefaultFooter } from "@/components/ui/default-footer"; +import { GenerateInviteLinkModal } from "./components/generate-invite-link-modal"; import { UserManagementModals } from "./components/user-management-modals"; import { UsersHeader } from "./components/users-header"; import { UsersTable } from "./components/users-table"; @@ -26,6 +29,8 @@ export default function AdminAreaPage() { formMethods, } = useUserManagement(); + const [isInviteModalOpen, setIsInviteModalOpen] = useState(false); + if (isLoading) { return ; } @@ -36,7 +41,7 @@ export default function AdminAreaPage() {
- + setIsInviteModalOpen(true)} /> + + setIsInviteModalOpen(false)} />
); diff --git a/apps/web/src/app/users-management/types/index.ts b/apps/web/src/app/users-management/types/index.ts index 96024f47..d11f44b9 100644 --- a/apps/web/src/app/users-management/types/index.ts +++ b/apps/web/src/app/users-management/types/index.ts @@ -55,6 +55,7 @@ export interface UserStatusModalProps { export interface UsersHeaderProps { onCreateUser: () => void; + onGenerateInvite: () => void; } export interface AuthUser { diff --git a/apps/web/src/components/auth/paths/public-paths.ts b/apps/web/src/components/auth/paths/public-paths.ts new file mode 100644 index 00000000..4793fe6c --- /dev/null +++ b/apps/web/src/components/auth/paths/public-paths.ts @@ -0,0 +1,10 @@ +export const publicPaths = [ + "/login", + "/forgot-password", + "/reset-password", + "/auth/callback", + "/auth/oidc/callback", + "/register-with-invite", + "/s/", + "/r/", +]; diff --git a/apps/web/src/components/auth/paths/unahthenticated-only-paths.ts b/apps/web/src/components/auth/paths/unahthenticated-only-paths.ts new file mode 100644 index 00000000..7bf8cdeb --- /dev/null +++ b/apps/web/src/components/auth/paths/unahthenticated-only-paths.ts @@ -0,0 +1,8 @@ +export const unauthenticatedOnlyPaths = [ + "/login", + "/forgot-password", + "/reset-password", + "/auth/callback", + "/auth/oidc/callback", + "/register-with-invite", +]; diff --git a/apps/web/src/components/auth/redirect-handler.tsx b/apps/web/src/components/auth/redirect-handler.tsx index 035cffc7..24f826c8 100644 --- a/apps/web/src/components/auth/redirect-handler.tsx +++ b/apps/web/src/components/auth/redirect-handler.tsx @@ -3,6 +3,8 @@ import { useEffect } from "react"; import { usePathname, useRouter } from "next/navigation"; +import { publicPaths } from "@/components/auth/paths/public-paths"; +import { unauthenticatedOnlyPaths } from "@/components/auth/paths/unahthenticated-only-paths"; import { LoadingScreen } from "@/components/layout/loading-screen"; import { useAuth } from "@/contexts/auth-context"; @@ -10,23 +12,6 @@ interface RedirectHandlerProps { children: React.ReactNode; } -const publicPaths = [ - "/login", - "/forgot-password", - "/reset-password", - "/auth/callback", - "/auth/oidc/callback", - "/s/", - "/r/", -]; - -const unauthenticatedOnlyPaths = [ - "/login", - "/forgot-password", - "/reset-password", - "/auth/callback", - "/auth/oidc/callback", -]; const homePaths = ["/"]; export function RedirectHandler({ children }: RedirectHandlerProps) { diff --git a/apps/web/src/components/download-queue-indicator.tsx b/apps/web/src/components/download-queue-indicator.tsx deleted file mode 100644 index 246803cb..00000000 --- a/apps/web/src/components/download-queue-indicator.tsx +++ /dev/null @@ -1,268 +0,0 @@ -"use client"; - -import { useEffect, useState } from "react"; -import { - IconAlertCircle, - IconBell, - IconBellOff, - IconClock, - IconDownload, - IconLoader2, - IconX, -} from "@tabler/icons-react"; -import { useTranslations } from "next-intl"; - -import { Badge } from "@/components/ui/badge"; -import { Button } from "@/components/ui/button"; -import { Progress } from "@/components/ui/progress"; -import { useDownloadQueue } from "@/hooks/use-download-queue"; -import { usePushNotifications } from "@/hooks/use-push-notifications"; -import { formatFileSize } from "@/utils/format-file-size"; - -interface PendingDownload { - downloadId: string; - fileName: string; - objectName: string; - startTime: number; - status: "pending" | "queued" | "downloading" | "completed" | "failed"; -} - -interface DownloadQueueIndicatorProps { - pendingDownloads?: PendingDownload[]; - onCancelDownload?: (downloadId: string) => void; - className?: string; -} - -export function DownloadQueueIndicator({ - pendingDownloads = [], - onCancelDownload, - className = "", -}: DownloadQueueIndicatorProps) { - const t = useTranslations(); - - const shouldAutoRefresh = pendingDownloads.length > 0; - const { queueStatus, refreshQueue, cancelDownload, getEstimatedWaitTime } = useDownloadQueue(shouldAutoRefresh); - const notifications = usePushNotifications(); - const [isOpen, setIsOpen] = useState(false); - - useEffect(() => { - if (pendingDownloads.length > 0 || (queueStatus && queueStatus.queueLength > 0)) { - setIsOpen(true); - } - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [pendingDownloads.length, queueStatus?.queueLength]); - - const totalDownloads = pendingDownloads.length + (queueStatus?.queueLength || 0); - const activeDownloads = queueStatus?.activeDownloads || 0; - - if (totalDownloads === 0 && activeDownloads === 0) { - return null; - } - - const getStatusIcon = (status: string) => { - switch (status) { - case "pending": - return ; - case "queued": - return ; - case "downloading": - return ; - case "completed": - return ; - case "failed": - return ; - default: - return ; - } - }; - - const getStatusText = (status: string) => { - switch (status) { - case "pending": - return t("downloadQueue.status.pending"); - case "queued": - return t("downloadQueue.status.queued"); - case "downloading": - return t("downloadQueue.status.downloading"); - case "completed": - return t("downloadQueue.status.completed"); - case "failed": - return t("downloadQueue.status.failed"); - default: - return status; - } - }; - - return ( -
-
- - - {isOpen && ( -
-
-
-

Download Manager

-
- {notifications.isSupported && ( - - )} - -
-
- - {queueStatus && ( -
-
- Active: - - {activeDownloads}/{queueStatus.maxConcurrent} - -
-
- Queued: - - {queueStatus.queueLength}/{queueStatus.maxQueueSize} - -
- {queueStatus.maxConcurrent > 0 && ( -
- -

- {Math.round((activeDownloads / queueStatus.maxConcurrent) * 100)}% capacity -

-
- )} -
- )} -
- -
- {pendingDownloads.map((download) => ( -
-
-
{getStatusIcon(download.status)}
-
-

{download.fileName}

-

{getStatusText(download.status)}

-
-
- - {(download.status === "pending" || download.status === "queued") && onCancelDownload && ( - - )} -
- ))} - - {(queueStatus?.queuedDownloads || []).map((download) => { - const waitTime = getEstimatedWaitTime(download.downloadId); - - return ( -
-
-
- -
-
-

- {download.fileName || t("downloadQueue.indicator.unknownFile")} -

-
-
- #{download.position} in queue - {download.fileSize && ( - • {formatFileSize(download.fileSize)} - )} -
- {waitTime &&

~{waitTime} remaining

} -
-
-
- - -
- ); - })} - - {totalDownloads === 0 && ( -
- -

No active downloads

-
- )} -
- - {queueStatus && queueStatus.queueLength > 0 && ( -
- -
- )} -
- )} -
-
- ); -} diff --git a/apps/web/src/components/files/files-view.tsx b/apps/web/src/components/files/files-view.tsx index 38493077..21604e80 100644 --- a/apps/web/src/components/files/files-view.tsx +++ b/apps/web/src/components/files/files-view.tsx @@ -37,7 +37,8 @@ interface Folder { interface FilesViewProps { files: File[]; - onPreview: (file: File) => void; + folders?: Folder[]; + onPreview?: (file: File) => void; onRename: (file: File) => void; onUpdateName: (fileId: string, newName: string) => void; onUpdateDescription: (fileId: string, newDescription: string) => void; diff --git a/apps/web/src/components/general/file-selector.tsx b/apps/web/src/components/general/file-selector.tsx index ff63a342..3ce72268 100644 --- a/apps/web/src/components/general/file-selector.tsx +++ b/apps/web/src/components/general/file-selector.tsx @@ -395,7 +395,9 @@ export function FileSelector({
-
{shareFiles.length + shareFolders.length} items selected
+
+ {t("fileSelector.itemsSelected", { count: shareFiles.length + shareFolders.length })} +

{formatFileSize(upload.file.size)}

- {upload.status === UploadStatus.UPLOADING && ( + {upload.status === "uploading" && (

{upload.progress}%

)} - {upload.status === UploadStatus.ERROR && upload.error && ( + {upload.status === "error" && upload.error && (

{upload.error}

)}
- {upload.status === UploadStatus.ERROR ? ( + {upload.status === "error" ? (
- ) : upload.status === UploadStatus.SUCCESS ? null : ( + ) : upload.status === "success" ? null : ( diff --git a/apps/web/src/components/general/language-switcher.tsx b/apps/web/src/components/general/language-switcher.tsx index 2b6c0da4..29e9090a 100644 --- a/apps/web/src/components/general/language-switcher.tsx +++ b/apps/web/src/components/general/language-switcher.tsx @@ -30,12 +30,20 @@ const languages = { "zh-CN": "中文 (Chinese)", "ja-JP": "日本語 (Japanese)", "ko-KR": "한국어 (Korean)", + "th-TH": "ไทย (Thai)", + "vi-VN": "Tiếng Việt (Vietnamese)", + "uk-UA": "Українська (Ukrainian)", + "fa-IR": "فارسی (Persian)", + "sv-SE": "Svenska (Swedish)", + "id-ID": "Bahasa Indonesia (Indonesian)", + "el-GR": "Ελληνικά (Greek)", + "he-IL": "עברית (Hebrew)", }; const COOKIE_LANG_KEY = "NEXT_LOCALE"; const COOKIE_MAX_AGE = 365 * 24 * 60 * 60; -const RTL_LANGUAGES = ["ar-SA"]; +const RTL_LANGUAGES = ["ar-SA", "fa-IR", "he-IL"]; export function LanguageSwitcher() { const locale = useLocale(); diff --git a/apps/web/src/components/layout/file-manager-layout.tsx b/apps/web/src/components/layout/file-manager-layout.tsx index 36817831..7f1c964b 100644 --- a/apps/web/src/components/layout/file-manager-layout.tsx +++ b/apps/web/src/components/layout/file-manager-layout.tsx @@ -3,7 +3,6 @@ import Link from "next/link"; import { IconLayoutDashboard } from "@tabler/icons-react"; import { useTranslations } from "next-intl"; -import { DownloadQueueIndicator } from "@/components/download-queue-indicator"; import { Navbar } from "@/components/layout/navbar"; import { Breadcrumb, @@ -21,14 +20,6 @@ interface FileManagerLayoutProps { icon: ReactNode; breadcrumbLabel?: string; showBreadcrumb?: boolean; - pendingDownloads?: Array<{ - downloadId: string; - fileName: string; - objectName: string; - startTime: number; - status: "pending" | "queued" | "downloading" | "completed" | "failed"; - }>; - onCancelDownload?: (downloadId: string) => void; } export function FileManagerLayout({ @@ -37,8 +28,6 @@ export function FileManagerLayout({ icon, breadcrumbLabel, showBreadcrumb = true, - pendingDownloads = [], - onCancelDownload, }: FileManagerLayoutProps) { const t = useTranslations(); @@ -79,8 +68,6 @@ export function FileManagerLayout({
- - ); } diff --git a/apps/web/src/components/modals/create-share-modal.tsx b/apps/web/src/components/modals/create-share-modal.tsx index 0bca9cfe..27f737ae 100644 --- a/apps/web/src/components/modals/create-share-modal.tsx +++ b/apps/web/src/components/modals/create-share-modal.tsx @@ -91,12 +91,12 @@ export function CreateShareModal({ isOpen, onClose, onSuccess, getAllFilesAndFol const handleSubmit = async () => { if (!formData.name.trim()) { - toast.error("Share name is required"); + toast.error(t("createShare.errors.nameRequired")); return; } if (selectedItems.length === 0) { - toast.error("Please select at least one file or folder"); + toast.error(t("createShare.errors.selectItems")); return; } @@ -216,7 +216,7 @@ export function CreateShareModal({ isOpen, onClose, onSuccess, getAllFilesAndFol type="password" value={formData.password} onChange={(e) => updateFormData("password", e.target.value)} - placeholder="Enter password" + placeholder={t("createShare.passwordPlaceholder")} /> )} @@ -271,9 +271,9 @@ export function CreateShareModal({ isOpen, onClose, onSuccess, getAllFilesAndFol
{selectedCount > 0 ? ( - {selectedCount} items selected + {t("createShare.itemsSelected", { count: selectedCount })} ) : ( - Select files and folders to share + {t("createShare.selectItemsPrompt")} )}
diff --git a/apps/web/src/components/modals/file-preview-modal.tsx b/apps/web/src/components/modals/file-preview-modal.tsx index 72bfdad5..02c39461 100644 --- a/apps/web/src/components/modals/file-preview-modal.tsx +++ b/apps/web/src/components/modals/file-preview-modal.tsx @@ -74,12 +74,12 @@ export function FilePreviewModal({ description={file.description} onDownload={previewState.handleDownload} /> - {isImage && previewState.previewUrl && !previewState.isLoading && file.id && ( + {!isReverseShare && isImage && previewState.previewUrl && !previewState.isLoading && file.id && (
)} - {(isVideo || isAudio) && !previewState.isLoading && file.id && ( + {!isReverseShare && (isVideo || isAudio) && !previewState.isLoading && file.id && (
diff --git a/apps/web/src/components/modals/previews/video-preview.tsx b/apps/web/src/components/modals/previews/video-preview.tsx index 87a518bc..703f3d29 100644 --- a/apps/web/src/components/modals/previews/video-preview.tsx +++ b/apps/web/src/components/modals/previews/video-preview.tsx @@ -1,5 +1,7 @@ import { useTranslations } from "next-intl"; +import { AspectRatio } from "@/components/ui/aspect-ratio"; + interface VideoPreviewProps { src: string; } @@ -8,13 +10,11 @@ export function VideoPreview({ src }: VideoPreviewProps) { const t = useTranslations(); return ( -
-
- -
-
+ + + ); } diff --git a/apps/web/src/components/modals/share-actions-modals.tsx b/apps/web/src/components/modals/share-actions-modals.tsx index 7fbe57b6..4f719bc7 100644 --- a/apps/web/src/components/modals/share-actions-modals.tsx +++ b/apps/web/src/components/modals/share-actions-modals.tsx @@ -343,7 +343,7 @@ export function ShareActionsModals({ {t("shareActions.manageFilesTitle")} - Select files and folders to include in this share + {t("shareActions.manageFilesDescription")}
@@ -362,7 +362,9 @@ export function ShareActionsModals({ {/* Selection Count */}
- {manageFilesSelectedItems.length > 0 && {manageFilesSelectedItems.length} items selected} + {manageFilesSelectedItems.length > 0 && ( + {t("shareActions.itemsSelected", { count: manageFilesSelectedItems.length })} + )}
{/* File Tree */} diff --git a/apps/web/src/components/modals/upload-file-modal.tsx b/apps/web/src/components/modals/upload-file-modal.tsx index 74eb843d..9a783e88 100644 --- a/apps/web/src/components/modals/upload-file-modal.tsx +++ b/apps/web/src/components/modals/upload-file-modal.tsx @@ -2,16 +2,14 @@ import { useEffect, useRef, useState } from "react"; import { IconAlertTriangle, IconCheck, IconCloudUpload, IconLoader, IconTrash, IconX } from "@tabler/icons-react"; -import axios from "axios"; import { useTranslations } from "next-intl"; import { toast } from "sonner"; import { Button } from "@/components/ui/button"; import { Dialog, DialogContent, DialogFooter, DialogHeader, DialogTitle } from "@/components/ui/dialog"; import { Progress } from "@/components/ui/progress"; +import { useUppyUpload } from "@/hooks/useUppyUpload"; import { checkFile, getFilePresignedUrl, registerFile } from "@/http/endpoints"; -import { getSystemInfo } from "@/http/endpoints/app"; -import { ChunkedUploader } from "@/utils/chunked-upload"; import { getFileIcon } from "@/utils/file-icons"; import { generateSafeFileName } from "@/utils/file-utils"; import { formatFileSize } from "@/utils/format-file-size"; @@ -24,25 +22,6 @@ interface UploadFileModalProps { currentFolderId?: string; } -enum UploadStatus { - PENDING = "pending", - UPLOADING = "uploading", - SUCCESS = "success", - ERROR = "error", - CANCELLED = "cancelled", -} - -interface FileUpload { - id: string; - file: File; - status: UploadStatus; - progress: number; - error?: string; - abortController?: AbortController; - objectName?: string; - previewUrl?: string; -} - interface ConfirmationModalProps { isOpen: boolean; onConfirm: () => void; @@ -85,81 +64,125 @@ function ConfirmationModal({ isOpen, onConfirm, onCancel, uploadsInProgress }: C export function UploadFileModal({ isOpen, onClose, onSuccess, currentFolderId }: UploadFileModalProps) { const t = useTranslations(); - const [fileUploads, setFileUploads] = useState([]); const [isDragOver, setIsDragOver] = useState(false); const [showConfirmation, setShowConfirmation] = useState(false); - const [hasShownSuccessToast, setHasShownSuccessToast] = useState(false); - const [isS3Enabled, setIsS3Enabled] = useState(null); + const hasShownSuccessToastRef = useRef(false); const fileInputRef = useRef(null); - useEffect(() => { - const fetchSystemInfo = async () => { - try { - const response = await getSystemInfo(); - setIsS3Enabled(response.data.s3Enabled); - } catch (error) { - console.warn("Failed to fetch system info, defaulting to filesystem mode:", error); - setIsS3Enabled(false); - } - }; - - fetchSystemInfo(); - }, []); + const { addFiles, startUpload, cancelUpload, retryUpload, removeFile, clearAll, fileUploads, isUploading } = + useUppyUpload({ + onValidate: async (file) => { + const fileName = file.name; + const extension = fileName.split(".").pop() || ""; + const safeObjectName = generateSafeFileName(fileName); + + try { + await checkFile({ + name: fileName, + objectName: safeObjectName, + size: file.size, + extension: extension, + folderId: currentFolderId, + }); + } catch (error) { + console.error("File check failed:", error); + const errorData = getErrorData(error); + let errorMessage = t("uploadFile.error"); + + if (errorData.code === "fileSizeExceeded") { + errorMessage = t(`uploadFile.${errorData.code}`, { maxsizemb: errorData.details || "0" }); + } else if (errorData.code === "insufficientStorage") { + errorMessage = t(`uploadFile.${errorData.code}`, { availablespace: errorData.details || "0" }); + } else if (errorData.code) { + errorMessage = t(`uploadFile.${errorData.code}`); + } - useEffect(() => { - return () => { - fileUploads.forEach((upload) => { - if (upload.previewUrl) { - URL.revokeObjectURL(upload.previewUrl); + toast.error(errorMessage); + throw new Error(errorMessage); } - }); - }; - }, [fileUploads]); + }, + onBeforeUpload: async (file) => { + const safeObjectName = generateSafeFileName(file.name); + return safeObjectName; + }, + getPresignedUrl: async (objectName, extension) => { + // Extract filename without extension (backend will add it) + const filenameWithoutExt = objectName.replace(`.${extension}`, ""); + + const response = await getFilePresignedUrl({ + filename: filenameWithoutExt, + extension, + }); - const generateFileId = () => { - return Date.now().toString() + Math.random().toString(36).substr(2, 9); - }; + // IMPORTANT: Use the objectName returned by backend, not the one we generated! + // The backend generates: userId/timestamp-random-filename.extension + const actualObjectName = response.data.objectName; - const createFileUpload = (file: File): FileUpload => { - const id = generateFileId(); - let previewUrl: string | undefined; + return { url: response.data.url, method: "PUT", actualObjectName }; + }, + onAfterUpload: async (fileId, file, objectName) => { + const fileName = file.name; + const extension = fileName.split(".").pop() || ""; - if (file.type.startsWith("image/")) { - try { - previewUrl = URL.createObjectURL(file); - } catch (error) { - console.warn("Failed to create preview URL:", error); - } + await registerFile({ + name: fileName, + objectName, + size: file.size, + extension, + folderId: currentFolderId, + }); + }, + }); + + // Monitor upload completion and call onSuccess when all done + useEffect(() => { + // Only process if we have uploads and they're not currently uploading + if (fileUploads.length === 0 || isUploading || hasShownSuccessToastRef.current) { + return; } - return { - id, - file, - status: UploadStatus.PENDING, - progress: 0, - previewUrl, - }; - }; + const successCount = fileUploads.filter((u) => u.status === "success").length; + const errorCount = fileUploads.filter((u) => u.status === "error").length; + const pendingCount = fileUploads.filter((u) => u.status === "pending" || u.status === "uploading").length; - const handleFilesSelect = (files: FileList | null) => { - if (!files) return; + // All uploads are done (no pending/uploading) + if (pendingCount === 0 && (successCount > 0 || errorCount > 0)) { + hasShownSuccessToastRef.current = true; - const newUploads = Array.from(files).map(createFileUpload); - setFileUploads((prev) => [...prev, ...newUploads]); - setHasShownSuccessToast(false); + if (successCount > 0) { + if (errorCount > 0) { + toast.error(t("uploadFile.partialSuccess", { success: successCount, error: errorCount })); + } else { + toast.success(t("uploadFile.allSuccess", { count: successCount })); + } - if (newUploads.length > 0) { - toast.info(t("uploadFile.filesQueued", { count: newUploads.length })); + // Call parent's onSuccess to refresh the file list + // Add delay to ensure backend has processed everything + setTimeout(() => { + onSuccess?.(); + }, 300); + } } - }; + }, [fileUploads, isUploading, onSuccess, t]); + + // Reset toast flag and clear uploads when modal closes + useEffect(() => { + if (!isOpen) { + hasShownSuccessToastRef.current = false; + clearAll(); + } + }, [isOpen, clearAll]); + // Handle file input change const handleFileInputChange = (event: React.ChangeEvent) => { - handleFilesSelect(event.target.files); - if (event.target) { - event.target.value = ""; + if (event.target.files) { + addFiles(Array.from(event.target.files)); + hasShownSuccessToastRef.current = false; // Reset when adding new files + event.target.value = ""; // Reset input } }; + // Handle drag and drop const handleDragOver = (event: React.DragEvent) => { event.preventDefault(); setIsDragOver(true); @@ -177,7 +200,8 @@ export function UploadFileModal({ isOpen, onClose, onSuccess, currentFolderId }: const files = event.dataTransfer.files; if (files.length > 0) { - handleFilesSelect(files); + addFiles(Array.from(files)); + hasShownSuccessToastRef.current = false; // Reset when adding new files } }; @@ -186,251 +210,40 @@ export function UploadFileModal({ isOpen, onClose, onSuccess, currentFolderId }: return ; }; - const getStatusIcon = (status: UploadStatus) => { + const getStatusIcon = (status: string) => { switch (status) { - case UploadStatus.UPLOADING: + case "uploading": return ; - case UploadStatus.SUCCESS: + case "success": return ; - case UploadStatus.ERROR: + case "error": return ; - case UploadStatus.CANCELLED: + case "cancelled": return ; default: return null; } }; - const removeFile = (fileId: string) => { - setFileUploads((prev) => { - const upload = prev.find((u) => u.id === fileId); - if (upload?.previewUrl) { - URL.revokeObjectURL(upload.previewUrl); - } - return prev.filter((u) => u.id !== fileId); - }); - }; - - const cancelUpload = async (fileId: string) => { - const upload = fileUploads.find((u) => u.id === fileId); - if (!upload) return; - - if (upload.abortController) { - upload.abortController.abort(); - } - - if (upload.objectName && upload.status === UploadStatus.UPLOADING) { - try { - } catch (error) { - console.error("Failed to delete uploaded file:", error); - } - } - - setFileUploads((prev) => - prev.map((u) => (u.id === fileId ? { ...u, status: UploadStatus.CANCELLED, abortController: undefined } : u)) - ); - }; - - const calculateUploadTimeout = (fileSize: number): number => { - const baseTimeout = 300000; - const fileSizeMB = fileSize / (1024 * 1024); - if (fileSizeMB > 500) { - const extraMB = fileSizeMB - 500; - const extraMinutes = Math.ceil(extraMB / 100); - return baseTimeout + extraMinutes * 60000; - } - - return baseTimeout; - }; - - const uploadFile = async (fileUpload: FileUpload) => { - const { file, id } = fileUpload; - - try { - const fileName = file.name; - const extension = fileName.split(".").pop() || ""; - const safeObjectName = generateSafeFileName(fileName); - - try { - await checkFile({ - name: fileName, - objectName: safeObjectName, - size: file.size, - extension: extension, - folderId: currentFolderId, - }); - } catch (error) { - console.error("File check failed:", error); - const errorData = getErrorData(error); - let errorMessage = t("uploadFile.error"); - - if (errorData.code === "fileSizeExceeded") { - errorMessage = t(`uploadFile.${errorData.code}`, { maxsizemb: errorData.details || "0" }); - } else if (errorData.code === "insufficientStorage") { - errorMessage = t(`uploadFile.${errorData.code}`, { availablespace: errorData.details || "0" }); - } else if (errorData.code) { - errorMessage = t(`uploadFile.${errorData.code}`); - } - - setFileUploads((prev) => - prev.map((u) => (u.id === id ? { ...u, status: UploadStatus.ERROR, error: errorMessage } : u)) - ); - return; - } - - setFileUploads((prev) => - prev.map((u) => (u.id === id ? { ...u, status: UploadStatus.UPLOADING, progress: 0 } : u)) - ); - - const presignedResponse = await getFilePresignedUrl({ - filename: safeObjectName.replace(`.${extension}`, ""), - extension: extension, - }); - - const { url, objectName } = presignedResponse.data; - - setFileUploads((prev) => prev.map((u) => (u.id === id ? { ...u, objectName } : u))); - - const abortController = new AbortController(); - setFileUploads((prev) => prev.map((u) => (u.id === id ? { ...u, abortController } : u))); - - const shouldUseChunked = ChunkedUploader.shouldUseChunkedUpload(file.size, isS3Enabled ?? undefined); - - if (shouldUseChunked) { - const chunkSize = ChunkedUploader.calculateOptimalChunkSize(file.size); - - const result = await ChunkedUploader.uploadFile({ - file, - url, - chunkSize, - signal: abortController.signal, - isS3Enabled: isS3Enabled ?? undefined, - onProgress: (progress) => { - setFileUploads((prev) => prev.map((u) => (u.id === id ? { ...u, progress } : u))); - }, - }); - - if (!result.success) { - throw new Error(result.error || "Chunked upload failed"); - } - - const finalObjectName = result.finalObjectName || objectName; - - await registerFile({ - name: fileName, - objectName: finalObjectName, - size: file.size, - extension: extension, - folderId: currentFolderId, - }); - } else { - const uploadTimeout = calculateUploadTimeout(file.size); - await axios.put(url, file, { - headers: { - "Content-Type": file.type, - }, - signal: abortController.signal, - timeout: uploadTimeout, - maxContentLength: Infinity, - maxBodyLength: Infinity, - onUploadProgress: (progressEvent) => { - const progress = (progressEvent.loaded / (progressEvent.total || file.size)) * 100; - setFileUploads((prev) => prev.map((u) => (u.id === id ? { ...u, progress: Math.round(progress) } : u))); - }, - }); - - await registerFile({ - name: fileName, - objectName: objectName, - size: file.size, - extension: extension, - folderId: currentFolderId, - }); - } - - setFileUploads((prev) => - prev.map((u) => - u.id === id ? { ...u, status: UploadStatus.SUCCESS, progress: 100, abortController: undefined } : u - ) - ); - } catch (error: any) { - if (error.name === "AbortError" || error.code === "ERR_CANCELED") { - return; - } - - console.error("Upload failed:", error); - const errorData = getErrorData(error); - let errorMessage = t("uploadFile.error"); - - if (errorData.code && errorData.code !== "error") { - errorMessage = t(`uploadFile.${errorData.code}`); - } - - setFileUploads((prev) => - prev.map((u) => - u.id === id ? { ...u, status: UploadStatus.ERROR, error: errorMessage, abortController: undefined } : u - ) - ); - } - }; - - const startUploads = async () => { - const pendingUploads = fileUploads.filter((u) => u.status === UploadStatus.PENDING); - - setHasShownSuccessToast(false); - - const uploadPromises = pendingUploads.map((upload) => uploadFile(upload)); - await Promise.all(uploadPromises); - - setTimeout(() => { - setFileUploads((currentUploads) => { - const allComplete = currentUploads.every( - (u) => - u.status === UploadStatus.SUCCESS || u.status === UploadStatus.ERROR || u.status === UploadStatus.CANCELLED - ); - - if (allComplete && !hasShownSuccessToast) { - const successCount = currentUploads.filter((u) => u.status === UploadStatus.SUCCESS).length; - const errorCount = currentUploads.filter((u) => u.status === UploadStatus.ERROR).length; - - if (successCount > 0) { - if (errorCount > 0) { - toast.error(t("uploadFile.partialSuccess", { success: successCount, error: errorCount })); - } - setHasShownSuccessToast(true); - - setTimeout(() => onSuccess?.(), 0); - } - } - - return currentUploads; - }); - }, 100); - }; - const handleConfirmClose = () => { + // Cancel all uploads fileUploads.forEach((upload) => { - if (upload.status === UploadStatus.UPLOADING && upload.abortController) { - upload.abortController.abort(); + if (upload.status === "uploading") { + cancelUpload(upload.id); } - }); - - fileUploads.forEach((upload) => { + // Revoke preview URLs if (upload.previewUrl) { URL.revokeObjectURL(upload.previewUrl); } }); - setFileUploads([]); setShowConfirmation(false); - setHasShownSuccessToast(false); onClose(); }; + // Prevent closing while uploading const handleClose = () => { - const uploadsInProgress = fileUploads.filter((u) => u.status === UploadStatus.UPLOADING).length; - - if (uploadsInProgress > 0) { + if (isUploading) { setShowConfirmation(true); } else { handleConfirmClose(); @@ -443,12 +256,9 @@ export function UploadFileModal({ isOpen, onClose, onSuccess, currentFolderId }: const allUploadsComplete = fileUploads.length > 0 && - fileUploads.every( - (u) => u.status === UploadStatus.SUCCESS || u.status === UploadStatus.ERROR || u.status === UploadStatus.CANCELLED - ); + fileUploads.every((u) => u.status === "success" || u.status === "error" || u.status === "cancelled"); - const hasUploadsInProgress = fileUploads.some((u) => u.status === UploadStatus.UPLOADING); - const hasPendingUploads = fileUploads.some((u) => u.status === UploadStatus.PENDING); + const hasPendingUploads = fileUploads.some((u) => u.status === "pending"); return ( <> @@ -504,20 +314,20 @@ export function UploadFileModal({ isOpen, onClose, onSuccess, currentFolderId }:

{formatFileSize(upload.file.size)}

- {upload.status === UploadStatus.UPLOADING && ( + {upload.status === "uploading" && (

{upload.progress}%

)} - {upload.status === UploadStatus.ERROR && upload.error && ( + {upload.status === "error" && upload.error && (

{upload.error}

)}
- {upload.status === UploadStatus.UPLOADING ? ( + {upload.status === "uploading" ? ( - ) : upload.status === UploadStatus.SUCCESS ? null : upload.status === UploadStatus.ERROR ? ( + ) : upload.status === "success" ? null : upload.status === "error" ? (
{!allUploadsComplete && ( -
-
- {/* Render folders first */} - {folders.map((folder) => { - const isSelected = selectedFolders.has(folder.id); - - return ( -
onNavigateToFolder?.(folder.id)} - > -
- { - const newSelected = new Set(selectedFolders); - if (checked) { - newSelected.add(folder.id); - } else { - newSelected.delete(folder.id); - } - setSelectedFolders(newSelected); - }} - aria-label={`Select folder ${folder.name}`} - className="bg-background border-2" - onClick={(e) => e.stopPropagation()} - /> -
- -
- {isShareMode ? ( - onDownloadFolder && ( - - ) - ) : ( - - - - - - {onRenameFolder && ( - { - e.stopPropagation(); - onRenameFolder(folder); - }} - > - - {t("filesTable.actions.edit")} - - )} - {onMoveFolder && ( - { - e.stopPropagation(); - onMoveFolder(folder); - }} - > - - {t("common.move")} - - )} - {onShareFolder && ( - { - e.stopPropagation(); - onShareFolder(folder); - }} - > - - {t("filesTable.actions.share")} - - )} - {onDownloadFolder && ( - { - e.stopPropagation(); - onDownloadFolder(folder.id, folder.name); - }} - > - - {t("filesTable.actions.download")} - - )} - {onDeleteFolder && ( - { - e.stopPropagation(); - onDeleteFolder(folder); + {t("filesTable.actions.download")} + + )} + {onDeleteFolder && ( + { + e.stopPropagation(); + onDeleteFolder(folder); + }} + className="cursor-pointer py-2 text-destructive focus:text-destructive" + variant="destructive" + > + + {t("filesTable.actions.delete")} + + )} + + ); + + return ( + + +
onNavigateToFolder?.(folder.id)} + draggable + onDragStart={(e) => { + e.stopPropagation(); + handleDragStart(e, { id: folder.id, type: "folder", name: folder.name }); + }} + onDragEnd={handleDragEnd} + onDragOver={(e) => { + e.stopPropagation(); + handleDragOver(e, { id: folder.id, type: "folder", name: folder.name }); + }} + onDragLeave={handleDragLeave} + onDrop={(e) => { + e.stopPropagation(); + handleDrop(e, { id: folder.id, type: "folder", name: folder.name }); + }} + onContextMenu={(e) => { + e.stopPropagation(); + }} + > +
+ { + const newSelected = new Set(selectedFolders); + if (checked) { + newSelected.add(folder.id); + } else { + newSelected.delete(folder.id); + } + setSelectedFolders(newSelected); }} - className="cursor-pointer py-2 text-destructive focus:text-destructive" - > - - {t("filesTable.actions.delete")} - - )} - - - )} -
- -
-
- -
- -
-

- {folder.name} -

- {folder.description && ( -

- {folder.description} -

+ aria-label={`Select folder ${folder.name}`} + className="bg-background border-2" + onClick={(e) => e.stopPropagation()} + /> +
+ +
+ {isShareMode ? ( + onDownloadFolder && ( + + ) + ) : ( + + + + + + {onRenameFolder && ( + { + e.stopPropagation(); + onRenameFolder(folder); + }} + > + + {t("filesTable.actions.edit")} + + )} + {onMoveFolder && ( + { + e.stopPropagation(); + onMoveFolder(folder); + }} + > + + {t("common.move")} + + )} + {onShareFolder && ( + { + e.stopPropagation(); + onShareFolder(folder); + }} + > + + {t("filesTable.actions.share")} + + )} + {onDownloadFolder && ( + { + e.stopPropagation(); + onDownloadFolder(folder.id, folder.name); + }} + > + + {t("filesTable.actions.download")} + + )} + {onDeleteFolder && ( + { + e.stopPropagation(); + onDeleteFolder(folder); + }} + className="cursor-pointer py-2 text-destructive focus:text-destructive" + > + + {t("filesTable.actions.delete")} + + )} + + + )} +
+ +
+
+ +
+
+

+ {folder.name} +

+ {folder.description && ( +

+ {folder.description} +

+ )} +
+

{folder.totalSize ? formatFileSize(Number(folder.totalSize)) : "—"}

+

{formatDateTime(folder.createdAt)}

+
+
+
+
+ + {folderContextMenu} + + ); + })} + + {/* Render files */} + {files.map((file) => { + const { icon: FileIcon, color } = getFileIcon(file.name); + const isSelected = selectedFiles.has(file.id); + const isImage = isImageFile(file.name); + const previewUrl = filePreviewUrls[file.id]; + const isDraggedOver = draggedItem?.id === file.id; + + // Check if this file is part of the dragged items (optimized with memoized Set) + const isBeingDragged = draggedItemIds.has(file.id); + const isAnySelectedItemDragged = isDragging && isSelected && draggedItems.length > 1; + + const fileContextMenu = !isShareMode && ( + + { + e.stopPropagation(); + onPreview?.(file); + }} + > + + {t("filesTable.actions.preview")} + + {onRename && ( + { + e.stopPropagation(); + onRename?.(file); + }} + > + + {t("filesTable.actions.edit")} + )} -
-

{folder.totalSize ? formatFileSize(Number(folder.totalSize)) : "—"}

-

{formatDateTime(folder.createdAt)}

-
-
-
-
- ); - })} - - {/* Render files */} - {files.map((file) => { - const { icon: FileIcon, color } = getFileIcon(file.name); - const isSelected = selectedFiles.has(file.id); - const isImage = isImageFile(file.name); - const previewUrl = filePreviewUrls[file.id]; - - return ( -
{ - if ( - (e.target as HTMLElement).closest(".checkbox-wrapper") || - (e.target as HTMLElement).closest("button") || - (e.target as HTMLElement).closest('[role="menuitem"]') - ) { - return; - } - if (onPreview) { - onPreview(file); - } - }} - > -
- { - handleSelectFile({ stopPropagation: () => {} } as React.MouseEvent, file.id, checked); - }} - aria-label={t("filesTable.selectFile", { fileName: file.name })} - className="bg-background border-2" - /> -
- -
- {isShareMode ? ( - - ) : ( - - - - - - { - e.stopPropagation(); - onPreview?.(file); - }} - > - - {t("filesTable.actions.preview")} - - {onRename && ( - { - e.stopPropagation(); - onRename?.(file); - }} - > - - {t("filesTable.actions.edit")} - - )} - { - e.stopPropagation(); - onDownload(file.objectName, file.name); - }} - > - - {t("filesTable.actions.download")} - - {onShare && ( - { - e.stopPropagation(); - onShare?.(file); - }} - > - - {t("filesTable.actions.share")} - - )} - {onMoveFile && ( - { - e.stopPropagation(); - onMoveFile?.(file); - }} - > - - {t("common.move")} - - )} - {onDelete && ( - { - e.stopPropagation(); - onDelete?.(file); - }} - className="cursor-pointer py-2 text-destructive focus:text-destructive" - > - - {t("filesTable.actions.delete")} - - )} - - - )} -
- -
-
- {isImage && previewUrl ? ( - {file.name} - ) : ( - + {t("filesTable.actions.download")} + + {onShare && ( + { + e.stopPropagation(); + onShare?.(file); + }} + > + + {t("filesTable.actions.share")} + )} -
- -
-

- {file.name} -

- {file.description && ( -

- {file.description} -

+ {onMoveFile && ( + { + e.stopPropagation(); + onMoveFile?.(file); + }} + > + + {t("common.move")} + )} -
-

{formatFileSize(file.size)}

-

{formatDateTime(file.createdAt)}

-
-
-
-
- ); - })} -
+ {onDelete && ( + { + e.stopPropagation(); + onDelete?.(file); + }} + className="cursor-pointer py-2 text-destructive focus:text-destructive" + variant="destructive" + > + + {t("filesTable.actions.delete")} + + )} + + ); + + return ( + + +
{ + if ( + (e.target as HTMLElement).closest(".checkbox-wrapper") || + (e.target as HTMLElement).closest("button") || + (e.target as HTMLElement).closest('[role="menuitem"]') + ) { + return; + } + if (onPreview) { + onPreview(file); + } + }} + draggable + onDragStart={(e) => { + e.stopPropagation(); + handleDragStart(e, { id: file.id, type: "file", name: file.name }); + }} + onDragEnd={handleDragEnd} + onContextMenu={(e) => { + e.stopPropagation(); + }} + > +
+ { + handleSelectFile({ stopPropagation: () => {} } as React.MouseEvent, file.id, checked); + }} + aria-label={t("filesTable.selectFile", { fileName: file.name })} + className="bg-background border-2" + /> +
+ +
+ {isShareMode ? ( + + ) : ( + + + + + + { + e.stopPropagation(); + onPreview?.(file); + }} + > + + {t("filesTable.actions.preview")} + + {onRename && ( + { + e.stopPropagation(); + onRename?.(file); + }} + > + + {t("filesTable.actions.edit")} + + )} + { + e.stopPropagation(); + onDownload(file.objectName, file.name); + }} + > + + {t("filesTable.actions.download")} + + {onShare && ( + { + e.stopPropagation(); + onShare?.(file); + }} + > + + {t("filesTable.actions.share")} + + )} + {onMoveFile && ( + { + e.stopPropagation(); + onMoveFile?.(file); + }} + > + + {t("common.move")} + + )} + {onDelete && ( + { + e.stopPropagation(); + onDelete?.(file); + }} + className="cursor-pointer py-2 text-destructive focus:text-destructive" + > + + {t("filesTable.actions.delete")} + + )} + + + )} +
+ +
+
+ {isImage && previewUrl ? ( + {file.name} + ) : ( + + )} +
+ +
+

+ {file.name} +

+ {file.description && ( +

+ {file.description} +

+ )} +
+

{formatFileSize(file.size)}

+

{formatDateTime(file.createdAt)}

+
+
+
+
+
+ {fileContextMenu} +
+ ); + })} +
+ + {!isShareMode && (onCreateFolder || onUpload) && ( + + {onCreateFolder && ( + + + {t("contextMenu.newFolder")} + + )} + {onUpload && ( + + + {t("contextMenu.uploadFile")} + + )} + + )} + ); } diff --git a/apps/web/src/components/ui/breadcrumb.tsx b/apps/web/src/components/ui/breadcrumb.tsx index ce08888f..9edb0891 100644 --- a/apps/web/src/components/ui/breadcrumb.tsx +++ b/apps/web/src/components/ui/breadcrumb.tsx @@ -13,7 +13,7 @@ function BreadcrumbList({ className, ...props }: React.ComponentProps<"ol">) {
    ) { } function BreadcrumbItem({ className, ...props }: React.ComponentProps<"li">) { - return
  1. ; + return
  2. ; } function BreadcrumbLink({ diff --git a/apps/web/src/components/ui/context-menu.tsx b/apps/web/src/components/ui/context-menu.tsx new file mode 100644 index 00000000..f024a9ce --- /dev/null +++ b/apps/web/src/components/ui/context-menu.tsx @@ -0,0 +1,252 @@ +"use client" + +import * as React from "react" +import * as ContextMenuPrimitive from "@radix-ui/react-context-menu" +import { CheckIcon, ChevronRightIcon, CircleIcon } from "lucide-react" + +import { cn } from "@/lib/utils" + +function ContextMenu({ + ...props +}: React.ComponentProps) { + return +} + +function ContextMenuTrigger({ + ...props +}: React.ComponentProps) { + return ( + + ) +} + +function ContextMenuGroup({ + ...props +}: React.ComponentProps) { + return ( + + ) +} + +function ContextMenuPortal({ + ...props +}: React.ComponentProps) { + return ( + + ) +} + +function ContextMenuSub({ + ...props +}: React.ComponentProps) { + return +} + +function ContextMenuRadioGroup({ + ...props +}: React.ComponentProps) { + return ( + + ) +} + +function ContextMenuSubTrigger({ + className, + inset, + children, + ...props +}: React.ComponentProps & { + inset?: boolean +}) { + return ( + + {children} + + + ) +} + +function ContextMenuSubContent({ + className, + ...props +}: React.ComponentProps) { + return ( + + ) +} + +function ContextMenuContent({ + className, + ...props +}: React.ComponentProps) { + return ( + + + + ) +} + +function ContextMenuItem({ + className, + inset, + variant = "default", + ...props +}: React.ComponentProps & { + inset?: boolean + variant?: "default" | "destructive" +}) { + return ( + + ) +} + +function ContextMenuCheckboxItem({ + className, + children, + checked, + ...props +}: React.ComponentProps) { + return ( + + + + + + + {children} + + ) +} + +function ContextMenuRadioItem({ + className, + children, + ...props +}: React.ComponentProps) { + return ( + + + + + + + {children} + + ) +} + +function ContextMenuLabel({ + className, + inset, + ...props +}: React.ComponentProps & { + inset?: boolean +}) { + return ( + + ) +} + +function ContextMenuSeparator({ + className, + ...props +}: React.ComponentProps) { + return ( + + ) +} + +function ContextMenuShortcut({ + className, + ...props +}: React.ComponentProps<"span">) { + return ( + + ) +} + +export { + ContextMenu, + ContextMenuTrigger, + ContextMenuContent, + ContextMenuItem, + ContextMenuCheckboxItem, + ContextMenuRadioItem, + ContextMenuLabel, + ContextMenuSeparator, + ContextMenuShortcut, + ContextMenuGroup, + ContextMenuPortal, + ContextMenuSub, + ContextMenuSubContent, + ContextMenuSubTrigger, + ContextMenuRadioGroup, +} diff --git a/apps/web/src/config/upload-config.ts b/apps/web/src/config/upload-config.ts new file mode 100644 index 00000000..346c6f6e --- /dev/null +++ b/apps/web/src/config/upload-config.ts @@ -0,0 +1,62 @@ +/** + * Upload configuration for the application + * Centralizes all upload-related settings + */ +export const UPLOAD_CONFIG = { + /** + * Size threshold for multipart upload (100MB) + * Files >= this size will use multipart upload + * Files < this size will use simple PUT upload + */ + MULTIPART_THRESHOLD: 50 * 1024 * 1024, + + /** + * No file size limit (managed by backend/user quota) + */ + MAX_FILE_SIZE: null, + + /** + * No file count limit (configurable per context) + */ + MAX_FILES: null, + + /** + * Allow all file types (restrictions are context-specific) + */ + ALLOWED_TYPES: null, + + /** + * Retry configuration + */ + MAX_RETRIES: 5, + RETRY_DELAYS: [1000, 3000, 5000, 10000, 15000], // ms + + /** + * Concurrent uploads (unlimited/maximum possible) + * 0 = unlimited + */ + MAX_CONCURRENT: 0, + + /** + * Progress update debounce (100ms for performance) + */ + PROGRESS_DEBOUNCE: 100, + + /** + * "Many files" threshold for UI optimizations + */ + MANY_FILES_THRESHOLD: 100, + + /** + * Toast auto-dismiss duration + */ + TOAST_DURATION: 3000, // ms + + /** + * Preview generation + */ + PREVIEW_MAX_SIZE: null, // No limit + PREVIEW_TYPES: ["image/*"], +} as const; + +export type UploadConfig = typeof UPLOAD_CONFIG; diff --git a/apps/web/src/hooks/use-download-queue.ts b/apps/web/src/hooks/use-download-queue.ts deleted file mode 100644 index 8b900ff4..00000000 --- a/apps/web/src/hooks/use-download-queue.ts +++ /dev/null @@ -1,144 +0,0 @@ -import { useCallback, useEffect, useState } from "react"; -import { useTranslations } from "next-intl"; -import { toast } from "sonner"; - -import { - cancelQueuedDownload, - getDownloadQueueStatus, - type DownloadQueueStatus, -} from "@/http/endpoints/download-queue"; - -export interface DownloadQueueHook { - queueStatus: DownloadQueueStatus | null; - isLoading: boolean; - error: string | null; - refreshQueue: () => Promise; - cancelDownload: (downloadId: string) => Promise; - getQueuePosition: (downloadId: string) => number | null; - isDownloadQueued: (downloadId: string) => boolean; - getEstimatedWaitTime: (downloadId: string) => string | null; -} - -export function useDownloadQueue(autoRefresh = true, initialIntervalMs = 3000) { - const t = useTranslations(); - const [queueStatus, setQueueStatus] = useState(null); - const [isLoading, setIsLoading] = useState(false); - const [error, setError] = useState(null); - const [currentInterval, setCurrentInterval] = useState(initialIntervalMs); - const [noActivityCount, setNoActivityCount] = useState(0); - - const refreshQueue = useCallback(async () => { - try { - setIsLoading(true); - setError(null); - const response = await getDownloadQueueStatus(); - const newStatus = response.data; - - const hasActivity = newStatus.activeDownloads > 0 || newStatus.queueLength > 0; - const previousActivity = (queueStatus?.activeDownloads || 0) > 0 || (queueStatus?.queueLength || 0) > 0; - const statusChanged = JSON.stringify(queueStatus) !== JSON.stringify(newStatus); - - if (!hasActivity && !previousActivity && !statusChanged) { - setNoActivityCount((prev) => prev + 1); - } else { - setNoActivityCount(0); - setCurrentInterval(initialIntervalMs); - } - - setQueueStatus(newStatus); - } catch (err: any) { - const errorMessage = err?.response?.data?.error || err?.message || "Failed to fetch queue status"; - setError(errorMessage); - console.error("Error fetching download queue status:", err); - } finally { - setIsLoading(false); - } - }, [queueStatus, initialIntervalMs]); - - const cancelDownload = useCallback( - async (downloadId: string) => { - try { - await cancelQueuedDownload(downloadId); - toast.success(t("downloadQueue.cancelSuccess")); - await refreshQueue(); - } catch (err: any) { - const errorMessage = err?.response?.data?.error || err?.message || "Failed to cancel download"; - toast.error(t("downloadQueue.cancelError", { error: errorMessage })); - console.error("Error cancelling download:", err); - } - }, - [refreshQueue, t] - ); - - const getQueuePosition = useCallback( - (downloadId: string): number | null => { - if (!queueStatus) return null; - const download = queueStatus.queuedDownloads.find((d) => d.downloadId === downloadId); - return download?.position || null; - }, - [queueStatus] - ); - - const isDownloadQueued = useCallback( - (downloadId: string): boolean => { - if (!queueStatus) return false; - return queueStatus.queuedDownloads.some((d) => d.downloadId === downloadId); - }, - [queueStatus] - ); - - const getEstimatedWaitTime = useCallback( - (downloadId: string): string | null => { - if (!queueStatus) return null; - - const download = queueStatus.queuedDownloads.find((d) => d.downloadId === downloadId); - if (!download) return null; - - const waitTimeMs = download.waitTime; - const waitTimeSeconds = Math.floor(waitTimeMs / 1000); - - if (waitTimeSeconds < 60) { - return t("downloadQueue.waitTime.seconds", { seconds: waitTimeSeconds }); - } else if (waitTimeSeconds < 3600) { - const minutes = Math.floor(waitTimeSeconds / 60); - return t("downloadQueue.waitTime.minutes", { minutes }); - } else { - const hours = Math.floor(waitTimeSeconds / 3600); - const minutes = Math.floor((waitTimeSeconds % 3600) / 60); - return t("downloadQueue.waitTime.hoursMinutes", { hours, minutes }); - } - }, - [queueStatus, t] - ); - - useEffect(() => { - if (!autoRefresh) return; - - let actualInterval = currentInterval; - - if (noActivityCount > 5) { - console.log("[DOWNLOAD QUEUE] No activity detected, stopping polling"); - return; - } else if (noActivityCount > 2) { - actualInterval = 10000; - setCurrentInterval(10000); - } - - refreshQueue(); - - const interval = setInterval(refreshQueue, actualInterval); - - return () => clearInterval(interval); - }, [autoRefresh, refreshQueue, currentInterval, noActivityCount]); - - return { - queueStatus, - isLoading, - error, - refreshQueue, - cancelDownload, - getQueuePosition, - isDownloadQueued, - getEstimatedWaitTime, - }; -} diff --git a/apps/web/src/hooks/use-drag-drop.ts b/apps/web/src/hooks/use-drag-drop.ts new file mode 100644 index 00000000..3682863f --- /dev/null +++ b/apps/web/src/hooks/use-drag-drop.ts @@ -0,0 +1,260 @@ +import { useCallback, useRef, useState } from "react"; +import { useTranslations } from "next-intl"; +import { toast } from "sonner"; + +import { moveFile } from "@/http/endpoints/files"; +import { moveFolder } from "@/http/endpoints/folders"; + +interface DragItem { + id: string; + type: "file" | "folder"; + name: string; +} + +interface DropTarget { + id: string; + type: "folder"; + name: string; +} + +interface UseDragDropProps { + onRefresh?: () => Promise; + onImmediateUpdate?: (itemId: string, itemType: "file" | "folder", newParentId: string | null) => void; + selectedFiles?: Set; + selectedFolders?: Set; + files?: any[]; + folders?: any[]; +} + +export function useDragDrop({ + onRefresh, + onImmediateUpdate, + selectedFiles, + selectedFolders, + files = [], + folders = [], +}: UseDragDropProps) { + const t = useTranslations(); + const [draggedItem, setDraggedItem] = useState(null); + const [draggedItems, setDraggedItems] = useState([]); + const [dragOverTarget, setDragOverTarget] = useState(null); + const [isDragging, setIsDragging] = useState(false); + const dragCounter = useRef(0); + + const createDragGhost = useCallback((items: DragItem[]) => { + const ghost = document.createElement("div"); + ghost.style.position = "absolute"; + ghost.style.top = "-9999px"; + ghost.style.left = "-9999px"; + ghost.style.display = "flex"; + ghost.style.flexDirection = "column"; + ghost.style.gap = "4px"; + ghost.style.padding = "12px"; + + // Dark mode support + const isDarkMode = document.documentElement.classList.contains("dark"); + ghost.style.backgroundColor = isDarkMode ? "rgba(10, 10, 10, 0.95)" : "rgba(255, 255, 255, 0.95)"; + ghost.style.border = "2px solid hsl(var(--primary))"; + ghost.style.borderRadius = "8px"; + ghost.style.boxShadow = isDarkMode + ? "0 8px 16px rgba(0, 0, 0, 0.4), 0 0 0 1px rgba(255, 255, 255, 0.1)" + : "0 8px 16px rgba(0, 0, 0, 0.15)"; + ghost.style.minWidth = "200px"; + ghost.style.maxWidth = "250px"; + ghost.style.zIndex = "9999"; + ghost.style.backdropFilter = "blur(12px)"; + + const itemsToShow = items.slice(0, 3); + const remaining = items.length - itemsToShow.length; + + itemsToShow.forEach((item) => { + const itemDiv = document.createElement("div"); + itemDiv.style.display = "flex"; + itemDiv.style.alignItems = "center"; + itemDiv.style.gap = "8px"; + itemDiv.style.padding = "6px 8px"; + itemDiv.style.backgroundColor = isDarkMode ? "rgba(255, 255, 255, 0.05)" : "rgba(0, 0, 0, 0.03)"; + itemDiv.style.borderRadius = "4px"; + itemDiv.style.fontSize = "13px"; + itemDiv.style.fontWeight = "500"; + itemDiv.style.color = "hsl(var(--foreground))"; + itemDiv.style.transition = "all 0.2s ease"; + itemDiv.style.boxShadow = isDarkMode ? "0 1px 3px rgba(0, 0, 0, 0.3)" : "0 1px 3px rgba(0, 0, 0, 0.1)"; + + const icon = document.createElement("span"); + icon.textContent = item.type === "folder" ? "📁" : "📄"; + icon.style.fontSize = "16px"; + + const name = document.createElement("span"); + name.textContent = item.name; + name.style.overflow = "hidden"; + name.style.textOverflow = "ellipsis"; + name.style.whiteSpace = "nowrap"; + + itemDiv.appendChild(icon); + itemDiv.appendChild(name); + ghost.appendChild(itemDiv); + }); + + if (remaining > 0) { + const moreDiv = document.createElement("div"); + moreDiv.style.padding = "4px 8px"; + moreDiv.style.fontSize = "12px"; + moreDiv.style.fontWeight = "600"; + moreDiv.style.color = "hsl(var(--primary))"; + moreDiv.style.textAlign = "center"; + moreDiv.style.backgroundColor = isDarkMode ? "rgba(255, 255, 255, 0.03)" : "rgba(0, 0, 0, 0.02)"; + moreDiv.style.borderRadius = "4px"; + moreDiv.style.marginTop = "2px"; + moreDiv.textContent = `+${remaining} more`; + ghost.appendChild(moreDiv); + } + + document.body.appendChild(ghost); + return ghost; + }, []); + + const handleDragStart = useCallback( + (e: React.DragEvent, item: DragItem) => { + e.dataTransfer.effectAllowed = "move"; + e.dataTransfer.setData("application/x-move-item", "true"); + + let itemsToDrag: DragItem[] = [item]; + + // Check if item is in selection + const isFileSelected = selectedFiles?.has(item.id) && item.type === "file"; + const isFolderSelected = selectedFolders?.has(item.id) && item.type === "folder"; + + if (isFileSelected || isFolderSelected) { + // Drag all selected items + const selectedFileItems: DragItem[] = + files + ?.filter((f) => selectedFiles?.has(f.id)) + .map((f) => ({ id: f.id, type: "file" as const, name: f.name })) || []; + const selectedFolderItems: DragItem[] = + folders + ?.filter((f) => selectedFolders?.has(f.id)) + .map((f) => ({ id: f.id, type: "folder" as const, name: f.name })) || []; + + itemsToDrag = [...selectedFolderItems, ...selectedFileItems]; + } + + e.dataTransfer.setData("text/plain", JSON.stringify(itemsToDrag)); + setDraggedItem(item); + setDraggedItems(itemsToDrag); + setIsDragging(true); + + // Create and set custom drag ghost (for single or multiple items) + const ghost = createDragGhost(itemsToDrag); + e.dataTransfer.setDragImage(ghost, 20, 20); + + // Clean up ghost after drag starts + setTimeout(() => { + document.body.removeChild(ghost); + }, 0); + }, + [selectedFiles, selectedFolders, files, folders, createDragGhost] + ); + + const handleDragEnd = useCallback(() => { + setDraggedItem(null); + setDraggedItems([]); + setDragOverTarget(null); + setIsDragging(false); + dragCounter.current = 0; + }, []); + + const handleDragOver = useCallback((e: React.DragEvent, target: DropTarget) => { + e.preventDefault(); + e.stopPropagation(); + e.dataTransfer.dropEffect = "move"; + setDragOverTarget(target); + }, []); + + const handleDragLeave = useCallback((e: React.DragEvent) => { + // Only clear drag over target if we're leaving the drop zone entirely + if (!e.currentTarget.contains(e.relatedTarget as Node)) { + setDragOverTarget(null); + } + }, []); + + const handleDrop = useCallback( + async (e: React.DragEvent, target: DropTarget) => { + e.preventDefault(); + e.stopPropagation(); + + try { + const itemData = e.dataTransfer.getData("text/plain"); + const items: DragItem[] = JSON.parse(itemData); + + // Filter out invalid moves + const validItems = items.filter((item) => { + // Prevent dropping on itself + if (item.id === target.id) { + return false; + } + // Prevent dropping folder into itself + if (item.type === "folder" && item.id === target.id) { + return false; + } + return true; + }); + + if (validItems.length === 0) { + toast.error(t("files.errors.cannotMoveHere")); + return; + } + + // Update UI immediately for all items (optimistic update) + if (onImmediateUpdate) { + validItems.forEach((item) => { + onImmediateUpdate(item.id, item.type, target.id); + }); + } + + // Move all items in parallel + const movePromises = validItems.map((item) => { + if (item.type === "file") { + return moveFile(item.id, { folderId: target.id }); + } else if (item.type === "folder") { + return moveFolder(item.id, { parentId: target.id }); + } + return Promise.resolve(); + }); + + await Promise.all(movePromises); + + // Show success message + if (validItems.length === 1) { + toast.success( + `${validItems[0].type === "folder" ? "Folder" : "File"} "${validItems[0].name}" moved to "${target.name}"` + ); + } else { + toast.success(`${validItems.length} items moved to "${target.name}"`); + } + } catch (error) { + console.error("Error moving items:", error); + toast.error(t("files.errors.moveItemsFailed")); + // Refresh to restore state on error + if (onRefresh) { + await onRefresh(); + } + } finally { + handleDragEnd(); + } + }, + [onImmediateUpdate, t, onRefresh, handleDragEnd] + ); + + return { + draggedItem, + draggedItems, + dragOverTarget, + isDragging, + handleDragStart, + handleDragEnd, + handleDragOver, + handleDragLeave, + handleDrop, + }; +} diff --git a/apps/web/src/hooks/use-enhanced-file-manager.ts b/apps/web/src/hooks/use-enhanced-file-manager.ts index a6b953f7..444e3304 100644 --- a/apps/web/src/hooks/use-enhanced-file-manager.ts +++ b/apps/web/src/hooks/use-enhanced-file-manager.ts @@ -1,11 +1,10 @@ -import { useCallback, useEffect, useState } from "react"; +import { useCallback, useState } from "react"; import { useTranslations } from "next-intl"; import { toast } from "sonner"; -import { deleteFile, getDownloadUrl, updateFile } from "@/http/endpoints"; +import { deleteFile, updateFile } from "@/http/endpoints"; import { deleteFolder, registerFolder, updateFolder } from "@/http/endpoints/folders"; -import { useDownloadQueue } from "./use-download-queue"; -import { usePushNotifications } from "./use-push-notifications"; +import { getCachedDownloadUrl } from "@/lib/download-url-cache"; interface FileToRename { id: string; @@ -62,6 +61,7 @@ interface BulkFile { description?: string; size: number; objectName: string; + folderId?: string; createdAt: string; updatedAt: string; relativePath?: string; @@ -83,14 +83,6 @@ interface BulkFolder { }; } -interface PendingDownload { - downloadId: string; - fileName: string; - objectName: string; - startTime: number; - status: "pending" | "queued" | "downloading" | "completed" | "failed"; -} - export interface EnhancedFileManagerHook { previewFile: PreviewFile | null; fileToDelete: any; @@ -101,7 +93,6 @@ export interface EnhancedFileManagerHook { filesToDownload: BulkFile[] | null; foldersToDelete: BulkFolder[] | null; isBulkDownloadModalOpen: boolean; - pendingDownloads: PendingDownload[]; folderToDelete: FolderToDelete | null; folderToRename: FolderToRename | null; @@ -144,15 +135,16 @@ export interface EnhancedFileManagerHook { clearSelection?: () => void; setClearSelectionCallback?: (callback: () => void) => void; - getDownloadStatus: (objectName: string) => PendingDownload | null; - cancelPendingDownload: (downloadId: string) => Promise; - isDownloadPending: (objectName: string) => boolean; } -export function useEnhancedFileManager(onRefresh: () => Promise, clearSelection?: () => void) { +export function useEnhancedFileManager( + onRefresh: () => Promise, + clearSelection?: () => void, + handleImmediateUpdate?: (itemId: string, itemType: "file" | "folder", newParentId: string | null) => void, + allFiles?: BulkFile[], + allFolders?: BulkFolder[] +) { const t = useTranslations(); - const downloadQueue = useDownloadQueue(true, 3000); - const notifications = usePushNotifications(); const [previewFile, setPreviewFile] = useState(null); const [fileToRename, setFileToRename] = useState(null); @@ -168,29 +160,19 @@ export function useEnhancedFileManager(onRefresh: () => Promise, clearSele const [folderToShare, setFolderToShare] = useState(null); const [isCreateFolderModalOpen, setCreateFolderModalOpen] = useState(false); const [isBulkDownloadModalOpen, setBulkDownloadModalOpen] = useState(false); - const [pendingDownloads, setPendingDownloads] = useState([]); const [clearSelectionCallback, setClearSelectionCallbackState] = useState<(() => void) | null>(null); const [foldersToShare, setFoldersToShare] = useState(null); const [foldersToDownload, setFoldersToDownload] = useState(null); - const startActualDownload = async ( - downloadId: string, - objectName: string, - fileName: string, - downloadUrl?: string - ) => { - try { - setPendingDownloads((prev) => - prev.map((d) => (d.downloadId === downloadId ? { ...d, status: "downloading" } : d)) - ); - - let url = downloadUrl; - if (!url) { - const response = await getDownloadUrl(objectName); - url = response.data.url; - } + const setClearSelectionCallback = useCallback((callback: () => void) => { + setClearSelectionCallbackState(() => callback); + }, []); + const handleDownload = async (objectName: string, fileName: string) => { + try { + const loadingToast = toast.loading(t("share.messages.downloadStarted")); + const url = await getCachedDownloadUrl(objectName); const link = document.createElement("a"); link.href = url; link.download = fileName; @@ -198,122 +180,20 @@ export function useEnhancedFileManager(onRefresh: () => Promise, clearSele link.click(); document.body.removeChild(link); - const wasQueued = pendingDownloads.some((d) => d.downloadId === downloadId); - - if (wasQueued) { - setPendingDownloads((prev) => - prev.map((d) => (d.downloadId === downloadId ? { ...d, status: "completed" } : d)) - ); - - const completedDownload = pendingDownloads.find((d) => d.downloadId === downloadId); - if (completedDownload) { - const fileSize = completedDownload.startTime ? Date.now() - completedDownload.startTime : undefined; - await notifications.notifyDownloadComplete(fileName, fileSize); - } - - setTimeout(() => { - setPendingDownloads((prev) => prev.filter((d) => d.downloadId !== downloadId)); - }, 5000); - } - - if (!wasQueued) { - toast.success(t("files.downloadStart", { fileName })); - } - } catch (error: any) { - const wasQueued = pendingDownloads.some((d) => d.downloadId === downloadId); - - if (wasQueued) { - setPendingDownloads((prev) => prev.map((d) => (d.downloadId === downloadId ? { ...d, status: "failed" } : d))); - - const errorMessage = - error?.response?.data?.message || error?.message || t("notifications.downloadFailed.unknownError"); - await notifications.notifyDownloadFailed(fileName, errorMessage); - - setTimeout(() => { - setPendingDownloads((prev) => prev.filter((d) => d.downloadId !== downloadId)); - }, 10000); - } - - if (!pendingDownloads.some((d) => d.downloadId === downloadId)) { - toast.error(t("files.downloadError")); - } - throw error; - } - }; - - useEffect(() => { - if (!downloadQueue.queueStatus) return; - - pendingDownloads.forEach(async (download) => { - if (download.status === "queued") { - const stillQueued = downloadQueue.queueStatus?.queuedDownloads.find((qd) => qd.fileName === download.fileName); - - if (!stillQueued) { - console.log(`[DOWNLOAD] Processing queued download: ${download.fileName}`); - - await notifications.notifyQueueProcessing(download.fileName); - - await startActualDownload(download.downloadId, download.objectName, download.fileName); - } - } - }); - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [downloadQueue.queueStatus, pendingDownloads, notifications]); - - const setClearSelectionCallback = useCallback((callback: () => void) => { - setClearSelectionCallbackState(() => callback); - }, []); - - const handleDownload = async (objectName: string, fileName: string) => { - try { - const { downloadFileWithQueue } = await import("@/utils/download-queue-utils"); - - await toast.promise( - downloadFileWithQueue(objectName, fileName, { - silent: true, - showToasts: false, - }), - { - loading: t("share.messages.downloadStarted"), - success: t("shareManager.downloadSuccess"), - error: t("share.errors.downloadFailed"), - } - ); - } catch (error) { - console.error("Download error:", error); - } - }; - - const cancelPendingDownload = async (downloadId: string) => { - try { - await downloadQueue.cancelDownload(downloadId); - setPendingDownloads((prev) => prev.filter((d) => d.downloadId !== downloadId)); + toast.dismiss(loadingToast); + toast.success(t("shareManager.downloadSuccess")); } catch (error) { - console.error("Error cancelling download:", error); + console.error(`[FileManager] ❌ Download failed for ${fileName}:`, error); + toast.error(t("share.errors.downloadFailed")); } }; - const getDownloadStatus = useCallback( - (objectName: string): PendingDownload | null => { - return pendingDownloads.find((d) => d.objectName === objectName) || null; - }, - [pendingDownloads] - ); - - const isDownloadPending = useCallback( - (objectName: string): boolean => { - return pendingDownloads.some((d) => d.objectName === objectName && d.status !== "completed"); - }, - [pendingDownloads] - ); - const handleRename = async (fileId: string, newName: string, description?: string) => { try { await updateFile(fileId, { name: newName, description: description || null, }); - await onRefresh(); toast.success(t("files.updateSuccess")); setFileToRename(null); } catch (error) { @@ -324,8 +204,12 @@ export function useEnhancedFileManager(onRefresh: () => Promise, clearSele const handleDelete = async (fileId: string) => { try { + // Optimistic update - remove from UI immediately + if (handleImmediateUpdate) { + handleImmediateUpdate(fileId, "file", "__DELETE__" as any); + } + await deleteFile(fileId); - await onRefresh(); toast.success(t("files.deleteSuccess")); setFileToDelete(null); } catch (error) { @@ -362,65 +246,98 @@ export function useEnhancedFileManager(onRefresh: () => Promise, clearSele } }; - const handleSingleFolderDownload = async (folderId: string, folderName: string) => { - try { - const { downloadFolderWithQueue } = await import("@/utils/download-queue-utils"); - - await toast.promise( - downloadFolderWithQueue(folderId, folderName, { - silent: true, - showToasts: false, - }), - { - loading: t("shareManager.creatingZip"), - success: t("shareManager.zipDownloadSuccess"), - error: t("share.errors.downloadFailed"), - } - ); - } catch (error) { - console.error("Error downloading folder:", error); - } - }; - const handleBulkDownloadWithZip = async (files: BulkFile[], zipName: string) => { try { const folders = foldersToDownload || []; - const { bulkDownloadWithQueue } = await import("@/utils/download-queue-utils"); - - const allItems = [ - ...files.map((file) => ({ - objectName: file.objectName, - name: file.relativePath || file.name, - isReverseShare: false, - type: "file" as const, - })), - ...folders.map((folder) => ({ - id: folder.id, - name: folder.name, - type: "folder" as const, - })), - ]; - - if (allItems.length === 0) { + + if (files.length === 0 && folders.length === 0) { toast.error(t("shareManager.noFilesToDownload")); return; } - toast.promise( - bulkDownloadWithQueue(allItems, zipName, undefined, false).then(() => { - setBulkDownloadModalOpen(false); - setFilesToDownload(null); - setFoldersToDownload(null); - if (clearSelectionCallback) { - clearSelectionCallback(); + const loadingToast = toast.loading(t("shareManager.creatingZip")); + + try { + // Collect all files including those in folders recursively + const allFilesToDownload: Array<{ url: string; name: string }> = []; + + // Helper function to get all files in a folder recursively with paths + const getFolderFilesWithPath = ( + targetFolderId: string, + currentPath: string = "" + ): Array<{ file: BulkFile; path: string }> => { + if (!allFiles || !allFolders) return []; + + const filesWithPath: Array<{ file: BulkFile; path: string }> = []; + + // Get direct files in this folder + const directFiles = allFiles.filter((f) => f.folderId === targetFolderId); + directFiles.forEach((file) => { + filesWithPath.push({ file, path: currentPath }); + }); + + // Get subfolders and process them recursively + const subfolders = allFolders.filter((f) => f.parentId === targetFolderId); + for (const subfolder of subfolders) { + const subfolderPath = currentPath ? `${currentPath}/${subfolder.name}` : subfolder.name; + filesWithPath.push(...getFolderFilesWithPath(subfolder.id, subfolderPath)); } - }), - { - loading: t("shareManager.creatingZip"), - success: t("shareManager.zipDownloadSuccess"), - error: t("shareManager.zipDownloadError"), + + return filesWithPath; + }; + + // Get presigned URLs for direct files (not in folders) + const directFileItems = await Promise.all( + files.map(async (file) => { + const url = await getCachedDownloadUrl(file.objectName); + return { + url, + name: file.name, + }; + }) + ); + allFilesToDownload.push(...directFileItems); + + // Get presigned URLs for files in selected folders + for (const folder of folders) { + const folderFilesWithPath = getFolderFilesWithPath(folder.id, folder.name); + + const folderFileItems = await Promise.all( + folderFilesWithPath.map(async ({ file, path }) => { + const url = await getCachedDownloadUrl(file.objectName); + return { + url, + name: path ? `${path}/${file.name}` : file.name, + }; + }) + ); + allFilesToDownload.push(...folderFileItems); + } + + if (allFilesToDownload.length === 0) { + toast.dismiss(loadingToast); + toast.error(t("shareManager.noFilesToDownload")); + return; } - ); + + // Create ZIP with all files + const { downloadFilesAsZip } = await import("@/utils/zip-download"); + await downloadFilesAsZip(allFilesToDownload, zipName.endsWith(".zip") ? zipName : `${zipName}.zip`); + + toast.dismiss(loadingToast); + toast.success(t("shareManager.zipDownloadSuccess")); + } catch (error) { + toast.dismiss(loadingToast); + toast.error(t("shareManager.zipDownloadError")); + throw error; + } + + setBulkDownloadModalOpen(false); + setFilesToDownload(null); + setFoldersToDownload(null); + if (clearSelectionCallback) { + clearSelectionCallback(); + } } catch (error) { console.error("Error in bulk download:", error); setBulkDownloadModalOpen(false); @@ -433,6 +350,16 @@ export function useEnhancedFileManager(onRefresh: () => Promise, clearSele if (!filesToDelete && !foldersToDelete) return; try { + // Optimistic update - remove all items from UI immediately + if (handleImmediateUpdate) { + filesToDelete?.forEach((file) => { + handleImmediateUpdate(file.id, "file", "__DELETE__" as any); + }); + foldersToDelete?.forEach((folder) => { + handleImmediateUpdate(folder.id, "folder", "__DELETE__" as any); + }); + } + const deletePromises = []; if (filesToDelete) { @@ -449,7 +376,6 @@ export function useEnhancedFileManager(onRefresh: () => Promise, clearSele toast.success(t("files.bulkDeleteSuccess", { count: totalCount })); setFilesToDelete(null); setFoldersToDelete(null); - onRefresh(); } catch (error) { console.error("Failed to delete items:", error); toast.error(t("files.bulkDeleteError")); @@ -467,7 +393,6 @@ export function useEnhancedFileManager(onRefresh: () => Promise, clearSele await registerFolder(folderData); toast.success(t("folderActions.folderCreated")); - await onRefresh(); setCreateFolderModalOpen(false); } catch (error) { console.error("Error creating folder:", error); @@ -480,7 +405,6 @@ export function useEnhancedFileManager(onRefresh: () => Promise, clearSele try { await updateFolder(folderId, { name: newName, description }); toast.success(t("folderActions.folderRenamed")); - await onRefresh(); setFolderToRename(null); } catch (error) { console.error("Error renaming folder:", error); @@ -490,9 +414,13 @@ export function useEnhancedFileManager(onRefresh: () => Promise, clearSele const handleFolderDelete = async (folderId: string) => { try { + // Optimistic update - remove from UI immediately + if (handleImmediateUpdate) { + handleImmediateUpdate(folderId, "folder", "__DELETE__" as any); + } + await deleteFolder(folderId); toast.success(t("folderActions.folderDeleted")); - await onRefresh(); setFolderToDelete(null); if (clearSelectionCallback) { clearSelectionCallback(); @@ -522,7 +450,6 @@ export function useEnhancedFileManager(onRefresh: () => Promise, clearSele setFoldersToDelete, isBulkDownloadModalOpen, setBulkDownloadModalOpen, - pendingDownloads, handleDownload, handleRename, handleDelete, @@ -552,9 +479,5 @@ export function useEnhancedFileManager(onRefresh: () => Promise, clearSele clearSelection, setClearSelectionCallback, - getDownloadStatus, - handleSingleFolderDownload, - cancelPendingDownload, - isDownloadPending, }; } diff --git a/apps/web/src/hooks/use-file-preview.ts b/apps/web/src/hooks/use-file-preview.ts index ebfe8f68..35f85398 100644 --- a/apps/web/src/hooks/use-file-preview.ts +++ b/apps/web/src/hooks/use-file-preview.ts @@ -2,9 +2,7 @@ import { useCallback, useEffect, useRef, useState } from "react"; import { useTranslations } from "next-intl"; import { toast } from "sonner"; -import { getDownloadUrl } from "@/http/endpoints"; -import { downloadReverseShareFile } from "@/http/endpoints/reverse-shares"; -import { downloadFileWithQueue, downloadReverseShareWithQueue } from "@/utils/download-queue-utils"; +import { getCachedDownloadUrl, getCachedReverseShareDownloadUrl } from "@/lib/download-url-cache"; import { getFileExtension, getFileType, type FileType } from "@/utils/file-types"; interface FilePreviewState { @@ -178,21 +176,10 @@ export function useFilePreview({ file, isOpen, isReverseShare = false, sharePass let url: string; if (isReverseShare) { - const response = await downloadReverseShareFile(file.id!); - url = response.data.url; + url = await getCachedReverseShareDownloadUrl(file.id!); } else { - const params: Record = {}; - if (sharePassword) params.password = sharePassword; - - const response = await getDownloadUrl( - file.objectName, - Object.keys(params).length > 0 - ? { - params: { ...params }, - } - : undefined - ); - url = response.data.url; + const options = sharePassword ? { headers: { "x-share-password": sharePassword } } : undefined; + url = await getCachedDownloadUrl(file.objectName, options); } setState((prev) => ({ ...prev, downloadUrl: url })); @@ -241,18 +228,27 @@ export function useFilePreview({ file, isOpen, isReverseShare = false, sharePass if (!fileKey) return; try { + const loadingToast = toast.loading(t("filePreview.downloading") || "Downloading..."); + let url: string; if (isReverseShare) { - await downloadReverseShareWithQueue(file.id!, file.name, { - onFail: () => toast.error(t("filePreview.downloadError")), - }); + url = await getCachedReverseShareDownloadUrl(file.id!); } else { - await downloadFileWithQueue(file.objectName, file.name, { - sharePassword, - onFail: () => toast.error(t("filePreview.downloadError")), - }); + const options = sharePassword ? { headers: { "x-share-password": sharePassword } } : undefined; + url = await getCachedDownloadUrl(file.objectName, options); } + + const link = document.createElement("a"); + link.href = url; + link.download = file.name; + document.body.appendChild(link); + link.click(); + document.body.removeChild(link); + + toast.dismiss(loadingToast); + toast.success(t("filePreview.downloadSuccess") || "Download started"); } catch (error) { console.error("Download error:", error); + toast.error(t("filePreview.downloadError")); } }, [isReverseShare, file.id, file.objectName, file.name, sharePassword, t]); diff --git a/apps/web/src/hooks/use-push-notifications.ts b/apps/web/src/hooks/use-push-notifications.ts deleted file mode 100644 index 81542647..00000000 --- a/apps/web/src/hooks/use-push-notifications.ts +++ /dev/null @@ -1,185 +0,0 @@ -import { useCallback, useEffect, useRef, useState } from "react"; -import { useTranslations } from "next-intl"; -import { toast } from "sonner"; - -interface NotificationOptions { - title: string; - body: string; - icon?: string; - badge?: string; - tag?: string; - requireInteraction?: boolean; - silent?: boolean; - data?: any; -} - -export function usePushNotifications() { - const t = useTranslations(); - const [permissionGranted, setPermissionGranted] = useState(false); - const isSupported = useRef(typeof window !== "undefined" && "Notification" in window); - - const requestPermission = useCallback(async (): Promise => { - if (!isSupported.current) { - console.warn("Push notifications are not supported in this browser"); - return false; - } - - try { - const permission = await Notification.requestPermission(); - const granted = permission === "granted"; - setPermissionGranted(granted); - - if (permission === "granted") { - console.log("🔔 Push notifications enabled"); - toast.success(t("notifications.permissionGranted")); - } else if (permission === "denied") { - console.warn("🚫 Push notifications denied"); - toast.warning(t("notifications.permissionDenied")); - } else { - console.info("⏸️ Push notifications dismissed"); - } - - return granted; - } catch (error) { - console.error("Error requesting notification permission:", error); - return false; - } - }, [t]); - - const sendNotification = useCallback( - async (options: NotificationOptions): Promise => { - if (!isSupported.current) { - console.warn("Push notifications not supported"); - return false; - } - - if (Notification.permission !== "granted") { - const granted = await requestPermission(); - if (!granted) return false; - } - - try { - const notification = new Notification(options.title, { - body: options.body, - icon: options.icon || "/favicon.ico", - badge: options.badge, - tag: options.tag, - requireInteraction: options.requireInteraction ?? false, - silent: options.silent ?? false, - data: options.data, - }); - - if (!options.requireInteraction) { - setTimeout(() => { - notification.close(); - }, 5000); - } - - notification.onclick = (event) => { - event.preventDefault(); - window.focus(); - notification.close(); - - if (options.data?.action === "focus-downloads") { - const downloadIndicator = document.querySelector("[data-download-indicator]"); - if (downloadIndicator) { - downloadIndicator.scrollIntoView({ behavior: "smooth" }); - } - } - }; - - return true; - } catch (error) { - console.error("Error sending notification:", error); - return false; - } - }, - [requestPermission] - ); - - useEffect(() => { - if (isSupported.current) { - setPermissionGranted(Notification.permission === "granted"); - } - }, []); - - const notifyDownloadComplete = useCallback( - async (fileName: string, fileSize?: number) => { - const sizeText = fileSize ? ` (${(fileSize / 1024 / 1024).toFixed(1)}MB)` : ""; - - return sendNotification({ - title: t("notifications.downloadComplete.title"), - body: t("notifications.downloadComplete.body", { - fileName: fileName + sizeText, - }), - icon: "/favicon.ico", - tag: `download-complete-${Date.now()}`, - requireInteraction: false, - data: { - action: "focus-downloads", - type: "download-complete", - fileName, - fileSize, - }, - }); - }, - [sendNotification, t] - ); - - const notifyDownloadFailed = useCallback( - async (fileName: string, error?: string) => { - return sendNotification({ - title: t("notifications.downloadFailed.title"), - body: t("notifications.downloadFailed.body", { - fileName, - error: error || t("notifications.downloadFailed.unknownError"), - }), - icon: "/favicon.ico", - tag: `download-failed-${Date.now()}`, - requireInteraction: true, - data: { - action: "focus-downloads", - type: "download-failed", - fileName, - error, - }, - }); - }, - [sendNotification, t] - ); - - const notifyQueueProcessing = useCallback( - async (fileName: string, position?: number) => { - const positionText = position ? t("notifications.queueProcessing.position", { position }) : ""; - - return sendNotification({ - title: t("notifications.queueProcessing.title"), - body: t("notifications.queueProcessing.body", { - fileName, - position: positionText, - }), - icon: "/favicon.ico", - tag: `queue-processing-${Date.now()}`, - requireInteraction: false, - silent: true, - data: { - action: "focus-downloads", - type: "queue-processing", - fileName, - position, - }, - }); - }, - [sendNotification, t] - ); - - return { - isSupported: isSupported.current, - hasPermission: permissionGranted, - requestPermission, - sendNotification, - notifyDownloadComplete, - notifyDownloadFailed, - notifyQueueProcessing, - }; -} diff --git a/apps/web/src/hooks/use-share-manager.ts b/apps/web/src/hooks/use-share-manager.ts index 0513b42a..edf42c7f 100644 --- a/apps/web/src/hooks/use-share-manager.ts +++ b/apps/web/src/hooks/use-share-manager.ts @@ -7,7 +7,7 @@ import { toast } from "sonner"; import { addRecipients, createShareAlias, deleteShare, notifyRecipients, updateShare } from "@/http/endpoints"; import { updateFolder } from "@/http/endpoints/folders"; import type { Share } from "@/http/endpoints/shares/types"; -import { bulkDownloadShareWithQueue, downloadFileWithQueue } from "@/utils/download-queue-utils"; +import { getCachedDownloadUrl } from "@/lib/download-url-cache"; export interface ShareManagerHook { shareToDelete: Share | null; @@ -230,22 +230,45 @@ export function useShareManager(onSuccess: () => void) { return; } - toast.promise( - bulkDownloadShareWithQueue(allItems, share.files || [], share.folders || [], zipName, undefined, true).then( - () => { - if (clearSelectionCallback) { - clearSelectionCallback(); - } - } - ), - { - loading: t("shareManager.creatingZip"), - success: t("shareManager.zipDownloadSuccess"), - error: t("shareManager.zipDownloadError"), + const loadingToast = toast.loading(t("shareManager.preparingDownload")); + + try { + // Get presigned URLs for all files + const downloadItems = await Promise.all( + allItems + .filter((item) => item.type === "file" && item.objectName) + .map(async (item) => { + const url = await getCachedDownloadUrl(item.objectName!); + return { + url, + name: item.name, + }; + }) + ); + + if (downloadItems.length === 0) { + toast.dismiss(loadingToast); + toast.error(t("shareManager.noFilesToDownload")); + return; } - ); + + // Create ZIP with all files + const { downloadFilesAsZip } = await import("@/utils/zip-download"); + await downloadFilesAsZip(downloadItems, zipName.endsWith(".zip") ? zipName : `${zipName}.zip`); + + toast.dismiss(loadingToast); + toast.success(t("shareManager.zipDownloadSuccess")); + + if (clearSelectionCallback) { + clearSelectionCallback(); + } + } catch (error) { + toast.dismiss(loadingToast); + toast.error(t("shareManager.zipDownloadError")); + throw error; + } } else { - toast.error("Multiple share download not yet supported - please download shares individually"); + toast.error(t("shareManager.errors.multipleDownloadNotSupported")); } } catch (error) { console.error("Error creating ZIP:", error); @@ -255,9 +278,8 @@ export function useShareManager(onSuccess: () => void) { const handleBulkDownload = (shares: Share[]) => { const zipName = shares.length === 1 - ? t("shareManager.singleShareZipName", { shareName: shares[0].name || t("shareManager.defaultShareName") }) + ? `${shares[0].name || t("shareManager.defaultShareName")}.zip` : t("shareManager.multipleSharesZipName", { count: shares.length }); - handleBulkDownloadWithZip(shares, zipName); }; @@ -273,17 +295,24 @@ export function useShareManager(onSuccess: () => void) { if (totalFiles === 1 && totalFolders === 0) { const file = share.files[0]; try { - await downloadFileWithQueue(file.objectName, file.name, { - onComplete: () => toast.success(t("shareManager.downloadSuccess")), - onFail: () => toast.error(t("shareManager.downloadError")), - }); + const loadingToast = toast.loading(t("shareManager.downloading")); + const url = await getCachedDownloadUrl(file.objectName); + + const link = document.createElement("a"); + link.href = url; + link.download = file.name; + document.body.appendChild(link); + link.click(); + document.body.removeChild(link); + + toast.dismiss(loadingToast); + toast.success(t("shareManager.downloadSuccess")); } catch (error) { console.error("Download error:", error); + toast.error(t("shareManager.downloadError")); } } else { - const zipName = t("shareManager.singleShareZipName", { - shareName: share.name || t("shareManager.defaultShareName"), - }); + const zipName = `${share.name || t("shareManager.defaultShareName")}.zip`; await handleBulkDownloadWithZip([share], zipName); } }; diff --git a/apps/web/src/hooks/useUppyUpload.ts b/apps/web/src/hooks/useUppyUpload.ts new file mode 100644 index 00000000..64f7af89 --- /dev/null +++ b/apps/web/src/hooks/useUppyUpload.ts @@ -0,0 +1,489 @@ +import { useCallback, useEffect, useRef, useState } from "react"; +import AwsS3, { type AwsS3UploadParameters } from "@uppy/aws-s3"; +import Uppy, { type UppyFile } from "@uppy/core"; +import { useTranslations } from "next-intl"; +import { toast } from "sonner"; + +import { UPLOAD_CONFIG } from "@/config/upload-config"; +import { + abortMultipartUpload, + completeMultipartUpload, + createMultipartUpload, + getMultipartPartUrl, +} from "@/http/endpoints/files"; + +/** + * Options for the useUppyUpload hook + */ +export interface UseUppyUploadOptions { + /** + * Validation before adding file to Uppy (checkFile endpoint) + */ + onValidate?: (file: File) => Promise; + + /** + * Generate objectName and perform pre-upload logic + */ + onBeforeUpload?: (file: File) => Promise; + + /** + * Register file in backend after successful upload + */ + onAfterUpload?: (fileId: string, file: File, objectName: string) => Promise; + + /** + * Function to get presigned URL for S3 upload + * Returns the URL, method, and the ACTUAL objectName generated by backend + */ + getPresignedUrl: ( + objectName: string, + extension: string + ) => Promise<{ url: string; method: string; actualObjectName?: string }>; + + /** + * Callback when all uploads complete successfully + */ + onSuccess?: () => void; + + /** + * Optional folder context for file organization + */ + currentFolderId?: string; +} + +/** + * Status of a file upload + */ +export type FileUploadStatus = "pending" | "uploading" | "success" | "error" | "cancelled"; + +/** + * State of a file upload + */ +export interface FileUploadState { + id: string; + file: File; + status: FileUploadStatus; + progress: number; + error?: string; + objectName?: string; + previewUrl?: string; +} + +/** + * Custom hook for managing file uploads with Uppy + * Provides a simple interface for components to handle file uploads + */ +export function useUppyUpload(options: UseUppyUploadOptions) { + const t = useTranslations(); + const uppyRef = useRef(null); + const [fileUploads, setFileUploads] = useState([]); + const [isUploading, setIsUploading] = useState(false); + + // Store callbacks in refs to avoid recreating Uppy instance + const onValidateRef = useRef(options.onValidate); + const onBeforeUploadRef = useRef(options.onBeforeUpload); + const onAfterUploadRef = useRef(options.onAfterUpload); + const getPresignedUrlRef = useRef(options.getPresignedUrl); + const onSuccessRef = useRef(options.onSuccess); + + // Update refs when callbacks change + useEffect(() => { + onValidateRef.current = options.onValidate; + onBeforeUploadRef.current = options.onBeforeUpload; + onAfterUploadRef.current = options.onAfterUpload; + getPresignedUrlRef.current = options.getPresignedUrl; + onSuccessRef.current = options.onSuccess; + }, [options]); + + // Initialize Uppy instance only once + useEffect(() => { + const uppy = new Uppy({ + autoProceed: false, // Manual control via startUpload() + allowMultipleUploads: true, + restrictions: { + // No global restrictions - handled per context + }, + }); + + // Setup AWS S3 plugin with conditional multipart support + // Files <100MB: Use simple PUT upload + // Files ≥100MB: Use multipart chunked upload + uppy.use(AwsS3, { + limit: 6, // Allow 6 concurrent part uploads + getChunkSize: () => 8 * 1024 * 1024, // 8MB chunk size + shouldUseMultipart: (file: any) => { + const fileSize = file.size || 0; + const useMultipart = fileSize >= UPLOAD_CONFIG.MULTIPART_THRESHOLD; + return useMultipart; + }, + + // For simple uploads (<100MB) + async getUploadParameters(file: UppyFile): Promise { + try { + // 1. Validate file if validation callback is provided + if (onValidateRef.current) { + try { + await onValidateRef.current(file.data as File); + } catch (error: any) { + const errorMessage = error.message || "Validation failed"; + setFileUploads((prev) => + prev.map((f) => (f.id === file.id ? { ...f, status: "error", error: errorMessage } : f)) + ); + throw error; + } + } + + // 2. Generate object name + let objectName: string; + if (onBeforeUploadRef.current) { + objectName = await onBeforeUploadRef.current(file.data as File); + } else { + objectName = file.name || "untitled"; + } + + // 3. Get presigned URL + const extension = (file.name || "").split(".").pop() || ""; + const result = await getPresignedUrlRef.current(objectName, extension); + + // Use actualObjectName from backend if provided + const finalObjectName = result.actualObjectName || objectName; + + // Store the FINAL object name in file metadata + uppy.setFileMeta(file.id, { objectName: finalObjectName }); + + return { + method: "PUT" as const, + url: result.url, + headers: { + "Content-Type": file.type || "application/octet-stream", + }, + }; + } catch (error) { + console.error("[Upload] Failed to get upload parameters:", error); + throw error; + } + }, + + // For multipart uploads (≥100MB) + async createMultipartUpload(file: UppyFile) { + try { + // 1. Validate file + if (onValidateRef.current) { + await onValidateRef.current(file.data as File); + } + + // 2. Generate object name + let objectName: string; + if (onBeforeUploadRef.current) { + objectName = await onBeforeUploadRef.current(file.data as File); + } else { + objectName = file.name || "untitled"; + } + + const extension = (file.name || "").split(".").pop() || ""; + const filename = objectName.replace(`.${extension}`, ""); + + // 3. Create multipart upload on backend + const response = await createMultipartUpload({ + filename, + extension, + }); + + const { uploadId, objectName: actualObjectName } = response.data; + + // Store metadata + uppy.setFileMeta(file.id, { + objectName: actualObjectName, + uploadId, + }); + + return { + uploadId, + key: actualObjectName, + }; + } catch (error) { + console.error("[Upload:Multipart] Failed to create multipart upload:", error); + throw error; + } + }, + + //TODO: List parts (for resuming multipart uploads) + async listParts(file: UppyFile, { uploadId, key }: any) { + console.log(`[Upload:Multipart] Listing parts for: ${file.name}`); + console.log(`Upload ID: ${uploadId}, Key: ${key}`); + // Para simplificar, não vamos implementar resumo de upload por enquanto + // Retornamos array vazio indicando que não há partes já enviadas + return []; + }, + + // Sign individual parts for multipart upload + async signPart(file: UppyFile, partData: any) { + const { uploadId, key, partNumber } = partData; + + try { + const response = await getMultipartPartUrl({ + uploadId, + objectName: key, + partNumber: partNumber.toString(), + }); + + // Return the signed URL object directly - Uppy expects { url, headers } + return { + url: response.data.url, + headers: {}, + }; + } catch (error) { + console.error(`[Upload:Multipart] Failed to sign part ${partNumber}:`, error); + throw error; + } + }, + + // Complete multipart upload + async completeMultipartUpload(file: UppyFile, data: any) { + const { uploadId, key, parts } = data; + const meta = file.meta as { objectName: string }; + + try { + await completeMultipartUpload({ + uploadId, + objectName: meta.objectName || key, + parts, + }); + + return {}; + } catch (error) { + console.error("[Upload:Multipart] Failed to complete multipart upload:", error); + throw error; + } + }, + + async abortMultipartUpload(file: UppyFile, data: any) { + const { uploadId, key } = data; + const meta = file.meta as { objectName: string }; + + try { + await abortMultipartUpload({ + uploadId, + objectName: meta.objectName || key, + }); + } catch (error) { + console.error("[Upload:Multipart] Failed to abort multipart upload:", error); + // Don't throw - abort is cleanup, shouldn't fail the operation + } + }, + }); + + uppyRef.current = uppy; + + // Cleanup on unmount only + return () => { + const files = uppy.getFiles(); + files.forEach((file) => { + uppy.removeFile(file.id); + }); + }; + }, []); // Empty dependency array - only run once + + // Event listeners for Uppy + useEffect(() => { + const uppy = uppyRef.current; + if (!uppy) return; + + // When file is added to Uppy + const handleFileAdded = (file: any) => { + setFileUploads((prev) => [ + ...prev, + { + id: file.id, + file: file.data, + status: "pending", + progress: 0, + previewUrl: file.data.type.startsWith("image/") ? URL.createObjectURL(file.data) : undefined, + }, + ]); + }; + + // Upload progress updates + const handleProgress = (file: any, progress: any) => { + const percent = (progress.bytesUploaded / progress.bytesTotal) * 100; + setFileUploads((prev) => + prev.map((f) => (f.id === file.id ? { ...f, status: "uploading", progress: Math.round(percent) } : f)) + ); + }; + + // Upload success + const handleSuccess = async (file: any) => { + const objectName = file.meta.objectName; + + try { + // Call registration callback + if (onAfterUploadRef.current) { + await onAfterUploadRef.current(file.id, file.data, objectName); + } + + setFileUploads((prev) => prev.map((f) => (f.id === file.id ? { ...f, status: "success", progress: 100 } : f))); + } catch (error: any) { + console.error("[Upload] Registration failed:", error); + // Handle registration error + const errorMessage = error.message || "Failed to register file"; + setFileUploads((prev) => + prev.map((f) => (f.id === file.id ? { ...f, status: "error", error: errorMessage } : f)) + ); + } + }; + + // Upload error + const handleError = (file: any, error: any) => { + console.error("[Upload] Upload failed:", file.name, error); + setFileUploads((prev) => + prev.map((f) => + f.id === file.id ? { ...f, status: "error", error: error.message || t("uploadFile.errors.uploadFailed") } : f + ) + ); + }; + + // All uploads complete + const handleComplete = () => { + setIsUploading(false); + }; + + uppy.on("file-added", handleFileAdded); + uppy.on("upload-progress", handleProgress); + uppy.on("upload-success", handleSuccess); + uppy.on("upload-error", handleError); + uppy.on("complete", handleComplete); + + return () => { + uppy.off("file-added", handleFileAdded); + uppy.off("upload-progress", handleProgress); + uppy.off("upload-success", handleSuccess); + uppy.off("upload-error", handleError); + uppy.off("complete", handleComplete); + }; + }, [t]); // Empty dependency array - callbacks use refs + + /** + * Add files to upload queue + */ + const addFiles = useCallback((files: File[]) => { + const uppy = uppyRef.current; + if (!uppy) return; + + files.forEach((file) => { + try { + uppy.addFile({ + name: file.name, + type: file.type, + data: file, + meta: { + relativePath: (file as any).webkitRelativePath || null, + }, + }); + } catch (error: any) { + console.error("[Upload] Error adding file:", error); + toast.error(`Failed to add ${file.name}: ${error.message}`); + } + }); + }, []); + + /** + * Start uploading all pending files + */ + const startUpload = useCallback(() => { + const uppy = uppyRef.current; + if (!uppy) return; + + setIsUploading(true); + uppy.upload(); + }, []); + + /** + * Cancel a specific file upload + */ + const cancelUpload = useCallback((fileId: string) => { + const uppy = uppyRef.current; + if (!uppy) return; + + uppy.removeFile(fileId); + setFileUploads((prev) => prev.map((f) => (f.id === fileId ? { ...f, status: "cancelled" } : f))); + }, []); + + /** + * Retry a failed upload + */ + const retryUpload = useCallback( + (fileId: string) => { + const uppy = uppyRef.current; + if (!uppy) return; + + const file = fileUploads.find((f) => f.id === fileId); + if (!file) return; + + // Reset status to pending + setFileUploads((prev) => prev.map((f) => (f.id === fileId ? { ...f, status: "pending", error: undefined } : f))); + + // Retry the upload + uppy.retryUpload(fileId); + }, + [fileUploads] + ); + + /** + * Remove a file from the upload queue + */ + const removeFile = useCallback( + (fileId: string) => { + const uppy = uppyRef.current; + if (!uppy) return; + + // Revoke preview URL if exists + const file = fileUploads.find((f) => f.id === fileId); + if (file?.previewUrl) { + URL.revokeObjectURL(file.previewUrl); + } + + uppy.removeFile(fileId); + setFileUploads((prev) => prev.filter((f) => f.id !== fileId)); + }, + [fileUploads] + ); + + /** + * Clear all files from the upload queue + */ + const clearAll = useCallback(() => { + const uppy = uppyRef.current; + if (!uppy) return; + + // Revoke all preview URLs from current state + setFileUploads((prev) => { + prev.forEach((file) => { + if (file.previewUrl) { + URL.revokeObjectURL(file.previewUrl); + } + }); + return []; + }); + + // Remove all files from Uppy + const uppyFiles = uppy.getFiles(); + uppyFiles.forEach((file) => { + uppy.removeFile(file.id); + }); + + // Reset uploading state + setIsUploading(false); + }, []); + + return { + addFiles, + startUpload, + cancelUpload, + retryUpload, + removeFile, + clearAll, + fileUploads, + isUploading, + uppy: uppyRef.current, + }; +} diff --git a/apps/web/src/http/endpoints/app/types.ts b/apps/web/src/http/endpoints/app/types.ts index ca1c2d21..962c24d3 100644 --- a/apps/web/src/http/endpoints/app/types.ts +++ b/apps/web/src/http/endpoints/app/types.ts @@ -33,7 +33,7 @@ export interface GetAppInfo200 { } export interface GetSystemInfo200 { - storageProvider: "s3" | "filesystem"; + storageProvider: "s3"; s3Enabled: boolean; } diff --git a/apps/web/src/http/endpoints/download-queue/index.ts b/apps/web/src/http/endpoints/download-queue/index.ts deleted file mode 100644 index 0768df70..00000000 --- a/apps/web/src/http/endpoints/download-queue/index.ts +++ /dev/null @@ -1,63 +0,0 @@ -import type { AxiosRequestConfig } from "axios"; - -import apiInstance from "@/config/api"; - -export interface QueuedDownload { - downloadId: string; - position: number; - waitTime: number; - fileName?: string; - fileSize?: number; -} - -export interface DownloadQueueStatus { - queueLength: number; - maxQueueSize: number; - activeDownloads: number; - maxConcurrent: number; - queuedDownloads: QueuedDownload[]; -} - -export interface DownloadQueueStatusResult { - status: string; - data: DownloadQueueStatus; -} - -export interface CancelDownloadResult { - message: string; - downloadId: string; -} - -export interface ClearQueueResult { - message: string; - clearedCount: number; -} - -/** - * Get current download queue status - * @summary Get Download Queue Status - */ -export const getDownloadQueueStatus = ( - options?: AxiosRequestConfig -): Promise => { - return apiInstance.get(`/api/filesystem/download-queue/status`, options); -}; - -/** - * Cancel a specific queued download - * @summary Cancel Queued Download - */ -export const cancelQueuedDownload = ( - downloadId: string, - options?: AxiosRequestConfig -): Promise => { - return apiInstance.delete(`/api/filesystem/download-queue/${downloadId}`, options); -}; - -/** - * Clear the entire download queue (admin operation) - * @summary Clear Download Queue - */ -export const clearDownloadQueue = (options?: AxiosRequestConfig): Promise => { - return apiInstance.delete(`/api/filesystem/download-queue`, options); -}; diff --git a/apps/web/src/http/endpoints/files/index.ts b/apps/web/src/http/endpoints/files/index.ts index a2b943e0..9a252f7e 100644 --- a/apps/web/src/http/endpoints/files/index.ts +++ b/apps/web/src/http/endpoints/files/index.ts @@ -2,10 +2,18 @@ import type { AxiosRequestConfig } from "axios"; import apiInstance from "@/config/api"; import type { + AbortMultipartUploadBody, + AbortMultipartUploadResult, CheckFileBody, CheckFileResult, + CompleteMultipartUploadBody, + CompleteMultipartUploadResult, + CreateMultipartUploadBody, + CreateMultipartUploadResult, DeleteFileResult, GetDownloadUrlResult, + GetMultipartPartUrlParams, + GetMultipartPartUrlResult, GetPresignedUrlParams, GetPresignedUrlResult, ListFilesResult, @@ -115,3 +123,50 @@ export const moveFile = ( ): Promise => { return apiInstance.put(`/api/files/${id}/move`, moveFileBody, options); }; + +/** + * Creates a multipart upload session + * @summary Create Multipart Upload + */ +export const createMultipartUpload = ( + createMultipartUploadBody: CreateMultipartUploadBody, + options?: AxiosRequestConfig +): Promise => { + return apiInstance.post(`/api/files/multipart/create`, createMultipartUploadBody, options); +}; + +/** + * Gets a presigned URL for uploading a specific part + * @summary Get Multipart Part URL + */ +export const getMultipartPartUrl = ( + params: GetMultipartPartUrlParams, + options?: AxiosRequestConfig +): Promise => { + return apiInstance.get(`/api/files/multipart/part-url`, { + ...options, + params: { ...params, ...options?.params }, + }); +}; + +/** + * Completes a multipart upload + * @summary Complete Multipart Upload + */ +export const completeMultipartUpload = ( + completeMultipartUploadBody: CompleteMultipartUploadBody, + options?: AxiosRequestConfig +): Promise => { + return apiInstance.post(`/api/files/multipart/complete`, completeMultipartUploadBody, options); +}; + +/** + * Aborts a multipart upload + * @summary Abort Multipart Upload + */ +export const abortMultipartUpload = ( + abortMultipartUploadBody: AbortMultipartUploadBody, + options?: AxiosRequestConfig +): Promise => { + return apiInstance.post(`/api/files/multipart/abort`, abortMultipartUploadBody, options); +}; diff --git a/apps/web/src/http/endpoints/files/types.ts b/apps/web/src/http/endpoints/files/types.ts index dd7b72df..f8e91ff7 100644 --- a/apps/web/src/http/endpoints/files/types.ts +++ b/apps/web/src/http/endpoints/files/types.ts @@ -59,6 +59,51 @@ export interface MoveFileBody { folderId: string | null; } +// Multipart upload types +export interface CreateMultipartUploadBody { + filename: string; + extension: string; +} + +export interface CreateMultipartUploadResponse { + uploadId: string; + objectName: string; +} + +export interface GetMultipartPartUrlParams { + uploadId: string; + objectName: string; + partNumber: string; +} + +export interface GetMultipartPartUrlResponse { + url: string; +} + +export interface MultipartPart { + PartNumber: number; + ETag: string; +} + +export interface CompleteMultipartUploadBody { + uploadId: string; + objectName: string; + parts: MultipartPart[]; +} + +export interface CompleteMultipartUploadResponse { + message: string; +} + +export interface AbortMultipartUploadBody { + uploadId: string; + objectName: string; +} + +export interface AbortMultipartUploadResponse { + message: string; +} + export interface GetPresignedUrlParams { filename: string; extension: string; @@ -71,6 +116,10 @@ export type DeleteFile200 = MessageOnlyResponse; export type CheckFile201 = MessageOnlyResponse; export type GetPresignedUrl200 = PresignedUrlResponse; export type GetDownloadUrl200 = DownloadUrlResponse; +export type CreateMultipartUpload201 = CreateMultipartUploadResponse; +export type GetMultipartPartUrl200 = GetMultipartPartUrlResponse; +export type CompleteMultipartUpload200 = CompleteMultipartUploadResponse; +export type AbortMultipartUpload200 = AbortMultipartUploadResponse; export type GetPresignedUrlResult = AxiosResponse; export type RegisterFileResult = AxiosResponse; @@ -80,3 +129,7 @@ export type GetDownloadUrlResult = AxiosResponse; export type DeleteFileResult = AxiosResponse; export type UpdateFileResult = AxiosResponse; export type MoveFileResult = AxiosResponse; +export type CreateMultipartUploadResult = AxiosResponse; +export type GetMultipartPartUrlResult = AxiosResponse; +export type CompleteMultipartUploadResult = AxiosResponse; +export type AbortMultipartUploadResult = AxiosResponse; diff --git a/apps/web/src/http/endpoints/index.ts b/apps/web/src/http/endpoints/index.ts index 6f3961b9..8c745452 100644 --- a/apps/web/src/http/endpoints/index.ts +++ b/apps/web/src/http/endpoints/index.ts @@ -7,3 +7,4 @@ export * from "./reverse-shares"; export * from "./config"; export * from "./app"; export * from "./auth/trusted-devices"; +export * from "./invite"; diff --git a/apps/web/src/http/endpoints/invite/index.ts b/apps/web/src/http/endpoints/invite/index.ts new file mode 100644 index 00000000..04be4e28 --- /dev/null +++ b/apps/web/src/http/endpoints/invite/index.ts @@ -0,0 +1,31 @@ +import type { AxiosRequestConfig } from "axios"; + +import apiInstance from "@/config/api"; +import type { + GenerateInviteTokenResponse, + RegisterWithInviteRequest, + RegisterWithInviteResponse, + ValidateInviteTokenResponse, +} from "./types"; + +export const generateInviteToken = async ( + options?: AxiosRequestConfig +): Promise => { + const response = await apiInstance.post(`/api/invite-tokens`, undefined, options); + return response.data; +}; + +export const validateInviteToken = async ( + token: string, + options?: AxiosRequestConfig +): Promise => { + const response = await apiInstance.get(`/api/invite-tokens/${token}`, options); + return response.data; +}; + +export const registerWithInvite = ( + data: RegisterWithInviteRequest, + options?: AxiosRequestConfig +): Promise => { + return apiInstance.post(`/api/register-with-invite`, data, options); +}; diff --git a/apps/web/src/http/endpoints/invite/types.ts b/apps/web/src/http/endpoints/invite/types.ts new file mode 100644 index 00000000..f89ea5de --- /dev/null +++ b/apps/web/src/http/endpoints/invite/types.ts @@ -0,0 +1,28 @@ +export interface GenerateInviteTokenResponse { + token: string; + expiresAt: string; +} + +export interface ValidateInviteTokenResponse { + valid: boolean; + used?: boolean; + expired?: boolean; +} + +export interface RegisterWithInviteRequest { + token: string; + firstName: string; + lastName: string; + username: string; + email: string; + password: string; +} + +export interface RegisterWithInviteResponse { + message: string; + user: { + id: string; + username: string; + email: string; + }; +} diff --git a/apps/web/src/i18n/request.ts b/apps/web/src/i18n/request.ts index 8b35e26a..2ef4bf59 100644 --- a/apps/web/src/i18n/request.ts +++ b/apps/web/src/i18n/request.ts @@ -17,6 +17,14 @@ const supportedLocales = [ "zh-CN", "ja-JP", "ko-KR", + "th-TH", + "vi-VN", + "uk-UA", + "fa-IR", + "sv-SE", + "id-ID", + "el-GR", + "he-IL", ]; const envDefault = process.env.NEXT_PUBLIC_DEFAULT_LANGUAGE || "en-US"; diff --git a/apps/web/src/lib/download-url-cache.ts b/apps/web/src/lib/download-url-cache.ts new file mode 100644 index 00000000..c498165c --- /dev/null +++ b/apps/web/src/lib/download-url-cache.ts @@ -0,0 +1,112 @@ +import { getDownloadUrl } from "@/http/endpoints"; +import { downloadReverseShareFile } from "@/http/endpoints/reverse-shares"; + +interface CacheEntry { + url: string; + expires: number; + createdAt: number; +} + +class DownloadUrlCache { + private cache = new Map(); + + // Presigned URLs expire in 3600s (1h) + // We cache for 3300s (55min) with a 5min safety margin + private readonly CACHE_DURATION = 3300 * 1000; // 55min in ms + private readonly SAFETY_BUFFER = 60 * 1000; // 1min buffer before using cached URL + + /** + * Generates unique cache key considering objectName and optional share password + */ + private getCacheKey(objectName: string, options?: { headers?: { "x-share-password"?: string } }): string { + const password = options?.headers?.["x-share-password"] || ""; + return password ? `${objectName}:${password}` : objectName; + } + + /** + * Checks if cache entry is still valid + */ + private isValidCacheEntry(entry: CacheEntry): boolean { + const now = Date.now(); + const timeUntilExpiration = entry.expires - now; + return timeUntilExpiration > this.SAFETY_BUFFER; + } + + /** + * Removes expired entries from cache + */ + private evictExpiredEntries(): void { + const now = Date.now(); + + for (const [key, entry] of this.cache.entries()) { + if (entry.expires <= now) { + this.cache.delete(key); + } + } + } + + /** + * Gets download URL with intelligent caching + */ + async getCachedDownloadUrl( + objectName: string, + options?: { headers?: { "x-share-password"?: string } } + ): Promise { + const cacheKey = this.getCacheKey(objectName, options); + const now = Date.now(); + const cached = this.cache.get(cacheKey); + + if (cached && this.isValidCacheEntry(cached)) { + return cached.url; + } + + const response = await getDownloadUrl(objectName, options); + const url = response.data.url; + const entry: CacheEntry = { + url, + expires: now + this.CACHE_DURATION, + createdAt: now, + }; + + this.cache.set(cacheKey, entry); + + if (this.cache.size % 10 === 0) { + this.evictExpiredEntries(); + } + + return url; + } + + /** + * Gets download URL for reverse share with caching + */ + async getCachedReverseShareDownloadUrl(fileId: string): Promise { + const cacheKey = `reverse:${fileId}`; + const now = Date.now(); + const cached = this.cache.get(cacheKey); + + if (cached && this.isValidCacheEntry(cached)) { + return cached.url; + } + + const response = await downloadReverseShareFile(fileId); + const url = response.data.url; + const entry: CacheEntry = { + url, + expires: now + this.CACHE_DURATION, + createdAt: now, + }; + + this.cache.set(cacheKey, entry); + + return url; + } +} + +// Singleton instance +export const downloadUrlCache = new DownloadUrlCache(); + +// Export main methods +export const getCachedDownloadUrl = downloadUrlCache.getCachedDownloadUrl.bind(downloadUrlCache); +export const getCachedReverseShareDownloadUrl = + downloadUrlCache.getCachedReverseShareDownloadUrl.bind(downloadUrlCache); diff --git a/apps/web/src/utils/chunked-upload.ts b/apps/web/src/utils/chunked-upload.ts deleted file mode 100644 index e23b0019..00000000 --- a/apps/web/src/utils/chunked-upload.ts +++ /dev/null @@ -1,311 +0,0 @@ -import axios from "axios"; - -export interface ChunkedUploadOptions { - file: File; - url: string; - chunkSize?: number; - onProgress?: (progress: number) => void; - onChunkComplete?: (chunkIndex: number, totalChunks: number) => void; - signal?: AbortSignal; - isS3Enabled?: boolean; -} - -export interface ChunkedUploadResult { - success: boolean; - objectName?: string; - finalObjectName?: string; - error?: string; -} - -export class ChunkedUploader { - private static defaultChunkSizeInBytes = 100 * 1024 * 1024; // 100MB - - /** - * Upload a file in chunks with streaming - */ - static async uploadFile(options: ChunkedUploadOptions): Promise { - const { file, url, chunkSize, onProgress, onChunkComplete, signal } = options; - - if (!this.shouldUseChunkedUpload(file.size, options.isS3Enabled)) { - throw new Error( - `File ${file.name} (${(file.size / (1024 * 1024)).toFixed(2)}MB) should not use chunked upload. Use regular upload instead.` - ); - } - - const optimalChunkSize = chunkSize || this.calculateOptimalChunkSize(file.size); - - try { - const fileId = this.generateFileId(); - - const totalChunks = Math.ceil(file.size / optimalChunkSize); - - const uploadedChunks = new Set(); - let completedChunks = 0; - let lastChunkResponse: any = null; - - for (let chunkIndex = 0; chunkIndex < totalChunks; chunkIndex++) { - if (signal?.aborted) { - throw new Error("Upload cancelled"); - } - - const start = chunkIndex * optimalChunkSize; - const end = Math.min(start + optimalChunkSize, file.size); - const chunk = file.slice(start, end); - const isLastChunk = chunkIndex === totalChunks - 1; - - let retries = 0; - const maxRetries = 3; - let chunkUploaded = false; - - while (retries < maxRetries && !chunkUploaded) { - try { - const response = await this.uploadChunk({ - fileId, - chunk, - chunkIndex, - totalChunks, - chunkSize: optimalChunkSize, - totalSize: file.size, - fileName: file.name, - isLastChunk, - url, - signal, - }); - - if (isLastChunk) { - lastChunkResponse = response; - } - - chunkUploaded = true; - } catch (error: any) { - retries++; - - if ( - error.response?.status === 400 && - (error.response?.data?.error?.includes("already uploaded") || - error.response?.data?.details?.includes("already uploaded")) - ) { - chunkUploaded = true; - break; - } - - console.warn(`Chunk ${chunkIndex + 1} failed (attempt ${retries}/${maxRetries}):`, error.message); - - if (retries >= maxRetries) { - throw error; - } - - const backoffDelay = error.message?.includes("timeout") ? 2000 * retries : 1000 * retries; - await new Promise((resolve) => setTimeout(resolve, backoffDelay)); - } - } - - if (!chunkUploaded) { - throw new Error(`Failed to upload chunk ${chunkIndex + 1} after ${maxRetries} attempts`); - } - - uploadedChunks.add(chunkIndex); - completedChunks++; - - const progress = Math.round((completedChunks / totalChunks) * 100); - onProgress?.(progress); - onChunkComplete?.(chunkIndex, totalChunks); - - if (!isLastChunk) { - await new Promise((resolve) => setTimeout(resolve, 100)); - } - } - - await new Promise((resolve) => setTimeout(resolve, 500)); - - return { - success: true, - finalObjectName: lastChunkResponse?.finalObjectName || lastChunkResponse?.objectName, - }; - } catch (error: any) { - console.error("Chunked upload failed:", error); - return { - success: false, - error: error.message || "Upload failed", - }; - } - } - - /** - * Upload a single chunk - */ - private static async uploadChunk({ - fileId, - chunk, - chunkIndex, - totalChunks, - chunkSize, - totalSize, - fileName, - isLastChunk, - url, - signal, - }: { - fileId: string; - chunk: Blob; - chunkIndex: number; - totalChunks: number; - chunkSize: number; - totalSize: number; - fileName: string; - isLastChunk: boolean; - url: string; - signal?: AbortSignal; - }): Promise { - // Encode filename as base64 to handle UTF-8 characters in HTTP headers - // This prevents errors when setting headers with non-ASCII characters - const encodedFileName = btoa(unescape(encodeURIComponent(fileName))); - - const headers = { - "Content-Type": "application/octet-stream", - "X-File-Id": fileId, - "X-Chunk-Index": chunkIndex.toString(), - "X-Total-Chunks": totalChunks.toString(), - "X-Chunk-Size": chunkSize.toString(), - "X-Total-Size": totalSize.toString(), - "X-File-Name": encodedFileName, - "X-Is-Last-Chunk": isLastChunk.toString(), - }; - - try { - const timeoutPer100MB = 120000; // 120 seconds per 100MB - const chunkSizeMB = chunk.size / (1024 * 1024); - const calculatedTimeout = Math.max(60000, Math.ceil(chunkSizeMB / 100) * timeoutPer100MB); - - const response = await axios.put(url, chunk, { - headers, - signal, - timeout: calculatedTimeout, - maxContentLength: Infinity, - maxBodyLength: Infinity, - }); - - if (response.status !== 200) { - throw new Error(`Failed to upload chunk ${chunkIndex}: ${response.statusText}`); - } - - return response.data; - } catch (error: any) { - if ( - error.response?.status === 400 && - (error.response?.data?.error?.includes("already uploaded") || - error.response?.data?.details?.includes("already uploaded")) - ) { - return error.response.data; - } - - if (error.code === "ECONNABORTED" || error.message?.includes("timeout")) { - console.warn(`Chunk ${chunkIndex + 1} upload timed out, will retry`); - throw new Error(`Upload timeout for chunk ${chunkIndex + 1}`); - } - - throw error; - } - } - - /** - * Get upload progress - */ - static async getUploadProgress(fileId: string): Promise<{ - uploaded: number; - total: number; - percentage: number; - } | null> { - try { - const response = await axios.get(`/api/filesystem/upload-progress/${fileId}`); - return response.data; - } catch (error) { - console.warn("Failed to get upload progress:", error); - return null; - } - } - - /** - * Cancel upload - */ - static async cancelUpload(fileId: string): Promise { - try { - await axios.delete(`/api/filesystem/cancel-upload/${fileId}`); - } catch (error) { - console.warn("Failed to cancel upload:", error); - } - } - - /** - * Generate unique file ID - */ - private static generateFileId(): string { - return `${Date.now()}-${Math.random().toString(36).substring(2, 15)}`; - } - - /** - * Check if file should use chunked upload - * Only use chunked upload for filesystem storage, not for S3 - */ - static shouldUseChunkedUpload(fileSize: number, isS3Enabled?: boolean): boolean { - if (isS3Enabled) { - return false; - } - - const threshold = this.getConfiguredChunkSize() || this.defaultChunkSizeInBytes; - const shouldUse = fileSize > threshold; - - return shouldUse; - } - - /** - * Calculate optimal chunk size based on file size - */ - static calculateOptimalChunkSize(fileSize: number): number { - const configuredChunkSize = this.getConfiguredChunkSize(); - const chunkSize = configuredChunkSize || this.defaultChunkSizeInBytes; - - if (fileSize <= chunkSize) { - throw new Error( - `calculateOptimalChunkSize should not be called for files <= ${chunkSize}. File size: ${(fileSize / (1024 * 1024)).toFixed(2)}MB` - ); - } - - if (configuredChunkSize) { - return configuredChunkSize; - } - - // For files > 1GB, use 150MB chunks - if (fileSize > 1024 * 1024 * 1024) { - return 150 * 1024 * 1024; - } - - // For files > 500MB, use 100MB chunks - if (fileSize > 500 * 1024 * 1024) { - return 100 * 1024 * 1024; - } - - // For files > 100MB, use 75MB chunks (minimum for chunked upload) - return 75 * 1024 * 1024; - } - - private static getConfiguredChunkSize(): number | null { - const configuredChunkSizeMb = process.env.NEXT_PUBLIC_UPLOAD_CHUNK_SIZE_MB; - - if (!configuredChunkSizeMb) { - return null; - } - - const parsedValue = Number(configuredChunkSizeMb); - - if (Number.isNaN(parsedValue) || parsedValue <= 0) { - console.warn( - `Invalid NEXT_PUBLIC_UPLOAD_CHUNK_SIZE_MB value: ${configuredChunkSizeMb}. Falling back to optimal chunk size.` - ); - - return null; - } - - return Math.floor(parsedValue * 1024 * 1024); - } -} diff --git a/apps/web/src/utils/download-queue-utils.ts b/apps/web/src/utils/download-queue-utils.ts deleted file mode 100644 index eb247a47..00000000 --- a/apps/web/src/utils/download-queue-utils.ts +++ /dev/null @@ -1,623 +0,0 @@ -import { toast } from "sonner"; - -import { getDownloadUrl } from "@/http/endpoints"; -import { downloadReverseShareFile } from "@/http/endpoints/reverse-shares"; - -interface DownloadWithQueueOptions { - useQueue?: boolean; - silent?: boolean; - showToasts?: boolean; - sharePassword?: string; - onStart?: (downloadId: string) => void; - onComplete?: (downloadId: string) => void; - onFail?: (downloadId: string, error: string) => void; -} - -async function waitForDownloadReady(objectName: string, fileName: string): Promise { - let attempts = 0; - const maxAttempts = 30; - let currentDelay = 2000; - const maxDelay = 10000; - - while (attempts < maxAttempts) { - try { - const response = await getDownloadUrl(objectName); - - if (response.status !== 202) { - return response.data.url; - } - - await new Promise((resolve) => setTimeout(resolve, currentDelay)); - - if (attempts > 3 && currentDelay < maxDelay) { - currentDelay = Math.min(currentDelay * 1.5, maxDelay); - } - - attempts++; - } catch (error) { - console.error(`Error checking download status for ${fileName}:`, error); - await new Promise((resolve) => setTimeout(resolve, currentDelay * 2)); - attempts++; - } - } - - throw new Error(`Download timeout for ${fileName} after ${attempts} attempts`); -} - -async function waitForReverseShareDownloadReady(fileId: string, fileName: string): Promise { - let attempts = 0; - const maxAttempts = 30; - let currentDelay = 2000; - const maxDelay = 10000; - - while (attempts < maxAttempts) { - try { - const response = await downloadReverseShareFile(fileId); - - if (response.status !== 202) { - return response.data.url; - } - - await new Promise((resolve) => setTimeout(resolve, currentDelay)); - - if (attempts > 3 && currentDelay < maxDelay) { - currentDelay = Math.min(currentDelay * 1.5, maxDelay); - } - - attempts++; - } catch (error) { - console.error(`Error checking reverse share download status for ${fileName}:`, error); - await new Promise((resolve) => setTimeout(resolve, currentDelay * 2)); - attempts++; - } - } - - throw new Error(`Reverse share download timeout for ${fileName} after ${attempts} attempts`); -} - -async function performDownload(url: string, fileName: string): Promise { - const link = document.createElement("a"); - link.href = url; - link.download = fileName; - document.body.appendChild(link); - link.click(); - document.body.removeChild(link); -} - -export async function downloadFileWithQueue( - objectName: string, - fileName: string, - options: DownloadWithQueueOptions = {} -): Promise { - const { useQueue = true, silent = false, showToasts = true, sharePassword } = options; - const downloadId = `${Date.now()}-${Math.random().toString(36).substring(2, 11)}`; - - try { - if (!silent) { - options.onStart?.(downloadId); - } - - // getDownloadUrl already handles encoding - const params: Record = {}; - if (sharePassword) params.password = sharePassword; - - const response = await getDownloadUrl( - objectName, - Object.keys(params).length > 0 - ? { - params: { ...params }, - } - : undefined - ); - - if (response.status === 202 && useQueue) { - if (!silent && showToasts) { - toast.info(`${fileName} was added to download queue`, { - description: "Download will start automatically when queue space is available", - duration: 5000, - }); - } - - const actualDownloadUrl = await waitForDownloadReady(objectName, fileName); - await performDownload(actualDownloadUrl, fileName); - } else { - await performDownload(response.data.url, fileName); - } - - if (!silent) { - options.onComplete?.(downloadId); - if (showToasts) { - toast.success(`${fileName} downloaded successfully`); - } - } - } catch (error: any) { - if (!silent) { - options.onFail?.(downloadId, error?.message || "Download failed"); - if (showToasts) { - toast.error(`Failed to download ${fileName}`); - } - } - throw error; - } -} - -export async function downloadReverseShareWithQueue( - fileId: string, - fileName: string, - options: DownloadWithQueueOptions = {} -): Promise { - const { silent = false, showToasts = true } = options; - const downloadId = `reverse-${Date.now()}-${Math.random().toString(36).substring(2, 11)}`; - - try { - if (!silent) { - options.onStart?.(downloadId); - } - - const response = await downloadReverseShareFile(fileId); - - if (response.status === 202) { - if (!silent && showToasts) { - toast.info(`${fileName} was added to download queue`, { - description: "Download will start automatically when queue space is available", - duration: 5000, - }); - } - - const actualDownloadUrl = await waitForReverseShareDownloadReady(fileId, fileName); - await performDownload(actualDownloadUrl, fileName); - } else { - await performDownload(response.data.url, fileName); - } - - if (!silent) { - options.onComplete?.(downloadId); - if (showToasts) { - toast.success(`${fileName} downloaded successfully`); - } - } - } catch (error: any) { - if (!silent) { - options.onFail?.(downloadId, error?.message || "Download failed"); - if (showToasts) { - toast.error(`Failed to download ${fileName}`); - } - } - throw error; - } -} - -export async function downloadFileAsBlobWithQueue( - objectName: string, - fileName: string, - isReverseShare: boolean = false, - fileId?: string, - sharePassword?: string -): Promise { - try { - let downloadUrl: string; - - if (isReverseShare && fileId) { - const response = await downloadReverseShareFile(fileId); - - if (response.status === 202) { - downloadUrl = await waitForReverseShareDownloadReady(fileId, fileName); - } else { - downloadUrl = response.data.url; - } - } else { - // getDownloadUrl already handles encoding - const params: Record = {}; - if (sharePassword) params.password = sharePassword; - - const response = await getDownloadUrl( - objectName, - Object.keys(params).length > 0 - ? { - params: { ...params }, - } - : undefined - ); - - if (response.status === 202) { - downloadUrl = await waitForDownloadReady(objectName, fileName); - } else { - downloadUrl = response.data.url; - } - } - - const fetchResponse = await fetch(downloadUrl); - if (!fetchResponse.ok) { - throw new Error(`Failed to download ${fileName}: ${fetchResponse.status}`); - } - - return await fetchResponse.blob(); - } catch (error: any) { - console.error(`Error downloading ${fileName}:`, error); - throw error; - } -} - -function collectFolderFiles( - folderId: string, - allFiles: any[], - allFolders: any[], - folderPath: string = "" -): Array<{ objectName: string; name: string; zipPath: string }> { - const result: Array<{ objectName: string; name: string; zipPath: string }> = []; - - const directFiles = allFiles.filter((file: any) => file.folderId === folderId); - for (const file of directFiles) { - result.push({ - objectName: file.objectName, - name: file.name, - zipPath: folderPath + file.name, - }); - } - - const subfolders = allFolders.filter((folder: any) => folder.parentId === folderId); - for (const subfolder of subfolders) { - const subfolderPath = folderPath + subfolder.name + "/"; - const subFiles = collectFolderFiles(subfolder.id, allFiles, allFolders, subfolderPath); - result.push(...subFiles); - } - - return result; -} - -function collectEmptyFolders(folderId: string, allFiles: any[], allFolders: any[], folderPath: string = ""): string[] { - const emptyFolders: string[] = []; - - const subfolders = allFolders.filter((folder: any) => folder.parentId === folderId); - for (const subfolder of subfolders) { - const subfolderPath = folderPath + subfolder.name + "/"; - - const subfolderFiles = collectFolderFiles(subfolder.id, allFiles, allFolders, ""); - - if (subfolderFiles.length === 0) { - emptyFolders.push(subfolderPath.slice(0, -1)); - } - - const nestedEmptyFolders = collectEmptyFolders(subfolder.id, allFiles, allFolders, subfolderPath); - emptyFolders.push(...nestedEmptyFolders); - } - - return emptyFolders; -} - -export async function downloadFolderWithQueue( - folderId: string, - folderName: string, - options: DownloadWithQueueOptions = {} -): Promise { - const { silent = false, showToasts = true } = options; - const downloadId = `folder-${Date.now()}-${Math.random().toString(36).substring(2, 11)}`; - - try { - if (!silent) { - options.onStart?.(downloadId); - } - - const { listFiles } = await import("@/http/endpoints/files"); - const { listFolders } = await import("@/http/endpoints/folders"); - - const [allFilesResponse, allFoldersResponse] = await Promise.all([listFiles(), listFolders()]); - const allFiles = allFilesResponse.data.files || []; - const allFolders = allFoldersResponse.data.folders || []; - - const folderFiles = collectFolderFiles(folderId, allFiles, allFolders, `${folderName}/`); - const emptyFolders = collectEmptyFolders(folderId, allFiles, allFolders, `${folderName}/`); - - if (folderFiles.length === 0 && emptyFolders.length === 0) { - const message = "Folder is empty"; - if (showToasts) { - toast.error(message); - } - throw new Error(message); - } - - const JSZip = (await import("jszip")).default; - const zip = new JSZip(); - - for (const emptyFolderPath of emptyFolders) { - zip.folder(emptyFolderPath); - } - - for (const file of folderFiles) { - try { - const blob = await downloadFileAsBlobWithQueue(file.objectName, file.name); - zip.file(file.zipPath, blob); - } catch (error) { - console.error(`Error downloading file ${file.name}:`, error); - } - } - - const zipBlob = await zip.generateAsync({ type: "blob" }); - const url = URL.createObjectURL(zipBlob); - const a = document.createElement("a"); - a.href = url; - a.download = `${folderName}.zip`; - document.body.appendChild(a); - a.click(); - document.body.removeChild(a); - URL.revokeObjectURL(url); - - if (!silent) { - options.onComplete?.(downloadId); - if (showToasts) { - toast.success(`${folderName} downloaded successfully`); - } - } - } catch (error: any) { - if (!silent) { - options.onFail?.(downloadId, error?.message || "Download failed"); - if (showToasts) { - toast.error(`Failed to download ${folderName}`); - } - } - throw error; - } -} - -export async function downloadShareFolderWithQueue( - folderId: string, - folderName: string, - shareFiles: any[], - shareFolders: any[], - options: DownloadWithQueueOptions = {} -): Promise { - const { silent = false, showToasts = true, sharePassword } = options; - const downloadId = `share-folder-${Date.now()}-${Math.random().toString(36).substring(2, 11)}`; - - try { - if (!silent) { - options.onStart?.(downloadId); - } - - const folderFiles = collectFolderFiles(folderId, shareFiles, shareFolders, `${folderName}/`); - const emptyFolders = collectEmptyFolders(folderId, shareFiles, shareFolders, `${folderName}/`); - - if (folderFiles.length === 0 && emptyFolders.length === 0) { - const message = "Folder is empty"; - if (showToasts) { - toast.error(message); - } - throw new Error(message); - } - - const JSZip = (await import("jszip")).default; - const zip = new JSZip(); - - for (const emptyFolderPath of emptyFolders) { - zip.folder(emptyFolderPath); - } - - for (const file of folderFiles) { - try { - const blob = await downloadFileAsBlobWithQueue(file.objectName, file.name, false, undefined, sharePassword); - zip.file(file.zipPath, blob); - } catch (error) { - console.error(`Error downloading file ${file.name}:`, error); - } - } - - const zipBlob = await zip.generateAsync({ type: "blob" }); - const url = URL.createObjectURL(zipBlob); - const a = document.createElement("a"); - a.href = url; - a.download = `${folderName}.zip`; - document.body.appendChild(a); - a.click(); - document.body.removeChild(a); - URL.revokeObjectURL(url); - - if (!silent) { - options.onComplete?.(downloadId); - if (showToasts) { - toast.success(`${folderName} downloaded successfully`); - } - } - } catch (error: any) { - if (!silent) { - options.onFail?.(downloadId, error?.message || "Download failed"); - if (showToasts) { - toast.error(`Failed to download ${folderName}`); - } - } - throw error; - } -} - -export async function bulkDownloadWithQueue( - items: Array<{ - objectName?: string; - name: string; - id?: string; - isReverseShare?: boolean; - type?: "file" | "folder"; - }>, - zipName: string, - onProgress?: (current: number, total: number) => void, - wrapInFolder?: boolean -): Promise { - try { - const JSZip = (await import("jszip")).default; - const zip = new JSZip(); - - const files = items.filter((item) => item.type !== "folder"); - const folders = items.filter((item) => item.type === "folder"); - - // eslint-disable-next-line prefer-const - let allFilesToDownload: Array<{ - objectName: string; - name: string; - zipPath: string; - isReverseShare?: boolean; - fileId?: string; - }> = []; - // eslint-disable-next-line prefer-const - let allEmptyFolders: string[] = []; - - if (folders.length > 0) { - const { listFiles } = await import("@/http/endpoints/files"); - const { listFolders } = await import("@/http/endpoints/folders"); - - const [allFilesResponse, allFoldersResponse] = await Promise.all([listFiles(), listFolders()]); - const allFiles = allFilesResponse.data.files || []; - const allFolders = allFoldersResponse.data.folders || []; - - const wrapperPath = wrapInFolder ? `${zipName.replace(".zip", "")}/` : ""; - for (const folder of folders) { - const folderPath = wrapperPath + `${folder.name}/`; - const folderFiles = collectFolderFiles(folder.id!, allFiles, allFolders, folderPath); - const emptyFolders = collectEmptyFolders(folder.id!, allFiles, allFolders, folderPath); - - allFilesToDownload.push(...folderFiles); - allEmptyFolders.push(...emptyFolders); - - if (folderFiles.length === 0 && emptyFolders.length === 0) { - allEmptyFolders.push(folderPath.slice(0, -1)); - } - } - - const filesInFolders = new Set(allFilesToDownload.map((f) => f.objectName)); - for (const file of files) { - if (!file.objectName || !filesInFolders.has(file.objectName)) { - allFilesToDownload.push({ - objectName: file.objectName || file.name, - name: file.name, - zipPath: wrapperPath + file.name, - isReverseShare: file.isReverseShare, - fileId: file.id, - }); - } - } - } else { - const wrapperPath = wrapInFolder ? `${zipName.replace(".zip", "")}/` : ""; - for (const file of files) { - allFilesToDownload.push({ - objectName: file.objectName || file.name, - name: file.name, - zipPath: wrapperPath + file.name, - isReverseShare: file.isReverseShare, - fileId: file.id, - }); - } - } - - for (const emptyFolderPath of allEmptyFolders) { - zip.folder(emptyFolderPath); - } - - for (let i = 0; i < allFilesToDownload.length; i++) { - const file = allFilesToDownload[i]; - try { - const blob = await downloadFileAsBlobWithQueue( - file.objectName, - file.name, - file.isReverseShare || false, - file.fileId - ); - zip.file(file.zipPath, blob); - onProgress?.(i + 1, allFilesToDownload.length); - } catch (error) { - console.error(`Error downloading file ${file.name}:`, error); - } - } - - const zipBlob = await zip.generateAsync({ type: "blob" }); - const url = URL.createObjectURL(zipBlob); - const a = document.createElement("a"); - a.href = url; - a.download = zipName.endsWith(".zip") ? zipName : `${zipName}.zip`; - document.body.appendChild(a); - a.click(); - document.body.removeChild(a); - URL.revokeObjectURL(url); - } catch (error) { - console.error("Error creating ZIP:", error); - throw error; - } -} - -export async function bulkDownloadShareWithQueue( - items: Array<{ - objectName?: string; - name: string; - id?: string; - type?: "file" | "folder"; - }>, - shareFiles: any[], - shareFolders: any[], - zipName: string, - onProgress?: (current: number, total: number) => void, - wrapInFolder?: boolean, - sharePassword?: string -): Promise { - try { - const JSZip = (await import("jszip")).default; - const zip = new JSZip(); - - const files = items.filter((item) => item.type !== "folder"); - const folders = items.filter((item) => item.type === "folder"); - - // eslint-disable-next-line prefer-const - let allFilesToDownload: Array<{ objectName: string; name: string; zipPath: string }> = []; - // eslint-disable-next-line prefer-const - let allEmptyFolders: string[] = []; - - const wrapperPath = wrapInFolder ? `${zipName.replace(".zip", "")}/` : ""; - - for (const folder of folders) { - const folderPath = wrapperPath + `${folder.name}/`; - const folderFiles = collectFolderFiles(folder.id!, shareFiles, shareFolders, folderPath); - const emptyFolders = collectEmptyFolders(folder.id!, shareFiles, shareFolders, folderPath); - - allFilesToDownload.push(...folderFiles); - allEmptyFolders.push(...emptyFolders); - - if (folderFiles.length === 0 && emptyFolders.length === 0) { - allEmptyFolders.push(folderPath.slice(0, -1)); - } - } - - const filesInFolders = new Set(allFilesToDownload.map((f) => f.objectName)); - for (const file of files) { - if (!file.objectName || !filesInFolders.has(file.objectName)) { - allFilesToDownload.push({ - objectName: file.objectName!, - name: file.name, - zipPath: wrapperPath + file.name, - }); - } - } - - for (const emptyFolderPath of allEmptyFolders) { - zip.folder(emptyFolderPath); - } - - for (let i = 0; i < allFilesToDownload.length; i++) { - const file = allFilesToDownload[i]; - try { - const blob = await downloadFileAsBlobWithQueue(file.objectName, file.name, false, undefined, sharePassword); - zip.file(file.zipPath, blob); - onProgress?.(i + 1, allFilesToDownload.length); - } catch (error) { - console.error(`Error downloading file ${file.name}:`, error); - } - } - - const zipBlob = await zip.generateAsync({ type: "blob" }); - const url = URL.createObjectURL(zipBlob); - const a = document.createElement("a"); - a.href = url; - a.download = zipName.endsWith(".zip") ? zipName : `${zipName}.zip`; - document.body.appendChild(a); - a.click(); - document.body.removeChild(a); - URL.revokeObjectURL(url); - } catch (error) { - console.error("Error creating ZIP:", error); - throw error; - } -} diff --git a/apps/web/src/utils/zip-download.ts b/apps/web/src/utils/zip-download.ts new file mode 100644 index 00000000..bb8a5d0b --- /dev/null +++ b/apps/web/src/utils/zip-download.ts @@ -0,0 +1,59 @@ +import JSZip from "jszip"; + +interface DownloadItem { + url: string; + name: string; +} + +/** + * Downloads multiple files and creates a ZIP archive in the browser + * @param items Array of items with presigned URLs and file names + * @param zipName Name for the ZIP file + */ +export async function downloadFilesAsZip(items: DownloadItem[], zipName: string): Promise { + const zip = new JSZip(); + + // Download all files and add to ZIP + const promises = items.map(async (item) => { + try { + const response = await fetch(item.url); + if (!response.ok) { + throw new Error(`Failed to download ${item.name}`); + } + const blob = await response.blob(); + zip.file(item.name, blob); + } catch (error) { + console.error(`Error downloading ${item.name}:`, error); + throw error; + } + }); + + await Promise.all(promises); + + // Generate ZIP file + const zipBlob = await zip.generateAsync({ type: "blob" }); + + // Trigger download + const url = URL.createObjectURL(zipBlob); + const link = document.createElement("a"); + link.href = url; + link.download = zipName; + document.body.appendChild(link); + link.click(); + document.body.removeChild(link); + URL.revokeObjectURL(url); +} + +/** + * Downloads a single file directly + * @param url Presigned URL + * @param fileName File name + */ +export async function downloadFile(url: string, fileName: string): Promise { + const link = document.createElement("a"); + link.href = url; + link.download = fileName; + document.body.appendChild(link); + link.click(); + document.body.removeChild(link); +} diff --git a/docker-compose-bind-mount-example.yaml b/docker-compose-bind-mount-example.yaml deleted file mode 100644 index 323c60aa..00000000 --- a/docker-compose-bind-mount-example.yaml +++ /dev/null @@ -1,31 +0,0 @@ -services: - palmr: - image: kyantech/palmr:latest - container_name: palmr - environment: - # Optional: Uncomment and configure as needed (if you don`t use, you can remove) - # - ENABLE_S3=true # Set to true to enable S3-compatible storage (OPTIONAL - default is false) - # - S3_REJECT_UNAUTHORIZED=false # Set to false to allow self-signed certificates (OPTIONAL - default is true) - # - DISABLE_FILESYSTEM_ENCRYPTION=false # Set to false to enable file encryption (ENCRYPTION_KEY becomes required) | (OPTIONAL - default is true) - # - ENCRYPTION_KEY=change-this-key-in-production-min-32-chars # CHANGE THIS KEY FOR SECURITY (REQUIRED if DISABLE_FILESYSTEM_ENCRYPTION is false) - # - PALMR_UID=1000 # UID for the container processes (OPTIONAL - default is 1000) | See our UID/GID Documentation for more information - # - PALMR_GID=1000 # GID for the container processes (OPTIONAL - default is 1000) | See our UID/GID Documentation for more information - # - DEFAULT_LANGUAGE=en-US # Default language for the application (optional, defaults to en-US) | See the docs for see all supported languages - # - PRESIGNED_URL_EXPIRATION=3600 # Duration in seconds for presigned URL expiration (OPTIONAL - default is 3600 seconds / 1 hour) - # - SECURE_SITE=true # Set to true if you are using a reverse proxy (OPTIONAL - default is false) - - # Download Memory Management Configuration (OPTIONAL - See documentation for details) - # - DOWNLOAD_MAX_CONCURRENT=5 # Maximum number of simultaneous downloads (OPTIONAL - auto-scales based on system memory if not set) - # - DOWNLOAD_MEMORY_THRESHOLD_MB=2048 # Memory threshold in MB before throttling (OPTIONAL - auto-scales based on system memory if not set) - # - DOWNLOAD_QUEUE_SIZE=25 # Maximum queue size for pending downloads (OPTIONAL - auto-scales based on system memory if not set) - # - DOWNLOAD_MIN_FILE_SIZE_GB=3.0 # Minimum file size in GB to activate memory management (OPTIONAL - default is 3.0) - # - DOWNLOAD_AUTO_SCALE=true # Enable auto-scaling based on system memory (OPTIONAL - default is true) - # - NODE_OPTIONS=--expose-gc # Enable garbage collection for large file downloads (RECOMMENDED for production) - # - NEXT_PUBLIC_UPLOAD_CHUNK_SIZE_MB=100 # Chunk size in MB for large file uploads (OPTIONAL - auto-calculates if not set) - ports: - - "5487:5487" # Web port - - "3333:3333" # API port (OPTIONAL EXPOSED - ONLY IF YOU WANT TO ACCESS THE API DIRECTLY) - volumes: - # Bind mount for persistent data (uploads, database, temp files) - - ./data:/app/server # Volume for the application data - restart: unless-stopped # Restart the container unless it is stopped diff --git a/docker-compose-minio.yaml b/docker-compose-minio.yaml deleted file mode 100644 index 6c1d491e..00000000 --- a/docker-compose-minio.yaml +++ /dev/null @@ -1,38 +0,0 @@ -services: - palmr: - image: kyantech/palmr:latest - container_name: palmr - environment: - # MinIO Configuration (only used when ENABLE_S3=true) - - ENABLE_S3=true # Set to true to use S3 storage - - S3_ENDPOINT=${S3_ENDPOINT} # S3 endpoint (you have to set this to the s3 endpoint of the s3 server) CHANGE THIS TO YOUR S3 ENDPOINT - - S3_USE_SSL=${S3_USE_SSL:-true} # Use ssl for the s3 server always true for s3 - - S3_ACCESS_KEY=${S3_ACCESS_KEY} # S3 access key (you have to generate this key in s3 server) - - S3_SECRET_KEY=${S3_SECRET_KEY} # S3 secret key (you have to generate this key in s3 server) - - S3_REGION=${S3_REGION:-us-east-1} # S3 region (us-east-1 is the default region) but it depends on your s3 server region - - S3_BUCKET_NAME=${S3_BUCKET_NAME:-palmr-files} # Bucket name for the S3 storage (here we are using palmr-files as the bucket name to understand that this is the bucket for palmr) - - S3_FORCE_PATH_STYLE=true # For MinIO compatibility we have to set this to true - # - S3_REJECT_UNAUTHORIZED=false # Set to false to allow self-signed certificates (default: true) - # - PALMR_UID=1000 # UID for the container processes (OPTIONAL - default is 1000) | See our UID/GID Documentation for more information - # - PALMR_GID=1000 # GID for the container processes (OPTIONAL - default is 1000) | See our UID/GID Documentation for more information - # - DEFAULT_LANGUAGE=en-US # Default language for the application (optional, defaults to en-US) | See the docs for see all supported languages - # - PRESIGNED_URL_EXPIRATION=3600 # Duration in seconds for presigned URL expiration (OPTIONAL - default is 3600 seconds / 1 hour) - # - SECURE_SITE=true # Set to true if you are using a reverse proxy (OPTIONAL - default is false) - - # Download Memory Management Configuration (OPTIONAL - See documentation for details) - # - DOWNLOAD_MAX_CONCURRENT=5 # Maximum number of simultaneous downloads (OPTIONAL - auto-scales based on system memory if not set) - # - DOWNLOAD_MEMORY_THRESHOLD_MB=2048 # Memory threshold in MB before throttling (OPTIONAL - auto-scales based on system memory if not set) - # - DOWNLOAD_QUEUE_SIZE=25 # Maximum queue size for pending downloads (OPTIONAL - auto-scales based on system memory if not set) - # - DOWNLOAD_MIN_FILE_SIZE_GB=3.0 # Minimum file size in GB to activate memory management (OPTIONAL - default is 3.0) - # - DOWNLOAD_AUTO_SCALE=true # Enable auto-scaling based on system memory (OPTIONAL - default is true) - # - NODE_OPTIONS=--expose-gc # Enable garbage collection for large file downloads (RECOMMENDED for production) - # - NEXT_PUBLIC_UPLOAD_CHUNK_SIZE_MB=100 # Chunk size in MB for large file uploads (OPTIONAL - auto-calculates if not set) - ports: - - "5487:5487" # Web port - - "3333:3333" # API port (OPTIONAL EXPOSED - ONLY IF YOU WANT TO ACCESS THE API DIRECTLY) - volumes: - - palmr_data:/app/server # Volume for the application data - restart: unless-stopped # Restart the container unless it is stopped - -volumes: - palmr_data: diff --git a/docker-compose-s3.yaml b/docker-compose-s3.yaml deleted file mode 100644 index a8e8ff54..00000000 --- a/docker-compose-s3.yaml +++ /dev/null @@ -1,38 +0,0 @@ -services: - palmr: - image: kyantech/palmr:latest - container_name: palmr - environment: - # S3 Configuration (only used when ENABLE_S3=true) - - ENABLE_S3=true # Set to true to use S3 storage - - S3_ENDPOINT=${S3_ENDPOINT} # S3 endpoint (you have to set this to the s3 endpoint of the s3 server) CHANGE THIS TO YOUR S3 ENDPOINT - - S3_USE_SSL=${S3_USE_SSL:-true} # Use ssl for the s3 server always true for s3 - - S3_ACCESS_KEY=${S3_ACCESS_KEY} # S3 access key (you have to generate this key in s3 server) - - S3_SECRET_KEY=${S3_SECRET_KEY} # S3 secret key (you have to generate this key in s3 server) - - S3_REGION=${S3_REGION:-us-east-1} # S3 region (us-east-1 is the default region) but it depends on your s3 server region - - S3_BUCKET_NAME=${S3_BUCKET_NAME:-palmr-files} # Bucket name for the S3 storage (here we are using palmr-files as the bucket name to understand that this is the bucket for palmr) - - S3_FORCE_PATH_STYLE=false # For S3 compatibility we have to set this to false - # - S3_REJECT_UNAUTHORIZED=false # Set to false to allow self-signed certificates (default: true) - # - PALMR_UID=1000 # UID for the container processes (OPTIONAL - default is 1000) | See our UID/GID Documentation for more information - # - PALMR_GID=1000 # GID for the container processes (OPTIONAL - default is 1000) | See our UID/GID Documentation for more information - # - DEFAULT_LANGUAGE=en-US # Default language for the application (optional, defaults to en-US) | See the docs for see all supported languages - # - PRESIGNED_URL_EXPIRATION=3600 # Duration in seconds for presigned URL expiration (OPTIONAL - default is 3600 seconds / 1 hour) - # - SECURE_SITE=true # Set to true if you are using a reverse proxy (OPTIONAL - default is false) - - # Download Memory Management Configuration (OPTIONAL - See documentation for details) - # - DOWNLOAD_MAX_CONCURRENT=5 # Maximum number of simultaneous downloads (OPTIONAL - auto-scales based on system memory if not set) - # - DOWNLOAD_MEMORY_THRESHOLD_MB=2048 # Memory threshold in MB before throttling (OPTIONAL - auto-scales based on system memory if not set) - # - DOWNLOAD_QUEUE_SIZE=25 # Maximum queue size for pending downloads (OPTIONAL - auto-scales based on system memory if not set) - # - DOWNLOAD_MIN_FILE_SIZE_GB=3.0 # Minimum file size in GB to activate memory management (OPTIONAL - default is 3.0) - # - DOWNLOAD_AUTO_SCALE=true # Enable auto-scaling based on system memory (OPTIONAL - default is true) - # - NODE_OPTIONS=--expose-gc # Enable garbage collection for large file downloads (RECOMMENDED for production) - # - NEXT_PUBLIC_UPLOAD_CHUNK_SIZE_MB=100 # Chunk size in MB for large file uploads (OPTIONAL - auto-calculates if not set) - ports: - - "5487:5487" # Web port - - "3333:3333" # API port (OPTIONAL EXPOSED - ONLY IF YOU WANT TO ACCESS THE API DIRECTLY) - volumes: - - palmr_data:/app/server # Volume for the application data - restart: unless-stopped # Restart the container unless it is stopped - -volumes: - palmr_data: diff --git a/docker-compose.yaml b/docker-compose.yaml index a14c1028..9d37b41d 100644 --- a/docker-compose.yaml +++ b/docker-compose.yaml @@ -3,31 +3,53 @@ services: image: kyantech/palmr:latest container_name: palmr environment: - # Optional: Uncomment and configure as needed (if you don`t use, you can remove) - # - ENABLE_S3=false # Set to true to enable S3-compatible storage (OPTIONAL - default is false) - # - S3_REJECT_UNAUTHORIZED=false # Set to false to allow self-signed certificates (OPTIONAL - default is true) - # - DISABLE_FILESYSTEM_ENCRYPTION=true # Set to false to enable file encryption (ENCRYPTION_KEY becomes required) | (OPTIONAL - default is true) - # - ENCRYPTION_KEY=change-this-key-in-production-min-32-chars # CHANGE THIS KEY FOR SECURITY (REQUIRED if DISABLE_FILESYSTEM_ENCRYPTION is false) - # - PALMR_UID=1000 # UID for the container processes (OPTIONAL - default is 1000) | See our UID/GID Documentation for more information - # - PALMR_GID=1000 # GID for the container processes (OPTIONAL - default is 1000) | See our UID/GID Documentation for more information - # - DEFAULT_LANGUAGE=en-US # Default language for the application (optional, defaults to en-US) | See the docs to see all supported languages - # - PRESIGNED_URL_EXPIRATION=3600 # Duration in seconds for presigned URL expiration (OPTIONAL - default is 3600 seconds / 1 hour) - # - SECURE_SITE=true # Set to true if you are using a reverse proxy (OPTIONAL - default is false) - - # Download Memory Management Configuration (OPTIONAL - See documentation for details) - # - DOWNLOAD_MAX_CONCURRENT=5 # Maximum number of simultaneous downloads (OPTIONAL - auto-scales based on system memory if not set) - # - DOWNLOAD_MEMORY_THRESHOLD_MB=2048 # Memory threshold in MB before throttling (OPTIONAL - auto-scales based on system memory if not set) - # - DOWNLOAD_QUEUE_SIZE=25 # Maximum queue size for pending downloads (OPTIONAL - auto-scales based on system memory if not set) - # - DOWNLOAD_MIN_FILE_SIZE_GB=3.0 # Minimum file size in GB to activate memory management (OPTIONAL - default is 3.0) - # - DOWNLOAD_AUTO_SCALE=true # Enable auto-scaling based on system memory (OPTIONAL - default is true) - # - NODE_OPTIONS=--expose-gc # Enable garbage collection for large file downloads (RECOMMENDED for production) - # - NEXT_PUBLIC_UPLOAD_CHUNK_SIZE_MB=100 # Chunk size in MB for large file uploads (OPTIONAL - auto-calculates if not set) + # ============================================================================== + # STORAGE CONFIGURATION + # ============================================================================== + # By default, Palmr uses internal storage - ZERO CONFIG NEEDED! + # Files are managed automatically with no setup required. + # + # Want to use external S3 storage (AWS, S3-compatible, etc)? Just add: + # - ENABLE_S3=true # Enable external S3 + # - S3_ENDPOINT=s3.amazonaws.com # Your S3 endpoint + # - S3_ACCESS_KEY=your-access-key # Your access key + # - S3_SECRET_KEY=your-secret-key # Your secret key + # - S3_BUCKET_NAME=palmr-files # Your bucket name + # - S3_REGION=us-east-1 # Region (optional) + # - S3_USE_SSL=true # Use SSL (optional) + # - S3_FORCE_PATH_STYLE=false # Path-style URLs (optional, true for Minio) + # - S3_REJECT_UNAUTHORIZED=true # Reject self-signed certs (optional) + # + # ============================================================================== + # USER/GROUP CONFIGURATION + # ============================================================================== + # - PALMR_UID=1000 # UID for container processes (optional) + # - PALMR_GID=1000 # GID for container processes (optional) + # + # ============================================================================== + # APPLICATION SETTINGS + # ============================================================================== + # - DEFAULT_LANGUAGE=en-US # Default language (optional) + # - PRESIGNED_URL_EXPIRATION=3600 # Presigned URL expiration in seconds (optional) + # - SECURE_SITE=true # Set true if using HTTPS reverse proxy (optional) + STORAGE_URL: "https://palmr-demo:9379" # REQUIRED for internal storage: Full storage URL with protocol (e.g., https://syrg.palmr.com or http://192.168.1.100:9379). Not needed when ENABLE_S3=true. + # ports: - - "5487:5487" # Web port - - "3333:3333" # API port (OPTIONAL EXPOSED - ONLY IF YOU WANT TO ACCESS THE API DIRECTLY) + - "9379:9379" # Internal storage (S3-compatible, REQUIRED for file uploads when using internal storage) + - "5487:5487" # Web interface + - "3333:3333" # API (optional, only if you need direct API access) volumes: - - palmr_data:/app/server # Volume for the application data (changed from /data to /app/server) - restart: unless-stopped # Restart the container unless it is stopped + - palmr_data:/app/server + # ============================================================================== + # ADVANCED: Use a different disk for file storage (for larger capacity) + # ============================================================================== + # Uncomment the line below to store files on a different disk: + # - /path/to/your/large/disk:/app/server/data + # + # Example: Mount a 2TB drive for files while keeping database on fast SSD + # - /mnt/storage/palmr-files:/app/server/data + # + restart: unless-stopped volumes: palmr_data: diff --git a/infra/install-minio.sh b/infra/install-minio.sh new file mode 100755 index 00000000..8335586c --- /dev/null +++ b/infra/install-minio.sh @@ -0,0 +1,60 @@ +#!/bin/sh +# Download storage system binary for the appropriate architecture +# This script is run during Docker build + +set -e + +MINIO_VERSION="RELEASE.2024-10-13T13-34-11Z" +ARCH=$(uname -m) + +echo "[BUILD] Downloading storage system ${MINIO_VERSION} for ${ARCH}..." + +case "$ARCH" in + x86_64) + MINIO_ARCH="linux-amd64" + ;; + aarch64|arm64) + MINIO_ARCH="linux-arm64" + ;; + *) + echo "[BUILD] Unsupported architecture: $ARCH" + echo "[BUILD] Palmr will fallback to external S3" + exit 0 + ;; +esac + +DOWNLOAD_URL="https://dl.min.io/server/minio/release/${MINIO_ARCH}/archive/minio.${MINIO_VERSION}" + +echo "[BUILD] Downloading from: $DOWNLOAD_URL" + +# Download with retry +MAX_RETRIES=3 +RETRY_COUNT=0 + +while [ $RETRY_COUNT -lt $MAX_RETRIES ]; do + if wget -O /tmp/minio "$DOWNLOAD_URL" 2>/dev/null; then + echo "[BUILD] ✓ Download successful" + break + fi + + RETRY_COUNT=$((RETRY_COUNT + 1)) + echo "[BUILD] Download failed, retry $RETRY_COUNT/$MAX_RETRIES..." + sleep 2 +done + +if [ $RETRY_COUNT -eq $MAX_RETRIES ]; then + echo "[BUILD] ✗ Failed to download storage system after $MAX_RETRIES attempts" + echo "[BUILD] Palmr will fallback to external S3" + exit 0 +fi + +# Install binary +chmod +x /tmp/minio +mv /tmp/minio /usr/local/bin/minio + +echo "[BUILD] ✓ Storage system installed successfully" +/usr/local/bin/minio --version + +exit 0 + + diff --git a/infra/load-minio-credentials.sh b/infra/load-minio-credentials.sh new file mode 100755 index 00000000..32b82196 --- /dev/null +++ b/infra/load-minio-credentials.sh @@ -0,0 +1,26 @@ +#!/bin/sh +# Load storage system credentials and export as environment variables + +CREDENTIALS_FILE="/app/server/.minio-credentials" + +if [ -f "$CREDENTIALS_FILE" ]; then + echo "[PALMR] Loading storage system credentials..." + + # Read and export each line + while IFS= read -r line; do + # Skip empty lines and comments + case "$line" in + ''|'#'*) continue ;; + esac + + # Export the variable + export "$line" + done < "$CREDENTIALS_FILE" + + echo "[PALMR] ✓ Storage system credentials loaded" +else + echo "[PALMR] ⚠ Storage system credentials not found at $CREDENTIALS_FILE" + echo "[PALMR] ⚠ No S3 configured - check your setup" +fi + + diff --git a/infra/minio-setup.sh b/infra/minio-setup.sh new file mode 100755 index 00000000..202f12cd --- /dev/null +++ b/infra/minio-setup.sh @@ -0,0 +1,96 @@ +#!/bin/sh +# Storage System Automatic Setup Script +# This script automatically configures storage system on first boot +# No user intervention required + +set -e + +# Configuration +MINIO_DATA_DIR="${MINIO_DATA_DIR:-/app/server/minio-data}" +MINIO_ROOT_USER="palmr-minio-admin" +MINIO_ROOT_PASSWORD="$(cat /app/server/.minio-root-password 2>/dev/null || echo 'password-not-generated')" +MINIO_BUCKET="${MINIO_BUCKET:-palmr-files}" +MINIO_INITIALIZED_FLAG="/app/server/.minio-initialized" +MINIO_CREDENTIALS="/app/server/.minio-credentials" + +echo "[STORAGE-SYSTEM-SETUP] Starting storage system configuration..." + +# Create data directory +mkdir -p "$MINIO_DATA_DIR" + +# Wait for storage system to start (managed by supervisor) +echo "[STORAGE-SYSTEM-SETUP] Waiting for storage system to start..." +MAX_RETRIES=30 +RETRY_COUNT=0 + +while [ $RETRY_COUNT -lt $MAX_RETRIES ]; do + if curl -sf http://127.0.0.1:9379/minio/health/live > /dev/null 2>&1; then + echo "[STORAGE-SYSTEM-SETUP] ✓ Storage system is responding" + break + fi + RETRY_COUNT=$((RETRY_COUNT + 1)) + echo "[STORAGE-SYSTEM-SETUP] Waiting... ($RETRY_COUNT/$MAX_RETRIES)" + sleep 2 +done + +if [ $RETRY_COUNT -eq $MAX_RETRIES ]; then + echo "[STORAGE-SYSTEM-SETUP] ✗ Storage system failed to start" + exit 1 +fi + +# Configure storage client (mc) - ALWAYS reconfigure with current password +echo "[STORAGE-SYSTEM-SETUP] Configuring storage client..." +mc alias set palmr-local http://127.0.0.1:9379 "$MINIO_ROOT_USER" "$MINIO_ROOT_PASSWORD" 2>/dev/null || { + echo "[STORAGE-SYSTEM-SETUP] ✗ Failed to configure storage client" + exit 1 +} + +# Create bucket (idempotent - won't fail if exists) +echo "[STORAGE-SYSTEM-SETUP] Ensuring storage bucket exists: $MINIO_BUCKET..." +if mc ls palmr-local/$MINIO_BUCKET > /dev/null 2>&1; then + echo "[STORAGE-SYSTEM-SETUP] ✓ Bucket '$MINIO_BUCKET' already exists" +else + echo "[STORAGE-SYSTEM-SETUP] Creating bucket '$MINIO_BUCKET'..." + mc mb "palmr-local/$MINIO_BUCKET" 2>/dev/null || { + echo "[STORAGE-SYSTEM-SETUP] ✗ Failed to create bucket" + exit 1 + } + echo "[STORAGE-SYSTEM-SETUP] ✓ Bucket created" +fi + +# Set bucket policy to private (always reapply) +echo "[STORAGE-SYSTEM-SETUP] Setting bucket policy..." +mc anonymous set none "palmr-local/$MINIO_BUCKET" 2>/dev/null || true + +# Save credentials for Palmr to use +echo "[STORAGE-SYSTEM-SETUP] Saving credentials to $MINIO_CREDENTIALS..." + +# Create credentials file +cat > "$MINIO_CREDENTIALS" </dev/null || true +echo "[STORAGE-SYSTEM-SETUP] ✓ Credentials file created and readable" + +echo "[STORAGE-SYSTEM-SETUP] ✓✓✓ Storage system configured successfully!" +echo "[STORAGE-SYSTEM-SETUP] Bucket: $MINIO_BUCKET" +echo "[STORAGE-SYSTEM-SETUP] Credentials: saved to .minio-credentials" +echo "[STORAGE-SYSTEM-SETUP] Palmr will use storage system" + +exit 0 + diff --git a/infra/server-start.sh b/infra/server-start.sh index 268459d7..272a94e4 100644 --- a/infra/server-start.sh +++ b/infra/server-start.sh @@ -1,7 +1,35 @@ #!/bin/sh set -e -echo "🌴 Starting Palmr Server..." +echo "🚀 Starting Palmr Server..." + +# Wait for storage system credentials to be ready (if using internal storage) +if [ "${ENABLE_S3}" != "true" ]; then + echo "⏳ Waiting for internal storage to initialize..." + MAX_WAIT=60 + WAIT_COUNT=0 + + while [ $WAIT_COUNT -lt $MAX_WAIT ]; do + if [ -f "/app/server/.minio-credentials" ]; then + echo "✅ Internal storage ready!" + break + fi + + WAIT_COUNT=$((WAIT_COUNT + 1)) + echo " Waiting for storage... ($WAIT_COUNT/$MAX_WAIT)" + sleep 1 + done + + if [ $WAIT_COUNT -eq $MAX_WAIT ]; then + echo "⚠️ WARNING: Internal storage not ready after ${MAX_WAIT}s" + echo "⚠️ Server will start but storage may not work until ready" + fi +fi + +# Load storage system credentials if available +if [ -f "/app/load-minio-credentials.sh" ]; then + . /app/load-minio-credentials.sh +fi TARGET_UID=${PALMR_UID:-1000} TARGET_GID=${PALMR_GID:-1000} @@ -31,8 +59,10 @@ echo "📁 Creating data directories..." mkdir -p /app/server/prisma /app/server/uploads /app/server/temp-uploads if [ "$(id -u)" = "0" ]; then - echo "🔐 Ensuring proper ownership before database operations..." - chown -R $TARGET_UID:$TARGET_GID /app/server/prisma 2>/dev/null || true + echo "🔐 Ensuring proper ownership for all operations..." + # Fix permissions for entire /app/server to allow migration and storage system operations + chown -R $TARGET_UID:$TARGET_GID /app/server 2>/dev/null || true + chmod -R 755 /app/server 2>/dev/null || true fi run_as_user() { diff --git a/infra/start-minio.sh b/infra/start-minio.sh new file mode 100755 index 00000000..6c9c77a7 --- /dev/null +++ b/infra/start-minio.sh @@ -0,0 +1,84 @@ +#!/bin/sh +# Start storage system with persistent root password +# This script MUST run as root to fix permissions, then drops to palmr user + +set -e + +DATA_DIR="/app/server/minio-data" +PASSWORD_FILE="/app/server/.minio-root-password" +MINIO_USER="palmr" + +echo "[STORAGE-SYSTEM] Initializing storage..." + +# DYNAMIC: Detect palmr user's actual UID and GID +# This works with any Docker user configuration +MINIO_UID=$(id -u $MINIO_USER 2>/dev/null || echo "1001") +MINIO_GID=$(id -g $MINIO_USER 2>/dev/null || echo "1001") + +echo "[STORAGE-SYSTEM] Target user: $MINIO_USER (UID:$MINIO_UID, GID:$MINIO_GID)" + +# CRITICAL: Fix permissions as root (supervisor runs this as root via user=root) +# This MUST happen before dropping to palmr user +if [ "$(id -u)" = "0" ]; then + echo "[STORAGE-SYSTEM] Fixing permissions (running as root)..." + + # Clean metadata + if [ -d "$DATA_DIR/.minio.sys" ]; then + echo "[STORAGE-SYSTEM] Cleaning metadata..." + rm -rf "$DATA_DIR/.minio.sys" 2>/dev/null || true + fi + + # Ensure directory exists + mkdir -p "$DATA_DIR" + + # FIX: Change ownership to palmr (using detected UID:GID) + chown -R ${MINIO_UID}:${MINIO_GID} "$DATA_DIR" 2>/dev/null || { + echo "[STORAGE-SYSTEM] ⚠️ chown -R failed, trying non-recursive..." + chown ${MINIO_UID}:${MINIO_GID} "$DATA_DIR" 2>/dev/null || true + } + + chmod 755 "$DATA_DIR" 2>/dev/null || true + + # Force filesystem sync to ensure changes are visible immediately + sync + + echo "[STORAGE-SYSTEM] ✓ Permissions fixed (owner: ${MINIO_UID}:${MINIO_GID})" +else + echo "[STORAGE-SYSTEM] ⚠️ WARNING: Not running as root, cannot fix permissions" +fi + +# Verify directory is writable (test as palmr with detected UID:GID) +su-exec ${MINIO_UID}:${MINIO_GID} sh -c " + if ! touch '$DATA_DIR/.test-write' 2>/dev/null; then + echo '[STORAGE-SYSTEM] ❌ FATAL: Still cannot write to $DATA_DIR' + ls -la '$DATA_DIR' + echo '[STORAGE-SYSTEM] This should not happen after chown!' + exit 1 + fi + rm -f '$DATA_DIR/.test-write' + echo '[STORAGE-SYSTEM] ✓ Write test passed' +" + +# Generate or reuse password (as root, then chown to palmr) +if [ -f "$PASSWORD_FILE" ]; then + MINIO_ROOT_PASSWORD=$(cat "$PASSWORD_FILE") + echo "[STORAGE-SYSTEM] Using existing root password" +else + MINIO_ROOT_PASSWORD="$(openssl rand -hex 16)" + echo "$MINIO_ROOT_PASSWORD" > "$PASSWORD_FILE" + chmod 600 "$PASSWORD_FILE" + chown ${MINIO_UID}:${MINIO_GID} "$PASSWORD_FILE" 2>/dev/null || true + echo "[STORAGE-SYSTEM] Generated new root password" +fi + +# Export for storage system +export MINIO_ROOT_USER="palmr-minio-admin" +export MINIO_ROOT_PASSWORD="$MINIO_ROOT_PASSWORD" + +echo "[STORAGE-SYSTEM] Starting storage server on 0.0.0.0:9379 as user palmr..." + +# Execute storage system as palmr user (dropping from root) +exec su-exec ${MINIO_UID}:${MINIO_GID} /usr/local/bin/minio server "$DATA_DIR" \ + --address 0.0.0.0:9379 \ + --console-address 0.0.0.0:9378 + diff --git a/infra/supervisord.conf b/infra/supervisord.conf index 81451cd3..0c06cb2b 100644 --- a/infra/supervisord.conf +++ b/infra/supervisord.conf @@ -6,6 +6,35 @@ logfile_maxbytes=0 pidfile=/var/run/supervisord.pid loglevel=info +[program:minio] +command=/bin/sh /app/start-minio.sh +directory=/app/server +user=root +autostart=true +autorestart=true +stdout_logfile=/dev/stdout +stdout_logfile_maxbytes=0 +stderr_logfile=/dev/stderr +stderr_logfile_maxbytes=0 +environment=HOME="/root" +priority=50 +startsecs=3 + +[program:minio-setup] +command=/bin/sh /app/minio-setup.sh +directory=/app +user=palmr +autostart=true +autorestart=unexpected +exitcodes=0 +stdout_logfile=/dev/stdout +stdout_logfile_maxbytes=0 +stderr_logfile=/dev/stderr +stderr_logfile_maxbytes=0 +environment=HOME="/home/palmr" +priority=60 +startsecs=0 + [program:server] command=/bin/sh -c "export DATABASE_URL='file:/app/server/prisma/palmr.db' && /app/server-start.sh" directory=/app/palmr-app @@ -31,4 +60,4 @@ stderr_logfile=/dev/stderr stderr_logfile_maxbytes=0 environment=PORT=5487,HOSTNAME="0.0.0.0",HOME="/home/palmr",API_BASE_URL="http://127.0.0.1:3333" priority=200 -startsecs=10 \ No newline at end of file +startsecs=10 diff --git a/package.json b/package.json index 9a721809..605cdd30 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "palmr-monorepo", - "version": "3.2.5-beta", + "version": "3.3.0-beta", "description": "Palmr monorepo with Husky configuration", "private": true, "packageManager": "pnpm@10.6.0",