mirror of
https://github.com/remvze/moodist.git
synced 2025-12-18 17:34:17 +00:00
feat: 完整实现中英文双语支持并修复所有声音翻译问题
## 主要功能 - 实现完整的中英文双语支持系统 - 添加语言切换器和路由配置 - 创建统一的翻译文件和hooks ## 核心组件 - 新增语言切换器组件 - 实现中英文页面路由 - 添加翻译系统核心文件 ## 翻译修复 - 修复所有声音名称的dataI18n映射 - 解决重复翻译键冲突问题 - 完善所有分类的声音翻译 ## 声音分类优化 - 修复雨声分类的重复翻译键问题 - 清理跨分类翻译键冲突 - 优化声音分类归属 ## UI优化 - 移除页面底部开源模块 - 完善顶部捐赠文本翻译 - 优化所有菜单项的翻译显示
This commit is contained in:
parent
6ac65c1948
commit
65958f8482
62 changed files with 2757 additions and 108 deletions
1
.serena/.gitignore
vendored
Normal file
1
.serena/.gitignore
vendored
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
/cache
|
||||||
84
.serena/project.yml
Normal file
84
.serena/project.yml
Normal file
|
|
@ -0,0 +1,84 @@
|
||||||
|
# list of languages for which language servers are started; choose from:
|
||||||
|
# al bash clojure cpp csharp csharp_omnisharp
|
||||||
|
# dart elixir elm erlang fortran go
|
||||||
|
# haskell java julia kotlin lua markdown
|
||||||
|
# nix perl php python python_jedi r
|
||||||
|
# rego ruby ruby_solargraph rust scala swift
|
||||||
|
# terraform typescript typescript_vts zig
|
||||||
|
# Note:
|
||||||
|
# - For C, use cpp
|
||||||
|
# - For JavaScript, use typescript
|
||||||
|
# Special requirements:
|
||||||
|
# - csharp: Requires the presence of a .sln file in the project folder.
|
||||||
|
# When using multiple languages, the first language server that supports a given file will be used for that file.
|
||||||
|
# The first language is the default language and the respective language server will be used as a fallback.
|
||||||
|
# Note that when using the JetBrains backend, language servers are not used and this list is correspondingly ignored.
|
||||||
|
languages:
|
||||||
|
- typescript
|
||||||
|
|
||||||
|
# the encoding used by text files in the project
|
||||||
|
# For a list of possible encodings, see https://docs.python.org/3.11/library/codecs.html#standard-encodings
|
||||||
|
encoding: "utf-8"
|
||||||
|
|
||||||
|
# whether to use the project's gitignore file to ignore files
|
||||||
|
# Added on 2025-04-07
|
||||||
|
ignore_all_files_in_gitignore: true
|
||||||
|
|
||||||
|
# list of additional paths to ignore
|
||||||
|
# same syntax as gitignore, so you can use * and **
|
||||||
|
# Was previously called `ignored_dirs`, please update your config if you are using that.
|
||||||
|
# Added (renamed) on 2025-04-07
|
||||||
|
ignored_paths: []
|
||||||
|
|
||||||
|
# whether the project is in read-only mode
|
||||||
|
# If set to true, all editing tools will be disabled and attempts to use them will result in an error
|
||||||
|
# Added on 2025-04-18
|
||||||
|
read_only: false
|
||||||
|
|
||||||
|
# list of tool names to exclude. We recommend not excluding any tools, see the readme for more details.
|
||||||
|
# Below is the complete list of tools for convenience.
|
||||||
|
# To make sure you have the latest list of tools, and to view their descriptions,
|
||||||
|
# execute `uv run scripts/print_tool_overview.py`.
|
||||||
|
#
|
||||||
|
# * `activate_project`: Activates a project by name.
|
||||||
|
# * `check_onboarding_performed`: Checks whether project onboarding was already performed.
|
||||||
|
# * `create_text_file`: Creates/overwrites a file in the project directory.
|
||||||
|
# * `delete_lines`: Deletes a range of lines within a file.
|
||||||
|
# * `delete_memory`: Deletes a memory from Serena's project-specific memory store.
|
||||||
|
# * `execute_shell_command`: Executes a shell command.
|
||||||
|
# * `find_referencing_code_snippets`: Finds code snippets in which the symbol at the given location is referenced.
|
||||||
|
# * `find_referencing_symbols`: Finds symbols that reference the symbol at the given location (optionally filtered by type).
|
||||||
|
# * `find_symbol`: Performs a global (or local) search for symbols with/containing a given name/substring (optionally filtered by type).
|
||||||
|
# * `get_current_config`: Prints the current configuration of the agent, including the active and available projects, tools, contexts, and modes.
|
||||||
|
# * `get_symbols_overview`: Gets an overview of the top-level symbols defined in a given file.
|
||||||
|
# * `initial_instructions`: Gets the initial instructions for the current project.
|
||||||
|
# Should only be used in settings where the system prompt cannot be set,
|
||||||
|
# e.g. in clients you have no control over, like Claude Desktop.
|
||||||
|
# * `insert_after_symbol`: Inserts content after the end of the definition of a given symbol.
|
||||||
|
# * `insert_at_line`: Inserts content at a given line in a file.
|
||||||
|
# * `insert_before_symbol`: Inserts content before the beginning of the definition of a given symbol.
|
||||||
|
# * `list_dir`: Lists files and directories in the given directory (optionally with recursion).
|
||||||
|
# * `list_memories`: Lists memories in Serena's project-specific memory store.
|
||||||
|
# * `onboarding`: Performs onboarding (identifying the project structure and essential tasks, e.g. for testing or building).
|
||||||
|
# * `prepare_for_new_conversation`: Provides instructions for preparing for a new conversation (in order to continue with the necessary context).
|
||||||
|
# * `read_file`: Reads a file within the project directory.
|
||||||
|
# * `read_memory`: Reads the memory with the given name from Serena's project-specific memory store.
|
||||||
|
# * `remove_project`: Removes a project from the Serena configuration.
|
||||||
|
# * `replace_lines`: Replaces a range of lines within a file with new content.
|
||||||
|
# * `replace_symbol_body`: Replaces the full definition of a symbol.
|
||||||
|
# * `restart_language_server`: Restarts the language server, may be necessary when edits not through Serena happen.
|
||||||
|
# * `search_for_pattern`: Performs a search for a pattern in the project.
|
||||||
|
# * `summarize_changes`: Provides instructions for summarizing the changes made to the codebase.
|
||||||
|
# * `switch_modes`: Activates modes by providing a list of their names
|
||||||
|
# * `think_about_collected_information`: Thinking tool for pondering the completeness of collected information.
|
||||||
|
# * `think_about_task_adherence`: Thinking tool for determining whether the agent is still on track with the current task.
|
||||||
|
# * `think_about_whether_you_are_done`: Thinking tool for determining whether the task is truly completed.
|
||||||
|
# * `write_memory`: Writes a named memory (for future reference) to Serena's project-specific memory store.
|
||||||
|
excluded_tools: []
|
||||||
|
|
||||||
|
# initial prompt for the project. It will always be given to the LLM upon activating the project
|
||||||
|
# (contrary to the memories, which are loaded on demand).
|
||||||
|
initial_prompt: ""
|
||||||
|
|
||||||
|
project_name: "moodist"
|
||||||
|
included_optional_tools: []
|
||||||
96
.spec-workflow/templates/design-template.md
Normal file
96
.spec-workflow/templates/design-template.md
Normal file
|
|
@ -0,0 +1,96 @@
|
||||||
|
# Design Document
|
||||||
|
|
||||||
|
## Overview
|
||||||
|
|
||||||
|
[High-level description of the feature and its place in the overall system]
|
||||||
|
|
||||||
|
## Steering Document Alignment
|
||||||
|
|
||||||
|
### Technical Standards (tech.md)
|
||||||
|
[How the design follows documented technical patterns and standards]
|
||||||
|
|
||||||
|
### Project Structure (structure.md)
|
||||||
|
[How the implementation will follow project organization conventions]
|
||||||
|
|
||||||
|
## Code Reuse Analysis
|
||||||
|
[What existing code will be leveraged, extended, or integrated with this feature]
|
||||||
|
|
||||||
|
### Existing Components to Leverage
|
||||||
|
- **[Component/Utility Name]**: [How it will be used]
|
||||||
|
- **[Service/Helper Name]**: [How it will be extended]
|
||||||
|
|
||||||
|
### Integration Points
|
||||||
|
- **[Existing System/API]**: [How the new feature will integrate]
|
||||||
|
- **[Database/Storage]**: [How data will connect to existing schemas]
|
||||||
|
|
||||||
|
## Architecture
|
||||||
|
|
||||||
|
[Describe the overall architecture and design patterns used]
|
||||||
|
|
||||||
|
### Modular Design Principles
|
||||||
|
- **Single File Responsibility**: Each file should handle one specific concern or domain
|
||||||
|
- **Component Isolation**: Create small, focused components rather than large monolithic files
|
||||||
|
- **Service Layer Separation**: Separate data access, business logic, and presentation layers
|
||||||
|
- **Utility Modularity**: Break utilities into focused, single-purpose modules
|
||||||
|
|
||||||
|
```mermaid
|
||||||
|
graph TD
|
||||||
|
A[Component A] --> B[Component B]
|
||||||
|
B --> C[Component C]
|
||||||
|
```
|
||||||
|
|
||||||
|
## Components and Interfaces
|
||||||
|
|
||||||
|
### Component 1
|
||||||
|
- **Purpose:** [What this component does]
|
||||||
|
- **Interfaces:** [Public methods/APIs]
|
||||||
|
- **Dependencies:** [What it depends on]
|
||||||
|
- **Reuses:** [Existing components/utilities it builds upon]
|
||||||
|
|
||||||
|
### Component 2
|
||||||
|
- **Purpose:** [What this component does]
|
||||||
|
- **Interfaces:** [Public methods/APIs]
|
||||||
|
- **Dependencies:** [What it depends on]
|
||||||
|
- **Reuses:** [Existing components/utilities it builds upon]
|
||||||
|
|
||||||
|
## Data Models
|
||||||
|
|
||||||
|
### Model 1
|
||||||
|
```
|
||||||
|
[Define the structure of Model1 in your language]
|
||||||
|
- id: [unique identifier type]
|
||||||
|
- name: [string/text type]
|
||||||
|
- [Additional properties as needed]
|
||||||
|
```
|
||||||
|
|
||||||
|
### Model 2
|
||||||
|
```
|
||||||
|
[Define the structure of Model2 in your language]
|
||||||
|
- id: [unique identifier type]
|
||||||
|
- [Additional properties as needed]
|
||||||
|
```
|
||||||
|
|
||||||
|
## Error Handling
|
||||||
|
|
||||||
|
### Error Scenarios
|
||||||
|
1. **Scenario 1:** [Description]
|
||||||
|
- **Handling:** [How to handle]
|
||||||
|
- **User Impact:** [What user sees]
|
||||||
|
|
||||||
|
2. **Scenario 2:** [Description]
|
||||||
|
- **Handling:** [How to handle]
|
||||||
|
- **User Impact:** [What user sees]
|
||||||
|
|
||||||
|
## Testing Strategy
|
||||||
|
|
||||||
|
### Unit Testing
|
||||||
|
- [Unit testing approach]
|
||||||
|
- [Key components to test]
|
||||||
|
|
||||||
|
### Integration Testing
|
||||||
|
- [Integration testing approach]
|
||||||
|
- [Key flows to test]
|
||||||
|
|
||||||
|
### End-to-End Testing
|
||||||
|
- [E2E testing approach]
|
||||||
|
- [User scenarios to test]
|
||||||
51
.spec-workflow/templates/product-template.md
Normal file
51
.spec-workflow/templates/product-template.md
Normal file
|
|
@ -0,0 +1,51 @@
|
||||||
|
# Product Overview
|
||||||
|
|
||||||
|
## Product Purpose
|
||||||
|
[Describe the core purpose of this product/project. What problem does it solve?]
|
||||||
|
|
||||||
|
## Target Users
|
||||||
|
[Who are the primary users of this product? What are their needs and pain points?]
|
||||||
|
|
||||||
|
## Key Features
|
||||||
|
[List the main features that deliver value to users]
|
||||||
|
|
||||||
|
1. **Feature 1**: [Description]
|
||||||
|
2. **Feature 2**: [Description]
|
||||||
|
3. **Feature 3**: [Description]
|
||||||
|
|
||||||
|
## Business Objectives
|
||||||
|
[What are the business goals this product aims to achieve?]
|
||||||
|
|
||||||
|
- [Objective 1]
|
||||||
|
- [Objective 2]
|
||||||
|
- [Objective 3]
|
||||||
|
|
||||||
|
## Success Metrics
|
||||||
|
[How will we measure the success of this product?]
|
||||||
|
|
||||||
|
- [Metric 1]: [Target]
|
||||||
|
- [Metric 2]: [Target]
|
||||||
|
- [Metric 3]: [Target]
|
||||||
|
|
||||||
|
## Product Principles
|
||||||
|
[Core principles that guide product decisions]
|
||||||
|
|
||||||
|
1. **[Principle 1]**: [Explanation]
|
||||||
|
2. **[Principle 2]**: [Explanation]
|
||||||
|
3. **[Principle 3]**: [Explanation]
|
||||||
|
|
||||||
|
## Monitoring & Visibility (if applicable)
|
||||||
|
[How do users track progress and monitor the system?]
|
||||||
|
|
||||||
|
- **Dashboard Type**: [e.g., Web-based, CLI, Desktop app]
|
||||||
|
- **Real-time Updates**: [e.g., WebSocket, polling, push notifications]
|
||||||
|
- **Key Metrics Displayed**: [What information is most important to surface]
|
||||||
|
- **Sharing Capabilities**: [e.g., read-only links, exports, reports]
|
||||||
|
|
||||||
|
## Future Vision
|
||||||
|
[Where do we see this product evolving in the future?]
|
||||||
|
|
||||||
|
### Potential Enhancements
|
||||||
|
- **Remote Access**: [e.g., Tunnel features for sharing dashboards with stakeholders]
|
||||||
|
- **Analytics**: [e.g., Historical trends, performance metrics]
|
||||||
|
- **Collaboration**: [e.g., Multi-user support, commenting]
|
||||||
50
.spec-workflow/templates/requirements-template.md
Normal file
50
.spec-workflow/templates/requirements-template.md
Normal file
|
|
@ -0,0 +1,50 @@
|
||||||
|
# Requirements Document
|
||||||
|
|
||||||
|
## Introduction
|
||||||
|
|
||||||
|
[Provide a brief overview of the feature, its purpose, and its value to users]
|
||||||
|
|
||||||
|
## Alignment with Product Vision
|
||||||
|
|
||||||
|
[Explain how this feature supports the goals outlined in product.md]
|
||||||
|
|
||||||
|
## Requirements
|
||||||
|
|
||||||
|
### Requirement 1
|
||||||
|
|
||||||
|
**User Story:** As a [role], I want [feature], so that [benefit]
|
||||||
|
|
||||||
|
#### Acceptance Criteria
|
||||||
|
|
||||||
|
1. WHEN [event] THEN [system] SHALL [response]
|
||||||
|
2. IF [precondition] THEN [system] SHALL [response]
|
||||||
|
3. WHEN [event] AND [condition] THEN [system] SHALL [response]
|
||||||
|
|
||||||
|
### Requirement 2
|
||||||
|
|
||||||
|
**User Story:** As a [role], I want [feature], so that [benefit]
|
||||||
|
|
||||||
|
#### Acceptance Criteria
|
||||||
|
|
||||||
|
1. WHEN [event] THEN [system] SHALL [response]
|
||||||
|
2. IF [precondition] THEN [system] SHALL [response]
|
||||||
|
|
||||||
|
## Non-Functional Requirements
|
||||||
|
|
||||||
|
### Code Architecture and Modularity
|
||||||
|
- **Single Responsibility Principle**: Each file should have a single, well-defined purpose
|
||||||
|
- **Modular Design**: Components, utilities, and services should be isolated and reusable
|
||||||
|
- **Dependency Management**: Minimize interdependencies between modules
|
||||||
|
- **Clear Interfaces**: Define clean contracts between components and layers
|
||||||
|
|
||||||
|
### Performance
|
||||||
|
- [Performance requirements]
|
||||||
|
|
||||||
|
### Security
|
||||||
|
- [Security requirements]
|
||||||
|
|
||||||
|
### Reliability
|
||||||
|
- [Reliability requirements]
|
||||||
|
|
||||||
|
### Usability
|
||||||
|
- [Usability requirements]
|
||||||
145
.spec-workflow/templates/structure-template.md
Normal file
145
.spec-workflow/templates/structure-template.md
Normal file
|
|
@ -0,0 +1,145 @@
|
||||||
|
# Project Structure
|
||||||
|
|
||||||
|
## Directory Organization
|
||||||
|
|
||||||
|
```
|
||||||
|
[Define your project's directory structure. Examples below - adapt to your project type]
|
||||||
|
|
||||||
|
Example for a library/package:
|
||||||
|
project-root/
|
||||||
|
├── src/ # Source code
|
||||||
|
├── tests/ # Test files
|
||||||
|
├── docs/ # Documentation
|
||||||
|
├── examples/ # Usage examples
|
||||||
|
└── [build/dist/out] # Build output
|
||||||
|
|
||||||
|
Example for an application:
|
||||||
|
project-root/
|
||||||
|
├── [src/app/lib] # Main source code
|
||||||
|
├── [assets/resources] # Static resources
|
||||||
|
├── [config/settings] # Configuration
|
||||||
|
├── [scripts/tools] # Build/utility scripts
|
||||||
|
└── [tests/spec] # Test files
|
||||||
|
|
||||||
|
Common patterns:
|
||||||
|
- Group by feature/module
|
||||||
|
- Group by layer (UI, business logic, data)
|
||||||
|
- Group by type (models, controllers, views)
|
||||||
|
- Flat structure for simple projects
|
||||||
|
```
|
||||||
|
|
||||||
|
## Naming Conventions
|
||||||
|
|
||||||
|
### Files
|
||||||
|
- **Components/Modules**: [e.g., `PascalCase`, `snake_case`, `kebab-case`]
|
||||||
|
- **Services/Handlers**: [e.g., `UserService`, `user_service`, `user-service`]
|
||||||
|
- **Utilities/Helpers**: [e.g., `dateUtils`, `date_utils`, `date-utils`]
|
||||||
|
- **Tests**: [e.g., `[filename]_test`, `[filename].test`, `[filename]Test`]
|
||||||
|
|
||||||
|
### Code
|
||||||
|
- **Classes/Types**: [e.g., `PascalCase`, `CamelCase`, `snake_case`]
|
||||||
|
- **Functions/Methods**: [e.g., `camelCase`, `snake_case`, `PascalCase`]
|
||||||
|
- **Constants**: [e.g., `UPPER_SNAKE_CASE`, `SCREAMING_CASE`, `PascalCase`]
|
||||||
|
- **Variables**: [e.g., `camelCase`, `snake_case`, `lowercase`]
|
||||||
|
|
||||||
|
## Import Patterns
|
||||||
|
|
||||||
|
### Import Order
|
||||||
|
1. External dependencies
|
||||||
|
2. Internal modules
|
||||||
|
3. Relative imports
|
||||||
|
4. Style imports
|
||||||
|
|
||||||
|
### Module/Package Organization
|
||||||
|
```
|
||||||
|
[Describe your project's import/include patterns]
|
||||||
|
Examples:
|
||||||
|
- Absolute imports from project root
|
||||||
|
- Relative imports within modules
|
||||||
|
- Package/namespace organization
|
||||||
|
- Dependency management approach
|
||||||
|
```
|
||||||
|
|
||||||
|
## Code Structure Patterns
|
||||||
|
|
||||||
|
[Define common patterns for organizing code within files. Below are examples - choose what applies to your project]
|
||||||
|
|
||||||
|
### Module/Class Organization
|
||||||
|
```
|
||||||
|
Example patterns:
|
||||||
|
1. Imports/includes/dependencies
|
||||||
|
2. Constants and configuration
|
||||||
|
3. Type/interface definitions
|
||||||
|
4. Main implementation
|
||||||
|
5. Helper/utility functions
|
||||||
|
6. Exports/public API
|
||||||
|
```
|
||||||
|
|
||||||
|
### Function/Method Organization
|
||||||
|
```
|
||||||
|
Example patterns:
|
||||||
|
- Input validation first
|
||||||
|
- Core logic in the middle
|
||||||
|
- Error handling throughout
|
||||||
|
- Clear return points
|
||||||
|
```
|
||||||
|
|
||||||
|
### File Organization Principles
|
||||||
|
```
|
||||||
|
Choose what works for your project:
|
||||||
|
- One class/module per file
|
||||||
|
- Related functionality grouped together
|
||||||
|
- Public API at the top/bottom
|
||||||
|
- Implementation details hidden
|
||||||
|
```
|
||||||
|
|
||||||
|
## Code Organization Principles
|
||||||
|
|
||||||
|
1. **Single Responsibility**: Each file should have one clear purpose
|
||||||
|
2. **Modularity**: Code should be organized into reusable modules
|
||||||
|
3. **Testability**: Structure code to be easily testable
|
||||||
|
4. **Consistency**: Follow patterns established in the codebase
|
||||||
|
|
||||||
|
## Module Boundaries
|
||||||
|
[Define how different parts of your project interact and maintain separation of concerns]
|
||||||
|
|
||||||
|
Examples of boundary patterns:
|
||||||
|
- **Core vs Plugins**: Core functionality vs extensible plugins
|
||||||
|
- **Public API vs Internal**: What's exposed vs implementation details
|
||||||
|
- **Platform-specific vs Cross-platform**: OS-specific code isolation
|
||||||
|
- **Stable vs Experimental**: Production code vs experimental features
|
||||||
|
- **Dependencies direction**: Which modules can depend on which
|
||||||
|
|
||||||
|
## Code Size Guidelines
|
||||||
|
[Define your project's guidelines for file and function sizes]
|
||||||
|
|
||||||
|
Suggested guidelines:
|
||||||
|
- **File size**: [Define maximum lines per file]
|
||||||
|
- **Function/Method size**: [Define maximum lines per function]
|
||||||
|
- **Class/Module complexity**: [Define complexity limits]
|
||||||
|
- **Nesting depth**: [Maximum nesting levels]
|
||||||
|
|
||||||
|
## Dashboard/Monitoring Structure (if applicable)
|
||||||
|
[How dashboard or monitoring components are organized]
|
||||||
|
|
||||||
|
### Example Structure:
|
||||||
|
```
|
||||||
|
src/
|
||||||
|
└── dashboard/ # Self-contained dashboard subsystem
|
||||||
|
├── server/ # Backend server components
|
||||||
|
├── client/ # Frontend assets
|
||||||
|
├── shared/ # Shared types/utilities
|
||||||
|
└── public/ # Static assets
|
||||||
|
```
|
||||||
|
|
||||||
|
### Separation of Concerns
|
||||||
|
- Dashboard isolated from core business logic
|
||||||
|
- Own CLI entry point for independent operation
|
||||||
|
- Minimal dependencies on main application
|
||||||
|
- Can be disabled without affecting core functionality
|
||||||
|
|
||||||
|
## Documentation Standards
|
||||||
|
- All public APIs must have documentation
|
||||||
|
- Complex logic should include inline comments
|
||||||
|
- README files for major modules
|
||||||
|
- Follow language-specific documentation conventions
|
||||||
139
.spec-workflow/templates/tasks-template.md
Normal file
139
.spec-workflow/templates/tasks-template.md
Normal file
|
|
@ -0,0 +1,139 @@
|
||||||
|
# Tasks Document
|
||||||
|
|
||||||
|
- [ ] 1. Create core interfaces in src/types/feature.ts
|
||||||
|
- File: src/types/feature.ts
|
||||||
|
- Define TypeScript interfaces for feature data structures
|
||||||
|
- Extend existing base interfaces from base.ts
|
||||||
|
- Purpose: Establish type safety for feature implementation
|
||||||
|
- _Leverage: src/types/base.ts_
|
||||||
|
- _Requirements: 1.1_
|
||||||
|
- _Prompt: Role: TypeScript Developer specializing in type systems and interfaces | Task: Create comprehensive TypeScript interfaces for the feature data structures following requirements 1.1, extending existing base interfaces from src/types/base.ts | Restrictions: Do not modify existing base interfaces, maintain backward compatibility, follow project naming conventions | Success: All interfaces compile without errors, proper inheritance from base types, full type coverage for feature requirements_
|
||||||
|
|
||||||
|
- [ ] 2. Create base model class in src/models/FeatureModel.ts
|
||||||
|
- File: src/models/FeatureModel.ts
|
||||||
|
- Implement base model extending BaseModel class
|
||||||
|
- Add validation methods using existing validation utilities
|
||||||
|
- Purpose: Provide data layer foundation for feature
|
||||||
|
- _Leverage: src/models/BaseModel.ts, src/utils/validation.ts_
|
||||||
|
- _Requirements: 2.1_
|
||||||
|
- _Prompt: Role: Backend Developer with expertise in Node.js and data modeling | Task: Create a base model class extending BaseModel and implementing validation following requirement 2.1, leveraging existing patterns from src/models/BaseModel.ts and src/utils/validation.ts | Restrictions: Must follow existing model patterns, do not bypass validation utilities, maintain consistent error handling | Success: Model extends BaseModel correctly, validation methods implemented and tested, follows project architecture patterns_
|
||||||
|
|
||||||
|
- [ ] 3. Add specific model methods to FeatureModel.ts
|
||||||
|
- File: src/models/FeatureModel.ts (continue from task 2)
|
||||||
|
- Implement create, update, delete methods
|
||||||
|
- Add relationship handling for foreign keys
|
||||||
|
- Purpose: Complete model functionality for CRUD operations
|
||||||
|
- _Leverage: src/models/BaseModel.ts_
|
||||||
|
- _Requirements: 2.2, 2.3_
|
||||||
|
- _Prompt: Role: Backend Developer with expertise in ORM and database operations | Task: Implement CRUD methods and relationship handling in FeatureModel.ts following requirements 2.2 and 2.3, extending patterns from src/models/BaseModel.ts | Restrictions: Must maintain transaction integrity, follow existing relationship patterns, do not duplicate base model functionality | Success: All CRUD operations work correctly, relationships are properly handled, database operations are atomic and efficient_
|
||||||
|
|
||||||
|
- [ ] 4. Create model unit tests in tests/models/FeatureModel.test.ts
|
||||||
|
- File: tests/models/FeatureModel.test.ts
|
||||||
|
- Write tests for model validation and CRUD methods
|
||||||
|
- Use existing test utilities and fixtures
|
||||||
|
- Purpose: Ensure model reliability and catch regressions
|
||||||
|
- _Leverage: tests/helpers/testUtils.ts, tests/fixtures/data.ts_
|
||||||
|
- _Requirements: 2.1, 2.2_
|
||||||
|
- _Prompt: Role: QA Engineer with expertise in unit testing and Jest/Mocha frameworks | Task: Create comprehensive unit tests for FeatureModel validation and CRUD methods covering requirements 2.1 and 2.2, using existing test utilities from tests/helpers/testUtils.ts and fixtures from tests/fixtures/data.ts | Restrictions: Must test both success and failure scenarios, do not test external dependencies directly, maintain test isolation | Success: All model methods are tested with good coverage, edge cases covered, tests run independently and consistently_
|
||||||
|
|
||||||
|
- [ ] 5. Create service interface in src/services/IFeatureService.ts
|
||||||
|
- File: src/services/IFeatureService.ts
|
||||||
|
- Define service contract with method signatures
|
||||||
|
- Extend base service interface patterns
|
||||||
|
- Purpose: Establish service layer contract for dependency injection
|
||||||
|
- _Leverage: src/services/IBaseService.ts_
|
||||||
|
- _Requirements: 3.1_
|
||||||
|
- _Prompt: Role: Software Architect specializing in service-oriented architecture and TypeScript interfaces | Task: Design service interface contract following requirement 3.1, extending base service patterns from src/services/IBaseService.ts for dependency injection | Restrictions: Must maintain interface segregation principle, do not expose internal implementation details, ensure contract compatibility with DI container | Success: Interface is well-defined with clear method signatures, extends base service appropriately, supports all required service operations_
|
||||||
|
|
||||||
|
- [ ] 6. Implement feature service in src/services/FeatureService.ts
|
||||||
|
- File: src/services/FeatureService.ts
|
||||||
|
- Create concrete service implementation using FeatureModel
|
||||||
|
- Add error handling with existing error utilities
|
||||||
|
- Purpose: Provide business logic layer for feature operations
|
||||||
|
- _Leverage: src/services/BaseService.ts, src/utils/errorHandler.ts, src/models/FeatureModel.ts_
|
||||||
|
- _Requirements: 3.2_
|
||||||
|
- _Prompt: Role: Backend Developer with expertise in service layer architecture and business logic | Task: Implement concrete FeatureService following requirement 3.2, using FeatureModel and extending BaseService patterns with proper error handling from src/utils/errorHandler.ts | Restrictions: Must implement interface contract exactly, do not bypass model validation, maintain separation of concerns from data layer | Success: Service implements all interface methods correctly, robust error handling implemented, business logic is well-encapsulated and testable_
|
||||||
|
|
||||||
|
- [ ] 7. Add service dependency injection in src/utils/di.ts
|
||||||
|
- File: src/utils/di.ts (modify existing)
|
||||||
|
- Register FeatureService in dependency injection container
|
||||||
|
- Configure service lifetime and dependencies
|
||||||
|
- Purpose: Enable service injection throughout application
|
||||||
|
- _Leverage: existing DI configuration in src/utils/di.ts_
|
||||||
|
- _Requirements: 3.1_
|
||||||
|
- _Prompt: Role: DevOps Engineer with expertise in dependency injection and IoC containers | Task: Register FeatureService in DI container following requirement 3.1, configuring appropriate lifetime and dependencies using existing patterns from src/utils/di.ts | Restrictions: Must follow existing DI container patterns, do not create circular dependencies, maintain service resolution efficiency | Success: FeatureService is properly registered and resolvable, dependencies are correctly configured, service lifetime is appropriate for use case_
|
||||||
|
|
||||||
|
- [ ] 8. Create service unit tests in tests/services/FeatureService.test.ts
|
||||||
|
- File: tests/services/FeatureService.test.ts
|
||||||
|
- Write tests for service methods with mocked dependencies
|
||||||
|
- Test error handling scenarios
|
||||||
|
- Purpose: Ensure service reliability and proper error handling
|
||||||
|
- _Leverage: tests/helpers/testUtils.ts, tests/mocks/modelMocks.ts_
|
||||||
|
- _Requirements: 3.2, 3.3_
|
||||||
|
- _Prompt: Role: QA Engineer with expertise in service testing and mocking frameworks | Task: Create comprehensive unit tests for FeatureService methods covering requirements 3.2 and 3.3, using mocked dependencies from tests/mocks/modelMocks.ts and test utilities | Restrictions: Must mock all external dependencies, test business logic in isolation, do not test framework code | Success: All service methods tested with proper mocking, error scenarios covered, tests verify business logic correctness and error handling_
|
||||||
|
|
||||||
|
- [ ] 4. Create API endpoints
|
||||||
|
- Design API structure
|
||||||
|
- _Leverage: src/api/baseApi.ts, src/utils/apiUtils.ts_
|
||||||
|
- _Requirements: 4.0_
|
||||||
|
- _Prompt: Role: API Architect specializing in RESTful design and Express.js | Task: Design comprehensive API structure following requirement 4.0, leveraging existing patterns from src/api/baseApi.ts and utilities from src/utils/apiUtils.ts | Restrictions: Must follow REST conventions, maintain API versioning compatibility, do not expose internal data structures directly | Success: API structure is well-designed and documented, follows existing patterns, supports all required operations with proper HTTP methods and status codes_
|
||||||
|
|
||||||
|
- [ ] 4.1 Set up routing and middleware
|
||||||
|
- Configure application routes
|
||||||
|
- Add authentication middleware
|
||||||
|
- Set up error handling middleware
|
||||||
|
- _Leverage: src/middleware/auth.ts, src/middleware/errorHandler.ts_
|
||||||
|
- _Requirements: 4.1_
|
||||||
|
- _Prompt: Role: Backend Developer with expertise in Express.js middleware and routing | Task: Configure application routes and middleware following requirement 4.1, integrating authentication from src/middleware/auth.ts and error handling from src/middleware/errorHandler.ts | Restrictions: Must maintain middleware order, do not bypass security middleware, ensure proper error propagation | Success: Routes are properly configured with correct middleware chain, authentication works correctly, errors are handled gracefully throughout the request lifecycle_
|
||||||
|
|
||||||
|
- [ ] 4.2 Implement CRUD endpoints
|
||||||
|
- Create API endpoints
|
||||||
|
- Add request validation
|
||||||
|
- Write API integration tests
|
||||||
|
- _Leverage: src/controllers/BaseController.ts, src/utils/validation.ts_
|
||||||
|
- _Requirements: 4.2, 4.3_
|
||||||
|
- _Prompt: Role: Full-stack Developer with expertise in API development and validation | Task: Implement CRUD endpoints following requirements 4.2 and 4.3, extending BaseController patterns and using validation utilities from src/utils/validation.ts | Restrictions: Must validate all inputs, follow existing controller patterns, ensure proper HTTP status codes and responses | Success: All CRUD operations work correctly, request validation prevents invalid data, integration tests pass and cover all endpoints_
|
||||||
|
|
||||||
|
- [ ] 5. Add frontend components
|
||||||
|
- Plan component architecture
|
||||||
|
- _Leverage: src/components/BaseComponent.tsx, src/styles/theme.ts_
|
||||||
|
- _Requirements: 5.0_
|
||||||
|
- _Prompt: Role: Frontend Architect with expertise in React component design and architecture | Task: Plan comprehensive component architecture following requirement 5.0, leveraging base patterns from src/components/BaseComponent.tsx and theme system from src/styles/theme.ts | Restrictions: Must follow existing component patterns, maintain design system consistency, ensure component reusability | Success: Architecture is well-planned and documented, components are properly organized, follows existing patterns and theme system_
|
||||||
|
|
||||||
|
- [ ] 5.1 Create base UI components
|
||||||
|
- Set up component structure
|
||||||
|
- Implement reusable components
|
||||||
|
- Add styling and theming
|
||||||
|
- _Leverage: src/components/BaseComponent.tsx, src/styles/theme.ts_
|
||||||
|
- _Requirements: 5.1_
|
||||||
|
- _Prompt: Role: Frontend Developer specializing in React and component architecture | Task: Create reusable UI components following requirement 5.1, extending BaseComponent patterns and using existing theme system from src/styles/theme.ts | Restrictions: Must use existing theme variables, follow component composition patterns, ensure accessibility compliance | Success: Components are reusable and properly themed, follow existing architecture, accessible and responsive_
|
||||||
|
|
||||||
|
- [ ] 5.2 Implement feature-specific components
|
||||||
|
- Create feature components
|
||||||
|
- Add state management
|
||||||
|
- Connect to API endpoints
|
||||||
|
- _Leverage: src/hooks/useApi.ts, src/components/BaseComponent.tsx_
|
||||||
|
- _Requirements: 5.2, 5.3_
|
||||||
|
- _Prompt: Role: React Developer with expertise in state management and API integration | Task: Implement feature-specific components following requirements 5.2 and 5.3, using API hooks from src/hooks/useApi.ts and extending BaseComponent patterns | Restrictions: Must use existing state management patterns, handle loading and error states properly, maintain component performance | Success: Components are fully functional with proper state management, API integration works smoothly, user experience is responsive and intuitive_
|
||||||
|
|
||||||
|
- [ ] 6. Integration and testing
|
||||||
|
- Plan integration approach
|
||||||
|
- _Leverage: src/utils/integrationUtils.ts, tests/helpers/testUtils.ts_
|
||||||
|
- _Requirements: 6.0_
|
||||||
|
- _Prompt: Role: Integration Engineer with expertise in system integration and testing strategies | Task: Plan comprehensive integration approach following requirement 6.0, leveraging integration utilities from src/utils/integrationUtils.ts and test helpers | Restrictions: Must consider all system components, ensure proper test coverage, maintain integration test reliability | Success: Integration plan is comprehensive and feasible, all system components work together correctly, integration points are well-tested_
|
||||||
|
|
||||||
|
- [ ] 6.1 Write end-to-end tests
|
||||||
|
- Set up E2E testing framework
|
||||||
|
- Write user journey tests
|
||||||
|
- Add test automation
|
||||||
|
- _Leverage: tests/helpers/testUtils.ts, tests/fixtures/data.ts_
|
||||||
|
- _Requirements: All_
|
||||||
|
- _Prompt: Role: QA Automation Engineer with expertise in E2E testing and test frameworks like Cypress or Playwright | Task: Implement comprehensive end-to-end tests covering all requirements, setting up testing framework and user journey tests using test utilities and fixtures | Restrictions: Must test real user workflows, ensure tests are maintainable and reliable, do not test implementation details | Success: E2E tests cover all critical user journeys, tests run reliably in CI/CD pipeline, user experience is validated from end-to-end_
|
||||||
|
|
||||||
|
- [ ] 6.2 Final integration and cleanup
|
||||||
|
- Integrate all components
|
||||||
|
- Fix any integration issues
|
||||||
|
- Clean up code and documentation
|
||||||
|
- _Leverage: src/utils/cleanup.ts, docs/templates/_
|
||||||
|
- _Requirements: All_
|
||||||
|
- _Prompt: Role: Senior Developer with expertise in code quality and system integration | Task: Complete final integration of all components and perform comprehensive cleanup covering all requirements, using cleanup utilities and documentation templates | Restrictions: Must not break existing functionality, ensure code quality standards are met, maintain documentation consistency | Success: All components are fully integrated and working together, code is clean and well-documented, system meets all requirements and quality standards_
|
||||||
99
.spec-workflow/templates/tech-template.md
Normal file
99
.spec-workflow/templates/tech-template.md
Normal file
|
|
@ -0,0 +1,99 @@
|
||||||
|
# Technology Stack
|
||||||
|
|
||||||
|
## Project Type
|
||||||
|
[Describe what kind of project this is: web application, CLI tool, desktop application, mobile app, library, API service, embedded system, game, etc.]
|
||||||
|
|
||||||
|
## Core Technologies
|
||||||
|
|
||||||
|
### Primary Language(s)
|
||||||
|
- **Language**: [e.g., Python 3.11, Go 1.21, TypeScript, Rust, C++]
|
||||||
|
- **Runtime/Compiler**: [if applicable]
|
||||||
|
- **Language-specific tools**: [package managers, build tools, etc.]
|
||||||
|
|
||||||
|
### Key Dependencies/Libraries
|
||||||
|
[List the main libraries and frameworks your project depends on]
|
||||||
|
- **[Library/Framework name]**: [Purpose and version]
|
||||||
|
- **[Library/Framework name]**: [Purpose and version]
|
||||||
|
|
||||||
|
### Application Architecture
|
||||||
|
[Describe how your application is structured - this could be MVC, event-driven, plugin-based, client-server, standalone, microservices, monolithic, etc.]
|
||||||
|
|
||||||
|
### Data Storage (if applicable)
|
||||||
|
- **Primary storage**: [e.g., PostgreSQL, files, in-memory, cloud storage]
|
||||||
|
- **Caching**: [e.g., Redis, in-memory, disk cache]
|
||||||
|
- **Data formats**: [e.g., JSON, Protocol Buffers, XML, binary]
|
||||||
|
|
||||||
|
### External Integrations (if applicable)
|
||||||
|
- **APIs**: [External services you integrate with]
|
||||||
|
- **Protocols**: [e.g., HTTP/REST, gRPC, WebSocket, TCP/IP]
|
||||||
|
- **Authentication**: [e.g., OAuth, API keys, certificates]
|
||||||
|
|
||||||
|
### Monitoring & Dashboard Technologies (if applicable)
|
||||||
|
- **Dashboard Framework**: [e.g., React, Vue, vanilla JS, terminal UI]
|
||||||
|
- **Real-time Communication**: [e.g., WebSocket, Server-Sent Events, polling]
|
||||||
|
- **Visualization Libraries**: [e.g., Chart.js, D3, terminal graphs]
|
||||||
|
- **State Management**: [e.g., Redux, Vuex, file system as source of truth]
|
||||||
|
|
||||||
|
## Development Environment
|
||||||
|
|
||||||
|
### Build & Development Tools
|
||||||
|
- **Build System**: [e.g., Make, CMake, Gradle, npm scripts, cargo]
|
||||||
|
- **Package Management**: [e.g., pip, npm, cargo, go mod, apt, brew]
|
||||||
|
- **Development workflow**: [e.g., hot reload, watch mode, REPL]
|
||||||
|
|
||||||
|
### Code Quality Tools
|
||||||
|
- **Static Analysis**: [Tools for code quality and correctness]
|
||||||
|
- **Formatting**: [Code style enforcement tools]
|
||||||
|
- **Testing Framework**: [Unit, integration, and/or end-to-end testing tools]
|
||||||
|
- **Documentation**: [Documentation generation tools]
|
||||||
|
|
||||||
|
### Version Control & Collaboration
|
||||||
|
- **VCS**: [e.g., Git, Mercurial, SVN]
|
||||||
|
- **Branching Strategy**: [e.g., Git Flow, GitHub Flow, trunk-based]
|
||||||
|
- **Code Review Process**: [How code reviews are conducted]
|
||||||
|
|
||||||
|
### Dashboard Development (if applicable)
|
||||||
|
- **Live Reload**: [e.g., Hot module replacement, file watchers]
|
||||||
|
- **Port Management**: [e.g., Dynamic allocation, configurable ports]
|
||||||
|
- **Multi-Instance Support**: [e.g., Running multiple dashboards simultaneously]
|
||||||
|
|
||||||
|
## Deployment & Distribution (if applicable)
|
||||||
|
- **Target Platform(s)**: [Where/how the project runs: cloud, on-premise, desktop, mobile, embedded]
|
||||||
|
- **Distribution Method**: [How users get your software: download, package manager, app store, SaaS]
|
||||||
|
- **Installation Requirements**: [Prerequisites, system requirements]
|
||||||
|
- **Update Mechanism**: [How updates are delivered]
|
||||||
|
|
||||||
|
## Technical Requirements & Constraints
|
||||||
|
|
||||||
|
### Performance Requirements
|
||||||
|
- [e.g., response time, throughput, memory usage, startup time]
|
||||||
|
- [Specific benchmarks or targets]
|
||||||
|
|
||||||
|
### Compatibility Requirements
|
||||||
|
- **Platform Support**: [Operating systems, architectures, versions]
|
||||||
|
- **Dependency Versions**: [Minimum/maximum versions of dependencies]
|
||||||
|
- **Standards Compliance**: [Industry standards, protocols, specifications]
|
||||||
|
|
||||||
|
### Security & Compliance
|
||||||
|
- **Security Requirements**: [Authentication, encryption, data protection]
|
||||||
|
- **Compliance Standards**: [GDPR, HIPAA, SOC2, etc. if applicable]
|
||||||
|
- **Threat Model**: [Key security considerations]
|
||||||
|
|
||||||
|
### Scalability & Reliability
|
||||||
|
- **Expected Load**: [Users, requests, data volume]
|
||||||
|
- **Availability Requirements**: [Uptime targets, disaster recovery]
|
||||||
|
- **Growth Projections**: [How the system needs to scale]
|
||||||
|
|
||||||
|
## Technical Decisions & Rationale
|
||||||
|
[Document key architectural and technology choices]
|
||||||
|
|
||||||
|
### Decision Log
|
||||||
|
1. **[Technology/Pattern Choice]**: [Why this was chosen, alternatives considered]
|
||||||
|
2. **[Architecture Decision]**: [Rationale, trade-offs accepted]
|
||||||
|
3. **[Tool/Library Selection]**: [Reasoning, evaluation criteria]
|
||||||
|
|
||||||
|
## Known Limitations
|
||||||
|
[Document any technical debt, limitations, or areas for improvement]
|
||||||
|
|
||||||
|
- [Limitation 1]: [Impact and potential future solutions]
|
||||||
|
- [Limitation 2]: [Why it exists and when it might be addressed]
|
||||||
64
.spec-workflow/user-templates/README.md
Normal file
64
.spec-workflow/user-templates/README.md
Normal file
|
|
@ -0,0 +1,64 @@
|
||||||
|
# User Templates
|
||||||
|
|
||||||
|
This directory allows you to create custom templates that override the default Spec Workflow templates.
|
||||||
|
|
||||||
|
## How to Use Custom Templates
|
||||||
|
|
||||||
|
1. **Create your custom template file** in this directory with the exact same name as the default template you want to override:
|
||||||
|
- `requirements-template.md` - Override requirements document template
|
||||||
|
- `design-template.md` - Override design document template
|
||||||
|
- `tasks-template.md` - Override tasks document template
|
||||||
|
- `product-template.md` - Override product steering template
|
||||||
|
- `tech-template.md` - Override tech steering template
|
||||||
|
- `structure-template.md` - Override structure steering template
|
||||||
|
|
||||||
|
2. **Template Loading Priority**:
|
||||||
|
- The system first checks this `user-templates/` directory
|
||||||
|
- If a matching template is found here, it will be used
|
||||||
|
- Otherwise, the default template from `templates/` will be used
|
||||||
|
|
||||||
|
## Example Custom Template
|
||||||
|
|
||||||
|
To create a custom requirements template:
|
||||||
|
|
||||||
|
1. Create a file named `requirements-template.md` in this directory
|
||||||
|
2. Add your custom structure, for example:
|
||||||
|
|
||||||
|
```markdown
|
||||||
|
# Requirements Document
|
||||||
|
|
||||||
|
## Executive Summary
|
||||||
|
[Your custom section]
|
||||||
|
|
||||||
|
## Business Requirements
|
||||||
|
[Your custom structure]
|
||||||
|
|
||||||
|
## Technical Requirements
|
||||||
|
[Your custom fields]
|
||||||
|
|
||||||
|
## Custom Sections
|
||||||
|
[Add any sections specific to your workflow]
|
||||||
|
```
|
||||||
|
|
||||||
|
## Template Variables
|
||||||
|
|
||||||
|
Templates can include placeholders that will be replaced when documents are created:
|
||||||
|
- `{{projectName}}` - The name of your project
|
||||||
|
- `{{featureName}}` - The name of the feature being specified
|
||||||
|
- `{{date}}` - The current date
|
||||||
|
- `{{author}}` - The document author
|
||||||
|
|
||||||
|
## Best Practices
|
||||||
|
|
||||||
|
1. **Start from defaults**: Copy a default template from `../templates/` as a starting point
|
||||||
|
2. **Keep structure consistent**: Maintain similar section headers for tool compatibility
|
||||||
|
3. **Document changes**: Add comments explaining why sections were added/modified
|
||||||
|
4. **Version control**: Track your custom templates in version control
|
||||||
|
5. **Test thoroughly**: Ensure custom templates work with the spec workflow tools
|
||||||
|
|
||||||
|
## Notes
|
||||||
|
|
||||||
|
- Custom templates are project-specific and not included in the package distribution
|
||||||
|
- The `templates/` directory contains the default templates which are updated with each version
|
||||||
|
- Your custom templates in this directory are preserved during updates
|
||||||
|
- If a custom template has errors, the system will fall back to the default template
|
||||||
|
|
@ -33,4 +33,9 @@ export default defineConfig({
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
],
|
],
|
||||||
|
vite: {
|
||||||
|
define: {
|
||||||
|
global: 'globalThis',
|
||||||
|
},
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|
|
||||||
672
package-lock.json
generated
672
package-lock.json
generated
|
|
@ -20,15 +20,18 @@
|
||||||
"@types/react-dom": "^18.2.10",
|
"@types/react-dom": "^18.2.10",
|
||||||
"@vite-pwa/astro": "0.5.0",
|
"@vite-pwa/astro": "0.5.0",
|
||||||
"astro": "4.10.3",
|
"astro": "4.10.3",
|
||||||
|
"astro-i18next": "1.0.0-beta.21",
|
||||||
"deepmerge": "4.3.1",
|
"deepmerge": "4.3.1",
|
||||||
"focus-trap-react": "10.2.3",
|
"focus-trap-react": "10.2.3",
|
||||||
"framer-motion": "10.16.4",
|
"framer-motion": "10.16.4",
|
||||||
"howler": "2.2.4",
|
"howler": "2.2.4",
|
||||||
|
"i18next": "25.6.2",
|
||||||
"js-confetti": "0.12.0",
|
"js-confetti": "0.12.0",
|
||||||
"motion": "12.23.24",
|
"motion": "12.23.24",
|
||||||
"react": "^18.2.0",
|
"react": "^18.2.0",
|
||||||
"react-dom": "^18.2.0",
|
"react-dom": "^18.2.0",
|
||||||
"react-hotkeys-hook": "3.2.1",
|
"react-hotkeys-hook": "3.2.1",
|
||||||
|
"react-i18next": "16.3.3",
|
||||||
"react-icons": "4.11.0",
|
"react-icons": "4.11.0",
|
||||||
"react-wrap-balancer": "1.1.0",
|
"react-wrap-balancer": "1.1.0",
|
||||||
"react-youtube": "10.1.0",
|
"react-youtube": "10.1.0",
|
||||||
|
|
@ -115,7 +118,8 @@
|
||||||
"node_modules/@astrojs/compiler": {
|
"node_modules/@astrojs/compiler": {
|
||||||
"version": "2.8.0",
|
"version": "2.8.0",
|
||||||
"resolved": "https://registry.npmjs.org/@astrojs/compiler/-/compiler-2.8.0.tgz",
|
"resolved": "https://registry.npmjs.org/@astrojs/compiler/-/compiler-2.8.0.tgz",
|
||||||
"integrity": "sha512-yrpD1WRGqsJwANaDIdtHo+YVjvIOFAjC83lu5qENIgrafwZcJgSXDuwVMXOgok4tFzpeKLsFQ6c3FoUdloLWBQ=="
|
"integrity": "sha512-yrpD1WRGqsJwANaDIdtHo+YVjvIOFAjC83lu5qENIgrafwZcJgSXDuwVMXOgok4tFzpeKLsFQ6c3FoUdloLWBQ==",
|
||||||
|
"peer": true
|
||||||
},
|
},
|
||||||
"node_modules/@astrojs/internal-helpers": {
|
"node_modules/@astrojs/internal-helpers": {
|
||||||
"version": "0.4.0",
|
"version": "0.4.0",
|
||||||
|
|
@ -229,6 +233,7 @@
|
||||||
"version": "7.24.7",
|
"version": "7.24.7",
|
||||||
"resolved": "https://registry.npmjs.org/@babel/core/-/core-7.24.7.tgz",
|
"resolved": "https://registry.npmjs.org/@babel/core/-/core-7.24.7.tgz",
|
||||||
"integrity": "sha512-nykK+LEK86ahTkX/3TgauT0ikKoNCfKHEaZYTUVupJdTLzGNvrblu4u6fa7DhZONAltdf8e662t/abY8idrd/g==",
|
"integrity": "sha512-nykK+LEK86ahTkX/3TgauT0ikKoNCfKHEaZYTUVupJdTLzGNvrblu4u6fa7DhZONAltdf8e662t/abY8idrd/g==",
|
||||||
|
"peer": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@ampproject/remapping": "^2.2.0",
|
"@ampproject/remapping": "^2.2.0",
|
||||||
"@babel/code-frame": "^7.24.7",
|
"@babel/code-frame": "^7.24.7",
|
||||||
|
|
@ -2044,12 +2049,10 @@
|
||||||
"integrity": "sha512-x/rqGMdzj+fWZvCOYForTghzbtqPDZ5gPwaoNGHdgDfF2QA/XZbCBp4Moo5scrkAMPhB7z26XM/AaHuIJdgauA=="
|
"integrity": "sha512-x/rqGMdzj+fWZvCOYForTghzbtqPDZ5gPwaoNGHdgDfF2QA/XZbCBp4Moo5scrkAMPhB7z26XM/AaHuIJdgauA=="
|
||||||
},
|
},
|
||||||
"node_modules/@babel/runtime": {
|
"node_modules/@babel/runtime": {
|
||||||
"version": "7.23.1",
|
"version": "7.28.4",
|
||||||
"resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.23.1.tgz",
|
"resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.28.4.tgz",
|
||||||
"integrity": "sha512-hC2v6p8ZSI/W0HUzh3V8C5g+NwSKzKPtJwSpTjwl0o297GP9+ZLQSkdvHz46CM3LqyoXxq+5G9komY+eSqSO0g==",
|
"integrity": "sha512-Q/N6JNWvIvPnLDvjlE1OUBLPQHH6l3CltCEsHIujp45zQUSSh8K+gHnaEX45yAT1nyngnINhvWtzN+Nb9D8RAQ==",
|
||||||
"dependencies": {
|
"license": "MIT",
|
||||||
"regenerator-runtime": "^0.14.0"
|
|
||||||
},
|
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=6.9.0"
|
"node": ">=6.9.0"
|
||||||
}
|
}
|
||||||
|
|
@ -2889,6 +2892,7 @@
|
||||||
"url": "https://opencollective.com/csstools"
|
"url": "https://opencollective.com/csstools"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
"peer": true,
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": "^14 || ^16 || >=18"
|
"node": "^14 || ^16 || >=18"
|
||||||
},
|
},
|
||||||
|
|
@ -2911,6 +2915,7 @@
|
||||||
"url": "https://opencollective.com/csstools"
|
"url": "https://opencollective.com/csstools"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
"peer": true,
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": "^14 || ^16 || >=18"
|
"node": "^14 || ^16 || >=18"
|
||||||
}
|
}
|
||||||
|
|
@ -4288,6 +4293,29 @@
|
||||||
"url": "https://opencollective.com/unts"
|
"url": "https://opencollective.com/unts"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@proload/core": {
|
||||||
|
"version": "0.3.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/@proload/core/-/core-0.3.3.tgz",
|
||||||
|
"integrity": "sha512-7dAFWsIK84C90AMl24+N/ProHKm4iw0akcnoKjRvbfHifJZBLhaDsDus1QJmhG12lXj4e/uB/8mB/0aduCW+NQ==",
|
||||||
|
"license": "MIT",
|
||||||
|
"peer": true,
|
||||||
|
"dependencies": {
|
||||||
|
"deepmerge": "^4.2.2",
|
||||||
|
"escalade": "^3.1.1"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@proload/plugin-tsm": {
|
||||||
|
"version": "0.2.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/@proload/plugin-tsm/-/plugin-tsm-0.2.1.tgz",
|
||||||
|
"integrity": "sha512-Ex1sL2BxU+g8MHdAdq9SZKz+pU34o8Zcl9PHWo2WaG9hrnlZme607PU6gnpoAYsDBpHX327+eu60wWUk+d/b+A==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"tsm": "^2.1.4"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"@proload/core": "^0.3.2"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@radix-ui/number": {
|
"node_modules/@radix-ui/number": {
|
||||||
"version": "1.1.0",
|
"version": "1.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/@radix-ui/number/-/number-1.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/@radix-ui/number/-/number-1.1.0.tgz",
|
||||||
|
|
@ -8850,7 +8878,8 @@
|
||||||
"version": "20.5.1",
|
"version": "20.5.1",
|
||||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-20.5.1.tgz",
|
"resolved": "https://registry.npmjs.org/@types/node/-/node-20.5.1.tgz",
|
||||||
"integrity": "sha512-4tT2UrL5LBqDwoed9wZ6N3umC4Yhz3W3FloMmiiG4JwmUJWpie0c7lcnUNd4gtMKuDEO4wRVS8B6Xa0uMRsMKg==",
|
"integrity": "sha512-4tT2UrL5LBqDwoed9wZ6N3umC4Yhz3W3FloMmiiG4JwmUJWpie0c7lcnUNd4gtMKuDEO4wRVS8B6Xa0uMRsMKg==",
|
||||||
"devOptional": true
|
"devOptional": true,
|
||||||
|
"peer": true
|
||||||
},
|
},
|
||||||
"node_modules/@types/normalize-package-data": {
|
"node_modules/@types/normalize-package-data": {
|
||||||
"version": "2.4.2",
|
"version": "2.4.2",
|
||||||
|
|
@ -8885,6 +8914,7 @@
|
||||||
"version": "18.2.25",
|
"version": "18.2.25",
|
||||||
"resolved": "https://registry.npmjs.org/@types/react/-/react-18.2.25.tgz",
|
"resolved": "https://registry.npmjs.org/@types/react/-/react-18.2.25.tgz",
|
||||||
"integrity": "sha512-24xqse6+VByVLIr+xWaQ9muX1B4bXJKXBbjszbld/UEDslGLY53+ZucF44HCmLbMPejTzGG9XgR+3m2/Wqu1kw==",
|
"integrity": "sha512-24xqse6+VByVLIr+xWaQ9muX1B4bXJKXBbjszbld/UEDslGLY53+ZucF44HCmLbMPejTzGG9XgR+3m2/Wqu1kw==",
|
||||||
|
"peer": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@types/prop-types": "*",
|
"@types/prop-types": "*",
|
||||||
"@types/scheduler": "*",
|
"@types/scheduler": "*",
|
||||||
|
|
@ -8895,6 +8925,7 @@
|
||||||
"version": "18.2.10",
|
"version": "18.2.10",
|
||||||
"resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.2.10.tgz",
|
"resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.2.10.tgz",
|
||||||
"integrity": "sha512-5VEC5RgXIk1HHdyN1pHlg0cOqnxHzvPGpMMyGAP5qSaDRmyZNDaQ0kkVAkK6NYlDhP6YBID3llaXlmAS/mdgCA==",
|
"integrity": "sha512-5VEC5RgXIk1HHdyN1pHlg0cOqnxHzvPGpMMyGAP5qSaDRmyZNDaQ0kkVAkK6NYlDhP6YBID3llaXlmAS/mdgCA==",
|
||||||
|
"peer": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@types/react": "*"
|
"@types/react": "*"
|
||||||
}
|
}
|
||||||
|
|
@ -9135,6 +9166,7 @@
|
||||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-6.7.4.tgz",
|
"resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-6.7.4.tgz",
|
||||||
"integrity": "sha512-I5zVZFY+cw4IMZUeNCU7Sh2PO5O57F7Lr0uyhgCJmhN/BuTlnc55KxPonR4+EM3GBdfiCyGZye6DgMjtubQkmA==",
|
"integrity": "sha512-I5zVZFY+cw4IMZUeNCU7Sh2PO5O57F7Lr0uyhgCJmhN/BuTlnc55KxPonR4+EM3GBdfiCyGZye6DgMjtubQkmA==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
|
"peer": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@typescript-eslint/scope-manager": "6.7.4",
|
"@typescript-eslint/scope-manager": "6.7.4",
|
||||||
"@typescript-eslint/types": "6.7.4",
|
"@typescript-eslint/types": "6.7.4",
|
||||||
|
|
@ -9591,6 +9623,7 @@
|
||||||
"version": "8.12.0",
|
"version": "8.12.0",
|
||||||
"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.12.0.tgz",
|
"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.12.0.tgz",
|
||||||
"integrity": "sha512-RTvkC4w+KNXrM39/lWCUaG0IbRkWdCv7W/IOW9oU6SawyxulvkQy5HQPVTKxEjczcUvapcrw3cFx/60VN/NRNw==",
|
"integrity": "sha512-RTvkC4w+KNXrM39/lWCUaG0IbRkWdCv7W/IOW9oU6SawyxulvkQy5HQPVTKxEjczcUvapcrw3cFx/60VN/NRNw==",
|
||||||
|
"peer": true,
|
||||||
"bin": {
|
"bin": {
|
||||||
"acorn": "bin/acorn"
|
"acorn": "bin/acorn"
|
||||||
},
|
},
|
||||||
|
|
@ -10025,6 +10058,7 @@
|
||||||
"version": "4.10.3",
|
"version": "4.10.3",
|
||||||
"resolved": "https://registry.npmjs.org/astro/-/astro-4.10.3.tgz",
|
"resolved": "https://registry.npmjs.org/astro/-/astro-4.10.3.tgz",
|
||||||
"integrity": "sha512-TWCJM+Vg+y0UoEz/H75rfp/u2N8yxeQQ2UrU9+fMcbjlzQJtGGDq3ApdundqPZgAuCryRuJnrKytStMZCFnlvQ==",
|
"integrity": "sha512-TWCJM+Vg+y0UoEz/H75rfp/u2N8yxeQQ2UrU9+fMcbjlzQJtGGDq3ApdundqPZgAuCryRuJnrKytStMZCFnlvQ==",
|
||||||
|
"peer": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@astrojs/compiler": "^2.8.0",
|
"@astrojs/compiler": "^2.8.0",
|
||||||
"@astrojs/internal-helpers": "0.4.0",
|
"@astrojs/internal-helpers": "0.4.0",
|
||||||
|
|
@ -10171,6 +10205,52 @@
|
||||||
"url": "https://opencollective.com/typescript-eslint"
|
"url": "https://opencollective.com/typescript-eslint"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/astro-i18next": {
|
||||||
|
"version": "1.0.0-beta.21",
|
||||||
|
"resolved": "https://registry.npmjs.org/astro-i18next/-/astro-i18next-1.0.0-beta.21.tgz",
|
||||||
|
"integrity": "sha512-1YPqwexumHpK/d9afEoi52CBFTu6k4MYv/oHjsaAasZDvFClU6U5VPttC/OgZcXRYggCM6ee2LOnyHqlmXOeLA==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@proload/core": "^0.3.3",
|
||||||
|
"@proload/plugin-tsm": "^0.2.1",
|
||||||
|
"i18next": "^22.4.10",
|
||||||
|
"i18next-browser-languagedetector": "^7.0.1",
|
||||||
|
"i18next-fs-backend": "^2.1.1",
|
||||||
|
"i18next-http-backend": "^2.1.1",
|
||||||
|
"iso-639-1": "^2.1.15",
|
||||||
|
"locale-emoji": "^0.3.0",
|
||||||
|
"pathe": "^1.1.0"
|
||||||
|
},
|
||||||
|
"bin": {
|
||||||
|
"astro-i18next": "dist/cli/index.js"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"astro": ">=1.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/astro-i18next/node_modules/i18next": {
|
||||||
|
"version": "22.5.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/i18next/-/i18next-22.5.1.tgz",
|
||||||
|
"integrity": "sha512-8TGPgM3pAD+VRsMtUMNknRz3kzqwp/gPALrWMsDnmC1mKqJwpWyooQRLMcbTwq8z8YwSmuj+ZYvc+xCuEpkssA==",
|
||||||
|
"funding": [
|
||||||
|
{
|
||||||
|
"type": "individual",
|
||||||
|
"url": "https://locize.com"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "individual",
|
||||||
|
"url": "https://locize.com/i18next.html"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "individual",
|
||||||
|
"url": "https://www.i18next.com/how-to/faq#i18next-is-awesome.-how-can-i-support-the-project"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@babel/runtime": "^7.20.6"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/astro/node_modules/@esbuild/android-arm": {
|
"node_modules/astro/node_modules/@esbuild/android-arm": {
|
||||||
"version": "0.21.5",
|
"version": "0.21.5",
|
||||||
"resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.21.5.tgz",
|
"resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.21.5.tgz",
|
||||||
|
|
@ -11028,6 +11108,7 @@
|
||||||
"url": "https://github.com/sponsors/ai"
|
"url": "https://github.com/sponsors/ai"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
"peer": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"caniuse-lite": "^1.0.30001587",
|
"caniuse-lite": "^1.0.30001587",
|
||||||
"electron-to-chromium": "^1.4.668",
|
"electron-to-chromium": "^1.4.668",
|
||||||
|
|
@ -13508,6 +13589,7 @@
|
||||||
"resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-8.3.6.tgz",
|
"resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-8.3.6.tgz",
|
||||||
"integrity": "sha512-kcZ6+W5QzcJ3P1Mt+83OUv/oHFqZHIx8DuxG6eZ5RGMERoLqp4BuGjhHLYGK+Kf5XVkQvqBSmAy/nGWN3qDgEA==",
|
"integrity": "sha512-kcZ6+W5QzcJ3P1Mt+83OUv/oHFqZHIx8DuxG6eZ5RGMERoLqp4BuGjhHLYGK+Kf5XVkQvqBSmAy/nGWN3qDgEA==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
|
"peer": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"import-fresh": "^3.3.0",
|
"import-fresh": "^3.3.0",
|
||||||
"js-yaml": "^4.1.0",
|
"js-yaml": "^4.1.0",
|
||||||
|
|
@ -13550,6 +13632,15 @@
|
||||||
"integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==",
|
"integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
|
"node_modules/cross-fetch": {
|
||||||
|
"version": "4.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/cross-fetch/-/cross-fetch-4.0.0.tgz",
|
||||||
|
"integrity": "sha512-e4a5N8lVvuLgAWgnCrLr2PP0YyDOTHa9H/Rj54dirp61qXnNq46m82bRhNqIA5VccJtWBvPTFRV3TtvHUKPB1g==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"node-fetch": "^2.6.12"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/cross-spawn": {
|
"node_modules/cross-spawn": {
|
||||||
"version": "7.0.3",
|
"version": "7.0.3",
|
||||||
"resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz",
|
"resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz",
|
||||||
|
|
@ -14730,6 +14821,7 @@
|
||||||
"integrity": "sha512-l7iffQpT2OrZfH2rXIp7/FkmaeZM0vxbxN9KfiCwGYuZqzMg/JdvX26R31Zxn/Pxvsrg3Y9N6XTcnknqDyyv4w==",
|
"integrity": "sha512-l7iffQpT2OrZfH2rXIp7/FkmaeZM0vxbxN9KfiCwGYuZqzMg/JdvX26R31Zxn/Pxvsrg3Y9N6XTcnknqDyyv4w==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"hasInstallScript": true,
|
"hasInstallScript": true,
|
||||||
|
"peer": true,
|
||||||
"bin": {
|
"bin": {
|
||||||
"esbuild": "bin/esbuild"
|
"esbuild": "bin/esbuild"
|
||||||
},
|
},
|
||||||
|
|
@ -14761,6 +14853,262 @@
|
||||||
"@esbuild/win32-x64": "0.19.8"
|
"@esbuild/win32-x64": "0.19.8"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/esbuild-android-64": {
|
||||||
|
"version": "0.15.18",
|
||||||
|
"resolved": "https://registry.npmjs.org/esbuild-android-64/-/esbuild-android-64-0.15.18.tgz",
|
||||||
|
"integrity": "sha512-wnpt3OXRhcjfIDSZu9bnzT4/TNTDsOUvip0foZOUBG7QbSt//w3QV4FInVJxNhKc/ErhUxc5z4QjHtMi7/TbgA==",
|
||||||
|
"cpu": [
|
||||||
|
"x64"
|
||||||
|
],
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"android"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">=12"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/esbuild-android-arm64": {
|
||||||
|
"version": "0.15.18",
|
||||||
|
"resolved": "https://registry.npmjs.org/esbuild-android-arm64/-/esbuild-android-arm64-0.15.18.tgz",
|
||||||
|
"integrity": "sha512-G4xu89B8FCzav9XU8EjsXacCKSG2FT7wW9J6hOc18soEHJdtWu03L3TQDGf0geNxfLTtxENKBzMSq9LlbjS8OQ==",
|
||||||
|
"cpu": [
|
||||||
|
"arm64"
|
||||||
|
],
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"android"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">=12"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/esbuild-darwin-64": {
|
||||||
|
"version": "0.15.18",
|
||||||
|
"resolved": "https://registry.npmjs.org/esbuild-darwin-64/-/esbuild-darwin-64-0.15.18.tgz",
|
||||||
|
"integrity": "sha512-2WAvs95uPnVJPuYKP0Eqx+Dl/jaYseZEUUT1sjg97TJa4oBtbAKnPnl3b5M9l51/nbx7+QAEtuummJZW0sBEmg==",
|
||||||
|
"cpu": [
|
||||||
|
"x64"
|
||||||
|
],
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"darwin"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">=12"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/esbuild-darwin-arm64": {
|
||||||
|
"version": "0.15.18",
|
||||||
|
"resolved": "https://registry.npmjs.org/esbuild-darwin-arm64/-/esbuild-darwin-arm64-0.15.18.tgz",
|
||||||
|
"integrity": "sha512-tKPSxcTJ5OmNb1btVikATJ8NftlyNlc8BVNtyT/UAr62JFOhwHlnoPrhYWz09akBLHI9nElFVfWSTSRsrZiDUA==",
|
||||||
|
"cpu": [
|
||||||
|
"arm64"
|
||||||
|
],
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"darwin"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">=12"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/esbuild-freebsd-64": {
|
||||||
|
"version": "0.15.18",
|
||||||
|
"resolved": "https://registry.npmjs.org/esbuild-freebsd-64/-/esbuild-freebsd-64-0.15.18.tgz",
|
||||||
|
"integrity": "sha512-TT3uBUxkteAjR1QbsmvSsjpKjOX6UkCstr8nMr+q7zi3NuZ1oIpa8U41Y8I8dJH2fJgdC3Dj3CXO5biLQpfdZA==",
|
||||||
|
"cpu": [
|
||||||
|
"x64"
|
||||||
|
],
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"freebsd"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">=12"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/esbuild-freebsd-arm64": {
|
||||||
|
"version": "0.15.18",
|
||||||
|
"resolved": "https://registry.npmjs.org/esbuild-freebsd-arm64/-/esbuild-freebsd-arm64-0.15.18.tgz",
|
||||||
|
"integrity": "sha512-R/oVr+X3Tkh+S0+tL41wRMbdWtpWB8hEAMsOXDumSSa6qJR89U0S/PpLXrGF7Wk/JykfpWNokERUpCeHDl47wA==",
|
||||||
|
"cpu": [
|
||||||
|
"arm64"
|
||||||
|
],
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"freebsd"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">=12"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/esbuild-linux-32": {
|
||||||
|
"version": "0.15.18",
|
||||||
|
"resolved": "https://registry.npmjs.org/esbuild-linux-32/-/esbuild-linux-32-0.15.18.tgz",
|
||||||
|
"integrity": "sha512-lphF3HiCSYtaa9p1DtXndiQEeQDKPl9eN/XNoBf2amEghugNuqXNZA/ZovthNE2aa4EN43WroO0B85xVSjYkbg==",
|
||||||
|
"cpu": [
|
||||||
|
"ia32"
|
||||||
|
],
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"linux"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">=12"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/esbuild-linux-64": {
|
||||||
|
"version": "0.15.18",
|
||||||
|
"resolved": "https://registry.npmjs.org/esbuild-linux-64/-/esbuild-linux-64-0.15.18.tgz",
|
||||||
|
"integrity": "sha512-hNSeP97IviD7oxLKFuii5sDPJ+QHeiFTFLoLm7NZQligur8poNOWGIgpQ7Qf8Balb69hptMZzyOBIPtY09GZYw==",
|
||||||
|
"cpu": [
|
||||||
|
"x64"
|
||||||
|
],
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"linux"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">=12"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/esbuild-linux-arm": {
|
||||||
|
"version": "0.15.18",
|
||||||
|
"resolved": "https://registry.npmjs.org/esbuild-linux-arm/-/esbuild-linux-arm-0.15.18.tgz",
|
||||||
|
"integrity": "sha512-UH779gstRblS4aoS2qpMl3wjg7U0j+ygu3GjIeTonCcN79ZvpPee12Qun3vcdxX+37O5LFxz39XeW2I9bybMVA==",
|
||||||
|
"cpu": [
|
||||||
|
"arm"
|
||||||
|
],
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"linux"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">=12"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/esbuild-linux-arm64": {
|
||||||
|
"version": "0.15.18",
|
||||||
|
"resolved": "https://registry.npmjs.org/esbuild-linux-arm64/-/esbuild-linux-arm64-0.15.18.tgz",
|
||||||
|
"integrity": "sha512-54qr8kg/6ilcxd+0V3h9rjT4qmjc0CccMVWrjOEM/pEcUzt8X62HfBSeZfT2ECpM7104mk4yfQXkosY8Quptug==",
|
||||||
|
"cpu": [
|
||||||
|
"arm64"
|
||||||
|
],
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"linux"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">=12"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/esbuild-linux-mips64le": {
|
||||||
|
"version": "0.15.18",
|
||||||
|
"resolved": "https://registry.npmjs.org/esbuild-linux-mips64le/-/esbuild-linux-mips64le-0.15.18.tgz",
|
||||||
|
"integrity": "sha512-Mk6Ppwzzz3YbMl/ZZL2P0q1tnYqh/trYZ1VfNP47C31yT0K8t9s7Z077QrDA/guU60tGNp2GOwCQnp+DYv7bxQ==",
|
||||||
|
"cpu": [
|
||||||
|
"mips64el"
|
||||||
|
],
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"linux"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">=12"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/esbuild-linux-ppc64le": {
|
||||||
|
"version": "0.15.18",
|
||||||
|
"resolved": "https://registry.npmjs.org/esbuild-linux-ppc64le/-/esbuild-linux-ppc64le-0.15.18.tgz",
|
||||||
|
"integrity": "sha512-b0XkN4pL9WUulPTa/VKHx2wLCgvIAbgwABGnKMY19WhKZPT+8BxhZdqz6EgkqCLld7X5qiCY2F/bfpUUlnFZ9w==",
|
||||||
|
"cpu": [
|
||||||
|
"ppc64"
|
||||||
|
],
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"linux"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">=12"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/esbuild-linux-riscv64": {
|
||||||
|
"version": "0.15.18",
|
||||||
|
"resolved": "https://registry.npmjs.org/esbuild-linux-riscv64/-/esbuild-linux-riscv64-0.15.18.tgz",
|
||||||
|
"integrity": "sha512-ba2COaoF5wL6VLZWn04k+ACZjZ6NYniMSQStodFKH/Pu6RxzQqzsmjR1t9QC89VYJxBeyVPTaHuBMCejl3O/xg==",
|
||||||
|
"cpu": [
|
||||||
|
"riscv64"
|
||||||
|
],
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"linux"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">=12"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/esbuild-linux-s390x": {
|
||||||
|
"version": "0.15.18",
|
||||||
|
"resolved": "https://registry.npmjs.org/esbuild-linux-s390x/-/esbuild-linux-s390x-0.15.18.tgz",
|
||||||
|
"integrity": "sha512-VbpGuXEl5FCs1wDVp93O8UIzl3ZrglgnSQ+Hu79g7hZu6te6/YHgVJxCM2SqfIila0J3k0csfnf8VD2W7u2kzQ==",
|
||||||
|
"cpu": [
|
||||||
|
"s390x"
|
||||||
|
],
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"linux"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">=12"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/esbuild-netbsd-64": {
|
||||||
|
"version": "0.15.18",
|
||||||
|
"resolved": "https://registry.npmjs.org/esbuild-netbsd-64/-/esbuild-netbsd-64-0.15.18.tgz",
|
||||||
|
"integrity": "sha512-98ukeCdvdX7wr1vUYQzKo4kQ0N2p27H7I11maINv73fVEXt2kyh4K4m9f35U1K43Xc2QGXlzAw0K9yoU7JUjOg==",
|
||||||
|
"cpu": [
|
||||||
|
"x64"
|
||||||
|
],
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"netbsd"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">=12"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/esbuild-openbsd-64": {
|
||||||
|
"version": "0.15.18",
|
||||||
|
"resolved": "https://registry.npmjs.org/esbuild-openbsd-64/-/esbuild-openbsd-64-0.15.18.tgz",
|
||||||
|
"integrity": "sha512-yK5NCcH31Uae076AyQAXeJzt/vxIo9+omZRKj1pauhk3ITuADzuOx5N2fdHrAKPxN+zH3w96uFKlY7yIn490xQ==",
|
||||||
|
"cpu": [
|
||||||
|
"x64"
|
||||||
|
],
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"openbsd"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">=12"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/esbuild-plugin-alias": {
|
"node_modules/esbuild-plugin-alias": {
|
||||||
"version": "0.2.1",
|
"version": "0.2.1",
|
||||||
"resolved": "https://registry.npmjs.org/esbuild-plugin-alias/-/esbuild-plugin-alias-0.2.1.tgz",
|
"resolved": "https://registry.npmjs.org/esbuild-plugin-alias/-/esbuild-plugin-alias-0.2.1.tgz",
|
||||||
|
|
@ -14779,6 +15127,70 @@
|
||||||
"esbuild": ">=0.12 <1"
|
"esbuild": ">=0.12 <1"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/esbuild-sunos-64": {
|
||||||
|
"version": "0.15.18",
|
||||||
|
"resolved": "https://registry.npmjs.org/esbuild-sunos-64/-/esbuild-sunos-64-0.15.18.tgz",
|
||||||
|
"integrity": "sha512-On22LLFlBeLNj/YF3FT+cXcyKPEI263nflYlAhz5crxtp3yRG1Ugfr7ITyxmCmjm4vbN/dGrb/B7w7U8yJR9yw==",
|
||||||
|
"cpu": [
|
||||||
|
"x64"
|
||||||
|
],
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"sunos"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">=12"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/esbuild-windows-32": {
|
||||||
|
"version": "0.15.18",
|
||||||
|
"resolved": "https://registry.npmjs.org/esbuild-windows-32/-/esbuild-windows-32-0.15.18.tgz",
|
||||||
|
"integrity": "sha512-o+eyLu2MjVny/nt+E0uPnBxYuJHBvho8vWsC2lV61A7wwTWC3jkN2w36jtA+yv1UgYkHRihPuQsL23hsCYGcOQ==",
|
||||||
|
"cpu": [
|
||||||
|
"ia32"
|
||||||
|
],
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"win32"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">=12"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/esbuild-windows-64": {
|
||||||
|
"version": "0.15.18",
|
||||||
|
"resolved": "https://registry.npmjs.org/esbuild-windows-64/-/esbuild-windows-64-0.15.18.tgz",
|
||||||
|
"integrity": "sha512-qinug1iTTaIIrCorAUjR0fcBk24fjzEedFYhhispP8Oc7SFvs+XeW3YpAKiKp8dRpizl4YYAhxMjlftAMJiaUw==",
|
||||||
|
"cpu": [
|
||||||
|
"x64"
|
||||||
|
],
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"win32"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">=12"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/esbuild-windows-arm64": {
|
||||||
|
"version": "0.15.18",
|
||||||
|
"resolved": "https://registry.npmjs.org/esbuild-windows-arm64/-/esbuild-windows-arm64-0.15.18.tgz",
|
||||||
|
"integrity": "sha512-q9bsYzegpZcLziq0zgUi5KqGVtfhjxGbnksaBFYmWLxeV/S1fK4OLdq2DFYnXcLMjlZw2L0jLsk1eGoB522WXQ==",
|
||||||
|
"cpu": [
|
||||||
|
"arm64"
|
||||||
|
],
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"win32"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">=12"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/escalade": {
|
"node_modules/escalade": {
|
||||||
"version": "3.1.1",
|
"version": "3.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz",
|
||||||
|
|
@ -14827,6 +15239,7 @@
|
||||||
"resolved": "https://registry.npmjs.org/eslint/-/eslint-8.50.0.tgz",
|
"resolved": "https://registry.npmjs.org/eslint/-/eslint-8.50.0.tgz",
|
||||||
"integrity": "sha512-FOnOGSuFuFLv/Sa+FDVRZl4GGVAAFFi8LecRsI5a1tMO5HIE8nCm4ivAlzt4dT3ol/PaaGC0rJEEXQmHJBGoOg==",
|
"integrity": "sha512-FOnOGSuFuFLv/Sa+FDVRZl4GGVAAFFi8LecRsI5a1tMO5HIE8nCm4ivAlzt4dT3ol/PaaGC0rJEEXQmHJBGoOg==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
|
"peer": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@eslint-community/eslint-utils": "^4.2.0",
|
"@eslint-community/eslint-utils": "^4.2.0",
|
||||||
"@eslint-community/regexpp": "^4.6.1",
|
"@eslint-community/regexpp": "^4.6.1",
|
||||||
|
|
@ -15012,6 +15425,7 @@
|
||||||
"resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.28.1.tgz",
|
"resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.28.1.tgz",
|
||||||
"integrity": "sha512-9I9hFlITvOV55alzoKBI+K9q74kv0iKMeY6av5+umsNwayt59fz692daGyjR+oStBQgx6nwR9rXldDev3Clw+A==",
|
"integrity": "sha512-9I9hFlITvOV55alzoKBI+K9q74kv0iKMeY6av5+umsNwayt59fz692daGyjR+oStBQgx6nwR9rXldDev3Clw+A==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
|
"peer": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"array-includes": "^3.1.6",
|
"array-includes": "^3.1.6",
|
||||||
"array.prototype.findlastindex": "^1.2.2",
|
"array.prototype.findlastindex": "^1.2.2",
|
||||||
|
|
@ -17280,6 +17694,7 @@
|
||||||
"version": "7.2.3",
|
"version": "7.2.3",
|
||||||
"resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz",
|
"resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz",
|
||||||
"integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==",
|
"integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==",
|
||||||
|
"peer": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"fs.realpath": "^1.0.0",
|
"fs.realpath": "^1.0.0",
|
||||||
"inflight": "^1.0.4",
|
"inflight": "^1.0.4",
|
||||||
|
|
@ -17911,6 +18326,15 @@
|
||||||
"resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-3.0.3.tgz",
|
"resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-3.0.3.tgz",
|
||||||
"integrity": "sha512-RuMffC89BOWQoY0WKGpIhn5gX3iI54O6nRA0yC124NYVtzjmFWBIiFd8M0x+ZdX0P9R4lADg1mgP8C7PxGOWuQ=="
|
"integrity": "sha512-RuMffC89BOWQoY0WKGpIhn5gX3iI54O6nRA0yC124NYVtzjmFWBIiFd8M0x+ZdX0P9R4lADg1mgP8C7PxGOWuQ=="
|
||||||
},
|
},
|
||||||
|
"node_modules/html-parse-stringify": {
|
||||||
|
"version": "3.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/html-parse-stringify/-/html-parse-stringify-3.0.1.tgz",
|
||||||
|
"integrity": "sha512-KknJ50kTInJ7qIScF3jeaFRpMpE8/lfiTdzf/twXyPBLAGrLRTmkz3AdTnKeh40X8k9L2fdYwEp/42WGXIRGcg==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"void-elements": "3.1.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/html-tags": {
|
"node_modules/html-tags": {
|
||||||
"version": "3.3.1",
|
"version": "3.3.1",
|
||||||
"resolved": "https://registry.npmjs.org/html-tags/-/html-tags-3.3.1.tgz",
|
"resolved": "https://registry.npmjs.org/html-tags/-/html-tags-3.3.1.tgz",
|
||||||
|
|
@ -17995,6 +18419,62 @@
|
||||||
"url": "https://github.com/sponsors/typicode"
|
"url": "https://github.com/sponsors/typicode"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/i18next": {
|
||||||
|
"version": "25.6.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/i18next/-/i18next-25.6.2.tgz",
|
||||||
|
"integrity": "sha512-0GawNyVUw0yvJoOEBq1VHMAsqdM23XrHkMtl2gKEjviJQSLVXsrPqsoYAxBEugW5AB96I2pZkwRxyl8WZVoWdw==",
|
||||||
|
"funding": [
|
||||||
|
{
|
||||||
|
"type": "individual",
|
||||||
|
"url": "https://locize.com"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "individual",
|
||||||
|
"url": "https://locize.com/i18next.html"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "individual",
|
||||||
|
"url": "https://www.i18next.com/how-to/faq#i18next-is-awesome.-how-can-i-support-the-project"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"license": "MIT",
|
||||||
|
"peer": true,
|
||||||
|
"dependencies": {
|
||||||
|
"@babel/runtime": "^7.27.6"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"typescript": "^5"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"typescript": {
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/i18next-browser-languagedetector": {
|
||||||
|
"version": "7.2.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/i18next-browser-languagedetector/-/i18next-browser-languagedetector-7.2.2.tgz",
|
||||||
|
"integrity": "sha512-6b7r75uIJDWCcCflmbof+sJ94k9UQO4X0YR62oUfqGI/GjCLVzlCwu8TFdRZIqVLzWbzNcmkmhfqKEr4TLz4HQ==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@babel/runtime": "^7.23.2"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/i18next-fs-backend": {
|
||||||
|
"version": "2.6.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/i18next-fs-backend/-/i18next-fs-backend-2.6.1.tgz",
|
||||||
|
"integrity": "sha512-eYWTX7QT7kJ0sZyCPK6x1q+R63zvNKv2D6UdbMf15A8vNb2ZLyw4NNNZxPFwXlIv/U+oUtg8SakW6ZgJZcoqHQ==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
|
"node_modules/i18next-http-backend": {
|
||||||
|
"version": "2.7.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/i18next-http-backend/-/i18next-http-backend-2.7.3.tgz",
|
||||||
|
"integrity": "sha512-FgZxrXdRA5u44xfYsJlEBL4/KH3f2IluBpgV/7riW0YW2VEyM8FzVt2XHAOi6id0Ppj7vZvCZVpp5LrGXnc8Ig==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"cross-fetch": "4.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/iconv-lite": {
|
"node_modules/iconv-lite": {
|
||||||
"version": "0.4.24",
|
"version": "0.4.24",
|
||||||
"resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz",
|
"resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz",
|
||||||
|
|
@ -19063,6 +19543,15 @@
|
||||||
"resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz",
|
||||||
"integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw=="
|
"integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw=="
|
||||||
},
|
},
|
||||||
|
"node_modules/iso-639-1": {
|
||||||
|
"version": "2.1.15",
|
||||||
|
"resolved": "https://registry.npmjs.org/iso-639-1/-/iso-639-1-2.1.15.tgz",
|
||||||
|
"integrity": "sha512-7c7mBznZu2ktfvyT582E2msM+Udc1EjOyhVRE/0ZsjD9LBtWSm23h3PtiRh2a35XoUsTQQjJXaJzuLjXsOdFDg==",
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=6.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/isobject": {
|
"node_modules/isobject": {
|
||||||
"version": "3.0.1",
|
"version": "3.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz",
|
||||||
|
|
@ -19774,6 +20263,12 @@
|
||||||
"url": "https://github.com/sponsors/antfu"
|
"url": "https://github.com/sponsors/antfu"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/locale-emoji": {
|
||||||
|
"version": "0.3.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/locale-emoji/-/locale-emoji-0.3.0.tgz",
|
||||||
|
"integrity": "sha512-JGm8+naU49CBDnH1jksS3LecPdfWQLxFgkLN6ZhYONKa850pJ0Xt8DPGJnYK0ZuJI8jTuiDDPCDtSL3nyacXwg==",
|
||||||
|
"license": "CC0-1.0"
|
||||||
|
},
|
||||||
"node_modules/locate-path": {
|
"node_modules/locate-path": {
|
||||||
"version": "6.0.0",
|
"version": "6.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz",
|
||||||
|
|
@ -21315,7 +21810,6 @@
|
||||||
"version": "2.7.0",
|
"version": "2.7.0",
|
||||||
"resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz",
|
"resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz",
|
||||||
"integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==",
|
"integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==",
|
||||||
"dev": true,
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"whatwg-url": "^5.0.0"
|
"whatwg-url": "^5.0.0"
|
||||||
},
|
},
|
||||||
|
|
@ -21953,8 +22447,7 @@
|
||||||
"node_modules/pathe": {
|
"node_modules/pathe": {
|
||||||
"version": "1.1.2",
|
"version": "1.1.2",
|
||||||
"resolved": "https://registry.npmjs.org/pathe/-/pathe-1.1.2.tgz",
|
"resolved": "https://registry.npmjs.org/pathe/-/pathe-1.1.2.tgz",
|
||||||
"integrity": "sha512-whLdWMYL2TwI08hn8/ZqAbrVemu0LNaNNJZX73O6qaIdCTfXutsLhMkjdENX0qhsQ9uIimo4/aQOmXkoon2nDQ==",
|
"integrity": "sha512-whLdWMYL2TwI08hn8/ZqAbrVemu0LNaNNJZX73O6qaIdCTfXutsLhMkjdENX0qhsQ9uIimo4/aQOmXkoon2nDQ=="
|
||||||
"dev": true
|
|
||||||
},
|
},
|
||||||
"node_modules/pathval": {
|
"node_modules/pathval": {
|
||||||
"version": "1.1.1",
|
"version": "1.1.1",
|
||||||
|
|
@ -22175,6 +22668,7 @@
|
||||||
"url": "https://github.com/sponsors/ai"
|
"url": "https://github.com/sponsors/ai"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
"peer": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"nanoid": "^3.3.7",
|
"nanoid": "^3.3.7",
|
||||||
"picocolors": "^1.0.0",
|
"picocolors": "^1.0.0",
|
||||||
|
|
@ -22258,6 +22752,7 @@
|
||||||
"resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.0.13.tgz",
|
"resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.0.13.tgz",
|
||||||
"integrity": "sha512-EaV1Gl4mUEV4ddhDnv/xtj7sxwrwxdetHdWUGnT4VJQf+4d05v6lHYZr8N573k5Z0BViss7BDhfWtKS3+sfAqQ==",
|
"integrity": "sha512-EaV1Gl4mUEV4ddhDnv/xtj7sxwrwxdetHdWUGnT4VJQf+4d05v6lHYZr8N573k5Z0BViss7BDhfWtKS3+sfAqQ==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
|
"peer": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"cssesc": "^3.0.0",
|
"cssesc": "^3.0.0",
|
||||||
"util-deprecate": "^1.0.2"
|
"util-deprecate": "^1.0.2"
|
||||||
|
|
@ -22312,6 +22807,7 @@
|
||||||
"resolved": "https://registry.npmjs.org/prettier/-/prettier-3.0.3.tgz",
|
"resolved": "https://registry.npmjs.org/prettier/-/prettier-3.0.3.tgz",
|
||||||
"integrity": "sha512-L/4pUDMxcNa8R/EthV08Zt42WBO4h1rarVtK0K+QJG0X187OLo7l699jWw0GKuwzkPQ//jMFA/8Xm6Fh3J/DAg==",
|
"integrity": "sha512-L/4pUDMxcNa8R/EthV08Zt42WBO4h1rarVtK0K+QJG0X187OLo7l699jWw0GKuwzkPQ//jMFA/8Xm6Fh3J/DAg==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
|
"peer": true,
|
||||||
"bin": {
|
"bin": {
|
||||||
"prettier": "bin/prettier.cjs"
|
"prettier": "bin/prettier.cjs"
|
||||||
},
|
},
|
||||||
|
|
@ -22463,6 +22959,7 @@
|
||||||
"version": "15.8.1",
|
"version": "15.8.1",
|
||||||
"resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz",
|
"resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz",
|
||||||
"integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==",
|
"integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==",
|
||||||
|
"peer": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"loose-envify": "^1.4.0",
|
"loose-envify": "^1.4.0",
|
||||||
"object-assign": "^4.1.1",
|
"object-assign": "^4.1.1",
|
||||||
|
|
@ -22633,6 +23130,7 @@
|
||||||
"version": "18.2.0",
|
"version": "18.2.0",
|
||||||
"resolved": "https://registry.npmjs.org/react/-/react-18.2.0.tgz",
|
"resolved": "https://registry.npmjs.org/react/-/react-18.2.0.tgz",
|
||||||
"integrity": "sha512-/3IjMdb2L9QbBdWiW5e3P2/npwMBaU9mHCSCUzNln0ZCYbcfTsGbTJrU/kGemdH2IWmB2ioZ+zkxtmq6g09fGQ==",
|
"integrity": "sha512-/3IjMdb2L9QbBdWiW5e3P2/npwMBaU9mHCSCUzNln0ZCYbcfTsGbTJrU/kGemdH2IWmB2ioZ+zkxtmq6g09fGQ==",
|
||||||
|
"peer": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"loose-envify": "^1.1.0"
|
"loose-envify": "^1.1.0"
|
||||||
},
|
},
|
||||||
|
|
@ -22705,6 +23203,7 @@
|
||||||
"version": "18.2.0",
|
"version": "18.2.0",
|
||||||
"resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.2.0.tgz",
|
"resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.2.0.tgz",
|
||||||
"integrity": "sha512-6IMTriUmvsjHUjNtEDudZfuDQUoWXVxKHhlEGSk81n4YFS+r/Kl99wXiwlVXtPBtJenozv2P+hxDsw9eA7Xo6g==",
|
"integrity": "sha512-6IMTriUmvsjHUjNtEDudZfuDQUoWXVxKHhlEGSk81n4YFS+r/Kl99wXiwlVXtPBtJenozv2P+hxDsw9eA7Xo6g==",
|
||||||
|
"peer": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"loose-envify": "^1.1.0",
|
"loose-envify": "^1.1.0",
|
||||||
"scheduler": "^0.23.0"
|
"scheduler": "^0.23.0"
|
||||||
|
|
@ -22746,6 +23245,42 @@
|
||||||
"react-dom": ">=16.8.1"
|
"react-dom": ">=16.8.1"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/react-i18next": {
|
||||||
|
"version": "16.3.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/react-i18next/-/react-i18next-16.3.3.tgz",
|
||||||
|
"integrity": "sha512-IaY2W+ueVd/fe7H6Wj2S4bTuLNChnajFUlZFfCTrTHWzGcOrUHlVzW55oXRSl+J51U8Onn6EvIhQ+Bar9FUcjw==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@babel/runtime": "^7.27.6",
|
||||||
|
"html-parse-stringify": "^3.0.1",
|
||||||
|
"use-sync-external-store": "^1.6.0"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"i18next": ">= 25.6.2",
|
||||||
|
"react": ">= 16.8.0",
|
||||||
|
"typescript": "^5"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"react-dom": {
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
|
"react-native": {
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
|
"typescript": {
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/react-i18next/node_modules/use-sync-external-store": {
|
||||||
|
"version": "1.6.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.6.0.tgz",
|
||||||
|
"integrity": "sha512-Pp6GSwGP/NrPIrxVFAIkOQeyw8lFenOHijQWkUTrDvrF4ALqylP2C/KCkeS9dpUM3KvYRQhna5vt7IL95+ZQ9w==",
|
||||||
|
"license": "MIT",
|
||||||
|
"peerDependencies": {
|
||||||
|
"react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/react-icons": {
|
"node_modules/react-icons": {
|
||||||
"version": "4.11.0",
|
"version": "4.11.0",
|
||||||
"resolved": "https://registry.npmjs.org/react-icons/-/react-icons-4.11.0.tgz",
|
"resolved": "https://registry.npmjs.org/react-icons/-/react-icons-4.11.0.tgz",
|
||||||
|
|
@ -23011,11 +23546,6 @@
|
||||||
"node": ">=4"
|
"node": ">=4"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/regenerator-runtime": {
|
|
||||||
"version": "0.14.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.14.0.tgz",
|
|
||||||
"integrity": "sha512-srw17NI0TUWHuGa5CFGGmhfNIeja30WMBfbslPNhf6JrqQlLN5gcrvig1oqPxiVaXb0oW0XRKtH6Nngs5lKCIA=="
|
|
||||||
},
|
|
||||||
"node_modules/regenerator-transform": {
|
"node_modules/regenerator-transform": {
|
||||||
"version": "0.15.2",
|
"version": "0.15.2",
|
||||||
"resolved": "https://registry.npmjs.org/regenerator-transform/-/regenerator-transform-0.15.2.tgz",
|
"resolved": "https://registry.npmjs.org/regenerator-transform/-/regenerator-transform-0.15.2.tgz",
|
||||||
|
|
@ -23795,6 +24325,7 @@
|
||||||
"version": "4.18.0",
|
"version": "4.18.0",
|
||||||
"resolved": "https://registry.npmjs.org/rollup/-/rollup-4.18.0.tgz",
|
"resolved": "https://registry.npmjs.org/rollup/-/rollup-4.18.0.tgz",
|
||||||
"integrity": "sha512-QmJz14PX3rzbJCN1SG4Xe/bAAX2a6NpCP8ab2vfu2GiUr8AQcr2nCV/oEO3yneFarB67zk8ShlIyWb2LGTb3Sg==",
|
"integrity": "sha512-QmJz14PX3rzbJCN1SG4Xe/bAAX2a6NpCP8ab2vfu2GiUr8AQcr2nCV/oEO3yneFarB67zk8ShlIyWb2LGTb3Sg==",
|
||||||
|
"peer": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@types/estree": "1.0.5"
|
"@types/estree": "1.0.5"
|
||||||
},
|
},
|
||||||
|
|
@ -25076,6 +25607,7 @@
|
||||||
"resolved": "https://registry.npmjs.org/stylelint/-/stylelint-15.10.3.tgz",
|
"resolved": "https://registry.npmjs.org/stylelint/-/stylelint-15.10.3.tgz",
|
||||||
"integrity": "sha512-aBQMMxYvFzJJwkmg+BUUg3YfPyeuCuKo2f+LOw7yYbU8AZMblibwzp9OV4srHVeQldxvSFdz0/Xu8blq2AesiA==",
|
"integrity": "sha512-aBQMMxYvFzJJwkmg+BUUg3YfPyeuCuKo2f+LOw7yYbU8AZMblibwzp9OV4srHVeQldxvSFdz0/Xu8blq2AesiA==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
|
"peer": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@csstools/css-parser-algorithms": "^2.3.1",
|
"@csstools/css-parser-algorithms": "^2.3.1",
|
||||||
"@csstools/css-tokenizer": "^2.2.0",
|
"@csstools/css-tokenizer": "^2.2.0",
|
||||||
|
|
@ -25675,6 +26207,7 @@
|
||||||
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.2.tgz",
|
||||||
"integrity": "sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==",
|
"integrity": "sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
"peer": true,
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=12"
|
"node": ">=12"
|
||||||
},
|
},
|
||||||
|
|
@ -25761,8 +26294,7 @@
|
||||||
"node_modules/tr46": {
|
"node_modules/tr46": {
|
||||||
"version": "0.0.3",
|
"version": "0.0.3",
|
||||||
"resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz",
|
"resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz",
|
||||||
"integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==",
|
"integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw=="
|
||||||
"dev": true
|
|
||||||
},
|
},
|
||||||
"node_modules/trim-lines": {
|
"node_modules/trim-lines": {
|
||||||
"version": "3.0.1",
|
"version": "3.0.1",
|
||||||
|
|
@ -25820,6 +26352,7 @@
|
||||||
"resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.1.tgz",
|
"resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.1.tgz",
|
||||||
"integrity": "sha512-NtVysVPkxxrwFGUUxGYhfux8k78pQB3JqYBXlLRZgdGUqTO5wU/UyHop5p70iEbGhB7q5KmiZiU0Y3KlJrScEw==",
|
"integrity": "sha512-NtVysVPkxxrwFGUUxGYhfux8k78pQB3JqYBXlLRZgdGUqTO5wU/UyHop5p70iEbGhB7q5KmiZiU0Y3KlJrScEw==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
|
"peer": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@cspotcode/source-map-support": "^0.8.0",
|
"@cspotcode/source-map-support": "^0.8.0",
|
||||||
"@tsconfig/node10": "^1.0.7",
|
"@tsconfig/node10": "^1.0.7",
|
||||||
|
|
@ -25930,6 +26463,90 @@
|
||||||
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz",
|
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz",
|
||||||
"integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q=="
|
"integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q=="
|
||||||
},
|
},
|
||||||
|
"node_modules/tsm": {
|
||||||
|
"version": "2.3.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/tsm/-/tsm-2.3.0.tgz",
|
||||||
|
"integrity": "sha512-++0HFnmmR+gMpDtKTnW3XJ4yv9kVGi20n+NfyQWB9qwJvTaIWY9kBmzek2YUQK5APTQ/1DTrXmm4QtFPmW9Rzw==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"esbuild": "^0.15.16"
|
||||||
|
},
|
||||||
|
"bin": {
|
||||||
|
"tsm": "bin.js"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=12"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/tsm/node_modules/@esbuild/android-arm": {
|
||||||
|
"version": "0.15.18",
|
||||||
|
"resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.15.18.tgz",
|
||||||
|
"integrity": "sha512-5GT+kcs2WVGjVs7+boataCkO5Fg0y4kCjzkB5bAip7H4jfnOS3dA6KPiww9W1OEKTKeAcUVhdZGvgI65OXmUnw==",
|
||||||
|
"cpu": [
|
||||||
|
"arm"
|
||||||
|
],
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"android"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">=12"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/tsm/node_modules/@esbuild/linux-loong64": {
|
||||||
|
"version": "0.15.18",
|
||||||
|
"resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.15.18.tgz",
|
||||||
|
"integrity": "sha512-L4jVKS82XVhw2nvzLg/19ClLWg0y27ulRwuP7lcyL6AbUWB5aPglXY3M21mauDQMDfRLs8cQmeT03r/+X3cZYQ==",
|
||||||
|
"cpu": [
|
||||||
|
"loong64"
|
||||||
|
],
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"linux"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">=12"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/tsm/node_modules/esbuild": {
|
||||||
|
"version": "0.15.18",
|
||||||
|
"resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.15.18.tgz",
|
||||||
|
"integrity": "sha512-x/R72SmW3sSFRm5zrrIjAhCeQSAWoni3CmHEqfQrZIQTM3lVCdehdwuIqaOtfC2slvpdlLa62GYoN8SxT23m6Q==",
|
||||||
|
"hasInstallScript": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"bin": {
|
||||||
|
"esbuild": "bin/esbuild"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=12"
|
||||||
|
},
|
||||||
|
"optionalDependencies": {
|
||||||
|
"@esbuild/android-arm": "0.15.18",
|
||||||
|
"@esbuild/linux-loong64": "0.15.18",
|
||||||
|
"esbuild-android-64": "0.15.18",
|
||||||
|
"esbuild-android-arm64": "0.15.18",
|
||||||
|
"esbuild-darwin-64": "0.15.18",
|
||||||
|
"esbuild-darwin-arm64": "0.15.18",
|
||||||
|
"esbuild-freebsd-64": "0.15.18",
|
||||||
|
"esbuild-freebsd-arm64": "0.15.18",
|
||||||
|
"esbuild-linux-32": "0.15.18",
|
||||||
|
"esbuild-linux-64": "0.15.18",
|
||||||
|
"esbuild-linux-arm": "0.15.18",
|
||||||
|
"esbuild-linux-arm64": "0.15.18",
|
||||||
|
"esbuild-linux-mips64le": "0.15.18",
|
||||||
|
"esbuild-linux-ppc64le": "0.15.18",
|
||||||
|
"esbuild-linux-riscv64": "0.15.18",
|
||||||
|
"esbuild-linux-s390x": "0.15.18",
|
||||||
|
"esbuild-netbsd-64": "0.15.18",
|
||||||
|
"esbuild-openbsd-64": "0.15.18",
|
||||||
|
"esbuild-sunos-64": "0.15.18",
|
||||||
|
"esbuild-windows-32": "0.15.18",
|
||||||
|
"esbuild-windows-64": "0.15.18",
|
||||||
|
"esbuild-windows-arm64": "0.15.18"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/tsutils": {
|
"node_modules/tsutils": {
|
||||||
"version": "3.21.0",
|
"version": "3.21.0",
|
||||||
"resolved": "https://registry.npmjs.org/tsutils/-/tsutils-3.21.0.tgz",
|
"resolved": "https://registry.npmjs.org/tsutils/-/tsutils-3.21.0.tgz",
|
||||||
|
|
@ -26074,6 +26691,7 @@
|
||||||
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.2.2.tgz",
|
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.2.2.tgz",
|
||||||
"integrity": "sha512-mI4WrpHsbCIcwT9cF4FZvr80QUeKvsUsUvKDoR+X/7XHQH98xYD8YHZg7ANtz2GtZt/CBq2QJ0thkGJMHfqc1w==",
|
"integrity": "sha512-mI4WrpHsbCIcwT9cF4FZvr80QUeKvsUsUvKDoR+X/7XHQH98xYD8YHZg7ANtz2GtZt/CBq2QJ0thkGJMHfqc1w==",
|
||||||
"devOptional": true,
|
"devOptional": true,
|
||||||
|
"peer": true,
|
||||||
"bin": {
|
"bin": {
|
||||||
"tsc": "bin/tsc",
|
"tsc": "bin/tsc",
|
||||||
"tsserver": "bin/tsserver"
|
"tsserver": "bin/tsserver"
|
||||||
|
|
@ -26571,6 +27189,7 @@
|
||||||
"version": "5.3.1",
|
"version": "5.3.1",
|
||||||
"resolved": "https://registry.npmjs.org/vite/-/vite-5.3.1.tgz",
|
"resolved": "https://registry.npmjs.org/vite/-/vite-5.3.1.tgz",
|
||||||
"integrity": "sha512-XBmSKRLXLxiaPYamLv3/hnP/KXDai1NDexN0FpkTaZXTfycHvkRHoenpgl/fvuK/kPbB6xAgoyiryAhQNxYmAQ==",
|
"integrity": "sha512-XBmSKRLXLxiaPYamLv3/hnP/KXDai1NDexN0FpkTaZXTfycHvkRHoenpgl/fvuK/kPbB6xAgoyiryAhQNxYmAQ==",
|
||||||
|
"peer": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"esbuild": "^0.21.3",
|
"esbuild": "^0.21.3",
|
||||||
"postcss": "^8.4.38",
|
"postcss": "^8.4.38",
|
||||||
|
|
@ -27081,6 +27700,7 @@
|
||||||
"resolved": "https://registry.npmjs.org/vitest/-/vitest-1.6.0.tgz",
|
"resolved": "https://registry.npmjs.org/vitest/-/vitest-1.6.0.tgz",
|
||||||
"integrity": "sha512-H5r/dN06swuFnzNFhq/dnz37bPXnq8xB2xB5JOVk8K09rUtoeNN+LHWkoQ0A/i3hvbUKKcCei9KpbxqHMLhLLA==",
|
"integrity": "sha512-H5r/dN06swuFnzNFhq/dnz37bPXnq8xB2xB5JOVk8K09rUtoeNN+LHWkoQ0A/i3hvbUKKcCei9KpbxqHMLhLLA==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
|
"peer": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@vitest/expect": "1.6.0",
|
"@vitest/expect": "1.6.0",
|
||||||
"@vitest/runner": "1.6.0",
|
"@vitest/runner": "1.6.0",
|
||||||
|
|
@ -27155,6 +27775,15 @@
|
||||||
"url": "https://opencollective.com/vitest"
|
"url": "https://opencollective.com/vitest"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/void-elements": {
|
||||||
|
"version": "3.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/void-elements/-/void-elements-3.1.0.tgz",
|
||||||
|
"integrity": "sha512-Dhxzh5HZuiHQhbvTW9AMetFfBHDMYpo23Uo9btPXgdYP+3T5S+p+jgNy7spra+veYhBP2dCSgxR/i2Y02h5/6w==",
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=0.10.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/watchpack": {
|
"node_modules/watchpack": {
|
||||||
"version": "2.4.1",
|
"version": "2.4.1",
|
||||||
"resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.4.1.tgz",
|
"resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.4.1.tgz",
|
||||||
|
|
@ -27189,8 +27818,7 @@
|
||||||
"node_modules/webidl-conversions": {
|
"node_modules/webidl-conversions": {
|
||||||
"version": "3.0.1",
|
"version": "3.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz",
|
||||||
"integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==",
|
"integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ=="
|
||||||
"dev": true
|
|
||||||
},
|
},
|
||||||
"node_modules/webpack-sources": {
|
"node_modules/webpack-sources": {
|
||||||
"version": "3.2.3",
|
"version": "3.2.3",
|
||||||
|
|
@ -27211,7 +27839,6 @@
|
||||||
"version": "5.0.0",
|
"version": "5.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz",
|
||||||
"integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==",
|
"integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==",
|
||||||
"dev": true,
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"tr46": "~0.0.3",
|
"tr46": "~0.0.3",
|
||||||
"webidl-conversions": "^3.0.0"
|
"webidl-conversions": "^3.0.0"
|
||||||
|
|
@ -27539,6 +28166,7 @@
|
||||||
"resolved": "https://registry.npmjs.org/ajv/-/ajv-8.17.1.tgz",
|
"resolved": "https://registry.npmjs.org/ajv/-/ajv-8.17.1.tgz",
|
||||||
"integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==",
|
"integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
"peer": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"fast-deep-equal": "^3.1.3",
|
"fast-deep-equal": "^3.1.3",
|
||||||
"fast-uri": "^3.0.1",
|
"fast-uri": "^3.0.1",
|
||||||
|
|
@ -27615,6 +28243,7 @@
|
||||||
"resolved": "https://registry.npmjs.org/rollup/-/rollup-2.79.2.tgz",
|
"resolved": "https://registry.npmjs.org/rollup/-/rollup-2.79.2.tgz",
|
||||||
"integrity": "sha512-fS6iqSPZDs3dr/y7Od6y5nha8dW1YnbgtsyotCVvoFGKbERG++CVRFv1meyGDE1SNItQA8BrnCw7ScdAhRJ3XQ==",
|
"integrity": "sha512-fS6iqSPZDs3dr/y7Od6y5nha8dW1YnbgtsyotCVvoFGKbERG++CVRFv1meyGDE1SNItQA8BrnCw7ScdAhRJ3XQ==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
"peer": true,
|
||||||
"bin": {
|
"bin": {
|
||||||
"rollup": "dist/bin/rollup"
|
"rollup": "dist/bin/rollup"
|
||||||
},
|
},
|
||||||
|
|
@ -28145,6 +28774,7 @@
|
||||||
"version": "3.23.8",
|
"version": "3.23.8",
|
||||||
"resolved": "https://registry.npmjs.org/zod/-/zod-3.23.8.tgz",
|
"resolved": "https://registry.npmjs.org/zod/-/zod-3.23.8.tgz",
|
||||||
"integrity": "sha512-XBx9AXhXktjUqnepgTiE5flcKIYWi/rme0Eaj+5Y0lftuGBq+jyRu/md4WnuxqgP1ubdpNCsYEYPxrzVHD8d6g==",
|
"integrity": "sha512-XBx9AXhXktjUqnepgTiE5flcKIYWi/rme0Eaj+5Y0lftuGBq+jyRu/md4WnuxqgP1ubdpNCsYEYPxrzVHD8d6g==",
|
||||||
|
"peer": true,
|
||||||
"funding": {
|
"funding": {
|
||||||
"url": "https://github.com/sponsors/colinhacks"
|
"url": "https://github.com/sponsors/colinhacks"
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -36,15 +36,18 @@
|
||||||
"@types/react-dom": "^18.2.10",
|
"@types/react-dom": "^18.2.10",
|
||||||
"@vite-pwa/astro": "0.5.0",
|
"@vite-pwa/astro": "0.5.0",
|
||||||
"astro": "4.10.3",
|
"astro": "4.10.3",
|
||||||
|
"astro-i18next": "1.0.0-beta.21",
|
||||||
"deepmerge": "4.3.1",
|
"deepmerge": "4.3.1",
|
||||||
"focus-trap-react": "10.2.3",
|
"focus-trap-react": "10.2.3",
|
||||||
"framer-motion": "10.16.4",
|
"framer-motion": "10.16.4",
|
||||||
"howler": "2.2.4",
|
"howler": "2.2.4",
|
||||||
|
"i18next": "25.6.2",
|
||||||
"js-confetti": "0.12.0",
|
"js-confetti": "0.12.0",
|
||||||
"motion": "12.23.24",
|
"motion": "12.23.24",
|
||||||
"react": "^18.2.0",
|
"react": "^18.2.0",
|
||||||
"react-dom": "^18.2.0",
|
"react-dom": "^18.2.0",
|
||||||
"react-hotkeys-hook": "3.2.1",
|
"react-hotkeys-hook": "3.2.1",
|
||||||
|
"react-i18next": "16.3.3",
|
||||||
"react-icons": "4.11.0",
|
"react-icons": "4.11.0",
|
||||||
"react-wrap-balancer": "1.1.0",
|
"react-wrap-balancer": "1.1.0",
|
||||||
"react-youtube": "10.1.0",
|
"react-youtube": "10.1.0",
|
||||||
|
|
|
||||||
119
src/components/about-unified.astro
Normal file
119
src/components/about-unified.astro
Normal file
|
|
@ -0,0 +1,119 @@
|
||||||
|
---
|
||||||
|
import { Container } from '@/components/container';
|
||||||
|
import { count as soundCount } from '@/lib/sounds';
|
||||||
|
import { getTranslation } from '@/data/i18n';
|
||||||
|
|
||||||
|
// Get language from URL path
|
||||||
|
const url = Astro.url;
|
||||||
|
const pathname = url.pathname;
|
||||||
|
const isZhPage = pathname === '/zh' || pathname.startsWith('/zh/');
|
||||||
|
const lang = isZhPage ? 'zh-CN' : 'en';
|
||||||
|
const t = getTranslation(lang);
|
||||||
|
const count = soundCount();
|
||||||
|
---
|
||||||
|
|
||||||
|
<section class="about">
|
||||||
|
<div class="effect"></div>
|
||||||
|
|
||||||
|
<Container tight>
|
||||||
|
</Container>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.about {
|
||||||
|
padding-top: 10px;
|
||||||
|
|
||||||
|
& .effect {
|
||||||
|
position: sticky;
|
||||||
|
top: 0;
|
||||||
|
height: 80px;
|
||||||
|
background: linear-gradient(var(--color-neutral-50), transparent);
|
||||||
|
}
|
||||||
|
|
||||||
|
& .paragraph {
|
||||||
|
padding: 30px 0;
|
||||||
|
background: linear-gradient(
|
||||||
|
transparent,
|
||||||
|
var(--color-neutral-50) 10%,
|
||||||
|
var(--color-neutral-50) 90%,
|
||||||
|
transparent
|
||||||
|
);
|
||||||
|
|
||||||
|
&:last-of-type {
|
||||||
|
padding-bottom: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
& .counter {
|
||||||
|
width: max-content;
|
||||||
|
padding: 6px 16px;
|
||||||
|
margin-bottom: 16px;
|
||||||
|
font-size: var(--font-xsm);
|
||||||
|
color: var(--color-foreground-subtle);
|
||||||
|
background: linear-gradient(var(--color-neutral-100), transparent);
|
||||||
|
border: 1px solid var(--color-neutral-300);
|
||||||
|
border-radius: 20px 20px 20px 8px;
|
||||||
|
|
||||||
|
& span {
|
||||||
|
font-weight: 500;
|
||||||
|
color: var(--color-foreground);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
& .title {
|
||||||
|
margin-bottom: 8px;
|
||||||
|
font-family: var(--font-heading);
|
||||||
|
font-size: var(--font-md);
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
& .body {
|
||||||
|
line-height: 1.6;
|
||||||
|
color: var(--color-foreground-subtle);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.button {
|
||||||
|
position: relative;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
padding: 10px 16px;
|
||||||
|
margin-top: 20px;
|
||||||
|
font-size: var(--font-xsm);
|
||||||
|
font-weight: 500;
|
||||||
|
color: var(--color-foreground);
|
||||||
|
cursor: pointer;
|
||||||
|
background-color: transparent;
|
||||||
|
border: 1px solid var(--color-neutral-200);
|
||||||
|
border-radius: 50px;
|
||||||
|
outline: none;
|
||||||
|
transition: 0.2s;
|
||||||
|
|
||||||
|
&::before {
|
||||||
|
position: absolute;
|
||||||
|
top: -1px;
|
||||||
|
left: 50%;
|
||||||
|
width: 70%;
|
||||||
|
height: 1px;
|
||||||
|
content: '';
|
||||||
|
background: linear-gradient(
|
||||||
|
90deg,
|
||||||
|
transparent,
|
||||||
|
var(--color-neutral-300),
|
||||||
|
transparent
|
||||||
|
);
|
||||||
|
transform: translateX(-50%);
|
||||||
|
}
|
||||||
|
|
||||||
|
&:hover,
|
||||||
|
&:focus-visible {
|
||||||
|
background-color: var(--color-neutral-100);
|
||||||
|
}
|
||||||
|
|
||||||
|
&:focus-visible {
|
||||||
|
outline: 2px solid var(--color-neutral-400);
|
||||||
|
outline-offset: 2px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
173
src/components/about-zh.astro
Normal file
173
src/components/about-zh.astro
Normal file
|
|
@ -0,0 +1,173 @@
|
||||||
|
---
|
||||||
|
import { Container } from '@/components/container';
|
||||||
|
|
||||||
|
import { count as soundCount } from '@/lib/sounds';
|
||||||
|
|
||||||
|
const count = soundCount();
|
||||||
|
---
|
||||||
|
|
||||||
|
<section class="about">
|
||||||
|
<div class="effect"></div>
|
||||||
|
|
||||||
|
<Container tight>
|
||||||
|
<div class="paragraph">
|
||||||
|
<div class="counter">
|
||||||
|
<span>01</span> / <span>04</span>
|
||||||
|
</div>
|
||||||
|
<h2 class="title">免费环境音</h2>
|
||||||
|
<p class="body">
|
||||||
|
渴望从日常繁杂中获得片刻宁静?需要完美的声音环境来提升专注力或帮助入眠?
|
||||||
|
Moodist 就是您的最佳选择——免费开源的环境音生成器!无需订阅注册,使用 Moodist,
|
||||||
|
您可以免费享受舒缓沉浸的音频体验。
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="paragraph">
|
||||||
|
<div class="counter">
|
||||||
|
<span>02</span> / <span>04</span>
|
||||||
|
</div>
|
||||||
|
<h2 class="title">精心挑选的声音</h2>
|
||||||
|
<p class="body">
|
||||||
|
探索包含 <span class="sound-count">{count}</span> 个精心挑选声音的庞大音库。
|
||||||
|
自然爱好者可以在溪流的轻柔潺潺声中、海浪的节拍拍岸声中、或篝火的温暖噼啪声中获得慰藉。
|
||||||
|
城市景观在咖啡馆的轻柔嗡嗡声、火车的节拍咔嗒声、或交通的舒缓白噪声中变得生动。
|
||||||
|
对于寻求更深专注或放松的人,Moodist 提供了专门设计来增强心境的双节拍和色彩噪声。
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="paragraph">
|
||||||
|
<div class="counter">
|
||||||
|
<span>03</span> / <span>04</span>
|
||||||
|
</div>
|
||||||
|
<h2 class="title">创造您的声音景观</h2>
|
||||||
|
<p class="body">
|
||||||
|
Moodist 的美妙之处在于其简洁性和自定义性。没有复杂的菜单或令人困惑的选项——只需选择您喜欢的声音,
|
||||||
|
调整音量平衡,然后点击播放。想要将鸟儿的轻柔啾鸣与雨水的舒缓声音融合?没问题!
|
||||||
|
随心所欲地叠加多个声音,创建个性化的声音绿洲。
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="paragraph">
|
||||||
|
<div class="counter">
|
||||||
|
<span>04</span> / <span>04</span>
|
||||||
|
</div>
|
||||||
|
<h2 class="title">适合每个时刻的声音</h2>
|
||||||
|
<p class="body">
|
||||||
|
无论您是想在漫长一天后放松身心,在工作中提升专注力,还是让自己进入宁静的睡眠,
|
||||||
|
Moodist 都有完美的声音景观等着您。最棒的是什么?它完全免费开源,您可以毫无负担地享受它的好处。
|
||||||
|
今天就开始使用 Moodist,发现您新的宁静和专注天堂吧!
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<button class="button" id="use-moodist">使用 Moodist</button>
|
||||||
|
</Container>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<script lang="ts">
|
||||||
|
const button = document.getElementById('use-moodist');
|
||||||
|
|
||||||
|
button?.addEventListener('click', () => {
|
||||||
|
const app = document.getElementById('app');
|
||||||
|
|
||||||
|
app?.scrollIntoView();
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.about {
|
||||||
|
padding-top: 10px;
|
||||||
|
|
||||||
|
& .effect {
|
||||||
|
position: sticky;
|
||||||
|
top: 0;
|
||||||
|
height: 80px;
|
||||||
|
background: linear-gradient(var(--color-neutral-50), transparent);
|
||||||
|
}
|
||||||
|
|
||||||
|
& .paragraph {
|
||||||
|
padding: 30px 0;
|
||||||
|
background: linear-gradient(
|
||||||
|
transparent,
|
||||||
|
var(--color-neutral-50) 10%,
|
||||||
|
var(--color-neutral-50) 90%,
|
||||||
|
transparent
|
||||||
|
);
|
||||||
|
|
||||||
|
&:last-of-type {
|
||||||
|
padding-bottom: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
& .counter {
|
||||||
|
width: max-content;
|
||||||
|
padding: 6px 16px;
|
||||||
|
margin-bottom: 16px;
|
||||||
|
font-size: var(--font-xsm);
|
||||||
|
color: var(--color-foreground-subtle);
|
||||||
|
background: linear-gradient(var(--color-neutral-100), transparent);
|
||||||
|
border: 1px solid var(--color-neutral-300);
|
||||||
|
border-radius: 20px 20px 20px 8px;
|
||||||
|
|
||||||
|
& span {
|
||||||
|
font-weight: 500;
|
||||||
|
color: var(--color-foreground);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
& .title {
|
||||||
|
margin-bottom: 8px;
|
||||||
|
font-family: var(--font-heading);
|
||||||
|
font-size: var(--font-md);
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
& .body {
|
||||||
|
line-height: 1.6;
|
||||||
|
color: var(--color-foreground-subtle);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.button {
|
||||||
|
position: relative;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
padding: 10px 16px;
|
||||||
|
margin-top: 20px;
|
||||||
|
font-size: var(--font-xsm);
|
||||||
|
font-weight: 500;
|
||||||
|
color: var(--color-foreground);
|
||||||
|
cursor: pointer;
|
||||||
|
background-color: transparent;
|
||||||
|
border: 1px solid var(--color-neutral-200);
|
||||||
|
border-radius: 50px;
|
||||||
|
outline: none;
|
||||||
|
transition: 0.2s;
|
||||||
|
|
||||||
|
&::before {
|
||||||
|
position: absolute;
|
||||||
|
top: -1px;
|
||||||
|
left: 50%;
|
||||||
|
width: 70%;
|
||||||
|
height: 1px;
|
||||||
|
content: '';
|
||||||
|
background: linear-gradient(
|
||||||
|
90deg,
|
||||||
|
transparent,
|
||||||
|
var(--color-neutral-300),
|
||||||
|
transparent
|
||||||
|
);
|
||||||
|
transform: translateX(-50%);
|
||||||
|
}
|
||||||
|
|
||||||
|
&:hover,
|
||||||
|
&:focus-visible {
|
||||||
|
background-color: var(--color-neutral-100);
|
||||||
|
}
|
||||||
|
|
||||||
|
&:focus-visible {
|
||||||
|
outline: 2px solid var(--color-neutral-400);
|
||||||
|
outline-offset: 2px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
@ -4,45 +4,53 @@ import { Container } from '@/components/container';
|
||||||
import { count as soundCount } from '@/lib/sounds';
|
import { count as soundCount } from '@/lib/sounds';
|
||||||
|
|
||||||
const count = soundCount();
|
const count = soundCount();
|
||||||
|
|
||||||
const paragraphs = [
|
|
||||||
{
|
|
||||||
body: 'Craving a calming escape from the daily grind? Do you need the perfect soundscape to boost your focus or lull you into peaceful sleep? Look no further than Moodist, your free and open-source ambient sound generator! Ditch the subscriptions and registrations – with Moodist, you unlock a world of soothing and immersive audio experiences, entirely for free.',
|
|
||||||
title: 'Free Ambient Sounds',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
body: `Dive into an expansive library of ${count} carefully curated sounds. Nature lovers will find solace in the gentle murmur of streams, the rhythmic crash of waves, or the crackling warmth of a campfire. Cityscapes come alive with the soft hum of cafes, the rhythmic clatter of trains, or the calming white noise of traffic. And for those seeking deeper focus or relaxation, Moodist offers binaural beats and color noise designed to enhance your state of mind.`,
|
|
||||||
title: 'Carefully Curated Sounds',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
body: 'The beauty of Moodist lies in its simplicity and customization. No complex menus or confusing options – just choose your desired sounds, adjust the volume balance, and hit play. Want to blend the gentle chirping of birds with the soothing sound of rain? No problem! Layer as many sounds as you like to create your personalized soundscape oasis.',
|
|
||||||
title: 'Create Your Soundscape',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
body: "Whether you're looking to unwind after a long day, enhance your focus during work, or lull yourself into a peaceful sleep, Moodist has the perfect soundscape waiting for you. The best part? It's completely free and open-source, so you can enjoy its benefits without any strings attached. Start using Moodist today and discover your new haven of tranquility and focus!",
|
|
||||||
title: 'Sounds for Every Moment',
|
|
||||||
},
|
|
||||||
];
|
|
||||||
---
|
---
|
||||||
|
|
||||||
<section class="about">
|
<section class="about">
|
||||||
<div class="effect"></div>
|
<div class="effect"></div>
|
||||||
|
|
||||||
<Container tight>
|
<Container tight>
|
||||||
{
|
<div class="paragraph">
|
||||||
paragraphs.map((paragraph, index) => (
|
<div class="counter">
|
||||||
<div class="paragraph">
|
<span>01</span> / <span>04</span>
|
||||||
<div class="counter">
|
</div>
|
||||||
<span>0{index + 1}</span> / 0{paragraphs.length}
|
<h2 class="title" data-i18n="about.freeAmbientSounds.title">Free Ambient Sounds</h2>
|
||||||
</div>
|
<p class="body" data-i18n="about.freeAmbientSounds.body">
|
||||||
|
Craving a calming escape from the daily grind? Do you need the perfect soundscape to boost your focus or lull you into peaceful sleep? Look no further than Moodist, your free and open-source ambient sound generator! Ditch the subscriptions and registrations – with Moodist, you unlock a world of soothing and immersive audio experiences, entirely for free.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
<h2 class="title">{paragraph.title}</h2>
|
<div class="paragraph">
|
||||||
<p class="body">{paragraph.body}</p>
|
<div class="counter">
|
||||||
</div>
|
<span>02</span> / <span>04</span>
|
||||||
))
|
</div>
|
||||||
}
|
<h2 class="title" data-i18n="about.carefullyCuratedSounds.title">Carefully Curated Sounds</h2>
|
||||||
|
<p class="body" data-i18n-count={count} data-i18n="about.carefullyCuratedSounds.body">
|
||||||
|
Dive into an expansive library of <span class="sound-count">{count}</span> carefully curated sounds. Nature lovers will find solace in the gentle murmur of streams, the rhythmic crash of waves, or the crackling warmth of a campfire. Cityscapes come alive with the soft hum of cafes, the rhythmic clatter of trains, or the calming white noise of traffic. And for those seeking deeper focus or relaxation, Moodist offers binaural beats and color noise designed to enhance your state of mind.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
<button class="button" id="use-moodist"> Use Moodist</button>
|
<div class="paragraph">
|
||||||
|
<div class="counter">
|
||||||
|
<span>03</span> / <span>04</span>
|
||||||
|
</div>
|
||||||
|
<h2 class="title" data-i18n="about.createYourSoundscape.title">Create Your Soundscape</h2>
|
||||||
|
<p class="body" data-i18n="about.createYourSoundscape.body">
|
||||||
|
The beauty of Moodist lies in its simplicity and customization. No complex menus or confusing options – just choose your desired sounds, adjust the volume balance, and hit play. Want to blend the gentle chirping of birds with the soothing sound of rain? No problem! Layer as many sounds as you like to create your personalized soundscape oasis.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="paragraph">
|
||||||
|
<div class="counter">
|
||||||
|
<span>04</span> / <span>04</span>
|
||||||
|
</div>
|
||||||
|
<h2 class="title" data-i18n="about.soundsForEveryMoment.title">Sounds for Every Moment</h2>
|
||||||
|
<p class="body" data-i18n="about.soundsForEveryMoment.body">
|
||||||
|
Whether you're looking to unwind after a long day, enhance your focus during work, or lull yourself into a peaceful sleep, Moodist has the perfect soundscape waiting for you. The best part? It's completely free and open-source, so you can enjoy its benefits without any strings attached. Start using Moodist today and discover your new haven of tranquility and focus!
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<button class="button" id="use-moodist" data-i18n="about.useMoodist">Use Moodist</button>
|
||||||
</Container>
|
</Container>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,8 @@ import { BiSolidHeart } from 'react-icons/bi/index';
|
||||||
import { Howler } from 'howler';
|
import { Howler } from 'howler';
|
||||||
|
|
||||||
import { useSoundStore } from '@/stores/sound';
|
import { useSoundStore } from '@/stores/sound';
|
||||||
|
import { useLocalizedSounds } from '@/hooks/useLocalizedSounds';
|
||||||
|
import { useTranslation } from '@/hooks/useTranslation';
|
||||||
|
|
||||||
import { Container } from '@/components/container';
|
import { Container } from '@/components/container';
|
||||||
import { StoreConsumer } from '@/components/store-consumer';
|
import { StoreConsumer } from '@/components/store-consumer';
|
||||||
|
|
@ -21,15 +23,17 @@ import type { Sound } from '@/data/types';
|
||||||
import { subscribe } from '@/lib/event';
|
import { subscribe } from '@/lib/event';
|
||||||
|
|
||||||
export function App() {
|
export function App() {
|
||||||
const categories = useMemo(() => sounds.categories, []);
|
const localizedCategories = useLocalizedSounds();
|
||||||
|
const { t } = useTranslation();
|
||||||
|
|
||||||
|
const categories = useMemo(() => sounds.categories, []);
|
||||||
const favorites = useSoundStore(useShallow(state => state.getFavorites()));
|
const favorites = useSoundStore(useShallow(state => state.getFavorites()));
|
||||||
const pause = useSoundStore(state => state.pause);
|
const pause = useSoundStore(state => state.pause);
|
||||||
const lock = useSoundStore(state => state.lock);
|
const lock = useSoundStore(state => state.lock);
|
||||||
const unlock = useSoundStore(state => state.unlock);
|
const unlock = useSoundStore(state => state.unlock);
|
||||||
|
|
||||||
const favoriteSounds = useMemo(() => {
|
const favoriteSounds = useMemo(() => {
|
||||||
const favoriteSounds = categories
|
const favoriteSounds = localizedCategories
|
||||||
.map(category => category.sounds)
|
.map(category => category.sounds)
|
||||||
.flat()
|
.flat()
|
||||||
.filter(sound => favorites.includes(sound.id));
|
.filter(sound => favorites.includes(sound.id));
|
||||||
|
|
@ -40,7 +44,7 @@ export function App() {
|
||||||
return favorites.map(favorite =>
|
return favorites.map(favorite =>
|
||||||
favoriteSounds.find(sound => sound.id === favorite),
|
favoriteSounds.find(sound => sound.id === favorite),
|
||||||
);
|
);
|
||||||
}, [favorites, categories]);
|
}, [favorites, localizedCategories]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const onChange = () => {
|
const onChange = () => {
|
||||||
|
|
@ -79,12 +83,12 @@ export function App() {
|
||||||
icon: <BiSolidHeart />,
|
icon: <BiSolidHeart />,
|
||||||
id: 'favorites',
|
id: 'favorites',
|
||||||
sounds: favoriteSounds as Array<Sound>,
|
sounds: favoriteSounds as Array<Sound>,
|
||||||
title: 'Favorites',
|
title: t('favorite'),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
return [...favorites, ...categories];
|
return [...favorites, ...localizedCategories];
|
||||||
}, [favoriteSounds, categories]);
|
}, [favoriteSounds, localizedCategories, t]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<SnackbarProvider>
|
<SnackbarProvider>
|
||||||
|
|
|
||||||
|
|
@ -4,11 +4,13 @@ import { useHotkeys } from 'react-hotkeys-hook';
|
||||||
|
|
||||||
import { useSoundStore } from '@/stores/sound';
|
import { useSoundStore } from '@/stores/sound';
|
||||||
import { useSnackbar } from '@/contexts/snackbar';
|
import { useSnackbar } from '@/contexts/snackbar';
|
||||||
|
import { useTranslation } from '@/hooks/useTranslation';
|
||||||
import { cn } from '@/helpers/styles';
|
import { cn } from '@/helpers/styles';
|
||||||
|
|
||||||
import styles from './play.module.css';
|
import styles from './play.module.css';
|
||||||
|
|
||||||
export function PlayButton() {
|
export function PlayButton() {
|
||||||
|
const { t } = useTranslation();
|
||||||
const isPlaying = useSoundStore(state => state.isPlaying);
|
const isPlaying = useSoundStore(state => state.isPlaying);
|
||||||
const pause = useSoundStore(state => state.pause);
|
const pause = useSoundStore(state => state.pause);
|
||||||
const toggle = useSoundStore(state => state.togglePlay);
|
const toggle = useSoundStore(state => state.togglePlay);
|
||||||
|
|
@ -42,14 +44,14 @@ export function PlayButton() {
|
||||||
<span aria-hidden="true">
|
<span aria-hidden="true">
|
||||||
<BiPause />
|
<BiPause />
|
||||||
</span>{' '}
|
</span>{' '}
|
||||||
Pause
|
{t('pause')}
|
||||||
</>
|
</>
|
||||||
) : (
|
) : (
|
||||||
<>
|
<>
|
||||||
<span aria-hidden="true">
|
<span aria-hidden="true">
|
||||||
<BiPlay />
|
<BiPlay />
|
||||||
</span>{' '}
|
</span>{' '}
|
||||||
Play
|
{t('play')}
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
</button>
|
</button>
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,6 @@
|
||||||
import { AnimatePresence } from 'motion/react';
|
import { AnimatePresence } from 'motion/react';
|
||||||
|
|
||||||
import { Category } from './category';
|
import { Category } from './category';
|
||||||
import { Donate } from './donate';
|
|
||||||
|
|
||||||
import type { Categories } from '@/data/types';
|
import type { Categories } from '@/data/types';
|
||||||
|
|
||||||
|
|
@ -12,12 +11,8 @@ interface CategoriesProps {
|
||||||
export function Categories({ categories }: CategoriesProps) {
|
export function Categories({ categories }: CategoriesProps) {
|
||||||
return (
|
return (
|
||||||
<AnimatePresence initial={false}>
|
<AnimatePresence initial={false}>
|
||||||
{categories.map((category, index) => (
|
{categories.map((category) => (
|
||||||
<div key={category.id}>
|
<Category key={category.id} functional={category.id !== 'favorites'} {...category} />
|
||||||
<Category functional={category.id !== 'favorites'} {...category} />
|
|
||||||
|
|
||||||
{index === 3 && <Donate />}
|
|
||||||
</div>
|
|
||||||
))}
|
))}
|
||||||
</AnimatePresence>
|
</AnimatePresence>
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,13 @@
|
||||||
import { FaCoffee } from 'react-icons/fa/index';
|
import { FaCoffee } from 'react-icons/fa/index';
|
||||||
|
|
||||||
import { SpecialButton } from '@/components/special-button';
|
import { SpecialButton } from '@/components/special-button';
|
||||||
|
import { useTranslation } from '@/hooks/useTranslation';
|
||||||
|
|
||||||
import styles from './donate.module.css';
|
import styles from './donate.module.css';
|
||||||
|
|
||||||
export function Donate() {
|
export function Donate() {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={styles.donate}>
|
<div className={styles.donate}>
|
||||||
<div className={styles.iconContainer}>
|
<div className={styles.iconContainer}>
|
||||||
|
|
@ -15,14 +18,14 @@ export function Donate() {
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className={styles.title}>
|
<div className={styles.title}>
|
||||||
<span>Support Me</span>
|
<span>{t('supportMe')}</span>
|
||||||
</div>
|
</div>
|
||||||
<p className={styles.desc}>Help me keep Moodist ad-free.</p>
|
<p className={styles.desc}>{t('helpKeepAdFree')}</p>
|
||||||
<SpecialButton
|
<SpecialButton
|
||||||
className={styles.button}
|
className={styles.button}
|
||||||
href="https://buymeacoffee.com/remvze"
|
href="https://buymeacoffee.com/remvze"
|
||||||
>
|
>
|
||||||
Donate Today
|
{t('donateToday')}
|
||||||
</SpecialButton>
|
</SpecialButton>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -1,17 +1,25 @@
|
||||||
---
|
---
|
||||||
import { Container } from './container';
|
import { Container } from './container';
|
||||||
|
import { getTranslation } from '@/data/i18n';
|
||||||
|
|
||||||
|
// Get language from URL path
|
||||||
|
const url = Astro.url;
|
||||||
|
const pathname = url.pathname;
|
||||||
|
const isZhPage = pathname === '/zh' || pathname.startsWith('/zh/');
|
||||||
|
const lang = isZhPage ? 'zh-CN' : 'en';
|
||||||
|
const t = getTranslation(lang);
|
||||||
---
|
---
|
||||||
|
|
||||||
<Container>
|
<Container>
|
||||||
<section class="wrapper">
|
<section class="wrapper">
|
||||||
<p class="text">
|
<p class="text">
|
||||||
Enjoy Moodist?{' '}
|
{t.enjoyMoodist}{' '}
|
||||||
<a
|
<a
|
||||||
href="https://buymeacoffee.com/remvze"
|
href="https://buymeacoffee.com/remvze"
|
||||||
rel="noreferrer"
|
rel="noreferrer"
|
||||||
target="_blank"
|
target="_blank"
|
||||||
>
|
>
|
||||||
Support with a donation!
|
{t.supportWithDonation}
|
||||||
</a>
|
</a>
|
||||||
</p>
|
</p>
|
||||||
</section>
|
</section>
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,19 @@
|
||||||
---
|
---
|
||||||
import { Container } from './container';
|
import { Container } from './container';
|
||||||
|
import { getTranslation } from '@/data/i18n';
|
||||||
|
|
||||||
|
// Get language from URL path
|
||||||
|
const url = Astro.url;
|
||||||
|
const pathname = url.pathname;
|
||||||
|
const isZhPage = pathname === '/zh' || pathname.startsWith('/zh/');
|
||||||
|
const lang = isZhPage ? 'zh-CN' : 'en';
|
||||||
|
const t = getTranslation(lang);
|
||||||
---
|
---
|
||||||
|
|
||||||
<footer class="footer">
|
<footer class="footer">
|
||||||
<Container>
|
<Container>
|
||||||
<p>
|
<p>
|
||||||
Created by <a href="https://twitter.com/remvze">Maze ✦</a>
|
{t.createdBy} <a href="https://twitter.com/remvze">Maze ✦</a>
|
||||||
</p>
|
</p>
|
||||||
</Container>
|
</Container>
|
||||||
</footer>
|
</footer>
|
||||||
|
|
|
||||||
|
|
@ -2,9 +2,15 @@
|
||||||
import { BsSoundwave } from 'react-icons/bs/index';
|
import { BsSoundwave } from 'react-icons/bs/index';
|
||||||
|
|
||||||
import { Container } from './container';
|
import { Container } from './container';
|
||||||
|
import { getTranslation } from '@/data/i18n';
|
||||||
import { count as soundCount } from '@/lib/sounds';
|
import { count as soundCount } from '@/lib/sounds';
|
||||||
|
|
||||||
|
// Get language from URL path
|
||||||
|
const url = Astro.url;
|
||||||
|
const pathname = url.pathname;
|
||||||
|
const isZhPage = pathname === '/zh' || pathname.startsWith('/zh/');
|
||||||
|
const lang = isZhPage ? 'zh-CN' : 'en';
|
||||||
|
const t = getTranslation(lang);
|
||||||
const count = soundCount();
|
const count = soundCount();
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|
@ -24,15 +30,15 @@ const count = soundCount();
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<h1 class="title">
|
<h1 class="title">
|
||||||
Ambient Sounds<span class="line">For Focus and Calm</span>
|
{t.heroTitle}<span class="line">{t.heroSubtitle}</span>
|
||||||
</h1>
|
</h1>
|
||||||
<h2 class="desc">Free and Open-Source.</h2>
|
<h2 class="desc">{t.heroDescription}</h2>
|
||||||
|
|
||||||
<p class="sounds">
|
<p class="sounds">
|
||||||
<span aria-hidden="true" class="icon">
|
<span aria-hidden="true" class="icon">
|
||||||
<BsSoundwave />
|
<BsSoundwave />
|
||||||
</span>
|
</span>
|
||||||
<span>{count} Sounds</span>
|
<span>{count}{t.soundsCount}</span>
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</Container>
|
</Container>
|
||||||
|
|
|
||||||
1
src/components/language-switcher/index.ts
Normal file
1
src/components/language-switcher/index.ts
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
export * from './language-switcher';
|
||||||
|
|
@ -0,0 +1,43 @@
|
||||||
|
.languageSwitcher {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
padding: 6px 12px;
|
||||||
|
border: 1px solid var(--color-neutral-200);
|
||||||
|
border-radius: 8px;
|
||||||
|
background-color: var(--color-neutral-50);
|
||||||
|
color: var(--color-foreground);
|
||||||
|
font-size: var(--font-xsm);
|
||||||
|
transition: all 0.2s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon {
|
||||||
|
color: var(--color-foreground-subtle);
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.select {
|
||||||
|
background: transparent;
|
||||||
|
border: none;
|
||||||
|
color: var(--color-foreground);
|
||||||
|
font-size: var(--font-xsm);
|
||||||
|
cursor: pointer;
|
||||||
|
outline: none;
|
||||||
|
padding: 2px;
|
||||||
|
border-radius: 4px;
|
||||||
|
min-width: 80px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.select:hover {
|
||||||
|
background-color: var(--color-neutral-100);
|
||||||
|
}
|
||||||
|
|
||||||
|
.select:focus-visible {
|
||||||
|
outline: 2px solid var(--color-neutral-400);
|
||||||
|
outline-offset: 2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.languageSwitcher:hover {
|
||||||
|
background-color: var(--color-neutral-100);
|
||||||
|
border-color: var(--color-neutral-300);
|
||||||
|
}
|
||||||
31
src/components/language-switcher/language-switcher.tsx
Normal file
31
src/components/language-switcher/language-switcher.tsx
Normal file
|
|
@ -0,0 +1,31 @@
|
||||||
|
import { FaGlobe } from 'react-icons/fa/index';
|
||||||
|
import { useTranslation } from '@/hooks/useTranslation';
|
||||||
|
|
||||||
|
import styles from './language-switcher.module.css';
|
||||||
|
|
||||||
|
interface LanguageSwitcherProps {
|
||||||
|
className?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function LanguageSwitcher({ className }: LanguageSwitcherProps) {
|
||||||
|
const { currentLang, changeLanguage, t } = useTranslation();
|
||||||
|
|
||||||
|
const handleLanguageChange = (e: React.ChangeEvent<HTMLSelectElement>) => {
|
||||||
|
changeLanguage(e.target.value);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className={`${styles.languageSwitcher} ${className || ''}`}>
|
||||||
|
<FaGlobe className={styles.icon} />
|
||||||
|
<select
|
||||||
|
value={currentLang}
|
||||||
|
onChange={handleLanguageChange}
|
||||||
|
className={styles.select}
|
||||||
|
aria-label={t('app.language') || 'Select language'}
|
||||||
|
>
|
||||||
|
<option value="en">English</option>
|
||||||
|
<option value="zh-CN">中文</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
@ -51,14 +51,16 @@ export function ShareLinkModal({ onClose, show }: ShareLinkModalProps) {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Modal show={show} onClose={onClose}>
|
<Modal show={show} onClose={onClose}>
|
||||||
<h1 className={styles.heading}>Share your sound selection!</h1>
|
<h1 className={styles.heading} data-i18n="modals.share.title">
|
||||||
<p className={styles.desc}>
|
Share your sound selection!
|
||||||
|
</h1>
|
||||||
|
<p className={styles.desc} data-i18n="modals.share.description">
|
||||||
Copy and send the following link to the person you want to share your
|
Copy and send the following link to the person you want to share your
|
||||||
selection with.
|
selection with.
|
||||||
</p>
|
</p>
|
||||||
<div className={styles.inputWrapper}>
|
<div className={styles.inputWrapper}>
|
||||||
<input readOnly type="text" value={url} />
|
<input readOnly type="text" value={url} />
|
||||||
<button onClick={() => copy(url)}>
|
<button onClick={() => copy(url)} aria-label="Copy link">
|
||||||
{copying ? <IoCheckmark /> : <IoCopyOutline />}
|
{copying ? <IoCheckmark /> : <IoCopyOutline />}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
import { useSoundStore } from '@/stores/sound';
|
import { useSoundStore } from '@/stores/sound';
|
||||||
|
import { useTranslation } from '@/hooks/useTranslation';
|
||||||
|
|
||||||
import styles from './range.module.css';
|
import styles from './range.module.css';
|
||||||
|
|
||||||
|
|
@ -8,6 +9,7 @@ interface RangeProps {
|
||||||
}
|
}
|
||||||
|
|
||||||
export function Range({ id, label }: RangeProps) {
|
export function Range({ id, label }: RangeProps) {
|
||||||
|
const { t } = useTranslation();
|
||||||
const setVolume = useSoundStore(state => state.setVolume);
|
const setVolume = useSoundStore(state => state.setVolume);
|
||||||
const volume = useSoundStore(state => state.sounds[id].volume);
|
const volume = useSoundStore(state => state.sounds[id].volume);
|
||||||
const isSelected = useSoundStore(state => state.sounds[id].isSelected);
|
const isSelected = useSoundStore(state => state.sounds[id].isSelected);
|
||||||
|
|
@ -15,7 +17,7 @@ export function Range({ id, label }: RangeProps) {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<input
|
<input
|
||||||
aria-label={`${label} sound volume`}
|
aria-label={`${label} ${t('volume').toLowerCase()}`}
|
||||||
autoComplete="off"
|
autoComplete="off"
|
||||||
className={styles.range}
|
className={styles.range}
|
||||||
disabled={!isSelected}
|
disabled={!isSelected}
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,7 @@ import { AnimatePresence, motion } from 'motion/react';
|
||||||
|
|
||||||
import { Sound } from './sound';
|
import { Sound } from './sound';
|
||||||
import { useLocalStorage } from '@/hooks/use-local-storage';
|
import { useLocalStorage } from '@/hooks/use-local-storage';
|
||||||
|
import { useTranslation } from '@/hooks/useTranslation';
|
||||||
import { cn } from '@/helpers/styles';
|
import { cn } from '@/helpers/styles';
|
||||||
import { fade, scale, mix } from '@/lib/motion';
|
import { fade, scale, mix } from '@/lib/motion';
|
||||||
|
|
||||||
|
|
@ -17,6 +18,7 @@ interface SoundsProps {
|
||||||
}
|
}
|
||||||
|
|
||||||
export function Sounds({ functional, id, sounds }: SoundsProps) {
|
export function Sounds({ functional, id, sounds }: SoundsProps) {
|
||||||
|
const { t } = useTranslation();
|
||||||
const [showAll, setShowAll] = useLocalStorage(`${id}-show-more`, false);
|
const [showAll, setShowAll] = useLocalStorage(`${id}-show-more`, false);
|
||||||
const [clickedMore, setClickedMore] = useState(false);
|
const [clickedMore, setClickedMore] = useState(false);
|
||||||
|
|
||||||
|
|
@ -106,7 +108,7 @@ export function Sounds({ functional, id, sounds }: SoundsProps) {
|
||||||
onAnimationComplete={() => setIsAnimating(false)}
|
onAnimationComplete={() => setIsAnimating(false)}
|
||||||
onAnimationStart={() => setIsAnimating(true)}
|
onAnimationStart={() => setIsAnimating(true)}
|
||||||
>
|
>
|
||||||
{showAll ? 'Show Less' : 'Show More'}
|
{showAll ? t('showLess') : t('showMore')}
|
||||||
</motion.span>
|
</motion.span>
|
||||||
</AnimatePresence>
|
</AnimatePresence>
|
||||||
</button>
|
</button>
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,7 @@ import styles from './item.module.css';
|
||||||
|
|
||||||
interface ItemProps {
|
interface ItemProps {
|
||||||
active?: boolean;
|
active?: boolean;
|
||||||
|
'data-i18n'?: string;
|
||||||
disabled?: boolean;
|
disabled?: boolean;
|
||||||
href?: string;
|
href?: string;
|
||||||
icon: React.ReactElement;
|
icon: React.ReactElement;
|
||||||
|
|
@ -15,6 +16,7 @@ interface ItemProps {
|
||||||
|
|
||||||
export function Item({
|
export function Item({
|
||||||
active,
|
active,
|
||||||
|
'data-i18n': dataI18n,
|
||||||
disabled = false,
|
disabled = false,
|
||||||
href,
|
href,
|
||||||
icon,
|
icon,
|
||||||
|
|
@ -30,10 +32,11 @@ export function Item({
|
||||||
className={styles.item}
|
className={styles.item}
|
||||||
disabled={disabled}
|
disabled={disabled}
|
||||||
{...(href ? { href, target: '_blank' } : {})}
|
{...(href ? { href, target: '_blank' } : {})}
|
||||||
|
{...(dataI18n ? { 'data-i18n': dataI18n } : {})}
|
||||||
aria-label={label}
|
aria-label={label}
|
||||||
>
|
>
|
||||||
<span className={styles.label}>
|
<span className={styles.label}>
|
||||||
<span className={styles.icon}>{icon}</span> {label}
|
<span className={styles.icon}>{icon}</span> <span data-i18n={dataI18n}>{label}</span>
|
||||||
{active && <div className={styles.active} />}
|
{active && <div className={styles.active} />}
|
||||||
</span>
|
</span>
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,13 +1,16 @@
|
||||||
import { FaHeadphonesAlt } from 'react-icons/fa/index';
|
import { FaHeadphonesAlt } from 'react-icons/fa/index';
|
||||||
|
|
||||||
import { Item } from '../item';
|
import { Item } from '../item';
|
||||||
|
import { useTranslation } from '@/hooks/useTranslation';
|
||||||
|
|
||||||
interface BinauralProps {
|
interface BinauralProps {
|
||||||
open: () => void;
|
open: () => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function Binaural({ open }: BinauralProps) {
|
export function Binaural({ open }: BinauralProps) {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Item icon={<FaHeadphonesAlt />} label="Binaural Beats" onClick={open} />
|
<Item icon={<FaHeadphonesAlt />} label={t('binauralBeats')} onClick={open} />
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,16 +1,19 @@
|
||||||
import { IoMdFlower } from 'react-icons/io/index';
|
import { IoMdFlower } from 'react-icons/io/index';
|
||||||
|
|
||||||
import { Item } from '../item';
|
import { Item } from '../item';
|
||||||
|
import { useTranslation } from '@/hooks/useTranslation';
|
||||||
|
|
||||||
interface BreathingExerciseProps {
|
interface BreathingExerciseProps {
|
||||||
open: () => void;
|
open: () => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function BreathingExercise({ open }: BreathingExerciseProps) {
|
export function BreathingExercise({ open }: BreathingExerciseProps) {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Item
|
<Item
|
||||||
icon={<IoMdFlower />}
|
icon={<IoMdFlower />}
|
||||||
label="Breathing Exercise"
|
label={t('breathingExercise')}
|
||||||
shortcut="Shift + B"
|
shortcut="Shift + B"
|
||||||
onClick={open}
|
onClick={open}
|
||||||
/>
|
/>
|
||||||
|
|
|
||||||
|
|
@ -1,16 +1,19 @@
|
||||||
import { MdOutlineTimer } from 'react-icons/md/index';
|
import { MdOutlineTimer } from 'react-icons/md/index';
|
||||||
|
|
||||||
import { Item } from '../item';
|
import { Item } from '../item';
|
||||||
|
import { useTranslation } from '@/hooks/useTranslation';
|
||||||
|
|
||||||
interface CountdownProps {
|
interface CountdownProps {
|
||||||
open: () => void;
|
open: () => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function Countdown({ open }: CountdownProps) {
|
export function Countdown({ open }: CountdownProps) {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Item
|
<Item
|
||||||
icon={<MdOutlineTimer />}
|
icon={<MdOutlineTimer />}
|
||||||
label="Countdown Timer"
|
label={t('countdownTimer')}
|
||||||
shortcut="Shift + C"
|
shortcut="Shift + C"
|
||||||
onClick={open}
|
onClick={open}
|
||||||
/>
|
/>
|
||||||
|
|
|
||||||
|
|
@ -1,13 +1,16 @@
|
||||||
import { SiBuymeacoffee } from 'react-icons/si/index';
|
import { SiBuymeacoffee } from 'react-icons/si/index';
|
||||||
|
|
||||||
import { Item } from '../item';
|
import { Item } from '../item';
|
||||||
|
import { useTranslation } from '@/hooks/useTranslation';
|
||||||
|
|
||||||
export function Donate() {
|
export function Donate() {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Item
|
<Item
|
||||||
href="https://buymeacoffee.com/remvze"
|
href="https://buymeacoffee.com/remvze"
|
||||||
icon={<SiBuymeacoffee />}
|
icon={<SiBuymeacoffee />}
|
||||||
label="Buy Me a Coffee"
|
label={t('buyMeACoffee')}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,14 @@
|
||||||
import { TbWaveSine } from 'react-icons/tb/index';
|
import { TbWaveSine } from 'react-icons/tb/index';
|
||||||
|
|
||||||
import { Item } from '../item';
|
import { Item } from '../item';
|
||||||
|
import { useTranslation } from '@/hooks/useTranslation';
|
||||||
|
|
||||||
interface IsochronicProps {
|
interface IsochronicProps {
|
||||||
open: () => void;
|
open: () => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function Isochronic({ open }: IsochronicProps) {
|
export function Isochronic({ open }: IsochronicProps) {
|
||||||
return <Item icon={<TbWaveSine />} label="Isochronic Tones" onClick={open} />;
|
const { t } = useTranslation();
|
||||||
|
|
||||||
|
return <Item icon={<TbWaveSine />} label={t('isochronicTones')} onClick={open} />;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,16 +1,19 @@
|
||||||
import { IoIosMusicalNote } from 'react-icons/io/index';
|
import { IoIosMusicalNote } from 'react-icons/io/index';
|
||||||
|
|
||||||
import { Item } from '../item';
|
import { Item } from '../item';
|
||||||
|
import { useTranslation } from '@/hooks/useTranslation';
|
||||||
|
|
||||||
interface LofiProps {
|
interface LofiProps {
|
||||||
open: () => void;
|
open: () => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function Lofi({ open }: LofiProps) {
|
export function Lofi({ open }: LofiProps) {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Item
|
<Item
|
||||||
icon={<IoIosMusicalNote />}
|
icon={<IoIosMusicalNote />}
|
||||||
label="Lofi Music Player"
|
label={t('lofiMusicPlayer')}
|
||||||
onClick={open}
|
onClick={open}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -3,19 +3,21 @@ import { MdNotes } from 'react-icons/md/index';
|
||||||
import { Item } from '../item';
|
import { Item } from '../item';
|
||||||
|
|
||||||
import { useNoteStore } from '@/stores/note';
|
import { useNoteStore } from '@/stores/note';
|
||||||
|
import { useTranslation } from '@/hooks/useTranslation';
|
||||||
|
|
||||||
interface NotepadProps {
|
interface NotepadProps {
|
||||||
open: () => void;
|
open: () => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function Notepad({ open }: NotepadProps) {
|
export function Notepad({ open }: NotepadProps) {
|
||||||
|
const { t } = useTranslation();
|
||||||
const note = useNoteStore(state => state.note);
|
const note = useNoteStore(state => state.note);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Item
|
<Item
|
||||||
active={!!note.length}
|
active={!!note.length}
|
||||||
icon={<MdNotes />}
|
icon={<MdNotes />}
|
||||||
label="Notepad"
|
label={t('notepad')}
|
||||||
shortcut="Shift + N"
|
shortcut="Shift + N"
|
||||||
onClick={open}
|
onClick={open}
|
||||||
/>
|
/>
|
||||||
|
|
|
||||||
|
|
@ -3,19 +3,21 @@ import { MdOutlineAvTimer } from 'react-icons/md/index';
|
||||||
import { Item } from '../item';
|
import { Item } from '../item';
|
||||||
|
|
||||||
import { usePomodoroStore } from '@/stores/pomodoro';
|
import { usePomodoroStore } from '@/stores/pomodoro';
|
||||||
|
import { useTranslation } from '@/hooks/useTranslation';
|
||||||
|
|
||||||
interface PomodoroProps {
|
interface PomodoroProps {
|
||||||
open: () => void;
|
open: () => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function Pomodoro({ open }: PomodoroProps) {
|
export function Pomodoro({ open }: PomodoroProps) {
|
||||||
|
const { t } = useTranslation();
|
||||||
const running = usePomodoroStore(state => state.running);
|
const running = usePomodoroStore(state => state.running);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Item
|
<Item
|
||||||
active={running}
|
active={running}
|
||||||
icon={<MdOutlineAvTimer />}
|
icon={<MdOutlineAvTimer />}
|
||||||
label="Pomodoro"
|
label={t('pomodoro')}
|
||||||
shortcut="Shift + P"
|
shortcut="Shift + P"
|
||||||
onClick={open}
|
onClick={open}
|
||||||
/>
|
/>
|
||||||
|
|
|
||||||
|
|
@ -1,16 +1,20 @@
|
||||||
import { RiPlayListFill } from 'react-icons/ri/index';
|
import { RiPlayListFill } from 'react-icons/ri/index';
|
||||||
|
|
||||||
import { Item } from '../item';
|
import { Item } from '../item';
|
||||||
|
import { useTranslation } from '@/hooks/useTranslation';
|
||||||
|
|
||||||
interface PresetsProps {
|
interface PresetsProps {
|
||||||
open: () => void;
|
open: () => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function Presets({ open }: PresetsProps) {
|
export function Presets({ open }: PresetsProps) {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Item
|
<Item
|
||||||
icon={<RiPlayListFill />}
|
icon={<RiPlayListFill />}
|
||||||
label="Your Presets"
|
label={t('presets')}
|
||||||
|
data-i18n="navigation.presets"
|
||||||
shortcut="Shift + Alt + P"
|
shortcut="Shift + Alt + P"
|
||||||
onClick={open}
|
onClick={open}
|
||||||
/>
|
/>
|
||||||
|
|
|
||||||
|
|
@ -3,19 +3,21 @@ import { IoShareSocialSharp } from 'react-icons/io5/index';
|
||||||
import { Item } from '../item';
|
import { Item } from '../item';
|
||||||
|
|
||||||
import { useSoundStore } from '@/stores/sound';
|
import { useSoundStore } from '@/stores/sound';
|
||||||
|
import { useTranslation } from '@/hooks/useTranslation';
|
||||||
|
|
||||||
interface ShareProps {
|
interface ShareProps {
|
||||||
open: () => void;
|
open: () => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function Share({ open }: ShareProps) {
|
export function Share({ open }: ShareProps) {
|
||||||
|
const { t } = useTranslation();
|
||||||
const noSelected = useSoundStore(state => state.noSelected());
|
const noSelected = useSoundStore(state => state.noSelected());
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Item
|
<Item
|
||||||
disabled={noSelected}
|
disabled={noSelected}
|
||||||
icon={<IoShareSocialSharp />}
|
icon={<IoShareSocialSharp />}
|
||||||
label="Share Sounds"
|
label={t('share')}
|
||||||
shortcut="Shift + S"
|
shortcut="Shift + S"
|
||||||
onClick={open}
|
onClick={open}
|
||||||
/>
|
/>
|
||||||
|
|
|
||||||
|
|
@ -1,16 +1,19 @@
|
||||||
import { MdKeyboardCommandKey } from 'react-icons/md/index';
|
import { MdKeyboardCommandKey } from 'react-icons/md/index';
|
||||||
|
|
||||||
import { Item } from '../item';
|
import { Item } from '../item';
|
||||||
|
import { useTranslation } from '@/hooks/useTranslation';
|
||||||
|
|
||||||
interface ShortcutsProps {
|
interface ShortcutsProps {
|
||||||
open: () => void;
|
open: () => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function Shortcuts({ open }: ShortcutsProps) {
|
export function Shortcuts({ open }: ShortcutsProps) {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Item
|
<Item
|
||||||
icon={<MdKeyboardCommandKey />}
|
icon={<MdKeyboardCommandKey />}
|
||||||
label="Shortcuts"
|
label={t('shortcuts')}
|
||||||
shortcut="Shift + H"
|
shortcut="Shift + H"
|
||||||
onClick={open}
|
onClick={open}
|
||||||
/>
|
/>
|
||||||
|
|
|
||||||
|
|
@ -3,8 +3,10 @@ import { BiShuffle } from 'react-icons/bi/index';
|
||||||
import { useSoundStore } from '@/stores/sound';
|
import { useSoundStore } from '@/stores/sound';
|
||||||
|
|
||||||
import { Item } from '../item';
|
import { Item } from '../item';
|
||||||
|
import { useTranslation } from '@/hooks/useTranslation';
|
||||||
|
|
||||||
export function Shuffle() {
|
export function Shuffle() {
|
||||||
|
const { t } = useTranslation();
|
||||||
const shuffle = useSoundStore(state => state.shuffle);
|
const shuffle = useSoundStore(state => state.shuffle);
|
||||||
const locked = useSoundStore(state => state.locked);
|
const locked = useSoundStore(state => state.locked);
|
||||||
|
|
||||||
|
|
@ -12,7 +14,7 @@ export function Shuffle() {
|
||||||
<Item
|
<Item
|
||||||
disabled={locked}
|
disabled={locked}
|
||||||
icon={<BiShuffle />}
|
icon={<BiShuffle />}
|
||||||
label="Shuffle Sounds"
|
label={t('shuffleSounds')}
|
||||||
onClick={shuffle}
|
onClick={shuffle}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -2,19 +2,21 @@ import { IoMoonSharp } from 'react-icons/io5/index';
|
||||||
|
|
||||||
import { useSleepTimerStore } from '@/stores/sleep-timer';
|
import { useSleepTimerStore } from '@/stores/sleep-timer';
|
||||||
import { Item } from '../item';
|
import { Item } from '../item';
|
||||||
|
import { useTranslation } from '@/hooks/useTranslation';
|
||||||
|
|
||||||
interface SleepTimerProps {
|
interface SleepTimerProps {
|
||||||
open: () => void;
|
open: () => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function SleepTimer({ open }: SleepTimerProps) {
|
export function SleepTimer({ open }: SleepTimerProps) {
|
||||||
|
const { t } = useTranslation();
|
||||||
const active = useSleepTimerStore(state => state.active);
|
const active = useSleepTimerStore(state => state.active);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Item
|
<Item
|
||||||
active={active}
|
active={active}
|
||||||
icon={<IoMoonSharp />}
|
icon={<IoMoonSharp />}
|
||||||
label="Sleep Timer"
|
label={t('sleepTimer')}
|
||||||
shortcut="Shift + Alt + T"
|
shortcut="Shift + Alt + T"
|
||||||
onClick={open}
|
onClick={open}
|
||||||
/>
|
/>
|
||||||
|
|
|
||||||
|
|
@ -1,13 +1,16 @@
|
||||||
import { LuGithub } from 'react-icons/lu/index';
|
import { LuGithub } from 'react-icons/lu/index';
|
||||||
|
|
||||||
import { Item } from '../item';
|
import { Item } from '../item';
|
||||||
|
import { useTranslation } from '@/hooks/useTranslation';
|
||||||
|
|
||||||
export function Source() {
|
export function Source() {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Item
|
<Item
|
||||||
href="https://github.com/remvze/moodist"
|
href="https://github.com/remvze/moodist"
|
||||||
icon={<LuGithub />}
|
icon={<LuGithub />}
|
||||||
label="Source Code"
|
label={t('sourceCode')}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,16 +1,19 @@
|
||||||
import { MdTaskAlt } from 'react-icons/md/index';
|
import { MdTaskAlt } from 'react-icons/md/index';
|
||||||
|
|
||||||
import { Item } from '../item';
|
import { Item } from '../item';
|
||||||
|
import { useTranslation } from '@/hooks/useTranslation';
|
||||||
|
|
||||||
interface TodoProps {
|
interface TodoProps {
|
||||||
open: () => void;
|
open: () => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function Todo({ open }: TodoProps) {
|
export function Todo({ open }: TodoProps) {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Item
|
<Item
|
||||||
icon={<MdTaskAlt />}
|
icon={<MdTaskAlt />}
|
||||||
label="Todo Checklist"
|
label={t('todoChecklist')}
|
||||||
shortcut="Shift + T"
|
shortcut="Shift + T"
|
||||||
onClick={open}
|
onClick={open}
|
||||||
/>
|
/>
|
||||||
|
|
|
||||||
|
|
@ -32,6 +32,7 @@ import { IsochronicModal } from '@/components/modals/isochronic';
|
||||||
import { LofiModal } from '@/components/modals/lofi';
|
import { LofiModal } from '@/components/modals/lofi';
|
||||||
import { Pomodoro, Notepad, Todo, Countdown } from '@/components/toolbox';
|
import { Pomodoro, Notepad, Todo, Countdown } from '@/components/toolbox';
|
||||||
import { Slider } from '@/components/slider';
|
import { Slider } from '@/components/slider';
|
||||||
|
import { useTranslation } from '@/hooks/useTranslation';
|
||||||
|
|
||||||
import { fade, mix, slideY } from '@/lib/motion';
|
import { fade, mix, slideY } from '@/lib/motion';
|
||||||
import { useSoundStore } from '@/stores/sound';
|
import { useSoundStore } from '@/stores/sound';
|
||||||
|
|
@ -41,6 +42,7 @@ import { useCloseListener } from '@/hooks/use-close-listener';
|
||||||
import { closeModals } from '@/lib/modal';
|
import { closeModals } from '@/lib/modal';
|
||||||
|
|
||||||
export function Menu() {
|
export function Menu() {
|
||||||
|
const { t } = useTranslation();
|
||||||
const [isOpen, setIsOpen] = useState(false);
|
const [isOpen, setIsOpen] = useState(false);
|
||||||
|
|
||||||
const noSelected = useSoundStore(state => state.noSelected());
|
const noSelected = useSoundStore(state => state.noSelected());
|
||||||
|
|
@ -103,7 +105,7 @@ export function Menu() {
|
||||||
<div className={styles.wrapper}>
|
<div className={styles.wrapper}>
|
||||||
<DropdownMenu.Root open={isOpen} onOpenChange={o => setIsOpen(o)}>
|
<DropdownMenu.Root open={isOpen} onOpenChange={o => setIsOpen(o)}>
|
||||||
<DropdownMenu.Trigger asChild>
|
<DropdownMenu.Trigger asChild>
|
||||||
<button aria-label="Menu" className={styles.menuButton}>
|
<button aria-label={t('menu')} className={styles.menuButton}>
|
||||||
{isOpen ? <IoClose /> : <IoMenu />}
|
{isOpen ? <IoClose /> : <IoMenu />}
|
||||||
</button>
|
</button>
|
||||||
</DropdownMenu.Trigger>
|
</DropdownMenu.Trigger>
|
||||||
|
|
@ -147,7 +149,7 @@ export function Menu() {
|
||||||
<Divider />
|
<Divider />
|
||||||
|
|
||||||
<div className={styles.globalVolume}>
|
<div className={styles.globalVolume}>
|
||||||
<label htmlFor="global-volume">Global Volume</label>
|
<label htmlFor="global-volume">{t('globalVolume')}</label>
|
||||||
<Slider
|
<Slider
|
||||||
max={100}
|
max={100}
|
||||||
min={0}
|
min={0}
|
||||||
|
|
|
||||||
522
src/data/i18n.ts
Normal file
522
src/data/i18n.ts
Normal file
|
|
@ -0,0 +1,522 @@
|
||||||
|
export interface Translations {
|
||||||
|
// Navigation & UI
|
||||||
|
presets: string;
|
||||||
|
share: string;
|
||||||
|
useMoodist: string;
|
||||||
|
|
||||||
|
// Hero section
|
||||||
|
heroTitle: string;
|
||||||
|
heroSubtitle: string;
|
||||||
|
heroDescription: string;
|
||||||
|
soundsCount: string;
|
||||||
|
|
||||||
|
// About section
|
||||||
|
freeAmbientSounds: {
|
||||||
|
title: string;
|
||||||
|
body: string;
|
||||||
|
};
|
||||||
|
carefullyCuratedSounds: {
|
||||||
|
title: string;
|
||||||
|
body: string;
|
||||||
|
};
|
||||||
|
createYourSoundscape: {
|
||||||
|
title: string;
|
||||||
|
body: string;
|
||||||
|
};
|
||||||
|
soundsForEveryMoment: {
|
||||||
|
title: string;
|
||||||
|
body: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Categories
|
||||||
|
categories: Record<string, string>;
|
||||||
|
|
||||||
|
// Sounds
|
||||||
|
sounds: Record<string, Record<string, string>>;
|
||||||
|
|
||||||
|
// Common
|
||||||
|
play: string;
|
||||||
|
pause: string;
|
||||||
|
favorite: string;
|
||||||
|
volume: string;
|
||||||
|
|
||||||
|
// Support & Donate
|
||||||
|
supportMe: string;
|
||||||
|
helpKeepAdFree: string;
|
||||||
|
donateToday: string;
|
||||||
|
buyMeACoffee: string;
|
||||||
|
createdBy: string;
|
||||||
|
enjoyMoodist: string;
|
||||||
|
supportWithDonation: string;
|
||||||
|
|
||||||
|
// UI Actions
|
||||||
|
showMore: string;
|
||||||
|
showLess: string;
|
||||||
|
|
||||||
|
// Settings
|
||||||
|
globalVolume: string;
|
||||||
|
menu: string;
|
||||||
|
|
||||||
|
// Menu Items
|
||||||
|
breathingExercise: string;
|
||||||
|
countdownTimer: string;
|
||||||
|
sleepTimer: string;
|
||||||
|
pomodoro: string;
|
||||||
|
notepad: string;
|
||||||
|
todoChecklist: string;
|
||||||
|
lofiMusicPlayer: string;
|
||||||
|
binauralBeats: string;
|
||||||
|
isochronicTones: string;
|
||||||
|
shortcuts: string;
|
||||||
|
shuffleSounds: string;
|
||||||
|
sourceCode: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const translations: Record<string, Translations> = {
|
||||||
|
en: {
|
||||||
|
// Navigation & UI
|
||||||
|
presets: 'Your Presets',
|
||||||
|
share: 'Share',
|
||||||
|
useMoodist: 'Use Moodist',
|
||||||
|
|
||||||
|
// Hero section
|
||||||
|
heroTitle: 'Ambient Sounds',
|
||||||
|
heroSubtitle: 'For Focus and Calm',
|
||||||
|
heroDescription: 'Free and Open-Source.',
|
||||||
|
soundsCount: 'Sounds',
|
||||||
|
|
||||||
|
// About section
|
||||||
|
freeAmbientSounds: {
|
||||||
|
title: 'Free Ambient Sounds',
|
||||||
|
body: 'Craving a calming escape from the daily grind? Do you need the perfect soundscape to boost your focus or lull you into peaceful sleep? Look no further than Moodist, your free and open-source ambient sound generator! Ditch the subscriptions and registrations – with Moodist, you unlock a world of soothing and immersive audio experiences, entirely for free.'
|
||||||
|
},
|
||||||
|
carefullyCuratedSounds: {
|
||||||
|
title: 'Carefully Curated Sounds',
|
||||||
|
body: 'Dive into an expansive library of {{count}} carefully curated sounds. Nature lovers will find solace in the gentle murmur of streams, the rhythmic crash of waves, or the crackling warmth of a campfire. Cityscapes come alive with the soft hum of cafes, the rhythmic clatter of trains, or the calming white noise of traffic. And for those seeking deeper focus or relaxation, Moodist offers binaural beats and color noise designed to enhance your state of mind.'
|
||||||
|
},
|
||||||
|
createYourSoundscape: {
|
||||||
|
title: 'Create Your Soundscape',
|
||||||
|
body: 'The beauty of Moodist lies in its simplicity and customization. No complex menus or confusing options – just choose your desired sounds, adjust the volume balance, and hit play. Want to blend the gentle chirping of birds with the soothing sound of rain? No problem! Layer as many sounds as you like to create your personalized soundscape oasis.'
|
||||||
|
},
|
||||||
|
soundsForEveryMoment: {
|
||||||
|
title: 'Sounds for Every Moment',
|
||||||
|
body: "Whether you're looking to unwind after a long day, enhance your focus during work, or lull yourself into a peaceful sleep, Moodist has the perfect soundscape waiting for you. The best part? It's completely free and open-source, so you can enjoy its benefits without any strings attached. Start using Moodist today and discover your new haven of tranquility and focus!"
|
||||||
|
},
|
||||||
|
|
||||||
|
// Categories
|
||||||
|
categories: {
|
||||||
|
nature: 'Nature',
|
||||||
|
rain: 'Rain',
|
||||||
|
animals: 'Animals',
|
||||||
|
urban: 'Urban',
|
||||||
|
places: 'Places',
|
||||||
|
transport: 'Transport',
|
||||||
|
things: 'Things',
|
||||||
|
noise: 'Noise'
|
||||||
|
},
|
||||||
|
|
||||||
|
// Sounds
|
||||||
|
sounds: {
|
||||||
|
nature: {
|
||||||
|
river: 'River',
|
||||||
|
waves: 'Waves',
|
||||||
|
campfire: 'Campfire',
|
||||||
|
wind: 'Wind',
|
||||||
|
howlingWind: 'Howling Wind',
|
||||||
|
windInTrees: 'Wind in Trees',
|
||||||
|
waterfall: 'Waterfall',
|
||||||
|
walkInSnow: 'Walk in Snow',
|
||||||
|
walkOnLeaves: 'Walk on Leaves',
|
||||||
|
walkOnGravel: 'Walk on Gravel',
|
||||||
|
droplets: 'Droplets',
|
||||||
|
jungle: 'Jungle'
|
||||||
|
},
|
||||||
|
rain: {
|
||||||
|
lightRain: 'Light Rain',
|
||||||
|
moderateRain: 'Moderate Rain',
|
||||||
|
heavyRain: 'Heavy Rain',
|
||||||
|
storm: 'Storm',
|
||||||
|
thunder: 'Thunder',
|
||||||
|
distantStorm: 'Distant Storm',
|
||||||
|
fire: 'Fire',
|
||||||
|
oceanWaves: 'Ocean Waves',
|
||||||
|
rainOnLeaves: 'Rain on Leaves',
|
||||||
|
rainOnPavement: 'Rain on Pavement',
|
||||||
|
rainOnWindow: 'Rain on Window',
|
||||||
|
rainOnUmbrella: 'Rain on Umbrella',
|
||||||
|
rainOnTent: 'Rain on Tent',
|
||||||
|
insideRain: 'Inside the Rain',
|
||||||
|
carRain: 'Car Rain'
|
||||||
|
},
|
||||||
|
animals: {
|
||||||
|
birds: 'Birds',
|
||||||
|
seagulls: 'Seagulls',
|
||||||
|
crickets: 'Crickets',
|
||||||
|
wolves: 'Wolves',
|
||||||
|
owl: 'Owl',
|
||||||
|
frogs: 'Frogs',
|
||||||
|
dogs: 'Dogs',
|
||||||
|
horses: 'Horses',
|
||||||
|
cats: 'Cats',
|
||||||
|
crows: 'Crows',
|
||||||
|
whale: 'Whale',
|
||||||
|
beehive: 'Beehive',
|
||||||
|
woodpecker: 'Woodpecker',
|
||||||
|
chickens: 'Chickens',
|
||||||
|
cows: 'Cows',
|
||||||
|
sheep: 'Sheep',
|
||||||
|
rooster: 'Rooster',
|
||||||
|
birdsMorning: 'Birds in Morning',
|
||||||
|
birdsEvening: 'Birds in Evening'
|
||||||
|
},
|
||||||
|
urban: {
|
||||||
|
cityStreet: 'City Street',
|
||||||
|
traffic: 'Traffic',
|
||||||
|
highway: 'Highway',
|
||||||
|
road: 'Road',
|
||||||
|
ambulanceSiren: 'Ambulance Siren',
|
||||||
|
busyStreet: 'Busy Street',
|
||||||
|
crowd: 'Crowd',
|
||||||
|
fireworks: 'Fireworks'
|
||||||
|
},
|
||||||
|
places: {
|
||||||
|
forest: 'Forest',
|
||||||
|
beach: 'Beach',
|
||||||
|
park: 'Park',
|
||||||
|
mountain: 'Mountain',
|
||||||
|
desert: 'Desert',
|
||||||
|
cave: 'Cave',
|
||||||
|
meadow: 'Meadow',
|
||||||
|
lake: 'Lake',
|
||||||
|
campsite: 'Campsite',
|
||||||
|
temple: 'Temple',
|
||||||
|
airport: 'Airport',
|
||||||
|
church: 'Church',
|
||||||
|
underwater: 'Underwater',
|
||||||
|
crowdedBar: 'Crowded Bar',
|
||||||
|
nightVillage: 'Night Village',
|
||||||
|
carousel: 'Carousel',
|
||||||
|
laboratory: 'Laboratory',
|
||||||
|
laundryRoom: 'Laundry Room',
|
||||||
|
subwayStation: 'Subway Station',
|
||||||
|
cafe: 'Cafe',
|
||||||
|
constructionSite: 'Construction Site',
|
||||||
|
office: 'Office',
|
||||||
|
supermarket: 'Supermarket',
|
||||||
|
restaurant: 'Restaurant',
|
||||||
|
library: 'Library'
|
||||||
|
},
|
||||||
|
transport: {
|
||||||
|
car: 'Car',
|
||||||
|
bus: 'Bus',
|
||||||
|
train: 'Train',
|
||||||
|
subway: 'Subway',
|
||||||
|
airplane: 'Airplane',
|
||||||
|
boat: 'Boat',
|
||||||
|
bicycle: 'Bicycle',
|
||||||
|
motorcycle: 'Motorcycle',
|
||||||
|
helicopter: 'Helicopter',
|
||||||
|
steamTrain: 'Steam Train',
|
||||||
|
insideTrain: 'Inside a Train',
|
||||||
|
submarine: 'Submarine',
|
||||||
|
sailboat: 'Sailboat',
|
||||||
|
rowingBoat: 'Rowing Boat'
|
||||||
|
},
|
||||||
|
things: {
|
||||||
|
fan: 'Fan',
|
||||||
|
clock: 'Clock',
|
||||||
|
typewriter: 'Typewriter',
|
||||||
|
keyboard: 'Keyboard',
|
||||||
|
printer: 'Printer',
|
||||||
|
refrigerator: 'Refrigerator',
|
||||||
|
washingMachine: 'Washing Machine',
|
||||||
|
vacuum: 'Vacuum',
|
||||||
|
airConditioner: 'Air Conditioner',
|
||||||
|
microwave: 'Microwave',
|
||||||
|
paper: 'Paper',
|
||||||
|
windChimes: 'Wind Chimes',
|
||||||
|
singingBowl: 'Singing Bowl',
|
||||||
|
ceilingFan: 'Ceiling Fan',
|
||||||
|
dryer: 'Dryer',
|
||||||
|
slideProjector: 'Slide Projector',
|
||||||
|
boilingWater: 'Boiling Water',
|
||||||
|
bubbles: 'Bubbles',
|
||||||
|
tuningRadio: 'Tuning Radio',
|
||||||
|
morseCode: 'Morse Code',
|
||||||
|
vinylEffect: 'Vinyl Effect',
|
||||||
|
windshieldWipers: 'Windshield Wipers'
|
||||||
|
},
|
||||||
|
noise: {
|
||||||
|
whiteNoise: 'White Noise',
|
||||||
|
pinkNoise: 'Pink Noise',
|
||||||
|
brownNoise: 'Brown Noise',
|
||||||
|
blueNoise: 'Blue Noise',
|
||||||
|
violetNoise: 'Violet Noise',
|
||||||
|
greyNoise: 'Grey Noise'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
// Common
|
||||||
|
play: 'Play',
|
||||||
|
pause: 'Pause',
|
||||||
|
favorite: 'Favorite',
|
||||||
|
volume: 'Volume',
|
||||||
|
|
||||||
|
// Support & Donate
|
||||||
|
supportMe: 'Support Me',
|
||||||
|
helpKeepAdFree: 'Help me keep Moodist ad-free.',
|
||||||
|
donateToday: 'Donate Today',
|
||||||
|
buyMeACoffee: 'Buy Me a Coffee',
|
||||||
|
createdBy: 'Created by',
|
||||||
|
enjoyMoodist: 'Enjoy Moodist?',
|
||||||
|
supportWithDonation: 'Support with a donation!',
|
||||||
|
|
||||||
|
// UI Actions
|
||||||
|
showMore: 'Show More',
|
||||||
|
showLess: 'Show Less',
|
||||||
|
|
||||||
|
// Settings
|
||||||
|
globalVolume: 'Global Volume',
|
||||||
|
menu: 'Menu',
|
||||||
|
|
||||||
|
// Menu Items
|
||||||
|
breathingExercise: 'Breathing Exercise',
|
||||||
|
countdownTimer: 'Countdown Timer',
|
||||||
|
sleepTimer: 'Sleep Timer',
|
||||||
|
pomodoro: 'Pomodoro',
|
||||||
|
notepad: 'Notepad',
|
||||||
|
todoChecklist: 'Todo Checklist',
|
||||||
|
lofiMusicPlayer: 'Lofi Music Player',
|
||||||
|
binauralBeats: 'Binaural Beats',
|
||||||
|
isochronicTones: 'Isochronic Tones',
|
||||||
|
shortcuts: 'Shortcuts',
|
||||||
|
shuffleSounds: 'Shuffle Sounds',
|
||||||
|
sourceCode: 'Source Code'
|
||||||
|
},
|
||||||
|
|
||||||
|
'zh-CN': {
|
||||||
|
// Navigation & UI
|
||||||
|
app: { language: '语言' },
|
||||||
|
presets: '我的预设',
|
||||||
|
share: '分享',
|
||||||
|
useMoodist: '使用 Moodist',
|
||||||
|
|
||||||
|
// Hero section
|
||||||
|
heroTitle: '环境音',
|
||||||
|
heroSubtitle: '专注与宁静',
|
||||||
|
heroDescription: '免费开源。',
|
||||||
|
soundsCount: '个声音',
|
||||||
|
|
||||||
|
// About section
|
||||||
|
freeAmbientSounds: {
|
||||||
|
title: '免费环境音',
|
||||||
|
body: '渴望从日常繁杂中获得片刻宁静?需要完美的声音环境来提升专注力或帮助入眠?Moodist 就是您的最佳选择——免费开源的环境音生成器!无需订阅注册,使用 Moodist,您可以免费享受舒缓沉浸的音频体验。'
|
||||||
|
},
|
||||||
|
carefullyCuratedSounds: {
|
||||||
|
title: '精心挑选的声音',
|
||||||
|
body: '探索包含 {{count}} 个精心挑选声音的庞大音库。自然爱好者可以在溪流的轻柔潺潺声中、海浪的节拍拍岸声中、或篝火的温暖噼啪声中获得慰藉。城市景观在咖啡馆的轻柔嗡嗡声、火车的节拍咔嗒声、或交通的舒缓白噪声中变得生动。对于寻求更深专注或放松的人,Moodist 提供了专门设计来增强心境的双节拍和色彩噪声。'
|
||||||
|
},
|
||||||
|
createYourSoundscape: {
|
||||||
|
title: '创造您的声音景观',
|
||||||
|
body: 'Moodist 的美妙之处在于其简洁性和自定义性。没有复杂的菜单或令人困惑的选项——只需选择您喜欢的声音,调整音量平衡,然后点击播放。想要将鸟儿的轻柔啾鸣与雨水的舒缓声音融合?没问题!随心所欲地叠加多个声音,创建个性化的声音绿洲。'
|
||||||
|
},
|
||||||
|
soundsForEveryMoment: {
|
||||||
|
title: '适合每个时刻的声音',
|
||||||
|
body: '无论您是想在漫长一天后放松身心,在工作中提升专注力,还是让自己进入宁静的睡眠,Moodist 都有完美的声音景观等着您。最棒的是什么?它完全免费开源,您可以毫无负担地享受它的好处。今天就开始使用 Moodist,发现您新的宁静和专注天堂吧!'
|
||||||
|
},
|
||||||
|
|
||||||
|
// Categories
|
||||||
|
categories: {
|
||||||
|
nature: '自然',
|
||||||
|
rain: '雨声',
|
||||||
|
animals: '动物',
|
||||||
|
urban: '城市',
|
||||||
|
places: '地点',
|
||||||
|
transport: '交通',
|
||||||
|
things: '物品',
|
||||||
|
noise: '噪声'
|
||||||
|
},
|
||||||
|
|
||||||
|
// Sounds
|
||||||
|
sounds: {
|
||||||
|
nature: {
|
||||||
|
river: '河流',
|
||||||
|
waves: '波浪',
|
||||||
|
campfire: '篝火',
|
||||||
|
wind: '风声',
|
||||||
|
howlingWind: '呼啸的风',
|
||||||
|
windInTrees: '林中风声',
|
||||||
|
waterfall: '瀑布',
|
||||||
|
walkInSnow: '雪地漫步',
|
||||||
|
walkOnLeaves: '落叶漫步',
|
||||||
|
walkOnGravel: '碎石漫步',
|
||||||
|
droplets: '水滴',
|
||||||
|
jungle: '丛林'
|
||||||
|
},
|
||||||
|
rain: {
|
||||||
|
lightRain: '小雨',
|
||||||
|
moderateRain: '中雨',
|
||||||
|
heavyRain: '大雨',
|
||||||
|
storm: '暴风雨',
|
||||||
|
thunder: '雷声',
|
||||||
|
distantStorm: '远处的暴风雨',
|
||||||
|
fire: '火焰',
|
||||||
|
oceanWaves: '海浪',
|
||||||
|
rainOnLeaves: '雨打叶子',
|
||||||
|
rainOnPavement: '雨打路面',
|
||||||
|
rainOnWindow: '雨打窗户',
|
||||||
|
rainOnUmbrella: '雨打雨伞',
|
||||||
|
rainOnTent: '雨打帐篷',
|
||||||
|
insideRain: '室内雨声',
|
||||||
|
carRain: '车中雨声'
|
||||||
|
},
|
||||||
|
animals: {
|
||||||
|
birds: '鸟儿',
|
||||||
|
seagulls: '海鸥',
|
||||||
|
crickets: '蟋蟀',
|
||||||
|
wolves: '狼群',
|
||||||
|
owl: '猫头鹰',
|
||||||
|
frogs: '青蛙',
|
||||||
|
dogs: '狗狗',
|
||||||
|
horses: '马儿',
|
||||||
|
cats: '猫咪',
|
||||||
|
crows: '乌鸦',
|
||||||
|
whale: '鲸鱼',
|
||||||
|
beehive: '蜂箱',
|
||||||
|
woodpecker: '啄木鸟',
|
||||||
|
chickens: '小鸡',
|
||||||
|
cows: '奶牛',
|
||||||
|
sheep: '绵羊',
|
||||||
|
rooster: '公鸡',
|
||||||
|
birdsMorning: '清晨鸟鸣',
|
||||||
|
birdsEvening: '傍晚鸟鸣'
|
||||||
|
},
|
||||||
|
urban: {
|
||||||
|
cityStreet: '城市街道',
|
||||||
|
traffic: '交通',
|
||||||
|
highway: '高速公路',
|
||||||
|
road: '马路',
|
||||||
|
ambulanceSiren: '救护车警报',
|
||||||
|
busyStreet: '繁忙街道',
|
||||||
|
crowd: '人群',
|
||||||
|
fireworks: '烟花'
|
||||||
|
},
|
||||||
|
places: {
|
||||||
|
forest: '森林',
|
||||||
|
beach: '海滩',
|
||||||
|
park: '公园',
|
||||||
|
mountain: '山脉',
|
||||||
|
desert: '沙漠',
|
||||||
|
cave: '洞穴',
|
||||||
|
meadow: '草原',
|
||||||
|
lake: '湖泊',
|
||||||
|
campsite: '露营地',
|
||||||
|
temple: '寺庙',
|
||||||
|
airport: '机场',
|
||||||
|
church: '教堂',
|
||||||
|
underwater: '水下',
|
||||||
|
crowdedBar: '拥挤酒吧',
|
||||||
|
nightVillage: '夜晚村庄',
|
||||||
|
carousel: '旋转木马',
|
||||||
|
laboratory: '实验室',
|
||||||
|
laundryRoom: '洗衣房',
|
||||||
|
subwayStation: '地铁站',
|
||||||
|
cafe: '咖啡馆',
|
||||||
|
constructionSite: '施工现场',
|
||||||
|
office: '办公室',
|
||||||
|
supermarket: '超市',
|
||||||
|
restaurant: '餐厅',
|
||||||
|
library: '图书馆'
|
||||||
|
},
|
||||||
|
transport: {
|
||||||
|
car: '汽车',
|
||||||
|
bus: '公交车',
|
||||||
|
train: '火车',
|
||||||
|
subway: '地铁',
|
||||||
|
airplane: '飞机',
|
||||||
|
boat: '小船',
|
||||||
|
bicycle: '自行车',
|
||||||
|
motorcycle: '摩托车',
|
||||||
|
helicopter: '直升机',
|
||||||
|
steamTrain: '蒸汽火车',
|
||||||
|
insideTrain: '火车内部',
|
||||||
|
submarine: '潜水艇',
|
||||||
|
sailboat: '帆船',
|
||||||
|
rowingBoat: '划船'
|
||||||
|
},
|
||||||
|
things: {
|
||||||
|
fan: '风扇',
|
||||||
|
clock: '时钟',
|
||||||
|
typewriter: '打字机',
|
||||||
|
keyboard: '键盘',
|
||||||
|
printer: '打印机',
|
||||||
|
refrigerator: '冰箱',
|
||||||
|
washingMachine: '洗衣机',
|
||||||
|
vacuum: '吸尘器',
|
||||||
|
airConditioner: '空调',
|
||||||
|
microwave: '微波炉',
|
||||||
|
paper: '纸张',
|
||||||
|
windChimes: '风铃',
|
||||||
|
singingBowl: '颂钵',
|
||||||
|
ceilingFan: '吊扇',
|
||||||
|
dryer: '干衣机',
|
||||||
|
slideProjector: '幻灯机',
|
||||||
|
boilingWater: '沸腾的水',
|
||||||
|
bubbles: '泡泡',
|
||||||
|
tuningRadio: '调谐收音机',
|
||||||
|
morseCode: '摩斯电码',
|
||||||
|
vinylEffect: '黑胶唱片效果',
|
||||||
|
windshieldWipers: '雨刮器'
|
||||||
|
},
|
||||||
|
noise: {
|
||||||
|
whiteNoise: '白噪声',
|
||||||
|
pinkNoise: '粉噪声',
|
||||||
|
brownNoise: '棕噪声',
|
||||||
|
blueNoise: '蓝噪声',
|
||||||
|
violetNoise: '紫噪声',
|
||||||
|
greyNoise: '灰噪声'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
// Common
|
||||||
|
play: '播放',
|
||||||
|
pause: '暂停',
|
||||||
|
favorite: '收藏',
|
||||||
|
volume: '音量',
|
||||||
|
|
||||||
|
// Support & Donate
|
||||||
|
supportMe: '支持我',
|
||||||
|
helpKeepAdFree: '帮助我保持 Moodist 无广告。',
|
||||||
|
donateToday: '立即捐赠',
|
||||||
|
buyMeACoffee: '请我喝杯咖啡',
|
||||||
|
createdBy: '作者',
|
||||||
|
enjoyMoodist: '喜欢 Moodist 吗?',
|
||||||
|
supportWithDonation: '欢迎捐赠支持!',
|
||||||
|
|
||||||
|
// UI Actions
|
||||||
|
showMore: '更多',
|
||||||
|
showLess: '收起',
|
||||||
|
|
||||||
|
// Settings
|
||||||
|
globalVolume: '全局音量',
|
||||||
|
menu: '菜单',
|
||||||
|
|
||||||
|
// Menu Items
|
||||||
|
breathingExercise: '呼吸练习',
|
||||||
|
countdownTimer: '倒计时器',
|
||||||
|
sleepTimer: '睡眠定时器',
|
||||||
|
pomodoro: '番茄钟',
|
||||||
|
notepad: '记事本',
|
||||||
|
todoChecklist: '待办清单',
|
||||||
|
lofiMusicPlayer: 'Lofi 音乐播放器',
|
||||||
|
binauralBeats: '双节拍',
|
||||||
|
isochronicTones: '等时音',
|
||||||
|
shortcuts: '快捷键',
|
||||||
|
shuffleSounds: '随机播放',
|
||||||
|
sourceCode: '源代码'
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export function getTranslation(lang: string = 'en'): Translations {
|
||||||
|
return translations[lang] || translations.en;
|
||||||
|
}
|
||||||
|
|
@ -31,98 +31,115 @@ export const animals: Category = {
|
||||||
icon: <PiBirdFill />,
|
icon: <PiBirdFill />,
|
||||||
id: 'birds',
|
id: 'birds',
|
||||||
label: 'Birds',
|
label: 'Birds',
|
||||||
|
dataI18n: 'sounds.animals.birds',
|
||||||
src: getAssetPath('/sounds/animals/birds.mp3'),
|
src: getAssetPath('/sounds/animals/birds.mp3'),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
icon: <GiSeagull />,
|
icon: <GiSeagull />,
|
||||||
id: 'seagulls',
|
id: 'seagulls',
|
||||||
label: 'Seagulls',
|
label: 'Seagulls',
|
||||||
|
dataI18n: 'sounds.animals.seagulls',
|
||||||
src: getAssetPath('/sounds/animals/seagulls.mp3'),
|
src: getAssetPath('/sounds/animals/seagulls.mp3'),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
icon: <GiCricket />,
|
icon: <GiCricket />,
|
||||||
id: 'crickets',
|
id: 'crickets',
|
||||||
label: 'Crickets',
|
label: 'Crickets',
|
||||||
|
dataI18n: 'sounds.animals.crickets',
|
||||||
src: getAssetPath('/sounds/animals/crickets.mp3'),
|
src: getAssetPath('/sounds/animals/crickets.mp3'),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
icon: <GiWolfHead />,
|
icon: <GiWolfHead />,
|
||||||
id: 'wolf',
|
id: 'wolf',
|
||||||
label: 'Wolf',
|
label: 'Wolf',
|
||||||
|
dataI18n: 'sounds.animals.wolves',
|
||||||
src: getAssetPath('/sounds/animals/wolf.mp3'),
|
src: getAssetPath('/sounds/animals/wolf.mp3'),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
icon: <GiOwl />,
|
icon: <GiOwl />,
|
||||||
id: 'owl',
|
id: 'owl',
|
||||||
label: 'Owl',
|
label: 'Owl',
|
||||||
|
dataI18n: 'sounds.animals.owl',
|
||||||
src: getAssetPath('/sounds/animals/owl.mp3'),
|
src: getAssetPath('/sounds/animals/owl.mp3'),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
icon: <FaFrog />,
|
icon: <FaFrog />,
|
||||||
id: 'frog',
|
id: 'frog',
|
||||||
label: 'Frog',
|
label: 'Frog',
|
||||||
|
dataI18n: 'sounds.animals.frogs',
|
||||||
src: getAssetPath('/sounds/animals/frog.mp3'),
|
src: getAssetPath('/sounds/animals/frog.mp3'),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
icon: <PiDogBold />,
|
icon: <PiDogBold />,
|
||||||
id: 'dog-barking',
|
id: 'dog-barking',
|
||||||
label: 'Dog Barking',
|
label: 'Dog Barking',
|
||||||
|
dataI18n: 'sounds.animals.dogs',
|
||||||
src: getAssetPath('/sounds/animals/dog-barking.mp3'),
|
src: getAssetPath('/sounds/animals/dog-barking.mp3'),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
icon: <FaHorseHead />,
|
icon: <FaHorseHead />,
|
||||||
id: 'horse-gallop',
|
id: 'horse-gallop',
|
||||||
label: 'Horse Gallop',
|
label: 'Horse Gallop',
|
||||||
|
dataI18n: 'sounds.animals.horses',
|
||||||
src: getAssetPath('/sounds/animals/horse-gallop.mp3'),
|
src: getAssetPath('/sounds/animals/horse-gallop.mp3'),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
icon: <FaCat />,
|
icon: <FaCat />,
|
||||||
id: 'cat-purring',
|
id: 'cat-purring',
|
||||||
label: 'Cat Purring',
|
label: 'Cat Purring',
|
||||||
|
dataI18n: 'sounds.animals.cats',
|
||||||
src: getAssetPath('/sounds/animals/cat-purring.mp3'),
|
src: getAssetPath('/sounds/animals/cat-purring.mp3'),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
icon: <FaCrow />,
|
icon: <FaCrow />,
|
||||||
id: 'crows',
|
id: 'crows',
|
||||||
label: 'Crows',
|
label: 'Crows',
|
||||||
|
dataI18n: 'sounds.animals.crows',
|
||||||
src: getAssetPath('/sounds/animals/crows.mp3'),
|
src: getAssetPath('/sounds/animals/crows.mp3'),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
icon: <GiWhaleTail />,
|
icon: <GiWhaleTail />,
|
||||||
id: 'whale',
|
id: 'whale',
|
||||||
label: 'Whale',
|
label: 'Whale',
|
||||||
|
dataI18n: 'sounds.animals.whale',
|
||||||
src: getAssetPath('/sounds/animals/whale.mp3'),
|
src: getAssetPath('/sounds/animals/whale.mp3'),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
icon: <GiTreeBeehive />,
|
icon: <GiTreeBeehive />,
|
||||||
id: 'beehive',
|
id: 'beehive',
|
||||||
label: 'Beehive',
|
label: 'Beehive',
|
||||||
|
dataI18n: 'sounds.animals.beehive',
|
||||||
src: getAssetPath('/sounds/animals/beehive.mp3'),
|
src: getAssetPath('/sounds/animals/beehive.mp3'),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
icon: <GiEgyptianBird />,
|
icon: <GiEgyptianBird />,
|
||||||
id: 'woodpecker',
|
id: 'woodpecker',
|
||||||
label: 'Woodpecker',
|
label: 'Woodpecker',
|
||||||
|
dataI18n: 'sounds.animals.woodpecker',
|
||||||
src: getAssetPath('/sounds/animals/woodpecker.mp3'),
|
src: getAssetPath('/sounds/animals/woodpecker.mp3'),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
icon: <GiChicken />,
|
icon: <GiChicken />,
|
||||||
id: 'chickens',
|
id: 'chickens',
|
||||||
label: 'Chickens',
|
label: 'Chickens',
|
||||||
|
dataI18n: 'sounds.animals.chickens',
|
||||||
src: getAssetPath('/sounds/animals/chickens.mp3'),
|
src: getAssetPath('/sounds/animals/chickens.mp3'),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
icon: <GiCow />,
|
icon: <GiCow />,
|
||||||
id: 'cows',
|
id: 'cows',
|
||||||
label: 'Cows',
|
label: 'Cows',
|
||||||
|
dataI18n: 'sounds.animals.cows',
|
||||||
src: getAssetPath('/sounds/animals/cows.mp3'),
|
src: getAssetPath('/sounds/animals/cows.mp3'),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
icon: <GiSheep />,
|
icon: <GiSheep />,
|
||||||
id: 'sheep',
|
id: 'sheep',
|
||||||
label: 'Sheep',
|
label: 'Sheep',
|
||||||
|
dataI18n: 'sounds.animals.sheep',
|
||||||
src: getAssetPath('/sounds/animals/sheep.mp3'),
|
src: getAssetPath('/sounds/animals/sheep.mp3'),
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
title: 'Animals',
|
title: 'Animals',
|
||||||
|
dataI18n: 'categories.animals',
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -21,74 +21,87 @@ export const nature: Category = {
|
||||||
icon: <BiWater />,
|
icon: <BiWater />,
|
||||||
id: 'river',
|
id: 'river',
|
||||||
label: 'River',
|
label: 'River',
|
||||||
|
dataI18n: 'sounds.nature.river',
|
||||||
src: getAssetPath('/sounds/nature/river.mp3'),
|
src: getAssetPath('/sounds/nature/river.mp3'),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
icon: <FaWater />,
|
icon: <FaWater />,
|
||||||
id: 'waves',
|
id: 'waves',
|
||||||
label: 'Waves',
|
label: 'Waves',
|
||||||
|
dataI18n: 'sounds.nature.waves',
|
||||||
src: getAssetPath('/sounds/nature/waves.mp3'),
|
src: getAssetPath('/sounds/nature/waves.mp3'),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
icon: <BsFire />,
|
icon: <BsFire />,
|
||||||
id: 'campfire',
|
id: 'campfire',
|
||||||
label: 'Campfire',
|
label: 'Campfire',
|
||||||
|
dataI18n: 'sounds.nature.campfire',
|
||||||
src: getAssetPath('/sounds/nature/campfire.mp3'),
|
src: getAssetPath('/sounds/nature/campfire.mp3'),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
icon: <FaWind />,
|
icon: <FaWind />,
|
||||||
id: 'wind',
|
id: 'wind',
|
||||||
label: 'Wind',
|
label: 'Wind',
|
||||||
|
dataI18n: 'sounds.nature.wind',
|
||||||
src: getAssetPath('/sounds/nature/wind.mp3'),
|
src: getAssetPath('/sounds/nature/wind.mp3'),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
icon: <FaWind />,
|
icon: <FaWind />,
|
||||||
id: 'howling-wind',
|
id: 'howling-wind',
|
||||||
label: 'Howling Wind',
|
label: 'Howling Wind',
|
||||||
|
dataI18n: 'sounds.nature.howlingWind',
|
||||||
src: getAssetPath('/sounds/nature/howling-wind.mp3'),
|
src: getAssetPath('/sounds/nature/howling-wind.mp3'),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
icon: <BiSolidTree />,
|
icon: <BiSolidTree />,
|
||||||
id: 'wind-in-trees',
|
id: 'wind-in-trees',
|
||||||
label: 'Wind in Trees',
|
label: 'Wind in Trees',
|
||||||
|
dataI18n: 'sounds.nature.windInTrees',
|
||||||
src: getAssetPath('/sounds/nature/wind-in-trees.mp3'),
|
src: getAssetPath('/sounds/nature/wind-in-trees.mp3'),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
icon: <GiWaterfall />,
|
icon: <GiWaterfall />,
|
||||||
id: 'waterfall',
|
id: 'waterfall',
|
||||||
label: 'Waterfall',
|
label: 'Waterfall',
|
||||||
|
dataI18n: 'sounds.nature.waterfall',
|
||||||
src: getAssetPath('/sounds/nature/waterfall.mp3'),
|
src: getAssetPath('/sounds/nature/waterfall.mp3'),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
icon: <FaRegSnowflake />,
|
icon: <FaRegSnowflake />,
|
||||||
id: 'walk-in-snow',
|
id: 'walk-in-snow',
|
||||||
label: 'Walk in Snow',
|
label: 'Walk in Snow',
|
||||||
|
dataI18n: 'sounds.nature.walkInSnow',
|
||||||
src: getAssetPath('/sounds/nature/walk-in-snow.mp3'),
|
src: getAssetPath('/sounds/nature/walk-in-snow.mp3'),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
icon: <FaLeaf />,
|
icon: <FaLeaf />,
|
||||||
id: 'walk-on-leaves',
|
id: 'walk-on-leaves',
|
||||||
label: 'Walk on Leaves',
|
label: 'Walk on Leaves',
|
||||||
|
dataI18n: 'sounds.nature.walkOnLeaves',
|
||||||
src: getAssetPath('/sounds/nature/walk-on-leaves.mp3'),
|
src: getAssetPath('/sounds/nature/walk-on-leaves.mp3'),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
icon: <GiStonePile />,
|
icon: <GiStonePile />,
|
||||||
id: 'walk-on-gravel',
|
id: 'walk-on-gravel',
|
||||||
label: 'Walk on Gravel',
|
label: 'Walk on Gravel',
|
||||||
|
dataI18n: 'sounds.nature.walkOnGravel',
|
||||||
src: getAssetPath('/sounds/nature/walk-on-gravel.mp3'),
|
src: getAssetPath('/sounds/nature/walk-on-gravel.mp3'),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
icon: <BsFillDropletFill />,
|
icon: <BsFillDropletFill />,
|
||||||
id: 'droplets',
|
id: 'droplets',
|
||||||
label: 'Droplets',
|
label: 'Droplets',
|
||||||
|
dataI18n: 'sounds.nature.droplets',
|
||||||
src: getAssetPath('/sounds/nature/droplets.mp3'),
|
src: getAssetPath('/sounds/nature/droplets.mp3'),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
icon: <FaTree />,
|
icon: <FaTree />,
|
||||||
id: 'jungle',
|
id: 'jungle',
|
||||||
label: 'Jungle',
|
label: 'Jungle',
|
||||||
|
dataI18n: 'sounds.nature.jungle',
|
||||||
src: getAssetPath('/sounds/nature/jungle.mp3'),
|
src: getAssetPath('/sounds/nature/jungle.mp3'),
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
title: 'Nature',
|
title: 'Nature',
|
||||||
|
dataI18n: 'categories.nature',
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -13,20 +13,24 @@ export const noise: Category = {
|
||||||
icon: <GiSoundWaves />,
|
icon: <GiSoundWaves />,
|
||||||
id: 'white-noise',
|
id: 'white-noise',
|
||||||
label: 'White Noise',
|
label: 'White Noise',
|
||||||
|
dataI18n: 'sounds.noise.whiteNoise',
|
||||||
src: getAssetPath('/sounds/noise/white-noise.wav'),
|
src: getAssetPath('/sounds/noise/white-noise.wav'),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
icon: <GiSoundWaves />,
|
icon: <GiSoundWaves />,
|
||||||
id: 'pink-noise',
|
id: 'pink-noise',
|
||||||
label: 'Pink Noise',
|
label: 'Pink Noise',
|
||||||
|
dataI18n: 'sounds.noise.pinkNoise',
|
||||||
src: getAssetPath('/sounds/noise/pink-noise.wav'),
|
src: getAssetPath('/sounds/noise/pink-noise.wav'),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
icon: <GiSoundWaves />,
|
icon: <GiSoundWaves />,
|
||||||
id: 'brown-noise',
|
id: 'brown-noise',
|
||||||
label: 'Brown Noise',
|
label: 'Brown Noise',
|
||||||
|
dataI18n: 'sounds.noise.brownNoise',
|
||||||
src: getAssetPath('/sounds/noise/brown-noise.wav'),
|
src: getAssetPath('/sounds/noise/brown-noise.wav'),
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
title: 'Noise',
|
title: 'Noise',
|
||||||
|
dataI18n: 'categories.noise',
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -28,98 +28,115 @@ export const places: Category = {
|
||||||
icon: <BiSolidCoffeeAlt />,
|
icon: <BiSolidCoffeeAlt />,
|
||||||
id: 'cafe',
|
id: 'cafe',
|
||||||
label: 'Cafe',
|
label: 'Cafe',
|
||||||
|
dataI18n: 'sounds.places.cafe',
|
||||||
src: getAssetPath('/sounds/places/cafe.mp3'),
|
src: getAssetPath('/sounds/places/cafe.mp3'),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
icon: <BiSolidPlaneAlt />,
|
icon: <BiSolidPlaneAlt />,
|
||||||
id: 'airport',
|
id: 'airport',
|
||||||
label: 'Airport',
|
label: 'Airport',
|
||||||
|
dataI18n: 'sounds.places.airport',
|
||||||
src: getAssetPath('/sounds/places/airport.mp3'),
|
src: getAssetPath('/sounds/places/airport.mp3'),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
icon: <FaChurch />,
|
icon: <FaChurch />,
|
||||||
id: 'church',
|
id: 'church',
|
||||||
label: 'Church',
|
label: 'Church',
|
||||||
|
dataI18n: 'sounds.places.church',
|
||||||
src: getAssetPath('/sounds/places/church.mp3'),
|
src: getAssetPath('/sounds/places/church.mp3'),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
icon: <MdTempleBuddhist />,
|
icon: <MdTempleBuddhist />,
|
||||||
id: 'temple',
|
id: 'temple',
|
||||||
label: 'Temple',
|
label: 'Temple',
|
||||||
|
dataI18n: 'sounds.places.temple',
|
||||||
src: getAssetPath('/sounds/places/temple.mp3'),
|
src: getAssetPath('/sounds/places/temple.mp3'),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
icon: <MdConstruction />,
|
icon: <MdConstruction />,
|
||||||
id: 'construction-site',
|
id: 'construction-site',
|
||||||
label: 'Construction Site',
|
label: 'Construction Site',
|
||||||
|
dataI18n: 'sounds.places.constructionSite',
|
||||||
src: getAssetPath('/sounds/places/construction-site.mp3'),
|
src: getAssetPath('/sounds/places/construction-site.mp3'),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
icon: <TbScubaMask />,
|
icon: <TbScubaMask />,
|
||||||
id: 'underwater',
|
id: 'underwater',
|
||||||
label: 'Underwater',
|
label: 'Underwater',
|
||||||
|
dataI18n: 'sounds.places.underwater',
|
||||||
src: getAssetPath('/sounds/places/underwater.mp3'),
|
src: getAssetPath('/sounds/places/underwater.mp3'),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
icon: <TbBeerFilled />,
|
icon: <TbBeerFilled />,
|
||||||
id: 'crowded-bar',
|
id: 'crowded-bar',
|
||||||
label: 'Crowded Bar',
|
label: 'Crowded Bar',
|
||||||
|
dataI18n: 'sounds.places.crowdedBar',
|
||||||
src: getAssetPath('/sounds/places/crowded-bar.mp3'),
|
src: getAssetPath('/sounds/places/crowded-bar.mp3'),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
icon: <GiVillage />,
|
icon: <GiVillage />,
|
||||||
id: 'night-village',
|
id: 'night-village',
|
||||||
label: 'Night Village',
|
label: 'Night Village',
|
||||||
|
dataI18n: 'sounds.places.nightVillage',
|
||||||
src: getAssetPath('/sounds/places/night-village.mp3'),
|
src: getAssetPath('/sounds/places/night-village.mp3'),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
icon: <FaSubway />,
|
icon: <FaSubway />,
|
||||||
id: 'subway-station',
|
id: 'subway-station',
|
||||||
label: 'Subway Station',
|
label: 'Subway Station',
|
||||||
|
dataI18n: 'sounds.places.subwayStation',
|
||||||
src: getAssetPath('/sounds/places/subway-station.mp3'),
|
src: getAssetPath('/sounds/places/subway-station.mp3'),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
icon: <HiOfficeBuilding />,
|
icon: <HiOfficeBuilding />,
|
||||||
id: 'office',
|
id: 'office',
|
||||||
label: 'Office',
|
label: 'Office',
|
||||||
|
dataI18n: 'sounds.places.office',
|
||||||
src: getAssetPath('/sounds/places/office.mp3'),
|
src: getAssetPath('/sounds/places/office.mp3'),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
icon: <FaShoppingBasket />,
|
icon: <FaShoppingBasket />,
|
||||||
id: 'supermarket',
|
id: 'supermarket',
|
||||||
label: 'Supermarket',
|
label: 'Supermarket',
|
||||||
|
dataI18n: 'sounds.places.supermarket',
|
||||||
src: getAssetPath('/sounds/places/supermarket.mp3'),
|
src: getAssetPath('/sounds/places/supermarket.mp3'),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
icon: <GiCarousel />,
|
icon: <GiCarousel />,
|
||||||
id: 'carousel',
|
id: 'carousel',
|
||||||
label: 'Carousel',
|
label: 'Carousel',
|
||||||
|
dataI18n: 'sounds.places.carousel',
|
||||||
src: getAssetPath('/sounds/places/carousel.mp3'),
|
src: getAssetPath('/sounds/places/carousel.mp3'),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
icon: <AiFillExperiment />,
|
icon: <AiFillExperiment />,
|
||||||
id: 'laboratory',
|
id: 'laboratory',
|
||||||
label: 'Laboratory',
|
label: 'Laboratory',
|
||||||
|
dataI18n: 'sounds.places.laboratory',
|
||||||
src: getAssetPath('/sounds/places/laboratory.mp3'),
|
src: getAssetPath('/sounds/places/laboratory.mp3'),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
icon: <BiSolidDryer />,
|
icon: <BiSolidDryer />,
|
||||||
id: 'laundry-room',
|
id: 'laundry-room',
|
||||||
label: 'Laundry Room',
|
label: 'Laundry Room',
|
||||||
|
dataI18n: 'sounds.places.laundryRoom',
|
||||||
src: getAssetPath('/sounds/places/laundry-room.mp3'),
|
src: getAssetPath('/sounds/places/laundry-room.mp3'),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
icon: <IoRestaurant />,
|
icon: <IoRestaurant />,
|
||||||
id: 'restaurant',
|
id: 'restaurant',
|
||||||
label: 'Restaurant',
|
label: 'Restaurant',
|
||||||
|
dataI18n: 'sounds.places.restaurant',
|
||||||
src: getAssetPath('/sounds/places/restaurant.mp3'),
|
src: getAssetPath('/sounds/places/restaurant.mp3'),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
icon: <FaBookOpen />,
|
icon: <FaBookOpen />,
|
||||||
id: 'library',
|
id: 'library',
|
||||||
label: 'Library',
|
label: 'Library',
|
||||||
|
dataI18n: 'sounds.places.library',
|
||||||
src: getAssetPath('/sounds/places/library.mp3'),
|
src: getAssetPath('/sounds/places/library.mp3'),
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
title: 'Places',
|
title: 'Places',
|
||||||
|
dataI18n: 'categories.places',
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -20,50 +20,73 @@ export const rain: Category = {
|
||||||
icon: <BsFillCloudRainFill />,
|
icon: <BsFillCloudRainFill />,
|
||||||
id: 'light-rain',
|
id: 'light-rain',
|
||||||
label: 'Light Rain',
|
label: 'Light Rain',
|
||||||
|
dataI18n: 'sounds.rain.lightRain',
|
||||||
src: getAssetPath('/sounds/rain/light-rain.mp3'),
|
src: getAssetPath('/sounds/rain/light-rain.mp3'),
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
icon: <BsFillCloudRainHeavyFill />,
|
||||||
|
id: 'moderate-rain',
|
||||||
|
label: 'Moderate Rain',
|
||||||
|
dataI18n: 'sounds.rain.moderateRain',
|
||||||
|
src: getAssetPath('/sounds/rain/moderate-rain.mp3'),
|
||||||
|
},
|
||||||
{
|
{
|
||||||
icon: <BsFillCloudRainHeavyFill />,
|
icon: <BsFillCloudRainHeavyFill />,
|
||||||
id: 'heavy-rain',
|
id: 'heavy-rain',
|
||||||
label: 'Heavy Rain',
|
label: 'Heavy Rain',
|
||||||
|
dataI18n: 'sounds.rain.heavyRain',
|
||||||
src: getAssetPath('/sounds/rain/heavy-rain.mp3'),
|
src: getAssetPath('/sounds/rain/heavy-rain.mp3'),
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
icon: <MdOutlineThunderstorm />,
|
||||||
|
id: 'storm',
|
||||||
|
label: 'Storm',
|
||||||
|
dataI18n: 'sounds.rain.storm',
|
||||||
|
src: getAssetPath('/sounds/rain/storm.mp3'),
|
||||||
|
},
|
||||||
{
|
{
|
||||||
icon: <MdOutlineThunderstorm />,
|
icon: <MdOutlineThunderstorm />,
|
||||||
id: 'thunder',
|
id: 'thunder',
|
||||||
label: 'Thunder',
|
label: 'Thunder',
|
||||||
|
dataI18n: 'sounds.rain.thunder',
|
||||||
src: getAssetPath('/sounds/rain/thunder.mp3'),
|
src: getAssetPath('/sounds/rain/thunder.mp3'),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
icon: <GiWindow />,
|
icon: <GiWindow />,
|
||||||
id: 'rain-on-window',
|
id: 'rain-on-window',
|
||||||
label: 'Rain on Window',
|
label: 'Rain on Window',
|
||||||
|
dataI18n: 'sounds.rain.rainOnWindow',
|
||||||
src: getAssetPath('/sounds/rain/rain-on-window.mp3'),
|
src: getAssetPath('/sounds/rain/rain-on-window.mp3'),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
icon: <FaCarSide />,
|
icon: <FaCarSide />,
|
||||||
id: 'rain-on-car-roof',
|
id: 'rain-on-car-roof',
|
||||||
label: 'Rain on Car Roof',
|
label: 'Rain on Car Roof',
|
||||||
|
dataI18n: 'sounds.rain.carRain',
|
||||||
src: getAssetPath('/sounds/rain/rain-on-car-roof.mp3'),
|
src: getAssetPath('/sounds/rain/rain-on-car-roof.mp3'),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
icon: <BsUmbrellaFill />,
|
icon: <BsUmbrellaFill />,
|
||||||
id: 'rain-on-umbrella',
|
id: 'rain-on-umbrella',
|
||||||
label: 'Rain on Umbrella',
|
label: 'Rain on Umbrella',
|
||||||
|
dataI18n: 'sounds.rain.rainOnUmbrella',
|
||||||
src: getAssetPath('/sounds/rain/rain-on-umbrella.mp3'),
|
src: getAssetPath('/sounds/rain/rain-on-umbrella.mp3'),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
icon: <PiTentFill />,
|
icon: <PiTentFill />,
|
||||||
id: 'rain-on-tent',
|
id: 'rain-on-tent',
|
||||||
label: 'Rain on Tent',
|
label: 'Rain on Tent',
|
||||||
|
dataI18n: 'sounds.rain.rainOnTent',
|
||||||
src: getAssetPath('/sounds/rain/rain-on-tent.mp3'),
|
src: getAssetPath('/sounds/rain/rain-on-tent.mp3'),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
icon: <FaLeaf />,
|
icon: <FaLeaf />,
|
||||||
id: 'rain-on-leaves',
|
id: 'rain-on-leaves',
|
||||||
label: 'Rain on Leaves',
|
label: 'Rain on Leaves',
|
||||||
|
dataI18n: 'sounds.rain.rainOnLeaves',
|
||||||
src: getAssetPath('/sounds/rain/rain-on-leaves.mp3'),
|
src: getAssetPath('/sounds/rain/rain-on-leaves.mp3'),
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
title: 'Rain',
|
title: 'Rain',
|
||||||
|
dataI18n: 'categories.rain',
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -24,98 +24,115 @@ export const things: Category = {
|
||||||
icon: <BsFillKeyboardFill />,
|
icon: <BsFillKeyboardFill />,
|
||||||
id: 'keyboard',
|
id: 'keyboard',
|
||||||
label: 'Keyboard',
|
label: 'Keyboard',
|
||||||
|
dataI18n: 'sounds.things.keyboard',
|
||||||
src: getAssetPath('/sounds/things/keyboard.mp3'),
|
src: getAssetPath('/sounds/things/keyboard.mp3'),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
icon: <FaKeyboard />,
|
icon: <FaKeyboard />,
|
||||||
id: 'typewriter',
|
id: 'typewriter',
|
||||||
label: 'Typewriter',
|
label: 'Typewriter',
|
||||||
|
dataI18n: 'sounds.things.typewriter',
|
||||||
src: getAssetPath('/sounds/things/typewriter.mp3'),
|
src: getAssetPath('/sounds/things/typewriter.mp3'),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
icon: <RiFilePaper2Fill />,
|
icon: <RiFilePaper2Fill />,
|
||||||
id: 'paper',
|
id: 'paper',
|
||||||
label: 'Paper',
|
label: 'Paper',
|
||||||
|
dataI18n: 'sounds.things.paper',
|
||||||
src: getAssetPath('/sounds/things/paper.mp3'),
|
src: getAssetPath('/sounds/things/paper.mp3'),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
icon: <FaClock />,
|
icon: <FaClock />,
|
||||||
id: 'clock',
|
id: 'clock',
|
||||||
label: 'Clock',
|
label: 'Clock',
|
||||||
|
dataI18n: 'sounds.things.clock',
|
||||||
src: getAssetPath('/sounds/things/clock.mp3'),
|
src: getAssetPath('/sounds/things/clock.mp3'),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
icon: <GiWindchimes />,
|
icon: <GiWindchimes />,
|
||||||
id: 'wind-chimes',
|
id: 'wind-chimes',
|
||||||
label: 'Wind Chimes',
|
label: 'Wind Chimes',
|
||||||
|
dataI18n: 'sounds.things.windChimes',
|
||||||
src: getAssetPath('/sounds/things/wind-chimes.mp3'),
|
src: getAssetPath('/sounds/things/wind-chimes.mp3'),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
icon: <TbBowlFilled />,
|
icon: <TbBowlFilled />,
|
||||||
id: 'singing-bowl',
|
id: 'singing-bowl',
|
||||||
label: 'Singing Bowl',
|
label: 'Singing Bowl',
|
||||||
|
dataI18n: 'sounds.things.singingBowl',
|
||||||
src: getAssetPath('/sounds/things/singing-bowl.mp3'),
|
src: getAssetPath('/sounds/things/singing-bowl.mp3'),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
icon: <FaFan />,
|
icon: <FaFan />,
|
||||||
id: 'ceiling-fan',
|
id: 'ceiling-fan',
|
||||||
label: 'Ceiling Fan',
|
label: 'Ceiling Fan',
|
||||||
|
dataI18n: 'sounds.things.ceilingFan',
|
||||||
src: getAssetPath('/sounds/things/ceiling-fan.mp3'),
|
src: getAssetPath('/sounds/things/ceiling-fan.mp3'),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
icon: <BiSolidDryer />,
|
icon: <BiSolidDryer />,
|
||||||
id: 'dryer',
|
id: 'dryer',
|
||||||
label: 'Dryer',
|
label: 'Dryer',
|
||||||
|
dataI18n: 'sounds.things.dryer',
|
||||||
src: getAssetPath('/sounds/things/dryer.mp3'),
|
src: getAssetPath('/sounds/things/dryer.mp3'),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
icon: <GiFilmProjector />,
|
icon: <GiFilmProjector />,
|
||||||
id: 'slide-projector',
|
id: 'slide-projector',
|
||||||
label: 'Slide Projector',
|
label: 'Slide Projector',
|
||||||
|
dataI18n: 'sounds.things.slideProjector',
|
||||||
src: getAssetPath('/sounds/things/slide-projector.mp3'),
|
src: getAssetPath('/sounds/things/slide-projector.mp3'),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
icon: <MdWaterDrop />,
|
icon: <MdWaterDrop />,
|
||||||
id: 'boiling-water',
|
id: 'boiling-water',
|
||||||
label: 'Boiling Water',
|
label: 'Boiling Water',
|
||||||
|
dataI18n: 'sounds.things.boilingWater',
|
||||||
src: getAssetPath('/sounds/things/boiling-water.mp3'),
|
src: getAssetPath('/sounds/things/boiling-water.mp3'),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
icon: <RiBubbleChartFill />,
|
icon: <RiBubbleChartFill />,
|
||||||
id: 'bubbles',
|
id: 'bubbles',
|
||||||
label: 'Bubbles',
|
label: 'Bubbles',
|
||||||
|
dataI18n: 'sounds.things.bubbles',
|
||||||
src: getAssetPath('/sounds/things/bubbles.mp3'),
|
src: getAssetPath('/sounds/things/bubbles.mp3'),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
icon: <MdRadio />,
|
icon: <MdRadio />,
|
||||||
id: 'tuning-radio',
|
id: 'tuning-radio',
|
||||||
label: 'Tuning Radio',
|
label: 'Tuning Radio',
|
||||||
|
dataI18n: 'sounds.things.tuningRadio',
|
||||||
src: getAssetPath('/sounds/things/tuning-radio.mp3'),
|
src: getAssetPath('/sounds/things/tuning-radio.mp3'),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
icon: <IoIosRadio />,
|
icon: <IoIosRadio />,
|
||||||
id: 'morse-code',
|
id: 'morse-code',
|
||||||
label: 'Morse Code',
|
label: 'Morse Code',
|
||||||
|
dataI18n: 'sounds.things.morseCode',
|
||||||
src: getAssetPath('/sounds/things/morse-code.mp3'),
|
src: getAssetPath('/sounds/things/morse-code.mp3'),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
icon: <GiWashingMachine />,
|
icon: <GiWashingMachine />,
|
||||||
id: 'washing-machine',
|
id: 'washing-machine',
|
||||||
label: 'Washing Machine',
|
label: 'Washing Machine',
|
||||||
|
dataI18n: 'sounds.things.washingMachine',
|
||||||
src: getAssetPath('/sounds/things/washing-machine.mp3'),
|
src: getAssetPath('/sounds/things/washing-machine.mp3'),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
icon: <PiVinylRecord />,
|
icon: <PiVinylRecord />,
|
||||||
id: 'vinyl-effect',
|
id: 'vinyl-effect',
|
||||||
label: 'Vinyl Effect',
|
label: 'Vinyl Effect',
|
||||||
|
dataI18n: 'sounds.things.vinylEffect',
|
||||||
src: getAssetPath('/sounds/things/vinyl-effect.mp3'),
|
src: getAssetPath('/sounds/things/vinyl-effect.mp3'),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
icon: <TbWiper />,
|
icon: <TbWiper />,
|
||||||
id: 'windshield-wipers',
|
id: 'windshield-wipers',
|
||||||
label: 'Windshield Wipers',
|
label: 'Windshield Wipers',
|
||||||
|
dataI18n: 'sounds.things.windshieldWipers',
|
||||||
src: getAssetPath('/sounds/things/windshield-wipers.mp3'),
|
src: getAssetPath('/sounds/things/windshield-wipers.mp3'),
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
title: 'Things',
|
title: 'Things',
|
||||||
|
dataI18n: 'categories.things',
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -15,38 +15,45 @@ export const transport: Category = {
|
||||||
icon: <BiSolidTrain />,
|
icon: <BiSolidTrain />,
|
||||||
id: 'train',
|
id: 'train',
|
||||||
label: 'Train',
|
label: 'Train',
|
||||||
|
dataI18n: 'sounds.transport.train',
|
||||||
src: getAssetPath('/sounds/transport/train.mp3'),
|
src: getAssetPath('/sounds/transport/train.mp3'),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
icon: <BiSolidTrain />,
|
icon: <BiSolidTrain />,
|
||||||
id: 'inside-a-train',
|
id: 'inside-a-train',
|
||||||
label: 'Inside a Train',
|
label: 'Inside a Train',
|
||||||
|
dataI18n: 'sounds.transport.insideTrain',
|
||||||
src: getAssetPath('/sounds/transport/inside-a-train.mp3'),
|
src: getAssetPath('/sounds/transport/inside-a-train.mp3'),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
icon: <BiSolidPlaneAlt />,
|
icon: <BiSolidPlaneAlt />,
|
||||||
id: 'airplane',
|
id: 'airplane',
|
||||||
label: 'Airplane',
|
label: 'Airplane',
|
||||||
|
dataI18n: 'sounds.transport.airplane',
|
||||||
src: getAssetPath('/sounds/transport/airplane.mp3'),
|
src: getAssetPath('/sounds/transport/airplane.mp3'),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
icon: <GiSubmarine />,
|
icon: <GiSubmarine />,
|
||||||
id: 'submarine',
|
id: 'submarine',
|
||||||
label: 'Submarine',
|
label: 'Submarine',
|
||||||
|
dataI18n: 'sounds.transport.submarine',
|
||||||
src: getAssetPath('/sounds/transport/submarine.mp3'),
|
src: getAssetPath('/sounds/transport/submarine.mp3'),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
icon: <GiSailboat />,
|
icon: <GiSailboat />,
|
||||||
id: 'sailboat',
|
id: 'sailboat',
|
||||||
label: 'Sailboat',
|
label: 'Sailboat',
|
||||||
|
dataI18n: 'sounds.transport.sailboat',
|
||||||
src: getAssetPath('/sounds/transport/sailboat.mp3'),
|
src: getAssetPath('/sounds/transport/sailboat.mp3'),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
icon: <TbSailboat />,
|
icon: <TbSailboat />,
|
||||||
id: 'rowing-boat',
|
id: 'rowing-boat',
|
||||||
label: 'Rowing Boat',
|
label: 'Rowing Boat',
|
||||||
|
dataI18n: 'sounds.transport.rowingBoat',
|
||||||
src: getAssetPath('/sounds/transport/rowing-boat.mp3'),
|
src: getAssetPath('/sounds/transport/rowing-boat.mp3'),
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
title: 'Transport',
|
title: 'Transport',
|
||||||
|
dataI18n: 'categories.transport',
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -16,44 +16,52 @@ export const urban: Category = {
|
||||||
icon: <PiRoadHorizonFill />,
|
icon: <PiRoadHorizonFill />,
|
||||||
id: 'highway',
|
id: 'highway',
|
||||||
label: 'Highway',
|
label: 'Highway',
|
||||||
|
dataI18n: 'sounds.urban.highway',
|
||||||
src: getAssetPath('/sounds/urban/highway.mp3'),
|
src: getAssetPath('/sounds/urban/highway.mp3'),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
icon: <FaRoad />,
|
icon: <FaRoad />,
|
||||||
id: 'road',
|
id: 'road',
|
||||||
label: 'Road',
|
label: 'Road',
|
||||||
|
dataI18n: 'sounds.urban.road',
|
||||||
src: getAssetPath('/sounds/urban/road.mp3'),
|
src: getAssetPath('/sounds/urban/road.mp3'),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
icon: <PiSirenBold />,
|
icon: <PiSirenBold />,
|
||||||
id: 'ambulance-siren',
|
id: 'ambulance-siren',
|
||||||
label: 'Ambulance Siren',
|
label: 'Ambulance Siren',
|
||||||
|
dataI18n: 'sounds.urban.ambulanceSiren',
|
||||||
src: getAssetPath('/sounds/urban/ambulance-siren.mp3'),
|
src: getAssetPath('/sounds/urban/ambulance-siren.mp3'),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
icon: <BsSoundwave />,
|
icon: <BsSoundwave />,
|
||||||
id: 'busy-street',
|
id: 'busy-street',
|
||||||
label: 'Busy Street',
|
label: 'Busy Street',
|
||||||
|
dataI18n: 'sounds.urban.busyStreet',
|
||||||
src: getAssetPath('/sounds/urban/busy-street.mp3'),
|
src: getAssetPath('/sounds/urban/busy-street.mp3'),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
icon: <BsPeopleFill />,
|
icon: <BsPeopleFill />,
|
||||||
id: 'crowd',
|
id: 'crowd',
|
||||||
label: 'Crowd',
|
label: 'Crowd',
|
||||||
|
dataI18n: 'sounds.urban.crowd',
|
||||||
src: getAssetPath('/sounds/urban/crowd.mp3'),
|
src: getAssetPath('/sounds/urban/crowd.mp3'),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
icon: <BiSolidTraffic />,
|
icon: <BiSolidTraffic />,
|
||||||
id: 'traffic',
|
id: 'traffic',
|
||||||
label: 'Traffic',
|
label: 'Traffic',
|
||||||
|
dataI18n: 'sounds.urban.traffic',
|
||||||
src: getAssetPath('/sounds/urban/traffic.mp3'),
|
src: getAssetPath('/sounds/urban/traffic.mp3'),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
icon: <RiSparkling2Fill />,
|
icon: <RiSparkling2Fill />,
|
||||||
id: 'fireworks',
|
id: 'fireworks',
|
||||||
label: 'Fireworks',
|
label: 'Fireworks',
|
||||||
|
dataI18n: 'sounds.urban.fireworks',
|
||||||
src: getAssetPath('/sounds/urban/fireworks.mp3'),
|
src: getAssetPath('/sounds/urban/fireworks.mp3'),
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
title: 'Urban',
|
title: 'Urban',
|
||||||
|
dataI18n: 'categories.urban',
|
||||||
};
|
};
|
||||||
|
|
|
||||||
2
src/data/types.d.ts
vendored
2
src/data/types.d.ts
vendored
|
|
@ -3,6 +3,7 @@ export interface Sound {
|
||||||
id: string;
|
id: string;
|
||||||
label: string;
|
label: string;
|
||||||
src: string;
|
src: string;
|
||||||
|
dataI18n?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export type Sounds = Array<Sound>;
|
export type Sounds = Array<Sound>;
|
||||||
|
|
@ -12,6 +13,7 @@ export interface Category {
|
||||||
id: string;
|
id: string;
|
||||||
sounds: Sounds;
|
sounds: Sounds;
|
||||||
title: string;
|
title: string;
|
||||||
|
dataI18n?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export type Categories = Array<Category>;
|
export type Categories = Array<Category>;
|
||||||
|
|
|
||||||
18
src/helpers/translation.ts
Normal file
18
src/helpers/translation.ts
Normal file
|
|
@ -0,0 +1,18 @@
|
||||||
|
import { useTranslation } from '@/hooks/use-translation';
|
||||||
|
|
||||||
|
export function useTranslatedSounds() {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
|
||||||
|
const translateCategory = (category: string) => {
|
||||||
|
return t(`categories.${category.toLowerCase()}`);
|
||||||
|
};
|
||||||
|
|
||||||
|
const translateSound = (category: string, soundId: string) => {
|
||||||
|
return t(`sounds.${category.toLowerCase()}.${soundId}`);
|
||||||
|
};
|
||||||
|
|
||||||
|
return {
|
||||||
|
translateCategory,
|
||||||
|
translateSound,
|
||||||
|
};
|
||||||
|
}
|
||||||
43
src/hooks/useLanguage.ts
Normal file
43
src/hooks/useLanguage.ts
Normal file
|
|
@ -0,0 +1,43 @@
|
||||||
|
import { useState, useEffect } from 'react';
|
||||||
|
|
||||||
|
export function useLanguage() {
|
||||||
|
const [currentLang, setCurrentLang] = useState('en');
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
// Detect language from URL path first
|
||||||
|
const pathname = window.location.pathname;
|
||||||
|
const isZhPage = pathname === '/zh' || pathname.startsWith('/zh/');
|
||||||
|
|
||||||
|
// Then check URL parameter
|
||||||
|
const urlParams = new URLSearchParams(window.location.search);
|
||||||
|
const urlLang = urlParams.get('lang');
|
||||||
|
|
||||||
|
// Finally check saved preference
|
||||||
|
const savedLang = localStorage.getItem('moodist-language');
|
||||||
|
|
||||||
|
const lang = urlLang || (isZhPage ? 'zh-CN' : savedLang) || 'en';
|
||||||
|
setCurrentLang(lang);
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const changeLanguage = (lang: string) => {
|
||||||
|
setCurrentLang(lang);
|
||||||
|
localStorage.setItem('moodist-language', lang);
|
||||||
|
|
||||||
|
// Update URL and navigate if needed
|
||||||
|
const url = new URL(window.location.href);
|
||||||
|
|
||||||
|
if (lang === 'zh-CN') {
|
||||||
|
// Navigate to Chinese page
|
||||||
|
window.location.href = `${window.location.origin}/zh`;
|
||||||
|
} else {
|
||||||
|
// Navigate to English page
|
||||||
|
window.location.href = `${window.location.origin}/`;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return {
|
||||||
|
currentLang,
|
||||||
|
changeLanguage,
|
||||||
|
isChinese: currentLang === 'zh-CN'
|
||||||
|
};
|
||||||
|
}
|
||||||
35
src/hooks/useLocalizedSounds.ts
Normal file
35
src/hooks/useLocalizedSounds.ts
Normal file
|
|
@ -0,0 +1,35 @@
|
||||||
|
import { useMemo } from 'react';
|
||||||
|
import { sounds } from '@/data/sounds';
|
||||||
|
import type { Category, Sound } from '@/data/types';
|
||||||
|
import { useTranslation } from './useTranslation';
|
||||||
|
|
||||||
|
export function useLocalizedSounds() {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
|
||||||
|
const localizedSounds = useMemo(() => {
|
||||||
|
return sounds.categories.map((category: Category): Category => {
|
||||||
|
// Translate category title
|
||||||
|
const categoryKey = `categories.${category.id}`;
|
||||||
|
const translatedTitle = t(categoryKey as any);
|
||||||
|
|
||||||
|
// Translate sounds
|
||||||
|
const translatedSounds = category.sounds.map((sound: Sound): Sound => {
|
||||||
|
if (sound.dataI18n) {
|
||||||
|
return {
|
||||||
|
...sound,
|
||||||
|
label: t(sound.dataI18n as any) || sound.label
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return sound;
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
...category,
|
||||||
|
title: translatedTitle,
|
||||||
|
sounds: translatedSounds
|
||||||
|
};
|
||||||
|
});
|
||||||
|
}, [t]);
|
||||||
|
|
||||||
|
return localizedSounds;
|
||||||
|
}
|
||||||
43
src/hooks/useTranslation.ts
Normal file
43
src/hooks/useTranslation.ts
Normal file
|
|
@ -0,0 +1,43 @@
|
||||||
|
import { useLanguage } from './useLanguage';
|
||||||
|
import { getTranslation } from '@/data/i18n';
|
||||||
|
import type { Translations } from '@/data/i18n';
|
||||||
|
|
||||||
|
export function useTranslation() {
|
||||||
|
const { currentLang, isChinese, changeLanguage } = useLanguage();
|
||||||
|
const translations: Translations = getTranslation(currentLang);
|
||||||
|
|
||||||
|
const t = (key: keyof Translations, params?: Record<string, string | number>) => {
|
||||||
|
let translation = getNestedValue(translations, key) as string;
|
||||||
|
|
||||||
|
if (typeof translation !== 'string') {
|
||||||
|
// Fallback to English if translation is missing
|
||||||
|
const englishTranslations = getTranslation('en');
|
||||||
|
translation = getNestedValue(englishTranslations, key) as string;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typeof translation !== 'string') {
|
||||||
|
return key; // Return key if no translation found
|
||||||
|
}
|
||||||
|
|
||||||
|
// Replace parameters like {{count}}
|
||||||
|
if (params) {
|
||||||
|
return translation.replace(/\{\{(\w+)\}\}/g, (match, param) => {
|
||||||
|
return params[param]?.toString() || match;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return translation;
|
||||||
|
};
|
||||||
|
|
||||||
|
return {
|
||||||
|
t,
|
||||||
|
currentLang,
|
||||||
|
isChinese,
|
||||||
|
changeLanguage,
|
||||||
|
translations
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function getNestedValue(obj: any, path: string) {
|
||||||
|
return path.split('.').reduce((current, key) => current?.[key], obj);
|
||||||
|
}
|
||||||
|
|
@ -2,6 +2,7 @@
|
||||||
import { pwaInfo } from 'virtual:pwa-info'; // eslint-disable-line
|
import { pwaInfo } from 'virtual:pwa-info'; // eslint-disable-line
|
||||||
|
|
||||||
import { Reload } from '@/components/reload';
|
import { Reload } from '@/components/reload';
|
||||||
|
import { LanguageSwitcher } from '@/components/language-switcher';
|
||||||
|
|
||||||
import { count } from '@/lib/sounds';
|
import { count } from '@/lib/sounds';
|
||||||
|
|
||||||
|
|
@ -16,10 +17,15 @@ const title = Astro.props.title || 'Moodist: Ambient Sounds for Focus and Calm';
|
||||||
const description =
|
const description =
|
||||||
Astro.props.description ||
|
Astro.props.description ||
|
||||||
`Moodist is a free and open-source ambient sound generator featuring ${count()} carefully curated sounds. Create your ideal atmosphere for relaxation, focus, or creativity with this versatile tool.`;
|
`Moodist is a free and open-source ambient sound generator featuring ${count()} carefully curated sounds. Create your ideal atmosphere for relaxation, focus, or creativity with this versatile tool.`;
|
||||||
|
|
||||||
|
// Get language from URL parameter
|
||||||
|
const url = Astro.url;
|
||||||
|
const langParam = url.searchParams.get('lang') || 'en';
|
||||||
|
const lang = ['en', 'zh-CN'].includes(langParam) ? langParam : 'en';
|
||||||
---
|
---
|
||||||
|
|
||||||
<!doctype html>
|
<!doctype html>
|
||||||
<html lang="en">
|
<html lang={lang}>
|
||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8" />
|
<meta charset="UTF-8" />
|
||||||
<meta content="width=device-width" name="viewport" />
|
<meta content="width=device-width" name="viewport" />
|
||||||
|
|
@ -40,11 +46,36 @@ const description =
|
||||||
|
|
||||||
<meta content="summary_large_image" name="twitter:card" />
|
<meta content="summary_large_image" name="twitter:card" />
|
||||||
|
|
||||||
|
<!-- Hreflang tags for SEO -->
|
||||||
|
<link rel="alternate" hreflang="en" href="https://moodist.app" />
|
||||||
|
<link rel="alternate" hreflang="zh-CN" href="https://moodist.app?lang=zh-CN" />
|
||||||
|
<link rel="alternate" hreflang="x-default" href="https://moodist.app" />
|
||||||
|
|
||||||
{pwaInfo && <Fragment set:html={pwaInfo.webManifest.linkTag} />}
|
{pwaInfo && <Fragment set:html={pwaInfo.webManifest.linkTag} />}
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
|
<LanguageSwitcher client:load className="language-switcher-fixed" />
|
||||||
<slot />
|
<slot />
|
||||||
|
|
||||||
<Reload client:load />
|
<Reload client:load />
|
||||||
</body>
|
</body>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.language-switcher-fixed {
|
||||||
|
position: fixed;
|
||||||
|
top: 20px;
|
||||||
|
right: 20px;
|
||||||
|
z-index: 1000;
|
||||||
|
background: rgba(24, 24, 27, 0.8);
|
||||||
|
backdrop-filter: blur(10px);
|
||||||
|
border: 1px solid rgba(255, 255, 255, 0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 768px) {
|
||||||
|
.language-switcher-fixed {
|
||||||
|
top: 15px;
|
||||||
|
right: 15px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
</html>
|
</html>
|
||||||
|
|
|
||||||
|
|
@ -3,18 +3,26 @@ import Layout from '@/layouts/layout.astro';
|
||||||
|
|
||||||
import Donate from '@/components/donate.astro';
|
import Donate from '@/components/donate.astro';
|
||||||
import Hero from '@/components/hero.astro';
|
import Hero from '@/components/hero.astro';
|
||||||
import About from '@/components/about.astro';
|
import About from '@/components/about-unified.astro';
|
||||||
import Source from '@/components/source.astro';
|
|
||||||
import Footer from '@/components/footer.astro';
|
|
||||||
|
|
||||||
import { App } from '@/components/app';
|
import { App } from '@/components/app';
|
||||||
|
import { getTranslation } from '@/data/i18n';
|
||||||
|
|
||||||
|
// Get language from URL path
|
||||||
|
const url = Astro.url;
|
||||||
|
const pathname = url.pathname;
|
||||||
|
const isZhPage = pathname === '/zh' || pathname.startsWith('/zh/');
|
||||||
|
const lang = isZhPage ? 'zh-CN' : 'en';
|
||||||
|
const t = getTranslation(lang);
|
||||||
|
const pageTitle = lang === 'zh-CN' ? 'Moodist:专注与放松的环境音' : 'Moodist: Ambient Sounds for Focus and Calm';
|
||||||
|
const pageDesc = lang === 'zh-CN'
|
||||||
|
? 'Moodist 是一个免费开源的环境音生成器,提供精心挑选的声音,帮您创造放松、专注或创意的理想氛围。'
|
||||||
|
: 'Moodist is a free and open-source ambient sound generator featuring carefully curated sounds. Create your ideal atmosphere for relaxation, focus, or creativity with this versatile tool.';
|
||||||
---
|
---
|
||||||
|
|
||||||
<Layout title="Moodist: Ambient Sounds for Focus and Calm">
|
<Layout title={pageTitle} description={pageDesc}>
|
||||||
<Donate />
|
<Donate />
|
||||||
<Hero />
|
<Hero />
|
||||||
<App client:load />
|
<App client:load />
|
||||||
<About />
|
<About />
|
||||||
<Source />
|
</Layout>
|
||||||
<Footer />
|
|
||||||
</Layout>
|
|
||||||
21
src/pages/zh.astro
Normal file
21
src/pages/zh.astro
Normal file
|
|
@ -0,0 +1,21 @@
|
||||||
|
---
|
||||||
|
import Layout from '@/layouts/layout.astro';
|
||||||
|
|
||||||
|
import Donate from '@/components/donate.astro';
|
||||||
|
import Hero from '@/components/hero.astro';
|
||||||
|
import About from '@/components/about-unified.astro';
|
||||||
|
|
||||||
|
import { App } from '@/components/app';
|
||||||
|
import { getTranslation } from '@/data/i18n';
|
||||||
|
|
||||||
|
const t = getTranslation('zh-CN');
|
||||||
|
const pageTitle = 'Moodist:专注与放松的环境音';
|
||||||
|
const pageDesc = 'Moodist 是一个免费开源的环境音生成器,提供精心挑选的声音,帮您创造放松、专注或创意的理想氛围。';
|
||||||
|
---
|
||||||
|
|
||||||
|
<Layout title={pageTitle} description={pageDesc}>
|
||||||
|
<Donate />
|
||||||
|
<Hero />
|
||||||
|
<App client:load />
|
||||||
|
<About />
|
||||||
|
</Layout>
|
||||||
Loading…
Add table
Reference in a new issue