diff --git a/.github/scripts/generate-i18n-changes.mjs b/.github/scripts/generate-i18n-changes.mjs index 9fb3f979db9..5bd8eb30c26 100644 --- a/.github/scripts/generate-i18n-changes.mjs +++ b/.github/scripts/generate-i18n-changes.mjs @@ -8,7 +8,8 @@ const headSha = process.env.HEAD_SHA || '' const files = (process.env.CHANGED_FILES || '').split(/\s+/).filter(Boolean) const outputPath = process.env.I18N_CHANGES_OUTPUT_PATH || '/tmp/i18n-changes.json' -const englishPath = (fileStem) => path.join(repoRoot, 'web', 'i18n', 'en-US', `${fileStem}.json`) +const englishPath = (fileStem) => + path.join(repoRoot, 'web', 'i18n', 'locales', 'en-US', `${fileStem}.json`) const readCurrentJson = (fileStem) => { const filePath = englishPath(fileStem) @@ -21,7 +22,7 @@ const readBaseJson = (fileStem) => { if (!baseSha) return null try { - const relativePath = `web/i18n/en-US/${fileStem}.json` + const relativePath = `web/i18n/locales/en-US/${fileStem}.json` const content = execFileSync('git', ['show', `${baseSha}:${relativePath}`], { encoding: 'utf8', }) diff --git a/.github/workflows/post-merge.yml b/.github/workflows/post-merge.yml index 89c06f6e5ed..b84461ce3b4 100644 --- a/.github/workflows/post-merge.yml +++ b/.github/workflows/post-merge.yml @@ -100,8 +100,8 @@ jobs: - 'web/app/components/workflow/hooks/use-nodes-sync-draft.ts' - 'web/app/components/workflow/nodes/_base/**' - 'web/app/components/workflow/nodes/agent-v2/**' - - 'web/i18n/en-US/agent-v-2.json' - - 'web/i18n/en-US/common.json' + - 'web/i18n/locales/en-US/agent-v-2.json' + - 'web/i18n/locales/en-US/common.json' - 'web/service/base.ts' - 'web/service/console/**' - 'web/service/share.ts' diff --git a/.github/workflows/style.yml b/.github/workflows/style.yml index 087963d0b0b..d68fae1b98f 100644 --- a/.github/workflows/style.yml +++ b/.github/workflows/style.yml @@ -143,6 +143,11 @@ jobs: working-directory: . run: vp run knip + - name: Web build and unused translations check + env: + NODE_OPTIONS: --max-old-space-size=8192 + run: vp run build:vinext + - name: Web dead code check production working-directory: . run: vp run knip:production diff --git a/.github/workflows/translate-i18n-claude.yml b/.github/workflows/translate-i18n-claude.yml index f2d1114ae17..f9aef3c605a 100644 --- a/.github/workflows/translate-i18n-claude.yml +++ b/.github/workflows/translate-i18n-claude.yml @@ -69,7 +69,7 @@ jobs: /supported: true/ && value != \"en-US\" { printf \"%s \", value } - " web/i18n-config/languages.ts | sed 's/[[:space:]]*$//') + " web/i18n/languages.ts | sed 's/[[:space:]]*$//') generate_changes_json() { node .github/scripts/generate-i18n-changes.mjs @@ -110,9 +110,9 @@ jobs: elif [ "$SYNC_MODE" = "incremental" ]; then BASE_SHA=$(git rev-parse HEAD~1 2>/dev/null || true) if [ -n "$BASE_SHA" ]; then - CHANGED_FILES=$(git diff --name-only "$BASE_SHA" "$HEAD_SHA" -- 'web/i18n/en-US/*.json' 2>/dev/null | sed -n 's@^.*/@@p' | sed 's/\.json$//' | tr '\n' ' ' | sed 's/[[:space:]]*$//') + CHANGED_FILES=$(git diff --name-only --find-renames=100% --diff-filter=AMDT "$BASE_SHA" "$HEAD_SHA" -- 'web/i18n/' 2>/dev/null | sed -n '\@^web/i18n/locales/en-US/[^/]*\.json$@p' | sed -n 's@^.*/@@p' | sed 's/\.json$//' | tr '\n' ' ' | sed 's/[[:space:]]*$//') else - CHANGED_FILES=$(find web/i18n/en-US -maxdepth 1 -type f -name '*.json' -print | sed -n 's@^.*/@@p' | sed 's/\.json$//' | sort | tr '\n' ' ' | sed 's/[[:space:]]*$//') + CHANGED_FILES=$(find web/i18n/locales/en-US -maxdepth 1 -type f -name '*.json' -print | sed -n 's@^.*/@@p' | sed 's/\.json$//' | sort | tr '\n' ' ' | sed 's/[[:space:]]*$//') fi elif [ "$SYNC_MODE" = "full" ]; then echo "workflow_dispatch full mode requires the files input to stay within CI limits." >&2 @@ -170,12 +170,12 @@ jobs: show_full_output: ${{ github.event_name == 'workflow_dispatch' }} prompt: | You are the i18n sync agent for the Dify repository. - Your job is to keep translations synchronized with the English source files under `${{ github.workspace }}/web/i18n/en-US/`. + Your job is to keep translations synchronized with the English source files under `${{ github.workspace }}/web/i18n/locales/en-US/`. Use absolute paths at all times: - Repo root: `${{ github.workspace }}` - Web directory: `${{ github.workspace }}/web` - - Language config: `${{ github.workspace }}/web/i18n-config/languages.ts` + - Language config: `${{ github.workspace }}/web/i18n/languages.ts` Inputs: - Files in scope: `${{ steps.context.outputs.CHANGED_FILES }}` @@ -198,12 +198,12 @@ jobs: Required execution plan: 1. Resolve target languages. - Use the provided `Target languages` value as the source of truth. - - If it is unexpectedly empty, read `${{ github.workspace }}/web/i18n-config/languages.ts` and use every language with `supported: true` except `en-US`. + - If it is unexpectedly empty, read `${{ github.workspace }}/web/i18n/languages.ts` and use every language with `supported: true` except `en-US`. 2. Stay strictly in scope. - Only process the files listed in `Files in scope`. - Only process the resolved target languages, never `en-US`. - Do not touch unrelated i18n files. - - Do not modify `${{ github.workspace }}/web/i18n/en-US/`. + - Do not modify `${{ github.workspace }}/web/i18n/locales/en-US/`. 3. Resolve source changes. - If `Structured change set available` is `true`, read `/tmp/i18n-changes.json` and use it as the source of truth for file-level and key-level changes. - For each file entry: @@ -227,7 +227,7 @@ jobs: - Match the existing terminology and register used by each locale. - Prefer one Edit per file when stable, but prioritize correctness over batching. 6. Verify only the edited files. - - Run `vp fmt ` + - Run `vp fmt ` - Run `vp run dify-web#i18n:check ${{ steps.context.outputs.FILE_ARGS }} ${{ steps.context.outputs.LANG_ARGS }}` - If verification fails, fix the remaining problems before continuing. 7. Stop after the scoped locale files are updated and verification passes. @@ -241,7 +241,7 @@ jobs: if: steps.context.outputs.CHANGED_FILES != '' shell: bash run: | - if [ -z "$(git -C "${{ github.workspace }}" status --porcelain -- web/i18n/)" ]; then + if [ -z "$(git -C "${{ github.workspace }}" status --porcelain -- web/i18n/locales/)" ]; then echo "has_changes=false" >> "$GITHUB_OUTPUT" exit 0 fi @@ -260,7 +260,7 @@ jobs: shell: bash run: | git -C "${{ github.workspace }}" checkout -B "${{ steps.pr_meta.outputs.branch_name }}" - git -C "${{ github.workspace }}" add web/i18n/ + git -C "${{ github.workspace }}" add web/i18n/locales/ git -C "${{ github.workspace }}" commit -m "chore(i18n): sync translations with en-US" - name: Push translation branch @@ -326,7 +326,7 @@ jobs: '## Verification', '', `- \`vp run dify-web#i18n:check --file ${process.env.FILES_IN_SCOPE} --lang ${process.env.TARGET_LANGS}\``, - `- \`vp fmt \``, + `- \`vp fmt \``, '', '## Notes', '', diff --git a/.github/workflows/trigger-i18n-sync.yml b/.github/workflows/trigger-i18n-sync.yml index 6cb096562ff..c576094d3b8 100644 --- a/.github/workflows/trigger-i18n-sync.yml +++ b/.github/workflows/trigger-i18n-sync.yml @@ -4,7 +4,7 @@ on: push: branches: [main] paths: - - 'web/i18n/en-US/*.json' + - 'web/i18n/locales/en-US/*.json' permissions: contents: write @@ -36,9 +36,9 @@ jobs: HEAD_SHA="${{ github.sha }}" if [ -n "$BASE_SHA" ]; then - CHANGED_FILES=$(git diff --name-only "$BASE_SHA" "$HEAD_SHA" -- 'web/i18n/en-US/*.json' 2>/dev/null | sed -n 's@^.*/@@p' | sed 's/\.json$//' | tr '\n' ' ' | sed 's/[[:space:]]*$//') + CHANGED_FILES=$(git diff --name-only --find-renames=100% --diff-filter=AMDT "$BASE_SHA" "$HEAD_SHA" -- 'web/i18n/' 2>/dev/null | sed -n '\@^web/i18n/locales/en-US/[^/]*\.json$@p' | sed -n 's@^.*/@@p' | sed 's/\.json$//' | tr '\n' ' ' | sed 's/[[:space:]]*$//') else - CHANGED_FILES=$(find web/i18n/en-US -maxdepth 1 -type f -name '*.json' -print | sed -n 's@^.*/@@p' | sed 's/\.json$//' | sort | tr '\n' ' ' | sed 's/[[:space:]]*$//') + CHANGED_FILES=$(find web/i18n/locales/en-US -maxdepth 1 -type f -name '*.json' -print | sed -n 's@^.*/@@p' | sed 's/\.json$//' | sort | tr '\n' ' ' | sed 's/[[:space:]]*$//') fi export BASE_SHA HEAD_SHA CHANGED_FILES diff --git a/README.md b/README.md index a0f1dcb5654..0b4df324ba5 100644 --- a/README.md +++ b/README.md @@ -122,7 +122,7 @@ All of Dify's offerings come with corresponding APIs, so you could effortlessly Use our [documentation](https://docs.dify.ai) for further references and more in-depth instructions. - **[Dify Enterprise](https://dify.ai/dify-enterprise)
** - We provide additional enterprise-centric features. Fill out our [Enterprise form](https://share-na2.hsforms.com/14-09ff5HS92Sh4m3f4yrcw40s9fk) to discuss your enterprise needs.
+ For organizations requiring self-hosting, SSO, RBAC Security and Enterprise Support SLAs, please fill out [this form](https://share-na2.hsforms.com/14-09ff5HS92Sh4m3f4yrcw40s9fk) to speak to a solution representative.
## Staying ahead @@ -140,7 +140,7 @@ Dify welcomes contributions of all kinds: - **Code**: Read the [Contribution Guide](CONTRIBUTING.md), then browse [good first issues](https://github.com/langgenius/dify/issues?q=is%3Aissue%20state%3Aopen%20label%3A%22good%20first%20issue%22). - **Ideas and feedback**: Start or join a [GitHub Discussion](https://github.com/langgenius/dify/discussions). -- **Translations**: Follow the [internationalization guide](web/i18n-config/README.md) to add or update a locale. +- **Translations**: Follow the [internationalization guide](web/i18n/README.md) to add or update a locale. - **Community**: Share the apps you build, help other users, and spread the word about Dify. ### Contributors diff --git a/api/commands/storage.py b/api/commands/storage.py index f23b17680a0..a21b66aa4e8 100644 --- a/api/commands/storage.py +++ b/api/commands/storage.py @@ -751,7 +751,7 @@ def migrate_oss( UploadFile.storage_type == source_storage_type, UploadFile.key.in_(copied_upload_file_keys), ) - .values(storage_type=dify_config.STORAGE_TYPE) + .values(storage_type=StorageType(dify_config.STORAGE_TYPE)) ), ).rowcount db.session.commit() diff --git a/api/controllers/console/app/conversation.py b/api/controllers/console/app/conversation.py index 2f0977b39f8..6b136bacb2d 100644 --- a/api/controllers/console/app/conversation.py +++ b/api/controllers/console/app/conversation.py @@ -406,9 +406,11 @@ def _get_conversation(session: Session, current_user: Account, app_model, conver .where(Conversation.id == conversation_id, Conversation.read_at.is_(None)) # Keep updated_at unchanged when only marking a conversation as read. .values( - read_at=naive_utc_now(), - read_account_id=current_user.id, - updated_at=Conversation.updated_at, + { + Conversation.read_at: naive_utc_now(), + Conversation.read_account_id: current_user.id, + Conversation.updated_at: Conversation.updated_at, + } ) ) session.flush() diff --git a/api/controllers/console/app/workflow_run.py b/api/controllers/console/app/workflow_run.py index 66873a9b523..08097d6d43e 100644 --- a/api/controllers/console/app/workflow_run.py +++ b/api/controllers/console/app/workflow_run.py @@ -3,11 +3,13 @@ from uuid import UUID from flask_restx import Resource from pydantic import BaseModel, Field, field_validator +from sqlalchemy.orm import Session from configs import dify_config from controllers.common.errors import NotFoundError from controllers.common.rbac import PlainApp, RBACCheck from controllers.common.schema import query_params_from_model, register_response_schema_models, register_schema_models +from controllers.common.session import with_session from controllers.console import console_ns from controllers.console.app.wraps import get_app_model from controllers.console.flask_admission import console_account_admission @@ -24,6 +26,8 @@ from fields.workflow_run_fields import ( WorkflowRunNodeExecutionListResponse, WorkflowRunNodeExecutionResponse, WorkflowRunPaginationResponse, + workflow_run_pagination_response_source, + workflow_run_response_source, ) from libs.custom_inputs import time_duration from libs.helper import dump_response, uuid_value @@ -147,7 +151,8 @@ class AdvancedChatAppWorkflowRunListApi(Resource): ) @get_app_model(mode=[AppMode.ADVANCED_CHAT]) @model_validate(WorkflowRunListQuery) - def get(self, req_data: WorkflowRunListQuery, request_context: RequestContext, app_model: App): + @with_session(write=False) + def get(self, session: Session, req_data: WorkflowRunListQuery, request_context: RequestContext, app_model: App): """ Get advanced chat app workflow run list """ @@ -158,7 +163,10 @@ class AdvancedChatAppWorkflowRunListApi(Resource): triggered_from=_triggered_from(req_data.triggered_from), ) - return dump_response(AdvancedChatWorkflowRunPaginationResponse, result) + return dump_response( + AdvancedChatWorkflowRunPaginationResponse, + workflow_run_pagination_response_source(result, session=session), + ) @console_ns.route("/apps//advanced-chat/workflow-runs/count") @@ -208,7 +216,8 @@ class WorkflowRunListApi(Resource): ) @get_app_model(mode=[AppMode.ADVANCED_CHAT, AppMode.WORKFLOW]) @model_validate(WorkflowRunListQuery) - def get(self, req_data: WorkflowRunListQuery, request_context: RequestContext, app_model: App): + @with_session(write=False) + def get(self, session: Session, req_data: WorkflowRunListQuery, request_context: RequestContext, app_model: App): """ Get workflow run list """ @@ -219,7 +228,9 @@ class WorkflowRunListApi(Resource): triggered_from=_triggered_from(req_data.triggered_from), ) - return dump_response(WorkflowRunPaginationResponse, result) + return dump_response( + WorkflowRunPaginationResponse, workflow_run_pagination_response_source(result, session=session) + ) @console_ns.route("/apps//workflow-runs/count") @@ -267,8 +278,9 @@ class WorkflowRunDetailApi(Resource): @console_account_admission( rbac_checks=[RBACCheck(RBACPermission.APP_CREATE_AND_MANAGEMENT, PlainApp())], ) + @with_session(write=False) @get_app_model(mode=[AppMode.ADVANCED_CHAT, AppMode.WORKFLOW]) - def get(self, request_context: RequestContext, app_model: App, run_id: UUID): + def get(self, session: Session, request_context: RequestContext, app_model: App, run_id: UUID): """ Get workflow run detail """ @@ -280,7 +292,7 @@ class WorkflowRunDetailApi(Resource): if workflow_run is None: raise NotFoundError("Workflow run not found") - return dump_response(WorkflowRunDetailResponse, workflow_run) + return dump_response(WorkflowRunDetailResponse, workflow_run_response_source(workflow_run, session=session)) @console_ns.route("/apps//workflow-runs//node-executions") diff --git a/api/controllers/console/datasets/data_source.py b/api/controllers/console/datasets/data_source.py index f5506e36850..39b1d454f13 100644 --- a/api/controllers/console/datasets/data_source.py +++ b/api/controllers/console/datasets/data_source.py @@ -157,7 +157,7 @@ class DataSourceApi(Resource): data_source_oauth_base_path = "/console/api/oauth/data-source" providers = ["notion"] - integrate_data = [] + integrate_data: list[dict[str, bool | datetime | dict[str, Any] | str | None]] = [] for provider in providers: # existing_integrate = next((ai for ai in data_source_integrates if ai.provider == provider), None) existing_integrates = filter(lambda item: item.provider == provider, data_source_integrates) diff --git a/api/controllers/console/datasets/rag_pipeline/rag_pipeline_workflow.py b/api/controllers/console/datasets/rag_pipeline/rag_pipeline_workflow.py index 230cc5a3cd5..4df2e49b5ec 100644 --- a/api/controllers/console/datasets/rag_pipeline/rag_pipeline_workflow.py +++ b/api/controllers/console/datasets/rag_pipeline/rag_pipeline_workflow.py @@ -54,6 +54,8 @@ from fields.workflow_run_fields import ( WorkflowRunNodeExecutionResponse, WorkflowRunPaginationResponse, node_execution_response_source, + workflow_run_pagination_response_source, + workflow_run_response_source, ) from graphon.model_runtime.utils.encoders import jsonable_encoder from libs import helper @@ -199,12 +201,12 @@ class DraftRagPipelineApi(Resource): @get_rag_pipeline @edit_permission_required @rbac_permission_required(RBACCheck(RBACPermission.DATASET_EDIT, DatasetByPipeline())) - def get(self, pipeline: Pipeline): + @with_session(write=False) + def get(self, session: Session, pipeline: Pipeline): """ Get draft rag pipeline's workflow """ # fetch draft workflow by app_model - session = db.session() rag_pipeline_service = RagPipelineService(session) workflow = rag_pipeline_service.get_draft_workflow(pipeline=pipeline) @@ -913,7 +915,8 @@ class RagPipelineWorkflowRunListApi(Resource): @login_required @account_initialization_required @get_rag_pipeline - def get(self, pipeline: Pipeline): + @with_session(write=False) + def get(self, session: Session, pipeline: Pipeline): """ Get workflow run list """ @@ -928,10 +931,12 @@ class RagPipelineWorkflowRunListApi(Resource): "limit": query.limit, } - rag_pipeline_service = RagPipelineService(db.session()) + rag_pipeline_service = RagPipelineService(session) result = rag_pipeline_service.get_rag_pipeline_paginate_workflow_runs(pipeline=pipeline, args=args) - return WorkflowRunPaginationResponse.model_validate(result, from_attributes=True).model_dump(mode="json") + return WorkflowRunPaginationResponse.model_validate( + workflow_run_pagination_response_source(result, session=session), from_attributes=True + ).model_dump(mode="json") @console_ns.route("/rag/pipelines//workflow-runs/") @@ -945,18 +950,21 @@ class RagPipelineWorkflowRunDetailApi(Resource): @login_required @account_initialization_required @get_rag_pipeline - def get(self, pipeline: Pipeline, run_id: UUID): + @with_session(write=False) + def get(self, session: Session, pipeline: Pipeline, run_id: UUID): """ Get workflow run detail """ run_id_str = str(run_id) - rag_pipeline_service = RagPipelineService(db.session()) + rag_pipeline_service = RagPipelineService(session) workflow_run = rag_pipeline_service.get_rag_pipeline_workflow_run(pipeline=pipeline, run_id=run_id_str) if workflow_run is None: raise NotFound("Workflow run not found") - return WorkflowRunDetailResponse.model_validate(workflow_run, from_attributes=True).model_dump(mode="json") + return WorkflowRunDetailResponse.model_validate( + workflow_run_response_source(workflow_run, session=session), from_attributes=True + ).model_dump(mode="json") @console_ns.route("/rag/pipelines//workflow-runs//node-executions") diff --git a/api/controllers/console/snippets/snippet_workflow.py b/api/controllers/console/snippets/snippet_workflow.py index 5cc8d42d5a6..d4a6497ad7a 100644 --- a/api/controllers/console/snippets/snippet_workflow.py +++ b/api/controllers/console/snippets/snippet_workflow.py @@ -12,6 +12,7 @@ from controllers.common.controller_schemas import WorkflowUpdatePayload from controllers.common.fields import GeneratedAppResponse, SimpleResultResponse from controllers.common.rbac import RBACCheck, Workspace from controllers.common.schema import query_params_from_model, register_response_schema_models, register_schema_models +from controllers.common.session import with_session from controllers.console import console_ns from controllers.console.app.error import DraftWorkflowNotExist, DraftWorkflowNotSync from controllers.console.app.workflow import ( @@ -52,6 +53,8 @@ from fields.workflow_run_fields import ( WorkflowRunNodeExecutionResponse, WorkflowRunPaginationResponse, node_execution_response_source, + workflow_run_pagination_response_source, + workflow_run_response_source, ) from graphon.graph_engine.manager import GraphEngineManager from libs import helper @@ -512,7 +515,8 @@ class SnippetWorkflowRunsApi(Resource): @login_required @account_initialization_required @get_snippet - def get(self, snippet: CustomizedSnippet): + @with_session(write=False) + def get(self, session: Session, snippet: CustomizedSnippet): """List workflow runs for snippet.""" query = WorkflowRunQuery.model_validate( { @@ -528,7 +532,9 @@ class SnippetWorkflowRunsApi(Resource): snippet_service = _snippet_service() result = snippet_service.get_snippet_workflow_runs(snippet=snippet, args=args) - return WorkflowRunPaginationResponse.model_validate(result, from_attributes=True).model_dump(mode="json") + return WorkflowRunPaginationResponse.model_validate( + workflow_run_pagination_response_source(result, session=session), from_attributes=True + ).model_dump(mode="json") @console_ns.route("/snippets//workflow-runs/") @@ -544,7 +550,8 @@ class SnippetWorkflowRunDetailApi(Resource): @login_required @account_initialization_required @get_snippet - def get(self, snippet: CustomizedSnippet, run_id): + @with_session(write=False) + def get(self, session: Session, snippet: CustomizedSnippet, run_id): """Get workflow run detail for snippet.""" run_id = str(run_id) @@ -554,7 +561,9 @@ class SnippetWorkflowRunDetailApi(Resource): if not workflow_run: raise NotFound("Workflow run not found") - return WorkflowRunDetailResponse.model_validate(workflow_run, from_attributes=True).model_dump(mode="json") + return WorkflowRunDetailResponse.model_validate( + workflow_run_response_source(workflow_run, session=session), from_attributes=True + ).model_dump(mode="json") @console_ns.route("/snippets//workflow-runs//node-executions") diff --git a/api/controllers/openapi/_models.py b/api/controllers/openapi/_models.py index c5e8a22d466..530f2afeb0d 100644 --- a/api/controllers/openapi/_models.py +++ b/api/controllers/openapi/_models.py @@ -3,7 +3,7 @@ from __future__ import annotations from enum import StrEnum -from typing import Any, Final, Literal +from typing import Any, Final, Literal, Self from pydantic import BaseModel, ConfigDict, Field, field_validator, model_validator @@ -51,7 +51,7 @@ class MessageMetadata(BaseModel): class PaginationEnvelope[T](BaseModel): - """Canonical pagination envelope for `/openapi/v1/*` list endpoints.""" + """The one shape every paginated list on this surface answers with.""" page: int limit: int @@ -60,7 +60,7 @@ class PaginationEnvelope[T](BaseModel): data: list[T] @classmethod - def build(cls, *, page: int, limit: int, total: int, items: list[T]) -> PaginationEnvelope[T]: + def build(cls, *, page: int, limit: int, total: int, items: list[T]) -> Self: return cls(page=page, limit=limit, total=total, has_more=page * limit < total, data=items) @@ -74,20 +74,12 @@ class AppListRow(BaseModel): workspace_name: str | None = None -class AppListResponse(BaseModel): - page: int - limit: int - total: int - has_more: bool - data: list[AppListRow] +class AppListResponse(PaginationEnvelope[AppListRow]): + pass -class PermittedExternalAppsListResponse(BaseModel): - page: int - limit: int - total: int - has_more: bool - data: list[AppListRow] +class PermittedExternalAppsListResponse(PaginationEnvelope[AppListRow]): + pass class AppInfo(BaseModel): @@ -109,29 +101,6 @@ class AppDescribeResponse(BaseModel): input_schema: dict[str, Any] | None = Field(default=None) -class ChatMessageResponse(BaseModel): - event: str - task_id: str - id: str - message_id: str - conversation_id: str - mode: str - answer: str - metadata: MessageMetadata = Field(default_factory=MessageMetadata) - created_at: int - - -class CompletionMessageResponse(BaseModel): - event: str - task_id: str - id: str - message_id: str - mode: str - answer: str - metadata: MessageMetadata = Field(default_factory=MessageMetadata) - created_at: int - - class WorkflowRunData(BaseModel): id: str workflow_id: str @@ -145,13 +114,6 @@ class WorkflowRunData(BaseModel): finished_at: int | None = None -class WorkflowRunResponse(BaseModel): - workflow_run_id: str - task_id: str - mode: Literal["workflow"] = "workflow" - data: WorkflowRunData - - class AccountPayload(BaseModel): id: str email: str @@ -195,12 +157,8 @@ class SessionRow(BaseModel): expires_at: str | None = None -class SessionListResponse(BaseModel): - page: int - limit: int - total: int - has_more: bool - data: list[SessionRow] +class SessionListResponse(PaginationEnvelope[SessionRow]): + pass class SessionListQuery(BaseModel): @@ -398,12 +356,8 @@ class MemberResponse(BaseModel): avatar: str | None = None -class MemberListResponse(BaseModel): - page: int - limit: int - total: int - has_more: bool - data: list[MemberResponse] +class MemberListResponse(PaginationEnvelope[MemberResponse]): + pass class MemberListQuery(BaseModel): diff --git a/api/controllers/openapi/apps.py b/api/controllers/openapi/apps.py index 5e1abe27b98..2675cac0400 100644 --- a/api/controllers/openapi/apps.py +++ b/api/controllers/openapi/apps.py @@ -37,8 +37,6 @@ from models.model import AppMode from services.account_service import TenantService from services.app_service import AppListParams, AppService -_ALLOWED_DESCRIBE_FIELDS: frozenset[str] = frozenset({"info", "parameters", "input_schema"}) - def _is_listable(app: App) -> bool: """Whether the openapi app face exposes this app (curated, listable types only).""" @@ -155,7 +153,7 @@ class AppListApi(Resource): def get(self, session: Session, *, auth_data: AuthData, query: AppListQuery): workspace_id = query.workspace_id - empty = AppListResponse(page=query.page, limit=query.limit, total=0, has_more=False, data=[]) + empty = AppListResponse.build(page=query.page, limit=query.limit, total=0, items=[]) if query.name: try: @@ -205,7 +203,7 @@ class AppListApi(Resource): workspace_id=str(workspace_id), workspace_name=tenant_name, ) - env = AppListResponse(page=1, limit=1, total=1, has_more=False, data=[item]) + env = AppListResponse.build(page=1, limit=1, total=1, items=[item]) return env params = AppListParams( @@ -244,11 +242,5 @@ class AppListApi(Resource): if _is_listable(r) ] - env = AppListResponse( - page=query.page, - limit=query.limit, - total=pagination.total, - has_more=query.page * query.limit < pagination.total, - data=items, - ) + env = AppListResponse.build(page=query.page, limit=query.limit, total=pagination.total, items=items) return env diff --git a/api/controllers/openapi/apps_permitted_external.py b/api/controllers/openapi/apps_permitted_external.py index a56ab4ae621..284ec0713d9 100644 --- a/api/controllers/openapi/apps_permitted_external.py +++ b/api/controllers/openapi/apps_permitted_external.py @@ -52,8 +52,8 @@ class PermittedExternalAppsListApi(Resource): ) if not page_result.app_ids: - env = PermittedExternalAppsListResponse( - page=query.page, limit=query.limit, total=page_result.total, has_more=False, data=[] + env = PermittedExternalAppsListResponse.build( + page=query.page, limit=query.limit, total=page_result.total, items=[] ) return env @@ -80,12 +80,8 @@ class PermittedExternalAppsListApi(Resource): workspace_name=tenant.name if tenant else None, ) ) - env = PermittedExternalAppsListResponse( - page=query.page, - limit=query.limit, - total=page_result.total, - has_more=query.page * query.limit < page_result.total, - data=items, + env = PermittedExternalAppsListResponse.build( + page=query.page, limit=query.limit, total=page_result.total, items=items ) return env diff --git a/api/controllers/openapi/auth/data.py b/api/controllers/openapi/auth/data.py index ab1d311fb06..de6f6eaf0fa 100644 --- a/api/controllers/openapi/auth/data.py +++ b/api/controllers/openapi/auth/data.py @@ -42,10 +42,8 @@ class AuthData(BaseModel): required_scope: Scope | None = None token_type: TokenType account_id: uuid.UUID | None = None - token_hash: str token_id: uuid.UUID | None = None scopes: frozenset[Scope] - tenants: dict[str, bool] = Field(default_factory=dict) external_identity: ExternalIdentity | None = None path_params: dict[str, str] = Field(default_factory=dict) diff --git a/api/controllers/openapi/auth/pipeline.py b/api/controllers/openapi/auth/pipeline.py index 12c3c9dce6c..bb32927725d 100644 --- a/api/controllers/openapi/auth/pipeline.py +++ b/api/controllers/openapi/auth/pipeline.py @@ -75,10 +75,8 @@ class AuthPipeline: data = AuthData( token_type=identity.token_type, account_id=identity.account_id, - token_hash=identity.token_hash, token_id=identity.token_id, scopes=frozenset(identity.scopes), - tenants=dict(identity.verified_tenants), required_scope=scope, allowed_roles=allowed_roles, rbac=rbac, diff --git a/api/controllers/openapi/auth/surface_gate.py b/api/controllers/openapi/auth/surface_gate.py deleted file mode 100644 index f3d2d0735bb..00000000000 --- a/api/controllers/openapi/auth/surface_gate.py +++ /dev/null @@ -1,90 +0,0 @@ -"""Surface gate. - -`@accept_subjects(...)` is the route-level form. `SurfaceCheck` (pipeline -step) is the pipeline-level form. Both delegate to `check_surface` so the -audit emit + canonical-path message are single-sourced. - -Subjects come from `libs.oauth_bearer.SubjectType` directly — no parallel -vocabulary. Caller hits the wrong surface → 403 ``wrong_surface`` + audit -``openapi.wrong_surface_denied``. -""" - -from __future__ import annotations - -from collections.abc import Callable -from functools import wraps -from typing import TypeVar - -from flask import request -from werkzeug.exceptions import Forbidden - -from controllers.openapi._audit import emit_wrong_surface -from libs.oauth_bearer import SubjectType, try_get_auth_ctx - -_CANONICAL_PATH: dict[SubjectType, str] = { - SubjectType.ACCOUNT: "/openapi/v1/apps", - SubjectType.EXTERNAL_SSO: "/openapi/v1/permitted-external-apps", -} - -F = TypeVar("F", bound=Callable[..., object]) - - -def check_surface(accepted: frozenset[SubjectType]) -> None: - """Enforce that the resolved subject is in ``accepted``. - - Reads the openapi auth ContextVar via :func:`try_get_auth_ctx`. Raises - ``Forbidden`` with ``wrong_surface`` + canonical-path hint on miss; - emits ``openapi.wrong_surface_denied`` audit. If no auth context is - set the bearer layer didn't run — that's a wiring bug, not a - user-driven failure, so surface it as a ``RuntimeError`` instead of - a silent 403. - """ - ctx = try_get_auth_ctx() - if ctx is None: - raise RuntimeError( - "check_surface called without an auth context; stack validate_bearer or BearerCheck above the surface gate" - ) - - subject = _coerce_subject_type(getattr(ctx, "subject_type", None)) - if subject in accepted: - return - - canonical = _CANONICAL_PATH.get(subject, "/openapi/v1/") if subject else "/openapi/v1/" - emit_wrong_surface( - subject_type=subject.value if subject else None, - attempted_path=request.path, - client_id=getattr(ctx, "client_id", None), - token_id=_stringify(getattr(ctx, "token_id", None)), - ) - raise Forbidden(description=f"wrong_surface (canonical: {canonical})") - - -def accept_subjects(*accepted: SubjectType) -> Callable[[F], F]: - accepted_set: frozenset[SubjectType] = frozenset(accepted) - - def deco(fn: F) -> F: - @wraps(fn) - def wrapper(*args: object, **kwargs: object) -> object: - check_surface(accepted_set) - return fn(*args, **kwargs) - - return wrapper # type: ignore[return-value] - - return deco - - -def _coerce_subject_type(raw: object) -> SubjectType | None: - match raw: - case None: - return None - case SubjectType(): - return raw - case str(): - return SubjectType(raw) - return None - - -def _stringify(value: object) -> str | None: - if value is None: - return None - return str(value) diff --git a/api/controllers/openapi/auth/verify.py b/api/controllers/openapi/auth/verify.py index 7420841a8f3..c669bfaecd9 100644 --- a/api/controllers/openapi/auth/verify.py +++ b/api/controllers/openapi/auth/verify.py @@ -8,7 +8,7 @@ from controllers.common.rbac import enforce_rbac_checks from controllers.openapi.auth.data import AuthData, CallerKind from extensions.ext_database import db from libs.oauth_bearer import Scope, TokenType -from services.account_service import AccountService, TenantService +from services.account_service import AccountService from services.enterprise.enterprise_service import EnterpriseService, WebAppAccessMode @@ -77,13 +77,6 @@ def check_app_api_enabled(data: AuthData) -> None: raise Forbidden("service_api_disabled") -def check_app_access(data: AuthData) -> None: - if data.tenant is None: - return - if not TenantService.account_belongs_to_tenant(data.account_id, data.tenant.id, session=db.session()): - raise Forbidden("subject_no_app_access") - - _ALLOWED_MODES_BY_TOKEN_TYPE: dict[TokenType, frozenset[WebAppAccessMode]] = { TokenType.OAUTH_ACCOUNT: frozenset( { diff --git a/api/controllers/openapi/workspaces.py b/api/controllers/openapi/workspaces.py index 6bff0349f78..6053e09bf28 100644 --- a/api/controllers/openapi/workspaces.py +++ b/api/controllers/openapi/workspaces.py @@ -163,12 +163,11 @@ class WorkspaceMembersApi(Resource): total = len(members) start = (query.page - 1) * query.limit page_items = members[start : start + query.limit] - return MemberListResponse( + return MemberListResponse.build( page=query.page, limit=query.limit, total=total, - has_more=query.page * query.limit < total, - data=[_member_response(m) for m in page_items], + items=[_member_response(m) for m in page_items], ) @auth_router.guard_workspace( diff --git a/api/core/rag/extractor/notion_extractor.py b/api/core/rag/extractor/notion_extractor.py index f0c30b36d7e..f8b849255c1 100644 --- a/api/core/rag/extractor/notion_extractor.py +++ b/api/core/rag/extractor/notion_extractor.py @@ -358,7 +358,7 @@ class NotionExtractor(BaseExtractor): db.session.execute( update(DocumentModel) .where(DocumentModel.id == document_model.id) - .values(data_source_info=json.dumps(data_source_info)) + .values({DocumentModel.data_source_info: json.dumps(data_source_info)}) ) db.session.commit() diff --git a/api/core/rag/index_processor/index_processor.py b/api/core/rag/index_processor/index_processor.py index dc951c554fe..2ab23ae3a82 100644 --- a/api/core/rag/index_processor/index_processor.py +++ b/api/core/rag/index_processor/index_processor.py @@ -15,6 +15,7 @@ from core.rag.index_processor.index_processor_base import SummaryIndexSettingDic from core.workflow.nodes.knowledge_index.exc import KnowledgeIndexNodeError from core.workflow.nodes.knowledge_index.protocols import IndexingResultDict, Preview, PreviewItem, QaPreview from models.dataset import Dataset, Document, DocumentSegment +from models.enums import SegmentStatus from services.vector_space_admission_service import VectorSpaceAdmissionService from .index_processor_factory import IndexProcessorFactory @@ -157,7 +158,7 @@ class IndexProcessor: DocumentSegment.tenant_id == dataset.tenant_id, ) .values( - status="completed", + status=SegmentStatus.COMPLETED, enabled=True, completed_at=datetime.datetime.now(datetime.UTC).replace(tzinfo=None), ) diff --git a/api/core/tools/tool_engine.py b/api/core/tools/tool_engine.py index d9761eb4dbd..9c747acc2f8 100644 --- a/api/core/tools/tool_engine.py +++ b/api/core/tools/tool_engine.py @@ -273,8 +273,15 @@ class ToolEngine: # Add JSON parts, avoiding duplicates from text parts. if json_parts: - existing_parts = set(parts) - parts.extend(p for p in json_parts if p not in existing_parts) + + def normalize_json(text: str) -> str: + try: + return json.dumps(json.loads(text), ensure_ascii=False, sort_keys=True, separators=(",", ":")) + except (ValueError, RecursionError): + return text + + existing_parts = {normalize_json(p) for p in parts} + parts.extend(p for p in json_parts if normalize_json(p) not in existing_parts) return "".join(parts) diff --git a/api/fields/base.py b/api/fields/base.py index 826a07d00ea..c8c1b8c8d56 100644 --- a/api/fields/base.py +++ b/api/fields/base.py @@ -1,7 +1,27 @@ from __future__ import annotations +from typing import TYPE_CHECKING + from pydantic import BaseModel, ConfigDict +if TYPE_CHECKING: + from sqlalchemy.orm import Session + + +class SessionResponseSource[SourceT]: + """Wrap a model so session-backed accessors resolve during response validation. + + Subclasses override the accessors that need the session as properties; + every other attribute is delegated to the wrapped model unchanged. + """ + + def __init__(self, source: SourceT, *, session: Session) -> None: + self._source = source + self._session = session + + def __getattr__(self, name: str) -> object: + return getattr(self._source, name) # guard-ignore: no-new-getattr -- delegates model fields + class ResponseModel(BaseModel): model_config = ConfigDict( diff --git a/api/fields/conversation_fields.py b/api/fields/conversation_fields.py index 37cd8cd6214..bb016b9ca53 100644 --- a/api/fields/conversation_fields.py +++ b/api/fields/conversation_fields.py @@ -5,9 +5,8 @@ from datetime import datetime from typing import Annotated, Any from pydantic import Field, WithJsonSchema, field_validator, model_validator -from sqlalchemy.orm import Session -from fields.base import ResponseModel +from fields.base import ResponseModel, SessionResponseSource from graphon.file import File from libs.helper import to_timestamp from models.account import Account @@ -31,22 +30,13 @@ Int64 = Annotated[int, WithJsonSchema({"format": "int64", "type": "integer"})] URLString = Annotated[str, WithJsonSchema({"format": "url", "type": "string"})] -class _SessionResponseSource[SourceT]: - def __init__(self, source: SourceT, *, session: Session) -> None: - self._source = source - self._session = session - - def __getattr__(self, name: str) -> object: - return getattr(self._source, name) # guard-ignore: no-new-getattr -- delegates model fields - - -class _FeedbackResponseSource(_SessionResponseSource[MessageFeedback]): +class _FeedbackResponseSource(SessionResponseSource[MessageFeedback]): @property def from_account(self) -> Account | None: return self._source.from_account_with_session(session=self._session) -class _AnnotationResponseSource(_SessionResponseSource[MessageAnnotation]): +class _AnnotationResponseSource(SessionResponseSource[MessageAnnotation]): @property def account(self) -> Account | None: return self._source.account_with_session(session=self._session) @@ -56,7 +46,7 @@ class _AnnotationResponseSource(_SessionResponseSource[MessageAnnotation]): return self._source.annotation_create_account_with_session(session=self._session) -class MessageResponseSource(_SessionResponseSource[MessageModel]): +class MessageResponseSource(SessionResponseSource[MessageModel]): @property def inputs(self) -> dict[str, Any]: return self._source.inputs_with_session(session=self._session) @@ -91,7 +81,7 @@ class MessageResponseSource(_SessionResponseSource[MessageModel]): return self._source.message_files_with_session(session=self._session) -class ConversationResponseSource(_SessionResponseSource[ConversationModel]): +class ConversationResponseSource(SessionResponseSource[ConversationModel]): @property def inputs(self) -> dict[str, Any]: return self._source.inputs_with_session(session=self._session) diff --git a/api/fields/workflow_run_fields.py b/api/fields/workflow_run_fields.py index 55caa4893f9..4b69c1a107a 100644 --- a/api/fields/workflow_run_fields.py +++ b/api/fields/workflow_run_fields.py @@ -6,7 +6,6 @@ Pydantic models in this module. from __future__ import annotations -from dataclasses import dataclass from datetime import datetime from enum import Enum from typing import Any @@ -14,7 +13,7 @@ from typing import Any from pydantic import AliasChoices, Field, field_validator from sqlalchemy.orm import Session -from fields.base import ResponseModel +from fields.base import ResponseModel, SessionResponseSource from fields.end_user_fields import SimpleEndUser from fields.member_fields import SimpleAccount from libs.helper import to_timestamp @@ -185,27 +184,45 @@ class WorkflowRunNodeExecutionResponse(ResponseModel): return to_timestamp(value) -@dataclass(frozen=True) -class WorkflowNodeExecutionResponseSource: - """Expose session-backed node-execution accessors during response validation.""" - - node_execution: Any - session: Session +class WorkflowRunResponseSource(SessionResponseSource[Any]): + """Expose session-backed workflow-run accessors during response validation.""" @property def created_by_account(self) -> Any: - return self.node_execution.created_by_account(self.session) + return self._source.created_by_account(self._session) @property def created_by_end_user(self) -> Any: - return self.node_execution.created_by_end_user(self.session) + return self._source.created_by_end_user(self._session) - def __getattr__(self, name: str) -> Any: - return getattr(self.node_execution, name) # guard-ignore: no-new-getattr -- delegates model fields + +def workflow_run_response_source(workflow_run: Any, *, session: Session) -> WorkflowRunResponseSource: + return WorkflowRunResponseSource(workflow_run, session=session) + + +def workflow_run_pagination_response_source(pagination: Any, *, session: Session) -> dict[str, Any]: + """Wrap each run in a pagination payload so list responses resolve accessors via the session.""" + return { + "limit": pagination.limit, + "has_more": pagination.has_more, + "data": [workflow_run_response_source(run, session=session) for run in pagination.data], + } + + +class WorkflowNodeExecutionResponseSource(SessionResponseSource[Any]): + """Expose session-backed node-execution accessors during response validation.""" + + @property + def created_by_account(self) -> Any: + return self._source.created_by_account(self._session) + + @property + def created_by_end_user(self) -> Any: + return self._source.created_by_end_user(self._session) def node_execution_response_source(node_execution: Any, *, session: Session) -> WorkflowNodeExecutionResponseSource: - return WorkflowNodeExecutionResponseSource(node_execution=node_execution, session=session) + return WorkflowNodeExecutionResponseSource(node_execution, session=session) class WorkflowRunNodeExecutionListResponse(ResponseModel): diff --git a/api/libs/oauth_bearer.py b/api/libs/oauth_bearer.py index 89ede4b8a12..bfd96650b8d 100644 --- a/api/libs/oauth_bearer.py +++ b/api/libs/oauth_bearer.py @@ -1,9 +1,4 @@ -"""OAuth bearer primitives. - -To add a token kind: write a Resolver, add a SubjectType + Accepts member, -append a TokenKind to build_registry, and update _SUBJECT_TO_ACCEPT. -Authenticator + validate_bearer stay untouched. -""" +"""OAuth bearer primitives.""" from __future__ import annotations @@ -13,23 +8,19 @@ import logging import uuid from collections.abc import Callable, Iterable from contextvars import ContextVar, Token -from dataclasses import dataclass, field +from dataclasses import dataclass from datetime import UTC, datetime from enum import StrEnum from functools import wraps -from typing import Literal, ParamSpec, Protocol, TypeVar +from typing import Literal, Protocol -from flask import request -from sqlalchemy import select, update +from sqlalchemy import update from sqlalchemy.orm import Session -from werkzeug.exceptions import Forbidden, ServiceUnavailable, Unauthorized +from werkzeug.exceptions import ServiceUnavailable from configs import dify_config -from enums import DeploymentEdition -from extensions.ext_database import db -from extensions.ext_redis import redis_client from libs.rate_limit import enforce_bearer_rate_limit -from models import Account, OAuthAccessToken, TenantAccountJoin +from models import OAuthAccessToken logger = logging.getLogger(__name__) @@ -53,8 +44,8 @@ class Scope(StrEnum): """Catalog of bearer scopes recognised by the openapi surface. `FULL` is the catch-all carried by `dfoa_` account tokens — it satisfies - any per-route `require_scope`. `dfoe_` tokens carry the per-feature scopes - (`APPS_RUN`, `APPS_READ_PERMITTED_EXTERNAL`). + any per-route scope requirement. `dfoe_` tokens carry the per-feature + scopes (`APPS_RUN`, `APPS_READ_PERMITTED_EXTERNAL`). """ FULL = "full" @@ -65,32 +56,12 @@ class Scope(StrEnum): WORKSPACE_WRITE = "workspace:write" -class Accepts(StrEnum): - """Subject types a route is willing to accept as caller.""" - - USER_ACCOUNT = "user_account" - USER_EXT_SSO = "user_ext_sso" - - -ACCEPT_USER_ANY: frozenset[Accepts] = frozenset({Accepts.USER_ACCOUNT, Accepts.USER_EXT_SSO}) -ACCEPT_USER_EXT_SSO: frozenset[Accepts] = frozenset({Accepts.USER_EXT_SSO}) - -_SUBJECT_TO_ACCEPT: dict[SubjectType, Accepts] = { - SubjectType.ACCOUNT: Accepts.USER_ACCOUNT, - SubjectType.EXTERNAL_SSO: Accepts.USER_EXT_SSO, -} - - @dataclass(frozen=True, slots=True) class AuthContext: """Per-request identity published via :data:`_auth_ctx_var` (see :func:`set_auth_ctx` / :func:`get_auth_ctx`). ``scopes`` / ``subject_type`` / ``token_type`` come from the TokenKind, not the DB — corrupt rows can't elevate scope. - - `verified_tenants` is a snapshot of the Layer-0 verdict cache at - authenticate time. Per-request mutations write through to Redis via - `record_layer0_verdict`; this snapshot is not updated in place (frozen). """ subject_type: SubjectType @@ -102,8 +73,6 @@ class AuthContext: token_id: uuid.UUID token_type: TokenType expires_at: datetime | None - token_hash: str - verified_tenants: dict[str, bool] = field(default_factory=dict) _auth_ctx_var: ContextVar[AuthContext] = ContextVar("openapi_auth_ctx") @@ -133,7 +102,6 @@ class ResolvedRow: client_id: str | None token_id: uuid.UUID expires_at: datetime | None - verified_tenants: dict[str, bool] = field(default_factory=dict) def to_cache(self) -> dict: return { @@ -143,7 +111,6 @@ class ResolvedRow: "client_id": self.client_id, "token_id": str(self.token_id), "expires_at": self.expires_at.isoformat() if self.expires_at else None, - "verified_tenants": dict(self.verified_tenants), } @classmethod @@ -155,29 +122,9 @@ class ResolvedRow: client_id=data.get("client_id"), token_id=uuid.UUID(data["token_id"]), expires_at=datetime.fromisoformat(data["expires_at"]) if data["expires_at"] else None, - verified_tenants=_coerce_verified_tenants(data.get("verified_tenants")), ) -def _coerce_verified_tenants(raw: object) -> dict[str, bool]: - """Tolerate legacy entries that stored 'ok'/'denied' string verdicts. - - TODO(post-v1.0): remove once the AuthContext cache TTL has fully cycled - on all live deployments (60s TTL → safe to drop one release after rollout). - """ - if not isinstance(raw, dict): - return {} - out: dict[str, bool] = {} - for k, v in raw.items(): - if isinstance(v, bool): - out[k] = v - elif v == "ok": - out[k] = True - elif v == "denied": - out[k] = False - return out - - class Resolver(Protocol): def resolve(self, token_hash: str) -> ResolvedRow | None: # pragma: no cover - contract ... @@ -279,9 +226,8 @@ class BearerAuthenticator: def authenticate(self, token: str) -> AuthContext: """Identity + per-token rate limit (single source). - Both the openapi pipeline (`BearerCheck`) and the decorator - (`validate_bearer`) call this — rate-limit fires exactly once per - request regardless of which path hosts the route. + The openapi auth pipeline is the only caller, so the rate limit fires + exactly once per request. """ kind = self._registry.find(token) if kind is None: @@ -301,8 +247,6 @@ class BearerAuthenticator: token_id=row.token_id, token_type=kind.token_type, expires_at=row.expires_at, - token_hash=token_hash, - verified_tenants=dict(row.verified_tenants), ) @@ -466,92 +410,6 @@ class _VariantResolver: ) -# ============================================================================ -# Layer 0 — workspace membership cache + helper -# ============================================================================ - - -def record_layer0_verdict(token_hash: str, tenant_id: str, verdict: bool) -> None: - """Merge a Layer-0 membership verdict into the AuthContext cache entry at - `auth:token:{hash}`. No-op if entry missing/expired/invalid — next request - rebuilds via authenticate() and re-runs Layer 0. - """ - cache_key = TOKEN_CACHE_KEY_FMT.format(hash=token_hash) - raw = redis_client.get(cache_key) - if raw is None: - return - text = raw.decode() if isinstance(raw, (bytes, bytearray)) else raw - if text == "invalid": - return - try: - data = json.loads(text) - except (ValueError, KeyError): - return - ttl = redis_client.ttl(cache_key) - if ttl <= 0: - return - data.setdefault("verified_tenants", {})[tenant_id] = verdict - redis_client.setex(cache_key, ttl, json.dumps(data)) - - -def check_workspace_membership( - *, - account_id: uuid.UUID | str, - tenant_id: str, - token_hash: str, - membership_cache: dict[str, bool] | None = None, - cached_verdicts: dict[str, bool] | None = None, -) -> None: - """Layer-0 enforcement core. Raises `Forbidden` on deny, returns on allow. - - Shared by the pipeline step (`WorkspaceMembershipCheck`) and the - inline helper (`require_workspace_member`). Caller is responsible for - short-circuiting on EE / SSO subjects before invoking — this function - runs the membership + active-status checks unconditionally. - """ - cache = membership_cache if membership_cache is not None else cached_verdicts or {} - cached = cache.get(tenant_id) - if cached is True: - return - if cached is False: - raise Forbidden("workspace_membership_revoked") - - join = db.session.execute( - select(TenantAccountJoin.id).where( - TenantAccountJoin.account_id == account_id, - TenantAccountJoin.tenant_id == tenant_id, - ) - ).scalar_one_or_none() - if join is None: - record_layer0_verdict(token_hash, tenant_id, False) - raise Forbidden("workspace_membership_revoked") - - status = db.session.execute(select(Account.status).where(Account.id == account_id)).scalar_one_or_none() - if status != "active": - record_layer0_verdict(token_hash, tenant_id, False) - raise Forbidden("workspace_membership_revoked") - - record_layer0_verdict(token_hash, tenant_id, True) - - -def require_workspace_member(ctx: AuthContext, tenant_id: str) -> None: - """AuthContext-flavoured wrapper around `check_workspace_membership`. - - No-op on EE (gateway RBAC owns tenant isolation) and for SSO subjects - (no `tenant_account_joins` row by definition). - """ - if dify_config.DEPLOYMENT_EDITION == DeploymentEdition.ENTERPRISE: - return - if ctx.subject_type != SubjectType.ACCOUNT or ctx.account_id is None: - return - check_workspace_membership( - account_id=ctx.account_id, - tenant_id=tenant_id, - token_hash=ctx.token_hash, - membership_cache=ctx.verified_tenants, - ) - - # ============================================================================ # Decorator — route-level bearer gate # ============================================================================ @@ -574,10 +432,9 @@ def get_authenticator() -> BearerAuthenticator: def extract_bearer(req) -> str | None: """Pull the bearer token out of an HTTP request's Authorization header. - Used by both attachment paths (the ``validate_bearer`` decorator and the - openapi ``Pipeline.guard``) so the parsing rule lives in one place. Pipeline - callers extract once at the boundary and pass the token through ``Context`` - so steps stay independent of the request object. + Used by the openapi auth pipeline, which extracts once at the request + boundary so the parsing rule lives in one place and later steps stay + independent of the request object. """ header = req.headers.get("Authorization", "") scheme, _, value = header.partition(" ") @@ -586,50 +443,6 @@ def extract_bearer(req) -> str | None: return value.strip() -_DP = ParamSpec("_DP") -_DR = TypeVar("_DR") - - -def validate_bearer(*, accept: frozenset[Accepts]) -> Callable[[Callable[_DP, _DR]], Callable[_DP, _DR]]: - """Opt-in: omitting it leaves the route unauthenticated. - - Resolves user-level OAuth bearers (``dfoa_`` / ``dfoe_``). Legacy - ``app-`` keys belong to ``service_api/wraps.py:validate_app_token`` - and are rejected here as the wrong auth scheme for this surface. - """ - - def wrap(fn: Callable[_DP, _DR]) -> Callable[_DP, _DR]: - @wraps(fn) - def inner(*args: _DP.args, **kwargs: _DP.kwargs) -> _DR: - token = extract_bearer(request) - if token is None: - raise Unauthorized("missing bearer token") - - if _authenticator is None: - raise ServiceUnavailable("bearer_auth_disabled: set ENABLE_OAUTH_BEARER=true to enable") - - try: - ctx = get_authenticator().authenticate(token) - except InvalidBearerError as e: - raise Unauthorized(str(e)) - - if _SUBJECT_TO_ACCEPT[ctx.subject_type] not in accept: - raise Forbidden("token subject type not accepted here") - - # Try/finally pairing — the WSGI worker thread is reused - # across requests, so a leaked ContextVar would publish the - # previous caller's identity to the next request. - reset_token = set_auth_ctx(ctx) - try: - return fn(*args, **kwargs) - finally: - reset_auth_ctx(reset_token) - - return inner - - return wrap - - def bearer_feature_required[**P, R](fn: Callable[P, R]) -> Callable[P, R]: """503 if ENABLE_OAUTH_BEARER is off — minted tokens would be unusable without the authenticator, so fail fast instead of approving silently. @@ -644,29 +457,6 @@ def bearer_feature_required[**P, R](fn: Callable[P, R]) -> Callable[P, R]: return inner -def require_scope(scope: Scope) -> Callable: - """Route-level scope gate — must run AFTER validate_bearer so that - the auth ContextVar is set. Raises ``Forbidden('insufficient_scope: ')`` - when the bearer lacks both the requested scope and ``Scope.FULL``. - """ - - def wrap(fn: Callable) -> Callable: - @wraps(fn) - def inner(*args, **kwargs): - ctx = try_get_auth_ctx() - if ctx is None: - raise RuntimeError( - "require_scope used without validate_bearer; stack @validate_bearer above @require_scope" - ) - if Scope.FULL not in ctx.scopes and scope not in ctx.scopes: - raise Forbidden(f"insufficient_scope: {scope}") - return fn(*args, **kwargs) - - return inner - - return wrap - - # ============================================================================ # Wiring — called once from the app factory # ============================================================================ diff --git a/api/models/workflow.py b/api/models/workflow.py index dbc248e5a11..a53cd4248c8 100644 --- a/api/models/workflow.py +++ b/api/models/workflow.py @@ -866,19 +866,15 @@ class WorkflowRun(Base): back_populates="workflow_run", ) - @property - @deprecated("This method is retained for historical reasons; avoid using it if possible.") - def created_by_account(self): + def created_by_account(self, session: orm.Session) -> Account | None: created_by_role = CreatorUserRole(self.created_by_role) - return db.session.get(Account, self.created_by) if created_by_role == CreatorUserRole.ACCOUNT else None + return session.get(Account, self.created_by) if created_by_role == CreatorUserRole.ACCOUNT else None - @property - @deprecated("This method is retained for historical reasons; avoid using it if possible.") - def created_by_end_user(self): + def created_by_end_user(self, session: orm.Session): from .model import EndUser created_by_role = CreatorUserRole(self.created_by_role) - return db.session.get(EndUser, self.created_by) if created_by_role == CreatorUserRole.END_USER else None + return session.get(EndUser, self.created_by) if created_by_role == CreatorUserRole.END_USER else None @property def graph_dict(self) -> Mapping[str, Any]: @@ -892,20 +888,6 @@ class WorkflowRun(Base): def outputs_dict(self) -> Mapping[str, Any]: return json.loads(self.outputs) if self.outputs else {} - @property - @deprecated("This method is retained for historical reasons; avoid using it if possible.") - def message(self): - from .model import Message - - return db.session.scalar( - select(Message).where(Message.app_id == self.app_id, Message.workflow_run_id == self.id) - ) - - @property - @deprecated("This method is retained for historical reasons; avoid using it if possible.") - def workflow(self): - return db.session.scalar(select(Workflow).where(Workflow.id == self.workflow_id)) - def to_dict(self) -> WorkflowRunDict: return WorkflowRunDict( id=self.id, diff --git a/api/pyproject.toml b/api/pyproject.toml index 28ccf3c3d8a..cfbf28c95b3 100644 --- a/api/pyproject.toml +++ b/api/pyproject.toml @@ -186,7 +186,7 @@ dev = [ # "locust>=2.40.4", # Temporarily removed due to compatibility issues. Uncomment when resolved. "pytest-timeout>=2.4.0", "pytest-xdist>=3.8.0", - "pyrefly>=1.2.0", + "pyrefly>=1.3.1", "xinference-client>=2.7.0", "types-pytz>=2026.3.1.20260727", "types-bleach>=6.4.0.20260728", diff --git a/api/services/file_service.py b/api/services/file_service.py index ded70111e3d..e06069c8cc2 100644 --- a/api/services/file_service.py +++ b/api/services/file_service.py @@ -1,6 +1,7 @@ import base64 import hashlib import os +import posixpath import uuid from collections.abc import Generator, Sequence # Changed Iterator to Generator from contextlib import contextmanager, suppress @@ -315,8 +316,9 @@ class FileService: We keep this conservative: the upload flow already rejects `/` and `\\`, but older rows (or imported data) could still contain unsafe names. """ - # Drop any directory components and prevent empty names. - base = os.path.basename(name).strip() or "file" + # Drop any directory components and prevent empty names. ZIP names are always POSIX-style, so split on + # `/` explicitly rather than through `os.path`, whose separators differ on Windows hosts. + base = posixpath.basename(name).strip() or "file" # ZIP uses forward slashes as separators; remove any residual separator characters. return base.replace("/", "_").replace("\\", "_") diff --git a/api/services/legacy_model_type_migration.py b/api/services/legacy_model_type_migration.py index 2034246991f..7fcbb95c421 100644 --- a/api/services/legacy_model_type_migration.py +++ b/api/services/legacy_model_type_migration.py @@ -876,7 +876,7 @@ class Migration: session.execute( sa.update(ProviderModel) .where(ProviderModel.id == str(plan.winner.row.id)) - .values(model_type=plan.winner.canonical_model_type.value) + .values(model_type=plan.winner.canonical_model_type) ) self._log_row_updated( ProviderModel.__tablename__, @@ -1099,7 +1099,7 @@ class Migration: session.execute( sa.update(TenantDefaultModel) .where(TenantDefaultModel.id == str(plan.winner.row.id)) - .values(model_type=plan.winner.canonical_model_type.value) + .values(model_type=plan.winner.canonical_model_type) ) self._log_row_updated( TenantDefaultModel.__tablename__, @@ -1314,7 +1314,7 @@ class Migration: session.execute( sa.update(ProviderModelSetting) .where(ProviderModelSetting.id == str(plan.winner.row.id)) - .values(model_type=plan.winner.canonical_model_type.value) + .values(model_type=plan.winner.canonical_model_type) ) self._log_row_updated( ProviderModelSetting.__tablename__, @@ -1751,7 +1751,7 @@ class Migration: session.execute( sa.update(LoadBalancingModelConfig) .where(LoadBalancingModelConfig.id == processed_row_id) - .values(model_type=current_row.canonical_model_type.value) + .values(model_type=current_row.canonical_model_type) ) self._log_row_updated( LoadBalancingModelConfig.__tablename__, @@ -2096,7 +2096,7 @@ class Migration: session.execute( sa.update(ProviderModelCredential) .where(ProviderModelCredential.id == winner_credential_id) - .values(model_type=plan.winner.canonical_model_type.value) + .values(model_type=plan.winner.canonical_model_type) ) self._log_row_updated( ProviderModelCredential.__tablename__, diff --git a/api/services/openapi/license_gate.py b/api/services/openapi/license_gate.py deleted file mode 100644 index 8df92eb344b..00000000000 --- a/api/services/openapi/license_gate.py +++ /dev/null @@ -1,53 +0,0 @@ -"""License gate for the /openapi/v1/permitted-external-apps* surface. - -Enterprise-edition only. Community and Cloud deployments skip the gate entirely — -the EE blueprint chain is what gives CE deploys no callers on this surface -in practice, but the explicit short-circuit avoids any test/fixture that -flips the surface on without flipping the license. - -Uses the narrow system license policy shared with Console admission. - -Companion to ``controllers.console.wraps.enterprise_license_required`` — -that one is for console (cookie-authed, force-logout 401). This one is -for bearer surface (token-authed, 403 ``license_required``). -""" - -from __future__ import annotations - -import logging -from collections.abc import Callable -from functools import wraps - -from werkzeug.exceptions import Forbidden - -from configs import dify_config -from enums import DeploymentEdition -from services.entities.feature_entities import LicenseStatus -from services.system_feature_service import SystemFeatureService - -logger = logging.getLogger(__name__) - -_VALID_LICENSE_STATUSES: frozenset[LicenseStatus] = frozenset({LicenseStatus.ACTIVE, LicenseStatus.EXPIRING}) - - -def license_required[**P, R](view: Callable[P, R]) -> Callable[P, R]: - """Decorator form. Raises ``Forbidden('license_required')`` when the EE - deployment has no valid license. No-op outside the Enterprise edition. - """ - - @wraps(view) - def decorated(*args: P.args, **kwargs: P.kwargs) -> R: - if dify_config.DEPLOYMENT_EDITION == DeploymentEdition.ENTERPRISE and not _is_license_valid(): - raise Forbidden(description="license_required") - return view(*args, **kwargs) - - return decorated - - -def _is_license_valid() -> bool: - try: - license_status = SystemFeatureService.get_license_status() - except Exception: - logger.exception("license_gate: SystemFeatureService.get_license_status failed") - return False - return license_status in _VALID_LICENSE_STATUSES diff --git a/api/services/rag_pipeline/rag_pipeline_transform_service.py b/api/services/rag_pipeline/rag_pipeline_transform_service.py index 90dd6e081bd..9304257de9a 100644 --- a/api/services/rag_pipeline/rag_pipeline_transform_service.py +++ b/api/services/rag_pipeline/rag_pipeline_transform_service.py @@ -123,29 +123,40 @@ class RagPipelineTransformService: case DataSourceType.UPLOAD_FILE: if indexing_technique == IndexTechniqueType.HIGH_QUALITY: # get graph from transform.file-general-high-quality.yml - with open(f"{Path(__file__).parent}/transform/file-general-high-quality.yml") as f: + with open( + f"{Path(__file__).parent}/transform/file-general-high-quality.yml", encoding="utf-8" + ) as f: pipeline_yaml = yaml.safe_load(f) if indexing_technique == IndexTechniqueType.ECONOMY: # get graph from transform.file-general-economy.yml - with open(f"{Path(__file__).parent}/transform/file-general-economy.yml") as f: + with open(f"{Path(__file__).parent}/transform/file-general-economy.yml", encoding="utf-8") as f: pipeline_yaml = yaml.safe_load(f) case DataSourceType.NOTION_IMPORT: if indexing_technique == IndexTechniqueType.HIGH_QUALITY: # get graph from transform.notion-general-high-quality.yml - with open(f"{Path(__file__).parent}/transform/notion-general-high-quality.yml") as f: + with open( + f"{Path(__file__).parent}/transform/notion-general-high-quality.yml", encoding="utf-8" + ) as f: pipeline_yaml = yaml.safe_load(f) if indexing_technique == IndexTechniqueType.ECONOMY: # get graph from transform.notion-general-economy.yml - with open(f"{Path(__file__).parent}/transform/notion-general-economy.yml") as f: + with open( + f"{Path(__file__).parent}/transform/notion-general-economy.yml", encoding="utf-8" + ) as f: pipeline_yaml = yaml.safe_load(f) case DataSourceType.WEBSITE_CRAWL: if indexing_technique == IndexTechniqueType.HIGH_QUALITY: # get graph from transform.website-crawl-general-high-quality.yml - with open(f"{Path(__file__).parent}/transform/website-crawl-general-high-quality.yml") as f: + with open( + f"{Path(__file__).parent}/transform/website-crawl-general-high-quality.yml", + encoding="utf-8", + ) as f: pipeline_yaml = yaml.safe_load(f) if indexing_technique == IndexTechniqueType.ECONOMY: # get graph from transform.website-crawl-general-economy.yml - with open(f"{Path(__file__).parent}/transform/website-crawl-general-economy.yml") as f: + with open( + f"{Path(__file__).parent}/transform/website-crawl-general-economy.yml", encoding="utf-8" + ) as f: pipeline_yaml = yaml.safe_load(f) case _: raise ValueError("Unsupported datasource type") @@ -153,15 +164,17 @@ class RagPipelineTransformService: match datasource_type: case DataSourceType.UPLOAD_FILE: # get graph from transform.file-parentchild.yml - with open(f"{Path(__file__).parent}/transform/file-parentchild.yml") as f: + with open(f"{Path(__file__).parent}/transform/file-parentchild.yml", encoding="utf-8") as f: pipeline_yaml = yaml.safe_load(f) case DataSourceType.NOTION_IMPORT: # get graph from transform.notion-parentchild.yml - with open(f"{Path(__file__).parent}/transform/notion-parentchild.yml") as f: + with open(f"{Path(__file__).parent}/transform/notion-parentchild.yml", encoding="utf-8") as f: pipeline_yaml = yaml.safe_load(f) case DataSourceType.WEBSITE_CRAWL: # get graph from transform.website-crawl-parentchild.yml - with open(f"{Path(__file__).parent}/transform/website-crawl-parentchild.yml") as f: + with open( + f"{Path(__file__).parent}/transform/website-crawl-parentchild.yml", encoding="utf-8" + ) as f: pipeline_yaml = yaml.safe_load(f) case _: raise ValueError("Unsupported datasource type") diff --git a/api/services/tag_service.py b/api/services/tag_service.py index f2e819175ac..8ba8add0310 100644 --- a/api/services/tag_service.py +++ b/api/services/tag_service.py @@ -5,7 +5,7 @@ import sqlalchemy as sa from flask_login import current_user from flask_sqlalchemy.session import Session as FlaskSession from pydantic import BaseModel, Field -from sqlalchemy import delete, func, select +from sqlalchemy import delete, func, select, update from sqlalchemy.engine import CursorResult from sqlalchemy.orm import Session, scoped_session from werkzeug.exceptions import NotFound @@ -179,7 +179,12 @@ class TagService: ) if existing: raise ValueError("Tag name already exists") - tag.name = payload.name + session.execute( + update(Tag) + .where(Tag.id == tag_id, Tag.tenant_id == current_tenant_id) + .values(name=payload.name) + .execution_options(synchronize_session="fetch") + ) session.commit() return tag diff --git a/api/tasks/deal_dataset_index_update_task.py b/api/tasks/deal_dataset_index_update_task.py index 63279348455..dfbbe3de065 100644 --- a/api/tasks/deal_dataset_index_update_task.py +++ b/api/tasks/deal_dataset_index_update_task.py @@ -48,7 +48,7 @@ def deal_dataset_index_update_task(dataset_id: str, action: str): session.execute( update(DatasetDocument) .where(DatasetDocument.id.in_(dataset_documents_ids)) - .values(indexing_status="indexing") + .values({DatasetDocument.indexing_status: "indexing"}) ) session.commit() @@ -90,14 +90,14 @@ def deal_dataset_index_update_task(dataset_id: str, action: str): session.execute( update(DatasetDocument) .where(DatasetDocument.id == dataset_document.id) - .values(indexing_status="completed") + .values({DatasetDocument.indexing_status: "completed"}) ) session.commit() except Exception as e: session.execute( update(DatasetDocument) .where(DatasetDocument.id == dataset_document.id) - .values(indexing_status="error", error=str(e)) + .values({DatasetDocument.indexing_status: "error", DatasetDocument.error: str(e)}) ) session.commit() elif action == "update": @@ -116,7 +116,7 @@ def deal_dataset_index_update_task(dataset_id: str, action: str): session.execute( update(DatasetDocument) .where(DatasetDocument.id.in_(dataset_documents_ids)) - .values(indexing_status="indexing") + .values({DatasetDocument.indexing_status: "indexing"}) ) session.commit() @@ -191,14 +191,14 @@ def deal_dataset_index_update_task(dataset_id: str, action: str): session.execute( update(DatasetDocument) .where(DatasetDocument.id == dataset_document.id) - .values(indexing_status="completed") + .values({DatasetDocument.indexing_status: "completed"}) ) session.commit() except Exception as e: session.execute( update(DatasetDocument) .where(DatasetDocument.id == dataset_document.id) - .values(indexing_status="error", error=str(e)) + .values({DatasetDocument.indexing_status: "error", DatasetDocument.error: str(e)}) ) session.commit() else: diff --git a/api/tasks/deal_dataset_vector_index_task.py b/api/tasks/deal_dataset_vector_index_task.py index 001436a9af6..928134fb7b7 100644 --- a/api/tasks/deal_dataset_vector_index_task.py +++ b/api/tasks/deal_dataset_vector_index_task.py @@ -52,7 +52,7 @@ def deal_dataset_vector_index_task(dataset_id: str, action: str): session.execute( update(DatasetDocument) .where(DatasetDocument.id.in_(dataset_documents_ids)) - .values(indexing_status="indexing") + .values({DatasetDocument.indexing_status: "indexing"}) ) session.commit() @@ -86,14 +86,14 @@ def deal_dataset_vector_index_task(dataset_id: str, action: str): session.execute( update(DatasetDocument) .where(DatasetDocument.id == dataset_document.id) - .values(indexing_status="completed") + .values({DatasetDocument.indexing_status: "completed"}) ) session.commit() except Exception as e: session.execute( update(DatasetDocument) .where(DatasetDocument.id == dataset_document.id) - .values(indexing_status="error", error=str(e)) + .values({DatasetDocument.indexing_status: "error", DatasetDocument.error: str(e)}) ) session.commit() elif action == "update": @@ -112,7 +112,7 @@ def deal_dataset_vector_index_task(dataset_id: str, action: str): session.execute( update(DatasetDocument) .where(DatasetDocument.id.in_(dataset_documents_ids)) - .values(indexing_status="indexing") + .values({DatasetDocument.indexing_status: "indexing"}) ) session.commit() @@ -187,14 +187,14 @@ def deal_dataset_vector_index_task(dataset_id: str, action: str): session.execute( update(DatasetDocument) .where(DatasetDocument.id == dataset_document.id) - .values(indexing_status="completed") + .values({DatasetDocument.indexing_status: "completed"}) ) session.commit() except Exception as e: session.execute( update(DatasetDocument) .where(DatasetDocument.id == dataset_document.id) - .values(indexing_status="error", error=str(e)) + .values({DatasetDocument.indexing_status: "error", DatasetDocument.error: str(e)}) ) session.commit() else: diff --git a/api/tasks/enable_segments_to_index_task.py b/api/tasks/enable_segments_to_index_task.py index d4ab9a333df..0de5effcd47 100644 --- a/api/tasks/enable_segments_to_index_task.py +++ b/api/tasks/enable_segments_to_index_task.py @@ -14,6 +14,7 @@ from extensions.ext_redis import redis_client from libs.datetime_utils import naive_utc_now from models.dataset import Dataset, DocumentSegment from models.dataset import Document as DatasetDocument +from models.enums import SegmentStatus logger = logging.getLogger(__name__) @@ -133,7 +134,7 @@ def enable_segments_to_index_task(segment_ids: list, dataset_id: str, document_i DocumentSegment.dataset_id == dataset_id, DocumentSegment.document_id == document_id, ) - .values(error=str(e), status="error", disabled_at=naive_utc_now(), enabled=False) + .values(error=str(e), status=SegmentStatus.ERROR, disabled_at=naive_utc_now(), enabled=False) ) session.commit() finally: diff --git a/api/tests/integration_tests/controllers/openapi/test_auth.py b/api/tests/integration_tests/controllers/openapi/test_auth.py deleted file mode 100644 index 956a0904dab..00000000000 --- a/api/tests/integration_tests/controllers/openapi/test_auth.py +++ /dev/null @@ -1,128 +0,0 @@ -"""Integration tests for the /openapi/v1 bearer auth surface. - -Layer 0 (workspace membership), per-token rate limit, and read-scope (`apps:read`) -acceptance/rejection on app-scoped routes. -""" - -from __future__ import annotations - -from collections.abc import Generator - -import pytest -from flask import Flask -from flask.testing import FlaskClient - -from enums import DeploymentEdition -from extensions.ext_database import db -from models import App, Tenant - - -def test_info_accepts_account_bearer_with_apps_read_scope( - test_client: FlaskClient, - app_in_workspace: App, - account_token: str, -) -> None: - res = test_client.get( - f"/openapi/v1/apps/{app_in_workspace.id}/info", - headers={"Authorization": f"Bearer {account_token}"}, - ) - assert res.status_code == 200 - assert res.json["id"] == app_in_workspace.id - - -@pytest.fixture -def other_workspace_app(flask_app: Flask) -> Generator[App, None, None]: - """A fresh app under a *different* tenant — caller has no membership row.""" - with flask_app.app_context(): - other_tenant = Tenant(name="other", status="normal") - db.session.add(other_tenant) - db.session.commit() - app = App( - tenant_id=other_tenant.id, - name="b", - mode="chat", - status="normal", - enable_site=True, - enable_api=True, - ) - db.session.add(app) - db.session.commit() - yield app - db.session.delete(app) - db.session.delete(other_tenant) - db.session.commit() - - -def test_layer0_denies_account_bearer_without_membership( - test_client: FlaskClient, - account_token: str, - other_workspace_app: App, -) -> None: - """Account A bearer hitting an app under tenant B — Layer 0 denies on CE.""" - res = test_client.get( - f"/openapi/v1/apps/{other_workspace_app.id}/info", - headers={"Authorization": f"Bearer {account_token}"}, - ) - assert res.status_code == 403 - assert res.json.get("message") == "workspace_membership_revoked" - - -def test_layer0_skipped_for_enterprise_edition( - test_client: FlaskClient, - account_token: str, - other_workspace_app: App, - monkeypatch, -) -> None: - """On EE, Layer 0 short-circuits — gateway RBAC owns tenant isolation. - - /info uses validate_bearer + require_workspace_member inline (no - AppAuthzCheck), so a cross-tenant bearer reaches the app lookup and - gets 200 — gateway is expected to enforce isolation upstream. - """ - from configs import dify_config - - # Override the conftest autouse default for this test only. - monkeypatch.setattr(dify_config, "DEPLOYMENT_EDITION", DeploymentEdition.ENTERPRISE) - - res = test_client.get( - f"/openapi/v1/apps/{other_workspace_app.id}/info", - headers={"Authorization": f"Bearer {account_token}"}, - ) - assert res.status_code == 200 - assert res.json.get("message") != "workspace_membership_revoked" - - -def test_rate_limit_returns_429_after_60_requests( - test_client: FlaskClient, - account_token: str, -) -> None: - """61st sequential GET to /account on the same bearer → 429 with Retry-After.""" - headers = {"Authorization": f"Bearer {account_token}"} - for i in range(60): - r = test_client.get("/openapi/v1/account", headers=headers) - assert r.status_code == 200, f"unexpected fail at i={i}" - - r = test_client.get("/openapi/v1/account", headers=headers) - assert r.status_code == 429 - assert r.headers.get("Retry-After"), "Retry-After header missing" - assert int(r.headers["Retry-After"]) >= 1 - body = r.json or {} - assert body.get("error") == "rate_limited" - assert isinstance(body.get("retry_after_ms"), int) - assert body["retry_after_ms"] >= 1000 - - -def test_rate_limit_bucket_shared_across_surfaces( - test_client: FlaskClient, - app_in_workspace: App, - account_token: str, -) -> None: - """30 calls to /account + 30 calls to /apps//info on same token → 61st 429s.""" - headers = {"Authorization": f"Bearer {account_token}"} - for _ in range(30): - assert test_client.get("/openapi/v1/account", headers=headers).status_code == 200 - for _ in range(30): - assert test_client.get(f"/openapi/v1/apps/{app_in_workspace.id}/info", headers=headers).status_code == 200 - - r = test_client.get("/openapi/v1/account", headers=headers) - assert r.status_code == 429 diff --git a/api/tests/test_containers_integration_tests/controllers/openapi/conftest.py b/api/tests/test_containers_integration_tests/controllers/openapi/conftest.py index 2047ee765d1..1b7894bcb8a 100644 --- a/api/tests/test_containers_integration_tests/controllers/openapi/conftest.py +++ b/api/tests/test_containers_integration_tests/controllers/openapi/conftest.py @@ -82,7 +82,6 @@ def auth_for( return AuthData( token_type=TokenType.OAUTH_ACCOUNT, account_id=uuid.UUID(str(account.id)), - token_hash="integration-test", token_id=token_id, scopes=frozenset({Scope.FULL}), caller=account, diff --git a/api/tests/unit_tests/commands/test_reset_encrypt_key_pair.py b/api/tests/unit_tests/commands/test_reset_encrypt_key_pair.py index d8c47e2747e..bb3201bbdaa 100644 --- a/api/tests/unit_tests/commands/test_reset_encrypt_key_pair.py +++ b/api/tests/unit_tests/commands/test_reset_encrypt_key_pair.py @@ -22,6 +22,7 @@ from models import Tenant from models.provider import Provider, ProviderModel, ProviderType from models.tools import ApiToolProvider, BuiltinToolProvider, MCPToolProvider from tests.unit_tests.config_override import apply_config_overrides +from tests.unit_tests.model_factories import make_tenant def _invoke_reset() -> int: @@ -38,9 +39,7 @@ USER_ID = "22222222-2222-2222-2222-222222222222" def _tenant(tenant_id: str, *, name: str = "Test tenant") -> Tenant: - tenant = Tenant(name=name, encrypt_public_key="old-key") - tenant.id = tenant_id - return tenant + return make_tenant(tenant_id=tenant_id, name=name, encrypt_public_key="old-key") def _encrypted_rows(tenant_id: str, *, suffix: str = "1") -> tuple[object, ...]: diff --git a/api/tests/unit_tests/controllers/console/agent/test_agent_controllers.py b/api/tests/unit_tests/controllers/console/agent/test_agent_controllers.py index ee10c5dc19f..d2af3b5fddc 100644 --- a/api/tests/unit_tests/controllers/console/agent/test_agent_controllers.py +++ b/api/tests/unit_tests/controllers/console/agent/test_agent_controllers.py @@ -75,6 +75,7 @@ from services.entities.agent_entities import ( WorkflowComposerCopyFromRosterPayload, ) from tests.unit_tests.config_override import apply_config_overrides +from tests.unit_tests.model_factories import make_account def _persist_conversation_message( @@ -228,12 +229,13 @@ def _app_detail_obj(**overrides) -> App: def _account(*, account_id: str = "account-1", privileged: bool = False, timezone: str | None = None) -> Account: - account = Account(name="Agent Controller Tester", email=f"{account_id}@example.com") - account.id = account_id - account.timezone = timezone - if privileged: - account.role = TenantAccountRole.OWNER - return account + return make_account( + account_id=account_id, + name="Agent Controller Tester", + email=f"{account_id}@example.com", + timezone=timezone, + role=TenantAccountRole.OWNER if privileged else None, + ) def _candidates_response(variant: str) -> dict: diff --git a/api/tests/unit_tests/controllers/console/agent/test_app_helpers.py b/api/tests/unit_tests/controllers/console/agent/test_app_helpers.py index b797a2956ad..8ad20b6b84c 100644 --- a/api/tests/unit_tests/controllers/console/agent/test_app_helpers.py +++ b/api/tests/unit_tests/controllers/console/agent/test_app_helpers.py @@ -6,10 +6,11 @@ from sqlalchemy.orm import Session from controllers.console.agent import app_helpers from models.model import App, AppMode +from tests.unit_tests.model_factories import make_app def _app() -> App: - return App(id="app-1", tenant_id="tenant-1", name="Agent App", mode=AppMode.AGENT) + return make_app(name="Agent App", mode=AppMode.AGENT, icon_type=None) def test_resolve_agent_app_model_reuses_caller_session( diff --git a/api/tests/unit_tests/controllers/console/app/test_agent_app_sandbox.py b/api/tests/unit_tests/controllers/console/app/test_agent_app_sandbox.py index 0ce728baef9..077b166917f 100644 --- a/api/tests/unit_tests/controllers/console/app/test_agent_app_sandbox.py +++ b/api/tests/unit_tests/controllers/console/app/test_agent_app_sandbox.py @@ -12,6 +12,7 @@ from models.account import Account from models.model import App, AppMode, IconType from services.agent_app_sandbox_service import AgentSandboxDownload, AgentSandboxInfo, AgentSandboxInspectorError from tests.unit_tests.controllers.rbac_introspection import rbac_checks +from tests.unit_tests.model_factories import make_account class _AgentAppService: @@ -142,9 +143,7 @@ def _app_model(app_id: str = "app-1") -> App: def _account() -> Account: - account = Account(name="Sandbox Tester", email="sandbox-tester@example.com") - account.id = "account-1" - return account + return make_account(name="Sandbox Tester", email="sandbox-tester@example.com") @pytest.mark.parametrize( diff --git a/api/tests/unit_tests/controllers/console/app/test_annotation_api.py b/api/tests/unit_tests/controllers/console/app/test_annotation_api.py index 38ae25284e1..5094bb1fab9 100644 --- a/api/tests/unit_tests/controllers/console/app/test_annotation_api.py +++ b/api/tests/unit_tests/controllers/console/app/test_annotation_api.py @@ -13,6 +13,7 @@ from controllers.console.app import annotation as annotation_module from models.account import Account from models.model import App, AppAnnotationHitHistory, AppMode, IconType, MessageAnnotation from services.app_ref_service import AnnotationRef, AppRef +from tests.unit_tests.model_factories import make_account def _persist_app(session: Session) -> App: @@ -33,9 +34,7 @@ def _persist_app(session: Session) -> App: def _account() -> Account: - account = Account(name="Owner", email="owner@example.com") - account.id = "account-1" - return account + return make_account(name="Owner", email="owner@example.com") def _annotation_model(annotation_id: str = "ann-1") -> MessageAnnotation: diff --git a/api/tests/unit_tests/controllers/console/app/test_annotation_security.py b/api/tests/unit_tests/controllers/console/app/test_annotation_security.py index 0aa896337e3..9d0f6c33cc5 100644 --- a/api/tests/unit_tests/controllers/console/app/test_annotation_security.py +++ b/api/tests/unit_tests/controllers/console/app/test_annotation_security.py @@ -21,12 +21,11 @@ from models.model import App, AppMode, IconType from services.annotation_service import AppAnnotationService from tasks.annotation.batch_import_annotations_task import batch_import_annotations_task from tests.unit_tests.config_override import config_overrides_context +from tests.unit_tests.model_factories import make_account def _account() -> Account: - account = Account(name="Annotation Tester", email="annotation-tester@example.com") - account.id = "user_id" - return account + return make_account(account_id="user_id", name="Annotation Tester", email="annotation-tester@example.com") def _persist_app(session: Session) -> App: diff --git a/api/tests/unit_tests/controllers/console/app/test_audio.py b/api/tests/unit_tests/controllers/console/app/test_audio.py index 1f8633923be..6e3efa6c985 100644 --- a/api/tests/unit_tests/controllers/console/app/test_audio.py +++ b/api/tests/unit_tests/controllers/console/app/test_audio.py @@ -50,6 +50,7 @@ from services.errors.audio import ( SpeechToTextDisabledServiceError, UnsupportedAudioTypeServiceError, ) +from tests.unit_tests.model_factories import make_account, make_app def _file_data(): @@ -57,22 +58,11 @@ def _file_data(): def _app(*, app_id: str = "a1", tenant_id: str = "tenant-1") -> App: - return App( - id=app_id, - tenant_id=tenant_id, - name="Audio app", - description="", - mode=AppMode.CHAT, - enable_site=True, - enable_api=True, - max_active_requests=0, - ) + return make_app(app_id=app_id, tenant_id=tenant_id, name="Audio app", icon_type=None, max_active_requests=0) def _account(account_id: str = "account-1") -> Account: - account = Account(name="Audio account", email=f"{account_id}@example.com") - account.id = account_id - return account + return make_account(account_id=account_id, name="Audio account", email=f"{account_id}@example.com") def test_console_audio_api_success(app: Flask, monkeypatch: pytest.MonkeyPatch) -> None: diff --git a/api/tests/unit_tests/controllers/console/app/test_conversation_api.py b/api/tests/unit_tests/controllers/console/app/test_conversation_api.py index c43af0fde83..8425c9560de 100644 --- a/api/tests/unit_tests/controllers/console/app/test_conversation_api.py +++ b/api/tests/unit_tests/controllers/console/app/test_conversation_api.py @@ -23,9 +23,10 @@ from models.enums import ( FeedbackRating, WorkflowRunTriggeredFrom, ) -from models.model import AppMode, Conversation, IconType +from models.model import AppMode, Conversation from models.workflow import WorkflowRun, WorkflowType from services.errors.conversation import ConversationNotExistsError +from tests.unit_tests.model_factories import make_app, make_conversation def _make_account() -> Account: @@ -35,30 +36,13 @@ def _make_account() -> Account: def _app(*, mode: AppMode = AppMode.CHAT) -> App: - return App( - id="app-1", - tenant_id="tenant-1", - name="Conversation app", - description="", - mode=mode, - icon_type=IconType.EMOJI, - icon="robot", - icon_background="#FFFFFF", - enable_site=True, - enable_api=True, - max_active_requests=None, - ) + return make_app(name="Conversation app", mode=mode) def _conversation(*, conversation_id: str = "c1", app_id: str = "app-1") -> Conversation: - conversation = Conversation( + return make_conversation( + conversation_id=conversation_id, app_id=app_id, - app_model_config_id=None, - model_provider=None, - override_model_configs=None, - model_id=None, - mode=AppMode.CHAT, - name="Conversation", inputs={}, introduction="", system_instruction="", @@ -66,11 +50,8 @@ def _conversation(*, conversation_id: str = "c1", app_id: str = "app-1") -> Conv status="normal", invoke_from=InvokeFrom.EXPLORE, from_source=ConversationFromSource.CONSOLE, - from_end_user_id=None, from_account_id="u1", ) - conversation.id = conversation_id - return conversation def test_completion_conversation_list_returns_paginated_result( diff --git a/api/tests/unit_tests/controllers/console/app/test_conversation_variables_api.py b/api/tests/unit_tests/controllers/console/app/test_conversation_variables_api.py index 07249da37c8..bfd030b2e7a 100644 --- a/api/tests/unit_tests/controllers/console/app/test_conversation_variables_api.py +++ b/api/tests/unit_tests/controllers/console/app/test_conversation_variables_api.py @@ -14,23 +14,11 @@ from controllers.console.app import conversation_variables as conversation_varia from factories import variable_factory from graphon.variables.types import SegmentType from models import App, AppMode, ConversationVariable -from models.model import IconType +from tests.unit_tests.model_factories import make_app def _app() -> App: - return App( - id="app-1", - tenant_id="tenant-1", - name="Conversation variables app", - description="", - mode=AppMode.ADVANCED_CHAT, - icon_type=IconType.EMOJI, - icon="robot", - icon_background="#FFFFFF", - enable_site=True, - enable_api=True, - max_active_requests=None, - ) + return make_app(name="Conversation variables app", mode=AppMode.ADVANCED_CHAT) def test_get_conversation_variables_returns_paginated_response( diff --git a/api/tests/unit_tests/controllers/console/app/test_generator_api.py b/api/tests/unit_tests/controllers/console/app/test_generator_api.py index b028bd15761..184f0a3f988 100644 --- a/api/tests/unit_tests/controllers/console/app/test_generator_api.py +++ b/api/tests/unit_tests/controllers/console/app/test_generator_api.py @@ -20,7 +20,8 @@ from controllers.console.app.generator import ( ) from core.errors.error import ProviderTokenNotInitError from models.model import App, AppMode -from models.workflow import Workflow, WorkflowType +from models.workflow import Workflow +from tests.unit_tests.model_factories import make_workflow def _persist_app(session: Session, *, tenant_id: str = "t1") -> App: @@ -44,14 +45,7 @@ def _model_config_payload(): def _workflow(graph_dict: dict[str, object]) -> Workflow: - return Workflow( - id="workflow-1", - tenant_id="t1", - app_id="app-1", - type=WorkflowType.WORKFLOW, - version=Workflow.VERSION_DRAFT, - graph=json.dumps(graph_dict), - ) + return make_workflow(workflow_id="workflow-1", tenant_id="t1", graph=graph_dict) def _install_workflow_service(monkeypatch: pytest.MonkeyPatch, workflow): diff --git a/api/tests/unit_tests/controllers/console/app/test_mcp_server_response.py b/api/tests/unit_tests/controllers/console/app/test_mcp_server_response.py index 0f53d0de7d8..edac7a463c0 100644 --- a/api/tests/unit_tests/controllers/console/app/test_mcp_server_response.py +++ b/api/tests/unit_tests/controllers/console/app/test_mcp_server_response.py @@ -20,8 +20,9 @@ from controllers.console.wraps import RBACPermission, RBACResourceScope from models import Account from models.account import AccountStatus from models.enums import AppMCPServerStatus -from models.model import App, AppMCPServer, AppMode, IconType +from models.model import App, AppMCPServer from tests.unit_tests.config_override import config_overrides_context +from tests.unit_tests.model_factories import make_app def _app( @@ -31,19 +32,7 @@ def _app( name: str = "Demo App", description: str = "App description", ) -> App: - return App( - id=app_id, - tenant_id=tenant_id, - name=name, - description=description, - mode=AppMode.CHAT, - icon_type=IconType.EMOJI, - icon="robot", - icon_background="#FFFFFF", - enable_site=True, - enable_api=True, - max_active_requests=None, - ) + return make_app(app_id=app_id, tenant_id=tenant_id, name=name, description=description) def _server( diff --git a/api/tests/unit_tests/controllers/console/app/test_workflow_convert_api.py b/api/tests/unit_tests/controllers/console/app/test_workflow_convert_api.py index 54163d65448..f9a9bc80d06 100644 --- a/api/tests/unit_tests/controllers/console/app/test_workflow_convert_api.py +++ b/api/tests/unit_tests/controllers/console/app/test_workflow_convert_api.py @@ -9,24 +9,12 @@ from flask import Flask from controllers.console.app import workflow as workflow_module from controllers.console.app.workflow import ConvertToWorkflowApi -from models import Account, App, AppMode -from models.model import IconType +from models import Account, App +from tests.unit_tests.model_factories import make_app def _app(app_id: str) -> App: - return App( - id=app_id, - tenant_id="tenant-1", - name=f"App {app_id}", - description="", - mode=AppMode.CHAT, - icon_type=IconType.EMOJI, - icon="robot", - icon_background="#FFFFFF", - enable_site=True, - enable_api=True, - max_active_requests=None, - ) + return make_app(app_id=app_id, name=f"App {app_id}") class TestConvertToWorkflowApi: diff --git a/api/tests/unit_tests/controllers/console/app/test_workflow_run_api.py b/api/tests/unit_tests/controllers/console/app/test_workflow_run_api.py index 2f9e597bcc1..7457dae7d80 100644 --- a/api/tests/unit_tests/controllers/console/app/test_workflow_run_api.py +++ b/api/tests/unit_tests/controllers/console/app/test_workflow_run_api.py @@ -20,13 +20,13 @@ from graphon.enums import WorkflowExecutionStatus, WorkflowNodeExecutionStatus from machinery.context import RequestContext from models import Account, App, AppMode from models.enums import CreatorUserRole, WorkflowRunTriggeredFrom -from models.model import IconType from models.workflow import ( WorkflowNodeExecutionModel, WorkflowNodeExecutionTriggeredFrom, WorkflowRun, WorkflowType, ) +from tests.unit_tests.model_factories import make_account, make_app def _serialize_200_response(handler, payload: Any) -> Any: @@ -41,27 +41,14 @@ def _serialize_200_response(handler, payload: Any) -> Any: def _account(session: Session) -> Account: - account = Account(name="Alice", email="alice@example.com") - account.id = "account-1" + account = make_account(name="Alice", email="alice@example.com") session.add(account) session.commit() return account def _app() -> App: - return App( - id="app-1", - tenant_id="tenant-1", - name="Workflow run app", - description="", - mode=AppMode.WORKFLOW, - icon_type=IconType.EMOJI, - icon="robot", - icon_background="#FFFFFF", - enable_site=True, - enable_api=True, - max_active_requests=None, - ) + return make_app(name="Workflow run app", mode=AppMode.WORKFLOW) def _request_context(*, workspace_id: str = "tenant-1") -> RequestContext: @@ -153,13 +140,12 @@ def test_workflow_run_list_returns_frontend_history_contract( _account(sqlite_session) workflow_run = _workflow_run_summary(sqlite_session) workflow_runs = Mock() - workflow_runs.get_paginate_workflow_runs.return_value = { - "limit": 10, - "has_more": False, - "data": [workflow_run], - } + workflow_runs.get_paginate_workflow_runs.return_value = SimpleNamespace( + limit=10, + has_more=False, + data=[workflow_run], + ) _mock_application_services(monkeypatch, workflow_runs) - monkeypatch.setattr(db, "session", sqlite_session) request_context = _request_context() api = workflow_run_module.WorkflowRunListApi() @@ -168,6 +154,7 @@ def test_workflow_run_list_returns_frontend_history_contract( with app.test_request_context("/apps/app-1/workflow-runs?limit=10", method="GET"): payload = handler( api, + sqlite_session, workflow_run_module.WorkflowRunListQuery(limit=10), request_context, app_model=_app(), @@ -208,13 +195,12 @@ def test_advanced_chat_workflow_run_list_keeps_message_fields( message_id="message-1", ) workflow_runs = Mock() - workflow_runs.get_paginate_advanced_chat_workflow_runs.return_value = { - "limit": 1, - "has_more": True, - "data": [workflow_run], - } + workflow_runs.get_paginate_advanced_chat_workflow_runs.return_value = SimpleNamespace( + limit=1, + has_more=True, + data=[workflow_run], + ) _mock_application_services(monkeypatch, workflow_runs) - monkeypatch.setattr(db, "session", sqlite_session) request_context = _request_context() api = workflow_run_module.AdvancedChatAppWorkflowRunListApi() @@ -223,6 +209,7 @@ def test_advanced_chat_workflow_run_list_keeps_message_fields( with app.test_request_context("/apps/app-1/advanced-chat/workflow-runs?limit=1", method="GET"): payload = handler( api, + sqlite_session, workflow_run_module.WorkflowRunListQuery(limit=1), request_context, app_model=_app(), @@ -288,14 +275,13 @@ def test_workflow_run_detail_returns_frontend_detail_contract( workflow_runs = Mock() workflow_runs.get_workflow_run.return_value = workflow_run _mock_application_services(monkeypatch, workflow_runs) - monkeypatch.setattr(db, "session", sqlite_session) request_context = _request_context() api = workflow_run_module.WorkflowRunDetailApi() handler = unwrap(api.get) with app.test_request_context("/apps/app-1/workflow-runs/run-1", method="GET"): - payload = handler(api, request_context, app_model=_app(), run_id="run-1") + payload = handler(api, sqlite_session, request_context, app_model=_app(), run_id="run-1") response = _serialize_200_response(api.get, payload) @@ -324,7 +310,9 @@ def test_workflow_run_detail_returns_frontend_detail_contract( ) -def test_workflow_run_detail_maps_missing_run_to_not_found(app: Flask, monkeypatch: pytest.MonkeyPatch) -> None: +def test_workflow_run_detail_maps_missing_run_to_not_found( + app: Flask, monkeypatch: pytest.MonkeyPatch, sqlite_session: Session +) -> None: workflow_runs = Mock() workflow_runs.get_workflow_run.return_value = None _mock_application_services(monkeypatch, workflow_runs) @@ -334,7 +322,7 @@ def test_workflow_run_detail_maps_missing_run_to_not_found(app: Flask, monkeypat with app.test_request_context("/apps/app-1/workflow-runs/run-1", method="GET"): with pytest.raises(NotFoundError, match="Workflow run not found"): - handler(api, request_context, app_model=_app(), run_id="run-1") + handler(api, sqlite_session, request_context, app_model=_app(), run_id="run-1") workflow_runs.get_workflow_run.assert_called_once_with( request_context, @@ -353,7 +341,7 @@ def test_workflow_run_node_executions_return_frontend_trace_contract( node_execution_response_source(execution, session=sqlite_session) ] _mock_application_services(monkeypatch, workflow_runs) - monkeypatch.setattr(db, "session", sqlite_session) + monkeypatch.setattr(db, "session", lambda: sqlite_session) request_context = _request_context() api = workflow_run_module.WorkflowRunNodeExecutionListApi() diff --git a/api/tests/unit_tests/controllers/console/datasets/rag_pipeline/test_datasource_auth.py b/api/tests/unit_tests/controllers/console/datasets/rag_pipeline/test_datasource_auth.py index 63a61586ef9..c1cbbaa8682 100644 --- a/api/tests/unit_tests/controllers/console/datasets/rag_pipeline/test_datasource_auth.py +++ b/api/tests/unit_tests/controllers/console/datasets/rag_pipeline/test_datasource_auth.py @@ -30,14 +30,13 @@ from graphon.model_runtime.errors.validate import CredentialsValidateFailedError from models.account import Account from services.datasource_provider_service import DatasourceProviderService from services.plugin.oauth_service import OAuthProxyService +from tests.unit_tests.model_factories import make_account _PROVIDER_ID = "langgenius/notion_datasource/notion" def _account() -> Account: - account = Account(name="Datasource Auth Tester", email="datasource-auth@example.com") - account.id = "user-1" - return account + return make_account(account_id="user-1", name="Datasource Auth Tester", email="datasource-auth@example.com") def _i18n(text: str) -> dict[str, str]: diff --git a/api/tests/unit_tests/controllers/console/datasets/rag_pipeline/test_rag_pipeline.py b/api/tests/unit_tests/controllers/console/datasets/rag_pipeline/test_rag_pipeline.py index 5ef83ea902f..53445379c5c 100644 --- a/api/tests/unit_tests/controllers/console/datasets/rag_pipeline/test_rag_pipeline.py +++ b/api/tests/unit_tests/controllers/console/datasets/rag_pipeline/test_rag_pipeline.py @@ -29,6 +29,7 @@ from services.errors.account import NoPermissionError from services.errors.rag_pipeline import RagPipelineResourceNotFoundError from tests.unit_tests.config_override import config_overrides_context from tests.unit_tests.controllers.rbac_introspection import rbac_checks +from tests.unit_tests.model_factories import make_account def _template_item() -> dict[str, object]: @@ -63,9 +64,7 @@ def _payload() -> dict[str, object]: def _account() -> Account: - account = Account(name="Test User", email="test@example.com") - account.id = "account-1" - return account + return make_account(name="Test User", email="test@example.com") def _pipeline() -> Pipeline: diff --git a/api/tests/unit_tests/controllers/console/datasets/rag_pipeline/test_rag_pipeline_datasets.py b/api/tests/unit_tests/controllers/console/datasets/rag_pipeline/test_rag_pipeline_datasets.py index 3e2c9cefd04..a892ad820fb 100644 --- a/api/tests/unit_tests/controllers/console/datasets/rag_pipeline/test_rag_pipeline_datasets.py +++ b/api/tests/unit_tests/controllers/console/datasets/rag_pipeline/test_rag_pipeline_datasets.py @@ -19,12 +19,16 @@ from controllers.console.datasets.rag_pipeline.rag_pipeline_datasets import ( ) from models.account import Account, TenantAccountRole from services.entities.dsl_entities import ImportStatus +from tests.unit_tests.model_factories import make_account def _account(*, editor: bool) -> Account: - account = Account(name="RAG Pipeline Tester", email="rag-pipeline@example.com") - account.role = TenantAccountRole.EDITOR if editor else TenantAccountRole.NORMAL - return account + return make_account( + account_id=None, + name="RAG Pipeline Tester", + email="rag-pipeline@example.com", + role=TenantAccountRole.EDITOR if editor else TenantAccountRole.NORMAL, + ) class TestCreateRagPipelineDatasetApi: diff --git a/api/tests/unit_tests/controllers/console/datasets/rag_pipeline/test_rag_pipeline_draft_variable.py b/api/tests/unit_tests/controllers/console/datasets/rag_pipeline/test_rag_pipeline_draft_variable.py index 33ff6a39a81..c5f43180cee 100644 --- a/api/tests/unit_tests/controllers/console/datasets/rag_pipeline/test_rag_pipeline_draft_variable.py +++ b/api/tests/unit_tests/controllers/console/datasets/rag_pipeline/test_rag_pipeline_draft_variable.py @@ -32,6 +32,7 @@ from models.account import Account, TenantAccountRole from models.dataset import Pipeline from models.workflow import Workflow, WorkflowDraftVariable, WorkflowType from tests.unit_tests.controllers.rbac_introspection import rbac_checks +from tests.unit_tests.model_factories import make_workflow @pytest.fixture @@ -48,17 +49,12 @@ def _pipeline(*, pipeline_id: str = "p1", tenant_id: str = "t1") -> Pipeline: def _workflow(pipeline: Pipeline, environment_variables=()) -> Workflow: - return Workflow.new( + return make_workflow( tenant_id=pipeline.tenant_id, app_id=pipeline.id, - type=WorkflowType.RAG_PIPELINE, - version=Workflow.VERSION_DRAFT, + workflow_type=WorkflowType.RAG_PIPELINE, graph="{}", - features="{}", - created_by="account-1", environment_variables=environment_variables, - conversation_variables=[], - rag_pipeline_variables=[], ) diff --git a/api/tests/unit_tests/controllers/console/datasets/rag_pipeline/test_rag_pipeline_import.py b/api/tests/unit_tests/controllers/console/datasets/rag_pipeline/test_rag_pipeline_import.py index c2e80c24bd5..9875ef27c1b 100644 --- a/api/tests/unit_tests/controllers/console/datasets/rag_pipeline/test_rag_pipeline_import.py +++ b/api/tests/unit_tests/controllers/console/datasets/rag_pipeline/test_rag_pipeline_import.py @@ -24,12 +24,11 @@ from models.dataset import Pipeline from models.engine import db from services.entities.dsl_entities import CheckDependenciesResult, ImportStatus from services.rag_pipeline.rag_pipeline_dsl_service import RagPipelineImportInfo +from tests.unit_tests.model_factories import make_account def _account() -> Account: - account = Account(name="RAG Import Tester", email="rag-import@example.com") - account.id = "account-1" - return account + return make_account(name="RAG Import Tester", email="rag-import@example.com") @pytest.fixture diff --git a/api/tests/unit_tests/controllers/console/datasets/rag_pipeline/test_rag_pipeline_workflow.py b/api/tests/unit_tests/controllers/console/datasets/rag_pipeline/test_rag_pipeline_workflow.py index f4a2e2c948f..294e0c04f35 100644 --- a/api/tests/unit_tests/controllers/console/datasets/rag_pipeline/test_rag_pipeline_workflow.py +++ b/api/tests/unit_tests/controllers/console/datasets/rag_pipeline/test_rag_pipeline_workflow.py @@ -25,7 +25,7 @@ from controllers.console.datasets.rag_pipeline.rag_pipeline_workflow import ( WorkflowUpdatePayload, ) from controllers.web.error import InvokeRateLimitError as InvokeRateLimitHttpError -from models.account import Account, Tenant, TenantAccountRole +from models.account import Account, TenantAccountRole from models.dataset import Dataset, Pipeline from models.engine import db from models.enums import PermissionEnum @@ -35,6 +35,7 @@ from services.errors.llm import InvokeRateLimitError from services.errors.rag_pipeline import RagPipelineResourceNotFoundError from services.rag_pipeline.rag_pipeline import RagPipelineService from tests.unit_tests.config_override import config_overrides_context +from tests.unit_tests.model_factories import make_account, make_dataset, make_tenant DEFAULT_WORKFLOW_TENANT_ID = "00000000-0000-0000-0000-000000000001" DEFAULT_WORKFLOW_APP_ID = "00000000-0000-0000-0000-000000000002" @@ -68,13 +69,13 @@ def _make_workflow(**overrides: object) -> Workflow: def _account() -> Account: - account = Account(name="Alice", email="alice@example.com") - account.id = DEFAULT_WORKFLOW_CREATED_BY - account.role = TenantAccountRole.EDITOR - tenant = Tenant(name="Tenant") - tenant.id = DEFAULT_WORKFLOW_TENANT_ID - account._current_tenant = tenant - return account + return make_account( + account_id=DEFAULT_WORKFLOW_CREATED_BY, + name="Alice", + email="alice@example.com", + role=TenantAccountRole.EDITOR, + tenant=make_tenant(tenant_id=DEFAULT_WORKFLOW_TENANT_ID, name="Tenant"), + ) def _pipeline() -> Pipeline: @@ -84,10 +85,9 @@ def _pipeline() -> Pipeline: def _dataset(*, tenant_id: str = DEFAULT_WORKFLOW_TENANT_ID, maintainer: str = DEFAULT_WORKFLOW_CREATED_BY) -> Dataset: - return Dataset( - id=DEFAULT_DATASET_ID, + return make_dataset( + dataset_id=DEFAULT_DATASET_ID, tenant_id=tenant_id, - name="Dataset", created_by=maintainer, maintainer=maintainer, permission=PermissionEnum.ONLY_ME, @@ -129,7 +129,7 @@ def test_draft_rag_pipeline_workflow_get_serializes_response_model() -> None: api = module.DraftRagPipelineApi() handler = unwrap_all(api.get) - response = handler(api, _pipeline()) + response = handler(api, db.session(), _pipeline()) assert response["id"] == DEFAULT_WORKFLOW_ID assert response["graph"] == {"nodes": [], "edges": []} diff --git a/api/tests/unit_tests/controllers/console/datasets/rag_pipeline/test_rag_pipeline_workflow_apis.py b/api/tests/unit_tests/controllers/console/datasets/rag_pipeline/test_rag_pipeline_workflow_apis.py index 46009877a09..fd26d0ec33f 100644 --- a/api/tests/unit_tests/controllers/console/datasets/rag_pipeline/test_rag_pipeline_workflow_apis.py +++ b/api/tests/unit_tests/controllers/console/datasets/rag_pipeline/test_rag_pipeline_workflow_apis.py @@ -255,7 +255,7 @@ def workflow_author(sqlite_database: scoped_session[Session]) -> Account: class TestDraftWorkflowApi: - def test_get_draft_success(self, app: Flask, workflow_author: Account) -> None: + def test_get_draft_success(self, app: Flask, workflow_author: Account, sqlite_session: Session) -> None: api = DraftRagPipelineApi() method = unwrap(api.get) @@ -272,7 +272,7 @@ class TestDraftWorkflowApi: return_value=service, ), ): - result = method(api, pipeline) + result = method(api, sqlite_session, pipeline) assert result["id"] == "workflow-1" assert result["graph"] == {"nodes": [], "edges": []} @@ -285,7 +285,7 @@ class TestDraftWorkflowApi: } assert result["updated_by"] is None - def test_get_draft_not_exist(self, app: Flask) -> None: + def test_get_draft_not_exist(self, app: Flask, sqlite_session: Session) -> None: api = DraftRagPipelineApi() method = unwrap(api.get) @@ -301,7 +301,7 @@ class TestDraftWorkflowApi: ), ): with pytest.raises(DraftWorkflowNotExist): - method(api, pipeline) + method(api, sqlite_session, pipeline) def test_sync_hash_not_match(self, app: Flask) -> None: api = DraftRagPipelineApi() diff --git a/api/tests/unit_tests/controllers/console/datasets/test_data_source_notion_apis.py b/api/tests/unit_tests/controllers/console/datasets/test_data_source_notion_apis.py index b96c09334b6..a0a7e37b3e3 100644 --- a/api/tests/unit_tests/controllers/console/datasets/test_data_source_notion_apis.py +++ b/api/tests/unit_tests/controllers/console/datasets/test_data_source_notion_apis.py @@ -20,23 +20,20 @@ from controllers.console.datasets.data_source import ( ) from core.rag.index_processor.constant.index_type import IndexStructureType from models import Account, Dataset, Document -from models.dataset import DataSourceType, DocumentCreatedFrom +from models.dataset import DataSourceType +from tests.unit_tests.model_factories import make_dataset, make_document def _dataset() -> Dataset: - return Dataset(id="ds-1", tenant_id="tenant-1", name="Dataset", created_by="u1") + return make_dataset(dataset_id="ds-1", created_by="u1") def _document() -> Document: - return Document( - id="d1", - tenant_id="tenant-1", + return make_document( + document_id="d1", dataset_id="ds-1", - position=1, data_source_type=DataSourceType.NOTION_IMPORT, - batch="batch-1", name="Notion page", - created_from=DocumentCreatedFrom.WEB, created_by="u1", ) diff --git a/api/tests/unit_tests/controllers/console/datasets/test_datasets_segments.py b/api/tests/unit_tests/controllers/console/datasets/test_datasets_segments.py index 9b258aec8bc..00e4737228d 100644 --- a/api/tests/unit_tests/controllers/console/datasets/test_datasets_segments.py +++ b/api/tests/unit_tests/controllers/console/datasets/test_datasets_segments.py @@ -37,6 +37,7 @@ from models.enums import PermissionEnum, SegmentStatus, SegmentType from models.model import UploadFile from services.errors.chunk import ChildChunkDeleteIndexError as ChildChunkDeleteIndexServiceError from services.errors.chunk import ChildChunkIndexingError as ChildChunkIndexingServiceError +from tests.unit_tests.model_factories import make_account, make_dataset, make_document, make_upload_file def _segment(): @@ -82,10 +83,9 @@ def _child_chunk(): def _account() -> Account: - account = Account(name="Dataset Editor", email="dataset-editor@example.com") - account.id = "u1" - account.role = TenantAccountRole.OWNER - return account + return make_account( + account_id="u1", name="Dataset Editor", email="dataset-editor@example.com", role=TenantAccountRole.OWNER + ) def _dataset( @@ -96,10 +96,9 @@ def _dataset( embedding_model_provider: str | None = None, embedding_model: str | None = None, ) -> Dataset: - return Dataset( - id=dataset_id, + return make_dataset( + dataset_id=dataset_id, tenant_id=tenant_id, - name="Dataset", description="", provider="vendor", permission=PermissionEnum.ONLY_ME, @@ -117,14 +116,10 @@ def _document( tenant_id: str = "tenant-1", doc_form: IndexStructureType = IndexStructureType.PARAGRAPH_INDEX, ) -> Document: - return Document( - id=document_id, + return make_document( + document_id=document_id, tenant_id=tenant_id, dataset_id=dataset_id, - position=1, - data_source_type="upload_file", - batch="batch-1", - name="Document", created_from="api", created_by="u1", doc_form=doc_form, @@ -132,21 +127,17 @@ def _document( def _upload_file(*, name: str = "test.csv", file_id: str = "test-file-id") -> UploadFile: - upload_file = UploadFile( - tenant_id="tenant-1", + return make_upload_file( + file_id=file_id, storage_type="opendal", key="test-key", name=name, size=0, extension=name.rsplit(".", maxsplit=1)[-1], mime_type="text/csv", - created_by_role="account", created_by="u1", created_at=datetime.now(), - used=False, ) - upload_file.id = file_id - return upload_file class SQLiteControllerTest: diff --git a/api/tests/unit_tests/controllers/console/explore/test_trial.py b/api/tests/unit_tests/controllers/console/explore/test_trial.py index 03d93348382..dc7d3ac12c3 100644 --- a/api/tests/unit_tests/controllers/console/explore/test_trial.py +++ b/api/tests/unit_tests/controllers/console/explore/test_trial.py @@ -49,6 +49,7 @@ from services.app_ref_service import AppRef, MessageRef from services.errors.audio import SpeechToTextDisabledServiceError from services.errors.conversation import ConversationNotExistsError from services.errors.llm import InvokeRateLimitError +from tests.unit_tests.model_factories import make_app, make_upload_file unwrap: Any = inspect_unwrap @@ -80,32 +81,19 @@ def trial_app_usage(monkeypatch: pytest.MonkeyPatch) -> MagicMock: def _app(*, app_id: str, mode: AppMode, tenant_id: str = "tenant-1") -> App: - return App( - id=app_id, - tenant_id=tenant_id, - name="Trial App", - mode=mode, - enable_site=True, - enable_api=False, - ) + return make_app(app_id=app_id, tenant_id=tenant_id, name="Trial App", mode=mode, icon_type=None, enable_api=False) def _upload_file(*, file_id: str = "upload-file-id", tenant_id: str = "app-tenant-id") -> UploadFile: - upload_file = UploadFile( + return make_upload_file( + file_id=file_id, tenant_id=tenant_id, storage_type="opendal", key="trial/file.txt", name="file.txt", size=1, - extension="txt", - mime_type="text/plain", - created_by_role="account", created_by="u1", - created_at=datetime(2024, 1, 1), - used=False, ) - upload_file.id = file_id - return upload_file def _file_data() -> Any: diff --git a/api/tests/unit_tests/controllers/console/explore/test_wraps.py b/api/tests/unit_tests/controllers/console/explore/test_wraps.py index 53f87e22c5f..c3f104de1a6 100644 --- a/api/tests/unit_tests/controllers/console/explore/test_wraps.py +++ b/api/tests/unit_tests/controllers/console/explore/test_wraps.py @@ -21,7 +21,8 @@ from controllers.console.explore.wraps import ( trial_feature_enable, user_allowed_to_access_app, ) -from models import Account, AccountTrialAppRecord, App, AppMode, InstalledApp, TrialApp +from models import Account, AccountTrialAppRecord, App, InstalledApp, TrialApp +from tests.unit_tests.model_factories import make_account, make_app def _bind_database(monkeypatch: pytest.MonkeyPatch, sqlite_session: Session) -> None: @@ -31,22 +32,11 @@ def _bind_database(monkeypatch: pytest.MonkeyPatch, sqlite_session: Session) -> def _account(*, account_id: str | None = None) -> Account: - account = Account(name="Explore user", email="user@example.com") - if account_id is not None: - account.id = account_id - return account + return make_account(account_id=account_id, name="Explore user", email="user@example.com") def _app() -> App: - app = App( - tenant_id=str(uuid4()), - name="Explore App", - mode=AppMode.CHAT, - enable_site=True, - enable_api=True, - ) - app.id = str(uuid4()) - return app + return make_app(app_id=str(uuid4()), tenant_id=str(uuid4()), name="Explore App", icon_type=None) def _installed_app(*, app_id: str, tenant_id: str) -> InstalledApp: diff --git a/api/tests/unit_tests/controllers/console/snippets/test_snippet_workflow.py b/api/tests/unit_tests/controllers/console/snippets/test_snippet_workflow.py index 8af0e633620..6d762791958 100644 --- a/api/tests/unit_tests/controllers/console/snippets/test_snippet_workflow.py +++ b/api/tests/unit_tests/controllers/console/snippets/test_snippet_workflow.py @@ -15,13 +15,16 @@ from werkzeug.exceptions import HTTPException, NotFound from controllers.console.snippets import snippet_workflow as snippet_workflow_module from models.account import Account, TenantAccountRole from models.snippet import CustomizedSnippet +from tests.unit_tests.model_factories import make_account def _account(account_id: str = "account-1") -> Account: - account = Account(name="Test User", email=f"{account_id}@example.com") - account.id = account_id - account.role = TenantAccountRole.EDITOR - return account + return make_account( + account_id=account_id, + name="Test User", + email=f"{account_id}@example.com", + role=TenantAccountRole.EDITOR, + ) def _snippet(**overrides) -> CustomizedSnippet: @@ -641,7 +644,9 @@ def test_delete_published_snippet_workflow_raises_bad_request_when_in_use( assert exc_info.value.code == 400 -def test_workflow_run_detail_raises_not_found_when_run_missing(app: Flask, monkeypatch: pytest.MonkeyPatch) -> None: +def test_workflow_run_detail_raises_not_found_when_run_missing( + app: Flask, monkeypatch: pytest.MonkeyPatch, sqlite_session: Session +) -> None: snippet = _snippet() monkeypatch.setattr( snippet_workflow_module, @@ -654,7 +659,7 @@ def test_workflow_run_detail_raises_not_found_when_run_missing(app: Flask, monke with app.test_request_context("/snippets/snippet-1/workflow-runs/run-1"): with pytest.raises(NotFound, match="Workflow run not found"): - handler(api, snippet=snippet, run_id="run-1") + handler(api, sqlite_session, snippet=snippet, run_id="run-1") def test_draft_node_last_run_raises_not_found_when_execution_missing( diff --git a/api/tests/unit_tests/controllers/console/tag/test_tags.py b/api/tests/unit_tests/controllers/console/tag/test_tags.py index d40b84bd4dd..41aa923a471 100644 --- a/api/tests/unit_tests/controllers/console/tag/test_tags.py +++ b/api/tests/unit_tests/controllers/console/tag/test_tags.py @@ -20,7 +20,7 @@ from controllers.console.tag.tags import ( ) from machinery.context import RequestContext from models import Account -from models.account import AccountStatus, TenantAccountRole +from models.account import TenantAccountRole from models.enums import TagType from services.tag_application_service import ( TagApplicationError, @@ -32,6 +32,7 @@ from services.tag_application_service import ( UpdateTagInput, ) from tests.unit_tests.config_override import config_overrides_context +from tests.unit_tests.model_factories import make_account def unwrap(func): @@ -58,14 +59,7 @@ def request_context() -> RequestContext: def _account(role: TenantAccountRole) -> Account: - account = Account( - name="Tag User", - email=f"{role.value}@example.com", - status=AccountStatus.ACTIVE, - ) - account.id = "user-1" - account.role = role - return account + return make_account(account_id="user-1", name="Tag User", email=f"{role.value}@example.com", role=role) @pytest.fixture diff --git a/api/tests/unit_tests/controllers/console/test_files.py b/api/tests/unit_tests/controllers/console/test_files.py index e5db1174b83..bf2b3da2671 100644 --- a/api/tests/unit_tests/controllers/console/test_files.py +++ b/api/tests/unit_tests/controllers/console/test_files.py @@ -1,5 +1,4 @@ import io -from datetime import datetime from unittest.mock import patch import pytest @@ -22,12 +21,11 @@ from controllers.console.files import ( FileSupportTypeApi, upload_file_from_request, ) -from extensions.storage.storage_type import StorageType from machinery.context import RequestContext from models import Account from models.account import AccountStatus, TenantAccountRole -from models.enums import CreatorUserRole from models.model import UploadFile +from tests.unit_tests.model_factories import make_upload_file def unwrap(func): @@ -40,21 +38,13 @@ def unwrap(func): def _upload_file(*, file_id: str = "file-id-123", size: int = 1024) -> UploadFile: - upload_file = UploadFile( + return make_upload_file( + file_id=file_id, tenant_id="tenant-123", - storage_type=StorageType.LOCAL, key=f"upload/{file_id}/test.txt", - name="test.txt", size=size, - extension="txt", - mime_type="text/plain", - created_by_role=CreatorUserRole.ACCOUNT, created_by="user-123", - created_at=datetime(2024, 1, 1), - used=False, ) - upload_file.id = file_id - return upload_file def _request_context(*, workspace_id: str = "tenant-1") -> RequestContext: diff --git a/api/tests/unit_tests/controllers/console/test_human_input_form.py b/api/tests/unit_tests/controllers/console/test_human_input_form.py index 39e8f089ce9..d799c634505 100644 --- a/api/tests/unit_tests/controllers/console/test_human_input_form.py +++ b/api/tests/unit_tests/controllers/console/test_human_input_form.py @@ -23,12 +23,12 @@ from controllers.console.human_input_form import ( from core.workflow.human_input_policy import HumanInputSurface from graphon.enums import WorkflowExecutionStatus, WorkflowType from models import Account -from models.account import AccountStatus from models.enums import CreatorUserRole, WorkflowRunTriggeredFrom from models.human_input import RecipientType from models.model import App, AppMode from models.workflow import WorkflowRun from tests.unit_tests.config_override import apply_config_overrides +from tests.unit_tests.model_factories import make_account, make_app @pytest.fixture(autouse=True) @@ -38,20 +38,15 @@ def bind_console_human_input_database(sqlite_engine: Engine, monkeypatch: pytest def _account(*, account_id: str = "user-1") -> Account: - account = Account(name="Console User", email=f"{account_id}@example.com", status=AccountStatus.ACTIVE) - account.id = account_id - return account + return make_account(account_id=account_id, name="Console User", email=f"{account_id}@example.com") def _app() -> App: - return App( - id="app-1", + return make_app( tenant_id="t1", name="Human Input App", - description="", mode=AppMode.WORKFLOW, - enable_site=True, - enable_api=True, + icon_type=None, max_active_requests=0, ) diff --git a/api/tests/unit_tests/controllers/console/test_workflow_run_archive.py b/api/tests/unit_tests/controllers/console/test_workflow_run_archive.py index 6348a2f8ce9..5b13c907f2d 100644 --- a/api/tests/unit_tests/controllers/console/test_workflow_run_archive.py +++ b/api/tests/unit_tests/controllers/console/test_workflow_run_archive.py @@ -37,13 +37,11 @@ _ENDPOINTS = [ WorkflowRunArchiveDownloadFileApi.get, ] from tests.unit_tests.config_override import apply_config_overrides +from tests.unit_tests.model_factories import make_account def _account(role: TenantAccountRole) -> Account: - account = Account(name="Test User", email="user@example.com") - account.id = "account-1" - account.role = role - return account + return make_account(name="Test User", email="user@example.com", role=role) def _original(method): diff --git a/api/tests/unit_tests/controllers/console/test_workspace_members.py b/api/tests/unit_tests/controllers/console/test_workspace_members.py index 2528b387623..0be530a33d6 100644 --- a/api/tests/unit_tests/controllers/console/test_workspace_members.py +++ b/api/tests/unit_tests/controllers/console/test_workspace_members.py @@ -9,12 +9,11 @@ from controllers.console.workspace.error import InvalidMemberRoleError from controllers.console.workspace.members import MemberInviteEmailApi from enums import DeploymentEdition from models.account import Account, Tenant, TenantAccountRole +from tests.unit_tests.model_factories import make_tenant def _tenant() -> Tenant: - tenant = Tenant(name="Test Tenant") - tenant.id = "tenant-1" - return tenant + return make_tenant(name="Test Tenant") @pytest.fixture diff --git a/api/tests/unit_tests/controllers/console/workspace/test_agent_providers.py b/api/tests/unit_tests/controllers/console/workspace/test_agent_providers.py index f62a0818333..462d0e3bb32 100644 --- a/api/tests/unit_tests/controllers/console/workspace/test_agent_providers.py +++ b/api/tests/unit_tests/controllers/console/workspace/test_agent_providers.py @@ -8,12 +8,11 @@ from controllers.console.workspace.agent_providers import ( AgentProviderListApi, ) from models.account import Account +from tests.unit_tests.model_factories import make_account def _account() -> Account: - account = Account(name="Agent Provider Tester", email="agent-provider@example.com") - account.id = "user1" - return account + return make_account(account_id="user1", name="Agent Provider Tester", email="agent-provider@example.com") class TestAgentProviderListApi: diff --git a/api/tests/unit_tests/controllers/console/workspace/test_load_balancing_config.py b/api/tests/unit_tests/controllers/console/workspace/test_load_balancing_config.py index cdfdcb0ca8c..e436a4e6393 100644 --- a/api/tests/unit_tests/controllers/console/workspace/test_load_balancing_config.py +++ b/api/tests/unit_tests/controllers/console/workspace/test_load_balancing_config.py @@ -19,6 +19,7 @@ if not hasattr(builtins, "MethodView"): builtins.MethodView = MethodView # type: ignore[attr-defined] from models.account import Account, TenantAccountRole +from tests.unit_tests.model_factories import make_account @pytest.fixture @@ -57,10 +58,7 @@ def load_balancing_module(monkeypatch: pytest.MonkeyPatch): def _account(role: TenantAccountRole) -> Account: - account = Account(name="Owner", email="owner@example.com") - account.id = "account-1" - account.role = role - return account + return make_account(name="Owner", email="owner@example.com", role=role) def _prepare_context(module, monkeypatch: pytest.MonkeyPatch, role=TenantAccountRole.OWNER): diff --git a/api/tests/unit_tests/controllers/console/workspace/test_models.py b/api/tests/unit_tests/controllers/console/workspace/test_models.py index 9f49e97d3d2..38f4d21db0d 100644 --- a/api/tests/unit_tests/controllers/console/workspace/test_models.py +++ b/api/tests/unit_tests/controllers/console/workspace/test_models.py @@ -28,12 +28,11 @@ from controllers.console.workspace.models import ( from graphon.model_runtime.entities.model_entities import ModelType from graphon.model_runtime.errors.validate import CredentialsValidateFailedError from models import Account +from tests.unit_tests.model_factories import make_account def _account() -> Account: - account = Account(name="Model User", email="model-user@example.com") - account.id = "u1" - return account + return make_account(account_id="u1", name="Model User", email="model-user@example.com") class TestDefaultModelApi: diff --git a/api/tests/unit_tests/controllers/console/workspace/test_plugin.py b/api/tests/unit_tests/controllers/console/workspace/test_plugin.py index ea0f79a851c..5424dc65634 100644 --- a/api/tests/unit_tests/controllers/console/workspace/test_plugin.py +++ b/api/tests/unit_tests/controllers/console/workspace/test_plugin.py @@ -83,6 +83,7 @@ from models.account import ( TenantPluginInstallPermission, ) from tests.unit_tests.config_override import config_overrides_context +from tests.unit_tests.model_factories import make_account def _plugin_category_list_item(category: str = "tool") -> dict[str, Any]: @@ -358,10 +359,7 @@ def _expected_dynamic_option_dump() -> dict[str, Any]: def _account(role: TenantAccountRole = TenantAccountRole.OWNER) -> Account: - account = Account(name="Test User", email="u1@example.com") - account.id = "u1" - account.role = role - return account + return make_account(account_id="u1", name="Test User", email="u1@example.com", role=role) @pytest.fixture diff --git a/api/tests/unit_tests/controllers/console/workspace/test_rbac.py b/api/tests/unit_tests/controllers/console/workspace/test_rbac.py index 15af9f1614d..3bbfe1e281c 100644 --- a/api/tests/unit_tests/controllers/console/workspace/test_rbac.py +++ b/api/tests/unit_tests/controllers/console/workspace/test_rbac.py @@ -28,6 +28,7 @@ from controllers.console.workspace import rbac as rbac_mod from controllers.console.workspace.rbac import _RolesListQuery from enums import DeploymentEdition from models import Account +from tests.unit_tests.model_factories import make_account @pytest.fixture @@ -47,9 +48,7 @@ def _rbac_config(config_overrides) -> None: def _account() -> Account: - account = Account(name="RBAC User", email="rbac@example.com") - account.id = "acct-1" - return account + return make_account(account_id="acct-1", name="RBAC User", email="rbac@example.com") class TestCurrentIds: diff --git a/api/tests/unit_tests/controllers/console/workspace/test_snippets.py b/api/tests/unit_tests/controllers/console/workspace/test_snippets.py index 75dc6180e90..78943e87482 100644 --- a/api/tests/unit_tests/controllers/console/workspace/test_snippets.py +++ b/api/tests/unit_tests/controllers/console/workspace/test_snippets.py @@ -14,6 +14,7 @@ from controllers.console.workspace import snippets as snippets_module from models.account import Account, TenantAccountRole from models.snippet import CustomizedSnippet from services.snippet_dsl_service import ImportStatus, SnippetImportInfo +from tests.unit_tests.model_factories import make_account @pytest.fixture(autouse=True) @@ -33,10 +34,12 @@ def _patch_snippet_service_factory( def _account(account_id: str = "account-1") -> Account: - account = Account(name="Test User", email=f"{account_id}@example.com") - account.id = account_id - account.role = TenantAccountRole.EDITOR - return account + return make_account( + account_id=account_id, + name="Test User", + email=f"{account_id}@example.com", + role=TenantAccountRole.EDITOR, + ) def _snippet(**overrides) -> CustomizedSnippet: diff --git a/api/tests/unit_tests/controllers/files/test_appdeploy_files.py b/api/tests/unit_tests/controllers/files/test_appdeploy_files.py index b9e7d6e289e..04623b8a2fc 100644 --- a/api/tests/unit_tests/controllers/files/test_appdeploy_files.py +++ b/api/tests/unit_tests/controllers/files/test_appdeploy_files.py @@ -12,7 +12,7 @@ from uuid import UUID import jwt import pytest from flask import Flask -from sqlalchemy import update +from sqlalchemy import null, update from sqlalchemy.engine import Engine from sqlalchemy.orm import Session, sessionmaker @@ -775,7 +775,7 @@ def test_content_disposition_follows_the_inline_whitelist( @pytest.mark.usefixtures("sqlite_db", "stored_bytes") def test_content_downloads_a_file_with_no_recorded_mime_type(app: Flask, sqlite_session: Session) -> None: upload_file = _persist_upload_file(sqlite_session, owner_id="anyone") - sqlite_session.execute(update(UploadFile).where(UploadFile.id == upload_file.id).values(mime_type=None)) + sqlite_session.execute(update(UploadFile).where(UploadFile.id == upload_file.id).values(mime_type=null())) sqlite_session.commit() token = _content_token(file_id=upload_file.id, kind=FileKind.UPLOAD) diff --git a/api/tests/unit_tests/controllers/inner_api/plugin/test_plugin.py b/api/tests/unit_tests/controllers/inner_api/plugin/test_plugin.py index 3f8a6efde6b..c0e644e9c73 100644 --- a/api/tests/unit_tests/controllers/inner_api/plugin/test_plugin.py +++ b/api/tests/unit_tests/controllers/inner_api/plugin/test_plugin.py @@ -36,12 +36,11 @@ from controllers.inner_api.plugin.plugin import ( from core.workflow.file_reference import build_file_reference from models import Account, Tenant from tests.unit_tests.config_override import apply_config_overrides +from tests.unit_tests.model_factories import make_tenant def _tenant() -> Tenant: - tenant = Tenant(name="Test Tenant") - tenant.id = "tenant-id" - return tenant + return make_tenant(tenant_id="tenant-id", name="Test Tenant") def _user() -> Account: diff --git a/api/tests/unit_tests/controllers/inner_api/test_agent_files.py b/api/tests/unit_tests/controllers/inner_api/test_agent_files.py index f5bb418d76a..040a0f13abc 100644 --- a/api/tests/unit_tests/controllers/inner_api/test_agent_files.py +++ b/api/tests/unit_tests/controllers/inner_api/test_agent_files.py @@ -16,14 +16,13 @@ from core.workflow.file_reference import build_file_reference from models.account import Account, Tenant from services.file_request_service import DownloadFileRequestResult from tests.unit_tests.config_override import apply_config_overrides +from tests.unit_tests.model_factories import make_tenant MODULE = "controllers.inner_api.agent.files" def _tenant() -> Tenant: - tenant = Tenant(name="Test Workspace") - tenant.id = "tenant-1" - return tenant + return make_tenant(name="Test Workspace") def _raw[R](method: Callable[..., R]) -> Callable[..., R]: diff --git a/api/tests/unit_tests/controllers/inner_api/test_agent_llm.py b/api/tests/unit_tests/controllers/inner_api/test_agent_llm.py index f37e1acd971..f9eb3006bc5 100644 --- a/api/tests/unit_tests/controllers/inner_api/test_agent_llm.py +++ b/api/tests/unit_tests/controllers/inner_api/test_agent_llm.py @@ -50,7 +50,7 @@ def _agent_inner_auth() -> Generator[None]: yield -def _app() -> Flask: +def _flask_app() -> Flask: app = Flask(__name__) app.config["TESTING"] = True app.register_blueprint(inner_api_bp) @@ -75,7 +75,7 @@ def test_post_streams_plugin_compatible_envelope() -> None: patch("controllers.inner_api.agent.llm.AgentLLMInnerService.invoke", return_value=iter([chunk])), ): response = ( - _app() + _flask_app() .test_client() .post( "/inner/api/agent/llm/invoke", @@ -134,7 +134,7 @@ def test_post_preserves_prompt_messages_without_transport_validation() -> None: patch("controllers.inner_api.agent.llm.AgentLLMInnerService.invoke", return_value=iter([])), ): response = ( - _app() + _flask_app() .test_client() .post( "/inner/api/agent/llm/invoke", @@ -150,7 +150,7 @@ def test_post_preserves_prompt_messages_without_transport_validation() -> None: def test_post_rejects_invalid_body_before_model_resolution() -> None: with _agent_inner_auth(): response = ( - _app() + _flask_app() .test_client() .post( "/inner/api/agent/llm/invoke", @@ -176,7 +176,7 @@ def test_post_preserves_preflight_quota_failure() -> None: ), ): response = ( - _app() + _flask_app() .test_client() .post( "/inner/api/agent/llm/invoke", @@ -208,7 +208,7 @@ def test_post_preserves_stream_quota_failure() -> None: ), ): response = ( - _app() + _flask_app() .test_client() .post( "/inner/api/agent/llm/invoke", diff --git a/api/tests/unit_tests/controllers/mcp/test_mcp.py b/api/tests/unit_tests/controllers/mcp/test_mcp.py index 03679199403..0dd960de400 100644 --- a/api/tests/unit_tests/controllers/mcp/test_mcp.py +++ b/api/tests/unit_tests/controllers/mcp/test_mcp.py @@ -18,6 +18,7 @@ from models.engine import db from models.enums import EndUserType from models.model import App, AppAnnotationSetting, AppMCPServer, AppModelConfig, EndUser, IconType from models.workflow import Workflow, WorkflowType +from tests.unit_tests.model_factories import make_end_user @pytest.fixture @@ -127,14 +128,13 @@ def _app( def _end_user() -> EndUser: - end_user = EndUser( + return make_end_user( + end_user_id=str(uuid4()), tenant_id=_TENANT_ID, app_id=_APP_ID, - type=EndUserType.MCP, + end_user_type=EndUserType.MCP, session_id=_SERVER_ID, ) - end_user.id = str(uuid4()) - return end_user class DummyResult: diff --git a/api/tests/unit_tests/controllers/openapi/auth/test_composition.py b/api/tests/unit_tests/controllers/openapi/auth/test_composition.py index d61a38848f0..e77bb15c9cc 100644 --- a/api/tests/unit_tests/controllers/openapi/auth/test_composition.py +++ b/api/tests/unit_tests/controllers/openapi/auth/test_composition.py @@ -157,7 +157,6 @@ def _selected_webapp_steps(*, scope, app_access_mode): ) data = AuthData( token_type=TokenType.OAUTH_ACCOUNT, - token_hash="x", scopes=frozenset({scope}) if scope is not None else frozenset(), app_access_mode=app_access_mode, ) diff --git a/api/tests/unit_tests/controllers/openapi/auth/test_conditions.py b/api/tests/unit_tests/controllers/openapi/auth/test_conditions.py index 59776892903..8c3f1bc20ed 100644 --- a/api/tests/unit_tests/controllers/openapi/auth/test_conditions.py +++ b/api/tests/unit_tests/controllers/openapi/auth/test_conditions.py @@ -1,3 +1,4 @@ +import uuid from unittest.mock import patch from controllers.common.rbac import PlainApp, RBACCheck @@ -27,6 +28,9 @@ from models.account import TenantAccountRole from services.enterprise.enterprise_service import WebAppAccessMode from tests.unit_tests.config_override import config_overrides_context +PROBE_ID = uuid.uuid4() +OTHER_ID = uuid.uuid4() + def _ctx(token_type=TokenType.OAUTH_ACCOUNT, path_params=None, **kwargs): return RequestContext( @@ -37,7 +41,7 @@ def _ctx(token_type=TokenType.OAUTH_ACCOUNT, path_params=None, **kwargs): def _data(**kwargs): - defaults: dict = {"token_type": TokenType.OAUTH_ACCOUNT, "token_hash": "x", "scopes": frozenset()} + defaults: dict = {"token_type": TokenType.OAUTH_ACCOUNT, "scopes": frozenset()} defaults.update(kwargs) return AuthData(**defaults) @@ -89,9 +93,9 @@ def test_data_cond_returns_false_when_data_none(): def test_data_cond_evaluates_when_data_present(): - c = data_cond(lambda data: data.token_hash == "secret") - assert c(_ctx(), _data(token_hash="secret")) is True - assert c(_ctx(), _data(token_hash="other")) is False + c = data_cond(lambda data: data.token_id == PROBE_ID) + assert c(_ctx(), _data(token_id=PROBE_ID)) is True + assert c(_ctx(), _data(token_id=OTHER_ID)) is False def test_config_cond_ignores_ctx_and_data(): diff --git a/api/tests/unit_tests/controllers/openapi/auth/test_data.py b/api/tests/unit_tests/controllers/openapi/auth/test_data.py index e5171e64303..73aed7bd615 100644 --- a/api/tests/unit_tests/controllers/openapi/auth/test_data.py +++ b/api/tests/unit_tests/controllers/openapi/auth/test_data.py @@ -39,7 +39,6 @@ def test_request_context_scope_optional(): def test_auth_data_is_mutable(): data = AuthData( token_type=TokenType.OAUTH_ACCOUNT, - token_hash="abc", scopes=frozenset({Scope.FULL}), ) data.token_type = TokenType.OAUTH_EXTERNAL_SSO @@ -49,7 +48,6 @@ def test_auth_data_is_mutable(): def test_auth_data_path_params_defaults_empty(): data = AuthData( token_type=TokenType.OAUTH_ACCOUNT, - token_hash="abc", scopes=frozenset(), ) assert data.path_params == {} @@ -58,7 +56,6 @@ def test_auth_data_path_params_defaults_empty(): def test_auth_data_account_id_optional(): data = AuthData( token_type=TokenType.OAUTH_EXTERNAL_SSO, - token_hash="abc", scopes=frozenset({Scope.APPS_RUN}), external_identity=ExternalIdentity(email="u@sso.com"), ) @@ -69,25 +66,14 @@ def test_auth_data_external_identity_none_for_account(): data = AuthData( token_type=TokenType.OAUTH_ACCOUNT, account_id=uuid.uuid4(), - token_hash="abc", scopes=frozenset({Scope.FULL}), ) assert data.external_identity is None -def test_auth_data_tenants_default_empty(): - data = AuthData( - token_type=TokenType.OAUTH_ACCOUNT, - token_hash="abc", - scopes=frozenset(), - ) - assert data.tenants == {} - - def test_auth_data_token_id_optional(): data = AuthData( token_type=TokenType.OAUTH_ACCOUNT, - token_hash="abc", scopes=frozenset(), ) assert data.token_id is None @@ -119,7 +105,6 @@ def test_request_context_allowed_roles_set(): def test_auth_data_allowed_roles_default_none(): data = AuthData( token_type=TokenType.OAUTH_ACCOUNT, - token_hash="abc", scopes=frozenset(), ) assert data.allowed_roles is None @@ -131,7 +116,6 @@ def test_auth_data_allowed_roles_set(): roles = frozenset({TenantAccountRole.ADMIN}) data = AuthData( token_type=TokenType.OAUTH_ACCOUNT, - token_hash="abc", scopes=frozenset(), allowed_roles=roles, ) @@ -141,7 +125,6 @@ def test_auth_data_allowed_roles_set(): def test_auth_data_tenant_role_default_none(): data = AuthData( token_type=TokenType.OAUTH_ACCOUNT, - token_hash="abc", scopes=frozenset(), ) assert data.tenant_role is None @@ -152,7 +135,6 @@ def test_auth_data_tenant_role_set(): data = AuthData( token_type=TokenType.OAUTH_ACCOUNT, - token_hash="abc", scopes=frozenset(), tenant_role=TenantAccountRole.ADMIN, ) diff --git a/api/tests/unit_tests/controllers/openapi/auth/test_flow.py b/api/tests/unit_tests/controllers/openapi/auth/test_flow.py index 3ea7ac2b125..1c87c3b6450 100644 --- a/api/tests/unit_tests/controllers/openapi/auth/test_flow.py +++ b/api/tests/unit_tests/controllers/openapi/auth/test_flow.py @@ -11,7 +11,7 @@ def _ctx(): def _data(): - return AuthData(token_type=TokenType.OAUTH_ACCOUNT, token_hash="x", scopes=frozenset()) + return AuthData(token_type=TokenType.OAUTH_ACCOUNT, scopes=frozenset()) def test_applies_returns_true_when_condition_true(): diff --git a/api/tests/unit_tests/controllers/openapi/auth/test_pipeline.py b/api/tests/unit_tests/controllers/openapi/auth/test_pipeline.py index f483c80eb38..fc215fa4dd1 100644 --- a/api/tests/unit_tests/controllers/openapi/auth/test_pipeline.py +++ b/api/tests/unit_tests/controllers/openapi/auth/test_pipeline.py @@ -16,20 +16,16 @@ def _make_identity( token_type=TokenType.OAUTH_ACCOUNT, account_id=None, scopes=None, - token_hash="testhash", subject_email=None, subject_issuer=None, - verified_tenants=None, token_id=None, ): identity = MagicMock() identity.token_type = token_type identity.account_id = account_id or uuid.uuid4() identity.scopes = scopes or frozenset({Scope.FULL}) - identity.token_hash = token_hash identity.subject_email = subject_email identity.subject_issuer = subject_issuer - identity.verified_tenants = verified_tenants or {} identity.token_id = token_id or uuid.uuid4() return identity diff --git a/api/tests/unit_tests/controllers/openapi/auth/test_prepare.py b/api/tests/unit_tests/controllers/openapi/auth/test_prepare.py index 96d823b178e..fa844520e3e 100644 --- a/api/tests/unit_tests/controllers/openapi/auth/test_prepare.py +++ b/api/tests/unit_tests/controllers/openapi/auth/test_prepare.py @@ -21,10 +21,9 @@ from controllers.openapi.auth.prepare import ( from libs.oauth_bearer import TokenType from models import Account, App, EndUser, Tenant, TenantAccountJoin from models.account import AccountStatus, TenantAccountRole, TenantStatus -from models.enums import AppStatus -from models.model import AppMode, IconType from services import end_user_service from services.enterprise.enterprise_service import WebAppAccessMode +from tests.unit_tests.model_factories import make_account, make_app, make_tenant APP_ID = "00000000-0000-0000-0000-000000000001" TENANT_ID = "00000000-0000-0000-0000-000000000002" @@ -34,7 +33,6 @@ ACCOUNT_ID = "00000000-0000-0000-0000-000000000003" def _make_auth_data(**kwargs: object) -> AuthData: return AuthData( token_type=kwargs.pop("token_type", TokenType.OAUTH_ACCOUNT), - token_hash=kwargs.pop("token_hash", "testhash"), scopes=kwargs.pop("scopes", frozenset()), **kwargs, ) @@ -46,32 +44,15 @@ def _app( tenant_id: str = TENANT_ID, enable_api: bool = True, ) -> App: - return App( - id=app_id, - tenant_id=tenant_id, - name="OpenAPI app", - description="", - mode=AppMode.CHAT, - icon_type=IconType.EMOJI, - icon="robot", - icon_background="#FFFFFF", - status=AppStatus.NORMAL, - enable_site=True, - enable_api=enable_api, - max_active_requests=None, - ) + return make_app(app_id=app_id, tenant_id=tenant_id, name="OpenAPI app", enable_api=enable_api) def _tenant(*, tenant_id: str = TENANT_ID, status: TenantStatus = TenantStatus.NORMAL) -> Tenant: - tenant = Tenant(name="OpenAPI tenant", status=status) - tenant.id = tenant_id - return tenant + return make_tenant(tenant_id=tenant_id, name="OpenAPI tenant", status=status) def _account(*, status: AccountStatus = AccountStatus.ACTIVE) -> Account: - account = Account(name="OpenAPI account", email="account@example.com", status=status) - account.id = ACCOUNT_ID - return account + return make_account(account_id=ACCOUNT_ID, name="OpenAPI account", email="account@example.com", status=status) def _persist(session: Session, *models: object) -> None: diff --git a/api/tests/unit_tests/controllers/openapi/auth/test_verify.py b/api/tests/unit_tests/controllers/openapi/auth/test_verify.py index 89c38c59e4d..baad1264abf 100644 --- a/api/tests/unit_tests/controllers/openapi/auth/test_verify.py +++ b/api/tests/unit_tests/controllers/openapi/auth/test_verify.py @@ -10,7 +10,6 @@ from controllers.common.rbac import PlainApp, RBACCheck from controllers.openapi.auth.data import AuthData from controllers.openapi.auth.verify import ( check_acl, - check_app_access, check_app_api_enabled, check_private_app_permission, check_rbac_permission, @@ -32,7 +31,7 @@ def _rbac_config(config_overrides: Callable[..., None]) -> None: def _data(**kwargs) -> AuthData: - defaults: dict = {"token_type": TokenType.OAUTH_ACCOUNT, "token_hash": "hash", "scopes": frozenset({Scope.FULL})} + defaults: dict = {"token_type": TokenType.OAUTH_ACCOUNT, "scopes": frozenset({Scope.FULL})} defaults.update(kwargs) return AuthData(**defaults) @@ -63,27 +62,6 @@ def test_check_workspace_member_passes_when_role_present(): check_workspace_member(_data(tenant_role=TenantAccountRole.NORMAL)) -def test_check_app_access_passes_when_tenant_none(): - check_app_access(_data(tenant=None)) - - -def test_check_app_access_passes_when_member(): - tenant = Tenant(name="Test Tenant") - tenant.id = "t1" - data = _data(account_id=uuid.uuid4(), tenant=tenant) - with patch("controllers.openapi.auth.verify.TenantService.account_belongs_to_tenant", return_value=True): - check_app_access(data) - - -def test_check_app_access_raises_when_not_member(): - tenant = Tenant(name="Test Tenant") - tenant.id = "t1" - data = _data(account_id=uuid.uuid4(), tenant=tenant) - with patch("controllers.openapi.auth.verify.TenantService.account_belongs_to_tenant", return_value=False): - with pytest.raises(Forbidden, match="subject_no_app_access"): - check_app_access(data) - - # --- check_rbac_permission --- _RBAC_REQ = RBACCheck(RBACPermission.APP_VIEW_LAYOUT, PlainApp()) diff --git a/api/tests/unit_tests/controllers/openapi/conftest.py b/api/tests/unit_tests/controllers/openapi/conftest.py index 6d17dcaa39c..f77e35a3b7f 100644 --- a/api/tests/unit_tests/controllers/openapi/conftest.py +++ b/api/tests/unit_tests/controllers/openapi/conftest.py @@ -28,7 +28,6 @@ def _stub_execute( kwargs["auth_data"] = AuthData( token_type=TokenType.OAUTH_ACCOUNT, account_id=uuid.uuid4(), - token_hash="test", token_id=uuid.uuid4(), scopes=frozenset({Scope.FULL}), required_scope=scope, diff --git a/api/tests/unit_tests/controllers/openapi/test_app_payloads.py b/api/tests/unit_tests/controllers/openapi/test_app_payloads.py index c73e6f3f454..2f548714484 100644 --- a/api/tests/unit_tests/controllers/openapi/test_app_payloads.py +++ b/api/tests/unit_tests/controllers/openapi/test_app_payloads.py @@ -13,23 +13,12 @@ from controllers.openapi.apps import ( # pyright: ignore[reportPrivateUsage] parameters_payload, ) from controllers.service_api.app.error import AppUnavailableError -from models.model import App, AppMode, IconType +from models.model import App, AppMode +from tests.unit_tests.model_factories import make_app def _app(*, mode: AppMode = AppMode.CHAT) -> App: - return App( - id="app1", - tenant_id="tenant-1", - name="X", - description="d", - mode=mode, - icon_type=IconType.EMOJI, - icon="robot", - icon_background="#FFFFFF", - enable_site=False, - enable_api=True, - max_active_requests=0, - ) + return make_app(app_id="app1", name="X", description="d", mode=mode, enable_site=False, max_active_requests=0) def test_parameters_payload_raises_app_unavailable_when_no_config(unbound_session: Session): diff --git a/api/tests/unit_tests/controllers/openapi/test_app_run_streaming.py b/api/tests/unit_tests/controllers/openapi/test_app_run_streaming.py index 7672e6414fd..88af3d3ff8f 100644 --- a/api/tests/unit_tests/controllers/openapi/test_app_run_streaming.py +++ b/api/tests/unit_tests/controllers/openapi/test_app_run_streaming.py @@ -109,7 +109,6 @@ def test_stop_task_calls_queue_manager_and_graph_engine(app: Flask, bypass_pipel auth_data = AuthData.model_construct( token_type=TokenType.OAUTH_ACCOUNT, account_id=uuid.UUID(_TEST_ACCOUNT_ID), - token_hash="test", scopes=frozenset({Scope.FULL}), app=_make_app(), caller=_make_account(), diff --git a/api/tests/unit_tests/controllers/openapi/test_human_input_form.py b/api/tests/unit_tests/controllers/openapi/test_human_input_form.py index 28947c69b35..97590ad6dfe 100644 --- a/api/tests/unit_tests/controllers/openapi/test_human_input_form.py +++ b/api/tests/unit_tests/controllers/openapi/test_human_input_form.py @@ -26,7 +26,6 @@ def _make_auth_data(app_model, caller, caller_kind): return AuthData.model_construct( token_type=TokenType.OAUTH_ACCOUNT, account_id=uuid.uuid4(), - token_hash="test", scopes=frozenset({Scope.FULL}), app=app_model, caller=caller, diff --git a/api/tests/unit_tests/controllers/openapi/test_pagination_envelope.py b/api/tests/unit_tests/controllers/openapi/test_pagination_envelope.py deleted file mode 100644 index 7b84911d788..00000000000 --- a/api/tests/unit_tests/controllers/openapi/test_pagination_envelope.py +++ /dev/null @@ -1,134 +0,0 @@ -"""Unit tests for PaginationEnvelope generic Pydantic model.""" - -from __future__ import annotations - -from pydantic import BaseModel - -from controllers.openapi._models import PaginationEnvelope - - -class _Row(BaseModel): - id: str - name: str - - -def test_envelope_basic_fields(): - env = PaginationEnvelope[_Row](page=1, limit=20, total=42, has_more=True, data=[_Row(id="a", name="A")]) - dumped = env.model_dump(mode="json") - assert dumped == { - "page": 1, - "limit": 20, - "total": 42, - "has_more": True, - "data": [{"id": "a", "name": "A"}], - } - - -def test_envelope_empty_data_no_more(): - env = PaginationEnvelope[_Row](page=1, limit=20, total=0, has_more=False, data=[]) - assert env.model_dump(mode="json")["data"] == [] - assert env.model_dump(mode="json")["has_more"] is False - - -def test_envelope_has_more_true_when_total_exceeds_page_window(): - env = PaginationEnvelope[_Row].build(page=1, limit=20, total=42, items=[_Row(id="a", name="A")]) - assert env.has_more is True - - -def test_envelope_has_more_false_when_total_within_page_window(): - env = PaginationEnvelope[_Row].build(page=2, limit=20, total=22, items=[_Row(id="a", name="A")]) - assert env.has_more is False - - -def test_envelope_has_more_false_for_last_page(): - env = PaginationEnvelope[_Row].build(page=3, limit=20, total=42, items=[_Row(id="a", name="A")]) - assert env.has_more is False - - -def test_max_page_limit_is_200(): - from controllers.openapi._models import MAX_PAGE_LIMIT - - assert MAX_PAGE_LIMIT == 200 - - -def test_envelope_uses_pep695_generics(): - """Verify the class uses PEP 695 native generic syntax (not legacy Generic[T]).""" - from controllers.openapi._models import PaginationEnvelope - - # PEP 695 syntax populates __type_params__; the legacy Generic[T] form does not. - assert PaginationEnvelope.__type_params__, "expected PEP 695 native generic syntax" - - fields = PaginationEnvelope.model_fields - assert {"page", "limit", "total", "has_more", "data"} <= set(fields) - - -def test_app_info_response_dump_matches_spec(): - from controllers.openapi._models import AppInfo - - obj = AppInfo( - id="app1", - name="X", - description="d", - mode="chat", - ) - assert obj.model_dump(mode="json") == { - "id": "app1", - "name": "X", - "description": "d", - "mode": "chat", - } - - -def test_app_describe_response_nests_info_and_parameters(): - from controllers.openapi._models import AppDescribeInfo, AppDescribeResponse - - info = AppDescribeInfo( - id="app1", - name="X", - mode="chat", - description=None, - updated_at="2026-05-05T00:00:00+00:00", - service_api_enabled=True, - ) - obj = AppDescribeResponse(info=info, parameters={"opening_statement": None}) - dumped = obj.model_dump(mode="json") - assert dumped["info"]["service_api_enabled"] is True - assert dumped["parameters"]["opening_statement"] is None - - -def test_response_models_dump_per_mode(): - from controllers.openapi._models import ( - ChatMessageResponse, - CompletionMessageResponse, - WorkflowRunData, - WorkflowRunResponse, - ) - - chat = ChatMessageResponse( - event="message", - task_id="t1", - id="m1", - message_id="m1", - conversation_id="c1", - mode="chat", - answer="hi", - created_at=0, - ) - assert chat.model_dump(mode="json")["mode"] == "chat" - wf = WorkflowRunResponse( - workflow_run_id="r1", - task_id="t1", - data=WorkflowRunData(id="r1", workflow_id="w1", status="succeeded"), - ) - assert wf.model_dump(mode="json")["data"]["status"] == "succeeded" - assert wf.model_dump(mode="json")["mode"] == "workflow" - comp = CompletionMessageResponse( - event="message", - task_id="t2", - id="m2", - message_id="m2", - mode="completion", - answer="ok", - created_at=0, - ) - assert comp.model_dump(mode="json")["mode"] == "completion" diff --git a/api/tests/unit_tests/controllers/openapi/test_workflow_events_openapi.py b/api/tests/unit_tests/controllers/openapi/test_workflow_events_openapi.py index 2e6af990003..7f906826403 100644 --- a/api/tests/unit_tests/controllers/openapi/test_workflow_events_openapi.py +++ b/api/tests/unit_tests/controllers/openapi/test_workflow_events_openapi.py @@ -31,7 +31,6 @@ def _make_auth_data(app_model, caller, caller_kind): return AuthData.model_construct( token_type=TokenType.OAUTH_ACCOUNT, account_id=uuid.uuid4(), - token_hash="test", scopes=frozenset({Scope.FULL}), app=app_model, caller=caller, diff --git a/api/tests/unit_tests/controllers/openapi/test_workspaces_members.py b/api/tests/unit_tests/controllers/openapi/test_workspaces_members.py index 33a726e9c41..9b998edff57 100644 --- a/api/tests/unit_tests/controllers/openapi/test_workspaces_members.py +++ b/api/tests/unit_tests/controllers/openapi/test_workspaces_members.py @@ -9,7 +9,7 @@ Coverage: Auth-pipeline plumbing is bypassed via the `bypass_pipeline` fixture from conftest.py; the bearer identity is seeded into the openapi auth ContextVar -via `_seed` (the slot `validate_bearer` publishes). Tests that exercise +via `_seed`. Tests that exercise endpoint *bodies* skip the single `guard_workspace` decorator via ``__wrapped__`` — membership and role enforcement live in the auth pipeline and are covered in `auth/test_prepare.py` and `auth/test_verify.py`. @@ -45,7 +45,7 @@ from controllers.openapi.workspaces import ( from enums import DeploymentEdition from libs.oauth_bearer import AuthContext, Scope, SubjectType, TokenType, reset_auth_ctx, set_auth_ctx from models import Account, Tenant, TenantAccountJoin -from models.account import AccountStatus, TenantAccountRole, TenantStatus +from models.account import TenantAccountRole, TenantStatus from services.account_service import TenantService as RealTenantService from services.errors.account import ( AccountAlreadyInTenantError, @@ -57,6 +57,7 @@ from services.errors.account import ( RoleAlreadyAssignedError, ) from tests.unit_tests.config_override import config_overrides_context +from tests.unit_tests.model_factories import make_account, make_tenant if not hasattr(builtins, "MethodView"): builtins.MethodView = MethodView # type: ignore[attr-defined] @@ -112,8 +113,6 @@ def _auth_ctx(account_id: uuid.UUID | None = None) -> AuthContext: token_id=uuid.uuid4(), token_type=TokenType.OAUTH_ACCOUNT, expires_at=datetime.now(UTC), - token_hash="h", - verified_tenants={}, ) @@ -124,20 +123,16 @@ def _auth_data(account_id: uuid.UUID) -> AuthData: return AuthData( token_type=TokenType.OAUTH_ACCOUNT, account_id=account_id, - token_hash="testhash", scopes=frozenset({Scope.FULL}), ) def _account(account_id: str = "acct-1", email: str = "u@example.com") -> Account: - account = Account(name="User", email=email, status=AccountStatus.ACTIVE) - account.id = account_id - return account + return make_account(account_id=account_id, name="User", email=email) def _tenant(tenant_id: str = "ws-1", *, status: TenantStatus = TenantStatus.NORMAL) -> Tenant: - tenant = Tenant(name="WS", status=status) - tenant.id = tenant_id + tenant = make_tenant(tenant_id=tenant_id, name="WS", status=status) tenant.created_at = datetime(2026, 5, 18) return tenant diff --git a/api/tests/unit_tests/controllers/service_api/app/test_audio.py b/api/tests/unit_tests/controllers/service_api/app/test_audio.py index 9e51ffea837..325c5d33f77 100644 --- a/api/tests/unit_tests/controllers/service_api/app/test_audio.py +++ b/api/tests/unit_tests/controllers/service_api/app/test_audio.py @@ -34,7 +34,7 @@ from controllers.service_api.app.error import ( from core.errors.error import ModelCurrentlyNotSupportError, ProviderTokenNotInitError, QuotaExceededError from graphon.model_runtime.errors.invoke import InvokeError from models.enums import EndUserType -from models.model import App, AppMode, EndUser +from models.model import App, EndUser from services.app_ref_service import AppRef, MessageRef from services.audio_service import AudioService from services.errors.app_model_config import AppModelConfigBrokenError @@ -45,6 +45,7 @@ from services.errors.audio import ( SpeechToTextDisabledServiceError, UnsupportedAudioTypeServiceError, ) +from tests.unit_tests.model_factories import make_app, make_end_user def _file_data(): @@ -52,24 +53,14 @@ def _file_data(): def _app(*, app_id: str = "a1", tenant_id: str = "tenant-1") -> App: - return App( - id=app_id, - tenant_id=tenant_id, - name="Audio app", - description="", - mode=AppMode.CHAT, - enable_site=True, - enable_api=True, - max_active_requests=0, - ) + return make_app(app_id=app_id, tenant_id=tenant_id, name="Audio app", icon_type=None, max_active_requests=0) def _end_user(*, end_user_id: str = "u1", external_user_id: str | None = None) -> EndUser: - return EndUser( - id=end_user_id, - tenant_id="tenant-1", + return make_end_user( + end_user_id=end_user_id, app_id="a1", - type=EndUserType.SERVICE_API, + end_user_type=EndUserType.SERVICE_API, external_user_id=external_user_id, name="Audio user", session_id=f"session-{end_user_id}", diff --git a/api/tests/unit_tests/controllers/service_api/app/test_conversation.py b/api/tests/unit_tests/controllers/service_api/app/test_conversation.py index 093852d792f..efb2c2566e2 100644 --- a/api/tests/unit_tests/controllers/service_api/app/test_conversation.py +++ b/api/tests/unit_tests/controllers/service_api/app/test_conversation.py @@ -53,6 +53,7 @@ from services.errors.conversation import ( ConversationVariableNotExistsError, ConversationVariableTypeMismatchError, ) +from tests.unit_tests.model_factories import make_conversation def _app(*, app_id: str = "app-1", mode: AppMode = AppMode.CHAT) -> App: @@ -86,17 +87,15 @@ def _conversation( app_id: str = "app-1", end_user_id: str = "end-user-1", ) -> Conversation: - conversation = Conversation( + return make_conversation( + conversation_id=conversation_id, app_id=app_id, - mode=AppMode.CHAT, name="Original Name", + inputs={}, from_source=ConversationFromSource.API, from_end_user_id=end_user_id, invoke_from=InvokeFrom.SERVICE_API, ) - conversation.id = conversation_id - conversation.inputs = {} - return conversation class TestConversationListQuery: diff --git a/api/tests/unit_tests/controllers/service_api/dataset/test_dataset_segment.py b/api/tests/unit_tests/controllers/service_api/dataset/test_dataset_segment.py index ace817a4bf3..56aefeda1c7 100644 --- a/api/tests/unit_tests/controllers/service_api/dataset/test_dataset_segment.py +++ b/api/tests/unit_tests/controllers/service_api/dataset/test_dataset_segment.py @@ -43,6 +43,7 @@ from models.dataset import ChildChunk, Dataset, Document, DocumentSegment, Docum from models.enums import IndexingStatus, SegmentType from services.api_token_service import CachedApiToken from services.dataset_service import DocumentService, SegmentService +from tests.unit_tests.model_factories import make_account def _segment_response_dict(summary: str | None = None): @@ -85,9 +86,9 @@ def mock_tenant() -> Tenant: def _account() -> Account: - account = Account(name="Segment API User", email=f"segment-api-{uuid.uuid4()}@example.com") - account.id = str(uuid.uuid4()) - return account + return make_account( + account_id=str(uuid.uuid4()), name="Segment API User", email=f"segment-api-{uuid.uuid4()}@example.com" + ) def _api_token(tenant_id: str) -> CachedApiToken: diff --git a/api/tests/unit_tests/controllers/service_api/dataset/test_document.py b/api/tests/unit_tests/controllers/service_api/dataset/test_document.py index efb38d0e340..8ea26e001f8 100644 --- a/api/tests/unit_tests/controllers/service_api/dataset/test_document.py +++ b/api/tests/unit_tests/controllers/service_api/dataset/test_document.py @@ -49,12 +49,10 @@ from controllers.service_api.dataset.document import ( from controllers.service_api.dataset.error import ArchivedDocumentImmutableError from core.rag.index_processor.constant.index_type import IndexStructureType from enums import DeploymentEdition -from extensions.storage.storage_type import StorageType from models.account import Account from models.dataset import Dataset, Document, DocumentSegment from models.enums import ( ApiTokenType, - CreatorUserRole, DataSourceType, DocumentCreatedFrom, DocumentDocType, @@ -67,6 +65,7 @@ from services.dataset_service import DocumentService from services.entities.knowledge_entities.knowledge_entities import ProcessRule, RetrievalModel from services.errors.file import FileTooLargeError as FileTooLargeServiceError from tests.unit_tests.config_override import config_overrides_context +from tests.unit_tests.model_factories import make_account, make_upload_file def _document_data_source_info() -> dict[str, str]: @@ -74,27 +73,17 @@ def _document_data_source_info() -> dict[str, str]: def _account() -> Account: - account = Account(name="Document API User", email=f"document-api-{uuid.uuid4()}@example.com") - account.id = "user-1" - return account + return make_account(account_id="user-1", name="Document API User", email=f"document-api-{uuid.uuid4()}@example.com") def _upload_file() -> UploadFile: - upload_file = UploadFile( - tenant_id="tenant-1", - storage_type=StorageType.LOCAL, + return make_upload_file( key="documents/file.txt", name="file.txt", - size=10, - extension="txt", - mime_type="text/plain", - created_by_role=CreatorUserRole.ACCOUNT, created_by="user-1", created_at=datetime.now(UTC), used=True, ) - upload_file.id = str(uuid.uuid4()) - return upload_file def _unwrap_non_wrapped_controller(view): diff --git a/api/tests/unit_tests/controllers/service_api/dataset/test_rag_pipeline_file_upload_serialization.py b/api/tests/unit_tests/controllers/service_api/dataset/test_rag_pipeline_file_upload_serialization.py index 9e09c8d8158..aa2b1f644af 100644 --- a/api/tests/unit_tests/controllers/service_api/dataset/test_rag_pipeline_file_upload_serialization.py +++ b/api/tests/unit_tests/controllers/service_api/dataset/test_rag_pipeline_file_upload_serialization.py @@ -5,28 +5,20 @@ Unit tests for Service API knowledge pipeline file-upload serialization. from datetime import UTC, datetime from controllers.service_api.dataset.rag_pipeline.rag_pipeline_workflow import PipelineUploadFileResponse -from extensions.storage.storage_type import StorageType from libs.helper import dump_response -from models.enums import CreatorUserRole from models.model import UploadFile +from tests.unit_tests.model_factories import make_upload_file def _upload_file(*, created_at: datetime) -> UploadFile: - upload_file = UploadFile( - tenant_id="tenant-1", - storage_type=StorageType.LOCAL, - key="uploads/file-1", + return make_upload_file( + file_id="file-1", name="test.pdf", size=123, extension="pdf", mime_type="application/pdf", - created_by_role=CreatorUserRole.ACCOUNT, - created_by="account-1", created_at=created_at, - used=False, ) - upload_file.id = "file-1" - return upload_file def test_file_upload_created_at_is_isoformat_string(): diff --git a/api/tests/unit_tests/controllers/service_api/test_trace_session_id_parsing.py b/api/tests/unit_tests/controllers/service_api/test_trace_session_id_parsing.py index 89766162a1d..05fcc5e400d 100644 --- a/api/tests/unit_tests/controllers/service_api/test_trace_session_id_parsing.py +++ b/api/tests/unit_tests/controllers/service_api/test_trace_session_id_parsing.py @@ -9,6 +9,7 @@ from controllers.service_api.app import workflow as workflow_module from core.helper.trace_id_helper import get_trace_session_id from models.enums import EndUserType from models.model import App, AppMode, EndUser +from tests.unit_tests.model_factories import make_app, make_end_user class _Request: @@ -41,16 +42,11 @@ def test_trace_session_id_invalid_highest_priority_raises_bad_request(): def _app(mode: AppMode) -> App: - return App(id="app-1", mode=mode, tenant_id="tenant-1") + return make_app(mode=mode, icon_type=None) def _end_user() -> EndUser: - return EndUser( - id="user-1", - tenant_id="tenant-1", - type=EndUserType.SERVICE_API, - session_id="session-1", - ) + return make_end_user(end_user_id="user-1", end_user_type=EndUserType.SERVICE_API) def _assert_generate_trace_session_id(mock_generate_service: MagicMock, expected: str) -> None: diff --git a/api/tests/unit_tests/controllers/trigger/test_webhook.py b/api/tests/unit_tests/controllers/trigger/test_webhook.py index 7cfd0306ff2..087922554ae 100644 --- a/api/tests/unit_tests/controllers/trigger/test_webhook.py +++ b/api/tests/unit_tests/controllers/trigger/test_webhook.py @@ -8,6 +8,7 @@ import controllers.trigger.webhook as module from models.trigger import WorkflowWebhookTrigger from models.workflow import Workflow from services.errors.app import QuotaExceededError +from tests.unit_tests.model_factories import make_workflow @pytest.fixture(autouse=True) @@ -35,7 +36,7 @@ def _webhook_trigger() -> WorkflowWebhookTrigger: def _workflow() -> Workflow: - return Workflow(id="workflow-1") + return make_workflow(workflow_id="workflow-1") class TestPrepareWebhookExecution: diff --git a/api/tests/unit_tests/controllers/web/test_audio.py b/api/tests/unit_tests/controllers/web/test_audio.py index 10dc7a3429f..78f40d6dcb9 100644 --- a/api/tests/unit_tests/controllers/web/test_audio.py +++ b/api/tests/unit_tests/controllers/web/test_audio.py @@ -22,7 +22,6 @@ from controllers.web.error import ( ) from core.errors.error import ModelCurrentlyNotSupportError, ProviderTokenNotInitError, QuotaExceededError from graphon.model_runtime.errors.invoke import InvokeError -from models.enums import EndUserType from models.model import App, AppMode, EndUser, IconType from services.app_ref_service import AppRef, MessageRef from services.errors.audio import ( @@ -32,6 +31,7 @@ from services.errors.audio import ( SpeechToTextDisabledServiceError, UnsupportedAudioTypeServiceError, ) +from tests.unit_tests.model_factories import make_end_user def _app_model() -> App: @@ -51,15 +51,7 @@ def _app_model() -> App: def _end_user() -> EndUser: - return EndUser( - id="eu-1", - tenant_id="tenant-1", - app_id="app-1", - type=EndUserType.BROWSER, - external_user_id="ext-1", - name="Web User", - session_id="session-1", - ) + return make_end_user(end_user_id="eu-1", app_id="app-1", external_user_id="ext-1", name="Web User") # --------------------------------------------------------------------------- diff --git a/api/tests/unit_tests/controllers/web/test_completion.py b/api/tests/unit_tests/controllers/web/test_completion.py index 6c7dce3c4ba..73f2ce6d20c 100644 --- a/api/tests/unit_tests/controllers/web/test_completion.py +++ b/api/tests/unit_tests/controllers/web/test_completion.py @@ -23,24 +23,12 @@ from controllers.web.error import ( from core.app.apps.agent_app.errors import AgentAppNotPublishedError from core.errors.error import ModelCurrentlyNotSupportError, ProviderTokenNotInitError, QuotaExceededError from graphon.model_runtime.errors.invoke import InvokeError -from models.enums import EndUserType -from models.model import App, AppMode, EndUser, IconType +from models.model import App, AppMode, EndUser +from tests.unit_tests.model_factories import make_app, make_end_user def _app(mode: AppMode) -> App: - return App( - id="app-1", - tenant_id="tenant-1", - name="Web App", - description="", - mode=mode, - icon_type=IconType.EMOJI, - icon="robot", - icon_background="#FFFFFF", - enable_site=True, - enable_api=False, - max_active_requests=0, - ) + return make_app(name="Web App", mode=mode, enable_api=False, max_active_requests=0) def _completion_app() -> App: @@ -52,14 +40,7 @@ def _chat_app() -> App: def _end_user() -> EndUser: - return EndUser( - id="eu-1", - tenant_id="tenant-1", - app_id="app-1", - type=EndUserType.BROWSER, - name="Web User", - session_id="session-1", - ) + return make_end_user(end_user_id="eu-1", app_id="app-1", name="Web User") # --------------------------------------------------------------------------- diff --git a/api/tests/unit_tests/controllers/web/test_files.py b/api/tests/unit_tests/controllers/web/test_files.py index 03d4241b62f..30c36a8af24 100644 --- a/api/tests/unit_tests/controllers/web/test_files.py +++ b/api/tests/unit_tests/controllers/web/test_files.py @@ -2,7 +2,6 @@ from __future__ import annotations -from datetime import datetime from io import BytesIO from unittest.mock import MagicMock, patch @@ -18,11 +17,11 @@ from controllers.common.errors import ( UnsupportedFileTypeError, ) from controllers.web.files import FileApi -from extensions.storage.storage_type import StorageType from libs.exception import BaseHTTPException -from models.enums import CreatorUserRole, EndUserType +from models.enums import CreatorUserRole from models.model import App, AppMode, EndUser, UploadFile from services.errors import file as file_errors +from tests.unit_tests.model_factories import make_end_user, make_upload_file def _app_model() -> App: @@ -39,31 +38,17 @@ def _app_model() -> App: def _end_user() -> EndUser: - return EndUser( - id="eu-1", - tenant_id="tenant-1", - app_id="app-1", - type=EndUserType.BROWSER, - session_id="session-1", - ) + return make_end_user(end_user_id="eu-1", app_id="app-1") def _upload_file() -> UploadFile: - upload_file = UploadFile( - tenant_id="tenant-1", - storage_type=StorageType.LOCAL, + return make_upload_file( + file_id="file-1", key="upload/test.txt", - name="test.txt", size=100, - extension="txt", - mime_type="text/plain", created_by_role=CreatorUserRole.END_USER, created_by="eu-1", - created_at=datetime(2024, 1, 1), - used=False, ) - upload_file.id = "file-1" - return upload_file class TestFileApi: diff --git a/api/tests/unit_tests/controllers/web/test_human_input_file_upload.py b/api/tests/unit_tests/controllers/web/test_human_input_file_upload.py index a3d2c8d188a..9285a6c8fc2 100644 --- a/api/tests/unit_tests/controllers/web/test_human_input_file_upload.py +++ b/api/tests/unit_tests/controllers/web/test_human_input_file_upload.py @@ -28,10 +28,7 @@ from controllers.web.human_input_file_upload import ( InvalidUploadTokenForbiddenError, InvalidUploadTokenUnauthorizedError, ) -from extensions.storage.storage_type import StorageType from models import Account -from models.account import AccountStatus -from models.enums import CreatorUserRole from models.model import UploadFile from services.errors.file import ( BlockedFileExtensionError as BlockedFileExtensionServiceError, @@ -53,6 +50,7 @@ from services.remote_file_service import RemoteFileNotFoundError as RemoteFileNo from services.remote_file_service import RemoteFileUnavailableError as RemoteFileUnavailableServiceError from services.remote_file_service import RemoteFileUploadResult from services.remote_file_service import RemoteFileUrlBlockedError as RemoteFileUrlBlockedServiceError +from tests.unit_tests.model_factories import make_account, make_upload_file @pytest.fixture @@ -63,9 +61,7 @@ def app() -> Flask: def _account() -> Account: - account = Account(name="Form Owner", email="owner@example.com", status=AccountStatus.ACTIVE) - account.id = "owner-1" - return account + return make_account(account_id="owner-1", name="Form Owner", email="owner@example.com") def _upload_context() -> HumanInputUploadContext: @@ -80,22 +76,14 @@ def _upload_context() -> HumanInputUploadContext: def _upload_file() -> UploadFile: - upload_file = UploadFile( - tenant_id="tenant-1", - storage_type=StorageType.LOCAL, + return make_upload_file( + file_id="file-1", key="upload/sample.txt", name="sample.txt", size=7, - extension="txt", - mime_type="text/plain", - created_by_role=CreatorUserRole.ACCOUNT, created_by="end-user-1", - created_at=datetime(2024, 1, 1), - used=False, source_url="signed-source-url", ) - upload_file.id = "file-1" - return upload_file def _remote_upload_file() -> RemoteFileUploadResult: diff --git a/api/tests/unit_tests/controllers/web/test_message_endpoints.py b/api/tests/unit_tests/controllers/web/test_message_endpoints.py index 5feea96596d..9fc9ad2fedc 100644 --- a/api/tests/unit_tests/controllers/web/test_message_endpoints.py +++ b/api/tests/unit_tests/controllers/web/test_message_endpoints.py @@ -22,10 +22,10 @@ from controllers.web.message import ( MessageMoreLikeThisQuery, MessageSuggestedQuestionApi, ) -from models.enums import EndUserType from models.model import App, AppMode, EndUser from services.errors.app import MoreLikeThisDisabledError from services.errors.message import MessageNotExistsError +from tests.unit_tests.model_factories import make_end_user def _chat_app() -> App: @@ -37,12 +37,7 @@ def _completion_app() -> App: def _end_user() -> EndUser: - return EndUser( - id="eu-1", - tenant_id="tenant-1", - type=EndUserType.BROWSER, - session_id="session-1", - ) + return make_end_user(end_user_id="eu-1") # The @model_validate and @with_session decorators wrap the handlers; tests diff --git a/api/tests/unit_tests/controllers/web/test_remote_files.py b/api/tests/unit_tests/controllers/web/test_remote_files.py index 791ec2770fb..0ca69debd55 100644 --- a/api/tests/unit_tests/controllers/web/test_remote_files.py +++ b/api/tests/unit_tests/controllers/web/test_remote_files.py @@ -23,7 +23,6 @@ from controllers.common.errors import ( ) from controllers.web import remote_files as remote_files_module from libs.exception import BaseHTTPException -from models.enums import EndUserType from models.model import App, AppMode, EndUser from services.errors.file import ( BlockedFileExtensionError as ServiceBlockedFileExtensionError, @@ -55,6 +54,7 @@ REMOTE_FILE_ERROR_CASES: tuple[tuple[type[RemoteFileError], type[BaseHTTPExcepti 502, ), ) +from tests.unit_tests.model_factories import make_end_user def _app_model() -> App: @@ -71,13 +71,7 @@ def _app_model() -> App: def _end_user() -> EndUser: - return EndUser( - id="eu-1", - tenant_id="tenant-1", - app_id="app-1", - type=EndUserType.BROWSER, - session_id="session-1", - ) + return make_end_user(end_user_id="eu-1", app_id="app-1") def _upload_result() -> RemoteFileUploadResult: diff --git a/api/tests/unit_tests/controllers/web/test_saved_message.py b/api/tests/unit_tests/controllers/web/test_saved_message.py index 22ec43a5b47..11ef11da686 100644 --- a/api/tests/unit_tests/controllers/web/test_saved_message.py +++ b/api/tests/unit_tests/controllers/web/test_saved_message.py @@ -14,9 +14,9 @@ from werkzeug.exceptions import NotFound from controllers.common.controller_schemas import SavedMessageCreatePayload, SavedMessageListQuery from controllers.web.error import NotCompletionAppError from controllers.web.saved_message import SavedMessageApi, SavedMessageListApi -from models.enums import EndUserType from models.model import App, AppMode, EndUser from services.errors.message import MessageNotExistsError +from tests.unit_tests.model_factories import make_end_user def _completion_app() -> App: @@ -28,12 +28,7 @@ def _chat_app() -> App: def _end_user() -> EndUser: - return EndUser( - id="eu-1", - tenant_id="tenant-1", - type=EndUserType.BROWSER, - session_id="session-1", - ) + return make_end_user(end_user_id="eu-1") # The @with_session and @model_validate decorators wrap the handler; tests call diff --git a/api/tests/unit_tests/controllers/web/test_workflow.py b/api/tests/unit_tests/controllers/web/test_workflow.py index 711976ed441..49c6ad74c6b 100644 --- a/api/tests/unit_tests/controllers/web/test_workflow.py +++ b/api/tests/unit_tests/controllers/web/test_workflow.py @@ -15,11 +15,11 @@ from controllers.web.error import ( ) from controllers.web.workflow import WorkflowRunApi, WorkflowTaskStopApi from core.errors.error import ProviderTokenNotInitError, QuotaExceededError -from models.enums import EndUserType from models.model import App, AppMode, EndUser from services.errors.app import ( TriggerWorkflowServiceModeUnavailableError as TriggerWorkflowServiceModeUnavailableServiceError, ) +from tests.unit_tests.model_factories import make_end_user def _workflow_app() -> App: @@ -31,12 +31,7 @@ def _chat_app() -> App: def _end_user() -> EndUser: - return EndUser( - id="eu-1", - tenant_id="tenant-1", - type=EndUserType.BROWSER, - session_id="session-1", - ) + return make_end_user(end_user_id="eu-1") # --------------------------------------------------------------------------- diff --git a/api/tests/unit_tests/controllers/web/test_workflow_events.py b/api/tests/unit_tests/controllers/web/test_workflow_events.py index 049ab04cb4f..e6a4abf86df 100644 --- a/api/tests/unit_tests/controllers/web/test_workflow_events.py +++ b/api/tests/unit_tests/controllers/web/test_workflow_events.py @@ -11,9 +11,10 @@ from flask import Flask from controllers.common.errors import NotFoundError from controllers.web.workflow_events import WorkflowEventsApi from graphon.enums import WorkflowExecutionStatus -from models.enums import CreatorUserRole, EndUserType, WorkflowRunTriggeredFrom +from models.enums import CreatorUserRole, WorkflowRunTriggeredFrom from models.model import App, AppMode, EndUser from models.workflow import WorkflowRun, WorkflowType +from tests.unit_tests.model_factories import make_end_user def _workflow_app() -> App: @@ -21,12 +22,7 @@ def _workflow_app() -> App: def _end_user() -> EndUser: - return EndUser( - id="eu-1", - tenant_id="tenant-1", - type=EndUserType.BROWSER, - session_id="session-1", - ) + return make_end_user(end_user_id="eu-1") def _workflow_run( diff --git a/api/tests/unit_tests/core/app/apps/advanced_chat/test_app_config_manager.py b/api/tests/unit_tests/core/app/apps/advanced_chat/test_app_config_manager.py index aaea73832ef..b2ebdb96318 100644 --- a/api/tests/unit_tests/core/app/apps/advanced_chat/test_app_config_manager.py +++ b/api/tests/unit_tests/core/app/apps/advanced_chat/test_app_config_manager.py @@ -1,26 +1,17 @@ -import json from unittest.mock import patch from core.app.apps.advanced_chat.app_config_manager import AdvancedChatAppConfigManager from models.model import App, AppMode from models.workflow import Workflow, WorkflowType +from tests.unit_tests.model_factories import make_app, make_workflow def _app() -> App: - return App(id="app-1", tenant_id="tenant-1", name="Advanced Chat App", mode=AppMode.ADVANCED_CHAT) + return make_app(name="Advanced Chat App", mode=AppMode.ADVANCED_CHAT, icon_type=None) def _workflow() -> Workflow: - return Workflow( - id="wf-1", - tenant_id="tenant-1", - app_id="app-1", - type=WorkflowType.CHAT, - version=Workflow.VERSION_DRAFT, - graph="{}", - features=json.dumps({}), - created_by="account-1", - ) + return make_workflow(workflow_id="wf-1", workflow_type=WorkflowType.CHAT, graph="{}") class TestAdvancedChatAppConfigManager: diff --git a/api/tests/unit_tests/core/app/apps/agent_app/test_input_guards.py b/api/tests/unit_tests/core/app/apps/agent_app/test_input_guards.py index 9445bc800d4..eaf50049612 100644 --- a/api/tests/unit_tests/core/app/apps/agent_app/test_input_guards.py +++ b/api/tests/unit_tests/core/app/apps/agent_app/test_input_guards.py @@ -23,6 +23,7 @@ from core.app.entities.queue_entities import ( ) from core.moderation.base import ModerationError from models.model import App, AppMode, Message, MessageAnnotation +from tests.unit_tests.model_factories import make_app, make_message class _FakeQueueManager: @@ -46,11 +47,11 @@ def _make_entity(query: str = "hello") -> SimpleNamespace: def _app() -> App: - return App(id="app-1", tenant_id="tenant-1", name="Agent App", mode=AppMode.AGENT_CHAT) + return make_app(name="Agent App", mode=AppMode.AGENT_CHAT, icon_type=None) def _message() -> Message: - return Message(id="msg-1", app_id="app-1", conversation_id="conversation-1") + return make_message(message_id="msg-1") def _patch_moderation(monkeypatch: pytest.MonkeyPatch, *, returns=None, raises: Exception | None = None) -> None: diff --git a/api/tests/unit_tests/core/app/apps/agent_app/test_resolve_agent.py b/api/tests/unit_tests/core/app/apps/agent_app/test_resolve_agent.py index 17f5fcfd19a..9ed6f108680 100644 --- a/api/tests/unit_tests/core/app/apps/agent_app/test_resolve_agent.py +++ b/api/tests/unit_tests/core/app/apps/agent_app/test_resolve_agent.py @@ -34,6 +34,7 @@ from services.agent.workspace_service import ( AgentWorkspaceBindingGenerationMismatchError, AgentWorkspaceService, ) +from tests.unit_tests.model_factories import make_account, make_app, make_conversation _SOUL_DICT = { "model": { @@ -84,24 +85,22 @@ def _snapshot( def _app() -> App: - return App( - id="app-1", + return make_app( tenant_id="t1", name="Agent App", mode=AppMode.AGENT_CHAT, + icon_type=None, enable_site=False, enable_api=False, ) def _account() -> Account: - account = Account(name="Agent User", email="agent-user@example.com") - account.id = "user-1" - return account + return make_account(account_id="user-1", name="Agent User", email="agent-user@example.com") def _conversation(*, binding_id: str) -> Conversation: - return Conversation(id="conversation-1", app_id="app-1", agent_workspace_binding_id=binding_id) + return make_conversation(agent_workspace_binding_id=binding_id) def _binding( diff --git a/api/tests/unit_tests/core/app/apps/chat/test_app_config_manager.py b/api/tests/unit_tests/core/app/apps/chat/test_app_config_manager.py index 7df8a5d8e4e..00052feff1e 100644 --- a/api/tests/unit_tests/core/app/apps/chat/test_app_config_manager.py +++ b/api/tests/unit_tests/core/app/apps/chat/test_app_config_manager.py @@ -6,10 +6,11 @@ from sqlalchemy.orm import Session from core.app.app_config.entities import EasyUIBasedAppModelConfigFrom, ModelConfigEntity, PromptTemplateEntity from core.app.apps.chat.app_config_manager import ChatAppConfigManager from models.model import App, AppMode, AppModelConfig +from tests.unit_tests.model_factories import make_app def _app() -> App: - return App(id="app-1", tenant_id="tenant-1", name="Chat App", mode=AppMode.CHAT) + return make_app(name="Chat App", icon_type=None) def _app_model_config() -> AppModelConfig: diff --git a/api/tests/unit_tests/core/app/apps/pipeline/test_pipeline_runner.py b/api/tests/unit_tests/core/app/apps/pipeline/test_pipeline_runner.py index 3e847f65d73..37e3912ba68 100644 --- a/api/tests/unit_tests/core/app/apps/pipeline/test_pipeline_runner.py +++ b/api/tests/unit_tests/core/app/apps/pipeline/test_pipeline_runner.py @@ -26,9 +26,10 @@ from core.app.apps.pipeline.pipeline_runner import PipelineRunner from core.app.entities.app_invoke_entities import InvokeFrom, UserFrom from graphon.graph_events import GraphRunFailedEvent from models.dataset import Dataset, Document, Pipeline -from models.enums import DataSourceType, DocumentCreatedFrom, EndUserType +from models.enums import DocumentCreatedFrom from models.model import EndUser from models.workflow import Workflow, WorkflowType +from tests.unit_tests.model_factories import make_dataset, make_document, make_end_user, make_workflow def _pipeline(*, tenant_id: str = "tenant", pipeline_id: str = "pipe") -> Pipeline: @@ -39,10 +40,9 @@ def _pipeline(*, tenant_id: str = "tenant", pipeline_id: str = "pipe") -> Pipeli def _dataset(*, tenant_id: str = "tenant", dataset_id: str = "ds", pipeline_id: str = "pipe") -> Dataset: - return Dataset( - id=dataset_id, + return make_dataset( + dataset_id=dataset_id, tenant_id=tenant_id, - name="Dataset", description="", created_by="user", pipeline_id=pipeline_id, @@ -50,40 +50,26 @@ def _dataset(*, tenant_id: str = "tenant", dataset_id: str = "ds", pipeline_id: def _workflow(*, tenant_id: str = "tenant", pipeline_id: str = "pipe", graph: dict | None = None) -> Workflow: - return Workflow.new( + return make_workflow( tenant_id=tenant_id, app_id=pipeline_id, - type=WorkflowType.RAG_PIPELINE.value, + workflow_type=WorkflowType.RAG_PIPELINE, version="v1", - graph=json.dumps(graph if graph is not None else {"nodes": [], "edges": []}), - features="{}", + graph=graph, created_by="user", - environment_variables=[], - conversation_variables=[], - rag_pipeline_variables=[], ) def _end_user() -> EndUser: - return EndUser( - id="user", - tenant_id="tenant", - app_id="pipe", - type=EndUserType.BROWSER, - name="User", - session_id="sess", - ) + return make_end_user(end_user_id="user", tenant_id="tenant", app_id="pipe", name="User", session_id="sess") def _document(*, document_id: str = "doc", dataset_id: str = "ds", tenant_id: str = "tenant") -> Document: - return Document( - id=document_id, + return make_document( + document_id=document_id, tenant_id=tenant_id, dataset_id=dataset_id, - position=1, - data_source_type=DataSourceType.UPLOAD_FILE, batch="batch", - name="Document", created_from=DocumentCreatedFrom.API, created_by="user", ) diff --git a/api/tests/unit_tests/core/app/apps/test_message_based_app_generator.py b/api/tests/unit_tests/core/app/apps/test_message_based_app_generator.py index 410ec9db8a9..fdd7b53c50a 100644 --- a/api/tests/unit_tests/core/app/apps/test_message_based_app_generator.py +++ b/api/tests/unit_tests/core/app/apps/test_message_based_app_generator.py @@ -18,6 +18,7 @@ from core.app.entities.app_invoke_entities import ChatAppGenerateEntity, InvokeF from models.account import Account from models.model import App, AppMode, Conversation, Message from services.errors.app_model_config import AppModelConfigBrokenError +from tests.unit_tests.model_factories import make_account, make_app class DummyModelConf: @@ -47,19 +48,11 @@ class DummyCompletionGenerateEntity: def _app(*, app_id: str = "app") -> App: - return App( - id=app_id, - tenant_id="tenant-id", - name="Message App", - mode=AppMode.CHAT, - app_model_config_id=None, - ) + return make_app(app_id=app_id, tenant_id="tenant-id", name="Message App", icon_type=None) def _account() -> Account: - account = Account(name="Message User", email="message-user@example.com") - account.id = "user-id" - return account + return make_account(account_id="user-id", name="Message User", email="message-user@example.com") def _make_app_config(app_mode: AppMode) -> EasyUIBasedAppConfig: diff --git a/api/tests/unit_tests/core/app/apps/test_workflow_app_generator.py b/api/tests/unit_tests/core/app/apps/test_workflow_app_generator.py index 43dcab8c241..c381fb1bbc4 100644 --- a/api/tests/unit_tests/core/app/apps/test_workflow_app_generator.py +++ b/api/tests/unit_tests/core/app/apps/test_workflow_app_generator.py @@ -1,7 +1,6 @@ """SQLite-backed tests for workflow app generation and worker reload behavior.""" import contextlib -import json from types import SimpleNamespace from unittest.mock import MagicMock @@ -22,7 +21,8 @@ from graphon.runtime import GraphRuntimeState, VariablePool from models.enums import CreatorUserRole, EndUserType, WorkflowRunTriggeredFrom from models.model import App, AppMode, EndUser from models.snippet import CustomizedSnippet -from models.workflow import Workflow, WorkflowKind, WorkflowNodeExecutionTriggeredFrom, WorkflowRun, WorkflowType +from models.workflow import Workflow, WorkflowKind, WorkflowNodeExecutionTriggeredFrom, WorkflowRun +from tests.unit_tests.model_factories import make_workflow def _workflow( @@ -32,19 +32,13 @@ def _workflow( tenant_id: str = "tenant", kind: WorkflowKind = WorkflowKind.STANDARD, ) -> Workflow: - return Workflow( - id=workflow_id, + return make_workflow( + workflow_id=workflow_id, tenant_id=tenant_id, app_id=app_id, - type=WorkflowType.WORKFLOW, kind=kind, version="1", - graph=json.dumps({"nodes": [], "edges": []}), - features="{}", created_by="creator", - environment_variables=[], - conversation_variables=[], - rag_pipeline_variables=[], ) diff --git a/api/tests/unit_tests/core/app/apps/test_workflow_app_runner_core.py b/api/tests/unit_tests/core/app/apps/test_workflow_app_runner_core.py index fbe5035d3fa..94dac233314 100644 --- a/api/tests/unit_tests/core/app/apps/test_workflow_app_runner_core.py +++ b/api/tests/unit_tests/core/app/apps/test_workflow_app_runner_core.py @@ -49,21 +49,16 @@ from graphon.node_events import NodeRunResult from graphon.runtime import GraphRuntimeState, VariablePool from graphon.variables.segments import StringSegment from graphon.variables.variables import StringVariable -from models.workflow import Workflow, WorkflowType +from models.workflow import Workflow +from tests.unit_tests.model_factories import make_workflow def _workflow(graph: dict[str, object] | None = None) -> Workflow: - return Workflow.new( + return make_workflow( tenant_id="tenant", app_id="app", - type=WorkflowType.WORKFLOW, - version=Workflow.VERSION_DRAFT, graph=json.dumps(graph or {}, default=str), - features="{}", created_by="account", - environment_variables=[], - conversation_variables=[], - rag_pipeline_variables=[], ) diff --git a/api/tests/unit_tests/core/app/apps/workflow/test_app_config_manager.py b/api/tests/unit_tests/core/app/apps/workflow/test_app_config_manager.py index 55911e1cedf..a443dbd4a29 100644 --- a/api/tests/unit_tests/core/app/apps/workflow/test_app_config_manager.py +++ b/api/tests/unit_tests/core/app/apps/workflow/test_app_config_manager.py @@ -1,26 +1,17 @@ -import json from unittest.mock import patch from core.app.apps.workflow.app_config_manager import WorkflowAppConfigManager from models.model import App, AppMode -from models.workflow import Workflow, WorkflowType +from models.workflow import Workflow +from tests.unit_tests.model_factories import make_app, make_workflow def _app() -> App: - return App(id="app-1", tenant_id="tenant-1", name="Workflow App", mode=AppMode.WORKFLOW) + return make_app(name="Workflow App", mode=AppMode.WORKFLOW, icon_type=None) def _workflow() -> Workflow: - return Workflow( - id="wf-1", - tenant_id="tenant-1", - app_id="app-1", - type=WorkflowType.WORKFLOW, - version=Workflow.VERSION_DRAFT, - graph="{}", - features=json.dumps({}), - created_by="account-1", - ) + return make_workflow(workflow_id="wf-1", graph="{}") class TestWorkflowAppConfigManager: diff --git a/api/tests/unit_tests/core/app/apps/workflow/test_generate_task_pipeline_core.py b/api/tests/unit_tests/core/app/apps/workflow/test_generate_task_pipeline_core.py index b4d8dbde01f..14af58bae44 100644 --- a/api/tests/unit_tests/core/app/apps/workflow/test_generate_task_pipeline_core.py +++ b/api/tests/unit_tests/core/app/apps/workflow/test_generate_task_pipeline_core.py @@ -1,6 +1,5 @@ from __future__ import annotations -import json from types import SimpleNamespace import pytest @@ -53,33 +52,19 @@ from graphon.enums import BuiltinNodeTypes, WorkflowExecutionStatus from graphon.model_runtime.entities.llm_entities import LLMUsage from graphon.runtime import GraphRuntimeState, VariablePool from libs.datetime_utils import naive_utc_now -from models.enums import CreatorUserRole, EndUserType +from models.enums import CreatorUserRole from models.model import AppMode, EndUser -from models.workflow import Workflow, WorkflowAppLog, WorkflowType +from models.workflow import Workflow, WorkflowAppLog +from tests.unit_tests.model_factories import make_end_user, make_workflow from tests.workflow_test_utils import build_test_variable_pool def _workflow() -> Workflow: - return Workflow( - id="workflow-id", - tenant_id="tenant", - app_id="app", - type=WorkflowType.WORKFLOW, - version=Workflow.VERSION_DRAFT, - graph="{}", - features=json.dumps({}), - created_by="user", - ) + return make_workflow(workflow_id="workflow-id", tenant_id="tenant", app_id="app", graph="{}", created_by="user") def _end_user(*, end_user_id: str = "user", session_id: str = "session") -> EndUser: - return EndUser( - id=end_user_id, - tenant_id="tenant", - app_id="app", - type=EndUserType.BROWSER, - session_id=session_id, - ) + return make_end_user(end_user_id=end_user_id, tenant_id="tenant", app_id="app", session_id=session_id) def _make_pipeline(): diff --git a/api/tests/unit_tests/core/app/features/test_annotation_reply.py b/api/tests/unit_tests/core/app/features/test_annotation_reply.py index a9ae870fabc..a9001c96c61 100644 --- a/api/tests/unit_tests/core/app/features/test_annotation_reply.py +++ b/api/tests/unit_tests/core/app/features/test_annotation_reply.py @@ -10,26 +10,18 @@ from core.app.entities.app_invoke_entities import InvokeFrom from core.app.features.annotation_reply.annotation_reply import AnnotationReplyFeature from models.dataset import DatasetCollectionBinding from models.enums import CollectionBindingType, ConversationFromSource -from models.model import App, AppAnnotationHitHistory, AppAnnotationSetting, AppMode, Message, MessageAnnotation +from models.model import App, AppAnnotationHitHistory, AppAnnotationSetting, Message, MessageAnnotation +from tests.unit_tests.model_factories import make_app, make_message TABLES = (AppAnnotationSetting, DatasetCollectionBinding, MessageAnnotation, AppAnnotationHitHistory) def _app() -> App: - return App( - id="app-1", - tenant_id="tenant-1", - name="Test App", - description="", - mode=AppMode.CHAT, - enable_site=True, - enable_api=True, - max_active_requests=0, - ) + return make_app(icon_type=None, max_active_requests=0) def _message() -> Message: - return Message(id="msg-1") + return make_message(message_id="msg-1", app_id=None, conversation_id=None) def _persist_binding(session: Session) -> DatasetCollectionBinding: diff --git a/api/tests/unit_tests/core/app/task_pipeline/test_easy_ui_based_generate_task_pipeline_core.py b/api/tests/unit_tests/core/app/task_pipeline/test_easy_ui_based_generate_task_pipeline_core.py index 7c696955c97..5ffb62a8a67 100644 --- a/api/tests/unit_tests/core/app/task_pipeline/test_easy_ui_based_generate_task_pipeline_core.py +++ b/api/tests/unit_tests/core/app/task_pipeline/test_easy_ui_based_generate_task_pipeline_core.py @@ -55,12 +55,12 @@ from core.app.task_pipeline.easy_ui_based_generate_task_pipeline import EasyUIBa from core.base.tts import AppGeneratorTTSPublisher, AudioTrunk from core.ops.entities.trace_entity import TraceTaskName from core.ops.ops_trace_manager import TraceQueueManager -from extensions.storage.storage_type import StorageType from graphon.file import FileTransferMethod, FileType from graphon.model_runtime.entities.llm_entities import LLMResult, LLMResultChunk, LLMResultChunkDelta, LLMUsage from graphon.model_runtime.entities.message_entities import AssistantPromptMessage, TextPromptMessageContent from models.enums import ConversationFromSource, CreatorUserRole from models.model import AppMode, Conversation, Message, MessageAgentThought, MessageFile, UploadFile +from tests.unit_tests.model_factories import make_upload_file class _DummyModelConf: @@ -212,21 +212,17 @@ def _message_file( def _upload_file(*, file_id: str, name: str, mime_type: str, size: int, extension: str) -> UploadFile: - upload_file = UploadFile( + return make_upload_file( + file_id=file_id, tenant_id="tenant", - storage_type=StorageType.LOCAL, key=f"uploads/{file_id}", name=name, size=size, extension=extension, mime_type=mime_type, - created_by_role=CreatorUserRole.ACCOUNT, created_by="user", created_at=datetime.now(UTC), - used=False, ) - upload_file.id = file_id - return upload_file def _agent_thought() -> MessageAgentThought: diff --git a/api/tests/unit_tests/core/app/task_pipeline/test_message_cycle_manager_optimization.py b/api/tests/unit_tests/core/app/task_pipeline/test_message_cycle_manager_optimization.py index 535bd04bcfa..1277d08699f 100644 --- a/api/tests/unit_tests/core/app/task_pipeline/test_message_cycle_manager_optimization.py +++ b/api/tests/unit_tests/core/app/task_pipeline/test_message_cycle_manager_optimization.py @@ -20,7 +20,8 @@ from graphon.file import FileTransferMethod, FileType from models import model as model_module from models.base import TypeBase from models.enums import ConversationFromSource, CreatorUserRole, MessageFileBelongsTo -from models.model import App, AppMode, Conversation, MessageAnnotation, MessageFile +from models.model import App, Conversation, MessageAnnotation, MessageFile +from tests.unit_tests.model_factories import make_app, make_conversation @dataclass(frozen=True) @@ -46,29 +47,18 @@ def cycle_db(sqlite_engine: Engine, monkeypatch: pytest.MonkeyPatch) -> Iterator def _app(*, app_id: str = "app-id", tenant_id: str = "tenant-1") -> App: - return App( - id=app_id, - tenant_id=tenant_id, - name="Test App", - description="", - mode=AppMode.CHAT, - enable_site=True, - enable_api=True, - max_active_requests=0, - ) + return make_app(app_id=app_id, tenant_id=tenant_id, icon_type=None, max_active_requests=0) def _conversation(*, conversation_id: str = "conv-1", app_id: str = "app-id") -> Conversation: - conversation = Conversation( + return make_conversation( + conversation_id=conversation_id, app_id=app_id, - mode=AppMode.CHAT, name="", status="normal", from_source=ConversationFromSource.API, inputs={}, ) - conversation.id = conversation_id - return conversation def _message_file( diff --git a/api/tests/unit_tests/core/datasource/test_datasource_file_manager.py b/api/tests/unit_tests/core/datasource/test_datasource_file_manager.py index c61b170eb0d..efd856dfbf9 100644 --- a/api/tests/unit_tests/core/datasource/test_datasource_file_manager.py +++ b/api/tests/unit_tests/core/datasource/test_datasource_file_manager.py @@ -6,28 +6,23 @@ import pytest from sqlalchemy.orm import Session from core.datasource.datasource_file_manager import DatasourceFileManager -from extensions.storage.storage_type import StorageType from models.enums import CreatorUserRole from models.model import MessageFile, UploadFile from models.tools import ToolFile +from tests.unit_tests.model_factories import make_upload_file def _upload_file(id: str, *, key: str, mime_type: str) -> UploadFile: - upload_file = UploadFile( - tenant_id="tenant-1", - storage_type=StorageType.LOCAL, + return make_upload_file( + file_id=id, key=key, name="file.png", size=4, extension=".png", mime_type=mime_type, - created_by_role=CreatorUserRole.ACCOUNT, created_by="user-1", created_at=datetime.now(UTC).replace(tzinfo=None), - used=False, ) - upload_file.id = id - return upload_file def _tool_file(id: str, *, key: str = "tool_key", mimetype: str = "image/png") -> ToolFile: diff --git a/api/tests/unit_tests/core/llm_generator/test_llm_generator_missing.py b/api/tests/unit_tests/core/llm_generator/test_llm_generator_missing.py index 77c887fef8d..6fbf18afbbb 100644 --- a/api/tests/unit_tests/core/llm_generator/test_llm_generator_missing.py +++ b/api/tests/unit_tests/core/llm_generator/test_llm_generator_missing.py @@ -15,6 +15,7 @@ from graphon.model_runtime.entities.llm_entities import LLMResult, LLMUsage from graphon.model_runtime.entities.message_entities import AssistantPromptMessage from models.dataset import Dataset from services.workflow_service import WorkflowService +from tests.unit_tests.model_factories import make_dataset @pytest.fixture @@ -45,8 +46,8 @@ def _model_manager() -> tuple[MagicMock, MagicMock]: def _dataset(*, dataset_id: str, tenant_id: str, name: str, created_at: datetime) -> Dataset: - return Dataset( - id=dataset_id, + return make_dataset( + dataset_id=dataset_id, tenant_id=tenant_id, name=name, created_by="account-id", diff --git a/api/tests/unit_tests/core/ops/test_ops_trace_manager.py b/api/tests/unit_tests/core/ops/test_ops_trace_manager.py index b37553f8510..c0675a15900 100644 --- a/api/tests/unit_tests/core/ops/test_ops_trace_manager.py +++ b/api/tests/unit_tests/core/ops/test_ops_trace_manager.py @@ -26,6 +26,7 @@ from models.model import App, AppMode, AppModelConfig, Conversation, Message, Me from models.workflow import WorkflowAppLog, WorkflowAppLogCreatedFrom, WorkflowRun, WorkflowType from repositories.sqlalchemy_api_workflow_run_repository import DifyAPISQLAlchemyWorkflowRunRepository from tests.unit_tests.config_override import apply_config_overrides +from tests.unit_tests.model_factories import make_app class DummyConfig: @@ -183,20 +184,7 @@ def encryption_functions( def _app(session: Session, *, app_id: str = "app-id", tracing: str | None = None) -> App: - app = App( - id=app_id, - tenant_id="tenant-1", - name="App", - description="description", - mode=AppMode.CHAT, - icon_type=None, - icon=None, - icon_background=None, - enable_site=True, - enable_api=True, - max_active_requests=None, - tracing=tracing, - ) + app = make_app(app_id=app_id, name="App", description="description", icon_type=None, tracing=tracing) session.add(app) session.commit() return app diff --git a/api/tests/unit_tests/core/ops/test_utils.py b/api/tests/unit_tests/core/ops/test_utils.py index 16fd87cfaa4..34752b2a447 100644 --- a/api/tests/unit_tests/core/ops/test_utils.py +++ b/api/tests/unit_tests/core/ops/test_utils.py @@ -19,6 +19,7 @@ from core.ops.utils import ( ) from models.enums import ConversationFromSource from models.model import Message +from tests.unit_tests.model_factories import make_message class _DatabaseBinding: @@ -39,10 +40,11 @@ def message_session(sqlite_session: Session, monkeypatch: pytest.MonkeyPatch) -> def _message(message_id: str) -> Message: - message = Message( - id=message_id, + return make_message( + message_id=message_id, app_id="app-id", conversation_id="conversation-id", + inputs={}, query="question", message={"role": "user", "content": "question"}, answer="answer", @@ -51,8 +53,6 @@ def _message(message_id: str) -> Message: currency="USD", from_source=ConversationFromSource.API, ) - message._inputs = {} - return message class TestValidateUrl: diff --git a/api/tests/unit_tests/core/plugin/test_backwards_invocation_app.py b/api/tests/unit_tests/core/plugin/test_backwards_invocation_app.py index 3716628927b..adfd4084b3e 100644 --- a/api/tests/unit_tests/core/plugin/test_backwards_invocation_app.py +++ b/api/tests/unit_tests/core/plugin/test_backwards_invocation_app.py @@ -13,7 +13,8 @@ from core.plugin.backwards_invocation.base import BaseBackwardsInvocation from models import Account, Tenant, TenantAccountJoin from models.enums import EndUserType from models.model import App, AppMode, AppModelConfig, EndUser -from models.workflow import Workflow, WorkflowType +from models.workflow import Workflow +from tests.unit_tests.model_factories import make_app, make_end_user, make_workflow class _Chunk(BaseModel): @@ -33,30 +34,21 @@ def _app( workflow_id: str | None = None, app_model_config_id: str | None = None, ) -> App: - return App( - id=app_id, + return make_app( + app_id=app_id, tenant_id=tenant_id, name="Plugin app", - description="", mode=mode, - enable_site=False, - enable_api=False, + icon_type=None, workflow_id=workflow_id, app_model_config_id=app_model_config_id, + enable_site=False, + enable_api=False, ) def _workflow(*, workflow_id: str = "workflow-1", app_id: str = "app-1", tenant_id: str = "tenant-1") -> Workflow: - return Workflow( - id=workflow_id, - tenant_id=tenant_id, - app_id=app_id, - type=WorkflowType.WORKFLOW, - version=Workflow.VERSION_DRAFT, - graph="{}", - _features="{}", - created_by="account-1", - ) + return make_workflow(workflow_id=workflow_id, tenant_id=tenant_id, app_id=app_id, graph="{}") def _end_user( @@ -66,14 +58,12 @@ def _end_user( app_id: str = "app-1", session_id: str = "browser-session", ) -> EndUser: - return EndUser( - id=user_id, + return make_end_user( + end_user_id=user_id, tenant_id=tenant_id, app_id=app_id, - type=EndUserType.BROWSER, session_id=session_id, name="Browser user", - is_anonymous=True, ) diff --git a/api/tests/unit_tests/core/rag/datasource/keyword/jieba/test_jieba.py b/api/tests/unit_tests/core/rag/datasource/keyword/jieba/test_jieba.py index 835e6b54e87..2cfbfd5db68 100644 --- a/api/tests/unit_tests/core/rag/datasource/keyword/jieba/test_jieba.py +++ b/api/tests/unit_tests/core/rag/datasource/keyword/jieba/test_jieba.py @@ -11,6 +11,7 @@ import core.rag.datasource.keyword.jieba.jieba as jieba_module from core.rag.datasource.keyword.jieba.jieba import Jieba, dumps_with_sets, set_orjson_default from core.rag.models.document import Document from models.dataset import Dataset, DatasetKeywordTable, DocumentSegment +from tests.unit_tests.model_factories import make_dataset class _DummyLock: @@ -34,11 +35,7 @@ def _dataset_keyword_table( def _dataset(dataset_keyword_table: DatasetKeywordTable | None = None, keyword_number: int | None = None) -> Dataset: - dataset = Dataset( - id="dataset-1", - tenant_id="tenant-1", - keyword_number=keyword_number, - ) + dataset = make_dataset(keyword_number=keyword_number) dataset.get_dataset_keyword_table = MagicMock(return_value=dataset_keyword_table) return dataset diff --git a/api/tests/unit_tests/core/rag/datasource/keyword/test_keyword_base.py b/api/tests/unit_tests/core/rag/datasource/keyword/test_keyword_base.py index e96472699b9..e3b781d3168 100644 --- a/api/tests/unit_tests/core/rag/datasource/keyword/test_keyword_base.py +++ b/api/tests/unit_tests/core/rag/datasource/keyword/test_keyword_base.py @@ -7,10 +7,11 @@ from sqlalchemy.orm import Session from core.rag.datasource.keyword.keyword_base import BaseKeyword from core.rag.models.document import Document from models import Dataset +from tests.unit_tests.model_factories import make_dataset def _dataset() -> Dataset: - return Dataset(id="dataset-1", tenant_id="tenant-1", name="Dataset", created_by="account-1") + return make_dataset() class _KeywordThatRaises(BaseKeyword): diff --git a/api/tests/unit_tests/core/rag/docstore/test_dataset_docstore.py b/api/tests/unit_tests/core/rag/docstore/test_dataset_docstore.py index 91e1ff4b391..07f7a87372a 100644 --- a/api/tests/unit_tests/core/rag/docstore/test_dataset_docstore.py +++ b/api/tests/unit_tests/core/rag/docstore/test_dataset_docstore.py @@ -14,6 +14,7 @@ from sqlalchemy.orm import Session from core.rag.docstore.dataset_docstore import DatasetDocumentStore, DocumentSegment from core.rag.models.document import AttachmentDocument, ChildDocument, Document from models.dataset import ChildChunk, Dataset, SegmentAttachmentBinding +from tests.unit_tests.model_factories import make_dataset TENANT_ID = "00000000-0000-0000-0000-000000000001" DATASET_ID = "00000000-0000-0000-0000-000000000002" @@ -23,11 +24,7 @@ ATTACHMENT_ID = "00000000-0000-0000-0000-000000000005" def _dataset() -> Dataset: - dataset = Dataset( - id=DATASET_ID, - tenant_id=TENANT_ID, - ) - return dataset + return make_dataset(dataset_id=DATASET_ID, tenant_id=TENANT_ID) def _persist_segment( diff --git a/api/tests/unit_tests/core/rag/extractor/test_extract_processor.py b/api/tests/unit_tests/core/rag/extractor/test_extract_processor.py index 2f00fda06db..b065556125e 100644 --- a/api/tests/unit_tests/core/rag/extractor/test_extract_processor.py +++ b/api/tests/unit_tests/core/rag/extractor/test_extract_processor.py @@ -9,28 +9,23 @@ import core.rag.extractor.extract_processor as processor_module from core.rag.extractor.entity.datasource_type import DatasourceType from core.rag.extractor.extract_processor import ExtractProcessor from core.rag.models.document import Document -from extensions.storage.storage_type import StorageType -from models.enums import CreatorUserRole from models.model import UploadFile from tests.unit_tests.config_override import apply_config_overrides +from tests.unit_tests.model_factories import make_upload_file def _upload_file(*, key: str, file_id: str = "upload-file-1") -> UploadFile: - upload_file = UploadFile( - tenant_id="tenant-1", - storage_type=StorageType.LOCAL, + return make_upload_file( + file_id=file_id, key=key, name=Path(key).name, size=1, extension=Path(key).suffix.lstrip("."), mime_type="application/octet-stream", - created_by_role=CreatorUserRole.ACCOUNT, created_by="user-1", created_at=datetime(2025, 1, 1), used=True, ) - upload_file.id = file_id - return upload_file class _ExtractorFactory: diff --git a/api/tests/unit_tests/core/repositories/test_sqlalchemy_workflow_node_execution_repository.py b/api/tests/unit_tests/core/repositories/test_sqlalchemy_workflow_node_execution_repository.py index b437cdcf205..953529b4c55 100644 --- a/api/tests/unit_tests/core/repositories/test_sqlalchemy_workflow_node_execution_repository.py +++ b/api/tests/unit_tests/core/repositories/test_sqlalchemy_workflow_node_execution_repository.py @@ -26,21 +26,22 @@ from core.repositories.sqlalchemy_workflow_node_execution_repository import ( _find_first, _replace_or_append_offload, ) -from extensions.storage.storage_type import StorageType from graphon.entities import WorkflowNodeExecution from graphon.enums import BuiltinNodeTypes, WorkflowNodeExecutionMetadataKey, WorkflowNodeExecutionStatus -from models import Account, EndUser, Tenant +from models import Account, EndUser from models.enums import CreatorUserRole, ExecutionOffLoadType from models.model import UploadFile from models.workflow import WorkflowNodeExecutionModel, WorkflowNodeExecutionOffload, WorkflowNodeExecutionTriggeredFrom +from tests.unit_tests.model_factories import make_account, make_tenant, make_upload_file def _account(*, tenant_id: str = "tenant-1", user_id: str = "user-1") -> Account: - user = Account(name="Test Account", email="test@example.com") - user.id = user_id - user._current_tenant = Tenant(name="Test Tenant") - user._current_tenant.id = tenant_id - return user + return make_account( + account_id=user_id, + name="Test Account", + email="test@example.com", + tenant=make_tenant(tenant_id=tenant_id, name="Test Tenant"), + ) def _end_user(*, tenant_id: str = "tenant-1", user_id: str = "end-user-1") -> EndUser: @@ -48,18 +49,14 @@ def _end_user(*, tenant_id: str = "tenant-1", user_id: str = "end-user-1") -> En def _upload_file(*, key: str = "storage-key") -> UploadFile: - return UploadFile( - tenant_id="tenant-1", - storage_type=StorageType.LOCAL, + return make_upload_file( key=key, name="offload.json", size=1, extension="json", mime_type="application/json", - created_by_role=CreatorUserRole.ACCOUNT, created_by="user-1", created_at=datetime.now(UTC), - used=False, ) diff --git a/api/tests/unit_tests/core/repositories/test_workflow_node_execution_conflict_handling.py b/api/tests/unit_tests/core/repositories/test_workflow_node_execution_conflict_handling.py index 0335bff634b..d9f39da073f 100644 --- a/api/tests/unit_tests/core/repositories/test_workflow_node_execution_conflict_handling.py +++ b/api/tests/unit_tests/core/repositories/test_workflow_node_execution_conflict_handling.py @@ -19,7 +19,8 @@ from graphon.entities.workflow_node_execution import ( ) from graphon.enums import BuiltinNodeTypes from libs.datetime_utils import naive_utc_now -from models import Account, Tenant, WorkflowNodeExecutionModel, WorkflowNodeExecutionTriggeredFrom +from models import Account, WorkflowNodeExecutionModel, WorkflowNodeExecutionTriggeredFrom +from tests.unit_tests.model_factories import make_account, make_tenant @dataclass(frozen=True) @@ -51,12 +52,12 @@ def conflict_database( def _account() -> Account: - tenant = Tenant(name="Conflict Tenant") - tenant.id = "test-tenant-id" - account = Account(name="Conflict User", email="conflict@example.com") - account.id = "test-user-id" - account._current_tenant = tenant - return account + return make_account( + account_id="test-user-id", + name="Conflict User", + email="conflict@example.com", + tenant=make_tenant(tenant_id="test-tenant-id", name="Conflict Tenant"), + ) @pytest.fixture diff --git a/api/tests/unit_tests/core/tools/test_signature.py b/api/tests/unit_tests/core/tools/test_signature.py index ed1b043087a..1214d8a4caf 100644 --- a/api/tests/unit_tests/core/tools/test_signature.py +++ b/api/tests/unit_tests/core/tools/test_signature.py @@ -10,6 +10,7 @@ from typing import Literal from urllib.parse import parse_qs, urlparse import pytest +from typing_extensions import TypedDict from core.tools.signature import ( bind_file_uri, @@ -22,6 +23,16 @@ from core.tools.signature import ( ) +class _SignedPluginFile(TypedDict, closed=True): + filename: str + mimetype: str + tenant_id: str + user_id: str + timestamp: str + nonce: str + sign: str + + @pytest.fixture(autouse=True) def _signature_config(config_overrides: Callable[..., None]) -> None: config_overrides( @@ -213,7 +224,7 @@ def test_plugin_upload_signature_binds_max_size_without_legacy_payload_ambiguity max_size=1024, ) query = parse_qs(urlparse(uri).query) - signed = { + signed: _SignedPluginFile = { "filename": "report.pdf", "mimetype": "application/pdf", "tenant_id": "tenant-id", @@ -227,7 +238,7 @@ def test_plugin_upload_signature_binds_max_size_without_legacy_payload_ambiguity assert verify_plugin_file_signature(**signed, user_from=user_from, max_size=1024) is True assert verify_plugin_file_signature(**signed, user_from=user_from, max_size=2048) is False assert verify_plugin_file_signature(**signed, user_from=user_from) is False - forged = {**signed, "nonce": f"{signed['nonce']}{forged_nonce_suffix}"} + forged: _SignedPluginFile = {**signed, "nonce": f"{signed['nonce']}{forged_nonce_suffix}"} assert verify_plugin_file_signature(**forged) is False @@ -248,7 +259,7 @@ def test_plugin_upload_signature_binds_account_user_from( query = parse_qs(urlparse(uri).query) assert query["user_from"] == ["account"] - signed = { + signed: _SignedPluginFile = { "filename": "report.pdf", "mimetype": "application/pdf", "tenant_id": "tenant-id", diff --git a/api/tests/unit_tests/core/tools/test_tool_engine.py b/api/tests/unit_tests/core/tools/test_tool_engine.py index f688c68cfa6..941dbb22a7d 100644 --- a/api/tests/unit_tests/core/tools/test_tool_engine.py +++ b/api/tests/unit_tests/core/tools/test_tool_engine.py @@ -162,6 +162,90 @@ def test_convert_tool_response_to_str_and_extract_binary_messages(): ) +@pytest.mark.parametrize("json_first", [True, False]) +@pytest.mark.parametrize( + ("text", "json_object"), + [ + pytest.param('{"a":1,"b":2}', {"a": 1, "b": 2}, id="compact"), + pytest.param(' \r\n{\n "a": 1,\n "b": 2\n}\t', {"a": 1, "b": 2}, id="whitespace"), + pytest.param('{"b": 2, "a": 1}', {"a": 1, "b": 2}, id="key-order"), + pytest.param( + '{"items":[{"b":2,"a":1}],"nested":{"y":2,"x":1}}', + {"nested": {"x": 1, "y": 2}, "items": [{"a": 1, "b": 2}]}, + id="nested-key-order", + ), + pytest.param(r'{"\u540d\u79f0":"\u4e2d\u6587"}', {"名称": "中文"}, id="unicode-escapes"), + pytest.param(r'{"path":"\/a\/b"}', {"path": "/a/b"}, id="slash-escapes"), + pytest.param('{"a":1e0}', {"a": 1.0}, id="exponent"), + pytest.param(" { } ", {}, id="empty-object"), + pytest.param('[ {"b":2,"a":1},null,true ]', [{"a": 1, "b": 2}, None, True], id="array"), + ], +) +def test_convert_tool_response_deduplicates_equivalent_json( + text: str, json_object: dict[str, Any] | list[Any], json_first: bool +): + tool = _build_tool() + messages = [ + ToolInvokeMessage( + type=ToolInvokeMessage.MessageType.JSON, + message=ToolInvokeMessage.JsonMessage(json_object=json_object), + ), + tool.create_text_message(text), + ] + if not json_first: + messages.reverse() + + assert ToolEngine.tool_response_to_str(messages) == text + + +@pytest.mark.parametrize( + ("text", "json_object", "json_text"), + [ + pytest.param("plain text", {"a": 1}, '{"a": 1}', id="plain-text"), + pytest.param("[" * 2000 + "0" + "]" * 2000, {"a": 1}, '{"a": 1}', id="deeply-nested-text"), + pytest.param("9" * 5000, {"a": 1}, '{"a": 1}', id="oversized-integer"), + pytest.param("{'a': 1}", {"a": 1}, '{"a": 1}', id="invalid-json"), + pytest.param('result: {"a":1}', {"a": 1}, '{"a": 1}', id="embedded-json"), + pytest.param('{"a":1}{"a":1}', {"a": 1}, '{"a": 1}', id="concatenated-json"), + pytest.param('{"a":1}', {"a": 2}, '{"a": 2}', id="different-value"), + pytest.param('{"a":1}', {"b": 1}, '{"b": 1}', id="different-key"), + pytest.param('{"a":[1,2]}', {"a": [2, 1]}, '{"a": [2, 1]}', id="array-order"), + pytest.param('{"a":true}', {"a": 1}, '{"a": 1}', id="boolean-vs-number"), + pytest.param('{"a":null}', {"a": False}, '{"a": false}', id="null-vs-boolean"), + pytest.param('{"a":"1"}', {"a": 1}, '{"a": 1}', id="string-vs-number"), + pytest.param('{"a":"a b"}', {"a": "ab"}, '{"a": "ab"}', id="string-whitespace"), + ], +) +def test_convert_tool_response_preserves_distinct_json(text: str, json_object: dict[str, Any], json_text: str): + tool = _build_tool() + messages = [tool.create_json_message(json_object), tool.create_text_message(text)] + + assert ToolEngine.tool_response_to_str(messages) == text + json_text + + +@pytest.mark.parametrize("suppress_output", [True, False]) +def test_convert_tool_response_preserves_json_only_output(suppress_output: bool): + tool = _build_tool() + messages = [tool.create_json_message({"b": 2, "a": "中文"}, suppress_output=suppress_output)] + + assert ToolEngine.tool_response_to_str(messages) == ("" if suppress_output else '{"b": 2, "a": "中文"}') + + +def test_convert_tool_response_deduplicates_only_matching_json(): + tool = _build_tool() + text = '{"b":2,"a":1}' + messages = [ + tool.create_json_message({"a": 1, "b": 2}), + tool.create_text_message(text), + tool.create_text_message("result"), + tool.create_text_message(text), + tool.create_json_message({"c": 3}), + tool.create_json_message({"c": 3}), + ] + + assert ToolEngine.tool_response_to_str(messages) == text + "result" + text + '{"c": 3}{"c": 3}' + + @pytest.mark.parametrize("sqlite_session", [(MessageFile,)], indirect=True) def test_create_message_files_and_invoke_generator(sqlite_engine: Engine, sqlite_session: Session): binaries = [ diff --git a/api/tests/unit_tests/core/tools/workflow_as_tool/test_provider.py b/api/tests/unit_tests/core/tools/workflow_as_tool/test_provider.py index 45d9bdbf770..efac03fa7ae 100644 --- a/api/tests/unit_tests/core/tools/workflow_as_tool/test_provider.py +++ b/api/tests/unit_tests/core/tools/workflow_as_tool/test_provider.py @@ -29,9 +29,10 @@ from extensions.ext_database import db from graphon.variables.input_entities import VariableEntity, VariableEntityType from models.account import Account from models.base import TypeBase -from models.model import App, AppMode, IconType +from models.model import App, AppMode from models.tools import WorkflowToolProvider -from models.workflow import Workflow, WorkflowType +from models.workflow import Workflow +from tests.unit_tests.model_factories import make_account, make_app, make_workflow @pytest.fixture @@ -64,35 +65,27 @@ def _controller(provider_id: str = "provider-1") -> WorkflowToolProviderControll def _app(*, tenant_id: str | None = None) -> App: - return App( - id=str(uuid.uuid4()), + return make_app( + app_id=str(uuid.uuid4()), tenant_id=tenant_id or str(uuid.uuid4()), name="Workflow App", mode=AppMode.WORKFLOW, - icon_type=IconType.EMOJI, icon="workflow", - icon_background="#FFFFFF", - enable_site=True, enable_api=False, ) def _account() -> Account: - return Account(name="Alice", email="alice@example.com") + return make_account(account_id=None, name="Alice", email="alice@example.com") def _workflow(app: App, account: Account | None = None) -> Workflow: - return Workflow.new( + return make_workflow( tenant_id=app.tenant_id, app_id=app.id, - type=WorkflowType.WORKFLOW.value, version="1", - graph=json.dumps({"nodes": []}), - features="{}", + graph={"nodes": []}, created_by=account.id if account else str(uuid.uuid4()), - environment_variables=[], - conversation_variables=[], - rag_pipeline_variables=[], ) diff --git a/api/tests/unit_tests/core/trigger/debug/test_debug_event_selectors.py b/api/tests/unit_tests/core/trigger/debug/test_debug_event_selectors.py index 616ec72dcaa..32f8a47ac41 100644 --- a/api/tests/unit_tests/core/trigger/debug/test_debug_event_selectors.py +++ b/api/tests/unit_tests/core/trigger/debug/test_debug_event_selectors.py @@ -32,6 +32,7 @@ from graphon.enums import BuiltinNodeTypes, NodeType, WorkflowType from models.model import App, AppMode from models.workflow import Workflow from tests.unit_tests.core.trigger.conftest import VALID_PROVIDER_ID +from tests.unit_tests.model_factories import make_app def _make_poller_args(node_config: dict[str, Any] | None = None) -> dict[str, Any]: @@ -75,14 +76,12 @@ def _workflow_with_node(node_type: NodeType | None) -> Workflow: def _app() -> App: - return App( - id="a1", + return make_app( + app_id="a1", tenant_id="t1", name="Trigger App", - description="", mode=AppMode.WORKFLOW, - enable_site=True, - enable_api=True, + icon_type=None, max_active_requests=0, ) diff --git a/api/tests/unit_tests/core/workflow/nodes/agent_v2/test_validators.py b/api/tests/unit_tests/core/workflow/nodes/agent_v2/test_validators.py index f5361f9cc71..a49468aeea2 100644 --- a/api/tests/unit_tests/core/workflow/nodes/agent_v2/test_validators.py +++ b/api/tests/unit_tests/core/workflow/nodes/agent_v2/test_validators.py @@ -1,4 +1,3 @@ -import json from datetime import UTC, datetime from unittest.mock import Mock @@ -23,15 +22,11 @@ from models.agent_config_entities import AgentSoulConfig, AgentSoulModelConfig, from models.enums import CreatorUserRole from models.model import UploadFile from models.workflow import Workflow +from tests.unit_tests.model_factories import make_workflow def _workflow(graph: dict) -> Workflow: - return Workflow( - id="workflow-1", - tenant_id="tenant-1", - app_id="app-1", - graph=json.dumps(graph), - ) + return make_workflow(workflow_id="workflow-1", graph=graph) def _binding(node_job: WorkflowNodeJobConfig) -> WorkflowAgentNodeBinding: diff --git a/api/tests/unit_tests/core/workflow/test_workflow_entry_helpers.py b/api/tests/unit_tests/core/workflow/test_workflow_entry_helpers.py index 938fe289b0a..384340c7d0b 100644 --- a/api/tests/unit_tests/core/workflow/test_workflow_entry_helpers.py +++ b/api/tests/unit_tests/core/workflow/test_workflow_entry_helpers.py @@ -1,4 +1,3 @@ -import json from collections import UserString from datetime import datetime from types import SimpleNamespace @@ -21,8 +20,9 @@ from graphon.node_events import NodeRunResult from graphon.nodes import BuiltinNodeTypes from graphon.runtime import VariablePool from graphon.variables.variables import StringVariable -from models.workflow import Workflow, WorkflowType +from models.workflow import Workflow from tests.unit_tests.config_override import config_overrides_context +from tests.unit_tests.model_factories import make_workflow def _build_typed_node_config(node_type: NodeType): @@ -31,16 +31,7 @@ def _build_typed_node_config(node_type: NodeType): def _workflow() -> Workflow: """Build a real transient workflow for single-step orchestration tests.""" - return Workflow( - id="workflow-id", - tenant_id="tenant-id", - app_id="app-id", - type=WorkflowType.WORKFLOW, - version=Workflow.VERSION_DRAFT, - graph=json.dumps({"nodes": [], "edges": []}), - _features="{}", - created_by="user-id", - ) + return make_workflow(workflow_id="workflow-id", tenant_id="tenant-id", app_id="app-id", created_by="user-id") def _build_minimal_workflow_entry( diff --git a/api/tests/unit_tests/events/event_handlers/test_queue_default_plugin_install_when_tenant_created.py b/api/tests/unit_tests/events/event_handlers/test_queue_default_plugin_install_when_tenant_created.py index 14303aef5d9..21c64cdd2fb 100644 --- a/api/tests/unit_tests/events/event_handlers/test_queue_default_plugin_install_when_tenant_created.py +++ b/api/tests/unit_tests/events/event_handlers/test_queue_default_plugin_install_when_tenant_created.py @@ -6,12 +6,11 @@ import pytest from events.event_handlers import queue_default_plugin_install_when_tenant_created as handler_module from models.account import Tenant from tests.unit_tests.config_override import apply_config_overrides +from tests.unit_tests.model_factories import make_tenant def _tenant() -> Tenant: - tenant = Tenant(name="Test tenant") - tenant.id = "tenant-1" - return tenant + return make_tenant(name="Test tenant") def test_handle_skips_when_no_default_plugins_are_configured(monkeypatch: pytest.MonkeyPatch) -> None: diff --git a/api/tests/unit_tests/libs/test_oauth_bearer_layer0_cache.py b/api/tests/unit_tests/libs/test_oauth_bearer_layer0_cache.py deleted file mode 100644 index 0023f17119e..00000000000 --- a/api/tests/unit_tests/libs/test_oauth_bearer_layer0_cache.py +++ /dev/null @@ -1,94 +0,0 @@ -"""Unit tests for record_layer0_verdict — merge L0 verdict into AuthContext cache.""" - -from __future__ import annotations - -import json -from unittest.mock import MagicMock, patch - -import pytest - -from libs.oauth_bearer import record_layer0_verdict - - -@pytest.fixture -def mock_redis(): - return MagicMock() - - -@patch("libs.oauth_bearer.redis_client") -def test_no_op_when_cache_entry_missing(mock_redis): - mock_redis.get.return_value = None - record_layer0_verdict("h1", "t1", True) - mock_redis.setex.assert_not_called() - - -@patch("libs.oauth_bearer.redis_client") -def test_no_op_when_cache_entry_invalid_marker(mock_redis): - mock_redis.get.return_value = b"invalid" - record_layer0_verdict("h1", "t1", True) - mock_redis.setex.assert_not_called() - - -@patch("libs.oauth_bearer.redis_client") -def test_no_op_when_json_malformed(mock_redis): - mock_redis.get.return_value = b"not json" - record_layer0_verdict("h1", "t1", True) - mock_redis.setex.assert_not_called() - - -@patch("libs.oauth_bearer.redis_client") -def test_no_op_when_ttl_expired(mock_redis): - mock_redis.get.return_value = json.dumps( - { - "subject_email": "e", - "subject_issuer": None, - "account_id": None, - "token_id": "tid", - "expires_at": None, - } - ).encode() - mock_redis.ttl.return_value = -1 - record_layer0_verdict("h1", "t1", True) - mock_redis.setex.assert_not_called() - - -@patch("libs.oauth_bearer.redis_client") -def test_merges_new_tenant_verdict(mock_redis): - mock_redis.get.return_value = json.dumps( - { - "subject_email": "e", - "subject_issuer": None, - "account_id": None, - "token_id": "tid", - "expires_at": None, - "verified_tenants": {"t0": True}, - } - ).encode() - mock_redis.ttl.return_value = 42 - - record_layer0_verdict("h1", "t1", False) - - mock_redis.setex.assert_called_once() - args = mock_redis.setex.call_args - assert args.args[0] == "auth:token:h1" - assert args.args[1] == 42 # remaining TTL preserved - written = json.loads(args.args[2]) - assert written["verified_tenants"] == {"t0": True, "t1": False} - - -@patch("libs.oauth_bearer.redis_client") -def test_merges_when_field_absent_from_legacy_entry(mock_redis): - """Backward compat: legacy cache entry without verified_tenants field.""" - mock_redis.get.return_value = json.dumps( - { - "subject_email": "e", - "subject_issuer": None, - "account_id": None, - "token_id": "tid", - "expires_at": None, - } - ).encode() - mock_redis.ttl.return_value = 42 - record_layer0_verdict("h1", "t1", True) - written = json.loads(mock_redis.setex.call_args.args[2]) - assert written["verified_tenants"] == {"t1": True} diff --git a/api/tests/unit_tests/libs/test_oauth_bearer_require_scope.py b/api/tests/unit_tests/libs/test_oauth_bearer_require_scope.py deleted file mode 100644 index e8204a6e2e0..00000000000 --- a/api/tests/unit_tests/libs/test_oauth_bearer_require_scope.py +++ /dev/null @@ -1,98 +0,0 @@ -"""require_scope is a route-level gate run after validate_bearer. -Tests use a fake auth_ctx published via the openapi auth ContextVar (no -authenticator wiring needed). The `_publish_auth_ctx` helper guarantees -the ContextVar is reset between tests so worker-thread reuse can't leak -identity into the next test. -""" - -from __future__ import annotations - -import uuid -from collections.abc import Iterator -from contextlib import contextmanager - -import pytest -from flask import Flask -from werkzeug.exceptions import Forbidden - -from libs.oauth_bearer import ( - AuthContext, - Scope, - SubjectType, - TokenType, - require_scope, - reset_auth_ctx, - set_auth_ctx, -) - - -@contextmanager -def _publish_auth_ctx(ctx: AuthContext) -> Iterator[None]: - token = set_auth_ctx(ctx) - try: - yield - finally: - reset_auth_ctx(token) - - -@pytest.fixture -def app() -> Flask: - app = Flask(__name__) - app.config["TESTING"] = True - return app - - -def _ctx(scopes) -> AuthContext: - return AuthContext( - subject_type=SubjectType.ACCOUNT, - subject_email="user@example.com", - subject_issuer="dify:account", - account_id=uuid.uuid4(), - client_id="difyctl", - scopes=scopes, - token_id=uuid.uuid4(), - token_type=TokenType.OAUTH_ACCOUNT, - expires_at=None, - token_hash="h1", - verified_tenants={}, - ) - - -def test_require_scope_allows_when_scope_present(app: Flask): - @require_scope("apps:read") - def view(): - return "ok" - - with app.test_request_context(), _publish_auth_ctx(_ctx(frozenset({"apps:read"}))): - assert view() == "ok" - - -def test_require_scope_rejects_when_scope_missing(app: Flask): - @require_scope("apps:write") - def view(): - return "ok" - - with app.test_request_context(), _publish_auth_ctx(_ctx(frozenset({"apps:read"}))): - with pytest.raises(Forbidden) as exc: - view() - assert "insufficient_scope: apps:write" in str(exc.value.description) - - -def test_require_scope_full_passes_any_check(app: Flask): - @require_scope("apps:write") - def view(): - return "ok" - - with app.test_request_context(), _publish_auth_ctx(_ctx(frozenset({Scope.FULL}))): - assert view() == "ok" - - -def test_require_scope_without_validate_bearer_raises_runtime_error(app: Flask): - @require_scope("apps:read") - def view(): - return "ok" - - with app.test_request_context(): - # No auth ContextVar published — validate_bearer was forgotten. - with pytest.raises(RuntimeError, match="stack @validate_bearer above @require_scope"): - view() diff --git a/api/tests/unit_tests/libs/test_workspace_member_helper.py b/api/tests/unit_tests/libs/test_workspace_member_helper.py deleted file mode 100644 index 6a202e79fa4..00000000000 --- a/api/tests/unit_tests/libs/test_workspace_member_helper.py +++ /dev/null @@ -1,201 +0,0 @@ -"""SQLite-backed unit tests for workspace membership enforcement.""" - -from __future__ import annotations - -import uuid -from collections.abc import Iterator -from dataclasses import dataclass -from unittest.mock import Mock - -import pytest -from sqlalchemy import event -from sqlalchemy.orm import Session -from werkzeug.exceptions import Forbidden - -from enums import DeploymentEdition -from libs import oauth_bearer -from libs.oauth_bearer import AuthContext, Scope, SubjectType, TokenType, require_workspace_member -from models.account import Account, AccountStatus, Tenant, TenantAccountJoin, TenantAccountRole -from tests.unit_tests.config_override import apply_config_overrides - -pytestmark = pytest.mark.usefixtures("community_edition") - - -@dataclass(frozen=True) -class Database: - """Real ORM binding and executed-statement log for one isolated test.""" - - session: Session - statements: list[str] - - -@pytest.fixture -def database(sqlite_session: Session, monkeypatch: pytest.MonkeyPatch) -> Iterator[Database]: - statements: list[str] = [] - - def record_statement(_connection, _cursor, statement, _parameters, _context, _executemany) -> None: - statements.append(statement) - - engine = sqlite_session.get_bind() - event.listen(engine, "before_cursor_execute", record_statement) - binding = Database(session=sqlite_session, statements=statements) - monkeypatch.setattr(oauth_bearer, "db", binding) - try: - yield binding - finally: - event.remove(engine, "before_cursor_execute", record_statement) - - -@pytest.fixture -def community_edition(monkeypatch: pytest.MonkeyPatch) -> None: - apply_config_overrides(monkeypatch, DEPLOYMENT_EDITION=DeploymentEdition.COMMUNITY) - - -def _ctx( - verified: dict[str, bool] | None = None, - *, - account_id: uuid.UUID | None = None, - account: bool = True, -) -> AuthContext: - return AuthContext( - subject_type=SubjectType.ACCOUNT if account else SubjectType.EXTERNAL_SSO, - subject_email="e@example.com", - subject_issuer=None, - account_id=account_id or (uuid.uuid4() if account else None), - client_id="difyctl", - scopes=frozenset({Scope.FULL}), - token_id=uuid.uuid4(), - token_type=TokenType.OAUTH_ACCOUNT if account else TokenType.OAUTH_EXTERNAL_SSO, - expires_at=None, - token_hash="h1", - verified_tenants=dict(verified or {}), - ) - - -def _persist_membership( - session: Session, - *, - account_id: uuid.UUID, - tenant_id: str, - status: AccountStatus = AccountStatus.ACTIVE, -) -> None: - account = _account(account_id, status=status) - tenant = Tenant(name=f"Tenant {tenant_id}") - tenant.id = tenant_id - membership = TenantAccountJoin( - tenant_id=tenant_id, - account_id=account_id.hex, - role=TenantAccountRole.NORMAL, - ) - session.add_all([account, tenant, membership]) - session.commit() - - -def _account(account_id: uuid.UUID, *, status: AccountStatus = AccountStatus.ACTIVE) -> Account: - account = Account(name="Workspace member", email=f"{account_id}@example.com", status=status) - # SQLite's StringUUID adapter binds UUID objects as compact hex, while - # PostgreSQL binds their dashed string form. Persist the SQLite-bound form - # so the production query can keep accepting the AuthContext UUID object. - account.id = account_id.hex - return account - - -def test_skips_for_enterprise_edition(database: Database, monkeypatch: pytest.MonkeyPatch) -> None: - apply_config_overrides(monkeypatch, DEPLOYMENT_EDITION=DeploymentEdition.ENTERPRISE) - before = len(database.statements) - - require_workspace_member(_ctx(), "tenant-1") - - assert len(database.statements) == before - - -def test_skips_for_external_sso(database: Database) -> None: - before = len(database.statements) - - require_workspace_member(_ctx(account=False), "tenant-1") - - assert len(database.statements) == before - - -def test_uses_cached_allow_without_database_access(database: Database) -> None: - before = len(database.statements) - - require_workspace_member(_ctx({"tenant-1": True}), "tenant-1") - - assert len(database.statements) == before - - -def test_uses_cached_denial_without_database_access(database: Database) -> None: - before = len(database.statements) - - with pytest.raises(Forbidden, match="workspace_membership_revoked"): - require_workspace_member(_ctx({"tenant-1": False}), "tenant-1") - - assert len(database.statements) == before - - -@pytest.mark.usefixtures("database") -def test_denies_when_membership_is_absent( - monkeypatch: pytest.MonkeyPatch, -) -> None: - record_verdict = Mock() - monkeypatch.setattr(oauth_bearer, "record_layer0_verdict", record_verdict) - - with pytest.raises(Forbidden, match="workspace_membership_revoked"): - require_workspace_member(_ctx(), "tenant-1") - - record_verdict.assert_called_once_with("h1", "tenant-1", False) - - -def test_denies_membership_from_another_tenant( - database: Database, - monkeypatch: pytest.MonkeyPatch, -) -> None: - account_id = uuid.uuid4() - requested_tenant_member_id = uuid.uuid4() - status_decoy_id = uuid.uuid4() - _persist_membership(database.session, account_id=account_id, tenant_id="tenant-2") - _persist_membership(database.session, account_id=requested_tenant_member_id, tenant_id="tenant-1") - database.session.add(_account(status_decoy_id, status=AccountStatus.BANNED)) - database.session.commit() - record_verdict = Mock() - monkeypatch.setattr(oauth_bearer, "record_layer0_verdict", record_verdict) - - with pytest.raises(Forbidden, match="workspace_membership_revoked"): - require_workspace_member(_ctx(account_id=account_id), "tenant-1") - - record_verdict.assert_called_once_with("h1", "tenant-1", False) - - -def test_denies_when_account_is_inactive( - database: Database, - monkeypatch: pytest.MonkeyPatch, -) -> None: - account_id = uuid.uuid4() - _persist_membership( - database.session, - account_id=account_id, - tenant_id="tenant-1", - status=AccountStatus.BANNED, - ) - record_verdict = Mock() - monkeypatch.setattr(oauth_bearer, "record_layer0_verdict", record_verdict) - - with pytest.raises(Forbidden, match="workspace_membership_revoked"): - require_workspace_member(_ctx(account_id=account_id), "tenant-1") - - record_verdict.assert_called_once_with("h1", "tenant-1", False) - - -def test_allows_active_member_and_records_verdict( - database: Database, - monkeypatch: pytest.MonkeyPatch, -) -> None: - account_id = uuid.uuid4() - _persist_membership(database.session, account_id=account_id, tenant_id="tenant-1") - record_verdict = Mock() - monkeypatch.setattr(oauth_bearer, "record_layer0_verdict", record_verdict) - - require_workspace_member(_ctx(account_id=account_id), "tenant-1") - - record_verdict.assert_called_once_with("h1", "tenant-1", True) diff --git a/api/tests/unit_tests/model_factories.py b/api/tests/unit_tests/model_factories.py new file mode 100644 index 00000000000..3aeb0f061b5 --- /dev/null +++ b/api/tests/unit_tests/model_factories.py @@ -0,0 +1,489 @@ +"""Builders for the model instances unit tests construct over and over. + +Every builder returns a transient (unpersisted) instance carrying the field set +that most call sites need, so a test module only spells out the values it +actually asserts on. Keyword arguments map one-to-one onto model columns. +""" + +import json +from collections.abc import Mapping, Sequence +from datetime import datetime +from decimal import Decimal + +from extensions.storage.storage_type import StorageType +from graphon.variables import VariableBase +from models.account import Account, AccountStatus, Tenant, TenantAccountRole, TenantStatus +from models.dataset import Dataset, Document +from models.enums import ( + AppStatus, + CreatorUserRole, + DataSourceType, + DocumentCreatedFrom, + EndUserType, + PermissionEnum, +) +from models.model import ( + App, + AppMode, + Conversation, + ConversationFromSource, + EndUser, + IconType, + Message, + UploadFile, +) +from models.workflow import Workflow, WorkflowKind, WorkflowType +from services.entities.account_entities import AccountSnapshot + + +def make_app( + *, + app_id: str = "app-1", + tenant_id: str = "tenant-1", + name: str = "Test App", + description: str = "", + mode: AppMode = AppMode.CHAT, + icon_type: IconType | None = IconType.EMOJI, + icon: str | None = "robot", + icon_background: str | None = "#FFFFFF", + status: AppStatus = AppStatus.NORMAL, + app_model_config_id: str | None = None, + workflow_id: str | None = None, + enable_site: bool = True, + enable_api: bool = True, + api_rpm: int = 0, + api_rph: int = 0, + is_demo: bool = False, + is_public: bool = False, + is_universal: bool = False, + max_active_requests: int | None = None, + tracing: str | None = None, + created_by: str | None = None, + maintainer: str | None = None, + use_icon_as_answer_icon: bool = False, +) -> App: + """Build a transient ``App``. + + ``app_id`` is spelled out instead of ``id`` so call sites never shadow the + builtin. ``icon_type=None`` clears ``icon`` and ``icon_background`` too, for + the tests that assert on an app without icon metadata. + """ + if icon_type is None: + icon = None + icon_background = None + return App( + id=app_id, + tenant_id=tenant_id, + name=name, + description=description, + mode=mode, + icon_type=icon_type, + icon=icon, + icon_background=icon_background, + status=status, + app_model_config_id=app_model_config_id, + workflow_id=workflow_id, + enable_site=enable_site, + enable_api=enable_api, + api_rpm=api_rpm, + api_rph=api_rph, + is_demo=is_demo, + is_public=is_public, + is_universal=is_universal, + max_active_requests=max_active_requests, + tracing=tracing, + created_by=created_by, + maintainer=maintainer, + use_icon_as_answer_icon=use_icon_as_answer_icon, + ) + + +def make_account( + *, + account_id: str | None = "account-1", + name: str = "Test User", + email: str = "test@example.com", + status: AccountStatus = AccountStatus.ACTIVE, + interface_language: str | None = None, + timezone: str | None = None, + role: TenantAccountRole | None = None, + tenant: Tenant | None = None, +) -> Account: + """Build a transient ``Account``. + + ``id``, ``role`` and ``_current_tenant`` are ``init=False`` on the dataclass + model, so they are assigned after construction. Passing ``account_id=None`` + keeps the generated identifier. + """ + account = Account( + name=name, + email=email, + status=status, + interface_language=interface_language, + timezone=timezone, + ) + if account_id is not None: + account.id = account_id + account.role = role + account._current_tenant = tenant + return account + + +def make_tenant( + *, + tenant_id: str | None = "tenant-1", + name: str = "Test Tenant", + status: TenantStatus = TenantStatus.NORMAL, + encrypt_public_key: str | None = None, +) -> Tenant: + """Build a transient ``Tenant``; ``tenant_id=None`` keeps the generated one.""" + tenant = Tenant(name=name, status=status, encrypt_public_key=encrypt_public_key) + if tenant_id is not None: + tenant.id = tenant_id + return tenant + + +def make_end_user( + *, + end_user_id: str = "end-user-1", + tenant_id: str = "tenant-1", + app_id: str | None = None, + end_user_type: EndUserType = EndUserType.BROWSER, + session_id: str = "session-1", + external_user_id: str | None = None, + name: str | None = None, + is_anonymous: bool = True, +) -> EndUser: + """Build a transient ``EndUser``.""" + return EndUser( + id=end_user_id, + tenant_id=tenant_id, + app_id=app_id, + type=end_user_type, + session_id=session_id, + external_user_id=external_user_id, + name=name, + is_anonymous=is_anonymous, + ) + + +def make_account_snapshot( + *, + account_id: str = "account-1", + name: str = "Account", + email: str = "account@example.com", + avatar: str | None = None, + is_password_set: bool = False, + interface_language: str | None = "en-US", + interface_theme: str | None = "light", + timezone: str | None = "UTC", + last_login_at: datetime | None = None, + last_login_ip: str | None = None, + status: str = "active", + initialized_at: datetime | None = None, + created_at: datetime = datetime(2026, 1, 1), +) -> AccountSnapshot: + """Build the framework-neutral ``AccountSnapshot`` the account services return.""" + return AccountSnapshot( + id=account_id, + name=name, + email=email, + avatar=avatar, + is_password_set=is_password_set, + interface_language=interface_language, + interface_theme=interface_theme, + timezone=timezone, + last_login_at=last_login_at, + last_login_ip=last_login_ip, + status=status, + initialized_at=initialized_at, + created_at=created_at, + ) + + +def make_workflow( + *, + workflow_id: str | None = None, + tenant_id: str = "tenant-1", + app_id: str = "app-1", + workflow_type: WorkflowType = WorkflowType.WORKFLOW, + kind: WorkflowKind = WorkflowKind.STANDARD, + version: str = Workflow.VERSION_DRAFT, + graph: str | Mapping[str, object] | None = None, + features: str | Mapping[str, object] = "{}", + created_by: str = "account-1", + environment_variables: Sequence[VariableBase] = (), + conversation_variables: Sequence[VariableBase] = (), + rag_pipeline_variables: list[dict[str, object]] | None = None, + marked_name: str = "", + marked_comment: str = "", + version_number: int | None = None, +) -> Workflow: + """Build a transient ``Workflow`` through ``Workflow.new``. + + ``graph`` and ``features`` accept either the serialized column value or the + mapping to serialize, since most call sites start from a graph literal. + ``workflow_id=None`` keeps the identifier ``Workflow.new`` generates. + """ + workflow = Workflow.new( + tenant_id=tenant_id, + app_id=app_id, + type=workflow_type.value, + kind=kind.value, + version=version, + graph=graph if isinstance(graph, str) else json.dumps({"nodes": [], "edges": []} if graph is None else graph), + features=features if isinstance(features, str) else json.dumps(features), + created_by=created_by, + environment_variables=environment_variables, + conversation_variables=conversation_variables, + rag_pipeline_variables=rag_pipeline_variables or [], + marked_name=marked_name, + marked_comment=marked_comment, + version_number=version_number, + ) + if workflow_id is not None: + workflow.id = workflow_id + return workflow + + +def make_upload_file( + *, + file_id: str | None = None, + tenant_id: str = "tenant-1", + storage_type: StorageType = StorageType.LOCAL, + key: str = "uploads/file-1", + name: str = "test.txt", + size: int = 10, + extension: str = "txt", + mime_type: str = "text/plain", + created_by_role: CreatorUserRole = CreatorUserRole.ACCOUNT, + created_by: str = "account-1", + created_at: datetime | None = None, + used: bool = False, + source_url: str = "", +) -> UploadFile: + """Build a transient ``UploadFile``; ``file_id=None`` keeps the generated one.""" + upload_file = UploadFile( + tenant_id=tenant_id, + storage_type=storage_type, + key=key, + name=name, + size=size, + extension=extension, + mime_type=mime_type, + created_by_role=created_by_role, + created_by=created_by, + created_at=created_at if created_at is not None else datetime(2024, 1, 1), + used=used, + source_url=source_url, + ) + if file_id is not None: + upload_file.id = file_id + return upload_file + + +def make_dataset( + *, + dataset_id: str | None = "dataset-1", + tenant_id: str = "tenant-1", + name: str = "Dataset", + description: str | None = None, + provider: str | None = None, + permission: PermissionEnum | None = None, + data_source_type: DataSourceType | None = None, + indexing_technique: str | None = None, + index_struct: str | None = None, + created_by: str = "account-1", + maintainer: str | None = None, + created_at: datetime | None = None, + embedding_model: str | None = None, + embedding_model_provider: str | None = None, + keyword_number: int | None = None, + built_in_field_enabled: bool | None = None, + icon_info: dict[str, str] | None = None, + pipeline_id: str | None = None, + chunk_structure: str | None = None, + enable_api: bool | None = None, +) -> Dataset: + """Build a transient ``Dataset``. + + Optional columns default to ``None`` so an unset keyword leaves the column + unset, matching a bare ``Dataset(...)``. ``dataset_id=None`` keeps the + generated identifier. + """ + values: dict[str, object] = {"tenant_id": tenant_id, "name": name, "created_by": created_by} + optional: dict[str, object | None] = { + "id": dataset_id, + "description": description, + "provider": provider, + "permission": permission, + "data_source_type": data_source_type, + "indexing_technique": indexing_technique, + "index_struct": index_struct, + "maintainer": maintainer, + "created_at": created_at, + "embedding_model": embedding_model, + "embedding_model_provider": embedding_model_provider, + "keyword_number": keyword_number, + "built_in_field_enabled": built_in_field_enabled, + "icon_info": icon_info, + "pipeline_id": pipeline_id, + "chunk_structure": chunk_structure, + "enable_api": enable_api, + } + values.update({key: value for key, value in optional.items() if value is not None}) + return Dataset(**values) + + +def make_conversation( + *, + conversation_id: str | None = "conversation-1", + app_id: str = "app-1", + mode: AppMode = AppMode.CHAT, + name: str = "Conversation", + inputs: Mapping[str, object] | None = None, + status: str | None = None, + from_source: ConversationFromSource | None = None, + from_account_id: str | None = None, + from_end_user_id: str | None = None, + invoke_from: str | None = None, + introduction: str | None = None, + system_instruction: str | None = None, + system_instruction_tokens: int | None = None, + override_model_configs: str | None = None, + summary: str | None = None, + dialogue_count: int | None = None, + is_deleted: bool | None = None, + agent_workspace_binding_id: str | None = None, +) -> Conversation: + """Build a transient ``Conversation``; unset keywords leave their column unset.""" + values: dict[str, object] = {"app_id": app_id, "mode": mode, "name": name} + optional: dict[str, object | None] = { + "id": conversation_id, + "inputs": inputs, + "status": status, + "from_source": from_source, + "from_account_id": from_account_id, + "from_end_user_id": from_end_user_id, + "invoke_from": invoke_from, + "introduction": introduction, + "system_instruction": system_instruction, + "system_instruction_tokens": system_instruction_tokens, + "override_model_configs": override_model_configs, + "summary": summary, + "dialogue_count": dialogue_count, + "is_deleted": is_deleted, + "agent_workspace_binding_id": agent_workspace_binding_id, + } + values.update({key: value for key, value in optional.items() if value is not None}) + return Conversation(**values) + + +def make_message( + *, + message_id: str | None = "message-1", + app_id: str | None = "app-1", + conversation_id: str | None = "conversation-1", + inputs: Mapping[str, object] | None = None, + query: str | None = None, + message: object | None = None, + answer: str | None = None, + status: str | None = None, + message_unit_price: Decimal | None = None, + answer_unit_price: Decimal | None = None, + total_price: Decimal | None = None, + currency: str | None = None, + from_source: ConversationFromSource | None = None, + from_account_id: str | None = None, + from_end_user_id: str | None = None, + invoke_from: str | None = None, + message_tokens: int | None = None, + answer_tokens: int | None = None, + provider_response_latency: float | None = None, + workflow_run_id: str | None = None, + created_at: datetime | None = None, + updated_at: datetime | None = None, +) -> Message: + """Build a transient ``Message``; unset keywords leave their column unset.""" + values: dict[str, object] = {} + optional: dict[str, object | None] = { + "id": message_id, + "app_id": app_id, + "conversation_id": conversation_id, + "inputs": inputs, + "query": query, + "message": message, + "answer": answer, + "status": status, + "message_unit_price": message_unit_price, + "answer_unit_price": answer_unit_price, + "total_price": total_price, + "currency": currency, + "from_source": from_source, + "from_account_id": from_account_id, + "from_end_user_id": from_end_user_id, + "invoke_from": invoke_from, + "message_tokens": message_tokens, + "answer_tokens": answer_tokens, + "provider_response_latency": provider_response_latency, + "workflow_run_id": workflow_run_id, + "created_at": created_at, + "updated_at": updated_at, + } + values.update({key: value for key, value in optional.items() if value is not None}) + return Message(**values) + + +def make_document( + *, + document_id: str | None = "document-1", + tenant_id: str = "tenant-1", + dataset_id: str = "dataset-1", + position: int = 1, + data_source_type: DataSourceType = DataSourceType.UPLOAD_FILE, + data_source_info: str | None = None, + batch: str = "batch-1", + name: str = "Document", + created_from: str = DocumentCreatedFrom.WEB, + created_by: str = "account-1", + doc_form: str | None = None, + doc_metadata: dict[str, object] | None = None, + created_at: datetime | None = None, + updated_at: datetime | None = None, +) -> Document: + """Build a transient ``Document``; unset keywords leave their column unset.""" + values: dict[str, object] = { + "tenant_id": tenant_id, + "dataset_id": dataset_id, + "position": position, + "data_source_type": data_source_type, + "batch": batch, + "name": name, + "created_from": created_from, + "created_by": created_by, + } + optional: dict[str, object | None] = { + "id": document_id, + "data_source_info": data_source_info, + "doc_form": doc_form, + "doc_metadata": doc_metadata, + "created_at": created_at, + "updated_at": updated_at, + } + values.update({key: value for key, value in optional.items() if value is not None}) + return Document(**values) + + +__all__ = [ + "make_account", + "make_account_snapshot", + "make_app", + "make_conversation", + "make_dataset", + "make_document", + "make_end_user", + "make_message", + "make_tenant", + "make_upload_file", + "make_workflow", +] diff --git a/api/tests/unit_tests/models/test_comment_models.py b/api/tests/unit_tests/models/test_comment_models.py index 480b6fadcc3..0dc1e3339d2 100644 --- a/api/tests/unit_tests/models/test_comment_models.py +++ b/api/tests/unit_tests/models/test_comment_models.py @@ -5,12 +5,11 @@ from sqlalchemy.orm import Session from models.account import Account from models.comment import WorkflowComment, WorkflowCommentMention, WorkflowCommentReply +from tests.unit_tests.model_factories import make_account def _account(name: str) -> Account: - account = Account(name=name, email=f"{name.lower()}@example.com") - account.id = str(uuid4()) - return account + return make_account(account_id=str(uuid4()), name=name, email=f"{name.lower()}@example.com") def _comment(created_by: str, resolved_by: str | None = None) -> WorkflowComment: diff --git a/api/tests/unit_tests/models/test_workflow_run_accessors.py b/api/tests/unit_tests/models/test_workflow_run_accessors.py new file mode 100644 index 00000000000..62c94d004ee --- /dev/null +++ b/api/tests/unit_tests/models/test_workflow_run_accessors.py @@ -0,0 +1,114 @@ +"""Regression coverage for ``WorkflowRun`` account accessors. + +Ensures the ``@property``→session-parameter refactor preserves the role-based dispatch: +``created_by_account`` looks up an Account only when role is ACCOUNT; ``created_by_end_user`` +looks up an EndUser only when role is END_USER. Also covers the session-carrying response +sources used by the console endpoints that validate the run detail / pagination responses +with ``from_attributes=True``. +""" + +from types import SimpleNamespace +from uuid import uuid4 + +from sqlalchemy.orm import Session + +from fields.workflow_run_fields import workflow_run_pagination_response_source, workflow_run_response_source +from models.account import Account +from models.enums import CreatorUserRole, EndUserType, WorkflowRunTriggeredFrom +from models.model import EndUser +from models.workflow import WorkflowRun + + +def _run(role: CreatorUserRole, created_by: str) -> WorkflowRun: + """Construct a WorkflowRun without touching the database.""" + return WorkflowRun( + id=str(uuid4()), + tenant_id="00000000-0000-0000-0000-000000000001", + app_id="00000000-0000-0000-0000-000000000002", + workflow_id=str(uuid4()), + type="workflow", + triggered_from=WorkflowRunTriggeredFrom.APP_RUN, + version="1.0", + status="succeeded", + created_by_role=role, + created_by=created_by, + ) + + +class TestCreatedByAccount: + def test_returns_account_lookup_when_role_is_account(self, sqlite_session: Session) -> None: + account = Account(name="Test Account", email="test@example.com") + sqlite_session.add(account) + sqlite_session.flush() + run = _run(CreatorUserRole.ACCOUNT, created_by=account.id) + + result = run.created_by_account(session=sqlite_session) + + assert result is not None + assert result.id == account.id + + def test_returns_none_when_role_is_end_user(self, sqlite_session: Session) -> None: + account = Account(name="Test Account", email="test@example.com") + sqlite_session.add(account) + sqlite_session.flush() + run = _run(CreatorUserRole.END_USER, created_by=account.id) + + assert run.created_by_account(session=sqlite_session) is None + + +class TestCreatedByEndUser: + def test_returns_end_user_lookup_when_role_is_end_user(self, sqlite_session: Session) -> None: + end_user = EndUser( + tenant_id="00000000-0000-0000-0000-000000000001", + type=EndUserType.BROWSER, + session_id="session-1", + ) + sqlite_session.add(end_user) + sqlite_session.flush() + run = _run(CreatorUserRole.END_USER, created_by=end_user.id) + + result = run.created_by_end_user(session=sqlite_session) + + assert result is not None + assert result.id == end_user.id + + def test_returns_none_when_role_is_account(self, sqlite_session: Session) -> None: + end_user = EndUser( + tenant_id="00000000-0000-0000-0000-000000000001", + type=EndUserType.BROWSER, + session_id="session-1", + ) + sqlite_session.add(end_user) + sqlite_session.flush() + run = _run(CreatorUserRole.ACCOUNT, created_by=end_user.id) + + assert run.created_by_end_user(session=sqlite_session) is None + + +class TestWorkflowRunResponseSources: + def test_detail_source_resolves_accessors_and_proxies_other_attributes(self, sqlite_session: Session) -> None: + account = Account(name="Test Account", email="test@example.com") + sqlite_session.add(account) + sqlite_session.flush() + run = _run(CreatorUserRole.ACCOUNT, created_by=account.id) + + source = workflow_run_response_source(run, session=sqlite_session) + + resolved = source.created_by_account + assert resolved is not None + assert resolved.id == account.id + assert source.created_by_end_user is None + assert source.version == "1.0" + + def test_pagination_source_wraps_each_run(self, sqlite_session: Session) -> None: + account = Account(name="Test Account", email="test@example.com") + sqlite_session.add(account) + sqlite_session.flush() + runs = [_run(CreatorUserRole.ACCOUNT, created_by=account.id) for _ in range(2)] + pagination = SimpleNamespace(data=runs, limit=20, has_more=False) + + payload = workflow_run_pagination_response_source(pagination, session=sqlite_session) + + assert payload["limit"] == 20 + assert payload["has_more"] is False + assert [item.created_by_account.id for item in payload["data"]] == [account.id, account.id] diff --git a/api/tests/unit_tests/services/agent/test_agent_observability_service.py b/api/tests/unit_tests/services/agent/test_agent_observability_service.py index 32a6e5a54d8..db6a8c19cc0 100644 --- a/api/tests/unit_tests/services/agent/test_agent_observability_service.py +++ b/api/tests/unit_tests/services/agent/test_agent_observability_service.py @@ -18,7 +18,7 @@ from models.enums import ( FeedbackRating, MessageStatus, ) -from models.model import App, AppMode, Conversation, IconType, Message, MessageFeedback +from models.model import App, AppMode, Conversation, Message, MessageFeedback from models.workflow import ( WorkflowExecutionStatus, WorkflowNodeExecutionModel, @@ -30,25 +30,16 @@ from models.workflow import ( from services.agent import observability_service as observability_service_module from services.agent.observability_service import AgentLogQueryParams, AgentObservabilityService from tests.unit_tests.config_override import apply_config_overrides +from tests.unit_tests.model_factories import make_app, make_conversation, make_message def _app(*, app_id: str = "app-1", name: str = "Iris", mode: AppMode = AppMode.AGENT_CHAT) -> App: - return App( - id=app_id, - tenant_id="tenant-1", - name=name, - mode=mode, - icon_type=IconType.EMOJI, - icon="robot", - icon_background="#fff", - enable_site=False, - enable_api=False, - ) + return make_app(app_id=app_id, name=name, mode=mode, icon_background="#fff", enable_site=False, enable_api=False) def _conversation(*, conversation_id: str = "conversation-1", app_id: str = "app-1") -> Conversation: - return Conversation( - id=conversation_id, + return make_conversation( + conversation_id=conversation_id, app_id=app_id, mode=AppMode.AGENT_CHAT, name="Debug conversation", @@ -66,8 +57,8 @@ def _message( created_at: datetime | None = None, ) -> Message: timestamp = created_at or naive_utc_now() - return Message( - id=message_id, + return make_message( + message_id=message_id, app_id=app_id, conversation_id=conversation_id, inputs={}, diff --git a/api/tests/unit_tests/services/agent/test_agent_services.py b/api/tests/unit_tests/services/agent/test_agent_services.py index 7b1558e7999..ef219521c6b 100644 --- a/api/tests/unit_tests/services/agent/test_agent_services.py +++ b/api/tests/unit_tests/services/agent/test_agent_services.py @@ -37,11 +37,11 @@ from models.agent_config_entities import ( DeclaredOutputType, WorkflowNodeJobConfig, ) -from models.enums import AppStatus, ConversationFromSource, ConversationStatus +from models.enums import ConversationFromSource, ConversationStatus from models.model import App, AppMode, AppModelConfig, Conversation, IconType, Message from models.skill import AgentSkillBindingSnapshot, Skill, SkillVersion, SkillVersionManifest from models.tools import ToolFile -from models.workflow import Workflow, WorkflowType +from models.workflow import Workflow from services.agent import composer_service, roster_service from services.agent.agent_soul_state import agent_soul_has_model from services.agent.composer_service import AgentComposerService @@ -66,6 +66,7 @@ from services.entities.agent_entities import ( ComposerSaveStrategy, ComposerVariant, ) +from tests.unit_tests.model_factories import make_account, make_app, make_conversation, make_workflow def _agent_soul_with_model() -> AgentSoulConfig: @@ -124,14 +125,13 @@ def _snapshot( def _conversation(*, conversation_id: str = "conversation-1", account_id: str = "account-1") -> Conversation: - return Conversation( - id=conversation_id, - app_id="app-1", - override_model_configs="{}", + return make_conversation( + conversation_id=conversation_id, mode=AppMode.AGENT_CHAT, name="Debug", + inputs={}, + override_model_configs="{}", summary="", - _inputs={}, introduction="", system_instruction="", status=ConversationStatus.NORMAL, @@ -142,19 +142,7 @@ def _conversation(*, conversation_id: str = "conversation-1", account_id: str = def _workflow(*, workflow_id: str = "workflow-1", tenant_id: str = "tenant-1", app_id: str = "app-1") -> Workflow: - return Workflow( - id=workflow_id, - tenant_id=tenant_id, - app_id=app_id, - type=WorkflowType.WORKFLOW, - version=Workflow.VERSION_DRAFT, - graph='{"nodes": [], "edges": []}', - _features="{}", - created_by="account-1", - _environment_variables="{}", - _conversation_variables="{}", - _rag_pipeline_variables="{}", - ) + return make_workflow(workflow_id=workflow_id, tenant_id=tenant_id, app_id=app_id) def _app( @@ -164,27 +152,20 @@ def _app( name: str = "Agent App", mode: AppMode = AppMode.AGENT_CHAT, ) -> App: - return App( - id=app_id, + return make_app( + app_id=app_id, tenant_id=tenant_id, name=name, - description="", mode=mode, - icon_type=IconType.EMOJI, icon="🤖", icon_background="#fff", - status=AppStatus.NORMAL, enable_site=False, - enable_api=True, - max_active_requests=None, created_by="account-1", ) def _account(*, account_id: str = "account-1") -> Account: - account = Account(name="Agent Tester", email=f"{account_id}@example.com") - account.id = account_id - return account + return make_account(account_id=account_id, name="Agent Tester", email=f"{account_id}@example.com") def test_agent_soul_has_model(): diff --git a/api/tests/unit_tests/services/agent/test_workflow_publish_service.py b/api/tests/unit_tests/services/agent/test_workflow_publish_service.py index d50f3589b68..fa8eee4c361 100644 --- a/api/tests/unit_tests/services/agent/test_workflow_publish_service.py +++ b/api/tests/unit_tests/services/agent/test_workflow_publish_service.py @@ -17,24 +17,14 @@ from models.agent import ( from models.agent_config_entities import AgentSoulConfig from models.enums import AppStatus from models.model import App, AppMode -from models.workflow import Workflow, WorkflowType +from models.workflow import Workflow from services.agent.dsl_service import AgentDslService from services.agent.workflow_publish_service import WorkflowAgentPublishService +from tests.unit_tests.model_factories import make_workflow def _workflow(*, workflow_id: str = "workflow-1", version: str = Workflow.VERSION_DRAFT) -> Workflow: - return Workflow( - id=workflow_id, - tenant_id="tenant-1", - app_id="app-1", - type=WorkflowType.WORKFLOW, - version=version, - graph={"nodes": [], "edges": []}, - features={}, - created_by="account-1", - environment_variables=[], - conversation_variables=[], - ) + return make_workflow(workflow_id=workflow_id, version=version) def _inline_agent( diff --git a/api/tests/unit_tests/services/openapi/test_mint_policy.py b/api/tests/unit_tests/services/openapi/test_mint_policy.py index e3bf6cd9d0e..c3df657ed4c 100644 --- a/api/tests/unit_tests/services/openapi/test_mint_policy.py +++ b/api/tests/unit_tests/services/openapi/test_mint_policy.py @@ -10,7 +10,6 @@ from __future__ import annotations import pytest -from enums import DeploymentEdition from libs.oauth_bearer import MINTABLE_PROFILES, Scope, SubjectType from services.openapi.mint_policy import MintPolicyViolation, validate_mint_policy @@ -73,50 +72,3 @@ def test_message_carries_both_drift_reasons(): msg = str(exc.value) assert "prefix" in msg assert "scopes" in msg - - -def test_license_required_decorator_skips_on_ce(config_overrides): - from services.openapi.license_gate import license_required - - @license_required - def view(): - return "ok" - - config_overrides(DEPLOYMENT_EDITION=DeploymentEdition.COMMUNITY) - assert view() == "ok" - - -def test_license_required_decorator_403_on_invalid_ee_license(config_overrides): - from unittest.mock import patch - - from werkzeug.exceptions import Forbidden - - from services.openapi.license_gate import license_required - - @license_required - def view(): - return "ok" - - config_overrides(DEPLOYMENT_EDITION=DeploymentEdition.ENTERPRISE) - with ( - patch("services.openapi.license_gate._is_license_valid", return_value=False), - ): - with pytest.raises(Forbidden) as exc: - view() - assert "license_required" in exc.value.description - - -def test_license_required_decorator_passes_on_valid_ee_license(config_overrides): - from unittest.mock import patch - - from services.openapi.license_gate import license_required - - @license_required - def view(): - return "ok" - - config_overrides(DEPLOYMENT_EDITION=DeploymentEdition.ENTERPRISE) - with ( - patch("services.openapi.license_gate._is_license_valid", return_value=True), - ): - assert view() == "ok" diff --git a/api/tests/unit_tests/services/rag_pipeline/test_rag_pipeline_dsl_service.py b/api/tests/unit_tests/services/rag_pipeline/test_rag_pipeline_dsl_service.py index 44859fb7e2d..de56869d4a2 100644 --- a/api/tests/unit_tests/services/rag_pipeline/test_rag_pipeline_dsl_service.py +++ b/api/tests/unit_tests/services/rag_pipeline/test_rag_pipeline_dsl_service.py @@ -22,7 +22,7 @@ from sqlalchemy.orm import Session, sessionmaker from core.workflow.llm_environment_variable import LLMEnvironmentVariable from core.workflow.nodes.knowledge_index import KNOWLEDGE_INDEX_NODE_TYPE from graphon.enums import BuiltinNodeTypes -from models import Account, Tenant +from models import Account from models.dataset import ( Dataset, Pipeline, @@ -39,6 +39,7 @@ from services.rag_pipeline.rag_pipeline_dsl_service import ( RagPipelineDslService, RagPipelinePendingData, ) +from tests.unit_tests.model_factories import make_account, make_dataset, make_tenant, make_workflow @pytest.fixture @@ -47,12 +48,12 @@ def service(sqlite_session: Session) -> RagPipelineDslService: def _account(*, tenant_id: str = "tenant-1", account_id: str = "account-1") -> Account: - tenant = Tenant(name="Tenant") - tenant.id = tenant_id - account = Account(name="Account", email="account@example.com") - account.id = account_id - account._current_tenant = tenant - return account + return make_account( + account_id=account_id, + name="Account", + email="account@example.com", + tenant=make_tenant(tenant_id=tenant_id, name="Tenant"), + ) def _pipeline( @@ -76,19 +77,12 @@ def _pipeline( def _workflow(session: Session, pipeline: Pipeline, *, graph: dict[str, Any] | None = None) -> Workflow: - workflow = Workflow( - id=f"workflow-{pipeline.id}", + workflow = make_workflow( + workflow_id=f"workflow-{pipeline.id}", tenant_id=pipeline.tenant_id, app_id=pipeline.id, - type=WorkflowType.RAG_PIPELINE, - kind=WorkflowKind.STANDARD, - version=Workflow.VERSION_DRAFT, - graph=json.dumps(graph or {"nodes": [], "edges": []}), - features="{}", - created_by="account-1", - environment_variables=[], - conversation_variables=[], - rag_pipeline_variables=[], + workflow_type=WorkflowType.RAG_PIPELINE, + graph=graph, ) pipeline.workflow_id = workflow.id session.add(workflow) @@ -124,13 +118,13 @@ def _dataset( name: str = "Dataset", chunk_structure: str = "text_model", ) -> Dataset: - dataset = Dataset( + dataset = make_dataset( + dataset_id=None, tenant_id=pipeline.tenant_id, name=name, description="description", data_source_type=DataSourceType.UPLOAD_FILE, indexing_technique="high_quality", - created_by="account-1", maintainer="account-1", chunk_structure=chunk_structure, pipeline_id=pipeline.id, diff --git a/api/tests/unit_tests/services/rag_pipeline/test_rag_pipeline_transform_service.py b/api/tests/unit_tests/services/rag_pipeline/test_rag_pipeline_transform_service.py index be5728f396e..43cfb85a828 100644 --- a/api/tests/unit_tests/services/rag_pipeline/test_rag_pipeline_transform_service.py +++ b/api/tests/unit_tests/services/rag_pipeline/test_rag_pipeline_transform_service.py @@ -7,14 +7,14 @@ from pytest_mock import MockerFixture from sqlalchemy import select from sqlalchemy.orm import Session -from extensions.storage.storage_type import StorageType from models.dataset import Dataset, Document, DocumentPipelineExecutionLog, Pipeline -from models.enums import CreatorUserRole, DataSourceType, DocumentCreatedFrom +from models.enums import DataSourceType, DocumentCreatedFrom from models.model import UploadFile from services.entities.knowledge_entities.rag_pipeline_entities import KnowledgeConfiguration from services.errors.rag_pipeline import RagPipelineResourceNotFoundError from services.rag_pipeline.rag_pipeline_transform_service import RagPipelineTransformService from tests.unit_tests.config_override import apply_config_overrides +from tests.unit_tests.model_factories import make_upload_file def _dataset(**overrides: object) -> Dataset: @@ -60,21 +60,14 @@ def _pipeline(*, pipeline_id: str = "p-new", tenant_id: str = "t1") -> Pipeline: def _upload_file(*, file_id: str = "file-1", tenant_id: str = "tenant-1") -> UploadFile: - upload_file = UploadFile( + return make_upload_file( + file_id=file_id, tenant_id=tenant_id, - storage_type=StorageType.LOCAL, key="files/f.txt", name="f.txt", - size=10, - extension="txt", - mime_type="text/plain", - created_by_role=CreatorUserRole.ACCOUNT, created_by="user-1", created_at=datetime.now(UTC).replace(tzinfo=None), - used=False, ) - upload_file.id = file_id - return upload_file @pytest.mark.parametrize( diff --git a/api/tests/unit_tests/services/test_account_change_email_service.py b/api/tests/unit_tests/services/test_account_change_email_service.py index 3fb3e8d6837..1556657038e 100644 --- a/api/tests/unit_tests/services/test_account_change_email_service.py +++ b/api/tests/unit_tests/services/test_account_change_email_service.py @@ -33,6 +33,7 @@ from services.entities.account_entities import ( AccountEmailResetStatus, AccountSnapshot, ) +from tests.unit_tests.model_factories import make_account_snapshot def _context() -> RequestContext: @@ -45,21 +46,7 @@ def _context() -> RequestContext: def _account(*, email: str = "old@example.com") -> AccountSnapshot: - return AccountSnapshot( - id="account-1", - name="Account", - email=email, - avatar=None, - is_password_set=True, - interface_language="en-US", - interface_theme="light", - timezone="UTC", - last_login_at=None, - last_login_ip=None, - status="active", - initialized_at=datetime(2026, 1, 1), - created_at=datetime(2026, 1, 1), - ) + return make_account_snapshot(email=email, is_password_set=True, initialized_at=datetime(2026, 1, 1)) def _service() -> tuple[AccountChangeEmailService, dict[str, Mock]]: diff --git a/api/tests/unit_tests/services/test_account_deletion_service.py b/api/tests/unit_tests/services/test_account_deletion_service.py index 0ba68d9d5f8..7d0f0e05ca6 100644 --- a/api/tests/unit_tests/services/test_account_deletion_service.py +++ b/api/tests/unit_tests/services/test_account_deletion_service.py @@ -17,6 +17,7 @@ from services.account_ports import ( AccountWorkspaceMembershipQuery, ) from services.entities.account_entities import AccountDeletionChallenge, AccountSnapshot +from tests.unit_tests.model_factories import make_account_snapshot def _context() -> RequestContext: @@ -29,21 +30,7 @@ def _context() -> RequestContext: def _account() -> AccountSnapshot: - return AccountSnapshot( - id="account-1", - name="Account", - email="account@example.com", - avatar=None, - is_password_set=True, - interface_language="en-US", - interface_theme="light", - timezone="UTC", - last_login_at=None, - last_login_ip=None, - status="active", - initialized_at=datetime(2026, 1, 1), - created_at=datetime(2026, 1, 1), - ) + return make_account_snapshot(is_password_set=True, initialized_at=datetime(2026, 1, 1)) def _service(*, accounts: Mock | None = None) -> tuple[AccountDeletionService, dict[str, Mock]]: diff --git a/api/tests/unit_tests/services/test_account_education_service.py b/api/tests/unit_tests/services/test_account_education_service.py index 5f413eeee62..96194f44e31 100644 --- a/api/tests/unit_tests/services/test_account_education_service.py +++ b/api/tests/unit_tests/services/test_account_education_service.py @@ -20,6 +20,7 @@ from services.entities.account_entities import ( AccountEducationVerification, AccountSnapshot, ) +from tests.unit_tests.model_factories import make_account_snapshot def _context() -> RequestContext: @@ -32,20 +33,11 @@ def _context() -> RequestContext: def _account() -> AccountSnapshot: - return AccountSnapshot( - id="account-1", + return make_account_snapshot( name="Student", email="student@example.edu", - avatar=None, is_password_set=True, - interface_language="en-US", - interface_theme="light", - timezone="UTC", - last_login_at=None, - last_login_ip=None, - status="active", initialized_at=datetime(2026, 1, 1), - created_at=datetime(2026, 1, 1), ) diff --git a/api/tests/unit_tests/services/test_account_email_registration_service.py b/api/tests/unit_tests/services/test_account_email_registration_service.py index b3091f51438..513b727e581 100644 --- a/api/tests/unit_tests/services/test_account_email_registration_service.py +++ b/api/tests/unit_tests/services/test_account_email_registration_service.py @@ -29,23 +29,15 @@ from services.entities.account_entities import ( AccountSessionTokens, AccountSnapshot, ) +from tests.unit_tests.model_factories import make_account_snapshot def _account(*, email: str = "stored@example.com") -> AccountSnapshot: - return AccountSnapshot( - id="account-1", + return make_account_snapshot( name="Stored Account", email=email, - avatar=None, is_password_set=True, - interface_language="en-US", - interface_theme="light", - timezone="UTC", - last_login_at=None, - last_login_ip=None, - status="active", initialized_at=datetime(2026, 1, 1), - created_at=datetime(2026, 1, 1), ) diff --git a/api/tests/unit_tests/services/test_account_initialization_service.py b/api/tests/unit_tests/services/test_account_initialization_service.py index e929e1ff11d..212fc61d149 100644 --- a/api/tests/unit_tests/services/test_account_initialization_service.py +++ b/api/tests/unit_tests/services/test_account_initialization_service.py @@ -19,6 +19,7 @@ from services.entities.account_entities import ( AccountInitializationStatus, AccountSnapshot, ) +from tests.unit_tests.model_factories import make_account_snapshot def _context() -> RequestContext: @@ -31,21 +32,7 @@ def _context() -> RequestContext: def _account(*, status: str = "uninitialized") -> AccountSnapshot: - return AccountSnapshot( - id="account-1", - name="Account", - email="account@example.com", - avatar=None, - is_password_set=False, - interface_language="en-US", - interface_theme="light", - timezone="UTC", - last_login_at=None, - last_login_ip=None, - status=status, - initialized_at=None, - created_at=datetime(2026, 1, 1), - ) + return make_account_snapshot(status=status) def test_cloud_initialization_consumes_invitation_and_updates_account_atomically() -> None: diff --git a/api/tests/unit_tests/services/test_account_password_service.py b/api/tests/unit_tests/services/test_account_password_service.py index d4b9f7c2050..19ccad4125e 100644 --- a/api/tests/unit_tests/services/test_account_password_service.py +++ b/api/tests/unit_tests/services/test_account_password_service.py @@ -1,6 +1,5 @@ from __future__ import annotations -from datetime import datetime from unittest.mock import Mock import pytest @@ -10,6 +9,7 @@ from services.account_errors import AccountNotFoundError, CurrentAccountPassword from services.account_password_service import AccountPasswordService from services.account_ports import AccountPasswordHasher, AccountRepository from services.entities.account_entities import AccountCredentials, AccountPasswordDigest, AccountSnapshot +from tests.unit_tests.model_factories import make_account_snapshot def _context() -> RequestContext: @@ -22,21 +22,7 @@ def _context() -> RequestContext: def _account() -> AccountSnapshot: - return AccountSnapshot( - id="account-1", - name="Account", - email="account@example.com", - avatar=None, - is_password_set=True, - interface_language="en-US", - interface_theme="light", - timezone="UTC", - last_login_at=None, - last_login_ip=None, - status="active", - initialized_at=None, - created_at=datetime(2026, 1, 1), - ) + return make_account_snapshot(is_password_set=True) def test_change_verifies_current_password_and_updates_digest() -> None: diff --git a/api/tests/unit_tests/services/test_account_profile_service.py b/api/tests/unit_tests/services/test_account_profile_service.py index 018cd45fc59..76837a88a3c 100644 --- a/api/tests/unit_tests/services/test_account_profile_service.py +++ b/api/tests/unit_tests/services/test_account_profile_service.py @@ -1,6 +1,5 @@ from __future__ import annotations -from datetime import datetime from unittest.mock import Mock import pytest @@ -10,6 +9,7 @@ from services.account_errors import AccountNotFoundError from services.account_ports import AccountRepository from services.account_profile_service import AccountProfileService from services.entities.account_entities import AccountProfileChanges, AccountSnapshot +from tests.unit_tests.model_factories import make_account_snapshot def _context() -> RequestContext: @@ -22,21 +22,7 @@ def _context() -> RequestContext: def _account() -> AccountSnapshot: - return AccountSnapshot( - id="account-1", - name="Account", - email="account@example.com", - avatar=None, - is_password_set=False, - interface_language="en-US", - interface_theme="light", - timezone="UTC", - last_login_at=None, - last_login_ip=None, - status="active", - initialized_at=None, - created_at=datetime(2026, 1, 1), - ) + return make_account_snapshot() def test_get_returns_framework_neutral_account_snapshot() -> None: diff --git a/api/tests/unit_tests/services/test_account_service.py b/api/tests/unit_tests/services/test_account_service.py index 9bd00353a90..83e83929725 100644 --- a/api/tests/unit_tests/services/test_account_service.py +++ b/api/tests/unit_tests/services/test_account_service.py @@ -37,6 +37,7 @@ from services.errors.account import ( NoPermissionError, ) from tests.unit_tests.config_override import config_overrides_context +from tests.unit_tests.model_factories import make_tenant type _MockDependencies = dict[str, MagicMock] @@ -75,7 +76,7 @@ class TestAccountAssociatedDataFactory: def _tenant(session: Session | None = None) -> Tenant: - tenant = Tenant(name="Test Workspace") + tenant = make_tenant(tenant_id=None, name="Test Workspace") if session is not None: session.add(tenant) return tenant diff --git a/api/tests/unit_tests/services/test_app_dsl_service.py b/api/tests/unit_tests/services/test_app_dsl_service.py index b7d84895d04..e4c1554f62c 100644 --- a/api/tests/unit_tests/services/test_app_dsl_service.py +++ b/api/tests/unit_tests/services/test_app_dsl_service.py @@ -1,4 +1,3 @@ -import json from collections.abc import Callable from types import SimpleNamespace from typing import cast @@ -12,9 +11,9 @@ from sqlalchemy.orm import Session, sessionmaker from constants.dsl_version import CURRENT_APP_DSL_VERSION from core.rbac import RBACPermission, RBACResourceScope from core.workflow.llm_environment_variable import LLMEnvironmentVariable -from models import Account, App, AppMode, Tenant +from models import Account, App, AppMode from models.model import AppModelConfig, AppModelConfigDict, IconType -from models.workflow import Workflow, WorkflowType +from models.workflow import Workflow from services.agent.dsl_entities import AgentPackage from services.app_dsl_service import AppDslService, Import, PendingData from services.enterprise.enterprise_service import EnterpriseService @@ -23,6 +22,7 @@ from services.errors.account import NoPermissionError from services.errors.app import WorkflowNotFoundError from services.system_feature_service import SystemFeatureService from tests.unit_tests.config_override import apply_config_overrides +from tests.unit_tests.model_factories import make_account, make_app, make_tenant, make_workflow _OVERWRITE_APP_ID = "11111111-1111-4111-8111-111111111111" _TENANT_ID = "22222222-2222-4222-8222-222222222222" @@ -60,12 +60,12 @@ def _persist_overwrite_target(session: Session, *, maintainer: str = _OTHER_ACCO def _account(*, account_id: str = "account-1", tenant_id: str = "tenant-1") -> Account: - account = Account(name="DSL author", email=f"{account_id}@example.com") - account.id = account_id - tenant = Tenant(name="DSL workspace") - tenant.id = tenant_id - account._current_tenant = tenant - return account + return make_account( + account_id=account_id, + name="DSL author", + email=f"{account_id}@example.com", + tenant=make_tenant(tenant_id=tenant_id, name="DSL workspace"), + ) def _app( @@ -75,38 +75,20 @@ def _app( mode: AppMode = AppMode.CHAT, app_model_config_id: str | None = None, ) -> App: - return App( - id=app_id, + return make_app( + app_id=app_id, tenant_id=tenant_id, app_model_config_id=app_model_config_id, name="Existing app", - description="", mode=mode, - icon_type=IconType.EMOJI, - icon="robot", - icon_background="#FFFFFF", - enable_site=True, - enable_api=True, max_active_requests=0, - use_icon_as_answer_icon=False, ) def _workflow( *, graph: dict[str, object], environment_variables: list[LLMEnvironmentVariable] | None = None ) -> Workflow: - workflow = Workflow( - id="workflow-1", - tenant_id="tenant-1", - app_id="app-1", - type=WorkflowType.WORKFLOW, - version="draft", - graph=json.dumps(graph), - _features="{}", - created_by="account-1", - ) - workflow.environment_variables = environment_variables or [] - return workflow + return make_workflow(workflow_id="workflow-1", graph=graph, environment_variables=environment_variables or []) @pytest.mark.parametrize("status", [ImportStatus.FAILED, ImportStatus.PENDING]) diff --git a/api/tests/unit_tests/services/test_audio_service.py b/api/tests/unit_tests/services/test_audio_service.py index 82568916445..88c4fe20964 100644 --- a/api/tests/unit_tests/services/test_audio_service.py +++ b/api/tests/unit_tests/services/test_audio_service.py @@ -81,6 +81,7 @@ from services.errors.audio import ( SpeechToTextDisabledServiceError, UnsupportedAudioTypeServiceError, ) +from tests.unit_tests.model_factories import make_message APP_ID = "11111111-1111-1111-1111-111111111111" TENANT_ID = "22222222-2222-2222-2222-222222222222" @@ -92,10 +93,11 @@ OTHER_ID = "77777777-7777-7777-7777-777777777777" def _message(*, answer: str = "Message answer") -> Message: - message = Message( - id=MESSAGE_ID, + return make_message( + message_id=MESSAGE_ID, app_id=APP_ID, conversation_id=CONVERSATION_ID, + inputs={}, query="Question", message={"role": "user", "content": "Question"}, answer=answer, @@ -107,8 +109,6 @@ def _message(*, answer: str = "Message answer") -> Message: from_end_user_id=END_USER_ID, from_account_id=ACCOUNT_ID, ) - message._inputs = {} - return message class AudioServiceTestDataFactory: diff --git a/api/tests/unit_tests/services/test_billing_portal_service.py b/api/tests/unit_tests/services/test_billing_portal_service.py index 676d0686629..9af98ed8368 100644 --- a/api/tests/unit_tests/services/test_billing_portal_service.py +++ b/api/tests/unit_tests/services/test_billing_portal_service.py @@ -1,4 +1,3 @@ -from datetime import datetime from unittest.mock import MagicMock, Mock import pytest @@ -8,6 +7,7 @@ from services.account_errors import AccountNotFoundError from services.account_ports import AccountRepository from services.billing_portal_service import BillingPortalService from services.entities.account_entities import AccountSnapshot +from tests.unit_tests.model_factories import make_account_snapshot def _context() -> RequestContext: @@ -20,21 +20,7 @@ def _context() -> RequestContext: def _account() -> AccountSnapshot: - return AccountSnapshot( - id="account-1", - name="Account", - email="owner@example.com", - avatar=None, - is_password_set=False, - interface_language="en-US", - interface_theme="light", - timezone="UTC", - last_login_at=None, - last_login_ip=None, - status="active", - initialized_at=None, - created_at=datetime(2026, 1, 1), - ) + return make_account_snapshot(email="owner@example.com") @pytest.fixture diff --git a/api/tests/unit_tests/services/test_billing_service.py b/api/tests/unit_tests/services/test_billing_service.py index 0e10220635b..f2acba94932 100644 --- a/api/tests/unit_tests/services/test_billing_service.py +++ b/api/tests/unit_tests/services/test_billing_service.py @@ -22,7 +22,6 @@ import pytest from werkzeug.exceptions import InternalServerError from enums import CloudPlan -from models import Account, Tenant from services.billing_service import BillingService, _BillingHTTPStatusError from services.errors.billing import ( BillingUpstreamInvalidResponseError, @@ -33,15 +32,6 @@ TENANT_ID = "11111111-1111-1111-1111-111111111111" ACCOUNT_ID = "33333333-3333-3333-3333-333333333333" -def _account(*, account_id: str = ACCOUNT_ID, email: str = "user@example.com", tenant_id: str = TENANT_ID) -> Account: - tenant = Tenant(name="Test Tenant") - tenant.id = tenant_id - account = Account(name="Test User", email=email) - account.id = account_id - account._current_tenant = tenant - return account - - class TestBillingServiceSendRequest: """Unit tests for BillingService._send_request method. diff --git a/api/tests/unit_tests/services/test_dataset_service_dataset.py b/api/tests/unit_tests/services/test_dataset_service_dataset.py index f20946f4e91..18a833afb55 100644 --- a/api/tests/unit_tests/services/test_dataset_service_dataset.py +++ b/api/tests/unit_tests/services/test_dataset_service_dataset.py @@ -19,7 +19,6 @@ from core.errors.error import LLMBadRequestError, ProviderTokenNotInitError from core.rag.index_processor.constant.index_type import IndexTechniqueType from graphon.model_runtime.entities.model_entities import ModelFeature, ModelType from models import Account -from models.account import Tenant from models.dataset import ( Dataset, DatasetCollectionBinding, @@ -36,6 +35,7 @@ from services.entities.knowledge_entities.rag_pipeline_entities import ( ) from services.errors.account import NoPermissionError from services.errors.dataset import DatasetNameDuplicateError +from tests.unit_tests.model_factories import make_account, make_dataset, make_tenant from .dataset_service_test_helpers import ( MagicMock, @@ -51,13 +51,13 @@ def _account( tenant_id: str = "tenant-1", role: TenantAccountRole = TenantAccountRole.OWNER, ) -> Account: - account = Account(name=f"User {account_id}", email=f"{account_id}@example.com") - account.id = account_id - account.role = role - tenant = Tenant(name=f"Tenant {tenant_id}") - tenant.id = tenant_id - account._current_tenant = tenant - return account + return make_account( + account_id=account_id, + name=f"User {account_id}", + email=f"{account_id}@example.com", + role=role, + tenant=make_tenant(tenant_id=tenant_id, name=f"Tenant {tenant_id}"), + ) def _dataset( @@ -71,8 +71,8 @@ def _dataset( indexing_technique: str = IndexTechniqueType.ECONOMY, chunk_structure: str | None = "text_model", ) -> Dataset: - return Dataset( - id=dataset_id, + return make_dataset( + dataset_id=dataset_id, tenant_id=tenant_id, name=name, description="", diff --git a/api/tests/unit_tests/services/test_dataset_service_document.py b/api/tests/unit_tests/services/test_dataset_service_document.py index c70468e5f65..a450560d231 100644 --- a/api/tests/unit_tests/services/test_dataset_service_document.py +++ b/api/tests/unit_tests/services/test_dataset_service_document.py @@ -7,13 +7,13 @@ from sqlalchemy import event, select from sqlalchemy.orm import Session from enums import DeploymentEdition -from models.account import Tenant from models.dataset import Dataset, DatasetCollectionBinding, DocumentSegment from models.enums import DataSourceType, DocumentCreatedFrom, IndexingStatus from models.model import UploadFile from models.source import DataSourceOauthBinding from services.dataset_ref_service import DatasetRefService from tests.unit_tests.config_override import config_overrides_context +from tests.unit_tests.model_factories import make_account, make_tenant, make_upload_file from .dataset_service_test_helpers import ( Account, @@ -53,12 +53,12 @@ from .dataset_service_test_helpers import ( def _account(*, account_id: str = "user-1", tenant_id: str = "tenant-1") -> Account: - account = Account(name="User", email=f"{account_id}@example.com") - account.id = account_id - tenant = Tenant(name="Tenant") - tenant.id = tenant_id - account._current_tenant = tenant - return account + return make_account( + account_id=account_id, + name="User", + email=f"{account_id}@example.com", + tenant=make_tenant(tenant_id=tenant_id, name="Tenant"), + ) def _dataset_row( @@ -121,21 +121,16 @@ def _document_row( def _upload_file(*, file_id: str, tenant_id: str = "tenant-1", name: str = "upload.txt") -> UploadFile: - upload_file = UploadFile( + return make_upload_file( + file_id=file_id, tenant_id=tenant_id, storage_type="opendal", key=f"key-{file_id}", name=name, size=1, - extension="txt", - mime_type="text/plain", - created_by_role="account", created_by="user-1", created_at=datetime(2026, 1, 1), - used=False, ) - upload_file.id = file_id - return upload_file def _process_rule( diff --git a/api/tests/unit_tests/services/test_file_service.py b/api/tests/unit_tests/services/test_file_service.py index a807bc6aa41..22106b85cc1 100644 --- a/api/tests/unit_tests/services/test_file_service.py +++ b/api/tests/unit_tests/services/test_file_service.py @@ -22,12 +22,11 @@ from services.errors.file import ( UnsupportedFileTypeError, ) from services.file_service import FileService +from tests.unit_tests.model_factories import make_account def _account() -> Account: - account = Account(name="Test Account", email="test@example.com") - account.id = "user_id" - return account + return make_account(account_id="user_id", name="Test Account", email="test@example.com") class TestFileService: diff --git a/api/tests/unit_tests/services/test_knowledge_fs_proxy.py b/api/tests/unit_tests/services/test_knowledge_fs_proxy.py index 51cd56b7bfd..1f7f2d6a2a0 100644 --- a/api/tests/unit_tests/services/test_knowledge_fs_proxy.py +++ b/api/tests/unit_tests/services/test_knowledge_fs_proxy.py @@ -31,15 +31,13 @@ from services.knowledge_fs_proxy import ( from services.knowledge_fs_proxy import ( _forward_knowledge_fs_request as forward_knowledge_fs_request, ) +from tests.unit_tests.model_factories import make_account _JWT_SECRET = "production-secret-with-at-least-32-bytes" def _account(*, role: TenantAccountRole = TenantAccountRole.DATASET_OPERATOR) -> Account: - account = Account(name="Knowledge User", email="knowledge@example.com") - account.id = "account-1" - account.role = role - return account + return make_account(name="Knowledge User", email="knowledge@example.com", role=role) def _account_for_legacy_role(role: str) -> Account: diff --git a/api/tests/unit_tests/services/test_knowledge_retrieval_inner_service.py b/api/tests/unit_tests/services/test_knowledge_retrieval_inner_service.py index 4a5511396d7..66a289d24fa 100644 --- a/api/tests/unit_tests/services/test_knowledge_retrieval_inner_service.py +++ b/api/tests/unit_tests/services/test_knowledge_retrieval_inner_service.py @@ -7,7 +7,6 @@ from sqlalchemy.orm import Session from core.workflow.nodes.knowledge_retrieval.retrieval import Source, SourceMetadata from models.dataset import Dataset -from models.enums import AppStatus from models.model import App, AppMode from services.entities.knowledge_retrieval_inner import InnerKnowledgeRetrieveRequest from services.errors.knowledge_retrieval import ( @@ -17,6 +16,7 @@ from services.errors.knowledge_retrieval import ( InnerKnowledgeRetrieveDatasetTenantMismatchError, ) from services.knowledge_retrieval_inner_service import InnerKnowledgeRetrievalService +from tests.unit_tests.model_factories import make_app, make_dataset TENANT_ID = "11111111-1111-1111-1111-111111111111" OTHER_TENANT_ID = "22222222-2222-2222-2222-222222222222" @@ -27,22 +27,19 @@ DATASET_2_ID = "66666666-6666-6666-6666-666666666666" def _app(*, tenant_id: str = TENANT_ID) -> App: - return App( - id=APP_ID, + return make_app( + app_id=APP_ID, tenant_id=tenant_id, - name="Test App", - description="", mode=AppMode.WORKFLOW, - status=AppStatus.NORMAL, + icon_type=None, enable_site=False, enable_api=False, - max_active_requests=None, ) def _dataset(*, dataset_id: str, tenant_id: str = TENANT_ID, enable_api: bool = True) -> Dataset: - return Dataset( - id=dataset_id, + return make_dataset( + dataset_id=dataset_id, tenant_id=tenant_id, name=f"Dataset {dataset_id[-1]}", description="", diff --git a/api/tests/unit_tests/services/test_metadata_service_session_boundary.py b/api/tests/unit_tests/services/test_metadata_service_session_boundary.py index 3414a1b0d10..eea773da175 100644 --- a/api/tests/unit_tests/services/test_metadata_service_session_boundary.py +++ b/api/tests/unit_tests/services/test_metadata_service_session_boundary.py @@ -8,7 +8,7 @@ from sqlalchemy.orm import Session, sessionmaker from core.rag.index_processor.constant.built_in_field import BuiltInField from models import Account from models.dataset import Dataset, DatasetMetadata, DatasetMetadataBinding, Document -from models.enums import DataSourceType, DocumentCreatedFrom +from models.enums import DocumentCreatedFrom from services.dataset_service import DocumentService from services.entities.knowledge_entities.knowledge_entities import ( DocumentMetadataOperation, @@ -18,6 +18,7 @@ from services.entities.knowledge_entities.knowledge_entities import ( ) from services.errors.metadata import MetadataResourceNotFoundError from services.metadata_service import MetadataService +from tests.unit_tests.model_factories import make_account, make_dataset, make_document DOCUMENT_ID = "11111111-1111-1111-1111-111111111111" FOREIGN_DOCUMENT_ID = "22222222-2222-2222-2222-222222222222" @@ -26,9 +27,7 @@ FOREIGN_METADATA_ID = "bbbbbbbb-bbbb-bbbb-bbbb-bbbbbbbbbbbb" def _account() -> Account: - account = Account(name="User", email="user@example.com") - account.id = "account-1" - return account + return make_account(name="User", email="user@example.com") def test_create_metadata_flushes_without_committing_caller_session(sqlite_session: Session) -> None: @@ -50,28 +49,17 @@ def test_create_metadata_flushes_without_committing_caller_session(sqlite_sessio def _dataset(*, built_in_field_enabled: bool) -> Dataset: - return Dataset( - id="dataset-1", - tenant_id="tenant-1", - name="Dataset", + return make_dataset( description="", provider="vendor", - created_by="account-1", built_in_field_enabled=built_in_field_enabled, ) def _document() -> Document: - return Document( - id=DOCUMENT_ID, - tenant_id="tenant-1", - dataset_id="dataset-1", - position=1, - data_source_type=DataSourceType.UPLOAD_FILE, - batch="batch-1", - name="Document", + return make_document( + document_id=DOCUMENT_ID, created_from=DocumentCreatedFrom.API, - created_by="account-1", created_at=datetime(2026, 1, 1), updated_at=datetime(2026, 1, 2), doc_metadata={}, diff --git a/api/tests/unit_tests/services/test_schedule_service.py b/api/tests/unit_tests/services/test_schedule_service.py index 4f5df2f8c8a..40f1801c561 100644 --- a/api/tests/unit_tests/services/test_schedule_service.py +++ b/api/tests/unit_tests/services/test_schedule_service.py @@ -10,8 +10,9 @@ from core.trigger.constants import TRIGGER_SCHEDULE_NODE_TYPE from core.workflow.nodes.trigger_schedule.entities import VisualConfig from core.workflow.nodes.trigger_schedule.exc import ScheduleConfigError from libs.schedule_utils import calculate_next_run_at, convert_12h_to_24h -from models.workflow import Workflow, WorkflowType +from models.workflow import Workflow from services.trigger.schedule_service import ScheduleService +from tests.unit_tests.model_factories import make_workflow class TestScheduleService(unittest.TestCase): @@ -507,18 +508,7 @@ class TestScheduleWithTimezone(unittest.TestCase): def _workflow(*, graph_dict: dict[str, Any]) -> Workflow: - return Workflow.new( - tenant_id="tenant-1", - app_id="app-1", - type=WorkflowType.WORKFLOW, - version="draft", - graph=json.dumps(graph_dict), - features="{}", - created_by="account-1", - environment_variables=[], - conversation_variables=[], - rag_pipeline_variables=[], - ) + return make_workflow(graph=graph_dict) def test_to_schedule_config_should_build_from_cron_mode() -> None: diff --git a/api/tests/unit_tests/services/test_snippet_dsl_service.py b/api/tests/unit_tests/services/test_snippet_dsl_service.py index 900f321c43b..bd06fe98613 100644 --- a/api/tests/unit_tests/services/test_snippet_dsl_service.py +++ b/api/tests/unit_tests/services/test_snippet_dsl_service.py @@ -8,9 +8,9 @@ from sqlalchemy import event from sqlalchemy.orm import Session from graphon.nodes import BuiltinNodeTypes -from models import Account, Tenant +from models import Account from models.snippet import CustomizedSnippet, SnippetType -from models.workflow import Workflow, WorkflowType +from models.workflow import Workflow from services.snippet_dsl_service import ( ImportMode, ImportStatus, @@ -18,6 +18,7 @@ from services.snippet_dsl_service import ( SnippetPendingData, _check_version_compatibility, ) +from tests.unit_tests.model_factories import make_account, make_tenant, make_workflow SQLITE_MODELS = (CustomizedSnippet,) pytestmark = [ @@ -33,12 +34,12 @@ def service(sqlite_session: Session) -> SnippetDslService: def _account(*, account_id: str = "account-1", tenant_id: str = "tenant-1") -> Account: - account = Account(name="Snippet author", email=f"{account_id}@example.com") - account.id = account_id - tenant = Tenant(name="Snippet workspace") - tenant.id = tenant_id - account._current_tenant = tenant - return account + return make_account( + account_id=account_id, + name="Snippet author", + email=f"{account_id}@example.com", + tenant=make_tenant(tenant_id=tenant_id, name="Snippet workspace"), + ) def _snippet( @@ -64,16 +65,7 @@ def _snippet( def _workflow(*, graph: dict | None = None) -> Workflow: - return Workflow( - id="workflow-1", - tenant_id="tenant-1", - app_id="snippet-1", - type=WorkflowType.WORKFLOW, - version="draft", - graph=json.dumps(graph or {"nodes": [], "edges": []}), - _features="{}", - created_by="account-1", - ) + return make_workflow(workflow_id="workflow-1", app_id="snippet-1", graph=graph) @pytest.mark.parametrize( diff --git a/api/tests/unit_tests/services/test_snippet_generate_service.py b/api/tests/unit_tests/services/test_snippet_generate_service.py index d1b3f428c7b..cc2a81ecdc4 100644 --- a/api/tests/unit_tests/services/test_snippet_generate_service.py +++ b/api/tests/unit_tests/services/test_snippet_generate_service.py @@ -8,25 +8,13 @@ from sqlalchemy.orm import Session, sessionmaker from core.workflow.snippet_start import SNIPPET_VIRTUAL_START_NODE_ID from models.account import Account from models.snippet import CustomizedSnippet, SnippetType -from models.workflow import Workflow, WorkflowKind, WorkflowNodeExecutionModel, WorkflowType +from models.workflow import Workflow, WorkflowKind, WorkflowNodeExecutionModel from services.snippet_generate_service import SnippetGenerateService +from tests.unit_tests.model_factories import make_account, make_workflow def _workflow(graph: dict) -> Workflow: - return Workflow( - id="workflow-1", - tenant_id="tenant-1", - app_id="snippet-1", - type=WorkflowType.WORKFLOW, - kind=WorkflowKind.SNIPPET, - version=Workflow.VERSION_DRAFT, - graph=json.dumps(graph), - features="{}", - created_by="account-1", - environment_variables=[], - conversation_variables=[], - rag_pipeline_variables=[], - ) + return make_workflow(workflow_id="workflow-1", app_id="snippet-1", kind=WorkflowKind.SNIPPET, graph=graph) def _snippet(*, input_fields: list[dict] | None = None) -> CustomizedSnippet: @@ -42,9 +30,7 @@ def _snippet(*, input_fields: list[dict] | None = None) -> CustomizedSnippet: def _account(account_id: str = "user-1") -> Account: - account = Account(name="Test User", email=f"{account_id}@example.com") - account.id = account_id - return account + return make_account(account_id=account_id, name="Test User", email=f"{account_id}@example.com") def test_filter_virtual_start_events_keeps_blocking_response_unchanged(): diff --git a/api/tests/unit_tests/services/test_snippet_service.py b/api/tests/unit_tests/services/test_snippet_service.py index ea2d83c810a..843c1857a81 100644 --- a/api/tests/unit_tests/services/test_snippet_service.py +++ b/api/tests/unit_tests/services/test_snippet_service.py @@ -37,6 +37,7 @@ from models.workflow import ( from services.errors.app import IsDraftWorkflowError, WorkflowHashNotEqualError, WorkflowNotFoundError from services.errors.workflow_service import DraftWorkflowDeletionError, WorkflowInUseError from services.snippet_service import SnippetService +from tests.unit_tests.model_factories import make_account def _create_workflow(*, workflow_id: str, version: str, graph: dict, features: dict) -> Workflow: @@ -70,9 +71,7 @@ def _snippet(**overrides) -> CustomizedSnippet: def _account(account_id: str = "account-1") -> Account: - account = Account(name="Test User", email=f"{account_id}@example.com") - account.id = account_id - return account + return make_account(account_id=account_id, name="Test User", email=f"{account_id}@example.com") def test_create_snippet_allows_duplicate_names( diff --git a/api/tests/unit_tests/services/test_step_by_step_tour_service.py b/api/tests/unit_tests/services/test_step_by_step_tour_service.py index 1290987ffce..25413aa3449 100644 --- a/api/tests/unit_tests/services/test_step_by_step_tour_service.py +++ b/api/tests/unit_tests/services/test_step_by_step_tour_service.py @@ -12,6 +12,7 @@ from services.account_ports import AccountRepository from services.entities.account_entities import AccountSnapshot from services.entities.onboarding_entities import StepByStepTourPatch, StepByStepTourResult, StepByStepTourState from services.step_by_step_tour_service import StepByStepTourService +from tests.unit_tests.model_factories import make_account_snapshot def _context(*, workspace_id: str = "workspace-1") -> RequestContext: @@ -55,21 +56,7 @@ class StateRepositoryStub: def _account(*, started_at: datetime = datetime(2026, 6, 28)) -> AccountSnapshot: - return AccountSnapshot( - id="account-1", - name="Account", - email="account@example.com", - avatar=None, - is_password_set=False, - interface_language="en-US", - interface_theme="light", - timezone="UTC", - last_login_at=None, - last_login_ip=None, - status="active", - initialized_at=started_at, - created_at=started_at, - ) + return make_account_snapshot(initialized_at=started_at, created_at=started_at) def _accounts(account: AccountSnapshot | None) -> Mock: diff --git a/api/tests/unit_tests/services/test_vector_service.py b/api/tests/unit_tests/services/test_vector_service.py index 50b0d2c58c4..a8914f41f1c 100644 --- a/api/tests/unit_tests/services/test_vector_service.py +++ b/api/tests/unit_tests/services/test_vector_service.py @@ -14,12 +14,12 @@ from sqlalchemy.orm import Session import services.vector_service as vector_service_module from core.rag.index_processor.constant.index_type import IndexStructureType, IndexTechniqueType -from extensions.storage.storage_type import StorageType from models import UploadFile from models.dataset import ChildChunk, Dataset, DatasetProcessRule, DocumentSegment, SegmentAttachmentBinding from models.dataset import Document as DatasetDocument -from models.enums import CreatorUserRole, DataSourceType, DocumentCreatedFrom, ProcessRuleMode +from models.enums import DataSourceType, DocumentCreatedFrom, ProcessRuleMode from services.vector_service import VectorService +from tests.unit_tests.model_factories import make_upload_file @dataclass(frozen=True) @@ -107,21 +107,15 @@ def _make_segment( def _upload_file(*, file_id: str = "file-1", name: str = "img.png", tenant_id: str = "tenant-1") -> UploadFile: - upload_file = UploadFile( + return make_upload_file( + file_id=file_id, tenant_id=tenant_id, - storage_type=StorageType.LOCAL, key=f"uploads/{file_id}", name=name, - size=10, extension="png", mime_type="image/png", - created_by_role=CreatorUserRole.ACCOUNT, - created_by="account-1", created_at=datetime(2026, 1, 1), - used=False, ) - upload_file.id = file_id - return upload_file def _make_child_chunk( diff --git a/api/tests/unit_tests/services/test_vector_space_admission_service.py b/api/tests/unit_tests/services/test_vector_space_admission_service.py index 267d35f7757..db66f40ec66 100644 --- a/api/tests/unit_tests/services/test_vector_space_admission_service.py +++ b/api/tests/unit_tests/services/test_vector_space_admission_service.py @@ -26,6 +26,7 @@ from services.vector_space_admission_service import ( get_vector_space_admission_error_fields, parse_vector_space_estimate_limits, ) +from tests.unit_tests.model_factories import make_dataset _MEBIBYTE = 1024 * 1024 _ESTIMATE_LIMITS = "sandbox:60,professional:6400,team:25600" @@ -74,10 +75,8 @@ class _FakeRedis: def _dataset(session: Session) -> Dataset: - dataset = Dataset( - id=str(uuid4()), - tenant_id="tenant-1", - name="Dataset", + dataset = make_dataset( + dataset_id=str(uuid4()), created_by=str(uuid4()), indexing_technique=IndexTechniqueType.HIGH_QUALITY, embedding_model_provider="provider", diff --git a/api/tests/unit_tests/services/test_webhook_service.py b/api/tests/unit_tests/services/test_webhook_service.py index f52541d3506..301997252cf 100644 --- a/api/tests/unit_tests/services/test_webhook_service.py +++ b/api/tests/unit_tests/services/test_webhook_service.py @@ -10,7 +10,6 @@ from sqlalchemy.engine import Engine from sqlalchemy.orm import Session from werkzeug.datastructures import FileStorage -from graphon.enums import WorkflowType from models.enums import AppTriggerStatus, AppTriggerType, EndUserType from models.model import App, AppMode, EndUser from models.tools import ToolFile @@ -19,6 +18,7 @@ from models.workflow import Workflow from services.errors.app import QuotaExceededError from services.trigger import webhook_service as webhook_service_module from services.trigger.webhook_service import WebhookService +from tests.unit_tests.model_factories import make_app, make_end_user, make_workflow def _webhook_trigger( @@ -59,31 +59,22 @@ def _workflow( app_id: str = "app-123", version: str = Workflow.VERSION_DRAFT, ) -> Workflow: - workflow = Workflow.new( + return make_workflow( + workflow_id=workflow_id, tenant_id=tenant_id, app_id=app_id, - type=WorkflowType.WORKFLOW.value, version=version, - graph='{"nodes": [], "edges": []}', - features="{}", created_by="account-123", - environment_variables=[], - conversation_variables=[], - rag_pipeline_variables=[], ) - workflow.id = workflow_id - return workflow def _app(*, tenant_id: str = "tenant-123", app_id: str = "app-123") -> App: - return App( - id=app_id, + return make_app( + app_id=app_id, tenant_id=tenant_id, name="Webhook App", - description="", mode=AppMode.WORKFLOW, - enable_site=True, - enable_api=True, + icon_type=None, max_active_requests=0, ) @@ -100,11 +91,11 @@ def _app_trigger(*, status: AppTriggerStatus = AppTriggerStatus.ENABLED) -> AppT def _end_user() -> EndUser: - return EndUser( - id="end-user-123", + return make_end_user( + end_user_id="end-user-123", tenant_id="tenant-123", app_id="app-123", - type=EndUserType.TRIGGER, + end_user_type=EndUserType.TRIGGER, session_id="webhook-session", ) diff --git a/api/tests/unit_tests/services/test_workflow_collaboration_service.py b/api/tests/unit_tests/services/test_workflow_collaboration_service.py index 943677502f7..c1cbe203d63 100644 --- a/api/tests/unit_tests/services/test_workflow_collaboration_service.py +++ b/api/tests/unit_tests/services/test_workflow_collaboration_service.py @@ -11,10 +11,11 @@ from sqlalchemy.orm import Session from core.rbac import RBACPermission, RBACResourceScope from models.account import Account, Tenant from models.base import TypeBase -from models.model import App, AppMode, IconType +from models.model import App, AppMode from repositories.workflow_collaboration_repository import WorkflowCollaborationRepository from services.workflow_collaboration_service import SYNC_REQUEST_TIMEOUT_SECONDS, WorkflowCollaborationService from tests.unit_tests.config_override import config_overrides_context +from tests.unit_tests.model_factories import make_app @pytest.fixture @@ -27,25 +28,15 @@ def db_session(sqlite_engine: Engine) -> Iterator[Session]: def _app(*, app_id: str, tenant_id: str, maintainer: str | None = None) -> App: - return App( - id=app_id, + return make_app( + app_id=app_id, tenant_id=tenant_id, name="Workflow app", - description="", mode=AppMode.WORKFLOW, - icon_type=IconType.EMOJI, - icon="robot", icon_background="#ffffff", enable_site=False, enable_api=False, - api_rpm=0, - api_rph=0, - is_demo=False, - is_public=False, - is_universal=False, - max_active_requests=None, maintainer=maintainer, - use_icon_as_answer_icon=False, ) diff --git a/api/tests/unit_tests/services/test_workflow_comment_service.py b/api/tests/unit_tests/services/test_workflow_comment_service.py index f850169d09d..31b428ac1ab 100644 --- a/api/tests/unit_tests/services/test_workflow_comment_service.py +++ b/api/tests/unit_tests/services/test_workflow_comment_service.py @@ -19,6 +19,7 @@ from models.account import Account, TenantAccountRole from models.model import AppMode from services import workflow_comment_service as service_module from services.workflow_comment_service import WorkflowCommentService +from tests.unit_tests.model_factories import make_account, make_app TENANT_ID = "11111111-1111-1111-1111-111111111111" OTHER_TENANT_ID = "11111111-1111-1111-1111-111111111112" @@ -55,22 +56,20 @@ def _account( email: str = "user@example.com", interface_language: str | None = "en-US", ) -> Account: - account = Account(name=name, email=email, interface_language=interface_language) - account.id = account_id - return account + return make_account(account_id=account_id, name=name, email=email, interface_language=interface_language) def _app(*, app_id: str = APP_ID, tenant_id: str = TENANT_ID, name: str = "My App") -> App: - app = App( + return make_app( + app_id=app_id, tenant_id=tenant_id, name=name, mode=AppMode.WORKFLOW, + icon_type=None, enable_site=False, enable_api=False, created_by=OWNER_ID, ) - app.id = app_id - return app def _comment( diff --git a/api/tests/unit_tests/services/workflow/test_workflow_human_input_delivery.py b/api/tests/unit_tests/services/workflow/test_workflow_human_input_delivery.py index 62c4b456c63..1f75c576b78 100644 --- a/api/tests/unit_tests/services/workflow/test_workflow_human_input_delivery.py +++ b/api/tests/unit_tests/services/workflow/test_workflow_human_input_delivery.py @@ -17,11 +17,11 @@ from core.workflow.human_input_adapter import ( from core.workflow.nodes.human_input.entities import HumanInputNodeData from graphon.enums import BuiltinNodeTypes from models import Account -from models.enums import AppStatus from models.model import App, AppMode from models.workflow import Workflow, WorkflowType from services import workflow_service as workflow_service_module from services.workflow_service import WorkflowService +from tests.unit_tests.model_factories import make_account, make_app TENANT_ID = "11111111-1111-1111-1111-111111111111" APP_ID = "22222222-2222-2222-2222-222222222222" @@ -35,23 +35,18 @@ def _make_service(sqlite_session: Session) -> WorkflowService: def _app() -> App: - return App( - id=APP_ID, + return make_app( + app_id=APP_ID, tenant_id=TENANT_ID, - name="Test App", - description="", mode=AppMode.WORKFLOW, - status=AppStatus.NORMAL, + icon_type=None, enable_site=False, enable_api=False, - max_active_requests=None, ) def _account() -> Account: - account = Account(name="Test User", email="test@example.com") - account.id = ACCOUNT_ID - return account + return make_account(account_id=ACCOUNT_ID, name="Test User", email="test@example.com") def _build_node_config(delivery_methods: list[EmailDeliveryMethod]) -> dict[str, object]: diff --git a/api/tests/unit_tests/tasks/test_clean_document_task.py b/api/tests/unit_tests/tasks/test_clean_document_task.py index 4aef0581933..259dfd6e6a1 100644 --- a/api/tests/unit_tests/tasks/test_clean_document_task.py +++ b/api/tests/unit_tests/tasks/test_clean_document_task.py @@ -20,9 +20,10 @@ from models.dataset import ( DocumentSegment, SegmentAttachmentBinding, ) -from models.enums import DataSourceType, DocumentCreatedFrom +from models.enums import DataSourceType from models.model import UploadFile from tasks.clean_document_task import clean_document_task +from tests.unit_tests.model_factories import make_document SQLITE_MODELS = ( Dataset, @@ -87,15 +88,11 @@ def mock_index_processor_factory(): def _document(*, document_id: str, dataset_id: str, tenant_id: str, created_by: str) -> Document: - return Document( - id=document_id, + return make_document( + document_id=document_id, tenant_id=tenant_id, dataset_id=dataset_id, - position=1, - data_source_type=DataSourceType.UPLOAD_FILE, - batch="batch-1", name=f"{document_id}.txt", - created_from=DocumentCreatedFrom.WEB, created_by=created_by, ) diff --git a/api/tests/unit_tests/tasks/test_delete_conversation_task.py b/api/tests/unit_tests/tasks/test_delete_conversation_task.py index e3e5d84488c..aa86ac156a0 100644 --- a/api/tests/unit_tests/tasks/test_delete_conversation_task.py +++ b/api/tests/unit_tests/tasks/test_delete_conversation_task.py @@ -12,7 +12,6 @@ from core.workflow.nodes.human_input.enums import HumanInputFormKind, HumanInput from graphon.file import FileTransferMethod, FileType from models import ( AgentDebugConversation, - AppMode, Conversation, ConversationVariable, HumanInputForm, @@ -39,6 +38,7 @@ from models.enums import ( from models.human_input import HumanInputDelivery, HumanInputFormRecipient, RecipientType from models.tools import ToolConversationVariables, ToolFile from tasks.delete_conversation_task import _cleanup_conversation_related_data, sweep_deleted_conversations +from tests.unit_tests.model_factories import make_conversation, make_message TENANT_ID = "11111111-1111-1111-1111-111111111111" APP_ID = "22222222-2222-2222-2222-222222222222" @@ -50,10 +50,9 @@ AGENT_ID = "77777777-7777-7777-7777-777777777777" def _conversation(conversation_id: str, *, deleted: bool) -> Conversation: - return Conversation( - id=conversation_id, + return make_conversation( + conversation_id=conversation_id, app_id=APP_ID, - mode=AppMode.CHAT, name="Test conversation", inputs={}, status=ConversationStatus.NORMAL, @@ -64,8 +63,8 @@ def _conversation(conversation_id: str, *, deleted: bool) -> Conversation: def _message() -> Message: - return Message( - id=MESSAGE_ID, + return make_message( + message_id=MESSAGE_ID, app_id=APP_ID, conversation_id=CONVERSATION_ID, inputs={}, diff --git a/api/tests/unit_tests/tasks/test_resume_agent_app_task.py b/api/tests/unit_tests/tasks/test_resume_agent_app_task.py index c9d20849af7..ce063d3a6bb 100644 --- a/api/tests/unit_tests/tasks/test_resume_agent_app_task.py +++ b/api/tests/unit_tests/tasks/test_resume_agent_app_task.py @@ -18,6 +18,7 @@ from models.enums import InvokeFrom as StoredInvokeFrom from models.human_input import HumanInputForm from models.model import App, AppMode, Conversation, EndUser from tasks.app_generate import resume_agent_app_task as mod +from tests.unit_tests.model_factories import make_app, make_conversation MODULE = "tasks.app_generate.resume_agent_app_task" @@ -33,15 +34,12 @@ def task_session(mocker: MockerFixture, sqlite_session_factory: sessionmaker[Ses def _app(*, app_id: str, tenant_id: str) -> App: - return App( - id=app_id, + return make_app( + app_id=app_id, tenant_id=tenant_id, name="Agent app", - description="", mode=AppMode.AGENT_CHAT, icon_type=None, - icon=None, - icon_background=None, enable_site=False, enable_api=False, ) @@ -55,8 +53,8 @@ def _conversation( end_user_id: str | None = None, invoke_from: StoredInvokeFrom = StoredInvokeFrom.WEB_APP, ) -> Conversation: - return Conversation( - id=conversation_id, + return make_conversation( + conversation_id=conversation_id, app_id=app_id, mode=AppMode.AGENT_CHAT, name="Agent conversation", diff --git a/api/tests/unit_tests/tasks/test_sync_website_document_indexing_task.py b/api/tests/unit_tests/tasks/test_sync_website_document_indexing_task.py index 041e9ba55e5..594c4295305 100644 --- a/api/tests/unit_tests/tasks/test_sync_website_document_indexing_task.py +++ b/api/tests/unit_tests/tasks/test_sync_website_document_indexing_task.py @@ -7,14 +7,15 @@ from sqlalchemy.orm import Session from core.rag.index_processor.constant.index_type import IndexStructureType from enums import DeploymentEdition from models.dataset import Dataset, Document, DocumentSegment -from models.enums import DataSourceType, DocumentCreatedFrom +from models.enums import DataSourceType from tasks.sync_website_document_indexing_task import sync_website_document_indexing_task from tests.unit_tests.config_override import config_overrides_context +from tests.unit_tests.model_factories import make_dataset, make_document def _dataset(tenant_id: str) -> Dataset: - return Dataset( - id=str(uuid.uuid4()), + return make_dataset( + dataset_id=str(uuid.uuid4()), tenant_id=tenant_id, name="Website dataset", data_source_type=DataSourceType.WEBSITE_CRAWL, @@ -23,15 +24,12 @@ def _dataset(tenant_id: str) -> Dataset: def _document(dataset: Dataset) -> Document: - return Document( - id=str(uuid.uuid4()), + return make_document( + document_id=str(uuid.uuid4()), tenant_id=dataset.tenant_id, dataset_id=dataset.id, - position=1, data_source_type=DataSourceType.WEBSITE_CRAWL, - batch="batch-1", name="Website document", - created_from=DocumentCreatedFrom.WEB, created_by=str(uuid.uuid4()), doc_form=IndexStructureType.PARAGRAPH_INDEX, ) diff --git a/api/tests/unit_tests/tasks/test_trigger_processing_tasks.py b/api/tests/unit_tests/tasks/test_trigger_processing_tasks.py index 6609633a9cb..ecb320b5a9d 100644 --- a/api/tests/unit_tests/tasks/test_trigger_processing_tasks.py +++ b/api/tests/unit_tests/tasks/test_trigger_processing_tasks.py @@ -1,52 +1,43 @@ -import json from unittest.mock import MagicMock, patch import pytest import tasks.trigger_processing_tasks as trigger_processing_tasks_module from core.plugin.entities.plugin_daemon import CredentialType -from graphon.enums import WorkflowType from models.enums import EndUserType from models.model import EndUser from models.trigger import TriggerSubscription, WorkflowPluginTrigger from models.workflow import Workflow from services.errors.app import QuotaExceededError from tasks.trigger_processing_tasks import dispatch_triggered_workflow +from tests.unit_tests.model_factories import make_end_user, make_workflow def _workflow(*, app_id: str = "app-123") -> Workflow: - workflow = Workflow.new( + return make_workflow( + workflow_id="workflow-123", tenant_id="tenant-123", app_id=app_id, - type=WorkflowType.WORKFLOW.value, version="published", - graph=json.dumps( - { - "nodes": [ - { - "id": "node-123", - "data": {"type": trigger_processing_tasks_module.TRIGGER_PLUGIN_NODE_TYPE}, - } - ], - "edges": [], - } - ), - features="{}", + graph={ + "nodes": [ + { + "id": "node-123", + "data": {"type": trigger_processing_tasks_module.TRIGGER_PLUGIN_NODE_TYPE}, + } + ], + "edges": [], + }, created_by="user-123", - environment_variables=[], - conversation_variables=[], - rag_pipeline_variables=[], ) - workflow.id = "workflow-123" - return workflow def _end_user() -> EndUser: - return EndUser( - id="end-user-123", + return make_end_user( + end_user_id="end-user-123", tenant_id="tenant-123", app_id="app-123", - type=EndUserType.TRIGGER, + end_user_type=EndUserType.TRIGGER, session_id="trigger-session", ) diff --git a/api/tests/unit_tests/tasks/test_workflow_node_execution_tasks.py b/api/tests/unit_tests/tasks/test_workflow_node_execution_tasks.py index 0b3e314a3f4..2a87d73fd35 100644 --- a/api/tests/unit_tests/tasks/test_workflow_node_execution_tasks.py +++ b/api/tests/unit_tests/tasks/test_workflow_node_execution_tasks.py @@ -9,6 +9,7 @@ from unittest.mock import MagicMock import pytest from sqlalchemy import func, select from sqlalchemy.orm import Session, sessionmaker +from typing_extensions import TypedDict from graphon.entities import WorkflowNodeExecution from graphon.entities.workflow_node_execution import WorkflowNodeExecutionMetadataKey @@ -29,6 +30,14 @@ ACCOUNT_ID = "00000000-0000-0000-0000-000000000050" EXECUTION_ID = "00000000-0000-0000-0000-000000000060" +class _ExecutionTaskKwargs(TypedDict, closed=True): + tenant_id: str + app_id: str + triggered_from: str + creator_user_id: str + creator_user_role: str + + class _TaskWithRequestContext(Protocol): def push_request(self, *, retries: int) -> None: ... @@ -313,7 +322,7 @@ def test_task_repeated_delivery_updates_one_row( outputs={"delivery": 2}, finished_at=created_at + timedelta(seconds=5), ) - task_kwargs = { + task_kwargs: _ExecutionTaskKwargs = { "tenant_id": TENANT_ID, "app_id": APP_ID, "triggered_from": WorkflowNodeExecutionTriggeredFrom.WORKFLOW_RUN.value, diff --git a/api/uv.lock b/api/uv.lock index 701309c91f3..9bf9ca07cff 100644 --- a/api/uv.lock +++ b/api/uv.lock @@ -1741,7 +1741,7 @@ dev = [ { name = "lxml-stubs", specifier = ">=0.5.1" }, { name = "mypy", specifier = ">=2.3.1" }, { name = "pandas-stubs", specifier = ">=3.0.5.260730" }, - { name = "pyrefly", specifier = ">=1.2.0" }, + { name = "pyrefly", specifier = ">=1.3.1" }, { name = "pytest", specifier = ">=9.1.1" }, { name = "pytest-benchmark", specifier = ">=5.3.0" }, { name = "pytest-cov", specifier = ">=7.1.0" }, @@ -3355,8 +3355,8 @@ name = "httpcore2" version = "2.12.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "h11", marker = "sys_platform != 'emscripten'" }, - { name = "truststore", marker = "sys_platform != 'emscripten'" }, + { name = "h11" }, + { name = "truststore" }, ] sdist = { url = "https://files.pythonhosted.org/packages/be/ad/f4f0e57345f1870f3e8cb624e058d7eca6e5a27d33bcc3311d9b618734cd/httpcore2-2.12.0.tar.gz", hash = "sha256:9293522bba0aa7c4c8e9e3f040c16575bd8868e155a77fa30c7a9085a5eae648", size = 67548, upload-time = "2026-08-18T13:22:08.211Z" } wheels = [ @@ -5606,21 +5606,21 @@ wheels = [ [[package]] name = "pyrefly" -version = "1.2.0" +version = "1.3.1" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/89/01/a86e9f24722b095c3f88e3616132b75a21b0df53804bdc6a45314dd4d93c/pyrefly-1.2.0.tar.gz", hash = "sha256:5485f960fc2481617068c918335c39ab1507ef90b6b5bd35bf57726e60e73185", size = 6243654, upload-time = "2026-08-01T02:56:27.592Z" } +sdist = { url = "https://files.pythonhosted.org/packages/4c/7e/082cc1b392318bacfbc087732524f3e089e9e7fe45b7645c4649b6f1f1e9/pyrefly-1.3.1.tar.gz", hash = "sha256:b774f1dfac82867fc16fe2175d80a630e2a72a6835d5b7e492568d6bfb6b05e7", size = 6679145, upload-time = "2026-09-15T05:09:34.49Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/7d/9d/3c0ef1d4843987b22f996ed381ec9cf5a3b1273e29804db276252e4c95eb/pyrefly-1.2.0-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:7f46d983ac49ddd2b043694960a01dc6a19a5cfd8eec609d6bd9c42866f91b4e", size = 14026305, upload-time = "2026-08-01T02:56:02.611Z" }, - { url = "https://files.pythonhosted.org/packages/0a/06/03bbb78fbea54cdc65b626619f3597d5611aca4fdef11e72a4e8360e7e63/pyrefly-1.2.0-py3-none-macosx_11_0_arm64.whl", hash = "sha256:756f669b5555090f5c1a4fef30db1785fabe657764f7e4e6dc88994dfb8ca82d", size = 13463880, upload-time = "2026-08-01T02:56:04.93Z" }, - { url = "https://files.pythonhosted.org/packages/13/5a/7d8bc00a38e93bbc9c3e7bd14d305f7948717e667c9bcddeab9dd42fd255/pyrefly-1.2.0-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e3465812ce5ef4781fb592edbf2724547296f0a3124be115d73c7e8b2401862d", size = 13907329, upload-time = "2026-08-01T02:56:07.104Z" }, - { url = "https://files.pythonhosted.org/packages/be/94/9e08b4bf799d0b8f36b55a2783c7ba5f51730cf0632a85a67b5b5ed876cd/pyrefly-1.2.0-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5de7b2ad2bba5c8055181681a84b74143eac2234a48ba5d1b7ed7e7a722b02bd", size = 15039020, upload-time = "2026-08-01T02:56:09.208Z" }, - { url = "https://files.pythonhosted.org/packages/5b/bd/bca5fd0c80f4daf8ee6903a29df9f3de1feb05ff0946b8f35ec8c5096b13/pyrefly-1.2.0-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:25822ea9505f589ea8a725e4268b475132fb89e038fbf092e446510443ac142a", size = 14986199, upload-time = "2026-08-01T02:56:11.924Z" }, - { url = "https://files.pythonhosted.org/packages/97/f7/f07087f3d185ad2eced0c56cef89ca5474dfb4ff25f146cd50a861c97553/pyrefly-1.2.0-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:90efe75e17491ef5d636e10469e9278d7d0256b3b4c5e1f4750069bf3ae0f5d1", size = 14393715, upload-time = "2026-08-01T02:56:14.143Z" }, - { url = "https://files.pythonhosted.org/packages/d3/70/0d142c320e284b9e3ce35e9b1e58b8ce2ee1f578f2a7234bc30e5022b94f/pyrefly-1.2.0-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:368aaf7eee4f511ddc0f8e564cf14e01ab2f10b0db9105c6d5b153bf498d07bf", size = 13933008, upload-time = "2026-08-01T02:56:16.525Z" }, - { url = "https://files.pythonhosted.org/packages/5d/e8/e84f11b6e1f63fd453ad3654213b9a0f6f4de8cef6b58038eef2d0d5955d/pyrefly-1.2.0-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:d52d5da7bc65fb7675fbaa80eda879d4f8787c494f04cac21603330d3abbdbbe", size = 14431827, upload-time = "2026-08-01T02:56:18.645Z" }, - { url = "https://files.pythonhosted.org/packages/0f/06/810d31380f66c75e1c0779a408d3b16117b1b368b57894f6aa66bef21686/pyrefly-1.2.0-py3-none-win32.whl", hash = "sha256:8c90751de8506d938e8f802659c74cf35bd7a0036510ee6c634a38eebb280bfa", size = 13229447, upload-time = "2026-08-01T02:56:20.921Z" }, - { url = "https://files.pythonhosted.org/packages/ed/98/4dafa3c7a1caed2dc8cc708dde09ba27963c7736508f55b626fff3024113/pyrefly-1.2.0-py3-none-win_amd64.whl", hash = "sha256:8a8964c224ccc4882730130955815de21ff443c1ac3f0b90685b19bf63848170", size = 14087387, upload-time = "2026-08-01T02:56:23.188Z" }, - { url = "https://files.pythonhosted.org/packages/1b/1c/df3cb0a2e5591660ded7a1836cd2f29dc48c91adb1c0a3a700a96f6d09e1/pyrefly-1.2.0-py3-none-win_arm64.whl", hash = "sha256:3a90bb8df39dfbac74b1f3b2e9d7c526b8f80568884c3944d955023a73ebf61e", size = 13430873, upload-time = "2026-08-01T02:56:25.425Z" }, + { url = "https://files.pythonhosted.org/packages/6b/bc/f09b5bd7eed796349bd93627aa2c3086df7ed0a114dafc3d2b2841599ee7/pyrefly-1.3.1-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:0a2a7dfed824fb066aa8a7c8d856c152b98e998fb84b954d5f892fb9834c0bd1", size = 15090117, upload-time = "2026-09-15T05:08:58.477Z" }, + { url = "https://files.pythonhosted.org/packages/78/24/a31e7de8e0f310c9f0753826d0df8b6a8ff86793941a6bd88ba24b735633/pyrefly-1.3.1-py3-none-macosx_11_0_arm64.whl", hash = "sha256:37a1a1fb41e7190fe3964a877875dd809d6ee2887809e2c3d6e5f25a69805bfd", size = 14430773, upload-time = "2026-09-15T05:09:01.67Z" }, + { url = "https://files.pythonhosted.org/packages/75/e1/022352beb186f36d6213a95a975c42676ee418ff5e730629378d25564c5e/pyrefly-1.3.1-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:45b7650bc651a5a784701f2fb4f579da9a421c4d1b5fd91f9048f4529e46093c", size = 14877762, upload-time = "2026-09-15T05:09:04.767Z" }, + { url = "https://files.pythonhosted.org/packages/19/2c/d62a1b445aad1fd46375b83393b8a67da53883aeeba73d269ce71b3e8fd1/pyrefly-1.3.1-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ad57f38bc73679c14a5f62995a2bd3bc43649b7270c858487e2eab78a6f95362", size = 16118225, upload-time = "2026-09-15T05:09:07.828Z" }, + { url = "https://files.pythonhosted.org/packages/32/67/3c3fa8a13e3ce8d8322ffb3627255a41a52261f17793e1a24e9214f10857/pyrefly-1.3.1-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3506b1252e9f5d755d0693c5a46d46b85579d36534f6e22a41eb8e098d0dde90", size = 16051197, upload-time = "2026-09-15T05:09:10.81Z" }, + { url = "https://files.pythonhosted.org/packages/19/d1/84f81b9e32288b5d11c75e006f67e0f6b78b85da575673bd1a21524c8172/pyrefly-1.3.1-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:39d5a17c1ad655e5f93a6e8e0be90b8db28c6015fe07d6ed4e054891c9e14ef8", size = 15440062, upload-time = "2026-09-15T05:09:14.641Z" }, + { url = "https://files.pythonhosted.org/packages/46/3c/2aa0c5ad6a2957a728bb38cfab70d1e9cafad21b0b1c1d92df74a3cf22bd/pyrefly-1.3.1-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:c61e9dcf888beb81ca0e0c60ea4442640fdcf2f1f65bfe3d9f5d89b85516eb03", size = 14898534, upload-time = "2026-09-15T05:09:17.823Z" }, + { url = "https://files.pythonhosted.org/packages/03/5d/2974f249fb2e285f966485719d977c5a2248cc4866403af94d935775ff15/pyrefly-1.3.1-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:a78eed1c53f37807f0e2997be8edbe833b91eb9915f2cb553a1bdcd599e7105c", size = 15477758, upload-time = "2026-09-15T05:09:20.964Z" }, + { url = "https://files.pythonhosted.org/packages/68/79/d31f8f0abf5b0610ec24183286ee8a1ee5b76c9aaaa91091399d7e1aaf60/pyrefly-1.3.1-py3-none-win32.whl", hash = "sha256:ea0d0349f1bc80fe8e00a7b3640e40db7486398b685f87b9bd5d2207063668f0", size = 14133005, upload-time = "2026-09-15T05:09:25.03Z" }, + { url = "https://files.pythonhosted.org/packages/3f/23/1859f47f536314755d80c4ec2a76d2698435ae8b73604bcd1dfa57ecb39b/pyrefly-1.3.1-py3-none-win_amd64.whl", hash = "sha256:4c24e7d99df9237916c33be23714219a7c8eaf22bad2ac0c3e1d45adcf7df8fb", size = 15066557, upload-time = "2026-09-15T05:09:28.265Z" }, + { url = "https://files.pythonhosted.org/packages/92/2a/73e3721921c2bb2c950a9f8c362ee5e2367556b5d4416eb4f8f88503e66f/pyrefly-1.3.1-py3-none-win_arm64.whl", hash = "sha256:1ed85e2b957a78e93353125d890ce3668ce450e49b74025a15c87b6e8ec7f3d1", size = 14348209, upload-time = "2026-09-15T05:09:31.422Z" }, ] [[package]] @@ -6304,11 +6304,11 @@ wheels = [ [[package]] name = "soupsieve" -version = "2.8.4" +version = "2.9" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/47/2c/0a5f6f8ee0d5589e48c7640213ed5175d52cf540a06725b628cc1a45d6ce/soupsieve-2.8.4.tar.gz", hash = "sha256:e121fd02e975c695e4e9e8774a5ee35d74714b59307868dcc5319ad2d9e3328e", size = 121110, upload-time = "2026-05-24T13:55:57.154Z" } +sdist = { url = "https://files.pythonhosted.org/packages/80/f1/93422647dd7e461f23d254e6b2bfa687a85b53aeb4903fcdbb74474d4584/soupsieve-2.9.tar.gz", hash = "sha256:acee8417325c5653e1377dc31eccad59eb82cbc65942afe6174c53b3aaad63fc", size = 122122, upload-time = "2026-07-19T01:35:18.425Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/5e/f5/0c41cb68dcae6b7de4fac4188a3a9589e21fb31df21ea3a2e888db95e6c9/soupsieve-2.8.4-py3-none-any.whl", hash = "sha256:e7e6b0769c8f51ed59acab6e994b00621096cfb1c640a7509295987388fbaf65", size = 37304, upload-time = "2026-05-24T13:55:55.406Z" }, + { url = "https://files.pythonhosted.org/packages/7b/d6/3185ab5ad1280319b31986898f3206dd7227cd75e293d4dba2a5e6bf27a0/soupsieve-2.9-py3-none-any.whl", hash = "sha256:a2b2c76d67df2382d245409fd71e321a571717e58463efa32ace87dcadac2c12", size = 37387, upload-time = "2026-07-19T01:35:17.106Z" }, ] [[package]] diff --git a/docs/ar-SA/README.md b/docs/ar-SA/README.md index 47ec128c0ac..b095b2d850d 100644 --- a/docs/ar-SA/README.md +++ b/docs/ar-SA/README.md @@ -129,7 +129,7 @@ docker compose up -d - **الشفرة البرمجية**: اقرأ [دليل المساهمة](https://github.com/langgenius/dify/blob/main/CONTRIBUTING.md)، ثم تصفح [المشكلات المناسبة للمساهمين الجدد](https://github.com/langgenius/dify/issues?q=is%3Aissue%20state%3Aopen%20label%3A%22good%20first%20issue%22). - **الأفكار والملاحظات**: ابدأ أو انضم إلى [مناقشة على GitHub](https://github.com/langgenius/dify/discussions). -- **الترجمات**: اتبع [دليل التدويل](https://github.com/langgenius/dify/blob/main/web/i18n-config/README.md) لإضافة لغة أو تحديثها. +- **الترجمات**: اتبع [دليل التدويل](https://github.com/langgenius/dify/blob/main/web/i18n/README.md) لإضافة لغة أو تحديثها. - **المجتمع**: شارك التطبيقات التي تبنيها، وساعد المستخدمين الآخرين، وساهم في نشر Dify. ### المساهمون diff --git a/docs/bn-BD/README.md b/docs/bn-BD/README.md index 2e30e5ee25b..91dc71bd034 100644 --- a/docs/bn-BD/README.md +++ b/docs/bn-BD/README.md @@ -145,7 +145,7 @@ Dify সব ধরনের অবদানকে স্বাগত জান - **কোড**: [অবদান নির্দেশিকা](https://github.com/langgenius/dify/blob/main/CONTRIBUTING.md) পড়ুন, তারপর [নতুন অবদানকারীদের উপযোগী সমস্যাগুলো](https://github.com/langgenius/dify/issues?q=is%3Aissue%20state%3Aopen%20label%3A%22good%20first%20issue%22) দেখুন। - **ধারণা ও প্রতিক্রিয়া**: একটি [GitHub আলোচনা](https://github.com/langgenius/dify/discussions) শুরু করুন বা বিদ্যমান আলোচনায় যোগ দিন। -- **অনুবাদ**: কোনো লোকেল যোগ বা হালনাগাদ করতে [আন্তর্জাতিকীকরণ নির্দেশিকা](https://github.com/langgenius/dify/blob/main/web/i18n-config/README.md) অনুসরণ করুন। +- **অনুবাদ**: কোনো লোকেল যোগ বা হালনাগাদ করতে [আন্তর্জাতিকীকরণ নির্দেশিকা](https://github.com/langgenius/dify/blob/main/web/i18n/README.md) অনুসরণ করুন। - **কমিউনিটি**: আপনার তৈরি অ্যাপ শেয়ার করুন, অন্য ব্যবহারকারীদের সাহায্য করুন এবং Dify সম্পর্কে সবাইকে জানান। ### অবদানকারীরা diff --git a/docs/de-DE/README.md b/docs/de-DE/README.md index 1608b0106e6..7f295d84534 100644 --- a/docs/de-DE/README.md +++ b/docs/de-DE/README.md @@ -144,7 +144,7 @@ Dify freut sich über Beiträge jeder Art: - **Code**: Lesen Sie den [Contribution Guide](./CONTRIBUTING.md) und stöbern Sie anschließend in den [Good First Issues](https://github.com/langgenius/dify/issues?q=is%3Aissue%20state%3Aopen%20label%3A%22good%20first%20issue%22). - **Ideen und Feedback**: Starten Sie eine [GitHub-Diskussion](https://github.com/langgenius/dify/discussions) oder beteiligen Sie sich an einer bestehenden. -- **Übersetzungen**: Folgen Sie dem [Internationalisierungsleitfaden](https://github.com/langgenius/dify/blob/main/web/i18n-config/README.md), um eine Sprache hinzuzufügen oder zu aktualisieren. +- **Übersetzungen**: Folgen Sie dem [Internationalisierungsleitfaden](https://github.com/langgenius/dify/blob/main/web/i18n/README.md), um eine Sprache hinzuzufügen oder zu aktualisieren. - **Community**: Teilen Sie Ihre Apps, helfen Sie anderen Nutzern und machen Sie Dify bekannter. ### Mitwirkende diff --git a/docs/es-ES/README.md b/docs/es-ES/README.md index fb914dc1be7..f578318ba65 100644 --- a/docs/es-ES/README.md +++ b/docs/es-ES/README.md @@ -140,7 +140,7 @@ Dify acepta contribuciones de todo tipo: - **Código**: Lee la [Guía de contribución](./CONTRIBUTING.md) y consulta las [incidencias para principiantes](https://github.com/langgenius/dify/issues?q=is%3Aissue%20state%3Aopen%20label%3A%22good%20first%20issue%22). - **Ideas y comentarios**: Inicia o únete a una [discusión en GitHub](https://github.com/langgenius/dify/discussions). -- **Traducciones**: Sigue la [guía de internacionalización](https://github.com/langgenius/dify/blob/main/web/i18n-config/README.md) para añadir o actualizar un idioma. +- **Traducciones**: Sigue la [guía de internacionalización](https://github.com/langgenius/dify/blob/main/web/i18n/README.md) para añadir o actualizar un idioma. - **Comunidad**: Comparte las aplicaciones que crees, ayuda a otros usuarios y da a conocer Dify. ### Contribuidores diff --git a/docs/fr-FR/README.md b/docs/fr-FR/README.md index 8d9f3941f8e..72b4fc3a7d6 100644 --- a/docs/fr-FR/README.md +++ b/docs/fr-FR/README.md @@ -140,7 +140,7 @@ Dify accueille les contributions de toutes sortes : - **Code** : consultez le [guide de contribution](./CONTRIBUTING.md), puis parcourez les [issues accessibles aux nouveaux contributeurs](https://github.com/langgenius/dify/issues?q=is%3Aissue%20state%3Aopen%20label%3A%22good%20first%20issue%22). - **Idées et retours** : lancez ou rejoignez une [discussion GitHub](https://github.com/langgenius/dify/discussions). -- **Traductions** : suivez le [guide d’internationalisation](https://github.com/langgenius/dify/blob/main/web/i18n-config/README.md) pour ajouter ou mettre à jour une langue. +- **Traductions** : suivez le [guide d’internationalisation](https://github.com/langgenius/dify/blob/main/web/i18n/README.md) pour ajouter ou mettre à jour une langue. - **Communauté** : partagez les applications que vous créez, aidez les autres utilisateurs et faites connaître Dify. ### Contributeurs diff --git a/docs/hi-IN/README.md b/docs/hi-IN/README.md index 747987f3a47..0a927af5ecb 100644 --- a/docs/hi-IN/README.md +++ b/docs/hi-IN/README.md @@ -146,7 +146,7 @@ Dify सभी प्रकार के योगदानों का स् - **कोड**: [Contribution Guide](./CONTRIBUTING.md) पढ़ें, फिर [नए योगदानकर्ताओं के लिए उपयुक्त issues](https://github.com/langgenius/dify/issues?q=is%3Aissue%20state%3Aopen%20label%3A%22good%20first%20issue%22) देखें। - **विचार और प्रतिक्रिया**: [GitHub Discussion](https://github.com/langgenius/dify/discussions) शुरू करें या किसी मौजूदा चर्चा में शामिल हों। -- **अनुवाद**: किसी locale को जोड़ने या अपडेट करने के लिए [internationalization guide](https://github.com/langgenius/dify/blob/main/web/i18n-config/README.md) का पालन करें। +- **अनुवाद**: किसी locale को जोड़ने या अपडेट करने के लिए [internationalization guide](https://github.com/langgenius/dify/blob/main/web/i18n/README.md) का पालन करें। - **समुदाय**: अपने बनाए हुए ऐप साझा करें, अन्य उपयोगकर्ताओं की मदद करें और Dify के बारे में लोगों को बताएं। ### योगदानकर्ता diff --git a/docs/it-IT/README.md b/docs/it-IT/README.md index 0d258a07707..427d3a42382 100644 --- a/docs/it-IT/README.md +++ b/docs/it-IT/README.md @@ -144,7 +144,7 @@ Dify accoglie contributi di ogni tipo: - **Codice**: leggi la [Guida ai contributi](../../CONTRIBUTING.md), quindi consulta le [issue adatte ai nuovi contributori](https://github.com/langgenius/dify/issues?q=is%3Aissue%20state%3Aopen%20label%3A%22good%20first%20issue%22). - **Idee e feedback**: avvia o partecipa a una [discussione su GitHub](https://github.com/langgenius/dify/discussions). -- **Traduzioni**: segui la [guida all’internazionalizzazione](https://github.com/langgenius/dify/blob/main/web/i18n-config/README.md) per aggiungere o aggiornare una lingua. +- **Traduzioni**: segui la [guida all’internazionalizzazione](https://github.com/langgenius/dify/blob/main/web/i18n/README.md) per aggiungere o aggiornare una lingua. - **Community**: condividi le app che crei, aiuta gli altri utenti e fai conoscere Dify. ### Collaboratori diff --git a/docs/ja-JP/README.md b/docs/ja-JP/README.md index fe1ea07c551..10403ad6d6d 100644 --- a/docs/ja-JP/README.md +++ b/docs/ja-JP/README.md @@ -141,7 +141,7 @@ Difyでは、あらゆる形の貢献を歓迎しています。 - **コード**:[コントリビューションガイド](./CONTRIBUTING.md)を読み、[good first issue](https://github.com/langgenius/dify/issues?q=is%3Aissue%20state%3Aopen%20label%3A%22good%20first%20issue%22)を確認してください。 - **アイデアとフィードバック**:[GitHub Discussions](https://github.com/langgenius/dify/discussions)で新しい議論を始めるか、既存の議論に参加してください。 -- **翻訳**:言語の追加や更新については、[国際化ガイド](https://github.com/langgenius/dify/blob/main/web/i18n-config/README.md)に従ってください。 +- **翻訳**:言語の追加や更新については、[国際化ガイド](https://github.com/langgenius/dify/blob/main/web/i18n/README.md)に従ってください。 - **コミュニティ**:作成したアプリを共有し、他のユーザーを支援し、Difyを広めてください。 ### 貢献者 diff --git a/docs/ko-KR/README.md b/docs/ko-KR/README.md index 40638aa2449..f49404c6d7f 100644 --- a/docs/ko-KR/README.md +++ b/docs/ko-KR/README.md @@ -134,7 +134,7 @@ Dify는 모든 형태의 기여를 환영합니다. - **코드**: [기여 가이드](./CONTRIBUTING.md)를 읽고 [good first issue](https://github.com/langgenius/dify/issues?q=is%3Aissue%20state%3Aopen%20label%3A%22good%20first%20issue%22)를 살펴보세요. - **아이디어 및 피드백**: [GitHub 토론](https://github.com/langgenius/dify/discussions)을 시작하거나 기존 토론에 참여하세요. -- **번역**: 언어를 추가하거나 업데이트하려면 [국제화 가이드](https://github.com/langgenius/dify/blob/main/web/i18n-config/README.md)를 따르세요. +- **번역**: 언어를 추가하거나 업데이트하려면 [국제화 가이드](https://github.com/langgenius/dify/blob/main/web/i18n/README.md)를 따르세요. - **커뮤니티**: 만든 앱을 공유하고, 다른 사용자를 돕고, Dify를 널리 알려주세요. ### 기여자 diff --git a/docs/pt-BR/README.md b/docs/pt-BR/README.md index 7dc7026c647..7fd3d738305 100644 --- a/docs/pt-BR/README.md +++ b/docs/pt-BR/README.md @@ -139,7 +139,7 @@ O Dify aceita contribuições de todos os tipos: - **Código**: leia o [Guia de Contribuição](./CONTRIBUTING.md) e consulte as [issues para iniciantes](https://github.com/langgenius/dify/issues?q=is%3Aissue%20state%3Aopen%20label%3A%22good%20first%20issue%22). - **Ideias e feedback**: inicie ou participe de uma [discussão no GitHub](https://github.com/langgenius/dify/discussions). -- **Traduções**: siga o [guia de internacionalização](https://github.com/langgenius/dify/blob/main/web/i18n-config/README.md) para adicionar ou atualizar um idioma. +- **Traduções**: siga o [guia de internacionalização](https://github.com/langgenius/dify/blob/main/web/i18n/README.md) para adicionar ou atualizar um idioma. - **Comunidade**: compartilhe os aplicativos que você criar, ajude outros usuários e divulgue o Dify. ### Contribuidores diff --git a/docs/sl-SI/README.md b/docs/sl-SI/README.md index 680a858634e..38c41317ecf 100644 --- a/docs/sl-SI/README.md +++ b/docs/sl-SI/README.md @@ -141,7 +141,7 @@ Dify pozdravlja vse vrste prispevkov: - **Koda**: preberite [vodnik za prispevke](https://github.com/langgenius/dify/blob/main/CONTRIBUTING.md), nato pa preglejte [naloge za nove sodelavce](https://github.com/langgenius/dify/issues?q=is%3Aissue%20state%3Aopen%20label%3A%22good%20first%20issue%22). - **Ideje in povratne informacije**: začnite ali se pridružite [razpravi na GitHubu](https://github.com/langgenius/dify/discussions). -- **Prevodi**: sledite [vodniku za internacionalizacijo](https://github.com/langgenius/dify/blob/main/web/i18n-config/README.md), da dodate ali posodobite jezik. +- **Prevodi**: sledite [vodniku za internacionalizacijo](https://github.com/langgenius/dify/blob/main/web/i18n/README.md), da dodate ali posodobite jezik. - **Skupnost**: delite ustvarjene aplikacije, pomagajte drugim uporabnikom in razširite glas o Difyju. ### Sodelavci diff --git a/docs/tlh/README.md b/docs/tlh/README.md index df8bfd47b8d..12d639fec38 100644 --- a/docs/tlh/README.md +++ b/docs/tlh/README.md @@ -140,7 +140,7 @@ Dify welcomes contributions of all kinds: - **Code**: Read the [Contribution Guide](https://github.com/langgenius/dify/blob/main/CONTRIBUTING.md), then browse [good first issues](https://github.com/langgenius/dify/issues?q=is%3Aissue%20state%3Aopen%20label%3A%22good%20first%20issue%22). - **Ideas and feedback**: Start or join a [GitHub Discussion](https://github.com/langgenius/dify/discussions). -- **Translations**: Follow the [internationalization guide](https://github.com/langgenius/dify/blob/main/web/i18n-config/README.md) to add or update a locale. +- **Translations**: Follow the [internationalization guide](https://github.com/langgenius/dify/blob/main/web/i18n/README.md) to add or update a locale. - **Community**: Share the apps you build, help other users, and spread the word about Dify. ### Contributors diff --git a/docs/tr-TR/README.md b/docs/tr-TR/README.md index 93ef7013235..d99d0f5c02a 100644 --- a/docs/tr-TR/README.md +++ b/docs/tr-TR/README.md @@ -134,7 +134,7 @@ Dify her türlü katkıyı memnuniyetle karşılar: - **Kod**: [Katkı Kılavuzu'nu](./CONTRIBUTING.md) okuyun, ardından [yeni katkıda bulunanlara uygun sorunlara](https://github.com/langgenius/dify/issues?q=is%3Aissue%20state%3Aopen%20label%3A%22good%20first%20issue%22) göz atın. - **Fikirler ve geri bildirim**: Bir [GitHub tartışması](https://github.com/langgenius/dify/discussions) başlatın veya mevcut bir tartışmaya katılın. -- **Çeviriler**: Bir dil eklemek veya güncellemek için [uluslararasılaştırma kılavuzunu](https://github.com/langgenius/dify/blob/main/web/i18n-config/README.md) izleyin. +- **Çeviriler**: Bir dil eklemek veya güncellemek için [uluslararasılaştırma kılavuzunu](https://github.com/langgenius/dify/blob/main/web/i18n/README.md) izleyin. - **Topluluk**: Oluşturduğunuz uygulamaları paylaşın, diğer kullanıcılara yardımcı olun ve Dify'ı duyurun. ### Katkıda bulunanlar diff --git a/docs/vi-VN/README.md b/docs/vi-VN/README.md index db8d5904321..de05098833c 100644 --- a/docs/vi-VN/README.md +++ b/docs/vi-VN/README.md @@ -135,7 +135,7 @@ Dify chào đón mọi hình thức đóng góp: - **Mã nguồn**: đọc [Hướng dẫn đóng góp](./CONTRIBUTING.md), sau đó xem các [issue phù hợp cho người mới](https://github.com/langgenius/dify/issues?q=is%3Aissue%20state%3Aopen%20label%3A%22good%20first%20issue%22). - **Ý tưởng và phản hồi**: bắt đầu hoặc tham gia một [thảo luận trên GitHub](https://github.com/langgenius/dify/discussions). -- **Bản dịch**: làm theo [hướng dẫn quốc tế hóa](https://github.com/langgenius/dify/blob/main/web/i18n-config/README.md) để thêm hoặc cập nhật một ngôn ngữ. +- **Bản dịch**: làm theo [hướng dẫn quốc tế hóa](https://github.com/langgenius/dify/blob/main/web/i18n/README.md) để thêm hoặc cập nhật một ngôn ngữ. - **Cộng đồng**: chia sẻ ứng dụng bạn xây dựng, hỗ trợ người dùng khác và giới thiệu Dify đến nhiều người hơn. ### Người đóng góp diff --git a/docs/zh-CN/README.md b/docs/zh-CN/README.md index 5db1ed94a01..a7ef1a3102a 100644 --- a/docs/zh-CN/README.md +++ b/docs/zh-CN/README.md @@ -147,7 +147,7 @@ Dify 欢迎各种形式的贡献: - **代码**:阅读[贡献指南](./CONTRIBUTING.md),然后浏览[适合新手的问题](https://github.com/langgenius/dify/issues?q=is%3Aissue%20state%3Aopen%20label%3A%22good%20first%20issue%22)。 - **想法与反馈**:发起或加入 [GitHub Discussions](https://github.com/langgenius/dify/discussions)。 -- **翻译**:按照[国际化指南](https://github.com/langgenius/dify/blob/main/web/i18n-config/README.md)添加或更新语言。 +- **翻译**:按照[国际化指南](https://github.com/langgenius/dify/blob/main/web/i18n/README.md)添加或更新语言。 - **社区**:分享您构建的应用、帮助其他用户,并向更多人介绍 Dify。 ### 贡献者 diff --git a/docs/zh-TW/README.md b/docs/zh-TW/README.md index b7682b181c6..c1418665269 100644 --- a/docs/zh-TW/README.md +++ b/docs/zh-TW/README.md @@ -145,7 +145,7 @@ Dify 歡迎各種形式的貢獻: - **程式碼**:閱讀[貢獻指南](./CONTRIBUTING.md),再瀏覽[適合新手的議題](https://github.com/langgenius/dify/issues?q=is%3Aissue%20state%3Aopen%20label%3A%22good%20first%20issue%22)。 - **想法與意見**:發起或加入 [GitHub Discussions](https://github.com/langgenius/dify/discussions)。 -- **翻譯**:依照[國際化指南](https://github.com/langgenius/dify/blob/main/web/i18n-config/README.md)新增或更新語言。 +- **翻譯**:依照[國際化指南](https://github.com/langgenius/dify/blob/main/web/i18n/README.md)新增或更新語言。 - **社群**:分享您建立的應用程式、協助其他使用者,並向更多人介紹 Dify。 ### 貢獻者 diff --git a/eslint.config.mjs b/eslint.config.mjs index 9b72b129bd9..001db22be9a 100644 --- a/eslint.config.mjs +++ b/eslint.config.mjs @@ -436,7 +436,7 @@ export default defineConfig([ }, }, { - files: ['web/i18n/**/*.json'], + files: ['web/i18n/locales/**/*.json'], plugins: { dify, }, diff --git a/knip.config.ts b/knip.config.ts index 4cf2444d674..d7bbb3a42d7 100644 --- a/knip.config.ts +++ b/knip.config.ts @@ -33,7 +33,7 @@ const config: KnipConfig = { '!vitest.setup.ts!', ], ignore: ['public/**'], - ignoreFiles: ['i18n-config/locale-resources/*.ts'], + ignoreFiles: ['i18n/locale-resources/*.ts'], ignoreDependencies: ['@iconify-json/*', '@storybook/addon-onboarding'], }, }, diff --git a/oxlint-suppressions.json b/oxlint-suppressions.json index 6d4ac1a0b74..20affb81ef4 100644 --- a/oxlint-suppressions.json +++ b/oxlint-suppressions.json @@ -769,12 +769,6 @@ } }, "web/app/components/base/chat/chat-with-history/header-in-mobile.tsx": { - "jsx-a11y/click-events-have-key-events": { - "count": 4 - }, - "jsx-a11y/no-static-element-interactions": { - "count": 4 - }, "typescript/no-explicit-any": { "count": 2 } @@ -1287,7 +1281,7 @@ }, "web/app/components/base/icons/src/vender/line/general/index.ts": { "no-barrel-files/no-barrel-files": { - "count": 10 + "count": 9 } }, "web/app/components/base/icons/src/vender/line/images/index.ts": { @@ -5163,12 +5157,7 @@ "count": 4 } }, - "web/i18n-config/index.ts": { - "no-barrel-files/no-barrel-files": { - "count": 1 - } - }, - "web/i18n-config/lib.client.ts": { + "web/i18n/index.ts": { "no-barrel-files/no-barrel-files": { "count": 1 } diff --git a/packages/dify-ui/README.md b/packages/dify-ui/README.md index db32e4e26dd..d124e42c99f 100644 --- a/packages/dify-ui/README.md +++ b/packages/dify-ui/README.md @@ -39,7 +39,7 @@ Import `styles.css` once from the consumer's root stylesheet or entrypoint. | Actions | `./button`, `./icon-button`, `./toggle` | | Controls | `./segmented-control` | | Display | `./collapsible`, `./kbd` | -| Feedback | `./meter`, `./progress`, `./status-dot`, `./toast` | +| Feedback | `./meter`, `./progress`, `./spinner`, `./status-dot`, `./toast` | | Form | `./form`, `./field`, `./fieldset`, `./input`, `./input-group`, `./textarea`, `./checkbox`, `./checkbox-group`, `./radio-group`, `./number-field`, `./select`, `./slider`, `./switch` | | Layout | `./scroll-area` | | Media | `./avatar` | diff --git a/packages/dify-ui/package.json b/packages/dify-ui/package.json index 71da5d3d9d7..8084254b75f 100644 --- a/packages/dify-ui/package.json +++ b/packages/dify-ui/package.json @@ -160,6 +160,10 @@ "./tooltip": { "types": "./src/tooltip/index.tsx", "import": "./src/tooltip/index.tsx" + }, + "./spinner": { + "types": "./src/spinner/index.tsx", + "import": "./src/spinner/index.tsx" } }, "scripts": { diff --git a/packages/dify-ui/src/spinner/README.md b/packages/dify-ui/src/spinner/README.md new file mode 100644 index 00000000000..f1ac8062eab --- /dev/null +++ b/packages/dify-ui/src/spinner/README.md @@ -0,0 +1,19 @@ +# Spinner + +`Spinner` is a named, indeterminate progress indicator built on Base UI Progress. Supply exactly +one accessible name through `aria-label` or `aria-labelledby`. Localization belongs to the +consumer. The component fixes `value` to `null` and omits value text rather than exposing Base UI's +English indeterminate fallback. It does not announce completion or manage the surrounding region's +busy state; those belong to the feature's status owner. + +`SpinnerIcon` is the same decorative artwork for controls that already own their loading state and +accessible name. It is always hidden from assistive technology. Do not place a second progress +indicator inside a loading button. + +Both exports occupy only their own size (12, 16, or 20 pixels; default 16). `className` styles that +size and color, not an enclosing page or panel. The four-square artwork, opacity sequence, two-second +cycle, and reduced-motion still frame preserve the former Web Loading visual. The component +retains its existing blue in both themes; a palette change is a separate design decision. + +Web owns loading placeholders, page geometry, translations, and data readiness. Do not add +`app`, `area`, fullscreen, centering, request, delay, or content-replacement props here. diff --git a/packages/dify-ui/src/spinner/index.stories.tsx b/packages/dify-ui/src/spinner/index.stories.tsx new file mode 100644 index 00000000000..3f312709da5 --- /dev/null +++ b/packages/dify-ui/src/spinner/index.stories.tsx @@ -0,0 +1,62 @@ +import type { Meta, StoryObj } from '@storybook/react-vite' +import { expect } from 'storybook/test' +import { Spinner, SpinnerIcon } from '.' + +const meta = { + title: 'Base/UI/Spinner', + component: Spinner, + parameters: { layout: 'centered' }, + tags: ['autodocs'], + args: { 'aria-label': 'Loading agent details' }, +} satisfies Meta + +export default meta +type Story = StoryObj + +export const Default: Story = { + play: async ({ canvas }) => { + const progress = canvas.getByRole('progressbar', { name: 'Loading agent details' }) + await expect(progress).not.toHaveAttribute('aria-valuenow') + await expect(progress).not.toHaveAttribute('aria-valuetext') + }, +} + +export const VisibleLabel: Story = { + render: () => ( +
+ + Loading files +
+ ), + play: async ({ canvas }) => { + await expect(canvas.getByRole('progressbar', { name: 'Loading files' })).toBeVisible() + }, +} + +export const Sizes: Story = { + render: () => ( +
+ + + + +
+ ), +} + +export const Decorative: Story = { + render: () => ( + + ), + play: async ({ canvas }) => { + await expect(canvas.getByRole('button', { name: 'Loading audio' })).toBeDisabled() + await expect(canvas.queryByRole('progressbar')).not.toBeInTheDocument() + }, +} diff --git a/packages/dify-ui/src/spinner/index.tsx b/packages/dify-ui/src/spinner/index.tsx new file mode 100644 index 00000000000..e5abe2e7809 --- /dev/null +++ b/packages/dify-ui/src/spinner/index.tsx @@ -0,0 +1,65 @@ +'use client' + +import type { ComponentProps } from 'react' +import { Progress as BaseProgress } from '@base-ui/react/progress' +import { cva } from 'class-variance-authority' +import { cn } from '../cn' + +const spinnerVariants = cva('inline-flex shrink-0 text-[#1c64f2]', { + variants: { + size: { + small: 'size-3', + medium: 'size-4', + large: 'size-5', + }, + }, + defaultVariants: { size: 'medium' }, +}) + +type SpinnerAppearanceProps = { + size?: 'small' | 'medium' | 'large' + className?: string +} + +type SpinnerIconProps = SpinnerAppearanceProps + +type SpinnerProps = SpinnerAppearanceProps & + Pick, 'id' | 'aria-describedby'> & + ( + | { 'aria-label': string; 'aria-labelledby'?: never } + | { 'aria-label'?: never; 'aria-labelledby': string } + ) + +function SpinnerIcon({ size = 'medium', className }: SpinnerIconProps) { + return ( + + ) +} + +function Spinner({ size = 'medium', className, ...props }: SpinnerProps) { + return ( + + + + ) +} + +export { Spinner, SpinnerIcon } +export type { SpinnerIconProps, SpinnerProps } diff --git a/packages/dify-ui/src/styles/components.css b/packages/dify-ui/src/styles/components.css index 06547b66320..f889ead1fe8 100644 --- a/packages/dify-ui/src/styles/components.css +++ b/packages/dify-ui/src/styles/components.css @@ -86,3 +86,41 @@ transition: none; } } + +[data-dify-spinner] > rect { + animation: dify-spinner-opacity 2s linear infinite; +} + +[data-dify-spinner] > rect:nth-child(2) { + animation-delay: 0.5s; +} +[data-dify-spinner] > rect:nth-child(3) { + animation-delay: 1s; +} +[data-dify-spinner] > rect:nth-child(4) { + animation-delay: 1.5s; +} + +@keyframes dify-spinner-opacity { + 0% { + opacity: 0; + } + 25% { + opacity: 0.1; + } + 50% { + opacity: 0.2; + } + 75% { + opacity: 0.5; + } + 100% { + opacity: 1; + } +} + +@media (prefers-reduced-motion: reduce) { + [data-dify-spinner] > rect { + animation: none; + } +} diff --git a/packages/dify-ui/src/toast/__tests__/index.spec.tsx b/packages/dify-ui/src/toast/__tests__/index.spec.tsx index 8c690c134dd..5428a446bc0 100644 --- a/packages/dify-ui/src/toast/__tests__/index.spec.tsx +++ b/packages/dify-ui/src/toast/__tests__/index.spec.tsx @@ -1,6 +1,45 @@ +import type { ToastManager, ToastViewportProps } from '../index' import { userEvent } from 'vite-plus/test/browser' import { render } from 'vitest-browser-react' -import { createToast, createToastManager, toast, ToastHost } from '../index' +import { + createToast, + createToastManager, + ToastCard, + ToastPortal, + ToastProvider, + ToastViewport, + useToastManager, +} from '../index' + +function ToastCards() { + const { toasts } = useToastManager>() + return toasts.map((item) => ) +} + +function ExampleToastHost({ + manager, + timeout, + limit, + offset, +}: { + manager: ToastManager + timeout?: number + limit?: number + offset?: ToastViewportProps['offset'] +}) { + return ( + + + + + + + + ) +} + +const manager = createToastManager() +const toast = createToast(manager) const asHTMLElement = (element: HTMLElement | SVGElement) => element as HTMLElement @@ -22,7 +61,7 @@ describe('@langgenius/dify-ui/toast', () => { }) it('should render a success toast when called through the typed shortcut', async () => { - const screen = await render() + const screen = await render() toast.success('Saved', { description: 'Your changes are available now.', @@ -35,7 +74,7 @@ describe('@langgenius/dify-ui/toast', () => { }) it('should keep multiple toast roots mounted in a collapsed stack', async () => { - const screen = await render() + const screen = await render() toast('First toast') await expect.element(screen.getByText('First toast')).toBeInTheDocument() @@ -63,7 +102,7 @@ describe('@langgenius/dify-ui/toast', () => { > Underlying action - + , ) @@ -130,7 +169,7 @@ describe('@langgenius/dify-ui/toast', () => { height: 20, }} /> - + , ) @@ -190,7 +229,7 @@ describe('@langgenius/dify-ui/toast', () => { height: 20, }} /> - + , ) @@ -220,7 +259,7 @@ describe('@langgenius/dify-ui/toast', () => { }) it('should render a neutral toast when called directly', async () => { - const screen = await render() + const screen = await render() toast('Neutral toast') @@ -232,8 +271,8 @@ describe('@langgenius/dify-ui/toast', () => { const localToast = createToast(localManager) const screen = await render( <> - - + + , ) @@ -254,7 +293,7 @@ describe('@langgenius/dify-ui/toast', () => { it('should apply custom positioning to the viewport', async () => { const localManager = createToastManager() const localToast = createToast(localManager) - const screen = await render() + const screen = await render() localToast('Positioned viewport') @@ -268,7 +307,7 @@ describe('@langgenius/dify-ui/toast', () => { }) it('should mark overflow toasts as limited when the stack exceeds the configured limit', async () => { - const screen = await render() + const screen = await render() toast('First toast') toast('Second toast') @@ -278,7 +317,7 @@ describe('@langgenius/dify-ui/toast', () => { }) it('should dismiss a toast when dismiss(id) is called', async () => { - const screen = await render() + const screen = await render() const toastId = toast('Closable', { description: 'This toast can be removed.', @@ -295,7 +334,7 @@ describe('@langgenius/dify-ui/toast', () => { it('should close a toast when the dismiss button is clicked', async () => { const onClose = vi.fn() - const screen = await render() + const screen = await render() toast('Dismiss me', { description: 'Manual dismissal path.', @@ -352,7 +391,7 @@ describe('@langgenius/dify-ui/toast', () => { > Underlying action - + , ) @@ -397,7 +436,7 @@ describe('@langgenius/dify-ui/toast', () => { }) it('should pass the host timeout to added toasts', async () => { - const screen = await render() + const screen = await render() toast('Auto dismiss') await expect.element(screen.getByText('Auto dismiss')).toBeInTheDocument() @@ -413,7 +452,7 @@ describe('@langgenius/dify-ui/toast', () => { }) it('should keep a toast persistent when its timeout is zero', async () => { - const screen = await render() + const screen = await render() toast('Persistent', { timeout: 0, @@ -426,7 +465,7 @@ describe('@langgenius/dify-ui/toast', () => { }) it('should update an existing toast', async () => { - const screen = await render() + const screen = await render() const toastId = toast.info('Loading', { description: 'Preparing your data…', @@ -445,7 +484,7 @@ describe('@langgenius/dify-ui/toast', () => { }) it('should upsert an existing toast when add is called with the same id', async () => { - const screen = await render() + const screen = await render() toast('Draft saving', { id: 'draft-save-status', @@ -468,7 +507,7 @@ describe('@langgenius/dify-ui/toast', () => { it('should render and invoke toast action props', async () => { const onAction = vi.fn() - const screen = await render() + const screen = await render() toast('Action toast', { actionProps: { @@ -484,7 +523,7 @@ describe('@langgenius/dify-ui/toast', () => { }) it('should transition a promise toast from loading to success', async () => { - const screen = await render() + const screen = await render() let resolvePromise: ((value: string) => void) | undefined const promise = new Promise((resolve) => { diff --git a/packages/dify-ui/src/toast/index.stories.tsx b/packages/dify-ui/src/toast/index.stories.tsx index 2a5e480e0cb..331d05d550d 100644 --- a/packages/dify-ui/src/toast/index.stories.tsx +++ b/packages/dify-ui/src/toast/index.stories.tsx @@ -1,9 +1,48 @@ import type { Meta, StoryObj } from '@storybook/react-vite' +import type { ToastManager, ToastViewportProps } from '.' import * as React from 'react' import { expect, within } from 'storybook/test' -import { toast, ToastHost } from '.' +import { + createToast, + createToastManager, + ToastCard, + ToastPortal, + ToastProvider, + ToastViewport, + useToastManager, +} from '.' import { Button } from '../button' +function ToastCards() { + const { toasts } = useToastManager>() + return toasts.map((item) => ) +} + +function ExampleToastHost({ + manager, + timeout, + limit, + offset, +}: { + manager: ToastManager + timeout?: number + limit?: number + offset?: ToastViewportProps['offset'] +}) { + return ( + + + + + + + + ) +} + +const manager = createToastManager() +const toast = createToast(manager) + const longToastTitle = 'operation error S3: PutObject, exceeded maximum number of attempts, 3, StatusCode: 0, RequestID: , HostID: , request send failed' const longToastDescription = @@ -329,7 +368,7 @@ const UpdateExamples = () => { const ToastDocsDemo = () => { return ( - +
diff --git a/packages/dify-ui/src/toast/index.tsx b/packages/dify-ui/src/toast/index.tsx index c020fbd607a..b0b11616889 100644 --- a/packages/dify-ui/src/toast/index.tsx +++ b/packages/dify-ui/src/toast/index.tsx @@ -79,13 +79,23 @@ type ToastPromiseOptions = { error: ToastPromiseResultOption } -type ToastHostProps = { - timeout?: number - limit?: number - manager?: ToastManager - offset?: ToastHostOffset +type ToastCardProps = { + toast: ToastObject + /** Additional content composed inside the card. */ + children?: React.ReactNode } +type ToastViewportProps = { + offset?: ToastHostOffset + children: React.ReactNode +} + +const ToastProvider = BaseToast.Provider +type ToastProviderProps = BaseToast.Provider.Props +const ToastPortal = BaseToast.Portal +type ToastPortalProps = BaseToast.Portal.Props +const useToastManager = BaseToast.useToastManager + type ToastHostOffset = Pick type ToastDismiss = (toastId?: string) => void @@ -161,9 +171,6 @@ function createToastManager(): ToastManager { return BaseToast.createToastManager() } -const defaultToastManager = createToastManager() -const toast = createToast(defaultToastManager) - function ToastIcon({ type }: { type?: ToastRenderType }) { return type ? (
)}
+ {children}
}) { ) } -function ToastViewport({ offset }: { offset?: ToastHostOffset }) { - const { toasts } = BaseToast.useToastManager() - +function ToastViewport({ offset, children }: ToastViewportProps) { return ( - {toasts.map((toastItem) => ( - - ))} + {children} ) } -function ToastHost({ timeout, limit, manager = defaultToastManager, offset }: ToastHostProps) { - return ( - - - - - - ) +export { + createToast, + createToastManager, + ToastCard, + ToastPortal, + ToastProvider, + ToastViewport, + useToastManager, } -export { createToast, createToastManager, toast, ToastHost } - -export type { ToastHostProps } +export type { + ToastApi, + ToastCardProps, + ToastManager, + ToastPortalProps, + ToastProviderProps, + ToastViewportProps, +} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 5d762e4b8a9..9653c66478b 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -185,9 +185,6 @@ catalogs: '@egoist/tailwindcss-icons': specifier: 1.9.2 version: 1.9.2 - '@emoji-mart/data': - specifier: 1.2.1 - version: 1.2.1 '@eslint-community/eslint-plugin-eslint-comments': specifier: 4.8.1 version: 4.8.1 @@ -464,9 +461,6 @@ catalogs: embla-carousel-react: specifier: 8.6.0 version: 8.6.0 - emoji-mart: - specifier: 5.6.0 - version: 5.6.0 es-toolkit: specifier: 1.52.0 version: 1.52.0 @@ -536,6 +530,9 @@ catalogs: foxact: specifier: 0.3.11 version: 0.3.11 + frimousse: + specifier: 0.3.0 + version: 0.3.0 fuse.js: specifier: 7.5.0 version: 7.5.0 @@ -1360,9 +1357,6 @@ importers: '@base-ui/react': specifier: 'catalog:' version: 1.8.0(@types/react@19.3.0)(react-dom@19.3.0(react@19.3.0))(react@19.3.0) - '@emoji-mart/data': - specifier: 'catalog:' - version: 1.2.1 '@floating-ui/react': specifier: 'catalog:' version: 0.27.20(react-dom@19.3.0(react@19.3.0))(react@19.3.0) @@ -1486,9 +1480,6 @@ importers: embla-carousel-react: specifier: 'catalog:' version: 8.6.0(react@19.3.0) - emoji-mart: - specifier: 'catalog:' - version: 5.6.0 es-toolkit: specifier: 'catalog:' version: 1.52.0 @@ -1498,6 +1489,9 @@ importers: foxact: specifier: 'catalog:' version: 0.3.11(react-dom@19.3.0(react@19.3.0))(react@19.3.0) + frimousse: + specifier: 'catalog:' + version: 0.3.0(@typescript/typescript6@6.0.2)(react@19.3.0) fuse.js: specifier: 'catalog:' version: 7.5.0 @@ -2177,9 +2171,6 @@ packages: '@emnapi/wasi-threads@1.2.2': resolution: {integrity: sha512-c95qOXkHdydNKhscBTebqEC1CVAZpyqOfVfBzQ1qgzyl3gfeldUjIggDbIZgDKsHLgnsM+igH7TJ/eAasaVuMA==} - '@emoji-mart/data@1.2.1': - resolution: {integrity: sha512-no2pQMWiBy6gpBEiqGeU77/bFejDqUTRY7KX+0+iur13op3bqUsXdnwoZs6Xb1zbv0gAj5VvS1PWoUUckSr5Dw==} - '@es-joy/jsdoccomment@0.92.0': resolution: {integrity: sha512-WivZqV5jPTYDQJgIMp0hTsyQESKYNY6yCJr4b0l84tYtQntkAMkc6zARh2vlRQyeg4aYHPUkCUDpvr2IWAiGpQ==} engines: {node: ^22.22.2 || >=24.15.0} @@ -6346,9 +6337,6 @@ packages: embla-carousel@8.6.0: resolution: {integrity: sha512-SjWyZBHJPbqxHOzckOfo8lHisEaJWmwd23XppYFYVh10bU66/Pn5tkVkbkCMZVdbUE5eTCI2nD8OyIP4Z+uwkA==} - emoji-mart@5.6.0: - resolution: {integrity: sha512-eJp3QRe79pjwa+duv+n7+5YsNhRcMl812EcFVwrnRvYKoNPoQb5qxU8DG6Bgwji0akHdp6D4Ln6tYLG58MFSow==} - emoji-regex-xs@2.0.1: resolution: {integrity: sha512-1QFuh8l7LqUcKe24LsPUNzjrzJQ7pgRwp1QMcZ5MX6mFplk2zQ08NVCM84++1cveaUUYtcCYHmeFEuNg16sU4g==} engines: {node: '>=10.0.0'} @@ -6810,6 +6798,15 @@ packages: react-dom: optional: true + frimousse@0.3.0: + resolution: {integrity: sha512-kO6LMoKY/cLAYEhXXtqLRaLIE6L/DagpFPrUZaLv3LsUa1/8Iza3HhwZcgN8eZ+weXnhv69eoclNUPohcCa/IQ==} + peerDependencies: + react: ^18 || ^19 + typescript: '>=5.1.0' + peerDependenciesMeta: + typescript: + optional: true + fs-constants@1.0.0: resolution: {integrity: sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==} @@ -10193,8 +10190,6 @@ snapshots: tslib: 2.8.1 optional: true - '@emoji-mart/data@1.2.1': {} - '@es-joy/jsdoccomment@0.92.0': dependencies: '@types/estree': 1.0.9 @@ -13962,8 +13957,6 @@ snapshots: embla-carousel@8.6.0: {} - emoji-mart@5.6.0: {} - emoji-regex-xs@2.0.1: {} empathic@2.0.1: {} @@ -14584,6 +14577,12 @@ snapshots: react: 19.3.0 react-dom: 19.3.0(react@19.3.0) + frimousse@0.3.0(@typescript/typescript6@6.0.2)(react@19.3.0): + dependencies: + react: 19.3.0 + optionalDependencies: + typescript: '@typescript/typescript6@6.0.2' + fs-constants@1.0.0: optional: true @@ -18102,7 +18101,6 @@ time: '@chromatic-com/storybook@5.3.1': '2026-09-02T13:43:27.595Z' '@cucumber/cucumber@13.2.1': '2026-08-04T07:20:28.681Z' '@egoist/tailwindcss-icons@1.9.2': '2026-01-31T10:48:44.594Z' - '@emoji-mart/data@1.2.1': '2024-04-25T15:36:14.556Z' '@eslint-community/eslint-plugin-eslint-comments@4.8.1': '2026-09-12T22:48:47.148Z' '@eslint-react/eslint-plugin@5.19.1': '2026-09-14T17:56:33.726Z' '@eslint/markdown@8.0.3': '2026-07-01T13:28:44.339Z' @@ -18196,7 +18194,6 @@ time: embla-carousel-autoplay@8.6.0: '2025-04-04T17:37:46.303Z' embla-carousel-fade@8.6.0: '2025-04-04T17:37:50.278Z' embla-carousel-react@8.6.0: '2025-04-04T17:37:53.976Z' - emoji-mart@5.6.0: '2024-04-25T14:22:21.440Z' es-toolkit@1.52.0: '2026-08-28T07:22:17.687Z' eslint-config-flat-gitignore@2.4.0: '2026-08-27T07:24:43.063Z' eslint-markdown@0.17.0: '2026-09-16T06:20:36.564Z' @@ -18220,6 +18217,7 @@ time: eventsource-parser@4.1.1: '2026-09-15T18:30:29.195Z' fast-deep-equal@3.1.3: '2020-06-08T07:27:28.474Z' foxact@0.3.11: '2026-09-05T12:25:54.102Z' + frimousse@0.3.0: '2025-07-15T10:44:32.958Z' fuse.js@7.5.0: '2026-07-13T17:23:36.705Z' happy-dom@20.14.5: '2026-09-12T00:07:47.688Z' hast-util-to-jsx-runtime@2.3.6: '2025-03-05T11:30:29.166Z' diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index e6160a92379..04ff09c4ba0 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -68,7 +68,6 @@ catalog: '@chromatic-com/storybook': 5.3.1 '@cucumber/cucumber': 13.2.1 '@egoist/tailwindcss-icons': 1.9.2 - '@emoji-mart/data': 1.2.1 '@eslint-community/eslint-plugin-eslint-comments': 4.8.1 '@eslint-react/eslint-plugin': 5.19.1 '@eslint/markdown': 8.0.3 @@ -162,7 +161,6 @@ catalog: embla-carousel-autoplay: 8.6.0 embla-carousel-fade: 8.6.0 embla-carousel-react: 8.6.0 - emoji-mart: 5.6.0 es-toolkit: 1.52.0 eslint: 10.10.0 eslint-config-flat-gitignore: 2.4.0 @@ -186,6 +184,7 @@ catalog: eventsource-parser: 4.1.1 fast-deep-equal: 3.1.3 foxact: 0.3.11 + frimousse: 0.3.0 fuse.js: 7.5.0 happy-dom: 20.14.5 hast-util-to-jsx-runtime: 2.3.6 diff --git a/vite.config.ts b/vite.config.ts index 8d47f7528bb..dee3d7eccca 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -40,6 +40,8 @@ const generatedIgnores = [ 'web/public/embed.min.js', 'web/public/pdf.worker.min.mjs', 'web/public/vs/**', + // Vendored Emojibase JSON is served verbatim. + 'web/public/emoji/emojibase-*/**', ] const formatterUnstableInputs = ['web/app/components/develop/template/*.mdx'] diff --git a/web/.storybook/main.ts b/web/.storybook/main.ts index e5bf0ee65e4..d493da8194b 100644 --- a/web/.storybook/main.ts +++ b/web/.storybook/main.ts @@ -4,6 +4,7 @@ const config: StorybookConfig = { stories: [ '../app/components/**/*.stories.@(js|jsx|mjs|ts|tsx)', '../features/**/*.stories.@(js|jsx|mjs|ts|tsx)', + '../app/notifications/**/*.stories.@(js|jsx|mjs|ts|tsx)', ], addons: [ // Not working with Storybook Vite framework diff --git a/web/.storybook/preview.tsx b/web/.storybook/preview.tsx index b9da6f7c6af..f91a9f14ba1 100644 --- a/web/.storybook/preview.tsx +++ b/web/.storybook/preview.tsx @@ -1,10 +1,10 @@ import type { Decorator, Preview } from '@storybook/react' import type { Resource } from 'i18next' -import { ToastHost } from '@langgenius/dify-ui/toast' import { withThemeByDataAttribute } from '@storybook/addon-themes' import { QueryClient, QueryClientProvider } from '@tanstack/react-query' +import { AppToastHost } from '@/app/notifications/host' import { I18nClientProvider as I18N } from '../app/components/provider/i18n' -import commonEnUS from '../i18n/en-US/common.json' +import commonEnUS from '../i18n/locales/en-US/common.json' import '../app/styles/markdown.css' import './storybook.css' @@ -38,7 +38,7 @@ export const decorators: Decorator[] = [ <> - + diff --git a/web/AGENTS.md b/web/AGENTS.md index dc59e66c9be..c4226a6fdba 100644 --- a/web/AGENTS.md +++ b/web/AGENTS.md @@ -10,7 +10,7 @@ Web owns application-specific requirements and consumes shared architecture guid - For truncated text disclosure and native `title` decisions, follow [Truncated Text Disclosure]. - When adding or changing page layouts, navigation, or landmark elements, follow [Page landmarks] and inspect the composed page, including parent layouts. -- User-facing strings must use `web/i18n/en-US/` keys. When adding or renaming a key, update every supported locale with the correct localized value. +- User-facing strings must use `web/i18n/locales/en-US/` keys. When adding or renaming a key, update every supported locale with the correct localized value. - For new backend calls and migrated surfaces, use generated `consoleQuery` / `consoleClient` APIs from `@/service/console`. Do not add handwritten REST helpers or DTO mirrors, mock-backed app state, or direct edits to generated contracts. - Prefer `@langgenius/dify-ui/*` primitives, data attributes, and design tokens. Use the [Dify UI package index] to find a primitive; read the relevant contract directly when it is already known. Preserve a visible focus indicator on the final focusable element. - Reuse the Web `SearchInput` composite when its search, clear, and IME contract matches the feature; otherwise follow the canonical [Input Group contract]. diff --git a/web/__tests__/app-star-i18n.test.ts b/web/__tests__/app-star-i18n.test.ts index 31d60ce7f7a..93685b27315 100644 --- a/web/__tests__/app-star-i18n.test.ts +++ b/web/__tests__/app-star-i18n.test.ts @@ -1,7 +1,7 @@ import fs from 'node:fs' import path from 'node:path' -const I18N_DIR = path.join(__dirname, '../i18n') +const I18N_DIR = path.join(__dirname, '../i18n/locales') const REQUIRED_APP_STAR_KEYS = [ 'studio.allApps', diff --git a/web/__tests__/base/chat-flow.test.tsx b/web/__tests__/base/chat-flow.test.tsx index f82ffdad606..7f7f3b2145a 100644 --- a/web/__tests__/base/chat-flow.test.tsx +++ b/web/__tests__/base/chat-flow.test.tsx @@ -186,7 +186,7 @@ describe('Base Chat Flow', () => { const { container } = render() expect(useDocumentTitle).toHaveBeenCalledWith('Chat') - expect(screen.getByRole('status')).toBeInTheDocument() + expect(screen.getByRole('progressbar', { name: 'appApi.loading' })).toBeInTheDocument() expect(container.querySelector('.mobile-chat-shell')).toBeInTheDocument() expect(container.querySelector('.rounded-t-2xl')).toBeInTheDocument() expect(container.querySelector('.rounded-2xl')).not.toBeInTheDocument() diff --git a/web/__tests__/billing/billing-integration.test.tsx b/web/__tests__/billing/billing-integration.test.tsx index cb41580ac00..30efc0f7c86 100644 --- a/web/__tests__/billing/billing-integration.test.tsx +++ b/web/__tests__/billing/billing-integration.test.tsx @@ -67,6 +67,10 @@ vi.mock('@/context/workspace-state', async () => { vi.mock('@/context/i18n', () => ({ useGetLanguage: () => 'en-US', +})) + +vi.mock('#i18n', async (importOriginal) => ({ + ...(await importOriginal()), useLocale: () => 'en-US', })) diff --git a/web/__tests__/billing/cloud-plan-payment-flow.test.tsx b/web/__tests__/billing/cloud-plan-payment-flow.test.tsx index 6b2971465a2..66b2e54465f 100644 --- a/web/__tests__/billing/cloud-plan-payment-flow.test.tsx +++ b/web/__tests__/billing/cloud-plan-payment-flow.test.tsx @@ -9,12 +9,13 @@ */ import type { GetBillingSubscriptionData } from '@dify/contracts/api/console/billing/types.gen' import type { CloudPlan } from '@dify/contracts/api/console/features/types.gen' -import { toast, ToastHost } from '@langgenius/dify-ui/toast' import { cleanup, screen, waitFor } from '@testing-library/react' import userEvent from '@testing-library/user-event' import * as React from 'react' import { ALL_PLANS } from '@/app/components/billing/config' import { CloudPlanItem } from '@/app/components/billing/pricing/plans/cloud-plan-item' +import { toast } from '@/app/notifications' +import { AppToastHost } from '@/app/notifications/host' import { createConsoleQueryWrapper } from '@/test/console/query-data' import { render } from '@/test/console/render' @@ -86,7 +87,7 @@ const renderCloudPlanItem = ({ const { wrapper } = createConsoleQueryWrapper() return render( <> - + { }) vi.mock('@/context/i18n', () => ({ useGetLanguage: () => 'en-US', +})) + +vi.mock('#i18n', async (importOriginal) => ({ + ...(await importOriginal()), useLocale: () => 'en-US', })) diff --git a/web/__tests__/i18n-upload-features.test.ts b/web/__tests__/i18n-upload-features.test.ts index 50aef054f0c..bd42227bb52 100644 --- a/web/__tests__/i18n-upload-features.test.ts +++ b/web/__tests__/i18n-upload-features.test.ts @@ -7,7 +7,7 @@ import fs from 'node:fs' import path from 'node:path' // Get all supported locales from the i18n directory -const I18N_DIR = path.join(__dirname, '../i18n') +const I18N_DIR = path.join(__dirname, '../i18n/locales') const getSupportedLocales = (): string[] => { return fs .readdirSync(I18N_DIR) diff --git a/web/app/(commonLayout)/app/(appDetailLayout)/[appId]/access-config/page.tsx b/web/app/(commonLayout)/app/(appDetailLayout)/[appId]/access-config/page.tsx index 6a111ee77d1..92dcff4064a 100644 --- a/web/app/(commonLayout)/app/(appDetailLayout)/[appId]/access-config/page.tsx +++ b/web/app/(commonLayout)/app/(appDetailLayout)/[appId]/access-config/page.tsx @@ -1,4 +1,4 @@ -import type { Locale } from '@/i18n-config' +import type { Locale } from '@/i18n' import AppAccessConfigPage from '@/app/components/app/access-config' export type AccessConfigPageProps = { diff --git a/web/app/(commonLayout)/app/(appDetailLayout)/[appId]/layout-main.tsx b/web/app/(commonLayout)/app/(appDetailLayout)/[appId]/layout-main.tsx index e37f124dd9a..4df033cbffe 100644 --- a/web/app/(commonLayout)/app/(appDetailLayout)/[appId]/layout-main.tsx +++ b/web/app/(commonLayout)/app/(appDetailLayout)/[appId]/layout-main.tsx @@ -9,7 +9,7 @@ import { useEffect, useState } from 'react' import { useTranslation } from 'react-i18next' import { useShallow } from 'zustand/react/shallow' import { useStore } from '@/app/components/app/store' -import Loading from '@/app/components/base/loading' +import { LoadingPlaceholder } from '@/app/components/base/loading-placeholder' import { workspacePermissionKeysAtom, workspacePermissionKeysLoadingAtom, @@ -218,7 +218,7 @@ const AppDetailLayout: FC = (props) => { const content = !appDetail || shouldBlockAgentResourceAccess || shouldBlockAccessPointAccess ? (
- +
) : (
({ updateTracingStatus: vi.fn(), })) -vi.mock('@langgenius/dify-ui/toast', () => ({ +vi.mock('@/app/notifications', () => ({ toast: vi.fn(), })) diff --git a/web/app/(commonLayout)/app/(appDetailLayout)/[appId]/overview/tracing/__tests__/provider-config-modal.spec.tsx b/web/app/(commonLayout)/app/(appDetailLayout)/[appId]/overview/tracing/__tests__/provider-config-modal.spec.tsx index 47880f0e3fd..c9adb3a699f 100644 --- a/web/app/(commonLayout)/app/(appDetailLayout)/[appId]/overview/tracing/__tests__/provider-config-modal.spec.tsx +++ b/web/app/(commonLayout)/app/(appDetailLayout)/[appId]/overview/tracing/__tests__/provider-config-modal.spec.tsx @@ -10,9 +10,9 @@ import type { TencentConfig, WeaveConfig, } from '../type' -import { toast } from '@langgenius/dify-ui/toast' import { render, screen, waitFor } from '@testing-library/react' import userEvent from '@testing-library/user-event' +import { toast } from '@/app/notifications' import { addTracingConfig, removeTracingConfig, updateTracingConfig } from '@/service/apps' import ConfigBtn from '../config-button' import ProviderConfigModal from '../provider-config-modal' @@ -24,7 +24,7 @@ vi.mock('@/service/apps', () => ({ updateTracingConfig: vi.fn(), })) -vi.mock('@langgenius/dify-ui/toast', () => ({ +vi.mock('@/app/notifications', () => ({ toast: vi.fn(), })) diff --git a/web/app/(commonLayout)/app/(appDetailLayout)/[appId]/overview/tracing/panel.tsx b/web/app/(commonLayout)/app/(appDetailLayout)/[appId]/overview/tracing/panel.tsx index 10a09d26416..254eb41736c 100644 --- a/web/app/(commonLayout)/app/(appDetailLayout)/[appId]/overview/tracing/panel.tsx +++ b/web/app/(commonLayout)/app/(appDetailLayout)/[appId]/overview/tracing/panel.tsx @@ -16,7 +16,6 @@ import type { import type { TracingStatus } from '@/models/app' import { cn } from '@langgenius/dify-ui/cn' import { StatusDot } from '@langgenius/dify-ui/status-dot' -import { toast } from '@langgenius/dify-ui/toast' import { useSuspenseQuery } from '@tanstack/react-query' import { useBoolean } from 'ahooks' import { useAtomValue } from 'jotai' @@ -37,7 +36,8 @@ import { TencentIcon, WeaveIcon, } from '@/app/components/base/icons/src/public/tracing' -import Loading from '@/app/components/base/loading' +import { LoadingPlaceholder } from '@/app/components/base/loading-placeholder' +import { toast } from '@/app/notifications' import { workspacePermissionKeysAtom } from '@/context/permission-state' import { userProfileQueryOptions } from '@/features/account-profile/client' import { usePathname } from '@/next/navigation' @@ -257,7 +257,7 @@ const Panel: FC = () => { return (
- +
) diff --git a/web/app/(commonLayout)/app/(appDetailLayout)/[appId]/overview/tracing/provider-config-modal.tsx b/web/app/(commonLayout)/app/(appDetailLayout)/[appId]/overview/tracing/provider-config-modal.tsx index d2d05f8d898..a97de487bfc 100644 --- a/web/app/(commonLayout)/app/(appDetailLayout)/[appId]/overview/tracing/provider-config-modal.tsx +++ b/web/app/(commonLayout)/app/(appDetailLayout)/[appId]/overview/tracing/provider-config-modal.tsx @@ -23,7 +23,6 @@ import { } from '@langgenius/dify-ui/alert-dialog' import { Button } from '@langgenius/dify-ui/button' import { Dialog, DialogContent } from '@langgenius/dify-ui/dialog' -import { toast } from '@langgenius/dify-ui/toast' import { useBoolean } from 'ahooks' import * as React from 'react' import { useCallback, useState } from 'react' @@ -31,6 +30,7 @@ import { useTranslation } from 'react-i18next' import Divider from '@/app/components/base/divider' import { LinkExternal02 } from '@/app/components/base/icons/src/vender/line/general' import { Lock01 } from '@/app/components/base/icons/src/vender/solid/security' +import { toast } from '@/app/notifications' import { addTracingConfig, removeTracingConfig, updateTracingConfig } from '@/service/apps' import { docURL } from './config' import Field from './field' diff --git a/web/app/(commonLayout)/datasets/(datasetDetailLayout)/[datasetId]/layout-main.tsx b/web/app/(commonLayout)/datasets/(datasetDetailLayout)/[datasetId]/layout-main.tsx index 8a304f946c7..1a181ac2511 100644 --- a/web/app/(commonLayout)/datasets/(datasetDetailLayout)/[datasetId]/layout-main.tsx +++ b/web/app/(commonLayout)/datasets/(datasetDetailLayout)/[datasetId]/layout-main.tsx @@ -7,7 +7,7 @@ import { useAtomValue } from 'jotai' import * as React from 'react' import { useEffect } from 'react' import { useTranslation } from 'react-i18next' -import Loading from '@/app/components/base/loading' +import { LoadingPlaceholder } from '@/app/components/base/loading-placeholder' import DatasetDetailContext from '@/context/dataset-detail' import { workspacePermissionKeysAtom, @@ -153,7 +153,7 @@ const DatasetDetailLayout: FC = (props) => { shouldRedirectUnauthorizedRoute const documentTitleOwnedByChild = isDocumentDetailPath(pathname) && !shouldShowLoading const content = shouldShowLoading ? ( - + ) : (
({})) -vi.mock('@/i18n-config/server', async (importOriginal) => ({ - ...(await importOriginal()), +vi.mock('@/i18n/server', async (importOriginal) => ({ + ...(await importOriginal()), getLocaleOnServer: async () => 'en-US', })) diff --git a/web/app/(commonLayout)/datasets/layout.spec.tsx b/web/app/(commonLayout)/datasets/layout.spec.tsx index 5821350e393..7a3dff397b9 100644 --- a/web/app/(commonLayout)/datasets/layout.spec.tsx +++ b/web/app/(commonLayout)/datasets/layout.spec.tsx @@ -71,7 +71,7 @@ describe('DatasetsLayout', () => { , ) - expect(screen.getByRole('status')).toBeInTheDocument() + expect(screen.getByRole('progressbar', { name: 'appApi.loading' })).toBeInTheDocument() expect(screen.queryByText('datasets')).not.toBeInTheDocument() expect(mockReplace).not.toHaveBeenCalled() }) @@ -88,7 +88,7 @@ describe('DatasetsLayout', () => { , ) - expect(screen.getByRole('status')).toBeInTheDocument() + expect(screen.getByRole('progressbar', { name: 'appApi.loading' })).toBeInTheDocument() expect(screen.queryByText('datasets')).not.toBeInTheDocument() expect(mockReplace).not.toHaveBeenCalled() }) diff --git a/web/app/(commonLayout)/datasets/layout.tsx b/web/app/(commonLayout)/datasets/layout.tsx index 4cd87ffb8bb..9707df20008 100644 --- a/web/app/(commonLayout)/datasets/layout.tsx +++ b/web/app/(commonLayout)/datasets/layout.tsx @@ -2,7 +2,7 @@ import { useAtomValue } from 'jotai' import { useEffect } from 'react' -import Loading from '@/app/components/base/loading' +import { LoadingPlaceholder } from '@/app/components/base/loading-placeholder' import { workspacePermissionKeysAtom, workspacePermissionKeysLoadingAtom, @@ -52,7 +52,7 @@ export default function DatasetsLayout({ children }: { children: React.ReactNode if (shouldRedirectToDatasets) router.replace('/datasets') }, [shouldRedirectToDatasets, router]) - if (isLoadingAccess || !currentWorkspaceId) return + if (isLoadingAccess || !currentWorkspaceId) return if (shouldRedirectToDatasets) { return null diff --git a/web/app/(commonLayout)/integrations/[[...slug]]/__tests__/metadata.spec.ts b/web/app/(commonLayout)/integrations/[[...slug]]/__tests__/metadata.spec.ts index de7f7a1db59..9ae932f831b 100644 --- a/web/app/(commonLayout)/integrations/[[...slug]]/__tests__/metadata.spec.ts +++ b/web/app/(commonLayout)/integrations/[[...slug]]/__tests__/metadata.spec.ts @@ -6,8 +6,8 @@ vi.mock('@/app/components/integrations', () => ({ default: () => null, })) -vi.mock('@/i18n-config/server', async (importOriginal) => ({ - ...(await importOriginal()), +vi.mock('@/i18n/server', async (importOriginal) => ({ + ...(await importOriginal()), getLocaleOnServer: async () => 'en-US', })) diff --git a/web/app/(commonLayout)/integrations/[[...slug]]/layout.tsx b/web/app/(commonLayout)/integrations/[[...slug]]/layout.tsx index b584c3e4a03..bf996fa6214 100644 --- a/web/app/(commonLayout)/integrations/[[...slug]]/layout.tsx +++ b/web/app/(commonLayout)/integrations/[[...slug]]/layout.tsx @@ -2,7 +2,7 @@ import type { PropsWithChildren } from 'react' import type { IntegrationSection } from '@/app/components/integrations/routes' import type { Metadata } from '@/next' import { getIntegrationRouteTargetBySlug } from '@/app/components/integrations/routes' -import { getLocaleOnServer, getTranslation } from '@/i18n-config/server' +import { getLocaleOnServer, getTranslation } from '@/i18n/server' type IntegrationsRouteLayoutProps = PropsWithChildren<{ params: Promise<{ diff --git a/web/app/(commonLayout)/layout.tsx b/web/app/(commonLayout)/layout.tsx index b011b432ef8..c2eed0a2818 100644 --- a/web/app/(commonLayout)/layout.tsx +++ b/web/app/(commonLayout)/layout.tsx @@ -1,6 +1,5 @@ import * as React from 'react' import Zendesk from '@/app/components/base/zendesk' -import MaintenanceNotice from '@/app/components/header/maintenance-notice' import MainNavLayout from '@/app/components/main-nav/server' import { CommonLayoutGlobalMounts } from './global-mounts' import { ConsoleContextProviders, ConsoleRuntimeProviders } from './providers' @@ -16,7 +15,6 @@ export default async function Layout({
- {children} diff --git a/web/app/(commonLayout)/marketplace/creator/[uniqueHandle]/page.tsx b/web/app/(commonLayout)/marketplace/creator/[uniqueHandle]/page.tsx index 1c9c5615aa6..6b64be6d96a 100644 --- a/web/app/(commonLayout)/marketplace/creator/[uniqueHandle]/page.tsx +++ b/web/app/(commonLayout)/marketplace/creator/[uniqueHandle]/page.tsx @@ -1,7 +1,7 @@ import { loadCreatorProfile } from '@/app/components/plugins/marketplace/creator-profile/data.server' import DifyCreatorProfile from '@/app/components/plugins/marketplace/creator-profile/dify-profile' import MarketplaceInstallPermissionProvider from '@/app/components/plugins/marketplace/marketplace-install-permission-provider' -import { getLocaleOnServer } from '@/i18n-config/server' +import { getLocaleOnServer } from '@/i18n/server' import { notFound } from '@/next/navigation' type CreatorPageSearchParams = { diff --git a/web/app/(commonLayout)/skills/__tests__/metadata.spec.ts b/web/app/(commonLayout)/skills/__tests__/metadata.spec.ts index 92e3975587c..5a0c1f5f50d 100644 --- a/web/app/(commonLayout)/skills/__tests__/metadata.spec.ts +++ b/web/app/(commonLayout)/skills/__tests__/metadata.spec.ts @@ -3,8 +3,8 @@ import { generateMetadata } from '../page' vi.mock('server-only', () => ({})) -vi.mock('@/i18n-config/server', async (importOriginal) => ({ - ...(await importOriginal()), +vi.mock('@/i18n/server', async (importOriginal) => ({ + ...(await importOriginal()), getLocaleOnServer: async () => 'en-US', })) diff --git a/web/app/(commonLayout)/templates/[[...category]]/__tests__/page.spec.tsx b/web/app/(commonLayout)/templates/[[...category]]/__tests__/page.spec.tsx index 2f1c403ebb0..242a96a7d43 100644 --- a/web/app/(commonLayout)/templates/[[...category]]/__tests__/page.spec.tsx +++ b/web/app/(commonLayout)/templates/[[...category]]/__tests__/page.spec.tsx @@ -63,7 +63,7 @@ vi.mock('@/app/components/plugins/marketplace/templates', () => ({ ), })) -vi.mock('@/i18n-config/server', () => ({ +vi.mock('@/i18n/server', () => ({ getLocaleOnServer: () => Promise.resolve('en-US'), })) diff --git a/web/app/(commonLayout)/templates/[[...category]]/page.tsx b/web/app/(commonLayout)/templates/[[...category]]/page.tsx index 3a4f554086c..3a2b1e1e8a3 100644 --- a/web/app/(commonLayout)/templates/[[...category]]/page.tsx +++ b/web/app/(commonLayout)/templates/[[...category]]/page.tsx @@ -2,7 +2,7 @@ import { MARKETPLACE_CONTAINER_ID } from '@/app/components/plugins/marketplace/c import { EmbeddedTemplatesMarketplace } from '@/app/components/plugins/marketplace/templates' import { TemplateDetailRouteProvider } from '@/app/components/plugins/marketplace/templates/template-detail-route' import { parseTemplatesRoute } from '@/app/components/plugins/marketplace/templates/template-links' -import { getLocaleOnServer } from '@/i18n-config/server' +import { getLocaleOnServer } from '@/i18n/server' import { redirect } from '@/next/navigation' type TemplatesPageProps = { diff --git a/web/app/(humanInputLayout)/form/[token]/__tests__/form.spec.tsx b/web/app/(humanInputLayout)/form/[token]/__tests__/form.spec.tsx index 8d0bdcb4534..4ea39a7a665 100644 --- a/web/app/(humanInputLayout)/form/[token]/__tests__/form.spec.tsx +++ b/web/app/(humanInputLayout)/form/[token]/__tests__/form.spec.tsx @@ -116,9 +116,8 @@ vi.mock('@/app/components/base/chat/chat/answer/human-input-content/expiration-t default: () =>
expiration-time
, })) -vi.mock('@/app/components/base/loading', () => ({ - __esModule: true, - default: () =>
loading
, +vi.mock('@/app/components/base/loading-placeholder', () => ({ + LoadingPlaceholder: () =>
loading
, })) vi.mock('@/app/components/base/logo/dify-logo', () => ({ diff --git a/web/app/(humanInputLayout)/form/[token]/form.tsx b/web/app/(humanInputLayout)/form/[token]/form.tsx index d320bc27ebf..7f533ae16b1 100644 --- a/web/app/(humanInputLayout)/form/[token]/form.tsx +++ b/web/app/(humanInputLayout)/form/[token]/form.tsx @@ -5,7 +5,7 @@ import type { HumanInputFormError } from '@/service/use-share' import type { HumanInputResolvedValue } from '@/types/workflow' import * as React from 'react' import { useTranslation } from 'react-i18next' -import Loading from '@/app/components/base/loading' +import { LoadingPlaceholder } from '@/app/components/base/loading-placeholder' import useDocumentTitle from '@/hooks/use-document-title' import { useParams } from '@/next/navigation' import { useGetHumanInputForm } from '@/service/use-share' @@ -57,7 +57,7 @@ const FormContent = () => { useDocumentTitle(documentTitle) if (isLoading) { - return + return } if (success) { diff --git a/web/app/(shareLayout)/components/authenticated-layout.tsx b/web/app/(shareLayout)/components/authenticated-layout.tsx index 2f052672562..cf6736c6eb2 100644 --- a/web/app/(shareLayout)/components/authenticated-layout.tsx +++ b/web/app/(shareLayout)/components/authenticated-layout.tsx @@ -4,7 +4,7 @@ import * as React from 'react' import { useCallback, useEffect } from 'react' import { useTranslation } from 'react-i18next' import AppUnavailable from '@/app/components/base/app-unavailable' -import Loading from '@/app/components/base/loading' +import { LoadingPlaceholder } from '@/app/components/base/loading-placeholder' import { useWebAppStore } from '@/context/web-app-context' import { usePathname, useRouter, useSearchParams } from '@/next/navigation' import { useGetUserCanAccessApp } from '@/service/access-control/use-app-access-control' @@ -116,7 +116,7 @@ const AuthenticatedLayout = ({ children }: { children: React.ReactNode }) => { ) { return (
- +
) } diff --git a/web/app/(shareLayout)/components/splash.tsx b/web/app/(shareLayout)/components/splash.tsx index eaede783da1..052d71e5829 100644 --- a/web/app/(shareLayout)/components/splash.tsx +++ b/web/app/(shareLayout)/components/splash.tsx @@ -8,7 +8,7 @@ import { resolveWebAppLoginRedirect, } from '@/app/(shareLayout)/webapp-signin/login-redirect' import AppUnavailable from '@/app/components/base/app-unavailable' -import Loading from '@/app/components/base/loading' +import { LoadingPlaceholder } from '@/app/components/base/loading-placeholder' import { useWebAppStore } from '@/context/web-app-context' import { AccessMode } from '@/models/access-control' import { usePathname, useRouter, useSearchParams } from '@/next/navigation' @@ -176,7 +176,7 @@ function Splash({ children }: PropsWithChildren) { if (isLoading) { return (
- +
) } diff --git a/web/app/(shareLayout)/webapp-reset-password/check-code/page.tsx b/web/app/(shareLayout)/webapp-reset-password/check-code/page.tsx index 0e378cd8e2e..e8435f51d9d 100644 --- a/web/app/(shareLayout)/webapp-reset-password/check-code/page.tsx +++ b/web/app/(shareLayout)/webapp-reset-password/check-code/page.tsx @@ -1,12 +1,12 @@ 'use client' import { Button } from '@langgenius/dify-ui/button' import { Input } from '@langgenius/dify-ui/input' -import { toast } from '@langgenius/dify-ui/toast' import { RiArrowLeftLine, RiMailSendFill } from '@remixicon/react' import { useState } from 'react' import { useTranslation } from 'react-i18next' +import { useLocale } from '#i18n' import Countdown from '@/app/components/signin/countdown' -import { useLocale } from '@/context/i18n' +import { toast } from '@/app/notifications' import useDocumentTitle from '@/hooks/use-document-title' import { useRouter, useSearchParams } from '@/next/navigation' import { sendWebAppResetPasswordCode, verifyWebAppResetPasswordCode } from '@/service/common' diff --git a/web/app/(shareLayout)/webapp-reset-password/page.tsx b/web/app/(shareLayout)/webapp-reset-password/page.tsx index 9c0fd0e2b34..7f98a682429 100644 --- a/web/app/(shareLayout)/webapp-reset-password/page.tsx +++ b/web/app/(shareLayout)/webapp-reset-password/page.tsx @@ -1,14 +1,14 @@ 'use client' import { Button } from '@langgenius/dify-ui/button' import { Input } from '@langgenius/dify-ui/input' -import { toast } from '@langgenius/dify-ui/toast' import { RiArrowLeftLine, RiLockPasswordLine } from '@remixicon/react' import { noop } from 'es-toolkit/function' import { useState } from 'react' import { useTranslation } from 'react-i18next' +import { useLocale } from '#i18n' import { COUNT_DOWN_TIME_MS, useSetCountdownLeftTime } from '@/app/components/signin/storage' +import { toast } from '@/app/notifications' import { emailRegex } from '@/config' -import { useLocale } from '@/context/i18n' import useDocumentTitle from '@/hooks/use-document-title' import Link from '@/next/link' import { useRouter, useSearchParams } from '@/next/navigation' diff --git a/web/app/(shareLayout)/webapp-reset-password/set-password/page.tsx b/web/app/(shareLayout)/webapp-reset-password/set-password/page.tsx index 96ce4c1335e..03ee325dcfe 100644 --- a/web/app/(shareLayout)/webapp-reset-password/set-password/page.tsx +++ b/web/app/(shareLayout)/webapp-reset-password/set-password/page.tsx @@ -5,11 +5,11 @@ import { Field, FieldDescription, FieldLabel } from '@langgenius/dify-ui/field' import { Form } from '@langgenius/dify-ui/form' import { IconButton } from '@langgenius/dify-ui/icon-button' import { InputGroup, InputGroupAddon, InputGroupInput } from '@langgenius/dify-ui/input-group' -import { toast } from '@langgenius/dify-ui/toast' import { RiCheckboxCircleFill } from '@remixicon/react' import { useCountDown } from 'ahooks' import { useCallback, useState } from 'react' import { useTranslation } from 'react-i18next' +import { toast } from '@/app/notifications' import { validPassword } from '@/config' import useDocumentTitle from '@/hooks/use-document-title' import Link from '@/next/link' diff --git a/web/app/(shareLayout)/webapp-signin/check-code/page.tsx b/web/app/(shareLayout)/webapp-signin/check-code/page.tsx index f586ae17452..5d113736890 100644 --- a/web/app/(shareLayout)/webapp-signin/check-code/page.tsx +++ b/web/app/(shareLayout)/webapp-signin/check-code/page.tsx @@ -2,15 +2,15 @@ import type { FormEvent } from 'react' import { Button } from '@langgenius/dify-ui/button' import { Input } from '@langgenius/dify-ui/input' -import { toast } from '@langgenius/dify-ui/toast' import { useEffect, useRef, useState } from 'react' import { useTranslation } from 'react-i18next' +import { useLocale } from '#i18n' import { navigateAfterWebAppLogin, resolveWebAppLoginRedirect, } from '@/app/(shareLayout)/webapp-signin/login-redirect' import Countdown from '@/app/components/signin/countdown' -import { useLocale } from '@/context/i18n' +import { toast } from '@/app/notifications' import { useWebAppStore } from '@/context/web-app-context' import { useRouter, useSearchParams } from '@/next/navigation' import { sendWebAppEMailLoginCode, webAppEmailLoginWithCode } from '@/service/common' diff --git a/web/app/(shareLayout)/webapp-signin/components/__tests__/mail-and-code-auth.spec.tsx b/web/app/(shareLayout)/webapp-signin/components/__tests__/mail-and-code-auth.spec.tsx index e0765426bcc..68eed546fc5 100644 --- a/web/app/(shareLayout)/webapp-signin/components/__tests__/mail-and-code-auth.spec.tsx +++ b/web/app/(shareLayout)/webapp-signin/components/__tests__/mail-and-code-auth.spec.tsx @@ -15,7 +15,8 @@ vi.mock('@/next/navigation', () => ({ useSearchParams: () => searchParams, })) -vi.mock('@/context/i18n', () => ({ +vi.mock('#i18n', async (importOriginal) => ({ + ...(await importOriginal()), useLocale: () => 'en-US', })) diff --git a/web/app/(shareLayout)/webapp-signin/components/__tests__/mail-and-password-auth.spec.tsx b/web/app/(shareLayout)/webapp-signin/components/__tests__/mail-and-password-auth.spec.tsx index 4c39b09f9f3..7bf924ba467 100644 --- a/web/app/(shareLayout)/webapp-signin/components/__tests__/mail-and-password-auth.spec.tsx +++ b/web/app/(shareLayout)/webapp-signin/components/__tests__/mail-and-password-auth.spec.tsx @@ -14,7 +14,8 @@ vi.mock('@/next/navigation', () => ({ useSearchParams: () => searchParams, })) -vi.mock('@/context/i18n', () => ({ +vi.mock('#i18n', async (importOriginal) => ({ + ...(await importOriginal()), useLocale: () => 'en-US', })) diff --git a/web/app/(shareLayout)/webapp-signin/components/external-member-sso-auth.tsx b/web/app/(shareLayout)/webapp-signin/components/external-member-sso-auth.tsx index 49b2d932f0f..8e76fcd9d71 100644 --- a/web/app/(shareLayout)/webapp-signin/components/external-member-sso-auth.tsx +++ b/web/app/(shareLayout)/webapp-signin/components/external-member-sso-auth.tsx @@ -1,12 +1,12 @@ 'use client' import { zSsoProtocol } from '@dify/contracts/api/console/system-features/zod.gen' -import { toast } from '@langgenius/dify-ui/toast' import { useSuspenseQuery } from '@tanstack/react-query' import * as React from 'react' import { useCallback, useEffect } from 'react' import { resolveWebAppLoginRedirect } from '@/app/(shareLayout)/webapp-signin/login-redirect' import AppUnavailable from '@/app/components/base/app-unavailable' -import Loading from '@/app/components/base/loading' +import { LoadingPlaceholder } from '@/app/components/base/loading-placeholder' +import { toast } from '@/app/notifications' import { systemFeaturesQueryOptions } from '@/features/system-features/client' import { useRouter, useSearchParams } from '@/next/navigation' import { fetchWebOAuth2SSOUrl, fetchWebOIDCSSOUrl, fetchWebSAMLSSOUrl } from '@/service/share' @@ -69,7 +69,7 @@ const ExternalMemberSSOAuth = () => { return (
- +
) } diff --git a/web/app/(shareLayout)/webapp-signin/components/mail-and-code-auth.tsx b/web/app/(shareLayout)/webapp-signin/components/mail-and-code-auth.tsx index 3906b57fe63..ddbed90e7aa 100644 --- a/web/app/(shareLayout)/webapp-signin/components/mail-and-code-auth.tsx +++ b/web/app/(shareLayout)/webapp-signin/components/mail-and-code-auth.tsx @@ -1,12 +1,12 @@ import { Button } from '@langgenius/dify-ui/button' import { Input } from '@langgenius/dify-ui/input' -import { toast } from '@langgenius/dify-ui/toast' import { useEffect, useState } from 'react' import { useTranslation } from 'react-i18next' +import { useLocale } from '#i18n' import { resolveWebAppLoginRedirect } from '@/app/(shareLayout)/webapp-signin/login-redirect' import { COUNT_DOWN_TIME_MS, useSetCountdownLeftTime } from '@/app/components/signin/storage' +import { toast } from '@/app/notifications' import { emailRegex } from '@/config' -import { useLocale } from '@/context/i18n' import { useRouter, useSearchParams } from '@/next/navigation' import { sendWebAppEMailLoginCode } from '@/service/common' import { getClientLoginFallback } from '@/utils/login-redirect' diff --git a/web/app/(shareLayout)/webapp-signin/components/mail-and-password-auth.tsx b/web/app/(shareLayout)/webapp-signin/components/mail-and-password-auth.tsx index 2fc180f80bb..1d3548a58c4 100644 --- a/web/app/(shareLayout)/webapp-signin/components/mail-and-password-auth.tsx +++ b/web/app/(shareLayout)/webapp-signin/components/mail-and-password-auth.tsx @@ -5,13 +5,13 @@ import { Form } from '@langgenius/dify-ui/form' import { IconButton } from '@langgenius/dify-ui/icon-button' import { Input } from '@langgenius/dify-ui/input' import { InputGroup, InputGroupAddon, InputGroupInput } from '@langgenius/dify-ui/input-group' -import { toast } from '@langgenius/dify-ui/toast' import { useEffect, useState } from 'react' import { useTranslation } from 'react-i18next' import { navigateAfterWebAppLogin, resolveWebAppLoginRedirect, } from '@/app/(shareLayout)/webapp-signin/login-redirect' +import { toast } from '@/app/notifications' import { emailRegex } from '@/config' import { useWebAppStore } from '@/context/web-app-context' import Link from '@/next/link' diff --git a/web/app/(shareLayout)/webapp-signin/components/sso-auth.tsx b/web/app/(shareLayout)/webapp-signin/components/sso-auth.tsx index 73cdffa360f..210a7077e31 100644 --- a/web/app/(shareLayout)/webapp-signin/components/sso-auth.tsx +++ b/web/app/(shareLayout)/webapp-signin/components/sso-auth.tsx @@ -2,11 +2,11 @@ import type { SsoProtocol } from '@dify/contracts/api/console/system-features/types.gen' import { zSsoProtocol } from '@dify/contracts/api/console/system-features/zod.gen' import { Button } from '@langgenius/dify-ui/button' -import { toast } from '@langgenius/dify-ui/toast' import { useEffect, useState } from 'react' import { useTranslation } from 'react-i18next' import { resolveWebAppLoginRedirect } from '@/app/(shareLayout)/webapp-signin/login-redirect' import { Lock01 } from '@/app/components/base/icons/src/vender/solid/security' +import { toast } from '@/app/notifications' import { useRouter, useSearchParams } from '@/next/navigation' import { fetchMembersOAuth2SSOUrl, diff --git a/web/app/(shareLayout)/webapp-signin/normalForm.tsx b/web/app/(shareLayout)/webapp-signin/normalForm.tsx index bfa8906f023..a6c2cd83e8c 100644 --- a/web/app/(shareLayout)/webapp-signin/normalForm.tsx +++ b/web/app/(shareLayout)/webapp-signin/normalForm.tsx @@ -6,7 +6,7 @@ import { useSuspenseQuery } from '@tanstack/react-query' import * as React from 'react' import { useCallback, useEffect, useState } from 'react' import { useTranslation } from 'react-i18next' -import Loading from '@/app/components/base/loading' +import { LoadingPlaceholder } from '@/app/components/base/loading-placeholder' import { systemFeaturesQueryOptions } from '@/features/system-features/client' import Link from '@/next/link' import MailAndCodeAuth from './components/mail-and-code-auth' @@ -55,7 +55,7 @@ const NormalForm = () => {
- +
) } diff --git a/web/app/(shareLayout)/webapp-signin/page.tsx b/web/app/(shareLayout)/webapp-signin/page.tsx index e0a3dc00994..44868ffa39e 100644 --- a/web/app/(shareLayout)/webapp-signin/page.tsx +++ b/web/app/(shareLayout)/webapp-signin/page.tsx @@ -4,7 +4,7 @@ import * as React from 'react' import { useCallback, useEffect, useSyncExternalStore } from 'react' import { useTranslation } from 'react-i18next' import AppUnavailable from '@/app/components/base/app-unavailable' -import Loading from '@/app/components/base/loading' +import { LoadingPlaceholder } from '@/app/components/base/loading-placeholder' import { useWebAppStore } from '@/context/web-app-context' import { systemFeaturesQueryOptions } from '@/features/system-features/client' import { AccessMode } from '@/models/access-control' @@ -54,7 +54,7 @@ function WebSSOForm() { if (!loginRedirect) { return (
- +
) } diff --git a/web/app/__tests__/home-banner.browser.spec.tsx b/web/app/__tests__/home-banner.browser.spec.tsx index 9a3595dba19..c6d4f913e70 100644 --- a/web/app/__tests__/home-banner.browser.spec.tsx +++ b/web/app/__tests__/home-banner.browser.spec.tsx @@ -17,7 +17,8 @@ vi.mock('@/features/account-profile/client', () => ({ userProfileQueryOptions: () => ({}), })) -vi.mock('@/context/i18n', () => ({ +vi.mock('#i18n', async (importOriginal) => ({ + ...(await importOriginal()), useLocale: () => 'en-US', })) diff --git a/web/app/__tests__/layout.spec.tsx b/web/app/__tests__/layout.spec.tsx index cee0bd121ed..9f93408da43 100644 --- a/web/app/__tests__/layout.spec.tsx +++ b/web/app/__tests__/layout.spec.tsx @@ -20,7 +20,7 @@ vi.mock('@/env', async (importOriginal) => { } }) -vi.mock('@/i18n-config/server', () => ({ +vi.mock('@/i18n/server', () => ({ getLocaleOnServer: async () => 'en-US', })) diff --git a/web/app/__tests__/route-metadata.spec.ts b/web/app/__tests__/route-metadata.spec.ts index b9308ee84b8..a85569332f0 100644 --- a/web/app/__tests__/route-metadata.spec.ts +++ b/web/app/__tests__/route-metadata.spec.ts @@ -18,8 +18,8 @@ import { generateMetadata as generateSignupSetPasswordMetadata } from '../signup vi.mock('server-only', () => ({})) -vi.mock('@/i18n-config/server', async (importOriginal) => ({ - ...(await importOriginal()), +vi.mock('@/i18n/server', async (importOriginal) => ({ + ...(await importOriginal()), getLocaleOnServer: async () => 'en-US', })) diff --git a/web/app/account/(commonLayout)/account-page/AvatarWithEdit.tsx b/web/app/account/(commonLayout)/account-page/AvatarWithEdit.tsx index bab0d50d9e4..243a7cd6293 100644 --- a/web/app/account/(commonLayout)/account-page/AvatarWithEdit.tsx +++ b/web/app/account/(commonLayout)/account-page/AvatarWithEdit.tsx @@ -7,13 +7,13 @@ import type { ImageFile } from '@/types/app' import { Avatar } from '@langgenius/dify-ui/avatar' import { Button } from '@langgenius/dify-ui/button' import { Dialog, DialogContent } from '@langgenius/dify-ui/dialog' -import { toast } from '@langgenius/dify-ui/toast' import * as React from 'react' import { useCallback, useState } from 'react' import { useTranslation } from 'react-i18next' import ImageInput from '@/app/components/base/app-icon-picker/ImageInput' import Divider from '@/app/components/base/divider' import { useLocalFileUploader } from '@/app/components/base/image-uploader/hooks' +import { toast } from '@/app/notifications' import { DISABLE_UPLOAD_IMAGE_AS_ICON } from '@/config' import { updateUserProfile } from '@/service/common' import { createAvatarImageFile, createCroppedAvatarImage } from './avatar-image' diff --git a/web/app/account/(commonLayout)/account-page/email-change-modal.tsx b/web/app/account/(commonLayout)/account-page/email-change-modal.tsx index 391a8d36281..5bf5cc29f3e 100644 --- a/web/app/account/(commonLayout)/account-page/email-change-modal.tsx +++ b/web/app/account/(commonLayout)/account-page/email-change-modal.tsx @@ -4,11 +4,11 @@ import { Dialog, DialogContent, DialogTitle } from '@langgenius/dify-ui/dialog' import { Field, FieldError, FieldLabel } from '@langgenius/dify-ui/field' import { IconButton } from '@langgenius/dify-ui/icon-button' import { Input } from '@langgenius/dify-ui/input' -import { toast } from '@langgenius/dify-ui/toast' import { RiCloseLine } from '@remixicon/react' import { useDebounceFn } from 'ahooks' import { useCallback, useEffect, useRef, useState } from 'react' import { Trans, useTranslation } from 'react-i18next' +import { toast } from '@/app/notifications' import { useRouter } from '@/next/navigation' import { checkEmailExisted, resetEmail, sendVerifyCode, verifyEmail } from '@/service/common' import { useLogout } from '@/service/use-common' diff --git a/web/app/account/(commonLayout)/account-page/index.tsx b/web/app/account/(commonLayout)/account-page/index.tsx index 6fa3ee3d882..51177d7de45 100644 --- a/web/app/account/(commonLayout)/account-page/index.tsx +++ b/web/app/account/(commonLayout)/account-page/index.tsx @@ -8,13 +8,13 @@ import { Field, FieldLabel } from '@langgenius/dify-ui/field' import { IconButton } from '@langgenius/dify-ui/icon-button' import { Input } from '@langgenius/dify-ui/input' import { InputGroup, InputGroupAddon, InputGroupInput } from '@langgenius/dify-ui/input-group' -import { toast } from '@langgenius/dify-ui/toast' import { useQuery, useQueryClient, useSuspenseQuery } from '@tanstack/react-query' import { useId, useState } from 'react' import { useTranslation } from 'react-i18next' import AppIcon from '@/app/components/base/app-icon' import PremiumBadge from '@/app/components/base/premium-badge' import Collapse from '@/app/components/header/account-setting/collapse' +import { toast } from '@/app/notifications' import { validPassword } from '@/config' import { userProfileQueryOptions } from '@/features/account-profile/client' import { systemFeaturesQueryOptions } from '@/features/system-features/client' diff --git a/web/app/account/(commonLayout)/delete-account/components/__tests__/feed-back.spec.tsx b/web/app/account/(commonLayout)/delete-account/components/__tests__/feed-back.spec.tsx index 6ee1deb284a..2a5649f7fff 100644 --- a/web/app/account/(commonLayout)/delete-account/components/__tests__/feed-back.spec.tsx +++ b/web/app/account/(commonLayout)/delete-account/components/__tests__/feed-back.spec.tsx @@ -28,7 +28,7 @@ vi.mock('@/service/use-common', () => ({ useLogout: () => ({ mutateAsync: mockLogout }), })) -vi.mock('@langgenius/dify-ui/toast', () => ({ +vi.mock('@/app/notifications', () => ({ toast: { info: vi.fn() }, })) diff --git a/web/app/account/(commonLayout)/delete-account/components/feed-back.tsx b/web/app/account/(commonLayout)/delete-account/components/feed-back.tsx index 8b40a65adec..754bd7285ad 100644 --- a/web/app/account/(commonLayout)/delete-account/components/feed-back.tsx +++ b/web/app/account/(commonLayout)/delete-account/components/feed-back.tsx @@ -4,10 +4,10 @@ import { Dialog, DialogContent, DialogTitle } from '@langgenius/dify-ui/dialog' import { Field, FieldLabel } from '@langgenius/dify-ui/field' import { Form } from '@langgenius/dify-ui/form' import { Textarea } from '@langgenius/dify-ui/textarea' -import { toast } from '@langgenius/dify-ui/toast' import { useSuspenseQuery } from '@tanstack/react-query' import { useCallback } from 'react' import { useTranslation } from 'react-i18next' +import { toast } from '@/app/notifications' import { userProfileQueryOptions } from '@/features/account-profile/client' import { useRouter } from '@/next/navigation' import { useLogout } from '@/service/use-common' diff --git a/web/app/account/(commonLayout)/layout.tsx b/web/app/account/(commonLayout)/layout.tsx index 06ec2ae4e5d..0e323e98798 100644 --- a/web/app/account/(commonLayout)/layout.tsx +++ b/web/app/account/(commonLayout)/layout.tsx @@ -1,7 +1,6 @@ import * as React from 'react' import { ConsoleContextProviders, ConsoleRuntimeProviders } from '@/app/(commonLayout)/providers' import HeaderWrapper from '@/app/components/header/header-wrapper' -import MaintenanceNotice from '@/app/components/header/maintenance-notice' import Header from './header' export default async function Layout({ children }: { children: React.ReactNode }) { @@ -9,7 +8,6 @@ export default async function Layout({ children }: { children: React.ReactNode }
-
diff --git a/web/app/account/oauth/authorize/page.tsx b/web/app/account/oauth/authorize/page.tsx index bbdf7f76188..2f6ef347f92 100644 --- a/web/app/account/oauth/authorize/page.tsx +++ b/web/app/account/oauth/authorize/page.tsx @@ -3,7 +3,6 @@ import { Avatar } from '@langgenius/dify-ui/avatar' import { Button, buttonVariants } from '@langgenius/dify-ui/button' import { cn } from '@langgenius/dify-ui/cn' -import { toast } from '@langgenius/dify-ui/toast' import { RiAccountCircleLine, RiGlobalLine, @@ -14,8 +13,9 @@ import { import { skipToken, useMutation, useQuery } from '@tanstack/react-query' import * as React from 'react' import { useTranslation } from 'react-i18next' -import Loading from '@/app/components/base/loading' +import { LoadingPlaceholder } from '@/app/components/base/loading-placeholder' import { useLanguage } from '@/app/components/header/account-setting/model-provider-page/hooks' +import { toast } from '@/app/notifications' import { isLegacyBase401, userProfileQueryOptions } from '@/features/account-profile/client' import useDocumentTitle from '@/hooks/use-document-title' import Link from '@/next/link' @@ -161,7 +161,7 @@ export default function OAuthAuthorize() { if (isProfileLoading || isOAuthLoading || isAutoAuthorizing) { return (
- +
) } diff --git a/web/app/account/oauth/authorize/use-silent-authorize.ts b/web/app/account/oauth/authorize/use-silent-authorize.ts index c967cd7cfa4..ad561b2e3f6 100644 --- a/web/app/account/oauth/authorize/use-silent-authorize.ts +++ b/web/app/account/oauth/authorize/use-silent-authorize.ts @@ -1,10 +1,10 @@ -import { toast } from '@langgenius/dify-ui/toast' import Cookies from 'js-cookie' import { useEffect, useRef, useState } from 'react' import { useTranslation } from 'react-i18next' import { flushRegistrationSuccess } from '@/app/components/base/amplitude/registration-tracking' import { flushEvents } from '@/app/components/base/amplitude/utils' import { reportOAuthRegistrationIfNewUser } from '@/app/components/oauth-registration-attribution' +import { toast } from '@/app/notifications' import { useRouter } from '@/next/navigation' export function buildReturnUrl(pathname: string, search: string) { diff --git a/web/app/activate/activateForm.tsx b/web/app/activate/activateForm.tsx index 188bc2c5a8a..f60dcf727c9 100644 --- a/web/app/activate/activateForm.tsx +++ b/web/app/activate/activateForm.tsx @@ -3,7 +3,7 @@ import { buttonVariants } from '@langgenius/dify-ui/button' import { cn } from '@langgenius/dify-ui/cn' import { useEffect } from 'react' import { useTranslation } from 'react-i18next' -import Loading from '@/app/components/base/loading' +import { LoadingPlaceholder } from '@/app/components/base/loading-placeholder' import useDocumentTitle from '@/hooks/use-document-title' import { useRouter, useSearchParams } from '@/next/navigation' import { useInvitationCheck } from '@/service/use-common' @@ -52,7 +52,7 @@ const ActivateForm = () => {
- {!checkRes && } + {!checkRes && } {checkRes && !checkRes.is_valid && (
diff --git a/web/app/components/access-rules-editor/__tests__/index.spec.tsx b/web/app/components/access-rules-editor/__tests__/index.spec.tsx index 4a6b498ece5..eed342ae46d 100644 --- a/web/app/components/access-rules-editor/__tests__/index.spec.tsx +++ b/web/app/components/access-rules-editor/__tests__/index.spec.tsx @@ -109,7 +109,7 @@ describe('AccessRulesEditor', () => { />, ) - expect(screen.getByRole('status', { name: 'appApi.loading' })).toBeInTheDocument() + expect(screen.getByRole('progressbar', { name: 'appApi.loading' })).toBeInTheDocument() expect(screen.queryByText('permission.accessRule.noUserAccessSettings')).not.toBeInTheDocument() }) diff --git a/web/app/components/access-rules-editor/add-access-subject-popover.tsx b/web/app/components/access-rules-editor/add-access-subject-popover.tsx index 3175f1fd500..57a541a9bec 100644 --- a/web/app/components/access-rules-editor/add-access-subject-popover.tsx +++ b/web/app/components/access-rules-editor/add-access-subject-popover.tsx @@ -8,7 +8,7 @@ import { Popover, PopoverContent, PopoverTitle, PopoverTrigger } from '@langgeni import { useQuery } from '@tanstack/react-query' import { memo, useCallback, useMemo, useState } from 'react' import { useTranslation } from 'react-i18next' -import Loading from '@/app/components/base/loading' +import { LoadingPlaceholder } from '@/app/components/base/loading-placeholder' import { consoleQuery } from '@/service/console' import { DEFAULT_ACCESS_POLICY_ID } from './constants' @@ -93,9 +93,7 @@ function AddAccessSubjectPopoverContent({
) : isLoading ? ( -
- -
+ ) : availableMembers.length === 0 ? (
{t(($) => $['accessRule.noAvailableMembers'], { ns: 'permission' })} diff --git a/web/app/components/access-rules-editor/index.tsx b/web/app/components/access-rules-editor/index.tsx index 1810c04da43..d37efe99048 100644 --- a/web/app/components/access-rules-editor/index.tsx +++ b/web/app/components/access-rules-editor/index.tsx @@ -10,7 +10,7 @@ import { cn } from '@langgenius/dify-ui/cn' import { Pagination } from '@langgenius/dify-ui/pagination' import { memo, useCallback, useMemo, useState } from 'react' import { useTranslation } from 'react-i18next' -import Loading from '@/app/components/base/loading' +import { LoadingPlaceholder } from '@/app/components/base/loading-placeholder' import { RESOURCE_ACCESS_SETTINGS_PAGE_SIZE_OPTIONS } from '@/service/access-control/constants' import AddAccessSubjectPopover from './add-access-subject-popover' import AutomaticIncludeWorkspaceMembersSection from './automatic-include-workspace-members-section' @@ -303,7 +303,7 @@ function AccessRulesEditor({ colSpan={3} className="flex flex-1 items-center justify-center px-4 py-8 text-center" > - + ) : userAccessSettings.length === 0 ? ( diff --git a/web/app/components/api-key/api-key-modal.tsx b/web/app/components/api-key/api-key-modal.tsx index d9cabd605f2..99884146aef 100644 --- a/web/app/components/api-key/api-key-modal.tsx +++ b/web/app/components/api-key/api-key-modal.tsx @@ -22,7 +22,7 @@ import { skipToken, useMutation, useQuery } from '@tanstack/react-query' import { useAtomValue } from 'jotai' import { useState } from 'react' import { useTranslation } from 'react-i18next' -import Loading from '@/app/components/base/loading' +import { LoadingPlaceholder } from '@/app/components/base/loading-placeholder' import { currentWorkspaceAtom } from '@/context/workspace-state' import { consoleQuery } from '@/service/console' import { ApiKeyTable } from './api-key-table' @@ -211,7 +211,7 @@ export function ApiKeyModal({ open, canManage, scope, onOpenChange }: ApiKeyModa /> {isLoading && (
- +
)} {!!apiKeys?.length && ( diff --git a/web/app/components/app-sidebar/app-info/__tests__/use-app-info-actions.spec.ts b/web/app/components/app-sidebar/app-info/__tests__/use-app-info-actions.spec.ts index a07aae99ba1..cf6b8161f60 100644 --- a/web/app/components/app-sidebar/app-info/__tests__/use-app-info-actions.spec.ts +++ b/web/app/components/app-sidebar/app-info/__tests__/use-app-info-actions.spec.ts @@ -66,7 +66,7 @@ vi.mock('@/app/components/app/use-export-app-dsl', () => ({ }), })) -vi.mock('@langgenius/dify-ui/toast', () => ({ +vi.mock('@/app/notifications', () => ({ toast: Object.assign(toastMocks.api, { success: vi.fn((message, options) => toastMocks.call({ type: 'success', message, ...options })), error: vi.fn((message, options) => toastMocks.call({ type: 'error', message, ...options })), diff --git a/web/app/components/app-sidebar/app-info/use-app-info-actions.ts b/web/app/components/app-sidebar/app-info/use-app-info-actions.ts index 6936882a1b1..116c09ad12b 100644 --- a/web/app/components/app-sidebar/app-info/use-app-info-actions.ts +++ b/web/app/components/app-sidebar/app-info/use-app-info-actions.ts @@ -6,12 +6,12 @@ import type { Dispatch, SetStateAction } from 'react' import type { DuplicateAppModalProps } from '@/app/components/app/duplicate-modal' import type { CreateAppModalProps } from '@/app/components/explore/create-app-modal' import type { App } from '@/types/app' -import { toast } from '@langgenius/dify-ui/toast' import { useMutation, useQueryClient, useSuspenseQuery } from '@tanstack/react-query' import { useCallback, useEffect, useState } from 'react' import { useTranslation } from 'react-i18next' import { useStore as useAppStore } from '@/app/components/app/store' import { useExportAppDsl, useExportWorkflowAppDsl } from '@/app/components/app/use-export-app-dsl' +import { toast } from '@/app/notifications' import { systemFeaturesQueryOptions } from '@/features/system-features/client' import { useRouter } from '@/next/navigation' import { diff --git a/web/app/components/app-sidebar/dataset-info/__tests__/dropdown-callbacks.spec.tsx b/web/app/components/app-sidebar/dataset-info/__tests__/dropdown-callbacks.spec.tsx index 86826918303..91cf28d2b56 100644 --- a/web/app/components/app-sidebar/dataset-info/__tests__/dropdown-callbacks.spec.tsx +++ b/web/app/components/app-sidebar/dataset-info/__tests__/dropdown-callbacks.spec.tsx @@ -129,7 +129,7 @@ vi.mock('@/service/datasets', () => ({ deleteDataset: (...args: unknown[]) => mockDeleteDataset(...args), })) -vi.mock('@langgenius/dify-ui/toast', () => ({ +vi.mock('@/app/notifications', () => ({ toast: { error: (...args: unknown[]) => mockToast(...args), }, diff --git a/web/app/components/app-sidebar/dataset-info/dropdown.tsx b/web/app/components/app-sidebar/dataset-info/dropdown.tsx index bfefae17b7e..a1def0ee008 100644 --- a/web/app/components/app-sidebar/dataset-info/dropdown.tsx +++ b/web/app/components/app-sidebar/dataset-info/dropdown.tsx @@ -17,12 +17,12 @@ import { DropdownMenuTrigger, } from '@langgenius/dify-ui/dropdown-menu' import { IconButton } from '@langgenius/dify-ui/icon-button' -import { toast } from '@langgenius/dify-ui/toast' import { useSuspenseQuery } from '@tanstack/react-query' import { useAtomValue } from 'jotai' import * as React from 'react' import { useCallback, useState } from 'react' import { useTranslation } from 'react-i18next' +import { toast } from '@/app/notifications' import { useDatasetDetailContextWithSelector } from '@/context/dataset-detail' import { workspacePermissionKeysAtom } from '@/context/permission-state' import { userProfileQueryOptions } from '@/features/account-profile/client' diff --git a/web/app/components/app-sidebar/snippet-info/__tests__/dropdown.spec.tsx b/web/app/components/app-sidebar/snippet-info/__tests__/dropdown.spec.tsx index 801d831b6ec..5d921704730 100644 --- a/web/app/components/app-sidebar/snippet-info/__tests__/dropdown.spec.tsx +++ b/web/app/components/app-sidebar/snippet-info/__tests__/dropdown.spec.tsx @@ -37,7 +37,7 @@ vi.mock('@/utils/download', () => ({ downloadBlob: (args: { data: Blob; fileName: string }) => mockDownloadBlob(args), })) -vi.mock('@langgenius/dify-ui/toast', () => ({ +vi.mock('@/app/notifications', () => ({ toast: { success: (...args: unknown[]) => mockToastSuccess(...args), error: (...args: unknown[]) => mockToastError(...args), diff --git a/web/app/components/app-sidebar/snippet-info/dropdown.tsx b/web/app/components/app-sidebar/snippet-info/dropdown.tsx index c1f69f0ff93..6ddc0d0081d 100644 --- a/web/app/components/app-sidebar/snippet-info/dropdown.tsx +++ b/web/app/components/app-sidebar/snippet-info/dropdown.tsx @@ -18,7 +18,6 @@ import { DropdownMenuTrigger, } from '@langgenius/dify-ui/dropdown-menu' import { IconButton } from '@langgenius/dify-ui/icon-button' -import { toast } from '@langgenius/dify-ui/toast' import { useAtomValue } from 'jotai' import * as React from 'react' import { useTranslation } from 'react-i18next' @@ -27,6 +26,7 @@ import { canCreateAndModifySnippets, canManageSnippets, } from '@/app/components/snippets/utils/permission' +import { toast } from '@/app/notifications' import { workspacePermissionKeysAtom } from '@/context/permission-state' import { useRouter } from '@/next/navigation' import { diff --git a/web/app/components/app/__tests__/use-export-app-dsl.spec.tsx b/web/app/components/app/__tests__/use-export-app-dsl.spec.tsx index cd54f987bc9..8dd0e69a450 100644 --- a/web/app/components/app/__tests__/use-export-app-dsl.spec.tsx +++ b/web/app/components/app/__tests__/use-export-app-dsl.spec.tsx @@ -34,7 +34,7 @@ vi.mock('@/utils/download', () => ({ downloadBlob: mocks.downloadBlob, })) -vi.mock('@langgenius/dify-ui/toast', () => ({ +vi.mock('@/app/notifications', () => ({ toast: { error: mocks.toastError, promise: mocks.toastPromise, diff --git a/web/app/components/app/access-config/index.tsx b/web/app/components/app/access-config/index.tsx index 79acb7e7b3c..570b7d850f2 100644 --- a/web/app/components/app/access-config/index.tsx +++ b/web/app/components/app/access-config/index.tsx @@ -5,13 +5,13 @@ import { useSuspenseQuery } from '@tanstack/react-query' import { useAtomValue } from 'jotai' import { useCallback, useMemo, useState } from 'react' import { useTranslation } from 'react-i18next' +import { useLocale } from '#i18n' import AccessRulesEditor from '@/app/components/access-rules-editor' import { useStore } from '@/app/components/app/store' -import { useLocale } from '@/context/i18n' import { workspacePermissionKeysAtom } from '@/context/permission-state' import { userProfileQueryOptions } from '@/features/account-profile/client' import { systemFeaturesQueryOptions } from '@/features/system-features/client' -import { getAccessControlTemplateLanguage } from '@/i18n-config/language' +import { getAccessControlTemplateLanguage } from '@/i18n/language' import { RESOURCE_ACCESS_SETTINGS_PAGE_SIZE } from '@/service/access-control/constants' import { useAppAccessRules, diff --git a/web/app/components/app/access-point/__tests__/environment-access-control.spec.tsx b/web/app/components/app/access-point/__tests__/environment-access-control.spec.tsx index a3e5376086b..57f3405fe6d 100644 --- a/web/app/components/app/access-point/__tests__/environment-access-control.spec.tsx +++ b/web/app/components/app/access-point/__tests__/environment-access-control.spec.tsx @@ -64,7 +64,7 @@ vi.mock('@/service/access-control', () => ({ }), })) -vi.mock('@langgenius/dify-ui/toast', () => ({ +vi.mock('@/app/notifications', () => ({ toast: { error: vi.fn(), success: vi.fn(), diff --git a/web/app/components/app/access-point/__tests__/environment-access-point-cards.spec.tsx b/web/app/components/app/access-point/__tests__/environment-access-point-cards.spec.tsx index 210dd6b189a..fce4ff54df5 100644 --- a/web/app/components/app/access-point/__tests__/environment-access-point-cards.spec.tsx +++ b/web/app/components/app/access-point/__tests__/environment-access-point-cards.spec.tsx @@ -1,8 +1,8 @@ import type { ReactElement } from 'react' -import { toast } from '@langgenius/dify-ui/toast' import { QueryClientProvider } from '@tanstack/react-query' import { screen, waitFor, within } from '@testing-library/react' import userEvent from '@testing-library/user-event' +import { toast } from '@/app/notifications' import { render } from '@/test/console/render' import { createTestQueryClient } from '@/test/query-client' import { EnvironmentServiceApiCard } from '../deployed-environment-access-points/environment-service-api-card' @@ -105,7 +105,7 @@ vi.mock('@/app/components/app/access-point/shared/api-secret-key-button', () => }, })) -vi.mock('@langgenius/dify-ui/toast', () => ({ +vi.mock('@/app/notifications', () => ({ toast: { error: vi.fn(), success: vi.fn(), diff --git a/web/app/components/app/access-point/__tests__/mcp-card.spec.tsx b/web/app/components/app/access-point/__tests__/mcp-card.spec.tsx index 2e47e1084bd..b272e10ed63 100644 --- a/web/app/components/app/access-point/__tests__/mcp-card.spec.tsx +++ b/web/app/components/app/access-point/__tests__/mcp-card.spec.tsx @@ -1,9 +1,9 @@ import type { AccessPointAppInfo, PublishedWorkflow } from '../shared/utils' -import { toast } from '@langgenius/dify-ui/toast' import { QueryClientProvider } from '@tanstack/react-query' import { screen, waitFor } from '@testing-library/react' import userEvent from '@testing-library/user-event' import { BlockEnum } from '@/app/components/workflow/types' +import { toast } from '@/app/notifications' import { render } from '@/test/console/render' import { createTestQueryClient } from '@/test/query-client' import { AppModeEnum } from '@/types/app' @@ -20,7 +20,7 @@ const mocks = vi.hoisted(() => ({ updateServer: vi.fn(), })) -vi.mock('@langgenius/dify-ui/toast', () => ({ +vi.mock('@/app/notifications', () => ({ toast: { error: vi.fn(), success: vi.fn(), diff --git a/web/app/components/app/access-point/__tests__/service-api-card.spec.tsx b/web/app/components/app/access-point/__tests__/service-api-card.spec.tsx index f317ef8880a..2f26c1af6bd 100644 --- a/web/app/components/app/access-point/__tests__/service-api-card.spec.tsx +++ b/web/app/components/app/access-point/__tests__/service-api-card.spec.tsx @@ -1,9 +1,9 @@ import type { AccessPointAppInfo } from '../shared/utils' -import { toast } from '@langgenius/dify-ui/toast' import { QueryClientProvider } from '@tanstack/react-query' import { screen, waitFor } from '@testing-library/react' import userEvent from '@testing-library/user-event' import { useStore as useAppStore } from '@/app/components/app/store' +import { toast } from '@/app/notifications' import { render } from '@/test/console/render' import { createTestQueryClient } from '@/test/query-client' import { AppModeEnum } from '@/types/app' @@ -14,7 +14,7 @@ const mocks = vi.hoisted(() => ({ apiEnable: vi.fn(), })) -vi.mock('@langgenius/dify-ui/toast', () => ({ +vi.mock('@/app/notifications', () => ({ toast: { error: vi.fn(), success: vi.fn(), diff --git a/web/app/components/app/access-point/__tests__/trigger-card.spec.tsx b/web/app/components/app/access-point/__tests__/trigger-card.spec.tsx index c07e7592f70..d4c78c09465 100644 --- a/web/app/components/app/access-point/__tests__/trigger-card.spec.tsx +++ b/web/app/components/app/access-point/__tests__/trigger-card.spec.tsx @@ -1,9 +1,9 @@ import type { AccessPointAppInfo } from '../shared/utils' import type { AppTrigger } from '@/service/use-tools' -import { toast } from '@langgenius/dify-ui/toast' import { QueryClientProvider } from '@tanstack/react-query' import { screen, waitFor } from '@testing-library/react' import userEvent from '@testing-library/user-event' +import { toast } from '@/app/notifications' import { render } from '@/test/console/render' import { createTestQueryClient } from '@/test/query-client' import { AppModeEnum } from '@/types/app' @@ -19,7 +19,7 @@ const mocks = vi.hoisted(() => ({ updateTriggerStatus: vi.fn(), })) -vi.mock('@langgenius/dify-ui/toast', () => ({ +vi.mock('@/app/notifications', () => ({ toast: { error: vi.fn(), success: vi.fn(), diff --git a/web/app/components/app/access-point/__tests__/use-access-point-actions.spec.ts b/web/app/components/app/access-point/__tests__/use-access-point-actions.spec.ts index b0b169bbc0b..711884b2afa 100644 --- a/web/app/components/app/access-point/__tests__/use-access-point-actions.spec.ts +++ b/web/app/components/app/access-point/__tests__/use-access-point-actions.spec.ts @@ -11,7 +11,7 @@ const mocks = vi.hoisted(() => ({ updateAppSiteConfig: vi.fn().mockResolvedValue({}), })) -vi.mock('@langgenius/dify-ui/toast', () => ({ toast: mocks.toast })) +vi.mock('@/app/notifications', () => ({ toast: mocks.toast })) vi.mock('@/app/components/app/store', () => ({ useStore: (selector: (state: { setAppDetail: typeof mocks.setAppDetail }) => unknown) => diff --git a/web/app/components/app/access-point/__tests__/web-app-card.spec.tsx b/web/app/components/app/access-point/__tests__/web-app-card.spec.tsx index 4115a40e206..402b94c5476 100644 --- a/web/app/components/app/access-point/__tests__/web-app-card.spec.tsx +++ b/web/app/components/app/access-point/__tests__/web-app-card.spec.tsx @@ -1,11 +1,11 @@ import type { AccessPointAppInfo, PublishedWorkflow } from '../shared/utils' import type { InputVar, Node } from '@/app/components/workflow/types' -import { toast } from '@langgenius/dify-ui/toast' import { QueryClientProvider } from '@tanstack/react-query' import { screen, waitFor, within } from '@testing-library/react' import userEvent from '@testing-library/user-event' import { useStore as useAppStore } from '@/app/components/app/store' import { BlockEnum, InputVarType } from '@/app/components/workflow/types' +import { toast } from '@/app/notifications' import { AccessMode } from '@/models/access-control' import { render } from '@/test/console/render' import { createTestQueryClient } from '@/test/query-client' @@ -18,7 +18,7 @@ const mocks = vi.hoisted(() => ({ resetSiteAccessToken: vi.fn().mockResolvedValue({}), })) -vi.mock('@langgenius/dify-ui/toast', () => ({ +vi.mock('@/app/notifications', () => ({ toast: { error: vi.fn(), success: vi.fn(), diff --git a/web/app/components/app/access-point/built-in-access-points/index.tsx b/web/app/components/app/access-point/built-in-access-points/index.tsx index 1713f41cdbd..cc48a5af3f0 100644 --- a/web/app/components/app/access-point/built-in-access-points/index.tsx +++ b/web/app/components/app/access-point/built-in-access-points/index.tsx @@ -6,7 +6,7 @@ import { cn } from '@langgenius/dify-ui/cn' import { useSuspenseQuery } from '@tanstack/react-query' import { useTranslation } from 'react-i18next' import { useStore as useAppStore } from '@/app/components/app/store' -import Loading from '@/app/components/base/loading' +import { LoadingPlaceholder } from '@/app/components/base/loading-placeholder' import { useDocLink } from '@/context/i18n' import { systemFeaturesQueryOptions } from '@/features/system-features/client' import Link from '@/next/link' @@ -51,7 +51,7 @@ export function BuiltInAccessPoints({ }) const actions = useAccessPointActions(appId, canManageAccessPoint) - if (!appInfo) return + if (!appInfo) return const workflowState = getPublishedWorkflowState(appInfo, workflow) const builtInLoading = workflowState.isWorkflowApp && workflowLoading diff --git a/web/app/components/app/access-point/built-in-access-points/mcp-card.tsx b/web/app/components/app/access-point/built-in-access-points/mcp-card.tsx index eff1bf597fe..59a754cc314 100644 --- a/web/app/components/app/access-point/built-in-access-points/mcp-card.tsx +++ b/web/app/components/app/access-point/built-in-access-points/mcp-card.tsx @@ -11,7 +11,6 @@ import { AlertDialogTitle, } from '@langgenius/dify-ui/alert-dialog' import { Button } from '@langgenius/dify-ui/button' -import { toast } from '@langgenius/dify-ui/toast' import { useMutation } from '@tanstack/react-query' import { useMemo, useState } from 'react' import { useTranslation } from 'react-i18next' @@ -19,6 +18,7 @@ import { AccessPointCard } from '@/app/components/base/access-point/card' import { AccessPointUrl } from '@/app/components/base/access-point/url' import MCPServerModal from '@/app/components/tools/mcp/mcp-server-modal' import { BlockEnum } from '@/app/components/workflow/types' +import { toast } from '@/app/notifications' import { consoleQuery } from '@/service/console' import { useInvalidateMCPServerDetail, diff --git a/web/app/components/app/access-point/built-in-access-points/service-api-card.tsx b/web/app/components/app/access-point/built-in-access-points/service-api-card.tsx index 09f1506ce88..4b6fa613341 100644 --- a/web/app/components/app/access-point/built-in-access-points/service-api-card.tsx +++ b/web/app/components/app/access-point/built-in-access-points/service-api-card.tsx @@ -2,11 +2,11 @@ import type { AccessPointAppInfo } from '../shared/utils' import type { AccessPointAvailability } from '@/app/components/base/access-point/status' -import { toast } from '@langgenius/dify-ui/toast' import { useMutation } from '@tanstack/react-query' import { useTranslation } from 'react-i18next' import { useStore as useAppStore } from '@/app/components/app/store' import { getAccessPointStatus } from '@/app/components/base/access-point/status' +import { toast } from '@/app/notifications' import { consoleQuery } from '@/service/console' import { ServiceApiCardView } from '../shared/service-api-card-view' import { getBuiltInAccessUrls } from '../shared/utils' diff --git a/web/app/components/app/access-point/built-in-access-points/trigger-card.tsx b/web/app/components/app/access-point/built-in-access-points/trigger-card.tsx index a5850a1f8ca..5c46fac1ee6 100644 --- a/web/app/components/app/access-point/built-in-access-points/trigger-card.tsx +++ b/web/app/components/app/access-point/built-in-access-points/trigger-card.tsx @@ -5,7 +5,6 @@ import type { TriggerWithProvider } from '@/app/components/workflow/block-select import type { AppTrigger } from '@/service/use-tools' import { StatusDot } from '@langgenius/dify-ui/status-dot' import { Switch } from '@langgenius/dify-ui/switch' -import { toast } from '@langgenius/dify-ui/toast' import { useMutation } from '@tanstack/react-query' import { useEffect, useMemo } from 'react' import { useTranslation } from 'react-i18next' @@ -13,6 +12,7 @@ import { AccessPointCard, AccessPointEmptyContent } from '@/app/components/base/ import BlockIcon from '@/app/components/workflow/block-icon' import { useTriggerStatusStore } from '@/app/components/workflow/store/trigger-status' import { BlockEnum } from '@/app/components/workflow/types' +import { toast } from '@/app/notifications' import { useDocLink } from '@/context/i18n' import Link from '@/next/link' import { consoleQuery } from '@/service/console' diff --git a/web/app/components/app/access-point/built-in-access-points/web-app-card.tsx b/web/app/components/app/access-point/built-in-access-points/web-app-card.tsx index 6f7e09de87c..4b8d353990c 100644 --- a/web/app/components/app/access-point/built-in-access-points/web-app-card.tsx +++ b/web/app/components/app/access-point/built-in-access-points/web-app-card.tsx @@ -14,7 +14,6 @@ import { AlertDialogTitle, } from '@langgenius/dify-ui/alert-dialog' import { Button } from '@langgenius/dify-ui/button' -import { toast } from '@langgenius/dify-ui/toast' import { useMutation } from '@tanstack/react-query' import { useState } from 'react' import { useTranslation } from 'react-i18next' @@ -28,6 +27,7 @@ import { AccessPointCard } from '@/app/components/base/access-point/card' import { getAccessPointStatus } from '@/app/components/base/access-point/status' import { AccessPointUrl } from '@/app/components/base/access-point/url' import AppIcon from '@/app/components/base/app-icon' +import { toast } from '@/app/notifications' import { AccessMode } from '@/models/access-control' import { useAppWhiteListSubjects } from '@/service/access-control/use-app-access-control' import { consoleQuery } from '@/service/console' diff --git a/web/app/components/app/access-point/deployed-environment-access-points/environment-access-control.tsx b/web/app/components/app/access-point/deployed-environment-access-points/environment-access-control.tsx index 3213bcb5afc..423d3b0cc2b 100644 --- a/web/app/components/app/access-point/deployed-environment-access-points/environment-access-control.tsx +++ b/web/app/components/app/access-point/deployed-environment-access-points/environment-access-control.tsx @@ -6,11 +6,11 @@ import type { AccessControlSubjectsStatus, } from '@/app/components/app/app-access-control/specific-groups-or-members' import type { AccessControlAccount, AccessControlGroup } from '@/models/access-control' -import { toast } from '@langgenius/dify-ui/toast' import { useMutation, useQuery, useQueryClient, useSuspenseQuery } from '@tanstack/react-query' import { useState } from 'react' import { useTranslation } from 'react-i18next' import { AccessControlForm } from '@/app/components/app/app-access-control/access-control-form' +import { toast } from '@/app/notifications' import { systemFeaturesQueryOptions } from '@/features/system-features/client' import { AccessMode, SubjectType } from '@/models/access-control' import { consoleQuery } from '@/service/console' diff --git a/web/app/components/app/access-point/deployed-environment-access-points/environment-service-api-card.tsx b/web/app/components/app/access-point/deployed-environment-access-points/environment-service-api-card.tsx index bcc25889b07..02c3e01dde1 100644 --- a/web/app/components/app/access-point/deployed-environment-access-points/environment-service-api-card.tsx +++ b/web/app/components/app/access-point/deployed-environment-access-points/environment-service-api-card.tsx @@ -1,9 +1,9 @@ 'use client' -import { toast } from '@langgenius/dify-ui/toast' import { useMutation, useQuery } from '@tanstack/react-query' import { useTranslation } from 'react-i18next' import { useStore as useAppStore } from '@/app/components/app/store' +import { toast } from '@/app/notifications' import { consoleQuery } from '@/service/console' import { ServiceApiCardView } from '../shared/service-api-card-view' diff --git a/web/app/components/app/access-point/deployed-environment-access-points/environment-web-app-card.tsx b/web/app/components/app/access-point/deployed-environment-access-points/environment-web-app-card.tsx index 0e8ce4ca6e6..95de24e62f8 100644 --- a/web/app/components/app/access-point/deployed-environment-access-points/environment-web-app-card.tsx +++ b/web/app/components/app/access-point/deployed-environment-access-points/environment-web-app-card.tsx @@ -11,7 +11,6 @@ import { AlertDialogTitle, } from '@langgenius/dify-ui/alert-dialog' import { Button } from '@langgenius/dify-ui/button' -import { toast } from '@langgenius/dify-ui/toast' import { useMutation, useQuery, useQueryClient, useSuspenseQuery } from '@tanstack/react-query' import { useState } from 'react' import { useTranslation } from 'react-i18next' @@ -21,6 +20,7 @@ import { useStore as useAppStore } from '@/app/components/app/store' import { AccessPointCard } from '@/app/components/base/access-point/card' import { AccessPointUrl } from '@/app/components/base/access-point/url' import AppIcon from '@/app/components/base/app-icon' +import { toast } from '@/app/notifications' import { systemFeaturesQueryOptions } from '@/features/system-features/client' import { AccessMode, isAccessMode } from '@/models/access-control' import { consoleQuery } from '@/service/console' diff --git a/web/app/components/app/access-point/shared/use-access-point-actions.ts b/web/app/components/app/access-point/shared/use-access-point-actions.ts index eb82d3d5743..ed08627abb1 100644 --- a/web/app/components/app/access-point/shared/use-access-point-actions.ts +++ b/web/app/components/app/access-point/shared/use-access-point-actions.ts @@ -3,11 +3,11 @@ import type { ConfigParams } from '@/app/components/app/overview/settings' import type { App } from '@/types/app' import type { I18nKeysByPrefix } from '@/types/i18n' -import { toast } from '@langgenius/dify-ui/toast' import { useQueryClient } from '@tanstack/react-query' import { useCallback } from 'react' import { useTranslation } from 'react-i18next' import { useStore as useAppStore } from '@/app/components/app/store' +import { toast } from '@/app/notifications' import { fetchAppDetail, updateAppSiteConfig } from '@/service/apps' import { consoleQuery } from '@/service/console' import { asyncRunSafe } from '@/utils' diff --git a/web/app/components/app/annotation/__tests__/index.spec.tsx b/web/app/components/app/annotation/__tests__/index.spec.tsx index 865196a19fb..06bd55a94c2 100644 --- a/web/app/components/app/annotation/__tests__/index.spec.tsx +++ b/web/app/components/app/annotation/__tests__/index.spec.tsx @@ -2,9 +2,9 @@ import type { ReactElement } from 'react' import type { Mock } from 'vite-plus/test' import type { AnnotationItem } from '../type' import type { App } from '@/types/app' -import { toast } from '@langgenius/dify-ui/toast' import { act, fireEvent, screen, waitFor } from '@testing-library/react' import * as React from 'react' +import { toast } from '@/app/notifications' import { addAnnotation, delAnnotation, diff --git a/web/app/components/app/annotation/add-annotation-modal/__tests__/index.spec.tsx b/web/app/components/app/annotation/add-annotation-modal/__tests__/index.spec.tsx index dfade0ee399..d53ae4f4746 100644 --- a/web/app/components/app/annotation/add-annotation-modal/__tests__/index.spec.tsx +++ b/web/app/components/app/annotation/add-annotation-modal/__tests__/index.spec.tsx @@ -5,7 +5,7 @@ import { renderWithConsoleQuery } from '@/test/console/query-data' import AddAnnotationModal from '../index' const mockToastNotify = vi.fn() -vi.mock('@langgenius/dify-ui/toast', () => ({ +vi.mock('@/app/notifications', () => ({ default: { notify: vi.fn((args) => mockToastNotify(args)), }, diff --git a/web/app/components/app/annotation/add-annotation-modal/index.tsx b/web/app/components/app/annotation/add-annotation-modal/index.tsx index f42400f84d0..5459b456b81 100644 --- a/web/app/components/app/annotation/add-annotation-modal/index.tsx +++ b/web/app/components/app/annotation/add-annotation-modal/index.tsx @@ -13,13 +13,13 @@ import { DrawerTitle, DrawerViewport, } from '@langgenius/dify-ui/drawer' -import { toast } from '@langgenius/dify-ui/toast' import { useQuery } from '@tanstack/react-query' import { useAtomValue } from 'jotai' import * as React from 'react' import { useState } from 'react' import { useTranslation } from 'react-i18next' import AnnotationFull from '@/app/components/billing/annotation-full' +import { toast } from '@/app/notifications' import { deploymentEditionAtom } from '@/features/system-features/state' import { consoleQuery } from '@/service/console' import EditItem, { EditItemType } from './edit-item' diff --git a/web/app/components/app/annotation/batch-add-annotation-modal/__tests__/csv-downloader.spec.tsx b/web/app/components/app/annotation/batch-add-annotation-modal/__tests__/csv-downloader.spec.tsx index d43e14c0fe1..9e77cfc47b2 100644 --- a/web/app/components/app/annotation/batch-add-annotation-modal/__tests__/csv-downloader.spec.tsx +++ b/web/app/components/app/annotation/batch-add-annotation-modal/__tests__/csv-downloader.spec.tsx @@ -1,12 +1,13 @@ import type { Mock } from 'vite-plus/test' -import type { Locale } from '@/i18n-config' +import type { Locale } from '@/i18n' import { render, screen } from '@testing-library/react' import * as React from 'react' -import { useLocale } from '@/context/i18n' -import { LanguagesSupported } from '@/i18n-config/language' +import { useLocale } from '#i18n' +import { LanguagesSupported } from '@/i18n/language' import CSVDownload from '../csv-downloader' -vi.mock('@/context/i18n', () => ({ +vi.mock('#i18n', async (importOriginal) => ({ + ...(await importOriginal()), useLocale: vi.fn(() => 'en-US'), })) diff --git a/web/app/components/app/annotation/batch-add-annotation-modal/__tests__/csv-uploader.spec.tsx b/web/app/components/app/annotation/batch-add-annotation-modal/__tests__/csv-uploader.spec.tsx index 4ce407f3ca3..fb1f689757a 100644 --- a/web/app/components/app/annotation/batch-add-annotation-modal/__tests__/csv-uploader.spec.tsx +++ b/web/app/components/app/annotation/batch-add-annotation-modal/__tests__/csv-uploader.spec.tsx @@ -10,7 +10,7 @@ const toastMocks = vi.hoisted(() => ({ promise: vi.fn(), })) -vi.mock('@langgenius/dify-ui/toast', () => ({ +vi.mock('@/app/notifications', () => ({ toast: { success: (message: string, options?: Record) => toastMocks.notify({ type: 'success', message, ...options }), diff --git a/web/app/components/app/annotation/batch-add-annotation-modal/__tests__/index.spec.tsx b/web/app/components/app/annotation/batch-add-annotation-modal/__tests__/index.spec.tsx index 7ecd4160d62..22744957f9c 100644 --- a/web/app/components/app/annotation/batch-add-annotation-modal/__tests__/index.spec.tsx +++ b/web/app/components/app/annotation/batch-add-annotation-modal/__tests__/index.spec.tsx @@ -42,7 +42,7 @@ vi.mock('@/app/components/billing/annotation-full', () => ({ })) const mockNotify = vi.fn() -vi.mock('@langgenius/dify-ui/toast', () => ({ +vi.mock('@/app/notifications', () => ({ default: { notify: (args: unknown) => mockNotify(args), }, diff --git a/web/app/components/app/annotation/batch-add-annotation-modal/csv-downloader.tsx b/web/app/components/app/annotation/batch-add-annotation-modal/csv-downloader.tsx index 69fa82bbbdb..6648a6472f7 100644 --- a/web/app/components/app/annotation/batch-add-annotation-modal/csv-downloader.tsx +++ b/web/app/components/app/annotation/batch-add-annotation-modal/csv-downloader.tsx @@ -3,9 +3,9 @@ import type { FC } from 'react' import * as React from 'react' import { useTranslation } from 'react-i18next' import { useCSVDownloader } from 'react-papaparse' +import { useLocale } from '#i18n' import { Download02 as DownloadIcon } from '@/app/components/base/icons/src/vender/solid/general' -import { useLocale } from '@/context/i18n' -import { LanguagesSupported } from '@/i18n-config/language' +import { LanguagesSupported } from '@/i18n/language' const CSV_TEMPLATE_QA_EN = [ ['question', 'answer'], diff --git a/web/app/components/app/annotation/batch-add-annotation-modal/csv-uploader.tsx b/web/app/components/app/annotation/batch-add-annotation-modal/csv-uploader.tsx index 38eb1e4ae00..7388d5cea69 100644 --- a/web/app/components/app/annotation/batch-add-annotation-modal/csv-uploader.tsx +++ b/web/app/components/app/annotation/batch-add-annotation-modal/csv-uploader.tsx @@ -2,12 +2,12 @@ import type { FC } from 'react' import { Button } from '@langgenius/dify-ui/button' import { cn } from '@langgenius/dify-ui/cn' -import { toast } from '@langgenius/dify-ui/toast' import { RiDeleteBinLine } from '@remixicon/react' import * as React from 'react' import { useEffect, useRef, useState } from 'react' import { useTranslation } from 'react-i18next' import { Csv as CSVIcon } from '@/app/components/base/icons/src/public/files' +import { toast } from '@/app/notifications' export type Props = Readonly<{ file: File | undefined diff --git a/web/app/components/app/annotation/batch-add-annotation-modal/index.tsx b/web/app/components/app/annotation/batch-add-annotation-modal/index.tsx index ff4d2ba0242..4fb651b70e7 100644 --- a/web/app/components/app/annotation/batch-add-annotation-modal/index.tsx +++ b/web/app/components/app/annotation/batch-add-annotation-modal/index.tsx @@ -2,7 +2,6 @@ import type { FC } from 'react' import { Button } from '@langgenius/dify-ui/button' import { Dialog, DialogContent } from '@langgenius/dify-ui/dialog' -import { toast } from '@langgenius/dify-ui/toast' import { RiCloseLine } from '@remixicon/react' import { useQuery } from '@tanstack/react-query' import { useAtomValue } from 'jotai' @@ -10,6 +9,7 @@ import * as React from 'react' import { useEffect, useState } from 'react' import { useTranslation } from 'react-i18next' import AnnotationFull from '@/app/components/billing/annotation-full' +import { toast } from '@/app/notifications' import { deploymentEditionAtom } from '@/features/system-features/state' import { annotationBatchImport, checkAnnotationBatchImportProgress } from '@/service/annotation' import { consoleQuery } from '@/service/console' diff --git a/web/app/components/app/annotation/edit-annotation-modal/__tests__/index.spec.tsx b/web/app/components/app/annotation/edit-annotation-modal/__tests__/index.spec.tsx index 7b2de8b4626..4030169a517 100644 --- a/web/app/components/app/annotation/edit-annotation-modal/__tests__/index.spec.tsx +++ b/web/app/components/app/annotation/edit-annotation-modal/__tests__/index.spec.tsx @@ -1,6 +1,6 @@ -import { toast } from '@langgenius/dify-ui/toast' import { render, screen, waitFor } from '@testing-library/react' import userEvent from '@testing-library/user-event' +import { toast } from '@/app/notifications' import EditAnnotationModal from '../index' const { mockEditAnnotation } = vi.hoisted(() => ({ diff --git a/web/app/components/app/annotation/edit-annotation-modal/index.tsx b/web/app/components/app/annotation/edit-annotation-modal/index.tsx index 67ab2207ee9..d3147815aba 100644 --- a/web/app/components/app/annotation/edit-annotation-modal/index.tsx +++ b/web/app/components/app/annotation/edit-annotation-modal/index.tsx @@ -18,11 +18,11 @@ import { DrawerTitle, DrawerViewport, } from '@langgenius/dify-ui/drawer' -import { toast } from '@langgenius/dify-ui/toast' import * as React from 'react' import { useState } from 'react' import { useTranslation } from 'react-i18next' import { MessageCheckRemove } from '@/app/components/base/icons/src/vender/line/communication' +import { toast } from '@/app/notifications' import useTimestamp from '@/hooks/use-timestamp' import { editAnnotation } from '@/service/annotation' import EditItem, { EditItemType } from './edit-item' diff --git a/web/app/components/app/annotation/header-opts/__tests__/index.spec.tsx b/web/app/components/app/annotation/header-opts/__tests__/index.spec.tsx index 374affd754e..95a731cd7d0 100644 --- a/web/app/components/app/annotation/header-opts/__tests__/index.spec.tsx +++ b/web/app/components/app/annotation/header-opts/__tests__/index.spec.tsx @@ -1,11 +1,11 @@ import type { ComponentProps } from 'react' import type { Mock } from 'vite-plus/test' import type { AnnotationItemBasic } from '../../type' -import type { Locale } from '@/i18n-config' +import type { Locale } from '@/i18n' import { act, screen, waitFor } from '@testing-library/react' import userEvent from '@testing-library/user-event' -import { useLocale } from '@/context/i18n' -import { LanguagesSupported } from '@/i18n-config/language' +import { useLocale } from '#i18n' +import { LanguagesSupported } from '@/i18n/language' import { clearAllAnnotations, fetchExportAnnotationList } from '@/service/annotation' import { renderWithConsoleQuery as render } from '@/test/console/query-data' import HeaderOptions from '../index' @@ -30,7 +30,8 @@ vi.mock('@/app/components/billing/annotation-full', () => ({ default: () =>
, })) -vi.mock('@/context/i18n', () => ({ +vi.mock('#i18n', async (importOriginal) => ({ + ...(await importOriginal()), useLocale: vi.fn(() => LanguagesSupported[0]), })) diff --git a/web/app/components/app/annotation/header-opts/index.tsx b/web/app/components/app/annotation/header-opts/index.tsx index c4de0662292..d725b92c4c7 100644 --- a/web/app/components/app/annotation/header-opts/index.tsx +++ b/web/app/components/app/annotation/header-opts/index.tsx @@ -15,8 +15,8 @@ import * as React from 'react' import { useEffect, useState } from 'react' import { useTranslation } from 'react-i18next' import { jsonToCSV } from 'react-papaparse' -import { useLocale } from '@/context/i18n' -import { LanguagesSupported } from '@/i18n-config/language' +import { useLocale } from '#i18n' +import { LanguagesSupported } from '@/i18n/language' import { clearAllAnnotations, fetchExportAnnotationList } from '@/service/annotation' import { downloadBlob } from '@/utils/download' import AddAnnotationModal from '../add-annotation-modal' diff --git a/web/app/components/app/annotation/index.tsx b/web/app/components/app/annotation/index.tsx index 6a3297f7b2a..01db0cb7a27 100644 --- a/web/app/components/app/annotation/index.tsx +++ b/web/app/components/app/annotation/index.tsx @@ -8,7 +8,6 @@ import { cn } from '@langgenius/dify-ui/cn' import { IconButton } from '@langgenius/dify-ui/icon-button' import { Pagination } from '@langgenius/dify-ui/pagination' import { Switch } from '@langgenius/dify-ui/switch' -import { toast } from '@langgenius/dify-ui/toast' import { RiEqualizer2Line } from '@remixicon/react' import { useQuery } from '@tanstack/react-query' import { useDebounce } from 'ahooks' @@ -18,8 +17,9 @@ import { useEffect, useState } from 'react' import { useTranslation } from 'react-i18next' import ConfigParamModal from '@/app/components/base/features/new-feature-panel/annotation-reply/config-param-modal' import { MessageFast } from '@/app/components/base/icons/src/vender/solid/communication' -import Loading from '@/app/components/base/loading' +import { LoadingPlaceholder } from '@/app/components/base/loading-placeholder' import AnnotationFullModal from '@/app/components/billing/annotation-full/modal' +import { toast } from '@/app/notifications' import { APP_PAGE_LIMIT } from '@/config' import { useDocLink } from '@/context/i18n' import { deploymentEditionAtom } from '@/features/system-features/state' @@ -248,7 +248,7 @@ const Annotation: FC = (props) => {
{isLoading ? ( - + ) : total > 0 ? ( { ) }) + it('keeps member-search loading text in its existing live region', async () => { + const query = { + isLoading: true, + isFetchingNextPage: false, + fetchNextPage: vi.fn(), + data: { pages: [] }, + } + mockUseSearchForWhiteListCandidates.mockReturnValue(query) + const user = userEvent.setup() + const view = render() + await user.click(screen.getByText('common.operation.add')) + const status = screen.getByRole('status') + expect(status).toHaveTextContent('common.loading') + expect(screen.queryByRole('progressbar')).not.toBeInTheDocument() + + mockUseSearchForWhiteListCandidates.mockReturnValue({ ...query, isLoading: false }) + view.rerender() + expect(screen.getByRole('status')).toBe(status) + expect(status).toHaveTextContent('app.accessControlDialog.operateGroupAndMember.noResult') + }) + it('should show the empty state when no candidates are returned', async () => { mockUseSearchForWhiteListCandidates.mockReturnValue({ isLoading: false, diff --git a/web/app/components/app/app-access-control/__tests__/index.spec.tsx b/web/app/components/app/app-access-control/__tests__/index.spec.tsx index 5e41e3cd594..946ba118a9c 100644 --- a/web/app/components/app/app-access-control/__tests__/index.spec.tsx +++ b/web/app/components/app/app-access-control/__tests__/index.spec.tsx @@ -1,8 +1,8 @@ import type { ReactElement } from 'react' import type { App } from '@/types/app' -import { toast } from '@langgenius/dify-ui/toast' import { screen, waitFor } from '@testing-library/react' import userEvent from '@testing-library/user-event' +import { toast } from '@/app/notifications' import { AccessMode } from '@/models/access-control' import { renderWithConsoleQuery } from '@/test/console/query-data' import AccessControl from '../index' diff --git a/web/app/components/app/app-access-control/__tests__/specific-groups-or-members.spec.tsx b/web/app/components/app/app-access-control/__tests__/specific-groups-or-members.spec.tsx index 3f15ca67169..f3a8889e552 100644 --- a/web/app/components/app/app-access-control/__tests__/specific-groups-or-members.spec.tsx +++ b/web/app/components/app/app-access-control/__tests__/specific-groups-or-members.spec.tsx @@ -1,5 +1,5 @@ import type { AccessControlAccount, AccessControlGroup } from '@/models/access-control' -import { render, screen } from '@testing-library/react' +import { render, screen, within } from '@testing-library/react' import userEvent from '@testing-library/user-event' import { AccessMode } from '@/models/access-control' import SpecificGroupsOrMembers from '../specific-groups-or-members' @@ -67,7 +67,7 @@ describe('SpecificGroupsOrMembers', () => { />, ) - expect(container.querySelector('.spin-animation')).toBeInTheDocument() + expect(within(container).queryByRole('progressbar')).toBeInTheDocument() expect(mockAddMemberOrGroupDialog).not.toHaveBeenCalled() }) diff --git a/web/app/components/app/app-access-control/add-member-or-group-pop/index.tsx b/web/app/components/app/app-access-control/add-member-or-group-pop/index.tsx index ebd3b76327a..e80cc9ce1d5 100644 --- a/web/app/components/app/app-access-control/add-member-or-group-pop/index.tsx +++ b/web/app/components/app/app-access-control/add-member-or-group-pop/index.tsx @@ -16,10 +16,10 @@ import { ScrollAreaThumb, ScrollAreaViewport, } from '@langgenius/dify-ui/scroll-area' +import { SpinnerIcon } from '@langgenius/dify-ui/spinner' import { useDebounce } from 'ahooks' import { useEffect, useRef, useState } from 'react' import { useTranslation } from 'react-i18next' -import Loading from '@/app/components/base/loading' import { SearchInput } from '@/app/components/base/search-input' import { SubjectType } from '@/models/access-control' import { useSearchForWhiteListCandidates } from '@/service/access-control' @@ -188,14 +188,24 @@ export default function AddMemberOrGroupDialog({ } > {isLoading ? ( - + <> + + {t(($) => $.loading, { ns: 'common' })} + ) : ( - statusText + <> + {statusText} + {isFetchingNextPage && ( + {t(($) => $.loading, { ns: 'common' })} + )} + )}
- {isFetchingNextPage && } + {isFetchingNextPage && ( +
+ +
+ )}
diff --git a/web/app/components/app/app-access-control/index.tsx b/web/app/components/app/app-access-control/index.tsx index b6fddfe41d4..92216f70e0b 100644 --- a/web/app/components/app/app-access-control/index.tsx +++ b/web/app/components/app/app-access-control/index.tsx @@ -6,10 +6,10 @@ import type { AccessControlSubjectsStatus, } from './specific-groups-or-members' import type { Subject } from '@/models/access-control' -import { toast } from '@langgenius/dify-ui/toast' import { useMutation, useSuspenseQuery } from '@tanstack/react-query' import { useState } from 'react' import { useTranslation } from 'react-i18next' +import { toast } from '@/app/notifications' import { systemFeaturesQueryOptions } from '@/features/system-features/client' import { AccessMode, isAccessMode, SubjectType } from '@/models/access-control' import { useAppWhiteListSubjects } from '@/service/access-control' diff --git a/web/app/components/app/app-access-control/specific-groups-or-members.tsx b/web/app/components/app/app-access-control/specific-groups-or-members.tsx index 8259a83d2f0..cb8a70aaf74 100644 --- a/web/app/components/app/app-access-control/specific-groups-or-members.tsx +++ b/web/app/components/app/app-access-control/specific-groups-or-members.tsx @@ -4,9 +4,9 @@ import { Avatar } from '@langgenius/dify-ui/avatar' import { Button } from '@langgenius/dify-ui/button' import { IconButton } from '@langgenius/dify-ui/icon-button' import { useTranslation } from 'react-i18next' +import { LoadingPlaceholder } from '@/app/components/base/loading-placeholder' import { AccessMode } from '@/models/access-control' import { Infotip } from '../../base/infotip' -import Loading from '../../base/loading' import AddMemberOrGroupDialog from './add-member-or-group-pop' export type AccessControlSubjects = { @@ -63,7 +63,7 @@ export default function SpecificGroupsOrMembers({
- {subjectsStatus === 'loading' && } + {subjectsStatus === 'loading' && } {subjectsStatus === 'error' && (

diff --git a/web/app/components/app/app-publisher/__tests__/environment-deployment-flow.spec.tsx b/web/app/components/app/app-publisher/__tests__/environment-deployment-flow.spec.tsx index 96642b8c1fe..56ba2f05db8 100644 --- a/web/app/components/app/app-publisher/__tests__/environment-deployment-flow.spec.tsx +++ b/web/app/components/app/app-publisher/__tests__/environment-deployment-flow.spec.tsx @@ -489,7 +489,7 @@ describe('PublisherEnvironmentFlow', () => { ) expect(screen.getByText('Environment tabs')).toBeInTheDocument() - expect(screen.getByRole('status', { name: 'appApi.loading' })).toBeInTheDocument() + expect(screen.getByRole('progressbar', { name: 'appApi.loading' })).toBeInTheDocument() }) it('formats deployed_at as a Unix timestamp in seconds', () => { diff --git a/web/app/components/app/app-publisher/__tests__/index.spec.tsx b/web/app/components/app/app-publisher/__tests__/index.spec.tsx index 8b1ec64b24a..a4adce4dcca 100644 --- a/web/app/components/app/app-publisher/__tests__/index.spec.tsx +++ b/web/app/components/app/app-publisher/__tests__/index.spec.tsx @@ -160,7 +160,7 @@ vi.mock('@/context/permission-state', async () => { })) }) -vi.mock('@langgenius/dify-ui/toast', () => ({ +vi.mock('@/app/notifications', () => ({ toast: { error: (...args: unknown[]) => mockToastError(...args), success: (...args: unknown[]) => mockToastSuccess(...args), @@ -632,7 +632,7 @@ describe('AppPublisher', () => { expect(environmentDeploymentListRequests.length).toBeGreaterThan(0) }) expect(environmentDeploymentDetailRequests).toHaveLength(0) - expect(screen.getByRole('status')).toBeInTheDocument() + expect(screen.getByRole('progressbar', { name: 'appApi.loading' })).toBeInTheDocument() expect(screen.queryByText(/studio\.accessPoint\.noPublishedTitle/)).not.toBeInTheDocument() expect(screen.queryByText(/studio\.publisher\.notDeployedYet/)).not.toBeInTheDocument() diff --git a/web/app/components/app/app-publisher/__tests__/version-info-modal.spec.tsx b/web/app/components/app/app-publisher/__tests__/version-info-modal.spec.tsx index 44c99cc2f29..24697749f7a 100644 --- a/web/app/components/app/app-publisher/__tests__/version-info-modal.spec.tsx +++ b/web/app/components/app/app-publisher/__tests__/version-info-modal.spec.tsx @@ -1,8 +1,8 @@ -import { toast } from '@langgenius/dify-ui/toast' import { fireEvent, render, screen } from '@testing-library/react' +import { toast } from '@/app/notifications' import VersionInfoModal from '../version-info-modal' -vi.mock('@langgenius/dify-ui/toast', () => ({ +vi.mock('@/app/notifications', () => ({ toast: { error: vi.fn(), }, diff --git a/web/app/components/app/app-publisher/environment-deployment-flow/index.tsx b/web/app/components/app/app-publisher/environment-deployment-flow/index.tsx index 4916eb37320..9a0328e297a 100644 --- a/web/app/components/app/app-publisher/environment-deployment-flow/index.tsx +++ b/web/app/components/app/app-publisher/environment-deployment-flow/index.tsx @@ -6,7 +6,7 @@ import type { DeploymentVersion } from '@/app/components/app/deploy/utils/versio import { useAtomValue, useSetAtom } from 'jotai' import { useTranslation } from 'react-i18next' import { EnvironmentDeploymentFlow } from '@/app/components/app/deploy/shared/environment-deployment-flow' -import Loading from '@/app/components/base/loading' +import { LoadingPlaceholder } from '@/app/components/base/loading-placeholder' import { publisherEnvironmentDeploymentPollingAtom, startPublisherEnvironmentDeploymentPollingAtom, @@ -52,7 +52,7 @@ export function PublisherEnvironmentFlow({

{environmentTabs} {isDeploymentLoading ? ( - + ) : (
{ it('should render a loading state before configuration data is ready', () => { render() - expect(screen.getByRole('status', { name: 'appApi.loading' })).toBeInTheDocument() + expect(screen.getByRole('progressbar', { name: 'appApi.loading' })).toBeInTheDocument() expect(screen.queryByTestId('app-publisher')).not.toBeInTheDocument() }) diff --git a/web/app/components/app/configuration/__tests__/index.spec.tsx b/web/app/components/app/configuration/__tests__/index.spec.tsx index 049a70c11de..3847c2f5535 100644 --- a/web/app/components/app/configuration/__tests__/index.spec.tsx +++ b/web/app/components/app/configuration/__tests__/index.spec.tsx @@ -1,5 +1,5 @@ -import { ToastHost } from '@langgenius/dify-ui/toast' import { act, render, screen } from '@testing-library/react' +import { AppToastHost } from '@/app/notifications/host' import Configuration from '../index' import { toast } from '../toast' @@ -23,7 +23,7 @@ describe('Configuration', () => { it('should render configuration notifications in its offset viewport', async () => { render( <> - + , ) diff --git a/web/app/components/app/configuration/config/agent/agent-tools/setting-built-in-tool.tsx b/web/app/components/app/configuration/config/agent/agent-tools/setting-built-in-tool.tsx index 1d5cd5c7dde..7538e1b68a6 100644 --- a/web/app/components/app/configuration/config/agent/agent-tools/setting-built-in-tool.tsx +++ b/web/app/components/app/configuration/config/agent/agent-tools/setting-built-in-tool.tsx @@ -17,7 +17,8 @@ import { RiArrowLeftLine, RiCloseLine } from '@remixicon/react' import * as React from 'react' import { useEffect, useState } from 'react' import { useTranslation } from 'react-i18next' -import Loading from '@/app/components/base/loading' +import { useLocale } from '#i18n' +import { LoadingPlaceholder } from '@/app/components/base/loading-placeholder' import TabSlider from '@/app/components/base/tab-slider-plain' import Form from '@/app/components/header/account-setting/model-provider-page/model-modal/Form' import Icon from '@/app/components/plugins/card/base/card-icon' @@ -27,8 +28,7 @@ import { AuthCategory, PluginAuthInAgent } from '@/app/components/plugins/plugin import { ReadmeEntrance } from '@/app/components/plugins/readme-panel/entrance' import { CollectionType } from '@/app/components/tools/types' import { toolParametersToFormSchemas } from '@/app/components/tools/utils/to-form-schema' -import { useLocale } from '@/context/i18n' -import { getLanguage } from '@/i18n-config/language' +import { getPluginLanguage } from '@/i18n/metadata' import { fetchBuiltInToolList, fetchCustomToolList, @@ -66,7 +66,7 @@ const SettingBuiltInTool: FC = ({ onAuthorizationItemClick, }) => { const locale = useLocale() - const language = getLanguage(locale) + const language = getPluginLanguage(locale) const { t } = useTranslation() const passedTools = (collection as ToolWithProvider).tools const hasPassedTools = passedTools?.length > 0 @@ -183,7 +183,7 @@ const SettingBuiltInTool: FC = ({ )} > - {isLoading && } + {isLoading && } {!isLoading && ( <> {/* header */} diff --git a/web/app/components/app/configuration/config/automatic/__tests__/generator-reflow.browser.spec.tsx b/web/app/components/app/configuration/config/automatic/__tests__/generator-reflow.browser.spec.tsx index 9227956b34b..24eaca6b69d 100644 --- a/web/app/components/app/configuration/config/automatic/__tests__/generator-reflow.browser.spec.tsx +++ b/web/app/components/app/configuration/config/automatic/__tests__/generator-reflow.browser.spec.tsx @@ -6,8 +6,8 @@ import GetAutomaticRes from '../get-automatic-res' vi.mock('react-i18next', async () => { const { createReactI18nextMock } = await import('@/test/i18n-mock') - const { default: appDebug } = await import('@/i18n/en-US/app-debug.json') - const { default: common } = await import('@/i18n/en-US/common.json') + const { default: appDebug } = await import('@/i18n/locales/en-US/app-debug.json') + const { default: common } = await import('@/i18n/locales/en-US/common.json') return createReactI18nextMock({ ...appDebug, ...common }) }) diff --git a/web/app/components/app/configuration/config/automatic/get-automatic-res.tsx b/web/app/components/app/configuration/config/automatic/get-automatic-res.tsx index 901733e32e0..2f2eb685346 100644 --- a/web/app/components/app/configuration/config/automatic/get-automatic-res.tsx +++ b/web/app/components/app/configuration/config/automatic/get-automatic-res.tsx @@ -21,7 +21,7 @@ import * as React from 'react' import { useCallback, useId, useMemo, useState } from 'react' import { useTranslation } from 'react-i18next' import { toast } from '@/app/components/app/configuration/toast' -import Loading from '@/app/components/base/loading' +import { LoadingPlaceholder } from '@/app/components/base/loading-placeholder' import { ModelTypeEnum } from '@/app/components/header/account-setting/model-provider-page/declarations' import { useModelListAndDefaultModelAndCurrentProviderAndModel } from '@/app/components/header/account-setting/model-provider-page/hooks' import ModelParameterModal from '@/app/components/header/account-setting/model-provider-page/model-parameter-modal' @@ -190,7 +190,7 @@ const GetAutomaticRes: FC = ({ const renderLoading = (
- +
{t(($) => $['generate.loading'], { ns: 'appDebug' })}
diff --git a/web/app/components/app/configuration/config/code-generator/get-code-generator-res.tsx b/web/app/components/app/configuration/config/code-generator/get-code-generator-res.tsx index ce0a57b76d9..06d905d85fd 100644 --- a/web/app/components/app/configuration/config/code-generator/get-code-generator-res.tsx +++ b/web/app/components/app/configuration/config/code-generator/get-code-generator-res.tsx @@ -20,7 +20,7 @@ import * as React from 'react' import { useCallback, useMemo, useState } from 'react' import { useTranslation } from 'react-i18next' import { toast } from '@/app/components/app/configuration/toast' -import Loading from '@/app/components/base/loading' +import { LoadingPlaceholder } from '@/app/components/base/loading-placeholder' import { ModelTypeEnum } from '@/app/components/header/account-setting/model-provider-page/declarations' import { useModelListAndDefaultModelAndCurrentProviderAndModel } from '@/app/components/header/account-setting/model-provider-page/hooks' import ModelParameterModal from '@/app/components/header/account-setting/model-provider-page/model-parameter-modal' @@ -189,7 +189,7 @@ export const GetCodeGeneratorResModal: FC = ({ const renderLoading = (
- +
{t(($) => $['codegen.loading'], { ns: 'appDebug' })}
diff --git a/web/app/components/app/configuration/configuration-view/index.tsx b/web/app/components/app/configuration/configuration-view/index.tsx index c94e9a01181..5ffa311e8c2 100644 --- a/web/app/components/app/configuration/configuration-view/index.tsx +++ b/web/app/components/app/configuration/configuration-view/index.tsx @@ -34,7 +34,7 @@ import Debug from '@/app/components/app/configuration/debug' import Divider from '@/app/components/base/divider' import { FeaturesProvider } from '@/app/components/base/features' import NewFeaturePanel from '@/app/components/base/features/new-feature-panel' -import Loading from '@/app/components/base/loading' +import { LoadingPlaceholder } from '@/app/components/base/loading-placeholder' import ModelParameterModal from '@/app/components/header/account-setting/model-provider-page/model-parameter-modal' import PluginDependency from '@/app/components/workflow/plugin-dependency' import ConfigContext from '@/context/debug-configuration' @@ -101,7 +101,7 @@ const ConfigurationView: FC = ({ if (showLoading) { return (
- +
) } diff --git a/web/app/components/app/configuration/dataset-config/card-item/__tests__/index.spec.tsx b/web/app/components/app/configuration/dataset-config/card-item/__tests__/index.spec.tsx index acb21be3f6a..48424a0322c 100644 --- a/web/app/components/app/configuration/dataset-config/card-item/__tests__/index.spec.tsx +++ b/web/app/components/app/configuration/dataset-config/card-item/__tests__/index.spec.tsx @@ -218,7 +218,7 @@ describe('dataset-config/card-item', () => { const iconElement = nameElement.parentElement?.firstElementChild as HTMLElement expect(iconElement)!.toHaveStyle({ background: '#FFF4ED' }) - expect(iconElement.querySelector('em-emoji'))!.toHaveAttribute('id', '📙') + expect(iconElement).toHaveTextContent('📙') }) it('should apply mask overlay on mobile when drawer is open', async () => { diff --git a/web/app/components/app/configuration/dataset-config/select-dataset/__tests__/index.spec.tsx b/web/app/components/app/configuration/dataset-config/select-dataset/__tests__/index.spec.tsx index e0278e5dd19..2fcc3bf14fe 100644 --- a/web/app/components/app/configuration/dataset-config/select-dataset/__tests__/index.spec.tsx +++ b/web/app/components/app/configuration/dataset-config/select-dataset/__tests__/index.spec.tsx @@ -8,16 +8,6 @@ import { render } from '@/test/console/render' import { RETRIEVE_METHOD } from '@/types/app' import SelectDataSet from '../index' -vi.mock('@/i18n-config/i18next-config', () => ({ - default: { - changeLanguage: vi.fn(), - addResourceBundle: vi.fn(), - use: vi.fn().mockReturnThis(), - init: vi.fn(), - addResource: vi.fn(), - hasResourceBundle: vi.fn().mockReturnValue(true), - }, -})) const mockUseInfiniteScroll = vi.fn() vi.mock('ahooks', async (importOriginal) => { const actual = await importOriginal() diff --git a/web/app/components/app/configuration/dataset-config/select-dataset/index.tsx b/web/app/components/app/configuration/dataset-config/select-dataset/index.tsx index 1e9b0037371..48e7a67e705 100644 --- a/web/app/components/app/configuration/dataset-config/select-dataset/index.tsx +++ b/web/app/components/app/configuration/dataset-config/select-dataset/index.tsx @@ -12,7 +12,7 @@ import { useCallback, useMemo, useRef, useState } from 'react' import { useTranslation } from 'react-i18next' import AppIcon from '@/app/components/base/app-icon' import Badge from '@/app/components/base/badge' -import Loading from '@/app/components/base/loading' +import { LoadingPlaceholder } from '@/app/components/base/loading-placeholder' import { ModelFeatureEnum } from '@/app/components/header/account-setting/model-provider-page/declarations' import FeatureIcon from '@/app/components/header/account-setting/model-provider-page/model-selector/feature-icon' import { workspacePermissionKeysAtom } from '@/context/permission-state' @@ -118,7 +118,7 @@ const SelectDataSet: FC = ({ /> {isLoading && datasets.length === 0 && (
- +
)} @@ -201,7 +201,7 @@ const SelectDataSet: FC = ({ )} ))} - {isFetchingNextPage && } + {isFetchingNextPage && }
)} diff --git a/web/app/components/app/configuration/index.tsx b/web/app/components/app/configuration/index.tsx index 2dadd0373e3..c88bb02cd2b 100644 --- a/web/app/components/app/configuration/index.tsx +++ b/web/app/components/app/configuration/index.tsx @@ -1,6 +1,6 @@ 'use client' -import { ToastHost } from '@langgenius/dify-ui/toast' import * as React from 'react' +import { AppToastHost } from '@/app/notifications/host' import ConfigurationView from './configuration-view' import { useConfiguration } from './hooks/use-configuration' import { appConfigurationToastManager } from './toast' @@ -9,7 +9,7 @@ const Configuration = () => { const viewModel = useConfiguration() return ( <> - + ) diff --git a/web/app/components/app/configuration/toast.ts b/web/app/components/app/configuration/toast.ts index f1e64bbc1f8..35678e9c205 100644 --- a/web/app/components/app/configuration/toast.ts +++ b/web/app/components/app/configuration/toast.ts @@ -2,7 +2,5 @@ import { createToast, createToastManager } from '@langgenius/dify-ui/toast' -const appConfigurationToastManager = createToastManager() -const toast = createToast(appConfigurationToastManager) - -export { appConfigurationToastManager, toast } +export const appConfigurationToastManager = createToastManager() +export const toast = createToast(appConfigurationToastManager) diff --git a/web/app/components/app/configuration/tools/__tests__/external-data-tool-modal-utils.spec.ts b/web/app/components/app/configuration/tools/__tests__/external-data-tool-modal-utils.spec.ts index e832efacf6e..b020d8cadcb 100644 --- a/web/app/components/app/configuration/tools/__tests__/external-data-tool-modal-utils.spec.ts +++ b/web/app/components/app/configuration/tools/__tests__/external-data-tool-modal-utils.spec.ts @@ -1,6 +1,6 @@ -import type { I18nText } from '@/i18n-config/language' +import type { I18nText } from '@/i18n/language' import type { CodeBasedExtensionItem } from '@/models/common' -import { LanguagesSupported } from '@/i18n-config/language' +import { LanguagesSupported } from '@/i18n/language' import { withSelectorKey } from '@/test/i18n-mock' import { buildProviders, diff --git a/web/app/components/app/configuration/tools/__tests__/external-data-tool-modal.spec.tsx b/web/app/components/app/configuration/tools/__tests__/external-data-tool-modal.spec.tsx index 4b5ca839428..099c7305771 100644 --- a/web/app/components/app/configuration/tools/__tests__/external-data-tool-modal.spec.tsx +++ b/web/app/components/app/configuration/tools/__tests__/external-data-tool-modal.spec.tsx @@ -1,4 +1,5 @@ import { fireEvent, render, screen, waitFor } from '@testing-library/react' +import { mockEmojiData } from '@/test/emoji-picker' import ExternalDataToolModal from '../external-data-tool-modal' const mockToastError = vi.fn() @@ -12,6 +13,10 @@ vi.mock('@/app/components/app/configuration/toast', () => ({ vi.mock('@/context/i18n', () => ({ useDocLink: () => (path: string) => `https://docs.example.com${path}`, +})) + +vi.mock('#i18n', async (importOriginal) => ({ + ...(await importOriginal()), useLocale: () => mockLocale, })) @@ -131,9 +136,9 @@ describe('ExternalDataToolModal', () => { fireEvent.click(screen.getByText('pick-extension')) fireEvent.click(screen.getByText('open-emoji-picker')) await waitFor(() => { - expect(screen.getByPlaceholderText('Search emojis...')).toBeInTheDocument() + expect(screen.getByPlaceholderText('common.operation.search')).toBeInTheDocument() }) - const emojiButton = document.querySelector('em-emoji')?.closest('button') + const emojiButton = await screen.findByRole('gridcell', { name: 'Grinning face' }) expect(emojiButton).toBeTruthy() fireEvent.click(emojiButton!) fireEvent.click(screen.getByRole('button', { name: '#E4FBCC' })) @@ -228,3 +233,5 @@ describe('ExternalDataToolModal', () => { expect(mockOnCancel).toHaveBeenCalled() }) }) + +mockEmojiData() diff --git a/web/app/components/app/configuration/tools/external-data-tool-modal-utils.ts b/web/app/components/app/configuration/tools/external-data-tool-modal-utils.ts index 27557168d13..165554ac3bd 100644 --- a/web/app/components/app/configuration/tools/external-data-tool-modal-utils.ts +++ b/web/app/components/app/configuration/tools/external-data-tool-modal-utils.ts @@ -1,7 +1,7 @@ import type { SelectorTranslate } from '@/app/components/app/configuration/utils' import type { CodeBasedExtensionItem, ExternalDataTool } from '@/models/common' import { getStringSelectorTranslate } from '@/app/components/app/configuration/utils' -import { LanguagesSupported } from '@/i18n-config/language' +import { LanguagesSupported } from '@/i18n/language' const systemTypes = ['api'] as const diff --git a/web/app/components/app/configuration/tools/external-data-tool-modal.tsx b/web/app/components/app/configuration/tools/external-data-tool-modal.tsx index 3cbff54a4f4..86b64daadb0 100644 --- a/web/app/components/app/configuration/tools/external-data-tool-modal.tsx +++ b/web/app/components/app/configuration/tools/external-data-tool-modal.tsx @@ -14,13 +14,14 @@ import { import { noop } from 'es-toolkit/function' import { useState } from 'react' import { useTranslation } from 'react-i18next' +import { useLocale } from '#i18n' import { toast } from '@/app/components/app/configuration/toast' import AppIcon from '@/app/components/base/app-icon' import EmojiPicker from '@/app/components/base/emoji-picker' import FormGeneration from '@/app/components/base/features/new-feature-panel/moderation/form-generation' import { BookOpen01 } from '@/app/components/base/icons/src/vender/line/education' import { ApiBasedExtensionSelector } from '@/app/components/header/account-setting/api-based-extension-page/selector' -import { useDocLink, useLocale } from '@/context/i18n' +import { useDocLink } from '@/context/i18n' import { useCodeBasedExtensions } from '@/service/use-common' import { buildProviders, diff --git a/web/app/components/app/create-app-dialog/app-list/__tests__/index.spec.tsx b/web/app/components/app/create-app-dialog/app-list/__tests__/index.spec.tsx index 4c36c5d6990..4ebf8d9aaaa 100644 --- a/web/app/components/app/create-app-dialog/app-list/__tests__/index.spec.tsx +++ b/web/app/components/app/create-app-dialog/app-list/__tests__/index.spec.tsx @@ -122,7 +122,7 @@ vi.mock('@/app/components/explore/create-app-modal', () => ({
) : null, })) -vi.mock('@langgenius/dify-ui/toast', () => ({ +vi.mock('@/app/notifications', () => ({ toast: { success: (...args: unknown[]) => mockToastSuccess(...args), error: (...args: unknown[]) => mockToastError(...args), @@ -415,7 +415,7 @@ describe('Apps', () => { render() - expect(screen.getByRole('status'))!.toBeInTheDocument() + expect(screen.getByRole('progressbar'))!.toBeInTheDocument() }) it('should handle an undefined template payload by falling back to the empty state', () => { diff --git a/web/app/components/app/create-app-dialog/app-list/index.tsx b/web/app/components/app/create-app-dialog/app-list/index.tsx index 691a97eac43..5d7e5207c5e 100644 --- a/web/app/components/app/create-app-dialog/app-list/index.tsx +++ b/web/app/components/app/create-app-dialog/app-list/index.tsx @@ -5,7 +5,6 @@ import type { App } from '@/models/explore' import { cn } from '@langgenius/dify-ui/cn' import { IconButton } from '@langgenius/dify-ui/icon-button' import { InputGroup, InputGroupAddon, InputGroupInput } from '@langgenius/dify-ui/input-group' -import { toast } from '@langgenius/dify-ui/toast' import { useMutation, useSuspenseQuery } from '@tanstack/react-query' import { useDebouncedValue } from 'foxact/use-debounced-value' import { useAtomValue } from 'jotai' @@ -14,9 +13,10 @@ import { useMemo, useState } from 'react' import { useTranslation } from 'react-i18next' import AppTypeSelector from '@/app/components/app/type-selector' import Divider from '@/app/components/base/divider' -import Loading from '@/app/components/base/loading' +import { LoadingPlaceholder } from '@/app/components/base/loading-placeholder' import CreateAppModal from '@/app/components/explore/create-app-modal' import { usePluginDependencies } from '@/app/components/workflow/plugin-dependency/hooks' +import { toast } from '@/app/notifications' import { workspacePermissionKeysAtom } from '@/context/permission-state' import { userProfileQueryOptions } from '@/features/account-profile/client' import { systemFeaturesQueryOptions } from '@/features/system-features/client' @@ -171,7 +171,7 @@ const Apps = ({ onClose, onCreateFromBlank }: AppsProps) => { if (isLoading) { return (
- +
) } diff --git a/web/app/components/app/create-app-modal/__tests__/index.spec.tsx b/web/app/components/app/create-app-modal/__tests__/index.spec.tsx index d1cbf8fc738..14cdf1aa470 100644 --- a/web/app/components/app/create-app-modal/__tests__/index.spec.tsx +++ b/web/app/components/app/create-app-modal/__tests__/index.spec.tsx @@ -5,6 +5,7 @@ import userEvent from '@testing-library/user-event' import { beforeEach, describe, expect, it, vi } from 'vite-plus/test' import { useRouter } from '@/next/navigation' import { renderWithConsoleQuery } from '@/test/console/query-data' +import { mockEmojiData } from '@/test/emoji-picker' import { AppModeEnum } from '@/types/app' import { getRedirection } from '@/utils/app-redirection' import { trackCreateApp } from '@/utils/create-app-tracking' @@ -73,7 +74,7 @@ const toastMocks = vi.hoisted(() => ({ mockToastSuccess: vi.fn(), mockToastError: vi.fn(), })) -vi.mock('@langgenius/dify-ui/toast', () => ({ +vi.mock('@/app/notifications', () => ({ toast: { success: toastMocks.mockToastSuccess, error: toastMocks.mockToastError, @@ -386,12 +387,12 @@ describe('CreateAppModal', () => { fireEvent.click(screen.getByText('app.types.chatbot')) fireEvent.click(screen.getByRole('button', { name: 'app.newApp.captionName' })) await waitFor(() => { - expect(screen.getByPlaceholderText('Search emojis...')).toBeInTheDocument() + expect(screen.getByPlaceholderText('common.operation.search')).toBeInTheDocument() }) fireEvent.click(screen.getByRole('button', { name: '#E4FBCC' })) fireEvent.click(screen.getByRole('button', { name: /iconPicker\.ok/ })) await waitFor(() => { - expect(screen.queryByPlaceholderText('Search emojis...')).not.toBeInTheDocument() + expect(screen.queryByPlaceholderText('common.operation.search')).not.toBeInTheDocument() }) fireEvent.change(screen.getByPlaceholderText('app.newApp.appNamePlaceholder'), { target: { value: 'Keyboard App' }, @@ -430,14 +431,14 @@ describe('CreateAppModal', () => { fireEvent.click(screen.getByRole('button', { name: 'app.newApp.captionName' })) await waitFor(() => { - expect(screen.getByPlaceholderText('Search emojis...')).toBeInTheDocument() + expect(screen.getByPlaceholderText('common.operation.search')).toBeInTheDocument() }) fireEvent.click(screen.getByRole('button', { name: /iconPicker\.cancel/ })) await waitFor(() => { - expect(screen.queryByPlaceholderText('Search emojis...')).not.toBeInTheDocument() + expect(screen.queryByPlaceholderText('common.operation.search')).not.toBeInTheDocument() }) - expect(screen.queryByPlaceholderText('Search emojis...')).not.toBeInTheDocument() + expect(screen.queryByPlaceholderText('common.operation.search')).not.toBeInTheDocument() ahooksMocks.keyPressHandlers.at(-1)?.() @@ -500,3 +501,5 @@ describe('CreateAppModal', () => { }) }) }) + +mockEmojiData() diff --git a/web/app/components/app/create-app-modal/index.tsx b/web/app/components/app/create-app-modal/index.tsx index da4f1704897..20761babce4 100644 --- a/web/app/components/app/create-app-modal/index.tsx +++ b/web/app/components/app/create-app-modal/index.tsx @@ -6,7 +6,6 @@ import { zPostAppsBody } from '@dify/contracts/api/console/apps/zod.gen' import { Button } from '@langgenius/dify-ui/button' import { Kbd, KbdGroup } from '@langgenius/dify-ui/kbd' import { SegmentedControl, SegmentedControlItem } from '@langgenius/dify-ui/segmented-control' -import { toast } from '@langgenius/dify-ui/toast' import { formatForDisplay, useHotkey } from '@tanstack/react-hotkeys' import { useMutation, useSuspenseQuery } from '@tanstack/react-query' import { useDebounceFn } from 'ahooks' @@ -16,6 +15,7 @@ import { useTranslation } from 'react-i18next' import Divider from '@/app/components/base/divider' import AppsFull from '@/app/components/billing/apps-full-in-dialog' import { difyBuilderPendingCreationAtom } from '@/app/components/workflow-app/components/dify-builder/creation' +import { toast } from '@/app/notifications' import { workspacePermissionKeysAtom } from '@/context/permission-state' import { userProfileQueryOptions } from '@/features/account-profile/client' import { systemFeaturesQueryOptions } from '@/features/system-features/client' diff --git a/web/app/components/app/create-from-dsl-modal/__tests__/index.spec.tsx b/web/app/components/app/create-from-dsl-modal/__tests__/index.spec.tsx index fc0af74cfae..6b4b9bc4c3d 100644 --- a/web/app/components/app/create-from-dsl-modal/__tests__/index.spec.tsx +++ b/web/app/components/app/create-from-dsl-modal/__tests__/index.spec.tsx @@ -132,7 +132,7 @@ vi.mock('@/utils/imported-app-redirection', () => ({ mockResolveImportedAppRedirectionTarget(target), })) -vi.mock('@langgenius/dify-ui/toast', () => ({ +vi.mock('@/app/notifications', () => ({ toast: Object.assign((...args: unknown[]) => toastMocks.call(...args), { success: (...args: unknown[]) => toastMocks.success(...args), error: (...args: unknown[]) => toastMocks.error(...args), diff --git a/web/app/components/app/create-from-dsl-modal/__tests__/uploader.spec.tsx b/web/app/components/app/create-from-dsl-modal/__tests__/uploader.spec.tsx index 97e2b364f11..2d4084445bd 100644 --- a/web/app/components/app/create-from-dsl-modal/__tests__/uploader.spec.tsx +++ b/web/app/components/app/create-from-dsl-modal/__tests__/uploader.spec.tsx @@ -1,10 +1,10 @@ -import { toast } from '@langgenius/dify-ui/toast' import { fireEvent, render, screen } from '@testing-library/react' import userEvent from '@testing-library/user-event' import { useState } from 'react' +import { toast } from '@/app/notifications' import { Uploader } from '../uploader' -vi.mock('@langgenius/dify-ui/toast', () => ({ +vi.mock('@/app/notifications', () => ({ toast: { error: vi.fn(), }, diff --git a/web/app/components/app/create-from-dsl-modal/index.tsx b/web/app/components/app/create-from-dsl-modal/index.tsx index 95a0cdf0ba1..26819825675 100644 --- a/web/app/components/app/create-from-dsl-modal/index.tsx +++ b/web/app/components/app/create-from-dsl-modal/index.tsx @@ -17,7 +17,6 @@ import { IconButton } from '@langgenius/dify-ui/icon-button' import { Input } from '@langgenius/dify-ui/input' import { Kbd, KbdGroup } from '@langgenius/dify-ui/kbd' import { Tabs, TabsList, TabsPanel, TabsTab } from '@langgenius/dify-ui/tabs' -import { toast } from '@langgenius/dify-ui/toast' import { formatForDisplay, useHotkey } from '@tanstack/react-hotkeys' import { useMutation, useQuery, useSuspenseQuery } from '@tanstack/react-query' import { useAtomValue } from 'jotai' @@ -25,6 +24,7 @@ import { useRef, useState } from 'react' import { useTranslation } from 'react-i18next' import AppsFull from '@/app/components/billing/apps-full-in-dialog' import { usePluginDependencies } from '@/app/components/workflow/plugin-dependency/hooks' +import { toast } from '@/app/notifications' import { workspacePermissionKeysAtom } from '@/context/permission-state' import { userProfileQueryOptions } from '@/features/account-profile/client' import { systemFeaturesQueryOptions } from '@/features/system-features/client' diff --git a/web/app/components/app/create-from-dsl-modal/uploader.tsx b/web/app/components/app/create-from-dsl-modal/uploader.tsx index 448e07ed7e5..2223d64578f 100644 --- a/web/app/components/app/create-from-dsl-modal/uploader.tsx +++ b/web/app/components/app/create-from-dsl-modal/uploader.tsx @@ -3,9 +3,9 @@ import type { ChangeEvent, DragEvent, MouseEvent, RefObject } from 'react' import { Button } from '@langgenius/dify-ui/button' import { cn } from '@langgenius/dify-ui/cn' import { IconButton } from '@langgenius/dify-ui/icon-button' -import { toast } from '@langgenius/dify-ui/toast' import { useId, useLayoutEffect, useRef, useState } from 'react' import { useTranslation } from 'react-i18next' +import { toast } from '@/app/notifications' import { formatFileSize } from '@/utils/format' type Props = Readonly<{ diff --git a/web/app/components/app/deploy/__tests__/index.spec.tsx b/web/app/components/app/deploy/__tests__/index.spec.tsx index 956e7dc3592..83851c64d24 100644 --- a/web/app/components/app/deploy/__tests__/index.spec.tsx +++ b/web/app/components/app/deploy/__tests__/index.spec.tsx @@ -19,9 +19,9 @@ import { PluginCategory, RuntimeState, } from '@dify/contracts/enterprise-app-deploy/types.gen' -import { toast } from '@langgenius/dify-ui/toast' import { act, screen, waitFor, within } from '@testing-library/react' import userEvent from '@testing-library/user-event' +import { toast } from '@/app/notifications' import { consoleQuery } from '@/service/console' import { appWorkflowQueryOptions, @@ -771,7 +771,6 @@ vi.mock('react-i18next', async () => { 'The app will stop running in this environment, and all of its access points will become unavailable.', 'deployments.studio.undeployConfirmTitle': 'Undeploy {{versionName}} from {{envName}}', 'deployments.status.RUNTIME_INSTANCE_STATUS_DEPLOYING': 'Deploying', - 'deployments.status.RUNTIME_INSTANCE_STATUS_FAILED': 'Deploy failed', 'deployments.status.RUNTIME_INSTANCE_STATUS_INVALID': 'Invalid', 'deployments.status.RUNTIME_INSTANCE_STATUS_READY': 'Running', 'deployments.status.RUNTIME_INSTANCE_STATUS_UNDEPLOYED': 'Not deployed', @@ -846,10 +845,14 @@ vi.mock('@/context/permission-state', async () => { vi.mock('@/context/i18n', () => ({ getEnterpriseDocUrl: mockGetEnterpriseDocUrl, +})) + +vi.mock('#i18n', async (importOriginal) => ({ + ...(await importOriginal()), useLocale: () => 'en-US', })) -vi.mock('@langgenius/dify-ui/toast', () => ({ +vi.mock('@/app/notifications', () => ({ toast: { error: vi.fn(), }, @@ -1109,7 +1112,7 @@ describe('AppDeploy', () => { render() - expect(screen.getByRole('status', { name: 'appApi.loading' })).toBeInTheDocument() + expect(screen.getByRole('progressbar', { name: 'appApi.loading' })).toBeInTheDocument() expect( screen.queryByRole('heading', { name: 'common.appMenus.deploy' }), ).not.toBeInTheDocument() @@ -2120,7 +2123,7 @@ describe('AppDeploy', () => { { queryClient }, ) - expect(screen.getByRole('status', { name: /loading/ })).toBeInTheDocument() + expect(screen.getByRole('progressbar', { name: /loading/ })).toBeInTheDocument() expect(screen.queryByRole('table')).not.toBeInTheDocument() await waitFor(() => { expect(deploymentRequests).toHaveLength(1) diff --git a/web/app/components/app/deploy/environment-table/deploy-menu/index.tsx b/web/app/components/app/deploy/environment-table/deploy-menu/index.tsx index 7c9c17e6cf9..74af3c9af76 100644 --- a/web/app/components/app/deploy/environment-table/deploy-menu/index.tsx +++ b/web/app/components/app/deploy/environment-table/deploy-menu/index.tsx @@ -12,7 +12,7 @@ import { } from '@langgenius/dify-ui/dropdown-menu' import { useAtomValue } from 'jotai' import { useTranslation } from 'react-i18next' -import Loading from '@/app/components/base/loading' +import { LoadingPlaceholder } from '@/app/components/base/loading-placeholder' import { appEnvironmentsIsErrorAtom, appEnvironmentsIsLoadingAtom, @@ -62,7 +62,7 @@ export function EnvironmentDeployMenu({ {t(($) => $['card.notDeployed'])} {isLoading ? ( - + ) : isError ? (

diff --git a/web/app/components/app/deploy/environment-table/index.tsx b/web/app/components/app/deploy/environment-table/index.tsx index 28230e6b857..79a12a105dd 100644 --- a/web/app/components/app/deploy/environment-table/index.tsx +++ b/web/app/components/app/deploy/environment-table/index.tsx @@ -11,7 +11,7 @@ import { cn } from '@langgenius/dify-ui/cn' import { useAtomValue } from 'jotai' import { memo } from 'react' import { useTranslation } from 'react-i18next' -import Loading from '@/app/components/base/loading' +import { LoadingPlaceholder } from '@/app/components/base/loading-placeholder' import { appEnvironmentDeploymentsAtom, appEnvironmentDeploymentsIsErrorAtom, @@ -167,7 +167,7 @@ export const EnvironmentTable = memo( )} > {showLoadingState ? ( - + ) : showErrorState ? ( + if (!appDetail) return const { canDeploy, canViewAccessPoint } = getAppACLCapabilities(appDetail.permission_keys, { currentUserId, diff --git a/web/app/components/app/deploy/shared/deployment-configuration/content.tsx b/web/app/components/app/deploy/shared/deployment-configuration/content.tsx index 1a725c9ce15..adc3d5cf10f 100644 --- a/web/app/components/app/deploy/shared/deployment-configuration/content.tsx +++ b/web/app/components/app/deploy/shared/deployment-configuration/content.tsx @@ -6,7 +6,7 @@ import type { DeploymentConfigurationQueryState } from './use-deployment-configu import type { DeploymentConfigurationValuesController } from './use-deployment-configuration-values' import { cn } from '@langgenius/dify-ui/cn' import { useTranslation } from 'react-i18next' -import Loading from '@/app/components/base/loading' +import { LoadingPlaceholder } from '@/app/components/base/loading-placeholder' import { CredentialsSection } from './credentials-section' import { DeploymentPrecheckAlert } from './deployment-precheck-alert' import { EnvironmentVariablesSection } from './environment-variables-section' @@ -99,7 +99,7 @@ export function DeploymentConfigurationContent({ aria-busy={isPrechecking || isLoadingDeploymentOptions} className="min-h-0 flex-1 overflow-y-auto" > - {isPrechecking && } + {isPrechecking && } {!isPrechecking && precheckError && (

)} - {isLoadingDeploymentOptions && } + {isLoadingDeploymentOptions && } {!isLoadingDeploymentOptions && deploymentOptionsError && (
- {isLoading && } + {isLoading && } {!isLoading && versionsError && versions.length === 0 && (

@@ -203,7 +203,7 @@ export function VersionList({ )}

)} - {isFetchingNextPage && versions.length > 0 && } + {isFetchingNextPage && versions.length > 0 && }
diff --git a/web/app/components/app/duplicate-modal/__tests__/index.spec.tsx b/web/app/components/app/duplicate-modal/__tests__/index.spec.tsx index 1f5adc98622..081afa0339e 100644 --- a/web/app/components/app/duplicate-modal/__tests__/index.spec.tsx +++ b/web/app/components/app/duplicate-modal/__tests__/index.spec.tsx @@ -14,7 +14,7 @@ const { mockAppQuota, toastErrorMock } = vi.hoisted(() => ({ toastErrorMock: vi.fn(), })) -vi.mock('@langgenius/dify-ui/toast', () => ({ +vi.mock('@/app/notifications', () => ({ toast: { error: (...args: unknown[]) => toastErrorMock(...args), }, @@ -41,7 +41,7 @@ vi.mock('@/app/components/base/app-icon-picker', () => ({
, +})) + +it('keeps desktop keyboard focus visible across sidebar states and conversation actions', async () => { + // Real Chromium is needed to prove native inert Tab skipping, focus restoration + // during collapse, and focus-visible opacity on a non-hovered conversation row. + const client = new QueryClient({ defaultOptions: { queries: { retry: false } } }) + const screen = await render( + +
+ + +
+
, + ) + const before = screen.getByRole('button', { name: 'Before chat' }) + const collapse = screen.getByRole('button', { name: 'layout.sidebar.collapseSidebar' }) + const chat = screen.getByRole('button', { name: 'Chat input boundary' }) + await before.click() + await userEvent.tab() + await expect.element(collapse).toHaveFocus() + await userEvent.tab() + await expect.element(screen.getByRole('button', { name: 'share.chat.newChat' })).toHaveFocus() + await userEvent.tab() + await expect.element(screen.getByRole('button', { name: 'Saved conversation' })).toHaveFocus() + await userEvent.tab() + const more = screen.getByRole('button', { name: 'common.operation.more' }).first() + await expect.element(more).toHaveFocus() + expect(getComputedStyle(more.element()).opacity).toBe('1') + await userEvent.keyboard('{Enter}') + await expect + .element(screen.getByRole('menuitem', { name: 'explore.sidebar.action.pin' })) + .toBeVisible() + await userEvent.keyboard('{Escape}') + await userEvent.tab() + await userEvent.tab() + await expect.element(chat).toHaveFocus() + + // Collapsing must put focus on the visible replacement toggle, and the next + // native Tab must skip both hidden copies of the sidebar's actions. + await collapse.click() + const expand = screen.getByRole('button', { name: 'layout.sidebar.expandSidebar' }).last() + await expect.element(expand).toHaveFocus() + await userEvent.tab() + await expect.element(chat).toHaveFocus() + await userEvent.tab({ shift: true }) + await expect.element(expand).toHaveFocus() + await userEvent.keyboard('{Enter}') + await expect.element(collapse).toHaveFocus() + expect(collapse.element().checkVisibility({ checkOpacity: true })).toBe(true) +}) + +it('keeps the floating sidebar available through its portalled menu and closes after focus leaves', async () => { + // Pointer leave and Escape cross the real portal boundary; focus must return + // to a visible, interactive trigger instead of a newly inert offscreen panel. + const client = new QueryClient({ defaultOptions: { queries: { retry: false } } }) + const screen = await render( + +
+ + +
+
, + ) + await screen.getByRole('button', { name: 'layout.sidebar.collapseSidebar' }).click() + await screen.getByRole('region', { name: 'Chat preview' }).hover({ position: { x: 2, y: 150 } }) + const conversation = screen.getByRole('button', { name: 'Saved conversation' }).last() + await expect.element(conversation).toBeVisible() + await conversation.click() + await userEvent.tab() + const more = screen.getByRole('button', { name: 'common.operation.more' }).nth(2) + await expect.element(more).toHaveFocus() + await userEvent.keyboard('{Enter}') + await expect + .element(screen.getByRole('menuitem', { name: 'explore.sidebar.action.pin' })) + .toBeVisible() + await screen.getByRole('button', { name: 'Outside sidebar' }).hover() + await userEvent.keyboard('{Escape}') + await expect.element(more).toHaveFocus() + expect(more.element().checkVisibility({ checkOpacity: true })).toBe(true) + expect(more.element().closest('[inert]')).toBeNull() + await userEvent.keyboard('{Enter}') + await expect + .element(screen.getByRole('menuitem', { name: 'explore.sidebar.action.pin' })) + .toBeVisible() + await userEvent.keyboard('{Escape}') + await expect.element(more).toHaveFocus() + await screen.getByRole('button', { name: 'Outside sidebar' }).click() + await expect.poll(() => conversation.element().closest('[inert]')).not.toBeNull() +}) + +it('closes the floating sidebar when both the pointer and keyboard focus leave it', async () => { + // Native pointer leave precedes blur here. Tab between sidebar controls must + // keep the panel usable, but moving focus outside must make its actions inert. + const client = new QueryClient({ defaultOptions: { queries: { retry: false } } }) + const screen = await render( + +
+ + +
+
, + ) + await screen.getByRole('button', { name: 'layout.sidebar.collapseSidebar' }).click() + await screen.getByRole('region', { name: 'Chat preview' }).hover({ position: { x: 2, y: 150 } }) + const conversation = screen.getByRole('button', { name: 'Saved conversation' }).last() + await conversation.click() + await screen.getByRole('button', { name: 'Outside sidebar' }).hover() + await expect.element(conversation).toHaveFocus() + + await userEvent.tab() + const more = screen.getByRole('button', { name: 'common.operation.more' }).nth(2) + await expect.element(more).toHaveFocus() + await userEvent.tab({ shift: true }) + await expect.element(conversation).toHaveFocus() + + const outside = screen.getByRole('button', { name: 'Outside sidebar' }) + await outside.click() + await expect.element(outside).toHaveFocus() + await expect.poll(() => conversation.element().closest('[inert]')).not.toBeNull() +}) diff --git a/web/app/components/base/chat/chat-with-history/header-in-mobile.tsx b/web/app/components/base/chat/chat-with-history/header-in-mobile.tsx index 11c0f04a649..39bff5e5008 100644 --- a/web/app/components/base/chat/chat-with-history/header-in-mobile.tsx +++ b/web/app/components/base/chat/chat-with-history/header-in-mobile.tsx @@ -8,6 +8,16 @@ import { AlertDialogDescription, AlertDialogTitle, } from '@langgenius/dify-ui/alert-dialog' +import { + Dialog, + DialogBackdrop, + DialogClose, + DialogPopup, + DialogPortal, + DialogTitle, + DialogTrigger, + DialogViewport, +} from '@langgenius/dify-ui/dialog' import { IconButton } from '@langgenius/dify-ui/icon-button' import { useCallback, useState } from 'react' import { useTranslation } from 'react-i18next' @@ -77,86 +87,105 @@ const HeaderInMobile = () => { return ( <> -
- $['sidebar.expandSidebar'], { ns: 'layout' })} - size="lg" - className="shrink-0" - onClick={() => setShowSidebar(true)} - > -
{sourcesQuery.isPending ? (
- +
) : sourcesQuery.error && !sourcesQuery.data ? (
@@ -724,7 +724,7 @@ export function SourcesPage({ knowledgeSpaceId }: { knowledgeSpaceId: string }) )} {!filteredSources.length && completingFilteredResults && (
- +
)}
diff --git a/web/features/new-rag/use-document-reindex.ts b/web/features/new-rag/use-document-reindex.ts index a4f7a7bd87f..5b9a8f3d611 100644 --- a/web/features/new-rag/use-document-reindex.ts +++ b/web/features/new-rag/use-document-reindex.ts @@ -1,10 +1,10 @@ 'use client' -import { toast } from '@langgenius/dify-ui/toast' import { useMutation, useQueryClient } from '@tanstack/react-query' import { useSetAtom } from 'jotai' import { useEffect, useRef, useState } from 'react' import { useTranslation } from 'react-i18next' +import { toast } from '@/app/notifications' import { refreshWorkspacePermissionKeysAfterMutationDenialAtom } from '@/context/permission-state' import { consoleQuery } from '@/service/console' import { DatasetACLPermission, hasPermission } from '@/utils/permission' diff --git a/web/features/skills/__tests__/detail-page.fixture.tsx b/web/features/skills/__tests__/detail-page.fixture.tsx index 595c1959233..a845467feda 100644 --- a/web/features/skills/__tests__/detail-page.fixture.tsx +++ b/web/features/skills/__tests__/detail-page.fixture.tsx @@ -60,7 +60,7 @@ const mocks = vi.hoisted(() => ({ versionPatchMutationFn: vi.fn(), })) -vi.mock('@langgenius/dify-ui/toast', () => ({ +vi.mock('@/app/notifications', () => ({ toast: { error: mocks.toastError, info: mocks.toastInfo, diff --git a/web/features/skills/__tests__/page.spec.tsx b/web/features/skills/__tests__/page.spec.tsx index 8149615e037..b935abe32a9 100644 --- a/web/features/skills/__tests__/page.spec.tsx +++ b/web/features/skills/__tests__/page.spec.tsx @@ -5,11 +5,11 @@ import type { SkillTagResponse, } from '@dify/contracts/api/console/workspaces/types.gen' import type { ReactNode } from 'react' -import { toast } from '@langgenius/dify-ui/toast' import { QueryClient, QueryClientProvider } from '@tanstack/react-query' import { fireEvent, render, screen, waitFor, within } from '@testing-library/react' import userEvent from '@testing-library/user-event' import { beforeEach, describe, expect, it, vi } from 'vite-plus/test' +import { toast } from '@/app/notifications' import SkillsPage from '../page' type SkillsInfiniteOptions = { @@ -45,7 +45,7 @@ const mocks = vi.hoisted(() => ({ tagsQueryOptions: vi.fn((_options: unknown) => ({})), })) -vi.mock('@langgenius/dify-ui/toast', () => ({ +vi.mock('@/app/notifications', () => ({ toast: { error: vi.fn(), success: vi.fn(), @@ -893,14 +893,7 @@ describe('SkillsPage', () => { }), ) await user.click(await screen.findByText('common.operation.export')) - const dialog = await screen.findByRole('alertdialog', { - name: 'skill.skillManagement.exportDialog.title', - }) - expect( - within(dialog).getByText('skill.skillManagement.exportDialog.description'), - ).toBeInTheDocument() - expect(mocks.exportSkillArchiveBlob).not.toHaveBeenCalled() - await user.click(within(dialog).getByRole('button', { name: 'common.operation.confirm' })) + expect(screen.queryByRole('alertdialog')).not.toBeInTheDocument() await waitFor(() => { expect(mocks.exportSkillArchiveBlob).toHaveBeenCalledWith('skill-1') @@ -924,37 +917,12 @@ describe('SkillsPage', () => { ) await user.click(await screen.findByText('common.operation.export')) - const dialog = await screen.findByRole('alertdialog', { - name: 'skill.skillManagement.exportDialog.title', - }) - expect( - within(dialog).getByText('skill.skillManagement.exportDialog.description'), - ).toBeInTheDocument() - expect(mocks.exportSkillArchiveBlob).not.toHaveBeenCalled() - await user.click(within(dialog).getByRole('button', { name: 'common.operation.confirm' })) + expect(screen.queryByRole('alertdialog')).not.toBeInTheDocument() await waitFor(() => { expect(mocks.exportSkillArchiveBlob).toHaveBeenCalledWith('skill-1') }) }) - it('cancels export without downloading the skill', async () => { - const user = userEvent.setup() - renderSkillsPage() - - await user.click( - await screen.findByRole('button', { - name: 'skill.skillManagement.moreActions:{"name":"Refund approval"}', - }), - ) - await user.click(await screen.findByText('common.operation.export')) - const dialog = await screen.findByRole('alertdialog') - await user.click(within(dialog).getByRole('button', { name: 'common.operation.cancel' })) - - await waitFor(() => expect(screen.queryByRole('alertdialog')).not.toBeInTheDocument()) - expect(mocks.exportSkillArchiveBlob).not.toHaveBeenCalled() - expect(mocks.downloadBlob).not.toHaveBeenCalled() - }) - it('confirms deletion with the skill name and refreshes list data', async () => { const user = userEvent.setup() renderSkillsPage() diff --git a/web/features/skills/detail/builder-panel.tsx b/web/features/skills/detail/builder-panel.tsx index 443189102fd..7d0f066aaa0 100644 --- a/web/features/skills/detail/builder-panel.tsx +++ b/web/features/skills/detail/builder-panel.tsx @@ -8,7 +8,6 @@ import type { BuilderChatMessage, SkillBuilderAttachment, SkillBuilderModel } fr import type { FormValue } from '@/app/components/header/account-setting/model-provider-page/declarations' import { Button } from '@langgenius/dify-ui/button' import { cn } from '@langgenius/dify-ui/cn' -import { toast } from '@langgenius/dify-ui/toast' import { useQuery, useQueryClient } from '@tanstack/react-query' import { useEffect, useId, useMemo, useRef, useState } from 'react' import { useTranslation } from 'react-i18next' @@ -20,6 +19,7 @@ import { import { useDefaultModel } from '@/app/components/header/account-setting/model-provider-page/hooks' import ModelParameterModal from '@/app/components/header/account-setting/model-provider-page/model-parameter-modal' import { ModelSelector } from '@/app/components/header/account-setting/model-provider-page/model-selector' +import { toast } from '@/app/notifications' import { consoleQuery } from '@/service/console' import { sendSkillAssistMessage, uploadSkillFile } from '../client' import { SkillBuilderGridTexture } from './builder-grid-texture' diff --git a/web/features/skills/detail/file-editor.tsx b/web/features/skills/detail/file-editor.tsx index fbe752440c4..38170101705 100644 --- a/web/features/skills/detail/file-editor.tsx +++ b/web/features/skills/detail/file-editor.tsx @@ -9,13 +9,13 @@ import type { ChangeEvent, FormEvent, KeyboardEvent } from 'react' import type { SkillFileMutationCoordinator } from './shared' import { Button } from '@langgenius/dify-ui/button' import { cn } from '@langgenius/dify-ui/cn' -import { toast } from '@langgenius/dify-ui/toast' import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query' import { noop } from 'es-toolkit/function' import { useCallback, useEffect, useMemo, useRef, useState } from 'react' import { useTranslation } from 'react-i18next' import { SkeletonRectangle } from '@/app/components/base/skeleton' import CodeEditor from '@/app/components/workflow/nodes/_base/components/editor/code-editor' +import { toast } from '@/app/notifications' import { useFormatTimeFromNow } from '@/hooks/use-format-time-from-now' import dynamic from '@/next/dynamic' import { consoleQuery } from '@/service/console' diff --git a/web/features/skills/detail/file-tree.tsx b/web/features/skills/detail/file-tree.tsx index 7c546f15e7a..39beea3b2de 100644 --- a/web/features/skills/detail/file-tree.tsx +++ b/web/features/skills/detail/file-tree.tsx @@ -50,7 +50,6 @@ import { ScrollAreaThumb, ScrollAreaViewport, } from '@langgenius/dify-ui/scroll-area' -import { toast } from '@langgenius/dify-ui/toast' import { Tooltip, TooltipContent, TooltipTrigger } from '@langgenius/dify-ui/tooltip' import { formatForDisplay, matchesKeyboardEvent, useHotkey } from '@tanstack/react-hotkeys' import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query' @@ -63,6 +62,7 @@ import { gotoAnythingDialogHandle } from '@/app/components/goto-anything/dialog- import { GOTO_ANYTHING_HOTKEY } from '@/app/components/goto-anything/hotkeys' import AccountSection from '@/app/components/main-nav/components/account-section' import HelpMenu from '@/app/components/main-nav/components/help-menu' +import { toast } from '@/app/notifications' import Link from '@/next/link' import { consoleQuery } from '@/service/console' import { fetchSkillFileBlob, uploadSkillFile } from '../client' diff --git a/web/features/skills/detail/page.tsx b/web/features/skills/detail/page.tsx index 1b918b5727a..5b9af3506b1 100644 --- a/web/features/skills/detail/page.tsx +++ b/web/features/skills/detail/page.tsx @@ -10,10 +10,10 @@ import { AlertDialogDescription, AlertDialogTitle, } from '@langgenius/dify-ui/alert-dialog' -import { toast } from '@langgenius/dify-ui/toast' import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query' import { useCallback, useEffect, useMemo, useState } from 'react' import { useTranslation } from 'react-i18next' +import { toast } from '@/app/notifications' import useDocumentTitle from '@/hooks/use-document-title' import Link from '@/next/link' import { consoleQuery } from '@/service/console' diff --git a/web/features/skills/detail/shared.tsx b/web/features/skills/detail/shared.tsx index 2afa1dd2933..e740e8d084a 100644 --- a/web/features/skills/detail/shared.tsx +++ b/web/features/skills/detail/shared.tsx @@ -12,8 +12,8 @@ import type { FormValue, } from '@/app/components/header/account-setting/model-provider-page/declarations' import { cn } from '@langgenius/dify-ui/cn' -import { toast } from '@langgenius/dify-ui/toast' import { CodeLanguage } from '@/app/components/workflow/nodes/code/types' +import { toast } from '@/app/notifications' import { getFileIconType } from '@/features/agent-v2/agent-detail/configure/components/orchestrate/files/file-icon' import { consoleClient, consoleQuery } from '@/service/console' import { diff --git a/web/features/skills/detail/sidebar-actions.tsx b/web/features/skills/detail/sidebar-actions.tsx index 611bad6c07c..191dc923d62 100644 --- a/web/features/skills/detail/sidebar-actions.tsx +++ b/web/features/skills/detail/sidebar-actions.tsx @@ -19,10 +19,10 @@ import { } from '@langgenius/dify-ui/dropdown-menu' import { Field, FieldLabel } from '@langgenius/dify-ui/field' import { InputGroup, InputGroupAddon, InputGroupInput } from '@langgenius/dify-ui/input-group' -import { toast } from '@langgenius/dify-ui/toast' import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query' import { useState } from 'react' import { Trans, useTranslation } from 'react-i18next' +import { toast } from '@/app/notifications' import { useRouter } from '@/next/navigation' import { consoleQuery } from '@/service/console' import { downloadBlob } from '@/utils/download' diff --git a/web/features/skills/detail/skill-display-name-editor.tsx b/web/features/skills/detail/skill-display-name-editor.tsx index d7c519e6139..54a38a1f519 100644 --- a/web/features/skills/detail/skill-display-name-editor.tsx +++ b/web/features/skills/detail/skill-display-name-editor.tsx @@ -2,10 +2,10 @@ import type { SkillDetailResponse } from '@dify/contracts/api/console/workspaces/types.gen' import type { SkillFileMutationCoordinator } from './shared' -import { toast } from '@langgenius/dify-ui/toast' import { useMutation, useQueryClient } from '@tanstack/react-query' import { useLayoutEffect, useRef, useState } from 'react' import { useTranslation } from 'react-i18next' +import { toast } from '@/app/notifications' import { consoleQuery } from '@/service/console' import { getAsyncSkillErrorPayload, diff --git a/web/features/skills/detail/skill-metadata.tsx b/web/features/skills/detail/skill-metadata.tsx index 461d7e895a2..529605cc946 100644 --- a/web/features/skills/detail/skill-metadata.tsx +++ b/web/features/skills/detail/skill-metadata.tsx @@ -17,12 +17,12 @@ import { ComboboxTrigger, createComboboxItems, } from '@langgenius/dify-ui/combobox' -import { toast } from '@langgenius/dify-ui/toast' import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query' import { useMemo, useState } from 'react' import { useTranslation } from 'react-i18next' import AppIcon from '@/app/components/base/app-icon' import { SkeletonRectangle } from '@/app/components/base/skeleton' +import { toast } from '@/app/notifications' import { isCreateTagOption } from '@/features/tag-management/components/tag-combobox-item' import { TagManagementModal } from '@/features/tag-management/components/tag-management-modal' import { TagSearchContentView } from '@/features/tag-management/components/tag-search-content' diff --git a/web/features/skills/detail/skill-pdf-preview.tsx b/web/features/skills/detail/skill-pdf-preview.tsx index d407c23f414..838a404230c 100644 --- a/web/features/skills/detail/skill-pdf-preview.tsx +++ b/web/features/skills/detail/skill-pdf-preview.tsx @@ -5,7 +5,7 @@ import { PdfHighlighter, PdfLoader, } from '@/app/components/base/file-uploader/pdf-highlighter-adapter' -import Loading from '@/app/components/base/loading' +import { LoadingPlaceholder } from '@/app/components/base/loading-placeholder' export function SkillPdfPreview({ fileName, url }: { fileName: string; url: string }) { return ( @@ -14,11 +14,7 @@ export function SkillPdfPreview({ fileName, url }: { fileName: string; url: stri - -
- } + beforeLoad={} > {(pdfDocument) => ( fetchSkillArchiveBlob(skill.id), onSuccess: (blob) => { downloadBlob({ data: blob, fileName: `${skill.name}.zip` }) - setIsExportOpen(false) }, onError: () => { toast.error(tCommon(($) => $['operation.downloadFailed'])) @@ -554,7 +552,11 @@ function SkillCard({ {tCommon(($) => $['operation.duplicate'])} )} - setIsExportOpen(true)}> +
- { - if (!exportMutation.isPending) setIsExportOpen(open) - }} - > - -
- - {t(($) => $['skillManagement.exportDialog.title'])} - - - {t(($) => $['skillManagement.exportDialog.description'])} - -
- - - {tCommon(($) => $['operation.cancel'])} - - - {tCommon(($) => $['operation.confirm'])} - - -
-
) diff --git a/web/features/tag-management/__tests__/tag-item-editor.spec.tsx b/web/features/tag-management/__tests__/tag-item-editor.spec.tsx index 4efdf35ff99..c40536c7634 100644 --- a/web/features/tag-management/__tests__/tag-item-editor.spec.tsx +++ b/web/features/tag-management/__tests__/tag-item-editor.spec.tsx @@ -70,7 +70,7 @@ vi.mock('@/service/console', () => ({ }, })) -vi.mock('@langgenius/dify-ui/toast', () => ({ +vi.mock('@/app/notifications', () => ({ toast: tagMocks.api, })) diff --git a/web/features/tag-management/__tests__/tag-management-modal.spec.tsx b/web/features/tag-management/__tests__/tag-management-modal.spec.tsx index 5bd1f6fdb32..42d41572a5e 100644 --- a/web/features/tag-management/__tests__/tag-management-modal.spec.tsx +++ b/web/features/tag-management/__tests__/tag-management-modal.spec.tsx @@ -20,7 +20,7 @@ const { mockNotify, mockToast } = vi.hoisted(() => { return { mockNotify, mockToast } }) -vi.mock('@langgenius/dify-ui/toast', () => ({ +vi.mock('@/app/notifications', () => ({ toast: mockToast, })) diff --git a/web/features/tag-management/__tests__/tag-selector.spec.tsx b/web/features/tag-management/__tests__/tag-selector.spec.tsx index 653fae669ec..65cb82ae6e5 100644 --- a/web/features/tag-management/__tests__/tag-selector.spec.tsx +++ b/web/features/tag-management/__tests__/tag-selector.spec.tsx @@ -19,7 +19,7 @@ const { mockToast } = vi.hoisted(() => { return { mockToast } }) -vi.mock('@langgenius/dify-ui/toast', () => ({ toast: mockToast })) +vi.mock('@/app/notifications', () => ({ toast: mockToast })) const { mockUseQueryData, createTag, bindTag, unBindTag } = vi.hoisted(() => { const mockUseQueryData: { current: Tag[] } = { current: [] } diff --git a/web/features/tag-management/components/tag-item-editor.tsx b/web/features/tag-management/components/tag-item-editor.tsx index 434ea325e68..ca531e69d25 100644 --- a/web/features/tag-management/components/tag-item-editor.tsx +++ b/web/features/tag-management/components/tag-item-editor.tsx @@ -9,12 +9,12 @@ import { AlertDialogTitle, } from '@langgenius/dify-ui/alert-dialog' import { cn } from '@langgenius/dify-ui/cn' -import { toast } from '@langgenius/dify-ui/toast' import { Tooltip, TooltipContent, TooltipTrigger } from '@langgenius/dify-ui/tooltip' import { useMutation } from '@tanstack/react-query' import { useDebounceFn } from 'ahooks' import { useState } from 'react' import { useTranslation } from 'react-i18next' +import { toast } from '@/app/notifications' import { consoleQuery } from '@/service/console' type TagItemEditorProps = { diff --git a/web/features/tag-management/components/tag-management-modal.tsx b/web/features/tag-management/components/tag-management-modal.tsx index 8b81e18d178..8e6eb9434ec 100644 --- a/web/features/tag-management/components/tag-management-modal.tsx +++ b/web/features/tag-management/components/tag-management-modal.tsx @@ -2,11 +2,11 @@ import type { TagType } from '@dify/contracts/api/console/tags/types.gen' import { Dialog, DialogClose, DialogContent } from '@langgenius/dify-ui/dialog' import { IconButton } from '@langgenius/dify-ui/icon-button' -import { toast } from '@langgenius/dify-ui/toast' import { useMutation, useQuery } from '@tanstack/react-query' import { useAtomValue } from 'jotai' import { useState } from 'react' import { useTranslation } from 'react-i18next' +import { toast } from '@/app/notifications' import { workspacePermissionKeysAtom } from '@/context/permission-state' import { consoleQuery } from '@/service/console' import { hasPermission } from '@/utils/permission' diff --git a/web/features/tag-management/components/tag-selector.tsx b/web/features/tag-management/components/tag-selector.tsx index 77719877b6d..35859accf66 100644 --- a/web/features/tag-management/components/tag-selector.tsx +++ b/web/features/tag-management/components/tag-selector.tsx @@ -10,11 +10,11 @@ import { ComboboxTrigger, createComboboxItems, } from '@langgenius/dify-ui/combobox' -import { toast } from '@langgenius/dify-ui/toast' import { useMutation, useQuery } from '@tanstack/react-query' import { useAtomValue } from 'jotai' import { useCallback, useMemo, useState } from 'react' import { useTranslation } from 'react-i18next' +import { toast } from '@/app/notifications' import { workspacePermissionKeysAtom } from '@/context/permission-state' import { consoleQuery } from '@/service/console' import { hasPermission } from '@/utils/permission' diff --git a/web/global.d.ts b/web/global.d.ts index 2d3231692a8..715eec30a85 100644 --- a/web/global.d.ts +++ b/web/global.d.ts @@ -1,5 +1,4 @@ import './types/i18n' -import './types/jsx' import './types/assets' declare global { diff --git a/web/hooks/use-app-favicon.spec.ts b/web/hooks/use-app-favicon.spec.ts index dc83e02b4d6..bacebbc8cf1 100644 --- a/web/hooks/use-app-favicon.spec.ts +++ b/web/hooks/use-app-favicon.spec.ts @@ -1,10 +1,6 @@ import { renderHook, waitFor } from '@testing-library/react' import { useAppFavicon } from './use-app-favicon' -vi.mock('@/utils/emoji', () => ({ - searchEmoji: vi.fn(async () => '🤖'), -})) - const firstAppIcon = 'https://example.com/app-one.png' const secondAppIcon = 'https://example.com/app-two.png' @@ -76,7 +72,7 @@ describe('useAppFavicon', () => { it('renders emoji icons as SVG favicons', async () => { renderHook(() => - useAppFavicon({ icon_type: 'emoji', icon: 'robot', icon_background: '#ffffff' }), + useAppFavicon({ icon_type: 'emoji', icon: 'robot_face', icon_background: '#ffffff' }), ) await waitFor(() => { diff --git a/web/hooks/use-app-favicon.ts b/web/hooks/use-app-favicon.ts index 1ef87c53a47..263e467d376 100644 --- a/web/hooks/use-app-favicon.ts +++ b/web/hooks/use-app-favicon.ts @@ -1,7 +1,7 @@ import type { AppIconType } from '@/types/app' import { useEffect } from 'react' import { appDefaultIconBackground } from '@/config' -import { searchEmoji } from '@/utils/emoji' +import { resolveEmoji } from '@/utils/emoji' type UseAppFaviconOptions = { enable?: boolean @@ -27,11 +27,11 @@ export function useAppFavicon(options: UseAppFaviconOptions) { } else if (icon_type === 'link') { href = icon } else if (icon_type !== 'emoji' || icon) { - const emoji = icon ? await searchEmoji(icon) : '🤖' + const emoji = resolveEmoji(icon) href = 'data:image/svg+xml,' + `` + - `${emoji}` + + `${emoji.replaceAll('&', '&').replaceAll('<', '<').replaceAll('>', '>')}` + '' type = 'image/svg+xml' } diff --git a/web/hooks/use-format-time-from-now.spec.ts b/web/hooks/use-format-time-from-now.spec.ts index da89c8206c8..adb0ad9f7e5 100644 --- a/web/hooks/use-format-time-from-now.spec.ts +++ b/web/hooks/use-format-time-from-now.spec.ts @@ -14,10 +14,11 @@ import type { Mock } from 'vite-plus/test' */ import { renderHook } from '@testing-library/react' // Import after mock to get the mocked version -import { useLocale } from '@/context/i18n' +import { useLocale } from '#i18n' import { useFormatTimeFromNow } from './use-format-time-from-now' -vi.mock('@/context/i18n', () => ({ +vi.mock('#i18n', async (importOriginal) => ({ + ...(await importOriginal()), useLocale: vi.fn(() => 'en-US'), })) diff --git a/web/hooks/use-format-time-from-now.ts b/web/hooks/use-format-time-from-now.ts index 759d24a9b34..a245758eb10 100644 --- a/web/hooks/use-format-time-from-now.ts +++ b/web/hooks/use-format-time-from-now.ts @@ -1,8 +1,8 @@ import dayjs from 'dayjs' import relativeTime from 'dayjs/plugin/relativeTime' import { useCallback } from 'react' -import { useLocale } from '@/context/i18n' -import { localeMap } from '@/i18n-config/language' +import { useLocale } from '#i18n' +import { localeMap } from '@/i18n/language' import 'dayjs/locale/az' import 'dayjs/locale/de' import 'dayjs/locale/es' diff --git a/web/hooks/use-i18n.ts b/web/hooks/use-i18n.ts index 606ea9da7f6..326750562ef 100644 --- a/web/hooks/use-i18n.ts +++ b/web/hooks/use-i18n.ts @@ -1,9 +1,7 @@ -import { useLanguage } from '@/app/components/header/account-setting/model-provider-page/hooks' -import { renderI18nObject } from '@/i18n-config' +import { useLocale } from '#i18n' +import { getModelLanguage, renderI18nObject } from '@/i18n/metadata' export const useRenderI18nObject = () => { - const language = useLanguage() - return (obj: Record) => { - return renderI18nObject(obj, language) - } + const language = getModelLanguage(useLocale()) + return (obj: Record) => renderI18nObject(obj, language) } diff --git a/web/hooks/use-import-dsl.spec.tsx b/web/hooks/use-import-dsl.spec.tsx index 65b0a141f15..d82a98aa12d 100644 --- a/web/hooks/use-import-dsl.spec.tsx +++ b/web/hooks/use-import-dsl.spec.tsx @@ -16,7 +16,7 @@ const toastMocks = vi.hoisted(() => ({ warning: vi.fn(), })) -vi.mock('@langgenius/dify-ui/toast', () => ({ +vi.mock('@/app/notifications', () => ({ toast: toastMocks, })) diff --git a/web/hooks/use-import-dsl.ts b/web/hooks/use-import-dsl.ts index b7b05c8edae..b137dc6c554 100644 --- a/web/hooks/use-import-dsl.ts +++ b/web/hooks/use-import-dsl.ts @@ -1,12 +1,12 @@ import type { AppImportPayload, Import } from '@dify/contracts/api/console/apps/types.gen' import type { AppIconType } from '@/types/app' -import { toast } from '@langgenius/dify-ui/toast' import { useMutation, useSuspenseQuery } from '@tanstack/react-query' import { useAtomValue } from 'jotai' import { createElement, useCallback, useRef, useState } from 'react' import { useTranslation } from 'react-i18next' import DSLImportWarningDescription from '@/app/components/app/create-from-dsl-modal/dsl-import-warning-description' import { usePluginDependencies } from '@/app/components/workflow/plugin-dependency/hooks' +import { toast } from '@/app/notifications' import { workspacePermissionKeysAtom } from '@/context/permission-state' import { userProfileQueryOptions } from '@/features/account-profile/client' import { systemFeaturesQueryOptions } from '@/features/system-features/client' diff --git a/web/hooks/use-timestamp.spec.ts b/web/hooks/use-timestamp.spec.ts index d4b62f7411b..58d41d1bc72 100644 --- a/web/hooks/use-timestamp.spec.ts +++ b/web/hooks/use-timestamp.spec.ts @@ -8,7 +8,8 @@ import useTimestamp from './use-timestamp' const mockUseLocale = vi.hoisted(() => vi.fn(() => 'en-US')) -vi.mock('@/context/i18n', () => ({ +vi.mock('#i18n', async (importOriginal) => ({ + ...(await importOriginal()), useLocale: mockUseLocale, })) diff --git a/web/hooks/use-timestamp.ts b/web/hooks/use-timestamp.ts index 35160d6c97b..b710d62bee8 100644 --- a/web/hooks/use-timestamp.ts +++ b/web/hooks/use-timestamp.ts @@ -4,7 +4,7 @@ import dayjs from 'dayjs' import timezone from 'dayjs/plugin/timezone' import utc from 'dayjs/plugin/utc' import { useCallback } from 'react' -import { useLocale } from '@/context/i18n' +import { useLocale } from '#i18n' import { userProfileQueryOptions } from '@/features/account-profile/client' import { formatToLocalTime } from '@/utils/format' diff --git a/web/i18n-config/README.md b/web/i18n-config/README.md deleted file mode 100644 index 1777800acc6..00000000000 --- a/web/i18n-config/README.md +++ /dev/null @@ -1,43 +0,0 @@ -# Internationalization - -English JSON files under `web/i18n/en-US/` are the source locale. Other locale directories must keep the same flat keys and placeholders. i18next uses `keySeparator: false`, so dots are part of a key rather than nested-object separators. - -## Owners - -- `languages.ts` is the source of truth for supported Web locales. -- `language.ts` owns locale normalization and product-specific locale mappings. -- `resources.ts` owns the typed namespace registry. File names use kebab case while namespaces use camel case, for example `app-debug.json` and `appDebug`. -- `locale-resources/.ts` owns lazy loading for one locale. -- `settings.ts` owns shared i18next options. -- `web/scripts/check-i18n.js` owns locale key validation and removal of extra keys. - -Do not copy the language registry into documentation. Read the current source files when adding a locale or namespace. - -## Add a locale - -1. Add the locale metadata to `languages.ts`. -2. Add a matching `web/i18n//` directory with every source namespace. -3. Add `locale-resources/.ts` and the required mappings in `language.ts`. -4. Keep the backend language and timezone registry in `api/constants/languages.py` aligned when the locale is accepted by backend APIs. -5. Run the complete i18n check before submitting the change. - -`LanguagesSupported` is populated from the supported entries in `languages.ts`. `language.ts` also owns the accepted locale spellings and the `I18nText` contract; keep them aligned when adding a locale. - -## Add or change copy - -Add or change the English key first, then update every supported locale. Preserve interpolation variables and markup placeholders exactly. - -Run from `web/`: - -```sh -pnpm i18n:check -pnpm i18n:check --file app billing --lang zh-Hans ja-JP -``` - -Arguments after `--file` and `--lang` are space-separated. Use `--auto-remove` only when intentionally deleting extra locale keys. - -## Automated translation - -Changes to `web/i18n/en-US/*.json` on `main` trigger the scoped translation workflow. The workflow derives target locales from `languages.ts`, translates only the changed namespaces and keys, verifies them with `i18n:check`, and opens a pull request when translations change. - -Use the `Translate i18n Files with Claude Code` workflow dispatch for a manual scoped sync. Full mode requires an explicit file list. diff --git a/web/i18n-config/index.ts b/web/i18n-config/index.ts deleted file mode 100644 index 2268cf2d540..00000000000 --- a/web/i18n-config/index.ts +++ /dev/null @@ -1,28 +0,0 @@ -import type { Locale } from '@/i18n-config/language' -import Cookies from 'js-cookie' -import { LOCALE_COOKIE_NAME } from '@/config' -import { changeLanguage } from '@/i18n-config/client' -import { LanguagesSupported } from '@/i18n-config/language' - -export const i18n = { - defaultLocale: 'en-US', - locales: LanguagesSupported, -} as const - -export type { Locale } - -export const setLocaleOnClient = async (locale: Locale, reloadPage = true) => { - Cookies.set(LOCALE_COOKIE_NAME, locale, { expires: 365 }) - await changeLanguage(locale) - if (reloadPage) location.reload() -} - -export const renderI18nObject = ( - obj: Record | null | undefined, - language: string, -) => { - if (!obj) return '' - if (obj?.[language]) return obj[language] - if (obj?.en_US) return obj.en_US - return Object.values(obj).find((value): value is string => !!value) || '' -} diff --git a/web/i18n-config/language.ts b/web/i18n-config/language.ts deleted file mode 100644 index e6d39916712..00000000000 --- a/web/i18n-config/language.ts +++ /dev/null @@ -1,139 +0,0 @@ -import type { DocLanguage } from '@/types/doc-paths' -import data from './languages' - -export type I18nText = Record<(typeof LanguagesSupported)[number], string> - -export const languages = data.languages - -// for compatibility -export type Locale = 'ja_JP' | 'zh_Hans' | 'en_US' | (typeof languages)[number]['value'] - -export const LanguagesSupported: Locale[] = languages - .filter((item) => item.supported) - .map((item) => item.value) - -export const getLanguage = (locale: Locale): Locale => { - // Plugin metadata supports only en_US, zh_Hans, ja_JP, and pt_BR; otherwise use en_US. - if (['zh-Hans', 'ja-JP', 'pt-BR'].includes(locale)) return locale.replace('-', '_') as Locale - - return LanguagesSupported[0]!.replace('-', '_') as Locale -} - -const DOC_LANGUAGE: Record = { - 'zh-Hans': 'zh', - 'ja-JP': 'ja', - 'en-US': 'en', -} - -export type AccessControlTemplateLanguage = 'zh' | 'ja' | 'en' - -const ACCESS_CONTROL_TEMPLATE_LANGUAGE: Record = { - 'zh-Hans': 'zh', - 'ja-JP': 'ja', - 'en-US': 'en', -} - -export const localeMap: Record = { - 'en-US': 'en', - en_US: 'en', - 'zh-Hans': 'zh-cn', - zh_Hans: 'zh-cn', - 'zh-Hant': 'zh-tw', - 'pt-BR': 'pt-br', - 'es-ES': 'es', - 'fr-FR': 'fr', - 'de-DE': 'de', - 'ja-JP': 'ja', - ja_JP: 'ja', - 'ko-KR': 'ko', - 'lo-LA': 'lo', - 'ru-RU': 'ru', - 'it-IT': 'it', - 'th-TH': 'th', - 'id-ID': 'id', - 'nl-NL': 'nl', - 'uk-UA': 'uk', - 'vi-VN': 'vi', - 'ro-RO': 'ro', - 'pl-PL': 'pl', - 'hi-IN': 'hi', - 'tr-TR': 'tr', - 'fa-IR': 'fa', - 'sl-SI': 'sl', - 'ar-TN': 'ar', - 'az-AZ': 'az', -} - -export const getDocLanguage = (locale: string): DocLanguage => { - return DOC_LANGUAGE[locale] || 'en' -} - -export const getAccessControlTemplateLanguage = (locale: string): AccessControlTemplateLanguage => { - return ACCESS_CONTROL_TEMPLATE_LANGUAGE[locale] || 'en' -} - -export const NOTICE_I18N = { - title: { - en_US: 'Important Notice', - zh_Hans: '重要公告', - zh_Hant: '重要公告', - pt_BR: 'Aviso Importante', - es_ES: 'Aviso Importante', - fr_FR: 'Avis important', - de_DE: 'Wichtiger Hinweis', - ja_JP: '重要なお知らせ', - ko_KR: '중요 공지', - ru_RU: 'Важное Уведомление', - it_IT: 'Avviso Importante', - th_TH: 'ประกาศสำคัญ', - id_ID: 'Pengumuman Penting', - uk_UA: 'Важливе повідомлення', - vi_VN: 'Thông báo quan trọng', - ro_RO: 'Anunț Important', - pl_PL: 'Ważne ogłoszenie', - hi_IN: 'महत्वपूर्ण सूचना', - tr_TR: 'Önemli Duyuru', - fa_IR: 'هشدار مهم', - sl_SI: 'Pomembno obvestilo', - ar_TN: 'إشعار مهم', - }, - desc: { - en_US: - 'Our system will be unavailable from 19:00 to 24:00 UTC on August 28 for an upgrade. For questions, kindly contact our support team (support@dify.ai). We value your patience.', - zh_Hans: - '为了有效提升数据检索能力及稳定性,Dify 将于 2023 年 8 月 29 日 03:00 至 08:00 期间进行服务升级,届时 Dify 云端版及应用将无法访问。感谢您的耐心与支持。', - pt_BR: - 'Our system will be unavailable from 19:00 to 24:00 UTC on August 28 for an upgrade. For questions, kindly contact our support team (support@dify.ai). We value your patience.', - es_ES: - 'Our system will be unavailable from 19:00 to 24:00 UTC on August 28 for an upgrade. For questions, kindly contact our support team (support@dify.ai). We value your patience.', - fr_FR: - 'Our system will be unavailable from 19:00 to 24:00 UTC on August 28 for an upgrade. For questions, kindly contact our support team (support@dify.ai). We value your patience.', - de_DE: - 'Our system will be unavailable from 19:00 to 24:00 UTC on August 28 for an upgrade. For questions, kindly contact our support team (support@dify.ai). We value your patience.', - ja_JP: - 'Our system will be unavailable from 19:00 to 24:00 UTC on August 28 for an upgrade. For questions, kindly contact our support team (support@dify.ai). We value your patience.', - ko_KR: - '시스템이 업그레이드를 위해 UTC 시간대로 8 월 28 일 19:00 ~ 24:00 에 사용 불가될 예정입니다. 질문이 있으시면 지원 팀에 연락주세요 (support@dify.ai). 최선을 다해 답변해드리겠습니다.', - pl_PL: - 'Nasz system będzie niedostępny od 19:00 do 24:00 UTC 28 sierpnia w celu aktualizacji. W przypadku pytań prosimy o kontakt z naszym zespołem wsparcia (support@dify.ai). Doceniamy Twoją cierpliwość.', - uk_UA: - 'Наша система буде недоступна з 19:00 до 24:00 UTC 28 серпня для оновлення. Якщо у вас виникнуть запитання, будь ласка, зв’яжіться з нашою службою підтримки (support@dify.ai). Дякуємо за терпіння.', - ru_RU: - 'Наша система будет недоступна с 19:00 до 24:00 UTC 28 августа для обновления. По вопросам, пожалуйста, обращайтесь в нашу службу поддержки (support@dify.ai). Спасибо за ваше терпение', - vi_VN: - 'Hệ thống của chúng tôi sẽ ngừng hoạt động từ 19:00 đến 24:00 UTC vào ngày 28 tháng 8 để nâng cấp. Nếu có thắc mắc, vui lòng liên hệ với nhóm hỗ trợ của chúng tôi (support@dify.ai). Chúng tôi đánh giá cao sự kiên nhẫn của bạn.', - id_ID: - 'Sistem kami tidak akan tersedia dari 19:00 hingga 24:00 UTC pada 28 Agustus untuk pemutakhiran. Untuk pertanyaan, silakan hubungi tim dukungan kami (support@dify.ai). Kami menghargai kesabaran Anda.', - tr_TR: - "Sistemimiz, 28 Ağustos'ta 19:00 ile 24:00 UTC saatleri arasında güncelleme nedeniyle kullanılamayacaktır. Sorularınız için lütfen destek ekibimizle iletişime geçin (support@dify.ai). Sabrınız için teşekkür ederiz.", - fa_IR: - 'سیستم ما از ساعت 19:00 تا 24:00 UTC در تاریخ 28 اوت برای ارتقاء در دسترس نخواهد بود. برای سؤالات، لطفاً با تیم پشتیبانی ما (support@dify.ai) تماس بگیرید. ما برای صبر شما ارزش قائلیم.', - sl_SI: - 'Naš sistem ne bo na voljo od 19:00 do 24:00 UTC 28. avgusta zaradi nadgradnje. Za vprašanja se obrnite na našo skupino za podporo (support@dify.ai). Cenimo vašo potrpežljivost.', - th_TH: - 'ระบบของเราจะไม่สามารถใช้งานได้ตั้งแต่เวลา 19:00 ถึง 24:00 UTC ในวันที่ 28 สิงหาคม เพื่อทำการอัปเกรด หากมีคำถามใดๆ กรุณาติดต่อทีมสนับสนุนของเรา (support@dify.ai) เราขอขอบคุณในความอดทนของท่าน', - ar_TN: - 'سيكون نظامنا غير متاح من الساعة 19:00 إلى 24:00 بالتوقيت العالمي المنسق في 28 أغسطس لإجراء ترقية. للأسئلة، يرجى الاتصال بفريق الدعم لدينا (support@dify.ai). نحن نقدر صبرك.', - }, - href: '#', -} diff --git a/web/i18n/README.md b/web/i18n/README.md new file mode 100644 index 00000000000..632a41e9e7d --- /dev/null +++ b/web/i18n/README.md @@ -0,0 +1,77 @@ +# Internationalization + +English JSON files under `web/i18n/locales/en-US/` are the source locale. Other locale directories must keep the same flat keys and placeholders. i18next uses `keySeparator: false`, so dots are part of a key rather than nested-object separators. + +The module keeps runtime code and bundled translation assets together. `#i18n` remains the conditional client/server entrypoint; `locales/` contains translation JSON only. Per-locale loaders preserve the existing dynamic-import boundaries. This layout is a project ownership convention, not an i18next requirement. + +## Owners + +- `languages.ts` is the source of truth for supported Web locales. +- `locale.ts` owns canonical UI locale tags, the default locale, and input normalization. +- `language.ts` owns documentation, access-template, and date-library locale mappings. +- `metadata.ts` owns plugin/model language mappings and localized metadata selection. +- `index.ts` exports only shared configuration and the canonical UI locale type. +- `client.ts` owns client language changes and cookie persistence. +- `#i18n` selects the client or server translation hooks; use it for `useLocale`. +- `resources.ts` owns the typed namespace registry. File names use kebab case while namespaces use camel case, for example `app-debug.json` and `appDebug`. +- `locale-resources/.ts` owns lazy loading for one locale. +- `settings.ts` owns shared i18next options. +- `web/scripts/check-i18n.js` owns locale key validation and removal of extra keys. + +Do not copy the language registry into documentation. Read the current source files when adding a locale or namespace. + +## Add a locale + +1. Add the locale metadata to `languages.ts`. +2. Add a matching `web/i18n/locales//` directory with every source namespace. +3. Add `locale-resources/.ts` and the required mappings in `language.ts`. +4. Keep the backend language and timezone registry in `api/constants/languages.py` aligned when the locale is accepted by backend APIs. +5. Run the complete i18n check before submitting the change. + +`supportedLocales` is populated from the supported entries in `languages.ts`. `language.ts` exposes the existing `LanguagesSupported` and `I18nText` contracts. + +## Locale selection + +A nonempty locale cookie takes priority over `Accept-Language`. An unusable cookie falls back to English without consulting the header. Header negotiation preserves valid preferences and their quality order while ignoring malformed tags and wildcards. + +`locale.ts` canonicalizes language tags and accepts the existing `en_US`, `zh_Hans`, and `ja_JP` spellings at input boundaries. Client language changes, saved preferences, and resource paths use supported UI tags or the English default. Share-app language overrides do not write the console locale cookie. Plugin and model metadata spellings remain separate product contracts. + +## Add or change copy + +Add or change the English key first, then update every supported locale. Preserve interpolation variables and markup placeholders exactly. + +Run from `web/`: + +```sh +pnpm i18n:check +pnpm i18n:check --file app billing --lang zh-Hans ja-JP +``` + +Arguments after `--file` and `--lang` are space-separated. Use `--auto-remove` only when intentionally deleting extra locale keys. + +## Unused translations + +Vite application builds (`vp run build:vinext`) fail when static analysis identifies +potentially unused English keys in the application module graph. CI runs the same build in Web Style. The plugin +collects original module sources before transforms and analyzes each final client, +SSR, and RSC graph with its own source map after all environments finish. A key is +reported only when it is unused in every environment. Files that +are not imported by the application, including standalone tests and stories, do +not count as usage. Type-only dependencies inform analysis but do not count as +usage themselves. + +Unresolved dynamic keys protect their namespaces. The report may contain false +positives or miss unused keys; review actual usage before deleting any translations. +Findings list namespace and key; remove confirmed unused keys from all locale files +or restore their application usage. The +plugin never modifies locale files. The previous whole-project prune CLI has been +removed so there is only one definition of unused translations. + +Development, Storybook, and test startup do not run this check. Next.js builds do +not load Vite plugins. + +## Automated translation + +Changes to `web/i18n/locales/en-US/*.json` on `main` trigger the scoped translation workflow. The workflow derives target locales from `languages.ts`, translates only the changed namespaces and keys, verifies them with `i18n:check`, and opens a pull request when translations change. + +Use the `Translate i18n Files with Claude Code` workflow dispatch for a manual scoped sync. Full mode requires an explicit file list. diff --git a/web/i18n/__tests__/client-locale.spec.ts b/web/i18n/__tests__/client-locale.spec.ts new file mode 100644 index 00000000000..a3d808b8603 --- /dev/null +++ b/web/i18n/__tests__/client-locale.spec.ts @@ -0,0 +1,43 @@ +import Cookies from 'js-cookie' +import { changeLanguage, setLocaleOnClient } from '../client' + +const mocks = vi.hoisted(() => ({ changeLanguage: vi.fn() })) + +vi.mock('js-cookie', () => ({ default: { set: vi.fn() } })) +vi.mock('react-i18next', () => ({ getI18n: () => ({ changeLanguage: mocks.changeLanguage }) })) + +describe('client locale inputs', () => { + beforeEach(() => { + vi.clearAllMocks() + mocks.changeLanguage.mockResolvedValue(undefined) + }) + + it.each([ + ['zh_Hans', 'zh-Hans'], + ['ja_JP', 'ja-JP'], + ['en_US', 'en-US'], + ['ZH-hans', 'zh-Hans'], + ['zh-Hant', 'zh-Hant'], + ['en--US', 'en-US'], + ['unsupported', 'en-US'], + ])('changes and persists %s as %s', async (input, expected) => { + await setLocaleOnClient(input, false) + + expect(Cookies.set).toHaveBeenCalledWith('locale', expected, { expires: 365 }) + expect(mocks.changeLanguage).toHaveBeenCalledWith(expected) + }) + + it('normalizes a share-app override without persisting console preferences', async () => { + await changeLanguage('zh_Hans') + + expect(mocks.changeLanguage).toHaveBeenCalledWith('zh-Hans') + expect(Cookies.set).not.toHaveBeenCalled() + }) + + it('preserves the current language when the app has no override', async () => { + await changeLanguage(undefined) + + expect(mocks.changeLanguage).not.toHaveBeenCalled() + expect(Cookies.set).not.toHaveBeenCalled() + }) +}) diff --git a/web/i18n/__tests__/load-resource.spec.ts b/web/i18n/__tests__/load-resource.spec.ts new file mode 100644 index 00000000000..ca2ceb67843 --- /dev/null +++ b/web/i18n/__tests__/load-resource.spec.ts @@ -0,0 +1,16 @@ +import { loadI18nResource } from '../load-resource' + +describe('locale resource loading', () => { + it.each([ + ['en_US', 'Save'], + ['zh_Hans', '保存'], + ['ja_JP', '保存'], + ['ZH-hans', '保存'], + ['en--US', 'Save'], + ['unsupported', 'Save'], + ])('loads usable translations for %s', async (locale, expected) => { + const resource = await loadI18nResource(locale, 'common') + + expect(resource.default).toMatchObject({ 'operation.save': expected }) + }) +}) diff --git a/web/i18n/__tests__/metadata.spec.ts b/web/i18n/__tests__/metadata.spec.ts new file mode 100644 index 00000000000..47c88abb817 --- /dev/null +++ b/web/i18n/__tests__/metadata.spec.ts @@ -0,0 +1,19 @@ +import { getModelLanguage, getPluginLanguage, renderI18nObject } from '../metadata' + +describe('localized metadata', () => { + it('preserves separate plugin and model language support', () => { + expect(getPluginLanguage('pt-BR')).toBe('pt_BR') + expect(getPluginLanguage('fr-FR')).toBe('en_US') + expect(getModelLanguage('fr-FR')).toBe('fr_FR') + expect(getPluginLanguage('zh-Hans')).toBe('zh_Hans') + expect(getPluginLanguage('ja-JP')).toBe('ja_JP') + }) + + it('prefers requested copy, then English, then the first available translation', () => { + const copy = { zh_Hans: '中文', en_US: 'English', fr_FR: 'Français' } + expect(renderI18nObject(copy, 'fr_FR')).toBe('Français') + expect(renderI18nObject(copy, 'ja_JP')).toBe('English') + expect(renderI18nObject({ zh_Hans: '', fr_FR: 'Français' }, 'ja_JP')).toBe('Français') + expect(renderI18nObject(null, 'en_US')).toBe('') + }) +}) diff --git a/web/i18n-config/__tests__/plural-selector.spec.ts b/web/i18n/__tests__/plural-selector.spec.ts similarity index 69% rename from web/i18n-config/__tests__/plural-selector.spec.ts rename to web/i18n/__tests__/plural-selector.spec.ts index ceeed3aeaa4..170e89bc2b2 100644 --- a/web/i18n-config/__tests__/plural-selector.spec.ts +++ b/web/i18n/__tests__/plural-selector.spec.ts @@ -1,8 +1,7 @@ import type { SelectorParam } from 'i18next' import { createInstance } from 'i18next' import { describe, expect, it } from 'vite-plus/test' -import agentV2 from '../../i18n/en-US/agent-v-2.json' -import skill from '../../i18n/en-US/skill.json' +import skill from '../locales/en-US/skill.json' import { getInitOptions } from '../settings' describe('i18n selector configuration', () => { @@ -46,23 +45,5 @@ describe('i18n selector configuration', () => { expect(instance.t(uploadFailureKey, { count: 1, ns: 'skill' })).toBe('1 file upload failed.') expect(instance.t(uploadFailureKey, { count: 2, ns: 'skill' })).toBe('2 file uploads failed.') }) - - it('should pluralize Skill upload failure copy in the Agent V2 namespace', async () => { - const instance = createInstance() - await instance.init({ - ...getInitOptions(), - lng: 'en-US', - resources: { 'en-US': { agentV2 } }, - }) - const uploadFailureKey: SelectorParam<'agentV2'> = ($) => - $['skillManagement.detail.uploadFilesFailedStatus'] - - expect(instance.t(uploadFailureKey, { count: 1, ns: 'agentV2' })).toBe( - '1 file upload failed.', - ) - expect(instance.t(uploadFailureKey, { count: 2, ns: 'agentV2' })).toBe( - '2 file uploads failed.', - ) - }) }) }) diff --git a/web/i18n-config/__tests__/server.spec.ts b/web/i18n/__tests__/server.spec.ts similarity index 76% rename from web/i18n-config/__tests__/server.spec.ts rename to web/i18n/__tests__/server.spec.ts index 8f19d2b4a94..df1ccce8525 100644 --- a/web/i18n-config/__tests__/server.spec.ts +++ b/web/i18n/__tests__/server.spec.ts @@ -117,4 +117,42 @@ describe('server locale', () => { await expect(getLocaleOnServer()).resolves.toBe('en-US') }, ) + + it.each([ + ['en_US', 'en-US'], + ['zh_Hans', 'zh-Hans'], + ['ja_JP', 'ja-JP'], + ['ZH-hans', 'zh-Hans'], + ])('normalizes the saved locale %s to %s', async (saved, expected) => { + mocks.getCookie.mockReturnValue({ value: saved }) + const { getLocaleOnServer } = await import('../server') + + await expect(getLocaleOnServer()).resolves.toBe(expected) + expect(mocks.headers).not.toHaveBeenCalled() + }) + + it.each(['en--US', 'en_US_invalid', '*', 'zz-ZZ'])( + 'falls back for an unusable cookie without consulting the browser: %s', + async (saved) => { + mocks.getCookie.mockReturnValue({ value: saved }) + mocks.headers.mockResolvedValue(new Headers({ 'accept-language': 'ja-JP' })) + const { getLocaleOnServer } = await import('../server') + + await expect(getLocaleOnServer()).resolves.toBe('en-US') + expect(mocks.headers).not.toHaveBeenCalled() + }, + ) + + it.each([ + ['en--US,ja-JP;q=0.9,en-US;q=0.8', 'ja-JP'], + ['ja-JP,en-US;q=0.8,*;q=0.5', 'ja-JP'], + ['en-US;q=0.5,zh-CN;q=0.9', 'zh-Hans'], + ['*,en--US;q=0.5', 'en-US'], + ['ja-JP;q=0,en-US;q=0.5', 'en-US'], + ])('negotiates valid header preferences from %s', async (header, expected) => { + mocks.headers.mockResolvedValue(new Headers({ 'accept-language': header })) + const { getLocaleOnServer } = await import('../server') + + await expect(getLocaleOnServer()).resolves.toBe(expected) + }) }) diff --git a/web/i18n-config/client.ts b/web/i18n/client.ts similarity index 61% rename from web/i18n-config/client.ts rename to web/i18n/client.ts index a1b949d4997..d1234f5c5cd 100644 --- a/web/i18n-config/client.ts +++ b/web/i18n/client.ts @@ -1,11 +1,14 @@ 'use client' import type { Resource } from 'i18next' -import type { Locale } from '.' +import type { Locale } from './locale' import type { Namespace, NamespaceInFileName } from './resources' import { createInstance } from 'i18next' import resourcesToBackend from 'i18next-resources-to-backend' +import Cookies from 'js-cookie' import { getI18n, initReactI18next } from 'react-i18next' +import { LOCALE_COOKIE_NAME } from '@/config' import { loadI18nResource } from './load-resource' +import { normalizeLocale } from './locale' import { getInitOptions } from './settings' export function createI18nextInstance(lng: Locale, resources: Resource) { @@ -25,8 +28,15 @@ export function createI18nextInstance(lng: Locale, resources: Resource) { return instance } -export const changeLanguage = async (lng?: Locale) => { +export const changeLanguage = async (lng?: string) => { if (!lng) return const i18n = getI18n() - await i18n.changeLanguage(lng) + await i18n.changeLanguage(normalizeLocale(lng)) +} + +export const setLocaleOnClient = async (locale: string, reloadPage = true) => { + const normalized = normalizeLocale(locale) + Cookies.set(LOCALE_COOKIE_NAME, normalized, { expires: 365 }) + await changeLanguage(normalized) + if (reloadPage) location.reload() } diff --git a/web/i18n/index.ts b/web/i18n/index.ts new file mode 100644 index 00000000000..aae1532738c --- /dev/null +++ b/web/i18n/index.ts @@ -0,0 +1,5 @@ +import { defaultLocale, supportedLocales } from './locale' + +export const i18n = { defaultLocale, locales: supportedLocales } as const + +export type { Locale } from './locale' diff --git a/web/i18n/language.ts b/web/i18n/language.ts new file mode 100644 index 00000000000..d976b420de9 --- /dev/null +++ b/web/i18n/language.ts @@ -0,0 +1,63 @@ +import type { Locale } from './locale' +import type { DocLanguage } from '@/types/doc-paths' +import data from './languages' +import { supportedLocales } from './locale' + +export type I18nText = Record<(typeof LanguagesSupported)[number], string> + +export const languages = data.languages + +export const LanguagesSupported: Locale[] = supportedLocales + +const DOC_LANGUAGE: Record = { + 'zh-Hans': 'zh', + 'ja-JP': 'ja', + 'en-US': 'en', +} + +export type AccessControlTemplateLanguage = 'zh' | 'ja' | 'en' + +const ACCESS_CONTROL_TEMPLATE_LANGUAGE: Record = { + 'zh-Hans': 'zh', + 'ja-JP': 'ja', + 'en-US': 'en', +} + +export const localeMap: Record = { + 'en-US': 'en', + en_US: 'en', + 'zh-Hans': 'zh-cn', + zh_Hans: 'zh-cn', + 'zh-Hant': 'zh-tw', + 'pt-BR': 'pt-br', + 'es-ES': 'es', + 'fr-FR': 'fr', + 'de-DE': 'de', + 'ja-JP': 'ja', + ja_JP: 'ja', + 'ko-KR': 'ko', + 'lo-LA': 'lo', + 'ru-RU': 'ru', + 'it-IT': 'it', + 'th-TH': 'th', + 'id-ID': 'id', + 'nl-NL': 'nl', + 'uk-UA': 'uk', + 'vi-VN': 'vi', + 'ro-RO': 'ro', + 'pl-PL': 'pl', + 'hi-IN': 'hi', + 'tr-TR': 'tr', + 'fa-IR': 'fa', + 'sl-SI': 'sl', + 'ar-TN': 'ar', + 'az-AZ': 'az', +} + +export const getDocLanguage = (locale: string): DocLanguage => { + return DOC_LANGUAGE[locale] || 'en' +} + +export const getAccessControlTemplateLanguage = (locale: string): AccessControlTemplateLanguage => { + return ACCESS_CONTROL_TEMPLATE_LANGUAGE[locale] || 'en' +} diff --git a/web/i18n-config/languages.ts b/web/i18n/languages.ts similarity index 100% rename from web/i18n-config/languages.ts rename to web/i18n/languages.ts diff --git a/web/i18n-config/lib.client.ts b/web/i18n/lib.client.ts similarity index 62% rename from web/i18n-config/lib.client.ts rename to web/i18n/lib.client.ts index 501737b2748..cbc0f3f4358 100644 --- a/web/i18n-config/lib.client.ts +++ b/web/i18n/lib.client.ts @@ -1,5 +1,6 @@ 'use client' +import type { Locale } from './locale' import type { Namespace } from './resources' import { useTranslation as useTranslationOriginal } from 'react-i18next' @@ -7,4 +8,7 @@ export function useTranslation(ns?: return useTranslationOriginal(ns) } -export { useLocale } from '@/context/i18n' +export function useLocale(): Locale { + const { i18n } = useTranslationOriginal() + return i18n.language as Locale +} diff --git a/web/i18n-config/lib.server.ts b/web/i18n/lib.server.ts similarity index 100% rename from web/i18n-config/lib.server.ts rename to web/i18n/lib.server.ts diff --git a/web/i18n-config/load-resource.ts b/web/i18n/load-resource.ts similarity index 52% rename from web/i18n-config/load-resource.ts rename to web/i18n/load-resource.ts index 214a351a31f..b767f8093b9 100644 --- a/web/i18n-config/load-resource.ts +++ b/web/i18n/load-resource.ts @@ -1,35 +1,19 @@ import type { ResourceKey } from 'i18next' -import type { Locale } from './language' import type { Namespace, NamespaceInFileName } from './resources' import { kebabCase } from 'es-toolkit/string' -import { LanguagesSupported } from './language' +import { normalizeLocale } from './locale' type LocaleResourceModule = { loadResource: (fileNamespace: string) => Promise<{ default: ResourceKey }> } -const legacyLocaleMap: Partial> = { - en_US: 'en-US', - ja_JP: 'ja-JP', - zh_Hans: 'zh-Hans', -} - -const defaultLocale = 'en-US' satisfies Locale - -const normalizeLocale = (locale: Locale): Locale => { - const normalized = legacyLocaleMap[locale] ?? locale - if (LanguagesSupported.includes(normalized)) return normalized - - return defaultLocale -} - -const loadLocaleResources = (locale: Locale): Promise => { +const loadLocaleResources = (locale: string): Promise => { const normalized = normalizeLocale(locale) return import(`./locale-resources/${normalized}.ts`) } export const loadI18nResource = async ( - locale: Locale, + locale: string, namespace: Namespace | NamespaceInFileName, ) => { const { loadResource } = await loadLocaleResources(locale) diff --git a/web/i18n-config/locale-resources/ar-TN.ts b/web/i18n/locale-resources/ar-TN.ts similarity index 51% rename from web/i18n-config/locale-resources/ar-TN.ts rename to web/i18n/locale-resources/ar-TN.ts index 0233334f0c4..67115a32d26 100644 --- a/web/i18n-config/locale-resources/ar-TN.ts +++ b/web/i18n/locale-resources/ar-TN.ts @@ -1,2 +1,2 @@ export const loadResource = (fileNamespace: string) => - import(`../../i18n/ar-TN/${fileNamespace}.json`) + import(`../locales/ar-TN/${fileNamespace}.json`) diff --git a/web/i18n-config/locale-resources/az-AZ.ts b/web/i18n/locale-resources/az-AZ.ts similarity index 51% rename from web/i18n-config/locale-resources/az-AZ.ts rename to web/i18n/locale-resources/az-AZ.ts index 6a515f06370..3db1baeb3d2 100644 --- a/web/i18n-config/locale-resources/az-AZ.ts +++ b/web/i18n/locale-resources/az-AZ.ts @@ -1,2 +1,2 @@ export const loadResource = (fileNamespace: string) => - import(`../../i18n/az-AZ/${fileNamespace}.json`) + import(`../locales/az-AZ/${fileNamespace}.json`) diff --git a/web/i18n-config/locale-resources/de-DE.ts b/web/i18n/locale-resources/de-DE.ts similarity index 51% rename from web/i18n-config/locale-resources/de-DE.ts rename to web/i18n/locale-resources/de-DE.ts index d518616ea12..a16f3a628ff 100644 --- a/web/i18n-config/locale-resources/de-DE.ts +++ b/web/i18n/locale-resources/de-DE.ts @@ -1,2 +1,2 @@ export const loadResource = (fileNamespace: string) => - import(`../../i18n/de-DE/${fileNamespace}.json`) + import(`../locales/de-DE/${fileNamespace}.json`) diff --git a/web/i18n-config/locale-resources/en-US.ts b/web/i18n/locale-resources/en-US.ts similarity index 51% rename from web/i18n-config/locale-resources/en-US.ts rename to web/i18n/locale-resources/en-US.ts index f7d33c53c6d..4dc709e11df 100644 --- a/web/i18n-config/locale-resources/en-US.ts +++ b/web/i18n/locale-resources/en-US.ts @@ -1,2 +1,2 @@ export const loadResource = (fileNamespace: string) => - import(`../../i18n/en-US/${fileNamespace}.json`) + import(`../locales/en-US/${fileNamespace}.json`) diff --git a/web/i18n-config/locale-resources/es-ES.ts b/web/i18n/locale-resources/es-ES.ts similarity index 51% rename from web/i18n-config/locale-resources/es-ES.ts rename to web/i18n/locale-resources/es-ES.ts index ad7d256275a..7a84ef6fef1 100644 --- a/web/i18n-config/locale-resources/es-ES.ts +++ b/web/i18n/locale-resources/es-ES.ts @@ -1,2 +1,2 @@ export const loadResource = (fileNamespace: string) => - import(`../../i18n/es-ES/${fileNamespace}.json`) + import(`../locales/es-ES/${fileNamespace}.json`) diff --git a/web/i18n-config/locale-resources/fa-IR.ts b/web/i18n/locale-resources/fa-IR.ts similarity index 51% rename from web/i18n-config/locale-resources/fa-IR.ts rename to web/i18n/locale-resources/fa-IR.ts index d95c6e62ca3..f4bfa9a858b 100644 --- a/web/i18n-config/locale-resources/fa-IR.ts +++ b/web/i18n/locale-resources/fa-IR.ts @@ -1,2 +1,2 @@ export const loadResource = (fileNamespace: string) => - import(`../../i18n/fa-IR/${fileNamespace}.json`) + import(`../locales/fa-IR/${fileNamespace}.json`) diff --git a/web/i18n-config/locale-resources/fr-FR.ts b/web/i18n/locale-resources/fr-FR.ts similarity index 51% rename from web/i18n-config/locale-resources/fr-FR.ts rename to web/i18n/locale-resources/fr-FR.ts index e82371bb143..7da11806152 100644 --- a/web/i18n-config/locale-resources/fr-FR.ts +++ b/web/i18n/locale-resources/fr-FR.ts @@ -1,2 +1,2 @@ export const loadResource = (fileNamespace: string) => - import(`../../i18n/fr-FR/${fileNamespace}.json`) + import(`../locales/fr-FR/${fileNamespace}.json`) diff --git a/web/i18n-config/locale-resources/hi-IN.ts b/web/i18n/locale-resources/hi-IN.ts similarity index 51% rename from web/i18n-config/locale-resources/hi-IN.ts rename to web/i18n/locale-resources/hi-IN.ts index c81c8c62eb7..65aa799773d 100644 --- a/web/i18n-config/locale-resources/hi-IN.ts +++ b/web/i18n/locale-resources/hi-IN.ts @@ -1,2 +1,2 @@ export const loadResource = (fileNamespace: string) => - import(`../../i18n/hi-IN/${fileNamespace}.json`) + import(`../locales/hi-IN/${fileNamespace}.json`) diff --git a/web/i18n-config/locale-resources/id-ID.ts b/web/i18n/locale-resources/id-ID.ts similarity index 51% rename from web/i18n-config/locale-resources/id-ID.ts rename to web/i18n/locale-resources/id-ID.ts index 1b8629e15f2..3fee5bdf5d2 100644 --- a/web/i18n-config/locale-resources/id-ID.ts +++ b/web/i18n/locale-resources/id-ID.ts @@ -1,2 +1,2 @@ export const loadResource = (fileNamespace: string) => - import(`../../i18n/id-ID/${fileNamespace}.json`) + import(`../locales/id-ID/${fileNamespace}.json`) diff --git a/web/i18n-config/locale-resources/it-IT.ts b/web/i18n/locale-resources/it-IT.ts similarity index 51% rename from web/i18n-config/locale-resources/it-IT.ts rename to web/i18n/locale-resources/it-IT.ts index 0920e7b4d20..61edaaa4b9a 100644 --- a/web/i18n-config/locale-resources/it-IT.ts +++ b/web/i18n/locale-resources/it-IT.ts @@ -1,2 +1,2 @@ export const loadResource = (fileNamespace: string) => - import(`../../i18n/it-IT/${fileNamespace}.json`) + import(`../locales/it-IT/${fileNamespace}.json`) diff --git a/web/i18n-config/locale-resources/ja-JP.ts b/web/i18n/locale-resources/ja-JP.ts similarity index 51% rename from web/i18n-config/locale-resources/ja-JP.ts rename to web/i18n/locale-resources/ja-JP.ts index 50732b3c6e3..eb14b53b55b 100644 --- a/web/i18n-config/locale-resources/ja-JP.ts +++ b/web/i18n/locale-resources/ja-JP.ts @@ -1,2 +1,2 @@ export const loadResource = (fileNamespace: string) => - import(`../../i18n/ja-JP/${fileNamespace}.json`) + import(`../locales/ja-JP/${fileNamespace}.json`) diff --git a/web/i18n-config/locale-resources/ko-KR.ts b/web/i18n/locale-resources/ko-KR.ts similarity index 51% rename from web/i18n-config/locale-resources/ko-KR.ts rename to web/i18n/locale-resources/ko-KR.ts index 60234fbd033..7a56e3326ef 100644 --- a/web/i18n-config/locale-resources/ko-KR.ts +++ b/web/i18n/locale-resources/ko-KR.ts @@ -1,2 +1,2 @@ export const loadResource = (fileNamespace: string) => - import(`../../i18n/ko-KR/${fileNamespace}.json`) + import(`../locales/ko-KR/${fileNamespace}.json`) diff --git a/web/i18n-config/locale-resources/lo-LA.ts b/web/i18n/locale-resources/lo-LA.ts similarity index 51% rename from web/i18n-config/locale-resources/lo-LA.ts rename to web/i18n/locale-resources/lo-LA.ts index b835990c5ee..c58cd9a1fc3 100644 --- a/web/i18n-config/locale-resources/lo-LA.ts +++ b/web/i18n/locale-resources/lo-LA.ts @@ -1,2 +1,2 @@ export const loadResource = (fileNamespace: string) => - import(`../../i18n/lo-LA/${fileNamespace}.json`) + import(`../locales/lo-LA/${fileNamespace}.json`) diff --git a/web/i18n-config/locale-resources/nl-NL.ts b/web/i18n/locale-resources/nl-NL.ts similarity index 51% rename from web/i18n-config/locale-resources/nl-NL.ts rename to web/i18n/locale-resources/nl-NL.ts index 56dda25160e..b10f825e1fe 100644 --- a/web/i18n-config/locale-resources/nl-NL.ts +++ b/web/i18n/locale-resources/nl-NL.ts @@ -1,2 +1,2 @@ export const loadResource = (fileNamespace: string) => - import(`../../i18n/nl-NL/${fileNamespace}.json`) + import(`../locales/nl-NL/${fileNamespace}.json`) diff --git a/web/i18n-config/locale-resources/pl-PL.ts b/web/i18n/locale-resources/pl-PL.ts similarity index 51% rename from web/i18n-config/locale-resources/pl-PL.ts rename to web/i18n/locale-resources/pl-PL.ts index e0782421886..2f0fd3908d5 100644 --- a/web/i18n-config/locale-resources/pl-PL.ts +++ b/web/i18n/locale-resources/pl-PL.ts @@ -1,2 +1,2 @@ export const loadResource = (fileNamespace: string) => - import(`../../i18n/pl-PL/${fileNamespace}.json`) + import(`../locales/pl-PL/${fileNamespace}.json`) diff --git a/web/i18n-config/locale-resources/pt-BR.ts b/web/i18n/locale-resources/pt-BR.ts similarity index 51% rename from web/i18n-config/locale-resources/pt-BR.ts rename to web/i18n/locale-resources/pt-BR.ts index d77363b7c67..7aa6f3723f4 100644 --- a/web/i18n-config/locale-resources/pt-BR.ts +++ b/web/i18n/locale-resources/pt-BR.ts @@ -1,2 +1,2 @@ export const loadResource = (fileNamespace: string) => - import(`../../i18n/pt-BR/${fileNamespace}.json`) + import(`../locales/pt-BR/${fileNamespace}.json`) diff --git a/web/i18n-config/locale-resources/ro-RO.ts b/web/i18n/locale-resources/ro-RO.ts similarity index 51% rename from web/i18n-config/locale-resources/ro-RO.ts rename to web/i18n/locale-resources/ro-RO.ts index db065496d0b..7188eb82896 100644 --- a/web/i18n-config/locale-resources/ro-RO.ts +++ b/web/i18n/locale-resources/ro-RO.ts @@ -1,2 +1,2 @@ export const loadResource = (fileNamespace: string) => - import(`../../i18n/ro-RO/${fileNamespace}.json`) + import(`../locales/ro-RO/${fileNamespace}.json`) diff --git a/web/i18n-config/locale-resources/ru-RU.ts b/web/i18n/locale-resources/ru-RU.ts similarity index 51% rename from web/i18n-config/locale-resources/ru-RU.ts rename to web/i18n/locale-resources/ru-RU.ts index f5034f141ba..6652e96e8e1 100644 --- a/web/i18n-config/locale-resources/ru-RU.ts +++ b/web/i18n/locale-resources/ru-RU.ts @@ -1,2 +1,2 @@ export const loadResource = (fileNamespace: string) => - import(`../../i18n/ru-RU/${fileNamespace}.json`) + import(`../locales/ru-RU/${fileNamespace}.json`) diff --git a/web/i18n-config/locale-resources/sl-SI.ts b/web/i18n/locale-resources/sl-SI.ts similarity index 51% rename from web/i18n-config/locale-resources/sl-SI.ts rename to web/i18n/locale-resources/sl-SI.ts index aa4e530b180..a8f138321d9 100644 --- a/web/i18n-config/locale-resources/sl-SI.ts +++ b/web/i18n/locale-resources/sl-SI.ts @@ -1,2 +1,2 @@ export const loadResource = (fileNamespace: string) => - import(`../../i18n/sl-SI/${fileNamespace}.json`) + import(`../locales/sl-SI/${fileNamespace}.json`) diff --git a/web/i18n-config/locale-resources/th-TH.ts b/web/i18n/locale-resources/th-TH.ts similarity index 51% rename from web/i18n-config/locale-resources/th-TH.ts rename to web/i18n/locale-resources/th-TH.ts index a7f3e322447..91b694ca0f1 100644 --- a/web/i18n-config/locale-resources/th-TH.ts +++ b/web/i18n/locale-resources/th-TH.ts @@ -1,2 +1,2 @@ export const loadResource = (fileNamespace: string) => - import(`../../i18n/th-TH/${fileNamespace}.json`) + import(`../locales/th-TH/${fileNamespace}.json`) diff --git a/web/i18n-config/locale-resources/tr-TR.ts b/web/i18n/locale-resources/tr-TR.ts similarity index 51% rename from web/i18n-config/locale-resources/tr-TR.ts rename to web/i18n/locale-resources/tr-TR.ts index e4efa9b5a83..18b0314b507 100644 --- a/web/i18n-config/locale-resources/tr-TR.ts +++ b/web/i18n/locale-resources/tr-TR.ts @@ -1,2 +1,2 @@ export const loadResource = (fileNamespace: string) => - import(`../../i18n/tr-TR/${fileNamespace}.json`) + import(`../locales/tr-TR/${fileNamespace}.json`) diff --git a/web/i18n-config/locale-resources/uk-UA.ts b/web/i18n/locale-resources/uk-UA.ts similarity index 51% rename from web/i18n-config/locale-resources/uk-UA.ts rename to web/i18n/locale-resources/uk-UA.ts index 245696237c4..64b8eba9a9b 100644 --- a/web/i18n-config/locale-resources/uk-UA.ts +++ b/web/i18n/locale-resources/uk-UA.ts @@ -1,2 +1,2 @@ export const loadResource = (fileNamespace: string) => - import(`../../i18n/uk-UA/${fileNamespace}.json`) + import(`../locales/uk-UA/${fileNamespace}.json`) diff --git a/web/i18n-config/locale-resources/vi-VN.ts b/web/i18n/locale-resources/vi-VN.ts similarity index 51% rename from web/i18n-config/locale-resources/vi-VN.ts rename to web/i18n/locale-resources/vi-VN.ts index dfde38233f8..09fd9637747 100644 --- a/web/i18n-config/locale-resources/vi-VN.ts +++ b/web/i18n/locale-resources/vi-VN.ts @@ -1,2 +1,2 @@ export const loadResource = (fileNamespace: string) => - import(`../../i18n/vi-VN/${fileNamespace}.json`) + import(`../locales/vi-VN/${fileNamespace}.json`) diff --git a/web/i18n-config/locale-resources/zh-Hans.ts b/web/i18n/locale-resources/zh-Hans.ts similarity index 50% rename from web/i18n-config/locale-resources/zh-Hans.ts rename to web/i18n/locale-resources/zh-Hans.ts index 3fecc27d70a..2404b82dabe 100644 --- a/web/i18n-config/locale-resources/zh-Hans.ts +++ b/web/i18n/locale-resources/zh-Hans.ts @@ -1,2 +1,2 @@ export const loadResource = (fileNamespace: string) => - import(`../../i18n/zh-Hans/${fileNamespace}.json`) + import(`../locales/zh-Hans/${fileNamespace}.json`) diff --git a/web/i18n-config/locale-resources/zh-Hant.ts b/web/i18n/locale-resources/zh-Hant.ts similarity index 50% rename from web/i18n-config/locale-resources/zh-Hant.ts rename to web/i18n/locale-resources/zh-Hant.ts index 30cd2cb64e1..8608870f8f1 100644 --- a/web/i18n-config/locale-resources/zh-Hant.ts +++ b/web/i18n/locale-resources/zh-Hant.ts @@ -1,2 +1,2 @@ export const loadResource = (fileNamespace: string) => - import(`../../i18n/zh-Hant/${fileNamespace}.json`) + import(`../locales/zh-Hant/${fileNamespace}.json`) diff --git a/web/i18n/locale.ts b/web/i18n/locale.ts new file mode 100644 index 00000000000..bf6244a7db9 --- /dev/null +++ b/web/i18n/locale.ts @@ -0,0 +1,27 @@ +import data from './languages' + +export type Locale = (typeof data.languages)[number]['value'] + +export const defaultLocale = 'en-US' satisfies Locale +export const supportedLocales = data.languages + .filter((language) => language.supported) + .map((language) => language.value) + +const legacyLocaleMap = new Map([ + ['en_US', 'en-US'], + ['ja_JP', 'ja-JP'], + ['zh_Hans', 'zh-Hans'], +]) + +export const canonicalizeLanguageTag = (language: string): string | undefined => { + try { + return Intl.getCanonicalLocales(legacyLocaleMap.get(language) ?? language)[0] + } catch { + return undefined + } +} + +export const normalizeLocale = (language: string): Locale => { + const canonical = canonicalizeLanguageTag(language) + return supportedLocales.find((locale) => locale === canonical) ?? defaultLocale +} diff --git a/web/i18n/ar-TN/agent-v-2.json b/web/i18n/locales/ar-TN/agent-v-2.json similarity index 99% rename from web/i18n/ar-TN/agent-v-2.json rename to web/i18n/locales/ar-TN/agent-v-2.json index 0ea495f0052..5000a419722 100644 --- a/web/i18n/ar-TN/agent-v-2.json +++ b/web/i18n/locales/ar-TN/agent-v-2.json @@ -69,6 +69,7 @@ "agentDetail.configure.answer.activity.runningCommands": "جارٍ تشغيل الأوامر", "agentDetail.configure.answer.duration.minute": "{{count}} د", "agentDetail.configure.answer.duration.second": "{{count}} ث", + "agentDetail.configure.answer.responseEnded": "انتهت الاستجابة", "agentDetail.configure.answer.thinking": "جارٍ التفكير", "agentDetail.configure.build.empty.communityEditionTip": "توفر Community Edition إدارة إصدارات للتكوينات المستخرجة، لكنها لا تدعم إدارة إصدارات نظام الملفات نفسه. كن حذراً في إجراءاتك ضمن وضع Build، لأن التغييرات التي تُجرى على نظام الملفات تحدث في الوقت الفعلي ولا يمكن دائماً التراجع عنها بشكل منظم. Community Edition ليست الخيار المثالي لخدمة جماهير خارجية واسعة.", "agentDetail.configure.build.empty.description": "صِف ما تريده وسيتم ملء النموذج على اليسار أثناء المحادثة.", @@ -269,7 +270,6 @@ "agentDetail.configure.workingDirectory.breadcrumbLabel": "مسار نظام الملفات", "agentDetail.configure.workingDirectory.description": "الملفات التي يقرأها الوكيل ويكتبها أثناء التشغيل. كل ما ينشئه، مثل qna_report.pdf، يظهر هنا.", "agentDetail.configure.workingDirectory.fileSystem": "نظام الملفات", - "agentDetail.configure.workingDirectory.open": "فتح دليل العمل", "agentDetail.configure.workingDirectory.persistentFiles": "دائمة", "agentDetail.configure.workingDirectory.persistentFilesTooltip": "ملفات يحتفظ بها الوكيل ويمكنه استخدامها أينما يعمل، بما في ذلك محادثات المستخدمين النهائيين وسير العمل. تسري أي تغييرات تُجرى هنا خلال محادثة البناء هذه بمجرد الضغط على «تطبيق».", "agentDetail.configure.workingDirectory.temporaryFiles": "مؤقتة", @@ -348,7 +348,6 @@ "agentDetail.sections.logs": "السجلات", "agentDetail.sections.monitoring": "المراقبة", "agentDetail.title": "وكيل", - "agentDetail.type": "وكيل", "agentDetail.versionHistory.empty": "لا توجد إصدارات بعد", "agentDetail.versionHistory.exitVersions": "الخروج من الإصدارات", "agentDetail.versionHistory.filter": "تصفية الإصدارات", @@ -408,8 +407,5 @@ "roster.sort.lastModified": "آخر تعديل", "roster.sort.recentlyCreated": "الأحدث إنشاءً", "roster.title": "الوكلاء", - "roster.usageStatus.draft": "مسودة", - "skillManagement.detail.uploadFilesFailedStatus_one": "فشل رفع {{count}} ملف.", - "skillManagement.detail.uploadFilesFailedStatus_other": "فشل رفع {{count}} ملفات.", - "skillManagement.title": "Skills" + "roster.usageStatus.draft": "مسودة" } diff --git a/web/i18n/ar-TN/app-annotation.json b/web/i18n/locales/ar-TN/app-annotation.json similarity index 100% rename from web/i18n/ar-TN/app-annotation.json rename to web/i18n/locales/ar-TN/app-annotation.json diff --git a/web/i18n/ar-TN/app-api.json b/web/i18n/locales/ar-TN/app-api.json similarity index 100% rename from web/i18n/ar-TN/app-api.json rename to web/i18n/locales/ar-TN/app-api.json diff --git a/web/i18n/ar-TN/app-debug.json b/web/i18n/locales/ar-TN/app-debug.json similarity index 100% rename from web/i18n/ar-TN/app-debug.json rename to web/i18n/locales/ar-TN/app-debug.json diff --git a/web/i18n/ar-TN/app-log.json b/web/i18n/locales/ar-TN/app-log.json similarity index 97% rename from web/i18n/ar-TN/app-log.json rename to web/i18n/locales/ar-TN/app-log.json index a377e91ca5a..4eddf685ffa 100644 --- a/web/i18n/ar-TN/app-log.json +++ b/web/i18n/locales/ar-TN/app-log.json @@ -4,11 +4,9 @@ "agentLogDetail.iteration": "تكرار", "agentLogDetail.iterations": "التكرارات", "agentLogDetail.toolUsed": "الأداة المستخدمة", - "archives.action.downloadMonth": "تنزيل أرشيف {{month}}", "archives.action.downloadReady": "تنزيل الأرشيف جاهز.", "archives.action.prepareDownload": "تحضير التنزيل", "archives.action.prepareFailed": "فشل تحضير تنزيل الأرشيف.", - "archives.action.prepareMonth": "تحضير تنزيل أرشيف {{month}}", "archives.action.prepareStarted": "جارٍ تحضير تنزيل الأرشيف.", "archives.action.preparing": "جارٍ التحضير", "archives.description": "نزّل صادرات CSV شهرية لسجلات تشغيل سير العمل المؤرشفة في مساحة العمل هذه.", diff --git a/web/i18n/ar-TN/app-overview.json b/web/i18n/locales/ar-TN/app-overview.json similarity index 100% rename from web/i18n/ar-TN/app-overview.json rename to web/i18n/locales/ar-TN/app-overview.json diff --git a/web/i18n/ar-TN/app.json b/web/i18n/locales/ar-TN/app.json similarity index 100% rename from web/i18n/ar-TN/app.json rename to web/i18n/locales/ar-TN/app.json diff --git a/web/i18n/ar-TN/billing.json b/web/i18n/locales/ar-TN/billing.json similarity index 100% rename from web/i18n/ar-TN/billing.json rename to web/i18n/locales/ar-TN/billing.json diff --git a/web/i18n/ar-TN/common.json b/web/i18n/locales/ar-TN/common.json similarity index 98% rename from web/i18n/ar-TN/common.json rename to web/i18n/locales/ar-TN/common.json index a6455c71a31..b32a43550c9 100644 --- a/web/i18n/ar-TN/common.json +++ b/web/i18n/locales/ar-TN/common.json @@ -209,9 +209,7 @@ "mainNav.marketplace": "سوق الإضافات", "mainNav.skills": "Skills", "mainNav.webApps.noResults": "لم يتم العثور على تطبيقات ويب", - "mainNav.webApps.openApp": "فتح تطبيق الويب {{name}}", "mainNav.webApps.searchPlaceholder": "البحث في تطبيقات الويب", - "mainNav.workspace.credits": "{{count}} رصيد", "mainNav.workspace.creditsUnit": "أرصدة", "mainNav.workspace.inviteMembers": "Invite members", "mainNav.workspace.noResults": "لم يتم العثور على مساحات عمل", @@ -233,7 +231,6 @@ "members.datasetOperatorTip": "يمكنه إدارة قاعدة المعرفة فقط", "members.editRole": "تعديل الدور", "members.editorTip": "يمكنه بناء وتعديل التطبيقات", - "members.email": "البريد الإلكتروني", "members.emailInvalid": "تنسيق البريد الإلكتروني غير صالح", "members.emailNotSetup": "لم يتم إعداد خادم البريد الإلكتروني، لذا لا يمكن إرسال رسائل بريد إلكتروني للدعوة. يرجى إخطار المستخدمين برابط الدعوة الذي سيتم إصداره بعد الدعوة بدلاً من ذلك.", "members.emailPlaceholder": "اكتب عناوين البريد الإلكتروني أو الصقها…", @@ -250,7 +247,6 @@ "members.inviteLimitExceeded": "لا تتوفر مقاعد كافية لجميع المستلمين.", "members.inviteTeamMember": "إضافة عضو فريق", "members.inviteTeamMemberTip": "يمكنهم الوصول إلى بيانات فريقك مباشرة بعد تسجيل الدخول.", - "members.invitedAsRole": "تمت الدعوة كـ {{role}}", "members.lastActive": "آخر نشاط", "members.memberActions": "إجراءات العضو", "members.memberDetails.assign": "تعيين", @@ -258,8 +254,6 @@ "members.memberDetails.assignedRoles": "الأدوار المعينة", "members.memberDetails.customGroup": "مخصص", "members.memberDetails.generalGroup": "عام", - "members.memberDetails.openAria": "فتح تفاصيل العضو لـ {{name}}", - "members.memberDetails.roleActionsAria": "فتح الإجراءات لدور {{role}}", "members.memberDetails.roleNoPermissionSummary": "الدور الحالي ليس له صلاحيات.", "members.memberDetails.rolePermissionSummary": "{{role}} يمكنه {{permissions}}", "members.memberDetails.title": "تفاصيل العضو", @@ -370,7 +364,6 @@ "modelProvider.card.usagePriorityTip": "تعيين المورد الذي يجب استخدامه أولاً عند تشغيل النماذج.", "modelProvider.configureTip": "قم بإعداد مفتاح api أو أضف نموذجًا للاستخدام", "modelProvider.confirmDelete": "تأكيد الحذف؟", - "modelProvider.credits": "أرصدة الرسائل", "modelProvider.defaultConfig": "التكوين الافتراضي", "modelProvider.discoverMore": "اكتشف المزيد في ", "modelProvider.embeddingModel.key": "نموذج التضمين", @@ -427,7 +420,6 @@ "modelProvider.selector.noProviderConfiguredDesc": "تصفح السوق لتثبيت مزود، أو قم بتكوين المزودين في الإعدادات.", "modelProvider.selector.onlyCompatibleModelsShown": "يتم عرض النماذج المتوافقة فقط", "modelProvider.selector.showIncompatibleModels": "عرض النماذج غير المتوافقة", - "modelProvider.selector.suggestion": "اقتراح", "modelProvider.selector.suggestionTip": "موصى به لمهام الوكيل", "modelProvider.showModels": "عرض النماذج", "modelProvider.speechToTextModel.key": "نموذج تحويل الكلام إلى نص", @@ -458,6 +450,8 @@ "operation.confirmAction": "يرجى تأكيد الإجراء الخاص بك.", "operation.copied": "تم النسخ", "operation.copy": "نسخ", + "operation.copyErrorDetails": "نسخ تفاصيل الخطأ", + "operation.copyErrorFailed": "فشل النسخ. أعد المحاولة", "operation.copyImage": "نسخ الصورة", "operation.create": "إنشاء", "operation.deSelectAll": "إلغاء تحديد الكل", @@ -475,7 +469,6 @@ "operation.format": "تنسيق", "operation.imageCopied": "تم نسخ الصورة", "operation.imageCopyFailed": "تعذّر نسخ الصورة. يرجى تنزيلها بدلاً من ذلك.", - "operation.imageDownloaded": "تم تنزيل الصورة", "operation.in": "في", "operation.learnMore": "تعرف على المزيد", "operation.log": "سجل", @@ -512,7 +505,6 @@ "operation.sure": "أنا متأكد", "operation.toggleFullscreen": "تبديل ملء الشاشة", "operation.toggleMute": "تبديل كتم الصوت", - "operation.upload": "Upload", "operation.view": "عرض", "operation.viewDetails": "عرض التفاصيل", "operation.viewMore": "عرض المزيد", @@ -542,7 +534,6 @@ "promptEditor.requestURL.item.title": "عنوان URL للطلب", "promptEditor.variable.modal.add": "متغير جديد", "promptEditor.variable.modal.addTool": "أداة جديدة", - "provider.anthropicHosted.trialQuotaTip": "ستنتهي حصة التجربة الخاصة بك في Anthropic في 2025/03/17 ولن تكون متاحة بعد ذلك. يرجى الاستفادة منها في الوقت المحدد.", "provider.encrypted.back": ".", "provider.encrypted.front": "سيتم تشفير مفتاح API الخاص بك وتخزينه باستخدام تقنية", "provider.validating": "جارٍ التحقق من المفتاح...", @@ -565,7 +556,6 @@ "settings.preferences": "Preferences", "settings.provider": "مزود النموذج", "settings.resourceAccess": "الوصول إلى الموارد", - "settings.resourceAccessDescription": "قم بتكوين قواعد الصلاحيات التي يمكن للتطبيقات وقواعد المعرفة استخدامها. يمكن تعيين قواعد الصلاحيات للأعضاء في تكوينات وصول محددة للموارد.", "settings.rolesAndPermissions": "الأدوار والصلاحيات", "settings.settings": "Settings", "settings.swaggerAPIAsTool": "Swagger API as Tool", @@ -695,7 +685,6 @@ "userProfile.helpCenter": "عرض المستندات", "userProfile.logout": "تسجيل الخروج", "userProfile.roadmap": "خارطة الطريق", - "userProfile.settings": "الإعدادات", "userProfile.workspace": "مساحة العمل", "voice.language.arTN": "العربية التونسية", "voice.language.deDE": "الألمانية", diff --git a/web/i18n/ar-TN/custom.json b/web/i18n/locales/ar-TN/custom.json similarity index 100% rename from web/i18n/ar-TN/custom.json rename to web/i18n/locales/ar-TN/custom.json diff --git a/web/i18n/ar-TN/dataset-creation.json b/web/i18n/locales/ar-TN/dataset-creation.json similarity index 100% rename from web/i18n/ar-TN/dataset-creation.json rename to web/i18n/locales/ar-TN/dataset-creation.json diff --git a/web/i18n/ar-TN/dataset-documents.json b/web/i18n/locales/ar-TN/dataset-documents.json similarity index 100% rename from web/i18n/ar-TN/dataset-documents.json rename to web/i18n/locales/ar-TN/dataset-documents.json diff --git a/web/i18n/ar-TN/dataset-hit-testing.json b/web/i18n/locales/ar-TN/dataset-hit-testing.json similarity index 100% rename from web/i18n/ar-TN/dataset-hit-testing.json rename to web/i18n/locales/ar-TN/dataset-hit-testing.json diff --git a/web/i18n/ar-TN/dataset-pipeline.json b/web/i18n/locales/ar-TN/dataset-pipeline.json similarity index 100% rename from web/i18n/ar-TN/dataset-pipeline.json rename to web/i18n/locales/ar-TN/dataset-pipeline.json diff --git a/web/i18n/ar-TN/dataset-settings.json b/web/i18n/locales/ar-TN/dataset-settings.json similarity index 100% rename from web/i18n/ar-TN/dataset-settings.json rename to web/i18n/locales/ar-TN/dataset-settings.json diff --git a/web/i18n/ar-TN/dataset.json b/web/i18n/locales/ar-TN/dataset.json similarity index 97% rename from web/i18n/ar-TN/dataset.json rename to web/i18n/locales/ar-TN/dataset.json index 468ffd3411a..8bf1fcbbe8c 100644 --- a/web/i18n/ar-TN/dataset.json +++ b/web/i18n/locales/ar-TN/dataset.json @@ -163,7 +163,6 @@ "newKnowledge.configureProvider": "تكوين {{provider}}", "newKnowledge.connectNotion": "ربط Notion", "newKnowledge.connectProvider": "توصيل {{provider}}", - "newKnowledge.connectProviderGeneric": "ربط المزوّد", "newKnowledge.connectSource": "ربط مصدر", "newKnowledge.connectSourceDescription": "يبقى المحتوى متزامنًا مع Notion والمواقع الإلكترونية والخدمات الأخرى.", "newKnowledge.connectingProvider": "جارٍ الاتصال…", @@ -179,18 +178,14 @@ "newKnowledge.crawlFailedDescription": "تعذّر على برنامج الزحف إكمال هذه المعاينة. تحقّق من عنوان URL أو عدّل خيارات الزحف أو أعد المحاولة.", "newKnowledge.crawlFailedProvider": "أبلغ {{provider}} عن خطأ. تحقّق من اتصال الموفّر وأعد المحاولة.", "newKnowledge.crawlFailedTimeout": "انتهت مهلة الزحف. جرّب عنوان URL أكثر تحديدًا أو قلّل الحد الأقصى لعدد الصفحات.", - "newKnowledge.crawlNotStarted": "لم يبدأ", "newKnowledge.crawlOptions": "خيارات الزحف", "newKnowledge.crawlPreview": "معاينة الزحف", "newKnowledge.crawlProgress": "تقدّم الزحف إلى {{host}}", - "newKnowledge.crawlSetupUnavailableDescription": "يتم تعطيل تكوين الزحف إلى موقع الويب حتى يتوفر سير عمل الزحف.", - "newKnowledge.crawlSetupUnavailableTitle": "الزحف الناري جاهز", "newKnowledge.crawlStartFailed": "تعذّر بدء معاينة الزحف. أعد المحاولة.", "newKnowledge.crawlStopped": "تم إيقاف الزحف", "newKnowledge.crawling": "جارٍ الزحف…", "newKnowledge.crawlingPages": "جارٍ الزحف إلى {{host}}… تم العثور على {{count}} صفحة", "newKnowledge.createFailed": "لم نتمكن من إنشاء قاعدة المعرفة هذه. تحقق من وصولك وحاول مرة أخرى.", - "newKnowledge.createIllustrationTitle": "اربط محتواك مرة واحدة — وسيجيب كل تطبيق بأحدث المعارف.", "newKnowledge.createTitle": "خلق المعرفة", "newKnowledge.createdAt": "تم الإنشاء", "newKnowledge.creators": "المنشئون", @@ -220,7 +215,6 @@ "newKnowledge.documentColumn": "وثيقة", "newKnowledge.documentContentIncomplete": "جارٍ تحميل بقية محتوى المستند…", "newKnowledge.documentContents": "المحتويات", - "newKnowledge.documentFacts": "تفاصيل المستند", "newKnowledge.documentFilterLabel": "تصفية المستندات حسب الحالة", "newKnowledge.documentLoadErrorDescription": "تعذّر تحميل هذا المستند. حاول مرة أخرى.", "newKnowledge.documentLoadErrorTitle": "تعذّر تحميل المستند", @@ -268,7 +262,6 @@ "newKnowledge.documentsSelected": "تم تحديد وثائق {{count}}", "newKnowledge.documentsUnavailable": "— مستندات", "newKnowledge.downloadDocuments": "تحميل", - "newKnowledge.editSource": "تحرير", "newKnowledge.emptyChunk": "مقطع فارغ", "newKnowledge.emptyDescription": "أضف مستنداتك ليتمكن الوكلاء من قراءتها مباشرة — من دون إعداد الفهرسة.", "newKnowledge.emptyTitle": "طريقة أذكى لبناء قواعد المعرفة", @@ -344,7 +337,6 @@ "newKnowledge.processingTaskState.succeeded": "مكتمل", "newKnowledge.processingTaskState.superseded": "تم استبداله", "newKnowledge.providerConnected": "تم توصيل {{provider}}", - "newKnowledge.providerLabel": "اختر مزودًا", "newKnowledge.providerLoadFailed": "لم يتمكن الموفرون من التحميل", "newKnowledge.providerNotConfigured": "{{provider}} لم يتم تكوينه", "newKnowledge.providerNotConfiguredDescription": "أضف مفتاح API أو عنوان URL لنقطة النهاية للزحف إلى مواقع الويب باستخدام {{provider}}.", @@ -369,12 +361,10 @@ "newKnowledge.revisionState.superseded": "سابقة", "newKnowledge.rootUrl": "عنوان URL الجذر", "newKnowledge.rootUrlPlaceholder": "https://docs.example.com", - "newKnowledge.routeUnavailable": "سيصبح هذا القسم متاحًا في خطوة الإعداد التالية.", "newKnowledge.searchDocuments": "البحث في المستندات", "newKnowledge.searchSources": "مصادر البحث", "newKnowledge.selectAll": "تحديد الكل", "newKnowledge.selectAllDocuments": "حدد كافة المستندات", - "newKnowledge.selectAllSources": "تحديد كل المصادر", "newKnowledge.selectChunk": "حدد مقطعًا لفحص محتواه.", "newKnowledge.selectFilesAndFolders": "حدد الملفات والمجلدات للمزامنة", "newKnowledge.skippedFailed": "تم التخطي (فشل)", @@ -384,7 +374,6 @@ "newKnowledge.sourceFilterLabel": "تصفية المصادر حسب الحالة", "newKnowledge.sourceName": "اسم المصدر", "newKnowledge.sourceNamePlaceholder": "مثال: الزحف إلى وثائق المنتج", - "newKnowledge.sourceNameRequired": "أدخل اسمًا للمصدر.", "newKnowledge.sourceSetupBackendDependency": "يُعرض إعداد المصدر بالكامل؛ تتطلب إجراءات التعديل والمعاينة مسار الواجهة الخلفية لمصدر المسودة.", "newKnowledge.sourceStatus.active": "نشط", "newKnowledge.sourceStatus.disabled": "معطل", @@ -431,7 +420,6 @@ "newKnowledge.unavailableDescription": "KnowledgeFS غير متاح لمساحة العمل هذه حاليًا.", "newKnowledge.unavailableTitle": "المعرفة الجديدة غير متاحة", "newKnowledge.updated": "تم التحديث في {{date}}", - "newKnowledge.updatedAt": "آخر تحديث", "newKnowledge.updatedColumn": "تم التحديث", "newKnowledge.uploadCharactersUnavailable": "الأحرف: —", "newKnowledge.uploadDocuments": "تحميل المستندات", @@ -460,11 +448,9 @@ "retrievalSettings": "إعداد الاسترجاع", "retry": "إعادة المحاولة", "selectExternalKnowledgeAPI.placeholder": "اختر واجهة برمجة تطبيقات معرفة خارجية", - "serviceApi.card.addApiKey": "إضافة مفتاح API", "serviceApi.card.apiKey": "مفتاح API", "serviceApi.card.apiReference": "مرجع API", "serviceApi.card.endpoint": "نقطة نهاية واجهة برمجة تطبيقات الخدمة", - "serviceApi.card.manageApiKey": "إدارة مفتاح API", "serviceApi.card.title": "واجهة برمجة تطبيقات خدمة الخلفية", "serviceApi.disabled": "معطل", "serviceApi.enabled": "في الخدمة", diff --git a/web/i18n/ar-TN/deployments.json b/web/i18n/locales/ar-TN/deployments.json similarity index 97% rename from web/i18n/ar-TN/deployments.json rename to web/i18n/locales/ar-TN/deployments.json index 6951e5402b5..7db9e5dd881 100644 --- a/web/i18n/ar-TN/deployments.json +++ b/web/i18n/locales/ar-TN/deployments.json @@ -27,7 +27,6 @@ "list.emptyTitle": "لا توجد عمليات نشر بعد", "overview.chip.latest": "الأحدث", "status.RUNTIME_INSTANCE_STATUS_DEPLOYING": "جاري النشر", - "status.RUNTIME_INSTANCE_STATUS_FAILED": "فشل النشر", "status.RUNTIME_INSTANCE_STATUS_INVALID": "غير صالح", "status.RUNTIME_INSTANCE_STATUS_READY": "قيد التشغيل", "status.RUNTIME_INSTANCE_STATUS_UNDEPLOYED": "غير منشور", @@ -87,6 +86,5 @@ "studio.versionValue": "قيمة الإصدار", "studio.versionsBehind_one": "متأخر بـ {{count}} إصدار", "studio.versionsBehind_other": "متأخر بـ {{count}} إصدارات", - "versions.checkingReleaseContent": "جاري التحقق من محتوى الإصدار...", "versions.deployTo": "النشر إلى {{name}}" } diff --git a/web/i18n/ar-TN/device-flow.json b/web/i18n/locales/ar-TN/device-flow.json similarity index 100% rename from web/i18n/ar-TN/device-flow.json rename to web/i18n/locales/ar-TN/device-flow.json diff --git a/web/i18n/ar-TN/education.json b/web/i18n/locales/ar-TN/education.json similarity index 100% rename from web/i18n/ar-TN/education.json rename to web/i18n/locales/ar-TN/education.json diff --git a/web/i18n/ar-TN/explore.json b/web/i18n/locales/ar-TN/explore.json similarity index 100% rename from web/i18n/ar-TN/explore.json rename to web/i18n/locales/ar-TN/explore.json diff --git a/web/i18n/ar-TN/layout.json b/web/i18n/locales/ar-TN/layout.json similarity index 100% rename from web/i18n/ar-TN/layout.json rename to web/i18n/locales/ar-TN/layout.json diff --git a/web/i18n/ar-TN/login.json b/web/i18n/locales/ar-TN/login.json similarity index 100% rename from web/i18n/ar-TN/login.json rename to web/i18n/locales/ar-TN/login.json diff --git a/web/i18n/ar-TN/oauth.json b/web/i18n/locales/ar-TN/oauth.json similarity index 100% rename from web/i18n/ar-TN/oauth.json rename to web/i18n/locales/ar-TN/oauth.json diff --git a/web/i18n/ar-TN/permission-keys.json b/web/i18n/locales/ar-TN/permission-keys.json similarity index 100% rename from web/i18n/ar-TN/permission-keys.json rename to web/i18n/locales/ar-TN/permission-keys.json diff --git a/web/i18n/ar-TN/permission.json b/web/i18n/locales/ar-TN/permission.json similarity index 100% rename from web/i18n/ar-TN/permission.json rename to web/i18n/locales/ar-TN/permission.json diff --git a/web/i18n/ar-TN/pipeline.json b/web/i18n/locales/ar-TN/pipeline.json similarity index 100% rename from web/i18n/ar-TN/pipeline.json rename to web/i18n/locales/ar-TN/pipeline.json diff --git a/web/i18n/ar-TN/plugin-tags.json b/web/i18n/locales/ar-TN/plugin-tags.json similarity index 100% rename from web/i18n/ar-TN/plugin-tags.json rename to web/i18n/locales/ar-TN/plugin-tags.json diff --git a/web/i18n/ar-TN/plugin-trigger.json b/web/i18n/locales/ar-TN/plugin-trigger.json similarity index 100% rename from web/i18n/ar-TN/plugin-trigger.json rename to web/i18n/locales/ar-TN/plugin-trigger.json diff --git a/web/i18n/ar-TN/plugin.json b/web/i18n/locales/ar-TN/plugin.json similarity index 99% rename from web/i18n/ar-TN/plugin.json rename to web/i18n/locales/ar-TN/plugin.json index 785fcca0ce3..02dce435056 100644 --- a/web/i18n/ar-TN/plugin.json +++ b/web/i18n/locales/ar-TN/plugin.json @@ -277,7 +277,6 @@ "readmeInfo.noReadmeAvailable": "لا يوجد ملف تمهيدي متاح", "readmeInfo.title": "الملف التمهيدي", "requestAPlugin": "طلب إضافة", - "requestSubmit": "طلب أو نشر", "search": "بحث", "searchCategories": "بحث في الفئات", "searchInMarketplace": "بحث في السوق", diff --git a/web/i18n/ar-TN/register.json b/web/i18n/locales/ar-TN/register.json similarity index 100% rename from web/i18n/ar-TN/register.json rename to web/i18n/locales/ar-TN/register.json diff --git a/web/i18n/ar-TN/run-log.json b/web/i18n/locales/ar-TN/run-log.json similarity index 100% rename from web/i18n/ar-TN/run-log.json rename to web/i18n/locales/ar-TN/run-log.json diff --git a/web/i18n/ar-TN/share.json b/web/i18n/locales/ar-TN/share.json similarity index 100% rename from web/i18n/ar-TN/share.json rename to web/i18n/locales/ar-TN/share.json diff --git a/web/i18n/ar-TN/skill.json b/web/i18n/locales/ar-TN/skill.json similarity index 82% rename from web/i18n/ar-TN/skill.json rename to web/i18n/locales/ar-TN/skill.json index 3943c3e1483..b4e12ee82bb 100644 --- a/web/i18n/ar-TN/skill.json +++ b/web/i18n/locales/ar-TN/skill.json @@ -1,5 +1,4 @@ { - "skillManagement.clearTags": "مسح العلامات", "skillManagement.create": "إنشاء", "skillManagement.createFailed": "فشل في خلق المهارة.", "skillManagement.createSuccess": "تم إنشاء المسودة.", @@ -13,12 +12,9 @@ "skillManagement.deleteFailed": "فشل في حذف المهارة.", "skillManagement.deleteSuccess": "تم حذف Skill.", "skillManagement.detail.addMetadata": "إضافة البيانات الوصفية", - "skillManagement.detail.addMetadataDescription": "أضف حقل المادة الأمامية إلى ملف Markdown هذا.", "skillManagement.detail.addTag": "إضافة علامة", - "skillManagement.detail.addTagDescription": "قم بإنشاء أو ربط علامة بهذا Skill. يتم حفظ العلامات الجديدة باستخدام البيانات التعريفية Skill.", "skillManagement.detail.addTagSuccess": "تمت إضافة العلامة.", "skillManagement.detail.back": "العودة إلى Skills", - "skillManagement.detail.builder.applyFailed": "فشل تطبيق مسودة التحديثات.", "skillManagement.detail.builder.attach": "إرفاق الملف", "skillManagement.detail.builder.attachFailed": "فشل في إرفاق الملف.", "skillManagement.detail.builder.attachLimit": "يمكنك إرفاق ما يصل إلى 10 ملفات.", @@ -26,22 +22,12 @@ "skillManagement.detail.builder.attachUnsupported": "يمكن إرفاق ملفات الصور والنصوص والمستندات فقط.", "skillManagement.detail.builder.attachmentOnlyMessage": "استخدم الملف المرفق للمساعدة في تحسين هذه المهارة.", "skillManagement.detail.builder.close": "إغلاق Skill Builder", - "skillManagement.detail.builder.compatibleModelsOnly": "يتم عرض النماذج المتوافقة فقط", "skillManagement.detail.builder.copyResponse": "نسخ الرد", "skillManagement.detail.builder.copySuccess": "تم نسخ الرد إلى الحافظة.", "skillManagement.detail.builder.editIntro": "لقد قمت بمراجعة هذه المهارة. ما الذي تريد تعديله؟", "skillManagement.detail.builder.exampleIssueTriage": "فرز مشكلة العملاء", "skillManagement.detail.builder.exampleOnboarding": "دليل تأهيل الموظفين الجدد", "skillManagement.detail.builder.exampleSalesFollowUp": "استراتيجية متابعة قيادة المبيعات", - "skillManagement.detail.builder.followUpDisplayName": "استخدم الموافقة على استرداد الأموال كاسم العرض", - "skillManagement.detail.builder.followUpNameIcon": "قم بتطبيق الاسم والرمز المقترحين", - "skillManagement.detail.builder.fromMarketplace": "من السوق", - "skillManagement.detail.builder.model": "GPT-4o", - "skillManagement.detail.builder.modelCredits.all": "جميع الاعتمادات", - "skillManagement.detail.builder.modelCredits.configure": "التكوين مطلوب", - "skillManagement.detail.builder.modelCredits.exhausted": "نفدت الحصة", - "skillManagement.detail.builder.modelProviderSettings": "إعدادات مزود النموذج", - "skillManagement.detail.builder.modelSearch": "البحث في النماذج...", "skillManagement.detail.builder.modifyPlaceholder": "اطلب من الذكاء الاصطناعي تعديل هذه المهارة...", "skillManagement.detail.builder.open": "افتح Skill Builder", "skillManagement.detail.builder.placeholder": "وصف السيناريو...", @@ -61,8 +47,6 @@ "skillManagement.detail.builder.thinkingUnavailable": "لم يتم إرجاع أي محتوى تفكير بواسطة النموذج.", "skillManagement.detail.builder.title": "Skill Builder", "skillManagement.detail.builder.tryExample": "جرب مثالا", - "skillManagement.detail.builder.voice": "إدخال صوتي", - "skillManagement.detail.builder.voiceUnavailable": "الإدخال الصوتي غير متاح بعد.", "skillManagement.detail.cancelAddMetadata": "إلغاء إضافة البيانات الوصفية", "skillManagement.detail.closeFileTab": "إغلاق {{name}}", "skillManagement.detail.closeVersions": "إغلاق الإصدارات", @@ -70,14 +54,10 @@ "skillManagement.detail.copyContentSuccess": "تم نسخ المحتوى إلى الحافظة.", "skillManagement.detail.copyFile": "نسخ", "skillManagement.detail.copyFileSuccess": "الملف جاهز للصقه.", - "skillManagement.detail.copyVersionId": "نسخ المعرف", - "skillManagement.detail.copyVersionIdSuccess": "تم نسخ المعرف إلى الحافظة.", "skillManagement.detail.createFile": "ملف جديد", - "skillManagement.detail.createFileDescription": "أدخل مسار الملف المتعلق بجذر المهارة.", "skillManagement.detail.createFileMenu": "ملف جديد...", "skillManagement.detail.createFileSuccess": "تم إنشاء الملف.", "skillManagement.detail.createFolder": "مجلد جديد", - "skillManagement.detail.createFolderDescription": "أدخل مسار المجلد المتعلق بجذر المهارة.", "skillManagement.detail.createFolderMenu": "مجلد جديد...", "skillManagement.detail.createFolderSuccess": "تم إنشاء المجلد.", "skillManagement.detail.createdBy": "تم إنشاؤها بواسطة {{name}}", @@ -100,10 +80,8 @@ "skillManagement.detail.fileMeta": "{{type}} · {{size}} بايت", "skillManagement.detail.fileMissing": "لم يتم العثور على الملف.", "skillManagement.detail.fileOperationFailed": "فشلت عملية الملف.", - "skillManagement.detail.files": "الملفات", "skillManagement.detail.latest": "الأحدث", "skillManagement.detail.loadFailed": "فشل في تحميل المهارة.", - "skillManagement.detail.loadingTags": "جارٍ تحميل العلامات...", "skillManagement.detail.markdownLiveMode": "معاينة حية", "skillManagement.detail.markdownSourceMode": "محرر المصدر", "skillManagement.detail.metadataKey": "اسم", @@ -113,10 +91,7 @@ "skillManagement.detail.nameThisVersion": "تسمية هذا الإصدار", "skillManagement.detail.noFileSelected": "حدد ملفًا لمعاينته.", "skillManagement.detail.noFiles": "لا توجد ملفات.", - "skillManagement.detail.noSearchResults": "لا توجد ملفات مطابقة.", - "skillManagement.detail.noSelectableTags": "لا توجد علامات متاحة.", "skillManagement.detail.noVersions": "لا توجد إصدارات منشورة حتى الآن.", - "skillManagement.detail.pasteFile": "لصق", "skillManagement.detail.pasteFileSuccess": "تم لصق الملف.", "skillManagement.detail.previewUnsupported": "هذا الملف لا يدعم المعاينة.", "skillManagement.detail.publish": "نشر", @@ -128,9 +103,7 @@ "skillManagement.detail.publishUpdate": "نشر التحديث", "skillManagement.detail.published": "تم النشر", "skillManagement.detail.publishedAt": "نُشر {{time}}", - "skillManagement.detail.publishedVersion": "تم النشر #{{number}}", "skillManagement.detail.publishing": "جارٍ النشر...", - "skillManagement.detail.readonly": "اقرأ فقط", "skillManagement.detail.referenceFiles.confirm": "أدخل تأكيد", "skillManagement.detail.referenceFiles.empty": "لا توجد ملفات متاحة.", "skillManagement.detail.referenceFiles.livePlaceholder": "كتابة تعليمات للوكيل. اكتب / للإشارة إلى الملفات.", @@ -138,14 +111,10 @@ "skillManagement.detail.referenceFiles.title": "الملفات المرجعية", "skillManagement.detail.referencedBy_one": "يستخدمه {{count}} وكيل", "skillManagement.detail.referencedBy_other": "يستخدمه {{count}} وكلاء", - "skillManagement.detail.removeMetadata": "احذف البيانات التعريفية {{name}}", "skillManagement.detail.removeTag": "إزالة {{tag}}", "skillManagement.detail.removeTagSuccess": "تمت إزالة العلامة.", - "skillManagement.detail.renameFile": "إعادة تسمية الملف", - "skillManagement.detail.renameFileDescription": "أدخل المسار الجديد المتعلق بجذر المهارة.", "skillManagement.detail.renameFileSuccess": "تمت إعادة تسمية الملف.", "skillManagement.detail.renameSkillSuccess": "تمت إعادة تسمية Skill.", - "skillManagement.detail.renameVersion": "إعادة تسمية", "skillManagement.detail.renameVersionFailed": "فشلت إعادة تسمية الإصدار.", "skillManagement.detail.renameVersionPrompt": "اسم الإصدار", "skillManagement.detail.renameVersionSuccess": "تمت إعادة تسمية الإصدار.", @@ -155,17 +124,14 @@ "skillManagement.detail.restoreVersionConfirmTitle": "استعادة هذا الإصدار؟", "skillManagement.detail.restoreVersionFailed": "فشل في استعادة الإصدار.", "skillManagement.detail.restoreVersionSuccess": "تمت استعادة الإصدار للمسودة.", - "skillManagement.detail.save": "حفظ", "skillManagement.detail.saveConflictConfirmDescription": "تم تحديث هذه المهارة في مكان آخر. قم بتحميل أحدث مسودة لمواصلة التحرير. سيتم تجاهل تغييراتك الحالية.", "skillManagement.detail.saveConflictConfirmTitle": "هل تريد تحميل أحدث مسودة؟", "skillManagement.detail.saveConflictReload": "تحميل أحدث مسودة", "skillManagement.detail.saveConflictStatus": "تم الكشف عن الصراع. قم بتحميل أحدث مسودة لمواصلة التحرير.", "skillManagement.detail.saveFailed": "فشل حفظ الملف.", - "skillManagement.detail.saveSuccess": "تم حفظ الملف.", "skillManagement.detail.saved": "تم الحفظ", "skillManagement.detail.savedAt": "تم الحفظ {{time}}", "skillManagement.detail.saving": "جارٍ الحفظ...", - "skillManagement.detail.searchFiles": "البحث عن الملفات", "skillManagement.detail.showMoreReferences": "أظهر {{count}} المزيد", "skillManagement.detail.skillDescriptionPlaceholder": "صف ما تفعله هذه المهارة ومتى يجب على الوكلاء استخدامها.", "skillManagement.detail.skillNamePlaceholder": "استخدم الحروف الصغيرة والأرقام والواصلات.", @@ -177,16 +143,12 @@ "skillManagement.detail.uploadConflict": "يوجد ملف بهذا الاسم بالفعل.", "skillManagement.detail.uploadFailureSummary": "فشل {{failed}} من ملفات {{total}}", "skillManagement.detail.uploadFailureTitle": "فشلت بعض عمليات التحميل", - "skillManagement.detail.uploadFile": "تحميل ملف", - "skillManagement.detail.uploadFileFailed": "فشل تحميل الملف.", "skillManagement.detail.uploadFileSuccess": "تم تحميل الملف.", "skillManagement.detail.uploadFilesButton": "تحميل ملفات {{count}}", "skillManagement.detail.uploadFilesFailedStatus_one": "فشل تحميل ملف {{count}}.", "skillManagement.detail.uploadFilesFailedStatus_other": "فشل تحميل الملفات {{count}}.", "skillManagement.detail.uploadFilesMenu": "تحميل الملفات...", "skillManagement.detail.uploadFilesProgress": "جارٍ تحميل {{completed}}/{{total}}", - "skillManagement.detail.uploadFilesResult": "تم تحميل {{uploaded}} · فشل {{failed}}", - "skillManagement.detail.uploadFilesStatus": "حالة التحميل", "skillManagement.detail.uploadInvalidExtension": "نوع الملف هذا غير مدعوم.", "skillManagement.detail.uploadInvalidName": "اسم غير صالح.", "skillManagement.detail.uploadInvalidPath": "مسار الملف غير صالح.", @@ -206,7 +168,6 @@ "skillManagement.detail.uploadWillReplace": "سيتم استبدال الملف الموجود", "skillManagement.detail.uploadWillUseName": "سيتم التحميل كـ {{name}}", "skillManagement.detail.versionHistory": "فتح سجل الإصدارات", - "skillManagement.detail.versionMeta": "{{hash}} · {{time}}", "skillManagement.detail.versionPublishNote": "ملاحظات الإصدار", "skillManagement.detail.versionPublishNotePlaceholder": "وصف هذا التغيير", "skillManagement.detail.versionPublishedMeta": "{{time}} · {{name}}", @@ -227,8 +188,6 @@ "skillManagement.errors.missingSkillMd": "لا تحتوي هذه الحزمة على SKILL.md. أضف الملف وقم باستيراده مرة أخرى.", "skillManagement.errors.nameConflict": "توجد بالفعل مهارة اسمها \"{{name}}\". أعد تسميته إلى SKILL.md وقم باستيراده مرة أخرى.", "skillManagement.errors.workspaceLimit": "وصلت مساحة العمل الخاصة بك إلى الحد الأقصى وهو 500 مهارة. احذف مهارة لإضافة مهارة جديدة.", - "skillManagement.exportDialog.description": "سيتم تصدير المسودة المحفوظة الحالية لمهارتك. إذا كانت لديك تغييرات غير محفوظة في المحرر، يُرجى حفظها قبل التصدير.", - "skillManagement.exportDialog.title": "تصدير المسودة المحفوظة الحالية", "skillManagement.import": "استيراد", "skillManagement.importFailed": "فشل استيراد المهارة.", "skillManagement.importSuccess": "Skill مستورد.", @@ -236,12 +195,10 @@ "skillManagement.loadingError": "فشل في تحميل المهارات", "skillManagement.moreActions": "إجراءات أخرى لـ {{name}}", "skillManagement.noDescription": "لا يوجد وصف بعد", - "skillManagement.noTags": "لا توجد علامات", "skillManagement.publishedAt": "تم النشر {{time}}", "skillManagement.referenceCount_one": "{{count}} وكيل", "skillManagement.referenceCount_other": "{{count}} وكلاء", "skillManagement.searchLabel": "البحث عن المهارات", "skillManagement.searchPlaceholder": "البحث بالاسم أو الوصف", - "skillManagement.tags": "العلامات", "skillManagement.title": "المهارات" } diff --git a/web/i18n/ar-TN/snippet.json b/web/i18n/locales/ar-TN/snippet.json similarity index 100% rename from web/i18n/ar-TN/snippet.json rename to web/i18n/locales/ar-TN/snippet.json diff --git a/web/i18n/ar-TN/time.json b/web/i18n/locales/ar-TN/time.json similarity index 100% rename from web/i18n/ar-TN/time.json rename to web/i18n/locales/ar-TN/time.json diff --git a/web/i18n/ar-TN/tools.json b/web/i18n/locales/ar-TN/tools.json similarity index 100% rename from web/i18n/ar-TN/tools.json rename to web/i18n/locales/ar-TN/tools.json diff --git a/web/i18n/ar-TN/workflow.json b/web/i18n/locales/ar-TN/workflow.json similarity index 99% rename from web/i18n/ar-TN/workflow.json rename to web/i18n/locales/ar-TN/workflow.json index 8fa607bec56..036eeab61ae 100644 --- a/web/i18n/ar-TN/workflow.json +++ b/web/i18n/locales/ar-TN/workflow.json @@ -460,8 +460,6 @@ "nodes.agent.pluginNotInstalledDesc": "تم تثبيت هذا الملحق من GitHub. يرجى الانتقال إلى الملحقات لإعادة التثبيت", "nodes.agent.roster.change": "تغيير", "nodes.agent.roster.editInConsole": "تعديل في Agents", - "nodes.agent.roster.editInConsoleDisabled": "يتطلب تعديل الوكلاء دور المحرر أو دورًا أعلى.", - "nodes.agent.roster.editInConsoleDisabledRbac": "لا يمكن تعديل الوكلاء إلا للأعضاء الذين لديهم دور المحرر أو دور أعلى، أو إذن إدارة Agents.", "nodes.agent.roster.inlineSetup.description": "إعداد لمرة واحدة يبقى فقط في هذه العقدة", "nodes.agent.roster.inlineSetup.name": "إعداد inline", "nodes.agent.roster.inlineSetup.type": "فقط في هذه العقدة", diff --git a/web/i18n/az-AZ/agent-v-2.json b/web/i18n/locales/az-AZ/agent-v-2.json similarity index 98% rename from web/i18n/az-AZ/agent-v-2.json rename to web/i18n/locales/az-AZ/agent-v-2.json index 875e8e2ffe3..0a8ab5db275 100644 --- a/web/i18n/az-AZ/agent-v-2.json +++ b/web/i18n/locales/az-AZ/agent-v-2.json @@ -69,6 +69,7 @@ "agentDetail.configure.answer.activity.runningCommands": "Əmrlər icra olunur", "agentDetail.configure.answer.duration.minute": "{{count}} dəq", "agentDetail.configure.answer.duration.second": "{{count}} san", + "agentDetail.configure.answer.responseEnded": "Cavab tamamlandı", "agentDetail.configure.answer.thinking": "Düşünür", "agentDetail.configure.build.empty.communityEditionTip": "Community Edition buraxılışı çıxarılmış konfiqurasiyaların versiyalarını saxlasa da, fayl sisteminin özündə versiyaların saxlanmasını dəstəkləmir. Qurma rejimində əməliyyatlarınızı ehtiyatla yerinə yetirin, çünki fayl sistemindəki dəyişikliklər real vaxtda baş verir və onları həmişə tam geri qaytarmaq olmur. Community Edition buraxılışı geniş xarici auditoriyaya xidmət göstərmək üçün ideal seçim deyil.", "agentDetail.configure.build.empty.description": "İstədiyinizi təsvir edin, söhbət davam etdikcə agent soldakı formanı dolduracaq.", @@ -269,7 +270,6 @@ "agentDetail.configure.workingDirectory.breadcrumbLabel": "Fayl sistemi yolu", "agentDetail.configure.workingDirectory.description": "Agentin işləyərkən oxuduğu və yazdığı fayllar. Yaratdığı hər şey, məsələn, qna_report.pdf, burada görünür.", "agentDetail.configure.workingDirectory.fileSystem": "Fayl sistemi", - "agentDetail.configure.workingDirectory.open": "İş qovluğunu aç", "agentDetail.configure.workingDirectory.persistentFiles": "Daimi", "agentDetail.configure.workingDirectory.persistentFilesTooltip": "Agentin saxladığı və son istifadəçilərin söhbətləri və iş axınları daxil olmaqla, işlədiyi hər yerdə istifadə edə bildiyi fayllar. Bu qurma söhbəti zamanı burada edilən dəyişikliklər «Tətbiq et» düyməsinə basdıqda qüvvəyə minir.", "agentDetail.configure.workingDirectory.temporaryFiles": "Müvəqqəti", @@ -348,7 +348,6 @@ "agentDetail.sections.logs": "Jurnallar", "agentDetail.sections.monitoring": "Monitorinq", "agentDetail.title": "Agent", - "agentDetail.type": "Agent", "agentDetail.versionHistory.empty": "Hələ versiya yoxdur", "agentDetail.versionHistory.exitVersions": "Versiyalardan çıx", "agentDetail.versionHistory.filter": "Versiyaları süzgəcdən keçir", @@ -408,8 +407,5 @@ "roster.sort.lastModified": "Son dəyişdirilənlər", "roster.sort.recentlyCreated": "Son yaradılanlar", "roster.title": "Agentlər", - "roster.usageStatus.draft": "Qaralama", - "skillManagement.detail.uploadFilesFailedStatus_one": "{{count}} faylın yüklənməsi uğursuz oldu.", - "skillManagement.detail.uploadFilesFailedStatus_other": "{{count}} faylın yüklənməsi uğursuz oldu.", - "skillManagement.title": "Bacarıqlar" + "roster.usageStatus.draft": "Qaralama" } diff --git a/web/i18n/az-AZ/app-annotation.json b/web/i18n/locales/az-AZ/app-annotation.json similarity index 100% rename from web/i18n/az-AZ/app-annotation.json rename to web/i18n/locales/az-AZ/app-annotation.json diff --git a/web/i18n/az-AZ/app-api.json b/web/i18n/locales/az-AZ/app-api.json similarity index 100% rename from web/i18n/az-AZ/app-api.json rename to web/i18n/locales/az-AZ/app-api.json diff --git a/web/i18n/az-AZ/app-debug.json b/web/i18n/locales/az-AZ/app-debug.json similarity index 100% rename from web/i18n/az-AZ/app-debug.json rename to web/i18n/locales/az-AZ/app-debug.json diff --git a/web/i18n/az-AZ/app-log.json b/web/i18n/locales/az-AZ/app-log.json similarity index 97% rename from web/i18n/az-AZ/app-log.json rename to web/i18n/locales/az-AZ/app-log.json index a3a79c71da7..97d1f90959a 100644 --- a/web/i18n/az-AZ/app-log.json +++ b/web/i18n/locales/az-AZ/app-log.json @@ -4,11 +4,9 @@ "agentLogDetail.iteration": "İterasiya", "agentLogDetail.iterations": "İterasiyalar", "agentLogDetail.toolUsed": "İstifadə olunan alət", - "archives.action.downloadMonth": "{{month}} arxivini yüklə", "archives.action.downloadReady": "Arxiv yükləməyə hazırdır.", "archives.action.prepareDownload": "Yükləməni hazırla", "archives.action.prepareFailed": "Arxivin yüklənməsini hazırlamaq mümkün olmadı.", - "archives.action.prepareMonth": "{{month}} arxivinin yüklənməsini hazırla", "archives.action.prepareStarted": "Arxivin yüklənməsi hazırlanır.", "archives.action.preparing": "Hazırlanır", "archives.description": "Bu iş məkanında arxivləşdirilmiş iş axını icra jurnallarını aylıq CSV faylları şəklində yükləyin.", @@ -91,6 +89,7 @@ "table.header.updatedTime": "Yenilənmə vaxtı", "table.header.user": "Son istifadəçi və ya hesab", "table.header.userRate": "İstifadəçi qiymətləndirməsi", + "table.unread": "Oxunmamış", "title": "Jurnallar", "triggerBy.appRun": "Veb tətbiq", "triggerBy.debugging": "Sazlama", diff --git a/web/i18n/az-AZ/app-overview.json b/web/i18n/locales/az-AZ/app-overview.json similarity index 100% rename from web/i18n/az-AZ/app-overview.json rename to web/i18n/locales/az-AZ/app-overview.json diff --git a/web/i18n/az-AZ/app.json b/web/i18n/locales/az-AZ/app.json similarity index 100% rename from web/i18n/az-AZ/app.json rename to web/i18n/locales/az-AZ/app.json diff --git a/web/i18n/az-AZ/billing.json b/web/i18n/locales/az-AZ/billing.json similarity index 100% rename from web/i18n/az-AZ/billing.json rename to web/i18n/locales/az-AZ/billing.json diff --git a/web/i18n/az-AZ/common.json b/web/i18n/locales/az-AZ/common.json similarity index 98% rename from web/i18n/az-AZ/common.json rename to web/i18n/locales/az-AZ/common.json index 030854fe715..42d8330e943 100644 --- a/web/i18n/az-AZ/common.json +++ b/web/i18n/locales/az-AZ/common.json @@ -107,6 +107,7 @@ "avatar.deleteDescription": "Profil şəklinizi çıxarmaq istədiyinizə əminsiniz? Hesabınızda baş hərfdən ibarət standart avatardan istifadə olunacaq.", "avatar.deleteTitle": "Avatarın çıxarılması", "avatar.editAction": "Avatarı redaktə et", + "calendar.selectedDate": "Seçilib", "chat.citation.characters": "Simvollar:", "chat.citation.hitCount": "Axtarış sayı:", "chat.citation.hitScore": "Axtarış balı:", @@ -208,9 +209,7 @@ "mainNav.marketplace": "Mağaza", "mainNav.skills": "Bacarıqlar", "mainNav.webApps.noResults": "Veb tətbiq tapılmadı", - "mainNav.webApps.openApp": "{{name}} veb tətbiqini aç", "mainNav.webApps.searchPlaceholder": "Veb tətbiqləri axtar", - "mainNav.workspace.credits": "{{count}} kredit", "mainNav.workspace.creditsUnit": "kredit", "mainNav.workspace.inviteMembers": "Üzvləri dəvət et", "mainNav.workspace.noResults": "İş sahəsi tapılmadı", @@ -232,7 +231,6 @@ "members.datasetOperatorTip": "Yalnız bilik bazasını idarə edə bilər", "members.editRole": "Rolu redaktə et", "members.editorTip": "Tətbiqlər yarada və redaktə edə bilər", - "members.email": "E-poçt", "members.emailInvalid": "Etibarsız e-poçt formatı", "members.emailNotSetup": "E-poçt serveri qurulmayıb, buna görə dəvət e-poçtları göndərilə bilmir. Dəvətdən sonra yaradılacaq dəvət keçidini istifadəçilərə bildirin.", "members.emailPlaceholder": "E-poçt ünvanlarını yaz və ya yapışdır…", @@ -249,7 +247,6 @@ "members.inviteLimitExceeded": "Bu dəvətlər üçün kifayət qədər üzv yeri yoxdur.", "members.inviteTeamMember": "Komanda üzvü əlavə et", "members.inviteTeamMemberTip": "Daxil olduqdan sonra komandanızın məlumatlarına birbaşa daxil ola bilərlər.", - "members.invitedAsRole": "{{role}} rolunda dəvət edilib", "members.lastActive": "Son aktivlik", "members.memberActions": "Üzv əməliyyatları", "members.memberDetails.assign": "Təyin et", @@ -257,8 +254,6 @@ "members.memberDetails.assignedRoles": "Təyin edilmiş rollar", "members.memberDetails.customGroup": "Fərdiləşdirilmiş", "members.memberDetails.generalGroup": "Ümumi", - "members.memberDetails.openAria": "{{name}} üçün üzv məlumatlarını aç", - "members.memberDetails.roleActionsAria": "{{role}} rolu üçün əməliyyatları aç", "members.memberDetails.roleNoPermissionSummary": "Cari rolun heç bir icazəsi yoxdur.", "members.memberDetails.rolePermissionSummary": "{{role}} üçün icazələr: {{permissions}}", "members.memberDetails.title": "Üzv məlumatları", @@ -369,7 +364,6 @@ "modelProvider.card.usagePriorityTip": "Modelləri işə salarkən ilk olaraq hansı resursdan istifadə ediləcəyini təyin et.", "modelProvider.configureTip": "İstifadə etmək üçün API açarını qur və ya model əlavə et", "modelProvider.confirmDelete": "Silinməni təsdiqləyirsiniz?", - "modelProvider.credits": "Mesaj kreditləri", "modelProvider.defaultConfig": "Standart konfiqurasiya", "modelProvider.discoverMore": "Daha çoxunu burada kəşf et", "modelProvider.embeddingModel.key": "Vektorlaşdırma modeli", @@ -426,7 +420,6 @@ "modelProvider.selector.noProviderConfiguredDesc": "Provayder quraşdırmaq üçün mağazaya bax və ya parametrlərdə provayderləri konfiqurasiya et.", "modelProvider.selector.onlyCompatibleModelsShown": "Yalnız uyğun modellər göstərilir", "modelProvider.selector.showIncompatibleModels": "Uyğun olmayan modelləri göstər", - "modelProvider.selector.suggestion": "Təklif", "modelProvider.selector.suggestionTip": "Agent tapşırıqları üçün tövsiyə olunur", "modelProvider.showModels": "Modelləri göstər", "modelProvider.speechToTextModel.key": "Nitqi mətnə çevirmə modeli", @@ -457,6 +450,8 @@ "operation.confirmAction": "Əməliyyatını təsdiqlə.", "operation.copied": "Kopyalandı", "operation.copy": "Kopyala", + "operation.copyErrorDetails": "Xəta təfərrüatlarını kopyala", + "operation.copyErrorFailed": "Kopyalama alınmadı. Yenidən cəhd et", "operation.copyImage": "Şəkli kopyala", "operation.create": "Yarat", "operation.deSelectAll": "Bütün seçimləri ləğv et", @@ -473,7 +468,7 @@ "operation.fill": "Avtomatik doldur", "operation.format": "Formatla", "operation.imageCopied": "Şəkil kopyalandı", - "operation.imageDownloaded": "Şəkil yükləndi", + "operation.imageCopyFailed": "Şəkli kopyalamaq alınmadı. Əvəzinə onu yükləyin.", "operation.in": "daxilində", "operation.learnMore": "Ətraflı öyrən", "operation.log": "Jurnal", @@ -510,7 +505,6 @@ "operation.sure": "Əminəm", "operation.toggleFullscreen": "Tam ekran rejimini dəyiş", "operation.toggleMute": "Səsi aç və ya söndür", - "operation.upload": "Yüklə", "operation.view": "Bax", "operation.viewDetails": "Ətraflı bax", "operation.viewMore": "Daha çoxuna bax", @@ -540,7 +534,6 @@ "promptEditor.requestURL.item.title": "Sorğu URL-i", "promptEditor.variable.modal.add": "Yeni dəyişən", "promptEditor.variable.modal.addTool": "Yeni alət", - "provider.anthropicHosted.trialQuotaTip": "Anthropic sınaq kvotanızın müddəti 2025/03/17 tarixində bitəcək və bundan sonra əlçatan olmayacaq. Ondan vaxtında istifadə edin.", "provider.encrypted.back": " texnologiyası ilə şifrələnəcək və saxlanılacaq.", "provider.encrypted.front": "API açarınız ", "provider.validating": "Açar yoxlanılır...", @@ -563,7 +556,6 @@ "settings.preferences": "Tərcihlər", "settings.provider": "Model provayderi", "settings.resourceAccess": "Resurslara giriş", - "settings.resourceAccessDescription": "Tətbiqlərin və bilik bazalarının istifadə edə biləcəyi icazə qaydalarını konfiqurasiya edin. İcazə qaydaları müəyyən resurslara giriş konfiqurasiyalarında üzvlərə təyin edilə bilər.", "settings.rolesAndPermissions": "Rollar və icazələr", "settings.settings": "Parametrlər", "settings.swaggerAPIAsTool": "Alət kimi Swagger API", @@ -693,7 +685,6 @@ "userProfile.helpCenter": "Sənədlərə bax", "userProfile.logout": "Çıxış", "userProfile.roadmap": "Yol xəritəsi", - "userProfile.settings": "Parametrlər", "userProfile.workspace": "İş sahəsi", "voice.language.arTN": "Tunis ərəb dili", "voice.language.deDE": "Alman dili", diff --git a/web/i18n/az-AZ/custom.json b/web/i18n/locales/az-AZ/custom.json similarity index 100% rename from web/i18n/az-AZ/custom.json rename to web/i18n/locales/az-AZ/custom.json diff --git a/web/i18n/az-AZ/dataset-creation.json b/web/i18n/locales/az-AZ/dataset-creation.json similarity index 100% rename from web/i18n/az-AZ/dataset-creation.json rename to web/i18n/locales/az-AZ/dataset-creation.json diff --git a/web/i18n/az-AZ/dataset-documents.json b/web/i18n/locales/az-AZ/dataset-documents.json similarity index 100% rename from web/i18n/az-AZ/dataset-documents.json rename to web/i18n/locales/az-AZ/dataset-documents.json diff --git a/web/i18n/az-AZ/dataset-hit-testing.json b/web/i18n/locales/az-AZ/dataset-hit-testing.json similarity index 100% rename from web/i18n/az-AZ/dataset-hit-testing.json rename to web/i18n/locales/az-AZ/dataset-hit-testing.json diff --git a/web/i18n/az-AZ/dataset-pipeline.json b/web/i18n/locales/az-AZ/dataset-pipeline.json similarity index 100% rename from web/i18n/az-AZ/dataset-pipeline.json rename to web/i18n/locales/az-AZ/dataset-pipeline.json diff --git a/web/i18n/az-AZ/dataset-settings.json b/web/i18n/locales/az-AZ/dataset-settings.json similarity index 100% rename from web/i18n/az-AZ/dataset-settings.json rename to web/i18n/locales/az-AZ/dataset-settings.json diff --git a/web/i18n/az-AZ/dataset.json b/web/i18n/locales/az-AZ/dataset.json similarity index 97% rename from web/i18n/az-AZ/dataset.json rename to web/i18n/locales/az-AZ/dataset.json index d5c77158719..5cadbb6ea7c 100644 --- a/web/i18n/az-AZ/dataset.json +++ b/web/i18n/locales/az-AZ/dataset.json @@ -163,7 +163,6 @@ "newKnowledge.configureProvider": "{{provider}} konfiqurasiyasını qur", "newKnowledge.connectNotion": "Notion-a qoşul", "newKnowledge.connectProvider": "{{provider}} ilə əlaqə qur", - "newKnowledge.connectProviderGeneric": "Provayderə qoşul", "newKnowledge.connectSource": "Mənbə qoş", "newKnowledge.connectSourceDescription": "Məzmun Notion, saytlar və digər xidmətlərlə sinxron qalır.", "newKnowledge.connectingProvider": "Qoşulur…", @@ -179,18 +178,14 @@ "newKnowledge.crawlFailedDescription": "Skaner bu önbaxışı tamamlaya bilmədi. URL-i yoxlayın, skan seçimlərini tənzimləyin və ya yenidən cəhd edin.", "newKnowledge.crawlFailedProvider": "{{provider}} xəta bildirdi. Provayderlə əlaqəni yoxlayın və yenidən cəhd edin.", "newKnowledge.crawlFailedTimeout": "Skan üçün ayrılmış vaxt bitdi. Daha məhdud əhatəli URL sınayın və ya maksimum səhifə sayını azaldın.", - "newKnowledge.crawlNotStarted": "Başlanmayıb", "newKnowledge.crawlOptions": "Skan seçimləri", "newKnowledge.crawlPreview": "Skan önbaxışı", "newKnowledge.crawlProgress": "{{host}} saytının skan gedişatı", - "newKnowledge.crawlSetupUnavailableDescription": "Skan iş axını əlçatan olana qədər saytın skan konfiqurasiyası deaktivdir.", - "newKnowledge.crawlSetupUnavailableTitle": "Firecrawl hazırdır", "newKnowledge.crawlStartFailed": "Skan önbaxışını başlatmaq mümkün olmadı. Yenidən cəhd edin.", "newKnowledge.crawlStopped": "Skan dayandırıldı", "newKnowledge.crawling": "Skan edilir…", "newKnowledge.crawlingPages": "{{host}} skan edilir… {{count}} səhifə tapıldı", "newKnowledge.createFailed": "Bu bilik bazasını yarada bilmədik. Giriş icazənizi yoxlayın və yenidən cəhd edin.", - "newKnowledge.createIllustrationTitle": "Məzmununuzu bir dəfə qoşun, hər tətbiq ən yeni biliklərlə cavab versin.", "newKnowledge.createTitle": "Bilik bazası yarat", "newKnowledge.createdAt": "Yaradılıb", "newKnowledge.creators": "Yaradanlar", @@ -220,7 +215,6 @@ "newKnowledge.documentColumn": "Sənəd", "newKnowledge.documentContentIncomplete": "Sənədin qalan məzmunu yüklənir…", "newKnowledge.documentContents": "Məzmun", - "newKnowledge.documentFacts": "Sənədin təfərrüatları", "newKnowledge.documentFilterLabel": "Sənədləri vəziyyətə görə süzgəcdən keçir", "newKnowledge.documentLoadErrorDescription": "Bu sənədi yükləyə bilmədik. Yenidən cəhd edin.", "newKnowledge.documentLoadErrorTitle": "Sənəd yüklənə bilmədi", @@ -268,7 +262,6 @@ "newKnowledge.documentsSelected": "{{count}} sənəd seçilib", "newKnowledge.documentsUnavailable": "Sənəd sayı məlum deyil", "newKnowledge.downloadDocuments": "Yüklə", - "newKnowledge.editSource": "Redaktə et", "newKnowledge.emptyChunk": "Boş hissə", "newKnowledge.emptyDescription": "Sənədlərinizi əlavə edin ki, agentlər onları birbaşa oxuya bilsin. İndeksləməni konfiqurasiya etməyə ehtiyac yoxdur.", "newKnowledge.emptyTitle": "Bilik bazaları yaratmağın daha ağıllı yolu", @@ -344,7 +337,6 @@ "newKnowledge.processingTaskState.succeeded": "Tamamlanıb", "newKnowledge.processingTaskState.superseded": "Əvəz edilib", "newKnowledge.providerConnected": "{{provider}} qoşulub", - "newKnowledge.providerLabel": "Provayder", "newKnowledge.providerLoadFailed": "Provayderlər yüklənə bilmədi", "newKnowledge.providerNotConfigured": "{{provider}} konfiqurasiya edilməyib", "newKnowledge.providerNotConfiguredDescription": "{{provider}} ilə saytları skan etmək üçün API açarı və ya son nöqtə URL-i əlavə edin.", @@ -369,12 +361,10 @@ "newKnowledge.revisionState.superseded": "Əvvəlki", "newKnowledge.rootUrl": "Kök URL", "newKnowledge.rootUrlPlaceholder": "https://docs.example.com", - "newKnowledge.routeUnavailable": "Bu bölmə quraşdırmanın növbəti addımında əlçatan olacaq.", "newKnowledge.searchDocuments": "Sənədləri axtar", "newKnowledge.searchSources": "Mənbələri axtar", "newKnowledge.selectAll": "Hamısını seç", "newKnowledge.selectAllDocuments": "Bütün sənədləri seç", - "newKnowledge.selectAllSources": "Bütün mənbələri seç", "newKnowledge.selectChunk": "Məzmununu yoxlamaq üçün hissə seçin.", "newKnowledge.selectFilesAndFolders": "Sinxronlaşdırılacaq fayl və qovluqları seç", "newKnowledge.skippedFailed": "Ötürüldü (uğursuz oldu)", @@ -384,7 +374,6 @@ "newKnowledge.sourceFilterLabel": "Mənbələrin vəziyyətinə görə süzgəc", "newKnowledge.sourceName": "Mənbə adı", "newKnowledge.sourceNamePlaceholder": "Məsələn, məhsul sənədlərinin skan edilməsi", - "newKnowledge.sourceNameRequired": "Mənbə adını daxil edin.", "newKnowledge.sourceSetupBackendDependency": "Mənbə quraşdırması tam şəkildə göstərilir; redaktə və önbaxış əməliyyatları qaralama mənbə üçün server iş axınını tələb edir.", "newKnowledge.sourceStatus.active": "Aktiv", "newKnowledge.sourceStatus.disabled": "Deaktiv", @@ -431,7 +420,6 @@ "newKnowledge.unavailableDescription": "KnowledgeFS hazırda bu iş sahəsi üçün əlçatan deyil.", "newKnowledge.unavailableTitle": "Yeni bilik bazası əlçatan deyil", "newKnowledge.updated": "{{date}} tarixində yenilənib", - "newKnowledge.updatedAt": "Son yenilənmə", "newKnowledge.updatedColumn": "Yenilənib", "newKnowledge.uploadCharactersUnavailable": "Simvol sayı: məlum deyil", "newKnowledge.uploadDocuments": "Sənədləri yüklə", @@ -460,11 +448,9 @@ "retrievalSettings": "Məlumat axtarışı parametri", "retry": "Yenidən cəhd et", "selectExternalKnowledgeAPI.placeholder": "Xarici bilik API-si seçin", - "serviceApi.card.addApiKey": "API açarı əlavə et", "serviceApi.card.apiKey": "API açarı", "serviceApi.card.apiReference": "API istinadı", "serviceApi.card.endpoint": "Xidmət API-sinin son nöqtəsi", - "serviceApi.card.manageApiKey": "API açarını idarə et", "serviceApi.card.title": "Server xidməti API-si", "serviceApi.disabled": "Deaktiv", "serviceApi.enabled": "Aktivdir", diff --git a/web/i18n/az-AZ/deployments.json b/web/i18n/locales/az-AZ/deployments.json similarity index 97% rename from web/i18n/az-AZ/deployments.json rename to web/i18n/locales/az-AZ/deployments.json index 096a7b51569..6238c01ec0f 100644 --- a/web/i18n/az-AZ/deployments.json +++ b/web/i18n/locales/az-AZ/deployments.json @@ -27,7 +27,6 @@ "list.emptyTitle": "Hələ yerləşdirmə yoxdur", "overview.chip.latest": "ən son", "status.RUNTIME_INSTANCE_STATUS_DEPLOYING": "Yerləşdirilir", - "status.RUNTIME_INSTANCE_STATUS_FAILED": "Yerləşdirmə uğursuz oldu", "status.RUNTIME_INSTANCE_STATUS_INVALID": "Etibarsız", "status.RUNTIME_INSTANCE_STATUS_READY": "İşləyir", "status.RUNTIME_INSTANCE_STATUS_UNDEPLOYED": "Yerləşdirilməyib", @@ -87,6 +86,5 @@ "studio.versionValue": "Versiyadakı dəyər", "studio.versionsBehind_one": "{{count}} versiya geridə", "studio.versionsBehind_other": "{{count}} versiya geridə", - "versions.checkingReleaseContent": "Buraxılışın məzmunu yoxlanılır...", "versions.deployTo": "{{name}} mühitinə yerləşdir" } diff --git a/web/i18n/az-AZ/device-flow.json b/web/i18n/locales/az-AZ/device-flow.json similarity index 100% rename from web/i18n/az-AZ/device-flow.json rename to web/i18n/locales/az-AZ/device-flow.json diff --git a/web/i18n/az-AZ/education.json b/web/i18n/locales/az-AZ/education.json similarity index 100% rename from web/i18n/az-AZ/education.json rename to web/i18n/locales/az-AZ/education.json diff --git a/web/i18n/az-AZ/explore.json b/web/i18n/locales/az-AZ/explore.json similarity index 100% rename from web/i18n/az-AZ/explore.json rename to web/i18n/locales/az-AZ/explore.json diff --git a/web/i18n/az-AZ/layout.json b/web/i18n/locales/az-AZ/layout.json similarity index 100% rename from web/i18n/az-AZ/layout.json rename to web/i18n/locales/az-AZ/layout.json diff --git a/web/i18n/az-AZ/login.json b/web/i18n/locales/az-AZ/login.json similarity index 100% rename from web/i18n/az-AZ/login.json rename to web/i18n/locales/az-AZ/login.json diff --git a/web/i18n/az-AZ/oauth.json b/web/i18n/locales/az-AZ/oauth.json similarity index 100% rename from web/i18n/az-AZ/oauth.json rename to web/i18n/locales/az-AZ/oauth.json diff --git a/web/i18n/az-AZ/permission-keys.json b/web/i18n/locales/az-AZ/permission-keys.json similarity index 100% rename from web/i18n/az-AZ/permission-keys.json rename to web/i18n/locales/az-AZ/permission-keys.json diff --git a/web/i18n/az-AZ/permission.json b/web/i18n/locales/az-AZ/permission.json similarity index 100% rename from web/i18n/az-AZ/permission.json rename to web/i18n/locales/az-AZ/permission.json diff --git a/web/i18n/az-AZ/pipeline.json b/web/i18n/locales/az-AZ/pipeline.json similarity index 100% rename from web/i18n/az-AZ/pipeline.json rename to web/i18n/locales/az-AZ/pipeline.json diff --git a/web/i18n/az-AZ/plugin-tags.json b/web/i18n/locales/az-AZ/plugin-tags.json similarity index 100% rename from web/i18n/az-AZ/plugin-tags.json rename to web/i18n/locales/az-AZ/plugin-tags.json diff --git a/web/i18n/az-AZ/plugin-trigger.json b/web/i18n/locales/az-AZ/plugin-trigger.json similarity index 100% rename from web/i18n/az-AZ/plugin-trigger.json rename to web/i18n/locales/az-AZ/plugin-trigger.json diff --git a/web/i18n/az-AZ/plugin.json b/web/i18n/locales/az-AZ/plugin.json similarity index 99% rename from web/i18n/az-AZ/plugin.json rename to web/i18n/locales/az-AZ/plugin.json index 175ca236b47..5db5b15cdaf 100644 --- a/web/i18n/az-AZ/plugin.json +++ b/web/i18n/locales/az-AZ/plugin.json @@ -277,7 +277,6 @@ "readmeInfo.noReadmeAvailable": "README faylı mövcud deyil", "readmeInfo.title": "README", "requestAPlugin": "İnteqrasiya tələb et", - "requestSubmit": "Tələb et və ya dərc et", "search": "Axtarış", "searchCategories": "Kateqoriya axtar", "searchInMarketplace": "Mağazada axtar", diff --git a/web/i18n/az-AZ/register.json b/web/i18n/locales/az-AZ/register.json similarity index 100% rename from web/i18n/az-AZ/register.json rename to web/i18n/locales/az-AZ/register.json diff --git a/web/i18n/az-AZ/run-log.json b/web/i18n/locales/az-AZ/run-log.json similarity index 100% rename from web/i18n/az-AZ/run-log.json rename to web/i18n/locales/az-AZ/run-log.json diff --git a/web/i18n/az-AZ/share.json b/web/i18n/locales/az-AZ/share.json similarity index 100% rename from web/i18n/az-AZ/share.json rename to web/i18n/locales/az-AZ/share.json diff --git a/web/i18n/az-AZ/skill.json b/web/i18n/locales/az-AZ/skill.json similarity index 83% rename from web/i18n/az-AZ/skill.json rename to web/i18n/locales/az-AZ/skill.json index ab1c862fd92..9dc09941db0 100644 --- a/web/i18n/az-AZ/skill.json +++ b/web/i18n/locales/az-AZ/skill.json @@ -1,5 +1,4 @@ { - "skillManagement.clearTags": "Etiketləri təmizlə", "skillManagement.create": "Yarat", "skillManagement.createFailed": "Bacarıq yaratmaq mümkün olmadı.", "skillManagement.createSuccess": "Qaralama yaradıldı.", @@ -13,12 +12,9 @@ "skillManagement.deleteFailed": "Bacarığı silmək mümkün olmadı.", "skillManagement.deleteSuccess": "Bacarıq silindi.", "skillManagement.detail.addMetadata": "Metaməlumat əlavə et", - "skillManagement.detail.addMetadataDescription": "Bu Markdown faylının başındakı metaməlumat bölməsinə sahə əlavə edin.", "skillManagement.detail.addTag": "Etiket əlavə et", - "skillManagement.detail.addTagDescription": "Bu bacarıq üçün etiket yaradın və ya mövcud etiketi ona bağlayın. Yeni etiketlər bacarığın metaməlumatları ilə birlikdə yadda saxlanılır.", "skillManagement.detail.addTagSuccess": "Etiket əlavə edildi.", "skillManagement.detail.back": "Bacarıqlara qayıt", - "skillManagement.detail.builder.applyFailed": "Qaralama yeniləmələrini tətbiq etmək mümkün olmadı.", "skillManagement.detail.builder.attach": "Fayl əlavə et", "skillManagement.detail.builder.attachFailed": "Faylı əlavə etmək mümkün olmadı.", "skillManagement.detail.builder.attachLimit": "Ən çox 10 fayl əlavə edə bilərsiniz.", @@ -26,22 +22,12 @@ "skillManagement.detail.builder.attachUnsupported": "Yalnız şəkil, mətn və sənəd faylları əlavə edilə bilər.", "skillManagement.detail.builder.attachmentOnlyMessage": "Bu bacarığı təkmilləşdirmək üçün əlavə edilmiş fayldan istifadə edin.", "skillManagement.detail.builder.close": "Bacarıq qurucusunu bağla", - "skillManagement.detail.builder.compatibleModelsOnly": "Yalnız uyğun modellər göstərilir", "skillManagement.detail.builder.copyResponse": "Cavabı kopyala", "skillManagement.detail.builder.copySuccess": "Cavab mübadilə buferinə kopyalandı.", "skillManagement.detail.builder.editIntro": "Bu bacarığı nəzərdən keçirdim. Nəyi dəyişmək istərdiniz?", "skillManagement.detail.builder.exampleIssueTriage": "Müştəri problemlərinin prioritetlərə görə çeşidlənməsi", "skillManagement.detail.builder.exampleOnboarding": "Yeni əməkdaş üçün uyğunlaşma təlimatı", "skillManagement.detail.builder.exampleSalesFollowUp": "Potensial müştərilərlə əlaqəni davam etdirmə strategiyası", - "skillManagement.detail.builder.followUpDisplayName": "Görünən ad kimi \"Geri ödənişin təsdiqi\" ifadəsindən istifadə et", - "skillManagement.detail.builder.followUpNameIcon": "Təklif olunan adı və işarəni tətbiq et", - "skillManagement.detail.builder.fromMarketplace": "Mağazadan", - "skillManagement.detail.builder.model": "GPT-4o", - "skillManagement.detail.builder.modelCredits.all": "Bütün kreditlər", - "skillManagement.detail.builder.modelCredits.configure": "Konfiqurasiya tələb olunur", - "skillManagement.detail.builder.modelCredits.exhausted": "Kreditlər tükənib", - "skillManagement.detail.builder.modelProviderSettings": "Model provayderinin parametrləri", - "skillManagement.detail.builder.modelSearch": "Modelləri axtar...", "skillManagement.detail.builder.modifyPlaceholder": "Süni intellektdən bu bacarığı dəyişməsini istə...", "skillManagement.detail.builder.open": "Bacarıq qurucusunu aç", "skillManagement.detail.builder.placeholder": "Ssenarini təsvir edin...", @@ -61,8 +47,6 @@ "skillManagement.detail.builder.thinkingUnavailable": "Model heç bir düşüncə məzmunu qaytarmadı.", "skillManagement.detail.builder.title": "Bacarıq qurucusu", "skillManagement.detail.builder.tryExample": "Nümunəni sına", - "skillManagement.detail.builder.voice": "Səslə daxiletmə", - "skillManagement.detail.builder.voiceUnavailable": "Səslə daxiletmə hələ mövcud deyil.", "skillManagement.detail.cancelAddMetadata": "Metaməlumat əlavə etməyi ləğv et", "skillManagement.detail.closeFileTab": "{{name}} faylının vərəqini bağla", "skillManagement.detail.closeVersions": "Versiyaları bağla", @@ -70,14 +54,10 @@ "skillManagement.detail.copyContentSuccess": "Məzmun mübadilə buferinə kopyalandı.", "skillManagement.detail.copyFile": "Kopyala", "skillManagement.detail.copyFileSuccess": "Fayl yapışdırılmağa hazırdır.", - "skillManagement.detail.copyVersionId": "ID-ni kopyala", - "skillManagement.detail.copyVersionIdSuccess": "ID mübadilə buferinə kopyalandı.", "skillManagement.detail.createFile": "Yeni fayl", - "skillManagement.detail.createFileDescription": "Bacarığın kök qovluğuna nəzərən fayl yolunu daxil edin.", "skillManagement.detail.createFileMenu": "Yeni fayl yarat...", "skillManagement.detail.createFileSuccess": "Fayl yaradıldı.", "skillManagement.detail.createFolder": "Yeni qovluq", - "skillManagement.detail.createFolderDescription": "Bacarığın kök qovluğuna nəzərən qovluq yolunu daxil edin.", "skillManagement.detail.createFolderMenu": "Yeni qovluq yarat...", "skillManagement.detail.createFolderSuccess": "Qovluq yaradıldı.", "skillManagement.detail.createdBy": "{{name}} tərəfindən yaradılıb", @@ -100,10 +80,8 @@ "skillManagement.detail.fileMeta": "{{type}} · {{size}} bayt", "skillManagement.detail.fileMissing": "Fayl tapılmadı.", "skillManagement.detail.fileOperationFailed": "Fayl əməliyyatı uğursuz oldu.", - "skillManagement.detail.files": "Fayllar", "skillManagement.detail.latest": "Ən son", "skillManagement.detail.loadFailed": "Bacarığı yükləmək mümkün olmadı.", - "skillManagement.detail.loadingTags": "Etiketlər yüklənir...", "skillManagement.detail.markdownLiveMode": "Canlı önbaxış", "skillManagement.detail.markdownSourceMode": "Mənbə redaktoru", "skillManagement.detail.metadataKey": "Ad", @@ -113,10 +91,7 @@ "skillManagement.detail.nameThisVersion": "Bu versiyaya ad ver", "skillManagement.detail.noFileSelected": "Önbaxış üçün fayl seçin.", "skillManagement.detail.noFiles": "Fayl yoxdur.", - "skillManagement.detail.noSearchResults": "Uyğun fayl yoxdur.", - "skillManagement.detail.noSelectableTags": "Mövcud etiket yoxdur.", "skillManagement.detail.noVersions": "Hələ dərc edilmiş versiya yoxdur.", - "skillManagement.detail.pasteFile": "Yapışdır", "skillManagement.detail.pasteFileSuccess": "Fayl yapışdırıldı.", "skillManagement.detail.previewUnsupported": "Bu fayl üçün önbaxış dəstəklənmir.", "skillManagement.detail.publish": "Dərc et", @@ -128,9 +103,7 @@ "skillManagement.detail.publishUpdate": "Yeniləməni dərc et", "skillManagement.detail.published": "Dərc edilib", "skillManagement.detail.publishedAt": "Dərc edilib: {{time}}", - "skillManagement.detail.publishedVersion": "Dərc edilmiş versiya #{{number}}", "skillManagement.detail.publishing": "Dərc edilir...", - "skillManagement.detail.readonly": "Yalnız oxumaq üçün", "skillManagement.detail.referenceFiles.confirm": "↵ düyməsi ilə təsdiqlə", "skillManagement.detail.referenceFiles.empty": "Mövcud fayl yoxdur.", "skillManagement.detail.referenceFiles.livePlaceholder": "Agent üçün təlimatlar yazın. Fayllara istinad etmək üçün / yazın.", @@ -138,14 +111,10 @@ "skillManagement.detail.referenceFiles.title": "İstinad faylları", "skillManagement.detail.referencedBy_one": "{{count}} agent istifadə edir", "skillManagement.detail.referencedBy_other": "{{count}} agent istifadə edir", - "skillManagement.detail.removeMetadata": "{{name}} metaməlumatını sil", "skillManagement.detail.removeTag": "{{tag}} etiketini çıxar", "skillManagement.detail.removeTagSuccess": "Etiket çıxarıldı.", - "skillManagement.detail.renameFile": "Faylın adını dəyiş", - "skillManagement.detail.renameFileDescription": "Bacarığın kök qovluğuna nəzərən yeni yolu daxil edin.", "skillManagement.detail.renameFileSuccess": "Faylın adı dəyişdirildi.", "skillManagement.detail.renameSkillSuccess": "Bacarığın adı dəyişdirildi.", - "skillManagement.detail.renameVersion": "Adını dəyiş", "skillManagement.detail.renameVersionFailed": "Versiyanın adını dəyişmək mümkün olmadı.", "skillManagement.detail.renameVersionPrompt": "Versiya adı", "skillManagement.detail.renameVersionSuccess": "Versiyanın adı dəyişdirildi.", @@ -155,17 +124,14 @@ "skillManagement.detail.restoreVersionConfirmTitle": "Bu versiya bərpa edilsin?", "skillManagement.detail.restoreVersionFailed": "Versiyanı bərpa etmək mümkün olmadı.", "skillManagement.detail.restoreVersionSuccess": "Versiya qaralama kimi bərpa edildi.", - "skillManagement.detail.save": "Yadda saxla", "skillManagement.detail.saveConflictConfirmDescription": "Bu bacarıq başqa yerdə yenilənib. Redaktəyə davam etmək üçün ən son qaralamanı yükləyin. Cari dəyişiklikləriniz ləğv ediləcək.", "skillManagement.detail.saveConflictConfirmTitle": "Ən son qaralama yüklənsin?", "skillManagement.detail.saveConflictReload": "Ən son qaralamanı yüklə", "skillManagement.detail.saveConflictStatus": "Ziddiyyət aşkarlandı. Redaktəyə davam etmək üçün ən son qaralamanı yükləyin.", "skillManagement.detail.saveFailed": "Faylı yadda saxlamaq mümkün olmadı.", - "skillManagement.detail.saveSuccess": "Fayl yadda saxlanıldı.", "skillManagement.detail.saved": "Yadda saxlanıldı", "skillManagement.detail.savedAt": "Yadda saxlanıldı: {{time}}", "skillManagement.detail.saving": "Yadda saxlanılır...", - "skillManagement.detail.searchFiles": "Faylları axtar", "skillManagement.detail.showMoreReferences": "Daha {{count}} agent göstər", "skillManagement.detail.skillDescriptionPlaceholder": "Bu bacarığın nə etdiyini və agentlərin ondan nə vaxt istifadə etməli olduğunu təsvir edin.", "skillManagement.detail.skillNamePlaceholder": "Kiçik hərflərdən, rəqəmlərdən və defislərdən istifadə edin.", @@ -177,16 +143,12 @@ "skillManagement.detail.uploadConflict": "Bu adda fayl artıq mövcuddur.", "skillManagement.detail.uploadFailureSummary": "{{total}} fayldan {{failed}} faylı yükləmək mümkün olmadı", "skillManagement.detail.uploadFailureTitle": "Bəzi yükləmələr uğursuz oldu", - "skillManagement.detail.uploadFile": "Fayl yüklə", - "skillManagement.detail.uploadFileFailed": "Faylın yüklənməsi uğursuz oldu.", "skillManagement.detail.uploadFileSuccess": "Fayl yükləndi.", "skillManagement.detail.uploadFilesButton": "{{count}} fayl yüklə", "skillManagement.detail.uploadFilesFailedStatus_one": "{{count}} faylın yüklənməsi uğursuz oldu.", "skillManagement.detail.uploadFilesFailedStatus_other": "{{count}} faylın yüklənməsi uğursuz oldu.", "skillManagement.detail.uploadFilesMenu": "Faylları yüklə...", "skillManagement.detail.uploadFilesProgress": "Yüklənir: {{completed}}/{{total}}", - "skillManagement.detail.uploadFilesResult": "{{uploaded}} fayl yükləndi · {{failed}} faylı yükləmək mümkün olmadı", - "skillManagement.detail.uploadFilesStatus": "Yükləmə vəziyyəti", "skillManagement.detail.uploadInvalidExtension": "Bu fayl növü dəstəklənmir.", "skillManagement.detail.uploadInvalidName": "Yanlış ad.", "skillManagement.detail.uploadInvalidPath": "Yanlış fayl yolu.", @@ -206,7 +168,6 @@ "skillManagement.detail.uploadWillReplace": "Mövcud faylı əvəz edəcək", "skillManagement.detail.uploadWillUseName": "{{name}} adı ilə yüklənəcək", "skillManagement.detail.versionHistory": "Versiya tarixçəsini aç", - "skillManagement.detail.versionMeta": "{{hash}} · {{time}}", "skillManagement.detail.versionPublishNote": "Buraxılış qeydləri", "skillManagement.detail.versionPublishNotePlaceholder": "Bu dəyişikliyi təsvir edin", "skillManagement.detail.versionPublishedMeta": "{{time}} · {{name}}", @@ -234,12 +195,10 @@ "skillManagement.loadingError": "Bacarıqları yükləmək mümkün olmadı", "skillManagement.moreActions": "{{name}} üçün əlavə əməliyyatlar", "skillManagement.noDescription": "Hələ təsvir yoxdur", - "skillManagement.noTags": "Etiket yoxdur", "skillManagement.publishedAt": "Dərc edilib: {{time}}", "skillManagement.referenceCount_one": "{{count}} agent", "skillManagement.referenceCount_other": "{{count}} agent", "skillManagement.searchLabel": "Bacarıq axtarışı", "skillManagement.searchPlaceholder": "Ada və ya təsvirə görə axtar", - "skillManagement.tags": "Etiketlər", "skillManagement.title": "Bacarıqlar" } diff --git a/web/i18n/az-AZ/snippet.json b/web/i18n/locales/az-AZ/snippet.json similarity index 100% rename from web/i18n/az-AZ/snippet.json rename to web/i18n/locales/az-AZ/snippet.json diff --git a/web/i18n/az-AZ/time.json b/web/i18n/locales/az-AZ/time.json similarity index 100% rename from web/i18n/az-AZ/time.json rename to web/i18n/locales/az-AZ/time.json diff --git a/web/i18n/az-AZ/tools.json b/web/i18n/locales/az-AZ/tools.json similarity index 100% rename from web/i18n/az-AZ/tools.json rename to web/i18n/locales/az-AZ/tools.json diff --git a/web/i18n/az-AZ/workflow.json b/web/i18n/locales/az-AZ/workflow.json similarity index 99% rename from web/i18n/az-AZ/workflow.json rename to web/i18n/locales/az-AZ/workflow.json index 40c07470373..05a374e60eb 100644 --- a/web/i18n/az-AZ/workflow.json +++ b/web/i18n/locales/az-AZ/workflow.json @@ -170,6 +170,7 @@ "common.disconnect": "Əlaqəni kəs", "common.draftSaveFailed": "Qaralamanı yadda saxlamaq alınmadı", "common.duplicate": "Surətini yarat", + "common.edgeLabel": "{{source}} düyünündən {{target}} düyününə əlaqə", "common.effectVarConfirm.content": "Dəyişən digər düyünlərdə istifadə olunur. Onu yenə də çıxarmaq istəyirsiniz?", "common.effectVarConfirm.title": "Dəyişənin çıxarılması", "common.enableJinja": "Jinja şablon dəstəyini aktivləşdir", @@ -207,6 +208,8 @@ "common.needConnectTip": "Bu addım heç nəyə qoşulmayıb", "common.needStartNode": "Ən azı bir başlanğıc qovşağı əlavə edilməlidir", "common.noVar": "Dəyişən yoxdur", + "common.nodeDescription": "Təsvir", + "common.nodeTitle": "Düyünün adı", "common.notPublishedYet": "Hələ dərc edilməyib", "common.notRunning": "Hələ işləmir", "common.onFailure": "Xəta baş verdikdə", @@ -402,8 +405,6 @@ "nodes.agent.pluginNotInstalledDesc": "Bu inteqrasiya GitHub-dan quraşdırılıb. Yenidən quraşdırmaq üçün inteqrasiyalar bölməsinə keçin", "nodes.agent.roster.change": "Dəyiş", "nodes.agent.roster.editInConsole": "Agentlər bölməsində redaktə et", - "nodes.agent.roster.editInConsoleDisabled": "Agentləri redaktə etmək üçün redaktor rolu və ya daha yüksək rol tələb olunur.", - "nodes.agent.roster.editInConsoleDisabledRbac": "Yalnız redaktor rolu və ya daha yüksək rolu, yaxud agentləri idarə etmək icazəsi olan üzvlər agentləri redaktə edə bilər.", "nodes.agent.roster.inlineSetup.description": "Yalnız bu düyündə mövcud olan birdəfəlik konfiqurasiya", "nodes.agent.roster.inlineSetup.name": "Daxili konfiqurasiya", "nodes.agent.roster.inlineSetup.type": "Yalnız bu düyündə", @@ -827,6 +828,7 @@ "nodes.note.editor.enterUrl": "URL daxil edin...", "nodes.note.editor.invalidUrl": "Etibarsız URL", "nodes.note.editor.italic": "Maili", + "nodes.note.editor.label": "Qeydin məzmunu", "nodes.note.editor.large": "Böyük", "nodes.note.editor.link": "Keçid", "nodes.note.editor.medium": "Orta", @@ -1066,6 +1068,11 @@ "tabs.unconfiguredStartDisabledTip": "İş sahəsinə konfiqurasiya edilməmiş başlanğıc düyünü əlavə edilib. Davam etməzdən əvvəl quraşdırmanı tamamlayın.", "tabs.utilities": "Köməkçi vasitələr", "tabs.workflowTool": "İş axını", + "tracing.status.exception": "İstisna", + "tracing.status.failed": "Uğursuz oldu", + "tracing.status.paused": "Fasilə verildi", + "tracing.status.stopped": "Dayandırıldı", + "tracing.status.succeeded": "Uğurlu", "tracing.stopBy": "{{user}} tərəfindən dayandırılıb", "triggerStatus.enabled": "Tətikləyici", "variableReference.assignedVarsDescription": "Təyin olunan dəyişənlər söhbət dəyişənləri kimi dəyəri dəyişdirilə bilən dəyişənlər olmalıdır.", diff --git a/web/i18n/de-DE/agent-v-2.json b/web/i18n/locales/de-DE/agent-v-2.json similarity index 98% rename from web/i18n/de-DE/agent-v-2.json rename to web/i18n/locales/de-DE/agent-v-2.json index c241b8ca8ad..fe6f8a54013 100644 --- a/web/i18n/de-DE/agent-v-2.json +++ b/web/i18n/locales/de-DE/agent-v-2.json @@ -69,6 +69,7 @@ "agentDetail.configure.answer.activity.runningCommands": "Befehle werden ausgeführt", "agentDetail.configure.answer.duration.minute": "{{count}} Min.", "agentDetail.configure.answer.duration.second": "{{count}} s", + "agentDetail.configure.answer.responseEnded": "Antwort beendet", "agentDetail.configure.answer.thinking": "Denkt nach", "agentDetail.configure.build.empty.communityEditionTip": "Community Edition bietet zwar Versionierung für extrahierte Konfigurationen, unterstützt aber keine Versionierung des Dateisystems selbst. Seien Sie bei Aktionen im Build-Modus vorsichtig, da Änderungen am Dateisystem in Echtzeit erfolgen und nicht immer sauber rückgängig gemacht werden können. Community Edition ist nicht die ideale Wahl, um große externe Zielgruppen zu bedienen.", "agentDetail.configure.build.empty.description": "Beschreiben Sie, was Sie möchten, und das Formular links wird während des Chats ausgefüllt.", @@ -269,7 +270,6 @@ "agentDetail.configure.workingDirectory.breadcrumbLabel": "Dateisystempfad", "agentDetail.configure.workingDirectory.description": "Dateien, die der Agent während der Ausführung liest und schreibt. Alles, was er erzeugt, z. B. qna_report.pdf, erscheint hier.", "agentDetail.configure.workingDirectory.fileSystem": "Dateisystem", - "agentDetail.configure.workingDirectory.open": "Arbeitsverzeichnis öffnen", "agentDetail.configure.workingDirectory.persistentFiles": "Dauerhaft", "agentDetail.configure.workingDirectory.persistentFilesTooltip": "Dateien, die der Agent behält und überall verwenden kann, wo er ausgeführt wird, einschließlich Unterhaltungen mit Endbenutzern und Workflows. Änderungen, die du während dieses Build-Chats hier vornimmst, werden wirksam, sobald du auf „Anwenden“ klickst.", "agentDetail.configure.workingDirectory.temporaryFiles": "Temporär", @@ -348,7 +348,6 @@ "agentDetail.sections.logs": "Logs", "agentDetail.sections.monitoring": "Monitoring", "agentDetail.title": "Agent", - "agentDetail.type": "AGENT", "agentDetail.versionHistory.empty": "Noch keine Versionen", "agentDetail.versionHistory.exitVersions": "Versionen verlassen", "agentDetail.versionHistory.filter": "Versionen filtern", @@ -408,8 +407,5 @@ "roster.sort.lastModified": "Zuletzt geändert", "roster.sort.recentlyCreated": "Zuletzt erstellt", "roster.title": "Agenten", - "roster.usageStatus.draft": "Entwurf", - "skillManagement.detail.uploadFilesFailedStatus_one": "{{count}} Datei konnte nicht hochgeladen werden.", - "skillManagement.detail.uploadFilesFailedStatus_other": "{{count}} Dateien konnten nicht hochgeladen werden.", - "skillManagement.title": "Skills" + "roster.usageStatus.draft": "Entwurf" } diff --git a/web/i18n/de-DE/app-annotation.json b/web/i18n/locales/de-DE/app-annotation.json similarity index 100% rename from web/i18n/de-DE/app-annotation.json rename to web/i18n/locales/de-DE/app-annotation.json diff --git a/web/i18n/de-DE/app-api.json b/web/i18n/locales/de-DE/app-api.json similarity index 100% rename from web/i18n/de-DE/app-api.json rename to web/i18n/locales/de-DE/app-api.json diff --git a/web/i18n/de-DE/app-debug.json b/web/i18n/locales/de-DE/app-debug.json similarity index 100% rename from web/i18n/de-DE/app-debug.json rename to web/i18n/locales/de-DE/app-debug.json diff --git a/web/i18n/de-DE/app-log.json b/web/i18n/locales/de-DE/app-log.json similarity index 97% rename from web/i18n/de-DE/app-log.json rename to web/i18n/locales/de-DE/app-log.json index c61a909fe1d..2e4443acecd 100644 --- a/web/i18n/de-DE/app-log.json +++ b/web/i18n/locales/de-DE/app-log.json @@ -4,11 +4,9 @@ "agentLogDetail.iteration": "Iteration", "agentLogDetail.iterations": "Iterationen", "agentLogDetail.toolUsed": "Verwendetes Werkzeug", - "archives.action.downloadMonth": "{{month}}-Archiv herunterladen", "archives.action.downloadReady": "Der Archivdownload ist bereit.", "archives.action.prepareDownload": "Download vorbereiten", "archives.action.prepareFailed": "Archivdownload konnte nicht vorbereitet werden.", - "archives.action.prepareMonth": "{{month}}-Archivdownload vorbereiten", "archives.action.prepareStarted": "Der Archivdownload wird vorbereitet.", "archives.action.preparing": "Wird vorbereitet", "archives.description": "Monatliche CSV-Exporte archivierter Workflow-Ausführungsprotokolle in diesem Workspace herunterladen.", diff --git a/web/i18n/de-DE/app-overview.json b/web/i18n/locales/de-DE/app-overview.json similarity index 100% rename from web/i18n/de-DE/app-overview.json rename to web/i18n/locales/de-DE/app-overview.json diff --git a/web/i18n/de-DE/app.json b/web/i18n/locales/de-DE/app.json similarity index 100% rename from web/i18n/de-DE/app.json rename to web/i18n/locales/de-DE/app.json diff --git a/web/i18n/de-DE/billing.json b/web/i18n/locales/de-DE/billing.json similarity index 100% rename from web/i18n/de-DE/billing.json rename to web/i18n/locales/de-DE/billing.json diff --git a/web/i18n/de-DE/common.json b/web/i18n/locales/de-DE/common.json similarity index 98% rename from web/i18n/de-DE/common.json rename to web/i18n/locales/de-DE/common.json index 25907e54b99..dd7e08e858b 100644 --- a/web/i18n/de-DE/common.json +++ b/web/i18n/locales/de-DE/common.json @@ -209,9 +209,7 @@ "mainNav.marketplace": "Marketplace", "mainNav.skills": "Skills", "mainNav.webApps.noResults": "Keine Web-Apps gefunden", - "mainNav.webApps.openApp": "Web-App {{name}} öffnen", "mainNav.webApps.searchPlaceholder": "Web-Apps suchen", - "mainNav.workspace.credits": "{{count}} Credits", "mainNav.workspace.creditsUnit": "Credits", "mainNav.workspace.inviteMembers": "Invite members", "mainNav.workspace.noResults": "Keine Arbeitsbereiche gefunden", @@ -233,7 +231,6 @@ "members.datasetOperatorTip": "Kann die Wissensdatenbank nur verwalten", "members.editRole": "Rolle bearbeiten", "members.editorTip": "Kann Apps erstellen und bearbeiten", - "members.email": "E-Mail", "members.emailInvalid": "Ungültiges E-Mail-Format", "members.emailNotSetup": "E-Mail-Server ist nicht eingerichtet, daher können keine Einladungs-E-Mails versendet werden. Bitte informieren Sie die Benutzer über den Einladungslink, der nach der Einladung ausgestellt wird.", "members.emailPlaceholder": "E-Mail-Adressen eingeben oder einfügen…", @@ -250,7 +247,6 @@ "members.inviteLimitExceeded": "Es sind nicht genügend Plätze für alle Empfänger verfügbar.", "members.inviteTeamMember": "Teammitglied hinzufügen", "members.inviteTeamMemberTip": "Sie können direkt nach der Anmeldung auf Ihre Teamdaten zugreifen.", - "members.invitedAsRole": "Eingeladen als {{role}}", "members.lastActive": "ZULETZT AKTIV", "members.memberActions": "Mitgliederaktionen", "members.memberDetails.assign": "Zuweisen", @@ -258,8 +254,6 @@ "members.memberDetails.assignedRoles": "Zugewiesene Rollen", "members.memberDetails.customGroup": "BENUTZERDEFINIERT", "members.memberDetails.generalGroup": "ALLGEMEIN", - "members.memberDetails.openAria": "Mitgliederdetails für {{name}} öffnen", - "members.memberDetails.roleActionsAria": "Aktionen für die Rolle {{role}} öffnen", "members.memberDetails.roleNoPermissionSummary": "Die aktuelle Rolle hat keine Berechtigungen.", "members.memberDetails.rolePermissionSummary": "{{role}} kann {{permissions}}", "members.memberDetails.title": "Mitgliederdetails", @@ -370,7 +364,6 @@ "modelProvider.card.usagePriorityTip": "Legen Sie fest, welche Ressource beim Ausführen von Modellen zuerst verwendet wird.", "modelProvider.configureTip": "Einrichten des API-Schlüssels oder Hinzufügen des zu verwendenden Modells", "modelProvider.confirmDelete": "Löschung bestätigen?", - "modelProvider.credits": "Nachrichtenguthaben", "modelProvider.defaultConfig": "Standardkonfiguration", "modelProvider.discoverMore": "Erfahren Sie mehr in", "modelProvider.embeddingModel.key": "Einbettungsmodell", @@ -427,7 +420,6 @@ "modelProvider.selector.noProviderConfiguredDesc": "Durchsuchen Sie den Marketplace, um einen zu installieren, oder konfigurieren Sie Anbieter in den Einstellungen.", "modelProvider.selector.onlyCompatibleModelsShown": "Es werden nur kompatible Modelle angezeigt", "modelProvider.selector.showIncompatibleModels": "Inkompatible Modelle anzeigen", - "modelProvider.selector.suggestion": "Vorschlag", "modelProvider.selector.suggestionTip": "Empfohlen für Agentenaufgaben", "modelProvider.showModels": "Modelle anzeigen", "modelProvider.speechToTextModel.key": "Sprach-zu-Text-Modell", @@ -458,6 +450,8 @@ "operation.confirmAction": "Bitte bestätigen Sie Ihre Aktion.", "operation.copied": "Kopiert", "operation.copy": "Kopieren", + "operation.copyErrorDetails": "Fehlerdetails kopieren", + "operation.copyErrorFailed": "Kopieren fehlgeschlagen. Erneut versuchen", "operation.copyImage": "Bild kopieren", "operation.create": "Erstellen", "operation.deSelectAll": "Alle abwählen", @@ -475,7 +469,6 @@ "operation.format": "Format", "operation.imageCopied": "Kopiertes Bild", "operation.imageCopyFailed": "Das Bild konnte nicht kopiert werden. Bitte laden Sie es stattdessen herunter.", - "operation.imageDownloaded": "Bild heruntergeladen", "operation.in": "in", "operation.learnMore": "Mehr erfahren", "operation.log": "Protokoll", @@ -512,7 +505,6 @@ "operation.sure": "Ich bin sicher", "operation.toggleFullscreen": "Vollbild umschalten", "operation.toggleMute": "Stummschaltung umschalten", - "operation.upload": "Upload", "operation.view": "Ansehen", "operation.viewDetails": "Details anzeigen", "operation.viewMore": "MEHR SEHEN", @@ -542,7 +534,6 @@ "promptEditor.requestURL.item.title": "Anfrage-URL", "promptEditor.variable.modal.add": "Neue Variable", "promptEditor.variable.modal.addTool": "Neues Werkzeug", - "provider.anthropicHosted.trialQuotaTip": "Ihr Anthropic-Testkontingent läuft am 11.03.2025 ab und steht danach nicht mehr zur Verfügung. Bitte machen Sie rechtzeitig davon Gebrauch.", "provider.encrypted.back": " Technologie gespeichert.", "provider.encrypted.front": "Ihr API-SCHLÜSSEL wird verschlüsselt und mit", "provider.validating": "Schlüssel wird validiert...", @@ -565,7 +556,6 @@ "settings.preferences": "Preferences", "settings.provider": "Modellanbieter", "settings.resourceAccess": "Ressourcenzugriff", - "settings.resourceAccessDescription": "Konfigurieren Sie Berechtigungsregeln, die Apps und Wissensdatenbanken verwenden können. Berechtigungsregeln können Mitgliedern in bestimmten Ressourcenzugriffskonfigurationen zugewiesen werden.", "settings.rolesAndPermissions": "Rollen & Berechtigungen", "settings.settings": "Settings", "settings.swaggerAPIAsTool": "Swagger API as Tool", @@ -695,7 +685,6 @@ "userProfile.helpCenter": "Hilfe", "userProfile.logout": "Abmelden", "userProfile.roadmap": "Fahrplan", - "userProfile.settings": "Einstellungen", "userProfile.workspace": "Arbeitsbereich", "voice.language.arTN": "Tunesisches Arabisch", "voice.language.deDE": "Deutsch", diff --git a/web/i18n/de-DE/custom.json b/web/i18n/locales/de-DE/custom.json similarity index 100% rename from web/i18n/de-DE/custom.json rename to web/i18n/locales/de-DE/custom.json diff --git a/web/i18n/de-DE/dataset-creation.json b/web/i18n/locales/de-DE/dataset-creation.json similarity index 100% rename from web/i18n/de-DE/dataset-creation.json rename to web/i18n/locales/de-DE/dataset-creation.json diff --git a/web/i18n/de-DE/dataset-documents.json b/web/i18n/locales/de-DE/dataset-documents.json similarity index 100% rename from web/i18n/de-DE/dataset-documents.json rename to web/i18n/locales/de-DE/dataset-documents.json diff --git a/web/i18n/de-DE/dataset-hit-testing.json b/web/i18n/locales/de-DE/dataset-hit-testing.json similarity index 100% rename from web/i18n/de-DE/dataset-hit-testing.json rename to web/i18n/locales/de-DE/dataset-hit-testing.json diff --git a/web/i18n/de-DE/dataset-pipeline.json b/web/i18n/locales/de-DE/dataset-pipeline.json similarity index 100% rename from web/i18n/de-DE/dataset-pipeline.json rename to web/i18n/locales/de-DE/dataset-pipeline.json diff --git a/web/i18n/de-DE/dataset-settings.json b/web/i18n/locales/de-DE/dataset-settings.json similarity index 100% rename from web/i18n/de-DE/dataset-settings.json rename to web/i18n/locales/de-DE/dataset-settings.json diff --git a/web/i18n/de-DE/dataset.json b/web/i18n/locales/de-DE/dataset.json similarity index 97% rename from web/i18n/de-DE/dataset.json rename to web/i18n/locales/de-DE/dataset.json index e849f076b3a..d6e6032d0b7 100644 --- a/web/i18n/de-DE/dataset.json +++ b/web/i18n/locales/de-DE/dataset.json @@ -163,7 +163,6 @@ "newKnowledge.configureProvider": "{{provider}} konfigurieren", "newKnowledge.connectNotion": "Notion verbinden", "newKnowledge.connectProvider": "Verbinden Sie {{provider}}", - "newKnowledge.connectProviderGeneric": "Anbieter verbinden", "newKnowledge.connectSource": "Quelle verbinden", "newKnowledge.connectSourceDescription": "Inhalte bleiben mit Notion, Websites und anderen Diensten synchron.", "newKnowledge.connectingProvider": "Verbinden…", @@ -179,18 +178,14 @@ "newKnowledge.crawlFailedDescription": "Der Crawler konnte diese Vorschau nicht abschließen. Prüfe die URL, passe die Crawl-Optionen an oder versuche es erneut.", "newKnowledge.crawlFailedProvider": "{{provider}} hat einen Fehler gemeldet. Prüfe die Anbieterverbindung und versuche es erneut.", "newKnowledge.crawlFailedTimeout": "Zeitüberschreitung beim Crawlen. Versuche eine spezifischere URL oder reduziere die maximale Seitenzahl.", - "newKnowledge.crawlNotStarted": "Nicht gestartet", "newKnowledge.crawlOptions": "Crawl-Optionen", "newKnowledge.crawlPreview": "Crawl-Vorschau", "newKnowledge.crawlProgress": "Crawl-Fortschritt für {{host}}", - "newKnowledge.crawlSetupUnavailableDescription": "Die Website-Crawling-Konfiguration ist deaktiviert, bis der Crawl-Workflow verfügbar ist.", - "newKnowledge.crawlSetupUnavailableTitle": "Firecrawl ist bereit", "newKnowledge.crawlStartFailed": "Die Crawl-Vorschau konnte nicht gestartet werden. Versuche es erneut.", "newKnowledge.crawlStopped": "Crawl gestoppt", "newKnowledge.crawling": "Wird gecrawlt…", "newKnowledge.crawlingPages": "{{host}} wird gecrawlt… {{count}} Seiten gefunden", "newKnowledge.createFailed": "Wir konnten diese Wissensdatenbank nicht erstellen. Überprüfen Sie Ihren Zugang und versuchen Sie es erneut.", - "newKnowledge.createIllustrationTitle": "Verbinde deine Inhalte einmal – jede App antwortet mit dem aktuellsten Wissen.", "newKnowledge.createTitle": "Wissen schaffen", "newKnowledge.createdAt": "Erstellt", "newKnowledge.creators": "Ersteller", @@ -220,7 +215,6 @@ "newKnowledge.documentColumn": "Dokument", "newKnowledge.documentContentIncomplete": "Verbleibender Dokumentinhalt wird geladen…", "newKnowledge.documentContents": "Inhalt", - "newKnowledge.documentFacts": "Dokumentdetails", "newKnowledge.documentFilterLabel": "Dokumente nach Status filtern", "newKnowledge.documentLoadErrorDescription": "Dieses Dokument konnte nicht geladen werden. Versuchen Sie es erneut.", "newKnowledge.documentLoadErrorTitle": "Dokument konnte nicht geladen werden", @@ -268,7 +262,6 @@ "newKnowledge.documentsSelected": "{{count}} Dokumente ausgewählt", "newKnowledge.documentsUnavailable": "— Dokumente", "newKnowledge.downloadDocuments": "Herunterladen", - "newKnowledge.editSource": "Bearbeiten", "newKnowledge.emptyChunk": "Leerer Abschnitt", "newKnowledge.emptyDescription": "Fügen Sie Ihre Dokumente hinzu, damit Agents sie direkt lesen können — ohne eine Indizierung einzurichten.", "newKnowledge.emptyTitle": "Wissensdatenbanken intelligenter aufbauen", @@ -344,7 +337,6 @@ "newKnowledge.processingTaskState.succeeded": "Abgeschlossen", "newKnowledge.processingTaskState.superseded": "Abgelöst", "newKnowledge.providerConnected": "{{provider}} verbunden", - "newKnowledge.providerLabel": "Wählen Sie einen Anbieter", "newKnowledge.providerLoadFailed": "Anbieter konnten nicht geladen werden", "newKnowledge.providerNotConfigured": "{{provider}} ist nicht konfiguriert", "newKnowledge.providerNotConfiguredDescription": "Fügen Sie einen API-Schlüssel oder eine Endpunkt-URL hinzu, um Websites mit {{provider}} zu crawlen.", @@ -369,12 +361,10 @@ "newKnowledge.revisionState.superseded": "Vorherige", "newKnowledge.rootUrl": "Stamm-URL", "newKnowledge.rootUrlPlaceholder": "https://docs.example.com", - "newKnowledge.routeUnavailable": "Dieser Abschnitt wird im nächsten Einrichtungsschritt verfügbar.", "newKnowledge.searchDocuments": "Dokumente durchsuchen", "newKnowledge.searchSources": "Quellen durchsuchen", "newKnowledge.selectAll": "Alle auswählen", "newKnowledge.selectAllDocuments": "Wählen Sie alle Dokumente aus", - "newKnowledge.selectAllSources": "Alle Quellen auswählen", "newKnowledge.selectChunk": "Wählen Sie einen Abschnitt aus, um seinen Inhalt zu prüfen.", "newKnowledge.selectFilesAndFolders": "Dateien und Ordner zum Synchronisieren auswählen", "newKnowledge.skippedFailed": "Übersprungen (fehlgeschlagen)", @@ -384,7 +374,6 @@ "newKnowledge.sourceFilterLabel": "Quellen nach Status filtern", "newKnowledge.sourceName": "Quellenname", "newKnowledge.sourceNamePlaceholder": "z. B. Crawl der Produktdokumentation", - "newKnowledge.sourceNameRequired": "Gib einen Quellennamen ein.", "newKnowledge.sourceSetupBackendDependency": "Die Quelleneinrichtung wird vollständig angezeigt; Bearbeitung und Vorschau erfordern den Backend-Ablauf für Entwurfsquellen.", "newKnowledge.sourceStatus.active": "Aktiv", "newKnowledge.sourceStatus.disabled": "Deaktiviert", @@ -431,7 +420,6 @@ "newKnowledge.unavailableDescription": "KnowledgeFS ist für diesen Arbeitsbereich derzeit nicht verfügbar.", "newKnowledge.unavailableTitle": "Neues Wissen ist nicht verfügbar", "newKnowledge.updated": "Aktualisiert am {{date}}", - "newKnowledge.updatedAt": "Zuletzt aktualisiert", "newKnowledge.updatedColumn": "Aktualisiert", "newKnowledge.uploadCharactersUnavailable": "Zeichen: —", "newKnowledge.uploadDocuments": "Dokumente hochladen", @@ -460,11 +448,9 @@ "retrievalSettings": "Abrufeinstellungen", "retry": "Wiederholen", "selectExternalKnowledgeAPI.placeholder": "Auswählen einer externen Wissens-API", - "serviceApi.card.addApiKey": "API-Schlüssel hinzufügen", "serviceApi.card.apiKey": "API-Schlüssel", "serviceApi.card.apiReference": "API Referenz", "serviceApi.card.endpoint": "Service-API-Endpunkt", - "serviceApi.card.manageApiKey": "API-Schlüssel verwalten", "serviceApi.card.title": "Backend-Dienst-API", "serviceApi.disabled": "Behindert", "serviceApi.enabled": "Im Dienst", diff --git a/web/i18n/de-DE/deployments.json b/web/i18n/locales/de-DE/deployments.json similarity index 97% rename from web/i18n/de-DE/deployments.json rename to web/i18n/locales/de-DE/deployments.json index 5e8e411e2ab..b6f4383f918 100644 --- a/web/i18n/de-DE/deployments.json +++ b/web/i18n/locales/de-DE/deployments.json @@ -27,7 +27,6 @@ "list.emptyTitle": "Noch keine Bereitstellungen", "overview.chip.latest": "neueste", "status.RUNTIME_INSTANCE_STATUS_DEPLOYING": "Wird bereitgestellt", - "status.RUNTIME_INSTANCE_STATUS_FAILED": "Bereitstellung fehlgeschlagen", "status.RUNTIME_INSTANCE_STATUS_INVALID": "Ungültig", "status.RUNTIME_INSTANCE_STATUS_READY": "Läuft", "status.RUNTIME_INSTANCE_STATUS_UNDEPLOYED": "Nicht bereitgestellt", @@ -87,6 +86,5 @@ "studio.versionValue": "Versionswert", "studio.versionsBehind_one": "{{count}} Version zurück", "studio.versionsBehind_other": "{{count}} Versionen zurück", - "versions.checkingReleaseContent": "Release-Inhalt wird überprüft...", "versions.deployTo": "In {{name}} bereitstellen" } diff --git a/web/i18n/de-DE/device-flow.json b/web/i18n/locales/de-DE/device-flow.json similarity index 100% rename from web/i18n/de-DE/device-flow.json rename to web/i18n/locales/de-DE/device-flow.json diff --git a/web/i18n/de-DE/education.json b/web/i18n/locales/de-DE/education.json similarity index 100% rename from web/i18n/de-DE/education.json rename to web/i18n/locales/de-DE/education.json diff --git a/web/i18n/de-DE/explore.json b/web/i18n/locales/de-DE/explore.json similarity index 100% rename from web/i18n/de-DE/explore.json rename to web/i18n/locales/de-DE/explore.json diff --git a/web/i18n/de-DE/layout.json b/web/i18n/locales/de-DE/layout.json similarity index 100% rename from web/i18n/de-DE/layout.json rename to web/i18n/locales/de-DE/layout.json diff --git a/web/i18n/de-DE/login.json b/web/i18n/locales/de-DE/login.json similarity index 100% rename from web/i18n/de-DE/login.json rename to web/i18n/locales/de-DE/login.json diff --git a/web/i18n/de-DE/oauth.json b/web/i18n/locales/de-DE/oauth.json similarity index 100% rename from web/i18n/de-DE/oauth.json rename to web/i18n/locales/de-DE/oauth.json diff --git a/web/i18n/de-DE/permission-keys.json b/web/i18n/locales/de-DE/permission-keys.json similarity index 100% rename from web/i18n/de-DE/permission-keys.json rename to web/i18n/locales/de-DE/permission-keys.json diff --git a/web/i18n/de-DE/permission.json b/web/i18n/locales/de-DE/permission.json similarity index 100% rename from web/i18n/de-DE/permission.json rename to web/i18n/locales/de-DE/permission.json diff --git a/web/i18n/de-DE/pipeline.json b/web/i18n/locales/de-DE/pipeline.json similarity index 100% rename from web/i18n/de-DE/pipeline.json rename to web/i18n/locales/de-DE/pipeline.json diff --git a/web/i18n/de-DE/plugin-tags.json b/web/i18n/locales/de-DE/plugin-tags.json similarity index 100% rename from web/i18n/de-DE/plugin-tags.json rename to web/i18n/locales/de-DE/plugin-tags.json diff --git a/web/i18n/de-DE/plugin-trigger.json b/web/i18n/locales/de-DE/plugin-trigger.json similarity index 100% rename from web/i18n/de-DE/plugin-trigger.json rename to web/i18n/locales/de-DE/plugin-trigger.json diff --git a/web/i18n/de-DE/plugin.json b/web/i18n/locales/de-DE/plugin.json similarity index 99% rename from web/i18n/de-DE/plugin.json rename to web/i18n/locales/de-DE/plugin.json index ebf6b28d0b6..90534538098 100644 --- a/web/i18n/de-DE/plugin.json +++ b/web/i18n/locales/de-DE/plugin.json @@ -277,7 +277,6 @@ "readmeInfo.noReadmeAvailable": "Keine README verfügbar", "readmeInfo.title": "LIESMICH", "requestAPlugin": "Ein Plugin anfordern", - "requestSubmit": "Anfordern oder veröffentlichen", "search": "Suchen", "searchCategories": "Kategorien durchsuchen", "searchInMarketplace": "Suche im Marketplace", diff --git a/web/i18n/de-DE/register.json b/web/i18n/locales/de-DE/register.json similarity index 100% rename from web/i18n/de-DE/register.json rename to web/i18n/locales/de-DE/register.json diff --git a/web/i18n/de-DE/run-log.json b/web/i18n/locales/de-DE/run-log.json similarity index 100% rename from web/i18n/de-DE/run-log.json rename to web/i18n/locales/de-DE/run-log.json diff --git a/web/i18n/de-DE/share.json b/web/i18n/locales/de-DE/share.json similarity index 100% rename from web/i18n/de-DE/share.json rename to web/i18n/locales/de-DE/share.json diff --git a/web/i18n/de-DE/skill.json b/web/i18n/locales/de-DE/skill.json similarity index 83% rename from web/i18n/de-DE/skill.json rename to web/i18n/locales/de-DE/skill.json index 835b7df069d..afacee20835 100644 --- a/web/i18n/de-DE/skill.json +++ b/web/i18n/locales/de-DE/skill.json @@ -1,5 +1,4 @@ { - "skillManagement.clearTags": "Tags löschen", "skillManagement.create": "Erstellen", "skillManagement.createFailed": "Fertigkeit konnte nicht erstellt werden.", "skillManagement.createSuccess": "Entwurf erstellt.", @@ -13,12 +12,9 @@ "skillManagement.deleteFailed": "Fertigkeit konnte nicht gelöscht werden.", "skillManagement.deleteSuccess": "Skill gelöscht.", "skillManagement.detail.addMetadata": "Metadaten hinzufügen", - "skillManagement.detail.addMetadataDescription": "Fügen Sie dieser Markdown-Datei ein Frontmatter-Feld hinzu.", "skillManagement.detail.addTag": "Tag hinzufügen", - "skillManagement.detail.addTagDescription": "Erstellen oder binden Sie ein Tag an dieses Skill. Neue Tags werden mit den Skill-Metadaten gespeichert.", "skillManagement.detail.addTagSuccess": "Tag hinzugefügt.", "skillManagement.detail.back": "Zurück zu Skills", - "skillManagement.detail.builder.applyFailed": "Entwurfsaktualisierungen konnten nicht angewendet werden.", "skillManagement.detail.builder.attach": "Datei anhängen", "skillManagement.detail.builder.attachFailed": "Datei konnte nicht angehängt werden.", "skillManagement.detail.builder.attachLimit": "Sie können bis zu 10 Dateien anhängen.", @@ -26,22 +22,12 @@ "skillManagement.detail.builder.attachUnsupported": "Es können nur Bilder, Texte und Dokumentdateien angehängt werden.", "skillManagement.detail.builder.attachmentOnlyMessage": "Verwenden Sie die angehängte Datei, um diese Fähigkeit zu verbessern.", "skillManagement.detail.builder.close": "Schließen Sie Skill Builder", - "skillManagement.detail.builder.compatibleModelsOnly": "Es werden nur kompatible Modelle angezeigt", "skillManagement.detail.builder.copyResponse": "Antwort kopieren", "skillManagement.detail.builder.copySuccess": "Antwort in die Zwischenablage kopiert.", "skillManagement.detail.builder.editIntro": "Ich habe diesen Skill überprüft. Was möchten Sie anpassen?", "skillManagement.detail.builder.exampleIssueTriage": "Triage von Kundenproblemen", "skillManagement.detail.builder.exampleOnboarding": "Onboarding-Leitfaden für neue Mitarbeiter", "skillManagement.detail.builder.exampleSalesFollowUp": "Vertriebs-Lead-Follow-up-Strategie", - "skillManagement.detail.builder.followUpDisplayName": "Verwenden Sie „Rückerstattungsgenehmigung“ als Anzeigenamen", - "skillManagement.detail.builder.followUpNameIcon": "Übernehmen Sie den vorgeschlagenen Namen und das vorgeschlagene Symbol", - "skillManagement.detail.builder.fromMarketplace": "Vom Marketplace", - "skillManagement.detail.builder.model": "GPT-4o", - "skillManagement.detail.builder.modelCredits.all": "Alle Credits", - "skillManagement.detail.builder.modelCredits.configure": "Konfiguration erforderlich", - "skillManagement.detail.builder.modelCredits.exhausted": "Kontingent erschöpft", - "skillManagement.detail.builder.modelProviderSettings": "Modellanbieter-Einstellungen", - "skillManagement.detail.builder.modelSearch": "Modelle suchen...", "skillManagement.detail.builder.modifyPlaceholder": "Bitten Sie die KI, diese Fähigkeit zu ändern ...", "skillManagement.detail.builder.open": "Öffnen Sie Skill Builder", "skillManagement.detail.builder.placeholder": "Beschreiben Sie das Szenario...", @@ -61,8 +47,6 @@ "skillManagement.detail.builder.thinkingUnavailable": "Das Modell gab keinen Denkinhalt zurück.", "skillManagement.detail.builder.title": "Skill Builder", "skillManagement.detail.builder.tryExample": "Versuchen Sie es mit einem Beispiel", - "skillManagement.detail.builder.voice": "Spracheingabe", - "skillManagement.detail.builder.voiceUnavailable": "Die Spracheingabe ist noch nicht verfügbar.", "skillManagement.detail.cancelAddMetadata": "Das Hinzufügen von Metadaten abbrechen", "skillManagement.detail.closeFileTab": "Schließen {{name}}", "skillManagement.detail.closeVersions": "Schließen Sie Versionen", @@ -70,14 +54,10 @@ "skillManagement.detail.copyContentSuccess": "Inhalt in die Zwischenablage kopiert.", "skillManagement.detail.copyFile": "Kopieren", "skillManagement.detail.copyFileSuccess": "Datei zum Einfügen bereit.", - "skillManagement.detail.copyVersionId": "ID kopieren", - "skillManagement.detail.copyVersionIdSuccess": "ID in die Zwischenablage kopiert.", "skillManagement.detail.createFile": "Neue Datei", - "skillManagement.detail.createFileDescription": "Geben Sie einen Dateipfad relativ zum Skill-Stamm ein.", "skillManagement.detail.createFileMenu": "Neue Datei...", "skillManagement.detail.createFileSuccess": "Datei erstellt.", "skillManagement.detail.createFolder": "Neuer Ordner", - "skillManagement.detail.createFolderDescription": "Geben Sie einen Ordnerpfad relativ zum Skill-Stamm ein.", "skillManagement.detail.createFolderMenu": "Neuer Ordner...", "skillManagement.detail.createFolderSuccess": "Ordner erstellt.", "skillManagement.detail.createdBy": "Erstellt von {{name}}", @@ -100,10 +80,8 @@ "skillManagement.detail.fileMeta": "{{type}} · {{size}} Bytes", "skillManagement.detail.fileMissing": "Datei nicht gefunden.", "skillManagement.detail.fileOperationFailed": "Der Dateivorgang ist fehlgeschlagen.", - "skillManagement.detail.files": "Dateien", "skillManagement.detail.latest": "Neueste", "skillManagement.detail.loadFailed": "Der Skill konnte nicht geladen werden.", - "skillManagement.detail.loadingTags": "Tags werden geladen...", "skillManagement.detail.markdownLiveMode": "Live-Vorschau", "skillManagement.detail.markdownSourceMode": "Quelleneditor", "skillManagement.detail.metadataKey": "Name", @@ -113,10 +91,7 @@ "skillManagement.detail.nameThisVersion": "Nennen Sie diese Version", "skillManagement.detail.noFileSelected": "Wählen Sie eine Datei zur Vorschau aus.", "skillManagement.detail.noFiles": "Keine Dateien.", - "skillManagement.detail.noSearchResults": "Keine passenden Dateien.", - "skillManagement.detail.noSelectableTags": "Keine verfügbaren Tags.", "skillManagement.detail.noVersions": "Noch keine veröffentlichten Versionen.", - "skillManagement.detail.pasteFile": "Paste", "skillManagement.detail.pasteFileSuccess": "Datei eingefügt.", "skillManagement.detail.previewUnsupported": "Für diese Datei wird keine Vorschau unterstützt.", "skillManagement.detail.publish": "Veröffentlichen", @@ -128,9 +103,7 @@ "skillManagement.detail.publishUpdate": "Update veröffentlichen", "skillManagement.detail.published": "Veröffentlicht", "skillManagement.detail.publishedAt": "Veröffentlicht {{time}}", - "skillManagement.detail.publishedVersion": "Veröffentlicht #{{number}}", "skillManagement.detail.publishing": "Wird veröffentlicht...", - "skillManagement.detail.readonly": "Nur lesen", "skillManagement.detail.referenceFiles.confirm": "Geben Sie Bestätigen ein", "skillManagement.detail.referenceFiles.empty": "Keine Dateien verfügbar.", "skillManagement.detail.referenceFiles.livePlaceholder": "Schreiben Sie Anweisungen für den Agenten. Geben Sie / ein, um auf Dateien zu verweisen.", @@ -138,14 +111,10 @@ "skillManagement.detail.referenceFiles.title": "Referenzdateien", "skillManagement.detail.referencedBy_one": "Von {{count}} Agent verwendet", "skillManagement.detail.referencedBy_other": "Von {{count}} Agenten verwendet", - "skillManagement.detail.removeMetadata": "Löschen Sie {{name}}-Metadaten", "skillManagement.detail.removeTag": "{{tag}} entfernen", "skillManagement.detail.removeTagSuccess": "Tag entfernt.", - "skillManagement.detail.renameFile": "Datei umbenennen", - "skillManagement.detail.renameFileDescription": "Geben Sie den neuen Pfad relativ zum Skill-Stamm ein.", "skillManagement.detail.renameFileSuccess": "Datei umbenannt.", "skillManagement.detail.renameSkillSuccess": "Skill umbenannt.", - "skillManagement.detail.renameVersion": "Umbenennen", "skillManagement.detail.renameVersionFailed": "Die Version konnte nicht umbenannt werden.", "skillManagement.detail.renameVersionPrompt": "Versionsname", "skillManagement.detail.renameVersionSuccess": "Version umbenannt.", @@ -155,17 +124,14 @@ "skillManagement.detail.restoreVersionConfirmTitle": "Diese Version wiederherstellen?", "skillManagement.detail.restoreVersionFailed": "Die Version konnte nicht wiederhergestellt werden.", "skillManagement.detail.restoreVersionSuccess": "Version als Entwurf wiederhergestellt.", - "skillManagement.detail.save": "Speichern", "skillManagement.detail.saveConflictConfirmDescription": "Diese Fertigkeit wurde an anderer Stelle aktualisiert. Laden Sie den neuesten Entwurf, um mit der Bearbeitung fortzufahren. Ihre aktuellen Änderungen werden verworfen.", "skillManagement.detail.saveConflictConfirmTitle": "Den neuesten Entwurf laden?", "skillManagement.detail.saveConflictReload": "Laden Sie den neuesten Entwurf", "skillManagement.detail.saveConflictStatus": "Konflikt erkannt. Laden Sie den neuesten Entwurf, um mit der Bearbeitung fortzufahren.", "skillManagement.detail.saveFailed": "Datei konnte nicht gespeichert werden.", - "skillManagement.detail.saveSuccess": "Datei gespeichert.", "skillManagement.detail.saved": "Gespeichert", "skillManagement.detail.savedAt": "Gespeichert {{time}}", "skillManagement.detail.saving": "Speichern...", - "skillManagement.detail.searchFiles": "Dateien suchen", "skillManagement.detail.showMoreReferences": "Zeige {{count}} mehr", "skillManagement.detail.skillDescriptionPlaceholder": "Beschreiben Sie, was diese Fähigkeit bewirkt und wann Agenten sie verwenden sollten.", "skillManagement.detail.skillNamePlaceholder": "Verwenden Sie Kleinbuchstaben, Zahlen und Bindestriche.", @@ -177,16 +143,12 @@ "skillManagement.detail.uploadConflict": "Eine Datei mit diesem Namen existiert bereits.", "skillManagement.detail.uploadFailureSummary": "{{failed}} von {{total}}-Dateien ist fehlgeschlagen", "skillManagement.detail.uploadFailureTitle": "Einige Uploads sind fehlgeschlagen", - "skillManagement.detail.uploadFile": "Datei hochladen", - "skillManagement.detail.uploadFileFailed": "Das Hochladen der Datei ist fehlgeschlagen.", "skillManagement.detail.uploadFileSuccess": "Datei hochgeladen.", "skillManagement.detail.uploadFilesButton": "Laden Sie {{count}}-Dateien hoch", "skillManagement.detail.uploadFilesFailedStatus_one": "Das Hochladen der {{count}}-Datei ist fehlgeschlagen.", "skillManagement.detail.uploadFilesFailedStatus_other": "Das Hochladen der {{count}}-Datei ist fehlgeschlagen.", "skillManagement.detail.uploadFilesMenu": "Dateien hochladen...", "skillManagement.detail.uploadFilesProgress": "Hochladen von {{completed}}/{{total}}", - "skillManagement.detail.uploadFilesResult": "{{uploaded}} hochgeladen · {{failed}} fehlgeschlagen", - "skillManagement.detail.uploadFilesStatus": "Upload-Status", "skillManagement.detail.uploadInvalidExtension": "Dieser Dateityp wird nicht unterstützt.", "skillManagement.detail.uploadInvalidName": "Ungültiger Name.", "skillManagement.detail.uploadInvalidPath": "Ungültiger Dateipfad.", @@ -206,7 +168,6 @@ "skillManagement.detail.uploadWillReplace": "Ersetzt die vorhandene Datei", "skillManagement.detail.uploadWillUseName": "Wird als {{name}} hochgeladen", "skillManagement.detail.versionHistory": "Versionsverlauf öffnen", - "skillManagement.detail.versionMeta": "{{hash}} · {{time}}", "skillManagement.detail.versionPublishNote": "Versionshinweise", "skillManagement.detail.versionPublishNotePlaceholder": "Beschreiben Sie diese Änderung", "skillManagement.detail.versionPublishedMeta": "{{time}} · {{name}}", @@ -227,8 +188,6 @@ "skillManagement.errors.missingSkillMd": "Dieses Paket enthält kein SKILL.md. Fügen Sie die Datei hinzu und importieren Sie sie erneut.", "skillManagement.errors.nameConflict": "Ein Skill mit dem Namen „{{name}}“ ist bereits vorhanden. Benennen Sie es in SKILL.md um und importieren Sie es erneut.", "skillManagement.errors.workspaceLimit": "Ihr Arbeitsbereich hat die Grenze von 500 Fertigkeiten erreicht. Löschen Sie eine Fertigkeit, um eine neue hinzuzufügen.", - "skillManagement.exportDialog.description": "Dadurch wird der aktuell gespeicherte Entwurf Ihres Skills exportiert. Speichern Sie nicht gespeicherte Änderungen im Editor vor dem Export.", - "skillManagement.exportDialog.title": "Aktuell gespeicherten Entwurf exportieren", "skillManagement.import": "Importieren", "skillManagement.importFailed": "Der Skill konnte nicht importiert werden.", "skillManagement.importSuccess": "Skill importiert.", @@ -236,12 +195,10 @@ "skillManagement.loadingError": "Fertigkeiten konnten nicht geladen werden", "skillManagement.moreActions": "Weitere Aktionen für {{name}}", "skillManagement.noDescription": "Noch keine Beschreibung", - "skillManagement.noTags": "Keine Tags", "skillManagement.publishedAt": "Veröffentlicht {{time}}", "skillManagement.referenceCount_one": "{{count}} Agent", "skillManagement.referenceCount_other": "{{count}} Agenten", "skillManagement.searchLabel": "Skills suchen", "skillManagement.searchPlaceholder": "Suchen Sie nach Name oder Beschreibung", - "skillManagement.tags": "Schilder", "skillManagement.title": "Skills" } diff --git a/web/i18n/de-DE/snippet.json b/web/i18n/locales/de-DE/snippet.json similarity index 100% rename from web/i18n/de-DE/snippet.json rename to web/i18n/locales/de-DE/snippet.json diff --git a/web/i18n/de-DE/time.json b/web/i18n/locales/de-DE/time.json similarity index 100% rename from web/i18n/de-DE/time.json rename to web/i18n/locales/de-DE/time.json diff --git a/web/i18n/de-DE/tools.json b/web/i18n/locales/de-DE/tools.json similarity index 100% rename from web/i18n/de-DE/tools.json rename to web/i18n/locales/de-DE/tools.json diff --git a/web/i18n/de-DE/workflow.json b/web/i18n/locales/de-DE/workflow.json similarity index 99% rename from web/i18n/de-DE/workflow.json rename to web/i18n/locales/de-DE/workflow.json index 550760a3793..612d5097fab 100644 --- a/web/i18n/de-DE/workflow.json +++ b/web/i18n/locales/de-DE/workflow.json @@ -460,8 +460,6 @@ "nodes.agent.pluginNotInstalledDesc": "Dieses Integration wird von GitHub installiert. Bitte gehen Sie zu Integrationen, um sie neu zu installieren", "nodes.agent.roster.change": "Wechseln", "nodes.agent.roster.editInConsole": "In Agents bearbeiten", - "nodes.agent.roster.editInConsoleDisabled": "Zum Bearbeiten von Agents ist mindestens die Rolle „Editor“ erforderlich.", - "nodes.agent.roster.editInConsoleDisabledRbac": "Nur Mitglieder mit mindestens der Rolle „Editor“ oder der Berechtigung „Agents verwalten“ können Agents bearbeiten.", "nodes.agent.roster.inlineSetup.description": "Eine einmalige Einrichtung, die nur in diesem Knoten existiert", "nodes.agent.roster.inlineSetup.name": "Inline-Einrichtung", "nodes.agent.roster.inlineSetup.type": "Nur in diesem Knoten", diff --git a/web/i18n/en-US/agent-v-2.json b/web/i18n/locales/en-US/agent-v-2.json similarity index 98% rename from web/i18n/en-US/agent-v-2.json rename to web/i18n/locales/en-US/agent-v-2.json index 2c7542ed717..594c2993733 100644 --- a/web/i18n/en-US/agent-v-2.json +++ b/web/i18n/locales/en-US/agent-v-2.json @@ -69,6 +69,7 @@ "agentDetail.configure.answer.activity.runningCommands": "Running commands", "agentDetail.configure.answer.duration.minute": "{{count}}m", "agentDetail.configure.answer.duration.second": "{{count}}s", + "agentDetail.configure.answer.responseEnded": "Response ended", "agentDetail.configure.answer.thinking": "Thinking", "agentDetail.configure.build.empty.communityEditionTip": "Community Edition, while offering versioning to the extracted configs, does not support versioning on the file system itself. Be careful with your actions in Build mode, as changes made to the file system happen in real time and cannot always be neatly reverted. Community Edition is not your ideal choice for serving mass external audiences.", "agentDetail.configure.build.empty.description": "Describe what you want and it fills in the form on the left as you go.", @@ -269,7 +270,6 @@ "agentDetail.configure.workingDirectory.breadcrumbLabel": "File system path", "agentDetail.configure.workingDirectory.description": "Files the agent reads and writes as it runs. Anything it generates, like qna_report.pdf, shows up here.", "agentDetail.configure.workingDirectory.fileSystem": "File system", - "agentDetail.configure.workingDirectory.open": "Open working directory", "agentDetail.configure.workingDirectory.persistentFiles": "Persistent", "agentDetail.configure.workingDirectory.persistentFilesTooltip": "Files the agent keeps and can use everywhere it runs, including end users' conversations and workflows. Any changes made here during this build chat take effect once you Apply.", "agentDetail.configure.workingDirectory.temporaryFiles": "Temporary", @@ -348,7 +348,6 @@ "agentDetail.sections.logs": "Logs", "agentDetail.sections.monitoring": "Monitoring", "agentDetail.title": "Agent", - "agentDetail.type": "AGENT", "agentDetail.versionHistory.empty": "No versions yet", "agentDetail.versionHistory.exitVersions": "Exit versions", "agentDetail.versionHistory.filter": "Filter versions", @@ -408,8 +407,5 @@ "roster.sort.lastModified": "Last modified", "roster.sort.recentlyCreated": "Recently created", "roster.title": "Agents", - "roster.usageStatus.draft": "Draft", - "skillManagement.detail.uploadFilesFailedStatus_one": "{{count}} file upload failed.", - "skillManagement.detail.uploadFilesFailedStatus_other": "{{count}} file uploads failed.", - "skillManagement.title": "Skills" + "roster.usageStatus.draft": "Draft" } diff --git a/web/i18n/en-US/app-annotation.json b/web/i18n/locales/en-US/app-annotation.json similarity index 100% rename from web/i18n/en-US/app-annotation.json rename to web/i18n/locales/en-US/app-annotation.json diff --git a/web/i18n/en-US/app-api.json b/web/i18n/locales/en-US/app-api.json similarity index 100% rename from web/i18n/en-US/app-api.json rename to web/i18n/locales/en-US/app-api.json diff --git a/web/i18n/en-US/app-debug.json b/web/i18n/locales/en-US/app-debug.json similarity index 100% rename from web/i18n/en-US/app-debug.json rename to web/i18n/locales/en-US/app-debug.json diff --git a/web/i18n/en-US/app-log.json b/web/i18n/locales/en-US/app-log.json similarity index 97% rename from web/i18n/en-US/app-log.json rename to web/i18n/locales/en-US/app-log.json index 68faeef63d8..3d08ca66e4b 100644 --- a/web/i18n/en-US/app-log.json +++ b/web/i18n/locales/en-US/app-log.json @@ -4,11 +4,9 @@ "agentLogDetail.iteration": "Iteration", "agentLogDetail.iterations": "Iterations", "agentLogDetail.toolUsed": "Tool Used", - "archives.action.downloadMonth": "Download {{month}} archive", "archives.action.downloadReady": "Archive download is ready.", "archives.action.prepareDownload": "Prepare download", "archives.action.prepareFailed": "Failed to prepare archive download.", - "archives.action.prepareMonth": "Prepare {{month}} archive download", "archives.action.prepareStarted": "Archive download is being prepared.", "archives.action.preparing": "Preparing", "archives.description": "Download monthly CSV exports of archived workflow run logs across this workspace.", diff --git a/web/i18n/en-US/app-overview.json b/web/i18n/locales/en-US/app-overview.json similarity index 100% rename from web/i18n/en-US/app-overview.json rename to web/i18n/locales/en-US/app-overview.json diff --git a/web/i18n/en-US/app.json b/web/i18n/locales/en-US/app.json similarity index 100% rename from web/i18n/en-US/app.json rename to web/i18n/locales/en-US/app.json diff --git a/web/i18n/en-US/billing.json b/web/i18n/locales/en-US/billing.json similarity index 100% rename from web/i18n/en-US/billing.json rename to web/i18n/locales/en-US/billing.json diff --git a/web/i18n/en-US/common.json b/web/i18n/locales/en-US/common.json similarity index 98% rename from web/i18n/en-US/common.json rename to web/i18n/locales/en-US/common.json index 27418cea05d..a79bf6625f8 100644 --- a/web/i18n/en-US/common.json +++ b/web/i18n/locales/en-US/common.json @@ -209,9 +209,7 @@ "mainNav.marketplace": "Marketplace", "mainNav.skills": "Skills", "mainNav.webApps.noResults": "No web apps found", - "mainNav.webApps.openApp": "Open {{name}} web app", "mainNav.webApps.searchPlaceholder": "Search web apps", - "mainNav.workspace.credits": "{{count}} credits", "mainNav.workspace.creditsUnit": "credits", "mainNav.workspace.inviteMembers": "Invite members", "mainNav.workspace.noResults": "No workspaces found", @@ -233,7 +231,6 @@ "members.datasetOperatorTip": "Only can manage the knowledge base", "members.editRole": "Edit Role", "members.editorTip": "Can build & edit apps", - "members.email": "Email", "members.emailInvalid": "Invalid Email Format", "members.emailNotSetup": "Email server is not set up, so invitation emails cannot be sent. Please notify users of the invitation link that will be issued after invitation instead.", "members.emailPlaceholder": "Type or paste email addresses…", @@ -250,7 +247,6 @@ "members.inviteLimitExceeded": "There are not enough member seats for these invitations.", "members.inviteTeamMember": "Add team member", "members.inviteTeamMemberTip": "They can access your team data directly after signing in.", - "members.invitedAsRole": "Invited as {{role}}", "members.lastActive": "LAST ACTIVE", "members.memberActions": "Member actions", "members.memberDetails.assign": "Assign", @@ -258,8 +254,6 @@ "members.memberDetails.assignedRoles": "Assigned Roles", "members.memberDetails.customGroup": "CUSTOMIZED", "members.memberDetails.generalGroup": "GENERAL", - "members.memberDetails.openAria": "Open member details for {{name}}", - "members.memberDetails.roleActionsAria": "Open actions for {{role}} role", "members.memberDetails.roleNoPermissionSummary": "Current role has no permissions.", "members.memberDetails.rolePermissionSummary": "{{role}} can {{permissions}}", "members.memberDetails.title": "Member Details", @@ -370,7 +364,6 @@ "modelProvider.card.usagePriorityTip": "Set which resource to use first when running models.", "modelProvider.configureTip": "Set up api-key or add model to use", "modelProvider.confirmDelete": "Confirm deletion?", - "modelProvider.credits": "Message Credits", "modelProvider.defaultConfig": "Default Config", "modelProvider.discoverMore": "Discover more in", "modelProvider.embeddingModel.key": "Embedding Model", @@ -427,7 +420,6 @@ "modelProvider.selector.noProviderConfiguredDesc": "Browse Marketplace to install one, or configure providers in settings.", "modelProvider.selector.onlyCompatibleModelsShown": "Only compatible models are shown", "modelProvider.selector.showIncompatibleModels": "Show incompatible models", - "modelProvider.selector.suggestion": "Suggestion", "modelProvider.selector.suggestionTip": "Recommended for agent tasks", "modelProvider.showModels": "Show Models", "modelProvider.speechToTextModel.key": "Speech-to-Text Model", @@ -458,6 +450,8 @@ "operation.confirmAction": "Please confirm your action.", "operation.copied": "Copied", "operation.copy": "Copy", + "operation.copyErrorDetails": "Copy error details", + "operation.copyErrorFailed": "Copy failed. Retry", "operation.copyImage": "Copy Image", "operation.create": "Create", "operation.deSelectAll": "Deselect All", @@ -475,7 +469,6 @@ "operation.format": "Format", "operation.imageCopied": "Image copied", "operation.imageCopyFailed": "Could not copy the image. Please download it instead.", - "operation.imageDownloaded": "Image downloaded", "operation.in": "in", "operation.learnMore": "Learn more", "operation.log": "Log", @@ -512,7 +505,6 @@ "operation.sure": "I'm sure", "operation.toggleFullscreen": "Toggle fullscreen", "operation.toggleMute": "Toggle mute", - "operation.upload": "Upload", "operation.view": "View", "operation.viewDetails": "View Details", "operation.viewMore": "VIEW MORE", @@ -542,7 +534,6 @@ "promptEditor.requestURL.item.title": "Request URL", "promptEditor.variable.modal.add": "New variable", "promptEditor.variable.modal.addTool": "New tool", - "provider.anthropicHosted.trialQuotaTip": "Your Anthropic trial quota will expire on 2025/03/17 and will no longer be available thereafter. Please make use of it in time.", "provider.encrypted.back": " technology.", "provider.encrypted.front": "Your API KEY will be encrypted and stored using", "provider.validating": "Validating key...", @@ -565,7 +556,6 @@ "settings.preferences": "Preferences", "settings.provider": "Model Provider", "settings.resourceAccess": "Resource Access", - "settings.resourceAccessDescription": "Configure permission rules that apps and knowledge bases can use. Permission rules can be assigned to members in specific resource access configurations.", "settings.rolesAndPermissions": "Roles & Permissions", "settings.settings": "Settings", "settings.swaggerAPIAsTool": "Swagger API as Tool", @@ -695,7 +685,6 @@ "userProfile.helpCenter": "View Docs", "userProfile.logout": "Log out", "userProfile.roadmap": "Roadmap", - "userProfile.settings": "Settings", "userProfile.workspace": "Workspace", "voice.language.arTN": "Tunisian Arabic", "voice.language.deDE": "German", diff --git a/web/i18n/en-US/custom.json b/web/i18n/locales/en-US/custom.json similarity index 100% rename from web/i18n/en-US/custom.json rename to web/i18n/locales/en-US/custom.json diff --git a/web/i18n/en-US/dataset-creation.json b/web/i18n/locales/en-US/dataset-creation.json similarity index 100% rename from web/i18n/en-US/dataset-creation.json rename to web/i18n/locales/en-US/dataset-creation.json diff --git a/web/i18n/en-US/dataset-documents.json b/web/i18n/locales/en-US/dataset-documents.json similarity index 100% rename from web/i18n/en-US/dataset-documents.json rename to web/i18n/locales/en-US/dataset-documents.json diff --git a/web/i18n/en-US/dataset-hit-testing.json b/web/i18n/locales/en-US/dataset-hit-testing.json similarity index 100% rename from web/i18n/en-US/dataset-hit-testing.json rename to web/i18n/locales/en-US/dataset-hit-testing.json diff --git a/web/i18n/en-US/dataset-pipeline.json b/web/i18n/locales/en-US/dataset-pipeline.json similarity index 100% rename from web/i18n/en-US/dataset-pipeline.json rename to web/i18n/locales/en-US/dataset-pipeline.json diff --git a/web/i18n/en-US/dataset-settings.json b/web/i18n/locales/en-US/dataset-settings.json similarity index 100% rename from web/i18n/en-US/dataset-settings.json rename to web/i18n/locales/en-US/dataset-settings.json diff --git a/web/i18n/en-US/dataset.json b/web/i18n/locales/en-US/dataset.json similarity index 97% rename from web/i18n/en-US/dataset.json rename to web/i18n/locales/en-US/dataset.json index c354f57aca1..b7f2eb7be6a 100644 --- a/web/i18n/en-US/dataset.json +++ b/web/i18n/locales/en-US/dataset.json @@ -163,7 +163,6 @@ "newKnowledge.configureProvider": "Configure {{provider}}", "newKnowledge.connectNotion": "Connect Notion", "newKnowledge.connectProvider": "Connect {{provider}}", - "newKnowledge.connectProviderGeneric": "Connect provider", "newKnowledge.connectSource": "Connect a source", "newKnowledge.connectSourceDescription": "Content stays in sync with Notion, websites, and other services.", "newKnowledge.connectingProvider": "Connecting…", @@ -179,18 +178,14 @@ "newKnowledge.crawlFailedDescription": "The crawler couldn't finish this preview. Check the URL, adjust crawl options, or try again.", "newKnowledge.crawlFailedProvider": "{{provider}} reported an error. Check the provider connection and try again.", "newKnowledge.crawlFailedTimeout": "The crawl timed out. Try a narrower URL or reduce the maximum page count.", - "newKnowledge.crawlNotStarted": "Not started", "newKnowledge.crawlOptions": "Crawl options", "newKnowledge.crawlPreview": "Crawl preview", "newKnowledge.crawlProgress": "Crawl progress for {{host}}", - "newKnowledge.crawlSetupUnavailableDescription": "Website crawl configuration is disabled until the crawl workflow is available.", - "newKnowledge.crawlSetupUnavailableTitle": "Firecrawl is ready", "newKnowledge.crawlStartFailed": "The crawl preview couldn't start. Try again.", "newKnowledge.crawlStopped": "Crawl stopped", "newKnowledge.crawling": "Crawling…", "newKnowledge.crawlingPages": "Crawling {{host}}… {{count}} pages found", "newKnowledge.createFailed": "We couldn't create this knowledge base. Check your access and try again.", - "newKnowledge.createIllustrationTitle": "Connect your content once — every app answers with the freshest knowledge.", "newKnowledge.createTitle": "Create Knowledge", "newKnowledge.createdAt": "Created", "newKnowledge.creators": "Creators", @@ -220,7 +215,6 @@ "newKnowledge.documentColumn": "Document", "newKnowledge.documentContentIncomplete": "Loading the remaining document content…", "newKnowledge.documentContents": "Contents", - "newKnowledge.documentFacts": "Document details", "newKnowledge.documentFilterLabel": "Filter documents by status", "newKnowledge.documentLoadErrorDescription": "We couldn't load this document. Try again.", "newKnowledge.documentLoadErrorTitle": "Document couldn't load", @@ -268,7 +262,6 @@ "newKnowledge.documentsSelected": "{{count}} documents selected", "newKnowledge.documentsUnavailable": "— documents", "newKnowledge.downloadDocuments": "Download", - "newKnowledge.editSource": "Edit", "newKnowledge.emptyChunk": "Empty chunk", "newKnowledge.emptyDescription": "Add your documents and agents can read them directly — no indexing to configure.", "newKnowledge.emptyTitle": "A smarter way to build knowledge bases", @@ -344,7 +337,6 @@ "newKnowledge.processingTaskState.succeeded": "Completed", "newKnowledge.processingTaskState.superseded": "Superseded", "newKnowledge.providerConnected": "{{provider}} connected", - "newKnowledge.providerLabel": "Provider", "newKnowledge.providerLoadFailed": "Providers couldn't load", "newKnowledge.providerNotConfigured": "{{provider}} is not configured", "newKnowledge.providerNotConfiguredDescription": "Add an API key or endpoint URL to crawl websites with {{provider}}.", @@ -369,12 +361,10 @@ "newKnowledge.revisionState.superseded": "Previous", "newKnowledge.rootUrl": "Root URL", "newKnowledge.rootUrlPlaceholder": "https://docs.example.com", - "newKnowledge.routeUnavailable": "This section will become available in the next setup step.", "newKnowledge.searchDocuments": "Search documents", "newKnowledge.searchSources": "Search sources", "newKnowledge.selectAll": "Select all", "newKnowledge.selectAllDocuments": "Select all documents", - "newKnowledge.selectAllSources": "Select all sources", "newKnowledge.selectChunk": "Select a chunk to inspect its content.", "newKnowledge.selectFilesAndFolders": "Select files & folders to sync", "newKnowledge.skippedFailed": "Skipped (failed)", @@ -384,7 +374,6 @@ "newKnowledge.sourceFilterLabel": "Filter sources by status", "newKnowledge.sourceName": "Source name", "newKnowledge.sourceNamePlaceholder": "e.g. Product docs crawl", - "newKnowledge.sourceNameRequired": "Enter a source name.", "newKnowledge.sourceSetupBackendDependency": "Source setup is shown in full; editing and preview actions require the draft source backend workflow.", "newKnowledge.sourceStatus.active": "Active", "newKnowledge.sourceStatus.disabled": "Disabled", @@ -431,7 +420,6 @@ "newKnowledge.unavailableDescription": "KnowledgeFS isn't available for this workspace right now.", "newKnowledge.unavailableTitle": "New Knowledge is unavailable", "newKnowledge.updated": "Updated {{date}}", - "newKnowledge.updatedAt": "Last updated", "newKnowledge.updatedColumn": "Updated", "newKnowledge.uploadCharactersUnavailable": "Characters: —", "newKnowledge.uploadDocuments": "Upload documents", @@ -460,11 +448,9 @@ "retrievalSettings": "Retrieval Setting", "retry": "Retry", "selectExternalKnowledgeAPI.placeholder": "Choose an External Knowledge API", - "serviceApi.card.addApiKey": "Add API Key", "serviceApi.card.apiKey": "API Key", "serviceApi.card.apiReference": "API Reference", "serviceApi.card.endpoint": "Service API Endpoint", - "serviceApi.card.manageApiKey": "Manage API Key", "serviceApi.card.title": "Backend service api", "serviceApi.disabled": "Disabled", "serviceApi.enabled": "Enabled", diff --git a/web/i18n/en-US/deployments.json b/web/i18n/locales/en-US/deployments.json similarity index 97% rename from web/i18n/en-US/deployments.json rename to web/i18n/locales/en-US/deployments.json index dbc046fb923..1328ccfdca4 100644 --- a/web/i18n/en-US/deployments.json +++ b/web/i18n/locales/en-US/deployments.json @@ -27,7 +27,6 @@ "list.emptyTitle": "No deployments yet", "overview.chip.latest": "latest", "status.RUNTIME_INSTANCE_STATUS_DEPLOYING": "Deploying", - "status.RUNTIME_INSTANCE_STATUS_FAILED": "Deploy failed", "status.RUNTIME_INSTANCE_STATUS_INVALID": "Invalid", "status.RUNTIME_INSTANCE_STATUS_READY": "Running", "status.RUNTIME_INSTANCE_STATUS_UNDEPLOYED": "Not deployed", @@ -87,6 +86,5 @@ "studio.versionValue": "Version value", "studio.versionsBehind_one": "{{count}} version behind", "studio.versionsBehind_other": "{{count}} versions behind", - "versions.checkingReleaseContent": "Checking release content...", "versions.deployTo": "Deploy to {{name}}" } diff --git a/web/i18n/en-US/device-flow.json b/web/i18n/locales/en-US/device-flow.json similarity index 100% rename from web/i18n/en-US/device-flow.json rename to web/i18n/locales/en-US/device-flow.json diff --git a/web/i18n/en-US/education.json b/web/i18n/locales/en-US/education.json similarity index 100% rename from web/i18n/en-US/education.json rename to web/i18n/locales/en-US/education.json diff --git a/web/i18n/en-US/explore.json b/web/i18n/locales/en-US/explore.json similarity index 100% rename from web/i18n/en-US/explore.json rename to web/i18n/locales/en-US/explore.json diff --git a/web/i18n/en-US/layout.json b/web/i18n/locales/en-US/layout.json similarity index 100% rename from web/i18n/en-US/layout.json rename to web/i18n/locales/en-US/layout.json diff --git a/web/i18n/en-US/login.json b/web/i18n/locales/en-US/login.json similarity index 100% rename from web/i18n/en-US/login.json rename to web/i18n/locales/en-US/login.json diff --git a/web/i18n/en-US/oauth.json b/web/i18n/locales/en-US/oauth.json similarity index 100% rename from web/i18n/en-US/oauth.json rename to web/i18n/locales/en-US/oauth.json diff --git a/web/i18n/en-US/permission-keys.json b/web/i18n/locales/en-US/permission-keys.json similarity index 100% rename from web/i18n/en-US/permission-keys.json rename to web/i18n/locales/en-US/permission-keys.json diff --git a/web/i18n/en-US/permission.json b/web/i18n/locales/en-US/permission.json similarity index 100% rename from web/i18n/en-US/permission.json rename to web/i18n/locales/en-US/permission.json diff --git a/web/i18n/en-US/pipeline.json b/web/i18n/locales/en-US/pipeline.json similarity index 100% rename from web/i18n/en-US/pipeline.json rename to web/i18n/locales/en-US/pipeline.json diff --git a/web/i18n/en-US/plugin-tags.json b/web/i18n/locales/en-US/plugin-tags.json similarity index 100% rename from web/i18n/en-US/plugin-tags.json rename to web/i18n/locales/en-US/plugin-tags.json diff --git a/web/i18n/en-US/plugin-trigger.json b/web/i18n/locales/en-US/plugin-trigger.json similarity index 100% rename from web/i18n/en-US/plugin-trigger.json rename to web/i18n/locales/en-US/plugin-trigger.json diff --git a/web/i18n/en-US/plugin.json b/web/i18n/locales/en-US/plugin.json similarity index 99% rename from web/i18n/en-US/plugin.json rename to web/i18n/locales/en-US/plugin.json index 00405eb1d4c..d994d6b1d53 100644 --- a/web/i18n/en-US/plugin.json +++ b/web/i18n/locales/en-US/plugin.json @@ -277,7 +277,6 @@ "readmeInfo.noReadmeAvailable": "No README available", "readmeInfo.title": "README", "requestAPlugin": "Request an integration", - "requestSubmit": "Request or publish", "search": "Search", "searchCategories": "Search Categories", "searchInMarketplace": "Search in Marketplace", diff --git a/web/i18n/en-US/register.json b/web/i18n/locales/en-US/register.json similarity index 100% rename from web/i18n/en-US/register.json rename to web/i18n/locales/en-US/register.json diff --git a/web/i18n/en-US/run-log.json b/web/i18n/locales/en-US/run-log.json similarity index 100% rename from web/i18n/en-US/run-log.json rename to web/i18n/locales/en-US/run-log.json diff --git a/web/i18n/en-US/share.json b/web/i18n/locales/en-US/share.json similarity index 100% rename from web/i18n/en-US/share.json rename to web/i18n/locales/en-US/share.json diff --git a/web/i18n/en-US/skill.json b/web/i18n/locales/en-US/skill.json similarity index 82% rename from web/i18n/en-US/skill.json rename to web/i18n/locales/en-US/skill.json index 3bc3a904d67..883139cc51f 100644 --- a/web/i18n/en-US/skill.json +++ b/web/i18n/locales/en-US/skill.json @@ -1,5 +1,4 @@ { - "skillManagement.clearTags": "Clear tags", "skillManagement.create": "Create", "skillManagement.createFailed": "Failed to create skill.", "skillManagement.createSuccess": "Draft created.", @@ -13,12 +12,9 @@ "skillManagement.deleteFailed": "Failed to delete skill.", "skillManagement.deleteSuccess": "Skill deleted.", "skillManagement.detail.addMetadata": "Add metadata", - "skillManagement.detail.addMetadataDescription": "Add a frontmatter field to this Markdown file.", "skillManagement.detail.addTag": "Add tag", - "skillManagement.detail.addTagDescription": "Create or bind a tag to this Skill. New tags are saved with the Skill metadata.", "skillManagement.detail.addTagSuccess": "Tag added.", "skillManagement.detail.back": "Back to Skills", - "skillManagement.detail.builder.applyFailed": "Failed to apply draft updates.", "skillManagement.detail.builder.attach": "Attach file", "skillManagement.detail.builder.attachFailed": "Failed to attach file.", "skillManagement.detail.builder.attachLimit": "You can attach up to 10 files.", @@ -26,22 +22,12 @@ "skillManagement.detail.builder.attachUnsupported": "Only images, text, and document files can be attached.", "skillManagement.detail.builder.attachmentOnlyMessage": "Use the attached file to help improve this skill.", "skillManagement.detail.builder.close": "Close Skill Builder", - "skillManagement.detail.builder.compatibleModelsOnly": "Only compatible models are shown", "skillManagement.detail.builder.copyResponse": "Copy response", "skillManagement.detail.builder.copySuccess": "Response copied to clipboard.", "skillManagement.detail.builder.editIntro": "I've reviewed this skill. What would you like to adjust?", "skillManagement.detail.builder.exampleIssueTriage": "Customer issue triage", "skillManagement.detail.builder.exampleOnboarding": "New hire onboarding guide", "skillManagement.detail.builder.exampleSalesFollowUp": "Sales lead follow-up strategy", - "skillManagement.detail.builder.followUpDisplayName": "Use Refund approval as the display name", - "skillManagement.detail.builder.followUpNameIcon": "Apply the suggested name and icon", - "skillManagement.detail.builder.fromMarketplace": "From Marketplace", - "skillManagement.detail.builder.model": "GPT-4o", - "skillManagement.detail.builder.modelCredits.all": "All credits", - "skillManagement.detail.builder.modelCredits.configure": "Configure required", - "skillManagement.detail.builder.modelCredits.exhausted": "Credits exhausted", - "skillManagement.detail.builder.modelProviderSettings": "Model Provider Settings", - "skillManagement.detail.builder.modelSearch": "Search models...", "skillManagement.detail.builder.modifyPlaceholder": "Ask AI to modify this skill...", "skillManagement.detail.builder.open": "Open Skill Builder", "skillManagement.detail.builder.placeholder": "Describe the scenario...", @@ -61,8 +47,6 @@ "skillManagement.detail.builder.thinkingUnavailable": "No thinking content was returned by the model.", "skillManagement.detail.builder.title": "Skill Builder", "skillManagement.detail.builder.tryExample": "Try an example", - "skillManagement.detail.builder.voice": "Voice input", - "skillManagement.detail.builder.voiceUnavailable": "Voice input is not available yet.", "skillManagement.detail.cancelAddMetadata": "Cancel adding metadata", "skillManagement.detail.closeFileTab": "Close {{name}}", "skillManagement.detail.closeVersions": "Close versions", @@ -70,14 +54,10 @@ "skillManagement.detail.copyContentSuccess": "Content copied to clipboard.", "skillManagement.detail.copyFile": "Copy", "skillManagement.detail.copyFileSuccess": "File ready to paste.", - "skillManagement.detail.copyVersionId": "Copy ID", - "skillManagement.detail.copyVersionIdSuccess": "ID copied to clipboard.", "skillManagement.detail.createFile": "New file", - "skillManagement.detail.createFileDescription": "Enter a file path relative to the skill root.", "skillManagement.detail.createFileMenu": "New file...", "skillManagement.detail.createFileSuccess": "File created.", "skillManagement.detail.createFolder": "New folder", - "skillManagement.detail.createFolderDescription": "Enter a folder path relative to the skill root.", "skillManagement.detail.createFolderMenu": "New folder...", "skillManagement.detail.createFolderSuccess": "Folder created.", "skillManagement.detail.createdBy": "Created by {{name}}", @@ -100,10 +80,8 @@ "skillManagement.detail.fileMeta": "{{type}} · {{size}} bytes", "skillManagement.detail.fileMissing": "File not found.", "skillManagement.detail.fileOperationFailed": "File operation failed.", - "skillManagement.detail.files": "Files", "skillManagement.detail.latest": "Latest", "skillManagement.detail.loadFailed": "Failed to load skill.", - "skillManagement.detail.loadingTags": "Loading tags...", "skillManagement.detail.markdownLiveMode": "Live preview", "skillManagement.detail.markdownSourceMode": "Source editor", "skillManagement.detail.metadataKey": "name", @@ -113,10 +91,7 @@ "skillManagement.detail.nameThisVersion": "Name this version", "skillManagement.detail.noFileSelected": "Select a file to preview.", "skillManagement.detail.noFiles": "No files.", - "skillManagement.detail.noSearchResults": "No matching files.", - "skillManagement.detail.noSelectableTags": "No available tags.", "skillManagement.detail.noVersions": "No published versions yet.", - "skillManagement.detail.pasteFile": "Paste", "skillManagement.detail.pasteFileSuccess": "File pasted.", "skillManagement.detail.previewUnsupported": "Preview is not supported for this file.", "skillManagement.detail.publish": "Publish", @@ -128,9 +103,7 @@ "skillManagement.detail.publishUpdate": "Publish update", "skillManagement.detail.published": "Published", "skillManagement.detail.publishedAt": "Published {{time}}", - "skillManagement.detail.publishedVersion": "Published #{{number}}", "skillManagement.detail.publishing": "Publishing...", - "skillManagement.detail.readonly": "Read only", "skillManagement.detail.referenceFiles.confirm": "Enter Confirm", "skillManagement.detail.referenceFiles.empty": "No files available.", "skillManagement.detail.referenceFiles.livePlaceholder": "Write instructions for the agent. Type / to reference files.", @@ -138,14 +111,10 @@ "skillManagement.detail.referenceFiles.title": "Reference files", "skillManagement.detail.referencedBy_one": "Used by {{count}} agent", "skillManagement.detail.referencedBy_other": "Used by {{count}} agents", - "skillManagement.detail.removeMetadata": "Delete {{name}} metadata", "skillManagement.detail.removeTag": "Remove {{tag}}", "skillManagement.detail.removeTagSuccess": "Tag removed.", - "skillManagement.detail.renameFile": "Rename file", - "skillManagement.detail.renameFileDescription": "Enter the new path relative to the skill root.", "skillManagement.detail.renameFileSuccess": "File renamed.", "skillManagement.detail.renameSkillSuccess": "Skill renamed.", - "skillManagement.detail.renameVersion": "Rename", "skillManagement.detail.renameVersionFailed": "Failed to rename version.", "skillManagement.detail.renameVersionPrompt": "Version name", "skillManagement.detail.renameVersionSuccess": "Version renamed.", @@ -155,17 +124,14 @@ "skillManagement.detail.restoreVersionConfirmTitle": "Restore this version?", "skillManagement.detail.restoreVersionFailed": "Failed to restore version.", "skillManagement.detail.restoreVersionSuccess": "Version restored to draft.", - "skillManagement.detail.save": "Save", "skillManagement.detail.saveConflictConfirmDescription": "This skill was updated elsewhere. Load the latest draft to continue editing. Your current changes will be discarded.", "skillManagement.detail.saveConflictConfirmTitle": "Load the latest draft?", "skillManagement.detail.saveConflictReload": "Load latest draft", "skillManagement.detail.saveConflictStatus": "Conflict detected. Load the latest draft to continue editing.", "skillManagement.detail.saveFailed": "Failed to save file.", - "skillManagement.detail.saveSuccess": "File saved.", "skillManagement.detail.saved": "Saved", "skillManagement.detail.savedAt": "Saved {{time}}", "skillManagement.detail.saving": "Saving...", - "skillManagement.detail.searchFiles": "Search files", "skillManagement.detail.showMoreReferences": "Show {{count}} more", "skillManagement.detail.skillDescriptionPlaceholder": "Describe what this skill does and when agents should use it.", "skillManagement.detail.skillNamePlaceholder": "Use lowercase letters, numbers, and hyphens.", @@ -177,16 +143,12 @@ "skillManagement.detail.uploadConflict": "A file with this name already exists.", "skillManagement.detail.uploadFailureSummary": "{{failed}} of {{total}} files failed", "skillManagement.detail.uploadFailureTitle": "Some uploads failed", - "skillManagement.detail.uploadFile": "Upload file", - "skillManagement.detail.uploadFileFailed": "File upload failed.", "skillManagement.detail.uploadFileSuccess": "File uploaded.", "skillManagement.detail.uploadFilesButton": "Upload {{count}} files", "skillManagement.detail.uploadFilesFailedStatus_one": "{{count}} file upload failed.", "skillManagement.detail.uploadFilesFailedStatus_other": "{{count}} file uploads failed.", "skillManagement.detail.uploadFilesMenu": "Upload files...", "skillManagement.detail.uploadFilesProgress": "Uploading {{completed}}/{{total}}", - "skillManagement.detail.uploadFilesResult": "{{uploaded}} uploaded · {{failed}} failed", - "skillManagement.detail.uploadFilesStatus": "Upload Status", "skillManagement.detail.uploadInvalidExtension": "This file type isn't supported.", "skillManagement.detail.uploadInvalidName": "Invalid name.", "skillManagement.detail.uploadInvalidPath": "Invalid file path.", @@ -206,7 +168,6 @@ "skillManagement.detail.uploadWillReplace": "Will replace the existing file", "skillManagement.detail.uploadWillUseName": "Will upload as {{name}}", "skillManagement.detail.versionHistory": "Open version history", - "skillManagement.detail.versionMeta": "{{hash}} · {{time}}", "skillManagement.detail.versionPublishNote": "Release notes", "skillManagement.detail.versionPublishNotePlaceholder": "Describe this change", "skillManagement.detail.versionPublishedMeta": "{{time}} · {{name}}", @@ -227,8 +188,6 @@ "skillManagement.errors.missingSkillMd": "This package has no SKILL.md. Add the file and import it again.", "skillManagement.errors.nameConflict": "A skill named \"{{name}}\" already exists. Rename it in SKILL.md and import again.", "skillManagement.errors.workspaceLimit": "Your workspace has reached the limit of 500 skills. Delete a skill to add a new one.", - "skillManagement.exportDialog.description": "This will export the current saved draft of your skill. If you have unsaved changes in the editor, please save them before exporting.", - "skillManagement.exportDialog.title": "Export Current Saved Draft", "skillManagement.import": "Import", "skillManagement.importFailed": "Failed to import skill.", "skillManagement.importSuccess": "Skill imported.", @@ -236,12 +195,10 @@ "skillManagement.loadingError": "Failed to load skills", "skillManagement.moreActions": "More actions for {{name}}", "skillManagement.noDescription": "No description yet", - "skillManagement.noTags": "No tags", "skillManagement.publishedAt": "Published {{time}}", "skillManagement.referenceCount_one": "{{count}} agent", "skillManagement.referenceCount_other": "{{count}} agents", "skillManagement.searchLabel": "Search skills", "skillManagement.searchPlaceholder": "Search by name or description", - "skillManagement.tags": "Tags", "skillManagement.title": "Skills" } diff --git a/web/i18n/en-US/snippet.json b/web/i18n/locales/en-US/snippet.json similarity index 100% rename from web/i18n/en-US/snippet.json rename to web/i18n/locales/en-US/snippet.json diff --git a/web/i18n/en-US/time.json b/web/i18n/locales/en-US/time.json similarity index 100% rename from web/i18n/en-US/time.json rename to web/i18n/locales/en-US/time.json diff --git a/web/i18n/en-US/tools.json b/web/i18n/locales/en-US/tools.json similarity index 100% rename from web/i18n/en-US/tools.json rename to web/i18n/locales/en-US/tools.json diff --git a/web/i18n/en-US/workflow.json b/web/i18n/locales/en-US/workflow.json similarity index 99% rename from web/i18n/en-US/workflow.json rename to web/i18n/locales/en-US/workflow.json index ce50f046338..156e7b4d4b5 100644 --- a/web/i18n/en-US/workflow.json +++ b/web/i18n/locales/en-US/workflow.json @@ -460,8 +460,6 @@ "nodes.agent.pluginNotInstalledDesc": "This integration is installed from GitHub. Please go to Integrations to reinstall", "nodes.agent.roster.change": "Change", "nodes.agent.roster.editInConsole": "Edit in Agents", - "nodes.agent.roster.editInConsoleDisabled": "Editing agents requires the Editor role or above.", - "nodes.agent.roster.editInConsoleDisabledRbac": "Only members with the Editor role or above, or the Manage Agents permission, can edit agents.", "nodes.agent.roster.inlineSetup.description": "A one-off setup that lives only in this node", "nodes.agent.roster.inlineSetup.name": "Inline setup", "nodes.agent.roster.inlineSetup.type": "Only in this node", diff --git a/web/i18n/es-ES/agent-v-2.json b/web/i18n/locales/es-ES/agent-v-2.json similarity index 98% rename from web/i18n/es-ES/agent-v-2.json rename to web/i18n/locales/es-ES/agent-v-2.json index 5a25fcf4528..bdf830e0fcf 100644 --- a/web/i18n/es-ES/agent-v-2.json +++ b/web/i18n/locales/es-ES/agent-v-2.json @@ -69,6 +69,7 @@ "agentDetail.configure.answer.activity.runningCommands": "Ejecutando comandos", "agentDetail.configure.answer.duration.minute": "{{count}} min", "agentDetail.configure.answer.duration.second": "{{count}} s", + "agentDetail.configure.answer.responseEnded": "La respuesta ha terminado", "agentDetail.configure.answer.thinking": "Pensando", "agentDetail.configure.build.empty.communityEditionTip": "Community Edition, aunque ofrece versionado para las configuraciones extraídas, no admite versionado del propio sistema de archivos. Ten cuidado con tus acciones en el modo Build, ya que los cambios realizados en el sistema de archivos ocurren en tiempo real y no siempre se pueden revertir limpiamente. Community Edition no es la opción ideal para atender audiencias externas masivas.", "agentDetail.configure.build.empty.description": "Describe lo que quieres y se irá completando el formulario de la izquierda mientras avanzas.", @@ -269,7 +270,6 @@ "agentDetail.configure.workingDirectory.breadcrumbLabel": "Ruta del sistema de archivos", "agentDetail.configure.workingDirectory.description": "Archivos que el agente lee y escribe mientras se ejecuta. Todo lo que genere, como qna_report.pdf, aparecerá aquí.", "agentDetail.configure.workingDirectory.fileSystem": "Sistema de archivos", - "agentDetail.configure.workingDirectory.open": "Abrir directorio de trabajo", "agentDetail.configure.workingDirectory.persistentFiles": "Persistentes", "agentDetail.configure.workingDirectory.persistentFilesTooltip": "Archivos que el agente conserva y puede usar en cualquier lugar donde se ejecute, incluidas las conversaciones de los usuarios finales y los flujos de trabajo. Cualquier cambio que hagas aquí durante este chat de compilación se aplicará cuando selecciones «Aplicar».", "agentDetail.configure.workingDirectory.temporaryFiles": "Temporales", @@ -348,7 +348,6 @@ "agentDetail.sections.logs": "Registros", "agentDetail.sections.monitoring": "Monitoreo", "agentDetail.title": "Agente", - "agentDetail.type": "AGENTE", "agentDetail.versionHistory.empty": "Aún no hay versiones", "agentDetail.versionHistory.exitVersions": "Salir de versiones", "agentDetail.versionHistory.filter": "Filtrar versiones", @@ -408,8 +407,5 @@ "roster.sort.lastModified": "Última modificación", "roster.sort.recentlyCreated": "Creados recientemente", "roster.title": "Agentes", - "roster.usageStatus.draft": "Borrador", - "skillManagement.detail.uploadFilesFailedStatus_one": "Error al subir {{count}} archivo.", - "skillManagement.detail.uploadFilesFailedStatus_other": "Error al subir {{count}} archivos.", - "skillManagement.title": "Skills" + "roster.usageStatus.draft": "Borrador" } diff --git a/web/i18n/es-ES/app-annotation.json b/web/i18n/locales/es-ES/app-annotation.json similarity index 100% rename from web/i18n/es-ES/app-annotation.json rename to web/i18n/locales/es-ES/app-annotation.json diff --git a/web/i18n/es-ES/app-api.json b/web/i18n/locales/es-ES/app-api.json similarity index 100% rename from web/i18n/es-ES/app-api.json rename to web/i18n/locales/es-ES/app-api.json diff --git a/web/i18n/es-ES/app-debug.json b/web/i18n/locales/es-ES/app-debug.json similarity index 100% rename from web/i18n/es-ES/app-debug.json rename to web/i18n/locales/es-ES/app-debug.json diff --git a/web/i18n/es-ES/app-log.json b/web/i18n/locales/es-ES/app-log.json similarity index 97% rename from web/i18n/es-ES/app-log.json rename to web/i18n/locales/es-ES/app-log.json index 695b684c833..8409f67ed4d 100644 --- a/web/i18n/es-ES/app-log.json +++ b/web/i18n/locales/es-ES/app-log.json @@ -4,11 +4,9 @@ "agentLogDetail.iteration": "Iteración", "agentLogDetail.iterations": "Iteraciones", "agentLogDetail.toolUsed": "Herramienta Utilizada", - "archives.action.downloadMonth": "Descargar archivo de {{month}}", "archives.action.downloadReady": "La descarga del archivo está lista.", "archives.action.prepareDownload": "Preparar descarga", "archives.action.prepareFailed": "No se pudo preparar la descarga del archivo.", - "archives.action.prepareMonth": "Preparar la descarga del archivo de {{month}}", "archives.action.prepareStarted": "Se está preparando la descarga del archivo.", "archives.action.preparing": "Preparando", "archives.description": "Descarga exportaciones CSV mensuales de los registros archivados de ejecuciones de workflow en este espacio de trabajo.", diff --git a/web/i18n/es-ES/app-overview.json b/web/i18n/locales/es-ES/app-overview.json similarity index 100% rename from web/i18n/es-ES/app-overview.json rename to web/i18n/locales/es-ES/app-overview.json diff --git a/web/i18n/es-ES/app.json b/web/i18n/locales/es-ES/app.json similarity index 100% rename from web/i18n/es-ES/app.json rename to web/i18n/locales/es-ES/app.json diff --git a/web/i18n/es-ES/billing.json b/web/i18n/locales/es-ES/billing.json similarity index 100% rename from web/i18n/es-ES/billing.json rename to web/i18n/locales/es-ES/billing.json diff --git a/web/i18n/es-ES/common.json b/web/i18n/locales/es-ES/common.json similarity index 98% rename from web/i18n/es-ES/common.json rename to web/i18n/locales/es-ES/common.json index 8000423afb7..75056e85d02 100644 --- a/web/i18n/es-ES/common.json +++ b/web/i18n/locales/es-ES/common.json @@ -209,9 +209,7 @@ "mainNav.marketplace": "Marketplace", "mainNav.skills": "Skills", "mainNav.webApps.noResults": "No se encontraron aplicaciones web", - "mainNav.webApps.openApp": "Abrir la aplicación web {{name}}", "mainNav.webApps.searchPlaceholder": "Buscar aplicaciones web", - "mainNav.workspace.credits": "{{count}} créditos", "mainNav.workspace.creditsUnit": "créditos", "mainNav.workspace.inviteMembers": "Invite members", "mainNav.workspace.noResults": "No se encontraron espacios de trabajo", @@ -233,7 +231,6 @@ "members.datasetOperatorTip": "Solo puede administrar la base de conocimiento", "members.editRole": "Editar rol", "members.editorTip": "Puede crear y editar apps", - "members.email": "Correo electrónico", "members.emailInvalid": "Formato de correo electrónico inválido", "members.emailNotSetup": "El servidor de correo no está configurado, por lo que no se pueden enviar correos de invitación. En su lugar, notifique a los usuarios el enlace de invitación que se emitirá después de la invitación.", "members.emailPlaceholder": "Escribe o pega direcciones de correo electrónico…", @@ -250,7 +247,6 @@ "members.inviteLimitExceeded": "No hay suficientes plazas disponibles para todos los destinatarios.", "members.inviteTeamMember": "Agregar miembro del equipo", "members.inviteTeamMemberTip": "Pueden acceder a tus datos del equipo directamente después de iniciar sesión.", - "members.invitedAsRole": "Invitado como {{role}}", "members.lastActive": "ÚLTIMA ACTIVIDAD", "members.memberActions": "Acciones del miembro", "members.memberDetails.assign": "Asignar", @@ -258,8 +254,6 @@ "members.memberDetails.assignedRoles": "Roles asignados", "members.memberDetails.customGroup": "PERSONALIZADO", "members.memberDetails.generalGroup": "GENERAL", - "members.memberDetails.openAria": "Abrir detalles del miembro de {{name}}", - "members.memberDetails.roleActionsAria": "Abrir acciones para el rol {{role}}", "members.memberDetails.roleNoPermissionSummary": "El rol actual no tiene permisos.", "members.memberDetails.rolePermissionSummary": "{{role}} puede {{permissions}}", "members.memberDetails.title": "Detalles del miembro", @@ -370,7 +364,6 @@ "modelProvider.card.usagePriorityTip": "Establece qué recurso usar primero al ejecutar modelos.", "modelProvider.configureTip": "Configurar la clave de API o agregar el modelo que se va a usar", "modelProvider.confirmDelete": "¿Confirmar eliminación?", - "modelProvider.credits": "Créditos de Mensaje", "modelProvider.defaultConfig": "Configuración Predeterminada", "modelProvider.discoverMore": "Descubre más en", "modelProvider.embeddingModel.key": "Modelo de Incrustación", @@ -427,7 +420,6 @@ "modelProvider.selector.noProviderConfiguredDesc": "Explora el Marketplace para instalar uno, o configura proveedores en los ajustes.", "modelProvider.selector.onlyCompatibleModelsShown": "Solo se muestran los modelos compatibles", "modelProvider.selector.showIncompatibleModels": "Mostrar modelos incompatibles", - "modelProvider.selector.suggestion": "Sugerencia", "modelProvider.selector.suggestionTip": "Recomendado para tareas de agente", "modelProvider.showModels": "Mostrar Modelos", "modelProvider.speechToTextModel.key": "Modelo de Voz a Texto", @@ -458,6 +450,8 @@ "operation.confirmAction": "Por favor, confirme su acción.", "operation.copied": "Copiado", "operation.copy": "Copiar", + "operation.copyErrorDetails": "Copiar detalles del error", + "operation.copyErrorFailed": "Error al copiar. Reintentar", "operation.copyImage": "Copiar imagen", "operation.create": "Crear", "operation.deSelectAll": "Deseleccionar todo", @@ -475,7 +469,6 @@ "operation.format": "Formato", "operation.imageCopied": "Imagen copiada", "operation.imageCopyFailed": "No se pudo copiar la imagen. Descárgala en su lugar.", - "operation.imageDownloaded": "Imagen descargada", "operation.in": "en", "operation.learnMore": "Aprender más", "operation.log": "Registro", @@ -512,7 +505,6 @@ "operation.sure": "Estoy seguro", "operation.toggleFullscreen": "Alternar pantalla completa", "operation.toggleMute": "Alternar silencio", - "operation.upload": "Upload", "operation.view": "Vista", "operation.viewDetails": "Ver detalles", "operation.viewMore": "VER MÁS", @@ -542,7 +534,6 @@ "promptEditor.requestURL.item.title": "URL de Solicitud", "promptEditor.variable.modal.add": "Nueva variable", "promptEditor.variable.modal.addTool": "Nueva herramienta", - "provider.anthropicHosted.trialQuotaTip": "Su cuota de prueba antrópica caducará el 11/03/2025 y ya no estará disponible a partir de entonces. Por favor, aprovéchelo a tiempo.", "provider.encrypted.back": " tecnología.", "provider.encrypted.front": "Tu CLAVE API será encriptada y almacenada usando", "provider.validating": "Validando clave...", @@ -565,7 +556,6 @@ "settings.preferences": "Preferences", "settings.provider": "Proveedor de Modelo", "settings.resourceAccess": "Acceso a recursos", - "settings.resourceAccessDescription": "Configura las reglas de permisos que pueden usar las aplicaciones y las bases de conocimiento. Las reglas de permisos se pueden asignar a los miembros en configuraciones específicas de acceso a recursos.", "settings.rolesAndPermissions": "Roles y permisos", "settings.settings": "Settings", "settings.swaggerAPIAsTool": "Swagger API as Tool", @@ -695,7 +685,6 @@ "userProfile.helpCenter": "Ayuda", "userProfile.logout": "Cerrar sesión", "userProfile.roadmap": "Hoja de ruta", - "userProfile.settings": "Configuraciones", "userProfile.workspace": "Espacio de trabajo", "voice.language.arTN": "Árabe tunecino", "voice.language.deDE": "Alemán", diff --git a/web/i18n/es-ES/custom.json b/web/i18n/locales/es-ES/custom.json similarity index 100% rename from web/i18n/es-ES/custom.json rename to web/i18n/locales/es-ES/custom.json diff --git a/web/i18n/es-ES/dataset-creation.json b/web/i18n/locales/es-ES/dataset-creation.json similarity index 100% rename from web/i18n/es-ES/dataset-creation.json rename to web/i18n/locales/es-ES/dataset-creation.json diff --git a/web/i18n/es-ES/dataset-documents.json b/web/i18n/locales/es-ES/dataset-documents.json similarity index 100% rename from web/i18n/es-ES/dataset-documents.json rename to web/i18n/locales/es-ES/dataset-documents.json diff --git a/web/i18n/es-ES/dataset-hit-testing.json b/web/i18n/locales/es-ES/dataset-hit-testing.json similarity index 100% rename from web/i18n/es-ES/dataset-hit-testing.json rename to web/i18n/locales/es-ES/dataset-hit-testing.json diff --git a/web/i18n/es-ES/dataset-pipeline.json b/web/i18n/locales/es-ES/dataset-pipeline.json similarity index 100% rename from web/i18n/es-ES/dataset-pipeline.json rename to web/i18n/locales/es-ES/dataset-pipeline.json diff --git a/web/i18n/es-ES/dataset-settings.json b/web/i18n/locales/es-ES/dataset-settings.json similarity index 100% rename from web/i18n/es-ES/dataset-settings.json rename to web/i18n/locales/es-ES/dataset-settings.json diff --git a/web/i18n/es-ES/dataset.json b/web/i18n/locales/es-ES/dataset.json similarity index 97% rename from web/i18n/es-ES/dataset.json rename to web/i18n/locales/es-ES/dataset.json index 021e795840e..0ad6e690cc0 100644 --- a/web/i18n/es-ES/dataset.json +++ b/web/i18n/locales/es-ES/dataset.json @@ -163,7 +163,6 @@ "newKnowledge.configureProvider": "Configurar {{provider}}", "newKnowledge.connectNotion": "Conectar Notion", "newKnowledge.connectProvider": "Conectar {{provider}}", - "newKnowledge.connectProviderGeneric": "Conectar proveedor", "newKnowledge.connectSource": "Conectar una fuente", "newKnowledge.connectSourceDescription": "El contenido se mantiene sincronizado con Notion, sitios web y otros servicios.", "newKnowledge.connectingProvider": "Conectando…", @@ -179,18 +178,14 @@ "newKnowledge.crawlFailedDescription": "El rastreador no pudo completar esta vista previa. Comprueba la URL, ajusta las opciones de rastreo o vuelve a intentarlo.", "newKnowledge.crawlFailedProvider": "{{provider}} informó de un error. Comprueba la conexión del proveedor y vuelve a intentarlo.", "newKnowledge.crawlFailedTimeout": "El rastreo agotó el tiempo de espera. Prueba una URL más específica o reduce el número máximo de páginas.", - "newKnowledge.crawlNotStarted": "Sin iniciar", "newKnowledge.crawlOptions": "Opciones de rastreo", "newKnowledge.crawlPreview": "Vista previa del rastreo", "newKnowledge.crawlProgress": "Progreso del rastreo de {{host}}", - "newKnowledge.crawlSetupUnavailableDescription": "La configuración de rastreo del sitio web está deshabilitada hasta que el flujo de trabajo de rastreo esté disponible.", - "newKnowledge.crawlSetupUnavailableTitle": "Firecrawl está listo", "newKnowledge.crawlStartFailed": "No se pudo iniciar la vista previa del rastreo. Vuelve a intentarlo.", "newKnowledge.crawlStopped": "Rastreo detenido", "newKnowledge.crawling": "Rastreando…", "newKnowledge.crawlingPages": "Rastreando {{host}}… {{count}} páginas encontradas", "newKnowledge.createFailed": "No pudimos crear esta base de conocimientos. Comprueba tu acceso y vuelve a intentarlo.", - "newKnowledge.createIllustrationTitle": "Conecta tu contenido una vez: cada aplicación responderá con el conocimiento más reciente.", "newKnowledge.createTitle": "Crear conocimiento", "newKnowledge.createdAt": "Creado", "newKnowledge.creators": "Creadores", @@ -220,7 +215,6 @@ "newKnowledge.documentColumn": "Documento", "newKnowledge.documentContentIncomplete": "Cargando el contenido restante del documento…", "newKnowledge.documentContents": "Contenido", - "newKnowledge.documentFacts": "Detalles del documento", "newKnowledge.documentFilterLabel": "Filtrar documentos por estado", "newKnowledge.documentLoadErrorDescription": "No se pudo cargar este documento. Inténtalo de nuevo.", "newKnowledge.documentLoadErrorTitle": "No se pudo cargar el documento", @@ -268,7 +262,6 @@ "newKnowledge.documentsSelected": "Documentos {{count}} seleccionados", "newKnowledge.documentsUnavailable": "— documentos", "newKnowledge.downloadDocuments": "Descargar", - "newKnowledge.editSource": "Editar", "newKnowledge.emptyChunk": "Fragmento vacío", "newKnowledge.emptyDescription": "Añade tus documentos y los agentes podrán leerlos directamente, sin configurar la indexación.", "newKnowledge.emptyTitle": "Una forma más inteligente de crear bases de conocimiento", @@ -344,7 +337,6 @@ "newKnowledge.processingTaskState.succeeded": "Completado", "newKnowledge.processingTaskState.superseded": "Reemplazado", "newKnowledge.providerConnected": "{{provider}} conectado", - "newKnowledge.providerLabel": "Seleccione un proveedor", "newKnowledge.providerLoadFailed": "Los proveedores no pudieron cargar", "newKnowledge.providerNotConfigured": "{{provider}} no está configurado", "newKnowledge.providerNotConfiguredDescription": "Agregue una clave API o una URL de punto final para rastrear sitios web con {{provider}}.", @@ -369,12 +361,10 @@ "newKnowledge.revisionState.superseded": "Anterior", "newKnowledge.rootUrl": "URL raíz", "newKnowledge.rootUrlPlaceholder": "https://docs.example.com", - "newKnowledge.routeUnavailable": "Esta sección estará disponible en el siguiente paso de configuración.", "newKnowledge.searchDocuments": "Buscar documentos", "newKnowledge.searchSources": "Buscar fuentes", "newKnowledge.selectAll": "Seleccionar todo", "newKnowledge.selectAllDocuments": "Seleccionar todos los documentos", - "newKnowledge.selectAllSources": "Seleccionar todos los orígenes", "newKnowledge.selectChunk": "Selecciona un fragmento para revisar su contenido.", "newKnowledge.selectFilesAndFolders": "Selecciona archivos y carpetas para sincronizar", "newKnowledge.skippedFailed": "Omitido (fallido)", @@ -384,7 +374,6 @@ "newKnowledge.sourceFilterLabel": "Filtrar fuentes por estado", "newKnowledge.sourceName": "Nombre de la fuente", "newKnowledge.sourceNamePlaceholder": "p. ej., rastreo de documentación del producto", - "newKnowledge.sourceNameRequired": "Introduce un nombre para la fuente.", "newKnowledge.sourceSetupBackendDependency": "La configuración del origen se muestra completa; la edición y la vista previa requieren el flujo de backend del origen en borrador.", "newKnowledge.sourceStatus.active": "Activo", "newKnowledge.sourceStatus.disabled": "Deshabilitado", @@ -431,7 +420,6 @@ "newKnowledge.unavailableDescription": "KnowledgeFS no está disponible para este espacio de trabajo ahora mismo.", "newKnowledge.unavailableTitle": "El nuevo conocimiento no está disponible", "newKnowledge.updated": "Actualizado el {{date}}", - "newKnowledge.updatedAt": "Última actualización", "newKnowledge.updatedColumn": "Actualizado", "newKnowledge.uploadCharactersUnavailable": "Caracteres: —", "newKnowledge.uploadDocuments": "Subir documentos", @@ -460,11 +448,9 @@ "retrievalSettings": "Configuración de recuperación", "retry": "Reintentar", "selectExternalKnowledgeAPI.placeholder": "Elegir una API de conocimiento externa", - "serviceApi.card.addApiKey": "Añadir clave API", "serviceApi.card.apiKey": "Clave API", "serviceApi.card.apiReference": "Referencia de la API", "serviceApi.card.endpoint": "Punto de enlace de la API de servicio", - "serviceApi.card.manageApiKey": "Gestionar clave API", "serviceApi.card.title": "API del servicio de backend", "serviceApi.disabled": "Discapacitado", "serviceApi.enabled": "En servicio", diff --git a/web/i18n/es-ES/deployments.json b/web/i18n/locales/es-ES/deployments.json similarity index 97% rename from web/i18n/es-ES/deployments.json rename to web/i18n/locales/es-ES/deployments.json index c106c952abd..9d84d1910d3 100644 --- a/web/i18n/es-ES/deployments.json +++ b/web/i18n/locales/es-ES/deployments.json @@ -27,7 +27,6 @@ "list.emptyTitle": "Aún no hay despliegues", "overview.chip.latest": "última", "status.RUNTIME_INSTANCE_STATUS_DEPLOYING": "Desplegando", - "status.RUNTIME_INSTANCE_STATUS_FAILED": "Despliegue fallido", "status.RUNTIME_INSTANCE_STATUS_INVALID": "No válido", "status.RUNTIME_INSTANCE_STATUS_READY": "En ejecución", "status.RUNTIME_INSTANCE_STATUS_UNDEPLOYED": "No desplegado", @@ -87,6 +86,5 @@ "studio.versionValue": "Valor de la versión", "studio.versionsBehind_one": "{{count}} versión por detrás", "studio.versionsBehind_other": "{{count}} versiones por detrás", - "versions.checkingReleaseContent": "Comprobando el contenido de la versión...", "versions.deployTo": "Desplegar a {{name}}" } diff --git a/web/i18n/es-ES/device-flow.json b/web/i18n/locales/es-ES/device-flow.json similarity index 100% rename from web/i18n/es-ES/device-flow.json rename to web/i18n/locales/es-ES/device-flow.json diff --git a/web/i18n/es-ES/education.json b/web/i18n/locales/es-ES/education.json similarity index 100% rename from web/i18n/es-ES/education.json rename to web/i18n/locales/es-ES/education.json diff --git a/web/i18n/es-ES/explore.json b/web/i18n/locales/es-ES/explore.json similarity index 100% rename from web/i18n/es-ES/explore.json rename to web/i18n/locales/es-ES/explore.json diff --git a/web/i18n/es-ES/layout.json b/web/i18n/locales/es-ES/layout.json similarity index 100% rename from web/i18n/es-ES/layout.json rename to web/i18n/locales/es-ES/layout.json diff --git a/web/i18n/es-ES/login.json b/web/i18n/locales/es-ES/login.json similarity index 100% rename from web/i18n/es-ES/login.json rename to web/i18n/locales/es-ES/login.json diff --git a/web/i18n/es-ES/oauth.json b/web/i18n/locales/es-ES/oauth.json similarity index 100% rename from web/i18n/es-ES/oauth.json rename to web/i18n/locales/es-ES/oauth.json diff --git a/web/i18n/es-ES/permission-keys.json b/web/i18n/locales/es-ES/permission-keys.json similarity index 100% rename from web/i18n/es-ES/permission-keys.json rename to web/i18n/locales/es-ES/permission-keys.json diff --git a/web/i18n/es-ES/permission.json b/web/i18n/locales/es-ES/permission.json similarity index 100% rename from web/i18n/es-ES/permission.json rename to web/i18n/locales/es-ES/permission.json diff --git a/web/i18n/es-ES/pipeline.json b/web/i18n/locales/es-ES/pipeline.json similarity index 100% rename from web/i18n/es-ES/pipeline.json rename to web/i18n/locales/es-ES/pipeline.json diff --git a/web/i18n/es-ES/plugin-tags.json b/web/i18n/locales/es-ES/plugin-tags.json similarity index 100% rename from web/i18n/es-ES/plugin-tags.json rename to web/i18n/locales/es-ES/plugin-tags.json diff --git a/web/i18n/es-ES/plugin-trigger.json b/web/i18n/locales/es-ES/plugin-trigger.json similarity index 100% rename from web/i18n/es-ES/plugin-trigger.json rename to web/i18n/locales/es-ES/plugin-trigger.json diff --git a/web/i18n/es-ES/plugin.json b/web/i18n/locales/es-ES/plugin.json similarity index 99% rename from web/i18n/es-ES/plugin.json rename to web/i18n/locales/es-ES/plugin.json index 2f36d92eab0..e2f7ab4db50 100644 --- a/web/i18n/es-ES/plugin.json +++ b/web/i18n/locales/es-ES/plugin.json @@ -277,7 +277,6 @@ "readmeInfo.noReadmeAvailable": "No hay archivo README disponible", "readmeInfo.title": "LEEME", "requestAPlugin": "Solicitar un plugin", - "requestSubmit": "Solicitar o publicar", "search": "Buscar", "searchCategories": "Categorías de búsqueda", "searchInMarketplace": "Buscar en Marketplace", diff --git a/web/i18n/es-ES/register.json b/web/i18n/locales/es-ES/register.json similarity index 100% rename from web/i18n/es-ES/register.json rename to web/i18n/locales/es-ES/register.json diff --git a/web/i18n/es-ES/run-log.json b/web/i18n/locales/es-ES/run-log.json similarity index 100% rename from web/i18n/es-ES/run-log.json rename to web/i18n/locales/es-ES/run-log.json diff --git a/web/i18n/es-ES/share.json b/web/i18n/locales/es-ES/share.json similarity index 100% rename from web/i18n/es-ES/share.json rename to web/i18n/locales/es-ES/share.json diff --git a/web/i18n/es-ES/skill.json b/web/i18n/locales/es-ES/skill.json similarity index 82% rename from web/i18n/es-ES/skill.json rename to web/i18n/locales/es-ES/skill.json index 839fd35d393..11c8ffec02b 100644 --- a/web/i18n/es-ES/skill.json +++ b/web/i18n/locales/es-ES/skill.json @@ -1,5 +1,4 @@ { - "skillManagement.clearTags": "Borrar etiquetas", "skillManagement.create": "Crear", "skillManagement.createFailed": "No se pudo crear habilidad.", "skillManagement.createSuccess": "Borrador creado.", @@ -13,12 +12,9 @@ "skillManagement.deleteFailed": "No se pudo eliminar la habilidad.", "skillManagement.deleteSuccess": "Skill eliminado.", "skillManagement.detail.addMetadata": "Agregar metadatos", - "skillManagement.detail.addMetadataDescription": "Agregue un campo frontal a este archivo Markdown.", "skillManagement.detail.addTag": "Agregar etiqueta", - "skillManagement.detail.addTagDescription": "Cree o vincule una etiqueta a este Skill. Las nuevas etiquetas se guardan con los metadatos Skill.", "skillManagement.detail.addTagSuccess": "Etiqueta agregada.", "skillManagement.detail.back": "Volver a Skills", - "skillManagement.detail.builder.applyFailed": "No se pudieron aplicar las actualizaciones del borrador.", "skillManagement.detail.builder.attach": "Adjuntar archivo", "skillManagement.detail.builder.attachFailed": "No se pudo adjuntar el archivo.", "skillManagement.detail.builder.attachLimit": "Puede adjuntar hasta 10 archivos.", @@ -26,22 +22,12 @@ "skillManagement.detail.builder.attachUnsupported": "Sólo se pueden adjuntar archivos de imágenes, texto y documentos.", "skillManagement.detail.builder.attachmentOnlyMessage": "Utilice el archivo adjunto para ayudar a mejorar esta habilidad.", "skillManagement.detail.builder.close": "Cerrar Skill Builder", - "skillManagement.detail.builder.compatibleModelsOnly": "Solo se muestran los modelos compatibles", "skillManagement.detail.builder.copyResponse": "Copiar respuesta", "skillManagement.detail.builder.copySuccess": "Respuesta copiada al portapapeles.", "skillManagement.detail.builder.editIntro": "He revisado esta habilidad. ¿Qué te gustaría ajustar?", "skillManagement.detail.builder.exampleIssueTriage": "Clasificación de problemas del cliente", "skillManagement.detail.builder.exampleOnboarding": "Guía de incorporación de nuevos empleados", "skillManagement.detail.builder.exampleSalesFollowUp": "Estrategia de seguimiento de leads de ventas", - "skillManagement.detail.builder.followUpDisplayName": "Utilice Aprobación de reembolso como nombre para mostrar", - "skillManagement.detail.builder.followUpNameIcon": "Aplicar el nombre y el icono sugeridos", - "skillManagement.detail.builder.fromMarketplace": "Desde el Marketplace", - "skillManagement.detail.builder.model": "GPT-4o", - "skillManagement.detail.builder.modelCredits.all": "Todos los créditos", - "skillManagement.detail.builder.modelCredits.configure": "Configuración requerida", - "skillManagement.detail.builder.modelCredits.exhausted": "Cuota agotada", - "skillManagement.detail.builder.modelProviderSettings": "Configuración del proveedor de modelos", - "skillManagement.detail.builder.modelSearch": "Buscar modelos...", "skillManagement.detail.builder.modifyPlaceholder": "Pídele a la IA que modifique esta habilidad...", "skillManagement.detail.builder.open": "Abrir Skill Builder", "skillManagement.detail.builder.placeholder": "Describe el escenario...", @@ -61,8 +47,6 @@ "skillManagement.detail.builder.thinkingUnavailable": "El modelo no devolvió ningún contenido de pensamiento.", "skillManagement.detail.builder.title": "Skill Builder", "skillManagement.detail.builder.tryExample": "Pruebe un ejemplo", - "skillManagement.detail.builder.voice": "Entrada de voz", - "skillManagement.detail.builder.voiceUnavailable": "La entrada de voz aún no está disponible.", "skillManagement.detail.cancelAddMetadata": "Cancelar agregar metadatos", "skillManagement.detail.closeFileTab": "Cerrar {{name}}", "skillManagement.detail.closeVersions": "Cerrar versiones", @@ -70,14 +54,10 @@ "skillManagement.detail.copyContentSuccess": "Contenido copiado al portapapeles.", "skillManagement.detail.copyFile": "Copiar", "skillManagement.detail.copyFileSuccess": "Archivo listo para pegar.", - "skillManagement.detail.copyVersionId": "Copiar ID", - "skillManagement.detail.copyVersionIdSuccess": "ID copiado al portapapeles.", "skillManagement.detail.createFile": "Nuevo archivo", - "skillManagement.detail.createFileDescription": "Ingrese una ruta de archivo relativa a la raíz de la habilidad.", "skillManagement.detail.createFileMenu": "Nuevo archivo...", "skillManagement.detail.createFileSuccess": "Archivo creado.", "skillManagement.detail.createFolder": "Nueva carpeta", - "skillManagement.detail.createFolderDescription": "Ingrese una ruta de carpeta relativa a la raíz de la habilidad.", "skillManagement.detail.createFolderMenu": "Nueva carpeta...", "skillManagement.detail.createFolderSuccess": "Carpeta creada.", "skillManagement.detail.createdBy": "Creado por {{name}}", @@ -100,10 +80,8 @@ "skillManagement.detail.fileMeta": "{{type}} · {{size}} bytes", "skillManagement.detail.fileMissing": "Archivo no encontrado.", "skillManagement.detail.fileOperationFailed": "La operación del archivo falló.", - "skillManagement.detail.files": "Archivos", "skillManagement.detail.latest": "último", "skillManagement.detail.loadFailed": "No se pudo cargar la habilidad.", - "skillManagement.detail.loadingTags": "Cargando etiquetas...", "skillManagement.detail.markdownLiveMode": "Vista previa en vivo", "skillManagement.detail.markdownSourceMode": "editor de código fuente", "skillManagement.detail.metadataKey": "nombre", @@ -113,10 +91,7 @@ "skillManagement.detail.nameThisVersion": "Nombra esta versión", "skillManagement.detail.noFileSelected": "Seleccione un archivo para obtener una vista previa.", "skillManagement.detail.noFiles": "Sin archivos.", - "skillManagement.detail.noSearchResults": "No hay archivos coincidentes.", - "skillManagement.detail.noSelectableTags": "No hay etiquetas disponibles.", "skillManagement.detail.noVersions": "Aún no hay versiones publicadas.", - "skillManagement.detail.pasteFile": "Pasta", "skillManagement.detail.pasteFileSuccess": "Archivo pegado.", "skillManagement.detail.previewUnsupported": "Este archivo no admite vista previa.", "skillManagement.detail.publish": "Publicar", @@ -128,9 +103,7 @@ "skillManagement.detail.publishUpdate": "Publicar actualización", "skillManagement.detail.published": "Publicado", "skillManagement.detail.publishedAt": "Publicado {{time}}", - "skillManagement.detail.publishedVersion": "Publicado #{{number}}", "skillManagement.detail.publishing": "Publicando...", - "skillManagement.detail.readonly": "Solo lectura", "skillManagement.detail.referenceFiles.confirm": "Ingresar Confirmar", "skillManagement.detail.referenceFiles.empty": "No hay archivos disponibles.", "skillManagement.detail.referenceFiles.livePlaceholder": "Escriba instrucciones para el agente. Escriba / para hacer referencia a archivos.", @@ -138,14 +111,10 @@ "skillManagement.detail.referenceFiles.title": "Archivos de referencia", "skillManagement.detail.referencedBy_one": "Utilizado por {{count}} agente", "skillManagement.detail.referencedBy_other": "Utilizado por {{count}} agentes", - "skillManagement.detail.removeMetadata": "Eliminar metadatos {{name}}", "skillManagement.detail.removeTag": "Quitar {{tag}}", "skillManagement.detail.removeTagSuccess": "Etiqueta eliminada.", - "skillManagement.detail.renameFile": "Cambiar nombre de archivo", - "skillManagement.detail.renameFileDescription": "Ingrese la nueva ruta relativa a la raíz de la habilidad.", "skillManagement.detail.renameFileSuccess": "Archivo renombrado.", "skillManagement.detail.renameSkillSuccess": "Skill renombrado.", - "skillManagement.detail.renameVersion": "Renombrar", "skillManagement.detail.renameVersionFailed": "No se pudo cambiar el nombre de la versión.", "skillManagement.detail.renameVersionPrompt": "Nombre de la versión", "skillManagement.detail.renameVersionSuccess": "Versión renombrada.", @@ -155,17 +124,14 @@ "skillManagement.detail.restoreVersionConfirmTitle": "¿Restaurar esta versión?", "skillManagement.detail.restoreVersionFailed": "No se pudo restaurar la versión.", "skillManagement.detail.restoreVersionSuccess": "Versión restaurada al borrador.", - "skillManagement.detail.save": "Guardar", "skillManagement.detail.saveConflictConfirmDescription": "Esta habilidad se actualizó en otro lugar. Cargue el último borrador para continuar editando. Sus cambios actuales serán descartados.", "skillManagement.detail.saveConflictConfirmTitle": "¿Cargar el último borrador?", "skillManagement.detail.saveConflictReload": "Cargar el último borrador", "skillManagement.detail.saveConflictStatus": "Conflicto detectado. Cargue el último borrador para continuar editando.", "skillManagement.detail.saveFailed": "No se pudo guardar el archivo.", - "skillManagement.detail.saveSuccess": "Archivo guardado.", "skillManagement.detail.saved": "Guardado", "skillManagement.detail.savedAt": "Guardado {{time}}", "skillManagement.detail.saving": "Guardando...", - "skillManagement.detail.searchFiles": "Buscar archivos", "skillManagement.detail.showMoreReferences": "Mostrar {{count}} más", "skillManagement.detail.skillDescriptionPlaceholder": "Describa qué hace esta habilidad y cuándo los agentes deben usarla.", "skillManagement.detail.skillNamePlaceholder": "Utilice letras minúsculas, números y guiones.", @@ -177,16 +143,12 @@ "skillManagement.detail.uploadConflict": "Ya existe un archivo con este nombre.", "skillManagement.detail.uploadFailureSummary": "Error en {{failed}} de archivos {{total}}", "skillManagement.detail.uploadFailureTitle": "Algunas cargas fallaron", - "skillManagement.detail.uploadFile": "Subir archivo", - "skillManagement.detail.uploadFileFailed": "Error al cargar el archivo.", "skillManagement.detail.uploadFileSuccess": "Archivo subido.", "skillManagement.detail.uploadFilesButton": "Cargar archivos {{count}}", "skillManagement.detail.uploadFilesFailedStatus_one": "Error al cargar el archivo {{count}}.", "skillManagement.detail.uploadFilesFailedStatus_other": "Error al cargar el archivo {{count}}.", "skillManagement.detail.uploadFilesMenu": "Subir archivos...", "skillManagement.detail.uploadFilesProgress": "Subiendo {{completed}}/{{total}}", - "skillManagement.detail.uploadFilesResult": "{{uploaded}} subido · {{failed}} falló", - "skillManagement.detail.uploadFilesStatus": "Estado de carga", "skillManagement.detail.uploadInvalidExtension": "Este tipo de archivo no es compatible.", "skillManagement.detail.uploadInvalidName": "Nombre no válido.", "skillManagement.detail.uploadInvalidPath": "Ruta de archivo no válida.", @@ -206,7 +168,6 @@ "skillManagement.detail.uploadWillReplace": "Reemplazará el archivo existente", "skillManagement.detail.uploadWillUseName": "Se cargará como {{name}}", "skillManagement.detail.versionHistory": "Abrir historial de versiones", - "skillManagement.detail.versionMeta": "{{hash}} · {{time}}", "skillManagement.detail.versionPublishNote": "Notas de la versión", "skillManagement.detail.versionPublishNotePlaceholder": "Describe este cambio", "skillManagement.detail.versionPublishedMeta": "{{time}} · {{name}}", @@ -227,8 +188,6 @@ "skillManagement.errors.missingSkillMd": "Este paquete no tiene SKILL.md. Agregue el archivo e impórtelo nuevamente.", "skillManagement.errors.nameConflict": "Ya existe una habilidad denominada \"{{name}}\". Cambie el nombre a SKILL.md e impórtelo nuevamente.", "skillManagement.errors.workspaceLimit": "Tu espacio de trabajo ha alcanzado el límite de 500 habilidades. Elimina una habilidad para agregar una nueva.", - "skillManagement.exportDialog.description": "Se exportará el borrador guardado actual de tu habilidad. Si tienes cambios sin guardar en el editor, guárdalos antes de exportar.", - "skillManagement.exportDialog.title": "Exportar el borrador guardado actual", "skillManagement.import": "Importar", "skillManagement.importFailed": "No se pudo importar la habilidad.", "skillManagement.importSuccess": "Skill importado.", @@ -236,12 +195,10 @@ "skillManagement.loadingError": "No se pudieron cargar las habilidades", "skillManagement.moreActions": "Más acciones para {{name}}", "skillManagement.noDescription": "Aún no hay descripción", - "skillManagement.noTags": "Sin etiquetas", "skillManagement.publishedAt": "Publicado {{time}}", "skillManagement.referenceCount_one": "{{count}} agente", "skillManagement.referenceCount_other": "{{count}} agentes", "skillManagement.searchLabel": "Buscar habilidades", "skillManagement.searchPlaceholder": "Buscar por nombre o descripción", - "skillManagement.tags": "Etiquetas", "skillManagement.title": "Habilidades" } diff --git a/web/i18n/es-ES/snippet.json b/web/i18n/locales/es-ES/snippet.json similarity index 100% rename from web/i18n/es-ES/snippet.json rename to web/i18n/locales/es-ES/snippet.json diff --git a/web/i18n/es-ES/time.json b/web/i18n/locales/es-ES/time.json similarity index 100% rename from web/i18n/es-ES/time.json rename to web/i18n/locales/es-ES/time.json diff --git a/web/i18n/es-ES/tools.json b/web/i18n/locales/es-ES/tools.json similarity index 100% rename from web/i18n/es-ES/tools.json rename to web/i18n/locales/es-ES/tools.json diff --git a/web/i18n/es-ES/workflow.json b/web/i18n/locales/es-ES/workflow.json similarity index 99% rename from web/i18n/es-ES/workflow.json rename to web/i18n/locales/es-ES/workflow.json index 97eccd0e277..31a6f82a1d8 100644 --- a/web/i18n/es-ES/workflow.json +++ b/web/i18n/locales/es-ES/workflow.json @@ -460,8 +460,6 @@ "nodes.agent.pluginNotInstalledDesc": "Este integración se instala desde GitHub. Por favor, vaya a Integraciones para reinstalar", "nodes.agent.roster.change": "Cambiar", "nodes.agent.roster.editInConsole": "Editar en Agents", - "nodes.agent.roster.editInConsoleDisabled": "Para editar agentes se requiere el rol Editor o uno superior.", - "nodes.agent.roster.editInConsoleDisabledRbac": "Solo los miembros con el rol Editor o uno superior, o con el permiso Gestionar Agents, pueden editar agentes.", "nodes.agent.roster.inlineSetup.description": "Una configuración puntual que existe solo en este nodo", "nodes.agent.roster.inlineSetup.name": "Configuración inline", "nodes.agent.roster.inlineSetup.type": "Solo en este nodo", diff --git a/web/i18n/fa-IR/agent-v-2.json b/web/i18n/locales/fa-IR/agent-v-2.json similarity index 99% rename from web/i18n/fa-IR/agent-v-2.json rename to web/i18n/locales/fa-IR/agent-v-2.json index 72522584459..629797e52d6 100644 --- a/web/i18n/fa-IR/agent-v-2.json +++ b/web/i18n/locales/fa-IR/agent-v-2.json @@ -69,6 +69,7 @@ "agentDetail.configure.answer.activity.runningCommands": "در حال اجرای دستورات", "agentDetail.configure.answer.duration.minute": "{{count}} دقیقه", "agentDetail.configure.answer.duration.second": "{{count}} ثانیه", + "agentDetail.configure.answer.responseEnded": "پاسخ پایان یافت", "agentDetail.configure.answer.thinking": "در حال فکر کردن", "agentDetail.configure.build.empty.communityEditionTip": "Community Edition با اینکه برای پیکربندی‌های استخراج‌شده نسخه‌بندی ارائه می‌کند، از نسخه‌بندی خود سیستم فایل پشتیبانی نمی‌کند. در حالت Build با اقدامات خود محتاط باشید، زیرا تغییرات روی سیستم فایل به‌صورت بلادرنگ انجام می‌شوند و همیشه نمی‌توان آن‌ها را به‌صورت تمیز بازگرداند. Community Edition انتخاب ایده‌آلی برای خدمت‌رسانی به مخاطبان خارجی گسترده نیست.", "agentDetail.configure.build.empty.description": "آنچه می‌خواهید را توضیح دهید تا فرم سمت چپ در طول گفتگو تکمیل شود.", @@ -269,7 +270,6 @@ "agentDetail.configure.workingDirectory.breadcrumbLabel": "مسیر سیستم فایل", "agentDetail.configure.workingDirectory.description": "فایل‌هایی که عامل هنگام اجرا می‌خواند و می‌نویسد. هر چیزی که تولید کند، مثل qna_report.pdf، اینجا نمایش داده می‌شود.", "agentDetail.configure.workingDirectory.fileSystem": "سیستم فایل", - "agentDetail.configure.workingDirectory.open": "باز کردن پوشه کاری", "agentDetail.configure.workingDirectory.persistentFiles": "ماندگار", "agentDetail.configure.workingDirectory.persistentFilesTooltip": "فایل‌هایی که عامل نگه می‌دارد و می‌تواند در هر جایی که اجرا می‌شود، از جمله گفتگوهای کاربران نهایی و گردش‌های کاری، از آن‌ها استفاده کند. تغییراتی که در این گفتگوی ساخت روی این فایل‌ها انجام می‌دهید، پس از کلیک روی «اعمال» اجرایی می‌شوند.", "agentDetail.configure.workingDirectory.temporaryFiles": "موقت", @@ -348,7 +348,6 @@ "agentDetail.sections.logs": "گزارش‌ها", "agentDetail.sections.monitoring": "نظارت", "agentDetail.title": "عامل", - "agentDetail.type": "عامل", "agentDetail.versionHistory.empty": "هنوز نسخه‌ای وجود ندارد", "agentDetail.versionHistory.exitVersions": "خروج از نسخه‌ها", "agentDetail.versionHistory.filter": "فیلتر نسخه‌ها", @@ -408,8 +407,5 @@ "roster.sort.lastModified": "آخرین تغییر", "roster.sort.recentlyCreated": "تازه‌ترین ایجاد", "roster.title": "عامل‌ها", - "roster.usageStatus.draft": "پیش‌نویس", - "skillManagement.detail.uploadFilesFailedStatus_one": "بارگذاری {{count}} فایل ناموفق بود.", - "skillManagement.detail.uploadFilesFailedStatus_other": "بارگذاری {{count}} فایل ناموفق بود.", - "skillManagement.title": "Skills" + "roster.usageStatus.draft": "پیش‌نویس" } diff --git a/web/i18n/fa-IR/app-annotation.json b/web/i18n/locales/fa-IR/app-annotation.json similarity index 100% rename from web/i18n/fa-IR/app-annotation.json rename to web/i18n/locales/fa-IR/app-annotation.json diff --git a/web/i18n/fa-IR/app-api.json b/web/i18n/locales/fa-IR/app-api.json similarity index 100% rename from web/i18n/fa-IR/app-api.json rename to web/i18n/locales/fa-IR/app-api.json diff --git a/web/i18n/fa-IR/app-debug.json b/web/i18n/locales/fa-IR/app-debug.json similarity index 100% rename from web/i18n/fa-IR/app-debug.json rename to web/i18n/locales/fa-IR/app-debug.json diff --git a/web/i18n/fa-IR/app-log.json b/web/i18n/locales/fa-IR/app-log.json similarity index 97% rename from web/i18n/fa-IR/app-log.json rename to web/i18n/locales/fa-IR/app-log.json index e2c3f2f94e8..79cb73a2c55 100644 --- a/web/i18n/fa-IR/app-log.json +++ b/web/i18n/locales/fa-IR/app-log.json @@ -4,11 +4,9 @@ "agentLogDetail.iteration": "تکرار", "agentLogDetail.iterations": "تکرارها", "agentLogDetail.toolUsed": "ابزار استفاده شده", - "archives.action.downloadMonth": "دانلود بایگانی {{month}}", "archives.action.downloadReady": "دانلود بایگانی آماده است.", "archives.action.prepareDownload": "آماده‌سازی دانلود", "archives.action.prepareFailed": "آماده‌سازی دانلود بایگانی ناموفق بود.", - "archives.action.prepareMonth": "آماده‌سازی دانلود بایگانی {{month}}", "archives.action.prepareStarted": "دانلود بایگانی در حال آماده‌سازی است.", "archives.action.preparing": "در حال آماده‌سازی", "archives.description": "خروجی‌های CSV ماهانه از لاگ‌های بایگانی‌شده اجرای workflow در این فضای کاری را دانلود کنید.", diff --git a/web/i18n/fa-IR/app-overview.json b/web/i18n/locales/fa-IR/app-overview.json similarity index 100% rename from web/i18n/fa-IR/app-overview.json rename to web/i18n/locales/fa-IR/app-overview.json diff --git a/web/i18n/fa-IR/app.json b/web/i18n/locales/fa-IR/app.json similarity index 100% rename from web/i18n/fa-IR/app.json rename to web/i18n/locales/fa-IR/app.json diff --git a/web/i18n/fa-IR/billing.json b/web/i18n/locales/fa-IR/billing.json similarity index 100% rename from web/i18n/fa-IR/billing.json rename to web/i18n/locales/fa-IR/billing.json diff --git a/web/i18n/fa-IR/common.json b/web/i18n/locales/fa-IR/common.json similarity index 97% rename from web/i18n/fa-IR/common.json rename to web/i18n/locales/fa-IR/common.json index d3b439caf99..f3a6381e4de 100644 --- a/web/i18n/fa-IR/common.json +++ b/web/i18n/locales/fa-IR/common.json @@ -209,9 +209,7 @@ "mainNav.marketplace": "بازارچه", "mainNav.skills": "Skills", "mainNav.webApps.noResults": "هیچ برنامه وبی یافت نشد", - "mainNav.webApps.openApp": "باز کردن برنامه وب {{name}}", "mainNav.webApps.searchPlaceholder": "جستجوی برنامه‌های وب", - "mainNav.workspace.credits": "{{count}} اعتبار", "mainNav.workspace.creditsUnit": "اعتبار", "mainNav.workspace.inviteMembers": "Invite members", "mainNav.workspace.noResults": "هیچ فضای کاری یافت نشد", @@ -233,7 +231,6 @@ "members.datasetOperatorTip": "فقط می‌تواند پایگاه دانش را مدیریت کند", "members.editRole": "ویرایش نقش", "members.editorTip": "می‌تواند برنامه‌ها را بسازد و ویرایش کند", - "members.email": "ایمیل", "members.emailInvalid": "فرمت ایمیل نامعتبر است", "members.emailNotSetup": "سرور ایمیل راه‌اندازی نشده است، بنابراین ایمیل‌های دعوت نمی‌توانند ارسال شوند. لطفاً کاربران را از لینک دعوت که پس از دعوت صادر خواهد شد مطلع کنید。", "members.emailPlaceholder": "نشانی‌های ایمیل را تایپ یا جای‌گذاری کنید…", @@ -250,7 +247,6 @@ "members.inviteLimitExceeded": "برای همه گیرندگان ظرفیت کافی وجود ندارد.", "members.inviteTeamMember": "افزودن عضو تیم", "members.inviteTeamMemberTip": "آنها می‌توانند پس از ورود به سیستم، مستقیماً به داده‌های تیم شما دسترسی پیدا کنند.", - "members.invitedAsRole": "به عنوان {{role}} دعوت شده", "members.lastActive": "آخرین فعالیت", "members.memberActions": "اقدامات عضو", "members.memberDetails.assign": "تخصیص", @@ -258,8 +254,6 @@ "members.memberDetails.assignedRoles": "نقش‌های تخصیص‌یافته", "members.memberDetails.customGroup": "سفارشی‌شده", "members.memberDetails.generalGroup": "عمومی", - "members.memberDetails.openAria": "باز کردن جزئیات عضو برای {{name}}", - "members.memberDetails.roleActionsAria": "باز کردن اقدامات برای نقش {{role}}", "members.memberDetails.roleNoPermissionSummary": "نقش فعلی هیچ مجوزی ندارد.", "members.memberDetails.rolePermissionSummary": "{{role}} می‌تواند {{permissions}}", "members.memberDetails.title": "جزئیات عضو", @@ -370,7 +364,6 @@ "modelProvider.card.usagePriorityTip": "تعیین کنید که هنگام اجرای مدل‌ها کدام منبع اول استفاده شود.", "modelProvider.configureTip": "api-key را راه اندازی کنید یا مدل را برای استفاده اضافه کنید", "modelProvider.confirmDelete": "تأیید حذف؟", - "modelProvider.credits": "اعتبار پیام", "modelProvider.defaultConfig": "پیکربندی پیش‌فرض", "modelProvider.discoverMore": "اطلاعات بیشتر در", "modelProvider.embeddingModel.key": "مدل تعبیه", @@ -427,7 +420,6 @@ "modelProvider.selector.noProviderConfiguredDesc": "در Marketplace جستجو کنید تا یکی نصب کنید، یا ارائه‌دهندگان را در تنظیمات پیکربندی کنید.", "modelProvider.selector.onlyCompatibleModelsShown": "فقط مدل‌های سازگار نمایش داده می‌شوند", "modelProvider.selector.showIncompatibleModels": "نمایش مدل‌های ناسازگار", - "modelProvider.selector.suggestion": "پیشنهاد", "modelProvider.selector.suggestionTip": "پیشنهادی برای وظایف عامل", "modelProvider.showModels": "نمایش مدل‌ها", "modelProvider.speechToTextModel.key": "مدل تبدیل گفتار به متن", @@ -458,6 +450,8 @@ "operation.confirmAction": "لطفاً اقدام خود را تأیید کنید.", "operation.copied": "کپی", "operation.copy": "کپی", + "operation.copyErrorDetails": "کپی جزئیات خطا", + "operation.copyErrorFailed": "کپی ناموفق بود. تلاش مجدد", "operation.copyImage": "کپی تصویر", "operation.create": "ایجاد", "operation.deSelectAll": "همه را انتخاب نکنید", @@ -475,7 +469,6 @@ "operation.format": "قالب", "operation.imageCopied": "تصویر کپی شده", "operation.imageCopyFailed": "تصویر کپی نشد. لطفاً به‌جای آن تصویر را دانلود کنید.", - "operation.imageDownloaded": "تصویر دانلود شد", "operation.in": "در", "operation.learnMore": "اطلاعات بیشتر", "operation.log": "گزارش", @@ -512,7 +505,6 @@ "operation.sure": "مطمئن هستم", "operation.toggleFullscreen": "تغییر حالت تمام‌صفحه", "operation.toggleMute": "تغییر حالت بی‌صدا", - "operation.upload": "Upload", "operation.view": "مشاهده", "operation.viewDetails": "دیدن جزئیات", "operation.viewMore": "بیشتر ببینید", @@ -542,7 +534,6 @@ "promptEditor.requestURL.item.title": "URL درخواست", "promptEditor.variable.modal.add": "متغیر جدید", "promptEditor.variable.modal.addTool": "ابزار جدید", - "provider.anthropicHosted.trialQuotaTip": "سهمیه آزمایشی Anthropic شما در تاریخ 2025/03/11 منقضی می شود و پس از آن دیگر در دسترس نخواهد بود. لطفا به موقع از آن استفاده کنید.", "provider.encrypted.back": " رمزگذاری و ذخیره خواهد شد.", "provider.encrypted.front": "کلید API شما با استفاده از فناوری", "provider.validating": "در حال اعتبارسنجی کلید...", @@ -565,7 +556,6 @@ "settings.preferences": "Preferences", "settings.provider": "ارائه دهنده مدل", "settings.resourceAccess": "دسترسی به منابع", - "settings.resourceAccessDescription": "قوانین مجوزی را که برنامه‌ها و پایگاه‌های دانش می‌توانند استفاده کنند پیکربندی کنید. قوانین مجوز می‌توانند در پیکربندی‌های خاص دسترسی به منابع به اعضا تخصیص داده شوند.", "settings.rolesAndPermissions": "نقش‌ها و مجوزها", "settings.settings": "Settings", "settings.swaggerAPIAsTool": "Swagger API as Tool", @@ -695,7 +685,6 @@ "userProfile.helpCenter": "راهنما", "userProfile.logout": "خروج", "userProfile.roadmap": "نقشه راه", - "userProfile.settings": "تنظیمات", "userProfile.workspace": "فضای کاری", "voice.language.arTN": "عربی تونسی", "voice.language.deDE": "آلمانی", diff --git a/web/i18n/fa-IR/custom.json b/web/i18n/locales/fa-IR/custom.json similarity index 100% rename from web/i18n/fa-IR/custom.json rename to web/i18n/locales/fa-IR/custom.json diff --git a/web/i18n/fa-IR/dataset-creation.json b/web/i18n/locales/fa-IR/dataset-creation.json similarity index 100% rename from web/i18n/fa-IR/dataset-creation.json rename to web/i18n/locales/fa-IR/dataset-creation.json diff --git a/web/i18n/fa-IR/dataset-documents.json b/web/i18n/locales/fa-IR/dataset-documents.json similarity index 100% rename from web/i18n/fa-IR/dataset-documents.json rename to web/i18n/locales/fa-IR/dataset-documents.json diff --git a/web/i18n/fa-IR/dataset-hit-testing.json b/web/i18n/locales/fa-IR/dataset-hit-testing.json similarity index 100% rename from web/i18n/fa-IR/dataset-hit-testing.json rename to web/i18n/locales/fa-IR/dataset-hit-testing.json diff --git a/web/i18n/fa-IR/dataset-pipeline.json b/web/i18n/locales/fa-IR/dataset-pipeline.json similarity index 100% rename from web/i18n/fa-IR/dataset-pipeline.json rename to web/i18n/locales/fa-IR/dataset-pipeline.json diff --git a/web/i18n/fa-IR/dataset-settings.json b/web/i18n/locales/fa-IR/dataset-settings.json similarity index 100% rename from web/i18n/fa-IR/dataset-settings.json rename to web/i18n/locales/fa-IR/dataset-settings.json diff --git a/web/i18n/fa-IR/dataset.json b/web/i18n/locales/fa-IR/dataset.json similarity index 97% rename from web/i18n/fa-IR/dataset.json rename to web/i18n/locales/fa-IR/dataset.json index 318d6f1c8b7..546da7231af 100644 --- a/web/i18n/fa-IR/dataset.json +++ b/web/i18n/locales/fa-IR/dataset.json @@ -163,7 +163,6 @@ "newKnowledge.configureProvider": "{{provider}} را پیکربندی کنید", "newKnowledge.connectNotion": "اتصال Notion", "newKnowledge.connectProvider": "{{provider}} را وصل کنید", - "newKnowledge.connectProviderGeneric": "اتصال ارائه‌دهنده", "newKnowledge.connectSource": "اتصال منبع", "newKnowledge.connectSourceDescription": "محتوا با Notion، وب‌سایت‌ها و سرویس‌های دیگر همگام می‌ماند.", "newKnowledge.connectingProvider": "در حال اتصال…", @@ -179,18 +178,14 @@ "newKnowledge.crawlFailedDescription": "خزنده نتوانست این پیش‌نمایش را کامل کند. نشانی را بررسی کنید، گزینه‌های پیمایش را تغییر دهید یا دوباره تلاش کنید.", "newKnowledge.crawlFailedProvider": "{{provider}} خطایی گزارش کرد. اتصال ارائه‌دهنده را بررسی کنید و دوباره تلاش کنید.", "newKnowledge.crawlFailedTimeout": "زمان پیمایش به پایان رسید. نشانی دقیق‌تری را امتحان کنید یا حداکثر تعداد صفحه‌ها را کاهش دهید.", - "newKnowledge.crawlNotStarted": "شروع نشده", "newKnowledge.crawlOptions": "گزینه‌های پیمایش", "newKnowledge.crawlPreview": "پیش‌نمایش خزش", "newKnowledge.crawlProgress": "پیشرفت پیمایش {{host}}", - "newKnowledge.crawlSetupUnavailableDescription": "پیکربندی خزیدن وب‌سایت تا زمانی که گردش کار خزیدن در دسترس نباشد، غیرفعال است.", - "newKnowledge.crawlSetupUnavailableTitle": "Firecrawl آماده است", "newKnowledge.crawlStartFailed": "پیش‌نمایش پیمایش آغاز نشد. دوباره تلاش کنید.", "newKnowledge.crawlStopped": "پیمایش متوقف شد", "newKnowledge.crawling": "در حال پیمایش…", "newKnowledge.crawlingPages": "در حال پیمایش {{host}}… {{count}} صفحه پیدا شد", "newKnowledge.createFailed": "ما نتوانستیم این پایگاه دانش را ایجاد کنیم. دسترسی خود را بررسی کنید و دوباره امتحان کنید.", - "newKnowledge.createIllustrationTitle": "محتوای خود را یک‌بار متصل کنید — هر برنامه با تازه‌ترین دانش پاسخ می‌دهد.", "newKnowledge.createTitle": "ایجاد دانش", "newKnowledge.createdAt": "ایجادشده", "newKnowledge.creators": "ایجادکنندگان", @@ -220,7 +215,6 @@ "newKnowledge.documentColumn": "سند", "newKnowledge.documentContentIncomplete": "در حال بارگذاری ادامه محتوای سند…", "newKnowledge.documentContents": "محتوا", - "newKnowledge.documentFacts": "جزئیات سند", "newKnowledge.documentFilterLabel": "اسناد را بر اساس وضعیت فیلتر کنید", "newKnowledge.documentLoadErrorDescription": "این سند بارگیری نشد. دوباره تلاش کنید.", "newKnowledge.documentLoadErrorTitle": "سند بارگیری نشد", @@ -268,7 +262,6 @@ "newKnowledge.documentsSelected": "اسناد {{count}} انتخاب شد", "newKnowledge.documentsUnavailable": "— سند", "newKnowledge.downloadDocuments": "دانلود کنید", - "newKnowledge.editSource": "ویرایش", "newKnowledge.emptyChunk": "قطعه خالی", "newKnowledge.emptyDescription": "اسناد خود را اضافه کنید تا عامل‌ها بتوانند مستقیماً آن‌ها را بخوانند — بدون نیاز به تنظیم نمایه‌سازی.", "newKnowledge.emptyTitle": "روشی هوشمندتر برای ساخت پایگاه‌های دانش", @@ -344,7 +337,6 @@ "newKnowledge.processingTaskState.succeeded": "تکمیل شد", "newKnowledge.processingTaskState.superseded": "جایگزین شد", "newKnowledge.providerConnected": "{{provider}} متصل شد", - "newKnowledge.providerLabel": "یک ارائه دهنده را انتخاب کنید", "newKnowledge.providerLoadFailed": "ارائه‌دهندگان نتوانستند بارگیری کنند", "newKnowledge.providerNotConfigured": "{{provider}} پیکربندی نشده است", "newKnowledge.providerNotConfiguredDescription": "برای خزیدن وب‌سایت‌ها با {{provider}}، یک کلید API یا URL نقطه پایانی اضافه کنید.", @@ -369,12 +361,10 @@ "newKnowledge.revisionState.superseded": "قبلی", "newKnowledge.rootUrl": "نشانی ریشه", "newKnowledge.rootUrlPlaceholder": "https://docs.example.com", - "newKnowledge.routeUnavailable": "این بخش در مرحله راه اندازی بعدی در دسترس خواهد بود.", "newKnowledge.searchDocuments": "جستجوی اسناد", "newKnowledge.searchSources": "منابع جستجو", "newKnowledge.selectAll": "انتخاب همه", "newKnowledge.selectAllDocuments": "همه اسناد را انتخاب کنید", - "newKnowledge.selectAllSources": "انتخاب همه منابع", "newKnowledge.selectChunk": "یک قطعه را برای بررسی محتوای آن انتخاب کنید.", "newKnowledge.selectFilesAndFolders": "فایل‌ها و پوشه‌ها را برای همگام‌سازی انتخاب کنید", "newKnowledge.skippedFailed": "رد شد (ناموفق)", @@ -384,7 +374,6 @@ "newKnowledge.sourceFilterLabel": "منابع را بر اساس وضعیت فیلتر کنید", "newKnowledge.sourceName": "نام منبع", "newKnowledge.sourceNamePlaceholder": "مثلاً پیمایش مستندات محصول", - "newKnowledge.sourceNameRequired": "نام منبع را وارد کنید.", "newKnowledge.sourceSetupBackendDependency": "تنظیم منبع به‌طور کامل نمایش داده شده است؛ ویرایش و پیش‌نمایش به گردش‌کار پشتیبان منبع پیش‌نویس نیاز دارد.", "newKnowledge.sourceStatus.active": "فعال", "newKnowledge.sourceStatus.disabled": "غیرفعال شد", @@ -431,7 +420,6 @@ "newKnowledge.unavailableDescription": "KnowledgeFS اکنون برای این فضای کاری در دسترس نیست.", "newKnowledge.unavailableTitle": "دانش جدید در دسترس نیست", "newKnowledge.updated": "به‌روزرسانی در {{date}}", - "newKnowledge.updatedAt": "آخرین به‌روزرسانی", "newKnowledge.updatedColumn": "به روز شد", "newKnowledge.uploadCharactersUnavailable": "نویسه‌ها: —", "newKnowledge.uploadDocuments": "اسناد را بارگذاری کنید", @@ -460,11 +448,9 @@ "retrievalSettings": "تنظیمات بازیابی", "retry": "تلاش مجدد", "selectExternalKnowledgeAPI.placeholder": "یک API دانش خارجی را انتخاب کنید", - "serviceApi.card.addApiKey": "افزودن کلید API", "serviceApi.card.apiKey": "کلید API", "serviceApi.card.apiReference": "مرجع API", "serviceApi.card.endpoint": "نقطه انتهایی رابط برنامه‌نویسی سرویس", - "serviceApi.card.manageApiKey": "مدیریت کلید API", "serviceApi.card.title": "رابط برنامه‌نویسی سرویس پشتیبان", "serviceApi.disabled": "معلول", "serviceApi.enabled": "در حال خدمت", diff --git a/web/i18n/fa-IR/deployments.json b/web/i18n/locales/fa-IR/deployments.json similarity index 97% rename from web/i18n/fa-IR/deployments.json rename to web/i18n/locales/fa-IR/deployments.json index a92a966c5ca..9b2b81f6da9 100644 --- a/web/i18n/fa-IR/deployments.json +++ b/web/i18n/locales/fa-IR/deployments.json @@ -27,7 +27,6 @@ "list.emptyTitle": "هنوز استقراری وجود ندارد", "overview.chip.latest": "آخرین", "status.RUNTIME_INSTANCE_STATUS_DEPLOYING": "در حال استقرار", - "status.RUNTIME_INSTANCE_STATUS_FAILED": "استقرار ناموفق", "status.RUNTIME_INSTANCE_STATUS_INVALID": "نامعتبر", "status.RUNTIME_INSTANCE_STATUS_READY": "در حال اجرا", "status.RUNTIME_INSTANCE_STATUS_UNDEPLOYED": "مستقر نشده", @@ -87,6 +86,5 @@ "studio.versionValue": "مقدار نسخه", "studio.versionsBehind_one": "{{count}} نسخه عقب‌تر", "studio.versionsBehind_other": "{{count}} نسخه عقب‌تر", - "versions.checkingReleaseContent": "در حال بررسی محتوای نسخه...", "versions.deployTo": "استقرار به {{name}}" } diff --git a/web/i18n/fa-IR/device-flow.json b/web/i18n/locales/fa-IR/device-flow.json similarity index 100% rename from web/i18n/fa-IR/device-flow.json rename to web/i18n/locales/fa-IR/device-flow.json diff --git a/web/i18n/fa-IR/education.json b/web/i18n/locales/fa-IR/education.json similarity index 100% rename from web/i18n/fa-IR/education.json rename to web/i18n/locales/fa-IR/education.json diff --git a/web/i18n/fa-IR/explore.json b/web/i18n/locales/fa-IR/explore.json similarity index 100% rename from web/i18n/fa-IR/explore.json rename to web/i18n/locales/fa-IR/explore.json diff --git a/web/i18n/fa-IR/layout.json b/web/i18n/locales/fa-IR/layout.json similarity index 100% rename from web/i18n/fa-IR/layout.json rename to web/i18n/locales/fa-IR/layout.json diff --git a/web/i18n/fa-IR/login.json b/web/i18n/locales/fa-IR/login.json similarity index 100% rename from web/i18n/fa-IR/login.json rename to web/i18n/locales/fa-IR/login.json diff --git a/web/i18n/fa-IR/oauth.json b/web/i18n/locales/fa-IR/oauth.json similarity index 100% rename from web/i18n/fa-IR/oauth.json rename to web/i18n/locales/fa-IR/oauth.json diff --git a/web/i18n/fa-IR/permission-keys.json b/web/i18n/locales/fa-IR/permission-keys.json similarity index 100% rename from web/i18n/fa-IR/permission-keys.json rename to web/i18n/locales/fa-IR/permission-keys.json diff --git a/web/i18n/fa-IR/permission.json b/web/i18n/locales/fa-IR/permission.json similarity index 100% rename from web/i18n/fa-IR/permission.json rename to web/i18n/locales/fa-IR/permission.json diff --git a/web/i18n/fa-IR/pipeline.json b/web/i18n/locales/fa-IR/pipeline.json similarity index 100% rename from web/i18n/fa-IR/pipeline.json rename to web/i18n/locales/fa-IR/pipeline.json diff --git a/web/i18n/fa-IR/plugin-tags.json b/web/i18n/locales/fa-IR/plugin-tags.json similarity index 100% rename from web/i18n/fa-IR/plugin-tags.json rename to web/i18n/locales/fa-IR/plugin-tags.json diff --git a/web/i18n/fa-IR/plugin-trigger.json b/web/i18n/locales/fa-IR/plugin-trigger.json similarity index 100% rename from web/i18n/fa-IR/plugin-trigger.json rename to web/i18n/locales/fa-IR/plugin-trigger.json diff --git a/web/i18n/fa-IR/plugin.json b/web/i18n/locales/fa-IR/plugin.json similarity index 99% rename from web/i18n/fa-IR/plugin.json rename to web/i18n/locales/fa-IR/plugin.json index d621fca76eb..48d1e9f19de 100644 --- a/web/i18n/fa-IR/plugin.json +++ b/web/i18n/locales/fa-IR/plugin.json @@ -277,7 +277,6 @@ "readmeInfo.noReadmeAvailable": "بدون پرونده README موجود", "readmeInfo.title": "خواندنی", "requestAPlugin": "درخواست یک افزونه", - "requestSubmit": "درخواست یا انتشار", "search": "جستجو", "searchCategories": "دسته بندی ها را جستجو کنید", "searchInMarketplace": "جستجو در Marketplace", diff --git a/web/i18n/fa-IR/register.json b/web/i18n/locales/fa-IR/register.json similarity index 100% rename from web/i18n/fa-IR/register.json rename to web/i18n/locales/fa-IR/register.json diff --git a/web/i18n/fa-IR/run-log.json b/web/i18n/locales/fa-IR/run-log.json similarity index 100% rename from web/i18n/fa-IR/run-log.json rename to web/i18n/locales/fa-IR/run-log.json diff --git a/web/i18n/fa-IR/share.json b/web/i18n/locales/fa-IR/share.json similarity index 100% rename from web/i18n/fa-IR/share.json rename to web/i18n/locales/fa-IR/share.json diff --git a/web/i18n/fa-IR/skill.json b/web/i18n/locales/fa-IR/skill.json similarity index 82% rename from web/i18n/fa-IR/skill.json rename to web/i18n/locales/fa-IR/skill.json index 82f13ebe6c0..7d340c2285b 100644 --- a/web/i18n/fa-IR/skill.json +++ b/web/i18n/locales/fa-IR/skill.json @@ -1,5 +1,4 @@ { - "skillManagement.clearTags": "برچسب ها را پاک کنید", "skillManagement.create": "ایجاد", "skillManagement.createFailed": "ایجاد مهارت ناموفق بود.", "skillManagement.createSuccess": "پیش نویس ایجاد شد.", @@ -13,12 +12,9 @@ "skillManagement.deleteFailed": "مهارت حذف نشد.", "skillManagement.deleteSuccess": "Skill حذف شد.", "skillManagement.detail.addMetadata": "متادیتا اضافه کنید", - "skillManagement.detail.addMetadataDescription": "یک فیلد frontmatter به این فایل Markdown اضافه کنید.", "skillManagement.detail.addTag": "برچسب اضافه کنید", - "skillManagement.detail.addTagDescription": "یک برچسب به این Skill ایجاد یا متصل کنید. برچسب‌های جدید با ابرداده Skill ذخیره می‌شوند.", "skillManagement.detail.addTagSuccess": "برچسب اضافه شد.", "skillManagement.detail.back": "بازگشت به Skills", - "skillManagement.detail.builder.applyFailed": "به‌روزرسانی‌های پیش‌نویس اعمال نشد.", "skillManagement.detail.builder.attach": "فایل پیوست کنید", "skillManagement.detail.builder.attachFailed": "فایل پیوست نشد.", "skillManagement.detail.builder.attachLimit": "می توانید حداکثر 10 فایل را پیوست کنید.", @@ -26,22 +22,12 @@ "skillManagement.detail.builder.attachUnsupported": "فقط تصاویر، متن و فایل های سند را می توان پیوست کرد.", "skillManagement.detail.builder.attachmentOnlyMessage": "برای کمک به بهبود این مهارت از فایل پیوست استفاده کنید.", "skillManagement.detail.builder.close": "Skill Builder را ببندید", - "skillManagement.detail.builder.compatibleModelsOnly": "فقط مدل‌های سازگار نمایش داده می‌شوند", "skillManagement.detail.builder.copyResponse": "پاسخ را کپی کنید", "skillManagement.detail.builder.copySuccess": "پاسخ در کلیپ بورد کپی شد.", "skillManagement.detail.builder.editIntro": "من این مهارت را مرور کردم. چه چیزی را دوست دارید تنظیم کنید؟", "skillManagement.detail.builder.exampleIssueTriage": "تریاژ مسئله مشتری", "skillManagement.detail.builder.exampleOnboarding": "راهنمای استخدام جدید", "skillManagement.detail.builder.exampleSalesFollowUp": "استراتژی پیگیری سرنخ فروش", - "skillManagement.detail.builder.followUpDisplayName": "از تأیید بازپرداخت به عنوان نام نمایشی استفاده کنید", - "skillManagement.detail.builder.followUpNameIcon": "نام و نماد پیشنهادی را اعمال کنید", - "skillManagement.detail.builder.fromMarketplace": "از Marketplace", - "skillManagement.detail.builder.model": "GPT-4o", - "skillManagement.detail.builder.modelCredits.all": "تمام اعتبارات", - "skillManagement.detail.builder.modelCredits.configure": "پیکربندی الزامی است", - "skillManagement.detail.builder.modelCredits.exhausted": "سهمیه تمام شده", - "skillManagement.detail.builder.modelProviderSettings": "تنظیمات ارائه‌دهنده مدل", - "skillManagement.detail.builder.modelSearch": "جستجوی مدل‌ها...", "skillManagement.detail.builder.modifyPlaceholder": "از هوش مصنوعی بخواهید این مهارت را اصلاح کند...", "skillManagement.detail.builder.open": "Skill Builder را باز کنید", "skillManagement.detail.builder.placeholder": "سناریو را شرح دهید ...", @@ -61,8 +47,6 @@ "skillManagement.detail.builder.thinkingUnavailable": "هیچ محتوای فکری توسط مدل برگردانده نشد.", "skillManagement.detail.builder.title": "Skill Builder", "skillManagement.detail.builder.tryExample": "یک مثال را امتحان کنید", - "skillManagement.detail.builder.voice": "ورودی صوتی", - "skillManagement.detail.builder.voiceUnavailable": "ورودی صوتی هنوز در دسترس نیست.", "skillManagement.detail.cancelAddMetadata": "لغو افزودن فراداده", "skillManagement.detail.closeFileTab": "بستن {{name}}", "skillManagement.detail.closeVersions": "بستن نسخه ها", @@ -70,14 +54,10 @@ "skillManagement.detail.copyContentSuccess": "محتوا در کلیپ بورد کپی شد.", "skillManagement.detail.copyFile": "کپی", "skillManagement.detail.copyFileSuccess": "فایل آماده برای چسباندن", - "skillManagement.detail.copyVersionId": "کپی شناسه", - "skillManagement.detail.copyVersionIdSuccess": "شناسه در کلیپ بورد کپی شد.", "skillManagement.detail.createFile": "فایل جدید", - "skillManagement.detail.createFileDescription": "یک مسیر فایل نسبت به ریشه مهارت وارد کنید.", "skillManagement.detail.createFileMenu": "فایل جدید...", "skillManagement.detail.createFileSuccess": "فایل ایجاد شد.", "skillManagement.detail.createFolder": "پوشه جدید", - "skillManagement.detail.createFolderDescription": "یک مسیر پوشه نسبت به ریشه مهارت وارد کنید.", "skillManagement.detail.createFolderMenu": "پوشه جدید...", "skillManagement.detail.createFolderSuccess": "پوشه ایجاد شد.", "skillManagement.detail.createdBy": "ایجاد شده توسط {{name}}", @@ -100,10 +80,8 @@ "skillManagement.detail.fileMeta": "{{type}} · {{size}} بایت", "skillManagement.detail.fileMissing": "فایل پیدا نشد", "skillManagement.detail.fileOperationFailed": "عملیات فایل انجام نشد.", - "skillManagement.detail.files": "فایل‌ها", "skillManagement.detail.latest": "آخرین", "skillManagement.detail.loadFailed": "مهارت بارگیری نشد.", - "skillManagement.detail.loadingTags": "در حال بارگیری برچسب ها...", "skillManagement.detail.markdownLiveMode": "پیش نمایش زنده", "skillManagement.detail.markdownSourceMode": "ویرایشگر منبع", "skillManagement.detail.metadataKey": "نام", @@ -113,10 +91,7 @@ "skillManagement.detail.nameThisVersion": "نام‌گذاری این نسخه", "skillManagement.detail.noFileSelected": "فایلی را برای پیش نمایش انتخاب کنید.", "skillManagement.detail.noFiles": "بدون فایل", - "skillManagement.detail.noSearchResults": "فایل منطبقی نیست.", - "skillManagement.detail.noSelectableTags": "هیچ برچسبی موجود نیست", "skillManagement.detail.noVersions": "هنوز هیچ نسخه منتشر شده ای وجود ندارد.", - "skillManagement.detail.pasteFile": "چسباندن", "skillManagement.detail.pasteFileSuccess": "فایل جایگذاری شد.", "skillManagement.detail.previewUnsupported": "این فایل از پیش‌نمایش پشتیبانی نمی‌کند.", "skillManagement.detail.publish": "انتشار", @@ -128,9 +103,7 @@ "skillManagement.detail.publishUpdate": "انتشار به‌روزرسانی", "skillManagement.detail.published": "منتشر شد", "skillManagement.detail.publishedAt": "{{time}} منتشر شد", - "skillManagement.detail.publishedVersion": "#{{number}} منتشر شد", "skillManagement.detail.publishing": "در حال انتشار...", - "skillManagement.detail.readonly": "فقط بخوانید", "skillManagement.detail.referenceFiles.confirm": "تایید را وارد کنید", "skillManagement.detail.referenceFiles.empty": "هیچ فایلی در دسترس نیست", "skillManagement.detail.referenceFiles.livePlaceholder": "دستورالعمل هایی را برای نماینده بنویسید. تایپ / به فایل های مرجع.", @@ -138,14 +111,10 @@ "skillManagement.detail.referenceFiles.title": "فایل های مرجع", "skillManagement.detail.referencedBy_one": "مورد استفاده توسط {{count}} عامل", "skillManagement.detail.referencedBy_other": "مورد استفاده توسط {{count}} عامل", - "skillManagement.detail.removeMetadata": "ابرداده {{name}} را حذف کنید", "skillManagement.detail.removeTag": "حذف {{tag}}", "skillManagement.detail.removeTagSuccess": "برچسب حذف شد.", - "skillManagement.detail.renameFile": "تغییر نام فایل", - "skillManagement.detail.renameFileDescription": "مسیر جدید را نسبت به ریشه مهارت وارد کنید.", "skillManagement.detail.renameFileSuccess": "فایل تغییر نام داد", "skillManagement.detail.renameSkillSuccess": "Skill تغییر نام داد.", - "skillManagement.detail.renameVersion": "تغییر نام", "skillManagement.detail.renameVersionFailed": "تغییر نام نسخه انجام نشد.", "skillManagement.detail.renameVersionPrompt": "نام نسخه", "skillManagement.detail.renameVersionSuccess": "نسخه تغییر نام داد.", @@ -155,17 +124,14 @@ "skillManagement.detail.restoreVersionConfirmTitle": "بازیابی این نسخه؟", "skillManagement.detail.restoreVersionFailed": "بازیابی نسخه انجام نشد.", "skillManagement.detail.restoreVersionSuccess": "نسخه به پیش نویس بازیابی شد.", - "skillManagement.detail.save": "ذخیره", "skillManagement.detail.saveConflictConfirmDescription": "این مهارت در جای دیگری به روز شد. برای ادامه ویرایش، آخرین پیش نویس را بارگیری کنید. تغییرات فعلی شما نادیده گرفته خواهد شد.", "skillManagement.detail.saveConflictConfirmTitle": "آخرین پیش نویس بارگیری شود؟", "skillManagement.detail.saveConflictReload": "آخرین پیش نویس را بارگیری کنید", "skillManagement.detail.saveConflictStatus": "درگیری شناسایی شد. برای ادامه ویرایش، آخرین پیش نویس را بارگیری کنید.", "skillManagement.detail.saveFailed": "فایل ذخیره نشد.", - "skillManagement.detail.saveSuccess": "فایل ذخیره شد.", "skillManagement.detail.saved": "ذخیره شد", "skillManagement.detail.savedAt": "ذخیره شد {{time}}", "skillManagement.detail.saving": "در حال ذخیره‌سازی...", - "skillManagement.detail.searchFiles": "جستجوی فایل‌ها", "skillManagement.detail.showMoreReferences": "نمایش {{count}} موارد بیشتر", "skillManagement.detail.skillDescriptionPlaceholder": "توضیح دهید که این مهارت چه کاری انجام می دهد و چه زمانی عوامل باید از آن استفاده کنند.", "skillManagement.detail.skillNamePlaceholder": "از حروف کوچک، اعداد و خط فاصله استفاده کنید.", @@ -177,16 +143,12 @@ "skillManagement.detail.uploadConflict": "فایلی با این نام از قبل وجود دارد.", "skillManagement.detail.uploadFailureSummary": "{{failed}} از فایل های {{total}} ناموفق بود", "skillManagement.detail.uploadFailureTitle": "برخی از آپلودها ناموفق بود", - "skillManagement.detail.uploadFile": "بارگذاری فایل", - "skillManagement.detail.uploadFileFailed": "آپلود فایل انجام نشد.", "skillManagement.detail.uploadFileSuccess": "فایل بارگذاری شد.", "skillManagement.detail.uploadFilesButton": "فایل های {{count}} را آپلود کنید", "skillManagement.detail.uploadFilesFailedStatus_one": "آپلود فایل {{count}} انجام نشد.", "skillManagement.detail.uploadFilesFailedStatus_other": "آپلود فایل {{count}} انجام نشد.", "skillManagement.detail.uploadFilesMenu": "آپلود فایل ها...", "skillManagement.detail.uploadFilesProgress": "در حال آپلود {{completed}}/{{total}}", - "skillManagement.detail.uploadFilesResult": "{{uploaded}} آپلود شد · {{failed}} ناموفق بود", - "skillManagement.detail.uploadFilesStatus": "وضعیت آپلود", "skillManagement.detail.uploadInvalidExtension": "این نوع فایل پشتیبانی نمی شود.", "skillManagement.detail.uploadInvalidName": "نام نامعتبر", "skillManagement.detail.uploadInvalidPath": "مسیر فایل نامعتبر است.", @@ -206,7 +168,6 @@ "skillManagement.detail.uploadWillReplace": "جایگزین فایل موجود می شود", "skillManagement.detail.uploadWillUseName": "به عنوان {{name}} آپلود خواهد شد", "skillManagement.detail.versionHistory": "باز کردن تاریخچه نسخه", - "skillManagement.detail.versionMeta": "{{hash}} · {{time}}", "skillManagement.detail.versionPublishNote": "یادداشت های انتشار", "skillManagement.detail.versionPublishNotePlaceholder": "این تغییر را شرح دهید", "skillManagement.detail.versionPublishedMeta": "{{time}} · {{name}}", @@ -227,8 +188,6 @@ "skillManagement.errors.missingSkillMd": "این بسته فاقد SKILL.md است. فایل را اضافه کنید و دوباره آن را وارد کنید.", "skillManagement.errors.nameConflict": "مهارتی به نام \"{{name}}\" از قبل وجود دارد. نام آن را در SKILL.md تغییر دهید و دوباره وارد کنید.", "skillManagement.errors.workspaceLimit": "فضای کاری شما به حداکثر 500 مهارت رسیده است. برای افزودن مهارت جدید، مهارت را حذف کنید.", - "skillManagement.exportDialog.description": "با این کار از پیش‌نویس ذخیره‌شدهٔ فعلی مهارت شما خروجی گرفته می‌شود. اگر در ویرایشگر تغییرات ذخیره‌نشده‌ای دارید، پیش از خروجی گرفتن آن‌ها را ذخیره کنید.", - "skillManagement.exportDialog.title": "خروجی گرفتن از پیش‌نویس ذخیره‌شدهٔ فعلی", "skillManagement.import": "وارد کردن", "skillManagement.importFailed": "وارد کردن مهارت انجام نشد.", "skillManagement.importSuccess": "Skill وارد شده است.", @@ -236,12 +195,10 @@ "skillManagement.loadingError": "مهارت ها بارگیری نشد", "skillManagement.moreActions": "اقدامات بیشتر برای {{name}}", "skillManagement.noDescription": "هنوز توضیحاتی ارائه نشده است", - "skillManagement.noTags": "بدون برچسب", "skillManagement.publishedAt": "منتشر شد {{time}}", "skillManagement.referenceCount_one": "{{count}} عامل", "skillManagement.referenceCount_other": "{{count}} عامل", "skillManagement.searchLabel": "جستجوی مهارت‌ها", "skillManagement.searchPlaceholder": "جستجو بر اساس نام یا توضیحات", - "skillManagement.tags": "برچسب‌ها", "skillManagement.title": "مهارت‌ها" } diff --git a/web/i18n/fa-IR/snippet.json b/web/i18n/locales/fa-IR/snippet.json similarity index 100% rename from web/i18n/fa-IR/snippet.json rename to web/i18n/locales/fa-IR/snippet.json diff --git a/web/i18n/fa-IR/time.json b/web/i18n/locales/fa-IR/time.json similarity index 100% rename from web/i18n/fa-IR/time.json rename to web/i18n/locales/fa-IR/time.json diff --git a/web/i18n/fa-IR/tools.json b/web/i18n/locales/fa-IR/tools.json similarity index 100% rename from web/i18n/fa-IR/tools.json rename to web/i18n/locales/fa-IR/tools.json diff --git a/web/i18n/fa-IR/workflow.json b/web/i18n/locales/fa-IR/workflow.json similarity index 99% rename from web/i18n/fa-IR/workflow.json rename to web/i18n/locales/fa-IR/workflow.json index fd1b0ada023..1b9484dba1c 100644 --- a/web/i18n/fa-IR/workflow.json +++ b/web/i18n/locales/fa-IR/workflow.json @@ -460,8 +460,6 @@ "nodes.agent.pluginNotInstalledDesc": "این یکپارچه‌سازی از GitHub نصب شده. برای نصب مجدد به بخش یکپارچه‌سازی‌ها بروید.", "nodes.agent.roster.change": "تغییر", "nodes.agent.roster.editInConsole": "ویرایش در Agents", - "nodes.agent.roster.editInConsoleDisabled": "ویرایش عامل‌ها به نقش ویرایشگر یا بالاتر نیاز دارد.", - "nodes.agent.roster.editInConsoleDisabledRbac": "فقط اعضایی که نقش ویرایشگر یا بالاتر، یا مجوز مدیریت Agents را دارند، می‌توانند عامل‌ها را ویرایش کنند.", "nodes.agent.roster.inlineSetup.description": "یک پیکربندی یک‌باره که فقط در این گره وجود دارد", "nodes.agent.roster.inlineSetup.name": "پیکربندی inline", "nodes.agent.roster.inlineSetup.type": "فقط در این گره", diff --git a/web/i18n/fr-FR/agent-v-2.json b/web/i18n/locales/fr-FR/agent-v-2.json similarity index 98% rename from web/i18n/fr-FR/agent-v-2.json rename to web/i18n/locales/fr-FR/agent-v-2.json index 4109ab0fa1c..a74a7fd64b8 100644 --- a/web/i18n/fr-FR/agent-v-2.json +++ b/web/i18n/locales/fr-FR/agent-v-2.json @@ -69,6 +69,7 @@ "agentDetail.configure.answer.activity.runningCommands": "Exécution des commandes", "agentDetail.configure.answer.duration.minute": "{{count}} min", "agentDetail.configure.answer.duration.second": "{{count}} s", + "agentDetail.configure.answer.responseEnded": "La réponse est terminée", "agentDetail.configure.answer.thinking": "Réflexion en cours", "agentDetail.configure.build.empty.communityEditionTip": "Community Edition, bien qu’elle offre la gestion des versions pour les configurations extraites, ne prend pas en charge la gestion des versions du système de fichiers lui-même. Soyez prudent avec vos actions en mode Build, car les modifications apportées au système de fichiers se produisent en temps réel et ne peuvent pas toujours être annulées proprement. Community Edition n’est pas le choix idéal pour servir un public externe massif.", "agentDetail.configure.build.empty.description": "Décrivez ce que vous voulez et le formulaire de gauche se remplit au fil de la conversation.", @@ -269,7 +270,6 @@ "agentDetail.configure.workingDirectory.breadcrumbLabel": "Chemin du système de fichiers", "agentDetail.configure.workingDirectory.description": "Fichiers que l’agent lit et écrit pendant son exécution. Tout ce qu’il génère, comme qna_report.pdf, apparaît ici.", "agentDetail.configure.workingDirectory.fileSystem": "Système de fichiers", - "agentDetail.configure.workingDirectory.open": "Ouvrir le répertoire de travail", "agentDetail.configure.workingDirectory.persistentFiles": "Persistants", "agentDetail.configure.workingDirectory.persistentFilesTooltip": "Fichiers que l’agent conserve et peut utiliser partout où il s’exécute, y compris dans les conversations des utilisateurs finaux et les workflows. Les modifications apportées ici pendant cette discussion de création prennent effet dès que vous cliquez sur « Appliquer ».", "agentDetail.configure.workingDirectory.temporaryFiles": "Temporaires", @@ -348,7 +348,6 @@ "agentDetail.sections.logs": "Journaux", "agentDetail.sections.monitoring": "Surveillance", "agentDetail.title": "Agent", - "agentDetail.type": "AGENT", "agentDetail.versionHistory.empty": "Pas encore de versions", "agentDetail.versionHistory.exitVersions": "Quitter les versions", "agentDetail.versionHistory.filter": "Filtrer les versions", @@ -408,8 +407,5 @@ "roster.sort.lastModified": "Dernière modification", "roster.sort.recentlyCreated": "Récemment créés", "roster.title": "Agents", - "roster.usageStatus.draft": "Brouillon", - "skillManagement.detail.uploadFilesFailedStatus_one": "Échec du téléversement de {{count}} fichier.", - "skillManagement.detail.uploadFilesFailedStatus_other": "Échec du téléversement de {{count}} fichiers.", - "skillManagement.title": "Skills" + "roster.usageStatus.draft": "Brouillon" } diff --git a/web/i18n/fr-FR/app-annotation.json b/web/i18n/locales/fr-FR/app-annotation.json similarity index 100% rename from web/i18n/fr-FR/app-annotation.json rename to web/i18n/locales/fr-FR/app-annotation.json diff --git a/web/i18n/fr-FR/app-api.json b/web/i18n/locales/fr-FR/app-api.json similarity index 100% rename from web/i18n/fr-FR/app-api.json rename to web/i18n/locales/fr-FR/app-api.json diff --git a/web/i18n/fr-FR/app-debug.json b/web/i18n/locales/fr-FR/app-debug.json similarity index 100% rename from web/i18n/fr-FR/app-debug.json rename to web/i18n/locales/fr-FR/app-debug.json diff --git a/web/i18n/fr-FR/app-log.json b/web/i18n/locales/fr-FR/app-log.json similarity index 97% rename from web/i18n/fr-FR/app-log.json rename to web/i18n/locales/fr-FR/app-log.json index 533efb47c9d..b5f67a80ca8 100644 --- a/web/i18n/fr-FR/app-log.json +++ b/web/i18n/locales/fr-FR/app-log.json @@ -4,11 +4,9 @@ "agentLogDetail.iteration": "Itération", "agentLogDetail.iterations": "Itérations", "agentLogDetail.toolUsed": "Outil utilisé", - "archives.action.downloadMonth": "Télécharger l’archive de {{month}}", "archives.action.downloadReady": "Le téléchargement de l’archive est prêt.", "archives.action.prepareDownload": "Préparer le téléchargement", "archives.action.prepareFailed": "Impossible de préparer le téléchargement de l’archive.", - "archives.action.prepareMonth": "Préparer le téléchargement de l’archive de {{month}}", "archives.action.prepareStarted": "Le téléchargement de l’archive est en cours de préparation.", "archives.action.preparing": "Préparation", "archives.description": "Téléchargez des exports CSV mensuels des journaux archivés d’exécutions de workflow dans cet espace de travail.", diff --git a/web/i18n/fr-FR/app-overview.json b/web/i18n/locales/fr-FR/app-overview.json similarity index 100% rename from web/i18n/fr-FR/app-overview.json rename to web/i18n/locales/fr-FR/app-overview.json diff --git a/web/i18n/fr-FR/app.json b/web/i18n/locales/fr-FR/app.json similarity index 100% rename from web/i18n/fr-FR/app.json rename to web/i18n/locales/fr-FR/app.json diff --git a/web/i18n/fr-FR/billing.json b/web/i18n/locales/fr-FR/billing.json similarity index 100% rename from web/i18n/fr-FR/billing.json rename to web/i18n/locales/fr-FR/billing.json diff --git a/web/i18n/fr-FR/common.json b/web/i18n/locales/fr-FR/common.json similarity index 98% rename from web/i18n/fr-FR/common.json rename to web/i18n/locales/fr-FR/common.json index 1115008f6fd..f72fe237736 100644 --- a/web/i18n/fr-FR/common.json +++ b/web/i18n/locales/fr-FR/common.json @@ -209,9 +209,7 @@ "mainNav.marketplace": "Marketplace", "mainNav.skills": "Skills", "mainNav.webApps.noResults": "Aucune application web trouvée", - "mainNav.webApps.openApp": "Ouvrir l’application web {{name}}", "mainNav.webApps.searchPlaceholder": "Rechercher des applications web", - "mainNav.workspace.credits": "{{count}} crédits", "mainNav.workspace.creditsUnit": "crédits", "mainNav.workspace.inviteMembers": "Invite members", "mainNav.workspace.noResults": "Aucun espace de travail trouvé", @@ -233,7 +231,6 @@ "members.datasetOperatorTip": "Seul peut gérer la base de connaissances", "members.editRole": "Modifier le rôle", "members.editorTip": "Peut créer et modifier des apps", - "members.email": "Courrier électronique", "members.emailInvalid": "Format de courriel invalide", "members.emailNotSetup": "Le serveur de messagerie n'est pas configuré, les e-mails d'invitation ne peuvent donc pas être envoyés. Veuillez informer les utilisateurs du lien d'invitation qui sera émis après l'invitation.", "members.emailPlaceholder": "Saisissez ou collez des adresses e-mail…", @@ -250,7 +247,6 @@ "members.inviteLimitExceeded": "Il n’y a pas assez de places disponibles pour tous les destinataires.", "members.inviteTeamMember": "Ajouter un membre de l'équipe", "members.inviteTeamMemberTip": "Ils peuvent accéder directement à vos données d'équipe après s'être connectés.", - "members.invitedAsRole": "Invité en tant que {{role}}", "members.lastActive": "DERNIÈRE ACTIVITÉ", "members.memberActions": "Actions du membre", "members.memberDetails.assign": "Attribuer", @@ -258,8 +254,6 @@ "members.memberDetails.assignedRoles": "Rôles attribués", "members.memberDetails.customGroup": "PERSONNALISÉ", "members.memberDetails.generalGroup": "GÉNÉRAL", - "members.memberDetails.openAria": "Ouvrir les détails du membre pour {{name}}", - "members.memberDetails.roleActionsAria": "Ouvrir les actions pour le rôle {{role}}", "members.memberDetails.roleNoPermissionSummary": "Le rôle actuel n'a aucune autorisation.", "members.memberDetails.rolePermissionSummary": "{{role}} peut {{permissions}}", "members.memberDetails.title": "Détails du membre", @@ -370,7 +364,6 @@ "modelProvider.card.usagePriorityTip": "Définissez la ressource à utiliser en priorité lors de l'exécution des modèles.", "modelProvider.configureTip": "Configurer api-key ou ajouter un modèle à utiliser", "modelProvider.confirmDelete": "confirmer la suppression?", - "modelProvider.credits": "Crédits de Messages", "modelProvider.defaultConfig": "Configuration par défaut", "modelProvider.discoverMore": "Découvrez-en plus dans", "modelProvider.embeddingModel.key": "Modèle d'Embedding", @@ -427,7 +420,6 @@ "modelProvider.selector.noProviderConfiguredDesc": "Parcourez le Marketplace pour en installer un, ou configurez les fournisseurs dans les paramètres.", "modelProvider.selector.onlyCompatibleModelsShown": "Seuls les modèles compatibles sont affichés", "modelProvider.selector.showIncompatibleModels": "Afficher les modèles incompatibles", - "modelProvider.selector.suggestion": "Suggestion", "modelProvider.selector.suggestionTip": "Recommandé pour les tâches d’agent", "modelProvider.showModels": "Montrer les modèles", "modelProvider.speechToTextModel.key": "Modèle de Texte-à-Parole", @@ -458,6 +450,8 @@ "operation.confirmAction": "Veuillez confirmer votre action.", "operation.copied": "Copied", "operation.copy": "Copier", + "operation.copyErrorDetails": "Copier les détails de l’erreur", + "operation.copyErrorFailed": "Échec de la copie. Réessayer", "operation.copyImage": "Copier l’image", "operation.create": "Créer", "operation.deSelectAll": "Désélectionner tout", @@ -475,7 +469,6 @@ "operation.format": "Format", "operation.imageCopied": "Image copied", "operation.imageCopyFailed": "Impossible de copier l’image. Veuillez la télécharger à la place.", - "operation.imageDownloaded": "Image téléchargée", "operation.in": "dans", "operation.learnMore": "En savoir plus", "operation.log": "Journal", @@ -512,7 +505,6 @@ "operation.sure": "Je suis sûr", "operation.toggleFullscreen": "Basculer en plein écran", "operation.toggleMute": "Activer/désactiver le son", - "operation.upload": "Upload", "operation.view": "Vue", "operation.viewDetails": "Voir les détails", "operation.viewMore": "VOIR PLUS", @@ -542,7 +534,6 @@ "promptEditor.requestURL.item.title": "URL de la Requête", "promptEditor.variable.modal.add": "Nouvelle variable", "promptEditor.variable.modal.addTool": "Nouvel outil", - "provider.anthropicHosted.trialQuotaTip": "Votre quota d’essai Anthropic expirera le 11/03/2025 et ne sera plus disponible par la suite. Veuillez l’utiliser à temps.", "provider.encrypted.back": "technologie.", "provider.encrypted.front": "Votre clé API sera chiffrée et stockée en utilisant", "provider.validating": "Validation de la clé...", @@ -565,7 +556,6 @@ "settings.preferences": "Preferences", "settings.provider": "Fournisseur de Modèle", "settings.resourceAccess": "Accès aux ressources", - "settings.resourceAccessDescription": "Configurez les règles d'autorisation que les applications et les bases de connaissances peuvent utiliser. Les règles d'autorisation peuvent être attribuées aux membres dans des configurations d'accès aux ressources spécifiques.", "settings.rolesAndPermissions": "Rôles et autorisations", "settings.settings": "Settings", "settings.swaggerAPIAsTool": "Swagger API as Tool", @@ -695,7 +685,6 @@ "userProfile.helpCenter": "Aide", "userProfile.logout": "Se déconnecter", "userProfile.roadmap": "Feuille de route", - "userProfile.settings": "Paramètres", "userProfile.workspace": "Espace de travail", "voice.language.arTN": "Arabe tunisien", "voice.language.deDE": "Allemand", diff --git a/web/i18n/fr-FR/custom.json b/web/i18n/locales/fr-FR/custom.json similarity index 100% rename from web/i18n/fr-FR/custom.json rename to web/i18n/locales/fr-FR/custom.json diff --git a/web/i18n/fr-FR/dataset-creation.json b/web/i18n/locales/fr-FR/dataset-creation.json similarity index 100% rename from web/i18n/fr-FR/dataset-creation.json rename to web/i18n/locales/fr-FR/dataset-creation.json diff --git a/web/i18n/fr-FR/dataset-documents.json b/web/i18n/locales/fr-FR/dataset-documents.json similarity index 100% rename from web/i18n/fr-FR/dataset-documents.json rename to web/i18n/locales/fr-FR/dataset-documents.json diff --git a/web/i18n/fr-FR/dataset-hit-testing.json b/web/i18n/locales/fr-FR/dataset-hit-testing.json similarity index 100% rename from web/i18n/fr-FR/dataset-hit-testing.json rename to web/i18n/locales/fr-FR/dataset-hit-testing.json diff --git a/web/i18n/fr-FR/dataset-pipeline.json b/web/i18n/locales/fr-FR/dataset-pipeline.json similarity index 100% rename from web/i18n/fr-FR/dataset-pipeline.json rename to web/i18n/locales/fr-FR/dataset-pipeline.json diff --git a/web/i18n/fr-FR/dataset-settings.json b/web/i18n/locales/fr-FR/dataset-settings.json similarity index 100% rename from web/i18n/fr-FR/dataset-settings.json rename to web/i18n/locales/fr-FR/dataset-settings.json diff --git a/web/i18n/fr-FR/dataset.json b/web/i18n/locales/fr-FR/dataset.json similarity index 97% rename from web/i18n/fr-FR/dataset.json rename to web/i18n/locales/fr-FR/dataset.json index e0d3e11a265..83b2737d225 100644 --- a/web/i18n/fr-FR/dataset.json +++ b/web/i18n/locales/fr-FR/dataset.json @@ -163,7 +163,6 @@ "newKnowledge.configureProvider": "Configurer {{provider}}", "newKnowledge.connectNotion": "Connecter Notion", "newKnowledge.connectProvider": "Connecter {{provider}}", - "newKnowledge.connectProviderGeneric": "Connecter un fournisseur", "newKnowledge.connectSource": "Connecter une source", "newKnowledge.connectSourceDescription": "Le contenu reste synchronisé avec Notion, les sites web et d’autres services.", "newKnowledge.connectingProvider": "Connexion…", @@ -179,18 +178,14 @@ "newKnowledge.crawlFailedDescription": "Le robot n’a pas pu terminer cet aperçu. Vérifiez l’URL, ajustez les options d’exploration ou réessayez.", "newKnowledge.crawlFailedProvider": "{{provider}} a signalé une erreur. Vérifiez la connexion au fournisseur et réessayez.", "newKnowledge.crawlFailedTimeout": "Le délai d’exploration a expiré. Essayez une URL plus précise ou réduisez le nombre maximal de pages.", - "newKnowledge.crawlNotStarted": "Non démarré", "newKnowledge.crawlOptions": "Options d’exploration", "newKnowledge.crawlPreview": "Aperçu de l’exploration", "newKnowledge.crawlProgress": "Progression de l’exploration de {{host}}", - "newKnowledge.crawlSetupUnavailableDescription": "La configuration de l'analyse du site Web est désactivée jusqu'à ce que le flux de travail d'analyse soit disponible.", - "newKnowledge.crawlSetupUnavailableTitle": "Firecrawl est prêt", "newKnowledge.crawlStartFailed": "Impossible de démarrer l’aperçu de l’exploration. Réessayez.", "newKnowledge.crawlStopped": "Exploration arrêtée", "newKnowledge.crawling": "Exploration en cours…", "newKnowledge.crawlingPages": "Exploration de {{host}}… {{count}} pages trouvées", "newKnowledge.createFailed": "Nous n'avons pas pu créer cette base de connaissances. Vérifiez votre accès et réessayez.", - "newKnowledge.createIllustrationTitle": "Connectez votre contenu une fois — chaque application répond avec les connaissances les plus récentes.", "newKnowledge.createTitle": "Créer des connaissances", "newKnowledge.createdAt": "Créé", "newKnowledge.creators": "Créateurs", @@ -220,7 +215,6 @@ "newKnowledge.documentColumn": "Document", "newKnowledge.documentContentIncomplete": "Chargement du reste du document…", "newKnowledge.documentContents": "Contenu", - "newKnowledge.documentFacts": "Détails du document", "newKnowledge.documentFilterLabel": "Filtrer les documents par statut", "newKnowledge.documentLoadErrorDescription": "Impossible de charger ce document. Réessayez.", "newKnowledge.documentLoadErrorTitle": "Impossible de charger le document", @@ -268,7 +262,6 @@ "newKnowledge.documentsSelected": "Documents {{count}} sélectionnés", "newKnowledge.documentsUnavailable": "— documents", "newKnowledge.downloadDocuments": "Télécharger", - "newKnowledge.editSource": "Modifier", "newKnowledge.emptyChunk": "Segment vide", "newKnowledge.emptyDescription": "Ajoutez vos documents et les agents pourront les lire directement, sans indexation à configurer.", "newKnowledge.emptyTitle": "Une façon plus intelligente de créer des bases de connaissances", @@ -344,7 +337,6 @@ "newKnowledge.processingTaskState.succeeded": "Terminé", "newKnowledge.processingTaskState.superseded": "Remplacé", "newKnowledge.providerConnected": "{{provider}} connecté", - "newKnowledge.providerLabel": "Sélectionnez un fournisseur", "newKnowledge.providerLoadFailed": "Les fournisseurs n'ont pas pu charger", "newKnowledge.providerNotConfigured": "{{provider}} n'est pas configuré", "newKnowledge.providerNotConfiguredDescription": "Ajoutez une clé API ou une URL de point de terminaison pour explorer les sites Web avec {{provider}}.", @@ -369,12 +361,10 @@ "newKnowledge.revisionState.superseded": "Précédente", "newKnowledge.rootUrl": "URL racine", "newKnowledge.rootUrlPlaceholder": "https://docs.example.com", - "newKnowledge.routeUnavailable": "Cette section deviendra disponible lors de la prochaine étape de configuration.", "newKnowledge.searchDocuments": "Rechercher des documents", "newKnowledge.searchSources": "Sources de recherche", "newKnowledge.selectAll": "Tout sélectionner", "newKnowledge.selectAllDocuments": "Sélectionnez tous les documents", - "newKnowledge.selectAllSources": "Sélectionner toutes les sources", "newKnowledge.selectChunk": "Sélectionnez un segment pour examiner son contenu.", "newKnowledge.selectFilesAndFolders": "Sélectionnez les fichiers et dossiers à synchroniser", "newKnowledge.skippedFailed": "Ignoré (échec)", @@ -384,7 +374,6 @@ "newKnowledge.sourceFilterLabel": "Filtrer les sources par statut", "newKnowledge.sourceName": "Nom de la source", "newKnowledge.sourceNamePlaceholder": "p. ex. exploration de la documentation produit", - "newKnowledge.sourceNameRequired": "Saisissez un nom de source.", "newKnowledge.sourceSetupBackendDependency": "La configuration de la source est affichée en entier ; la modification et l’aperçu nécessitent le flux backend de source brouillon.", "newKnowledge.sourceStatus.active": "Actif", "newKnowledge.sourceStatus.disabled": "Désactivé", @@ -431,7 +420,6 @@ "newKnowledge.unavailableDescription": "KnowledgeFS n’est pas disponible pour cet espace de travail actuellement.", "newKnowledge.unavailableTitle": "Les nouvelles connaissances sont indisponibles", "newKnowledge.updated": "Mis à jour le {{date}}", - "newKnowledge.updatedAt": "Dernière mise à jour", "newKnowledge.updatedColumn": "Mis à jour", "newKnowledge.uploadCharactersUnavailable": "Caractères : —", "newKnowledge.uploadDocuments": "Télécharger des documents", @@ -460,11 +448,9 @@ "retrievalSettings": "Paramètres de récupération", "retry": "Réessayer", "selectExternalKnowledgeAPI.placeholder": "Choisir une API de connaissances externe", - "serviceApi.card.addApiKey": "Ajouter une clé API", "serviceApi.card.apiKey": "Clé API", "serviceApi.card.apiReference": "Référence API", "serviceApi.card.endpoint": "Point de terminaison de l'API", - "serviceApi.card.manageApiKey": "Gérer la clé API", "serviceApi.card.title": "API du service backend", "serviceApi.disabled": "désactivé", "serviceApi.enabled": "En service", diff --git a/web/i18n/fr-FR/deployments.json b/web/i18n/locales/fr-FR/deployments.json similarity index 97% rename from web/i18n/fr-FR/deployments.json rename to web/i18n/locales/fr-FR/deployments.json index f8944a8329f..a6055160629 100644 --- a/web/i18n/fr-FR/deployments.json +++ b/web/i18n/locales/fr-FR/deployments.json @@ -27,7 +27,6 @@ "list.emptyTitle": "Aucun déploiement pour le moment", "overview.chip.latest": "dernière", "status.RUNTIME_INSTANCE_STATUS_DEPLOYING": "Déploiement en cours", - "status.RUNTIME_INSTANCE_STATUS_FAILED": "Échec du déploiement", "status.RUNTIME_INSTANCE_STATUS_INVALID": "Invalide", "status.RUNTIME_INSTANCE_STATUS_READY": "En cours d'exécution", "status.RUNTIME_INSTANCE_STATUS_UNDEPLOYED": "Non déployé", @@ -87,6 +86,5 @@ "studio.versionValue": "Valeur de la version", "studio.versionsBehind_one": "{{count}} version de retard", "studio.versionsBehind_other": "{{count}} versions de retard", - "versions.checkingReleaseContent": "Vérification du contenu de la version...", "versions.deployTo": "Déployer dans {{name}}" } diff --git a/web/i18n/fr-FR/device-flow.json b/web/i18n/locales/fr-FR/device-flow.json similarity index 100% rename from web/i18n/fr-FR/device-flow.json rename to web/i18n/locales/fr-FR/device-flow.json diff --git a/web/i18n/fr-FR/education.json b/web/i18n/locales/fr-FR/education.json similarity index 100% rename from web/i18n/fr-FR/education.json rename to web/i18n/locales/fr-FR/education.json diff --git a/web/i18n/fr-FR/explore.json b/web/i18n/locales/fr-FR/explore.json similarity index 100% rename from web/i18n/fr-FR/explore.json rename to web/i18n/locales/fr-FR/explore.json diff --git a/web/i18n/fr-FR/layout.json b/web/i18n/locales/fr-FR/layout.json similarity index 100% rename from web/i18n/fr-FR/layout.json rename to web/i18n/locales/fr-FR/layout.json diff --git a/web/i18n/fr-FR/login.json b/web/i18n/locales/fr-FR/login.json similarity index 100% rename from web/i18n/fr-FR/login.json rename to web/i18n/locales/fr-FR/login.json diff --git a/web/i18n/fr-FR/oauth.json b/web/i18n/locales/fr-FR/oauth.json similarity index 100% rename from web/i18n/fr-FR/oauth.json rename to web/i18n/locales/fr-FR/oauth.json diff --git a/web/i18n/fr-FR/permission-keys.json b/web/i18n/locales/fr-FR/permission-keys.json similarity index 100% rename from web/i18n/fr-FR/permission-keys.json rename to web/i18n/locales/fr-FR/permission-keys.json diff --git a/web/i18n/fr-FR/permission.json b/web/i18n/locales/fr-FR/permission.json similarity index 100% rename from web/i18n/fr-FR/permission.json rename to web/i18n/locales/fr-FR/permission.json diff --git a/web/i18n/fr-FR/pipeline.json b/web/i18n/locales/fr-FR/pipeline.json similarity index 100% rename from web/i18n/fr-FR/pipeline.json rename to web/i18n/locales/fr-FR/pipeline.json diff --git a/web/i18n/fr-FR/plugin-tags.json b/web/i18n/locales/fr-FR/plugin-tags.json similarity index 100% rename from web/i18n/fr-FR/plugin-tags.json rename to web/i18n/locales/fr-FR/plugin-tags.json diff --git a/web/i18n/fr-FR/plugin-trigger.json b/web/i18n/locales/fr-FR/plugin-trigger.json similarity index 100% rename from web/i18n/fr-FR/plugin-trigger.json rename to web/i18n/locales/fr-FR/plugin-trigger.json diff --git a/web/i18n/fr-FR/plugin.json b/web/i18n/locales/fr-FR/plugin.json similarity index 99% rename from web/i18n/fr-FR/plugin.json rename to web/i18n/locales/fr-FR/plugin.json index 713b7eb3e40..6d73d3482bd 100644 --- a/web/i18n/fr-FR/plugin.json +++ b/web/i18n/locales/fr-FR/plugin.json @@ -277,7 +277,6 @@ "readmeInfo.noReadmeAvailable": "Aucun README disponible", "readmeInfo.title": "Lisez-moi", "requestAPlugin": "Demander un plugin", - "requestSubmit": "Demander ou publier", "search": "Rechercher", "searchCategories": "Catégories de recherche", "searchInMarketplace": "Rechercher sur Marketplace", diff --git a/web/i18n/fr-FR/register.json b/web/i18n/locales/fr-FR/register.json similarity index 100% rename from web/i18n/fr-FR/register.json rename to web/i18n/locales/fr-FR/register.json diff --git a/web/i18n/fr-FR/run-log.json b/web/i18n/locales/fr-FR/run-log.json similarity index 100% rename from web/i18n/fr-FR/run-log.json rename to web/i18n/locales/fr-FR/run-log.json diff --git a/web/i18n/fr-FR/share.json b/web/i18n/locales/fr-FR/share.json similarity index 100% rename from web/i18n/fr-FR/share.json rename to web/i18n/locales/fr-FR/share.json diff --git a/web/i18n/fr-FR/skill.json b/web/i18n/locales/fr-FR/skill.json similarity index 82% rename from web/i18n/fr-FR/skill.json rename to web/i18n/locales/fr-FR/skill.json index 85901c4db20..3ecc767e05f 100644 --- a/web/i18n/fr-FR/skill.json +++ b/web/i18n/locales/fr-FR/skill.json @@ -1,5 +1,4 @@ { - "skillManagement.clearTags": "Effacer les balises", "skillManagement.create": "Créer", "skillManagement.createFailed": "Échec de la création de compétences.", "skillManagement.createSuccess": "Brouillon créé.", @@ -13,12 +12,9 @@ "skillManagement.deleteFailed": "Échec de la suppression de la compétence.", "skillManagement.deleteSuccess": "Skill supprimé.", "skillManagement.detail.addMetadata": "Ajouter des métadonnées", - "skillManagement.detail.addMetadataDescription": "Ajoutez un champ frontmatter à ce fichier Markdown.", "skillManagement.detail.addTag": "Ajouter une balise", - "skillManagement.detail.addTagDescription": "Créez ou liez une balise à ce Skill. Les nouvelles balises sont enregistrées avec les métadonnées Skill.", "skillManagement.detail.addTagSuccess": "Balise ajoutée.", "skillManagement.detail.back": "Retour à Skills", - "skillManagement.detail.builder.applyFailed": "Échec de l'application des brouillons de mises à jour.", "skillManagement.detail.builder.attach": "Joindre un fichier", "skillManagement.detail.builder.attachFailed": "Échec de la pièce jointe du fichier.", "skillManagement.detail.builder.attachLimit": "Vous pouvez joindre jusqu'à 10 fichiers.", @@ -26,22 +22,12 @@ "skillManagement.detail.builder.attachUnsupported": "Seuls les fichiers d’images, de texte et de documents peuvent être joints.", "skillManagement.detail.builder.attachmentOnlyMessage": "Utilisez le fichier ci-joint pour vous aider à améliorer cette compétence.", "skillManagement.detail.builder.close": "Fermer Skill Builder", - "skillManagement.detail.builder.compatibleModelsOnly": "Seuls les modèles compatibles sont affichés", "skillManagement.detail.builder.copyResponse": "Copier la réponse", "skillManagement.detail.builder.copySuccess": "Réponse copiée dans le presse-papiers.", "skillManagement.detail.builder.editIntro": "J'ai examiné cette compétence. Que souhaiteriez-vous régler ?", "skillManagement.detail.builder.exampleIssueTriage": "Tri des problèmes clients", "skillManagement.detail.builder.exampleOnboarding": "Guide d'intégration des nouveaux employés", "skillManagement.detail.builder.exampleSalesFollowUp": "Stratégie de suivi des leads commerciaux", - "skillManagement.detail.builder.followUpDisplayName": "Utiliser l'approbation du remboursement comme nom d'affichage", - "skillManagement.detail.builder.followUpNameIcon": "Appliquer le nom et l'icône suggérés", - "skillManagement.detail.builder.fromMarketplace": "Depuis le Marketplace", - "skillManagement.detail.builder.model": "GPT-4o", - "skillManagement.detail.builder.modelCredits.all": "Tous les crédits", - "skillManagement.detail.builder.modelCredits.configure": "Configuration requise", - "skillManagement.detail.builder.modelCredits.exhausted": "Quota épuisé", - "skillManagement.detail.builder.modelProviderSettings": "Paramètres du fournisseur de modèle", - "skillManagement.detail.builder.modelSearch": "Rechercher des modèles...", "skillManagement.detail.builder.modifyPlaceholder": "Demandez à l'IA de modifier cette compétence...", "skillManagement.detail.builder.open": "Ouvrir Skill Builder", "skillManagement.detail.builder.placeholder": "Décrivez le scénario...", @@ -61,8 +47,6 @@ "skillManagement.detail.builder.thinkingUnavailable": "Aucun contenu de réflexion n'a été renvoyé par le modèle.", "skillManagement.detail.builder.title": "Skill Builder", "skillManagement.detail.builder.tryExample": "Essayez un exemple", - "skillManagement.detail.builder.voice": "Saisie vocale", - "skillManagement.detail.builder.voiceUnavailable": "La saisie vocale n'est pas encore disponible.", "skillManagement.detail.cancelAddMetadata": "Annuler l'ajout de métadonnées", "skillManagement.detail.closeFileTab": "Fermer {{name}}", "skillManagement.detail.closeVersions": "Fermer les versions", @@ -70,14 +54,10 @@ "skillManagement.detail.copyContentSuccess": "Contenu copié dans le presse-papiers.", "skillManagement.detail.copyFile": "Copier", "skillManagement.detail.copyFileSuccess": "Fichier prêt à coller.", - "skillManagement.detail.copyVersionId": "Copier l’ID", - "skillManagement.detail.copyVersionIdSuccess": "ID copié dans le presse-papiers.", "skillManagement.detail.createFile": "Nouveau fichier", - "skillManagement.detail.createFileDescription": "Entrez un chemin de fichier relatif à la racine de la compétence.", "skillManagement.detail.createFileMenu": "Nouveau fichier...", "skillManagement.detail.createFileSuccess": "Fichier créé.", "skillManagement.detail.createFolder": "Nouveau dossier", - "skillManagement.detail.createFolderDescription": "Entrez un chemin de dossier relatif à la racine de la compétence.", "skillManagement.detail.createFolderMenu": "Nouveau dossier...", "skillManagement.detail.createFolderSuccess": "Dossier créé.", "skillManagement.detail.createdBy": "Créé par {{name}}", @@ -100,10 +80,8 @@ "skillManagement.detail.fileMeta": "{{type}} · {{size}} octets", "skillManagement.detail.fileMissing": "Fichier introuvable.", "skillManagement.detail.fileOperationFailed": "L'opération sur le fichier a échoué.", - "skillManagement.detail.files": "Fichiers", "skillManagement.detail.latest": "Dernier", "skillManagement.detail.loadFailed": "Échec du chargement de la compétence.", - "skillManagement.detail.loadingTags": "Chargement des balises...", "skillManagement.detail.markdownLiveMode": "Aperçu en direct", "skillManagement.detail.markdownSourceMode": "Éditeur de sources", "skillManagement.detail.metadataKey": "nom", @@ -113,10 +91,7 @@ "skillManagement.detail.nameThisVersion": "Nommez cette version", "skillManagement.detail.noFileSelected": "Sélectionnez un fichier à prévisualiser.", "skillManagement.detail.noFiles": "Aucun fichier.", - "skillManagement.detail.noSearchResults": "Aucun fichier correspondant.", - "skillManagement.detail.noSelectableTags": "Aucune balise disponible.", "skillManagement.detail.noVersions": "Aucune version publiée pour l'instant.", - "skillManagement.detail.pasteFile": "Coller", "skillManagement.detail.pasteFileSuccess": "Fichier collé.", "skillManagement.detail.previewUnsupported": "Ce fichier ne prend pas en charge l’aperçu.", "skillManagement.detail.publish": "Publier", @@ -128,9 +103,7 @@ "skillManagement.detail.publishUpdate": "Publier la mise à jour", "skillManagement.detail.published": "Publié", "skillManagement.detail.publishedAt": "Publié {{time}}", - "skillManagement.detail.publishedVersion": "Publié #{{number}}", "skillManagement.detail.publishing": "Publication...", - "skillManagement.detail.readonly": "Lecture seule", "skillManagement.detail.referenceFiles.confirm": "Entrer Confirmer", "skillManagement.detail.referenceFiles.empty": "Aucun fichier disponible.", "skillManagement.detail.referenceFiles.livePlaceholder": "Rédigez des instructions pour l’agent. Tapez / pour référencer des fichiers.", @@ -138,14 +111,10 @@ "skillManagement.detail.referenceFiles.title": "Fichiers de référence", "skillManagement.detail.referencedBy_one": "Utilisé par {{count}} Agent", "skillManagement.detail.referencedBy_other": "Utilisé par {{count}} Agents", - "skillManagement.detail.removeMetadata": "Supprimer les métadonnées {{name}}", "skillManagement.detail.removeTag": "Supprimer {{tag}}", "skillManagement.detail.removeTagSuccess": "Balise supprimée.", - "skillManagement.detail.renameFile": "Renommer le fichier", - "skillManagement.detail.renameFileDescription": "Entrez le nouveau chemin par rapport à la racine de la compétence.", "skillManagement.detail.renameFileSuccess": "Fichier renommé.", "skillManagement.detail.renameSkillSuccess": "Skill renommé.", - "skillManagement.detail.renameVersion": "Renommer", "skillManagement.detail.renameVersionFailed": "Échec du renommage de la version.", "skillManagement.detail.renameVersionPrompt": "Nom de la version", "skillManagement.detail.renameVersionSuccess": "Version renommée.", @@ -155,17 +124,14 @@ "skillManagement.detail.restoreVersionConfirmTitle": "Restaurer cette version ?", "skillManagement.detail.restoreVersionFailed": "Échec de la restauration de la version.", "skillManagement.detail.restoreVersionSuccess": "Version restaurée au brouillon.", - "skillManagement.detail.save": "Enregistrer", "skillManagement.detail.saveConflictConfirmDescription": "Cette compétence a été mise à jour ailleurs. Chargez le dernier brouillon pour continuer l'édition. Vos modifications actuelles seront ignorées.", "skillManagement.detail.saveConflictConfirmTitle": "Charger le dernier brouillon ?", "skillManagement.detail.saveConflictReload": "Charger le dernier brouillon", "skillManagement.detail.saveConflictStatus": "Conflit détecté. Chargez le dernier brouillon pour continuer l'édition.", "skillManagement.detail.saveFailed": "Échec de l'enregistrement du fichier.", - "skillManagement.detail.saveSuccess": "Fichier enregistré.", "skillManagement.detail.saved": "Sauvegardé", "skillManagement.detail.savedAt": "Enregistré {{time}}", "skillManagement.detail.saving": "Enregistrement...", - "skillManagement.detail.searchFiles": "Rechercher des fichiers", "skillManagement.detail.showMoreReferences": "Afficher {{count}} plus", "skillManagement.detail.skillDescriptionPlaceholder": "Décrivez à quoi sert cette compétence et quand les agents doivent l'utiliser.", "skillManagement.detail.skillNamePlaceholder": "Utilisez des lettres minuscules, des chiffres et des traits d'union.", @@ -177,16 +143,12 @@ "skillManagement.detail.uploadConflict": "Un fichier portant ce nom existe déjà.", "skillManagement.detail.uploadFailureSummary": "Échec du {{failed}} des fichiers {{total}}", "skillManagement.detail.uploadFailureTitle": "Certains téléchargements ont échoué", - "skillManagement.detail.uploadFile": "Téléverser un fichier", - "skillManagement.detail.uploadFileFailed": "Le téléchargement du fichier a échoué.", "skillManagement.detail.uploadFileSuccess": "Fichier téléversé.", "skillManagement.detail.uploadFilesButton": "Téléchargez des fichiers {{count}}", "skillManagement.detail.uploadFilesFailedStatus_one": "Échec du téléchargement du fichier {{count}}.", "skillManagement.detail.uploadFilesFailedStatus_other": "Les téléchargements du fichier {{count}} ont échoué.", "skillManagement.detail.uploadFilesMenu": "Télécharger des fichiers...", "skillManagement.detail.uploadFilesProgress": "Téléchargement de {{completed}}/{{total}}", - "skillManagement.detail.uploadFilesResult": "{{uploaded}} téléchargé · {{failed}} a échoué", - "skillManagement.detail.uploadFilesStatus": "Statut de téléchargement", "skillManagement.detail.uploadInvalidExtension": "Ce type de fichier n'est pas pris en charge.", "skillManagement.detail.uploadInvalidName": "Nom invalide.", "skillManagement.detail.uploadInvalidPath": "Chemin de fichier invalide.", @@ -206,7 +168,6 @@ "skillManagement.detail.uploadWillReplace": "Remplacera le fichier existant", "skillManagement.detail.uploadWillUseName": "Sera téléchargé en tant que {{name}}", "skillManagement.detail.versionHistory": "Ouvrir l’historique des versions", - "skillManagement.detail.versionMeta": "{{hash}} · {{time}}", "skillManagement.detail.versionPublishNote": "Notes de version", "skillManagement.detail.versionPublishNotePlaceholder": "Décrivez ce changement", "skillManagement.detail.versionPublishedMeta": "{{time}} · {{name}}", @@ -227,8 +188,6 @@ "skillManagement.errors.missingSkillMd": "Ce package ne contient pas de SKILL.md. Ajoutez le fichier et importez-le à nouveau.", "skillManagement.errors.nameConflict": "Une compétence nommée \"{{name}}\" existe déjà. Renommez-le en SKILL.md et importez-le à nouveau.", "skillManagement.errors.workspaceLimit": "Votre espace de travail a atteint la limite de 500 compétences. Supprimez une compétence pour en ajouter une nouvelle.", - "skillManagement.exportDialog.description": "Le brouillon actuellement enregistré de votre compétence sera exporté. Si des modifications ne sont pas enregistrées dans l’éditeur, enregistrez-les avant l’exportation.", - "skillManagement.exportDialog.title": "Exporter le brouillon actuellement enregistré", "skillManagement.import": "Importer", "skillManagement.importFailed": "Échec de l'importation de la compétence.", "skillManagement.importSuccess": "Skill importé.", @@ -236,12 +195,10 @@ "skillManagement.loadingError": "Échec du chargement des compétences", "skillManagement.moreActions": "Plus d’actions pour {{name}}", "skillManagement.noDescription": "Pas encore de description", - "skillManagement.noTags": "Aucune balise", "skillManagement.publishedAt": "Publié {{time}}", "skillManagement.referenceCount_one": "{{count}} Agent", "skillManagement.referenceCount_other": "{{count}} Agents", "skillManagement.searchLabel": "Rechercher des compétences", "skillManagement.searchPlaceholder": "Rechercher par nom ou description", - "skillManagement.tags": "Balises", "skillManagement.title": "Compétences" } diff --git a/web/i18n/fr-FR/snippet.json b/web/i18n/locales/fr-FR/snippet.json similarity index 100% rename from web/i18n/fr-FR/snippet.json rename to web/i18n/locales/fr-FR/snippet.json diff --git a/web/i18n/fr-FR/time.json b/web/i18n/locales/fr-FR/time.json similarity index 100% rename from web/i18n/fr-FR/time.json rename to web/i18n/locales/fr-FR/time.json diff --git a/web/i18n/fr-FR/tools.json b/web/i18n/locales/fr-FR/tools.json similarity index 100% rename from web/i18n/fr-FR/tools.json rename to web/i18n/locales/fr-FR/tools.json diff --git a/web/i18n/fr-FR/workflow.json b/web/i18n/locales/fr-FR/workflow.json similarity index 99% rename from web/i18n/fr-FR/workflow.json rename to web/i18n/locales/fr-FR/workflow.json index e7c9d61e467..19555844821 100644 --- a/web/i18n/fr-FR/workflow.json +++ b/web/i18n/locales/fr-FR/workflow.json @@ -460,8 +460,6 @@ "nodes.agent.pluginNotInstalledDesc": "Ce intégration est installé à partir de GitHub. Veuillez aller dans Intégrations pour réinstaller", "nodes.agent.roster.change": "Changer", "nodes.agent.roster.editInConsole": "Modifier dans Agents", - "nodes.agent.roster.editInConsoleDisabled": "La modification des agents nécessite le rôle Éditeur ou un rôle supérieur.", - "nodes.agent.roster.editInConsoleDisabledRbac": "Seuls les membres ayant le rôle Éditeur ou un rôle supérieur, ou l’autorisation Gérer Agents, peuvent modifier les agents.", "nodes.agent.roster.inlineSetup.description": "Une configuration ponctuelle qui n’existe que dans ce nœud", "nodes.agent.roster.inlineSetup.name": "Configuration inline", "nodes.agent.roster.inlineSetup.type": "Uniquement dans ce nœud", diff --git a/web/i18n/hi-IN/agent-v-2.json b/web/i18n/locales/hi-IN/agent-v-2.json similarity index 99% rename from web/i18n/hi-IN/agent-v-2.json rename to web/i18n/locales/hi-IN/agent-v-2.json index e85880e2891..104584650fd 100644 --- a/web/i18n/hi-IN/agent-v-2.json +++ b/web/i18n/locales/hi-IN/agent-v-2.json @@ -69,6 +69,7 @@ "agentDetail.configure.answer.activity.runningCommands": "कमांड चलाए जा रहे हैं", "agentDetail.configure.answer.duration.minute": "{{count}} मिनट", "agentDetail.configure.answer.duration.second": "{{count}} सेकंड", + "agentDetail.configure.answer.responseEnded": "उत्तर समाप्त हुआ", "agentDetail.configure.answer.thinking": "सोच रहा है", "agentDetail.configure.build.empty.communityEditionTip": "Community Edition निकाले गए कॉन्फ़िगरेशन के लिए वर्शनिंग प्रदान करता है, लेकिन फ़ाइल सिस्टम पर स्वयं वर्शनिंग का समर्थन नहीं करता। Build मोड में अपनी कार्रवाइयों के साथ सावधान रहें, क्योंकि फ़ाइल सिस्टम में किए गए बदलाव वास्तविक समय में होते हैं और हमेशा साफ़-सुथरे ढंग से वापस नहीं किए जा सकते। बड़े पैमाने पर बाहरी दर्शकों को सेवा देने के लिए Community Edition आदर्श विकल्प नहीं है।", "agentDetail.configure.build.empty.description": "आप जो चाहते हैं उसका वर्णन करें और बाईं ओर का फ़ॉर्म बातचीत के साथ भरता जाएगा।", @@ -269,7 +270,6 @@ "agentDetail.configure.workingDirectory.breadcrumbLabel": "फ़ाइल सिस्टम पथ", "agentDetail.configure.workingDirectory.description": "एजेंट चलते समय जिन फाइलों को पढ़ता और लिखता है। वह जो भी बनाता है, जैसे qna_report.pdf, यहां दिखता है।", "agentDetail.configure.workingDirectory.fileSystem": "फ़ाइल सिस्टम", - "agentDetail.configure.workingDirectory.open": "वर्किंग डायरेक्टरी खोलें", "agentDetail.configure.workingDirectory.persistentFiles": "स्थायी", "agentDetail.configure.workingDirectory.persistentFilesTooltip": "वे फ़ाइलें जिन्हें एजेंट रखता है और जहाँ भी वह चलता है वहाँ उपयोग कर सकता है, जिसमें अंतिम उपयोगकर्ताओं की बातचीत और वर्कफ़्लो शामिल हैं। इस बिल्ड चैट के दौरान यहाँ किए गए बदलाव आपके “लागू करें” पर क्लिक करने के बाद प्रभावी होते हैं।", "agentDetail.configure.workingDirectory.temporaryFiles": "अस्थायी", @@ -348,7 +348,6 @@ "agentDetail.sections.logs": "लॉग", "agentDetail.sections.monitoring": "निगरानी", "agentDetail.title": "एजेंट", - "agentDetail.type": "एजेंट", "agentDetail.versionHistory.empty": "अभी तक कोई संस्करण नहीं", "agentDetail.versionHistory.exitVersions": "संस्करणों से बाहर निकलें", "agentDetail.versionHistory.filter": "संस्करण फ़िल्टर करें", @@ -408,8 +407,5 @@ "roster.sort.lastModified": "अंतिम संशोधन", "roster.sort.recentlyCreated": "हाल ही में बनाए गए", "roster.title": "एजेंट", - "roster.usageStatus.draft": "ड्राफ्ट", - "skillManagement.detail.uploadFilesFailedStatus_one": "{{count}} फ़ाइल अपलोड नहीं हो सकी।", - "skillManagement.detail.uploadFilesFailedStatus_other": "{{count}} फ़ाइलें अपलोड नहीं हो सकीं।", - "skillManagement.title": "Skills" + "roster.usageStatus.draft": "ड्राफ्ट" } diff --git a/web/i18n/hi-IN/app-annotation.json b/web/i18n/locales/hi-IN/app-annotation.json similarity index 100% rename from web/i18n/hi-IN/app-annotation.json rename to web/i18n/locales/hi-IN/app-annotation.json diff --git a/web/i18n/hi-IN/app-api.json b/web/i18n/locales/hi-IN/app-api.json similarity index 100% rename from web/i18n/hi-IN/app-api.json rename to web/i18n/locales/hi-IN/app-api.json diff --git a/web/i18n/hi-IN/app-debug.json b/web/i18n/locales/hi-IN/app-debug.json similarity index 100% rename from web/i18n/hi-IN/app-debug.json rename to web/i18n/locales/hi-IN/app-debug.json diff --git a/web/i18n/hi-IN/app-log.json b/web/i18n/locales/hi-IN/app-log.json similarity index 97% rename from web/i18n/hi-IN/app-log.json rename to web/i18n/locales/hi-IN/app-log.json index 9f391be5bbd..6b057abb4a0 100644 --- a/web/i18n/hi-IN/app-log.json +++ b/web/i18n/locales/hi-IN/app-log.json @@ -4,11 +4,9 @@ "agentLogDetail.iteration": "चलना", "agentLogDetail.iterations": "पुनरूक्तियाँ", "agentLogDetail.toolUsed": "प्रयुक्त उपकरण", - "archives.action.downloadMonth": "{{month}} का आर्काइव डाउनलोड करें", "archives.action.downloadReady": "आर्काइव डाउनलोड तैयार है।", "archives.action.prepareDownload": "डाउनलोड तैयार करें", "archives.action.prepareFailed": "आर्काइव डाउनलोड तैयार करने में विफल रहा।", - "archives.action.prepareMonth": "{{month}} का आर्काइव डाउनलोड तैयार करें", "archives.action.prepareStarted": "आर्काइव डाउनलोड तैयार किया जा रहा है।", "archives.action.preparing": "तैयार किया जा रहा है", "archives.description": "इस वर्कस्पेस में आर्काइव किए गए workflow रन लॉग के मासिक CSV निर्यात डाउनलोड करें।", diff --git a/web/i18n/hi-IN/app-overview.json b/web/i18n/locales/hi-IN/app-overview.json similarity index 100% rename from web/i18n/hi-IN/app-overview.json rename to web/i18n/locales/hi-IN/app-overview.json diff --git a/web/i18n/hi-IN/app.json b/web/i18n/locales/hi-IN/app.json similarity index 100% rename from web/i18n/hi-IN/app.json rename to web/i18n/locales/hi-IN/app.json diff --git a/web/i18n/hi-IN/billing.json b/web/i18n/locales/hi-IN/billing.json similarity index 100% rename from web/i18n/hi-IN/billing.json rename to web/i18n/locales/hi-IN/billing.json diff --git a/web/i18n/hi-IN/common.json b/web/i18n/locales/hi-IN/common.json similarity index 97% rename from web/i18n/hi-IN/common.json rename to web/i18n/locales/hi-IN/common.json index e282692a184..d97f31dad19 100644 --- a/web/i18n/hi-IN/common.json +++ b/web/i18n/locales/hi-IN/common.json @@ -209,9 +209,7 @@ "mainNav.marketplace": "मार्केटप्लेस", "mainNav.skills": "Skills", "mainNav.webApps.noResults": "कोई वेब ऐप नहीं मिला", - "mainNav.webApps.openApp": "{{name}} वेब ऐप खोलें", "mainNav.webApps.searchPlaceholder": "वेब ऐप खोजें", - "mainNav.workspace.credits": "{{count}} क्रेडिट", "mainNav.workspace.creditsUnit": "क्रेडिट", "mainNav.workspace.inviteMembers": "Invite members", "mainNav.workspace.noResults": "कोई वर्कस्पेस नहीं मिला", @@ -233,7 +231,6 @@ "members.datasetOperatorTip": "केवल नॉलेज बेस प्रबंधित कर सकते हैं", "members.editRole": "भूमिका संपादित करें", "members.editorTip": "ऐप्स बना और संपादित कर सकता है", - "members.email": "ईमेल", "members.emailInvalid": "अवैध ईमेल प्रारूप", "members.emailNotSetup": "ईमेल सर्वर सेट नहीं है, इसलिए आमंत्रण ईमेल नहीं भेजे जा सकते। कृपया उपयोगकर्ताओं को आमंत्रण के बाद जारी किए जाने वाले आमंत्रण लिंक के बारे में सूचित करें。", "members.emailPlaceholder": "ईमेल पते टाइप या पेस्ट करें…", @@ -250,7 +247,6 @@ "members.inviteLimitExceeded": "सभी प्राप्तकर्ताओं के लिए पर्याप्त सीटें उपलब्ध नहीं हैं।", "members.inviteTeamMember": "टीम सदस्य जोड़ें", "members.inviteTeamMemberTip": "वे साइन इन करने के बाद सीधे आपकी टीम डेटा तक पहुंच सकते हैं।", - "members.invitedAsRole": "{{role}} के रूप में आमंत्रित किया गया", "members.lastActive": "अंतिम सक्रियता", "members.memberActions": "सदस्य कार्रवाइयाँ", "members.memberDetails.assign": "असाइन करें", @@ -258,8 +254,6 @@ "members.memberDetails.assignedRoles": "असाइन की गई भूमिकाएँ", "members.memberDetails.customGroup": "अनुकूलित", "members.memberDetails.generalGroup": "सामान्य", - "members.memberDetails.openAria": "{{name}} के लिए सदस्य विवरण खोलें", - "members.memberDetails.roleActionsAria": "{{role}} भूमिका के लिए कार्रवाइयाँ खोलें", "members.memberDetails.roleNoPermissionSummary": "वर्तमान भूमिका के पास कोई अनुमति नहीं है।", "members.memberDetails.rolePermissionSummary": "{{role}} {{permissions}} कर सकता है", "members.memberDetails.title": "सदस्य विवरण", @@ -370,7 +364,6 @@ "modelProvider.card.usagePriorityTip": "मॉडल चलाते समय पहले किस संसाधन का उपयोग करना है, यह सेट करें।", "modelProvider.configureTip": "एपीआई-कुंजी सेट करें या उपयोग के लिए मॉडल जोड़ें", "modelProvider.confirmDelete": "हटाने की पुष्टि करें?", - "modelProvider.credits": "संदेश क्रेडिट्स", "modelProvider.defaultConfig": "डिफ़ॉल्ट कॉन्फ़िग", "modelProvider.discoverMore": "और अधिक खोजें", "modelProvider.embeddingModel.key": "एंबेडिंग मॉडल", @@ -427,7 +420,6 @@ "modelProvider.selector.noProviderConfiguredDesc": "इंस्टॉल करने के लिए Marketplace ब्राउज़ करें, या सेटिंग्स में प्रदाता कॉन्फ़िगर करें।", "modelProvider.selector.onlyCompatibleModelsShown": "केवल संगत मॉडल दिखाए गए हैं", "modelProvider.selector.showIncompatibleModels": "असंगत मॉडल दिखाएं", - "modelProvider.selector.suggestion": "सुझाव", "modelProvider.selector.suggestionTip": "एजेंट कार्यों के लिए अनुशंसित", "modelProvider.showModels": "मॉडल्स दिखाएं", "modelProvider.speechToTextModel.key": "भाषण-से-पाठ मॉडल", @@ -458,6 +450,8 @@ "operation.confirmAction": "कृपया अपनी क्रिया की पुष्टि करें।", "operation.copied": "कॉपी किया गया", "operation.copy": "कॉपी करें", + "operation.copyErrorDetails": "त्रुटि का विवरण कॉपी करें", + "operation.copyErrorFailed": "कॉपी नहीं हुआ। फिर कोशिश करें", "operation.copyImage": "छवि कॉपी करें", "operation.create": "बनाएं", "operation.deSelectAll": "सभी चयन हटाएँ", @@ -475,7 +469,6 @@ "operation.format": "फॉर्मेट", "operation.imageCopied": "कॉपी की गई छवि", "operation.imageCopyFailed": "इमेज कॉपी नहीं हो सकी। कृपया इसे डाउनलोड करें।", - "operation.imageDownloaded": "छवि डाउनलोड की गई", "operation.in": "में", "operation.learnMore": "अधिक जानें", "operation.log": "लॉग", @@ -512,7 +505,6 @@ "operation.sure": "मुझे यकीन है", "operation.toggleFullscreen": "फ़ुलस्क्रीन टॉगल करें", "operation.toggleMute": "म्यूट टॉगल करें", - "operation.upload": "Upload", "operation.view": "देखना", "operation.viewDetails": "विवरण देखें", "operation.viewMore": "और देखें", @@ -542,7 +534,6 @@ "promptEditor.requestURL.item.title": "अनुरोध URL", "promptEditor.variable.modal.add": "नया वेरिएबल", "promptEditor.variable.modal.addTool": "नया उपकरण", - "provider.anthropicHosted.trialQuotaTip": "आपका एंथ्रोपिक परीक्षण कोटा 2025/03/11 को समाप्त हो जाएगा और उसके बाद उपलब्ध नहीं रहेगा। कृपया इसका समय पर उपयोग करें।", "provider.encrypted.back": " तकनीक का उपयोग करके।", "provider.encrypted.front": "आपकी एपीआई कुंजी को एन्क्रिप्ट किया जाएगा और संग्रहीत किया जाएगा", "provider.validating": "कुंजी का सत्यापन हो रहा है...", @@ -565,7 +556,6 @@ "settings.preferences": "Preferences", "settings.provider": "मॉडल प्रदाता", "settings.resourceAccess": "संसाधन पहुँच", - "settings.resourceAccessDescription": "ऐप्स और ज्ञान आधार उपयोग कर सकने वाले अनुमति नियमों को कॉन्फ़िगर करें। अनुमति नियमों को विशिष्ट संसाधन पहुँच कॉन्फ़िगरेशन में सदस्यों को असाइन किया जा सकता है।", "settings.rolesAndPermissions": "भूमिकाएँ और अनुमतियाँ", "settings.settings": "Settings", "settings.swaggerAPIAsTool": "Swagger API as Tool", @@ -695,7 +685,6 @@ "userProfile.helpCenter": "सहायता", "userProfile.logout": "लॉग आउट", "userProfile.roadmap": "रोडमैप", - "userProfile.settings": "सेटिंग्स", "userProfile.workspace": "वर्कस्पेस", "voice.language.arTN": "ट्यूनीशियाई अरबी", "voice.language.deDE": "जर्मन", diff --git a/web/i18n/hi-IN/custom.json b/web/i18n/locales/hi-IN/custom.json similarity index 100% rename from web/i18n/hi-IN/custom.json rename to web/i18n/locales/hi-IN/custom.json diff --git a/web/i18n/hi-IN/dataset-creation.json b/web/i18n/locales/hi-IN/dataset-creation.json similarity index 100% rename from web/i18n/hi-IN/dataset-creation.json rename to web/i18n/locales/hi-IN/dataset-creation.json diff --git a/web/i18n/hi-IN/dataset-documents.json b/web/i18n/locales/hi-IN/dataset-documents.json similarity index 100% rename from web/i18n/hi-IN/dataset-documents.json rename to web/i18n/locales/hi-IN/dataset-documents.json diff --git a/web/i18n/hi-IN/dataset-hit-testing.json b/web/i18n/locales/hi-IN/dataset-hit-testing.json similarity index 100% rename from web/i18n/hi-IN/dataset-hit-testing.json rename to web/i18n/locales/hi-IN/dataset-hit-testing.json diff --git a/web/i18n/hi-IN/dataset-pipeline.json b/web/i18n/locales/hi-IN/dataset-pipeline.json similarity index 100% rename from web/i18n/hi-IN/dataset-pipeline.json rename to web/i18n/locales/hi-IN/dataset-pipeline.json diff --git a/web/i18n/hi-IN/dataset-settings.json b/web/i18n/locales/hi-IN/dataset-settings.json similarity index 100% rename from web/i18n/hi-IN/dataset-settings.json rename to web/i18n/locales/hi-IN/dataset-settings.json diff --git a/web/i18n/hi-IN/dataset.json b/web/i18n/locales/hi-IN/dataset.json similarity index 97% rename from web/i18n/hi-IN/dataset.json rename to web/i18n/locales/hi-IN/dataset.json index 8ef4bff4595..2569c60f138 100644 --- a/web/i18n/hi-IN/dataset.json +++ b/web/i18n/locales/hi-IN/dataset.json @@ -163,7 +163,6 @@ "newKnowledge.configureProvider": "{{provider}} कॉन्फ़िगर करें", "newKnowledge.connectNotion": "Notion कनेक्ट करें", "newKnowledge.connectProvider": "{{provider}} कनेक्ट करें", - "newKnowledge.connectProviderGeneric": "प्रदाता कनेक्ट करें", "newKnowledge.connectSource": "स्रोत कनेक्ट करें", "newKnowledge.connectSourceDescription": "कंटेंट Notion, वेबसाइटों और अन्य सेवाओं के साथ सिंक रहता है।", "newKnowledge.connectingProvider": "कनेक्ट हो रहा है...", @@ -179,18 +178,14 @@ "newKnowledge.crawlFailedDescription": "क्रॉलर यह पूर्वावलोकन पूरा नहीं कर सका। URL जाँचें, क्रॉल विकल्प बदलें या फिर से प्रयास करें।", "newKnowledge.crawlFailedProvider": "{{provider}} ने त्रुटि की सूचना दी। प्रदाता कनेक्शन जाँचें और फिर से प्रयास करें।", "newKnowledge.crawlFailedTimeout": "क्रॉल का समय समाप्त हो गया। अधिक विशिष्ट URL आज़माएँ या पेज की अधिकतम संख्या घटाएँ।", - "newKnowledge.crawlNotStarted": "शुरू नहीं हुआ", "newKnowledge.crawlOptions": "क्रॉल विकल्प", "newKnowledge.crawlPreview": "क्रॉल पूर्वावलोकन", "newKnowledge.crawlProgress": "{{host}} के लिए क्रॉल प्रगति", - "newKnowledge.crawlSetupUnavailableDescription": "क्रॉल वर्कफ़्लो उपलब्ध होने तक वेबसाइट क्रॉल कॉन्फ़िगरेशन अक्षम है।", - "newKnowledge.crawlSetupUnavailableTitle": "फायरक्रॉल तैयार है", "newKnowledge.crawlStartFailed": "क्रॉल पूर्वावलोकन शुरू नहीं हो सका। फिर से प्रयास करें।", "newKnowledge.crawlStopped": "क्रॉल रोक दिया गया", "newKnowledge.crawling": "क्रॉल किया जा रहा है…", "newKnowledge.crawlingPages": "{{host}} को क्रॉल किया जा रहा है… {{count}} पेज मिले", "newKnowledge.createFailed": "हम यह ज्ञानकोष नहीं बना सके। अपनी पहुंच जांचें और पुनः प्रयास करें।", - "newKnowledge.createIllustrationTitle": "अपनी सामग्री को एक बार जोड़ें — हर ऐप नवीनतम ज्ञान के साथ उत्तर देगा।", "newKnowledge.createTitle": "ज्ञान पैदा करें", "newKnowledge.createdAt": "बनाया गया", "newKnowledge.creators": "निर्माता", @@ -220,7 +215,6 @@ "newKnowledge.documentColumn": "दस्तावेज़", "newKnowledge.documentContentIncomplete": "दस्तावेज़ की शेष सामग्री लोड हो रही है…", "newKnowledge.documentContents": "विषय-सूची", - "newKnowledge.documentFacts": "दस्तावेज़ विवरण", "newKnowledge.documentFilterLabel": "स्थिति के अनुसार दस्तावेज़ फ़िल्टर करें", "newKnowledge.documentLoadErrorDescription": "यह दस्तावेज़ लोड नहीं किया जा सका। फिर से कोशिश करें।", "newKnowledge.documentLoadErrorTitle": "दस्तावेज़ लोड नहीं हुआ", @@ -268,7 +262,6 @@ "newKnowledge.documentsSelected": "{{count}} दस्तावेज़ चयनित", "newKnowledge.documentsUnavailable": "— दस्तावेज़", "newKnowledge.downloadDocuments": "डाउनलोड करें", - "newKnowledge.editSource": "संपादित करें", "newKnowledge.emptyChunk": "खाली खंड", "newKnowledge.emptyDescription": "अपने दस्तावेज़ जोड़ें ताकि एजेंट उन्हें सीधे पढ़ सकें — इंडेक्सिंग कॉन्फ़िगर करने की ज़रूरत नहीं।", "newKnowledge.emptyTitle": "नॉलेज बेस बनाने का अधिक स्मार्ट तरीका", @@ -344,7 +337,6 @@ "newKnowledge.processingTaskState.succeeded": "पूरा हुआ", "newKnowledge.processingTaskState.superseded": "अधिक्रमण किया हुआ", "newKnowledge.providerConnected": "{{provider}} जुड़ा", - "newKnowledge.providerLabel": "एक प्रदाता का चयन करें", "newKnowledge.providerLoadFailed": "प्रदाता लोड नहीं कर सके", "newKnowledge.providerNotConfigured": "{{provider}} कॉन्फ़िगर नहीं है", "newKnowledge.providerNotConfiguredDescription": "{{provider}} के साथ वेबसाइटों को क्रॉल करने के लिए एक एपीआई कुंजी या एंडपॉइंट यूआरएल जोड़ें।", @@ -369,12 +361,10 @@ "newKnowledge.revisionState.superseded": "पिछला", "newKnowledge.rootUrl": "रूट URL", "newKnowledge.rootUrlPlaceholder": "https://docs.example.com", - "newKnowledge.routeUnavailable": "यह अनुभाग अगले सेटअप चरण में उपलब्ध हो जाएगा.", "newKnowledge.searchDocuments": "दस्तावेज़ खोजें", "newKnowledge.searchSources": "स्रोत खोजें", "newKnowledge.selectAll": "सभी चुनें", "newKnowledge.selectAllDocuments": "सभी दस्तावेज़ चुनें", - "newKnowledge.selectAllSources": "सभी स्रोत चुनें", "newKnowledge.selectChunk": "सामग्री देखने के लिए कोई खंड चुनें।", "newKnowledge.selectFilesAndFolders": "सिंक करने के लिए फ़ाइलें और फ़ोल्डर चुनें", "newKnowledge.skippedFailed": "छोड़ा गया (विफल)", @@ -384,7 +374,6 @@ "newKnowledge.sourceFilterLabel": "स्थिति के अनुसार स्रोतों को फ़िल्टर करें", "newKnowledge.sourceName": "स्रोत का नाम", "newKnowledge.sourceNamePlaceholder": "जैसे, उत्पाद दस्तावेज़ क्रॉल", - "newKnowledge.sourceNameRequired": "स्रोत का नाम दर्ज करें।", "newKnowledge.sourceSetupBackendDependency": "स्रोत सेटअप पूरा दिखाया गया है; संपादन और पूर्वावलोकन के लिए ड्राफ़्ट स्रोत बैकएंड कार्यप्रवाह आवश्यक है।", "newKnowledge.sourceStatus.active": "सक्रिय", "newKnowledge.sourceStatus.disabled": "विकलांग", @@ -431,7 +420,6 @@ "newKnowledge.unavailableDescription": "KnowledgeFS अभी इस कार्यस्थान के लिए उपलब्ध नहीं है।", "newKnowledge.unavailableTitle": "नया नॉलेज उपलब्ध नहीं है", "newKnowledge.updated": "{{date}} को अपडेट किया गया", - "newKnowledge.updatedAt": "अंतिम अपडेट", "newKnowledge.updatedColumn": "अद्यतन किया गया", "newKnowledge.uploadCharactersUnavailable": "वर्ण: —", "newKnowledge.uploadDocuments": "दस्तावेज़ अपलोड करें", @@ -460,11 +448,9 @@ "retrievalSettings": "पुनर्प्राप्ति सेटिंग्स", "retry": "पुनः प्रयास करें", "selectExternalKnowledgeAPI.placeholder": "एक बाहरी ज्ञान API चुनें", - "serviceApi.card.addApiKey": "एपीआई कुंजी जोड़ें", "serviceApi.card.apiKey": "एपीआई कुंजी", "serviceApi.card.apiReference": "एपीआई संदर्भ", "serviceApi.card.endpoint": "सेवा एपीआई एंडपॉइंट", - "serviceApi.card.manageApiKey": "एपीआई कुंजी प्रबंधित करें", "serviceApi.card.title": "बैकएंड सेवा एपीआई", "serviceApi.disabled": "अक्षम", "serviceApi.enabled": "सेवा में", diff --git a/web/i18n/hi-IN/deployments.json b/web/i18n/locales/hi-IN/deployments.json similarity index 98% rename from web/i18n/hi-IN/deployments.json rename to web/i18n/locales/hi-IN/deployments.json index 2ee9198915f..db62e98f2d3 100644 --- a/web/i18n/hi-IN/deployments.json +++ b/web/i18n/locales/hi-IN/deployments.json @@ -27,7 +27,6 @@ "list.emptyTitle": "अभी तक कोई डिप्लॉयमेंट नहीं", "overview.chip.latest": "नवीनतम", "status.RUNTIME_INSTANCE_STATUS_DEPLOYING": "डिप्लॉय हो रहा है", - "status.RUNTIME_INSTANCE_STATUS_FAILED": "डिप्लॉय विफल", "status.RUNTIME_INSTANCE_STATUS_INVALID": "अमान्य", "status.RUNTIME_INSTANCE_STATUS_READY": "चल रहा है", "status.RUNTIME_INSTANCE_STATUS_UNDEPLOYED": "डिप्लॉय नहीं किया गया", @@ -87,6 +86,5 @@ "studio.versionValue": "वर्शन का मान", "studio.versionsBehind_one": "{{count}} वर्शन पीछे", "studio.versionsBehind_other": "{{count}} वर्शन पीछे", - "versions.checkingReleaseContent": "रिलीज़ सामग्री जांची जा रही है...", "versions.deployTo": "{{name}} पर डिप्लॉय करें" } diff --git a/web/i18n/hi-IN/device-flow.json b/web/i18n/locales/hi-IN/device-flow.json similarity index 100% rename from web/i18n/hi-IN/device-flow.json rename to web/i18n/locales/hi-IN/device-flow.json diff --git a/web/i18n/hi-IN/education.json b/web/i18n/locales/hi-IN/education.json similarity index 100% rename from web/i18n/hi-IN/education.json rename to web/i18n/locales/hi-IN/education.json diff --git a/web/i18n/hi-IN/explore.json b/web/i18n/locales/hi-IN/explore.json similarity index 100% rename from web/i18n/hi-IN/explore.json rename to web/i18n/locales/hi-IN/explore.json diff --git a/web/i18n/hi-IN/layout.json b/web/i18n/locales/hi-IN/layout.json similarity index 100% rename from web/i18n/hi-IN/layout.json rename to web/i18n/locales/hi-IN/layout.json diff --git a/web/i18n/hi-IN/login.json b/web/i18n/locales/hi-IN/login.json similarity index 100% rename from web/i18n/hi-IN/login.json rename to web/i18n/locales/hi-IN/login.json diff --git a/web/i18n/hi-IN/oauth.json b/web/i18n/locales/hi-IN/oauth.json similarity index 100% rename from web/i18n/hi-IN/oauth.json rename to web/i18n/locales/hi-IN/oauth.json diff --git a/web/i18n/hi-IN/permission-keys.json b/web/i18n/locales/hi-IN/permission-keys.json similarity index 100% rename from web/i18n/hi-IN/permission-keys.json rename to web/i18n/locales/hi-IN/permission-keys.json diff --git a/web/i18n/hi-IN/permission.json b/web/i18n/locales/hi-IN/permission.json similarity index 100% rename from web/i18n/hi-IN/permission.json rename to web/i18n/locales/hi-IN/permission.json diff --git a/web/i18n/hi-IN/pipeline.json b/web/i18n/locales/hi-IN/pipeline.json similarity index 100% rename from web/i18n/hi-IN/pipeline.json rename to web/i18n/locales/hi-IN/pipeline.json diff --git a/web/i18n/hi-IN/plugin-tags.json b/web/i18n/locales/hi-IN/plugin-tags.json similarity index 100% rename from web/i18n/hi-IN/plugin-tags.json rename to web/i18n/locales/hi-IN/plugin-tags.json diff --git a/web/i18n/hi-IN/plugin-trigger.json b/web/i18n/locales/hi-IN/plugin-trigger.json similarity index 100% rename from web/i18n/hi-IN/plugin-trigger.json rename to web/i18n/locales/hi-IN/plugin-trigger.json diff --git a/web/i18n/hi-IN/plugin.json b/web/i18n/locales/hi-IN/plugin.json similarity index 99% rename from web/i18n/hi-IN/plugin.json rename to web/i18n/locales/hi-IN/plugin.json index fddc56901cc..de2d725b439 100644 --- a/web/i18n/hi-IN/plugin.json +++ b/web/i18n/locales/hi-IN/plugin.json @@ -277,7 +277,6 @@ "readmeInfo.noReadmeAvailable": "कोई README उपलब्ध नहीं है", "readmeInfo.title": "पढ़ें मुझे", "requestAPlugin": "एक प्लगइन का अनुरोध करें", - "requestSubmit": "अनुरोध करें या प्रकाशित करें", "search": "खोज", "searchCategories": "खोज श्रेणियाँ", "searchInMarketplace": "मार्केटप्लेस में खोजें", diff --git a/web/i18n/hi-IN/register.json b/web/i18n/locales/hi-IN/register.json similarity index 100% rename from web/i18n/hi-IN/register.json rename to web/i18n/locales/hi-IN/register.json diff --git a/web/i18n/hi-IN/run-log.json b/web/i18n/locales/hi-IN/run-log.json similarity index 100% rename from web/i18n/hi-IN/run-log.json rename to web/i18n/locales/hi-IN/run-log.json diff --git a/web/i18n/hi-IN/share.json b/web/i18n/locales/hi-IN/share.json similarity index 100% rename from web/i18n/hi-IN/share.json rename to web/i18n/locales/hi-IN/share.json diff --git a/web/i18n/hi-IN/skill.json b/web/i18n/locales/hi-IN/skill.json similarity index 83% rename from web/i18n/hi-IN/skill.json rename to web/i18n/locales/hi-IN/skill.json index 0a2ac7307bc..32696bf5ec4 100644 --- a/web/i18n/hi-IN/skill.json +++ b/web/i18n/locales/hi-IN/skill.json @@ -1,5 +1,4 @@ { - "skillManagement.clearTags": "टैग साफ़ करें", "skillManagement.create": "बनाएँ", "skillManagement.createFailed": "कौशल पैदा करने में असफल.", "skillManagement.createSuccess": "ड्राफ्ट बनाया गया.", @@ -13,12 +12,9 @@ "skillManagement.deleteFailed": "कौशल हटाने में विफल.", "skillManagement.deleteSuccess": "Skill हटा दिया गया.", "skillManagement.detail.addMetadata": "मेटाडेटा जोड़ें", - "skillManagement.detail.addMetadataDescription": "इस मार्कडाउन फ़ाइल में एक फ्रंटमैटर फ़ील्ड जोड़ें।", "skillManagement.detail.addTag": "टैग जोड़ें", - "skillManagement.detail.addTagDescription": "इस Skill पर एक टैग बनाएं या बाइंड करें। नए टैग Skill मेटाडेटा के साथ सहेजे जाते हैं।", "skillManagement.detail.addTagSuccess": "टैग जोड़ा गया.", "skillManagement.detail.back": "Skills पर वापस जाएँ", - "skillManagement.detail.builder.applyFailed": "ड्राफ्ट अपडेट लागू करने में विफल.", "skillManagement.detail.builder.attach": "फ़ाइल जोड़ें", "skillManagement.detail.builder.attachFailed": "फ़ाइल संलग्न करने में विफल.", "skillManagement.detail.builder.attachLimit": "आप अधिकतम 10 फ़ाइलें संलग्न कर सकते हैं.", @@ -26,22 +22,12 @@ "skillManagement.detail.builder.attachUnsupported": "केवल छवियाँ, पाठ और दस्तावेज़ फ़ाइलें ही संलग्न की जा सकती हैं।", "skillManagement.detail.builder.attachmentOnlyMessage": "इस कौशल को बेहतर बनाने में सहायता के लिए संलग्न फ़ाइल का उपयोग करें।", "skillManagement.detail.builder.close": "Skill Builder बंद करें", - "skillManagement.detail.builder.compatibleModelsOnly": "केवल संगत मॉडल दिखाए गए हैं", "skillManagement.detail.builder.copyResponse": "प्रतिक्रिया कॉपी करें", "skillManagement.detail.builder.copySuccess": "प्रतिक्रिया क्लिपबोर्ड पर कॉपी की गई.", "skillManagement.detail.builder.editIntro": "मैंने इस कौशल की समीक्षा की है. आप क्या समायोजित करना चाहेंगे?", "skillManagement.detail.builder.exampleIssueTriage": "ग्राहक समस्या ट्राइएज", "skillManagement.detail.builder.exampleOnboarding": "नया किराया ऑनबोर्डिंग गाइड", "skillManagement.detail.builder.exampleSalesFollowUp": "बिक्री नेतृत्व अनुवर्ती रणनीति", - "skillManagement.detail.builder.followUpDisplayName": "प्रदर्शन नाम के रूप में धनवापसी अनुमोदन का उपयोग करें", - "skillManagement.detail.builder.followUpNameIcon": "सुझाए गए नाम और आइकन को लागू करें", - "skillManagement.detail.builder.fromMarketplace": "Marketplace से", - "skillManagement.detail.builder.model": "GPT-4o", - "skillManagement.detail.builder.modelCredits.all": "सभी श्रेय", - "skillManagement.detail.builder.modelCredits.configure": "कॉन्फ़िगरेशन आवश्यक", - "skillManagement.detail.builder.modelCredits.exhausted": "कोटा समाप्त", - "skillManagement.detail.builder.modelProviderSettings": "मॉडल प्रदाता सेटिंग्स", - "skillManagement.detail.builder.modelSearch": "मॉडल खोजें...", "skillManagement.detail.builder.modifyPlaceholder": "एआई से इस कौशल को संशोधित करने के लिए कहें...", "skillManagement.detail.builder.open": "Skill Builder खोलें", "skillManagement.detail.builder.placeholder": "परिदृश्य का वर्णन करें...", @@ -61,8 +47,6 @@ "skillManagement.detail.builder.thinkingUnavailable": "मॉडल द्वारा कोई विचारशील सामग्री वापस नहीं की गई।", "skillManagement.detail.builder.title": "Skill Builder", "skillManagement.detail.builder.tryExample": "एक उदाहरण आज़माएँ", - "skillManagement.detail.builder.voice": "वॉइस इनपुट", - "skillManagement.detail.builder.voiceUnavailable": "वॉयस इनपुट अभी उपलब्ध नहीं है.", "skillManagement.detail.cancelAddMetadata": "मेटाडेटा जोड़ना रद्द करें", "skillManagement.detail.closeFileTab": "बंद करें {{name}}", "skillManagement.detail.closeVersions": "संस्करण बंद करें", @@ -70,14 +54,10 @@ "skillManagement.detail.copyContentSuccess": "सामग्री क्लिपबोर्ड पर कॉपी की गई.", "skillManagement.detail.copyFile": "प्रतिलिपि", "skillManagement.detail.copyFileSuccess": "फ़ाइल चिपकाने के लिए तैयार है.", - "skillManagement.detail.copyVersionId": "आईडी कॉपी करें", - "skillManagement.detail.copyVersionIdSuccess": "आईडी को क्लिपबोर्ड पर कॉपी किया गया.", "skillManagement.detail.createFile": "नई फ़ाइल", - "skillManagement.detail.createFileDescription": "कौशल रूट के सापेक्ष एक फ़ाइल पथ दर्ज करें।", "skillManagement.detail.createFileMenu": "नई फ़ाइल...", "skillManagement.detail.createFileSuccess": "फ़ाइल बनाई गई.", "skillManagement.detail.createFolder": "नया फ़ोल्डर", - "skillManagement.detail.createFolderDescription": "कौशल रूट के सापेक्ष एक फ़ोल्डर पथ दर्ज करें।", "skillManagement.detail.createFolderMenu": "नया फ़ोल्डर...", "skillManagement.detail.createFolderSuccess": "फ़ोल्डर बनाया गया.", "skillManagement.detail.createdBy": "{{name}} द्वारा बनाया गया", @@ -100,10 +80,8 @@ "skillManagement.detail.fileMeta": "{{type}} · {{size}} बाइट्स", "skillManagement.detail.fileMissing": "फ़ाइल प्राप्त नहीं हुई।", "skillManagement.detail.fileOperationFailed": "फ़ाइल कार्रवाई विफल रही.", - "skillManagement.detail.files": "फ़ाइलें", "skillManagement.detail.latest": "नवीनतम", "skillManagement.detail.loadFailed": "कौशल लोड करने में विफल.", - "skillManagement.detail.loadingTags": "टैग लोड हो रहे हैं...", "skillManagement.detail.markdownLiveMode": "लाइव पूर्वावलोकन", "skillManagement.detail.markdownSourceMode": "स्रोत संपादक", "skillManagement.detail.metadataKey": "नाम", @@ -113,10 +91,7 @@ "skillManagement.detail.nameThisVersion": "इस संस्करण का नाम दें", "skillManagement.detail.noFileSelected": "पूर्वावलोकन करने के लिए एक फ़ाइल का चयन करें.", "skillManagement.detail.noFiles": "कोई फ़ाइल नहीं.", - "skillManagement.detail.noSearchResults": "कोई मेल खाती फ़ाइल नहीं.", - "skillManagement.detail.noSelectableTags": "कोई टैग उपलब्ध नहीं.", "skillManagement.detail.noVersions": "अभी तक कोई प्रकाशित संस्करण नहीं.", - "skillManagement.detail.pasteFile": "पेस्ट करें", "skillManagement.detail.pasteFileSuccess": "फ़ाइल चिपकाई गई.", "skillManagement.detail.previewUnsupported": "यह फ़ाइल पूर्वावलोकन का समर्थन नहीं करती।", "skillManagement.detail.publish": "प्रकाशित करें", @@ -128,9 +103,7 @@ "skillManagement.detail.publishUpdate": "अपडेट प्रकाशित करें", "skillManagement.detail.published": "प्रकाशित", "skillManagement.detail.publishedAt": "{{time}} प्रकाशित", - "skillManagement.detail.publishedVersion": "#{{number}} प्रकाशित", "skillManagement.detail.publishing": "प्रकाशित हो रहा है...", - "skillManagement.detail.readonly": "केवल पढ़ने के लिए", "skillManagement.detail.referenceFiles.confirm": "पुष्टि दर्ज करें", "skillManagement.detail.referenceFiles.empty": "कोई फ़ाइल उपलब्ध नहीं.", "skillManagement.detail.referenceFiles.livePlaceholder": "एजेंट के लिए निर्देश लिखें. फ़ाइलों को संदर्भित करने के लिए / टाइप करें।", @@ -138,14 +111,10 @@ "skillManagement.detail.referenceFiles.title": "संदर्भ फ़ाइलें", "skillManagement.detail.referencedBy_one": "{{count}} एजेंट द्वारा उपयोग किया जाता है", "skillManagement.detail.referencedBy_other": "{{count}} एजेंटों द्वारा उपयोग किया जाता है", - "skillManagement.detail.removeMetadata": "{{name}} मेटाडेटा हटाएं", "skillManagement.detail.removeTag": "{{tag}} हटाएं", "skillManagement.detail.removeTagSuccess": "टैग हटा दिया गया.", - "skillManagement.detail.renameFile": "फ़ाइल का नाम बदलें", - "skillManagement.detail.renameFileDescription": "कौशल रूट के सापेक्ष नया पथ दर्ज करें।", "skillManagement.detail.renameFileSuccess": "फ़ाइल का नाम बदला गया.", "skillManagement.detail.renameSkillSuccess": "Skill का नाम बदला गया.", - "skillManagement.detail.renameVersion": "नाम बदलें", "skillManagement.detail.renameVersionFailed": "संस्करण का नाम बदलने में विफल.", "skillManagement.detail.renameVersionPrompt": "संस्करण का नाम", "skillManagement.detail.renameVersionSuccess": "संस्करण का नाम बदला गया.", @@ -155,17 +124,14 @@ "skillManagement.detail.restoreVersionConfirmTitle": "इस संस्करण को पुनर्स्थापित करें?", "skillManagement.detail.restoreVersionFailed": "संस्करण पुनर्स्थापित करने में विफल.", "skillManagement.detail.restoreVersionSuccess": "संस्करण को ड्राफ्ट में पुनर्स्थापित किया गया।", - "skillManagement.detail.save": "सहेजें", "skillManagement.detail.saveConflictConfirmDescription": "यह कौशल अन्यत्र अद्यतन किया गया था. संपादन जारी रखने के लिए नवीनतम ड्राफ्ट लोड करें। आपके वर्तमान परिवर्तन खारिज कर दिए जाएंगे.", "skillManagement.detail.saveConflictConfirmTitle": "नवीनतम ड्राफ्ट लोड करें?", "skillManagement.detail.saveConflictReload": "नवीनतम ड्राफ्ट लोड करें", "skillManagement.detail.saveConflictStatus": "संघर्ष का पता चला. संपादन जारी रखने के लिए नवीनतम ड्राफ्ट लोड करें।", "skillManagement.detail.saveFailed": "फ़ाइल सहेजने में विफल.", - "skillManagement.detail.saveSuccess": "फ़ाइल सहेजी गई.", "skillManagement.detail.saved": "सहेजा गया", "skillManagement.detail.savedAt": "{{time}} सहेजा गया", "skillManagement.detail.saving": "सेव हो रहा है...", - "skillManagement.detail.searchFiles": "फ़ाइलें खोजें", "skillManagement.detail.showMoreReferences": "{{count}} और अधिक दिखाएँ", "skillManagement.detail.skillDescriptionPlaceholder": "वर्णन करें कि यह कौशल क्या करता है और एजेंटों को इसका उपयोग कब करना चाहिए।", "skillManagement.detail.skillNamePlaceholder": "छोटे अक्षरों, संख्याओं और हाइफ़न का उपयोग करें।", @@ -177,16 +143,12 @@ "skillManagement.detail.uploadConflict": "इस नाम की एक फ़ाइल पहले से मौजूद है.", "skillManagement.detail.uploadFailureSummary": "{{total}} में से {{failed}} फ़ाइलें विफल रहीं", "skillManagement.detail.uploadFailureTitle": "कुछ अपलोड विफल रहे", - "skillManagement.detail.uploadFile": "फ़ाइल अपलोड करें", - "skillManagement.detail.uploadFileFailed": "फ़ाइल अपलोड विफल रहा.", "skillManagement.detail.uploadFileSuccess": "फ़ाइल अपलोड हो गई।", "skillManagement.detail.uploadFilesButton": "{{count}} फ़ाइलें अपलोड करें", "skillManagement.detail.uploadFilesFailedStatus_one": "{{count}} फ़ाइल अपलोड विफल रहा.", "skillManagement.detail.uploadFilesFailedStatus_other": "{{count}} फ़ाइल अपलोड विफल रहा.", "skillManagement.detail.uploadFilesMenu": "फाइलें अपलोड करें...", "skillManagement.detail.uploadFilesProgress": "{{completed}}/{{total}} अपलोड हो रहा है", - "skillManagement.detail.uploadFilesResult": "{{uploaded}} अपलोड किया गया · {{failed}} विफल रहा", - "skillManagement.detail.uploadFilesStatus": "अपलोड स्थिति", "skillManagement.detail.uploadInvalidExtension": "यह फ़ाइल प्रकार समर्थित नहीं है.", "skillManagement.detail.uploadInvalidName": "अमान्य नाम।", "skillManagement.detail.uploadInvalidPath": "अमान्य फ़ाइल पथ.", @@ -206,7 +168,6 @@ "skillManagement.detail.uploadWillReplace": "मौजूदा फ़ाइल को बदल देगा", "skillManagement.detail.uploadWillUseName": "{{name}} के रूप में अपलोड किया जाएगा", "skillManagement.detail.versionHistory": "संस्करण इतिहास खोलें", - "skillManagement.detail.versionMeta": "{{hash}} · {{time}}", "skillManagement.detail.versionPublishNote": "रिलीज नोट्स", "skillManagement.detail.versionPublishNotePlaceholder": "इस परिवर्तन का वर्णन करें", "skillManagement.detail.versionPublishedMeta": "{{time}} · {{name}}", @@ -227,8 +188,6 @@ "skillManagement.errors.missingSkillMd": "इस पैकेज में कोई SKILL.md नहीं है। फ़ाइल जोड़ें और इसे फिर से आयात करें।", "skillManagement.errors.nameConflict": "\"{{name}}\" नामक कौशल पहले से मौजूद है। इसे SKILL.md में नाम बदलें और फिर से आयात करें।", "skillManagement.errors.workspaceLimit": "आपका कार्यक्षेत्र 500 कौशल की सीमा तक पहुंच गया है। नया जोड़ने के लिए एक कौशल हटाएँ।", - "skillManagement.exportDialog.description": "इससे आपके कौशल का वर्तमान सहेजा गया ड्राफ़्ट निर्यात होगा। यदि संपादक में कोई बदलाव सहेजे नहीं गए हैं, तो निर्यात करने से पहले उन्हें सहेज लें।", - "skillManagement.exportDialog.title": "वर्तमान सहेजा गया ड्राफ़्ट निर्यात करें", "skillManagement.import": "आयात करें", "skillManagement.importFailed": "कौशल आयात करने में विफल.", "skillManagement.importSuccess": "Skill आयातित.", @@ -236,12 +195,10 @@ "skillManagement.loadingError": "कौशल लोड करने में विफल", "skillManagement.moreActions": "{{name}} के लिए और क्रियाएँ", "skillManagement.noDescription": "अभी तक कोई विवरण नहीं", - "skillManagement.noTags": "कोई टैग नहीं", "skillManagement.publishedAt": "{{time}} प्रकाशित", "skillManagement.referenceCount_one": "{{count}} एजेंट", "skillManagement.referenceCount_other": "{{count}} एजेंट", "skillManagement.searchLabel": "कौशल खोजें", "skillManagement.searchPlaceholder": "नाम या विवरण से खोजें", - "skillManagement.tags": "टैग्स", "skillManagement.title": "कौशल" } diff --git a/web/i18n/hi-IN/snippet.json b/web/i18n/locales/hi-IN/snippet.json similarity index 100% rename from web/i18n/hi-IN/snippet.json rename to web/i18n/locales/hi-IN/snippet.json diff --git a/web/i18n/hi-IN/time.json b/web/i18n/locales/hi-IN/time.json similarity index 100% rename from web/i18n/hi-IN/time.json rename to web/i18n/locales/hi-IN/time.json diff --git a/web/i18n/hi-IN/tools.json b/web/i18n/locales/hi-IN/tools.json similarity index 100% rename from web/i18n/hi-IN/tools.json rename to web/i18n/locales/hi-IN/tools.json diff --git a/web/i18n/hi-IN/workflow.json b/web/i18n/locales/hi-IN/workflow.json similarity index 99% rename from web/i18n/hi-IN/workflow.json rename to web/i18n/locales/hi-IN/workflow.json index ab97ed9fa1c..ab319549522 100644 --- a/web/i18n/hi-IN/workflow.json +++ b/web/i18n/locales/hi-IN/workflow.json @@ -460,8 +460,6 @@ "nodes.agent.pluginNotInstalledDesc": "यह एकीकरण गिटहब से स्थापित किया गया है। कृपया पुनः स्थापित करने के लिए एकीकरण पर जाएं।", "nodes.agent.roster.change": "बदलें", "nodes.agent.roster.editInConsole": "Agents में संपादित करें", - "nodes.agent.roster.editInConsoleDisabled": "एजेंट संपादित करने के लिए एडिटर या उससे ऊपर की भूमिका आवश्यक है।", - "nodes.agent.roster.editInConsoleDisabledRbac": "केवल एडिटर या उससे ऊपर की भूमिका, या Agents प्रबंधित करें अनुमति वाले सदस्य ही एजेंट संपादित कर सकते हैं।", "nodes.agent.roster.inlineSetup.description": "एक एकल सेटअप जो केवल इस नोड में रहता है", "nodes.agent.roster.inlineSetup.name": "इनलाइन सेटअप", "nodes.agent.roster.inlineSetup.type": "केवल इस नोड में", diff --git a/web/i18n/id-ID/agent-v-2.json b/web/i18n/locales/id-ID/agent-v-2.json similarity index 98% rename from web/i18n/id-ID/agent-v-2.json rename to web/i18n/locales/id-ID/agent-v-2.json index a4cff778845..c00107f85da 100644 --- a/web/i18n/id-ID/agent-v-2.json +++ b/web/i18n/locales/id-ID/agent-v-2.json @@ -69,6 +69,7 @@ "agentDetail.configure.answer.activity.runningCommands": "Menjalankan perintah", "agentDetail.configure.answer.duration.minute": "{{count}} mnt", "agentDetail.configure.answer.duration.second": "{{count}} dtk", + "agentDetail.configure.answer.responseEnded": "Respons berakhir", "agentDetail.configure.answer.thinking": "Sedang berpikir", "agentDetail.configure.build.empty.communityEditionTip": "Community Edition, meskipun menawarkan versioning untuk konfigurasi yang diekstrak, tidak mendukung versioning pada sistem file itu sendiri. Berhati-hatilah dengan tindakan Anda dalam mode Build, karena perubahan pada sistem file terjadi secara real time dan tidak selalu dapat dikembalikan dengan rapi. Community Edition bukan pilihan ideal untuk melayani audiens eksternal dalam skala besar.", "agentDetail.configure.build.empty.description": "Jelaskan yang Anda inginkan dan formulir di kiri akan terisi seiring percakapan.", @@ -269,7 +270,6 @@ "agentDetail.configure.workingDirectory.breadcrumbLabel": "Jalur sistem file", "agentDetail.configure.workingDirectory.description": "File yang dibaca dan ditulis agent saat berjalan. Semua yang dibuatnya, seperti qna_report.pdf, muncul di sini.", "agentDetail.configure.workingDirectory.fileSystem": "Sistem file", - "agentDetail.configure.workingDirectory.open": "Buka direktori kerja", "agentDetail.configure.workingDirectory.persistentFiles": "Permanen", "agentDetail.configure.workingDirectory.persistentFilesTooltip": "File yang disimpan agen dan dapat digunakan di mana pun agen berjalan, termasuk percakapan pengguna akhir dan alur kerja. Setiap perubahan yang dibuat di sini selama chat build ini akan berlaku setelah Anda mengeklik Terapkan.", "agentDetail.configure.workingDirectory.temporaryFiles": "Sementara", @@ -348,7 +348,6 @@ "agentDetail.sections.logs": "Log", "agentDetail.sections.monitoring": "Pemantauan", "agentDetail.title": "Agen", - "agentDetail.type": "AGEN", "agentDetail.versionHistory.empty": "Belum ada versi", "agentDetail.versionHistory.exitVersions": "Keluar versi", "agentDetail.versionHistory.filter": "Filter versi", @@ -408,8 +407,5 @@ "roster.sort.lastModified": "Terakhir diubah", "roster.sort.recentlyCreated": "Baru dibuat", "roster.title": "Agen", - "roster.usageStatus.draft": "Draf", - "skillManagement.detail.uploadFilesFailedStatus_one": "{{count}} berkas gagal diunggah.", - "skillManagement.detail.uploadFilesFailedStatus_other": "{{count}} berkas gagal diunggah.", - "skillManagement.title": "Skills" + "roster.usageStatus.draft": "Draf" } diff --git a/web/i18n/id-ID/app-annotation.json b/web/i18n/locales/id-ID/app-annotation.json similarity index 100% rename from web/i18n/id-ID/app-annotation.json rename to web/i18n/locales/id-ID/app-annotation.json diff --git a/web/i18n/id-ID/app-api.json b/web/i18n/locales/id-ID/app-api.json similarity index 100% rename from web/i18n/id-ID/app-api.json rename to web/i18n/locales/id-ID/app-api.json diff --git a/web/i18n/id-ID/app-debug.json b/web/i18n/locales/id-ID/app-debug.json similarity index 100% rename from web/i18n/id-ID/app-debug.json rename to web/i18n/locales/id-ID/app-debug.json diff --git a/web/i18n/id-ID/app-log.json b/web/i18n/locales/id-ID/app-log.json similarity index 97% rename from web/i18n/id-ID/app-log.json rename to web/i18n/locales/id-ID/app-log.json index f112ecf1c9e..92bf23d7e01 100644 --- a/web/i18n/id-ID/app-log.json +++ b/web/i18n/locales/id-ID/app-log.json @@ -4,11 +4,9 @@ "agentLogDetail.iteration": "Iterasi", "agentLogDetail.iterations": "Iterasi", "agentLogDetail.toolUsed": "Alat yang Digunakan", - "archives.action.downloadMonth": "Unduh arsip {{month}}", "archives.action.downloadReady": "Unduhan arsip sudah siap.", "archives.action.prepareDownload": "Siapkan unduhan", "archives.action.prepareFailed": "Gagal menyiapkan unduhan arsip.", - "archives.action.prepareMonth": "Siapkan unduhan arsip {{month}}", "archives.action.prepareStarted": "Unduhan arsip sedang disiapkan.", "archives.action.preparing": "Menyiapkan", "archives.description": "Unduh ekspor CSV bulanan dari log workflow run yang diarsipkan di workspace ini.", diff --git a/web/i18n/id-ID/app-overview.json b/web/i18n/locales/id-ID/app-overview.json similarity index 100% rename from web/i18n/id-ID/app-overview.json rename to web/i18n/locales/id-ID/app-overview.json diff --git a/web/i18n/id-ID/app.json b/web/i18n/locales/id-ID/app.json similarity index 100% rename from web/i18n/id-ID/app.json rename to web/i18n/locales/id-ID/app.json diff --git a/web/i18n/id-ID/billing.json b/web/i18n/locales/id-ID/billing.json similarity index 100% rename from web/i18n/id-ID/billing.json rename to web/i18n/locales/id-ID/billing.json diff --git a/web/i18n/id-ID/common.json b/web/i18n/locales/id-ID/common.json similarity index 98% rename from web/i18n/id-ID/common.json rename to web/i18n/locales/id-ID/common.json index 805ffd9168c..c8e27ba8989 100644 --- a/web/i18n/id-ID/common.json +++ b/web/i18n/locales/id-ID/common.json @@ -209,9 +209,7 @@ "mainNav.marketplace": "Marketplace", "mainNav.skills": "Skills", "mainNav.webApps.noResults": "Tidak ada aplikasi web ditemukan", - "mainNav.webApps.openApp": "Buka aplikasi web {{name}}", "mainNav.webApps.searchPlaceholder": "Cari aplikasi web", - "mainNav.workspace.credits": "{{count}} kredit", "mainNav.workspace.creditsUnit": "kredit", "mainNav.workspace.inviteMembers": "Invite members", "mainNav.workspace.noResults": "Tidak ada workspace yang ditemukan", @@ -233,7 +231,6 @@ "members.datasetOperatorTip": "Hanya dapat mengelola basis pengetahuan", "members.editRole": "Edit Peran", "members.editorTip": "Dapat membuat dan mengedit aplikasi", - "members.email": "Email", "members.emailInvalid": "Format Email Tidak Valid", "members.emailNotSetup": "Server email tidak disiapkan, sehingga email undangan tidak dapat dikirim. Harap beri tahu pengguna tentang tautan undangan yang akan dikeluarkan setelah undangan.", "members.emailPlaceholder": "Ketik atau tempel alamat email…", @@ -250,7 +247,6 @@ "members.inviteLimitExceeded": "Slot yang tersedia tidak cukup untuk semua penerima.", "members.inviteTeamMember": "Tambahkan anggota tim", "members.inviteTeamMemberTip": "Mereka dapat mengakses data tim Anda langsung setelah masuk.", - "members.invitedAsRole": "Diundang sebagai {{role}}", "members.lastActive": "TERAKHIR AKTIF", "members.memberActions": "Tindakan anggota", "members.memberDetails.assign": "Tetapkan", @@ -258,8 +254,6 @@ "members.memberDetails.assignedRoles": "Peran yang Ditetapkan", "members.memberDetails.customGroup": "DISESUAIKAN", "members.memberDetails.generalGroup": "UMUM", - "members.memberDetails.openAria": "Buka detail anggota untuk {{name}}", - "members.memberDetails.roleActionsAria": "Buka tindakan untuk peran {{role}}", "members.memberDetails.roleNoPermissionSummary": "Peran saat ini tidak memiliki izin.", "members.memberDetails.rolePermissionSummary": "{{role}} dapat {{permissions}}", "members.memberDetails.title": "Detail Anggota", @@ -370,7 +364,6 @@ "modelProvider.card.usagePriorityTip": "Tentukan sumber daya mana yang digunakan terlebih dahulu saat menjalankan model.", "modelProvider.configureTip": "Menyiapkan api-key atau menambahkan model untuk digunakan", "modelProvider.confirmDelete": "Mengkonfirmasi penghapusan?", - "modelProvider.credits": "Kredit Pesan", "modelProvider.defaultConfig": "Konfigurasi Default", "modelProvider.discoverMore": "Temukan lebih lanjut di", "modelProvider.embeddingModel.key": "Menyematkan Model", @@ -427,7 +420,6 @@ "modelProvider.selector.noProviderConfiguredDesc": "Jelajahi Marketplace untuk menginstal, atau konfigurasikan penyedia di pengaturan.", "modelProvider.selector.onlyCompatibleModelsShown": "Hanya model yang kompatibel yang ditampilkan", "modelProvider.selector.showIncompatibleModels": "Tampilkan model yang tidak kompatibel", - "modelProvider.selector.suggestion": "Saran", "modelProvider.selector.suggestionTip": "Direkomendasikan untuk tugas agen", "modelProvider.showModels": "Tampilkan Model", "modelProvider.speechToTextModel.key": "Model Ucapan-ke-Teks", @@ -458,6 +450,8 @@ "operation.confirmAction": "Silakan konfirmasi tindakan Anda.", "operation.copied": "Disalin", "operation.copy": "Menyalin", + "operation.copyErrorDetails": "Salin detail kesalahan", + "operation.copyErrorFailed": "Gagal menyalin. Coba lagi", "operation.copyImage": "Salin Gambar", "operation.create": "Menciptakan", "operation.deSelectAll": "Batalkan pilihan Semua", @@ -475,7 +469,6 @@ "operation.format": "Format", "operation.imageCopied": "Gambar yang disalin", "operation.imageCopyFailed": "Gambar tidak dapat disalin. Silakan unduh sebagai gantinya.", - "operation.imageDownloaded": "Gambar diunduh", "operation.in": "di", "operation.learnMore": "Pelajari lebih lanjut", "operation.log": "Batang", @@ -512,7 +505,6 @@ "operation.sure": "Saya yakin", "operation.toggleFullscreen": "Alihkan layar penuh", "operation.toggleMute": "Alihkan bisu", - "operation.upload": "Upload", "operation.view": "Lihat", "operation.viewDetails": "Lihat Detail", "operation.viewMore": "LIHAT LEBIH BANYAK", @@ -542,7 +534,6 @@ "promptEditor.requestURL.item.title": "URL Permintaan", "promptEditor.variable.modal.add": "Variabel baru", "promptEditor.variable.modal.addTool": "Alat baru", - "provider.anthropicHosted.trialQuotaTip": "Kuota uji coba Anthropic Anda akan berakhir pada 17/03/2025 dan tidak akan tersedia lagi setelahnya. Silakan manfaatkan tepat waktu.", "provider.encrypted.back": "Teknologi.", "provider.encrypted.front": "API KEY Anda akan dienkripsi dan disimpan menggunakan", "provider.validating": "Memvalidasi kunci...", @@ -565,7 +556,6 @@ "settings.preferences": "Preferences", "settings.provider": "Penyedia Model", "settings.resourceAccess": "Akses Sumber Daya", - "settings.resourceAccessDescription": "Konfigurasikan aturan izin yang dapat digunakan oleh aplikasi dan basis pengetahuan. Aturan izin dapat ditetapkan ke anggota dalam konfigurasi akses sumber daya tertentu.", "settings.rolesAndPermissions": "Peran & Izin", "settings.settings": "Settings", "settings.swaggerAPIAsTool": "Swagger API as Tool", @@ -695,7 +685,6 @@ "userProfile.helpCenter": "Docs", "userProfile.logout": "Keluar", "userProfile.roadmap": "Peta jalan", - "userProfile.settings": "Pengaturan", "userProfile.workspace": "Workspace", "voice.language.arTN": "Bahasa Arab Tunisia", "voice.language.deDE": "Jerman", diff --git a/web/i18n/id-ID/custom.json b/web/i18n/locales/id-ID/custom.json similarity index 100% rename from web/i18n/id-ID/custom.json rename to web/i18n/locales/id-ID/custom.json diff --git a/web/i18n/id-ID/dataset-creation.json b/web/i18n/locales/id-ID/dataset-creation.json similarity index 100% rename from web/i18n/id-ID/dataset-creation.json rename to web/i18n/locales/id-ID/dataset-creation.json diff --git a/web/i18n/id-ID/dataset-documents.json b/web/i18n/locales/id-ID/dataset-documents.json similarity index 100% rename from web/i18n/id-ID/dataset-documents.json rename to web/i18n/locales/id-ID/dataset-documents.json diff --git a/web/i18n/id-ID/dataset-hit-testing.json b/web/i18n/locales/id-ID/dataset-hit-testing.json similarity index 100% rename from web/i18n/id-ID/dataset-hit-testing.json rename to web/i18n/locales/id-ID/dataset-hit-testing.json diff --git a/web/i18n/id-ID/dataset-pipeline.json b/web/i18n/locales/id-ID/dataset-pipeline.json similarity index 100% rename from web/i18n/id-ID/dataset-pipeline.json rename to web/i18n/locales/id-ID/dataset-pipeline.json diff --git a/web/i18n/id-ID/dataset-settings.json b/web/i18n/locales/id-ID/dataset-settings.json similarity index 100% rename from web/i18n/id-ID/dataset-settings.json rename to web/i18n/locales/id-ID/dataset-settings.json diff --git a/web/i18n/id-ID/dataset.json b/web/i18n/locales/id-ID/dataset.json similarity index 97% rename from web/i18n/id-ID/dataset.json rename to web/i18n/locales/id-ID/dataset.json index d0753c554c9..b9c9bd9f7f3 100644 --- a/web/i18n/id-ID/dataset.json +++ b/web/i18n/locales/id-ID/dataset.json @@ -163,7 +163,6 @@ "newKnowledge.configureProvider": "Konfigurasikan {{provider}}", "newKnowledge.connectNotion": "Hubungkan Notion", "newKnowledge.connectProvider": "Hubungkan {{provider}}", - "newKnowledge.connectProviderGeneric": "Hubungkan penyedia", "newKnowledge.connectSource": "Hubungkan sumber", "newKnowledge.connectSourceDescription": "Konten tetap tersinkronisasi dengan Notion, situs web, dan layanan lainnya.", "newKnowledge.connectingProvider": "Menghubungkan…", @@ -179,18 +178,14 @@ "newKnowledge.crawlFailedDescription": "Perayap tidak dapat menyelesaikan pratinjau ini. Periksa URL, sesuaikan opsi perayapan, atau coba lagi.", "newKnowledge.crawlFailedProvider": "{{provider}} melaporkan kesalahan. Periksa koneksi penyedia dan coba lagi.", "newKnowledge.crawlFailedTimeout": "Waktu perayapan habis. Coba URL yang lebih spesifik atau kurangi jumlah maksimum halaman.", - "newKnowledge.crawlNotStarted": "Belum dimulai", "newKnowledge.crawlOptions": "Opsi perayapan", "newKnowledge.crawlPreview": "Pratinjau perayapan", "newKnowledge.crawlProgress": "Progres perayapan untuk {{host}}", - "newKnowledge.crawlSetupUnavailableDescription": "Konfigurasi perayapan situs web dinonaktifkan hingga alur kerja perayapan tersedia.", - "newKnowledge.crawlSetupUnavailableTitle": "Firecrawl sudah siap", "newKnowledge.crawlStartFailed": "Pratinjau perayapan tidak dapat dimulai. Coba lagi.", "newKnowledge.crawlStopped": "Perayapan dihentikan", "newKnowledge.crawling": "Merayapi…", "newKnowledge.crawlingPages": "Merayapi {{host}}… {{count}} halaman ditemukan", "newKnowledge.createFailed": "Kami tidak dapat membuat basis pengetahuan ini. Periksa akses Anda dan coba lagi.", - "newKnowledge.createIllustrationTitle": "Hubungkan konten sekali — setiap aplikasi menjawab dengan pengetahuan terbaru.", "newKnowledge.createTitle": "Ciptakan Pengetahuan", "newKnowledge.createdAt": "Dibuat", "newKnowledge.creators": "Pembuat", @@ -220,7 +215,6 @@ "newKnowledge.documentColumn": "Dokumen", "newKnowledge.documentContentIncomplete": "Memuat sisa konten dokumen…", "newKnowledge.documentContents": "Isi", - "newKnowledge.documentFacts": "Detail dokumen", "newKnowledge.documentFilterLabel": "Filter dokumen berdasarkan status", "newKnowledge.documentLoadErrorDescription": "Dokumen ini tidak dapat dimuat. Coba lagi.", "newKnowledge.documentLoadErrorTitle": "Dokumen tidak dapat dimuat", @@ -268,7 +262,6 @@ "newKnowledge.documentsSelected": "Dokumen {{count}} dipilih", "newKnowledge.documentsUnavailable": "— dokumen", "newKnowledge.downloadDocuments": "Unduh", - "newKnowledge.editSource": "Sunting", "newKnowledge.emptyChunk": "Potongan kosong", "newKnowledge.emptyDescription": "Tambahkan dokumen agar agen dapat membacanya langsung — tanpa perlu mengatur pengindeksan.", "newKnowledge.emptyTitle": "Cara yang lebih cerdas untuk membuat basis pengetahuan", @@ -344,7 +337,6 @@ "newKnowledge.processingTaskState.succeeded": "Selesai", "newKnowledge.processingTaskState.superseded": "Digantikan", "newKnowledge.providerConnected": "{{provider}} terhubung", - "newKnowledge.providerLabel": "Pilih penyedia", "newKnowledge.providerLoadFailed": "Penyedia tidak dapat memuat", "newKnowledge.providerNotConfigured": "{{provider}} tidak dikonfigurasi", "newKnowledge.providerNotConfiguredDescription": "Tambahkan kunci API atau URL titik akhir untuk merayapi situs web dengan {{provider}}.", @@ -369,12 +361,10 @@ "newKnowledge.revisionState.superseded": "Sebelumnya", "newKnowledge.rootUrl": "URL root", "newKnowledge.rootUrlPlaceholder": "https://docs.example.com", - "newKnowledge.routeUnavailable": "Bagian ini akan tersedia pada langkah penyiapan berikutnya.", "newKnowledge.searchDocuments": "Cari dokumen", "newKnowledge.searchSources": "Cari sumber", "newKnowledge.selectAll": "Pilih semua", "newKnowledge.selectAllDocuments": "Pilih semua dokumen", - "newKnowledge.selectAllSources": "Pilih semua sumber", "newKnowledge.selectChunk": "Pilih potongan untuk memeriksa isinya.", "newKnowledge.selectFilesAndFolders": "Pilih file dan folder untuk disinkronkan", "newKnowledge.skippedFailed": "Dilewati (gagal)", @@ -384,7 +374,6 @@ "newKnowledge.sourceFilterLabel": "Filter sumber berdasarkan status", "newKnowledge.sourceName": "Nama sumber", "newKnowledge.sourceNamePlaceholder": "mis. perayapan dokumentasi produk", - "newKnowledge.sourceNameRequired": "Masukkan nama sumber.", "newKnowledge.sourceSetupBackendDependency": "Penyiapan sumber ditampilkan secara lengkap; tindakan edit dan pratinjau memerlukan alur backend sumber draf.", "newKnowledge.sourceStatus.active": "Aktif", "newKnowledge.sourceStatus.disabled": "Dinonaktifkan", @@ -431,7 +420,6 @@ "newKnowledge.unavailableDescription": "KnowledgeFS saat ini tidak tersedia untuk ruang kerja ini.", "newKnowledge.unavailableTitle": "Pengetahuan Baru tidak tersedia", "newKnowledge.updated": "Diperbarui {{date}}", - "newKnowledge.updatedAt": "Terakhir diperbarui", "newKnowledge.updatedColumn": "Diperbarui", "newKnowledge.uploadCharactersUnavailable": "Karakter: —", "newKnowledge.uploadDocuments": "Unggah dokumen", @@ -460,11 +448,9 @@ "retrievalSettings": "Pengaturan Pengambilan", "retry": "Pengulangan", "selectExternalKnowledgeAPI.placeholder": "Pilih API Pengetahuan Eksternal", - "serviceApi.card.addApiKey": "Tambah API Key", "serviceApi.card.apiKey": "Kunci API", "serviceApi.card.apiReference": "Referensi API", "serviceApi.card.endpoint": "Titik Akhir API Layanan", - "serviceApi.card.manageApiKey": "Kelola API Key", "serviceApi.card.title": "API layanan backend", "serviceApi.disabled": "Dinonaktifkan", "serviceApi.enabled": "Sedang Beroperasi", diff --git a/web/i18n/id-ID/deployments.json b/web/i18n/locales/id-ID/deployments.json similarity index 97% rename from web/i18n/id-ID/deployments.json rename to web/i18n/locales/id-ID/deployments.json index 7b1acc4817c..08d1f06efeb 100644 --- a/web/i18n/id-ID/deployments.json +++ b/web/i18n/locales/id-ID/deployments.json @@ -27,7 +27,6 @@ "list.emptyTitle": "Belum ada penerapan", "overview.chip.latest": "terbaru", "status.RUNTIME_INSTANCE_STATUS_DEPLOYING": "Menerapkan", - "status.RUNTIME_INSTANCE_STATUS_FAILED": "Penerapan gagal", "status.RUNTIME_INSTANCE_STATUS_INVALID": "Tidak valid", "status.RUNTIME_INSTANCE_STATUS_READY": "Berjalan", "status.RUNTIME_INSTANCE_STATUS_UNDEPLOYED": "Belum diterapkan", @@ -87,6 +86,5 @@ "studio.versionValue": "Nilai versi", "studio.versionsBehind_one": "Tertinggal {{count}} versi", "studio.versionsBehind_other": "Tertinggal {{count}} versi", - "versions.checkingReleaseContent": "Memeriksa konten rilis...", "versions.deployTo": "Terapkan ke {{name}}" } diff --git a/web/i18n/id-ID/device-flow.json b/web/i18n/locales/id-ID/device-flow.json similarity index 100% rename from web/i18n/id-ID/device-flow.json rename to web/i18n/locales/id-ID/device-flow.json diff --git a/web/i18n/id-ID/education.json b/web/i18n/locales/id-ID/education.json similarity index 100% rename from web/i18n/id-ID/education.json rename to web/i18n/locales/id-ID/education.json diff --git a/web/i18n/id-ID/explore.json b/web/i18n/locales/id-ID/explore.json similarity index 100% rename from web/i18n/id-ID/explore.json rename to web/i18n/locales/id-ID/explore.json diff --git a/web/i18n/id-ID/layout.json b/web/i18n/locales/id-ID/layout.json similarity index 100% rename from web/i18n/id-ID/layout.json rename to web/i18n/locales/id-ID/layout.json diff --git a/web/i18n/id-ID/login.json b/web/i18n/locales/id-ID/login.json similarity index 100% rename from web/i18n/id-ID/login.json rename to web/i18n/locales/id-ID/login.json diff --git a/web/i18n/id-ID/oauth.json b/web/i18n/locales/id-ID/oauth.json similarity index 100% rename from web/i18n/id-ID/oauth.json rename to web/i18n/locales/id-ID/oauth.json diff --git a/web/i18n/id-ID/permission-keys.json b/web/i18n/locales/id-ID/permission-keys.json similarity index 100% rename from web/i18n/id-ID/permission-keys.json rename to web/i18n/locales/id-ID/permission-keys.json diff --git a/web/i18n/id-ID/permission.json b/web/i18n/locales/id-ID/permission.json similarity index 100% rename from web/i18n/id-ID/permission.json rename to web/i18n/locales/id-ID/permission.json diff --git a/web/i18n/id-ID/pipeline.json b/web/i18n/locales/id-ID/pipeline.json similarity index 100% rename from web/i18n/id-ID/pipeline.json rename to web/i18n/locales/id-ID/pipeline.json diff --git a/web/i18n/id-ID/plugin-tags.json b/web/i18n/locales/id-ID/plugin-tags.json similarity index 100% rename from web/i18n/id-ID/plugin-tags.json rename to web/i18n/locales/id-ID/plugin-tags.json diff --git a/web/i18n/id-ID/plugin-trigger.json b/web/i18n/locales/id-ID/plugin-trigger.json similarity index 100% rename from web/i18n/id-ID/plugin-trigger.json rename to web/i18n/locales/id-ID/plugin-trigger.json diff --git a/web/i18n/id-ID/plugin.json b/web/i18n/locales/id-ID/plugin.json similarity index 99% rename from web/i18n/id-ID/plugin.json rename to web/i18n/locales/id-ID/plugin.json index e860f91b398..17dc80fbc05 100644 --- a/web/i18n/id-ID/plugin.json +++ b/web/i18n/locales/id-ID/plugin.json @@ -277,7 +277,6 @@ "readmeInfo.noReadmeAvailable": "Tidak ada README yang tersedia", "readmeInfo.title": "BACA SAYA", "requestAPlugin": "Minta plugin", - "requestSubmit": "Minta atau terbitkan", "search": "Mencari", "searchCategories": "Cari Kategori", "searchInMarketplace": "Cari di Marketplace", diff --git a/web/i18n/id-ID/register.json b/web/i18n/locales/id-ID/register.json similarity index 100% rename from web/i18n/id-ID/register.json rename to web/i18n/locales/id-ID/register.json diff --git a/web/i18n/id-ID/run-log.json b/web/i18n/locales/id-ID/run-log.json similarity index 100% rename from web/i18n/id-ID/run-log.json rename to web/i18n/locales/id-ID/run-log.json diff --git a/web/i18n/id-ID/share.json b/web/i18n/locales/id-ID/share.json similarity index 100% rename from web/i18n/id-ID/share.json rename to web/i18n/locales/id-ID/share.json diff --git a/web/i18n/id-ID/skill.json b/web/i18n/locales/id-ID/skill.json similarity index 82% rename from web/i18n/id-ID/skill.json rename to web/i18n/locales/id-ID/skill.json index a3e71d763d9..17b93d404ca 100644 --- a/web/i18n/id-ID/skill.json +++ b/web/i18n/locales/id-ID/skill.json @@ -1,5 +1,4 @@ { - "skillManagement.clearTags": "Hapus tag", "skillManagement.create": "Buat", "skillManagement.createFailed": "Gagal membuat keterampilan.", "skillManagement.createSuccess": "Draf dibuat.", @@ -13,12 +12,9 @@ "skillManagement.deleteFailed": "Gagal menghapus keterampilan.", "skillManagement.deleteSuccess": "Skill dihapus.", "skillManagement.detail.addMetadata": "Tambahkan metadata", - "skillManagement.detail.addMetadataDescription": "Tambahkan bidang frontmatter ke file penurunan harga ini.", "skillManagement.detail.addTag": "Tambahkan tanda", - "skillManagement.detail.addTagDescription": "Buat atau ikat tag ke Skill ini. Tag baru disimpan dengan metadata Skill.", "skillManagement.detail.addTagSuccess": "Tag ditambahkan.", "skillManagement.detail.back": "Kembali ke Skills", - "skillManagement.detail.builder.applyFailed": "Gagal menerapkan pembaruan draf.", "skillManagement.detail.builder.attach": "Lampirkan berkas", "skillManagement.detail.builder.attachFailed": "Gagal melampirkan file.", "skillManagement.detail.builder.attachLimit": "Anda dapat melampirkan hingga 10 file.", @@ -26,22 +22,12 @@ "skillManagement.detail.builder.attachUnsupported": "Hanya file gambar, teks, dan dokumen yang dapat dilampirkan.", "skillManagement.detail.builder.attachmentOnlyMessage": "Gunakan file terlampir untuk membantu meningkatkan keterampilan ini.", "skillManagement.detail.builder.close": "Tutup Skill Builder", - "skillManagement.detail.builder.compatibleModelsOnly": "Hanya model yang kompatibel yang ditampilkan", "skillManagement.detail.builder.copyResponse": "Salin respons", "skillManagement.detail.builder.copySuccess": "Respons disalin ke papan klip.", "skillManagement.detail.builder.editIntro": "Saya telah meninjau keterampilan ini. Apa yang ingin Anda sesuaikan?", "skillManagement.detail.builder.exampleIssueTriage": "Triase masalah pelanggan", "skillManagement.detail.builder.exampleOnboarding": "Panduan orientasi karyawan baru", "skillManagement.detail.builder.exampleSalesFollowUp": "Strategi tindak lanjut prospek penjualan", - "skillManagement.detail.builder.followUpDisplayName": "Gunakan Persetujuan Pengembalian Dana sebagai nama tampilan", - "skillManagement.detail.builder.followUpNameIcon": "Terapkan nama dan ikon yang disarankan", - "skillManagement.detail.builder.fromMarketplace": "Dari Marketplace", - "skillManagement.detail.builder.model": "GPT-4o", - "skillManagement.detail.builder.modelCredits.all": "Semua kredit", - "skillManagement.detail.builder.modelCredits.configure": "Konfigurasi diperlukan", - "skillManagement.detail.builder.modelCredits.exhausted": "Kuota habis", - "skillManagement.detail.builder.modelProviderSettings": "Pengaturan Penyedia Model", - "skillManagement.detail.builder.modelSearch": "Cari model...", "skillManagement.detail.builder.modifyPlaceholder": "Minta AI untuk memodifikasi keterampilan ini...", "skillManagement.detail.builder.open": "Buka Skill Builder", "skillManagement.detail.builder.placeholder": "Jelaskan skenarionya...", @@ -61,8 +47,6 @@ "skillManagement.detail.builder.thinkingUnavailable": "Tidak ada konten pemikiran yang dikembalikan oleh model.", "skillManagement.detail.builder.title": "Skill Builder", "skillManagement.detail.builder.tryExample": "Coba sebuah contoh", - "skillManagement.detail.builder.voice": "Input suara", - "skillManagement.detail.builder.voiceUnavailable": "Masukan suara belum tersedia.", "skillManagement.detail.cancelAddMetadata": "Batalkan penambahan metadata", "skillManagement.detail.closeFileTab": "Tutup {{name}}", "skillManagement.detail.closeVersions": "Tutup versi", @@ -70,14 +54,10 @@ "skillManagement.detail.copyContentSuccess": "Konten disalin ke papan klip.", "skillManagement.detail.copyFile": "Salin", "skillManagement.detail.copyFileSuccess": "File siap ditempel.", - "skillManagement.detail.copyVersionId": "Salin ID", - "skillManagement.detail.copyVersionIdSuccess": "ID disalin ke papan klip.", "skillManagement.detail.createFile": "Berkas baru", - "skillManagement.detail.createFileDescription": "Masukkan jalur file yang berhubungan dengan akar keterampilan.", "skillManagement.detail.createFileMenu": "Berkas baru...", "skillManagement.detail.createFileSuccess": "File dibuat.", "skillManagement.detail.createFolder": "Folder baru", - "skillManagement.detail.createFolderDescription": "Masukkan jalur folder yang berhubungan dengan akar keterampilan.", "skillManagement.detail.createFolderMenu": "Folder baru...", "skillManagement.detail.createFolderSuccess": "Folder dibuat.", "skillManagement.detail.createdBy": "Dibuat oleh {{name}}", @@ -100,10 +80,8 @@ "skillManagement.detail.fileMeta": "{{type}} · {{size}} byte", "skillManagement.detail.fileMissing": "Berkas tidak ditemukan.", "skillManagement.detail.fileOperationFailed": "Operasi berkas gagal.", - "skillManagement.detail.files": "File", "skillManagement.detail.latest": "Terbaru", "skillManagement.detail.loadFailed": "Gagal memuat keterampilan.", - "skillManagement.detail.loadingTags": "Memuat tag...", "skillManagement.detail.markdownLiveMode": "Pratinjau langsung", "skillManagement.detail.markdownSourceMode": "Penyunting sumber", "skillManagement.detail.metadataKey": "nama", @@ -113,10 +91,7 @@ "skillManagement.detail.nameThisVersion": "Beri nama versi ini", "skillManagement.detail.noFileSelected": "Pilih file untuk dipratinjau.", "skillManagement.detail.noFiles": "Tidak ada file.", - "skillManagement.detail.noSearchResults": "Tidak ada file yang cocok.", - "skillManagement.detail.noSelectableTags": "Tidak ada tag yang tersedia.", "skillManagement.detail.noVersions": "Belum ada versi yang dipublikasikan.", - "skillManagement.detail.pasteFile": "Pasta", "skillManagement.detail.pasteFileSuccess": "File ditempel.", "skillManagement.detail.previewUnsupported": "File ini tidak mendukung pratinjau.", "skillManagement.detail.publish": "Terbitkan", @@ -128,9 +103,7 @@ "skillManagement.detail.publishUpdate": "Terbitkan pembaruan", "skillManagement.detail.published": "Diterbitkan", "skillManagement.detail.publishedAt": "Diterbitkan {{time}}", - "skillManagement.detail.publishedVersion": "Diterbitkan #{{number}}", "skillManagement.detail.publishing": "Menerbitkan...", - "skillManagement.detail.readonly": "Hanya baca", "skillManagement.detail.referenceFiles.confirm": "Masukkan Konfirmasi", "skillManagement.detail.referenceFiles.empty": "Tidak ada file yang tersedia.", "skillManagement.detail.referenceFiles.livePlaceholder": "Tulis instruksi untuk agen. Ketik / untuk mereferensikan file.", @@ -138,14 +111,10 @@ "skillManagement.detail.referenceFiles.title": "File referensi", "skillManagement.detail.referencedBy_one": "Digunakan oleh {{count}} agen", "skillManagement.detail.referencedBy_other": "Digunakan oleh {{count}} agen", - "skillManagement.detail.removeMetadata": "Hapus metadata {{name}}", "skillManagement.detail.removeTag": "Hapus {{tag}}", "skillManagement.detail.removeTagSuccess": "Tandai dihapus.", - "skillManagement.detail.renameFile": "Ganti nama berkas", - "skillManagement.detail.renameFileDescription": "Masukkan jalur baru yang berhubungan dengan akar keterampilan.", "skillManagement.detail.renameFileSuccess": "File diganti namanya.", "skillManagement.detail.renameSkillSuccess": "Skill diganti namanya.", - "skillManagement.detail.renameVersion": "Ubah nama", "skillManagement.detail.renameVersionFailed": "Gagal mengganti nama versi.", "skillManagement.detail.renameVersionPrompt": "Nama versi", "skillManagement.detail.renameVersionSuccess": "Versi diganti namanya.", @@ -155,17 +124,14 @@ "skillManagement.detail.restoreVersionConfirmTitle": "Pulihkan versi ini?", "skillManagement.detail.restoreVersionFailed": "Gagal memulihkan versi.", "skillManagement.detail.restoreVersionSuccess": "Versi dikembalikan ke draf.", - "skillManagement.detail.save": "Simpan", "skillManagement.detail.saveConflictConfirmDescription": "Keterampilan ini telah diperbarui di tempat lain. Muat draf terbaru untuk melanjutkan pengeditan. Perubahan Anda saat ini akan dibuang.", "skillManagement.detail.saveConflictConfirmTitle": "Muat draf terbaru?", "skillManagement.detail.saveConflictReload": "Muat draf terbaru", "skillManagement.detail.saveConflictStatus": "Konflik terdeteksi. Muat draf terbaru untuk melanjutkan pengeditan.", "skillManagement.detail.saveFailed": "Gagal menyimpan file.", - "skillManagement.detail.saveSuccess": "Berkas disimpan.", "skillManagement.detail.saved": "Disimpan", "skillManagement.detail.savedAt": "Disimpan {{time}}", "skillManagement.detail.saving": "Menyimpan...", - "skillManagement.detail.searchFiles": "Cari file", "skillManagement.detail.showMoreReferences": "Tampilkan {{count}} lebih lanjut", "skillManagement.detail.skillDescriptionPlaceholder": "Jelaskan fungsi keterampilan ini dan kapan agen harus menggunakannya.", "skillManagement.detail.skillNamePlaceholder": "Gunakan huruf kecil, angka, dan tanda hubung.", @@ -177,16 +143,12 @@ "skillManagement.detail.uploadConflict": "File dengan nama ini sudah ada.", "skillManagement.detail.uploadFailureSummary": "{{failed}} dari file {{total}} gagal", "skillManagement.detail.uploadFailureTitle": "Beberapa unggahan gagal", - "skillManagement.detail.uploadFile": "Unggah file", - "skillManagement.detail.uploadFileFailed": "Gagal mengunggah file.", "skillManagement.detail.uploadFileSuccess": "File diunggah.", "skillManagement.detail.uploadFilesButton": "Unggah file {{count}}", "skillManagement.detail.uploadFilesFailedStatus_one": "Pengunggahan file {{count}} gagal.", "skillManagement.detail.uploadFilesFailedStatus_other": "Gagal mengunggah file {{count}}.", "skillManagement.detail.uploadFilesMenu": "Unggah file...", "skillManagement.detail.uploadFilesProgress": "Mengunggah {{completed}}/{{total}}", - "skillManagement.detail.uploadFilesResult": "{{uploaded}} diunggah · {{failed}} gagal", - "skillManagement.detail.uploadFilesStatus": "Status Unggah", "skillManagement.detail.uploadInvalidExtension": "Jenis file ini tidak didukung.", "skillManagement.detail.uploadInvalidName": "Nama tidak valid.", "skillManagement.detail.uploadInvalidPath": "Jalur file tidak valid.", @@ -206,7 +168,6 @@ "skillManagement.detail.uploadWillReplace": "Akan menggantikan file yang ada", "skillManagement.detail.uploadWillUseName": "Akan diunggah sebagai {{name}}", "skillManagement.detail.versionHistory": "Buka riwayat versi", - "skillManagement.detail.versionMeta": "{{hash}} · {{time}}", "skillManagement.detail.versionPublishNote": "Catatan rilis", "skillManagement.detail.versionPublishNotePlaceholder": "Jelaskan perubahan ini", "skillManagement.detail.versionPublishedMeta": "{{time}} · {{name}}", @@ -227,8 +188,6 @@ "skillManagement.errors.missingSkillMd": "Paket ini tidak memiliki SKILL.md. Tambahkan file dan impor lagi.", "skillManagement.errors.nameConflict": "Skill bernama \"{{name}}\" sudah ada. Ubah namanya menjadi SKILL.md dan impor lagi.", "skillManagement.errors.workspaceLimit": "Ruang kerja Anda telah mencapai batas 500 keterampilan. Hapus keterampilan untuk menambahkan keterampilan baru.", - "skillManagement.exportDialog.description": "Ini akan mengekspor draf keterampilan Anda yang tersimpan saat ini. Jika ada perubahan yang belum disimpan di editor, simpan terlebih dahulu sebelum mengekspor.", - "skillManagement.exportDialog.title": "Ekspor Draf Tersimpan Saat Ini", "skillManagement.import": "Impor", "skillManagement.importFailed": "Gagal mengimpor keterampilan.", "skillManagement.importSuccess": "Skill diimpor.", @@ -236,12 +195,10 @@ "skillManagement.loadingError": "Gagal memuat keterampilan", "skillManagement.moreActions": "Tindakan lainnya untuk {{name}}", "skillManagement.noDescription": "Belum ada deskripsi", - "skillManagement.noTags": "Tidak ada tag", "skillManagement.publishedAt": "Diterbitkan {{time}}", "skillManagement.referenceCount_one": "{{count}} agen", "skillManagement.referenceCount_other": "{{count}} agen", "skillManagement.searchLabel": "Cari keterampilan", "skillManagement.searchPlaceholder": "Cari berdasarkan nama atau deskripsi", - "skillManagement.tags": "Tag", "skillManagement.title": "Keterampilan" } diff --git a/web/i18n/id-ID/snippet.json b/web/i18n/locales/id-ID/snippet.json similarity index 100% rename from web/i18n/id-ID/snippet.json rename to web/i18n/locales/id-ID/snippet.json diff --git a/web/i18n/id-ID/time.json b/web/i18n/locales/id-ID/time.json similarity index 100% rename from web/i18n/id-ID/time.json rename to web/i18n/locales/id-ID/time.json diff --git a/web/i18n/id-ID/tools.json b/web/i18n/locales/id-ID/tools.json similarity index 100% rename from web/i18n/id-ID/tools.json rename to web/i18n/locales/id-ID/tools.json diff --git a/web/i18n/id-ID/workflow.json b/web/i18n/locales/id-ID/workflow.json similarity index 99% rename from web/i18n/id-ID/workflow.json rename to web/i18n/locales/id-ID/workflow.json index 342e9d6c942..40d6921b409 100644 --- a/web/i18n/id-ID/workflow.json +++ b/web/i18n/locales/id-ID/workflow.json @@ -460,8 +460,6 @@ "nodes.agent.pluginNotInstalledDesc": "Integrasi ini diinstal dari GitHub. Silakan buka Integrasis untuk menginstal ulang", "nodes.agent.roster.change": "Ubah", "nodes.agent.roster.editInConsole": "Edit di Agents", - "nodes.agent.roster.editInConsoleDisabled": "Mengedit agen memerlukan peran Editor atau yang lebih tinggi.", - "nodes.agent.roster.editInConsoleDisabledRbac": "Hanya anggota dengan peran Editor atau yang lebih tinggi, atau izin Kelola Agents, yang dapat mengedit agen.", "nodes.agent.roster.inlineSetup.description": "Pengaturan sekali pakai yang hanya berada di simpul ini", "nodes.agent.roster.inlineSetup.name": "Pengaturan inline", "nodes.agent.roster.inlineSetup.type": "Hanya di simpul ini", diff --git a/web/i18n/it-IT/agent-v-2.json b/web/i18n/locales/it-IT/agent-v-2.json similarity index 98% rename from web/i18n/it-IT/agent-v-2.json rename to web/i18n/locales/it-IT/agent-v-2.json index db01e854827..904d0ba2005 100644 --- a/web/i18n/it-IT/agent-v-2.json +++ b/web/i18n/locales/it-IT/agent-v-2.json @@ -69,6 +69,7 @@ "agentDetail.configure.answer.activity.runningCommands": "Esecuzione dei comandi", "agentDetail.configure.answer.duration.minute": "{{count}} min", "agentDetail.configure.answer.duration.second": "{{count}} s", + "agentDetail.configure.answer.responseEnded": "La risposta è terminata", "agentDetail.configure.answer.thinking": "Sto pensando", "agentDetail.configure.build.empty.communityEditionTip": "Community Edition, pur offrendo il versionamento delle configurazioni estratte, non supporta il versionamento del file system stesso. Fai attenzione alle azioni in modalità Build, perché le modifiche al file system avvengono in tempo reale e non sempre possono essere annullate in modo pulito. Community Edition non è la scelta ideale per servire un pubblico esterno di massa.", "agentDetail.configure.build.empty.description": "Descrivi ciò che vuoi e il modulo a sinistra verrà compilato man mano.", @@ -269,7 +270,6 @@ "agentDetail.configure.workingDirectory.breadcrumbLabel": "Percorso del file system", "agentDetail.configure.workingDirectory.description": "File che l’agente legge e scrive durante l’esecuzione. Tutto ciò che genera, come qna_report.pdf, appare qui.", "agentDetail.configure.workingDirectory.fileSystem": "File system", - "agentDetail.configure.workingDirectory.open": "Apri directory di lavoro", "agentDetail.configure.workingDirectory.persistentFiles": "Persistenti", "agentDetail.configure.workingDirectory.persistentFilesTooltip": "File che l’agente conserva e può usare ovunque venga eseguito, incluse le conversazioni degli utenti finali e i workflow. Le modifiche apportate qui durante questa chat di creazione diventano effettive dopo aver fatto clic su «Applica».", "agentDetail.configure.workingDirectory.temporaryFiles": "Temporanei", @@ -348,7 +348,6 @@ "agentDetail.sections.logs": "Log", "agentDetail.sections.monitoring": "Monitoraggio", "agentDetail.title": "Agente", - "agentDetail.type": "AGENTE", "agentDetail.versionHistory.empty": "Nessuna versione al momento", "agentDetail.versionHistory.exitVersions": "Esci dalle versioni", "agentDetail.versionHistory.filter": "Filtra versioni", @@ -408,8 +407,5 @@ "roster.sort.lastModified": "Ultima modifica", "roster.sort.recentlyCreated": "Creati di recente", "roster.title": "Agenti", - "roster.usageStatus.draft": "Bozza", - "skillManagement.detail.uploadFilesFailedStatus_one": "Caricamento di {{count}} file non riuscito.", - "skillManagement.detail.uploadFilesFailedStatus_other": "Caricamento di {{count}} file non riuscito.", - "skillManagement.title": "Skills" + "roster.usageStatus.draft": "Bozza" } diff --git a/web/i18n/it-IT/app-annotation.json b/web/i18n/locales/it-IT/app-annotation.json similarity index 100% rename from web/i18n/it-IT/app-annotation.json rename to web/i18n/locales/it-IT/app-annotation.json diff --git a/web/i18n/it-IT/app-api.json b/web/i18n/locales/it-IT/app-api.json similarity index 100% rename from web/i18n/it-IT/app-api.json rename to web/i18n/locales/it-IT/app-api.json diff --git a/web/i18n/it-IT/app-debug.json b/web/i18n/locales/it-IT/app-debug.json similarity index 100% rename from web/i18n/it-IT/app-debug.json rename to web/i18n/locales/it-IT/app-debug.json diff --git a/web/i18n/it-IT/app-log.json b/web/i18n/locales/it-IT/app-log.json similarity index 97% rename from web/i18n/it-IT/app-log.json rename to web/i18n/locales/it-IT/app-log.json index 94962c1a9a7..0f8531e144f 100644 --- a/web/i18n/it-IT/app-log.json +++ b/web/i18n/locales/it-IT/app-log.json @@ -4,11 +4,9 @@ "agentLogDetail.iteration": "Iterazione", "agentLogDetail.iterations": "Iterazioni", "agentLogDetail.toolUsed": "Strumento Usato", - "archives.action.downloadMonth": "Scarica l’archivio di {{month}}", "archives.action.downloadReady": "Il download dell’archivio è pronto.", "archives.action.prepareDownload": "Prepara download", "archives.action.prepareFailed": "Impossibile preparare il download dell’archivio.", - "archives.action.prepareMonth": "Prepara il download dell’archivio di {{month}}", "archives.action.prepareStarted": "Il download dell’archivio è in preparazione.", "archives.action.preparing": "Preparazione", "archives.description": "Scarica esportazioni CSV mensili dei log archiviati delle esecuzioni workflow in questo workspace.", diff --git a/web/i18n/it-IT/app-overview.json b/web/i18n/locales/it-IT/app-overview.json similarity index 100% rename from web/i18n/it-IT/app-overview.json rename to web/i18n/locales/it-IT/app-overview.json diff --git a/web/i18n/it-IT/app.json b/web/i18n/locales/it-IT/app.json similarity index 100% rename from web/i18n/it-IT/app.json rename to web/i18n/locales/it-IT/app.json diff --git a/web/i18n/it-IT/billing.json b/web/i18n/locales/it-IT/billing.json similarity index 100% rename from web/i18n/it-IT/billing.json rename to web/i18n/locales/it-IT/billing.json diff --git a/web/i18n/it-IT/common.json b/web/i18n/locales/it-IT/common.json similarity index 98% rename from web/i18n/it-IT/common.json rename to web/i18n/locales/it-IT/common.json index 1d92c4abf83..5f37d415ee3 100644 --- a/web/i18n/it-IT/common.json +++ b/web/i18n/locales/it-IT/common.json @@ -209,9 +209,7 @@ "mainNav.marketplace": "Marketplace", "mainNav.skills": "Skills", "mainNav.webApps.noResults": "Nessuna app web trovata", - "mainNav.webApps.openApp": "Apri l'app web {{name}}", "mainNav.webApps.searchPlaceholder": "Cerca app web", - "mainNav.workspace.credits": "{{count}} crediti", "mainNav.workspace.creditsUnit": "crediti", "mainNav.workspace.inviteMembers": "Invite members", "mainNav.workspace.noResults": "Nessun workspace trovato", @@ -233,7 +231,6 @@ "members.datasetOperatorTip": "Può solo gestire la base di conoscenza", "members.editRole": "Modifica ruolo", "members.editorTip": "Può creare e modificare app", - "members.email": "Email", "members.emailInvalid": "Formato Email non valido", "members.emailNotSetup": "Il server email non è configurato, quindi non è possibile inviare email di invito. Si prega di notificare agli utenti il link di invito che verrà emesso dopo l'invito.", "members.emailPlaceholder": "Digita o incolla gli indirizzi e-mail…", @@ -250,7 +247,6 @@ "members.inviteLimitExceeded": "Non ci sono posti sufficienti per tutti i destinatari.", "members.inviteTeamMember": "Aggiungi membro del team", "members.inviteTeamMemberTip": "Potranno accedere ai dati del tuo team direttamente dopo aver effettuato l'accesso.", - "members.invitedAsRole": "Invitato come {{role}}", "members.lastActive": "ULTIMA ATTIVITÀ", "members.memberActions": "Azioni membro", "members.memberDetails.assign": "Assegna", @@ -258,8 +254,6 @@ "members.memberDetails.assignedRoles": "Ruoli assegnati", "members.memberDetails.customGroup": "PERSONALIZZATO", "members.memberDetails.generalGroup": "GENERALE", - "members.memberDetails.openAria": "Apri i dettagli del membro per {{name}}", - "members.memberDetails.roleActionsAria": "Apri le azioni per il ruolo {{role}}", "members.memberDetails.roleNoPermissionSummary": "Il ruolo attuale non ha autorizzazioni.", "members.memberDetails.rolePermissionSummary": "{{role}} può {{permissions}}", "members.memberDetails.title": "Dettagli membro", @@ -370,7 +364,6 @@ "modelProvider.card.usagePriorityTip": "Imposta quale risorsa utilizzare per prima durante l'esecuzione dei modelli.", "modelProvider.configureTip": "Configura la chiave API o aggiungi il modello da utilizzare", "modelProvider.confirmDelete": "confermare l'eliminazione?", - "modelProvider.credits": "Crediti Messaggi", "modelProvider.defaultConfig": "Config predefinito", "modelProvider.discoverMore": "Scopri di più in", "modelProvider.embeddingModel.key": "Modello di Embedding", @@ -427,7 +420,6 @@ "modelProvider.selector.noProviderConfiguredDesc": "Cerca nel Marketplace per installarne uno o configura i fornitori nelle impostazioni.", "modelProvider.selector.onlyCompatibleModelsShown": "Vengono mostrati solo i modelli compatibili", "modelProvider.selector.showIncompatibleModels": "Mostra modelli incompatibili", - "modelProvider.selector.suggestion": "Suggerimento", "modelProvider.selector.suggestionTip": "Consigliato per le attività degli agenti", "modelProvider.showModels": "Mostra Modelli", "modelProvider.speechToTextModel.key": "Modello da Voce a Testo", @@ -458,6 +450,8 @@ "operation.confirmAction": "Per favore conferma la tua azione.", "operation.copied": "Copiato", "operation.copy": "Copia", + "operation.copyErrorDetails": "Copia dettagli errore", + "operation.copyErrorFailed": "Copia non riuscita. Riprova", "operation.copyImage": "Copia immagine", "operation.create": "Crea", "operation.deSelectAll": "Deseleziona tutto", @@ -475,7 +469,6 @@ "operation.format": "Formato", "operation.imageCopied": "Immagine copiata", "operation.imageCopyFailed": "Impossibile copiare l’immagine. Scaricala invece.", - "operation.imageDownloaded": "Immagine scaricata", "operation.in": "in", "operation.learnMore": "Scopri di più", "operation.log": "Log", @@ -512,7 +505,6 @@ "operation.sure": "Sono sicuro", "operation.toggleFullscreen": "Attiva/disattiva schermo intero", "operation.toggleMute": "Attiva/disattiva muto", - "operation.upload": "Upload", "operation.view": "Vista", "operation.viewDetails": "Visualizza dettagli", "operation.viewMore": "SCOPRI DI PIÙ", @@ -542,7 +534,6 @@ "promptEditor.requestURL.item.title": "URL richiesta", "promptEditor.variable.modal.add": "Nuova variabile", "promptEditor.variable.modal.addTool": "Nuovo strumento", - "provider.anthropicHosted.trialQuotaTip": "La tua quota di prova di Anthropic scadrà l'11/03/2025 e non sarà più disponibile in seguito. Sfruttalo in tempo.", "provider.encrypted.back": ".", "provider.encrypted.front": "La tua API KEY sarà crittografata e archiviata utilizzando la tecnologia", "provider.validating": "Convalida chiave in corso...", @@ -565,7 +556,6 @@ "settings.preferences": "Preferences", "settings.provider": "Fornitore di Modelli", "settings.resourceAccess": "Accesso alle risorse", - "settings.resourceAccessDescription": "Configura le regole di autorizzazione che le app e le basi di conoscenza possono utilizzare. Le regole di autorizzazione possono essere assegnate ai membri in configurazioni specifiche di accesso alle risorse.", "settings.rolesAndPermissions": "Ruoli e autorizzazioni", "settings.settings": "Settings", "settings.swaggerAPIAsTool": "Swagger API as Tool", @@ -695,7 +685,6 @@ "userProfile.helpCenter": "Aiuto", "userProfile.logout": "Esci", "userProfile.roadmap": "Tabella di marcia", - "userProfile.settings": "Impostazioni", "userProfile.workspace": "Workspace", "voice.language.arTN": "Arabo tunisino", "voice.language.deDE": "Tedesco", diff --git a/web/i18n/it-IT/custom.json b/web/i18n/locales/it-IT/custom.json similarity index 100% rename from web/i18n/it-IT/custom.json rename to web/i18n/locales/it-IT/custom.json diff --git a/web/i18n/it-IT/dataset-creation.json b/web/i18n/locales/it-IT/dataset-creation.json similarity index 100% rename from web/i18n/it-IT/dataset-creation.json rename to web/i18n/locales/it-IT/dataset-creation.json diff --git a/web/i18n/it-IT/dataset-documents.json b/web/i18n/locales/it-IT/dataset-documents.json similarity index 100% rename from web/i18n/it-IT/dataset-documents.json rename to web/i18n/locales/it-IT/dataset-documents.json diff --git a/web/i18n/it-IT/dataset-hit-testing.json b/web/i18n/locales/it-IT/dataset-hit-testing.json similarity index 100% rename from web/i18n/it-IT/dataset-hit-testing.json rename to web/i18n/locales/it-IT/dataset-hit-testing.json diff --git a/web/i18n/it-IT/dataset-pipeline.json b/web/i18n/locales/it-IT/dataset-pipeline.json similarity index 100% rename from web/i18n/it-IT/dataset-pipeline.json rename to web/i18n/locales/it-IT/dataset-pipeline.json diff --git a/web/i18n/it-IT/dataset-settings.json b/web/i18n/locales/it-IT/dataset-settings.json similarity index 100% rename from web/i18n/it-IT/dataset-settings.json rename to web/i18n/locales/it-IT/dataset-settings.json diff --git a/web/i18n/it-IT/dataset.json b/web/i18n/locales/it-IT/dataset.json similarity index 97% rename from web/i18n/it-IT/dataset.json rename to web/i18n/locales/it-IT/dataset.json index c40eba9f329..cfa42fdd468 100644 --- a/web/i18n/it-IT/dataset.json +++ b/web/i18n/locales/it-IT/dataset.json @@ -163,7 +163,6 @@ "newKnowledge.configureProvider": "Configura {{provider}}", "newKnowledge.connectNotion": "Connetti Notion", "newKnowledge.connectProvider": "Connetti {{provider}}", - "newKnowledge.connectProviderGeneric": "Connetti provider", "newKnowledge.connectSource": "Collega una fonte", "newKnowledge.connectSourceDescription": "I contenuti restano sincronizzati con Notion, siti web e altri servizi.", "newKnowledge.connectingProvider": "Connessione…", @@ -179,18 +178,14 @@ "newKnowledge.crawlFailedDescription": "Il crawler non ha potuto completare questa anteprima. Controlla l’URL, modifica le opzioni di scansione o riprova.", "newKnowledge.crawlFailedProvider": "{{provider}} ha segnalato un errore. Controlla la connessione al provider e riprova.", "newKnowledge.crawlFailedTimeout": "La scansione ha superato il tempo limite. Prova un URL più specifico o riduci il numero massimo di pagine.", - "newKnowledge.crawlNotStarted": "Non avviato", "newKnowledge.crawlOptions": "Opzioni di scansione", "newKnowledge.crawlPreview": "Anteprima scansione", "newKnowledge.crawlProgress": "Avanzamento della scansione di {{host}}", - "newKnowledge.crawlSetupUnavailableDescription": "La configurazione della scansione del sito web è disabilitata finché il flusso di lavoro di scansione non è disponibile.", - "newKnowledge.crawlSetupUnavailableTitle": "Firecrawl è pronto", "newKnowledge.crawlStartFailed": "Impossibile avviare l’anteprima della scansione. Riprova.", "newKnowledge.crawlStopped": "Scansione interrotta", "newKnowledge.crawling": "Scansione in corso…", "newKnowledge.crawlingPages": "Scansione di {{host}}… {{count}} pagine trovate", "newKnowledge.createFailed": "Non è stato possibile creare questa base di conoscenza. Controlla il tuo accesso e riprova.", - "newKnowledge.createIllustrationTitle": "Collega i contenuti una sola volta: ogni app risponde con le conoscenze più aggiornate.", "newKnowledge.createTitle": "Creare Conoscenza", "newKnowledge.createdAt": "Creato", "newKnowledge.creators": "Autori", @@ -220,7 +215,6 @@ "newKnowledge.documentColumn": "Documento", "newKnowledge.documentContentIncomplete": "Caricamento del contenuto restante del documento…", "newKnowledge.documentContents": "Contenuti", - "newKnowledge.documentFacts": "Dettagli documento", "newKnowledge.documentFilterLabel": "Filtra i documenti per stato", "newKnowledge.documentLoadErrorDescription": "Impossibile caricare questo documento. Riprova.", "newKnowledge.documentLoadErrorTitle": "Impossibile caricare il documento", @@ -268,7 +262,6 @@ "newKnowledge.documentsSelected": "Documenti {{count}} selezionati", "newKnowledge.documentsUnavailable": "— documenti", "newKnowledge.downloadDocuments": "Scarica", - "newKnowledge.editSource": "Modifica", "newKnowledge.emptyChunk": "Segmento vuoto", "newKnowledge.emptyDescription": "Aggiungi i tuoi documenti e gli agenti potranno leggerli direttamente, senza configurare l’indicizzazione.", "newKnowledge.emptyTitle": "Un modo più intelligente per creare knowledge base", @@ -344,7 +337,6 @@ "newKnowledge.processingTaskState.succeeded": "Completato", "newKnowledge.processingTaskState.superseded": "Sostituito", "newKnowledge.providerConnected": "{{provider}} connesso", - "newKnowledge.providerLabel": "Seleziona un fornitore", "newKnowledge.providerLoadFailed": "Impossibile caricare i provider", "newKnowledge.providerNotConfigured": "{{provider}} non è configurato", "newKnowledge.providerNotConfiguredDescription": "Aggiungi una chiave API o un URL endpoint per eseguire la scansione dei siti web con {{provider}}.", @@ -369,12 +361,10 @@ "newKnowledge.revisionState.superseded": "Precedente", "newKnowledge.rootUrl": "URL principale", "newKnowledge.rootUrlPlaceholder": "https://docs.example.com", - "newKnowledge.routeUnavailable": "Questa sezione diventerà disponibile nella fase di configurazione successiva.", "newKnowledge.searchDocuments": "Cerca documenti", "newKnowledge.searchSources": "Cerca fonti", "newKnowledge.selectAll": "Seleziona tutto", "newKnowledge.selectAllDocuments": "Seleziona tutti i documenti", - "newKnowledge.selectAllSources": "Seleziona tutte le origini", "newKnowledge.selectChunk": "Seleziona un segmento per esaminarne il contenuto.", "newKnowledge.selectFilesAndFolders": "Seleziona file e cartelle da sincronizzare", "newKnowledge.skippedFailed": "Ignorato (errore)", @@ -384,7 +374,6 @@ "newKnowledge.sourceFilterLabel": "Filtra le fonti per stato", "newKnowledge.sourceName": "Nome della fonte", "newKnowledge.sourceNamePlaceholder": "es. scansione della documentazione del prodotto", - "newKnowledge.sourceNameRequired": "Inserisci un nome per la fonte.", "newKnowledge.sourceSetupBackendDependency": "La configurazione dell’origine è mostrata per intero; modifica e anteprima richiedono il flusso backend dell’origine in bozza.", "newKnowledge.sourceStatus.active": "Attivo", "newKnowledge.sourceStatus.disabled": "Disabilitato", @@ -431,7 +420,6 @@ "newKnowledge.unavailableDescription": "KnowledgeFS non è al momento disponibile per questo spazio di lavoro.", "newKnowledge.unavailableTitle": "La nuova conoscenza non è disponibile", "newKnowledge.updated": "Aggiornato il {{date}}", - "newKnowledge.updatedAt": "Ultimo aggiornamento", "newKnowledge.updatedColumn": "Aggiornato", "newKnowledge.uploadCharactersUnavailable": "Caratteri: —", "newKnowledge.uploadDocuments": "Carica documenti", @@ -460,11 +448,9 @@ "retrievalSettings": "Impostazioni di recupero", "retry": "Riprova", "selectExternalKnowledgeAPI.placeholder": "Scegliere un'API di conoscenza esterna", - "serviceApi.card.addApiKey": "Aggiungi Chiave API", "serviceApi.card.apiKey": "Chiave API", "serviceApi.card.apiReference": "Riferimento API", "serviceApi.card.endpoint": "Endpoint dell'API di servizio", - "serviceApi.card.manageApiKey": "Gestisci Chiave API", "serviceApi.card.title": "API del servizio backend", "serviceApi.disabled": "Disabilitato", "serviceApi.enabled": "In servizio", diff --git a/web/i18n/it-IT/deployments.json b/web/i18n/locales/it-IT/deployments.json similarity index 97% rename from web/i18n/it-IT/deployments.json rename to web/i18n/locales/it-IT/deployments.json index ce823dc8737..158f2e151a3 100644 --- a/web/i18n/it-IT/deployments.json +++ b/web/i18n/locales/it-IT/deployments.json @@ -27,7 +27,6 @@ "list.emptyTitle": "Ancora nessuna distribuzione", "overview.chip.latest": "più recente", "status.RUNTIME_INSTANCE_STATUS_DEPLOYING": "In distribuzione", - "status.RUNTIME_INSTANCE_STATUS_FAILED": "Distribuzione non riuscita", "status.RUNTIME_INSTANCE_STATUS_INVALID": "Non valido", "status.RUNTIME_INSTANCE_STATUS_READY": "In esecuzione", "status.RUNTIME_INSTANCE_STATUS_UNDEPLOYED": "Non distribuito", @@ -87,6 +86,5 @@ "studio.versionValue": "Valore della versione", "studio.versionsBehind_one": "{{count}} versione indietro", "studio.versionsBehind_other": "{{count}} versioni indietro", - "versions.checkingReleaseContent": "Verifica del contenuto della release in corso...", "versions.deployTo": "Distribuisci in {{name}}" } diff --git a/web/i18n/it-IT/device-flow.json b/web/i18n/locales/it-IT/device-flow.json similarity index 100% rename from web/i18n/it-IT/device-flow.json rename to web/i18n/locales/it-IT/device-flow.json diff --git a/web/i18n/it-IT/education.json b/web/i18n/locales/it-IT/education.json similarity index 100% rename from web/i18n/it-IT/education.json rename to web/i18n/locales/it-IT/education.json diff --git a/web/i18n/it-IT/explore.json b/web/i18n/locales/it-IT/explore.json similarity index 100% rename from web/i18n/it-IT/explore.json rename to web/i18n/locales/it-IT/explore.json diff --git a/web/i18n/it-IT/layout.json b/web/i18n/locales/it-IT/layout.json similarity index 100% rename from web/i18n/it-IT/layout.json rename to web/i18n/locales/it-IT/layout.json diff --git a/web/i18n/it-IT/login.json b/web/i18n/locales/it-IT/login.json similarity index 100% rename from web/i18n/it-IT/login.json rename to web/i18n/locales/it-IT/login.json diff --git a/web/i18n/it-IT/oauth.json b/web/i18n/locales/it-IT/oauth.json similarity index 100% rename from web/i18n/it-IT/oauth.json rename to web/i18n/locales/it-IT/oauth.json diff --git a/web/i18n/it-IT/permission-keys.json b/web/i18n/locales/it-IT/permission-keys.json similarity index 100% rename from web/i18n/it-IT/permission-keys.json rename to web/i18n/locales/it-IT/permission-keys.json diff --git a/web/i18n/it-IT/permission.json b/web/i18n/locales/it-IT/permission.json similarity index 100% rename from web/i18n/it-IT/permission.json rename to web/i18n/locales/it-IT/permission.json diff --git a/web/i18n/it-IT/pipeline.json b/web/i18n/locales/it-IT/pipeline.json similarity index 100% rename from web/i18n/it-IT/pipeline.json rename to web/i18n/locales/it-IT/pipeline.json diff --git a/web/i18n/it-IT/plugin-tags.json b/web/i18n/locales/it-IT/plugin-tags.json similarity index 100% rename from web/i18n/it-IT/plugin-tags.json rename to web/i18n/locales/it-IT/plugin-tags.json diff --git a/web/i18n/it-IT/plugin-trigger.json b/web/i18n/locales/it-IT/plugin-trigger.json similarity index 100% rename from web/i18n/it-IT/plugin-trigger.json rename to web/i18n/locales/it-IT/plugin-trigger.json diff --git a/web/i18n/it-IT/plugin.json b/web/i18n/locales/it-IT/plugin.json similarity index 99% rename from web/i18n/it-IT/plugin.json rename to web/i18n/locales/it-IT/plugin.json index b4431b34f88..dbfb575f708 100644 --- a/web/i18n/it-IT/plugin.json +++ b/web/i18n/locales/it-IT/plugin.json @@ -277,7 +277,6 @@ "readmeInfo.noReadmeAvailable": "Nessun README disponibile", "readmeInfo.title": "LEGGIMI", "requestAPlugin": "Richiedi un plugin", - "requestSubmit": "Richiedi o pubblica", "search": "Ricerca", "searchCategories": "Cerca Categorie", "searchInMarketplace": "Cerca nel Marketplace", diff --git a/web/i18n/it-IT/register.json b/web/i18n/locales/it-IT/register.json similarity index 100% rename from web/i18n/it-IT/register.json rename to web/i18n/locales/it-IT/register.json diff --git a/web/i18n/it-IT/run-log.json b/web/i18n/locales/it-IT/run-log.json similarity index 100% rename from web/i18n/it-IT/run-log.json rename to web/i18n/locales/it-IT/run-log.json diff --git a/web/i18n/it-IT/share.json b/web/i18n/locales/it-IT/share.json similarity index 100% rename from web/i18n/it-IT/share.json rename to web/i18n/locales/it-IT/share.json diff --git a/web/i18n/it-IT/skill.json b/web/i18n/locales/it-IT/skill.json similarity index 82% rename from web/i18n/it-IT/skill.json rename to web/i18n/locales/it-IT/skill.json index fdbfcd5648b..19b818ec852 100644 --- a/web/i18n/it-IT/skill.json +++ b/web/i18n/locales/it-IT/skill.json @@ -1,5 +1,4 @@ { - "skillManagement.clearTags": "Cancella tag", "skillManagement.create": "Crea", "skillManagement.createFailed": "Impossibile creare la competenza.", "skillManagement.createSuccess": "Bozza creata.", @@ -13,12 +12,9 @@ "skillManagement.deleteFailed": "Impossibile eliminare la competenza.", "skillManagement.deleteSuccess": "Skill eliminato.", "skillManagement.detail.addMetadata": "Aggiungi metadati", - "skillManagement.detail.addMetadataDescription": "Aggiungi un campo in primo piano a questo file Markdown.", "skillManagement.detail.addTag": "Aggiungi etichetta", - "skillManagement.detail.addTagDescription": "Crea o associa un tag a questo Skill. I nuovi tag vengono salvati con i metadati Skill.", "skillManagement.detail.addTagSuccess": "Etichetta aggiunta.", "skillManagement.detail.back": "Torna a Skills", - "skillManagement.detail.builder.applyFailed": "Impossibile applicare gli aggiornamenti della bozza.", "skillManagement.detail.builder.attach": "Allega file", "skillManagement.detail.builder.attachFailed": "Impossibile allegare il file.", "skillManagement.detail.builder.attachLimit": "Puoi allegare fino a 10 file.", @@ -26,22 +22,12 @@ "skillManagement.detail.builder.attachUnsupported": "È possibile allegare solo immagini, testo e file di documenti.", "skillManagement.detail.builder.attachmentOnlyMessage": "Utilizza il file allegato per migliorare questa abilità.", "skillManagement.detail.builder.close": "Chiudi Skill Builder", - "skillManagement.detail.builder.compatibleModelsOnly": "Vengono mostrati solo i modelli compatibili", "skillManagement.detail.builder.copyResponse": "Copia la risposta", "skillManagement.detail.builder.copySuccess": "Risposta copiata negli appunti.", "skillManagement.detail.builder.editIntro": "Ho rivisto questa abilità. Cosa vorresti regolare?", "skillManagement.detail.builder.exampleIssueTriage": "Valutazione dei problemi del cliente", "skillManagement.detail.builder.exampleOnboarding": "Guida all'onboarding dei nuovi assunti", "skillManagement.detail.builder.exampleSalesFollowUp": "Strategia di follow-up dei lead di vendita", - "skillManagement.detail.builder.followUpDisplayName": "Utilizza Approvazione rimborso come nome visualizzato", - "skillManagement.detail.builder.followUpNameIcon": "Applica il nome e l'icona suggeriti", - "skillManagement.detail.builder.fromMarketplace": "Dal Marketplace", - "skillManagement.detail.builder.model": "GPT-4o", - "skillManagement.detail.builder.modelCredits.all": "Tutti i crediti", - "skillManagement.detail.builder.modelCredits.configure": "Configurazione richiesta", - "skillManagement.detail.builder.modelCredits.exhausted": "Quota esaurita", - "skillManagement.detail.builder.modelProviderSettings": "Impostazioni fornitore modelli", - "skillManagement.detail.builder.modelSearch": "Cerca modelli...", "skillManagement.detail.builder.modifyPlaceholder": "Chiedi all'IA di modificare questa abilità...", "skillManagement.detail.builder.open": "Apri Skill Builder", "skillManagement.detail.builder.placeholder": "Descrivi lo scenario...", @@ -61,8 +47,6 @@ "skillManagement.detail.builder.thinkingUnavailable": "Il modello non ha restituito alcun contenuto pensante.", "skillManagement.detail.builder.title": "Skill Builder", "skillManagement.detail.builder.tryExample": "Prova un esempio", - "skillManagement.detail.builder.voice": "Input vocale", - "skillManagement.detail.builder.voiceUnavailable": "L'input vocale non è ancora disponibile.", "skillManagement.detail.cancelAddMetadata": "Annulla l'aggiunta di metadati", "skillManagement.detail.closeFileTab": "Chiudi {{name}}", "skillManagement.detail.closeVersions": "Versioni vicine", @@ -70,14 +54,10 @@ "skillManagement.detail.copyContentSuccess": "Contenuto copiato negli appunti.", "skillManagement.detail.copyFile": "Copia", "skillManagement.detail.copyFileSuccess": "File pronto per essere incollato.", - "skillManagement.detail.copyVersionId": "Copia ID", - "skillManagement.detail.copyVersionIdSuccess": "ID copiato negli appunti.", "skillManagement.detail.createFile": "Nuovo fascicolo", - "skillManagement.detail.createFileDescription": "Immettere un percorso file relativo alla radice della competenza.", "skillManagement.detail.createFileMenu": "Nuovo fascicolo...", "skillManagement.detail.createFileSuccess": "File creato.", "skillManagement.detail.createFolder": "Nuova cartella", - "skillManagement.detail.createFolderDescription": "Immettere un percorso di cartella relativo alla radice della competenza.", "skillManagement.detail.createFolderMenu": "Nuova cartella...", "skillManagement.detail.createFolderSuccess": "Cartella creata.", "skillManagement.detail.createdBy": "Creato da {{name}}", @@ -100,10 +80,8 @@ "skillManagement.detail.fileMeta": "{{type}} · {{size}} byte", "skillManagement.detail.fileMissing": "File non trovato.", "skillManagement.detail.fileOperationFailed": "Operazione sul file non riuscita.", - "skillManagement.detail.files": "File", "skillManagement.detail.latest": "Ultimo", "skillManagement.detail.loadFailed": "Impossibile caricare la competenza.", - "skillManagement.detail.loadingTags": "Caricamento tag...", "skillManagement.detail.markdownLiveMode": "Anteprima dal vivo", "skillManagement.detail.markdownSourceMode": "Redattore della fonte", "skillManagement.detail.metadataKey": "nome", @@ -113,10 +91,7 @@ "skillManagement.detail.nameThisVersion": "Chiamare questa versione", "skillManagement.detail.noFileSelected": "Seleziona un file da visualizzare in anteprima.", "skillManagement.detail.noFiles": "Nessun file.", - "skillManagement.detail.noSearchResults": "Nessun file corrispondente.", - "skillManagement.detail.noSelectableTags": "Nessun tag disponibile.", "skillManagement.detail.noVersions": "Nessuna versione pubblicata ancora.", - "skillManagement.detail.pasteFile": "Impasto", "skillManagement.detail.pasteFileSuccess": "File incollato.", "skillManagement.detail.previewUnsupported": "Questo file non supporta l’anteprima.", "skillManagement.detail.publish": "Pubblica", @@ -128,9 +103,7 @@ "skillManagement.detail.publishUpdate": "Pubblica aggiornamento", "skillManagement.detail.published": "Pubblicato", "skillManagement.detail.publishedAt": "Pubblicato {{time}}", - "skillManagement.detail.publishedVersion": "Pubblicato #{{number}}", "skillManagement.detail.publishing": "Pubblicazione...", - "skillManagement.detail.readonly": "Sola lettura", "skillManagement.detail.referenceFiles.confirm": "Inserisci Conferma", "skillManagement.detail.referenceFiles.empty": "Nessun file disponibile.", "skillManagement.detail.referenceFiles.livePlaceholder": "Scrivi le istruzioni per l'agente. Digitare / per fare riferimento ai file.", @@ -138,14 +111,10 @@ "skillManagement.detail.referenceFiles.title": "File di riferimento", "skillManagement.detail.referencedBy_one": "Utilizzato da {{count}} agente", "skillManagement.detail.referencedBy_other": "Utilizzato da {{count}} agenti", - "skillManagement.detail.removeMetadata": "Elimina i metadati {{name}}", "skillManagement.detail.removeTag": "Rimuovi {{tag}}", "skillManagement.detail.removeTagSuccess": "Etichetta rimossa.", - "skillManagement.detail.renameFile": "Rinominare il file", - "skillManagement.detail.renameFileDescription": "Inserisci il nuovo percorso relativo alla radice della competenza.", "skillManagement.detail.renameFileSuccess": "File rinominato.", "skillManagement.detail.renameSkillSuccess": "Skill rinominato.", - "skillManagement.detail.renameVersion": "Rinomina", "skillManagement.detail.renameVersionFailed": "Impossibile rinominare la versione.", "skillManagement.detail.renameVersionPrompt": "Nome della versione", "skillManagement.detail.renameVersionSuccess": "Versione rinominata.", @@ -155,17 +124,14 @@ "skillManagement.detail.restoreVersionConfirmTitle": "Ripristinare questa versione?", "skillManagement.detail.restoreVersionFailed": "Impossibile ripristinare la versione.", "skillManagement.detail.restoreVersionSuccess": "Versione ripristinata alla bozza.", - "skillManagement.detail.save": "Salva", "skillManagement.detail.saveConflictConfirmDescription": "Questa abilità è stata aggiornata altrove. Carica l'ultima bozza per continuare a modificare. Le modifiche attuali verranno annullate.", "skillManagement.detail.saveConflictConfirmTitle": "Caricare l'ultima bozza?", "skillManagement.detail.saveConflictReload": "Carica l'ultima bozza", "skillManagement.detail.saveConflictStatus": "Rilevato conflitto. Carica l'ultima bozza per continuare a modificare.", "skillManagement.detail.saveFailed": "Impossibile salvare il file.", - "skillManagement.detail.saveSuccess": "File salvato.", "skillManagement.detail.saved": "Salvato", "skillManagement.detail.savedAt": "Salvato {{time}}", "skillManagement.detail.saving": "Salvataggio...", - "skillManagement.detail.searchFiles": "Cerca file", "skillManagement.detail.showMoreReferences": "Mostra altro {{count}}", "skillManagement.detail.skillDescriptionPlaceholder": "Descrivi cosa fa questa abilità e quando gli agenti dovrebbero utilizzarla.", "skillManagement.detail.skillNamePlaceholder": "Utilizza lettere minuscole, numeri e trattini.", @@ -177,16 +143,12 @@ "skillManagement.detail.uploadConflict": "Esiste già un file con questo nome.", "skillManagement.detail.uploadFailureSummary": "{{failed}} dei file {{total}} non riuscito", "skillManagement.detail.uploadFailureTitle": "Alcuni caricamenti non sono riusciti", - "skillManagement.detail.uploadFile": "Carica file", - "skillManagement.detail.uploadFileFailed": "Caricamento file non riuscito.", "skillManagement.detail.uploadFileSuccess": "File caricato.", "skillManagement.detail.uploadFilesButton": "Carica file {{count}}", "skillManagement.detail.uploadFilesFailedStatus_one": "Caricamento file {{count}} non riuscito.", "skillManagement.detail.uploadFilesFailedStatus_other": "Caricamenti di file {{count}} non riusciti.", "skillManagement.detail.uploadFilesMenu": "Carica file...", "skillManagement.detail.uploadFilesProgress": "Caricamento {{completed}}/{{total}}", - "skillManagement.detail.uploadFilesResult": "{{uploaded}} caricato · {{failed}} non riuscito", - "skillManagement.detail.uploadFilesStatus": "Stato del caricamento", "skillManagement.detail.uploadInvalidExtension": "Questo tipo di file non è supportato.", "skillManagement.detail.uploadInvalidName": "Nome non valido.", "skillManagement.detail.uploadInvalidPath": "Percorso file non valido.", @@ -206,7 +168,6 @@ "skillManagement.detail.uploadWillReplace": "Sostituirà il file esistente", "skillManagement.detail.uploadWillUseName": "Verrà caricato come {{name}}", "skillManagement.detail.versionHistory": "Apri cronologia versioni", - "skillManagement.detail.versionMeta": "{{hash}} · {{time}}", "skillManagement.detail.versionPublishNote": "Note sulla versione", "skillManagement.detail.versionPublishNotePlaceholder": "Descrivi questo cambiamento", "skillManagement.detail.versionPublishedMeta": "{{time}} · {{name}}", @@ -227,8 +188,6 @@ "skillManagement.errors.missingSkillMd": "Questo pacchetto non ha SKILL.md. Aggiungi il file e importalo di nuovo.", "skillManagement.errors.nameConflict": "Esiste già una competenza denominata \"{{name}}\". Rinominalo in SKILL.md e importalo di nuovo.", "skillManagement.errors.workspaceLimit": "La tua area di lavoro ha raggiunto il limite di 500 competenze. Elimina una competenza per aggiungerne una nuova.", - "skillManagement.exportDialog.description": "Verrà esportata la bozza attualmente salvata della tua competenza. Se ci sono modifiche non salvate nell’editor, salvale prima di esportare.", - "skillManagement.exportDialog.title": "Esporta la bozza attualmente salvata", "skillManagement.import": "Importa", "skillManagement.importFailed": "Impossibile importare la competenza.", "skillManagement.importSuccess": "Skill importato.", @@ -236,12 +195,10 @@ "skillManagement.loadingError": "Impossibile caricare le competenze", "skillManagement.moreActions": "Altre azioni per {{name}}", "skillManagement.noDescription": "Nessuna descrizione ancora", - "skillManagement.noTags": "Nessun tag", "skillManagement.publishedAt": "Pubblicato {{time}}", "skillManagement.referenceCount_one": "{{count}} agente", "skillManagement.referenceCount_other": "{{count}} agenti", "skillManagement.searchLabel": "Cerca abilità", "skillManagement.searchPlaceholder": "Cerca per nome o descrizione", - "skillManagement.tags": "Tag", "skillManagement.title": "Abilità" } diff --git a/web/i18n/it-IT/snippet.json b/web/i18n/locales/it-IT/snippet.json similarity index 100% rename from web/i18n/it-IT/snippet.json rename to web/i18n/locales/it-IT/snippet.json diff --git a/web/i18n/it-IT/time.json b/web/i18n/locales/it-IT/time.json similarity index 100% rename from web/i18n/it-IT/time.json rename to web/i18n/locales/it-IT/time.json diff --git a/web/i18n/it-IT/tools.json b/web/i18n/locales/it-IT/tools.json similarity index 100% rename from web/i18n/it-IT/tools.json rename to web/i18n/locales/it-IT/tools.json diff --git a/web/i18n/it-IT/workflow.json b/web/i18n/locales/it-IT/workflow.json similarity index 99% rename from web/i18n/it-IT/workflow.json rename to web/i18n/locales/it-IT/workflow.json index b8817cf097e..82f7d356dab 100644 --- a/web/i18n/it-IT/workflow.json +++ b/web/i18n/locales/it-IT/workflow.json @@ -460,8 +460,6 @@ "nodes.agent.pluginNotInstalledDesc": "Questo integrazione viene installato da GitHub. Vai su Integrazione per reinstallare", "nodes.agent.roster.change": "Cambia", "nodes.agent.roster.editInConsole": "Modifica in Agents", - "nodes.agent.roster.editInConsoleDisabled": "Per modificare gli agenti è necessario il ruolo Editor o superiore.", - "nodes.agent.roster.editInConsoleDisabledRbac": "Solo i membri con il ruolo Editor o superiore, o con l’autorizzazione Gestisci Agents, possono modificare gli agenti.", "nodes.agent.roster.inlineSetup.description": "Una configurazione una tantum che vive solo in questo nodo", "nodes.agent.roster.inlineSetup.name": "Configurazione inline", "nodes.agent.roster.inlineSetup.type": "Solo in questo nodo", diff --git a/web/i18n/ja-JP/agent-v-2.json b/web/i18n/locales/ja-JP/agent-v-2.json similarity index 98% rename from web/i18n/ja-JP/agent-v-2.json rename to web/i18n/locales/ja-JP/agent-v-2.json index 37089f8aaa7..d1e131b6a1f 100644 --- a/web/i18n/ja-JP/agent-v-2.json +++ b/web/i18n/locales/ja-JP/agent-v-2.json @@ -69,6 +69,7 @@ "agentDetail.configure.answer.activity.runningCommands": "コマンドを実行中", "agentDetail.configure.answer.duration.minute": "{{count}}分", "agentDetail.configure.answer.duration.second": "{{count}}秒", + "agentDetail.configure.answer.responseEnded": "応答が終了しました", "agentDetail.configure.answer.thinking": "思考中", "agentDetail.configure.build.empty.communityEditionTip": "Community Edition は抽出された設定のバージョン管理を提供しますが、ファイルシステム自体のバージョン管理には対応していません。Build モードでの操作には注意してください。ファイルシステムへの変更はリアルタイムで発生し、常にきれいに元に戻せるとは限りません。Community Edition は大規模な外部利用者向けサービスには理想的な選択ではありません。", "agentDetail.configure.build.empty.description": "やりたいことを説明すると、左側のフォームが会話に合わせて入力されます。", @@ -269,7 +270,6 @@ "agentDetail.configure.workingDirectory.breadcrumbLabel": "ファイルシステムのパス", "agentDetail.configure.workingDirectory.description": "エージェントが実行中に読み書きするファイルです。qna_report.pdf など、生成されたものはここに表示されます。", "agentDetail.configure.workingDirectory.fileSystem": "ファイルシステム", - "agentDetail.configure.workingDirectory.open": "作業ディレクトリを開く", "agentDetail.configure.workingDirectory.persistentFiles": "永続ファイル", "agentDetail.configure.workingDirectory.persistentFilesTooltip": "Agent が保持し続けるファイルです。エンドユーザーとの会話やワークフローを含め、Agent が実行されるあらゆる場所で使えます。このビルドチャットでこれらのファイルに加えた変更は、「適用」を押すと反映されます。", "agentDetail.configure.workingDirectory.temporaryFiles": "一時ファイル", @@ -348,7 +348,6 @@ "agentDetail.sections.logs": "ログ", "agentDetail.sections.monitoring": "モニタリング", "agentDetail.title": "エージェント", - "agentDetail.type": "エージェント", "agentDetail.versionHistory.empty": "バージョンはまだありません", "agentDetail.versionHistory.exitVersions": "バージョンを終了", "agentDetail.versionHistory.filter": "バージョンをフィルター", @@ -408,8 +407,5 @@ "roster.sort.lastModified": "最終更新", "roster.sort.recentlyCreated": "最近作成", "roster.title": "エージェント", - "roster.usageStatus.draft": "ドラフト", - "skillManagement.detail.uploadFilesFailedStatus_one": "{{count}} 件のファイルのアップロードに失敗しました。", - "skillManagement.detail.uploadFilesFailedStatus_other": "{{count}} 件のファイルのアップロードに失敗しました。", - "skillManagement.title": "Skills" + "roster.usageStatus.draft": "ドラフト" } diff --git a/web/i18n/ja-JP/app-annotation.json b/web/i18n/locales/ja-JP/app-annotation.json similarity index 100% rename from web/i18n/ja-JP/app-annotation.json rename to web/i18n/locales/ja-JP/app-annotation.json diff --git a/web/i18n/ja-JP/app-api.json b/web/i18n/locales/ja-JP/app-api.json similarity index 100% rename from web/i18n/ja-JP/app-api.json rename to web/i18n/locales/ja-JP/app-api.json diff --git a/web/i18n/ja-JP/app-debug.json b/web/i18n/locales/ja-JP/app-debug.json similarity index 100% rename from web/i18n/ja-JP/app-debug.json rename to web/i18n/locales/ja-JP/app-debug.json diff --git a/web/i18n/ja-JP/app-log.json b/web/i18n/locales/ja-JP/app-log.json similarity index 97% rename from web/i18n/ja-JP/app-log.json rename to web/i18n/locales/ja-JP/app-log.json index d27b81de630..2baa3625151 100644 --- a/web/i18n/ja-JP/app-log.json +++ b/web/i18n/locales/ja-JP/app-log.json @@ -4,11 +4,9 @@ "agentLogDetail.iteration": "反復", "agentLogDetail.iterations": "反復", "agentLogDetail.toolUsed": "使用したツール", - "archives.action.downloadMonth": "{{month}} のアーカイブをダウンロード", "archives.action.downloadReady": "アーカイブのダウンロード準備が完了しました。", "archives.action.prepareDownload": "ダウンロードを準備", "archives.action.prepareFailed": "アーカイブのダウンロード準備に失敗しました。", - "archives.action.prepareMonth": "{{month}} のアーカイブダウンロードを準備", "archives.action.prepareStarted": "アーカイブのダウンロードを準備しています。", "archives.action.preparing": "準備中", "archives.description": "このワークスペース内のアーカイブされたワークフロー実行ログを月別 CSV エクスポートとしてダウンロードします。", diff --git a/web/i18n/ja-JP/app-overview.json b/web/i18n/locales/ja-JP/app-overview.json similarity index 100% rename from web/i18n/ja-JP/app-overview.json rename to web/i18n/locales/ja-JP/app-overview.json diff --git a/web/i18n/ja-JP/app.json b/web/i18n/locales/ja-JP/app.json similarity index 100% rename from web/i18n/ja-JP/app.json rename to web/i18n/locales/ja-JP/app.json diff --git a/web/i18n/ja-JP/billing.json b/web/i18n/locales/ja-JP/billing.json similarity index 100% rename from web/i18n/ja-JP/billing.json rename to web/i18n/locales/ja-JP/billing.json diff --git a/web/i18n/ja-JP/common.json b/web/i18n/locales/ja-JP/common.json similarity index 98% rename from web/i18n/ja-JP/common.json rename to web/i18n/locales/ja-JP/common.json index ac269c6421c..796264cdf6c 100644 --- a/web/i18n/ja-JP/common.json +++ b/web/i18n/locales/ja-JP/common.json @@ -209,9 +209,7 @@ "mainNav.marketplace": "マーケットプレイス", "mainNav.skills": "Skills", "mainNav.webApps.noResults": "Webアプリが見つかりません", - "mainNav.webApps.openApp": "{{name}} のWebアプリを開く", "mainNav.webApps.searchPlaceholder": "Webアプリを検索", - "mainNav.workspace.credits": "{{count}} クレジット", "mainNav.workspace.creditsUnit": "クレジット", "mainNav.workspace.inviteMembers": "メンバーを招待", "mainNav.workspace.noResults": "ワークスペースが見つかりません", @@ -233,7 +231,6 @@ "members.datasetOperatorTip": "ナレッジベースのみを管理できる", "members.editRole": "ロールを編集", "members.editorTip": "アプリを作成・編集できます", - "members.email": "メール", "members.emailInvalid": "無効なメール形式", "members.emailNotSetup": "メールサーバーがセットアップされていないので、招待メールを送信することはできません。代わりに招待後に発行される招待リンクをユーザーに通知してください。", "members.emailPlaceholder": "メールアドレスを入力または貼り付け…", @@ -250,7 +247,6 @@ "members.inviteLimitExceeded": "メンバー枠が不足しているため、これらの招待を送信できません。", "members.inviteTeamMember": "チームメンバーを招待する", "members.inviteTeamMemberTip": "彼らはサインイン後、直接あなたのチームデータにアクセスできます。", - "members.invitedAsRole": "{{role}}として招待されました", "members.lastActive": "最終アクティブ", "members.memberActions": "メンバー操作", "members.memberDetails.assign": "割り当て", @@ -258,8 +254,6 @@ "members.memberDetails.assignedRoles": "割り当て済みロール", "members.memberDetails.customGroup": "カスタム", "members.memberDetails.generalGroup": "一般", - "members.memberDetails.openAria": "{{name}} のメンバー詳細を開く", - "members.memberDetails.roleActionsAria": "{{role}} ロールの操作を開く", "members.memberDetails.roleNoPermissionSummary": "現在のロールには権限がありません。", "members.memberDetails.rolePermissionSummary": "{{role}} は {{permissions}} できます", "members.memberDetails.title": "メンバー詳細", @@ -370,7 +364,6 @@ "modelProvider.card.usagePriorityTip": "モデル実行時に優先して使用するリソースを設定します。", "modelProvider.configureTip": "API キーを設定するか、使用するモデルを追加してください", "modelProvider.confirmDelete": "削除を確認", - "modelProvider.credits": "クレジット", "modelProvider.defaultConfig": "デフォルトの設定", "modelProvider.discoverMore": "さらに探す", "modelProvider.embeddingModel.key": "埋め込みモデル", @@ -427,7 +420,6 @@ "modelProvider.selector.noProviderConfiguredDesc": "マーケットプレイスでインストールするか、設定でプロバイダーを設定してください。", "modelProvider.selector.onlyCompatibleModelsShown": "互換性のあるモデルのみが表示されます", "modelProvider.selector.showIncompatibleModels": "互換性のないモデルを表示", - "modelProvider.selector.suggestion": "提案", "modelProvider.selector.suggestionTip": "エージェントタスクに推奨", "modelProvider.showModels": "モデルの表示", "modelProvider.speechToTextModel.key": "音声-to-テキストモデル", @@ -458,6 +450,8 @@ "operation.confirmAction": "操作を確認してください。", "operation.copied": "コピーしました", "operation.copy": "コピー", + "operation.copyErrorDetails": "エラーの詳細をコピー", + "operation.copyErrorFailed": "コピーに失敗しました。再試行", "operation.copyImage": "画像をコピー", "operation.create": "作成", "operation.deSelectAll": "すべて選択解除", @@ -475,7 +469,6 @@ "operation.format": "フォーマット", "operation.imageCopied": "コピーした画像", "operation.imageCopyFailed": "画像をコピーできませんでした。代わりにダウンロードしてください。", - "operation.imageDownloaded": "画像がダウンロードされました", "operation.in": "中", "operation.learnMore": "詳細はこちら", "operation.log": "ログ", @@ -512,7 +505,6 @@ "operation.sure": "確認済み", "operation.toggleFullscreen": "全画面表示を切り替え", "operation.toggleMute": "ミュートを切り替え", - "operation.upload": "Upload", "operation.view": "表示", "operation.viewDetails": "詳細を見る", "operation.viewMore": "さらに表示", @@ -542,7 +534,6 @@ "promptEditor.requestURL.item.title": "リクエストURL", "promptEditor.variable.modal.add": "新しい変数", "promptEditor.variable.modal.addTool": "新しいツール", - "provider.anthropicHosted.trialQuotaTip": "お客様の Anthropic 試用枠は 2025/03/17 に失効し、その後は利用できなくなります。お早めにご利用ください。", "provider.encrypted.back": "技術を使用して暗号化および保存されます。", "provider.encrypted.front": "API KEY は", "provider.validating": "キーの検証中...", @@ -565,7 +556,6 @@ "settings.preferences": "ユーザー設定", "settings.provider": "モデルプロバイダー", "settings.resourceAccess": "リソースアクセス", - "settings.resourceAccessDescription": "アプリとナレッジベースで使用できる権限ルールを設定します。権限ルールは、具体的なリソースアクセス設定でメンバーに割り当てられます。", "settings.rolesAndPermissions": "ロールと権限", "settings.settings": "設定", "settings.swaggerAPIAsTool": "Swagger API をツールとして利用", @@ -695,7 +685,6 @@ "userProfile.helpCenter": "ドキュメントを見る", "userProfile.logout": "ログアウト", "userProfile.roadmap": "ロードマップ", - "userProfile.settings": "設定", "userProfile.workspace": "ワークスペース", "voice.language.arTN": "チュニジア・アラビア語", "voice.language.deDE": "ドイツ語", diff --git a/web/i18n/ja-JP/custom.json b/web/i18n/locales/ja-JP/custom.json similarity index 100% rename from web/i18n/ja-JP/custom.json rename to web/i18n/locales/ja-JP/custom.json diff --git a/web/i18n/ja-JP/dataset-creation.json b/web/i18n/locales/ja-JP/dataset-creation.json similarity index 100% rename from web/i18n/ja-JP/dataset-creation.json rename to web/i18n/locales/ja-JP/dataset-creation.json diff --git a/web/i18n/ja-JP/dataset-documents.json b/web/i18n/locales/ja-JP/dataset-documents.json similarity index 100% rename from web/i18n/ja-JP/dataset-documents.json rename to web/i18n/locales/ja-JP/dataset-documents.json diff --git a/web/i18n/ja-JP/dataset-hit-testing.json b/web/i18n/locales/ja-JP/dataset-hit-testing.json similarity index 100% rename from web/i18n/ja-JP/dataset-hit-testing.json rename to web/i18n/locales/ja-JP/dataset-hit-testing.json diff --git a/web/i18n/ja-JP/dataset-pipeline.json b/web/i18n/locales/ja-JP/dataset-pipeline.json similarity index 100% rename from web/i18n/ja-JP/dataset-pipeline.json rename to web/i18n/locales/ja-JP/dataset-pipeline.json diff --git a/web/i18n/ja-JP/dataset-settings.json b/web/i18n/locales/ja-JP/dataset-settings.json similarity index 100% rename from web/i18n/ja-JP/dataset-settings.json rename to web/i18n/locales/ja-JP/dataset-settings.json diff --git a/web/i18n/ja-JP/dataset.json b/web/i18n/locales/ja-JP/dataset.json similarity index 97% rename from web/i18n/ja-JP/dataset.json rename to web/i18n/locales/ja-JP/dataset.json index 3504e66beb5..57c31d9d82a 100644 --- a/web/i18n/ja-JP/dataset.json +++ b/web/i18n/locales/ja-JP/dataset.json @@ -163,7 +163,6 @@ "newKnowledge.configureProvider": "{{provider}} を構成する", "newKnowledge.connectNotion": "Notion に接続", "newKnowledge.connectProvider": "{{provider}} を接続する", - "newKnowledge.connectProviderGeneric": "プロバイダーに接続", "newKnowledge.connectSource": "ソースを接続", "newKnowledge.connectSourceDescription": "コンテンツは Notion、Web サイト、その他のサービスと同期され続けます。", "newKnowledge.connectingProvider": "接続中…", @@ -179,18 +178,14 @@ "newKnowledge.crawlFailedDescription": "クローラーはこのプレビューを完了できませんでした。URLを確認し、クロールオプションを調整するか、もう一度お試しください。", "newKnowledge.crawlFailedProvider": "{{provider}}でエラーが報告されました。プロバイダー接続を確認して、もう一度お試しください。", "newKnowledge.crawlFailedTimeout": "クロールがタイムアウトしました。対象を絞ったURLを指定するか、最大ページ数を減らしてください。", - "newKnowledge.crawlNotStarted": "未開始", "newKnowledge.crawlOptions": "クロールオプション", "newKnowledge.crawlPreview": "クロールのプレビュー", "newKnowledge.crawlProgress": "{{host}} のクロール進行状況", - "newKnowledge.crawlSetupUnavailableDescription": "Web サイトのクロール設定は、クロール ワークフローが利用可能になるまで無効になります。", - "newKnowledge.crawlSetupUnavailableTitle": "Firecrawl の準備ができました", "newKnowledge.crawlStartFailed": "クロールプレビューを開始できませんでした。もう一度お試しください。", "newKnowledge.crawlStopped": "クロールを停止しました", "newKnowledge.crawling": "クロール中…", "newKnowledge.crawlingPages": "{{host}} をクロール中… {{count}}ページが見つかりました", "newKnowledge.createFailed": "このナレッジ ベースを作成できませんでした。アクセスを確認して再試行してください。", - "newKnowledge.createIllustrationTitle": "コンテンツを一度接続すれば、すべてのアプリが最新のナレッジで回答します。", "newKnowledge.createTitle": "知識の創造", "newKnowledge.createdAt": "作成日時", "newKnowledge.creators": "作成者", @@ -220,7 +215,6 @@ "newKnowledge.documentColumn": "文書", "newKnowledge.documentContentIncomplete": "ドキュメントの残りの内容を読み込んでいます…", "newKnowledge.documentContents": "目次", - "newKnowledge.documentFacts": "ドキュメントの詳細", "newKnowledge.documentFilterLabel": "ドキュメントをステータスでフィルタリングする", "newKnowledge.documentLoadErrorDescription": "このドキュメントを読み込めませんでした。もう一度お試しください。", "newKnowledge.documentLoadErrorTitle": "ドキュメントを読み込めませんでした", @@ -268,7 +262,6 @@ "newKnowledge.documentsSelected": "{{count}} ドキュメントが選択されました", "newKnowledge.documentsUnavailable": "— 件のドキュメント", "newKnowledge.downloadDocuments": "ダウンロード", - "newKnowledge.editSource": "編集", "newKnowledge.emptyChunk": "空のチャンク", "newKnowledge.emptyDescription": "ドキュメントを追加すると、エージェントが直接読み取れます。インデックス設定は不要です。", "newKnowledge.emptyTitle": "ナレッジベースをよりスマートに構築", @@ -344,7 +337,6 @@ "newKnowledge.processingTaskState.succeeded": "完了しました", "newKnowledge.processingTaskState.superseded": "置き換えられました", "newKnowledge.providerConnected": "{{provider}} が接続されました", - "newKnowledge.providerLabel": "プロバイダーを選択する", "newKnowledge.providerLoadFailed": "プロバイダーを読み込めませんでした", "newKnowledge.providerNotConfigured": "{{provider}} が構成されていません", "newKnowledge.providerNotConfiguredDescription": "{{provider}} を使用して Web サイトをクロールするための API キーまたはエンドポイント URL を追加します。", @@ -369,12 +361,10 @@ "newKnowledge.revisionState.superseded": "以前", "newKnowledge.rootUrl": "ルートURL", "newKnowledge.rootUrlPlaceholder": "https://docs.example.com", - "newKnowledge.routeUnavailable": "このセクションは、次のセットアップ手順で使用できるようになります。", "newKnowledge.searchDocuments": "文書の検索", "newKnowledge.searchSources": "ソースを検索", "newKnowledge.selectAll": "すべて選択", "newKnowledge.selectAllDocuments": "すべてのドキュメントを選択します", - "newKnowledge.selectAllSources": "すべてのソースを選択", "newKnowledge.selectChunk": "内容を確認するチャンクを選択してください。", "newKnowledge.selectFilesAndFolders": "同期するファイルとフォルダーを選択", "newKnowledge.skippedFailed": "スキップ(失敗)", @@ -384,7 +374,6 @@ "newKnowledge.sourceFilterLabel": "ソースをステータスでフィルタリングする", "newKnowledge.sourceName": "ソース名", "newKnowledge.sourceNamePlaceholder": "例:製品ドキュメントのクロール", - "newKnowledge.sourceNameRequired": "ソース名を入力してください。", "newKnowledge.sourceSetupBackendDependency": "ソース設定はすべて表示されています。編集とプレビューには、下書きソースのバックエンドワークフローが必要です。", "newKnowledge.sourceStatus.active": "アクティブ", "newKnowledge.sourceStatus.disabled": "無効", @@ -431,7 +420,6 @@ "newKnowledge.unavailableDescription": "このワークスペースでは現在 KnowledgeFS を利用できません。", "newKnowledge.unavailableTitle": "新しいナレッジは利用できません", "newKnowledge.updated": "{{date}} に更新", - "newKnowledge.updatedAt": "最終更新", "newKnowledge.updatedColumn": "更新されました", "newKnowledge.uploadCharactersUnavailable": "文字数:—", "newKnowledge.uploadDocuments": "書類のアップロード", @@ -460,11 +448,9 @@ "retrievalSettings": "検索設定", "retry": "再試行", "selectExternalKnowledgeAPI.placeholder": "外部ナレッジベース連携 API を選択", - "serviceApi.card.addApiKey": "APIキーを追加", "serviceApi.card.apiKey": "APIキー", "serviceApi.card.apiReference": "APIリファレンス", "serviceApi.card.endpoint": "サービスAPIエンドポイント", - "serviceApi.card.manageApiKey": "APIキーを管理", "serviceApi.card.title": "バックエンドサービスAPI", "serviceApi.disabled": "無効", "serviceApi.enabled": "サービス中", diff --git a/web/i18n/ja-JP/deployments.json b/web/i18n/locales/ja-JP/deployments.json similarity index 97% rename from web/i18n/ja-JP/deployments.json rename to web/i18n/locales/ja-JP/deployments.json index 8d2c18aca21..ca3f1f3a84d 100644 --- a/web/i18n/ja-JP/deployments.json +++ b/web/i18n/locales/ja-JP/deployments.json @@ -27,7 +27,6 @@ "list.emptyTitle": "デプロイはまだありません", "overview.chip.latest": "最新", "status.RUNTIME_INSTANCE_STATUS_DEPLOYING": "デプロイ中", - "status.RUNTIME_INSTANCE_STATUS_FAILED": "デプロイ失敗", "status.RUNTIME_INSTANCE_STATUS_INVALID": "無効", "status.RUNTIME_INSTANCE_STATUS_READY": "実行中", "status.RUNTIME_INSTANCE_STATUS_UNDEPLOYED": "未デプロイ", @@ -87,6 +86,5 @@ "studio.versionValue": "バージョンの値", "studio.versionsBehind_one": "{{count}} バージョン遅れ", "studio.versionsBehind_other": "{{count}} バージョン遅れ", - "versions.checkingReleaseContent": "リリース内容を確認中...", "versions.deployTo": "{{name}} にデプロイ" } diff --git a/web/i18n/ja-JP/device-flow.json b/web/i18n/locales/ja-JP/device-flow.json similarity index 100% rename from web/i18n/ja-JP/device-flow.json rename to web/i18n/locales/ja-JP/device-flow.json diff --git a/web/i18n/ja-JP/education.json b/web/i18n/locales/ja-JP/education.json similarity index 100% rename from web/i18n/ja-JP/education.json rename to web/i18n/locales/ja-JP/education.json diff --git a/web/i18n/ja-JP/explore.json b/web/i18n/locales/ja-JP/explore.json similarity index 100% rename from web/i18n/ja-JP/explore.json rename to web/i18n/locales/ja-JP/explore.json diff --git a/web/i18n/ja-JP/layout.json b/web/i18n/locales/ja-JP/layout.json similarity index 100% rename from web/i18n/ja-JP/layout.json rename to web/i18n/locales/ja-JP/layout.json diff --git a/web/i18n/ja-JP/login.json b/web/i18n/locales/ja-JP/login.json similarity index 100% rename from web/i18n/ja-JP/login.json rename to web/i18n/locales/ja-JP/login.json diff --git a/web/i18n/ja-JP/oauth.json b/web/i18n/locales/ja-JP/oauth.json similarity index 100% rename from web/i18n/ja-JP/oauth.json rename to web/i18n/locales/ja-JP/oauth.json diff --git a/web/i18n/ja-JP/permission-keys.json b/web/i18n/locales/ja-JP/permission-keys.json similarity index 100% rename from web/i18n/ja-JP/permission-keys.json rename to web/i18n/locales/ja-JP/permission-keys.json diff --git a/web/i18n/ja-JP/permission.json b/web/i18n/locales/ja-JP/permission.json similarity index 100% rename from web/i18n/ja-JP/permission.json rename to web/i18n/locales/ja-JP/permission.json diff --git a/web/i18n/ja-JP/pipeline.json b/web/i18n/locales/ja-JP/pipeline.json similarity index 100% rename from web/i18n/ja-JP/pipeline.json rename to web/i18n/locales/ja-JP/pipeline.json diff --git a/web/i18n/ja-JP/plugin-tags.json b/web/i18n/locales/ja-JP/plugin-tags.json similarity index 100% rename from web/i18n/ja-JP/plugin-tags.json rename to web/i18n/locales/ja-JP/plugin-tags.json diff --git a/web/i18n/ja-JP/plugin-trigger.json b/web/i18n/locales/ja-JP/plugin-trigger.json similarity index 100% rename from web/i18n/ja-JP/plugin-trigger.json rename to web/i18n/locales/ja-JP/plugin-trigger.json diff --git a/web/i18n/ja-JP/plugin.json b/web/i18n/locales/ja-JP/plugin.json similarity index 99% rename from web/i18n/ja-JP/plugin.json rename to web/i18n/locales/ja-JP/plugin.json index 3617103693e..1bee35ced1c 100644 --- a/web/i18n/ja-JP/plugin.json +++ b/web/i18n/locales/ja-JP/plugin.json @@ -277,7 +277,6 @@ "readmeInfo.noReadmeAvailable": "READMEは利用できません", "readmeInfo.title": "リードミー", "requestAPlugin": "プラグインをリクエスト", - "requestSubmit": "リクエストまたは公開", "search": "検索", "searchCategories": "検索カテゴリ", "searchInMarketplace": "マーケットプレイスで検索", diff --git a/web/i18n/ja-JP/register.json b/web/i18n/locales/ja-JP/register.json similarity index 100% rename from web/i18n/ja-JP/register.json rename to web/i18n/locales/ja-JP/register.json diff --git a/web/i18n/ja-JP/run-log.json b/web/i18n/locales/ja-JP/run-log.json similarity index 100% rename from web/i18n/ja-JP/run-log.json rename to web/i18n/locales/ja-JP/run-log.json diff --git a/web/i18n/ja-JP/share.json b/web/i18n/locales/ja-JP/share.json similarity index 100% rename from web/i18n/ja-JP/share.json rename to web/i18n/locales/ja-JP/share.json diff --git a/web/i18n/ja-JP/skill.json b/web/i18n/locales/ja-JP/skill.json similarity index 83% rename from web/i18n/ja-JP/skill.json rename to web/i18n/locales/ja-JP/skill.json index b3a906712b1..ddd892f6696 100644 --- a/web/i18n/ja-JP/skill.json +++ b/web/i18n/locales/ja-JP/skill.json @@ -1,5 +1,4 @@ { - "skillManagement.clearTags": "タグをクリアする", "skillManagement.create": "作成する", "skillManagement.createFailed": "スキルの作成に失敗しました。", "skillManagement.createSuccess": "ドラフトを作成しました。", @@ -13,12 +12,9 @@ "skillManagement.deleteFailed": "スキルの削除に失敗しました。", "skillManagement.deleteSuccess": "Skill が削除されました。", "skillManagement.detail.addMetadata": "メタデータの追加", - "skillManagement.detail.addMetadataDescription": "この Markdown ファイルに、frontmatter フィールドを追加します。", "skillManagement.detail.addTag": "タグを追加", - "skillManagement.detail.addTagDescription": "タグを作成するか、この Skill にバインドします。新しいタグは Skill メタデータとともに保存されます。", "skillManagement.detail.addTagSuccess": "タグが追加されました。", "skillManagement.detail.back": "Skills に戻る", - "skillManagement.detail.builder.applyFailed": "ドラフトの更新を適用できませんでした。", "skillManagement.detail.builder.attach": "ファイルを添付", "skillManagement.detail.builder.attachFailed": "ファイルを添付できませんでした。", "skillManagement.detail.builder.attachLimit": "最大 10 個のファイルを添付できます。", @@ -26,22 +22,12 @@ "skillManagement.detail.builder.attachUnsupported": "テキストファイルとドキュメントファイルのみ添付できます。", "skillManagement.detail.builder.attachmentOnlyMessage": "添付ファイルを使って、このスキルを改善してください。", "skillManagement.detail.builder.close": "Skill Builder を閉じる", - "skillManagement.detail.builder.compatibleModelsOnly": "互換性のあるモデルのみ表示", "skillManagement.detail.builder.copyResponse": "応答をコピーする", "skillManagement.detail.builder.copySuccess": "応答がクリップボードにコピーされました。", "skillManagement.detail.builder.editIntro": "このスキルを確認しました。何を調整しますか?", "skillManagement.detail.builder.exampleIssueTriage": "顧客問い合わせの優先度分類", "skillManagement.detail.builder.exampleOnboarding": "新入社員オンボーディングガイド", "skillManagement.detail.builder.exampleSalesFollowUp": "営業リードのフォローアップ戦略", - "skillManagement.detail.builder.followUpDisplayName": "表示名を Refund approval にする", - "skillManagement.detail.builder.followUpNameIcon": "提案された名前とアイコンを適用", - "skillManagement.detail.builder.fromMarketplace": "Marketplace から", - "skillManagement.detail.builder.model": "GPT-4o", - "skillManagement.detail.builder.modelCredits.all": "すべてのクレジット", - "skillManagement.detail.builder.modelCredits.configure": "設定が必要", - "skillManagement.detail.builder.modelCredits.exhausted": "クレジットを使い切りました", - "skillManagement.detail.builder.modelProviderSettings": "モデルプロバイダー設定", - "skillManagement.detail.builder.modelSearch": "モデルを検索...", "skillManagement.detail.builder.modifyPlaceholder": "AI にこの Skill の修正を依頼...", "skillManagement.detail.builder.open": "Skill Builder を開く", "skillManagement.detail.builder.placeholder": "シナリオを説明...", @@ -61,8 +47,6 @@ "skillManagement.detail.builder.thinkingUnavailable": "モデルから思考内容は返されませんでした。", "skillManagement.detail.builder.title": "Skill Builder", "skillManagement.detail.builder.tryExample": "例を試す", - "skillManagement.detail.builder.voice": "音声入力", - "skillManagement.detail.builder.voiceUnavailable": "音声入力はまだ利用できません。", "skillManagement.detail.cancelAddMetadata": "メタデータの追加をキャンセルします", "skillManagement.detail.closeFileTab": "{{name}}を閉じる", "skillManagement.detail.closeVersions": "バージョンを閉じる", @@ -70,14 +54,10 @@ "skillManagement.detail.copyContentSuccess": "内容をクリップボードにコピーしました。", "skillManagement.detail.copyFile": "コピー", "skillManagement.detail.copyFileSuccess": "ファイルを貼り付けできる状態にしました。", - "skillManagement.detail.copyVersionId": "IDをコピー", - "skillManagement.detail.copyVersionIdSuccess": "ID がクリップボードにコピーされました。", "skillManagement.detail.createFile": "新しいファイル", - "skillManagement.detail.createFileDescription": "スキル ルートからの相対ファイル パスを入力します。", "skillManagement.detail.createFileMenu": "新しいファイル...", "skillManagement.detail.createFileSuccess": "ファイルが作成されました。", "skillManagement.detail.createFolder": "新しいフォルダー", - "skillManagement.detail.createFolderDescription": "", "skillManagement.detail.createFolderMenu": "新しいフォルダ...", "skillManagement.detail.createFolderSuccess": "フォルダーが作成されました。", "skillManagement.detail.createdBy": "作成者: {{name}}", @@ -100,10 +80,8 @@ "skillManagement.detail.fileMeta": "{{type}} · {{size}} バイト", "skillManagement.detail.fileMissing": "ファイルが見つかりません。", "skillManagement.detail.fileOperationFailed": "ファイル操作に失敗しました。", - "skillManagement.detail.files": "ファイル", "skillManagement.detail.latest": "最新の", "skillManagement.detail.loadFailed": "", - "skillManagement.detail.loadingTags": "タグを読み込み中...", "skillManagement.detail.markdownLiveMode": "", "skillManagement.detail.markdownSourceMode": "ソースエディタ", "skillManagement.detail.metadataKey": "名前", @@ -113,10 +91,7 @@ "skillManagement.detail.nameThisVersion": "バージョン名を付ける", "skillManagement.detail.noFileSelected": "プレビューするファイルを選択します。", "skillManagement.detail.noFiles": "ファイルがありません。", - "skillManagement.detail.noSearchResults": "一致するファイルはありません。", - "skillManagement.detail.noSelectableTags": "利用可能なタグはありません。", "skillManagement.detail.noVersions": "まだ公開されたバージョンはありません。", - "skillManagement.detail.pasteFile": "貼り付け", "skillManagement.detail.pasteFileSuccess": "ファイルを貼り付けました。", "skillManagement.detail.previewUnsupported": "ファイルはプレビューに対応していません。", "skillManagement.detail.publish": "公開", @@ -128,9 +103,7 @@ "skillManagement.detail.publishUpdate": "更新を公開", "skillManagement.detail.published": "公開済み", "skillManagement.detail.publishedAt": "{{time}}に公開", - "skillManagement.detail.publishedVersion": "#{{number}} を公開しました", "skillManagement.detail.publishing": "公開中...", - "skillManagement.detail.readonly": "読み取り専用", "skillManagement.detail.referenceFiles.confirm": "入力 確認", "skillManagement.detail.referenceFiles.empty": "利用可能なファイルがありません。", "skillManagement.detail.referenceFiles.livePlaceholder": "Agent への指示を記述してください。/ を入力するとファイルを参照できます。", @@ -138,14 +111,10 @@ "skillManagement.detail.referenceFiles.title": "参照ファイル", "skillManagement.detail.referencedBy_one": "{{count}} 個の Agent が使用中", "skillManagement.detail.referencedBy_other": "{{count}} 個の Agent が使用中", - "skillManagement.detail.removeMetadata": "{{name}} メタデータを削除する", "skillManagement.detail.removeTag": "{{tag}} を削除", "skillManagement.detail.removeTagSuccess": "タグは外しました。", - "skillManagement.detail.renameFile": "ファイル名の変更", - "skillManagement.detail.renameFileDescription": "スキル ルートを基準とした新しいパスを入力します。", "skillManagement.detail.renameFileSuccess": "ファイル名が変更されました。", "skillManagement.detail.renameSkillSuccess": "Skill の名前が変更されました。", - "skillManagement.detail.renameVersion": "名前の変更", "skillManagement.detail.renameVersionFailed": "バージョン名の変更に失敗しました。", "skillManagement.detail.renameVersionPrompt": "バージョン名", "skillManagement.detail.renameVersionSuccess": "バージョン名が変更されました。", @@ -155,17 +124,14 @@ "skillManagement.detail.restoreVersionConfirmTitle": "このバージョンを復元しますか?", "skillManagement.detail.restoreVersionFailed": "バージョンの復元に失敗しました。", "skillManagement.detail.restoreVersionSuccess": "バージョンはドラフトに復元されました。", - "skillManagement.detail.save": "保存", "skillManagement.detail.saveConflictConfirmDescription": "このスキルは別の場所で更新されました。編集を続けるには、最新のドラフトを読み込んでください。現在の変更内容は破棄されます。", "skillManagement.detail.saveConflictConfirmTitle": "最新のドラフトをロードしますか?", "skillManagement.detail.saveConflictReload": "最新のドラフトをロードする", "skillManagement.detail.saveConflictStatus": "競合が検出されました。編集を続けるには、最新のドラフトをロードしてください。", "skillManagement.detail.saveFailed": "ファイルの保存に失敗しました。", - "skillManagement.detail.saveSuccess": "ファイルが保存されました。", "skillManagement.detail.saved": "保存済み", "skillManagement.detail.savedAt": "{{time}} に保存", "skillManagement.detail.saving": "保存中...", - "skillManagement.detail.searchFiles": "ファイルを検索", "skillManagement.detail.showMoreReferences": "さらに {{count}} 件を表示", "skillManagement.detail.skillDescriptionPlaceholder": "このスキルの用途と、Agent が使用するタイミングを説明してください。", "skillManagement.detail.skillNamePlaceholder": "使用できる文字は英小文字、数字、ハイフンのみです。", @@ -177,16 +143,12 @@ "skillManagement.detail.uploadConflict": "この名前のファイルはすでに存在します。", "skillManagement.detail.uploadFailureSummary": "{{failed}}/{{total}} ファイルが失敗しました", "skillManagement.detail.uploadFailureTitle": "一部のアップロードに失敗しました", - "skillManagement.detail.uploadFile": "ファイルをアップロード", - "skillManagement.detail.uploadFileFailed": "ファイルのアップロードに失敗しました。", "skillManagement.detail.uploadFileSuccess": "ファイルをアップロードしました。", "skillManagement.detail.uploadFilesButton": "{{count}} ファイルをアップロードする", "skillManagement.detail.uploadFilesFailedStatus_one": "{{count}} ファイルのアップロードに失敗しました。", "skillManagement.detail.uploadFilesFailedStatus_other": "{{count}} ファイルのアップロードに失敗しました。", "skillManagement.detail.uploadFilesMenu": "ファイルをアップロード...", "skillManagement.detail.uploadFilesProgress": "{{completed}}/{{total}} をアップロードしています", - "skillManagement.detail.uploadFilesResult": "{{uploaded}} がアップロードされました · {{failed}} は失敗しました", - "skillManagement.detail.uploadFilesStatus": "アップロードステータス", "skillManagement.detail.uploadInvalidExtension": "このファイルの種類はサポートされていません。", "skillManagement.detail.uploadInvalidName": "無効な名前です。", "skillManagement.detail.uploadInvalidPath": "無効なファイルパスです。", @@ -206,7 +168,6 @@ "skillManagement.detail.uploadWillReplace": "既存のファイルを置き換えます", "skillManagement.detail.uploadWillUseName": "{{name}}としてアップロードします", "skillManagement.detail.versionHistory": "バージョン履歴を開く", - "skillManagement.detail.versionMeta": "{{hash}} · {{time}}", "skillManagement.detail.versionPublishNote": "リリースノート", "skillManagement.detail.versionPublishNotePlaceholder": "この変更について説明してください", "skillManagement.detail.versionPublishedMeta": "{{time}} · {{name}}", @@ -227,8 +188,6 @@ "skillManagement.errors.missingSkillMd": "このスキルパッケージには SKILL.md がありません。SKILL.md を追加してから、もう一度インポートしてください。", "skillManagement.errors.nameConflict": "「{{name}}」という名前のスキルはすでに存在します。SKILL.md で名前を変更してから、もう一度インポートしてください。", "skillManagement.errors.workspaceLimit": "ワークスペースに追加できるスキルは 500 個までです。スキルを 1 つ削除してから、新しいスキルを追加してください。", - "skillManagement.exportDialog.description": "このスキルの保存済みの現在の下書きをエクスポートします。エディターに未保存の変更がある場合は、エクスポートする前に保存してください。", - "skillManagement.exportDialog.title": "保存済みの現在の下書きをエクスポート", "skillManagement.import": "インポート", "skillManagement.importFailed": "スキルのインポートに失敗しました。", "skillManagement.importSuccess": "Skill がインポートされました。", @@ -236,12 +195,10 @@ "skillManagement.loadingError": "スキルのロードに失敗しました", "skillManagement.moreActions": "{{name}} のその他の操作", "skillManagement.noDescription": "説明はまだありません", - "skillManagement.noTags": "タグなし", "skillManagement.publishedAt": "{{time}} に公開", "skillManagement.referenceCount_one": "{{count}} 個の Agent", "skillManagement.referenceCount_other": "{{count}} 個の Agent", "skillManagement.searchLabel": "スキルを検索", "skillManagement.searchPlaceholder": "名前または説明で検索", - "skillManagement.tags": "タグ", "skillManagement.title": "Skills" } diff --git a/web/i18n/ja-JP/snippet.json b/web/i18n/locales/ja-JP/snippet.json similarity index 100% rename from web/i18n/ja-JP/snippet.json rename to web/i18n/locales/ja-JP/snippet.json diff --git a/web/i18n/ja-JP/time.json b/web/i18n/locales/ja-JP/time.json similarity index 100% rename from web/i18n/ja-JP/time.json rename to web/i18n/locales/ja-JP/time.json diff --git a/web/i18n/ja-JP/tools.json b/web/i18n/locales/ja-JP/tools.json similarity index 100% rename from web/i18n/ja-JP/tools.json rename to web/i18n/locales/ja-JP/tools.json diff --git a/web/i18n/ja-JP/workflow.json b/web/i18n/locales/ja-JP/workflow.json similarity index 99% rename from web/i18n/ja-JP/workflow.json rename to web/i18n/locales/ja-JP/workflow.json index a251c7e37ee..1b16c78f338 100644 --- a/web/i18n/ja-JP/workflow.json +++ b/web/i18n/locales/ja-JP/workflow.json @@ -460,8 +460,6 @@ "nodes.agent.pluginNotInstalledDesc": "このインテグレーションは GitHub からインストールされています。再インストールするにはインテグレーションに移動してください。", "nodes.agent.roster.change": "変更", "nodes.agent.roster.editInConsole": "Agents で編集", - "nodes.agent.roster.editInConsoleDisabled": "Agent の編集にはエディター以上のロールが必要です。", - "nodes.agent.roster.editInConsoleDisabledRbac": "Agent を編集できるのは、エディター以上のロール、または「Agentsを管理」権限を持つメンバーのみです。", "nodes.agent.roster.inlineSetup.description": "このノード内にのみ存在する一度きりのセットアップ", "nodes.agent.roster.inlineSetup.name": "インラインセットアップ", "nodes.agent.roster.inlineSetup.type": "このノード内のみ", diff --git a/web/i18n/ko-KR/agent-v-2.json b/web/i18n/locales/ko-KR/agent-v-2.json similarity index 98% rename from web/i18n/ko-KR/agent-v-2.json rename to web/i18n/locales/ko-KR/agent-v-2.json index c1c06ca66dc..37c62260638 100644 --- a/web/i18n/ko-KR/agent-v-2.json +++ b/web/i18n/locales/ko-KR/agent-v-2.json @@ -69,6 +69,7 @@ "agentDetail.configure.answer.activity.runningCommands": "명령 실행 중", "agentDetail.configure.answer.duration.minute": "{{count}}분", "agentDetail.configure.answer.duration.second": "{{count}}초", + "agentDetail.configure.answer.responseEnded": "응답이 종료되었습니다", "agentDetail.configure.answer.thinking": "생각 중", "agentDetail.configure.build.empty.communityEditionTip": "Community Edition은 추출된 설정에 대한 버전 관리는 제공하지만 파일 시스템 자체의 버전 관리는 지원하지 않습니다. Build 모드에서의 작업에 주의하세요. 파일 시스템 변경은 실시간으로 발생하며 항상 깔끔하게 되돌릴 수 있는 것은 아닙니다. Community Edition은 대규모 외부 사용자에게 서비스를 제공하기에 이상적인 선택이 아닙니다.", "agentDetail.configure.build.empty.description": "원하는 내용을 설명하면 대화에 맞춰 왼쪽 양식이 채워집니다.", @@ -269,7 +270,6 @@ "agentDetail.configure.workingDirectory.breadcrumbLabel": "파일 시스템 경로", "agentDetail.configure.workingDirectory.description": "에이전트가 실행 중에 읽고 쓰는 파일입니다. qna_report.pdf 같은 생성 결과가 여기에 표시됩니다.", "agentDetail.configure.workingDirectory.fileSystem": "파일 시스템", - "agentDetail.configure.workingDirectory.open": "작업 디렉터리 열기", "agentDetail.configure.workingDirectory.persistentFiles": "영구", "agentDetail.configure.workingDirectory.persistentFilesTooltip": "에이전트가 유지하며 최종 사용자 대화와 워크플로를 포함해 에이전트가 실행되는 모든 곳에서 사용할 수 있는 파일입니다. 이 빌드 채팅에서 이러한 파일을 변경한 내용은 적용을 클릭하면 반영됩니다.", "agentDetail.configure.workingDirectory.temporaryFiles": "임시", @@ -348,7 +348,6 @@ "agentDetail.sections.logs": "로그", "agentDetail.sections.monitoring": "모니터링", "agentDetail.title": "에이전트", - "agentDetail.type": "에이전트", "agentDetail.versionHistory.empty": "아직 버전이 없습니다", "agentDetail.versionHistory.exitVersions": "버전 나가기", "agentDetail.versionHistory.filter": "버전 필터", @@ -408,8 +407,5 @@ "roster.sort.lastModified": "마지막 수정", "roster.sort.recentlyCreated": "최근 생성", "roster.title": "에이전트", - "roster.usageStatus.draft": "초안", - "skillManagement.detail.uploadFilesFailedStatus_one": "파일 {{count}}개 업로드에 실패했습니다.", - "skillManagement.detail.uploadFilesFailedStatus_other": "파일 {{count}}개 업로드에 실패했습니다.", - "skillManagement.title": "Skills" + "roster.usageStatus.draft": "초안" } diff --git a/web/i18n/ko-KR/app-annotation.json b/web/i18n/locales/ko-KR/app-annotation.json similarity index 100% rename from web/i18n/ko-KR/app-annotation.json rename to web/i18n/locales/ko-KR/app-annotation.json diff --git a/web/i18n/ko-KR/app-api.json b/web/i18n/locales/ko-KR/app-api.json similarity index 100% rename from web/i18n/ko-KR/app-api.json rename to web/i18n/locales/ko-KR/app-api.json diff --git a/web/i18n/ko-KR/app-debug.json b/web/i18n/locales/ko-KR/app-debug.json similarity index 100% rename from web/i18n/ko-KR/app-debug.json rename to web/i18n/locales/ko-KR/app-debug.json diff --git a/web/i18n/ko-KR/app-log.json b/web/i18n/locales/ko-KR/app-log.json similarity index 97% rename from web/i18n/ko-KR/app-log.json rename to web/i18n/locales/ko-KR/app-log.json index bdb0311fb19..4dbcf812ece 100644 --- a/web/i18n/ko-KR/app-log.json +++ b/web/i18n/locales/ko-KR/app-log.json @@ -4,11 +4,9 @@ "agentLogDetail.iteration": "반복", "agentLogDetail.iterations": "반복", "agentLogDetail.toolUsed": "사용된 도구", - "archives.action.downloadMonth": "{{month}} 아카이브 다운로드", "archives.action.downloadReady": "아카이브 다운로드가 준비되었습니다.", "archives.action.prepareDownload": "다운로드 준비", "archives.action.prepareFailed": "아카이브 다운로드를 준비하지 못했습니다.", - "archives.action.prepareMonth": "{{month}} 아카이브 다운로드 준비", "archives.action.prepareStarted": "아카이브 다운로드를 준비하는 중입니다.", "archives.action.preparing": "준비 중", "archives.description": "이 워크스페이스의 보관된 워크플로 실행 로그를 월별 CSV 내보내기로 다운로드합니다.", diff --git a/web/i18n/ko-KR/app-overview.json b/web/i18n/locales/ko-KR/app-overview.json similarity index 100% rename from web/i18n/ko-KR/app-overview.json rename to web/i18n/locales/ko-KR/app-overview.json diff --git a/web/i18n/ko-KR/app.json b/web/i18n/locales/ko-KR/app.json similarity index 100% rename from web/i18n/ko-KR/app.json rename to web/i18n/locales/ko-KR/app.json diff --git a/web/i18n/ko-KR/billing.json b/web/i18n/locales/ko-KR/billing.json similarity index 100% rename from web/i18n/ko-KR/billing.json rename to web/i18n/locales/ko-KR/billing.json diff --git a/web/i18n/ko-KR/common.json b/web/i18n/locales/ko-KR/common.json similarity index 98% rename from web/i18n/ko-KR/common.json rename to web/i18n/locales/ko-KR/common.json index cea78dacec9..6b987bbf03e 100644 --- a/web/i18n/ko-KR/common.json +++ b/web/i18n/locales/ko-KR/common.json @@ -209,9 +209,7 @@ "mainNav.marketplace": "마켓플레이스", "mainNav.skills": "Skills", "mainNav.webApps.noResults": "웹 앱을 찾을 수 없습니다", - "mainNav.webApps.openApp": "{{name}} 웹 앱 열기", "mainNav.webApps.searchPlaceholder": "웹 앱 검색", - "mainNav.workspace.credits": "{{count}} 크레딧", "mainNav.workspace.creditsUnit": "크레딧", "mainNav.workspace.inviteMembers": "Invite members", "mainNav.workspace.noResults": "워크스페이스를 찾을 수 없습니다", @@ -233,7 +231,6 @@ "members.datasetOperatorTip": "기술 자료만 관리할 수 있습니다.", "members.editRole": "역할 편집", "members.editorTip": "앱을 만들고 편집할 수 있음", - "members.email": "이메일", "members.emailInvalid": "유효하지 않은 이메일 형식", "members.emailNotSetup": "이메일 서버가 설정되지 않아 초대 이메일을 보낼 수 없습니다. 대신 초대 후 발급되는 초대 링크를 사용자에게 알려주세요.", "members.emailPlaceholder": "이메일 주소를 입력하거나 붙여넣으세요…", @@ -250,7 +247,6 @@ "members.inviteLimitExceeded": "모든 수신자를 초대할 수 있는 좌석이 부족합니다.", "members.inviteTeamMember": "팀 멤버 초대", "members.inviteTeamMemberTip": "로그인 후에 바로 팀 데이터에 액세스할 수 있습니다.", - "members.invitedAsRole": "{{role}}로 초대되었습니다", "members.lastActive": "최근 활동", "members.memberActions": "멤버 작업", "members.memberDetails.assign": "할당", @@ -258,8 +254,6 @@ "members.memberDetails.assignedRoles": "할당된 역할", "members.memberDetails.customGroup": "사용자 지정", "members.memberDetails.generalGroup": "일반", - "members.memberDetails.openAria": "{{name}}님의 멤버 세부 정보 열기", - "members.memberDetails.roleActionsAria": "{{role}} 역할에 대한 작업 열기", "members.memberDetails.roleNoPermissionSummary": "현재 역할에는 권한이 없습니다.", "members.memberDetails.rolePermissionSummary": "{{role}}은(는) {{permissions}}을(를) 할 수 있습니다", "members.memberDetails.title": "멤버 세부 정보", @@ -370,7 +364,6 @@ "modelProvider.card.usagePriorityTip": "모델 실행 시 우선 사용할 리소스를 설정합니다.", "modelProvider.configureTip": "api-key 설정 또는 사용할 모델 추가", "modelProvider.confirmDelete": "삭제를 확인하시겠습니까?", - "modelProvider.credits": "메시지 크레딧", "modelProvider.defaultConfig": "기본 구성", "modelProvider.discoverMore": "더 알아보기", "modelProvider.embeddingModel.key": "임베딩 모델", @@ -427,7 +420,6 @@ "modelProvider.selector.noProviderConfiguredDesc": "마켓플레이스에서 설치하거나 설정에서 공급자를 구성하세요.", "modelProvider.selector.onlyCompatibleModelsShown": "호환되는 모델만 표시됩니다", "modelProvider.selector.showIncompatibleModels": "호환되지 않는 모델 표시", - "modelProvider.selector.suggestion": "추천", "modelProvider.selector.suggestionTip": "에이전트 작업에 권장", "modelProvider.showModels": "모델 표시", "modelProvider.speechToTextModel.key": "음성-to-텍스트 모델", @@ -458,6 +450,8 @@ "operation.confirmAction": "귀하의 행동을 확인해 주세요.", "operation.copied": "복사", "operation.copy": "복사", + "operation.copyErrorDetails": "오류 세부 정보 복사", + "operation.copyErrorFailed": "복사 실패. 다시 시도", "operation.copyImage": "이미지 복사", "operation.create": "생성", "operation.deSelectAll": "모두 선택 해제", @@ -475,7 +469,6 @@ "operation.format": "형식", "operation.imageCopied": "복사된 이미지", "operation.imageCopyFailed": "이미지를 복사할 수 없습니다. 대신 다운로드해 주세요.", - "operation.imageDownloaded": "이미지 다운로드됨", "operation.in": "안으로", "operation.learnMore": "자세히 알아보기", "operation.log": "로그", @@ -512,7 +505,6 @@ "operation.sure": "확인", "operation.toggleFullscreen": "전체 화면 전환", "operation.toggleMute": "음소거 전환", - "operation.upload": "Upload", "operation.view": "보기", "operation.viewDetails": "세부 정보보기", "operation.viewMore": "더보기", @@ -542,7 +534,6 @@ "promptEditor.requestURL.item.title": "요청 URL", "promptEditor.variable.modal.add": "새로운 변수", "promptEditor.variable.modal.addTool": "새로운 도구", - "provider.anthropicHosted.trialQuotaTip": "Anthropic 평가판 할당량은 2025/03/11에 만료되며 그 이후에는 더 이상 사용할 수 없습니다. 제때 활용하시기 바랍니다.", "provider.encrypted.back": "기술을 사용하여 암호화 및 저장됩니다.", "provider.encrypted.front": "API KEY 는", "provider.validating": "키를 확인하는 중...", @@ -565,7 +556,6 @@ "settings.preferences": "Preferences", "settings.provider": "모델 제공자", "settings.resourceAccess": "리소스 접근", - "settings.resourceAccessDescription": "앱과 지식 베이스가 사용할 수 있는 권한 규칙을 구성합니다. 권한 규칙은 특정 리소스 접근 구성에서 멤버에게 할당할 수 있습니다.", "settings.rolesAndPermissions": "역할 및 권한", "settings.settings": "Settings", "settings.swaggerAPIAsTool": "Swagger API as Tool", @@ -695,7 +685,6 @@ "userProfile.helpCenter": "도움말 센터", "userProfile.logout": "로그아웃", "userProfile.roadmap": "로드맵", - "userProfile.settings": "설정", "userProfile.workspace": "작업 공간", "voice.language.arTN": "튀니지 아랍어", "voice.language.deDE": "독일어", diff --git a/web/i18n/ko-KR/custom.json b/web/i18n/locales/ko-KR/custom.json similarity index 100% rename from web/i18n/ko-KR/custom.json rename to web/i18n/locales/ko-KR/custom.json diff --git a/web/i18n/ko-KR/dataset-creation.json b/web/i18n/locales/ko-KR/dataset-creation.json similarity index 100% rename from web/i18n/ko-KR/dataset-creation.json rename to web/i18n/locales/ko-KR/dataset-creation.json diff --git a/web/i18n/ko-KR/dataset-documents.json b/web/i18n/locales/ko-KR/dataset-documents.json similarity index 100% rename from web/i18n/ko-KR/dataset-documents.json rename to web/i18n/locales/ko-KR/dataset-documents.json diff --git a/web/i18n/ko-KR/dataset-hit-testing.json b/web/i18n/locales/ko-KR/dataset-hit-testing.json similarity index 100% rename from web/i18n/ko-KR/dataset-hit-testing.json rename to web/i18n/locales/ko-KR/dataset-hit-testing.json diff --git a/web/i18n/ko-KR/dataset-pipeline.json b/web/i18n/locales/ko-KR/dataset-pipeline.json similarity index 100% rename from web/i18n/ko-KR/dataset-pipeline.json rename to web/i18n/locales/ko-KR/dataset-pipeline.json diff --git a/web/i18n/ko-KR/dataset-settings.json b/web/i18n/locales/ko-KR/dataset-settings.json similarity index 100% rename from web/i18n/ko-KR/dataset-settings.json rename to web/i18n/locales/ko-KR/dataset-settings.json diff --git a/web/i18n/ko-KR/dataset.json b/web/i18n/locales/ko-KR/dataset.json similarity index 97% rename from web/i18n/ko-KR/dataset.json rename to web/i18n/locales/ko-KR/dataset.json index 38a32a0a131..e607ba6ee15 100644 --- a/web/i18n/ko-KR/dataset.json +++ b/web/i18n/locales/ko-KR/dataset.json @@ -163,7 +163,6 @@ "newKnowledge.configureProvider": "{{provider}} 구성", "newKnowledge.connectNotion": "Notion 연결", "newKnowledge.connectProvider": "{{provider}} 연결", - "newKnowledge.connectProviderGeneric": "공급자 연결", "newKnowledge.connectSource": "소스 연결", "newKnowledge.connectSourceDescription": "콘텐츠가 Notion, 웹사이트 및 기타 서비스와 계속 동기화됩니다.", "newKnowledge.connectingProvider": "연결 중…", @@ -179,18 +178,14 @@ "newKnowledge.crawlFailedDescription": "크롤러가 이 미리보기를 완료하지 못했습니다. URL을 확인하고 크롤링 옵션을 조정한 후 다시 시도하세요.", "newKnowledge.crawlFailedProvider": "{{provider}}에서 오류를 보고했습니다. 제공업체 연결을 확인하고 다시 시도하세요.", "newKnowledge.crawlFailedTimeout": "크롤링 시간이 초과되었습니다. 범위가 더 좁은 URL을 사용하거나 최대 페이지 수를 줄이세요.", - "newKnowledge.crawlNotStarted": "시작되지 않음", "newKnowledge.crawlOptions": "크롤링 옵션", "newKnowledge.crawlPreview": "크롤링 미리보기", "newKnowledge.crawlProgress": "{{host}} 크롤링 진행률", - "newKnowledge.crawlSetupUnavailableDescription": "크롤링 워크플로를 사용할 수 있을 때까지 웹사이트 크롤링 구성이 비활성화됩니다.", - "newKnowledge.crawlSetupUnavailableTitle": "Firecrawl이 준비되었습니다.", "newKnowledge.crawlStartFailed": "크롤링 미리보기를 시작할 수 없습니다. 다시 시도하세요.", "newKnowledge.crawlStopped": "크롤링이 중지됨", "newKnowledge.crawling": "크롤링 중…", "newKnowledge.crawlingPages": "{{host}} 크롤링 중… {{count}}개 페이지 발견", "newKnowledge.createFailed": "이 기술 자료를 만들 수 없습니다. 액세스 권한을 확인하고 다시 시도하세요.", - "newKnowledge.createIllustrationTitle": "콘텐츠를 한 번 연결하면 모든 앱이 최신 지식으로 답변합니다.", "newKnowledge.createTitle": "지식 창조", "newKnowledge.createdAt": "생성됨", "newKnowledge.creators": "생성자", @@ -220,7 +215,6 @@ "newKnowledge.documentColumn": "문서", "newKnowledge.documentContentIncomplete": "나머지 문서 콘텐츠를 불러오는 중…", "newKnowledge.documentContents": "목차", - "newKnowledge.documentFacts": "문서 세부 정보", "newKnowledge.documentFilterLabel": "상태별로 문서 필터링", "newKnowledge.documentLoadErrorDescription": "이 문서를 불러오지 못했습니다. 다시 시도하세요.", "newKnowledge.documentLoadErrorTitle": "문서를 불러오지 못함", @@ -268,7 +262,6 @@ "newKnowledge.documentsSelected": "{{count}} 문서가 선택됨", "newKnowledge.documentsUnavailable": "문서 —개", "newKnowledge.downloadDocuments": "다운로드", - "newKnowledge.editSource": "편집", "newKnowledge.emptyChunk": "빈 청크", "newKnowledge.emptyDescription": "문서를 추가하면 에이전트가 직접 읽을 수 있습니다. 인덱싱 설정은 필요하지 않습니다.", "newKnowledge.emptyTitle": "지식 베이스를 구축하는 더 스마트한 방법", @@ -344,7 +337,6 @@ "newKnowledge.processingTaskState.succeeded": "완료됨", "newKnowledge.processingTaskState.superseded": "대체됨", "newKnowledge.providerConnected": "{{provider}} 연결됨", - "newKnowledge.providerLabel": "제공자 선택", "newKnowledge.providerLoadFailed": "제공자를 로드할 수 없습니다.", "newKnowledge.providerNotConfigured": "{{provider}}이 구성되지 않았습니다.", "newKnowledge.providerNotConfiguredDescription": "{{provider}}을(를) 사용하여 웹사이트를 크롤링하려면 API 키 또는 엔드포인트 URL을 추가하세요.", @@ -369,12 +361,10 @@ "newKnowledge.revisionState.superseded": "이전", "newKnowledge.rootUrl": "루트 URL", "newKnowledge.rootUrlPlaceholder": "https://docs.example.com", - "newKnowledge.routeUnavailable": "이 섹션은 다음 설정 단계에서 사용할 수 있습니다.", "newKnowledge.searchDocuments": "문서 검색", "newKnowledge.searchSources": "소스 검색", "newKnowledge.selectAll": "모두 선택", "newKnowledge.selectAllDocuments": "모든 문서 선택", - "newKnowledge.selectAllSources": "모든 소스 선택", "newKnowledge.selectChunk": "내용을 확인할 청크를 선택하세요.", "newKnowledge.selectFilesAndFolders": "동기화할 파일과 폴더 선택", "newKnowledge.skippedFailed": "건너뜀(실패)", @@ -384,7 +374,6 @@ "newKnowledge.sourceFilterLabel": "상태별로 소스 필터링", "newKnowledge.sourceName": "소스 이름", "newKnowledge.sourceNamePlaceholder": "예: 제품 문서 크롤링", - "newKnowledge.sourceNameRequired": "소스 이름을 입력하세요.", "newKnowledge.sourceSetupBackendDependency": "소스 설정은 전체가 표시되며 편집과 미리보기에는 초안 소스 백엔드 워크플로가 필요합니다.", "newKnowledge.sourceStatus.active": "활성", "newKnowledge.sourceStatus.disabled": "비활성화됨", @@ -431,7 +420,6 @@ "newKnowledge.unavailableDescription": "현재 이 워크스페이스에서 KnowledgeFS를 사용할 수 없습니다.", "newKnowledge.unavailableTitle": "새 지식을 사용할 수 없음", "newKnowledge.updated": "{{date}} 업데이트", - "newKnowledge.updatedAt": "마지막 업데이트", "newKnowledge.updatedColumn": "업데이트됨", "newKnowledge.uploadCharactersUnavailable": "문자 수: —", "newKnowledge.uploadDocuments": "문서 업로드", @@ -460,11 +448,9 @@ "retrievalSettings": "검색 설정", "retry": "재시도", "selectExternalKnowledgeAPI.placeholder": "외부 지식 API 선택", - "serviceApi.card.addApiKey": "API 키 추가", "serviceApi.card.apiKey": "API 키", "serviceApi.card.apiReference": "API 참고", "serviceApi.card.endpoint": "서비스 API 엔드포인트", - "serviceApi.card.manageApiKey": "API 키 관리", "serviceApi.card.title": "백엔드 서비스 API", "serviceApi.disabled": "비활성화됨", "serviceApi.enabled": "서비스 중", diff --git a/web/i18n/ko-KR/deployments.json b/web/i18n/locales/ko-KR/deployments.json similarity index 97% rename from web/i18n/ko-KR/deployments.json rename to web/i18n/locales/ko-KR/deployments.json index e7235629114..b1cb5357fe2 100644 --- a/web/i18n/ko-KR/deployments.json +++ b/web/i18n/locales/ko-KR/deployments.json @@ -27,7 +27,6 @@ "list.emptyTitle": "아직 배포가 없습니다", "overview.chip.latest": "최신", "status.RUNTIME_INSTANCE_STATUS_DEPLOYING": "배포 중", - "status.RUNTIME_INSTANCE_STATUS_FAILED": "배포 실패", "status.RUNTIME_INSTANCE_STATUS_INVALID": "유효하지 않음", "status.RUNTIME_INSTANCE_STATUS_READY": "실행 중", "status.RUNTIME_INSTANCE_STATUS_UNDEPLOYED": "배포되지 않음", @@ -87,6 +86,5 @@ "studio.versionValue": "버전 값", "studio.versionsBehind_one": "{{count}}개 버전 뒤처짐", "studio.versionsBehind_other": "{{count}}개 버전 뒤처짐", - "versions.checkingReleaseContent": "릴리스 콘텐츠 확인 중...", "versions.deployTo": "{{name}}에 배포" } diff --git a/web/i18n/ko-KR/device-flow.json b/web/i18n/locales/ko-KR/device-flow.json similarity index 100% rename from web/i18n/ko-KR/device-flow.json rename to web/i18n/locales/ko-KR/device-flow.json diff --git a/web/i18n/ko-KR/education.json b/web/i18n/locales/ko-KR/education.json similarity index 100% rename from web/i18n/ko-KR/education.json rename to web/i18n/locales/ko-KR/education.json diff --git a/web/i18n/ko-KR/explore.json b/web/i18n/locales/ko-KR/explore.json similarity index 100% rename from web/i18n/ko-KR/explore.json rename to web/i18n/locales/ko-KR/explore.json diff --git a/web/i18n/ko-KR/layout.json b/web/i18n/locales/ko-KR/layout.json similarity index 100% rename from web/i18n/ko-KR/layout.json rename to web/i18n/locales/ko-KR/layout.json diff --git a/web/i18n/ko-KR/login.json b/web/i18n/locales/ko-KR/login.json similarity index 100% rename from web/i18n/ko-KR/login.json rename to web/i18n/locales/ko-KR/login.json diff --git a/web/i18n/ko-KR/oauth.json b/web/i18n/locales/ko-KR/oauth.json similarity index 100% rename from web/i18n/ko-KR/oauth.json rename to web/i18n/locales/ko-KR/oauth.json diff --git a/web/i18n/ko-KR/permission-keys.json b/web/i18n/locales/ko-KR/permission-keys.json similarity index 100% rename from web/i18n/ko-KR/permission-keys.json rename to web/i18n/locales/ko-KR/permission-keys.json diff --git a/web/i18n/ko-KR/permission.json b/web/i18n/locales/ko-KR/permission.json similarity index 100% rename from web/i18n/ko-KR/permission.json rename to web/i18n/locales/ko-KR/permission.json diff --git a/web/i18n/ko-KR/pipeline.json b/web/i18n/locales/ko-KR/pipeline.json similarity index 100% rename from web/i18n/ko-KR/pipeline.json rename to web/i18n/locales/ko-KR/pipeline.json diff --git a/web/i18n/ko-KR/plugin-tags.json b/web/i18n/locales/ko-KR/plugin-tags.json similarity index 100% rename from web/i18n/ko-KR/plugin-tags.json rename to web/i18n/locales/ko-KR/plugin-tags.json diff --git a/web/i18n/ko-KR/plugin-trigger.json b/web/i18n/locales/ko-KR/plugin-trigger.json similarity index 100% rename from web/i18n/ko-KR/plugin-trigger.json rename to web/i18n/locales/ko-KR/plugin-trigger.json diff --git a/web/i18n/ko-KR/plugin.json b/web/i18n/locales/ko-KR/plugin.json similarity index 99% rename from web/i18n/ko-KR/plugin.json rename to web/i18n/locales/ko-KR/plugin.json index 45054ccfd53..bd7d7d40d05 100644 --- a/web/i18n/ko-KR/plugin.json +++ b/web/i18n/locales/ko-KR/plugin.json @@ -277,7 +277,6 @@ "readmeInfo.noReadmeAvailable": "사용 가능한 README가 없습니다", "readmeInfo.title": "읽어보기", "requestAPlugin": "플러그인을 요청하세요", - "requestSubmit": "요청 또는 게시", "search": "검색", "searchCategories": "검색 카테고리", "searchInMarketplace": "Marketplace 에서 검색", diff --git a/web/i18n/ko-KR/register.json b/web/i18n/locales/ko-KR/register.json similarity index 100% rename from web/i18n/ko-KR/register.json rename to web/i18n/locales/ko-KR/register.json diff --git a/web/i18n/ko-KR/run-log.json b/web/i18n/locales/ko-KR/run-log.json similarity index 100% rename from web/i18n/ko-KR/run-log.json rename to web/i18n/locales/ko-KR/run-log.json diff --git a/web/i18n/ko-KR/share.json b/web/i18n/locales/ko-KR/share.json similarity index 100% rename from web/i18n/ko-KR/share.json rename to web/i18n/locales/ko-KR/share.json diff --git a/web/i18n/ko-KR/skill.json b/web/i18n/locales/ko-KR/skill.json similarity index 83% rename from web/i18n/ko-KR/skill.json rename to web/i18n/locales/ko-KR/skill.json index ea439d14f7a..816757fd74b 100644 --- a/web/i18n/ko-KR/skill.json +++ b/web/i18n/locales/ko-KR/skill.json @@ -1,5 +1,4 @@ { - "skillManagement.clearTags": "태그 지우기", "skillManagement.create": "만들기", "skillManagement.createFailed": "스킬 생성에 실패했습니다.", "skillManagement.createSuccess": "초안이 생성되었습니다.", @@ -13,12 +12,9 @@ "skillManagement.deleteFailed": "스킬 삭제에 실패했습니다.", "skillManagement.deleteSuccess": "Skill이(가) 삭제되었습니다.", "skillManagement.detail.addMetadata": "메타데이터 추가", - "skillManagement.detail.addMetadataDescription": "이 Markdown 파일에 머리말 필드를 추가합니다.", "skillManagement.detail.addTag": "태그 추가", - "skillManagement.detail.addTagDescription": "이 Skill에 태그를 생성하거나 바인딩하세요. 새 태그는 Skill 메타데이터와 함께 저장됩니다.", "skillManagement.detail.addTagSuccess": "태그가 추가되었습니다.", "skillManagement.detail.back": "Skills(으)로 돌아가기", - "skillManagement.detail.builder.applyFailed": "초안 업데이트를 적용하지 못했습니다.", "skillManagement.detail.builder.attach": "파일첨부", "skillManagement.detail.builder.attachFailed": "파일을 첨부하지 못했습니다.", "skillManagement.detail.builder.attachLimit": "최대 10개의 파일을 첨부할 수 있습니다.", @@ -26,22 +22,12 @@ "skillManagement.detail.builder.attachUnsupported": "이미지, 텍스트, 문서 파일만 첨부 가능합니다.", "skillManagement.detail.builder.attachmentOnlyMessage": "첨부파일을 활용하시면 이 스킬을 향상하는데 도움이 됩니다.", "skillManagement.detail.builder.close": "Skill Builder 닫기", - "skillManagement.detail.builder.compatibleModelsOnly": "호환되는 모델만 표시됩니다", "skillManagement.detail.builder.copyResponse": "응답 복사", "skillManagement.detail.builder.copySuccess": "응답이 클립보드에 복사되었습니다.", "skillManagement.detail.builder.editIntro": "이 스킬을 리뷰해봤습니다. 무엇을 조정하시겠습니까?", "skillManagement.detail.builder.exampleIssueTriage": "고객 문제 분류", "skillManagement.detail.builder.exampleOnboarding": "신입사원 온보딩 가이드", "skillManagement.detail.builder.exampleSalesFollowUp": "영업 리드 후속 전략", - "skillManagement.detail.builder.followUpDisplayName": "표시 이름으로 환불 승인을 사용하세요.", - "skillManagement.detail.builder.followUpNameIcon": "제안된 이름과 아이콘을 적용하세요.", - "skillManagement.detail.builder.fromMarketplace": "마켓플레이스에서", - "skillManagement.detail.builder.model": "GPT-4o", - "skillManagement.detail.builder.modelCredits.all": "모든 학점", - "skillManagement.detail.builder.modelCredits.configure": "구성 필요", - "skillManagement.detail.builder.modelCredits.exhausted": "할당량이 다 사용되었습니다", - "skillManagement.detail.builder.modelProviderSettings": "모델 공급자 설정", - "skillManagement.detail.builder.modelSearch": "모델 검색...", "skillManagement.detail.builder.modifyPlaceholder": "AI에게 이 스킬을 수정해달라고 요청하세요...", "skillManagement.detail.builder.open": "Skill Builder 열기", "skillManagement.detail.builder.placeholder": "시나리오를 설명해보세요..", @@ -61,8 +47,6 @@ "skillManagement.detail.builder.thinkingUnavailable": "모델이 사고 콘텐츠를 반환하지 않았습니다.", "skillManagement.detail.builder.title": "Skill Builder", "skillManagement.detail.builder.tryExample": "예시를 들어보세요", - "skillManagement.detail.builder.voice": "음성 입력", - "skillManagement.detail.builder.voiceUnavailable": "아직 음성 입력을 사용할 수 없습니다.", "skillManagement.detail.cancelAddMetadata": "메타데이터 추가 취소", "skillManagement.detail.closeFileTab": "{{name}} 닫기", "skillManagement.detail.closeVersions": "버전 닫기", @@ -70,14 +54,10 @@ "skillManagement.detail.copyContentSuccess": "콘텐츠가 클립보드에 복사되었습니다.", "skillManagement.detail.copyFile": "복사", "skillManagement.detail.copyFileSuccess": "파일을 붙여넣을 준비가 되었습니다.", - "skillManagement.detail.copyVersionId": "ID 복사", - "skillManagement.detail.copyVersionIdSuccess": "ID가 클립보드에 복사되었습니다.", "skillManagement.detail.createFile": "새 파일", - "skillManagement.detail.createFileDescription": "스킬 루트를 기준으로 파일 경로를 입력하세요.", "skillManagement.detail.createFileMenu": "새 파일...", "skillManagement.detail.createFileSuccess": "파일이 생성되었습니다.", "skillManagement.detail.createFolder": "새 폴더", - "skillManagement.detail.createFolderDescription": "스킬 루트를 기준으로 폴더 경로를 입력하세요.", "skillManagement.detail.createFolderMenu": "새 폴더...", "skillManagement.detail.createFolderSuccess": "폴더가 생성되었습니다.", "skillManagement.detail.createdBy": "작성자: {{name}}", @@ -100,10 +80,8 @@ "skillManagement.detail.fileMeta": "{{type}} · {{size}} 바이트", "skillManagement.detail.fileMissing": "파일을 찾을 수 없습니다.", "skillManagement.detail.fileOperationFailed": "파일 작업이 실패했습니다.", - "skillManagement.detail.files": "파일", "skillManagement.detail.latest": "최신", "skillManagement.detail.loadFailed": "스킬 로딩에 실패했습니다.", - "skillManagement.detail.loadingTags": "태그 로드 중...", "skillManagement.detail.markdownLiveMode": "실시간 미리보기", "skillManagement.detail.markdownSourceMode": "소스 편집기", "skillManagement.detail.metadataKey": "이름", @@ -113,10 +91,7 @@ "skillManagement.detail.nameThisVersion": "이름 바꾸기", "skillManagement.detail.noFileSelected": "미리 볼 파일을 선택하세요.", "skillManagement.detail.noFiles": "파일이 없습니다.", - "skillManagement.detail.noSearchResults": "일치하는 파일이 없습니다.", - "skillManagement.detail.noSelectableTags": "사용 가능한 태그가 없습니다.", "skillManagement.detail.noVersions": "아직 게시된 버전이 없습니다.", - "skillManagement.detail.pasteFile": "반죽", "skillManagement.detail.pasteFileSuccess": "파일을 붙여넣었습니다.", "skillManagement.detail.previewUnsupported": "이 파일은 미리보기를 지원하지 않습니다.", "skillManagement.detail.publish": "게시", @@ -128,9 +103,7 @@ "skillManagement.detail.publishUpdate": "업데이트 게시", "skillManagement.detail.published": "게시됨", "skillManagement.detail.publishedAt": "{{time}} 게시됨", - "skillManagement.detail.publishedVersion": "#{{number}} 게시됨", "skillManagement.detail.publishing": "게시 중...", - "skillManagement.detail.readonly": "읽기 전용", "skillManagement.detail.referenceFiles.confirm": "확인을 입력하세요", "skillManagement.detail.referenceFiles.empty": "사용 가능한 파일이 없습니다.", "skillManagement.detail.referenceFiles.livePlaceholder": "상담원을 위한 지침을 작성합니다. 파일을 참조하려면 /를 입력하세요.", @@ -138,14 +111,10 @@ "skillManagement.detail.referenceFiles.title": "참조 파일", "skillManagement.detail.referencedBy_one": "{{count}}개의 Agent가 사용 중", "skillManagement.detail.referencedBy_other": "{{count}}개의 Agent가 사용 중", - "skillManagement.detail.removeMetadata": "{{name}} 메타데이터 삭제", "skillManagement.detail.removeTag": "{{tag}} 제거", "skillManagement.detail.removeTagSuccess": "태그가 삭제되었습니다.", - "skillManagement.detail.renameFile": "파일 이름 바꾸기", - "skillManagement.detail.renameFileDescription": "스킬 루트를 기준으로 새 경로를 입력합니다.", "skillManagement.detail.renameFileSuccess": "파일 이름이 변경되었습니다.", "skillManagement.detail.renameSkillSuccess": "Skill 이름이 변경되었습니다.", - "skillManagement.detail.renameVersion": "이름 바꾸기", "skillManagement.detail.renameVersionFailed": "버전 이름을 바꾸지 못했습니다.", "skillManagement.detail.renameVersionPrompt": "버전 이름", "skillManagement.detail.renameVersionSuccess": "버전 이름이 변경되었습니다.", @@ -155,17 +124,14 @@ "skillManagement.detail.restoreVersionConfirmTitle": "이 버전을 복원하시겠습니까?", "skillManagement.detail.restoreVersionFailed": "버전을 복원하지 못했습니다.", "skillManagement.detail.restoreVersionSuccess": "버전이 초안으로 복원되었습니다.", - "skillManagement.detail.save": "저장", "skillManagement.detail.saveConflictConfirmDescription": "이 스킬은 다른 곳에서 업데이트되었습니다. 계속 편집하려면 최신 초안을 로드하세요. 현재 변경사항이 삭제됩니다.", "skillManagement.detail.saveConflictConfirmTitle": "최신 초안을 로드하시겠습니까?", "skillManagement.detail.saveConflictReload": "최신 초안 로드", "skillManagement.detail.saveConflictStatus": "충돌이 감지되었습니다. 계속 편집하려면 최신 초안을 로드하세요.", "skillManagement.detail.saveFailed": "파일을 저장하지 못했습니다.", - "skillManagement.detail.saveSuccess": "파일이 저장되었습니다.", "skillManagement.detail.saved": "저장됨", "skillManagement.detail.savedAt": "{{time}}에 저장", "skillManagement.detail.saving": "저장 중...", - "skillManagement.detail.searchFiles": "파일 검색", "skillManagement.detail.showMoreReferences": "{{count}} 더 보기", "skillManagement.detail.skillDescriptionPlaceholder": "이 스킬의 기능과 상담원이 언제 사용해야 하는지 설명하세요.", "skillManagement.detail.skillNamePlaceholder": "소문자, 숫자, 하이픈을 사용하세요.", @@ -177,16 +143,12 @@ "skillManagement.detail.uploadConflict": "이 이름을 가진 파일이 이미 존재합니다.", "skillManagement.detail.uploadFailureSummary": "{{total}} 파일 중 {{failed}}가 실패했습니다.", "skillManagement.detail.uploadFailureTitle": "일부 업로드에 실패했습니다.", - "skillManagement.detail.uploadFile": "파일 업로드", - "skillManagement.detail.uploadFileFailed": "파일 업로드에 실패했습니다.", "skillManagement.detail.uploadFileSuccess": "파일을 업로드했습니다.", "skillManagement.detail.uploadFilesButton": "{{count}} 파일 업로드", "skillManagement.detail.uploadFilesFailedStatus_one": "{{count}} 파일 업로드에 실패했습니다.", "skillManagement.detail.uploadFilesFailedStatus_other": "{{count}} 파일 업로드에 실패했습니다.", "skillManagement.detail.uploadFilesMenu": "파일 업로드...", "skillManagement.detail.uploadFilesProgress": "{{completed}}/{{total}} 업로드 중", - "skillManagement.detail.uploadFilesResult": "{{uploaded}} 업로드됨 · {{failed}} 실패", - "skillManagement.detail.uploadFilesStatus": "업로드 상태", "skillManagement.detail.uploadInvalidExtension": "이 파일 형식은 지원되지 않습니다.", "skillManagement.detail.uploadInvalidName": "이름이 잘못되었습니다.", "skillManagement.detail.uploadInvalidPath": "파일 경로가 잘못되었습니다.", @@ -206,7 +168,6 @@ "skillManagement.detail.uploadWillReplace": "기존 파일을 대체합니다.", "skillManagement.detail.uploadWillUseName": "{{name}}로 업로드됩니다", "skillManagement.detail.versionHistory": "버전 기록 열기", - "skillManagement.detail.versionMeta": "{{hash}} · {{time}}", "skillManagement.detail.versionPublishNote": "릴리스 노트", "skillManagement.detail.versionPublishNotePlaceholder": "이 변경사항을 설명하세요.", "skillManagement.detail.versionPublishedMeta": "{{time}} · {{name}}", @@ -227,8 +188,6 @@ "skillManagement.errors.missingSkillMd": "이 패키지에는 SKILL.md가 없습니다. 파일을 추가하고 다시 가져오세요.", "skillManagement.errors.nameConflict": "\"{{name}}\"라는 스킬이 이미 존재합니다. SKILL.md에서 이름을 바꾸고 다시 가져옵니다.", "skillManagement.errors.workspaceLimit": "작업 영역이 스킬 한도인 500개에 도달했습니다. 새로운 스킬을 추가하려면 스킬을 삭제하세요.", - "skillManagement.exportDialog.description": "이 스킬의 현재 저장된 초안을 내보냅니다. 편집기에 저장하지 않은 변경 사항이 있으면 내보내기 전에 저장해 주세요.", - "skillManagement.exportDialog.title": "현재 저장된 초안 내보내기", "skillManagement.import": "가져오기", "skillManagement.importFailed": "스킬을 가져오지 못했습니다.", "skillManagement.importSuccess": "Skill 가져왔습니다.", @@ -236,12 +195,10 @@ "skillManagement.loadingError": "스킬을 로드하지 못했습니다.", "skillManagement.moreActions": "{{name}}의 추가 작업", "skillManagement.noDescription": "아직 설명이 없습니다", - "skillManagement.noTags": "태그 없음", "skillManagement.publishedAt": "{{time}}에 게시", "skillManagement.referenceCount_one": "{{count}}개 Agent", "skillManagement.referenceCount_other": "{{count}}개 Agent", "skillManagement.searchLabel": "스킬 검색", "skillManagement.searchPlaceholder": "이름이나 설명으로 검색", - "skillManagement.tags": "태그", "skillManagement.title": "스킬" } diff --git a/web/i18n/ko-KR/snippet.json b/web/i18n/locales/ko-KR/snippet.json similarity index 100% rename from web/i18n/ko-KR/snippet.json rename to web/i18n/locales/ko-KR/snippet.json diff --git a/web/i18n/ko-KR/time.json b/web/i18n/locales/ko-KR/time.json similarity index 100% rename from web/i18n/ko-KR/time.json rename to web/i18n/locales/ko-KR/time.json diff --git a/web/i18n/ko-KR/tools.json b/web/i18n/locales/ko-KR/tools.json similarity index 100% rename from web/i18n/ko-KR/tools.json rename to web/i18n/locales/ko-KR/tools.json diff --git a/web/i18n/ko-KR/workflow.json b/web/i18n/locales/ko-KR/workflow.json similarity index 99% rename from web/i18n/ko-KR/workflow.json rename to web/i18n/locales/ko-KR/workflow.json index 0da16771e07..a339003ba0d 100644 --- a/web/i18n/ko-KR/workflow.json +++ b/web/i18n/locales/ko-KR/workflow.json @@ -460,8 +460,6 @@ "nodes.agent.pluginNotInstalledDesc": "이 통합은 GitHub 에서 설치됩니다. 통합으로 이동하여 다시 설치하십시오.", "nodes.agent.roster.change": "변경", "nodes.agent.roster.editInConsole": "Agents에서 편집", - "nodes.agent.roster.editInConsoleDisabled": "에이전트를 편집하려면 편집자 이상의 역할이 필요합니다.", - "nodes.agent.roster.editInConsoleDisabledRbac": "편집자 이상의 역할 또는 Agents 관리 권한이 있는 멤버만 에이전트를 편집할 수 있습니다.", "nodes.agent.roster.inlineSetup.description": "이 노드 내에만 존재하는 일회성 설정", "nodes.agent.roster.inlineSetup.name": "인라인 설정", "nodes.agent.roster.inlineSetup.type": "이 노드에서만", diff --git a/web/i18n/lo-LA/agent-v-2.json b/web/i18n/locales/lo-LA/agent-v-2.json similarity index 99% rename from web/i18n/lo-LA/agent-v-2.json rename to web/i18n/locales/lo-LA/agent-v-2.json index b6d62f45b27..4164805abfb 100644 --- a/web/i18n/lo-LA/agent-v-2.json +++ b/web/i18n/locales/lo-LA/agent-v-2.json @@ -69,6 +69,7 @@ "agentDetail.configure.answer.activity.runningCommands": "ກຳລັງປະຕິບັດຄຳສັ່ງ", "agentDetail.configure.answer.duration.minute": "{{count}} ນາທີ", "agentDetail.configure.answer.duration.second": "{{count}} ວິນາທີ", + "agentDetail.configure.answer.responseEnded": "ການຕອບກັບສິ້ນສຸດແລ້ວ", "agentDetail.configure.answer.thinking": "ກຳລັງຄິດ", "agentDetail.configure.build.empty.communityEditionTip": "Community Edition ເຖິງວ່າຈະມີລະບົບເວີຊັນໃຫ້ກັບການຕັ້ງຄ່າທີ່ສະກັດອອກມາ ແຕ່ບໍ່ຮອງຮັບລະບົບເວີຊັນໃນລະບົບໄຟລ໌ໂດຍກົງ. ກະລຸນາລະມັດລະວັງໃນການໃຊ້ໂໝດສ້າງ (Build mode) ເພາະການປ່ຽນແປງໃນລະບົບໄຟລ໌ຈະເກີດຂຶ້ນທັນທີ ແລະ ບາງຄັ້ງບໍ່ສາມາດຍ້ອນກັບໄດ້. Community Edition ບໍ່ແມ່ນທາງເລືອກທີ່ດີທີ່ສຸດສຳລັບການໃຫ້ບໍລິການຜູ້ໃຊ້ພາຍນອກຈຳນວນຫຼາຍ.", "agentDetail.configure.build.empty.description": "ອະທິບາຍສິ່ງທີ່ທ່ານຕ້ອງການ ແລ້ວລະບົບຈະຕື່ມຂໍ້ມູນໃສ່ແບບຟອມດ້ານຊ້າຍໃຫ້ເອງ.", @@ -269,7 +270,6 @@ "agentDetail.configure.workingDirectory.breadcrumbLabel": "ເສັ້ນທາງລະບົບໄຟລ໌", "agentDetail.configure.workingDirectory.description": "ໄຟລ໌ທີ່ຕົວແທນອ່ານ ແລະ ຂຽນໃນຂະນະທີ່ເຮັດວຽກ. ສິ່ງໃດກໍຕາມທີ່ມັນສ້າງຂຶ້ນ ເຊັ່ນ: qna_report.pdf ຈະສະແດງຢູ່ນີ້.", "agentDetail.configure.workingDirectory.fileSystem": "ລະບົບໄຟລ໌", - "agentDetail.configure.workingDirectory.open": "ເປີດໂຟນເດີເຮັດວຽກ", "agentDetail.configure.workingDirectory.persistentFiles": "ຖາວອນ", "agentDetail.configure.workingDirectory.persistentFilesTooltip": "ໄຟລ໌ທີ່ເອເຈນເກັບຮັກສາ ແລະ ສາມາດໃຊ້ໄດ້ທຸກບ່ອນທີ່ເອເຈນເຮັດວຽກ ລວມທັງການສົນທະນາຂອງຜູ້ໃຊ້ປາຍທາງ ແລະ ເວີກໂຟລ. ການປ່ຽນແປງທີ່ເຮັດຢູ່ນີ້ໃນແຊັດສ້າງນີ້ຈະມີຜົນເມື່ອທ່ານກົດ “ນຳໃຊ້”.", "agentDetail.configure.workingDirectory.temporaryFiles": "ຊົ່ວຄາວ", @@ -348,7 +348,6 @@ "agentDetail.sections.logs": "ບັນທຶກ (Logs)", "agentDetail.sections.monitoring": "ການຕິດຕາມ", "agentDetail.title": "ຕົວແທນ (Agent)", - "agentDetail.type": "AGENT", "agentDetail.versionHistory.empty": "ຍັງບໍ່ມີເວີຊັນເທື່ອ", "agentDetail.versionHistory.exitVersions": "ອອກຈາກໜ້າເວີຊັນ", "agentDetail.versionHistory.filter": "ກັ່ນຕອງເວີຊັນ", @@ -408,8 +407,5 @@ "roster.sort.lastModified": "ແກ້ໄຂຫຼ້າສຸດ", "roster.sort.recentlyCreated": "ສ້າງຫຼ້າສຸດ", "roster.title": "ຕົວແທນ", - "roster.usageStatus.draft": "ສະບັບຮ່າງ", - "skillManagement.detail.uploadFilesFailedStatus_one": "ອັບໂຫຼດໄຟລ໌ບໍ່ສຳເລັດ {{count}} ໄຟລ໌.", - "skillManagement.detail.uploadFilesFailedStatus_other": "ອັບໂຫຼດໄຟລ໌ບໍ່ສຳເລັດ {{count}} ໄຟລ໌.", - "skillManagement.title": "Skills" + "roster.usageStatus.draft": "ສະບັບຮ່າງ" } diff --git a/web/i18n/lo-LA/app-annotation.json b/web/i18n/locales/lo-LA/app-annotation.json similarity index 100% rename from web/i18n/lo-LA/app-annotation.json rename to web/i18n/locales/lo-LA/app-annotation.json diff --git a/web/i18n/lo-LA/app-api.json b/web/i18n/locales/lo-LA/app-api.json similarity index 100% rename from web/i18n/lo-LA/app-api.json rename to web/i18n/locales/lo-LA/app-api.json diff --git a/web/i18n/lo-LA/app-debug.json b/web/i18n/locales/lo-LA/app-debug.json similarity index 100% rename from web/i18n/lo-LA/app-debug.json rename to web/i18n/locales/lo-LA/app-debug.json diff --git a/web/i18n/lo-LA/app-log.json b/web/i18n/locales/lo-LA/app-log.json similarity index 97% rename from web/i18n/lo-LA/app-log.json rename to web/i18n/locales/lo-LA/app-log.json index 9d00d455381..da913e5360a 100644 --- a/web/i18n/lo-LA/app-log.json +++ b/web/i18n/locales/lo-LA/app-log.json @@ -4,11 +4,9 @@ "agentLogDetail.iteration": "ຮອບວຽນ", "agentLogDetail.iterations": "ຮອບວຽນ", "agentLogDetail.toolUsed": "ເຄື່ອງມືທີ່ໃຊ້", - "archives.action.downloadMonth": "ດາວໂຫລດບົດຈັດເກັບເດືອນ {{month}}", "archives.action.downloadReady": "ການດາວໂຫລດບົດຈັດເກັບພ້ອມແລ້ວ.", "archives.action.prepareDownload": "ກະກຽມການດາວໂຫລດ", "archives.action.prepareFailed": "ກະກຽມການດາວໂຫລດບົດຈັດເກັບບໍ່ສຳເລັດ.", - "archives.action.prepareMonth": "ກະກຽມດາວໂຫລດບົດຈັດເກັບເດືອນ {{month}}", "archives.action.prepareStarted": "ກຳລັງກະກຽມການດາວໂຫລດບົດຈັດເກັບ.", "archives.action.preparing": "ກຳລັງກະກຽມ", "archives.description": "ດາວໂຫລດການສົ່ງອອກ CSV ປະຈຳເດືອນຂອງບັນທຶກການເຮັດວຽກ Workflow ທີ່ຖືກຈັດເກັບໄວ້ໃນພື້ນທີ່ເຮັດວຽກນີ້.", diff --git a/web/i18n/lo-LA/app-overview.json b/web/i18n/locales/lo-LA/app-overview.json similarity index 100% rename from web/i18n/lo-LA/app-overview.json rename to web/i18n/locales/lo-LA/app-overview.json diff --git a/web/i18n/lo-LA/app.json b/web/i18n/locales/lo-LA/app.json similarity index 100% rename from web/i18n/lo-LA/app.json rename to web/i18n/locales/lo-LA/app.json diff --git a/web/i18n/lo-LA/billing.json b/web/i18n/locales/lo-LA/billing.json similarity index 100% rename from web/i18n/lo-LA/billing.json rename to web/i18n/locales/lo-LA/billing.json diff --git a/web/i18n/lo-LA/common.json b/web/i18n/locales/lo-LA/common.json similarity index 98% rename from web/i18n/lo-LA/common.json rename to web/i18n/locales/lo-LA/common.json index ae8482d0221..a9c1e13e536 100644 --- a/web/i18n/lo-LA/common.json +++ b/web/i18n/locales/lo-LA/common.json @@ -209,9 +209,7 @@ "mainNav.marketplace": "ຕະຫຼາດແອັບ", "mainNav.skills": "ທັກສະ", "mainNav.webApps.noResults": "ບໍ່ພົບເວັບແອັບ", - "mainNav.webApps.openApp": "ເປີດເວັບແອັບ {{name}}", "mainNav.webApps.searchPlaceholder": "ຄົ້ນຫາເວັບແອັບ", - "mainNav.workspace.credits": "{{count}} ເຄຣດິດ", "mainNav.workspace.creditsUnit": "ເຄຣດິດ", "mainNav.workspace.inviteMembers": "ເຊີນສະມາຊິກ", "mainNav.workspace.noResults": "ບໍ່ພົບພື້ນທີ່ເຮັດວຽກ", @@ -233,7 +231,6 @@ "members.datasetOperatorTip": "ສາມາດຈັດການໄດ້ສະເພາະຖານຄວາມຮູ້ເທົ່ານັ້ນ", "members.editRole": "ແກ້ໄຂບົດບາດ", "members.editorTip": "ສາມາດສ້າງ ແລະ ແກ້ໄຂແອັບໄດ້", - "members.email": "ອີເມວ", "members.emailInvalid": "ຮູບແບບອີເມວບໍ່ຖືກຕ້ອງ", "members.emailNotSetup": "ຍັງບໍ່ໄດ້ຕັ້ງຄ່າເຊີບເວີອີເມວ, ຈຶ່ງບໍ່ສາມາດສົ່ງອີເມວເຊີນໄດ້. ກະລຸນາແຈ້ງຜູ້ໃຊ້ຜ່ານລິ້ງການເຊີນທີ່ຈະອອກໃຫ້ຫຼັງຈາກການເຊີນແທນ.", "members.emailPlaceholder": "ພິມ ຫຼື ວາງທີ່ຢູື່ອີເມວ...", @@ -250,7 +247,6 @@ "members.inviteLimitExceeded": "ມີບ່ອນນັ່ງສະມາຊິກບໍ່ພຽງພໍສຳລັບຄຳເຊີນເຫຼົ່ານີ້.", "members.inviteTeamMember": "ເພີ່ມສະມາຊິກທີມ", "members.inviteTeamMemberTip": "ເຂົາເຈົ້າສາມາດເຂົ້າເຖິງຂໍ້ມູນທີມຂອງທ່ານໄດ້ທັນທີຫຼັງຈາກເຂົ້າສູ່ລະບົບ.", - "members.invitedAsRole": "ຖືກເຊີນໃນຖານະ {{role}}", "members.lastActive": "ເຄື່ອນໄຫວຫຼ້າສຸດ", "members.memberActions": "ການດຳເນີນການຂອງສະມາຊິກ", "members.memberDetails.assign": "ມອບໝາຍ", @@ -258,8 +254,6 @@ "members.memberDetails.assignedRoles": "ບົດບາດທີ່ໄດ້ຮັບມອບໝາຍ", "members.memberDetails.customGroup": "ປັບແຕ່ງເອງ", "members.memberDetails.generalGroup": "ທົ່ວໄປ", - "members.memberDetails.openAria": "ເປີດລາຍລະອຽດສະມາຊິກສຳລັບ {{name}}", - "members.memberDetails.roleActionsAria": "ເປີດການດຳເນີນການສຳລັບບົດບາດ {{role}}", "members.memberDetails.roleNoPermissionSummary": "ບົດບາດປັດຈຸບັນບໍ່ມີສິດທິໃດໆ.", "members.memberDetails.rolePermissionSummary": "{{role}} ສາມາດ {{permissions}}", "members.memberDetails.title": "ລາຍລະອຽດສະມາຊິກ", @@ -370,7 +364,6 @@ "modelProvider.card.usagePriorityTip": "ຕັ້ງຄ່າວ່າຈະໃຊ້ຊັບພະຍາກອນໃດກ່ອນໃນການລັນໂມເດວ.", "modelProvider.configureTip": "ຕັ້ງຄ່າ api-key ຫຼື ເພີ່ມໂມເດວເພື່ອໃຊ້ງານ", "modelProvider.confirmDelete": "ຢືນຢັນການລຶບບໍ?", - "modelProvider.credits": "ເຄຣດິດຂໍ້ຄວາມ", "modelProvider.defaultConfig": "ການຕັ້ງຄ່າເລີ່ມຕົ້ນ", "modelProvider.discoverMore": "ຄົ້ນຫາເພີ່ມເຕີມໃນ", "modelProvider.embeddingModel.key": "ໂມເດວການຝັງຕົວ (Embedding)", @@ -427,7 +420,6 @@ "modelProvider.selector.noProviderConfiguredDesc": "ຄົ້ນຫາຕະຫຼາດແອັບເພື່ອຕິດຕັ້ງ, ຫຼື ຕັ້ງຄ່າຜູ້ໃຫ້ບໍລິການໃນການຕັ້ງຄ່າ.", "modelProvider.selector.onlyCompatibleModelsShown": "ສະແດງສະເພາະໂມເດວທີ່ເຂົ້າກັນໄດ້ເທົ່ານັ້ນ", "modelProvider.selector.showIncompatibleModels": "ສະແດງໂມເດວທີ່ເຂົ້າກັນບໍ່ໄດ້", - "modelProvider.selector.suggestion": "ຄຳແນະນຳ", "modelProvider.selector.suggestionTip": "ແນະນຳສຳລັບວຽກງານຂອງ Agent", "modelProvider.showModels": "ສະແດງໂມເດວ", "modelProvider.speechToTextModel.key": "ໂມເດວປ່ຽນສຽງເປັນຂໍ້ຄວາມ", @@ -458,6 +450,8 @@ "operation.confirmAction": "ກະລຸນາຢືນຢັນການດຳເນີນການຂອງທ່ານ.", "operation.copied": "ຄັດລອກແລ້ວ", "operation.copy": "ຄັດລອກ", + "operation.copyErrorDetails": "ສຳເນົາລາຍລະອຽດຂໍ້ຜິດພາດ", + "operation.copyErrorFailed": "ສຳເນົາບໍ່ສຳເລັດ. ລອງໃໝ່", "operation.copyImage": "ຄັດລອກຮູບພາບ", "operation.create": "ສ້າງ", "operation.deSelectAll": "ຍົກເລີກການເລືອກທັງໝົດ", @@ -475,7 +469,6 @@ "operation.format": "ຈັດຮູບແບບ", "operation.imageCopied": "ຄັດລອກຮູບແລ້ວ", "operation.imageCopyFailed": "ບໍ່ສາມາດສຳເນົາຮູບພາບໄດ້. ກະລຸນາດາວໂຫຼດຮູບພາບແທນ.", - "operation.imageDownloaded": "ດາວໂຫຼດຮູບແລ້ວ", "operation.in": "ໃນ", "operation.learnMore": "ຮຽນຮູ້ເພີ່ມເຕີມ", "operation.log": "ບັນທຶກ", @@ -512,7 +505,6 @@ "operation.sure": "ຂ້ອຍແນ່ໃຈ", "operation.toggleFullscreen": "ເປີດ/ປິດ ເຕັມຈໍ", "operation.toggleMute": "ເປີດ/ປິດ ສຽງ", - "operation.upload": "ອັບໂຫຼດ", "operation.view": "ເບິ່ງ", "operation.viewDetails": "ເບິ່ງລາຍລະອຽດ", "operation.viewMore": "ເບິ່ງເພີ່ມເຕີມ", @@ -542,7 +534,6 @@ "promptEditor.requestURL.item.title": "Request URL", "promptEditor.variable.modal.add": "ຕົວປ່ຽນໃໝ່", "promptEditor.variable.modal.addTool": "ເຄື່ອງມືໃໝ່", - "provider.anthropicHosted.trialQuotaTip": "ໂຄຕ້າທົດລອງ Anthropic ຂອງທ່ານຈະໝົດອາຍຸໃນວັນທີ 17/03/2025 ແລະ ຈະບໍ່ສາມາດໃຊ້ໄດ້ຫຼັງຈາກນັ້ນ. ກະລຸນາໃຊ້ໃຫ້ທັນເວລາ.", "provider.encrypted.back": " ເຕັກໂນໂລຊີ.", "provider.encrypted.front": "API KEY ຂອງທ່ານຈະຖືກເຂົ້າລະຫັດ ແລະ ເກັບຮັກສາໂດຍໃຊ້", "provider.validating": "ກຳລັງກວດສອບ Key...", @@ -565,7 +556,6 @@ "settings.preferences": "ການຕັ້ງຄ່າທີ່ມັກ", "settings.provider": "ຜູ້ໃຫ້ບໍລິການໂມເດວ", "settings.resourceAccess": "ການເຂົ້າເຖິງຊັບພະຍາກອນ", - "settings.resourceAccessDescription": "ຕັ້ງຄ່າກົດການອະນຸຍາດທີ່ແອັບ ແລະ ຖານຄວາມຮູ້ສາມາດໃຊ້ໄດ້. ກົດການອະນຸຍາດສາມາດມອບໝາຍໃຫ້ສະມາຊິກໃນການຕັ້ງຄ່າການເຂົ້າເຖິງຊັບພະຍາກອນສະເພາະ.", "settings.rolesAndPermissions": "ບົດບາດ ແລະ ສິດທິ", "settings.settings": "ການຕັ້ງຄ່າ", "settings.swaggerAPIAsTool": "Swagger API ເປັນເຄື່ອງມື", @@ -695,7 +685,6 @@ "userProfile.helpCenter": "ເບິ່ງເອກະສານ", "userProfile.logout": "ອອກຈາກລະບົບ", "userProfile.roadmap": "ແຜນງານ (Roadmap)", - "userProfile.settings": "ການຕັ້ງຄ່າ", "userProfile.workspace": "ພື້ນທີ່ເຮັດວຽກ", "voice.language.arTN": "ອາຣັບ (ຕູນີເຊຍ)", "voice.language.deDE": "ເຢຍລະມັນ", diff --git a/web/i18n/lo-LA/custom.json b/web/i18n/locales/lo-LA/custom.json similarity index 100% rename from web/i18n/lo-LA/custom.json rename to web/i18n/locales/lo-LA/custom.json diff --git a/web/i18n/lo-LA/dataset-creation.json b/web/i18n/locales/lo-LA/dataset-creation.json similarity index 100% rename from web/i18n/lo-LA/dataset-creation.json rename to web/i18n/locales/lo-LA/dataset-creation.json diff --git a/web/i18n/lo-LA/dataset-documents.json b/web/i18n/locales/lo-LA/dataset-documents.json similarity index 100% rename from web/i18n/lo-LA/dataset-documents.json rename to web/i18n/locales/lo-LA/dataset-documents.json diff --git a/web/i18n/lo-LA/dataset-hit-testing.json b/web/i18n/locales/lo-LA/dataset-hit-testing.json similarity index 100% rename from web/i18n/lo-LA/dataset-hit-testing.json rename to web/i18n/locales/lo-LA/dataset-hit-testing.json diff --git a/web/i18n/lo-LA/dataset-pipeline.json b/web/i18n/locales/lo-LA/dataset-pipeline.json similarity index 100% rename from web/i18n/lo-LA/dataset-pipeline.json rename to web/i18n/locales/lo-LA/dataset-pipeline.json diff --git a/web/i18n/lo-LA/dataset-settings.json b/web/i18n/locales/lo-LA/dataset-settings.json similarity index 100% rename from web/i18n/lo-LA/dataset-settings.json rename to web/i18n/locales/lo-LA/dataset-settings.json diff --git a/web/i18n/lo-LA/dataset.json b/web/i18n/locales/lo-LA/dataset.json similarity index 97% rename from web/i18n/lo-LA/dataset.json rename to web/i18n/locales/lo-LA/dataset.json index facd9afab3e..24983dee68c 100644 --- a/web/i18n/lo-LA/dataset.json +++ b/web/i18n/locales/lo-LA/dataset.json @@ -163,7 +163,6 @@ "newKnowledge.configureProvider": "ຕັ້ງຄ່າ {{provider}}", "newKnowledge.connectNotion": "ເຊື່ອມຕໍ່ແນວຄິດ", "newKnowledge.connectProvider": "ເຊື່ອມຕໍ່ {{provider}}", - "newKnowledge.connectProviderGeneric": "ເຊື່ອມຕໍ່ຜູ້ໃຫ້ບໍລິການ", "newKnowledge.connectSource": "ເຊື່ອມຕໍ່ແຫຼ່ງຂໍ້ມູນ", "newKnowledge.connectSourceDescription": "ເນື້ອຫາຈະຖືກຊິງຄ໌ກັບ Notion, ເວັບໄຊ, ແລະ ບໍລິການອື່ນໆ.", "newKnowledge.connectingProvider": "ກຳລັງເຊື່ອມຕໍ່...", @@ -179,18 +178,14 @@ "newKnowledge.crawlFailedDescription": "ຕົວກວາດເວັບບໍ່ສາມາດເຮັດຕົວຢ່າງນີ້ໄດ້. ກວດເບິ່ງ URL, ປັບຕົວເລືອກການລວບລວມຂໍ້ມູນ, ຫຼືລອງອີກຄັ້ງ.", "newKnowledge.crawlFailedProvider": "{{provider}} ລາຍງານຂໍ້ຜິດພາດ. ກວດເບິ່ງການເຊື່ອມຕໍ່ຜູ້ໃຫ້ບໍລິການແລ້ວລອງໃໝ່ອີກຄັ້ງ.", "newKnowledge.crawlFailedTimeout": "ການລວບລວມຂໍ້ມູນໝົດເວລາ. ລອງໃຊ້ URL ທີ່ແຄບກວ່າ ຫຼືຫຼຸດຈຳນວນໜ້າສູງສຸດ.", - "newKnowledge.crawlNotStarted": "ບໍ່ໄດ້ເລີ່ມຕົ້ນ", "newKnowledge.crawlOptions": "ຕົວເລືອກການລວບລວມຂໍ້ມູນ", "newKnowledge.crawlPreview": "ຕົວຢ່າງການລວບລວມຂໍ້ມູນ", "newKnowledge.crawlProgress": "ຄວາມຄືບໜ້າການລວບລວມຂໍ້ມູນສຳລັບ {{host}}", - "newKnowledge.crawlSetupUnavailableDescription": "ການຕັ້ງຄ່າການລວບລວມຂໍ້ມູນເວັບໄຊທ໌ຖືກປິດໃຊ້ງານຈົນກ່ວາຂະບວນການລວບລວມຂໍ້ມູນສາມາດໃຊ້ໄດ້.", - "newKnowledge.crawlSetupUnavailableTitle": "Firecrawl ແມ່ນກຽມພ້ອມ", "newKnowledge.crawlStartFailed": "ການສະແດງຕົວຢ່າງການລວບລວມຂໍ້ມູນບໍ່ສາມາດເລີ່ມຕົ້ນໄດ້. ລອງອີກຄັ້ງ.", "newKnowledge.crawlStopped": "ການລວບລວມຂໍ້ມູນຢຸດ", "newKnowledge.crawling": "ກຳລັງລວບລວມຂໍ້ມູນ...", "newKnowledge.crawlingPages": "ກຳລັງລວບລວມຂໍ້ມູນໜ້າ {{host}}… {{count}} ໜ້າ", "newKnowledge.createFailed": "ພວກເຮົາບໍ່ສາມາດສ້າງຖານຄວາມຮູ້ນີ້ໄດ້. ກະລຸນາກວດສອບສິດການເຂົ້າເຖິງແລ້ວລອງໃໝ່ອີກຄັ້ງ.", - "newKnowledge.createIllustrationTitle": "ເຊື່ອມ​ຕໍ່​ເນື້ອ​ໃນ​ຂອງ​ທ່ານ​ຫນຶ່ງ​ຄັ້ງ — ທຸກໆ app ຕອບ​ສະ​ຫນອງ​ຄວາມ​ຮູ້​ໃຫມ່​ທີ່​ສຸດ​.", "newKnowledge.createTitle": "ສ້າງຄວາມຮູ້", "newKnowledge.createdAt": "ສ້າງແລ້ວ", "newKnowledge.creators": "ຜູ້ສ້າງ", @@ -220,7 +215,6 @@ "newKnowledge.documentColumn": "ເອກະສານ", "newKnowledge.documentContentIncomplete": "ກຳລັງໂຫຼດເນື້ອໃນເອກະສານທີ່ຍັງເຫຼືອ...", "newKnowledge.documentContents": "ເນື້ອໃນ", - "newKnowledge.documentFacts": "ລາຍລະອຽດເອກະສານ", "newKnowledge.documentFilterLabel": "ກັ່ນຕອງເອກະສານຕາມສະຖານະ", "newKnowledge.documentLoadErrorDescription": "ພວກເຮົາບໍ່ສາມາດໂຫຼດເອກະສານນີ້ໄດ້. ລອງອີກຄັ້ງ.", "newKnowledge.documentLoadErrorTitle": "ບໍ່ສາມາດໂຫຼດເອກະສານໄດ້", @@ -268,7 +262,6 @@ "newKnowledge.documentsSelected": "ເລືອກເອກະສານ {{count}} ແລ້ວ", "newKnowledge.documentsUnavailable": "— ເອກະສານ", "newKnowledge.downloadDocuments": "ດາວໂຫຼດ", - "newKnowledge.editSource": "ແກ້ໄຂ", "newKnowledge.emptyChunk": "ຫວ່າງເປົ່າ", "newKnowledge.emptyDescription": "ເພີ່ມເອກະສານຂອງທ່ານ ແລ້ວໃຫ້ Agent ອ່ານພວກມັນໂດຍກົງ — ບໍ່ຈຳເປັນຕ້ອງຕັ້ງຄ່າການສ້າງດັດຊະນີ.", "newKnowledge.emptyTitle": "ວິທີການທີ່ສະຫຼາດກວ່າໃນການສ້າງຖານຄວາມຮູ້", @@ -344,7 +337,6 @@ "newKnowledge.processingTaskState.succeeded": "ສຳເລັດແລ້ວ", "newKnowledge.processingTaskState.superseded": "ປ່ຽນແທນ", "newKnowledge.providerConnected": "ເຊື່ອມຕໍ່ {{provider}} ແລ້ວ", - "newKnowledge.providerLabel": "ຜູ້ໃຫ້ບໍລິການ", "newKnowledge.providerLoadFailed": "ຜູ້ໃຫ້ບໍລິການບໍ່ສາມາດໂຫຼດໄດ້", "newKnowledge.providerNotConfigured": "{{provider}} ບໍ່ໄດ້ຖືກຕັ້ງຄ່າ", "newKnowledge.providerNotConfiguredDescription": "ເພີ່ມລະຫັດ API ຫຼື URL ຈຸດສິ້ນສຸດເພື່ອລວບລວມຂໍ້ມູນເວັບໄຊທ໌ດ້ວຍ {{provider}}.", @@ -369,12 +361,10 @@ "newKnowledge.revisionState.superseded": "ທີ່ຜ່ານມາ", "newKnowledge.rootUrl": "ຮາກ URL", "newKnowledge.rootUrlPlaceholder": "https://docs.example.com", - "newKnowledge.routeUnavailable": "ສ່ວນນີ້ຈະພ້ອມໃຊ້ງານໃນຂັ້ນຕອນການຕັ້ງຄ່າຖັດໄປ.", "newKnowledge.searchDocuments": "ຊອກຫາເອກະສານ", "newKnowledge.searchSources": "ຄົ້ນຫາແຫຼ່ງຂໍ້ມູນ", "newKnowledge.selectAll": "ເລືອກທັງໝົດ", "newKnowledge.selectAllDocuments": "ເລືອກເອກະສານທັງໝົດ", - "newKnowledge.selectAllSources": "ເລືອກແຫຼ່ງທັງໝົດ", "newKnowledge.selectChunk": "ເລືອກອັນໃດນຶ່ງເພື່ອກວດກາເບິ່ງເນື້ອຫາຂອງມັນ.", "newKnowledge.selectFilesAndFolders": "ເລືອກໄຟລ໌ ແລະໂຟນເດີທີ່ຈະຊິ້ງຂໍ້ມູນ", "newKnowledge.skippedFailed": "ຂ້າມ (ລົ້ມເຫລວ)", @@ -384,7 +374,6 @@ "newKnowledge.sourceFilterLabel": "ກັ່ນຕອງແຫຼ່ງຕາມສະຖານະ", "newKnowledge.sourceName": "ຊື່ແຫຼ່ງ", "newKnowledge.sourceNamePlaceholder": "ຕົວຢ່າງ: ການລວບລວມເອກະສານຜະລິດຕະພັນ", - "newKnowledge.sourceNameRequired": "ໃສ່ຊື່ແຫຼ່ງ.", "newKnowledge.sourceSetupBackendDependency": "ການຕັ້ງຄ່າແຫຼ່ງແມ່ນສະແດງໃຫ້ເຫັນຢ່າງເຕັມທີ່; ການແກ້ໄຂ ແລະສະແດງຕົວຢ່າງການດໍາເນີນການຕ້ອງໃຊ້ຂັ້ນຕອນວຽກ backend ສະບັບຮ່າງ.", "newKnowledge.sourceStatus.active": "ເຄື່ອນໄຫວ", "newKnowledge.sourceStatus.disabled": "ຄົນພິການ", @@ -431,7 +420,6 @@ "newKnowledge.unavailableDescription": "KnowledgeFS ຍັງບໍ່ພ້ອມໃຊ້ງານສຳລັບພື້ນທີ່ເຮັດວຽກນີ້ໃນຂະນະນີ້.", "newKnowledge.unavailableTitle": "ຄວາມຮູ້ແບບໃໝ່ບໍ່ພ້ອມໃຊ້ງານ", "newKnowledge.updated": "ອັບເດດເມື່ອ {{date}}", - "newKnowledge.updatedAt": "ອັບເດດຫຼ້າສຸດ", "newKnowledge.updatedColumn": "ອັບເດດ", "newKnowledge.uploadCharactersUnavailable": "ລັກ​ສະ​ນະ​: —", "newKnowledge.uploadDocuments": "ອັບໂຫລດເອກະສານ", @@ -460,11 +448,9 @@ "retrievalSettings": "ການຕັ້ງຄ່າການດຶງຂໍ້ມູນ", "retry": "ລອງໃໝ່", "selectExternalKnowledgeAPI.placeholder": "ເລືອກ API ຄວາມຮູ້ພາຍນອກ", - "serviceApi.card.addApiKey": "ເພີ່ມ API Key", "serviceApi.card.apiKey": "API Key", "serviceApi.card.apiReference": "ເອກະສານອ້າງອີງ API", "serviceApi.card.endpoint": "Service API Endpoint", - "serviceApi.card.manageApiKey": "ຈັດການ API Key", "serviceApi.card.title": "Backend Service API", "serviceApi.disabled": "ປິດໃຊ້ງານ", "serviceApi.enabled": "ເປີດໃຊ້ງານ", diff --git a/web/i18n/lo-LA/deployments.json b/web/i18n/locales/lo-LA/deployments.json similarity index 97% rename from web/i18n/lo-LA/deployments.json rename to web/i18n/locales/lo-LA/deployments.json index e6fca869da3..13797d61533 100644 --- a/web/i18n/lo-LA/deployments.json +++ b/web/i18n/locales/lo-LA/deployments.json @@ -27,7 +27,6 @@ "list.emptyTitle": "ຍັງບໍ່ມີການຕິດຕັ້ງ", "overview.chip.latest": "ຫຼ້າສຸດ", "status.RUNTIME_INSTANCE_STATUS_DEPLOYING": "ກຳລັງຕິດຕັ້ງ", - "status.RUNTIME_INSTANCE_STATUS_FAILED": "ຕິດຕັ້ງບໍ່ສຳເລັດ", "status.RUNTIME_INSTANCE_STATUS_INVALID": "ບໍ່ຖືກຕ້ອງ", "status.RUNTIME_INSTANCE_STATUS_READY": "ກຳລັງເຮັດວຽກ", "status.RUNTIME_INSTANCE_STATUS_UNDEPLOYED": "ຍັງບໍ່ໄດ້ຕິດຕັ້ງ", @@ -87,6 +86,5 @@ "studio.versionValue": "ຄ່າຂອງເວີຊັນ", "studio.versionsBehind_one": "ຊ້າກວ່າ {{count}} ເວີຊັນ", "studio.versionsBehind_other": "ຊ້າກວ່າ {{count}} ເວີຊັນ", - "versions.checkingReleaseContent": "ກຳລັງກວດສອບເນື້ອໃນຂອງລຸ້ນ...", "versions.deployTo": "ຕິດຕັ້ງໃສ່ {{name}}" } diff --git a/web/i18n/lo-LA/device-flow.json b/web/i18n/locales/lo-LA/device-flow.json similarity index 100% rename from web/i18n/lo-LA/device-flow.json rename to web/i18n/locales/lo-LA/device-flow.json diff --git a/web/i18n/lo-LA/education.json b/web/i18n/locales/lo-LA/education.json similarity index 100% rename from web/i18n/lo-LA/education.json rename to web/i18n/locales/lo-LA/education.json diff --git a/web/i18n/lo-LA/explore.json b/web/i18n/locales/lo-LA/explore.json similarity index 100% rename from web/i18n/lo-LA/explore.json rename to web/i18n/locales/lo-LA/explore.json diff --git a/web/i18n/lo-LA/layout.json b/web/i18n/locales/lo-LA/layout.json similarity index 100% rename from web/i18n/lo-LA/layout.json rename to web/i18n/locales/lo-LA/layout.json diff --git a/web/i18n/lo-LA/login.json b/web/i18n/locales/lo-LA/login.json similarity index 100% rename from web/i18n/lo-LA/login.json rename to web/i18n/locales/lo-LA/login.json diff --git a/web/i18n/lo-LA/oauth.json b/web/i18n/locales/lo-LA/oauth.json similarity index 100% rename from web/i18n/lo-LA/oauth.json rename to web/i18n/locales/lo-LA/oauth.json diff --git a/web/i18n/lo-LA/permission-keys.json b/web/i18n/locales/lo-LA/permission-keys.json similarity index 100% rename from web/i18n/lo-LA/permission-keys.json rename to web/i18n/locales/lo-LA/permission-keys.json diff --git a/web/i18n/lo-LA/permission.json b/web/i18n/locales/lo-LA/permission.json similarity index 100% rename from web/i18n/lo-LA/permission.json rename to web/i18n/locales/lo-LA/permission.json diff --git a/web/i18n/lo-LA/pipeline.json b/web/i18n/locales/lo-LA/pipeline.json similarity index 100% rename from web/i18n/lo-LA/pipeline.json rename to web/i18n/locales/lo-LA/pipeline.json diff --git a/web/i18n/lo-LA/plugin-tags.json b/web/i18n/locales/lo-LA/plugin-tags.json similarity index 100% rename from web/i18n/lo-LA/plugin-tags.json rename to web/i18n/locales/lo-LA/plugin-tags.json diff --git a/web/i18n/lo-LA/plugin-trigger.json b/web/i18n/locales/lo-LA/plugin-trigger.json similarity index 100% rename from web/i18n/lo-LA/plugin-trigger.json rename to web/i18n/locales/lo-LA/plugin-trigger.json diff --git a/web/i18n/lo-LA/plugin.json b/web/i18n/locales/lo-LA/plugin.json similarity index 99% rename from web/i18n/lo-LA/plugin.json rename to web/i18n/locales/lo-LA/plugin.json index 83d62c474fb..ce97bda04f1 100644 --- a/web/i18n/lo-LA/plugin.json +++ b/web/i18n/locales/lo-LA/plugin.json @@ -277,7 +277,6 @@ "readmeInfo.noReadmeAvailable": "ບໍ່ມີ README", "readmeInfo.title": "README", "requestAPlugin": "ຮ້ອງຂໍການເຊື່ອມຕໍ່", - "requestSubmit": "ຮ້ອງຂໍ ຫຼື ເຜີຍແຜ່", "search": "ຄົ້ນຫາ", "searchCategories": "ຄົ້ນຫາໝວດໝູ່", "searchInMarketplace": "ຄົ້ນຫາໃນ Marketplace", diff --git a/web/i18n/lo-LA/register.json b/web/i18n/locales/lo-LA/register.json similarity index 100% rename from web/i18n/lo-LA/register.json rename to web/i18n/locales/lo-LA/register.json diff --git a/web/i18n/lo-LA/run-log.json b/web/i18n/locales/lo-LA/run-log.json similarity index 100% rename from web/i18n/lo-LA/run-log.json rename to web/i18n/locales/lo-LA/run-log.json diff --git a/web/i18n/lo-LA/share.json b/web/i18n/locales/lo-LA/share.json similarity index 100% rename from web/i18n/lo-LA/share.json rename to web/i18n/locales/lo-LA/share.json diff --git a/web/i18n/lo-LA/skill.json b/web/i18n/locales/lo-LA/skill.json similarity index 82% rename from web/i18n/lo-LA/skill.json rename to web/i18n/locales/lo-LA/skill.json index c55f8482e1f..da4e872b5a1 100644 --- a/web/i18n/lo-LA/skill.json +++ b/web/i18n/locales/lo-LA/skill.json @@ -1,5 +1,4 @@ { - "skillManagement.clearTags": "ລຶບແທັກ", "skillManagement.create": "ສ້າງ", "skillManagement.createFailed": "ລົ້ມເຫລວໃນການສ້າງທັກສະ.", "skillManagement.createSuccess": "ສ້າງຮ່າງ.", @@ -13,12 +12,9 @@ "skillManagement.deleteFailed": "ລຶບທັກສະບໍ່ສຳເລັດ.", "skillManagement.deleteSuccess": "Skill ຖືກລຶບແລ້ວ.", "skillManagement.detail.addMetadata": "ເພີ່ມ metadata", - "skillManagement.detail.addMetadataDescription": "ເພີ່ມຊ່ອງຂໍ້ມູນດ້ານໜ້າໃສ່ໄຟລ໌ Markdown ນີ້.", "skillManagement.detail.addTag": "ເພີ່ມແທັກ", - "skillManagement.detail.addTagDescription": "ສ້າງ ຫຼືຜູກມັດແທັກກັບ Skill ນີ້. ແທັກໃໝ່ຖືກບັນທຶກດ້ວຍເມຕາເດຕາ Skill.", "skillManagement.detail.addTagSuccess": "ເພີ່ມແທັກແລ້ວ.", "skillManagement.detail.back": "ກັບໄປທີ່ Skills", - "skillManagement.detail.builder.applyFailed": "ນຳໃຊ້ການອັບເດດສະບັບຮ່າງບໍ່ສຳເລັດ.", "skillManagement.detail.builder.attach": "ແນບໄຟລ໌", "skillManagement.detail.builder.attachFailed": "ລົ້ມເຫລວໃນການແນບໄຟລ໌.", "skillManagement.detail.builder.attachLimit": "ທ່ານສາມາດແນບໄດ້ເຖິງ 10 ໄຟລ໌.", @@ -26,22 +22,12 @@ "skillManagement.detail.builder.attachUnsupported": "ມີພຽງແຕ່ຮູບພາບ, ຂໍ້ຄວາມ, ແລະໄຟລ໌ເອກະສານທີ່ສາມາດຕິດໄດ້.", "skillManagement.detail.builder.attachmentOnlyMessage": "ໃຊ້ໄຟລ໌ທີ່ຕິດຄັດມາເພື່ອຊ່ວຍປັບປຸງທັກສະນີ້.", "skillManagement.detail.builder.close": "ປິດ Skill Builder", - "skillManagement.detail.builder.compatibleModelsOnly": "ສະແດງສະເພາະໂມເດວທີ່ເຂົ້າກັນໄດ້ເທົ່ານັ້ນ", "skillManagement.detail.builder.copyResponse": "ສຳເນົາຄຳຕອບ", "skillManagement.detail.builder.copySuccess": "ສຳເນົາຄຳຕອບໄປໃສ່ຄລິບບອດແລ້ວ.", "skillManagement.detail.builder.editIntro": "ຂ້ອຍໄດ້ທົບທວນທັກສະນີ້. ເຈົ້າຢາກປັບຫຍັງ?", "skillManagement.detail.builder.exampleIssueTriage": "ບັນຫາລູກຄ້າ triage", "skillManagement.detail.builder.exampleOnboarding": "ຄູ່ມືການຈ້າງຄົນໃຫມ່", "skillManagement.detail.builder.exampleSalesFollowUp": "ຍຸດທະສາດການຕິດຕາມການນໍາພາການຂາຍ", - "skillManagement.detail.builder.followUpDisplayName": "ໃຊ້ການອະນຸມັດການຄືນເງິນເປັນຊື່ສະແດງ", - "skillManagement.detail.builder.followUpNameIcon": "ນຳໃຊ້ຊື່ ແລະໄອຄອນທີ່ແນະນຳ", - "skillManagement.detail.builder.fromMarketplace": "ຈາກຕະຫຼາດແອັບ", - "skillManagement.detail.builder.model": "GPT-4o", - "skillManagement.detail.builder.modelCredits.all": "ສິນເຊື່ອທັງໝົດ", - "skillManagement.detail.builder.modelCredits.configure": "ຕ້ອງຕັ້ງຄ່າກ່ອນ", - "skillManagement.detail.builder.modelCredits.exhausted": "ເຄຣດິດໝົດແລ້ວ", - "skillManagement.detail.builder.modelProviderSettings": "ການຕັ້ງຄ່າຜູ້ໃຫ້ບໍລິການໂມເດວ", - "skillManagement.detail.builder.modelSearch": "ຄົ້ນຫາໂມເດວ...", "skillManagement.detail.builder.modifyPlaceholder": "ຂໍໃຫ້ AI ແກ້ໄຂທັກສະນີ້ ...", "skillManagement.detail.builder.open": "ເປີດ Skill Builder", "skillManagement.detail.builder.placeholder": "ອະທິບາຍສະຖານະການ...", @@ -61,8 +47,6 @@ "skillManagement.detail.builder.thinkingUnavailable": "ບໍ່ມີເນື້ອຫາຄິດຖືກສົ່ງຄືນໂດຍຕົວແບບ.", "skillManagement.detail.builder.title": "Skill Builder", "skillManagement.detail.builder.tryExample": "ລອງຕົວຢ່າງ", - "skillManagement.detail.builder.voice": "ການປ້ອນຂໍ້ມູນດ້ວຍສຽງ", - "skillManagement.detail.builder.voiceUnavailable": "ການປ້ອນຂໍ້ມູນດ້ວຍສຽງຍັງບໍ່ສາມາດໃຊ້ໄດ້ເທື່ອ.", "skillManagement.detail.cancelAddMetadata": "ຍົກເລີກການເພີ່ມ metadata", "skillManagement.detail.closeFileTab": "ປິດ {{name}}", "skillManagement.detail.closeVersions": "ປິດສະບັບ", @@ -70,14 +54,10 @@ "skillManagement.detail.copyContentSuccess": "ສຳເນົາເນື້ອຫາໃສ່ຄລິບບອດແລ້ວ.", "skillManagement.detail.copyFile": "ສຳເນົາ", "skillManagement.detail.copyFileSuccess": "ໄຟລ໌ພ້ອມທີ່ຈະວາງ.", - "skillManagement.detail.copyVersionId": "ກັອບປີ້ໄອດີ", - "skillManagement.detail.copyVersionIdSuccess": "ສຳເນົາ ID ໄປໃສ່ຄລິບບອດແລ້ວ.", "skillManagement.detail.createFile": "ໄຟລ໌ໃຫມ່", - "skillManagement.detail.createFileDescription": "ໃສ່ເສັ້ນທາງໄຟລ໌ທີ່ກ່ຽວຂ້ອງກັບຮາກທັກສະ.", "skillManagement.detail.createFileMenu": "ໄຟລ໌ໃໝ່...", "skillManagement.detail.createFileSuccess": "ສ້າງໄຟລ໌.", "skillManagement.detail.createFolder": "ໂຟນເດີໃໝ່", - "skillManagement.detail.createFolderDescription": "ໃສ່ເສັ້ນທາງໂຟນເດີທີ່ກ່ຽວຂ້ອງກັບຮາກທັກສະ.", "skillManagement.detail.createFolderMenu": "ໂຟນເດີໃໝ່...", "skillManagement.detail.createFolderSuccess": "ສ້າງໂຟນເດີແລ້ວ.", "skillManagement.detail.createdBy": "ສ້າງໂດຍ {{name}}", @@ -100,10 +80,8 @@ "skillManagement.detail.fileMeta": "{{type}} · {{size}} bytes", "skillManagement.detail.fileMissing": "ບໍ່ພົບໄຟລ໌.", "skillManagement.detail.fileOperationFailed": "ການດໍາເນີນງານໄຟລ໌ລົ້ມເຫລວ.", - "skillManagement.detail.files": "ໄຟລ໌", "skillManagement.detail.latest": "ຫຼ້າສຸດ", "skillManagement.detail.loadFailed": "ໂຫຼດທັກສະບໍ່ສຳເລັດ.", - "skillManagement.detail.loadingTags": "ກຳລັງໂຫລດແທັກ...", "skillManagement.detail.markdownLiveMode": "ຕົວຢ່າງສົດ", "skillManagement.detail.markdownSourceMode": "ບັນນາທິການແຫຼ່ງ", "skillManagement.detail.metadataKey": "ຊື່", @@ -113,10 +91,7 @@ "skillManagement.detail.nameThisVersion": "ຕັ້ງຊື່ເວີຊັນນີ້", "skillManagement.detail.noFileSelected": "ເລືອກໄຟລ໌ເພື່ອສະແດງຕົວຢ່າງ.", "skillManagement.detail.noFiles": "ບໍ່ມີໄຟລ໌.", - "skillManagement.detail.noSearchResults": "ບໍ່ມີໄຟລ໌ທີ່ກົງກັນ.", - "skillManagement.detail.noSelectableTags": "ບໍ່ມີແທັກທີ່ມີຢູ່.", "skillManagement.detail.noVersions": "ບໍ່​ມີ​ສະ​ບັບ​ພິມ​ເຜີຍ​ແຜ່​ເທື່ອ​.", - "skillManagement.detail.pasteFile": "ວາງ", "skillManagement.detail.pasteFileSuccess": "ວາງໄຟລ໌ແລ້ວ.", "skillManagement.detail.previewUnsupported": "ບໍ່ຮອງຮັບການເບິ່ງຕົວຢ່າງສຳລັບໄຟລ໌ນີ້.", "skillManagement.detail.publish": "ເຜີຍແຜ່", @@ -128,9 +103,7 @@ "skillManagement.detail.publishUpdate": "ເຜີຍແຜ່ການອັບເດດ", "skillManagement.detail.published": "ເຜີຍແຜ່ແລ້ວ", "skillManagement.detail.publishedAt": "ເຜີຍແຜ່ເມື່ອ {{time}}", - "skillManagement.detail.publishedVersion": "ເຜີຍແຜ່ #{{number}}", "skillManagement.detail.publishing": "ກຳລັງເຜີຍແຜ່...", - "skillManagement.detail.readonly": "ອ່ານເທົ່ານັ້ນ", "skillManagement.detail.referenceFiles.confirm": "ໃສ່ຢືນຢັນ", "skillManagement.detail.referenceFiles.empty": "ບໍ່ມີໄຟລ໌ທີ່ມີຢູ່.", "skillManagement.detail.referenceFiles.livePlaceholder": "ຂຽນຄໍາແນະນໍາສໍາລັບຕົວແທນ. ພິມ / ເພື່ອອ້າງອີງໄຟລ໌.", @@ -138,14 +111,10 @@ "skillManagement.detail.referenceFiles.title": "ໄຟລ໌ອ້າງອີງ", "skillManagement.detail.referencedBy_one": "ໃຊ້ໂດຍ {{count}} ຕົວແທນ", "skillManagement.detail.referencedBy_other": "ໃຊ້ໂດຍ {{count}} ຕົວແທນ", - "skillManagement.detail.removeMetadata": "ລຶບຂໍ້ມູນເມຕາເດຕາ {{name}}", "skillManagement.detail.removeTag": "ເອົາ {{tag}} ອອກ", "skillManagement.detail.removeTagSuccess": "ລຶບແທັກອອກ.", - "skillManagement.detail.renameFile": "ປ່ຽນຊື່ໄຟລ໌", - "skillManagement.detail.renameFileDescription": "ເຂົ້າໄປໃນເສັ້ນທາງໃຫມ່ທີ່ກ່ຽວຂ້ອງກັບຮາກທັກສະ.", "skillManagement.detail.renameFileSuccess": "ປ່ຽນຊື່ໄຟລ໌ແລ້ວ.", "skillManagement.detail.renameSkillSuccess": "Skill ປ່ຽນຊື່ແລ້ວ.", - "skillManagement.detail.renameVersion": "ປ່ຽນຊື່", "skillManagement.detail.renameVersionFailed": "ປ່ຽນຊື່ເວີຊັນບໍ່ສຳເລັດ.", "skillManagement.detail.renameVersionPrompt": "ຊື່ເວີຊັນ", "skillManagement.detail.renameVersionSuccess": "ລຸ້ນປ່ຽນຊື່ແລ້ວ.", @@ -155,17 +124,14 @@ "skillManagement.detail.restoreVersionConfirmTitle": "ກູ້ຄືນເວີຊັນນີ້ບໍ?", "skillManagement.detail.restoreVersionFailed": "ກູ້ເວີຊັນຄືນມາບໍ່ສຳເລັດ.", "skillManagement.detail.restoreVersionSuccess": "ກູ້ເວີຊັນເປັນສະບັບຮ່າງແລ້ວ.", - "skillManagement.detail.save": "ບັນທຶກ", "skillManagement.detail.saveConflictConfirmDescription": "ທັກສະນີ້ໄດ້ຖືກປັບປຸງຢູ່ບ່ອນອື່ນ. ໂຫຼດສະບັບຮ່າງຫຼ້າສຸດເພື່ອສືບຕໍ່ການແກ້ໄຂ. ການປ່ຽນແປງປັດຈຸບັນຂອງເຈົ້າຈະຖືກຍົກເລີກ.", "skillManagement.detail.saveConflictConfirmTitle": "ໂຫຼດສະບັບຮ່າງຫຼ້າສຸດບໍ?", "skillManagement.detail.saveConflictReload": "ໂຫຼດສະບັບຮ່າງຫຼ້າສຸດ", "skillManagement.detail.saveConflictStatus": "ກວດພົບການຂັດແຍ້ງ. ໂຫຼດສະບັບຮ່າງຫຼ້າສຸດເພື່ອສືບຕໍ່ການແກ້ໄຂ.", "skillManagement.detail.saveFailed": "ລົ້ມເຫລວໃນການບັນທຶກໄຟລ໌.", - "skillManagement.detail.saveSuccess": "ບັນທຶກໄຟລ໌ແລ້ວ.", "skillManagement.detail.saved": "ບັນທຶກແລ້ວ", "skillManagement.detail.savedAt": "ບັນທຶກເມື່ອ {{time}}", "skillManagement.detail.saving": "ກຳລັງບັນທຶກ...", - "skillManagement.detail.searchFiles": "ຊອກຫາໄຟລ໌", "skillManagement.detail.showMoreReferences": "ສະແດງ {{count}} ເພີ່ມເຕີມ", "skillManagement.detail.skillDescriptionPlaceholder": "ອະທິບາຍສິ່ງທີ່ທັກສະນີ້ເຮັດ ແລະເວລາທີ່ຕົວແທນຄວນໃຊ້ມັນ.", "skillManagement.detail.skillNamePlaceholder": "ໃຊ້ຕົວພິມນ້ອຍ, ຕົວເລກ, ແລະຍັບຍັ້ງ.", @@ -177,16 +143,12 @@ "skillManagement.detail.uploadConflict": "ໄຟລ໌ທີ່ມີຊື່ນີ້ມີຢູ່ແລ້ວ.", "skillManagement.detail.uploadFailureSummary": "ໄຟລ໌ {{failed}} ຂອງ {{total}} ລົ້ມເຫລວ", "skillManagement.detail.uploadFailureTitle": "ການອັບໂຫລດບາງອັນລົ້ມເຫລວ", - "skillManagement.detail.uploadFile": "ອັບໂຫຼດໄຟລ໌", - "skillManagement.detail.uploadFileFailed": "ການອັບໂຫລດໄຟລ໌ລົ້ມເຫລວ.", "skillManagement.detail.uploadFileSuccess": "ອັບໂຫຼດໄຟລ໌ສຳເລັດ.", "skillManagement.detail.uploadFilesButton": "ອັບໂຫຼດໄຟລ໌ {{count}}", "skillManagement.detail.uploadFilesFailedStatus_one": "ການອັບໂຫລດໄຟລ໌ {{count}} ລົ້ມເຫລວ.", "skillManagement.detail.uploadFilesFailedStatus_other": "ການອັບໂຫລດໄຟລ໌ {{count}} ລົ້ມເຫລວ.", "skillManagement.detail.uploadFilesMenu": "ອັບໂຫຼດໄຟລ໌...", "skillManagement.detail.uploadFilesProgress": "ກຳລັງອັບໂຫລດ {{completed}}/{{total}}", - "skillManagement.detail.uploadFilesResult": "ອັບໂຫຼດ {{uploaded}} · {{failed}} ລົ້ມເຫລວ", - "skillManagement.detail.uploadFilesStatus": "ສະຖານະອັບໂຫຼດ", "skillManagement.detail.uploadInvalidExtension": "ບໍ່ຮອງຮັບໄຟລ໌ປະເພດນີ້.", "skillManagement.detail.uploadInvalidName": "ຊື່ບໍ່ຖືກຕ້ອງ.", "skillManagement.detail.uploadInvalidPath": "ເສັ້ນທາງໄຟລ໌ບໍ່ຖືກຕ້ອງ.", @@ -206,7 +168,6 @@ "skillManagement.detail.uploadWillReplace": "ຈະປ່ຽນແທນໄຟລ໌ທີ່ມີຢູ່ແລ້ວ", "skillManagement.detail.uploadWillUseName": "ຈະອັບໂຫລດເປັນ {{name}}", "skillManagement.detail.versionHistory": "ເປີດປະຫວັດເວີຊັນ", - "skillManagement.detail.versionMeta": "{{hash}} · {{time}}", "skillManagement.detail.versionPublishNote": "ບັນທຶກການປ່ອຍ", "skillManagement.detail.versionPublishNotePlaceholder": "ອະທິບາຍການປ່ຽນແປງນີ້", "skillManagement.detail.versionPublishedMeta": "{{time}} · {{name}}", @@ -227,8 +188,6 @@ "skillManagement.errors.missingSkillMd": "ຊຸດນີ້ບໍ່ມີ SKILL.md. ເພີ່ມໄຟລ໌ແລະນໍາເຂົ້າມັນອີກເທື່ອຫນຶ່ງ.", "skillManagement.errors.nameConflict": "ທັກສະທີ່ມີຊື່ວ່າ \"{{name}}\" ມີຢູ່ແລ້ວ. ປ່ຽນຊື່ມັນຢູ່ໃນ SKILL.md ແລະນໍາເຂົ້າອີກເທື່ອຫນຶ່ງ.", "skillManagement.errors.workspaceLimit": "ພື້ນທີ່ເຮັດວຽກຂອງທ່ານຮອດຂີດຈຳກັດຂອງ 500 ທັກສະແລ້ວ. ລຶບທັກສະເພື່ອເພີ່ມອັນໃໝ່.", - "skillManagement.exportDialog.description": "ການດຳເນີນການນີ້ຈະສົ່ງອອກສະບັບຮ່າງຂອງທັກສະທີ່ບັນທຶກໄວ້ໃນປັດຈຸບັນ. ຖ້າມີການປ່ຽນແປງທີ່ຍັງບໍ່ໄດ້ບັນທຶກໃນຕົວແກ້ໄຂ, ກະລຸນາບັນທຶກກ່ອນສົ່ງອອກ.", - "skillManagement.exportDialog.title": "ສົ່ງອອກສະບັບຮ່າງທີ່ບັນທຶກໄວ້ໃນປັດຈຸບັນ", "skillManagement.import": "ນຳເຂົ້າ", "skillManagement.importFailed": "ລົ້ມເຫລວໃນການນໍາເຂົ້າທັກສະ.", "skillManagement.importSuccess": "Skill ນໍາເຂົ້າ.", @@ -236,12 +195,10 @@ "skillManagement.loadingError": "ລົ້ມເຫລວໃນການໂຫຼດທັກສະ", "skillManagement.moreActions": "ການດຳເນີນການເພີ່ມເຕີມສຳລັບ {{name}}", "skillManagement.noDescription": "ບໍ່ມີລາຍລະອຽດເທື່ອ", - "skillManagement.noTags": "ບໍ່ມີແທັກ", "skillManagement.publishedAt": "ເຜີຍແຜ່ເມື່ອ {{time}}", "skillManagement.referenceCount_one": "{{count}} ຕົວແທນ", "skillManagement.referenceCount_other": "{{count}} ຕົວແທນ", "skillManagement.searchLabel": "ຄົ້ນຫາທັກສະ", "skillManagement.searchPlaceholder": "ຄົ້ນຫາໂດຍຊື່ຫຼືຄໍາອະທິບາຍ", - "skillManagement.tags": "ແທັກ", "skillManagement.title": "ທັກສະ (Skills)" } diff --git a/web/i18n/lo-LA/snippet.json b/web/i18n/locales/lo-LA/snippet.json similarity index 100% rename from web/i18n/lo-LA/snippet.json rename to web/i18n/locales/lo-LA/snippet.json diff --git a/web/i18n/lo-LA/time.json b/web/i18n/locales/lo-LA/time.json similarity index 100% rename from web/i18n/lo-LA/time.json rename to web/i18n/locales/lo-LA/time.json diff --git a/web/i18n/lo-LA/tools.json b/web/i18n/locales/lo-LA/tools.json similarity index 100% rename from web/i18n/lo-LA/tools.json rename to web/i18n/locales/lo-LA/tools.json diff --git a/web/i18n/lo-LA/workflow.json b/web/i18n/locales/lo-LA/workflow.json similarity index 99% rename from web/i18n/lo-LA/workflow.json rename to web/i18n/locales/lo-LA/workflow.json index d92caeb54ac..dc4fcec38c4 100644 --- a/web/i18n/lo-LA/workflow.json +++ b/web/i18n/locales/lo-LA/workflow.json @@ -460,8 +460,6 @@ "nodes.agent.pluginNotInstalledDesc": "ການເຊື່ອມຕໍ່ນີ້ຖືກຕິດຕັ້ງຈາກ GitHub. ກະລຸນາໄປທີ່ການເຊື່ອມຕໍ່ເພື່ອຕິດຕັ້ງໃໝ່", "nodes.agent.roster.change": "ປ່ຽນ", "nodes.agent.roster.editInConsole": "ແກ້ໄຂໃນ Agents", - "nodes.agent.roster.editInConsoleDisabled": "ການແກ້ໄຂ Agent ຕ້ອງໃຊ້ບົດບາດ Editor ຫຼືສູງກວ່າ.", - "nodes.agent.roster.editInConsoleDisabledRbac": "ສະເພາະສະມາຊິກທີ່ມີບົດບາດ Editor ຫຼືສູງກວ່າ ຫຼືສິດ ຈັດການ Agents ເທົ່ານັ້ນທີ່ສາມາດແກ້ໄຂ Agent ໄດ້.", "nodes.agent.roster.inlineSetup.description": "ການຕັ້ງຄ່າຄັ້ງດຽວທີ່ມີຜົນສະເພາະໃນໂນດນີ້", "nodes.agent.roster.inlineSetup.name": "ການຕັ້ງຄ່າພາຍໃນ (Inline)", "nodes.agent.roster.inlineSetup.type": "ສະເພາະໃນໂນດນີ້ເທົ່ານັ້ນ", diff --git a/web/i18n/nl-NL/agent-v-2.json b/web/i18n/locales/nl-NL/agent-v-2.json similarity index 98% rename from web/i18n/nl-NL/agent-v-2.json rename to web/i18n/locales/nl-NL/agent-v-2.json index c83dee850fc..dffcb5e88c8 100644 --- a/web/i18n/nl-NL/agent-v-2.json +++ b/web/i18n/locales/nl-NL/agent-v-2.json @@ -69,6 +69,7 @@ "agentDetail.configure.answer.activity.runningCommands": "Opdrachten uitvoeren", "agentDetail.configure.answer.duration.minute": "{{count}} min", "agentDetail.configure.answer.duration.second": "{{count}} s", + "agentDetail.configure.answer.responseEnded": "Antwoord beëindigd", "agentDetail.configure.answer.thinking": "Aan het nadenken", "agentDetail.configure.build.empty.communityEditionTip": "Community Edition biedt wel versiebeheer voor geëxtraheerde configuraties, maar ondersteunt geen versiebeheer van het bestandssysteem zelf. Wees voorzichtig met je acties in de Build-modus, omdat wijzigingen aan het bestandssysteem in realtime plaatsvinden en niet altijd netjes kunnen worden teruggedraaid. Community Edition is niet de ideale keuze voor het bedienen van grote externe doelgroepen.", "agentDetail.configure.build.empty.description": "Beschrijf wat je wilt en het formulier links wordt gaandeweg ingevuld.", @@ -269,7 +270,6 @@ "agentDetail.configure.workingDirectory.breadcrumbLabel": "Bestandssysteempad", "agentDetail.configure.workingDirectory.description": "Bestanden die de agent leest en schrijft tijdens het uitvoeren. Alles wat hij genereert, zoals qna_report.pdf, verschijnt hier.", "agentDetail.configure.workingDirectory.fileSystem": "Bestandssysteem", - "agentDetail.configure.workingDirectory.open": "Werkmap openen", "agentDetail.configure.workingDirectory.persistentFiles": "Permanent", "agentDetail.configure.workingDirectory.persistentFilesTooltip": "Bestanden die de agent bewaart en overal kan gebruiken waar deze wordt uitgevoerd, waaronder gesprekken van eindgebruikers en workflows. Wijzigingen die je hier tijdens deze buildchat aanbrengt, worden van kracht zodra je op ‘Toepassen’ klikt.", "agentDetail.configure.workingDirectory.temporaryFiles": "Tijdelijk", @@ -348,7 +348,6 @@ "agentDetail.sections.logs": "Logs", "agentDetail.sections.monitoring": "Monitoring", "agentDetail.title": "Agent", - "agentDetail.type": "AGENT", "agentDetail.versionHistory.empty": "Nog geen versies", "agentDetail.versionHistory.exitVersions": "Versies verlaten", "agentDetail.versionHistory.filter": "Versies filteren", @@ -408,8 +407,5 @@ "roster.sort.lastModified": "Laatst gewijzigd", "roster.sort.recentlyCreated": "Recent aangemaakt", "roster.title": "Agenten", - "roster.usageStatus.draft": "Concept", - "skillManagement.detail.uploadFilesFailedStatus_one": "Uploaden van {{count}} bestand is mislukt.", - "skillManagement.detail.uploadFilesFailedStatus_other": "Uploaden van {{count}} bestanden is mislukt.", - "skillManagement.title": "Skills" + "roster.usageStatus.draft": "Concept" } diff --git a/web/i18n/nl-NL/app-annotation.json b/web/i18n/locales/nl-NL/app-annotation.json similarity index 100% rename from web/i18n/nl-NL/app-annotation.json rename to web/i18n/locales/nl-NL/app-annotation.json diff --git a/web/i18n/nl-NL/app-api.json b/web/i18n/locales/nl-NL/app-api.json similarity index 100% rename from web/i18n/nl-NL/app-api.json rename to web/i18n/locales/nl-NL/app-api.json diff --git a/web/i18n/nl-NL/app-debug.json b/web/i18n/locales/nl-NL/app-debug.json similarity index 100% rename from web/i18n/nl-NL/app-debug.json rename to web/i18n/locales/nl-NL/app-debug.json diff --git a/web/i18n/nl-NL/app-log.json b/web/i18n/locales/nl-NL/app-log.json similarity index 97% rename from web/i18n/nl-NL/app-log.json rename to web/i18n/locales/nl-NL/app-log.json index 2fc485193f3..88bd42b38e7 100644 --- a/web/i18n/nl-NL/app-log.json +++ b/web/i18n/locales/nl-NL/app-log.json @@ -4,11 +4,9 @@ "agentLogDetail.iteration": "Iteration", "agentLogDetail.iterations": "Iterations", "agentLogDetail.toolUsed": "Tool Used", - "archives.action.downloadMonth": "Archief van {{month}} downloaden", "archives.action.downloadReady": "De archiefdownload is klaar.", "archives.action.prepareDownload": "Download voorbereiden", "archives.action.prepareFailed": "Kan archiefdownload niet voorbereiden.", - "archives.action.prepareMonth": "Archiefdownload van {{month}} voorbereiden", "archives.action.prepareStarted": "De archiefdownload wordt voorbereid.", "archives.action.preparing": "Voorbereiden", "archives.description": "Download maandelijkse CSV-exports van gearchiveerde workflow-runlogs in deze werkruimte.", diff --git a/web/i18n/nl-NL/app-overview.json b/web/i18n/locales/nl-NL/app-overview.json similarity index 100% rename from web/i18n/nl-NL/app-overview.json rename to web/i18n/locales/nl-NL/app-overview.json diff --git a/web/i18n/nl-NL/app.json b/web/i18n/locales/nl-NL/app.json similarity index 100% rename from web/i18n/nl-NL/app.json rename to web/i18n/locales/nl-NL/app.json diff --git a/web/i18n/nl-NL/billing.json b/web/i18n/locales/nl-NL/billing.json similarity index 100% rename from web/i18n/nl-NL/billing.json rename to web/i18n/locales/nl-NL/billing.json diff --git a/web/i18n/nl-NL/common.json b/web/i18n/locales/nl-NL/common.json similarity index 98% rename from web/i18n/nl-NL/common.json rename to web/i18n/locales/nl-NL/common.json index 94140469646..87bc98c3a5a 100644 --- a/web/i18n/nl-NL/common.json +++ b/web/i18n/locales/nl-NL/common.json @@ -209,9 +209,7 @@ "mainNav.marketplace": "Marketplace", "mainNav.skills": "Skills", "mainNav.webApps.noResults": "Geen webapps gevonden", - "mainNav.webApps.openApp": "Webapp {{name}} openen", "mainNav.webApps.searchPlaceholder": "Webapps zoeken", - "mainNav.workspace.credits": "{{count}} credits", "mainNav.workspace.creditsUnit": "credits", "mainNav.workspace.inviteMembers": "Invite members", "mainNav.workspace.noResults": "Geen werkruimtes gevonden", @@ -233,7 +231,6 @@ "members.datasetOperatorTip": "Only can manage the knowledge base", "members.editRole": "Rol bewerken", "members.editorTip": "Kan apps maken en bewerken", - "members.email": "Email", "members.emailInvalid": "Invalid Email Format", "members.emailNotSetup": "Email server is not set up, so invitation emails cannot be sent. Please notify users of the invitation link that will be issued after invitation instead.", "members.emailPlaceholder": "Typ of plak e-mailadressen…", @@ -250,7 +247,6 @@ "members.inviteLimitExceeded": "Er zijn niet genoeg plaatsen beschikbaar voor alle ontvangers.", "members.inviteTeamMember": "Add team member", "members.inviteTeamMemberTip": "They can access your team data directly after signing in.", - "members.invitedAsRole": "Uitgenodigd als {{role}}", "members.lastActive": "LAST ACTIVE", "members.memberActions": "Ledenacties", "members.memberDetails.assign": "Toewijzen", @@ -258,8 +254,6 @@ "members.memberDetails.assignedRoles": "Toegewezen rollen", "members.memberDetails.customGroup": "AANGEPAST", "members.memberDetails.generalGroup": "ALGEMEEN", - "members.memberDetails.openAria": "Ledendetails openen voor {{name}}", - "members.memberDetails.roleActionsAria": "Acties openen voor de rol {{role}}", "members.memberDetails.roleNoPermissionSummary": "De huidige rol heeft geen rechten.", "members.memberDetails.rolePermissionSummary": "{{role}} kan {{permissions}}", "members.memberDetails.title": "Ledendetails", @@ -370,7 +364,6 @@ "modelProvider.card.usagePriorityTip": "Stel in welke resource als eerste wordt gebruikt bij het uitvoeren van modellen.", "modelProvider.configureTip": "Set up api-key or add model to use", "modelProvider.confirmDelete": "Confirm deletion?", - "modelProvider.credits": "Message Credits", "modelProvider.defaultConfig": "Default Config", "modelProvider.discoverMore": "Discover more in ", "modelProvider.embeddingModel.key": "Embedding Model", @@ -427,7 +420,6 @@ "modelProvider.selector.noProviderConfiguredDesc": "Blader in de Marketplace om er een te installeren, of configureer providers in de instellingen.", "modelProvider.selector.onlyCompatibleModelsShown": "Alleen compatibele modellen worden weergegeven", "modelProvider.selector.showIncompatibleModels": "Incompatibele modellen weergeven", - "modelProvider.selector.suggestion": "Suggestie", "modelProvider.selector.suggestionTip": "Aanbevolen voor agenttaken", "modelProvider.showModels": "Show Models", "modelProvider.speechToTextModel.key": "Speech-to-Text Model", @@ -458,6 +450,8 @@ "operation.confirmAction": "Please confirm your action.", "operation.copied": "Copied", "operation.copy": "Copy", + "operation.copyErrorDetails": "Foutdetails kopiëren", + "operation.copyErrorFailed": "Kopiëren mislukt. Opnieuw proberen", "operation.copyImage": "Copy Image", "operation.create": "Create", "operation.deSelectAll": "Deselect All", @@ -475,7 +469,6 @@ "operation.format": "Format", "operation.imageCopied": "Image copied", "operation.imageCopyFailed": "De afbeelding kon niet worden gekopieerd. Download deze in plaats daarvan.", - "operation.imageDownloaded": "Image downloaded", "operation.in": "in", "operation.learnMore": "Meer informatie", "operation.log": "Log", @@ -512,7 +505,6 @@ "operation.sure": "I'm sure", "operation.toggleFullscreen": "Volledig scherm schakelen", "operation.toggleMute": "Dempen schakelen", - "operation.upload": "Upload", "operation.view": "View", "operation.viewDetails": "View Details", "operation.viewMore": "VIEW MORE", @@ -542,7 +534,6 @@ "promptEditor.requestURL.item.title": "Request URL", "promptEditor.variable.modal.add": "New variable", "promptEditor.variable.modal.addTool": "New tool", - "provider.anthropicHosted.trialQuotaTip": "Your Anthropic trial quota will expire on 2025/03/17 and will no longer be available thereafter. Please make use of it in time.", "provider.encrypted.back": " technology.", "provider.encrypted.front": "Your API KEY will be encrypted and stored using", "provider.validating": "Validating key...", @@ -565,7 +556,6 @@ "settings.preferences": "Preferences", "settings.provider": "Model Provider", "settings.resourceAccess": "Toegang tot bronnen", - "settings.resourceAccessDescription": "Configureer rechtenregels die apps en kennisbanken kunnen gebruiken. Rechtenregels kunnen aan leden worden toegewezen in specifieke configuraties voor toegang tot bronnen.", "settings.rolesAndPermissions": "Rollen en rechten", "settings.settings": "Settings", "settings.swaggerAPIAsTool": "Swagger API as Tool", @@ -695,7 +685,6 @@ "userProfile.helpCenter": "View Docs", "userProfile.logout": "Log out", "userProfile.roadmap": "Roadmap", - "userProfile.settings": "Settings", "userProfile.workspace": "Workspace", "voice.language.arTN": "Tunisian Arabic", "voice.language.deDE": "German", diff --git a/web/i18n/nl-NL/custom.json b/web/i18n/locales/nl-NL/custom.json similarity index 100% rename from web/i18n/nl-NL/custom.json rename to web/i18n/locales/nl-NL/custom.json diff --git a/web/i18n/nl-NL/dataset-creation.json b/web/i18n/locales/nl-NL/dataset-creation.json similarity index 100% rename from web/i18n/nl-NL/dataset-creation.json rename to web/i18n/locales/nl-NL/dataset-creation.json diff --git a/web/i18n/nl-NL/dataset-documents.json b/web/i18n/locales/nl-NL/dataset-documents.json similarity index 100% rename from web/i18n/nl-NL/dataset-documents.json rename to web/i18n/locales/nl-NL/dataset-documents.json diff --git a/web/i18n/nl-NL/dataset-hit-testing.json b/web/i18n/locales/nl-NL/dataset-hit-testing.json similarity index 100% rename from web/i18n/nl-NL/dataset-hit-testing.json rename to web/i18n/locales/nl-NL/dataset-hit-testing.json diff --git a/web/i18n/nl-NL/dataset-pipeline.json b/web/i18n/locales/nl-NL/dataset-pipeline.json similarity index 100% rename from web/i18n/nl-NL/dataset-pipeline.json rename to web/i18n/locales/nl-NL/dataset-pipeline.json diff --git a/web/i18n/nl-NL/dataset-settings.json b/web/i18n/locales/nl-NL/dataset-settings.json similarity index 100% rename from web/i18n/nl-NL/dataset-settings.json rename to web/i18n/locales/nl-NL/dataset-settings.json diff --git a/web/i18n/nl-NL/dataset.json b/web/i18n/locales/nl-NL/dataset.json similarity index 97% rename from web/i18n/nl-NL/dataset.json rename to web/i18n/locales/nl-NL/dataset.json index 1551b5c2cc5..64e1cbdb254 100644 --- a/web/i18n/nl-NL/dataset.json +++ b/web/i18n/locales/nl-NL/dataset.json @@ -163,7 +163,6 @@ "newKnowledge.configureProvider": "{{provider}} configureren", "newKnowledge.connectNotion": "Notion verbinden", "newKnowledge.connectProvider": "{{provider}} verbinden", - "newKnowledge.connectProviderGeneric": "Provider verbinden", "newKnowledge.connectSource": "Bron verbinden", "newKnowledge.connectSourceDescription": "Inhoud blijft gesynchroniseerd met Notion, websites en andere diensten.", "newKnowledge.connectingProvider": "Verbinden…", @@ -179,18 +178,14 @@ "newKnowledge.crawlFailedDescription": "De crawler kon dit voorbeeld niet voltooien. Controleer de URL, pas de crawlopties aan of probeer het opnieuw.", "newKnowledge.crawlFailedProvider": "{{provider}} heeft een fout gemeld. Controleer de providerverbinding en probeer het opnieuw.", "newKnowledge.crawlFailedTimeout": "De crawl heeft de tijdslimiet overschreden. Probeer een specifiekere URL of verlaag het maximale aantal pagina’s.", - "newKnowledge.crawlNotStarted": "Niet gestart", "newKnowledge.crawlOptions": "Crawlopties", "newKnowledge.crawlPreview": "Crawlvoorbeeld", "newKnowledge.crawlProgress": "Crawlvoortgang voor {{host}}", - "newKnowledge.crawlSetupUnavailableDescription": "De configuratie van de websitecrawl is uitgeschakeld totdat de crawlworkflow beschikbaar is.", - "newKnowledge.crawlSetupUnavailableTitle": "Firecrawl is klaar", "newKnowledge.crawlStartFailed": "Het crawlvoorbeeld kon niet worden gestart. Probeer het opnieuw.", "newKnowledge.crawlStopped": "Crawl gestopt", "newKnowledge.crawling": "Bezig met crawlen…", "newKnowledge.crawlingPages": "{{host}} wordt gecrawld… {{count}} pagina’s gevonden", "newKnowledge.createFailed": "We konden deze kennisbank niet creëren. Controleer uw toegang en probeer het opnieuw.", - "newKnowledge.createIllustrationTitle": "Koppel je inhoud één keer — elke app antwoordt met de meest actuele kennis.", "newKnowledge.createTitle": "Creëer kennis", "newKnowledge.createdAt": "Aangemaakt", "newKnowledge.creators": "Makers", @@ -220,7 +215,6 @@ "newKnowledge.documentColumn": "Document", "newKnowledge.documentContentIncomplete": "Resterende documentinhoud laden…", "newKnowledge.documentContents": "Inhoud", - "newKnowledge.documentFacts": "Documentdetails", "newKnowledge.documentFilterLabel": "Documenten filteren op status", "newKnowledge.documentLoadErrorDescription": "Dit document kon niet worden geladen. Probeer het opnieuw.", "newKnowledge.documentLoadErrorTitle": "Document kon niet worden geladen", @@ -268,7 +262,6 @@ "newKnowledge.documentsSelected": "{{count}}-documenten geselecteerd", "newKnowledge.documentsUnavailable": "— documenten", "newKnowledge.downloadDocuments": "Downloaden", - "newKnowledge.editSource": "Bewerken", "newKnowledge.emptyChunk": "Leeg fragment", "newKnowledge.emptyDescription": "Voeg je documenten toe zodat agents ze direct kunnen lezen, zonder indexering te configureren.", "newKnowledge.emptyTitle": "Een slimmere manier om kennisbanken te bouwen", @@ -344,7 +337,6 @@ "newKnowledge.processingTaskState.succeeded": "Voltooid", "newKnowledge.processingTaskState.superseded": "Vervangen", "newKnowledge.providerConnected": "{{provider}} aangesloten", - "newKnowledge.providerLabel": "Select a provider", "newKnowledge.providerLoadFailed": "Providers konden niet laden", "newKnowledge.providerNotConfigured": "{{provider}} is niet geconfigureerd", "newKnowledge.providerNotConfiguredDescription": "Voeg een API-sleutel of eindpunt-URL toe om websites te crawlen met {{provider}}.", @@ -369,12 +361,10 @@ "newKnowledge.revisionState.superseded": "Vorige", "newKnowledge.rootUrl": "Hoofd-URL", "newKnowledge.rootUrlPlaceholder": "https://docs.example.com", - "newKnowledge.routeUnavailable": "Deze sectie komt beschikbaar in de volgende installatiestap.", "newKnowledge.searchDocuments": "Documenten zoeken", "newKnowledge.searchSources": "Zoek bronnen", "newKnowledge.selectAll": "Alles selecteren", "newKnowledge.selectAllDocuments": "Selecteer alle documenten", - "newKnowledge.selectAllSources": "Alle bronnen selecteren", "newKnowledge.selectChunk": "Selecteer een fragment om de inhoud te bekijken.", "newKnowledge.selectFilesAndFolders": "Selecteer bestanden en mappen om te synchroniseren", "newKnowledge.skippedFailed": "Overgeslagen (mislukt)", @@ -384,7 +374,6 @@ "newKnowledge.sourceFilterLabel": "Filter bronnen op status", "newKnowledge.sourceName": "Bronnaam", "newKnowledge.sourceNamePlaceholder": "bijv. crawl van productdocumentatie", - "newKnowledge.sourceNameRequired": "Voer een bronnaam in.", "newKnowledge.sourceSetupBackendDependency": "De bronconfiguratie wordt volledig getoond; bewerken en bekijken vereisen de backendworkflow voor conceptbronnen.", "newKnowledge.sourceStatus.active": "Actief", "newKnowledge.sourceStatus.disabled": "Uitgeschakeld", @@ -431,7 +420,6 @@ "newKnowledge.unavailableDescription": "KnowledgeFS is momenteel niet beschikbaar voor deze werkruimte.", "newKnowledge.unavailableTitle": "Nieuwe kennis is niet beschikbaar", "newKnowledge.updated": "Bijgewerkt op {{date}}", - "newKnowledge.updatedAt": "Laatst bijgewerkt", "newKnowledge.updatedColumn": "Bijgewerkt", "newKnowledge.uploadCharactersUnavailable": "Tekens: —", "newKnowledge.uploadDocuments": "Documenten uploaden", @@ -460,11 +448,9 @@ "retrievalSettings": "Retrieval Setting", "retry": "Retry", "selectExternalKnowledgeAPI.placeholder": "Choose an External Knowledge API", - "serviceApi.card.addApiKey": "API Key toevoegen", "serviceApi.card.apiKey": "API Key", "serviceApi.card.apiReference": "API Reference", "serviceApi.card.endpoint": "Service API Endpoint", - "serviceApi.card.manageApiKey": "API Key beheren", "serviceApi.card.title": "Backend service api", "serviceApi.disabled": "Disabled", "serviceApi.enabled": "Enabled", diff --git a/web/i18n/nl-NL/deployments.json b/web/i18n/locales/nl-NL/deployments.json similarity index 97% rename from web/i18n/nl-NL/deployments.json rename to web/i18n/locales/nl-NL/deployments.json index f41bf709f36..50b13a68263 100644 --- a/web/i18n/nl-NL/deployments.json +++ b/web/i18n/locales/nl-NL/deployments.json @@ -27,7 +27,6 @@ "list.emptyTitle": "Nog geen implementaties", "overview.chip.latest": "nieuwste", "status.RUNTIME_INSTANCE_STATUS_DEPLOYING": "Implementeren", - "status.RUNTIME_INSTANCE_STATUS_FAILED": "Implementatie mislukt", "status.RUNTIME_INSTANCE_STATUS_INVALID": "Ongeldig", "status.RUNTIME_INSTANCE_STATUS_READY": "Actief", "status.RUNTIME_INSTANCE_STATUS_UNDEPLOYED": "Niet geïmplementeerd", @@ -87,6 +86,5 @@ "studio.versionValue": "Versiewaarde", "studio.versionsBehind_one": "{{count}} versie achter", "studio.versionsBehind_other": "{{count}} versies achter", - "versions.checkingReleaseContent": "Release-inhoud controleren...", "versions.deployTo": "Implementeren naar {{name}}" } diff --git a/web/i18n/nl-NL/device-flow.json b/web/i18n/locales/nl-NL/device-flow.json similarity index 100% rename from web/i18n/nl-NL/device-flow.json rename to web/i18n/locales/nl-NL/device-flow.json diff --git a/web/i18n/nl-NL/education.json b/web/i18n/locales/nl-NL/education.json similarity index 100% rename from web/i18n/nl-NL/education.json rename to web/i18n/locales/nl-NL/education.json diff --git a/web/i18n/nl-NL/explore.json b/web/i18n/locales/nl-NL/explore.json similarity index 100% rename from web/i18n/nl-NL/explore.json rename to web/i18n/locales/nl-NL/explore.json diff --git a/web/i18n/nl-NL/layout.json b/web/i18n/locales/nl-NL/layout.json similarity index 100% rename from web/i18n/nl-NL/layout.json rename to web/i18n/locales/nl-NL/layout.json diff --git a/web/i18n/nl-NL/login.json b/web/i18n/locales/nl-NL/login.json similarity index 100% rename from web/i18n/nl-NL/login.json rename to web/i18n/locales/nl-NL/login.json diff --git a/web/i18n/nl-NL/oauth.json b/web/i18n/locales/nl-NL/oauth.json similarity index 100% rename from web/i18n/nl-NL/oauth.json rename to web/i18n/locales/nl-NL/oauth.json diff --git a/web/i18n/nl-NL/permission-keys.json b/web/i18n/locales/nl-NL/permission-keys.json similarity index 100% rename from web/i18n/nl-NL/permission-keys.json rename to web/i18n/locales/nl-NL/permission-keys.json diff --git a/web/i18n/nl-NL/permission.json b/web/i18n/locales/nl-NL/permission.json similarity index 100% rename from web/i18n/nl-NL/permission.json rename to web/i18n/locales/nl-NL/permission.json diff --git a/web/i18n/nl-NL/pipeline.json b/web/i18n/locales/nl-NL/pipeline.json similarity index 100% rename from web/i18n/nl-NL/pipeline.json rename to web/i18n/locales/nl-NL/pipeline.json diff --git a/web/i18n/nl-NL/plugin-tags.json b/web/i18n/locales/nl-NL/plugin-tags.json similarity index 100% rename from web/i18n/nl-NL/plugin-tags.json rename to web/i18n/locales/nl-NL/plugin-tags.json diff --git a/web/i18n/nl-NL/plugin-trigger.json b/web/i18n/locales/nl-NL/plugin-trigger.json similarity index 100% rename from web/i18n/nl-NL/plugin-trigger.json rename to web/i18n/locales/nl-NL/plugin-trigger.json diff --git a/web/i18n/nl-NL/plugin.json b/web/i18n/locales/nl-NL/plugin.json similarity index 99% rename from web/i18n/nl-NL/plugin.json rename to web/i18n/locales/nl-NL/plugin.json index cd35956d35d..e9f0e7fea83 100644 --- a/web/i18n/nl-NL/plugin.json +++ b/web/i18n/locales/nl-NL/plugin.json @@ -277,7 +277,6 @@ "readmeInfo.noReadmeAvailable": "No README available", "readmeInfo.title": "README", "requestAPlugin": "Een plugin aanvragen", - "requestSubmit": "Aanvragen of publiceren", "search": "Zoeken", "searchCategories": "Categorieën zoeken", "searchInMarketplace": "Zoeken in Marketplace", diff --git a/web/i18n/nl-NL/register.json b/web/i18n/locales/nl-NL/register.json similarity index 100% rename from web/i18n/nl-NL/register.json rename to web/i18n/locales/nl-NL/register.json diff --git a/web/i18n/nl-NL/run-log.json b/web/i18n/locales/nl-NL/run-log.json similarity index 100% rename from web/i18n/nl-NL/run-log.json rename to web/i18n/locales/nl-NL/run-log.json diff --git a/web/i18n/nl-NL/share.json b/web/i18n/locales/nl-NL/share.json similarity index 100% rename from web/i18n/nl-NL/share.json rename to web/i18n/locales/nl-NL/share.json diff --git a/web/i18n/nl-NL/skill.json b/web/i18n/locales/nl-NL/skill.json similarity index 82% rename from web/i18n/nl-NL/skill.json rename to web/i18n/locales/nl-NL/skill.json index 335f11c4966..dbf592bef27 100644 --- a/web/i18n/nl-NL/skill.json +++ b/web/i18n/locales/nl-NL/skill.json @@ -1,5 +1,4 @@ { - "skillManagement.clearTags": "Duidelijke tags", "skillManagement.create": "Creëren", "skillManagement.createFailed": "Kan geen vaardigheid creëren.", "skillManagement.createSuccess": "Concept gemaakt.", @@ -13,12 +12,9 @@ "skillManagement.deleteFailed": "Kan vaardigheid niet verwijderen.", "skillManagement.deleteSuccess": "Skill verwijderd.", "skillManagement.detail.addMetadata": "Voeg metagegevens toe", - "skillManagement.detail.addMetadataDescription": "Voeg een frontmatter-veld toe aan dit Markdown-bestand.", "skillManagement.detail.addTag": "Label toevoegen", - "skillManagement.detail.addTagDescription": "Maak of koppel een tag aan deze Skill. Nieuwe tags worden opgeslagen met de Skill-metagegevens.", "skillManagement.detail.addTagSuccess": "Label toegevoegd.", "skillManagement.detail.back": "Terug naar Skills", - "skillManagement.detail.builder.applyFailed": "Kan conceptupdates niet toepassen.", "skillManagement.detail.builder.attach": "Bestand bijvoegen", "skillManagement.detail.builder.attachFailed": "Kan bestand niet bijvoegen.", "skillManagement.detail.builder.attachLimit": "U kunt maximaal 10 bestanden bijvoegen.", @@ -26,22 +22,12 @@ "skillManagement.detail.builder.attachUnsupported": "Alleen afbeeldingen, tekst en documentbestanden kunnen worden bijgevoegd.", "skillManagement.detail.builder.attachmentOnlyMessage": "Gebruik het bijgevoegde bestand om deze vaardigheid te verbeteren.", "skillManagement.detail.builder.close": "Sluit Skill Builder", - "skillManagement.detail.builder.compatibleModelsOnly": "Alleen compatibele modellen worden weergegeven", "skillManagement.detail.builder.copyResponse": "Kopieer het antwoord", "skillManagement.detail.builder.copySuccess": "Reactie gekopieerd naar klembord.", "skillManagement.detail.builder.editIntro": "Ik heb deze vaardigheid beoordeeld. Wat zou je willen aanpassen?", "skillManagement.detail.builder.exampleIssueTriage": "Triage van klantproblemen", "skillManagement.detail.builder.exampleOnboarding": "Onboardinggids voor nieuwe medewerkers", "skillManagement.detail.builder.exampleSalesFollowUp": "Opvolgstrategie voor verkoopleads", - "skillManagement.detail.builder.followUpDisplayName": "Gebruik Terugbetalingsgoedkeuring als weergavenaam", - "skillManagement.detail.builder.followUpNameIcon": "Pas de voorgestelde naam en het pictogram toe", - "skillManagement.detail.builder.fromMarketplace": "Vanuit de Marketplace", - "skillManagement.detail.builder.model": "GPT-4o", - "skillManagement.detail.builder.modelCredits.all": "Alle kredieten", - "skillManagement.detail.builder.modelCredits.configure": "Configuratie vereist", - "skillManagement.detail.builder.modelCredits.exhausted": "Quota exhausted", - "skillManagement.detail.builder.modelProviderSettings": "Modelproviderinstellingen", - "skillManagement.detail.builder.modelSearch": "Modellen zoeken...", "skillManagement.detail.builder.modifyPlaceholder": "Vraag AI om deze vaardigheid aan te passen...", "skillManagement.detail.builder.open": "Open Skill Builder", "skillManagement.detail.builder.placeholder": "Beschrijf het scenario...", @@ -61,8 +47,6 @@ "skillManagement.detail.builder.thinkingUnavailable": "Er werd geen denkinhoud geretourneerd door het model.", "skillManagement.detail.builder.title": "Skill Builder", "skillManagement.detail.builder.tryExample": "Probeer een voorbeeld", - "skillManagement.detail.builder.voice": "Spraakinvoer", - "skillManagement.detail.builder.voiceUnavailable": "Spraakinvoer is nog niet beschikbaar.", "skillManagement.detail.cancelAddMetadata": "Annuleer het toevoegen van metagegevens", "skillManagement.detail.closeFileTab": "Sluiten {{name}}", "skillManagement.detail.closeVersions": "Versies sluiten", @@ -70,14 +54,10 @@ "skillManagement.detail.copyContentSuccess": "Inhoud gekopieerd naar klembord.", "skillManagement.detail.copyFile": "Kopiëren", "skillManagement.detail.copyFileSuccess": "Bestand klaar om te plakken.", - "skillManagement.detail.copyVersionId": "Kopieer identiteitsbewijs", - "skillManagement.detail.copyVersionIdSuccess": "ID gekopieerd naar klembord.", "skillManagement.detail.createFile": "Nieuw bestand", - "skillManagement.detail.createFileDescription": "Voer een bestandspad in ten opzichte van de vaardigheidsroot.", "skillManagement.detail.createFileMenu": "Nieuw bestand...", "skillManagement.detail.createFileSuccess": "Bestand gemaakt.", "skillManagement.detail.createFolder": "Nieuwe map", - "skillManagement.detail.createFolderDescription": "Voer een mappad in ten opzichte van de vaardigheidsroot.", "skillManagement.detail.createFolderMenu": "Nieuwe map...", "skillManagement.detail.createFolderSuccess": "Map gemaakt.", "skillManagement.detail.createdBy": "Gemaakt door {{name}}", @@ -100,10 +80,8 @@ "skillManagement.detail.fileMeta": "{{type}} · {{size}} bytes", "skillManagement.detail.fileMissing": "Bestand niet gevonden.", "skillManagement.detail.fileOperationFailed": "Bestandsbewerking is mislukt.", - "skillManagement.detail.files": "Bestanden", "skillManagement.detail.latest": "Nieuwste", "skillManagement.detail.loadFailed": "Kan vaardigheid niet laden.", - "skillManagement.detail.loadingTags": "Labels laden...", "skillManagement.detail.markdownLiveMode": "Live-voorbeeld", "skillManagement.detail.markdownSourceMode": "Bron-editor", "skillManagement.detail.metadataKey": "naam", @@ -113,10 +91,7 @@ "skillManagement.detail.nameThisVersion": "Geef deze versie een naam", "skillManagement.detail.noFileSelected": "Selecteer een bestand waarvan u een voorbeeld wilt bekijken.", "skillManagement.detail.noFiles": "Geen bestanden.", - "skillManagement.detail.noSearchResults": "Geen overeenkomende bestanden.", - "skillManagement.detail.noSelectableTags": "Geen beschikbare tags.", "skillManagement.detail.noVersions": "Nog geen gepubliceerde versies.", - "skillManagement.detail.pasteFile": "Plakken", "skillManagement.detail.pasteFileSuccess": "Bestand geplakt.", "skillManagement.detail.previewUnsupported": "Dit bestand ondersteunt geen voorbeeldweergave.", "skillManagement.detail.publish": "Publiceren", @@ -128,9 +103,7 @@ "skillManagement.detail.publishUpdate": "Update publiceren", "skillManagement.detail.published": "Gepubliceerd", "skillManagement.detail.publishedAt": "Gepubliceerd {{time}}", - "skillManagement.detail.publishedVersion": "Gepubliceerd #{{number}}", "skillManagement.detail.publishing": "Publiceren...", - "skillManagement.detail.readonly": "Alleen lezen", "skillManagement.detail.referenceFiles.confirm": "Voer Bevestigen in", "skillManagement.detail.referenceFiles.empty": "Geen bestanden beschikbaar.", "skillManagement.detail.referenceFiles.livePlaceholder": "Schrijf instructies voor de agent. Typ / om naar bestanden te verwijzen.", @@ -138,14 +111,10 @@ "skillManagement.detail.referenceFiles.title": "Referentiebestanden", "skillManagement.detail.referencedBy_one": "Gebruikt door {{count}} agent", "skillManagement.detail.referencedBy_other": "Gebruikt door {{count}} agenten", - "skillManagement.detail.removeMetadata": "Verwijder {{name}}-metagegevens", "skillManagement.detail.removeTag": "Verwijder {{tag}}", "skillManagement.detail.removeTagSuccess": "Label verwijderd.", - "skillManagement.detail.renameFile": "Bestand hernoemen", - "skillManagement.detail.renameFileDescription": "Voer het nieuwe pad in ten opzichte van de vaardigheidsroot.", "skillManagement.detail.renameFileSuccess": "Bestand hernoemd.", "skillManagement.detail.renameSkillSuccess": "Skill hernoemd.", - "skillManagement.detail.renameVersion": "Hernoemen", "skillManagement.detail.renameVersionFailed": "Kan de naam van de versie niet wijzigen.", "skillManagement.detail.renameVersionPrompt": "Versienaam", "skillManagement.detail.renameVersionSuccess": "Versie hernoemd.", @@ -155,17 +124,14 @@ "skillManagement.detail.restoreVersionConfirmTitle": "Deze versie herstellen?", "skillManagement.detail.restoreVersionFailed": "Kan de versie niet herstellen.", "skillManagement.detail.restoreVersionSuccess": "Versie hersteld naar concept.", - "skillManagement.detail.save": "Redden", "skillManagement.detail.saveConflictConfirmDescription": "Deze vaardigheid is elders bijgewerkt. Laad het nieuwste concept om door te gaan met bewerken. Uw huidige wijzigingen worden verwijderd.", "skillManagement.detail.saveConflictConfirmTitle": "Het laatste concept laden?", "skillManagement.detail.saveConflictReload": "Laad het laatste concept", "skillManagement.detail.saveConflictStatus": "Conflict gedetecteerd. Laad het nieuwste concept om door te gaan met bewerken.", "skillManagement.detail.saveFailed": "Kan bestand niet opslaan.", - "skillManagement.detail.saveSuccess": "Bestand opgeslagen.", "skillManagement.detail.saved": "Opgeslagen", "skillManagement.detail.savedAt": "Opgeslagen {{time}}", "skillManagement.detail.saving": "Besparing...", - "skillManagement.detail.searchFiles": "Bestanden zoeken", "skillManagement.detail.showMoreReferences": "Laat {{count}} meer zien", "skillManagement.detail.skillDescriptionPlaceholder": "Beschrijf wat deze vaardigheid doet en wanneer agenten deze moeten gebruiken.", "skillManagement.detail.skillNamePlaceholder": "Gebruik kleine letters, cijfers en koppeltekens.", @@ -177,16 +143,12 @@ "skillManagement.detail.uploadConflict": "Er bestaat al een bestand met deze naam.", "skillManagement.detail.uploadFailureSummary": "{{failed}} van {{total}}-bestanden zijn mislukt", "skillManagement.detail.uploadFailureTitle": "Sommige uploads zijn mislukt", - "skillManagement.detail.uploadFile": "Bestand uploaden", - "skillManagement.detail.uploadFileFailed": "Bestandsupload mislukt.", "skillManagement.detail.uploadFileSuccess": "Bestand geüpload.", "skillManagement.detail.uploadFilesButton": "Upload {{count}}-bestanden", "skillManagement.detail.uploadFilesFailedStatus_one": "Uploaden van {{count}}-bestand mislukt.", "skillManagement.detail.uploadFilesFailedStatus_other": "Uploaden van {{count}}-bestanden is mislukt.", "skillManagement.detail.uploadFilesMenu": "Bestanden uploaden...", "skillManagement.detail.uploadFilesProgress": "{{completed}}/{{total}} uploaden", - "skillManagement.detail.uploadFilesResult": "{{uploaded}} geüpload · {{failed}} mislukt", - "skillManagement.detail.uploadFilesStatus": "Uploadstatus", "skillManagement.detail.uploadInvalidExtension": "Dit bestandstype wordt niet ondersteund.", "skillManagement.detail.uploadInvalidName": "Ongeldige naam.", "skillManagement.detail.uploadInvalidPath": "Ongeldig bestandspad.", @@ -206,7 +168,6 @@ "skillManagement.detail.uploadWillReplace": "Zal het bestaande bestand vervangen", "skillManagement.detail.uploadWillUseName": "Zal uploaden als {{name}}", "skillManagement.detail.versionHistory": "Versiegeschiedenis openen", - "skillManagement.detail.versionMeta": "{{hash}} · {{time}}", "skillManagement.detail.versionPublishNote": "Release-opmerkingen", "skillManagement.detail.versionPublishNotePlaceholder": "Beschrijf deze verandering", "skillManagement.detail.versionPublishedMeta": "{{time}} · {{name}}", @@ -227,8 +188,6 @@ "skillManagement.errors.missingSkillMd": "Dit pakket heeft geen SKILL.md. Voeg het bestand toe en importeer het opnieuw.", "skillManagement.errors.nameConflict": "Er bestaat al een vaardigheid met de naam \"{{name}}\". Hernoem het in SKILL.md en importeer opnieuw.", "skillManagement.errors.workspaceLimit": "Uw werkruimte heeft de limiet van 500 vaardigheden bereikt. Verwijder een vaardigheid om een ​​nieuwe toe te voegen.", - "skillManagement.exportDialog.description": "Hiermee exporteer je het huidige opgeslagen concept van je vaardigheid. Sla eventuele niet-opgeslagen wijzigingen in de editor op voordat je exporteert.", - "skillManagement.exportDialog.title": "Huidige opgeslagen concept exporteren", "skillManagement.import": "Importeren", "skillManagement.importFailed": "Kan vaardigheid niet importeren.", "skillManagement.importSuccess": "Skill geïmporteerd.", @@ -236,12 +195,10 @@ "skillManagement.loadingError": "Kan vaardigheden niet laden", "skillManagement.moreActions": "Meer acties voor {{name}}", "skillManagement.noDescription": "Nog geen beschrijving", - "skillManagement.noTags": "Geen labels", "skillManagement.publishedAt": "Gepubliceerd {{time}}", "skillManagement.referenceCount_one": "{{count}} agent", "skillManagement.referenceCount_other": "{{count}} agenten", "skillManagement.searchLabel": "Skills zoeken", "skillManagement.searchPlaceholder": "Zoek op naam of beschrijving", - "skillManagement.tags": "Labels", "skillManagement.title": "Skills" } diff --git a/web/i18n/nl-NL/snippet.json b/web/i18n/locales/nl-NL/snippet.json similarity index 100% rename from web/i18n/nl-NL/snippet.json rename to web/i18n/locales/nl-NL/snippet.json diff --git a/web/i18n/nl-NL/time.json b/web/i18n/locales/nl-NL/time.json similarity index 100% rename from web/i18n/nl-NL/time.json rename to web/i18n/locales/nl-NL/time.json diff --git a/web/i18n/nl-NL/tools.json b/web/i18n/locales/nl-NL/tools.json similarity index 100% rename from web/i18n/nl-NL/tools.json rename to web/i18n/locales/nl-NL/tools.json diff --git a/web/i18n/nl-NL/workflow.json b/web/i18n/locales/nl-NL/workflow.json similarity index 99% rename from web/i18n/nl-NL/workflow.json rename to web/i18n/locales/nl-NL/workflow.json index 81c0d5f83c4..2952a16d2e8 100644 --- a/web/i18n/nl-NL/workflow.json +++ b/web/i18n/locales/nl-NL/workflow.json @@ -460,8 +460,6 @@ "nodes.agent.pluginNotInstalledDesc": "This integratie is installed from GitHub. Please go to Integraties to reinstall", "nodes.agent.roster.change": "Wijzigen", "nodes.agent.roster.editInConsole": "Bewerken in Agents", - "nodes.agent.roster.editInConsoleDisabled": "Voor het bewerken van agents is de rol Editor of hoger vereist.", - "nodes.agent.roster.editInConsoleDisabledRbac": "Alleen leden met de rol Editor of hoger, of de machtiging Agents beheren, kunnen agents bewerken.", "nodes.agent.roster.inlineSetup.description": "Een eenmalige setup die alleen in dit knooppunt bestaat", "nodes.agent.roster.inlineSetup.name": "Inline-setup", "nodes.agent.roster.inlineSetup.type": "Alleen in dit knooppunt", diff --git a/web/i18n/pl-PL/agent-v-2.json b/web/i18n/locales/pl-PL/agent-v-2.json similarity index 98% rename from web/i18n/pl-PL/agent-v-2.json rename to web/i18n/locales/pl-PL/agent-v-2.json index 5e1f825a29c..d7465200e91 100644 --- a/web/i18n/pl-PL/agent-v-2.json +++ b/web/i18n/locales/pl-PL/agent-v-2.json @@ -69,6 +69,7 @@ "agentDetail.configure.answer.activity.runningCommands": "Uruchamianie poleceń", "agentDetail.configure.answer.duration.minute": "{{count}} min", "agentDetail.configure.answer.duration.second": "{{count}} s", + "agentDetail.configure.answer.responseEnded": "Odpowiedź zakończona", "agentDetail.configure.answer.thinking": "Myśli", "agentDetail.configure.build.empty.communityEditionTip": "Community Edition oferuje wersjonowanie wyodrębnionych konfiguracji, ale nie obsługuje wersjonowania samego systemu plików. Zachowaj ostrożność podczas działań w trybie Build, ponieważ zmiany w systemie plików zachodzą w czasie rzeczywistym i nie zawsze można je czysto cofnąć. Community Edition nie jest idealnym wyborem do obsługi masowej publiczności zewnętrznej.", "agentDetail.configure.build.empty.description": "Opisz, czego chcesz, a formularz po lewej będzie wypełniany w trakcie rozmowy.", @@ -269,7 +270,6 @@ "agentDetail.configure.workingDirectory.breadcrumbLabel": "Ścieżka systemu plików", "agentDetail.configure.workingDirectory.description": "Pliki, które agent odczytuje i zapisuje podczas działania. Wszystko, co wygeneruje, np. qna_report.pdf, pojawi się tutaj.", "agentDetail.configure.workingDirectory.fileSystem": "System plików", - "agentDetail.configure.workingDirectory.open": "Otwórz katalog roboczy", "agentDetail.configure.workingDirectory.persistentFiles": "Trwałe", "agentDetail.configure.workingDirectory.persistentFilesTooltip": "Pliki, które agent zachowuje i może wykorzystywać wszędzie tam, gdzie działa, w tym w rozmowach użytkowników końcowych i przepływach pracy. Zmiany wprowadzone tutaj podczas tego czatu tworzenia zaczną obowiązywać po kliknięciu „Zastosuj”.", "agentDetail.configure.workingDirectory.temporaryFiles": "Tymczasowe", @@ -348,7 +348,6 @@ "agentDetail.sections.logs": "Logi", "agentDetail.sections.monitoring": "Monitorowanie", "agentDetail.title": "Agent", - "agentDetail.type": "AGENT", "agentDetail.versionHistory.empty": "Brak wersji", "agentDetail.versionHistory.exitVersions": "Opuść wersje", "agentDetail.versionHistory.filter": "Filtruj wersje", @@ -408,8 +407,5 @@ "roster.sort.lastModified": "Ostatnio zmodyfikowane", "roster.sort.recentlyCreated": "Ostatnio utworzone", "roster.title": "Agenci", - "roster.usageStatus.draft": "Wersja robocza", - "skillManagement.detail.uploadFilesFailedStatus_one": "Nie udało się przesłać {{count}} pliku.", - "skillManagement.detail.uploadFilesFailedStatus_other": "Nie udało się przesłać {{count}} plików.", - "skillManagement.title": "Skills" + "roster.usageStatus.draft": "Wersja robocza" } diff --git a/web/i18n/pl-PL/app-annotation.json b/web/i18n/locales/pl-PL/app-annotation.json similarity index 100% rename from web/i18n/pl-PL/app-annotation.json rename to web/i18n/locales/pl-PL/app-annotation.json diff --git a/web/i18n/pl-PL/app-api.json b/web/i18n/locales/pl-PL/app-api.json similarity index 100% rename from web/i18n/pl-PL/app-api.json rename to web/i18n/locales/pl-PL/app-api.json diff --git a/web/i18n/pl-PL/app-debug.json b/web/i18n/locales/pl-PL/app-debug.json similarity index 100% rename from web/i18n/pl-PL/app-debug.json rename to web/i18n/locales/pl-PL/app-debug.json diff --git a/web/i18n/pl-PL/app-log.json b/web/i18n/locales/pl-PL/app-log.json similarity index 97% rename from web/i18n/pl-PL/app-log.json rename to web/i18n/locales/pl-PL/app-log.json index 01e6ddd623e..1b24143e5cd 100644 --- a/web/i18n/pl-PL/app-log.json +++ b/web/i18n/locales/pl-PL/app-log.json @@ -4,11 +4,9 @@ "agentLogDetail.iteration": "Iteracja", "agentLogDetail.iterations": "Iteracje", "agentLogDetail.toolUsed": "Użyte narzędzia", - "archives.action.downloadMonth": "Pobierz archiwum z {{month}}", "archives.action.downloadReady": "Pobieranie archiwum jest gotowe.", "archives.action.prepareDownload": "Przygotuj pobieranie", "archives.action.prepareFailed": "Nie udało się przygotować pobierania archiwum.", - "archives.action.prepareMonth": "Przygotuj pobieranie archiwum z {{month}}", "archives.action.prepareStarted": "Pobieranie archiwum jest przygotowywane.", "archives.action.preparing": "Przygotowywanie", "archives.description": "Pobierz miesięczne eksporty CSV zarchiwizowanych logów uruchomień workflow w tym obszarze roboczym.", diff --git a/web/i18n/pl-PL/app-overview.json b/web/i18n/locales/pl-PL/app-overview.json similarity index 100% rename from web/i18n/pl-PL/app-overview.json rename to web/i18n/locales/pl-PL/app-overview.json diff --git a/web/i18n/pl-PL/app.json b/web/i18n/locales/pl-PL/app.json similarity index 100% rename from web/i18n/pl-PL/app.json rename to web/i18n/locales/pl-PL/app.json diff --git a/web/i18n/pl-PL/billing.json b/web/i18n/locales/pl-PL/billing.json similarity index 100% rename from web/i18n/pl-PL/billing.json rename to web/i18n/locales/pl-PL/billing.json diff --git a/web/i18n/pl-PL/common.json b/web/i18n/locales/pl-PL/common.json similarity index 98% rename from web/i18n/pl-PL/common.json rename to web/i18n/locales/pl-PL/common.json index 1736e5eb631..897481aca3a 100644 --- a/web/i18n/pl-PL/common.json +++ b/web/i18n/locales/pl-PL/common.json @@ -209,9 +209,7 @@ "mainNav.marketplace": "Marketplace", "mainNav.skills": "Skills", "mainNav.webApps.noResults": "Nie znaleziono aplikacji webowych", - "mainNav.webApps.openApp": "Otwórz aplikację webową {{name}}", "mainNav.webApps.searchPlaceholder": "Szukaj aplikacji webowych", - "mainNav.workspace.credits": "{{count}} kredytów", "mainNav.workspace.creditsUnit": "kredyty", "mainNav.workspace.inviteMembers": "Invite members", "mainNav.workspace.noResults": "Nie znaleziono obszarów roboczych", @@ -233,7 +231,6 @@ "members.datasetOperatorTip": "Może zarządzać tylko bazą wiedzy", "members.editRole": "Edytuj rolę", "members.editorTip": "Może tworzyć i edytować aplikacje", - "members.email": "Email", "members.emailInvalid": "Nieprawidłowy format e-maila", "members.emailNotSetup": "Serwer poczty nie jest skonfigurowany, więc nie można wysyłać zaproszeń e-mail. Proszę powiadomić użytkowników o linku do zaproszenia, który zostanie wydany po zaproszeniu.", "members.emailPlaceholder": "Wpisz lub wklej adresy e-mail…", @@ -250,7 +247,6 @@ "members.inviteLimitExceeded": "Brak wystarczającej liczby miejsc dla wszystkich odbiorców.", "members.inviteTeamMember": "Dodaj członka zespołu", "members.inviteTeamMemberTip": "Mogą uzyskać bezpośredni dostęp do danych Twojego zespołu po zalogowaniu.", - "members.invitedAsRole": "Zaproszony jako {{role}}", "members.lastActive": "OSTATNIA AKTYWNOŚĆ", "members.memberActions": "Akcje członka", "members.memberDetails.assign": "Przypisz", @@ -258,8 +254,6 @@ "members.memberDetails.assignedRoles": "Przypisane role", "members.memberDetails.customGroup": "NIESTANDARDOWE", "members.memberDetails.generalGroup": "OGÓLNE", - "members.memberDetails.openAria": "Otwórz szczegóły członka dla {{name}}", - "members.memberDetails.roleActionsAria": "Otwórz akcje dla roli {{role}}", "members.memberDetails.roleNoPermissionSummary": "Bieżąca rola nie ma uprawnień.", "members.memberDetails.rolePermissionSummary": "{{role}} może {{permissions}}", "members.memberDetails.title": "Szczegóły członka", @@ -370,7 +364,6 @@ "modelProvider.card.usagePriorityTip": "Ustaw, który zasób ma być używany jako pierwszy przy uruchamianiu modeli.", "modelProvider.configureTip": "Konfigurowanie klucza interfejsu API lub dodawanie modelu do użycia", "modelProvider.confirmDelete": "potwierdzić usunięcie?", - "modelProvider.credits": "Kredyty wiadomości", "modelProvider.defaultConfig": "Domyślna konfiguracja", "modelProvider.discoverMore": "Dowiedz się więcej w", "modelProvider.embeddingModel.key": "Model osadzania", @@ -427,7 +420,6 @@ "modelProvider.selector.noProviderConfiguredDesc": "Przeglądaj Marketplace, aby zainstalować dostawcę, lub skonfiguruj dostawców w ustawieniach.", "modelProvider.selector.onlyCompatibleModelsShown": "Wyświetlane są tylko kompatybilne modele", "modelProvider.selector.showIncompatibleModels": "Pokaż niezgodne modele", - "modelProvider.selector.suggestion": "Sugestia", "modelProvider.selector.suggestionTip": "Zalecane do zadań agentów", "modelProvider.showModels": "Pokaż modele", "modelProvider.speechToTextModel.key": "Model mowy na tekst", @@ -458,6 +450,8 @@ "operation.confirmAction": "Proszę potwierdzić swoją akcję.", "operation.copied": "Kopiowane", "operation.copy": "Kopiuj", + "operation.copyErrorDetails": "Kopiuj szczegóły błędu", + "operation.copyErrorFailed": "Kopiowanie nie powiodło się. Spróbuj ponownie", "operation.copyImage": "Kopiuj obraz", "operation.create": "Utwórz", "operation.deSelectAll": "Odznacz wszystkie", @@ -475,7 +469,6 @@ "operation.format": "Format", "operation.imageCopied": "Skopiowany obraz", "operation.imageCopyFailed": "Nie można skopiować obrazu. Zamiast tego pobierz go.", - "operation.imageDownloaded": "Obraz pobrany", "operation.in": "w", "operation.learnMore": "Dowiedz się więcej", "operation.log": "Dziennik", @@ -512,7 +505,6 @@ "operation.sure": "Jestem pewien", "operation.toggleFullscreen": "Przełącz pełny ekran", "operation.toggleMute": "Przełącz wyciszenie", - "operation.upload": "Upload", "operation.view": "Widok", "operation.viewDetails": "Wyświetl szczegóły", "operation.viewMore": "ZOBACZ WIĘCEJ", @@ -542,7 +534,6 @@ "promptEditor.requestURL.item.title": "URL żądania", "promptEditor.variable.modal.add": "Nowa zmienna", "promptEditor.variable.modal.addTool": "Nowe narzędzie", - "provider.anthropicHosted.trialQuotaTip": "Twój limit próbny Anthropic wygaśnie w dniu 11.03.2025 i nie będzie już dostępny po tym czasie. Prosimy o skorzystanie z niego w odpowiednim czasie.", "provider.encrypted.back": " technologii.", "provider.encrypted.front": "Twój KLUCZ API będzie szyfrowany i przechowywany za pomocą", "provider.validating": "Weryfikowanie klucza...", @@ -565,7 +556,6 @@ "settings.preferences": "Preferences", "settings.provider": "Dostawca modelu", "settings.resourceAccess": "Dostęp do zasobów", - "settings.resourceAccessDescription": "Skonfiguruj reguły uprawnień, których mogą używać aplikacje i bazy wiedzy. Reguły uprawnień można przypisywać członkom w określonych konfiguracjach dostępu do zasobów.", "settings.rolesAndPermissions": "Role i uprawnienia", "settings.settings": "Settings", "settings.swaggerAPIAsTool": "Swagger API as Tool", @@ -695,7 +685,6 @@ "userProfile.helpCenter": "Pomoc", "userProfile.logout": "Wyloguj się", "userProfile.roadmap": "Plan działania", - "userProfile.settings": "Ustawienia", "userProfile.workspace": "Przestrzeń robocza", "voice.language.arTN": "Arabski tunezyjski", "voice.language.deDE": "Niemiecki", diff --git a/web/i18n/pl-PL/custom.json b/web/i18n/locales/pl-PL/custom.json similarity index 100% rename from web/i18n/pl-PL/custom.json rename to web/i18n/locales/pl-PL/custom.json diff --git a/web/i18n/pl-PL/dataset-creation.json b/web/i18n/locales/pl-PL/dataset-creation.json similarity index 100% rename from web/i18n/pl-PL/dataset-creation.json rename to web/i18n/locales/pl-PL/dataset-creation.json diff --git a/web/i18n/pl-PL/dataset-documents.json b/web/i18n/locales/pl-PL/dataset-documents.json similarity index 100% rename from web/i18n/pl-PL/dataset-documents.json rename to web/i18n/locales/pl-PL/dataset-documents.json diff --git a/web/i18n/pl-PL/dataset-hit-testing.json b/web/i18n/locales/pl-PL/dataset-hit-testing.json similarity index 100% rename from web/i18n/pl-PL/dataset-hit-testing.json rename to web/i18n/locales/pl-PL/dataset-hit-testing.json diff --git a/web/i18n/pl-PL/dataset-pipeline.json b/web/i18n/locales/pl-PL/dataset-pipeline.json similarity index 100% rename from web/i18n/pl-PL/dataset-pipeline.json rename to web/i18n/locales/pl-PL/dataset-pipeline.json diff --git a/web/i18n/pl-PL/dataset-settings.json b/web/i18n/locales/pl-PL/dataset-settings.json similarity index 100% rename from web/i18n/pl-PL/dataset-settings.json rename to web/i18n/locales/pl-PL/dataset-settings.json diff --git a/web/i18n/pl-PL/dataset.json b/web/i18n/locales/pl-PL/dataset.json similarity index 97% rename from web/i18n/pl-PL/dataset.json rename to web/i18n/locales/pl-PL/dataset.json index 30abb1716ae..ed9d3c4fda9 100644 --- a/web/i18n/pl-PL/dataset.json +++ b/web/i18n/locales/pl-PL/dataset.json @@ -163,7 +163,6 @@ "newKnowledge.configureProvider": "Skonfiguruj {{provider}}", "newKnowledge.connectNotion": "Połącz z Notion", "newKnowledge.connectProvider": "Połącz {{provider}}", - "newKnowledge.connectProviderGeneric": "Połącz dostawcę", "newKnowledge.connectSource": "Połącz źródło", "newKnowledge.connectSourceDescription": "Treści pozostają zsynchronizowane z Notion, witrynami internetowymi i innymi usługami.", "newKnowledge.connectingProvider": "Łączenie…", @@ -179,18 +178,14 @@ "newKnowledge.crawlFailedDescription": "Robot nie mógł ukończyć tego podglądu. Sprawdź adres URL, dostosuj opcje skanowania lub spróbuj ponownie.", "newKnowledge.crawlFailedProvider": "{{provider}} zgłosił błąd. Sprawdź połączenie z dostawcą i spróbuj ponownie.", "newKnowledge.crawlFailedTimeout": "Przekroczono limit czasu skanowania. Użyj bardziej szczegółowego adresu URL lub zmniejsz maksymalną liczbę stron.", - "newKnowledge.crawlNotStarted": "Nie rozpoczęto", "newKnowledge.crawlOptions": "Opcje skanowania", "newKnowledge.crawlPreview": "Podgląd indeksowania", "newKnowledge.crawlProgress": "Postęp skanowania {{host}}", - "newKnowledge.crawlSetupUnavailableDescription": "Konfiguracja indeksowania witryny jest wyłączona do czasu udostępnienia przepływu pracy indeksowania.", - "newKnowledge.crawlSetupUnavailableTitle": "Firecrawl jest gotowy", "newKnowledge.crawlStartFailed": "Nie udało się uruchomić podglądu skanowania. Spróbuj ponownie.", "newKnowledge.crawlStopped": "Skanowanie zatrzymane", "newKnowledge.crawling": "Skanowanie…", "newKnowledge.crawlingPages": "Skanowanie {{host}}… znaleziono {{count}} stron", "newKnowledge.createFailed": "Nie mogliśmy utworzyć tej bazy wiedzy. Sprawdź swój dostęp i spróbuj ponownie.", - "newKnowledge.createIllustrationTitle": "Połącz treści raz — każda aplikacja odpowie, korzystając z najnowszej wiedzy.", "newKnowledge.createTitle": "Twórz wiedzę", "newKnowledge.createdAt": "Utworzono", "newKnowledge.creators": "Twórcy", @@ -220,7 +215,6 @@ "newKnowledge.documentColumn": "Dokument", "newKnowledge.documentContentIncomplete": "Wczytywanie pozostałej treści dokumentu…", "newKnowledge.documentContents": "Zawartość", - "newKnowledge.documentFacts": "Szczegóły dokumentu", "newKnowledge.documentFilterLabel": "Filtruj dokumenty według statusu", "newKnowledge.documentLoadErrorDescription": "Nie udało się wczytać tego dokumentu. Spróbuj ponownie.", "newKnowledge.documentLoadErrorTitle": "Nie udało się wczytać dokumentu", @@ -268,7 +262,6 @@ "newKnowledge.documentsSelected": "Wybrano dokumenty {{count}}", "newKnowledge.documentsUnavailable": "— dokumentów", "newKnowledge.downloadDocuments": "Pobierz", - "newKnowledge.editSource": "Edytuj", "newKnowledge.emptyChunk": "Pusty fragment", "newKnowledge.emptyDescription": "Dodaj dokumenty, aby agenci mogli je czytać bezpośrednio — bez konfigurowania indeksowania.", "newKnowledge.emptyTitle": "Inteligentniejszy sposób tworzenia baz wiedzy", @@ -344,7 +337,6 @@ "newKnowledge.processingTaskState.succeeded": "Ukończono", "newKnowledge.processingTaskState.superseded": "Zastąpione", "newKnowledge.providerConnected": "{{provider}} podłączony", - "newKnowledge.providerLabel": "Wybierz dostawcę", "newKnowledge.providerLoadFailed": "Dostawcy nie mogli załadować", "newKnowledge.providerNotConfigured": "{{provider}} nie jest skonfigurowany", "newKnowledge.providerNotConfiguredDescription": "Dodaj klucz API lub adres URL punktu końcowego, aby indeksować strony internetowe za pomocą {{provider}}.", @@ -369,12 +361,10 @@ "newKnowledge.revisionState.superseded": "Poprzednia", "newKnowledge.rootUrl": "Główny adres URL", "newKnowledge.rootUrlPlaceholder": "https://docs.example.com", - "newKnowledge.routeUnavailable": "Ta sekcja będzie dostępna w następnym kroku konfiguracji.", "newKnowledge.searchDocuments": "Szukaj dokumentów", "newKnowledge.searchSources": "Wyszukaj źródła", "newKnowledge.selectAll": "Zaznacz wszystko", "newKnowledge.selectAllDocuments": "Wybierz wszystkie dokumenty", - "newKnowledge.selectAllSources": "Wybierz wszystkie źródła", "newKnowledge.selectChunk": "Wybierz fragment, aby sprawdzić jego zawartość.", "newKnowledge.selectFilesAndFolders": "Wybierz pliki i foldery do synchronizacji", "newKnowledge.skippedFailed": "Pominięto (błąd)", @@ -384,7 +374,6 @@ "newKnowledge.sourceFilterLabel": "Filtruj źródła według stanu", "newKnowledge.sourceName": "Nazwa źródła", "newKnowledge.sourceNamePlaceholder": "np. skanowanie dokumentacji produktu", - "newKnowledge.sourceNameRequired": "Wprowadź nazwę źródła.", "newKnowledge.sourceSetupBackendDependency": "Konfiguracja źródła jest pokazana w całości; edycja i podgląd wymagają przepływu backendowego źródła roboczego.", "newKnowledge.sourceStatus.active": "Aktywny", "newKnowledge.sourceStatus.disabled": "Wyłączone", @@ -431,7 +420,6 @@ "newKnowledge.unavailableDescription": "KnowledgeFS nie jest obecnie dostępny dla tego obszaru roboczego.", "newKnowledge.unavailableTitle": "Nowa wiedza jest niedostępna", "newKnowledge.updated": "Zaktualizowano {{date}}", - "newKnowledge.updatedAt": "Ostatnia aktualizacja", "newKnowledge.updatedColumn": "Zaktualizowano", "newKnowledge.uploadCharactersUnavailable": "Znaki: —", "newKnowledge.uploadDocuments": "Prześlij dokumenty", @@ -460,11 +448,9 @@ "retrievalSettings": "Ustawienia wyszukiwania", "retry": "Ponów", "selectExternalKnowledgeAPI.placeholder": "Wybieranie interfejsu API wiedzy zewnętrznej", - "serviceApi.card.addApiKey": "Dodaj Klucz API", "serviceApi.card.apiKey": "Klucz API", "serviceApi.card.apiReference": "Dokumentacja API", "serviceApi.card.endpoint": "Punkt końcowy API usługi", - "serviceApi.card.manageApiKey": "Zarządzaj Kluczem API", "serviceApi.card.title": "Usługa backendowa API", "serviceApi.disabled": "Niepełnosprawny", "serviceApi.enabled": "W serwisie", diff --git a/web/i18n/pl-PL/deployments.json b/web/i18n/locales/pl-PL/deployments.json similarity index 97% rename from web/i18n/pl-PL/deployments.json rename to web/i18n/locales/pl-PL/deployments.json index 0ba09b657c3..94991ddb41b 100644 --- a/web/i18n/pl-PL/deployments.json +++ b/web/i18n/locales/pl-PL/deployments.json @@ -27,7 +27,6 @@ "list.emptyTitle": "Brak wdrożeń", "overview.chip.latest": "najnowsze", "status.RUNTIME_INSTANCE_STATUS_DEPLOYING": "Wdrażanie", - "status.RUNTIME_INSTANCE_STATUS_FAILED": "Wdrożenie nie powiodło się", "status.RUNTIME_INSTANCE_STATUS_INVALID": "Nieprawidłowe", "status.RUNTIME_INSTANCE_STATUS_READY": "Uruchomione", "status.RUNTIME_INSTANCE_STATUS_UNDEPLOYED": "Nie wdrożone", @@ -87,6 +86,5 @@ "studio.versionValue": "Wartość wersji", "studio.versionsBehind_one": "{{count}} wersja wstecz", "studio.versionsBehind_other": "{{count}} wersji wstecz", - "versions.checkingReleaseContent": "Sprawdzanie zawartości wydania...", "versions.deployTo": "Wdróż do {{name}}" } diff --git a/web/i18n/pl-PL/device-flow.json b/web/i18n/locales/pl-PL/device-flow.json similarity index 100% rename from web/i18n/pl-PL/device-flow.json rename to web/i18n/locales/pl-PL/device-flow.json diff --git a/web/i18n/pl-PL/education.json b/web/i18n/locales/pl-PL/education.json similarity index 100% rename from web/i18n/pl-PL/education.json rename to web/i18n/locales/pl-PL/education.json diff --git a/web/i18n/pl-PL/explore.json b/web/i18n/locales/pl-PL/explore.json similarity index 100% rename from web/i18n/pl-PL/explore.json rename to web/i18n/locales/pl-PL/explore.json diff --git a/web/i18n/pl-PL/layout.json b/web/i18n/locales/pl-PL/layout.json similarity index 100% rename from web/i18n/pl-PL/layout.json rename to web/i18n/locales/pl-PL/layout.json diff --git a/web/i18n/pl-PL/login.json b/web/i18n/locales/pl-PL/login.json similarity index 100% rename from web/i18n/pl-PL/login.json rename to web/i18n/locales/pl-PL/login.json diff --git a/web/i18n/pl-PL/oauth.json b/web/i18n/locales/pl-PL/oauth.json similarity index 100% rename from web/i18n/pl-PL/oauth.json rename to web/i18n/locales/pl-PL/oauth.json diff --git a/web/i18n/pl-PL/permission-keys.json b/web/i18n/locales/pl-PL/permission-keys.json similarity index 100% rename from web/i18n/pl-PL/permission-keys.json rename to web/i18n/locales/pl-PL/permission-keys.json diff --git a/web/i18n/pl-PL/permission.json b/web/i18n/locales/pl-PL/permission.json similarity index 100% rename from web/i18n/pl-PL/permission.json rename to web/i18n/locales/pl-PL/permission.json diff --git a/web/i18n/pl-PL/pipeline.json b/web/i18n/locales/pl-PL/pipeline.json similarity index 100% rename from web/i18n/pl-PL/pipeline.json rename to web/i18n/locales/pl-PL/pipeline.json diff --git a/web/i18n/pl-PL/plugin-tags.json b/web/i18n/locales/pl-PL/plugin-tags.json similarity index 100% rename from web/i18n/pl-PL/plugin-tags.json rename to web/i18n/locales/pl-PL/plugin-tags.json diff --git a/web/i18n/pl-PL/plugin-trigger.json b/web/i18n/locales/pl-PL/plugin-trigger.json similarity index 100% rename from web/i18n/pl-PL/plugin-trigger.json rename to web/i18n/locales/pl-PL/plugin-trigger.json diff --git a/web/i18n/pl-PL/plugin.json b/web/i18n/locales/pl-PL/plugin.json similarity index 99% rename from web/i18n/pl-PL/plugin.json rename to web/i18n/locales/pl-PL/plugin.json index 1279d1434ac..c81e183d2aa 100644 --- a/web/i18n/pl-PL/plugin.json +++ b/web/i18n/locales/pl-PL/plugin.json @@ -277,7 +277,6 @@ "readmeInfo.noReadmeAvailable": "Brak dostępnego pliku README", "readmeInfo.title": "PRZECZYTAJMNIE", "requestAPlugin": "Poproś o wtyczkę", - "requestSubmit": "Poproś lub opublikuj", "search": "Szukać", "searchCategories": "Kategorie wyszukiwania", "searchInMarketplace": "Wyszukiwanie w Marketplace", diff --git a/web/i18n/pl-PL/register.json b/web/i18n/locales/pl-PL/register.json similarity index 100% rename from web/i18n/pl-PL/register.json rename to web/i18n/locales/pl-PL/register.json diff --git a/web/i18n/pl-PL/run-log.json b/web/i18n/locales/pl-PL/run-log.json similarity index 100% rename from web/i18n/pl-PL/run-log.json rename to web/i18n/locales/pl-PL/run-log.json diff --git a/web/i18n/pl-PL/share.json b/web/i18n/locales/pl-PL/share.json similarity index 100% rename from web/i18n/pl-PL/share.json rename to web/i18n/locales/pl-PL/share.json diff --git a/web/i18n/pl-PL/skill.json b/web/i18n/locales/pl-PL/skill.json similarity index 82% rename from web/i18n/pl-PL/skill.json rename to web/i18n/locales/pl-PL/skill.json index 5e98b5153d7..7f8d1337292 100644 --- a/web/i18n/pl-PL/skill.json +++ b/web/i18n/locales/pl-PL/skill.json @@ -1,5 +1,4 @@ { - "skillManagement.clearTags": "Wyczyść tagi", "skillManagement.create": "Utwórz", "skillManagement.createFailed": "Nie udało się utworzyć umiejętności.", "skillManagement.createSuccess": "Wersja robocza utworzona.", @@ -13,12 +12,9 @@ "skillManagement.deleteFailed": "Nie udało się usunąć umiejętności.", "skillManagement.deleteSuccess": "Skill usunięty.", "skillManagement.detail.addMetadata": "Dodaj metadane", - "skillManagement.detail.addMetadataDescription": "Dodaj pole frontmatter do tego pliku Markdown.", "skillManagement.detail.addTag": "Dodaj tag", - "skillManagement.detail.addTagDescription": "Utwórz lub powiąż tag z tym Skill. Nowe tagi są zapisywane z metadanymi Skill.", "skillManagement.detail.addTagSuccess": "Dodano tag.", "skillManagement.detail.back": "Powrót do Skills", - "skillManagement.detail.builder.applyFailed": "Nie udało się zastosować wersji roboczej aktualizacji.", "skillManagement.detail.builder.attach": "Załącz plik", "skillManagement.detail.builder.attachFailed": "Nie udało się załączyć pliku.", "skillManagement.detail.builder.attachLimit": "Możesz załączyć maksymalnie 10 plików.", @@ -26,22 +22,12 @@ "skillManagement.detail.builder.attachUnsupported": "Załączać można wyłącznie obrazy, teksty i pliki dokumentów.", "skillManagement.detail.builder.attachmentOnlyMessage": "Skorzystaj z załączonego pliku, aby udoskonalić tę umiejętność.", "skillManagement.detail.builder.close": "Zamknij Skill Builder", - "skillManagement.detail.builder.compatibleModelsOnly": "Wyświetlane są tylko kompatybilne modele", "skillManagement.detail.builder.copyResponse": "Skopiuj odpowiedź", "skillManagement.detail.builder.copySuccess": "Odpowiedź skopiowana do schowka.", "skillManagement.detail.builder.editIntro": "Sprawdziłem tę umiejętność. Co chciałbyś dostosować?", "skillManagement.detail.builder.exampleIssueTriage": "Selekcja problemów klientów", "skillManagement.detail.builder.exampleOnboarding": "Przewodnik dotyczący wdrażania nowych pracowników", "skillManagement.detail.builder.exampleSalesFollowUp": "Strategia śledzenia leadów sprzedażowych", - "skillManagement.detail.builder.followUpDisplayName": "Jako nazwy wyświetlanej użyj Zatwierdzenia zwrotu pieniędzy", - "skillManagement.detail.builder.followUpNameIcon": "Zastosuj sugerowaną nazwę i ikonę", - "skillManagement.detail.builder.fromMarketplace": "Z Marketplace", - "skillManagement.detail.builder.model": "GPT-4o", - "skillManagement.detail.builder.modelCredits.all": "Wszystkie kredyty", - "skillManagement.detail.builder.modelCredits.configure": "Wymagana konfiguracja", - "skillManagement.detail.builder.modelCredits.exhausted": "Wyczerpany limit", - "skillManagement.detail.builder.modelProviderSettings": "Ustawienia dostawcy modeli", - "skillManagement.detail.builder.modelSearch": "Szukaj modeli...", "skillManagement.detail.builder.modifyPlaceholder": "Poproś sztuczną inteligencję o zmodyfikowanie tej umiejętności...", "skillManagement.detail.builder.open": "Otwórz Skill Builder", "skillManagement.detail.builder.placeholder": "Opisz scenariusz...", @@ -61,8 +47,6 @@ "skillManagement.detail.builder.thinkingUnavailable": "Model nie zwrócił żadnej treści myślącej.", "skillManagement.detail.builder.title": "Skill Builder", "skillManagement.detail.builder.tryExample": "Spróbuj przykładu", - "skillManagement.detail.builder.voice": "Wprowadzanie głosowe", - "skillManagement.detail.builder.voiceUnavailable": "Wprowadzanie głosowe nie jest jeszcze dostępne.", "skillManagement.detail.cancelAddMetadata": "Anuluj dodawanie metadanych", "skillManagement.detail.closeFileTab": "Zamknij {{name}}", "skillManagement.detail.closeVersions": "Zamknij wersje", @@ -70,14 +54,10 @@ "skillManagement.detail.copyContentSuccess": "Treść skopiowana do schowka.", "skillManagement.detail.copyFile": "Kopiuj", "skillManagement.detail.copyFileSuccess": "Plik gotowy do wklejenia.", - "skillManagement.detail.copyVersionId": "Kopiuj ID", - "skillManagement.detail.copyVersionIdSuccess": "Identyfikator skopiowany do schowka.", "skillManagement.detail.createFile": "Nowy plik", - "skillManagement.detail.createFileDescription": "Wprowadź ścieżkę pliku względem katalogu głównego umiejętności.", "skillManagement.detail.createFileMenu": "Nowy plik...", "skillManagement.detail.createFileSuccess": "Plik został utworzony.", "skillManagement.detail.createFolder": "Nowy folder", - "skillManagement.detail.createFolderDescription": "Wprowadź ścieżkę folderu względem katalogu głównego umiejętności.", "skillManagement.detail.createFolderMenu": "Nowy folder...", "skillManagement.detail.createFolderSuccess": "Folder został utworzony.", "skillManagement.detail.createdBy": "Utworzony przez {{name}}", @@ -100,10 +80,8 @@ "skillManagement.detail.fileMeta": "{{type}} · {{size}} bajtów", "skillManagement.detail.fileMissing": "Nie znaleziono pliku.", "skillManagement.detail.fileOperationFailed": "Operacja na pliku nie powiodła się.", - "skillManagement.detail.files": "Pliki", "skillManagement.detail.latest": "Najnowszy", "skillManagement.detail.loadFailed": "Nie udało się załadować umiejętności.", - "skillManagement.detail.loadingTags": "Ładowanie tagów...", "skillManagement.detail.markdownLiveMode": "Podgląd na żywo", "skillManagement.detail.markdownSourceMode": "Redaktor źródła", "skillManagement.detail.metadataKey": "nazwa", @@ -113,10 +91,7 @@ "skillManagement.detail.nameThisVersion": "Nazwij tę wersję", "skillManagement.detail.noFileSelected": "Wybierz plik do podglądu.", "skillManagement.detail.noFiles": "Brak plików.", - "skillManagement.detail.noSearchResults": "Brak pasujących plików.", - "skillManagement.detail.noSelectableTags": "Brak dostępnych tagów.", "skillManagement.detail.noVersions": "Nie ma jeszcze opublikowanych wersji.", - "skillManagement.detail.pasteFile": "Pasta", "skillManagement.detail.pasteFileSuccess": "Plik wklejony.", "skillManagement.detail.previewUnsupported": "Ten plik nie obsługuje podglądu.", "skillManagement.detail.publish": "Opublikuj", @@ -128,9 +103,7 @@ "skillManagement.detail.publishUpdate": "Opublikuj aktualizację", "skillManagement.detail.published": "Opublikowane", "skillManagement.detail.publishedAt": "Opublikowano {{time}}", - "skillManagement.detail.publishedVersion": "Opublikowano #{{number}}", "skillManagement.detail.publishing": "Publikowanie...", - "skillManagement.detail.readonly": "Tylko do odczytu", "skillManagement.detail.referenceFiles.confirm": "Wprowadź Potwierdź", "skillManagement.detail.referenceFiles.empty": "Brak dostępnych plików.", "skillManagement.detail.referenceFiles.livePlaceholder": "Napisz instrukcje dla agenta. Wpisz /, aby odwołać się do plików.", @@ -138,14 +111,10 @@ "skillManagement.detail.referenceFiles.title": "Pliki referencyjne", "skillManagement.detail.referencedBy_one": "Używany przez {{count}} agenta", "skillManagement.detail.referencedBy_other": "Używany przez {{count}} agentów", - "skillManagement.detail.removeMetadata": "Usuń metadane {{name}}", "skillManagement.detail.removeTag": "Usuń {{tag}}", "skillManagement.detail.removeTagSuccess": "Etykieta usunięta.", - "skillManagement.detail.renameFile": "Zmień nazwę pliku", - "skillManagement.detail.renameFileDescription": "Wprowadź nową ścieżkę względem korzenia umiejętności.", "skillManagement.detail.renameFileSuccess": "Zmieniono nazwę pliku.", "skillManagement.detail.renameSkillSuccess": "Zmieniono nazwę Skill.", - "skillManagement.detail.renameVersion": "Zmień nazwę", "skillManagement.detail.renameVersionFailed": "Nie udało się zmienić nazwy wersji.", "skillManagement.detail.renameVersionPrompt": "Nazwa wersji", "skillManagement.detail.renameVersionSuccess": "Zmieniono nazwę wersji.", @@ -155,17 +124,14 @@ "skillManagement.detail.restoreVersionConfirmTitle": "Przywrócić tę wersję?", "skillManagement.detail.restoreVersionFailed": "Nie udało się przywrócić wersji.", "skillManagement.detail.restoreVersionSuccess": "Wersja przywrócona do wersji roboczej.", - "skillManagement.detail.save": "Zapisz", "skillManagement.detail.saveConflictConfirmDescription": "Ta umiejętność została zaktualizowana w innym miejscu. Załaduj najnowszą wersję roboczą, aby kontynuować edycję. Twoje obecne zmiany zostaną odrzucone.", "skillManagement.detail.saveConflictConfirmTitle": "Załadować najnowszą wersję roboczą?", "skillManagement.detail.saveConflictReload": "Załaduj najnowszą wersję roboczą", "skillManagement.detail.saveConflictStatus": "Wykryto konflikt. Załaduj najnowszą wersję roboczą, aby kontynuować edycję.", "skillManagement.detail.saveFailed": "Nie udało się zapisać pliku.", - "skillManagement.detail.saveSuccess": "Plik został zapisany.", "skillManagement.detail.saved": "Zapisane", "skillManagement.detail.savedAt": "Zapisano {{time}}", "skillManagement.detail.saving": "Zapisywanie...", - "skillManagement.detail.searchFiles": "Szukaj plików", "skillManagement.detail.showMoreReferences": "Pokaż więcej {{count}}", "skillManagement.detail.skillDescriptionPlaceholder": "Opisz, do czego służy ta umiejętność i kiedy agenci powinni z niej korzystać.", "skillManagement.detail.skillNamePlaceholder": "Używaj małych liter, cyfr i łączników.", @@ -177,16 +143,12 @@ "skillManagement.detail.uploadConflict": "Plik o tej nazwie już istnieje.", "skillManagement.detail.uploadFailureSummary": "Pliki {{failed}} lub {{total}} nie powiodły się", "skillManagement.detail.uploadFailureTitle": "Nie udało się przesłać niektórych plików", - "skillManagement.detail.uploadFile": "Prześlij plik", - "skillManagement.detail.uploadFileFailed": "Przesyłanie pliku nie powiodło się.", "skillManagement.detail.uploadFileSuccess": "Plik przesłany.", "skillManagement.detail.uploadFilesButton": "Prześlij pliki {{count}}", "skillManagement.detail.uploadFilesFailedStatus_one": "Przesyłanie pliku {{count}} nie powiodło się.", "skillManagement.detail.uploadFilesFailedStatus_other": "Przesyłanie pliku {{count}} nie powiodło się.", "skillManagement.detail.uploadFilesMenu": "Prześlij pliki...", "skillManagement.detail.uploadFilesProgress": "Przesyłanie {{completed}}/{{total}}", - "skillManagement.detail.uploadFilesResult": "Przesłano {{uploaded}} · {{failed}} nie powiodło się", - "skillManagement.detail.uploadFilesStatus": "Prześlij stan", "skillManagement.detail.uploadInvalidExtension": "Ten typ pliku nie jest obsługiwany.", "skillManagement.detail.uploadInvalidName": "Nieprawidłowa nazwa.", "skillManagement.detail.uploadInvalidPath": "Nieprawidłowa ścieżka pliku.", @@ -206,7 +168,6 @@ "skillManagement.detail.uploadWillReplace": "Zastąpi istniejący plik", "skillManagement.detail.uploadWillUseName": "Przesłane jako {{name}}", "skillManagement.detail.versionHistory": "Otwórz historię wersji", - "skillManagement.detail.versionMeta": "{{hash}} · {{time}}", "skillManagement.detail.versionPublishNote": "Informacje o wydaniu", "skillManagement.detail.versionPublishNotePlaceholder": "Opisz tę zmianę", "skillManagement.detail.versionPublishedMeta": "{{time}} · {{name}}", @@ -227,8 +188,6 @@ "skillManagement.errors.missingSkillMd": "Ten pakiet nie zawiera SKILL.md. Dodaj plik i zaimportuj go ponownie.", "skillManagement.errors.nameConflict": "Umiejętność o nazwie „{{name}}” już istnieje. Zmień jego nazwę na SKILL.md i zaimportuj ponownie.", "skillManagement.errors.workspaceLimit": "Twój obszar roboczy osiągnął limit 500 umiejętności. Usuń umiejętność, aby dodać nową.", - "skillManagement.exportDialog.description": "Zostanie wyeksportowany aktualnie zapisany szkic umiejętności. Jeśli w edytorze są niezapisane zmiany, zapisz je przed eksportem.", - "skillManagement.exportDialog.title": "Eksportuj aktualnie zapisany szkic", "skillManagement.import": "Importuj", "skillManagement.importFailed": "Nie udało się zaimportować umiejętności.", "skillManagement.importSuccess": "Skill zaimportowano.", @@ -236,12 +195,10 @@ "skillManagement.loadingError": "Nie udało się załadować umiejętności", "skillManagement.moreActions": "Więcej akcji dla {{name}}", "skillManagement.noDescription": "Nie ma jeszcze opisu", - "skillManagement.noTags": "Brak tagów", "skillManagement.publishedAt": "Opublikowano {{time}}", "skillManagement.referenceCount_one": "{{count}} agent", "skillManagement.referenceCount_other": "{{count}} agentów", "skillManagement.searchLabel": "Szukaj umiejętności", "skillManagement.searchPlaceholder": "Szukaj według nazwy lub opisu", - "skillManagement.tags": "Tagi", "skillManagement.title": "Umiejętności" } diff --git a/web/i18n/pl-PL/snippet.json b/web/i18n/locales/pl-PL/snippet.json similarity index 100% rename from web/i18n/pl-PL/snippet.json rename to web/i18n/locales/pl-PL/snippet.json diff --git a/web/i18n/pl-PL/time.json b/web/i18n/locales/pl-PL/time.json similarity index 100% rename from web/i18n/pl-PL/time.json rename to web/i18n/locales/pl-PL/time.json diff --git a/web/i18n/pl-PL/tools.json b/web/i18n/locales/pl-PL/tools.json similarity index 100% rename from web/i18n/pl-PL/tools.json rename to web/i18n/locales/pl-PL/tools.json diff --git a/web/i18n/pl-PL/workflow.json b/web/i18n/locales/pl-PL/workflow.json similarity index 99% rename from web/i18n/pl-PL/workflow.json rename to web/i18n/locales/pl-PL/workflow.json index 715a03d78d4..4a94c08a052 100644 --- a/web/i18n/pl-PL/workflow.json +++ b/web/i18n/locales/pl-PL/workflow.json @@ -460,8 +460,6 @@ "nodes.agent.pluginNotInstalledDesc": "Ta integracja jest instalowana z GitHub. Przejdź do Integracje, aby ponownie zainstalować", "nodes.agent.roster.change": "Zmień", "nodes.agent.roster.editInConsole": "Edytuj w Agents", - "nodes.agent.roster.editInConsoleDisabled": "Edytowanie agentów wymaga roli Edytor lub wyższej.", - "nodes.agent.roster.editInConsoleDisabledRbac": "Tylko członkowie z rolą Edytor lub wyższą albo uprawnieniem Zarządzaj Agents mogą edytować agentów.", "nodes.agent.roster.inlineSetup.description": "Jednorazowa konfiguracja istniejąca tylko w tym węźle", "nodes.agent.roster.inlineSetup.name": "Konfiguracja inline", "nodes.agent.roster.inlineSetup.type": "Tylko w tym węźle", diff --git a/web/i18n/pt-BR/agent-v-2.json b/web/i18n/locales/pt-BR/agent-v-2.json similarity index 98% rename from web/i18n/pt-BR/agent-v-2.json rename to web/i18n/locales/pt-BR/agent-v-2.json index 309448a5f77..142f047eb1a 100644 --- a/web/i18n/pt-BR/agent-v-2.json +++ b/web/i18n/locales/pt-BR/agent-v-2.json @@ -69,6 +69,7 @@ "agentDetail.configure.answer.activity.runningCommands": "Executando comandos", "agentDetail.configure.answer.duration.minute": "{{count}} min", "agentDetail.configure.answer.duration.second": "{{count}} s", + "agentDetail.configure.answer.responseEnded": "A resposta terminou", "agentDetail.configure.answer.thinking": "Pensando", "agentDetail.configure.build.empty.communityEditionTip": "A Community Edition, embora ofereça versionamento para as configurações extraídas, não oferece suporte a versionamento do próprio sistema de arquivos. Tenha cuidado com suas ações no modo Build, pois as alterações feitas no sistema de arquivos acontecem em tempo real e nem sempre podem ser revertidas de forma limpa. A Community Edition não é a escolha ideal para atender grandes públicos externos.", "agentDetail.configure.build.empty.description": "Descreva o que você quer e o formulário à esquerda será preenchido conforme a conversa avança.", @@ -269,7 +270,6 @@ "agentDetail.configure.workingDirectory.breadcrumbLabel": "Caminho do sistema de arquivos", "agentDetail.configure.workingDirectory.description": "Arquivos que o agente lê e grava enquanto executa. Tudo o que ele gera, como qna_report.pdf, aparece aqui.", "agentDetail.configure.workingDirectory.fileSystem": "Sistema de arquivos", - "agentDetail.configure.workingDirectory.open": "Abrir diretório de trabalho", "agentDetail.configure.workingDirectory.persistentFiles": "Persistentes", "agentDetail.configure.workingDirectory.persistentFilesTooltip": "Arquivos que o agente mantém e pode usar em qualquer lugar em que seja executado, incluindo conversas de usuários finais e fluxos de trabalho. As alterações feitas aqui durante este chat de criação entram em vigor quando você clica em “Aplicar”.", "agentDetail.configure.workingDirectory.temporaryFiles": "Temporários", @@ -348,7 +348,6 @@ "agentDetail.sections.logs": "Logs", "agentDetail.sections.monitoring": "Monitoramento", "agentDetail.title": "Agente", - "agentDetail.type": "AGENTE", "agentDetail.versionHistory.empty": "Ainda não há versões", "agentDetail.versionHistory.exitVersions": "Sair das versões", "agentDetail.versionHistory.filter": "Filtrar versões", @@ -408,8 +407,5 @@ "roster.sort.lastModified": "Última modificação", "roster.sort.recentlyCreated": "Criados recentemente", "roster.title": "Agentes", - "roster.usageStatus.draft": "Rascunho", - "skillManagement.detail.uploadFilesFailedStatus_one": "Falha no envio de {{count}} arquivo.", - "skillManagement.detail.uploadFilesFailedStatus_other": "Falha no envio de {{count}} arquivos.", - "skillManagement.title": "Skills" + "roster.usageStatus.draft": "Rascunho" } diff --git a/web/i18n/pt-BR/app-annotation.json b/web/i18n/locales/pt-BR/app-annotation.json similarity index 100% rename from web/i18n/pt-BR/app-annotation.json rename to web/i18n/locales/pt-BR/app-annotation.json diff --git a/web/i18n/pt-BR/app-api.json b/web/i18n/locales/pt-BR/app-api.json similarity index 100% rename from web/i18n/pt-BR/app-api.json rename to web/i18n/locales/pt-BR/app-api.json diff --git a/web/i18n/pt-BR/app-debug.json b/web/i18n/locales/pt-BR/app-debug.json similarity index 100% rename from web/i18n/pt-BR/app-debug.json rename to web/i18n/locales/pt-BR/app-debug.json diff --git a/web/i18n/pt-BR/app-log.json b/web/i18n/locales/pt-BR/app-log.json similarity index 97% rename from web/i18n/pt-BR/app-log.json rename to web/i18n/locales/pt-BR/app-log.json index b6ed9b4a7db..a690149d350 100644 --- a/web/i18n/pt-BR/app-log.json +++ b/web/i18n/locales/pt-BR/app-log.json @@ -4,11 +4,9 @@ "agentLogDetail.iteration": "Iteração", "agentLogDetail.iterations": "Iterações", "agentLogDetail.toolUsed": "Ferramenta usada", - "archives.action.downloadMonth": "Baixar arquivo de {{month}}", "archives.action.downloadReady": "O download do arquivo está pronto.", "archives.action.prepareDownload": "Preparar download", "archives.action.prepareFailed": "Falha ao preparar o download do arquivo.", - "archives.action.prepareMonth": "Preparar download do arquivo de {{month}}", "archives.action.prepareStarted": "O download do arquivo está sendo preparado.", "archives.action.preparing": "Preparando", "archives.description": "Baixe exportações CSV mensais dos logs arquivados de execuções de workflow neste workspace.", diff --git a/web/i18n/pt-BR/app-overview.json b/web/i18n/locales/pt-BR/app-overview.json similarity index 100% rename from web/i18n/pt-BR/app-overview.json rename to web/i18n/locales/pt-BR/app-overview.json diff --git a/web/i18n/pt-BR/app.json b/web/i18n/locales/pt-BR/app.json similarity index 100% rename from web/i18n/pt-BR/app.json rename to web/i18n/locales/pt-BR/app.json diff --git a/web/i18n/pt-BR/billing.json b/web/i18n/locales/pt-BR/billing.json similarity index 100% rename from web/i18n/pt-BR/billing.json rename to web/i18n/locales/pt-BR/billing.json diff --git a/web/i18n/pt-BR/common.json b/web/i18n/locales/pt-BR/common.json similarity index 98% rename from web/i18n/pt-BR/common.json rename to web/i18n/locales/pt-BR/common.json index f3bd441e5d1..089f40bda76 100644 --- a/web/i18n/pt-BR/common.json +++ b/web/i18n/locales/pt-BR/common.json @@ -209,9 +209,7 @@ "mainNav.marketplace": "Marketplace", "mainNav.skills": "Skills", "mainNav.webApps.noResults": "Nenhum app web encontrado", - "mainNav.webApps.openApp": "Abrir o app web {{name}}", "mainNav.webApps.searchPlaceholder": "Pesquisar apps web", - "mainNav.workspace.credits": "{{count}} créditos", "mainNav.workspace.creditsUnit": "créditos", "mainNav.workspace.inviteMembers": "Invite members", "mainNav.workspace.noResults": "Nenhum workspace encontrado", @@ -233,7 +231,6 @@ "members.datasetOperatorTip": "Só pode gerenciar a base de dados de conhecimento", "members.editRole": "Editar Função", "members.editorTip": "Pode criar e editar apps", - "members.email": "E-mail", "members.emailInvalid": "Formato de e-mail inválido", "members.emailNotSetup": "O servidor de e-mail não está configurado, então os e-mails de convite não podem ser enviados. Por favor, notifique os usuários sobre o link de convite que será emitido após o convite.", "members.emailPlaceholder": "Digite ou cole endereços de e-mail…", @@ -250,7 +247,6 @@ "members.inviteLimitExceeded": "Não há vagas suficientes para todos os destinatários.", "members.inviteTeamMember": "Adicionar membro da equipe", "members.inviteTeamMemberTip": "Eles podem acessar os dados da sua equipe diretamente após fazer login.", - "members.invitedAsRole": "Convidado como {{role}}", "members.lastActive": "ÚLTIMA ATIVIDADE", "members.memberActions": "Ações do membro", "members.memberDetails.assign": "Atribuir", @@ -258,8 +254,6 @@ "members.memberDetails.assignedRoles": "Funções Atribuídas", "members.memberDetails.customGroup": "PERSONALIZADO", "members.memberDetails.generalGroup": "GERAL", - "members.memberDetails.openAria": "Abrir detalhes do membro de {{name}}", - "members.memberDetails.roleActionsAria": "Abrir ações para a função {{role}}", "members.memberDetails.roleNoPermissionSummary": "A função atual não possui permissões.", "members.memberDetails.rolePermissionSummary": "{{role}} pode {{permissions}}", "members.memberDetails.title": "Detalhes do Membro", @@ -370,7 +364,6 @@ "modelProvider.card.usagePriorityTip": "Defina qual recurso usar primeiro ao executar modelos.", "modelProvider.configureTip": "Configure a chave de API ou adicione o modelo a ser usado", "modelProvider.confirmDelete": "confirmar exclusão?", - "modelProvider.credits": "Créditos de Mensagem", "modelProvider.defaultConfig": "Configuração padrão", "modelProvider.discoverMore": "Descubra mais em", "modelProvider.embeddingModel.key": "Modelo de Incorporação", @@ -427,7 +420,6 @@ "modelProvider.selector.noProviderConfiguredDesc": "Navegue pelo Marketplace para instalar um, ou configure provedores nas configurações.", "modelProvider.selector.onlyCompatibleModelsShown": "Apenas modelos compatíveis são exibidos", "modelProvider.selector.showIncompatibleModels": "Mostrar modelos incompatíveis", - "modelProvider.selector.suggestion": "Sugestão", "modelProvider.selector.suggestionTip": "Recomendado para tarefas de agente", "modelProvider.showModels": "Mostrar Modelos", "modelProvider.speechToTextModel.key": "Modelo de Fala para Texto", @@ -458,6 +450,8 @@ "operation.confirmAction": "Por favor, confirme sua ação.", "operation.copied": "Copiado", "operation.copy": "Copiar", + "operation.copyErrorDetails": "Copiar detalhes do erro", + "operation.copyErrorFailed": "Falha ao copiar. Tentar novamente", "operation.copyImage": "Copiar imagem", "operation.create": "Criar", "operation.deSelectAll": "Desmarcar tudo", @@ -475,7 +469,6 @@ "operation.format": "Formato", "operation.imageCopied": "Imagem copiada", "operation.imageCopyFailed": "Não foi possível copiar a imagem. Baixe-a em vez disso.", - "operation.imageDownloaded": "Imagem baixada", "operation.in": "em", "operation.learnMore": "Saiba mais", "operation.log": "Log", @@ -512,7 +505,6 @@ "operation.sure": "Tenho certeza", "operation.toggleFullscreen": "Alternar tela cheia", "operation.toggleMute": "Alternar mudo", - "operation.upload": "Upload", "operation.view": "Vista", "operation.viewDetails": "Ver detalhes", "operation.viewMore": "VER MAIS", @@ -542,7 +534,6 @@ "promptEditor.requestURL.item.title": "URL de Solicitação", "promptEditor.variable.modal.add": "Nova variável", "promptEditor.variable.modal.addTool": "Nova ferramenta", - "provider.anthropicHosted.trialQuotaTip": "Sua cota de teste do Anthropic expirará em 11/03/2025 e não estará mais disponível depois disso. Por favor, use-o a tempo.", "provider.encrypted.back": " tecnologia.", "provider.encrypted.front": "Sua CHAVE DA API será criptografada e armazenada usando", "provider.validating": "Validando chave...", @@ -565,7 +556,6 @@ "settings.preferences": "Preferences", "settings.provider": "Fornecedor de modelo", "settings.resourceAccess": "Acesso a Recursos", - "settings.resourceAccessDescription": "Configure regras de permissão que aplicativos e bases de conhecimento podem usar. As regras de permissão podem ser atribuídas a membros em configurações específicas de acesso a recursos.", "settings.rolesAndPermissions": "Funções e Permissões", "settings.settings": "Settings", "settings.swaggerAPIAsTool": "Swagger API as Tool", @@ -695,7 +685,6 @@ "userProfile.helpCenter": "Ajuda", "userProfile.logout": "Sair", "userProfile.roadmap": "Roteiro", - "userProfile.settings": "Configurações", "userProfile.workspace": "Espaço de trabalho", "voice.language.arTN": "Árabe Tunisiano", "voice.language.deDE": "Alemão", diff --git a/web/i18n/pt-BR/custom.json b/web/i18n/locales/pt-BR/custom.json similarity index 100% rename from web/i18n/pt-BR/custom.json rename to web/i18n/locales/pt-BR/custom.json diff --git a/web/i18n/pt-BR/dataset-creation.json b/web/i18n/locales/pt-BR/dataset-creation.json similarity index 100% rename from web/i18n/pt-BR/dataset-creation.json rename to web/i18n/locales/pt-BR/dataset-creation.json diff --git a/web/i18n/pt-BR/dataset-documents.json b/web/i18n/locales/pt-BR/dataset-documents.json similarity index 100% rename from web/i18n/pt-BR/dataset-documents.json rename to web/i18n/locales/pt-BR/dataset-documents.json diff --git a/web/i18n/pt-BR/dataset-hit-testing.json b/web/i18n/locales/pt-BR/dataset-hit-testing.json similarity index 100% rename from web/i18n/pt-BR/dataset-hit-testing.json rename to web/i18n/locales/pt-BR/dataset-hit-testing.json diff --git a/web/i18n/pt-BR/dataset-pipeline.json b/web/i18n/locales/pt-BR/dataset-pipeline.json similarity index 100% rename from web/i18n/pt-BR/dataset-pipeline.json rename to web/i18n/locales/pt-BR/dataset-pipeline.json diff --git a/web/i18n/pt-BR/dataset-settings.json b/web/i18n/locales/pt-BR/dataset-settings.json similarity index 100% rename from web/i18n/pt-BR/dataset-settings.json rename to web/i18n/locales/pt-BR/dataset-settings.json diff --git a/web/i18n/pt-BR/dataset.json b/web/i18n/locales/pt-BR/dataset.json similarity index 97% rename from web/i18n/pt-BR/dataset.json rename to web/i18n/locales/pt-BR/dataset.json index d4da87f13ce..edc0af02856 100644 --- a/web/i18n/pt-BR/dataset.json +++ b/web/i18n/locales/pt-BR/dataset.json @@ -163,7 +163,6 @@ "newKnowledge.configureProvider": "Configurar {{provider}}", "newKnowledge.connectNotion": "Conectar o Notion", "newKnowledge.connectProvider": "Conectar {{provider}}", - "newKnowledge.connectProviderGeneric": "Conectar provedor", "newKnowledge.connectSource": "Conectar uma fonte", "newKnowledge.connectSourceDescription": "O conteúdo permanece sincronizado com Notion, sites e outros serviços.", "newKnowledge.connectingProvider": "Conectando…", @@ -179,18 +178,14 @@ "newKnowledge.crawlFailedDescription": "O rastreador não conseguiu concluir esta visualização. Verifique a URL, ajuste as opções de rastreamento ou tente novamente.", "newKnowledge.crawlFailedProvider": "O {{provider}} informou um erro. Verifique a conexão do provedor e tente novamente.", "newKnowledge.crawlFailedTimeout": "O tempo limite do rastreamento expirou. Tente uma URL mais específica ou reduza o número máximo de páginas.", - "newKnowledge.crawlNotStarted": "Não iniciado", "newKnowledge.crawlOptions": "Opções de rastreamento", "newKnowledge.crawlPreview": "Prévia do rastreamento", "newKnowledge.crawlProgress": "Progresso do rastreamento de {{host}}", - "newKnowledge.crawlSetupUnavailableDescription": "A configuração de rastreamento de sites será desativada até que o fluxo de trabalho de rastreamento esteja disponível.", - "newKnowledge.crawlSetupUnavailableTitle": "O Firecrawl está pronto", "newKnowledge.crawlStartFailed": "Não foi possível iniciar a visualização do rastreamento. Tente novamente.", "newKnowledge.crawlStopped": "Rastreamento interrompido", "newKnowledge.crawling": "Rastreando…", "newKnowledge.crawlingPages": "Rastreando {{host}}… {{count}} páginas encontradas", "newKnowledge.createFailed": "Não foi possível criar esta base de conhecimento. Verifique seu acesso e tente novamente.", - "newKnowledge.createIllustrationTitle": "Conecte seu conteúdo uma vez — cada aplicativo responde com o conhecimento mais atualizado.", "newKnowledge.createTitle": "Crie Conhecimento", "newKnowledge.createdAt": "Criado", "newKnowledge.creators": "Criadores", @@ -220,7 +215,6 @@ "newKnowledge.documentColumn": "Documento", "newKnowledge.documentContentIncomplete": "Carregando o restante do conteúdo do documento…", "newKnowledge.documentContents": "Conteúdo", - "newKnowledge.documentFacts": "Detalhes do documento", "newKnowledge.documentFilterLabel": "Filtrar documentos por status", "newKnowledge.documentLoadErrorDescription": "Não foi possível carregar este documento. Tente novamente.", "newKnowledge.documentLoadErrorTitle": "Não foi possível carregar o documento", @@ -268,7 +262,6 @@ "newKnowledge.documentsSelected": "Documentos {{count}} selecionados", "newKnowledge.documentsUnavailable": "— documentos", "newKnowledge.downloadDocuments": "Baixar", - "newKnowledge.editSource": "Editar", "newKnowledge.emptyChunk": "Trecho vazio", "newKnowledge.emptyDescription": "Adicione seus documentos para que os agentes possam lê-los diretamente, sem configurar a indexação.", "newKnowledge.emptyTitle": "Uma forma mais inteligente de criar bases de conhecimento", @@ -344,7 +337,6 @@ "newKnowledge.processingTaskState.succeeded": "Concluído", "newKnowledge.processingTaskState.superseded": "Substituído", "newKnowledge.providerConnected": "{{provider}} conectado", - "newKnowledge.providerLabel": "Selecione um provedor", "newKnowledge.providerLoadFailed": "Os provedores não conseguiram carregar", "newKnowledge.providerNotConfigured": "{{provider}} não está configurado", "newKnowledge.providerNotConfiguredDescription": "Adicione uma chave de API ou URL de endpoint para rastrear sites com {{provider}}.", @@ -369,12 +361,10 @@ "newKnowledge.revisionState.superseded": "Anterior", "newKnowledge.rootUrl": "URL raiz", "newKnowledge.rootUrlPlaceholder": "https://docs.example.com", - "newKnowledge.routeUnavailable": "Esta seção estará disponível na próxima etapa de configuração.", "newKnowledge.searchDocuments": "Pesquisar documentos", "newKnowledge.searchSources": "Fontes de pesquisa", "newKnowledge.selectAll": "Selecionar tudo", "newKnowledge.selectAllDocuments": "Selecione todos os documentos", - "newKnowledge.selectAllSources": "Selecionar todas as origens", "newKnowledge.selectChunk": "Selecione um trecho para inspecionar seu conteúdo.", "newKnowledge.selectFilesAndFolders": "Selecione arquivos e pastas para sincronizar", "newKnowledge.skippedFailed": "Ignorado (falha)", @@ -384,7 +374,6 @@ "newKnowledge.sourceFilterLabel": "Filtrar fontes por status", "newKnowledge.sourceName": "Nome da fonte", "newKnowledge.sourceNamePlaceholder": "ex.: rastreamento da documentação do produto", - "newKnowledge.sourceNameRequired": "Insira um nome para a fonte.", "newKnowledge.sourceSetupBackendDependency": "A configuração da origem é exibida por completo; a edição e a prévia exigem o fluxo de backend da origem em rascunho.", "newKnowledge.sourceStatus.active": "Ativo", "newKnowledge.sourceStatus.disabled": "Desativado", @@ -431,7 +420,6 @@ "newKnowledge.unavailableDescription": "O KnowledgeFS não está disponível para este espaço de trabalho no momento.", "newKnowledge.unavailableTitle": "O Novo Conhecimento não está disponível", "newKnowledge.updated": "Atualizado em {{date}}", - "newKnowledge.updatedAt": "Última atualização", "newKnowledge.updatedColumn": "Atualizado", "newKnowledge.uploadCharactersUnavailable": "Caracteres: —", "newKnowledge.uploadDocuments": "Carregar documentos", @@ -460,11 +448,9 @@ "retrievalSettings": "Configurações de Recuperação", "retry": "Tentar novamente", "selectExternalKnowledgeAPI.placeholder": "Escolher uma API de conhecimento externa", - "serviceApi.card.addApiKey": "Adicionar Chave de API", "serviceApi.card.apiKey": "Chave de API", "serviceApi.card.apiReference": "Referência da API", "serviceApi.card.endpoint": "Endpoint da API de Serviço", - "serviceApi.card.manageApiKey": "Gerenciar Chave de API", "serviceApi.card.title": "API de serviço de backend", "serviceApi.disabled": "Desativado", "serviceApi.enabled": "Em serviço", diff --git a/web/i18n/pt-BR/deployments.json b/web/i18n/locales/pt-BR/deployments.json similarity index 97% rename from web/i18n/pt-BR/deployments.json rename to web/i18n/locales/pt-BR/deployments.json index 8818f88bb72..966a694db54 100644 --- a/web/i18n/pt-BR/deployments.json +++ b/web/i18n/locales/pt-BR/deployments.json @@ -27,7 +27,6 @@ "list.emptyTitle": "Nenhuma implantação ainda", "overview.chip.latest": "mais recente", "status.RUNTIME_INSTANCE_STATUS_DEPLOYING": "Implantando", - "status.RUNTIME_INSTANCE_STATUS_FAILED": "Falha na implantação", "status.RUNTIME_INSTANCE_STATUS_INVALID": "Inválido", "status.RUNTIME_INSTANCE_STATUS_READY": "Em execução", "status.RUNTIME_INSTANCE_STATUS_UNDEPLOYED": "Não implantado", @@ -87,6 +86,5 @@ "studio.versionValue": "Valor da versão", "studio.versionsBehind_one": "{{count}} versão atrás", "studio.versionsBehind_other": "{{count}} versões atrás", - "versions.checkingReleaseContent": "Verificando o conteúdo da versão...", "versions.deployTo": "Implantar em {{name}}" } diff --git a/web/i18n/pt-BR/device-flow.json b/web/i18n/locales/pt-BR/device-flow.json similarity index 100% rename from web/i18n/pt-BR/device-flow.json rename to web/i18n/locales/pt-BR/device-flow.json diff --git a/web/i18n/pt-BR/education.json b/web/i18n/locales/pt-BR/education.json similarity index 100% rename from web/i18n/pt-BR/education.json rename to web/i18n/locales/pt-BR/education.json diff --git a/web/i18n/pt-BR/explore.json b/web/i18n/locales/pt-BR/explore.json similarity index 100% rename from web/i18n/pt-BR/explore.json rename to web/i18n/locales/pt-BR/explore.json diff --git a/web/i18n/pt-BR/layout.json b/web/i18n/locales/pt-BR/layout.json similarity index 100% rename from web/i18n/pt-BR/layout.json rename to web/i18n/locales/pt-BR/layout.json diff --git a/web/i18n/pt-BR/login.json b/web/i18n/locales/pt-BR/login.json similarity index 100% rename from web/i18n/pt-BR/login.json rename to web/i18n/locales/pt-BR/login.json diff --git a/web/i18n/pt-BR/oauth.json b/web/i18n/locales/pt-BR/oauth.json similarity index 100% rename from web/i18n/pt-BR/oauth.json rename to web/i18n/locales/pt-BR/oauth.json diff --git a/web/i18n/pt-BR/permission-keys.json b/web/i18n/locales/pt-BR/permission-keys.json similarity index 100% rename from web/i18n/pt-BR/permission-keys.json rename to web/i18n/locales/pt-BR/permission-keys.json diff --git a/web/i18n/pt-BR/permission.json b/web/i18n/locales/pt-BR/permission.json similarity index 100% rename from web/i18n/pt-BR/permission.json rename to web/i18n/locales/pt-BR/permission.json diff --git a/web/i18n/pt-BR/pipeline.json b/web/i18n/locales/pt-BR/pipeline.json similarity index 100% rename from web/i18n/pt-BR/pipeline.json rename to web/i18n/locales/pt-BR/pipeline.json diff --git a/web/i18n/pt-BR/plugin-tags.json b/web/i18n/locales/pt-BR/plugin-tags.json similarity index 100% rename from web/i18n/pt-BR/plugin-tags.json rename to web/i18n/locales/pt-BR/plugin-tags.json diff --git a/web/i18n/pt-BR/plugin-trigger.json b/web/i18n/locales/pt-BR/plugin-trigger.json similarity index 100% rename from web/i18n/pt-BR/plugin-trigger.json rename to web/i18n/locales/pt-BR/plugin-trigger.json diff --git a/web/i18n/pt-BR/plugin.json b/web/i18n/locales/pt-BR/plugin.json similarity index 99% rename from web/i18n/pt-BR/plugin.json rename to web/i18n/locales/pt-BR/plugin.json index e9c921a6e2c..0dec95b5a6f 100644 --- a/web/i18n/pt-BR/plugin.json +++ b/web/i18n/locales/pt-BR/plugin.json @@ -277,7 +277,6 @@ "readmeInfo.noReadmeAvailable": "Nenhum README disponível", "readmeInfo.title": "LEIA-ME", "requestAPlugin": "Solicitar um plugin", - "requestSubmit": "Solicitar ou publicar", "search": "Procurar", "searchCategories": "Categorias de pesquisa", "searchInMarketplace": "Pesquisar no Marketplace", diff --git a/web/i18n/pt-BR/register.json b/web/i18n/locales/pt-BR/register.json similarity index 100% rename from web/i18n/pt-BR/register.json rename to web/i18n/locales/pt-BR/register.json diff --git a/web/i18n/pt-BR/run-log.json b/web/i18n/locales/pt-BR/run-log.json similarity index 100% rename from web/i18n/pt-BR/run-log.json rename to web/i18n/locales/pt-BR/run-log.json diff --git a/web/i18n/pt-BR/share.json b/web/i18n/locales/pt-BR/share.json similarity index 100% rename from web/i18n/pt-BR/share.json rename to web/i18n/locales/pt-BR/share.json diff --git a/web/i18n/pt-BR/skill.json b/web/i18n/locales/pt-BR/skill.json similarity index 82% rename from web/i18n/pt-BR/skill.json rename to web/i18n/locales/pt-BR/skill.json index 9b5b1d995e0..9e8d9075739 100644 --- a/web/i18n/pt-BR/skill.json +++ b/web/i18n/locales/pt-BR/skill.json @@ -1,5 +1,4 @@ { - "skillManagement.clearTags": "Limpar tags", "skillManagement.create": "Criar", "skillManagement.createFailed": "Falha ao criar habilidade.", "skillManagement.createSuccess": "Rascunho criado.", @@ -13,12 +12,9 @@ "skillManagement.deleteFailed": "Falha ao excluir habilidade.", "skillManagement.deleteSuccess": "Skill excluído.", "skillManagement.detail.addMetadata": "Adicionar metadados", - "skillManagement.detail.addMetadataDescription": "Adicione um campo frontmatter a este arquivo Markdown.", "skillManagement.detail.addTag": "Adicionar etiqueta", - "skillManagement.detail.addTagDescription": "Crie ou vincule uma tag a este Skill. Novas tags são salvas com os metadados Skill.", "skillManagement.detail.addTagSuccess": "Etiqueta adicionada.", "skillManagement.detail.back": "Voltar para Skills", - "skillManagement.detail.builder.applyFailed": "Falha ao aplicar atualizações de rascunho.", "skillManagement.detail.builder.attach": "Anexar arquivo", "skillManagement.detail.builder.attachFailed": "Falha ao anexar arquivo.", "skillManagement.detail.builder.attachLimit": "Você pode anexar até 10 arquivos.", @@ -26,22 +22,12 @@ "skillManagement.detail.builder.attachUnsupported": "Somente imagens, texto e arquivos de documentos podem ser anexados.", "skillManagement.detail.builder.attachmentOnlyMessage": "Use o arquivo anexo para ajudar a melhorar essa habilidade.", "skillManagement.detail.builder.close": "Fechar Skill Builder", - "skillManagement.detail.builder.compatibleModelsOnly": "Apenas modelos compatíveis são exibidos", "skillManagement.detail.builder.copyResponse": "Copiar resposta", "skillManagement.detail.builder.copySuccess": "Resposta copiada para a área de transferência.", "skillManagement.detail.builder.editIntro": "Eu revisei essa habilidade. O que você gostaria de ajustar?", "skillManagement.detail.builder.exampleIssueTriage": "Triagem de problemas do cliente", "skillManagement.detail.builder.exampleOnboarding": "Guia de integração para novos contratados", "skillManagement.detail.builder.exampleSalesFollowUp": "Estratégia de acompanhamento de leads de vendas", - "skillManagement.detail.builder.followUpDisplayName": "Use aprovação de reembolso como nome de exibição", - "skillManagement.detail.builder.followUpNameIcon": "Aplique o nome e ícone sugeridos", - "skillManagement.detail.builder.fromMarketplace": "Do Marketplace", - "skillManagement.detail.builder.model": "GPT-4o", - "skillManagement.detail.builder.modelCredits.all": "Todos os créditos", - "skillManagement.detail.builder.modelCredits.configure": "Configuração necessária", - "skillManagement.detail.builder.modelCredits.exhausted": "Quota esgotada", - "skillManagement.detail.builder.modelProviderSettings": "Configurações do provedor de modelo", - "skillManagement.detail.builder.modelSearch": "Buscar modelos...", "skillManagement.detail.builder.modifyPlaceholder": "Peça à IA para modificar esta habilidade...", "skillManagement.detail.builder.open": "Abra Skill Builder", "skillManagement.detail.builder.placeholder": "Descreva o cenário...", @@ -61,8 +47,6 @@ "skillManagement.detail.builder.thinkingUnavailable": "Nenhum conteúdo de pensamento foi retornado pelo modelo.", "skillManagement.detail.builder.title": "Skill Builder", "skillManagement.detail.builder.tryExample": "Experimente um exemplo", - "skillManagement.detail.builder.voice": "Entrada por voz", - "skillManagement.detail.builder.voiceUnavailable": "A entrada de voz ainda não está disponível.", "skillManagement.detail.cancelAddMetadata": "Cancelar adição de metadados", "skillManagement.detail.closeFileTab": "Fechar {{name}}", "skillManagement.detail.closeVersions": "Fechar versões", @@ -70,14 +54,10 @@ "skillManagement.detail.copyContentSuccess": "Conteúdo copiado para a área de transferência.", "skillManagement.detail.copyFile": "Copiar", "skillManagement.detail.copyFileSuccess": "Arquivo pronto para colar.", - "skillManagement.detail.copyVersionId": "Copiar ID", - "skillManagement.detail.copyVersionIdSuccess": "ID copiado para a área de transferência.", "skillManagement.detail.createFile": "Novo arquivo", - "skillManagement.detail.createFileDescription": "Insira um caminho de arquivo relativo à raiz da habilidade.", "skillManagement.detail.createFileMenu": "Novo arquivo...", "skillManagement.detail.createFileSuccess": "Arquivo criado.", "skillManagement.detail.createFolder": "Nova pasta", - "skillManagement.detail.createFolderDescription": "Insira um caminho de pasta relativo à raiz da habilidade.", "skillManagement.detail.createFolderMenu": "Nova pasta...", "skillManagement.detail.createFolderSuccess": "Pasta criada.", "skillManagement.detail.createdBy": "Criado por {{name}}", @@ -100,10 +80,8 @@ "skillManagement.detail.fileMeta": "{{type}} · {{size}} bytes", "skillManagement.detail.fileMissing": "Arquivo não encontrado.", "skillManagement.detail.fileOperationFailed": "A operação do arquivo falhou.", - "skillManagement.detail.files": "Arquivos", "skillManagement.detail.latest": "Último", "skillManagement.detail.loadFailed": "Falha ao carregar habilidade.", - "skillManagement.detail.loadingTags": "Carregando etiquetas...", "skillManagement.detail.markdownLiveMode": "Visualização ao vivo", "skillManagement.detail.markdownSourceMode": "Editor de origem", "skillManagement.detail.metadataKey": "nome", @@ -113,10 +91,7 @@ "skillManagement.detail.nameThisVersion": "Nomeie esta versão", "skillManagement.detail.noFileSelected": "Selecione um arquivo para visualizar.", "skillManagement.detail.noFiles": "Nenhum arquivo.", - "skillManagement.detail.noSearchResults": "Nenhum arquivo correspondente.", - "skillManagement.detail.noSelectableTags": "Nenhuma tag disponível.", "skillManagement.detail.noVersions": "Nenhuma versão publicada ainda.", - "skillManagement.detail.pasteFile": "Colar", "skillManagement.detail.pasteFileSuccess": "Arquivo colado.", "skillManagement.detail.previewUnsupported": "Este arquivo não oferece suporte à pré-visualização.", "skillManagement.detail.publish": "Publicar", @@ -128,9 +103,7 @@ "skillManagement.detail.publishUpdate": "Publicar atualização", "skillManagement.detail.published": "Publicado", "skillManagement.detail.publishedAt": "Publicado {{time}}", - "skillManagement.detail.publishedVersion": "Publicado #{{number}}", "skillManagement.detail.publishing": "Publicando...", - "skillManagement.detail.readonly": "Somente leitura", "skillManagement.detail.referenceFiles.confirm": "Digite Confirmar", "skillManagement.detail.referenceFiles.empty": "Nenhum arquivo disponível.", "skillManagement.detail.referenceFiles.livePlaceholder": "Escreva instruções para o agente. Digite / para referenciar arquivos.", @@ -138,14 +111,10 @@ "skillManagement.detail.referenceFiles.title": "Arquivos de referência", "skillManagement.detail.referencedBy_one": "Usado por {{count}} agente", "skillManagement.detail.referencedBy_other": "Usado por {{count}} agentes", - "skillManagement.detail.removeMetadata": "Excluir metadados {{name}}", "skillManagement.detail.removeTag": "Remover {{tag}}", "skillManagement.detail.removeTagSuccess": "Etiqueta removida.", - "skillManagement.detail.renameFile": "Renomear arquivo", - "skillManagement.detail.renameFileDescription": "Insira o novo caminho relativo à raiz da habilidade.", "skillManagement.detail.renameFileSuccess": "Arquivo renomeado.", "skillManagement.detail.renameSkillSuccess": "Skill renomeado.", - "skillManagement.detail.renameVersion": "Renomear", "skillManagement.detail.renameVersionFailed": "Falha ao renomear a versão.", "skillManagement.detail.renameVersionPrompt": "Nome da versão", "skillManagement.detail.renameVersionSuccess": "Versão renomeada.", @@ -155,17 +124,14 @@ "skillManagement.detail.restoreVersionConfirmTitle": "Restaurar esta versão?", "skillManagement.detail.restoreVersionFailed": "Falha ao restaurar a versão.", "skillManagement.detail.restoreVersionSuccess": "Versão restaurada para rascunho.", - "skillManagement.detail.save": "Salvar", "skillManagement.detail.saveConflictConfirmDescription": "Esta habilidade foi atualizada em outro lugar. Carregue o rascunho mais recente para continuar editando. Suas alterações atuais serão descartadas.", "skillManagement.detail.saveConflictConfirmTitle": "Carregar o rascunho mais recente?", "skillManagement.detail.saveConflictReload": "Carregar rascunho mais recente", "skillManagement.detail.saveConflictStatus": "Conflito detectado. Carregue o rascunho mais recente para continuar editando.", "skillManagement.detail.saveFailed": "Falha ao salvar o arquivo.", - "skillManagement.detail.saveSuccess": "Arquivo salvo.", "skillManagement.detail.saved": "Salvo", "skillManagement.detail.savedAt": "Salvo em {{time}}", "skillManagement.detail.saving": "Salvando...", - "skillManagement.detail.searchFiles": "Buscar arquivos", "skillManagement.detail.showMoreReferences": "Mostrar mais {{count}}", "skillManagement.detail.skillDescriptionPlaceholder": "Descreva o que essa habilidade faz e quando os agentes devem usá-la.", "skillManagement.detail.skillNamePlaceholder": "Use letras minúsculas, números e hifens.", @@ -177,16 +143,12 @@ "skillManagement.detail.uploadConflict": "Já existe um arquivo com este nome.", "skillManagement.detail.uploadFailureSummary": "{{failed}} dos arquivos {{total}} falhou", "skillManagement.detail.uploadFailureTitle": "Alguns uploads falharam", - "skillManagement.detail.uploadFile": "Enviar arquivo", - "skillManagement.detail.uploadFileFailed": "Falha no upload do arquivo.", "skillManagement.detail.uploadFileSuccess": "Arquivo enviado.", "skillManagement.detail.uploadFilesButton": "Carregar arquivos {{count}}", "skillManagement.detail.uploadFilesFailedStatus_one": "Falha no upload do arquivo {{count}}.", "skillManagement.detail.uploadFilesFailedStatus_other": "Falha no upload do arquivo {{count}}.", "skillManagement.detail.uploadFilesMenu": "Carregar arquivos...", "skillManagement.detail.uploadFilesProgress": "Carregando {{completed}}/{{total}}", - "skillManagement.detail.uploadFilesResult": "{{uploaded}} carregado · {{failed}} falhou", - "skillManagement.detail.uploadFilesStatus": "Carregar status", "skillManagement.detail.uploadInvalidExtension": "Este tipo de arquivo não é compatível.", "skillManagement.detail.uploadInvalidName": "Nome inválido.", "skillManagement.detail.uploadInvalidPath": "Caminho de arquivo inválido.", @@ -206,7 +168,6 @@ "skillManagement.detail.uploadWillReplace": "Substituirá o arquivo existente", "skillManagement.detail.uploadWillUseName": "Será carregado como {{name}}", "skillManagement.detail.versionHistory": "Abrir histórico de versões", - "skillManagement.detail.versionMeta": "{{hash}} · {{time}}", "skillManagement.detail.versionPublishNote": "Notas de lançamento", "skillManagement.detail.versionPublishNotePlaceholder": "Descreva esta mudança", "skillManagement.detail.versionPublishedMeta": "{{time}} · {{name}}", @@ -227,8 +188,6 @@ "skillManagement.errors.missingSkillMd": "Este pacote não possui SKILL.md. Adicione o arquivo e importe-o novamente.", "skillManagement.errors.nameConflict": "Uma habilidade chamada \"{{name}}\" já existe. Renomeie-o para SKILL.md e importe novamente.", "skillManagement.errors.workspaceLimit": "Seu espaço de trabalho atingiu o limite de 500 habilidades. Exclua uma habilidade para adicionar uma nova.", - "skillManagement.exportDialog.description": "Isso exportará o rascunho salvo atual da sua habilidade. Se houver alterações não salvas no editor, salve-as antes de exportar.", - "skillManagement.exportDialog.title": "Exportar o rascunho salvo atual", "skillManagement.import": "Importar", "skillManagement.importFailed": "Falha ao importar habilidade.", "skillManagement.importSuccess": "Skill importado.", @@ -236,12 +195,10 @@ "skillManagement.loadingError": "Falha ao carregar habilidades", "skillManagement.moreActions": "Mais ações para {{name}}", "skillManagement.noDescription": "Ainda não há descrição", - "skillManagement.noTags": "Sem tags", "skillManagement.publishedAt": "Publicado em {{time}}", "skillManagement.referenceCount_one": "{{count}} agente", "skillManagement.referenceCount_other": "{{count}} agentes", "skillManagement.searchLabel": "Buscar habilidades", "skillManagement.searchPlaceholder": "Pesquise por nome ou descrição", - "skillManagement.tags": "Etiquetas", "skillManagement.title": "Habilidades" } diff --git a/web/i18n/pt-BR/snippet.json b/web/i18n/locales/pt-BR/snippet.json similarity index 100% rename from web/i18n/pt-BR/snippet.json rename to web/i18n/locales/pt-BR/snippet.json diff --git a/web/i18n/pt-BR/time.json b/web/i18n/locales/pt-BR/time.json similarity index 100% rename from web/i18n/pt-BR/time.json rename to web/i18n/locales/pt-BR/time.json diff --git a/web/i18n/pt-BR/tools.json b/web/i18n/locales/pt-BR/tools.json similarity index 100% rename from web/i18n/pt-BR/tools.json rename to web/i18n/locales/pt-BR/tools.json diff --git a/web/i18n/pt-BR/workflow.json b/web/i18n/locales/pt-BR/workflow.json similarity index 99% rename from web/i18n/pt-BR/workflow.json rename to web/i18n/locales/pt-BR/workflow.json index 70c36c156a2..17d2437df92 100644 --- a/web/i18n/pt-BR/workflow.json +++ b/web/i18n/locales/pt-BR/workflow.json @@ -460,8 +460,6 @@ "nodes.agent.pluginNotInstalledDesc": "Este integração é instalado a partir do GitHub. Por favor, vá para Integrações para reinstalar", "nodes.agent.roster.change": "Alterar", "nodes.agent.roster.editInConsole": "Editar no Agents", - "nodes.agent.roster.editInConsoleDisabled": "A edição de agentes requer a função Editor ou superior.", - "nodes.agent.roster.editInConsoleDisabledRbac": "Somente membros com a função Editor ou superior, ou com a permissão Gerenciar Agents, podem editar agentes.", "nodes.agent.roster.inlineSetup.description": "Uma configuração pontual que existe apenas neste nó", "nodes.agent.roster.inlineSetup.name": "Configuração inline", "nodes.agent.roster.inlineSetup.type": "Apenas neste nó", diff --git a/web/i18n/ro-RO/agent-v-2.json b/web/i18n/locales/ro-RO/agent-v-2.json similarity index 98% rename from web/i18n/ro-RO/agent-v-2.json rename to web/i18n/locales/ro-RO/agent-v-2.json index df9564cd05a..30b0885646c 100644 --- a/web/i18n/ro-RO/agent-v-2.json +++ b/web/i18n/locales/ro-RO/agent-v-2.json @@ -69,6 +69,7 @@ "agentDetail.configure.answer.activity.runningCommands": "Se execută comenzile", "agentDetail.configure.answer.duration.minute": "{{count}} min", "agentDetail.configure.answer.duration.second": "{{count}} s", + "agentDetail.configure.answer.responseEnded": "Răspunsul s-a încheiat", "agentDetail.configure.answer.thinking": "Se gândește", "agentDetail.configure.build.empty.communityEditionTip": "Community Edition, deși oferă versionare pentru configurațiile extrase, nu acceptă versionarea sistemului de fișiere în sine. Fii atent la acțiunile din modul Build, deoarece modificările făcute în sistemul de fișiere au loc în timp real și nu pot fi întotdeauna anulate curat. Community Edition nu este alegerea ideală pentru servirea unor audiențe externe numeroase.", "agentDetail.configure.build.empty.description": "Descrie ce dorești, iar formularul din stânga se completează pe măsură ce avansezi.", @@ -269,7 +270,6 @@ "agentDetail.configure.workingDirectory.breadcrumbLabel": "Calea sistemului de fișiere", "agentDetail.configure.workingDirectory.description": "Fișierele pe care agentul le citește și le scrie în timpul rulării. Tot ce generează, precum qna_report.pdf, apare aici.", "agentDetail.configure.workingDirectory.fileSystem": "Sistem de fișiere", - "agentDetail.configure.workingDirectory.open": "Deschide directorul de lucru", "agentDetail.configure.workingDirectory.persistentFiles": "Persistente", "agentDetail.configure.workingDirectory.persistentFilesTooltip": "Fișiere pe care agentul le păstrează și le poate folosi oriunde rulează, inclusiv în conversațiile utilizatorilor finali și în fluxurile de lucru. Modificările făcute aici în timpul acestui chat de creare intră în vigoare după ce faceți clic pe „Aplică”.", "agentDetail.configure.workingDirectory.temporaryFiles": "Temporare", @@ -348,7 +348,6 @@ "agentDetail.sections.logs": "Jurnale", "agentDetail.sections.monitoring": "Monitorizare", "agentDetail.title": "Agent", - "agentDetail.type": "AGENT", "agentDetail.versionHistory.empty": "Nicio versiune încă", "agentDetail.versionHistory.exitVersions": "Ieși din versiuni", "agentDetail.versionHistory.filter": "Filtrați versiunile", @@ -408,8 +407,5 @@ "roster.sort.lastModified": "Ultima modificare", "roster.sort.recentlyCreated": "Create recent", "roster.title": "Agenți", - "roster.usageStatus.draft": "Ciornă", - "skillManagement.detail.uploadFilesFailedStatus_one": "{{count}} fișier nu a putut fi încărcat.", - "skillManagement.detail.uploadFilesFailedStatus_other": "{{count}} fișiere nu au putut fi încărcate.", - "skillManagement.title": "Skills" + "roster.usageStatus.draft": "Ciornă" } diff --git a/web/i18n/ro-RO/app-annotation.json b/web/i18n/locales/ro-RO/app-annotation.json similarity index 100% rename from web/i18n/ro-RO/app-annotation.json rename to web/i18n/locales/ro-RO/app-annotation.json diff --git a/web/i18n/ro-RO/app-api.json b/web/i18n/locales/ro-RO/app-api.json similarity index 100% rename from web/i18n/ro-RO/app-api.json rename to web/i18n/locales/ro-RO/app-api.json diff --git a/web/i18n/ro-RO/app-debug.json b/web/i18n/locales/ro-RO/app-debug.json similarity index 100% rename from web/i18n/ro-RO/app-debug.json rename to web/i18n/locales/ro-RO/app-debug.json diff --git a/web/i18n/ro-RO/app-log.json b/web/i18n/locales/ro-RO/app-log.json similarity index 97% rename from web/i18n/ro-RO/app-log.json rename to web/i18n/locales/ro-RO/app-log.json index 0a9753d0e07..a832065cb97 100644 --- a/web/i18n/ro-RO/app-log.json +++ b/web/i18n/locales/ro-RO/app-log.json @@ -4,11 +4,9 @@ "agentLogDetail.iteration": "Iterație", "agentLogDetail.iterations": "Iterații", "agentLogDetail.toolUsed": "Instrument utilizat", - "archives.action.downloadMonth": "Descarcă arhiva {{month}}", "archives.action.downloadReady": "Descărcarea arhivei este gata.", "archives.action.prepareDownload": "Pregătește descărcarea", "archives.action.prepareFailed": "Pregătirea descărcării arhivei a eșuat.", - "archives.action.prepareMonth": "Pregătește descărcarea arhivei {{month}}", "archives.action.prepareStarted": "Descărcarea arhivei este în pregătire.", "archives.action.preparing": "Se pregătește", "archives.description": "Descarcă exporturi CSV lunare ale jurnalelor arhivate de rulări workflow din acest spațiu de lucru.", diff --git a/web/i18n/ro-RO/app-overview.json b/web/i18n/locales/ro-RO/app-overview.json similarity index 100% rename from web/i18n/ro-RO/app-overview.json rename to web/i18n/locales/ro-RO/app-overview.json diff --git a/web/i18n/ro-RO/app.json b/web/i18n/locales/ro-RO/app.json similarity index 100% rename from web/i18n/ro-RO/app.json rename to web/i18n/locales/ro-RO/app.json diff --git a/web/i18n/ro-RO/billing.json b/web/i18n/locales/ro-RO/billing.json similarity index 100% rename from web/i18n/ro-RO/billing.json rename to web/i18n/locales/ro-RO/billing.json diff --git a/web/i18n/ro-RO/common.json b/web/i18n/locales/ro-RO/common.json similarity index 98% rename from web/i18n/ro-RO/common.json rename to web/i18n/locales/ro-RO/common.json index 92f3d7c821e..6c0e3da3bba 100644 --- a/web/i18n/ro-RO/common.json +++ b/web/i18n/locales/ro-RO/common.json @@ -209,9 +209,7 @@ "mainNav.marketplace": "Marketplace", "mainNav.skills": "Skills", "mainNav.webApps.noResults": "Nu s-au găsit aplicații web", - "mainNav.webApps.openApp": "Deschide aplicația web {{name}}", "mainNav.webApps.searchPlaceholder": "Caută aplicații web", - "mainNav.workspace.credits": "{{count}} credite", "mainNav.workspace.creditsUnit": "credite", "mainNav.workspace.inviteMembers": "Invite members", "mainNav.workspace.noResults": "Nu s-au găsit spații de lucru", @@ -233,7 +231,6 @@ "members.datasetOperatorTip": "Numai poate gestiona baza de cunoștințe", "members.editRole": "Editează rol", "members.editorTip": "Poate crea și edita aplicații", - "members.email": "Email", "members.emailInvalid": "Format de email invalid", "members.emailNotSetup": "Serverul de e-mail nu este configurat, astfel încât e-mailurile de invitație nu pot fi trimise. Vă rugăm să notificați utilizatorii despre linkul de invitație care va fi emis după invitație.", "members.emailPlaceholder": "Tastați sau lipiți adrese de e-mail…", @@ -250,7 +247,6 @@ "members.inviteLimitExceeded": "Nu sunt suficiente locuri disponibile pentru toți destinatarii.", "members.inviteTeamMember": "Adaugă membru în echipă", "members.inviteTeamMemberTip": "Pot accesa direct datele echipei dvs. după autentificare.", - "members.invitedAsRole": "Invitat ca {{role}}", "members.lastActive": "ULTIMA ACTIVITATE", "members.memberActions": "Acțiuni membru", "members.memberDetails.assign": "Atribuie", @@ -258,8 +254,6 @@ "members.memberDetails.assignedRoles": "Roluri atribuite", "members.memberDetails.customGroup": "PERSONALIZAT", "members.memberDetails.generalGroup": "GENERAL", - "members.memberDetails.openAria": "Deschide detaliile membrului pentru {{name}}", - "members.memberDetails.roleActionsAria": "Deschide acțiunile pentru rolul {{role}}", "members.memberDetails.roleNoPermissionSummary": "Rolul actual nu are permisiuni.", "members.memberDetails.rolePermissionSummary": "{{role}} poate {{permissions}}", "members.memberDetails.title": "Detalii membru", @@ -370,7 +364,6 @@ "modelProvider.card.usagePriorityTip": "Setați ce resursă să fie utilizată prima la rularea modelelor.", "modelProvider.configureTip": "Configurați api-key sau adăugați modelul de utilizat", "modelProvider.confirmDelete": "confirmați ștergerea?", - "modelProvider.credits": "Credite mesaje", "modelProvider.defaultConfig": "Configurație implicită", "modelProvider.discoverMore": "Descoperă mai multe în", "modelProvider.embeddingModel.key": "Model de încorporare", @@ -427,7 +420,6 @@ "modelProvider.selector.noProviderConfiguredDesc": "Răsfoiți Marketplace pentru a instala unul sau configurați furnizorii în setări.", "modelProvider.selector.onlyCompatibleModelsShown": "Sunt afișate doar modelele compatibile", "modelProvider.selector.showIncompatibleModels": "Afișează modelele incompatibile", - "modelProvider.selector.suggestion": "Sugestie", "modelProvider.selector.suggestionTip": "Recomandat pentru sarcinile agentului", "modelProvider.showModels": "Arată modele", "modelProvider.speechToTextModel.key": "Model de conversie text-la-vorbire", @@ -458,6 +450,8 @@ "operation.confirmAction": "Vă rugăm să confirmați acțiunea dumneavoastră.", "operation.copied": "Copiat", "operation.copy": "Copiază", + "operation.copyErrorDetails": "Copiază detaliile erorii", + "operation.copyErrorFailed": "Copierea a eșuat. Reîncearcă", "operation.copyImage": "Copiere imagine", "operation.create": "Creează", "operation.deSelectAll": "Deselectați tot", @@ -475,7 +469,6 @@ "operation.format": "Format", "operation.imageCopied": "Imagine copiată", "operation.imageCopyFailed": "Imaginea nu a putut fi copiată. Descărcați-o în schimb.", - "operation.imageDownloaded": "Imagine descărcată", "operation.in": "în", "operation.learnMore": "Află mai multe", "operation.log": "Jurnal", @@ -512,7 +505,6 @@ "operation.sure": "Sunt sigur", "operation.toggleFullscreen": "Comută ecran complet", "operation.toggleMute": "Comută sunetul", - "operation.upload": "Upload", "operation.view": "Vedere", "operation.viewDetails": "Vezi detalii", "operation.viewMore": "VEZI MAI MULT", @@ -542,7 +534,6 @@ "promptEditor.requestURL.item.title": "URL cerere", "promptEditor.variable.modal.add": "Nouă variabilă", "promptEditor.variable.modal.addTool": "Nou instrument", - "provider.anthropicHosted.trialQuotaTip": "Cota de încercare Anthropic va expira pe 11.03.2025 și nu va mai fi disponibilă ulterior. Vă rugăm să o utilizați la timp.", "provider.encrypted.back": " tehnologie.", "provider.encrypted.front": "Cheia dvs. API va fi criptată și stocată folosind", "provider.validating": "Se validează cheia...", @@ -565,7 +556,6 @@ "settings.preferences": "Preferences", "settings.provider": "Furnizor de modele", "settings.resourceAccess": "Acces la resurse", - "settings.resourceAccessDescription": "Configurați regulile de permisiune pe care le pot folosi aplicațiile și bazele de cunoștințe. Regulile de permisiune pot fi atribuite membrilor în configurații specifice de acces la resurse.", "settings.rolesAndPermissions": "Roluri și permisiuni", "settings.settings": "Settings", "settings.swaggerAPIAsTool": "Swagger API as Tool", @@ -695,7 +685,6 @@ "userProfile.helpCenter": "Ajutor", "userProfile.logout": "Deconectare", "userProfile.roadmap": "Plan de acțiune", - "userProfile.settings": "Setări", "userProfile.workspace": "Spațiu de lucru", "voice.language.arTN": "Arabă tunisiană", "voice.language.deDE": "Germană", diff --git a/web/i18n/ro-RO/custom.json b/web/i18n/locales/ro-RO/custom.json similarity index 100% rename from web/i18n/ro-RO/custom.json rename to web/i18n/locales/ro-RO/custom.json diff --git a/web/i18n/ro-RO/dataset-creation.json b/web/i18n/locales/ro-RO/dataset-creation.json similarity index 100% rename from web/i18n/ro-RO/dataset-creation.json rename to web/i18n/locales/ro-RO/dataset-creation.json diff --git a/web/i18n/ro-RO/dataset-documents.json b/web/i18n/locales/ro-RO/dataset-documents.json similarity index 100% rename from web/i18n/ro-RO/dataset-documents.json rename to web/i18n/locales/ro-RO/dataset-documents.json diff --git a/web/i18n/ro-RO/dataset-hit-testing.json b/web/i18n/locales/ro-RO/dataset-hit-testing.json similarity index 100% rename from web/i18n/ro-RO/dataset-hit-testing.json rename to web/i18n/locales/ro-RO/dataset-hit-testing.json diff --git a/web/i18n/ro-RO/dataset-pipeline.json b/web/i18n/locales/ro-RO/dataset-pipeline.json similarity index 100% rename from web/i18n/ro-RO/dataset-pipeline.json rename to web/i18n/locales/ro-RO/dataset-pipeline.json diff --git a/web/i18n/ro-RO/dataset-settings.json b/web/i18n/locales/ro-RO/dataset-settings.json similarity index 100% rename from web/i18n/ro-RO/dataset-settings.json rename to web/i18n/locales/ro-RO/dataset-settings.json diff --git a/web/i18n/ro-RO/dataset.json b/web/i18n/locales/ro-RO/dataset.json similarity index 97% rename from web/i18n/ro-RO/dataset.json rename to web/i18n/locales/ro-RO/dataset.json index 872104808b8..7846a10a4d1 100644 --- a/web/i18n/ro-RO/dataset.json +++ b/web/i18n/locales/ro-RO/dataset.json @@ -163,7 +163,6 @@ "newKnowledge.configureProvider": "Configurați {{provider}}", "newKnowledge.connectNotion": "Conectează Notion", "newKnowledge.connectProvider": "Conectați {{provider}}", - "newKnowledge.connectProviderGeneric": "Conectează furnizorul", "newKnowledge.connectSource": "Conectează o sursă", "newKnowledge.connectSourceDescription": "Conținutul rămâne sincronizat cu Notion, site-uri web și alte servicii.", "newKnowledge.connectingProvider": "Se conectează...", @@ -179,18 +178,14 @@ "newKnowledge.crawlFailedDescription": "Crawlerul nu a putut finaliza această previzualizare. Verifică adresa URL, ajustează opțiunile de explorare sau încearcă din nou.", "newKnowledge.crawlFailedProvider": "{{provider}} a raportat o eroare. Verifică conexiunea furnizorului și încearcă din nou.", "newKnowledge.crawlFailedTimeout": "Explorarea a depășit timpul limită. Încearcă o adresă URL mai specifică sau redu numărul maxim de pagini.", - "newKnowledge.crawlNotStarted": "Nepornit", "newKnowledge.crawlOptions": "Opțiuni de explorare", "newKnowledge.crawlPreview": "Previzualizare accesare", "newKnowledge.crawlProgress": "Progresul explorării pentru {{host}}", - "newKnowledge.crawlSetupUnavailableDescription": "Configurarea accesării cu crawlere a site-ului este dezactivată până când fluxul de lucru cu crawlere este disponibil.", - "newKnowledge.crawlSetupUnavailableTitle": "Firecrawl este gata", "newKnowledge.crawlStartFailed": "Previzualizarea explorării nu a putut porni. Încearcă din nou.", "newKnowledge.crawlStopped": "Explorare oprită", "newKnowledge.crawling": "Se explorează…", "newKnowledge.crawlingPages": "Se explorează {{host}}… {{count}} pagini găsite", "newKnowledge.createFailed": "Nu am putut crea această bază de cunoștințe. Verificați accesul și încercați din nou.", - "newKnowledge.createIllustrationTitle": "Conectează conținutul o singură dată — fiecare aplicație răspunde cu cele mai noi cunoștințe.", "newKnowledge.createTitle": "Creați cunoștințe", "newKnowledge.createdAt": "Creat", "newKnowledge.creators": "Creatori", @@ -220,7 +215,6 @@ "newKnowledge.documentColumn": "Fișier", "newKnowledge.documentContentIncomplete": "Se încarcă restul conținutului documentului…", "newKnowledge.documentContents": "Conținut", - "newKnowledge.documentFacts": "Detalii document", "newKnowledge.documentFilterLabel": "Filtrați documentele după stare", "newKnowledge.documentLoadErrorDescription": "Nu am putut încărca acest document. Încearcă din nou.", "newKnowledge.documentLoadErrorTitle": "Documentul nu a putut fi încărcat", @@ -268,7 +262,6 @@ "newKnowledge.documentsSelected": "{{count}} documente selectate", "newKnowledge.documentsUnavailable": "— documente", "newKnowledge.downloadDocuments": "Descărcați", - "newKnowledge.editSource": "Edita", "newKnowledge.emptyChunk": "Fragment gol", "newKnowledge.emptyDescription": "Adaugă documentele, iar agenții le pot citi direct — fără indexare de configurat.", "newKnowledge.emptyTitle": "O modalitate mai inteligentă de a crea baze de cunoștințe", @@ -344,7 +337,6 @@ "newKnowledge.processingTaskState.succeeded": "Terminat", "newKnowledge.processingTaskState.superseded": "Inlocuit", "newKnowledge.providerConnected": "{{provider}} conectat", - "newKnowledge.providerLabel": "Selectați un furnizor", "newKnowledge.providerLoadFailed": "Furnizorii nu s-au putut încărca", "newKnowledge.providerNotConfigured": "{{provider}} nu este configurat", "newKnowledge.providerNotConfiguredDescription": "Adăugați o cheie API sau o adresă URL a punctului final pentru a accesa cu crawlere site-urile web cu {{provider}}.", @@ -369,12 +361,10 @@ "newKnowledge.revisionState.superseded": "Anterioară", "newKnowledge.rootUrl": "URL rădăcină", "newKnowledge.rootUrlPlaceholder": "https://docs.example.com", - "newKnowledge.routeUnavailable": "Această secțiune va deveni disponibilă în următorul pas de configurare.", "newKnowledge.searchDocuments": "Căutați documente", "newKnowledge.searchSources": "Căutați surse", "newKnowledge.selectAll": "Selectează tot", "newKnowledge.selectAllDocuments": "Selectați toate documentele", - "newKnowledge.selectAllSources": "Selectează toate sursele", "newKnowledge.selectChunk": "Selectează un fragment pentru a-i verifica conținutul.", "newKnowledge.selectFilesAndFolders": "Selectează fișierele și folderele de sincronizat", "newKnowledge.skippedFailed": "Omis (eșuat)", @@ -384,7 +374,6 @@ "newKnowledge.sourceFilterLabel": "Filtrați sursele după stare", "newKnowledge.sourceName": "Numele sursei", "newKnowledge.sourceNamePlaceholder": "de ex., explorarea documentației produsului", - "newKnowledge.sourceNameRequired": "Introdu un nume pentru sursă.", "newKnowledge.sourceSetupBackendDependency": "Configurarea sursei este afișată integral; editarea și previzualizarea necesită fluxul backend pentru sursa ciornă.", "newKnowledge.sourceStatus.active": "Activ", "newKnowledge.sourceStatus.disabled": "Dezactivat", @@ -431,7 +420,6 @@ "newKnowledge.unavailableDescription": "KnowledgeFS nu este disponibil momentan pentru acest spațiu de lucru.", "newKnowledge.unavailableTitle": "Noua bază de cunoștințe nu este disponibilă", "newKnowledge.updated": "Actualizat la {{date}}", - "newKnowledge.updatedAt": "Ultima actualizare", "newKnowledge.updatedColumn": "Actualizat", "newKnowledge.uploadCharactersUnavailable": "Caractere: —", "newKnowledge.uploadDocuments": "Încărcați documente", @@ -460,11 +448,9 @@ "retrievalSettings": "Setări de recuperare", "retry": "Reîncercați", "selectExternalKnowledgeAPI.placeholder": "Alegeți un API de cunoștințe extern", - "serviceApi.card.addApiKey": "Adaugă cheie API", "serviceApi.card.apiKey": "Cheie API", "serviceApi.card.apiReference": "Referință API", "serviceApi.card.endpoint": "Punct final API de servicii", - "serviceApi.card.manageApiKey": "Gestionează cheia API", "serviceApi.card.title": "API pentru serviciul backend", "serviceApi.disabled": "Dezactivat", "serviceApi.enabled": "În serviciu", diff --git a/web/i18n/ro-RO/deployments.json b/web/i18n/locales/ro-RO/deployments.json similarity index 97% rename from web/i18n/ro-RO/deployments.json rename to web/i18n/locales/ro-RO/deployments.json index 22efe318f8f..728ead93592 100644 --- a/web/i18n/ro-RO/deployments.json +++ b/web/i18n/locales/ro-RO/deployments.json @@ -27,7 +27,6 @@ "list.emptyTitle": "Nicio implementare încă", "overview.chip.latest": "cea mai recentă", "status.RUNTIME_INSTANCE_STATUS_DEPLOYING": "Se implementează", - "status.RUNTIME_INSTANCE_STATUS_FAILED": "Implementare eșuată", "status.RUNTIME_INSTANCE_STATUS_INVALID": "Invalid", "status.RUNTIME_INSTANCE_STATUS_READY": "Rulează", "status.RUNTIME_INSTANCE_STATUS_UNDEPLOYED": "Neimplementat", @@ -87,6 +86,5 @@ "studio.versionValue": "Valoarea versiunii", "studio.versionsBehind_one": "{{count}} versiune în urmă", "studio.versionsBehind_other": "{{count}} versiuni în urmă", - "versions.checkingReleaseContent": "Se verifică conținutul versiunii...", "versions.deployTo": "Implementează în {{name}}" } diff --git a/web/i18n/ro-RO/device-flow.json b/web/i18n/locales/ro-RO/device-flow.json similarity index 100% rename from web/i18n/ro-RO/device-flow.json rename to web/i18n/locales/ro-RO/device-flow.json diff --git a/web/i18n/ro-RO/education.json b/web/i18n/locales/ro-RO/education.json similarity index 100% rename from web/i18n/ro-RO/education.json rename to web/i18n/locales/ro-RO/education.json diff --git a/web/i18n/ro-RO/explore.json b/web/i18n/locales/ro-RO/explore.json similarity index 100% rename from web/i18n/ro-RO/explore.json rename to web/i18n/locales/ro-RO/explore.json diff --git a/web/i18n/ro-RO/layout.json b/web/i18n/locales/ro-RO/layout.json similarity index 100% rename from web/i18n/ro-RO/layout.json rename to web/i18n/locales/ro-RO/layout.json diff --git a/web/i18n/ro-RO/login.json b/web/i18n/locales/ro-RO/login.json similarity index 100% rename from web/i18n/ro-RO/login.json rename to web/i18n/locales/ro-RO/login.json diff --git a/web/i18n/ro-RO/oauth.json b/web/i18n/locales/ro-RO/oauth.json similarity index 100% rename from web/i18n/ro-RO/oauth.json rename to web/i18n/locales/ro-RO/oauth.json diff --git a/web/i18n/ro-RO/permission-keys.json b/web/i18n/locales/ro-RO/permission-keys.json similarity index 100% rename from web/i18n/ro-RO/permission-keys.json rename to web/i18n/locales/ro-RO/permission-keys.json diff --git a/web/i18n/ro-RO/permission.json b/web/i18n/locales/ro-RO/permission.json similarity index 100% rename from web/i18n/ro-RO/permission.json rename to web/i18n/locales/ro-RO/permission.json diff --git a/web/i18n/ro-RO/pipeline.json b/web/i18n/locales/ro-RO/pipeline.json similarity index 100% rename from web/i18n/ro-RO/pipeline.json rename to web/i18n/locales/ro-RO/pipeline.json diff --git a/web/i18n/ro-RO/plugin-tags.json b/web/i18n/locales/ro-RO/plugin-tags.json similarity index 100% rename from web/i18n/ro-RO/plugin-tags.json rename to web/i18n/locales/ro-RO/plugin-tags.json diff --git a/web/i18n/ro-RO/plugin-trigger.json b/web/i18n/locales/ro-RO/plugin-trigger.json similarity index 100% rename from web/i18n/ro-RO/plugin-trigger.json rename to web/i18n/locales/ro-RO/plugin-trigger.json diff --git a/web/i18n/ro-RO/plugin.json b/web/i18n/locales/ro-RO/plugin.json similarity index 99% rename from web/i18n/ro-RO/plugin.json rename to web/i18n/locales/ro-RO/plugin.json index f6d2114e4ce..3d6e3d0b3e5 100644 --- a/web/i18n/ro-RO/plugin.json +++ b/web/i18n/locales/ro-RO/plugin.json @@ -277,7 +277,6 @@ "readmeInfo.noReadmeAvailable": "Nu există fișier README disponibil", "readmeInfo.title": "Citiți-mă", "requestAPlugin": "Solicitați un plugin", - "requestSubmit": "Solicitați sau publicați", "search": "Căutare", "searchCategories": "Categorii de căutare", "searchInMarketplace": "Căutare în Marketplace", diff --git a/web/i18n/ro-RO/register.json b/web/i18n/locales/ro-RO/register.json similarity index 100% rename from web/i18n/ro-RO/register.json rename to web/i18n/locales/ro-RO/register.json diff --git a/web/i18n/ro-RO/run-log.json b/web/i18n/locales/ro-RO/run-log.json similarity index 100% rename from web/i18n/ro-RO/run-log.json rename to web/i18n/locales/ro-RO/run-log.json diff --git a/web/i18n/ro-RO/share.json b/web/i18n/locales/ro-RO/share.json similarity index 100% rename from web/i18n/ro-RO/share.json rename to web/i18n/locales/ro-RO/share.json diff --git a/web/i18n/ro-RO/skill.json b/web/i18n/locales/ro-RO/skill.json similarity index 82% rename from web/i18n/ro-RO/skill.json rename to web/i18n/locales/ro-RO/skill.json index 9222e91cd18..230e0dbe548 100644 --- a/web/i18n/ro-RO/skill.json +++ b/web/i18n/locales/ro-RO/skill.json @@ -1,5 +1,4 @@ { - "skillManagement.clearTags": "Ștergeți etichetele", "skillManagement.create": "Creează", "skillManagement.createFailed": "Nu s-a putut crea abilitate.", "skillManagement.createSuccess": "Ciornă creată.", @@ -13,12 +12,9 @@ "skillManagement.deleteFailed": "Skill-ul nu a putut fi șters.", "skillManagement.deleteSuccess": "Skill șters.", "skillManagement.detail.addMetadata": "Adăugați metadate", - "skillManagement.detail.addMetadataDescription": "Adăugați un câmp principal la acest fișier Markdown.", "skillManagement.detail.addTag": "Adăugați etichetă", - "skillManagement.detail.addTagDescription": "Creați sau legați o etichetă la acest Skill. Etichetele noi sunt salvate cu metadatele Skill.", "skillManagement.detail.addTagSuccess": "Etichetă adăugată.", "skillManagement.detail.back": "Înapoi la Skills", - "skillManagement.detail.builder.applyFailed": "Nu s-au aplicat actualizările schiței.", "skillManagement.detail.builder.attach": "Atașați fișierul", "skillManagement.detail.builder.attachFailed": "Fișierul nu a fost atașat.", "skillManagement.detail.builder.attachLimit": "Puteți atașa până la 10 fișiere.", @@ -26,22 +22,12 @@ "skillManagement.detail.builder.attachUnsupported": "Pot fi atașate numai imagini, text și fișiere de document.", "skillManagement.detail.builder.attachmentOnlyMessage": "Utilizați fișierul atașat pentru a ajuta la îmbunătățirea acestei aptitudini.", "skillManagement.detail.builder.close": "Închideți Skill Builder", - "skillManagement.detail.builder.compatibleModelsOnly": "Sunt afișate doar modelele compatibile", "skillManagement.detail.builder.copyResponse": "Copiați răspunsul", "skillManagement.detail.builder.copySuccess": "Răspunsul a fost copiat în clipboard.", "skillManagement.detail.builder.editIntro": "Am trecut în revistă această abilitate. Ce ai vrea să ajustezi?", "skillManagement.detail.builder.exampleIssueTriage": "Triajul problemelor clienților", "skillManagement.detail.builder.exampleOnboarding": "Ghid de înscriere pentru angajați noi", "skillManagement.detail.builder.exampleSalesFollowUp": "Strategia de urmărire a conducerii de vânzări", - "skillManagement.detail.builder.followUpDisplayName": "Utilizați aprobarea rambursării ca nume afișat", - "skillManagement.detail.builder.followUpNameIcon": "Aplicați numele și pictograma sugerate", - "skillManagement.detail.builder.fromMarketplace": "Din Marketplace", - "skillManagement.detail.builder.model": "GPT-4o", - "skillManagement.detail.builder.modelCredits.all": "Toate creditele", - "skillManagement.detail.builder.modelCredits.configure": "Configurare necesară", - "skillManagement.detail.builder.modelCredits.exhausted": "Cotă epuizată", - "skillManagement.detail.builder.modelProviderSettings": "Setări furnizor de modele", - "skillManagement.detail.builder.modelSearch": "Caută modele...", "skillManagement.detail.builder.modifyPlaceholder": "Cereți AI să modifice această abilitate...", "skillManagement.detail.builder.open": "Deschideți Skill Builder", "skillManagement.detail.builder.placeholder": "Descrie scenariul...", @@ -61,8 +47,6 @@ "skillManagement.detail.builder.thinkingUnavailable": "Nu a fost returnat niciun conținut de gândire de către model.", "skillManagement.detail.builder.title": "Skill Builder", "skillManagement.detail.builder.tryExample": "Încearcă un exemplu", - "skillManagement.detail.builder.voice": "Introducere vocală", - "skillManagement.detail.builder.voiceUnavailable": "Intrarea vocală nu este încă disponibilă.", "skillManagement.detail.cancelAddMetadata": "Anulați adăugarea metadatelor", "skillManagement.detail.closeFileTab": "Închide {{name}}", "skillManagement.detail.closeVersions": "Versiuni apropiate", @@ -70,14 +54,10 @@ "skillManagement.detail.copyContentSuccess": "Conținut copiat în clipboard.", "skillManagement.detail.copyFile": "Copiază", "skillManagement.detail.copyFileSuccess": "Fișier gata de lipit.", - "skillManagement.detail.copyVersionId": "Copiază ID", - "skillManagement.detail.copyVersionIdSuccess": "ID copiat în clipboard.", "skillManagement.detail.createFile": "Fișier nou", - "skillManagement.detail.createFileDescription": "Introduceți o cale de fișier în raport cu rădăcina abilității.", "skillManagement.detail.createFileMenu": "Fișier nou...", "skillManagement.detail.createFileSuccess": "Fișier creat.", "skillManagement.detail.createFolder": "Dosar nou", - "skillManagement.detail.createFolderDescription": "Introduceți o cale de folder în raport cu rădăcina abilității.", "skillManagement.detail.createFolderMenu": "Dosar nou...", "skillManagement.detail.createFolderSuccess": "Folder creat.", "skillManagement.detail.createdBy": "Creat de {{name}}", @@ -100,10 +80,8 @@ "skillManagement.detail.fileMeta": "{{type}} · {{size}} octeți", "skillManagement.detail.fileMissing": "Fișierul nu a fost găsit.", "skillManagement.detail.fileOperationFailed": "Operațiunea fișierului a eșuat.", - "skillManagement.detail.files": "Fișiere", "skillManagement.detail.latest": "Cea mai recentă", "skillManagement.detail.loadFailed": "Nu s-a putut încărca calificarea.", - "skillManagement.detail.loadingTags": "Se încarcă etichetele...", "skillManagement.detail.markdownLiveMode": "Previzualizare live", "skillManagement.detail.markdownSourceMode": "Editor sursă", "skillManagement.detail.metadataKey": "nume", @@ -113,10 +91,7 @@ "skillManagement.detail.nameThisVersion": "Numește această versiune", "skillManagement.detail.noFileSelected": "Selectați un fișier pentru previzualizare.", "skillManagement.detail.noFiles": "Fără fișiere.", - "skillManagement.detail.noSearchResults": "Nu există fișiere potrivite.", - "skillManagement.detail.noSelectableTags": "Nu există etichete disponibile.", "skillManagement.detail.noVersions": "Nu există încă versiuni publicate.", - "skillManagement.detail.pasteFile": "Pastă", "skillManagement.detail.pasteFileSuccess": "Fișierul a fost lipit.", "skillManagement.detail.previewUnsupported": "Acest fișier nu acceptă previzualizarea.", "skillManagement.detail.publish": "Publică", @@ -128,9 +103,7 @@ "skillManagement.detail.publishUpdate": "Publică actualizarea", "skillManagement.detail.published": "Publicat", "skillManagement.detail.publishedAt": "Publicat {{time}}", - "skillManagement.detail.publishedVersion": "Publicat #{{number}}", "skillManagement.detail.publishing": "Se publică...", - "skillManagement.detail.readonly": "Numai citire", "skillManagement.detail.referenceFiles.confirm": "Introduceți Confirmare", "skillManagement.detail.referenceFiles.empty": "Nu există fișiere disponibile.", "skillManagement.detail.referenceFiles.livePlaceholder": "Scrieți instrucțiuni pentru agent. Tastați / pentru a face referire la fișiere.", @@ -138,14 +111,10 @@ "skillManagement.detail.referenceFiles.title": "Fișiere de referință", "skillManagement.detail.referencedBy_one": "Folosit de {{count}} agent", "skillManagement.detail.referencedBy_other": "Folosit de {{count}} agenți", - "skillManagement.detail.removeMetadata": "Ștergeți metadatele {{name}}", "skillManagement.detail.removeTag": "Eliminați {{tag}}", "skillManagement.detail.removeTagSuccess": "Eticheta a fost eliminată.", - "skillManagement.detail.renameFile": "Redenumiți fișierul", - "skillManagement.detail.renameFileDescription": "Introduceți noua cale în raport cu rădăcina abilității.", "skillManagement.detail.renameFileSuccess": "Fișier redenumit.", "skillManagement.detail.renameSkillSuccess": "Skill redenumit.", - "skillManagement.detail.renameVersion": "Redenumește", "skillManagement.detail.renameVersionFailed": "Nu s-a redenumit versiunea.", "skillManagement.detail.renameVersionPrompt": "Numele versiunii", "skillManagement.detail.renameVersionSuccess": "Versiune redenumită.", @@ -155,17 +124,14 @@ "skillManagement.detail.restoreVersionConfirmTitle": "Restabiliți această versiune?", "skillManagement.detail.restoreVersionFailed": "Nu s-a restabilit versiunea.", "skillManagement.detail.restoreVersionSuccess": "Versiunea a fost restaurată în versiune nefinalizată.", - "skillManagement.detail.save": "Salvează", "skillManagement.detail.saveConflictConfirmDescription": "Această abilitate a fost actualizată în altă parte. Încărcați cea mai recentă schiță pentru a continua editarea. Modificările dvs. curente vor fi renunțate.", "skillManagement.detail.saveConflictConfirmTitle": "Încărcați cea mai recentă schiță?", "skillManagement.detail.saveConflictReload": "Încărcați cea mai recentă schiță", "skillManagement.detail.saveConflictStatus": "Conflict detectat. Încărcați cea mai recentă schiță pentru a continua editarea.", "skillManagement.detail.saveFailed": "Nu s-a putut salva fișierul.", - "skillManagement.detail.saveSuccess": "Fișier salvat.", "skillManagement.detail.saved": "Salvat", "skillManagement.detail.savedAt": "Salvat {{time}}", "skillManagement.detail.saving": "Se salvează...", - "skillManagement.detail.searchFiles": "Caută fișiere", "skillManagement.detail.showMoreReferences": "Afișați mai multe {{count}}", "skillManagement.detail.skillDescriptionPlaceholder": "Descrieți ce face această abilitate și când ar trebui să o folosească agenții.", "skillManagement.detail.skillNamePlaceholder": "Folosiți litere mici, cifre și cratime.", @@ -177,16 +143,12 @@ "skillManagement.detail.uploadConflict": "Un fișier cu acest nume există deja.", "skillManagement.detail.uploadFailureSummary": "{{failed}} a fișierelor {{total}} a eșuat", "skillManagement.detail.uploadFailureTitle": "Unele încărcări nu au reușit", - "skillManagement.detail.uploadFile": "Încarcă fișier", - "skillManagement.detail.uploadFileFailed": "Încărcarea fișierului a eșuat.", "skillManagement.detail.uploadFileSuccess": "Fișier încărcat.", "skillManagement.detail.uploadFilesButton": "Încărcați fișiere {{count}}", "skillManagement.detail.uploadFilesFailedStatus_one": "Încărcarea fișierului {{count}} a eșuat.", "skillManagement.detail.uploadFilesFailedStatus_other": "Încărcarea fișierului {{count}} nu a reușit.", "skillManagement.detail.uploadFilesMenu": "Încărcați fișiere...", "skillManagement.detail.uploadFilesProgress": "Se încarcă {{completed}}/{{total}}", - "skillManagement.detail.uploadFilesResult": "{{uploaded}} încărcat · {{failed}} a eșuat", - "skillManagement.detail.uploadFilesStatus": "Stare încărcare", "skillManagement.detail.uploadInvalidExtension": "Acest tip de fișier nu este acceptat.", "skillManagement.detail.uploadInvalidName": "Nume nevalid.", "skillManagement.detail.uploadInvalidPath": "Cale de fișier nevalidă.", @@ -206,7 +168,6 @@ "skillManagement.detail.uploadWillReplace": "Va înlocui fișierul existent", "skillManagement.detail.uploadWillUseName": "Se va încărca ca {{name}}", "skillManagement.detail.versionHistory": "Deschide istoricul versiunilor", - "skillManagement.detail.versionMeta": "{{hash}} · {{time}}", "skillManagement.detail.versionPublishNote": "Note de lansare", "skillManagement.detail.versionPublishNotePlaceholder": "Descrieți această schimbare", "skillManagement.detail.versionPublishedMeta": "{{time}} · {{name}}", @@ -227,8 +188,6 @@ "skillManagement.errors.missingSkillMd": "Acest pachet nu are SKILL.md. Adăugați fișierul și importați-l din nou.", "skillManagement.errors.nameConflict": "O abilitate numită „{{name}}” există deja. Redenumiți-l în SKILL.md și importați din nou.", "skillManagement.errors.workspaceLimit": "Spațiul tău de lucru a atins limita de 500 de aptitudini. Ștergeți o abilitate pentru a adăuga una nouă.", - "skillManagement.exportDialog.description": "Se va exporta schița salvată curentă a abilității. Dacă există modificări nesalvate în editor, salvează-le înainte de export.", - "skillManagement.exportDialog.title": "Exportă schița salvată curentă", "skillManagement.import": "Importați", "skillManagement.importFailed": "Nu s-a putut importa abilitate.", "skillManagement.importSuccess": "Skill importat.", @@ -236,12 +195,10 @@ "skillManagement.loadingError": "Nu s-au încărcat competențele", "skillManagement.moreActions": "Mai multe acțiuni pentru {{name}}", "skillManagement.noDescription": "Nicio descriere încă", - "skillManagement.noTags": "Nicio etichetă", "skillManagement.publishedAt": "Publicat {{time}}", "skillManagement.referenceCount_one": "{{count}} agent", "skillManagement.referenceCount_other": "{{count}} agenți", "skillManagement.searchLabel": "Caută abilități", "skillManagement.searchPlaceholder": "Căutați după nume sau descriere", - "skillManagement.tags": "Etichete", "skillManagement.title": "Abilități" } diff --git a/web/i18n/ro-RO/snippet.json b/web/i18n/locales/ro-RO/snippet.json similarity index 100% rename from web/i18n/ro-RO/snippet.json rename to web/i18n/locales/ro-RO/snippet.json diff --git a/web/i18n/ro-RO/time.json b/web/i18n/locales/ro-RO/time.json similarity index 100% rename from web/i18n/ro-RO/time.json rename to web/i18n/locales/ro-RO/time.json diff --git a/web/i18n/ro-RO/tools.json b/web/i18n/locales/ro-RO/tools.json similarity index 100% rename from web/i18n/ro-RO/tools.json rename to web/i18n/locales/ro-RO/tools.json diff --git a/web/i18n/ro-RO/workflow.json b/web/i18n/locales/ro-RO/workflow.json similarity index 99% rename from web/i18n/ro-RO/workflow.json rename to web/i18n/locales/ro-RO/workflow.json index 3a0533c8f37..59fa04cc760 100644 --- a/web/i18n/ro-RO/workflow.json +++ b/web/i18n/locales/ro-RO/workflow.json @@ -460,8 +460,6 @@ "nodes.agent.pluginNotInstalledDesc": "Acest integrare este instalat de pe GitHub. Vă rugăm să accesați Integrări pentru a reinstala", "nodes.agent.roster.change": "Schimbă", "nodes.agent.roster.editInConsole": "Editează în Agents", - "nodes.agent.roster.editInConsoleDisabled": "Editarea agenților necesită rolul Editor sau unul superior.", - "nodes.agent.roster.editInConsoleDisabledRbac": "Numai membrii cu rolul Editor sau unul superior ori cu permisiunea Gestionează Agents pot edita agenți.", "nodes.agent.roster.inlineSetup.description": "O configurare unică ce există doar în acest nod", "nodes.agent.roster.inlineSetup.name": "Configurare inline", "nodes.agent.roster.inlineSetup.type": "Doar în acest nod", diff --git a/web/i18n/ru-RU/agent-v-2.json b/web/i18n/locales/ru-RU/agent-v-2.json similarity index 99% rename from web/i18n/ru-RU/agent-v-2.json rename to web/i18n/locales/ru-RU/agent-v-2.json index e6e2f15d93b..2af72f2db8a 100644 --- a/web/i18n/ru-RU/agent-v-2.json +++ b/web/i18n/locales/ru-RU/agent-v-2.json @@ -69,6 +69,7 @@ "agentDetail.configure.answer.activity.runningCommands": "Выполнение команд", "agentDetail.configure.answer.duration.minute": "{{count}} мин", "agentDetail.configure.answer.duration.second": "{{count}} с", + "agentDetail.configure.answer.responseEnded": "Ответ завершён", "agentDetail.configure.answer.thinking": "Размышляет", "agentDetail.configure.build.empty.communityEditionTip": "Community Edition предоставляет версионирование извлеченных конфигураций, но не поддерживает версионирование самой файловой системы. Будьте осторожны с действиями в режиме Build: изменения файловой системы происходят в реальном времени и не всегда могут быть аккуратно отменены. Community Edition не является идеальным выбором для обслуживания массовой внешней аудитории.", "agentDetail.configure.build.empty.description": "Опишите, что вам нужно, и форма слева будет заполняться по ходу диалога.", @@ -269,7 +270,6 @@ "agentDetail.configure.workingDirectory.breadcrumbLabel": "Путь файловой системы", "agentDetail.configure.workingDirectory.description": "Файлы, которые агент читает и записывает во время работы. Все, что он создает, например qna_report.pdf, появится здесь.", "agentDetail.configure.workingDirectory.fileSystem": "Файловая система", - "agentDetail.configure.workingDirectory.open": "Открыть рабочий каталог", "agentDetail.configure.workingDirectory.persistentFiles": "Постоянные", "agentDetail.configure.workingDirectory.persistentFilesTooltip": "Файлы, которые агент сохраняет и может использовать везде, где он запускается, включая беседы конечных пользователей и рабочие процессы. Изменения, внесённые здесь во время этого чата сборки, вступят в силу после нажатия «Применить».", "agentDetail.configure.workingDirectory.temporaryFiles": "Временные", @@ -348,7 +348,6 @@ "agentDetail.sections.logs": "Журналы", "agentDetail.sections.monitoring": "Мониторинг", "agentDetail.title": "Агент", - "agentDetail.type": "АГЕНТ", "agentDetail.versionHistory.empty": "Версий пока нет", "agentDetail.versionHistory.exitVersions": "Выйти из версий", "agentDetail.versionHistory.filter": "Фильтровать версии", @@ -408,8 +407,5 @@ "roster.sort.lastModified": "Последнее изменение", "roster.sort.recentlyCreated": "Недавно созданные", "roster.title": "Агенты", - "roster.usageStatus.draft": "Черновик", - "skillManagement.detail.uploadFilesFailedStatus_one": "Не удалось загрузить {{count}} файл.", - "skillManagement.detail.uploadFilesFailedStatus_other": "Не удалось загрузить {{count}} файлов.", - "skillManagement.title": "Skills" + "roster.usageStatus.draft": "Черновик" } diff --git a/web/i18n/ru-RU/app-annotation.json b/web/i18n/locales/ru-RU/app-annotation.json similarity index 100% rename from web/i18n/ru-RU/app-annotation.json rename to web/i18n/locales/ru-RU/app-annotation.json diff --git a/web/i18n/ru-RU/app-api.json b/web/i18n/locales/ru-RU/app-api.json similarity index 100% rename from web/i18n/ru-RU/app-api.json rename to web/i18n/locales/ru-RU/app-api.json diff --git a/web/i18n/ru-RU/app-debug.json b/web/i18n/locales/ru-RU/app-debug.json similarity index 100% rename from web/i18n/ru-RU/app-debug.json rename to web/i18n/locales/ru-RU/app-debug.json diff --git a/web/i18n/ru-RU/app-log.json b/web/i18n/locales/ru-RU/app-log.json similarity index 97% rename from web/i18n/ru-RU/app-log.json rename to web/i18n/locales/ru-RU/app-log.json index 9ae68f9ec8d..6b482a7759d 100644 --- a/web/i18n/ru-RU/app-log.json +++ b/web/i18n/locales/ru-RU/app-log.json @@ -4,11 +4,9 @@ "agentLogDetail.iteration": "Итерация", "agentLogDetail.iterations": "Итерации", "agentLogDetail.toolUsed": "Использованный инструмент", - "archives.action.downloadMonth": "Скачать архив за {{month}}", "archives.action.downloadReady": "Архив готов к скачиванию.", "archives.action.prepareDownload": "Подготовить скачивание", "archives.action.prepareFailed": "Не удалось подготовить скачивание архива.", - "archives.action.prepareMonth": "Подготовить скачивание архива за {{month}}", "archives.action.prepareStarted": "Скачивание архива подготавливается.", "archives.action.preparing": "Подготовка", "archives.description": "Скачивайте ежемесячные CSV-экспорты архивных логов запусков workflow в этом рабочем пространстве.", diff --git a/web/i18n/ru-RU/app-overview.json b/web/i18n/locales/ru-RU/app-overview.json similarity index 100% rename from web/i18n/ru-RU/app-overview.json rename to web/i18n/locales/ru-RU/app-overview.json diff --git a/web/i18n/ru-RU/app.json b/web/i18n/locales/ru-RU/app.json similarity index 100% rename from web/i18n/ru-RU/app.json rename to web/i18n/locales/ru-RU/app.json diff --git a/web/i18n/ru-RU/billing.json b/web/i18n/locales/ru-RU/billing.json similarity index 100% rename from web/i18n/ru-RU/billing.json rename to web/i18n/locales/ru-RU/billing.json diff --git a/web/i18n/ru-RU/common.json b/web/i18n/locales/ru-RU/common.json similarity index 98% rename from web/i18n/ru-RU/common.json rename to web/i18n/locales/ru-RU/common.json index 0be7d670a74..7573cf2347a 100644 --- a/web/i18n/ru-RU/common.json +++ b/web/i18n/locales/ru-RU/common.json @@ -209,9 +209,7 @@ "mainNav.marketplace": "Маркетплейс", "mainNav.skills": "Skills", "mainNav.webApps.noResults": "Веб-приложения не найдены", - "mainNav.webApps.openApp": "Открыть веб-приложение {{name}}", "mainNav.webApps.searchPlaceholder": "Поиск веб-приложений", - "mainNav.workspace.credits": "{{count}} кредитов", "mainNav.workspace.creditsUnit": "кредиты", "mainNav.workspace.inviteMembers": "Invite members", "mainNav.workspace.noResults": "Рабочие пространства не найдены", @@ -233,7 +231,6 @@ "members.datasetOperatorTip": "Может управлять только базой знаний", "members.editRole": "Редактировать роль", "members.editorTip": "Может создавать и редактировать приложения", - "members.email": "Электронная почта", "members.emailInvalid": "Неверный формат электронной почты", "members.emailNotSetup": "Почтовый сервер не настроен, поэтому приглашения по электронной почте не могут быть отправлены. Пожалуйста, уведомите пользователей о ссылке для приглашения, которая будет выдана после приглашения.", "members.emailPlaceholder": "Введите или вставьте адреса электронной почты…", @@ -250,7 +247,6 @@ "members.inviteLimitExceeded": "Для всех получателей недостаточно свободных мест.", "members.inviteTeamMember": "Добавить участника команды", "members.inviteTeamMemberTip": "Они могут получить доступ к данным вашей команды сразу после входа в систему.", - "members.invitedAsRole": "Приглашен как {{role}}", "members.lastActive": "ПОСЛЕДНЯЯ АКТИВНОСТЬ", "members.memberActions": "Действия с участником", "members.memberDetails.assign": "Назначить", @@ -258,8 +254,6 @@ "members.memberDetails.assignedRoles": "Назначенные роли", "members.memberDetails.customGroup": "НАСТРАИВАЕМЫЕ", "members.memberDetails.generalGroup": "ОБЩИЕ", - "members.memberDetails.openAria": "Открыть сведения об участнике {{name}}", - "members.memberDetails.roleActionsAria": "Открыть действия для роли {{role}}", "members.memberDetails.roleNoPermissionSummary": "У текущей роли нет разрешений.", "members.memberDetails.rolePermissionSummary": "{{role}} может {{permissions}}", "members.memberDetails.title": "Сведения об участнике", @@ -370,7 +364,6 @@ "modelProvider.card.usagePriorityTip": "Укажите, какой ресурс использовать первым при запуске моделей.", "modelProvider.configureTip": "Настройте api-ключ или добавьте модель для использования", "modelProvider.confirmDelete": "Подтвердить удаление?", - "modelProvider.credits": "Кредиты на сообщения", "modelProvider.defaultConfig": "Настройка по умолчанию", "modelProvider.discoverMore": "Узнайте больше в", "modelProvider.embeddingModel.key": "Модель встраивания", @@ -427,7 +420,6 @@ "modelProvider.selector.noProviderConfiguredDesc": "Установите из Marketplace или настройте поставщиков в настройках.", "modelProvider.selector.onlyCompatibleModelsShown": "Показаны только совместимые модели", "modelProvider.selector.showIncompatibleModels": "Показать несовместимые модели", - "modelProvider.selector.suggestion": "Рекомендация", "modelProvider.selector.suggestionTip": "Рекомендуется для задач агента", "modelProvider.showModels": "Показать модели", "modelProvider.speechToTextModel.key": "Модель преобразования речи в текст", @@ -458,6 +450,8 @@ "operation.confirmAction": "Пожалуйста, подтвердите ваше действие.", "operation.copied": "Скопированы", "operation.copy": "Копировать", + "operation.copyErrorDetails": "Копировать сведения об ошибке", + "operation.copyErrorFailed": "Не удалось скопировать. Повторить", "operation.copyImage": "Скопировать изображение", "operation.create": "Создать", "operation.deSelectAll": "Снять выделение со всех", @@ -475,7 +469,6 @@ "operation.format": "Формат", "operation.imageCopied": "Скопированное изображение", "operation.imageCopyFailed": "Не удалось скопировать изображение. Попробуйте скачать его.", - "operation.imageDownloaded": "Изображение загружено", "operation.in": "в", "operation.learnMore": "Узнать больше", "operation.log": "Журнал", @@ -512,7 +505,6 @@ "operation.sure": "Я уверен", "operation.toggleFullscreen": "Переключить полноэкранный режим", "operation.toggleMute": "Переключить звук", - "operation.upload": "Upload", "operation.view": "Вид", "operation.viewDetails": "Подробнее", "operation.viewMore": "ПОДРОБНЕЕ", @@ -542,7 +534,6 @@ "promptEditor.requestURL.item.title": "URL запроса", "promptEditor.variable.modal.add": "Новая переменная", "promptEditor.variable.modal.addTool": "Новый инструмент", - "provider.anthropicHosted.trialQuotaTip": "Ваша квота на пробную версию Anthropic истечет 11.03.2025 и больше не будет доступна. Пожалуйста, используйте его вовремя.", "provider.encrypted.back": " технологии.", "provider.encrypted.front": "Ваш API-ключ будет зашифрован и сохранен с использованием", "provider.validating": "Проверка ключа...", @@ -565,7 +556,6 @@ "settings.preferences": "Preferences", "settings.provider": "Поставщик модели", "settings.resourceAccess": "Доступ к ресурсам", - "settings.resourceAccessDescription": "Настройте правила разрешений, которые могут использовать приложения и базы знаний. Правила разрешений можно назначать участникам в определённых конфигурациях доступа к ресурсам.", "settings.rolesAndPermissions": "Роли и разрешения", "settings.settings": "Settings", "settings.swaggerAPIAsTool": "Swagger API as Tool", @@ -695,7 +685,6 @@ "userProfile.helpCenter": "Помощь", "userProfile.logout": "Выйти", "userProfile.roadmap": "План развития", - "userProfile.settings": "Настройки", "userProfile.workspace": "Рабочее пространство", "voice.language.arTN": "Тунисский арабский", "voice.language.deDE": "Немецкий", diff --git a/web/i18n/ru-RU/custom.json b/web/i18n/locales/ru-RU/custom.json similarity index 100% rename from web/i18n/ru-RU/custom.json rename to web/i18n/locales/ru-RU/custom.json diff --git a/web/i18n/ru-RU/dataset-creation.json b/web/i18n/locales/ru-RU/dataset-creation.json similarity index 100% rename from web/i18n/ru-RU/dataset-creation.json rename to web/i18n/locales/ru-RU/dataset-creation.json diff --git a/web/i18n/ru-RU/dataset-documents.json b/web/i18n/locales/ru-RU/dataset-documents.json similarity index 100% rename from web/i18n/ru-RU/dataset-documents.json rename to web/i18n/locales/ru-RU/dataset-documents.json diff --git a/web/i18n/ru-RU/dataset-hit-testing.json b/web/i18n/locales/ru-RU/dataset-hit-testing.json similarity index 100% rename from web/i18n/ru-RU/dataset-hit-testing.json rename to web/i18n/locales/ru-RU/dataset-hit-testing.json diff --git a/web/i18n/ru-RU/dataset-pipeline.json b/web/i18n/locales/ru-RU/dataset-pipeline.json similarity index 100% rename from web/i18n/ru-RU/dataset-pipeline.json rename to web/i18n/locales/ru-RU/dataset-pipeline.json diff --git a/web/i18n/ru-RU/dataset-settings.json b/web/i18n/locales/ru-RU/dataset-settings.json similarity index 100% rename from web/i18n/ru-RU/dataset-settings.json rename to web/i18n/locales/ru-RU/dataset-settings.json diff --git a/web/i18n/ru-RU/dataset.json b/web/i18n/locales/ru-RU/dataset.json similarity index 97% rename from web/i18n/ru-RU/dataset.json rename to web/i18n/locales/ru-RU/dataset.json index ceef6ca4d55..91f3ed92e27 100644 --- a/web/i18n/ru-RU/dataset.json +++ b/web/i18n/locales/ru-RU/dataset.json @@ -163,7 +163,6 @@ "newKnowledge.configureProvider": "Настроить {{provider}}", "newKnowledge.connectNotion": "Подключить Notion", "newKnowledge.connectProvider": "Подключите {{provider}}", - "newKnowledge.connectProviderGeneric": "Подключить провайдера", "newKnowledge.connectSource": "Подключить источник", "newKnowledge.connectSourceDescription": "Контент остаётся синхронизированным с Notion, веб-сайтами и другими сервисами.", "newKnowledge.connectingProvider": "Подключение…", @@ -179,18 +178,14 @@ "newKnowledge.crawlFailedDescription": "Не удалось завершить предварительное сканирование. Проверьте URL, измените параметры сканирования или повторите попытку.", "newKnowledge.crawlFailedProvider": "{{provider}} сообщил об ошибке. Проверьте подключение к провайдеру и повторите попытку.", "newKnowledge.crawlFailedTimeout": "Истекло время ожидания сканирования. Укажите более точный URL или уменьшите максимальное число страниц.", - "newKnowledge.crawlNotStarted": "Не запущено", "newKnowledge.crawlOptions": "Параметры сканирования", "newKnowledge.crawlPreview": "Предпросмотр обхода", "newKnowledge.crawlProgress": "Ход сканирования {{host}}", - "newKnowledge.crawlSetupUnavailableDescription": "Конфигурация сканирования веб-сайта отключена до тех пор, пока не будет доступен рабочий процесс сканирования.", - "newKnowledge.crawlSetupUnavailableTitle": "Firecrawl готов", "newKnowledge.crawlStartFailed": "Не удалось запустить предварительное сканирование. Повторите попытку.", "newKnowledge.crawlStopped": "Сканирование остановлено", "newKnowledge.crawling": "Сканирование…", "newKnowledge.crawlingPages": "Сканирование {{host}}… найдено страниц: {{count}}", "newKnowledge.createFailed": "Мы не смогли создать эту базу знаний. Проверьте доступ и повторите попытку.", - "newKnowledge.createIllustrationTitle": "Подключите контент один раз — каждое приложение будет отвечать на основе самых свежих знаний.", "newKnowledge.createTitle": "Создавайте знания", "newKnowledge.createdAt": "Создано", "newKnowledge.creators": "Создатели", @@ -220,7 +215,6 @@ "newKnowledge.documentColumn": "Документ", "newKnowledge.documentContentIncomplete": "Загружается оставшееся содержимое документа…", "newKnowledge.documentContents": "Содержание", - "newKnowledge.documentFacts": "Сведения о документе", "newKnowledge.documentFilterLabel": "Фильтровать документы по статусу", "newKnowledge.documentLoadErrorDescription": "Не удалось загрузить этот документ. Повторите попытку.", "newKnowledge.documentLoadErrorTitle": "Не удалось загрузить документ", @@ -268,7 +262,6 @@ "newKnowledge.documentsSelected": "{{count}} выбраны документы", "newKnowledge.documentsUnavailable": "— документов", "newKnowledge.downloadDocuments": "Скачать", - "newKnowledge.editSource": "Править", "newKnowledge.emptyChunk": "Пустой фрагмент", "newKnowledge.emptyDescription": "Добавьте документы, и агенты смогут читать их напрямую — без настройки индексации.", "newKnowledge.emptyTitle": "Более умный способ создавать базы знаний", @@ -344,7 +337,6 @@ "newKnowledge.processingTaskState.succeeded": "Завершено", "newKnowledge.processingTaskState.superseded": "Заменено", "newKnowledge.providerConnected": "{{provider}} подключен", - "newKnowledge.providerLabel": "Выберите провайдера", "newKnowledge.providerLoadFailed": "Поставщикам не удалось загрузить", "newKnowledge.providerNotConfigured": "{{provider}} не настроено", "newKnowledge.providerNotConfiguredDescription": "Добавьте ключ API или URL-адрес конечной точки для сканирования веб-сайтов с помощью {{provider}}.", @@ -369,12 +361,10 @@ "newKnowledge.revisionState.superseded": "Предыдущая", "newKnowledge.rootUrl": "Корневой URL", "newKnowledge.rootUrlPlaceholder": "https://docs.example.com", - "newKnowledge.routeUnavailable": "Этот раздел станет доступен на следующем этапе настройки.", "newKnowledge.searchDocuments": "Поиск документов", "newKnowledge.searchSources": "Поиск источников", "newKnowledge.selectAll": "Выбрать все", "newKnowledge.selectAllDocuments": "Выбрать все документы", - "newKnowledge.selectAllSources": "Выбрать все источники", "newKnowledge.selectChunk": "Выберите фрагмент, чтобы просмотреть его содержимое.", "newKnowledge.selectFilesAndFolders": "Выберите файлы и папки для синхронизации", "newKnowledge.skippedFailed": "Пропущено (ошибка)", @@ -384,7 +374,6 @@ "newKnowledge.sourceFilterLabel": "Фильтровать источники по статусу", "newKnowledge.sourceName": "Название источника", "newKnowledge.sourceNamePlaceholder": "например, сканирование документации продукта", - "newKnowledge.sourceNameRequired": "Введите название источника.", "newKnowledge.sourceSetupBackendDependency": "Настройка источника показана полностью; для редактирования и предпросмотра требуется серверный процесс черновика источника.", "newKnowledge.sourceStatus.active": "Активен", "newKnowledge.sourceStatus.disabled": "Отключено", @@ -431,7 +420,6 @@ "newKnowledge.unavailableDescription": "KnowledgeFS сейчас недоступен для этого рабочего пространства.", "newKnowledge.unavailableTitle": "Новая база знаний недоступна", "newKnowledge.updated": "Обновлено {{date}}", - "newKnowledge.updatedAt": "Последнее обновление", "newKnowledge.updatedColumn": "Обновлено", "newKnowledge.uploadCharactersUnavailable": "Символы: —", "newKnowledge.uploadDocuments": "Загрузить документы", @@ -460,11 +448,9 @@ "retrievalSettings": "Настройки поиска", "retry": "Повторить попытку", "selectExternalKnowledgeAPI.placeholder": "Выбор API внешних знаний", - "serviceApi.card.addApiKey": "Добавить API ключ", "serviceApi.card.apiKey": "API ключ", "serviceApi.card.apiReference": "Справочник API", "serviceApi.card.endpoint": "Конечная точка API сервиса", - "serviceApi.card.manageApiKey": "Управление API ключом", "serviceApi.card.title": "API бэкенд-сервиса", "serviceApi.disabled": "Отключено", "serviceApi.enabled": "На службе", diff --git a/web/i18n/ru-RU/deployments.json b/web/i18n/locales/ru-RU/deployments.json similarity index 97% rename from web/i18n/ru-RU/deployments.json rename to web/i18n/locales/ru-RU/deployments.json index a15eff066d2..4f87cf1da69 100644 --- a/web/i18n/ru-RU/deployments.json +++ b/web/i18n/locales/ru-RU/deployments.json @@ -27,7 +27,6 @@ "list.emptyTitle": "Пока нет развертываний", "overview.chip.latest": "последний", "status.RUNTIME_INSTANCE_STATUS_DEPLOYING": "Развертывание", - "status.RUNTIME_INSTANCE_STATUS_FAILED": "Ошибка развертывания", "status.RUNTIME_INSTANCE_STATUS_INVALID": "Недействительный", "status.RUNTIME_INSTANCE_STATUS_READY": "Запущено", "status.RUNTIME_INSTANCE_STATUS_UNDEPLOYED": "Не развернуто", @@ -87,6 +86,5 @@ "studio.versionValue": "Значение версии", "studio.versionsBehind_one": "Отставание на {{count}} версию", "studio.versionsBehind_other": "Отставание на {{count}} версий", - "versions.checkingReleaseContent": "Проверка содержимого Релиза...", "versions.deployTo": "Развернуть в {{name}}" } diff --git a/web/i18n/ru-RU/device-flow.json b/web/i18n/locales/ru-RU/device-flow.json similarity index 100% rename from web/i18n/ru-RU/device-flow.json rename to web/i18n/locales/ru-RU/device-flow.json diff --git a/web/i18n/ru-RU/education.json b/web/i18n/locales/ru-RU/education.json similarity index 100% rename from web/i18n/ru-RU/education.json rename to web/i18n/locales/ru-RU/education.json diff --git a/web/i18n/ru-RU/explore.json b/web/i18n/locales/ru-RU/explore.json similarity index 100% rename from web/i18n/ru-RU/explore.json rename to web/i18n/locales/ru-RU/explore.json diff --git a/web/i18n/ru-RU/layout.json b/web/i18n/locales/ru-RU/layout.json similarity index 100% rename from web/i18n/ru-RU/layout.json rename to web/i18n/locales/ru-RU/layout.json diff --git a/web/i18n/ru-RU/login.json b/web/i18n/locales/ru-RU/login.json similarity index 100% rename from web/i18n/ru-RU/login.json rename to web/i18n/locales/ru-RU/login.json diff --git a/web/i18n/ru-RU/oauth.json b/web/i18n/locales/ru-RU/oauth.json similarity index 100% rename from web/i18n/ru-RU/oauth.json rename to web/i18n/locales/ru-RU/oauth.json diff --git a/web/i18n/ru-RU/permission-keys.json b/web/i18n/locales/ru-RU/permission-keys.json similarity index 100% rename from web/i18n/ru-RU/permission-keys.json rename to web/i18n/locales/ru-RU/permission-keys.json diff --git a/web/i18n/ru-RU/permission.json b/web/i18n/locales/ru-RU/permission.json similarity index 100% rename from web/i18n/ru-RU/permission.json rename to web/i18n/locales/ru-RU/permission.json diff --git a/web/i18n/ru-RU/pipeline.json b/web/i18n/locales/ru-RU/pipeline.json similarity index 100% rename from web/i18n/ru-RU/pipeline.json rename to web/i18n/locales/ru-RU/pipeline.json diff --git a/web/i18n/ru-RU/plugin-tags.json b/web/i18n/locales/ru-RU/plugin-tags.json similarity index 100% rename from web/i18n/ru-RU/plugin-tags.json rename to web/i18n/locales/ru-RU/plugin-tags.json diff --git a/web/i18n/ru-RU/plugin-trigger.json b/web/i18n/locales/ru-RU/plugin-trigger.json similarity index 100% rename from web/i18n/ru-RU/plugin-trigger.json rename to web/i18n/locales/ru-RU/plugin-trigger.json diff --git a/web/i18n/ru-RU/plugin.json b/web/i18n/locales/ru-RU/plugin.json similarity index 99% rename from web/i18n/ru-RU/plugin.json rename to web/i18n/locales/ru-RU/plugin.json index 5f0e69c4557..08844a7ace2 100644 --- a/web/i18n/ru-RU/plugin.json +++ b/web/i18n/locales/ru-RU/plugin.json @@ -277,7 +277,6 @@ "readmeInfo.noReadmeAvailable": "README недоступен", "readmeInfo.title": "ПРОЧТИ_МЕНЯ", "requestAPlugin": "Запросите плагин", - "requestSubmit": "Запросить или опубликовать", "search": "Искать", "searchCategories": "Поиск категорий", "searchInMarketplace": "Поиск в маркетплейсе", diff --git a/web/i18n/ru-RU/register.json b/web/i18n/locales/ru-RU/register.json similarity index 100% rename from web/i18n/ru-RU/register.json rename to web/i18n/locales/ru-RU/register.json diff --git a/web/i18n/ru-RU/run-log.json b/web/i18n/locales/ru-RU/run-log.json similarity index 100% rename from web/i18n/ru-RU/run-log.json rename to web/i18n/locales/ru-RU/run-log.json diff --git a/web/i18n/ru-RU/share.json b/web/i18n/locales/ru-RU/share.json similarity index 100% rename from web/i18n/ru-RU/share.json rename to web/i18n/locales/ru-RU/share.json diff --git a/web/i18n/ru-RU/skill.json b/web/i18n/locales/ru-RU/skill.json similarity index 83% rename from web/i18n/ru-RU/skill.json rename to web/i18n/locales/ru-RU/skill.json index 6fc685dfaa7..eb3f70eef19 100644 --- a/web/i18n/ru-RU/skill.json +++ b/web/i18n/locales/ru-RU/skill.json @@ -1,5 +1,4 @@ { - "skillManagement.clearTags": "Очистить теги", "skillManagement.create": "Создать", "skillManagement.createFailed": "Не удалось создать навык.", "skillManagement.createSuccess": "Черновик создан.", @@ -13,12 +12,9 @@ "skillManagement.deleteFailed": "Не удалось удалить навык.", "skillManagement.deleteSuccess": "Skill удален.", "skillManagement.detail.addMetadata": "Добавить метаданные", - "skillManagement.detail.addMetadataDescription": "Добавьте поле frontmatter в этот файл Markdown.", "skillManagement.detail.addTag": "Добавить тег", - "skillManagement.detail.addTagDescription": "Создайте или привяжите тег к этому Skill. Новые теги сохраняются с метаданными Skill.", "skillManagement.detail.addTagSuccess": "Тег добавлен.", "skillManagement.detail.back": "Вернуться к Skills", - "skillManagement.detail.builder.applyFailed": "Не удалось применить черновики обновлений.", "skillManagement.detail.builder.attach": "Прикрепить файл", "skillManagement.detail.builder.attachFailed": "Не удалось прикрепить файл.", "skillManagement.detail.builder.attachLimit": "Вы можете прикрепить до 10 файлов.", @@ -26,22 +22,12 @@ "skillManagement.detail.builder.attachUnsupported": "Прикреплять можно только изображения, текст и файлы документов.", "skillManagement.detail.builder.attachmentOnlyMessage": "Используйте прикрепленный файл, чтобы улучшить этот навык.", "skillManagement.detail.builder.close": "Закрыть", - "skillManagement.detail.builder.compatibleModelsOnly": "Показаны только совместимые модели", "skillManagement.detail.builder.copyResponse": "Копировать ответ", "skillManagement.detail.builder.copySuccess": "Ответ скопирован в буфер обмена.", "skillManagement.detail.builder.editIntro": "Я рассмотрел этот навык. Что бы вы хотели настроить?", "skillManagement.detail.builder.exampleIssueTriage": "Сортировка проблем клиентов", "skillManagement.detail.builder.exampleOnboarding": "Руководство по адаптации новых сотрудников", "skillManagement.detail.builder.exampleSalesFollowUp": "Стратегия отслеживания продаж", - "skillManagement.detail.builder.followUpDisplayName": "Использовать одобрение возврата в качестве отображаемого имени.", - "skillManagement.detail.builder.followUpNameIcon": "Примените предложенное имя и значок", - "skillManagement.detail.builder.fromMarketplace": "Из Marketplace", - "skillManagement.detail.builder.model": "GPT-4o", - "skillManagement.detail.builder.modelCredits.all": "Все кредиты", - "skillManagement.detail.builder.modelCredits.configure": "Требуется настройка", - "skillManagement.detail.builder.modelCredits.exhausted": "Квота исчерпана", - "skillManagement.detail.builder.modelProviderSettings": "Настройки поставщика моделей", - "skillManagement.detail.builder.modelSearch": "Поиск моделей...", "skillManagement.detail.builder.modifyPlaceholder": "Попросите ИИ изменить этот навык...", "skillManagement.detail.builder.open": "Открыть Skill Builder", "skillManagement.detail.builder.placeholder": "Опишите сценарий...", @@ -61,8 +47,6 @@ "skillManagement.detail.builder.thinkingUnavailable": "Модель не вернула никакого содержания мышления.", "skillManagement.detail.builder.title": "Skill Builder", "skillManagement.detail.builder.tryExample": "Попробуйте пример", - "skillManagement.detail.builder.voice": "Голосовой ввод", - "skillManagement.detail.builder.voiceUnavailable": "Голосовой ввод пока недоступен.", "skillManagement.detail.cancelAddMetadata": "Отменить добавление метаданных", "skillManagement.detail.closeFileTab": "Закрыть {{name}}", "skillManagement.detail.closeVersions": "Закрыть версии", @@ -70,14 +54,10 @@ "skillManagement.detail.copyContentSuccess": "Содержимое скопировано в буфер обмена.", "skillManagement.detail.copyFile": "Копировать", "skillManagement.detail.copyFileSuccess": "Файл готов к вставке.", - "skillManagement.detail.copyVersionId": "Копировать ID", - "skillManagement.detail.copyVersionIdSuccess": "Идентификатор скопирован в буфер обмена.", "skillManagement.detail.createFile": "Новый файл", - "skillManagement.detail.createFileDescription": "Введите путь к файлу относительно корня навыка.", "skillManagement.detail.createFileMenu": "Новый файл...", "skillManagement.detail.createFileSuccess": "Файл создан.", "skillManagement.detail.createFolder": "Новая папка", - "skillManagement.detail.createFolderDescription": "Введите путь к папке относительно корня навыка.", "skillManagement.detail.createFolderMenu": "Новая папка...", "skillManagement.detail.createFolderSuccess": "Папка создана.", "skillManagement.detail.createdBy": "Создано {{name}}", @@ -100,10 +80,8 @@ "skillManagement.detail.fileMeta": "{{type}} · {{size}} байт", "skillManagement.detail.fileMissing": "Файл не найден.", "skillManagement.detail.fileOperationFailed": "Операция с файлом не удалась.", - "skillManagement.detail.files": "Файлы", "skillManagement.detail.latest": "Последний", "skillManagement.detail.loadFailed": "Не удалось загрузить навык.", - "skillManagement.detail.loadingTags": "Загрузка тегов...", "skillManagement.detail.markdownLiveMode": "Предварительный просмотр в реальном времени", "skillManagement.detail.markdownSourceMode": "Редактор исходного кода", "skillManagement.detail.metadataKey": "имя", @@ -113,10 +91,7 @@ "skillManagement.detail.nameThisVersion": "Назовите эту версию", "skillManagement.detail.noFileSelected": "Выберите файл для предварительного просмотра.", "skillManagement.detail.noFiles": "Нет файлов.", - "skillManagement.detail.noSearchResults": "Нет подходящих файлов.", - "skillManagement.detail.noSelectableTags": "Нет доступных тегов.", "skillManagement.detail.noVersions": "Опубликованных версий пока нет.", - "skillManagement.detail.pasteFile": "Вставить", "skillManagement.detail.pasteFileSuccess": "Файл вставлен.", "skillManagement.detail.previewUnsupported": "Этот файл не поддерживает предварительный просмотр.", "skillManagement.detail.publish": "Опубликовать", @@ -128,9 +103,7 @@ "skillManagement.detail.publishUpdate": "Опубликовать обновление", "skillManagement.detail.published": "Опубликовано", "skillManagement.detail.publishedAt": "Опубликовано {{time}}", - "skillManagement.detail.publishedVersion": "Опубликовано #{{number}}", "skillManagement.detail.publishing": "Публикация...", - "skillManagement.detail.readonly": "Только чтение", "skillManagement.detail.referenceFiles.confirm": "Войти Подтвердить", "skillManagement.detail.referenceFiles.empty": "Нет доступных файлов.", "skillManagement.detail.referenceFiles.livePlaceholder": "Напишите инструкции для агента. Введите / для ссылки на файлы.", @@ -138,14 +111,10 @@ "skillManagement.detail.referenceFiles.title": "Справочные файлы", "skillManagement.detail.referencedBy_one": "Используется {{count}} агентом", "skillManagement.detail.referencedBy_other": "Используется {{count}} агентами", - "skillManagement.detail.removeMetadata": "Удалить метаданные {{name}}", "skillManagement.detail.removeTag": "Удалить {{tag}}", "skillManagement.detail.removeTagSuccess": "Тег удален.", - "skillManagement.detail.renameFile": "Переименовать файл", - "skillManagement.detail.renameFileDescription": "Введите новый путь относительно корня навыка.", "skillManagement.detail.renameFileSuccess": "Файл переименован.", "skillManagement.detail.renameSkillSuccess": "Skill переименован.", - "skillManagement.detail.renameVersion": "Переименовать", "skillManagement.detail.renameVersionFailed": "Не удалось переименовать версию.", "skillManagement.detail.renameVersionPrompt": "Название версии", "skillManagement.detail.renameVersionSuccess": "Версия переименована.", @@ -155,17 +124,14 @@ "skillManagement.detail.restoreVersionConfirmTitle": "Восстановить эту версию?", "skillManagement.detail.restoreVersionFailed": "Не удалось восстановить версию.", "skillManagement.detail.restoreVersionSuccess": "Версия восстановлена ​​до черновика.", - "skillManagement.detail.save": "Сохранить", "skillManagement.detail.saveConflictConfirmDescription": "Этот навык был обновлен в другом месте. Загрузите последний черновик, чтобы продолжить редактирование. Ваши текущие изменения будут отменены.", "skillManagement.detail.saveConflictConfirmTitle": "Загрузить последний черновик?", "skillManagement.detail.saveConflictReload": "Загрузить последний черновик", "skillManagement.detail.saveConflictStatus": "Обнаружен конфликт. Загрузите последний черновик, чтобы продолжить редактирование.", "skillManagement.detail.saveFailed": "Не удалось сохранить файл.", - "skillManagement.detail.saveSuccess": "Файл сохранен.", "skillManagement.detail.saved": "Сохранено", "skillManagement.detail.savedAt": "Сохранено {{time}}", "skillManagement.detail.saving": "Сохранение...", - "skillManagement.detail.searchFiles": "Поиск файлов", "skillManagement.detail.showMoreReferences": "Показать {{count}} больше", "skillManagement.detail.skillDescriptionPlaceholder": "Опишите, что делает этот навык и когда агентам следует его использовать.", "skillManagement.detail.skillNamePlaceholder": "Используйте строчные буквы, цифры и дефисы.", @@ -177,16 +143,12 @@ "skillManagement.detail.uploadConflict": "Файл с таким именем уже существует.", "skillManagement.detail.uploadFailureSummary": "Ошибка {{failed}} из файлов {{total}}.", "skillManagement.detail.uploadFailureTitle": "Не удалось загрузить некоторые файлы", - "skillManagement.detail.uploadFile": "Загрузить файл", - "skillManagement.detail.uploadFileFailed": "Загрузка файла не удалась.", "skillManagement.detail.uploadFileSuccess": "Файл загружен.", "skillManagement.detail.uploadFilesButton": "Загрузите файлы {{count}}", "skillManagement.detail.uploadFilesFailedStatus_one": "Не удалось загрузить файл {{count}}.", "skillManagement.detail.uploadFilesFailedStatus_other": "Не удалось загрузить файл {{count}}.", "skillManagement.detail.uploadFilesMenu": "Загрузить файлы...", "skillManagement.detail.uploadFilesProgress": "Загрузка {{completed}}/{{total}}", - "skillManagement.detail.uploadFilesResult": "{{uploaded}} загружен · {{failed}} не удалось", - "skillManagement.detail.uploadFilesStatus": "Статус загрузки", "skillManagement.detail.uploadInvalidExtension": "Этот тип файла не поддерживается.", "skillManagement.detail.uploadInvalidName": "Неверное имя.", "skillManagement.detail.uploadInvalidPath": "Неверный путь к файлу.", @@ -206,7 +168,6 @@ "skillManagement.detail.uploadWillReplace": "Заменит существующий файл", "skillManagement.detail.uploadWillUseName": "Загружу как {{name}}", "skillManagement.detail.versionHistory": "Открыть историю версий", - "skillManagement.detail.versionMeta": "{{hash}} · {{time}}", "skillManagement.detail.versionPublishNote": "Примечания к выпуску", "skillManagement.detail.versionPublishNotePlaceholder": "Опишите это изменение", "skillManagement.detail.versionPublishedMeta": "{{time}} · {{name}}", @@ -227,8 +188,6 @@ "skillManagement.errors.missingSkillMd": "В этом пакете нет SKILL.md. Добавьте файл и импортируйте его еще раз.", "skillManagement.errors.nameConflict": "Навык под названием «{{name}}» уже существует. Переименуйте его в SKILL.md и импортируйте снова.", "skillManagement.errors.workspaceLimit": "Ваше рабочее пространство достигло предела в 500 навыков. Удалите навык, чтобы добавить новый.", - "skillManagement.exportDialog.description": "Будет экспортирован текущий сохранённый черновик навыка. Если в редакторе есть несохранённые изменения, сохраните их перед экспортом.", - "skillManagement.exportDialog.title": "Экспорт текущего сохранённого черновика", "skillManagement.import": "Импортировать", "skillManagement.importFailed": "Не удалось импортировать навык.", "skillManagement.importSuccess": "Skill импортирован.", @@ -236,12 +195,10 @@ "skillManagement.loadingError": "Не удалось загрузить навыки", "skillManagement.moreActions": "Дополнительные действия для {{name}}", "skillManagement.noDescription": "Описания пока нет", - "skillManagement.noTags": "Нет тегов", "skillManagement.publishedAt": "Опубликовано {{time}}", "skillManagement.referenceCount_one": "{{count}} агент", "skillManagement.referenceCount_other": "{{count}} агентов", "skillManagement.searchLabel": "Поиск навыков", "skillManagement.searchPlaceholder": "Поиск по имени или описанию", - "skillManagement.tags": "Теги", "skillManagement.title": "Навыки" } diff --git a/web/i18n/ru-RU/snippet.json b/web/i18n/locales/ru-RU/snippet.json similarity index 100% rename from web/i18n/ru-RU/snippet.json rename to web/i18n/locales/ru-RU/snippet.json diff --git a/web/i18n/ru-RU/time.json b/web/i18n/locales/ru-RU/time.json similarity index 100% rename from web/i18n/ru-RU/time.json rename to web/i18n/locales/ru-RU/time.json diff --git a/web/i18n/ru-RU/tools.json b/web/i18n/locales/ru-RU/tools.json similarity index 100% rename from web/i18n/ru-RU/tools.json rename to web/i18n/locales/ru-RU/tools.json diff --git a/web/i18n/ru-RU/workflow.json b/web/i18n/locales/ru-RU/workflow.json similarity index 99% rename from web/i18n/ru-RU/workflow.json rename to web/i18n/locales/ru-RU/workflow.json index 47e4b7fbc44..251bcc9ab63 100644 --- a/web/i18n/ru-RU/workflow.json +++ b/web/i18n/locales/ru-RU/workflow.json @@ -460,8 +460,6 @@ "nodes.agent.pluginNotInstalledDesc": "Этот интеграция устанавливается с GitHub. Пожалуйста, перейдите в раздел Интеграции для переустановки", "nodes.agent.roster.change": "Сменить", "nodes.agent.roster.editInConsole": "Редактировать в Agents", - "nodes.agent.roster.editInConsoleDisabled": "Для редактирования агентов требуется роль «Редактор» или выше.", - "nodes.agent.roster.editInConsoleDisabledRbac": "Редактировать агентов могут только участники с ролью «Редактор» или выше либо с разрешением «Управление Agents».", "nodes.agent.roster.inlineSetup.description": "Одноразовая настройка, существующая только в этом узле", "nodes.agent.roster.inlineSetup.name": "Встроенная настройка", "nodes.agent.roster.inlineSetup.type": "Только в этом узле", diff --git a/web/i18n/sl-SI/agent-v-2.json b/web/i18n/locales/sl-SI/agent-v-2.json similarity index 98% rename from web/i18n/sl-SI/agent-v-2.json rename to web/i18n/locales/sl-SI/agent-v-2.json index de265bcbda0..0d9e8dbb142 100644 --- a/web/i18n/sl-SI/agent-v-2.json +++ b/web/i18n/locales/sl-SI/agent-v-2.json @@ -69,6 +69,7 @@ "agentDetail.configure.answer.activity.runningCommands": "Izvajanje ukazov", "agentDetail.configure.answer.duration.minute": "{{count}} min", "agentDetail.configure.answer.duration.second": "{{count}} s", + "agentDetail.configure.answer.responseEnded": "Odgovor je končan", "agentDetail.configure.answer.thinking": "Razmišlja", "agentDetail.configure.build.empty.communityEditionTip": "Community Edition sicer ponuja različice za izvlečene konfiguracije, vendar ne podpira različic samega datotečnega sistema. Pri dejanjih v načinu Build bodite previdni, saj se spremembe datotečnega sistema zgodijo v realnem času in jih ni vedno mogoče lepo razveljaviti. Community Edition ni idealna izbira za množično zunanjo publiko.", "agentDetail.configure.build.empty.description": "Opišite, kaj želite, in obrazec na levi se bo sproti izpolnjeval.", @@ -269,7 +270,6 @@ "agentDetail.configure.workingDirectory.breadcrumbLabel": "Pot datotečnega sistema", "agentDetail.configure.workingDirectory.description": "Datoteke, ki jih agent bere in zapisuje med izvajanjem. Vse, kar ustvari, na primer qna_report.pdf, se prikaže tukaj.", "agentDetail.configure.workingDirectory.fileSystem": "Datotečni sistem", - "agentDetail.configure.workingDirectory.open": "Odpri delovni imenik", "agentDetail.configure.workingDirectory.persistentFiles": "Trajno", "agentDetail.configure.workingDirectory.persistentFilesTooltip": "Datoteke, ki jih agent ohrani in jih lahko uporablja povsod, kjer se izvaja, vključno s pogovori končnih uporabnikov in delovnimi tokovi. Spremembe, ki jih med tem pogovorom za gradnjo naredite tukaj, začnejo veljati, ko kliknete »Uporabi«.", "agentDetail.configure.workingDirectory.temporaryFiles": "Začasno", @@ -348,7 +348,6 @@ "agentDetail.sections.logs": "Dnevniki", "agentDetail.sections.monitoring": "Spremljanje", "agentDetail.title": "Agent", - "agentDetail.type": "AGENT", "agentDetail.versionHistory.empty": "Še ni različic", "agentDetail.versionHistory.exitVersions": "Zapusti različice", "agentDetail.versionHistory.filter": "Filtriraj različice", @@ -408,8 +407,5 @@ "roster.sort.lastModified": "Nazadnje spremenjeno", "roster.sort.recentlyCreated": "Nedavno ustvarjeno", "roster.title": "Agenti", - "roster.usageStatus.draft": "Osnutek", - "skillManagement.detail.uploadFilesFailedStatus_one": "Nalaganje {{count}} datoteke ni uspelo.", - "skillManagement.detail.uploadFilesFailedStatus_other": "Nalaganje {{count}} datotek ni uspelo.", - "skillManagement.title": "Skills" + "roster.usageStatus.draft": "Osnutek" } diff --git a/web/i18n/sl-SI/app-annotation.json b/web/i18n/locales/sl-SI/app-annotation.json similarity index 100% rename from web/i18n/sl-SI/app-annotation.json rename to web/i18n/locales/sl-SI/app-annotation.json diff --git a/web/i18n/sl-SI/app-api.json b/web/i18n/locales/sl-SI/app-api.json similarity index 100% rename from web/i18n/sl-SI/app-api.json rename to web/i18n/locales/sl-SI/app-api.json diff --git a/web/i18n/sl-SI/app-debug.json b/web/i18n/locales/sl-SI/app-debug.json similarity index 100% rename from web/i18n/sl-SI/app-debug.json rename to web/i18n/locales/sl-SI/app-debug.json diff --git a/web/i18n/sl-SI/app-log.json b/web/i18n/locales/sl-SI/app-log.json similarity index 97% rename from web/i18n/sl-SI/app-log.json rename to web/i18n/locales/sl-SI/app-log.json index 25ca1864388..8c35e0ece50 100644 --- a/web/i18n/sl-SI/app-log.json +++ b/web/i18n/locales/sl-SI/app-log.json @@ -4,11 +4,9 @@ "agentLogDetail.iteration": "Iteracija", "agentLogDetail.iterations": "Iteracije", "agentLogDetail.toolUsed": "Uporabljeno orodje", - "archives.action.downloadMonth": "Prenesi arhiv za {{month}}", "archives.action.downloadReady": "Prenos arhiva je pripravljen.", "archives.action.prepareDownload": "Pripravi prenos", "archives.action.prepareFailed": "Priprava prenosa arhiva ni uspela.", - "archives.action.prepareMonth": "Pripravi prenos arhiva za {{month}}", "archives.action.prepareStarted": "Prenos arhiva se pripravlja.", "archives.action.preparing": "Pripravljanje", "archives.description": "Prenesite mesečne izvoze CSV arhiviranih dnevnikov zagonov workflow v tem delovnem prostoru.", diff --git a/web/i18n/sl-SI/app-overview.json b/web/i18n/locales/sl-SI/app-overview.json similarity index 100% rename from web/i18n/sl-SI/app-overview.json rename to web/i18n/locales/sl-SI/app-overview.json diff --git a/web/i18n/sl-SI/app.json b/web/i18n/locales/sl-SI/app.json similarity index 100% rename from web/i18n/sl-SI/app.json rename to web/i18n/locales/sl-SI/app.json diff --git a/web/i18n/sl-SI/billing.json b/web/i18n/locales/sl-SI/billing.json similarity index 100% rename from web/i18n/sl-SI/billing.json rename to web/i18n/locales/sl-SI/billing.json diff --git a/web/i18n/sl-SI/common.json b/web/i18n/locales/sl-SI/common.json similarity index 98% rename from web/i18n/sl-SI/common.json rename to web/i18n/locales/sl-SI/common.json index 62feb5826a0..af9f0c968cb 100644 --- a/web/i18n/sl-SI/common.json +++ b/web/i18n/locales/sl-SI/common.json @@ -209,9 +209,7 @@ "mainNav.marketplace": "Marketplace", "mainNav.skills": "Skills", "mainNav.webApps.noResults": "Ni najdenih spletnih aplikacij", - "mainNav.webApps.openApp": "Odpri spletno aplikacijo {{name}}", "mainNav.webApps.searchPlaceholder": "Išči spletne aplikacije", - "mainNav.workspace.credits": "{{count}} kreditov", "mainNav.workspace.creditsUnit": "krediti", "mainNav.workspace.inviteMembers": "Invite members", "mainNav.workspace.noResults": "Ni najdenih delovnih prostorov", @@ -233,7 +231,6 @@ "members.datasetOperatorTip": "Lahko upravlja samo bazo znanja", "members.editRole": "Uredi vlogo", "members.editorTip": "Lahko ustvarja in ureja aplikacije", - "members.email": "E-pošta", "members.emailInvalid": "Neveljaven format e-pošte", "members.emailNotSetup": "E-poštni strežnik ni nastavljen, zato vabil po e-pošti ni mogoče poslati. Prosimo, obvestite uporabnike o povezavi za povabilo, ki bo izdana po povabilu.", "members.emailPlaceholder": "Vnesite ali prilepite e-poštne naslove…", @@ -250,7 +247,6 @@ "members.inviteLimitExceeded": "Za vse prejemnike ni na voljo dovolj mest.", "members.inviteTeamMember": "Dodaj člana ekipe", "members.inviteTeamMemberTip": "Do vaših podatkov bo lahko dostopal takoj po prijavi.", - "members.invitedAsRole": "Povabljen kot {{role}}", "members.lastActive": "NAZADNJE AKTIVEN", "members.memberActions": "Dejanja člana", "members.memberDetails.assign": "Dodeli", @@ -258,8 +254,6 @@ "members.memberDetails.assignedRoles": "Dodeljene vloge", "members.memberDetails.customGroup": "PRILAGOJENO", "members.memberDetails.generalGroup": "SPLOŠNO", - "members.memberDetails.openAria": "Odpri podrobnosti člana za {{name}}", - "members.memberDetails.roleActionsAria": "Odpri dejanja za vlogo {{role}}", "members.memberDetails.roleNoPermissionSummary": "Trenutna vloga nima dovoljenj.", "members.memberDetails.rolePermissionSummary": "{{role}} lahko {{permissions}}", "members.memberDetails.title": "Podrobnosti člana", @@ -370,7 +364,6 @@ "modelProvider.card.usagePriorityTip": "Nastavite, kateri vir se bo najprej uporabil pri zaganjanju modelov.", "modelProvider.configureTip": "Nastavitev tipke API ali dodajanje modela za uporabo", "modelProvider.confirmDelete": "Potrdite izbris?", - "modelProvider.credits": "Sporočilni krediti", "modelProvider.defaultConfig": "Privzeta konfiguracija", "modelProvider.discoverMore": "Odkrijte več v", "modelProvider.embeddingModel.key": "Model za vdelavo", @@ -427,7 +420,6 @@ "modelProvider.selector.noProviderConfiguredDesc": "Poiščite v Tržnici za namestitev ali konfigurirajte ponudnike v nastavitvah.", "modelProvider.selector.onlyCompatibleModelsShown": "Prikazani so samo združljivi modeli", "modelProvider.selector.showIncompatibleModels": "Prikaži nezdružljive modele", - "modelProvider.selector.suggestion": "Predlog", "modelProvider.selector.suggestionTip": "Priporočeno za opravila agenta", "modelProvider.showModels": "Prikaži modele", "modelProvider.speechToTextModel.key": "Model za pretvorbo govora v besedilo", @@ -458,6 +450,8 @@ "operation.confirmAction": "Prosimo, potrdite svoje dejanje.", "operation.copied": "Kopirati", "operation.copy": "Kopiraj", + "operation.copyErrorDetails": "Kopiraj podrobnosti napake", + "operation.copyErrorFailed": "Kopiranje ni uspelo. Poskusi znova", "operation.copyImage": "Kopiraj sliko", "operation.create": "Ustvari", "operation.deSelectAll": "Odberi vse", @@ -475,7 +469,6 @@ "operation.format": "Format", "operation.imageCopied": "Kopirana slika", "operation.imageCopyFailed": "Slike ni bilo mogoče kopirati. Namesto tega jo prenesite.", - "operation.imageDownloaded": "Slika prenesena", "operation.in": "v", "operation.learnMore": "Izvedi več", "operation.log": "Dnevnik", @@ -512,7 +505,6 @@ "operation.sure": "Prepričan sem", "operation.toggleFullscreen": "Preklopi celozaslonski način", "operation.toggleMute": "Preklopi utišanje", - "operation.upload": "Upload", "operation.view": "Pogled", "operation.viewDetails": "Poglej podrobnosti", "operation.viewMore": "POGLEJ VEČ", @@ -542,7 +534,6 @@ "promptEditor.requestURL.item.title": "URL zahteve", "promptEditor.variable.modal.add": "Nova spremenljivka", "promptEditor.variable.modal.addTool": "Novo orodje", - "provider.anthropicHosted.trialQuotaTip": "Vaša kvota za preskušanje antropičnih izdelkov bo potekla 11.3.2025 in po tem datumu ne bo več na voljo. Prosimo, da jo pravočasno izkoristite.", "provider.encrypted.back": " tehnologije.", "provider.encrypted.front": "Vaš API ključ bo šifriran in shranjen z uporabo", "provider.validating": "Preverjam ključ...", @@ -565,7 +556,6 @@ "settings.preferences": "Preferences", "settings.provider": "Ponudnik modelov", "settings.resourceAccess": "Dostop do virov", - "settings.resourceAccessDescription": "Konfigurirajte pravila dovoljenj, ki jih lahko uporabljajo aplikacije in baze znanja. Pravila dovoljenj je mogoče dodeliti članom v določenih konfiguracijah dostopa do virov.", "settings.rolesAndPermissions": "Vloge in dovoljenja", "settings.settings": "Settings", "settings.swaggerAPIAsTool": "Swagger API as Tool", @@ -695,7 +685,6 @@ "userProfile.helpCenter": "Pomoč", "userProfile.logout": "Odjava", "userProfile.roadmap": "Načrt razvoja", - "userProfile.settings": "Nastavitve", "userProfile.workspace": "Delovni prostor", "voice.language.arTN": "Tunizijska arabščina", "voice.language.deDE": "Nemščina", diff --git a/web/i18n/sl-SI/custom.json b/web/i18n/locales/sl-SI/custom.json similarity index 100% rename from web/i18n/sl-SI/custom.json rename to web/i18n/locales/sl-SI/custom.json diff --git a/web/i18n/sl-SI/dataset-creation.json b/web/i18n/locales/sl-SI/dataset-creation.json similarity index 100% rename from web/i18n/sl-SI/dataset-creation.json rename to web/i18n/locales/sl-SI/dataset-creation.json diff --git a/web/i18n/sl-SI/dataset-documents.json b/web/i18n/locales/sl-SI/dataset-documents.json similarity index 100% rename from web/i18n/sl-SI/dataset-documents.json rename to web/i18n/locales/sl-SI/dataset-documents.json diff --git a/web/i18n/sl-SI/dataset-hit-testing.json b/web/i18n/locales/sl-SI/dataset-hit-testing.json similarity index 100% rename from web/i18n/sl-SI/dataset-hit-testing.json rename to web/i18n/locales/sl-SI/dataset-hit-testing.json diff --git a/web/i18n/sl-SI/dataset-pipeline.json b/web/i18n/locales/sl-SI/dataset-pipeline.json similarity index 100% rename from web/i18n/sl-SI/dataset-pipeline.json rename to web/i18n/locales/sl-SI/dataset-pipeline.json diff --git a/web/i18n/sl-SI/dataset-settings.json b/web/i18n/locales/sl-SI/dataset-settings.json similarity index 100% rename from web/i18n/sl-SI/dataset-settings.json rename to web/i18n/locales/sl-SI/dataset-settings.json diff --git a/web/i18n/sl-SI/dataset.json b/web/i18n/locales/sl-SI/dataset.json similarity index 97% rename from web/i18n/sl-SI/dataset.json rename to web/i18n/locales/sl-SI/dataset.json index 37ce7660171..b66e1727f2c 100644 --- a/web/i18n/sl-SI/dataset.json +++ b/web/i18n/locales/sl-SI/dataset.json @@ -163,7 +163,6 @@ "newKnowledge.configureProvider": "Konfigurirajte {{provider}}", "newKnowledge.connectNotion": "Poveži Notion", "newKnowledge.connectProvider": "Povežite {{provider}}", - "newKnowledge.connectProviderGeneric": "Poveži ponudnika", "newKnowledge.connectSource": "Poveži vir", "newKnowledge.connectSourceDescription": "Vsebina ostaja sinhronizirana z Notionom, spletnimi mesti in drugimi storitvami.", "newKnowledge.connectingProvider": "Povezovanje ...", @@ -179,18 +178,14 @@ "newKnowledge.crawlFailedDescription": "Pajek ni mogel dokončati tega predogleda. Preverite URL, prilagodite možnosti preiskovanja ali poskusite znova.", "newKnowledge.crawlFailedProvider": "{{provider}} je sporočil napako. Preverite povezavo s ponudnikom in poskusite znova.", "newKnowledge.crawlFailedTimeout": "Čas za preiskovanje je potekel. Poskusite z natančnejšim URL-jem ali zmanjšajte največje število strani.", - "newKnowledge.crawlNotStarted": "Ni začeto", "newKnowledge.crawlOptions": "Možnosti preiskovanja", "newKnowledge.crawlPreview": "Predogled iskanja", "newKnowledge.crawlProgress": "Napredek preiskovanja za {{host}}", - "newKnowledge.crawlSetupUnavailableDescription": "Konfiguracija iskanja po vsebini spletnega mesta je onemogočena, dokler potek dela po vsebini ni na voljo.", - "newKnowledge.crawlSetupUnavailableTitle": "Firecrawl je pripravljen", "newKnowledge.crawlStartFailed": "Predogleda preiskovanja ni bilo mogoče zagnati. Poskusite znova.", "newKnowledge.crawlStopped": "Preiskovanje ustavljeno", "newKnowledge.crawling": "Preiskovanje…", "newKnowledge.crawlingPages": "Preiskovanje {{host}}… najdenih strani: {{count}}", "newKnowledge.createFailed": "Te baze znanja nismo mogli ustvariti. Preverite dostop in poskusite znova.", - "newKnowledge.createIllustrationTitle": "Vsebino povežite enkrat — vsaka aplikacija odgovarja z najnovejšim znanjem.", "newKnowledge.createTitle": "Ustvarite znanje", "newKnowledge.createdAt": "Ustvarjeno", "newKnowledge.creators": "Ustvarjalci", @@ -220,7 +215,6 @@ "newKnowledge.documentColumn": "Dokument", "newKnowledge.documentContentIncomplete": "Nalaganje preostale vsebine dokumenta…", "newKnowledge.documentContents": "Vsebina", - "newKnowledge.documentFacts": "Podrobnosti dokumenta", "newKnowledge.documentFilterLabel": "Filtrirajte dokumente po statusu", "newKnowledge.documentLoadErrorDescription": "Tega dokumenta ni bilo mogoče naložiti. Poskusite znova.", "newKnowledge.documentLoadErrorTitle": "Dokumenta ni bilo mogoče naložiti", @@ -268,7 +262,6 @@ "newKnowledge.documentsSelected": "Izbrani dokumenti {{count}}", "newKnowledge.documentsUnavailable": "— dokumentov", "newKnowledge.downloadDocuments": "Prenos", - "newKnowledge.editSource": "Uredi", "newKnowledge.emptyChunk": "Prazen del", "newKnowledge.emptyDescription": "Dodajte dokumente in agenti jih lahko berejo neposredno — brez nastavljanja indeksiranja.", "newKnowledge.emptyTitle": "Pametnejši način ustvarjanja baz znanja", @@ -344,7 +337,6 @@ "newKnowledge.processingTaskState.succeeded": "Dokončano", "newKnowledge.processingTaskState.superseded": "Nadomeščeno", "newKnowledge.providerConnected": "{{provider}} je povezan", - "newKnowledge.providerLabel": "Izberi ponudnika", "newKnowledge.providerLoadFailed": "Ponudnikov ni bilo mogoče naložiti", "newKnowledge.providerNotConfigured": "{{provider}} ni konfiguriran", "newKnowledge.providerNotConfiguredDescription": "Dodajte ključ API ali URL končne točke za iskanje po spletnih mestih z {{provider}}.", @@ -369,12 +361,10 @@ "newKnowledge.revisionState.superseded": "Prejšnja", "newKnowledge.rootUrl": "Korenski URL", "newKnowledge.rootUrlPlaceholder": "https://docs.example.com", - "newKnowledge.routeUnavailable": "Ta razdelek bo na voljo v naslednjem koraku nastavitve.", "newKnowledge.searchDocuments": "Iskanje dokumentov", "newKnowledge.searchSources": "Iskanje virov", "newKnowledge.selectAll": "Izberi vse", "newKnowledge.selectAllDocuments": "Izberite vse dokumente", - "newKnowledge.selectAllSources": "Izberi vse vire", "newKnowledge.selectChunk": "Izberite del za pregled njegove vsebine.", "newKnowledge.selectFilesAndFolders": "Izberite datoteke in mape za sinhronizacijo", "newKnowledge.skippedFailed": "Preskočeno (neuspešno)", @@ -384,7 +374,6 @@ "newKnowledge.sourceFilterLabel": "Filtrirajte vire po statusu", "newKnowledge.sourceName": "Ime vira", "newKnowledge.sourceNamePlaceholder": "npr. preiskovanje dokumentacije izdelka", - "newKnowledge.sourceNameRequired": "Vnesite ime vira.", "newKnowledge.sourceSetupBackendDependency": "Nastavitev vira je prikazana v celoti; urejanje in predogled zahtevata zaledni potek za osnutek vira.", "newKnowledge.sourceStatus.active": "Aktivno", "newKnowledge.sourceStatus.disabled": "Onemogočeno", @@ -431,7 +420,6 @@ "newKnowledge.unavailableDescription": "KnowledgeFS trenutno ni na voljo za ta delovni prostor.", "newKnowledge.unavailableTitle": "Novo znanje ni na voljo", "newKnowledge.updated": "Posodobljeno {{date}}", - "newKnowledge.updatedAt": "Nazadnje posodobljeno", "newKnowledge.updatedColumn": "Posodobljeno", "newKnowledge.uploadCharactersUnavailable": "Znaki: —", "newKnowledge.uploadDocuments": "Nalaganje dokumentov", @@ -460,11 +448,9 @@ "retrievalSettings": "Nastavitve pridobivanja", "retry": "Poskusi znova", "selectExternalKnowledgeAPI.placeholder": "Izberite zunanji API za znanje", - "serviceApi.card.addApiKey": "Dodaj API ključ", "serviceApi.card.apiKey": "API ključ", "serviceApi.card.apiReference": "API Referenca", "serviceApi.card.endpoint": "Vhodna točka API storitve", - "serviceApi.card.manageApiKey": "Upravljanje API ključa", "serviceApi.card.title": "API storitev za zaledje", "serviceApi.disabled": "Onemogočeno", "serviceApi.enabled": "V storitvi", diff --git a/web/i18n/sl-SI/deployments.json b/web/i18n/locales/sl-SI/deployments.json similarity index 97% rename from web/i18n/sl-SI/deployments.json rename to web/i18n/locales/sl-SI/deployments.json index 1a53ea271d3..31e6ae12c98 100644 --- a/web/i18n/sl-SI/deployments.json +++ b/web/i18n/locales/sl-SI/deployments.json @@ -27,7 +27,6 @@ "list.emptyTitle": "Še ni uvedb", "overview.chip.latest": "najnovejša", "status.RUNTIME_INSTANCE_STATUS_DEPLOYING": "Uvajanje", - "status.RUNTIME_INSTANCE_STATUS_FAILED": "Uvedba ni uspela", "status.RUNTIME_INSTANCE_STATUS_INVALID": "Neveljavno", "status.RUNTIME_INSTANCE_STATUS_READY": "Se izvaja", "status.RUNTIME_INSTANCE_STATUS_UNDEPLOYED": "Ni uvedeno", @@ -87,6 +86,5 @@ "studio.versionValue": "Vrednost različice", "studio.versionsBehind_one": "{{count}} različica zadaj", "studio.versionsBehind_other": "{{count}} različic zadaj", - "versions.checkingReleaseContent": "Preverjanje vsebine izdaje...", "versions.deployTo": "Uvedi v {{name}}" } diff --git a/web/i18n/sl-SI/device-flow.json b/web/i18n/locales/sl-SI/device-flow.json similarity index 100% rename from web/i18n/sl-SI/device-flow.json rename to web/i18n/locales/sl-SI/device-flow.json diff --git a/web/i18n/sl-SI/education.json b/web/i18n/locales/sl-SI/education.json similarity index 100% rename from web/i18n/sl-SI/education.json rename to web/i18n/locales/sl-SI/education.json diff --git a/web/i18n/sl-SI/explore.json b/web/i18n/locales/sl-SI/explore.json similarity index 100% rename from web/i18n/sl-SI/explore.json rename to web/i18n/locales/sl-SI/explore.json diff --git a/web/i18n/sl-SI/layout.json b/web/i18n/locales/sl-SI/layout.json similarity index 100% rename from web/i18n/sl-SI/layout.json rename to web/i18n/locales/sl-SI/layout.json diff --git a/web/i18n/sl-SI/login.json b/web/i18n/locales/sl-SI/login.json similarity index 100% rename from web/i18n/sl-SI/login.json rename to web/i18n/locales/sl-SI/login.json diff --git a/web/i18n/sl-SI/oauth.json b/web/i18n/locales/sl-SI/oauth.json similarity index 100% rename from web/i18n/sl-SI/oauth.json rename to web/i18n/locales/sl-SI/oauth.json diff --git a/web/i18n/sl-SI/permission-keys.json b/web/i18n/locales/sl-SI/permission-keys.json similarity index 100% rename from web/i18n/sl-SI/permission-keys.json rename to web/i18n/locales/sl-SI/permission-keys.json diff --git a/web/i18n/sl-SI/permission.json b/web/i18n/locales/sl-SI/permission.json similarity index 100% rename from web/i18n/sl-SI/permission.json rename to web/i18n/locales/sl-SI/permission.json diff --git a/web/i18n/sl-SI/pipeline.json b/web/i18n/locales/sl-SI/pipeline.json similarity index 100% rename from web/i18n/sl-SI/pipeline.json rename to web/i18n/locales/sl-SI/pipeline.json diff --git a/web/i18n/sl-SI/plugin-tags.json b/web/i18n/locales/sl-SI/plugin-tags.json similarity index 100% rename from web/i18n/sl-SI/plugin-tags.json rename to web/i18n/locales/sl-SI/plugin-tags.json diff --git a/web/i18n/sl-SI/plugin-trigger.json b/web/i18n/locales/sl-SI/plugin-trigger.json similarity index 100% rename from web/i18n/sl-SI/plugin-trigger.json rename to web/i18n/locales/sl-SI/plugin-trigger.json diff --git a/web/i18n/sl-SI/plugin.json b/web/i18n/locales/sl-SI/plugin.json similarity index 99% rename from web/i18n/sl-SI/plugin.json rename to web/i18n/locales/sl-SI/plugin.json index 5d9a0e813b0..04af361a1d5 100644 --- a/web/i18n/sl-SI/plugin.json +++ b/web/i18n/locales/sl-SI/plugin.json @@ -277,7 +277,6 @@ "readmeInfo.noReadmeAvailable": "README ni na voljo", "readmeInfo.title": "PREBERIME", "requestAPlugin": "Zahtevajte vtičnik", - "requestSubmit": "Zahtevaj ali objavi", "search": "Iskanje", "searchCategories": "Išči kategorije", "searchInMarketplace": "Iskanje na trgu", diff --git a/web/i18n/sl-SI/register.json b/web/i18n/locales/sl-SI/register.json similarity index 100% rename from web/i18n/sl-SI/register.json rename to web/i18n/locales/sl-SI/register.json diff --git a/web/i18n/sl-SI/run-log.json b/web/i18n/locales/sl-SI/run-log.json similarity index 100% rename from web/i18n/sl-SI/run-log.json rename to web/i18n/locales/sl-SI/run-log.json diff --git a/web/i18n/sl-SI/share.json b/web/i18n/locales/sl-SI/share.json similarity index 100% rename from web/i18n/sl-SI/share.json rename to web/i18n/locales/sl-SI/share.json diff --git a/web/i18n/sl-SI/skill.json b/web/i18n/locales/sl-SI/skill.json similarity index 83% rename from web/i18n/sl-SI/skill.json rename to web/i18n/locales/sl-SI/skill.json index 4613ff865f5..9896fd83f8d 100644 --- a/web/i18n/sl-SI/skill.json +++ b/web/i18n/locales/sl-SI/skill.json @@ -1,5 +1,4 @@ { - "skillManagement.clearTags": "Počisti oznake", "skillManagement.create": "Ustvari", "skillManagement.createFailed": "Ustvarjanje spretnosti ni uspelo.", "skillManagement.createSuccess": "Osnutek ustvarjen.", @@ -13,12 +12,9 @@ "skillManagement.deleteFailed": "Spretnosti ni bilo mogoče izbrisati.", "skillManagement.deleteSuccess": "Skill izbrisano.", "skillManagement.detail.addMetadata": "Dodajte metapodatke", - "skillManagement.detail.addMetadataDescription": "V to datoteko Markdown dodajte polje frontmatter.", "skillManagement.detail.addTag": "Dodaj oznako", - "skillManagement.detail.addTagDescription": "Ustvarite ali povežite oznako s tem Skill. Nove oznake so shranjene z metapodatki Skill.", "skillManagement.detail.addTagSuccess": "Dodana oznaka.", "skillManagement.detail.back": "Nazaj na Skills", - "skillManagement.detail.builder.applyFailed": "Uveljavitev osnutkov posodobitev ni uspela.", "skillManagement.detail.builder.attach": "Pripni datoteko", "skillManagement.detail.builder.attachFailed": "Datoteke ni bilo mogoče priložiti.", "skillManagement.detail.builder.attachLimit": "Priložite lahko do 10 datotek.", @@ -26,22 +22,12 @@ "skillManagement.detail.builder.attachUnsupported": "Priložite lahko samo slike, besedilo in dokumente.", "skillManagement.detail.builder.attachmentOnlyMessage": "Uporabite priloženo datoteko, da izboljšate to veščino.", "skillManagement.detail.builder.close": "Zapri Skill Builder", - "skillManagement.detail.builder.compatibleModelsOnly": "Prikazani so samo združljivi modeli", "skillManagement.detail.builder.copyResponse": "Kopiraj odgovor", "skillManagement.detail.builder.copySuccess": "Odgovor je kopiran v odložišče.", "skillManagement.detail.builder.editIntro": "Pregledal sem to veščino. Kaj bi radi prilagodili?", "skillManagement.detail.builder.exampleIssueTriage": "Triaža strankinih težav", "skillManagement.detail.builder.exampleOnboarding": "Priročnik za novo zaposlitev", "skillManagement.detail.builder.exampleSalesFollowUp": "Strategija spremljanja vodstva prodaje", - "skillManagement.detail.builder.followUpDisplayName": "Kot prikazno ime uporabite odobritev vračila", - "skillManagement.detail.builder.followUpNameIcon": "Uporabite predlagano ime in ikono", - "skillManagement.detail.builder.fromMarketplace": "Iz Tržnice", - "skillManagement.detail.builder.model": "GPT-4o", - "skillManagement.detail.builder.modelCredits.all": "Vsi krediti", - "skillManagement.detail.builder.modelCredits.configure": "Konfiguracija je obvezna", - "skillManagement.detail.builder.modelCredits.exhausted": "Kvote porabljene", - "skillManagement.detail.builder.modelProviderSettings": "Nastavitve ponudnika modelov", - "skillManagement.detail.builder.modelSearch": "Išči modele...", "skillManagement.detail.builder.modifyPlaceholder": "Prosite AI, da spremeni to veščino ...", "skillManagement.detail.builder.open": "Odprite Skill Builder", "skillManagement.detail.builder.placeholder": "Opišite scenarij ...", @@ -61,8 +47,6 @@ "skillManagement.detail.builder.thinkingUnavailable": "Model ni vrnil nobene vsebine razmišljanja.", "skillManagement.detail.builder.title": "Skill Builder", "skillManagement.detail.builder.tryExample": "Poskusite s primerom", - "skillManagement.detail.builder.voice": "Glasovni vnos", - "skillManagement.detail.builder.voiceUnavailable": "Glasovni vnos še ni na voljo.", "skillManagement.detail.cancelAddMetadata": "Prekliči dodajanje metapodatkov", "skillManagement.detail.closeFileTab": "Zapri {{name}}", "skillManagement.detail.closeVersions": "Zaprte različice", @@ -70,14 +54,10 @@ "skillManagement.detail.copyContentSuccess": "Vsebina kopirana v odložišče.", "skillManagement.detail.copyFile": "Kopiraj", "skillManagement.detail.copyFileSuccess": "Datoteka pripravljena za lepljenje.", - "skillManagement.detail.copyVersionId": "Kopiraj ID", - "skillManagement.detail.copyVersionIdSuccess": "ID kopiran v odložišče.", "skillManagement.detail.createFile": "Nova datoteka", - "skillManagement.detail.createFileDescription": "Vnesite pot datoteke glede na koren spretnosti.", "skillManagement.detail.createFileMenu": "Nova datoteka ...", "skillManagement.detail.createFileSuccess": "Datoteka ustvarjena.", "skillManagement.detail.createFolder": "Nova mapa", - "skillManagement.detail.createFolderDescription": "Vnesite pot do mape glede na koren spretnosti.", "skillManagement.detail.createFolderMenu": "Nova mapa ...", "skillManagement.detail.createFolderSuccess": "Mapa ustvarjena.", "skillManagement.detail.createdBy": "Ustvaril {{name}}", @@ -100,10 +80,8 @@ "skillManagement.detail.fileMeta": "{{type}} · {{size}} bajtov", "skillManagement.detail.fileMissing": "Datoteke ni mogoče najti.", "skillManagement.detail.fileOperationFailed": "Operacija datoteke ni uspela.", - "skillManagement.detail.files": "Datoteke", "skillManagement.detail.latest": "Najnovejši", "skillManagement.detail.loadFailed": "Spretnosti ni bilo mogoče naložiti.", - "skillManagement.detail.loadingTags": "Nalaganje oznak ...", "skillManagement.detail.markdownLiveMode": "Predogled v živo", "skillManagement.detail.markdownSourceMode": "Urejevalnik vira", "skillManagement.detail.metadataKey": "ime", @@ -113,10 +91,7 @@ "skillManagement.detail.nameThisVersion": "Poimenujte to različico", "skillManagement.detail.noFileSelected": "Izberite datoteko za predogled.", "skillManagement.detail.noFiles": "Ni datotek.", - "skillManagement.detail.noSearchResults": "Ni ujemajočih se datotek.", - "skillManagement.detail.noSelectableTags": "Ni razpoložljivih oznak.", "skillManagement.detail.noVersions": "Ni še objavljenih različic.", - "skillManagement.detail.pasteFile": "Prilepi", "skillManagement.detail.pasteFileSuccess": "Datoteka prilepljena.", "skillManagement.detail.previewUnsupported": "Ta datoteka ne podpira predogleda.", "skillManagement.detail.publish": "Objavi", @@ -128,9 +103,7 @@ "skillManagement.detail.publishUpdate": "Objavi posodobitev", "skillManagement.detail.published": "Objavljeno", "skillManagement.detail.publishedAt": "Objavljeno {{time}}", - "skillManagement.detail.publishedVersion": "Objavljeno #{{number}}", "skillManagement.detail.publishing": "Objavljanje...", - "skillManagement.detail.readonly": "Samo za branje", "skillManagement.detail.referenceFiles.confirm": "Vnesite Potrdi", "skillManagement.detail.referenceFiles.empty": "Ni datotek na voljo.", "skillManagement.detail.referenceFiles.livePlaceholder": "Napišite navodila za agenta. Vnesite / za referenčne datoteke.", @@ -138,14 +111,10 @@ "skillManagement.detail.referenceFiles.title": "Referenčne datoteke", "skillManagement.detail.referencedBy_one": "Uporablja ga {{count}} agent", "skillManagement.detail.referencedBy_other": "Uporablja ga {{count}} agentov", - "skillManagement.detail.removeMetadata": "Izbriši metapodatke {{name}}", "skillManagement.detail.removeTag": "Odstrani {{tag}}", "skillManagement.detail.removeTagSuccess": "Oznaka odstranjena.", - "skillManagement.detail.renameFile": "Preimenuj datoteko", - "skillManagement.detail.renameFileDescription": "Vnesite novo pot glede na koren spretnosti.", "skillManagement.detail.renameFileSuccess": "Datoteka preimenovana.", "skillManagement.detail.renameSkillSuccess": "Skill preimenovan.", - "skillManagement.detail.renameVersion": "Preimenuj", "skillManagement.detail.renameVersionFailed": "Preimenovanje različice ni uspelo.", "skillManagement.detail.renameVersionPrompt": "Ime različice", "skillManagement.detail.renameVersionSuccess": "Različica preimenovana.", @@ -155,17 +124,14 @@ "skillManagement.detail.restoreVersionConfirmTitle": "Obnoviti to različico?", "skillManagement.detail.restoreVersionFailed": "Različice ni bilo mogoče obnoviti.", "skillManagement.detail.restoreVersionSuccess": "Različica obnovljena v osnutek.", - "skillManagement.detail.save": "Shrani", "skillManagement.detail.saveConflictConfirmDescription": "Ta veščina je bila posodobljena drugje. Za nadaljevanje urejanja naložite najnovejši osnutek. Vaše trenutne spremembe bodo zavržene.", "skillManagement.detail.saveConflictConfirmTitle": "Naj naložim najnovejši osnutek?", "skillManagement.detail.saveConflictReload": "Naloži najnovejši osnutek", "skillManagement.detail.saveConflictStatus": "Zaznan spor. Za nadaljevanje urejanja naložite najnovejši osnutek.", "skillManagement.detail.saveFailed": "Datoteke ni bilo mogoče shraniti.", - "skillManagement.detail.saveSuccess": "Datoteka shranjena.", "skillManagement.detail.saved": "Shranjeno", "skillManagement.detail.savedAt": "Shranjeno {{time}}", "skillManagement.detail.saving": "Shranjevanje...", - "skillManagement.detail.searchFiles": "Išči datoteke", "skillManagement.detail.showMoreReferences": "Pokaži {{count}} več", "skillManagement.detail.skillDescriptionPlaceholder": "Opišite, kaj počne ta veščina in kdaj naj jo agenti uporabijo.", "skillManagement.detail.skillNamePlaceholder": "Uporabljajte male črke, številke in vezaje.", @@ -177,16 +143,12 @@ "skillManagement.detail.uploadConflict": "Datoteka s tem imenom že obstaja.", "skillManagement.detail.uploadFailureSummary": "{{failed}} od datotek {{total}} ni uspelo", "skillManagement.detail.uploadFailureTitle": "Nekateri prenosi niso uspeli", - "skillManagement.detail.uploadFile": "Naloži datoteko", - "skillManagement.detail.uploadFileFailed": "Nalaganje datoteke ni uspelo.", "skillManagement.detail.uploadFileSuccess": "Datoteka naložena.", "skillManagement.detail.uploadFilesButton": "Naložite datoteke {{count}}", "skillManagement.detail.uploadFilesFailedStatus_one": "Nalaganje datoteke {{count}} ni uspelo.", "skillManagement.detail.uploadFilesFailedStatus_other": "Nalaganje datotek {{count}} ni uspelo.", "skillManagement.detail.uploadFilesMenu": "Naloži datoteke ...", "skillManagement.detail.uploadFilesProgress": "Nalaganje {{completed}}/{{total}}", - "skillManagement.detail.uploadFilesResult": "{{uploaded}} naloženo · {{failed}} ni uspelo", - "skillManagement.detail.uploadFilesStatus": "Stanje nalaganja", "skillManagement.detail.uploadInvalidExtension": "Ta vrsta datoteke ni podprta.", "skillManagement.detail.uploadInvalidName": "Neveljavno ime.", "skillManagement.detail.uploadInvalidPath": "Neveljavna pot datoteke.", @@ -206,7 +168,6 @@ "skillManagement.detail.uploadWillReplace": "Zamenjal bo obstoječo datoteko", "skillManagement.detail.uploadWillUseName": "Naložen bo kot {{name}}", "skillManagement.detail.versionHistory": "Odpri zgodovino različic", - "skillManagement.detail.versionMeta": "{{hash}} · {{time}}", "skillManagement.detail.versionPublishNote": "Opombe ob izdaji", "skillManagement.detail.versionPublishNotePlaceholder": "Opišite to spremembo", "skillManagement.detail.versionPublishedMeta": "{{time}} · {{name}}", @@ -227,8 +188,6 @@ "skillManagement.errors.missingSkillMd": "Ta paket nima SKILL.md. Dodajte datoteko in jo znova uvozite.", "skillManagement.errors.nameConflict": "Spretnost z imenom \"{{name}}\" že obstaja. Preimenujte ga v SKILL.md in znova uvozite.", "skillManagement.errors.workspaceLimit": "Vaš delovni prostor je dosegel omejitev 500 veščin. Izbrišite veščino, da dodate novo.", - "skillManagement.exportDialog.description": "Izvožen bo trenutno shranjeni osnutek vaše veščine. Če imate v urejevalniku neshranjene spremembe, jih pred izvozom shranite.", - "skillManagement.exportDialog.title": "Izvozi trenutno shranjeni osnutek", "skillManagement.import": "Uvozi", "skillManagement.importFailed": "Spretnosti ni bilo mogoče uvoziti.", "skillManagement.importSuccess": "Skill uvoženo.", @@ -236,12 +195,10 @@ "skillManagement.loadingError": "Spretnosti ni bilo mogoče naložiti", "skillManagement.moreActions": "Več dejanj za {{name}}", "skillManagement.noDescription": "Ni še opisa", - "skillManagement.noTags": "Brez oznak", "skillManagement.publishedAt": "Objavljeno {{time}}", "skillManagement.referenceCount_one": "{{count}} agent", "skillManagement.referenceCount_other": "{{count}} agentov", "skillManagement.searchLabel": "Poišči veščine", "skillManagement.searchPlaceholder": "Iskanje po imenu ali opisu", - "skillManagement.tags": "Oznake", "skillManagement.title": "Veščine" } diff --git a/web/i18n/sl-SI/snippet.json b/web/i18n/locales/sl-SI/snippet.json similarity index 100% rename from web/i18n/sl-SI/snippet.json rename to web/i18n/locales/sl-SI/snippet.json diff --git a/web/i18n/sl-SI/time.json b/web/i18n/locales/sl-SI/time.json similarity index 100% rename from web/i18n/sl-SI/time.json rename to web/i18n/locales/sl-SI/time.json diff --git a/web/i18n/sl-SI/tools.json b/web/i18n/locales/sl-SI/tools.json similarity index 100% rename from web/i18n/sl-SI/tools.json rename to web/i18n/locales/sl-SI/tools.json diff --git a/web/i18n/sl-SI/workflow.json b/web/i18n/locales/sl-SI/workflow.json similarity index 99% rename from web/i18n/sl-SI/workflow.json rename to web/i18n/locales/sl-SI/workflow.json index f95dffdb5cf..4de12b91fb1 100644 --- a/web/i18n/sl-SI/workflow.json +++ b/web/i18n/locales/sl-SI/workflow.json @@ -460,8 +460,6 @@ "nodes.agent.pluginNotInstalledDesc": "Ta integracija je nameščen iz GitHuba. Prosimo, da greste v integracijae in ga ponovo namestite.", "nodes.agent.roster.change": "Spremeni", "nodes.agent.roster.editInConsole": "Uredi v Agents", - "nodes.agent.roster.editInConsoleDisabled": "Za urejanje agentov je potrebna vloga Urednik ali višja.", - "nodes.agent.roster.editInConsoleDisabledRbac": "Agente lahko urejajo samo člani z vlogo Urednik ali višjo oziroma z dovoljenjem Upravljanje Agents.", "nodes.agent.roster.inlineSetup.description": "Enkratna namestitev, ki obstaja le v tem vozlišču", "nodes.agent.roster.inlineSetup.name": "Vgrajena nastavitev", "nodes.agent.roster.inlineSetup.type": "Samo v tem vozlišču", diff --git a/web/i18n/th-TH/agent-v-2.json b/web/i18n/locales/th-TH/agent-v-2.json similarity index 99% rename from web/i18n/th-TH/agent-v-2.json rename to web/i18n/locales/th-TH/agent-v-2.json index a75e19f09b1..7199e24af07 100644 --- a/web/i18n/th-TH/agent-v-2.json +++ b/web/i18n/locales/th-TH/agent-v-2.json @@ -69,6 +69,7 @@ "agentDetail.configure.answer.activity.runningCommands": "กำลังเรียกใช้คำสั่ง", "agentDetail.configure.answer.duration.minute": "{{count}} นาที", "agentDetail.configure.answer.duration.second": "{{count}} วินาที", + "agentDetail.configure.answer.responseEnded": "การตอบกลับสิ้นสุดแล้ว", "agentDetail.configure.answer.thinking": "กำลังคิด", "agentDetail.configure.build.empty.communityEditionTip": "Community Edition แม้จะมีการจัดการเวอร์ชันสำหรับการกำหนดค่าที่แยกออกมา แต่ไม่รองรับการจัดการเวอร์ชันของระบบไฟล์เอง โปรดระมัดระวังการดำเนินการในโหมด Build เนื่องจากการเปลี่ยนแปลงระบบไฟล์เกิดขึ้นแบบเรียลไทม์และอาจไม่สามารถย้อนกลับได้อย่างเรียบร้อยเสมอไป Community Edition ไม่ใช่ตัวเลือกที่เหมาะสมที่สุดสำหรับการให้บริการผู้ชมภายนอกจำนวนมาก", "agentDetail.configure.build.empty.description": "อธิบายสิ่งที่คุณต้องการ แล้วแบบฟอร์มด้านซ้ายจะถูกกรอกไปพร้อมกับการสนทนา", @@ -269,7 +270,6 @@ "agentDetail.configure.workingDirectory.breadcrumbLabel": "เส้นทางระบบไฟล์", "agentDetail.configure.workingDirectory.description": "ไฟล์ที่ Agent อ่านและเขียนระหว่างทำงาน สิ่งที่สร้างขึ้น เช่น qna_report.pdf จะแสดงที่นี่", "agentDetail.configure.workingDirectory.fileSystem": "ระบบไฟล์", - "agentDetail.configure.workingDirectory.open": "เปิดไดเรกทอรีทำงาน", "agentDetail.configure.workingDirectory.persistentFiles": "ถาวร", "agentDetail.configure.workingDirectory.persistentFilesTooltip": "ไฟล์ที่เอเจนต์เก็บไว้และสามารถใช้ได้ทุกที่ที่เอเจนต์ทำงาน รวมถึงการสนทนาของผู้ใช้ปลายทางและเวิร์กโฟลว์ การเปลี่ยนแปลงใด ๆ ที่ทำกับไฟล์เหล่านี้ในแชตการสร้างนี้จะมีผลเมื่อคุณคลิก “นำไปใช้”", "agentDetail.configure.workingDirectory.temporaryFiles": "ชั่วคราว", @@ -348,7 +348,6 @@ "agentDetail.sections.logs": "บันทึก", "agentDetail.sections.monitoring": "การมอนิเตอร์", "agentDetail.title": "ตัวแทน", - "agentDetail.type": "ตัวแทน", "agentDetail.versionHistory.empty": "ยังไม่มีเวอร์ชัน", "agentDetail.versionHistory.exitVersions": "ออกจากเวอร์ชัน", "agentDetail.versionHistory.filter": "กรองเวอร์ชัน", @@ -408,8 +407,5 @@ "roster.sort.lastModified": "แก้ไขล่าสุด", "roster.sort.recentlyCreated": "สร้างล่าสุด", "roster.title": "ตัวแทน", - "roster.usageStatus.draft": "ฉบับร่าง", - "skillManagement.detail.uploadFilesFailedStatus_one": "อัปโหลดไฟล์ไม่สำเร็จ {{count}} ไฟล์", - "skillManagement.detail.uploadFilesFailedStatus_other": "อัปโหลดไฟล์ไม่สำเร็จ {{count}} ไฟล์", - "skillManagement.title": "Skills" + "roster.usageStatus.draft": "ฉบับร่าง" } diff --git a/web/i18n/th-TH/app-annotation.json b/web/i18n/locales/th-TH/app-annotation.json similarity index 100% rename from web/i18n/th-TH/app-annotation.json rename to web/i18n/locales/th-TH/app-annotation.json diff --git a/web/i18n/th-TH/app-api.json b/web/i18n/locales/th-TH/app-api.json similarity index 100% rename from web/i18n/th-TH/app-api.json rename to web/i18n/locales/th-TH/app-api.json diff --git a/web/i18n/th-TH/app-debug.json b/web/i18n/locales/th-TH/app-debug.json similarity index 100% rename from web/i18n/th-TH/app-debug.json rename to web/i18n/locales/th-TH/app-debug.json diff --git a/web/i18n/th-TH/app-log.json b/web/i18n/locales/th-TH/app-log.json similarity index 97% rename from web/i18n/th-TH/app-log.json rename to web/i18n/locales/th-TH/app-log.json index 738d915a0cc..2bdf54c5385 100644 --- a/web/i18n/th-TH/app-log.json +++ b/web/i18n/locales/th-TH/app-log.json @@ -4,11 +4,9 @@ "agentLogDetail.iteration": "เกิด ซ้ำ", "agentLogDetail.iterations": "เกิด ซ้ำ", "agentLogDetail.toolUsed": "เครื่องมือที่ใช้", - "archives.action.downloadMonth": "ดาวน์โหลดไฟล์เก็บถาวรของ {{month}}", "archives.action.downloadReady": "ไฟล์เก็บถาวรพร้อมดาวน์โหลดแล้ว", "archives.action.prepareDownload": "เตรียมดาวน์โหลด", "archives.action.prepareFailed": "เตรียมดาวน์โหลดไฟล์เก็บถาวรไม่สำเร็จ", - "archives.action.prepareMonth": "เตรียมดาวน์โหลดไฟล์เก็บถาวรของ {{month}}", "archives.action.prepareStarted": "กำลังเตรียมดาวน์โหลดไฟล์เก็บถาวร", "archives.action.preparing": "กำลังเตรียม", "archives.description": "ดาวน์โหลดไฟล์ส่งออก CSV รายเดือนของบันทึกการรัน workflow ที่เก็บถาวรในเวิร์กสเปซนี้", diff --git a/web/i18n/th-TH/app-overview.json b/web/i18n/locales/th-TH/app-overview.json similarity index 100% rename from web/i18n/th-TH/app-overview.json rename to web/i18n/locales/th-TH/app-overview.json diff --git a/web/i18n/th-TH/app.json b/web/i18n/locales/th-TH/app.json similarity index 100% rename from web/i18n/th-TH/app.json rename to web/i18n/locales/th-TH/app.json diff --git a/web/i18n/th-TH/billing.json b/web/i18n/locales/th-TH/billing.json similarity index 100% rename from web/i18n/th-TH/billing.json rename to web/i18n/locales/th-TH/billing.json diff --git a/web/i18n/th-TH/common.json b/web/i18n/locales/th-TH/common.json similarity index 97% rename from web/i18n/th-TH/common.json rename to web/i18n/locales/th-TH/common.json index f5818f03845..be75457107b 100644 --- a/web/i18n/th-TH/common.json +++ b/web/i18n/locales/th-TH/common.json @@ -209,9 +209,7 @@ "mainNav.marketplace": "Marketplace", "mainNav.skills": "Skills", "mainNav.webApps.noResults": "ไม่พบเว็บแอป", - "mainNav.webApps.openApp": "เปิดเว็บแอป {{name}}", "mainNav.webApps.searchPlaceholder": "ค้นหาเว็บแอป", - "mainNav.workspace.credits": "{{count}} เครดิต", "mainNav.workspace.creditsUnit": "เครดิต", "mainNav.workspace.inviteMembers": "Invite members", "mainNav.workspace.noResults": "ไม่พบพื้นที่ทำงาน", @@ -233,7 +231,6 @@ "members.datasetOperatorTip": "สามารถจัดการฐานความรู้ได้เท่านั้น", "members.editRole": "แก้ไขบทบาท", "members.editorTip": "สามารถสร้างและแก้ไขแอปได้", - "members.email": "อีเมล", "members.emailInvalid": "รูปแบบอีเมลไม่ถูกต้อง", "members.emailNotSetup": "เซิร์ฟเวอร์อีเมลไม่ได้ตั้งค่าไว้ จึงไม่สามารถส่งอีเมลเชิญได้ กรุณาแจ้งผู้ใช้เกี่ยวกับลิงก์เชิญที่จะออกหลังจากการเชิญแทน", "members.emailPlaceholder": "พิมพ์หรือวางที่อยู่อีเมล…", @@ -250,7 +247,6 @@ "members.inviteLimitExceeded": "มีที่นั่งไม่เพียงพอสำหรับผู้รับทั้งหมด", "members.inviteTeamMember": "เพิ่มสมาชิกในทีม", "members.inviteTeamMemberTip": "พวกเขาสามารถเข้าถึงข้อมูลทีมของคุณได้โดยตรงหลังจากลงชื่อเข้าใช้", - "members.invitedAsRole": "ได้รับเชิญให้เป็น {{role}}", "members.lastActive": "ใช้งานล่าสุด", "members.memberActions": "การดำเนินการกับสมาชิก", "members.memberDetails.assign": "กำหนด", @@ -258,8 +254,6 @@ "members.memberDetails.assignedRoles": "บทบาทที่กำหนด", "members.memberDetails.customGroup": "กำหนดเอง", "members.memberDetails.generalGroup": "ทั่วไป", - "members.memberDetails.openAria": "เปิดรายละเอียดสมาชิกสำหรับ {{name}}", - "members.memberDetails.roleActionsAria": "เปิดการดำเนินการสำหรับบทบาท {{role}}", "members.memberDetails.roleNoPermissionSummary": "บทบาทปัจจุบันไม่มีสิทธิ์ใด ๆ", "members.memberDetails.rolePermissionSummary": "{{role}} สามารถ {{permissions}}", "members.memberDetails.title": "รายละเอียดสมาชิก", @@ -370,7 +364,6 @@ "modelProvider.card.usagePriorityTip": "ตั้งค่าทรัพยากรที่จะใช้ก่อนเมื่อเรียกใช้โมเดล", "modelProvider.configureTip": "ตั้งค่า api-key หรือเพิ่มโมเดลเพื่อใช้", "modelProvider.confirmDelete": "ยืนยันการลบ?", - "modelProvider.credits": "เครดิตข้อความ", "modelProvider.defaultConfig": "การกําหนดค่าเริ่มต้น", "modelProvider.discoverMore": "ดูเพิ่มเติมใน", "modelProvider.embeddingModel.key": "โมเดลการฝัง", @@ -427,7 +420,6 @@ "modelProvider.selector.noProviderConfiguredDesc": "เรียกดู Marketplace เพื่อติดตั้ง หรือกำหนดค่าผู้ให้บริการในการตั้งค่า", "modelProvider.selector.onlyCompatibleModelsShown": "แสดงเฉพาะโมเดลที่รองรับเท่านั้น", "modelProvider.selector.showIncompatibleModels": "แสดงโมเดลที่ไม่รองรับ", - "modelProvider.selector.suggestion": "คำแนะนำ", "modelProvider.selector.suggestionTip": "แนะนำสำหรับงานของเอเจนต์", "modelProvider.showModels": "แสดงโมเดล", "modelProvider.speechToTextModel.key": "โมเดลคําพูดเป็นข้อความ", @@ -458,6 +450,8 @@ "operation.confirmAction": "กรุณายืนยันการกระทำของคุณ", "operation.copied": "คัด ลอก", "operation.copy": "ลอก", + "operation.copyErrorDetails": "คัดลอกรายละเอียดข้อผิดพลาด", + "operation.copyErrorFailed": "คัดลอกไม่สำเร็จ ลองอีกครั้ง", "operation.copyImage": "คัดลอกรูปภาพ", "operation.create": "สร้าง", "operation.deSelectAll": "ยกเลิกการเลือกทั้งหมด", @@ -475,7 +469,6 @@ "operation.format": "รูปแบบ", "operation.imageCopied": "ภาพที่คัดลอก", "operation.imageCopyFailed": "ไม่สามารถคัดลอกรูปภาพได้ โปรดดาวน์โหลดรูปภาพแทน", - "operation.imageDownloaded": "ดาวน์โหลดรูปภาพแล้ว", "operation.in": "ใน", "operation.learnMore": "ศึกษาเพิ่มเติม", "operation.log": "ซุง", @@ -512,7 +505,6 @@ "operation.sure": "ฉันแน่ใจ", "operation.toggleFullscreen": "สลับเต็มหน้าจอ", "operation.toggleMute": "สลับปิดเสียง", - "operation.upload": "Upload", "operation.view": "ทิวทัศน์", "operation.viewDetails": "ดูรายละเอียด", "operation.viewMore": "ดูเพิ่มเติม", @@ -542,7 +534,6 @@ "promptEditor.requestURL.item.title": "URL คำขอ", "promptEditor.variable.modal.add": "ตัวแปรใหม่", "promptEditor.variable.modal.addTool": "เครื่องมือใหม่", - "provider.anthropicHosted.trialQuotaTip": "โควต้าการทดลองใช้ Anthropic ของคุณจะหมดอายุในวันที่ 2025/03/11 และจะไม่สามารถใช้งานได้อีกต่อไปหลังจากนั้นโปรดใช้มันให้ทันเวลา", "provider.encrypted.back": "เทคโนโลยี ", "provider.encrypted.front": "คีย์ API ของคุณจะถูกเข้ารหัสและจัดเก็บโดยใช้", "provider.validating": "กําลังตรวจสอบความถูกต้องของคีย์...", @@ -565,7 +556,6 @@ "settings.preferences": "Preferences", "settings.provider": "ผู้ให้บริการโมเดล", "settings.resourceAccess": "การเข้าถึงทรัพยากร", - "settings.resourceAccessDescription": "กำหนดค่ากฎสิทธิ์ที่แอปและฐานความรู้สามารถใช้ได้ กฎสิทธิ์สามารถกำหนดให้กับสมาชิกในการกำหนดค่าการเข้าถึงทรัพยากรเฉพาะได้", "settings.rolesAndPermissions": "บทบาทและสิทธิ์", "settings.settings": "Settings", "settings.swaggerAPIAsTool": "Swagger API as Tool", @@ -695,7 +685,6 @@ "userProfile.helpCenter": "วิธีใช้", "userProfile.logout": "ออกจากระบบ", "userProfile.roadmap": "แผนงาน", - "userProfile.settings": "การตั้งค่า", "userProfile.workspace": "พื้นที่", "voice.language.arTN": "ภาษาอาหรับตูนิเซีย", "voice.language.deDE": "เยอรมัน", diff --git a/web/i18n/th-TH/custom.json b/web/i18n/locales/th-TH/custom.json similarity index 100% rename from web/i18n/th-TH/custom.json rename to web/i18n/locales/th-TH/custom.json diff --git a/web/i18n/th-TH/dataset-creation.json b/web/i18n/locales/th-TH/dataset-creation.json similarity index 100% rename from web/i18n/th-TH/dataset-creation.json rename to web/i18n/locales/th-TH/dataset-creation.json diff --git a/web/i18n/th-TH/dataset-documents.json b/web/i18n/locales/th-TH/dataset-documents.json similarity index 100% rename from web/i18n/th-TH/dataset-documents.json rename to web/i18n/locales/th-TH/dataset-documents.json diff --git a/web/i18n/th-TH/dataset-hit-testing.json b/web/i18n/locales/th-TH/dataset-hit-testing.json similarity index 100% rename from web/i18n/th-TH/dataset-hit-testing.json rename to web/i18n/locales/th-TH/dataset-hit-testing.json diff --git a/web/i18n/th-TH/dataset-pipeline.json b/web/i18n/locales/th-TH/dataset-pipeline.json similarity index 100% rename from web/i18n/th-TH/dataset-pipeline.json rename to web/i18n/locales/th-TH/dataset-pipeline.json diff --git a/web/i18n/th-TH/dataset-settings.json b/web/i18n/locales/th-TH/dataset-settings.json similarity index 100% rename from web/i18n/th-TH/dataset-settings.json rename to web/i18n/locales/th-TH/dataset-settings.json diff --git a/web/i18n/th-TH/dataset.json b/web/i18n/locales/th-TH/dataset.json similarity index 97% rename from web/i18n/th-TH/dataset.json rename to web/i18n/locales/th-TH/dataset.json index 0fe5ffc7bca..4e4dc564f90 100644 --- a/web/i18n/th-TH/dataset.json +++ b/web/i18n/locales/th-TH/dataset.json @@ -163,7 +163,6 @@ "newKnowledge.configureProvider": "กำหนดค่า {{provider}}", "newKnowledge.connectNotion": "เชื่อมต่อ Notion", "newKnowledge.connectProvider": "เชื่อมต่อ {{provider}}", - "newKnowledge.connectProviderGeneric": "เชื่อมต่อผู้ให้บริการ", "newKnowledge.connectSource": "เชื่อมต่อแหล่งข้อมูล", "newKnowledge.connectSourceDescription": "เนื้อหาจะซิงค์กับ Notion เว็บไซต์ และบริการอื่น ๆ อยู่เสมอ", "newKnowledge.connectingProvider": "กำลังเชื่อมต่อ...", @@ -179,18 +178,14 @@ "newKnowledge.crawlFailedDescription": "โปรแกรมรวบรวมข้อมูลไม่สามารถทำตัวอย่างนี้ให้เสร็จได้ ตรวจสอบ URL ปรับตัวเลือกการรวบรวมข้อมูล หรือลองอีกครั้ง", "newKnowledge.crawlFailedProvider": "{{provider}} รายงานข้อผิดพลาด ตรวจสอบการเชื่อมต่อผู้ให้บริการแล้วลองอีกครั้ง", "newKnowledge.crawlFailedTimeout": "หมดเวลาการรวบรวมข้อมูล ลองใช้ URL ที่เจาะจงขึ้นหรือลดจำนวนหน้าสูงสุด", - "newKnowledge.crawlNotStarted": "ยังไม่เริ่ม", "newKnowledge.crawlOptions": "ตัวเลือกการรวบรวมข้อมูล", "newKnowledge.crawlPreview": "ตัวอย่างการเก็บข้อมูล", "newKnowledge.crawlProgress": "ความคืบหน้าการรวบรวมข้อมูลของ {{host}}", - "newKnowledge.crawlSetupUnavailableDescription": "การกำหนดค่าการรวบรวมข้อมูลเว็บไซต์ถูกปิดใช้งานจนกว่าเวิร์กโฟลว์การรวบรวมข้อมูลจะพร้อมใช้งาน", - "newKnowledge.crawlSetupUnavailableTitle": "Firecrawl พร้อมแล้ว", "newKnowledge.crawlStartFailed": "ไม่สามารถเริ่มตัวอย่างการรวบรวมข้อมูลได้ ลองอีกครั้ง", "newKnowledge.crawlStopped": "หยุดการรวบรวมข้อมูลแล้ว", "newKnowledge.crawling": "กำลังรวบรวมข้อมูล…", "newKnowledge.crawlingPages": "กำลังรวบรวมข้อมูล {{host}}… พบ {{count}} หน้า", "newKnowledge.createFailed": "เราไม่สามารถสร้างฐานความรู้นี้ได้ ตรวจสอบการเข้าถึงของคุณแล้วลองอีกครั้ง", - "newKnowledge.createIllustrationTitle": "เชื่อมต่อเนื้อหาเพียงครั้งเดียว — ทุกแอปจะตอบด้วยความรู้ล่าสุด", "newKnowledge.createTitle": "สร้างองค์ความรู้", "newKnowledge.createdAt": "สร้างเมื่อ", "newKnowledge.creators": "ผู้สร้าง", @@ -220,7 +215,6 @@ "newKnowledge.documentColumn": "เอกสาร", "newKnowledge.documentContentIncomplete": "กำลังโหลดเนื้อหาเอกสารที่เหลือ…", "newKnowledge.documentContents": "สารบัญ", - "newKnowledge.documentFacts": "รายละเอียดเอกสาร", "newKnowledge.documentFilterLabel": "กรองเอกสารตามสถานะ", "newKnowledge.documentLoadErrorDescription": "ไม่สามารถโหลดเอกสารนี้ได้ โปรดลองอีกครั้ง", "newKnowledge.documentLoadErrorTitle": "โหลดเอกสารไม่ได้", @@ -268,7 +262,6 @@ "newKnowledge.documentsSelected": "เลือกเอกสาร {{count}} แล้ว", "newKnowledge.documentsUnavailable": "— เอกสาร", "newKnowledge.downloadDocuments": "ดาวน์โหลด", - "newKnowledge.editSource": "แก้ไข", "newKnowledge.emptyChunk": "ส่วนข้อมูลว่าง", "newKnowledge.emptyDescription": "เพิ่มเอกสารของคุณ แล้วเอเจนต์จะอ่านได้โดยตรง โดยไม่ต้องตั้งค่าการทำดัชนี", "newKnowledge.emptyTitle": "วิธีที่ชาญฉลาดกว่าในการสร้างฐานความรู้", @@ -344,7 +337,6 @@ "newKnowledge.processingTaskState.succeeded": "เสร็จสิ้น", "newKnowledge.processingTaskState.superseded": "เข้ามาแทนที่", "newKnowledge.providerConnected": "เชื่อมต่อ {{provider}} แล้ว", - "newKnowledge.providerLabel": "เลือกผู้ให้บริการ", "newKnowledge.providerLoadFailed": "ผู้ให้บริการไม่สามารถโหลดได้", "newKnowledge.providerNotConfigured": "{{provider}} ไม่ได้กำหนดค่า", "newKnowledge.providerNotConfiguredDescription": "เพิ่มคีย์ API หรือ URL ปลายทางเพื่อรวบรวมข้อมูลเว็บไซต์ด้วย {{provider}}", @@ -369,12 +361,10 @@ "newKnowledge.revisionState.superseded": "ก่อนหน้า", "newKnowledge.rootUrl": "URL หลัก", "newKnowledge.rootUrlPlaceholder": "https://docs.example.com", - "newKnowledge.routeUnavailable": "ส่วนนี้จะพร้อมใช้งานในขั้นตอนการตั้งค่าถัดไป", "newKnowledge.searchDocuments": "ค้นหาเอกสาร", "newKnowledge.searchSources": "แหล่งค้นหา", "newKnowledge.selectAll": "เลือกทั้งหมด", "newKnowledge.selectAllDocuments": "เลือกเอกสารทั้งหมด", - "newKnowledge.selectAllSources": "เลือกแหล่งที่มาทั้งหมด", "newKnowledge.selectChunk": "เลือกส่วนข้อมูลเพื่อตรวจสอบเนื้อหา", "newKnowledge.selectFilesAndFolders": "เลือกไฟล์และโฟลเดอร์ที่จะซิงค์", "newKnowledge.skippedFailed": "ข้าม (ล้มเหลว)", @@ -384,7 +374,6 @@ "newKnowledge.sourceFilterLabel": "กรองแหล่งที่มาตามสถานะ", "newKnowledge.sourceName": "ชื่อแหล่งข้อมูล", "newKnowledge.sourceNamePlaceholder": "เช่น การรวบรวมข้อมูลเอกสารผลิตภัณฑ์", - "newKnowledge.sourceNameRequired": "ป้อนชื่อแหล่งข้อมูล", "newKnowledge.sourceSetupBackendDependency": "การตั้งค่าแหล่งที่มาแสดงอย่างครบถ้วน การแก้ไขและดูตัวอย่างต้องใช้เวิร์กโฟลว์แบ็กเอนด์ของแหล่งที่มาฉบับร่าง", "newKnowledge.sourceStatus.active": "แอคทีฟ", "newKnowledge.sourceStatus.disabled": "ปิดใช้งาน", @@ -431,7 +420,6 @@ "newKnowledge.unavailableDescription": "ขณะนี้ KnowledgeFS ไม่พร้อมใช้งานสำหรับพื้นที่ทำงานนี้", "newKnowledge.unavailableTitle": "ความรู้แบบใหม่ไม่พร้อมใช้งาน", "newKnowledge.updated": "อัปเดตเมื่อ {{date}}", - "newKnowledge.updatedAt": "อัปเดตล่าสุด", "newKnowledge.updatedColumn": "อัปเดตแล้ว", "newKnowledge.uploadCharactersUnavailable": "อักขระ: —", "newKnowledge.uploadDocuments": "อัพโหลดเอกสาร", @@ -460,11 +448,9 @@ "retrievalSettings": "การตั้งค่าการดึงข้อมูล", "retry": "ลอง", "selectExternalKnowledgeAPI.placeholder": "เลือก API ความรู้ภายนอก", - "serviceApi.card.addApiKey": "เพิ่มกุญแจ API", "serviceApi.card.apiKey": "กุญแจ API", "serviceApi.card.apiReference": "เอกสารอ้างอิง API", "serviceApi.card.endpoint": "จุดเชื่อมต่อ API บริการ", - "serviceApi.card.manageApiKey": "จัดการกุญแจ API", "serviceApi.card.title": "บริการแบ็กเอนด์ API", "serviceApi.disabled": "ถูกปิดใช้งาน", "serviceApi.enabled": "ให้บริการ", diff --git a/web/i18n/th-TH/deployments.json b/web/i18n/locales/th-TH/deployments.json similarity index 97% rename from web/i18n/th-TH/deployments.json rename to web/i18n/locales/th-TH/deployments.json index d323d910bae..6a53f6d56dc 100644 --- a/web/i18n/th-TH/deployments.json +++ b/web/i18n/locales/th-TH/deployments.json @@ -27,7 +27,6 @@ "list.emptyTitle": "ยังไม่มีการปรับใช้", "overview.chip.latest": "ล่าสุด", "status.RUNTIME_INSTANCE_STATUS_DEPLOYING": "กำลังปรับใช้", - "status.RUNTIME_INSTANCE_STATUS_FAILED": "การปรับใช้ล้มเหลว", "status.RUNTIME_INSTANCE_STATUS_INVALID": "ไม่ถูกต้อง", "status.RUNTIME_INSTANCE_STATUS_READY": "กำลังทำงาน", "status.RUNTIME_INSTANCE_STATUS_UNDEPLOYED": "ยังไม่ได้ปรับใช้", @@ -87,6 +86,5 @@ "studio.versionValue": "ค่าของเวอร์ชัน", "studio.versionsBehind_one": "ล้าหลัง {{count}} เวอร์ชัน", "studio.versionsBehind_other": "ล้าหลัง {{count}} เวอร์ชัน", - "versions.checkingReleaseContent": "กำลังตรวจสอบเนื้อหารีลีส...", "versions.deployTo": "ปรับใช้กับ {{name}}" } diff --git a/web/i18n/th-TH/device-flow.json b/web/i18n/locales/th-TH/device-flow.json similarity index 100% rename from web/i18n/th-TH/device-flow.json rename to web/i18n/locales/th-TH/device-flow.json diff --git a/web/i18n/th-TH/education.json b/web/i18n/locales/th-TH/education.json similarity index 100% rename from web/i18n/th-TH/education.json rename to web/i18n/locales/th-TH/education.json diff --git a/web/i18n/th-TH/explore.json b/web/i18n/locales/th-TH/explore.json similarity index 100% rename from web/i18n/th-TH/explore.json rename to web/i18n/locales/th-TH/explore.json diff --git a/web/i18n/th-TH/layout.json b/web/i18n/locales/th-TH/layout.json similarity index 100% rename from web/i18n/th-TH/layout.json rename to web/i18n/locales/th-TH/layout.json diff --git a/web/i18n/th-TH/login.json b/web/i18n/locales/th-TH/login.json similarity index 100% rename from web/i18n/th-TH/login.json rename to web/i18n/locales/th-TH/login.json diff --git a/web/i18n/th-TH/oauth.json b/web/i18n/locales/th-TH/oauth.json similarity index 100% rename from web/i18n/th-TH/oauth.json rename to web/i18n/locales/th-TH/oauth.json diff --git a/web/i18n/th-TH/permission-keys.json b/web/i18n/locales/th-TH/permission-keys.json similarity index 100% rename from web/i18n/th-TH/permission-keys.json rename to web/i18n/locales/th-TH/permission-keys.json diff --git a/web/i18n/th-TH/permission.json b/web/i18n/locales/th-TH/permission.json similarity index 100% rename from web/i18n/th-TH/permission.json rename to web/i18n/locales/th-TH/permission.json diff --git a/web/i18n/th-TH/pipeline.json b/web/i18n/locales/th-TH/pipeline.json similarity index 100% rename from web/i18n/th-TH/pipeline.json rename to web/i18n/locales/th-TH/pipeline.json diff --git a/web/i18n/th-TH/plugin-tags.json b/web/i18n/locales/th-TH/plugin-tags.json similarity index 100% rename from web/i18n/th-TH/plugin-tags.json rename to web/i18n/locales/th-TH/plugin-tags.json diff --git a/web/i18n/th-TH/plugin-trigger.json b/web/i18n/locales/th-TH/plugin-trigger.json similarity index 100% rename from web/i18n/th-TH/plugin-trigger.json rename to web/i18n/locales/th-TH/plugin-trigger.json diff --git a/web/i18n/th-TH/plugin.json b/web/i18n/locales/th-TH/plugin.json similarity index 99% rename from web/i18n/th-TH/plugin.json rename to web/i18n/locales/th-TH/plugin.json index 67d2af4ec5a..f4817896c66 100644 --- a/web/i18n/th-TH/plugin.json +++ b/web/i18n/locales/th-TH/plugin.json @@ -277,7 +277,6 @@ "readmeInfo.noReadmeAvailable": "ไม่มีไฟล์ README", "readmeInfo.title": "อ่านฉัน", "requestAPlugin": "ขอปลั๊กอิน", - "requestSubmit": "ขอหรือเผยแพร่", "search": "ค้น", "searchCategories": "หมวดหมู่การค้นหา", "searchInMarketplace": "ค้นหาใน Marketplace", diff --git a/web/i18n/th-TH/register.json b/web/i18n/locales/th-TH/register.json similarity index 100% rename from web/i18n/th-TH/register.json rename to web/i18n/locales/th-TH/register.json diff --git a/web/i18n/th-TH/run-log.json b/web/i18n/locales/th-TH/run-log.json similarity index 100% rename from web/i18n/th-TH/run-log.json rename to web/i18n/locales/th-TH/run-log.json diff --git a/web/i18n/th-TH/share.json b/web/i18n/locales/th-TH/share.json similarity index 100% rename from web/i18n/th-TH/share.json rename to web/i18n/locales/th-TH/share.json diff --git a/web/i18n/th-TH/skill.json b/web/i18n/locales/th-TH/skill.json similarity index 82% rename from web/i18n/th-TH/skill.json rename to web/i18n/locales/th-TH/skill.json index bd38d237229..bf1c6c6d2c4 100644 --- a/web/i18n/th-TH/skill.json +++ b/web/i18n/locales/th-TH/skill.json @@ -1,5 +1,4 @@ { - "skillManagement.clearTags": "ล้างแท็ก", "skillManagement.create": "สร้าง", "skillManagement.createFailed": "ไม่สามารถสร้างทักษะได้", "skillManagement.createSuccess": "สร้างฉบับร่างแล้ว", @@ -13,12 +12,9 @@ "skillManagement.deleteFailed": "ไม่สามารถลบทักษะได้", "skillManagement.deleteSuccess": "Skill ลบแล้ว", "skillManagement.detail.addMetadata": "เพิ่มข้อมูลเมตา", - "skillManagement.detail.addMetadataDescription": "เพิ่มช่อง frontmatter ให้กับไฟล์ Markdown นี้", "skillManagement.detail.addTag": "เพิ่มแท็ก", - "skillManagement.detail.addTagDescription": "สร้างหรือผูกแท็กกับ Skill นี้ แท็กใหม่จะถูกบันทึกพร้อมกับข้อมูลเมตา Skill", "skillManagement.detail.addTagSuccess": "เพิ่มแท็กแล้ว", "skillManagement.detail.back": "กลับไปที่ Skills", - "skillManagement.detail.builder.applyFailed": "ไม่สามารถใช้การอัปเดตฉบับร่างได้", "skillManagement.detail.builder.attach": "แนบไฟล์", "skillManagement.detail.builder.attachFailed": "ไม่สามารถแนบไฟล์ได้", "skillManagement.detail.builder.attachLimit": "คุณสามารถแนบไฟล์ได้สูงสุด 10 ไฟล์", @@ -26,22 +22,12 @@ "skillManagement.detail.builder.attachUnsupported": "สามารถแนบได้เฉพาะไฟล์รูปภาพ ข้อความ และเอกสารเท่านั้น", "skillManagement.detail.builder.attachmentOnlyMessage": "ใช้ไฟล์แนบเพื่อช่วยพัฒนาทักษะนี้", "skillManagement.detail.builder.close": "ปิด Skill Builder", - "skillManagement.detail.builder.compatibleModelsOnly": "แสดงเฉพาะโมเดลที่รองรับเท่านั้น", "skillManagement.detail.builder.copyResponse": "คัดลอกคำตอบ", "skillManagement.detail.builder.copySuccess": "คัดลอกคำตอบไปยังคลิปบอร์ดแล้ว", "skillManagement.detail.builder.editIntro": "ฉันได้ทบทวนทักษะนี้แล้ว คุณต้องการปรับอะไร?", "skillManagement.detail.builder.exampleIssueTriage": "การตรวจสอบปัญหาของลูกค้า", "skillManagement.detail.builder.exampleOnboarding": "คู่มือการเริ่มต้นใช้งานการจ้างงานใหม่", "skillManagement.detail.builder.exampleSalesFollowUp": "กลยุทธ์การติดตามผลการขาย", - "skillManagement.detail.builder.followUpDisplayName": "ใช้การอนุมัติการคืนเงินเป็นชื่อที่แสดง", - "skillManagement.detail.builder.followUpNameIcon": "ใช้ชื่อและไอคอนที่แนะนำ", - "skillManagement.detail.builder.fromMarketplace": "จาก Marketplace", - "skillManagement.detail.builder.model": "GPT-4o", - "skillManagement.detail.builder.modelCredits.all": "เครดิตทั้งหมด", - "skillManagement.detail.builder.modelCredits.configure": "ต้องกำหนดค่า", - "skillManagement.detail.builder.modelCredits.exhausted": "โควต้าหมด", - "skillManagement.detail.builder.modelProviderSettings": "การตั้งค่าผู้ให้บริการโมเดล", - "skillManagement.detail.builder.modelSearch": "ค้นหาโมเดล...", "skillManagement.detail.builder.modifyPlaceholder": "ขอให้ AI ปรับเปลี่ยนทักษะนี้...", "skillManagement.detail.builder.open": "เปิด Skill Builder", "skillManagement.detail.builder.placeholder": "อธิบายเหตุการณ์...", @@ -61,8 +47,6 @@ "skillManagement.detail.builder.thinkingUnavailable": "ไม่มีการส่งคืนเนื้อหาการคิดโดยโมเดล", "skillManagement.detail.builder.title": "Skill Builder", "skillManagement.detail.builder.tryExample": "ลองตัวอย่าง", - "skillManagement.detail.builder.voice": "ป้อนข้อมูลด้วยเสียง", - "skillManagement.detail.builder.voiceUnavailable": "การป้อนข้อมูลด้วยเสียงยังไม่พร้อมใช้งาน", "skillManagement.detail.cancelAddMetadata": "ยกเลิกการเพิ่มข้อมูลเมตา", "skillManagement.detail.closeFileTab": "ปิด {{name}}", "skillManagement.detail.closeVersions": "ปิดเวอร์ชัน", @@ -70,14 +54,10 @@ "skillManagement.detail.copyContentSuccess": "คัดลอกเนื้อหาไปยังคลิปบอร์ดแล้ว", "skillManagement.detail.copyFile": "ลอก", "skillManagement.detail.copyFileSuccess": "ไฟล์พร้อมที่จะวาง", - "skillManagement.detail.copyVersionId": "คัดลอก ID", - "skillManagement.detail.copyVersionIdSuccess": "คัดลอก ID ไปยังคลิปบอร์ดแล้ว", "skillManagement.detail.createFile": "ไฟล์ใหม่", - "skillManagement.detail.createFileDescription": "ป้อนเส้นทางของไฟล์ที่เกี่ยวข้องกับรูททักษะ", "skillManagement.detail.createFileMenu": "ไฟล์ใหม่...", "skillManagement.detail.createFileSuccess": "สร้างไฟล์แล้ว", "skillManagement.detail.createFolder": "โฟลเดอร์ใหม่", - "skillManagement.detail.createFolderDescription": "ป้อนเส้นทางโฟลเดอร์ที่เกี่ยวข้องกับรูททักษะ", "skillManagement.detail.createFolderMenu": "โฟลเดอร์ใหม่...", "skillManagement.detail.createFolderSuccess": "สร้างโฟลเดอร์แล้ว", "skillManagement.detail.createdBy": "สร้างโดย {{name}}", @@ -100,10 +80,8 @@ "skillManagement.detail.fileMeta": "{{type}} · {{size}} ไบต์", "skillManagement.detail.fileMissing": "ไม่พบไฟล์.", "skillManagement.detail.fileOperationFailed": "การดำเนินการไฟล์ล้มเหลว", - "skillManagement.detail.files": "ไฟล์", "skillManagement.detail.latest": "ล่าสุด", "skillManagement.detail.loadFailed": "โหลดทักษะไม่สำเร็จ", - "skillManagement.detail.loadingTags": "กำลังโหลดแท็ก...", "skillManagement.detail.markdownLiveMode": "ดูตัวอย่างสด", "skillManagement.detail.markdownSourceMode": "ตัวแก้ไขแหล่งที่มา", "skillManagement.detail.metadataKey": "ชื่อ", @@ -113,10 +91,7 @@ "skillManagement.detail.nameThisVersion": "ชื่อเวอร์ชันนี้", "skillManagement.detail.noFileSelected": "เลือกไฟล์เพื่อดูตัวอย่าง", "skillManagement.detail.noFiles": "ไม่มีไฟล์.", - "skillManagement.detail.noSearchResults": "ไม่พบไฟล์ที่ตรงกัน", - "skillManagement.detail.noSelectableTags": "ไม่มีแท็กที่ใช้ได้", "skillManagement.detail.noVersions": "ยังไม่มีเวอร์ชันเผยแพร่", - "skillManagement.detail.pasteFile": "แปะ", "skillManagement.detail.pasteFileSuccess": "วางไฟล์แล้ว", "skillManagement.detail.previewUnsupported": "ไฟล์นี้ไม่รองรับการแสดงตัวอย่าง", "skillManagement.detail.publish": "เผยแพร่", @@ -128,9 +103,7 @@ "skillManagement.detail.publishUpdate": "เผยแพร่อัปเดต", "skillManagement.detail.published": "เผยแพร่แล้ว", "skillManagement.detail.publishedAt": "เผยแพร่เมื่อ {{time}}", - "skillManagement.detail.publishedVersion": "เผยแพร่แล้ว #{{number}}", "skillManagement.detail.publishing": "กำลังเผยแพร่...", - "skillManagement.detail.readonly": "อ่านอย่างเดียว", "skillManagement.detail.referenceFiles.confirm": "เข้าสู่ ยืนยัน", "skillManagement.detail.referenceFiles.empty": "ไม่มีไฟล์ที่ใช้ได้", "skillManagement.detail.referenceFiles.livePlaceholder": "เขียนคำแนะนำสำหรับตัวแทน พิมพ์ / เพื่ออ้างอิงไฟล์", @@ -138,14 +111,10 @@ "skillManagement.detail.referenceFiles.title": "ไฟล์อ้างอิง", "skillManagement.detail.referencedBy_one": "ใช้โดย {{count}} เอเจนต์", "skillManagement.detail.referencedBy_other": "ใช้โดย {{count}} เอเจนต์", - "skillManagement.detail.removeMetadata": "ลบข้อมูลเมตา {{name}}", "skillManagement.detail.removeTag": "ลบ {{tag}}", "skillManagement.detail.removeTagSuccess": "ลบแท็กแล้ว", - "skillManagement.detail.renameFile": "เปลี่ยนชื่อไฟล์", - "skillManagement.detail.renameFileDescription": "เข้าสู่เส้นทางใหม่ที่เกี่ยวข้องกับรูททักษะ", "skillManagement.detail.renameFileSuccess": "เปลี่ยนชื่อไฟล์แล้ว", "skillManagement.detail.renameSkillSuccess": "Skill เปลี่ยนชื่อแล้ว", - "skillManagement.detail.renameVersion": "ตั้งชื่อใหม่", "skillManagement.detail.renameVersionFailed": "ไม่สามารถเปลี่ยนชื่อเวอร์ชันได้", "skillManagement.detail.renameVersionPrompt": "ชื่อเวอร์ชัน", "skillManagement.detail.renameVersionSuccess": "เปลี่ยนชื่อเวอร์ชันแล้ว", @@ -155,17 +124,14 @@ "skillManagement.detail.restoreVersionConfirmTitle": "กู้คืนเวอร์ชันนี้ไหม", "skillManagement.detail.restoreVersionFailed": "ไม่สามารถกู้คืนเวอร์ชันได้", "skillManagement.detail.restoreVersionSuccess": "กู้คืนเวอร์ชันเป็นฉบับร่างแล้ว", - "skillManagement.detail.save": "ประหยัด", "skillManagement.detail.saveConflictConfirmDescription": "ทักษะนี้ได้รับการอัปเดตที่อื่น โหลดฉบับร่างล่าสุดเพื่อแก้ไขต่อ การเปลี่ยนแปลงในปัจจุบันของคุณจะถูกยกเลิก", "skillManagement.detail.saveConflictConfirmTitle": "โหลดฉบับร่างล่าสุดใช่ไหม", "skillManagement.detail.saveConflictReload": "โหลดฉบับร่างล่าสุด", "skillManagement.detail.saveConflictStatus": "ตรวจพบข้อขัดแย้ง โหลดฉบับร่างล่าสุดเพื่อแก้ไขต่อ", "skillManagement.detail.saveFailed": "ไม่สามารถบันทึกไฟล์ได้", - "skillManagement.detail.saveSuccess": "บันทึกไฟล์แล้ว", "skillManagement.detail.saved": "บันทึก", "skillManagement.detail.savedAt": "บันทึกเมื่อ {{time}}", "skillManagement.detail.saving": "กำลังบันทึก...", - "skillManagement.detail.searchFiles": "ค้นหาไฟล์", "skillManagement.detail.showMoreReferences": "แสดง {{count}} เพิ่มเติม", "skillManagement.detail.skillDescriptionPlaceholder": "อธิบายว่าทักษะนี้ทำอะไรและเมื่อใดที่ตัวแทนควรใช้", "skillManagement.detail.skillNamePlaceholder": "ใช้ตัวอักษรพิมพ์เล็ก ตัวเลข และขีดกลาง", @@ -177,16 +143,12 @@ "skillManagement.detail.uploadConflict": "มีไฟล์ชื่อนี้อยู่แล้ว", "skillManagement.detail.uploadFailureSummary": "ไฟล์ {{failed}} ของ {{total}} ล้มเหลว", "skillManagement.detail.uploadFailureTitle": "การอัปโหลดบางรายการล้มเหลว", - "skillManagement.detail.uploadFile": "อัปโหลดไฟล์", - "skillManagement.detail.uploadFileFailed": "การอัปโหลดไฟล์ล้มเหลว", "skillManagement.detail.uploadFileSuccess": "อัปโหลดไฟล์แล้ว", "skillManagement.detail.uploadFilesButton": "อัปโหลดไฟล์ {{count}}", "skillManagement.detail.uploadFilesFailedStatus_one": "การอัปโหลดไฟล์ {{count}} ล้มเหลว", "skillManagement.detail.uploadFilesFailedStatus_other": "การอัปโหลดไฟล์ {{count}} ล้มเหลว", "skillManagement.detail.uploadFilesMenu": "อัปโหลดไฟล์...", "skillManagement.detail.uploadFilesProgress": "กำลังอัพโหลด {{completed}}/{{total}}", - "skillManagement.detail.uploadFilesResult": "อัปโหลด {{uploaded}} แล้ว · {{failed}} ล้มเหลว", - "skillManagement.detail.uploadFilesStatus": "สถานะการอัปโหลด", "skillManagement.detail.uploadInvalidExtension": "ไม่รองรับไฟล์ประเภทนี้", "skillManagement.detail.uploadInvalidName": "ชื่อไม่ถูกต้อง", "skillManagement.detail.uploadInvalidPath": "เส้นทางไฟล์ไม่ถูกต้อง", @@ -206,7 +168,6 @@ "skillManagement.detail.uploadWillReplace": "จะแทนที่ไฟล์ที่มีอยู่", "skillManagement.detail.uploadWillUseName": "จะอัพโหลดเป็น {{name}}", "skillManagement.detail.versionHistory": "เปิดประวัติเวอร์ชัน", - "skillManagement.detail.versionMeta": "{{hash}} · {{time}}", "skillManagement.detail.versionPublishNote": "บันทึกประจำรุ่น", "skillManagement.detail.versionPublishNotePlaceholder": "อธิบายการเปลี่ยนแปลงนี้", "skillManagement.detail.versionPublishedMeta": "{{time}} · {{name}}", @@ -227,8 +188,6 @@ "skillManagement.errors.missingSkillMd": "แพ็คเกจนี้ไม่มี SKILL.md เพิ่มไฟล์และนำเข้าอีกครั้ง", "skillManagement.errors.nameConflict": "มีทักษะชื่อ \"{{name}}\" อยู่แล้ว เปลี่ยนชื่อใน SKILL.md และนำเข้าอีกครั้ง", "skillManagement.errors.workspaceLimit": "พื้นที่ทำงานของคุณถึงขีดจำกัด 500 ทักษะแล้ว ลบทักษะเพื่อเพิ่มทักษะใหม่", - "skillManagement.exportDialog.description": "การดำเนินการนี้จะส่งออกฉบับร่างของทักษะที่บันทึกไว้ปัจจุบัน หากมีการเปลี่ยนแปลงที่ยังไม่ได้บันทึกในตัวแก้ไข โปรดบันทึกก่อนส่งออก", - "skillManagement.exportDialog.title": "ส่งออกฉบับร่างที่บันทึกไว้ปัจจุบัน", "skillManagement.import": "นำเข้า", "skillManagement.importFailed": "ไม่สามารถนำเข้าทักษะได้", "skillManagement.importSuccess": "Skill นำเข้า", @@ -236,12 +195,10 @@ "skillManagement.loadingError": "โหลดทักษะไม่สำเร็จ", "skillManagement.moreActions": "การกระทำเพิ่มเติมสำหรับ {{name}}", "skillManagement.noDescription": "ยังไม่มีคำอธิบาย", - "skillManagement.noTags": "ไม่มีแท็ก", "skillManagement.publishedAt": "เผยแพร่เมื่อ {{time}}", "skillManagement.referenceCount_one": "{{count}} เอเจนต์", "skillManagement.referenceCount_other": "{{count}} เอเจนต์", "skillManagement.searchLabel": "ค้นหาทักษะ", "skillManagement.searchPlaceholder": "ค้นหาตามชื่อหรือคำอธิบาย", - "skillManagement.tags": "แท็ก", "skillManagement.title": "ทักษะ" } diff --git a/web/i18n/th-TH/snippet.json b/web/i18n/locales/th-TH/snippet.json similarity index 100% rename from web/i18n/th-TH/snippet.json rename to web/i18n/locales/th-TH/snippet.json diff --git a/web/i18n/th-TH/time.json b/web/i18n/locales/th-TH/time.json similarity index 100% rename from web/i18n/th-TH/time.json rename to web/i18n/locales/th-TH/time.json diff --git a/web/i18n/th-TH/tools.json b/web/i18n/locales/th-TH/tools.json similarity index 100% rename from web/i18n/th-TH/tools.json rename to web/i18n/locales/th-TH/tools.json diff --git a/web/i18n/th-TH/workflow.json b/web/i18n/locales/th-TH/workflow.json similarity index 99% rename from web/i18n/th-TH/workflow.json rename to web/i18n/locales/th-TH/workflow.json index 725cd7b5228..22e53d2a04f 100644 --- a/web/i18n/th-TH/workflow.json +++ b/web/i18n/locales/th-TH/workflow.json @@ -460,8 +460,6 @@ "nodes.agent.pluginNotInstalledDesc": "การผสานรวมนี้ติดตั้งจาก GitHub โปรดไปที่การผสานรวมเพื่อติดตั้งใหม่", "nodes.agent.roster.change": "เปลี่ยน", "nodes.agent.roster.editInConsole": "แก้ไขใน Agents", - "nodes.agent.roster.editInConsoleDisabled": "การแก้ไข Agent ต้องมีบทบาท Editor ขึ้นไป", - "nodes.agent.roster.editInConsoleDisabledRbac": "เฉพาะสมาชิกที่มีบทบาท Editor ขึ้นไป หรือมีสิทธิ์จัดการ Agents เท่านั้นที่แก้ไข Agent ได้", "nodes.agent.roster.inlineSetup.description": "การตั้งค่าครั้งเดียวที่อยู่เฉพาะในโหนดนี้", "nodes.agent.roster.inlineSetup.name": "การตั้งค่าแบบอินไลน์", "nodes.agent.roster.inlineSetup.type": "เฉพาะในโหนดนี้", diff --git a/web/i18n/tr-TR/agent-v-2.json b/web/i18n/locales/tr-TR/agent-v-2.json similarity index 99% rename from web/i18n/tr-TR/agent-v-2.json rename to web/i18n/locales/tr-TR/agent-v-2.json index 49761163407..3b955f9507f 100644 --- a/web/i18n/tr-TR/agent-v-2.json +++ b/web/i18n/locales/tr-TR/agent-v-2.json @@ -69,6 +69,7 @@ "agentDetail.configure.answer.activity.runningCommands": "Komutlar çalıştırılıyor", "agentDetail.configure.answer.duration.minute": "{{count}} dk", "agentDetail.configure.answer.duration.second": "{{count}} sn", + "agentDetail.configure.answer.responseEnded": "Yanıt sona erdi", "agentDetail.configure.answer.thinking": "Düşünüyor", "agentDetail.configure.build.empty.communityEditionTip": "Community Edition, çıkarılan yapılandırmalar için sürümleme sunsa da dosya sisteminin kendisi için sürümleme desteklemez. Build modundaki işlemlerinizde dikkatli olun; dosya sisteminde yapılan değişiklikler gerçek zamanlı gerçekleşir ve her zaman temiz bir şekilde geri alınamayabilir. Community Edition, kitlesel dış hedef kitlelere hizmet vermek için ideal seçiminiz değildir.", "agentDetail.configure.build.empty.description": "Ne istediğinizi açıklayın; soldaki form ilerledikçe doldurulur.", @@ -269,7 +270,6 @@ "agentDetail.configure.workingDirectory.breadcrumbLabel": "Dosya sistemi yolu", "agentDetail.configure.workingDirectory.description": "Agent çalışırken okuduğu ve yazdığı dosyalar. qna_report.pdf gibi ürettiği her şey burada görünür.", "agentDetail.configure.workingDirectory.fileSystem": "Dosya sistemi", - "agentDetail.configure.workingDirectory.open": "Çalışma dizinini aç", "agentDetail.configure.workingDirectory.persistentFiles": "Kalıcı", "agentDetail.configure.workingDirectory.persistentFilesTooltip": "Agent'ın sakladığı ve son kullanıcı görüşmeleri ile iş akışları dahil olmak üzere çalıştığı her yerde kullanabildiği dosyalar. Bu oluşturma sohbeti sırasında burada yaptığınız değişiklikler “Uygula”ya tıkladığınızda geçerli olur.", "agentDetail.configure.workingDirectory.temporaryFiles": "Geçici", @@ -348,7 +348,6 @@ "agentDetail.sections.logs": "Günlükler", "agentDetail.sections.monitoring": "İzleme", "agentDetail.title": "Ajan", - "agentDetail.type": "AJAN", "agentDetail.versionHistory.empty": "Henüz sürüm yok", "agentDetail.versionHistory.exitVersions": "Sürümlerden çık", "agentDetail.versionHistory.filter": "Sürümleri filtrele", @@ -408,8 +407,5 @@ "roster.sort.lastModified": "Son değiştirilme", "roster.sort.recentlyCreated": "Yeni oluşturulanlar", "roster.title": "Ajanlar", - "roster.usageStatus.draft": "Taslak", - "skillManagement.detail.uploadFilesFailedStatus_one": "{{count}} dosya yüklenemedi.", - "skillManagement.detail.uploadFilesFailedStatus_other": "{{count}} dosya yüklenemedi.", - "skillManagement.title": "Skills" + "roster.usageStatus.draft": "Taslak" } diff --git a/web/i18n/tr-TR/app-annotation.json b/web/i18n/locales/tr-TR/app-annotation.json similarity index 100% rename from web/i18n/tr-TR/app-annotation.json rename to web/i18n/locales/tr-TR/app-annotation.json diff --git a/web/i18n/tr-TR/app-api.json b/web/i18n/locales/tr-TR/app-api.json similarity index 100% rename from web/i18n/tr-TR/app-api.json rename to web/i18n/locales/tr-TR/app-api.json diff --git a/web/i18n/tr-TR/app-debug.json b/web/i18n/locales/tr-TR/app-debug.json similarity index 100% rename from web/i18n/tr-TR/app-debug.json rename to web/i18n/locales/tr-TR/app-debug.json diff --git a/web/i18n/tr-TR/app-log.json b/web/i18n/locales/tr-TR/app-log.json similarity index 97% rename from web/i18n/tr-TR/app-log.json rename to web/i18n/locales/tr-TR/app-log.json index c7c4cf28187..f4fcc1a88cd 100644 --- a/web/i18n/tr-TR/app-log.json +++ b/web/i18n/locales/tr-TR/app-log.json @@ -4,11 +4,9 @@ "agentLogDetail.iteration": "Yineleme", "agentLogDetail.iterations": "Yinelemeler", "agentLogDetail.toolUsed": "Kullanılan Araç", - "archives.action.downloadMonth": "{{month}} arşivini indir", "archives.action.downloadReady": "Arşiv indirmesi hazır.", "archives.action.prepareDownload": "İndirmeyi hazırla", "archives.action.prepareFailed": "Arşiv indirmesi hazırlanamadı.", - "archives.action.prepareMonth": "{{month}} arşiv indirmesini hazırla", "archives.action.prepareStarted": "Arşiv indirmesi hazırlanıyor.", "archives.action.preparing": "Hazırlanıyor", "archives.description": "Bu çalışma alanındaki arşivlenmiş workflow çalıştırma günlüklerinin aylık CSV dışa aktarımlarını indirin.", diff --git a/web/i18n/tr-TR/app-overview.json b/web/i18n/locales/tr-TR/app-overview.json similarity index 100% rename from web/i18n/tr-TR/app-overview.json rename to web/i18n/locales/tr-TR/app-overview.json diff --git a/web/i18n/tr-TR/app.json b/web/i18n/locales/tr-TR/app.json similarity index 100% rename from web/i18n/tr-TR/app.json rename to web/i18n/locales/tr-TR/app.json diff --git a/web/i18n/tr-TR/billing.json b/web/i18n/locales/tr-TR/billing.json similarity index 100% rename from web/i18n/tr-TR/billing.json rename to web/i18n/locales/tr-TR/billing.json diff --git a/web/i18n/tr-TR/common.json b/web/i18n/locales/tr-TR/common.json similarity index 98% rename from web/i18n/tr-TR/common.json rename to web/i18n/locales/tr-TR/common.json index b48035b0e71..2be6c1e708a 100644 --- a/web/i18n/tr-TR/common.json +++ b/web/i18n/locales/tr-TR/common.json @@ -209,9 +209,7 @@ "mainNav.marketplace": "Marketplace", "mainNav.skills": "Skills", "mainNav.webApps.noResults": "Web uygulaması bulunamadı", - "mainNav.webApps.openApp": "{{name}} web uygulamasını aç", "mainNav.webApps.searchPlaceholder": "Web uygulamalarında ara", - "mainNav.workspace.credits": "{{count}} kredi", "mainNav.workspace.creditsUnit": "kredi", "mainNav.workspace.inviteMembers": "Invite members", "mainNav.workspace.noResults": "Çalışma alanı bulunamadı", @@ -233,7 +231,6 @@ "members.datasetOperatorTip": "Sadece bilgi tabanını yönetebilir", "members.editRole": "Rolü Düzenle", "members.editorTip": "Uygulama oluşturup düzenleyebilir", - "members.email": "E-posta", "members.emailInvalid": "Geçersiz E-posta Formatı", "members.emailNotSetup": "E-posta sunucusu kurulu değil, bu nedenle davet e-postaları gönderilemiyor. Lütfen kullanıcıları davetten sonra verilecek davet bağlantısı hakkında bilgilendirin.", "members.emailPlaceholder": "E-posta adreslerini yazın veya yapıştırın…", @@ -250,7 +247,6 @@ "members.inviteLimitExceeded": "Tüm alıcılar için yeterli koltuk yok.", "members.inviteTeamMember": "Takım Üyesi Ekle", "members.inviteTeamMemberTip": "Giriş yaptıktan sonra takım verilerinize doğrudan erişebilirler.", - "members.invitedAsRole": "{{role}} olarak davet edildi", "members.lastActive": "SON AKTİF", "members.memberActions": "Üye işlemleri", "members.memberDetails.assign": "Ata", @@ -258,8 +254,6 @@ "members.memberDetails.assignedRoles": "Atanan Roller", "members.memberDetails.customGroup": "ÖZELLEŞTİRİLMİŞ", "members.memberDetails.generalGroup": "GENEL", - "members.memberDetails.openAria": "{{name}} için üye ayrıntılarını aç", - "members.memberDetails.roleActionsAria": "{{role}} rolü için işlemleri aç", "members.memberDetails.roleNoPermissionSummary": "Mevcut rolün izni yok.", "members.memberDetails.rolePermissionSummary": "{{role}} {{permissions}} yapabilir", "members.memberDetails.title": "Üye Ayrıntıları", @@ -370,7 +364,6 @@ "modelProvider.card.usagePriorityTip": "Modelleri çalıştırırken önce hangi kaynağın kullanılacağını belirleyin.", "modelProvider.configureTip": "Api-key'i ayarlayın veya kullanmak için model ekleyin", "modelProvider.confirmDelete": "silme onayı?", - "modelProvider.credits": "Mesaj Kredileri", "modelProvider.defaultConfig": "Varsayılan Yapılandırma", "modelProvider.discoverMore": "Daha fazlasını keşfedin", "modelProvider.embeddingModel.key": "Yerleştirme Modeli", @@ -427,7 +420,6 @@ "modelProvider.selector.noProviderConfiguredDesc": "Yüklemek için Pazar Yeri'ne göz atın veya ayarlardan sağlayıcıları yapılandırın.", "modelProvider.selector.onlyCompatibleModelsShown": "Yalnızca uyumlu modeller gösterilir", "modelProvider.selector.showIncompatibleModels": "Uyumsuz modelleri göster", - "modelProvider.selector.suggestion": "Öneri", "modelProvider.selector.suggestionTip": "Aracı görevleri için önerilir", "modelProvider.showModels": "Modelleri Göster", "modelProvider.speechToTextModel.key": "Konuşmadan Metne Modeli", @@ -458,6 +450,8 @@ "operation.confirmAction": "Lütfen işleminizi onaylayın.", "operation.copied": "Kopyalandı", "operation.copy": "Kopyala", + "operation.copyErrorDetails": "Hata ayrıntılarını kopyala", + "operation.copyErrorFailed": "Kopyalama başarısız. Yeniden dene", "operation.copyImage": "Resmi Kopyala", "operation.create": "Oluştur", "operation.deSelectAll": "Hepsini Seçme", @@ -475,7 +469,6 @@ "operation.format": "Format", "operation.imageCopied": "Kopyalanan görüntü", "operation.imageCopyFailed": "Görüntü kopyalanamadı. Lütfen bunun yerine indirin.", - "operation.imageDownloaded": "Resim indirildi", "operation.in": "içinde", "operation.learnMore": "Daha fazla bilgi", "operation.log": "log", @@ -512,7 +505,6 @@ "operation.sure": "Eminim", "operation.toggleFullscreen": "Tam ekranı aç/kapat", "operation.toggleMute": "Sessize al/aç", - "operation.upload": "Yükle", "operation.view": "Görüntüle", "operation.viewDetails": "Detayları Görüntüle", "operation.viewMore": "DAHA FAZLA GÖSTER", @@ -542,7 +534,6 @@ "promptEditor.requestURL.item.title": "İstek URL'si", "promptEditor.variable.modal.add": "Yeni değişken", "promptEditor.variable.modal.addTool": "Yeni araç", - "provider.anthropicHosted.trialQuotaTip": "Antropik deneme kotanız 11/03/2025 tarihinde sona erecek ve bu tarihten sonra kullanılamayacaktır. Lütfen zamanında kullanın.", "provider.encrypted.back": " teknolojisi.", "provider.encrypted.front": "API ANAHTARINIZ şu kullanılarak şifrelenip saklanacak:", "provider.validating": "Anahtar doğrulanıyor...", @@ -565,7 +556,6 @@ "settings.preferences": "Tercihler", "settings.provider": "Model Sağlayıcı", "settings.resourceAccess": "Kaynak Erişimi", - "settings.resourceAccessDescription": "Uygulamaların ve bilgi tabanlarının kullanabileceği izin kurallarını yapılandırın. İzin kuralları, belirli kaynak erişimi yapılandırmalarında üyelere atanabilir.", "settings.rolesAndPermissions": "Roller ve İzinler", "settings.settings": "Ayarlar", "settings.swaggerAPIAsTool": "Swagger API as Tool", @@ -695,7 +685,6 @@ "userProfile.helpCenter": "Yardım", "userProfile.logout": "Çıkış Yap", "userProfile.roadmap": "Yol haritası", - "userProfile.settings": "Ayarlar", "userProfile.workspace": "Çalışma Alanı", "voice.language.arTN": "Tunus Arapçası", "voice.language.deDE": "Almanca", diff --git a/web/i18n/tr-TR/custom.json b/web/i18n/locales/tr-TR/custom.json similarity index 100% rename from web/i18n/tr-TR/custom.json rename to web/i18n/locales/tr-TR/custom.json diff --git a/web/i18n/tr-TR/dataset-creation.json b/web/i18n/locales/tr-TR/dataset-creation.json similarity index 100% rename from web/i18n/tr-TR/dataset-creation.json rename to web/i18n/locales/tr-TR/dataset-creation.json diff --git a/web/i18n/tr-TR/dataset-documents.json b/web/i18n/locales/tr-TR/dataset-documents.json similarity index 100% rename from web/i18n/tr-TR/dataset-documents.json rename to web/i18n/locales/tr-TR/dataset-documents.json diff --git a/web/i18n/tr-TR/dataset-hit-testing.json b/web/i18n/locales/tr-TR/dataset-hit-testing.json similarity index 100% rename from web/i18n/tr-TR/dataset-hit-testing.json rename to web/i18n/locales/tr-TR/dataset-hit-testing.json diff --git a/web/i18n/tr-TR/dataset-pipeline.json b/web/i18n/locales/tr-TR/dataset-pipeline.json similarity index 100% rename from web/i18n/tr-TR/dataset-pipeline.json rename to web/i18n/locales/tr-TR/dataset-pipeline.json diff --git a/web/i18n/tr-TR/dataset-settings.json b/web/i18n/locales/tr-TR/dataset-settings.json similarity index 100% rename from web/i18n/tr-TR/dataset-settings.json rename to web/i18n/locales/tr-TR/dataset-settings.json diff --git a/web/i18n/tr-TR/dataset.json b/web/i18n/locales/tr-TR/dataset.json similarity index 97% rename from web/i18n/tr-TR/dataset.json rename to web/i18n/locales/tr-TR/dataset.json index c83f01bfad7..61afea02c69 100644 --- a/web/i18n/tr-TR/dataset.json +++ b/web/i18n/locales/tr-TR/dataset.json @@ -163,7 +163,6 @@ "newKnowledge.configureProvider": "{{provider}}'ı Yapılandır", "newKnowledge.connectNotion": "Notion’a bağlan", "newKnowledge.connectProvider": "{{provider}}'a bağlanın", - "newKnowledge.connectProviderGeneric": "Sağlayıcıya bağlan", "newKnowledge.connectSource": "Kaynak bağla", "newKnowledge.connectSourceDescription": "İçerik Notion, web siteleri ve diğer hizmetlerle senkronize kalır.", "newKnowledge.connectingProvider": "Bağlanıyor…", @@ -179,18 +178,14 @@ "newKnowledge.crawlFailedDescription": "Tarayıcı bu önizlemeyi tamamlayamadı. URL’yi kontrol edin, tarama seçeneklerini ayarlayın veya yeniden deneyin.", "newKnowledge.crawlFailedProvider": "{{provider}} bir hata bildirdi. Sağlayıcı bağlantısını kontrol edip yeniden deneyin.", "newKnowledge.crawlFailedTimeout": "Tarama zaman aşımına uğradı. Daha dar kapsamlı bir URL deneyin veya en fazla sayfa sayısını azaltın.", - "newKnowledge.crawlNotStarted": "Başlatılmadı", "newKnowledge.crawlOptions": "Tarama seçenekleri", "newKnowledge.crawlPreview": "Tarama önizlemesi", "newKnowledge.crawlProgress": "{{host}} için tarama ilerlemesi", - "newKnowledge.crawlSetupUnavailableDescription": "Web sitesi tarama yapılandırması, tarama iş akışı kullanılabilir olana kadar devre dışı bırakılır.", - "newKnowledge.crawlSetupUnavailableTitle": "Firecrawl hazır", "newKnowledge.crawlStartFailed": "Tarama önizlemesi başlatılamadı. Yeniden deneyin.", "newKnowledge.crawlStopped": "Tarama durduruldu", "newKnowledge.crawling": "Taranıyor…", "newKnowledge.crawlingPages": "{{host}} taranıyor… {{count}} sayfa bulundu", "newKnowledge.createFailed": "Bu bilgi tabanını oluşturamadık. Erişiminizi kontrol edip tekrar deneyin.", - "newKnowledge.createIllustrationTitle": "İçeriğinizi bir kez bağlayın — her uygulama en güncel bilgilerle yanıt versin.", "newKnowledge.createTitle": "Bilgi Yaratın", "newKnowledge.createdAt": "Oluşturuldu", "newKnowledge.creators": "Oluşturanlar", @@ -220,7 +215,6 @@ "newKnowledge.documentColumn": "Belge", "newKnowledge.documentContentIncomplete": "Belgenin kalan içeriği yükleniyor…", "newKnowledge.documentContents": "İçerik", - "newKnowledge.documentFacts": "Belge ayrıntıları", "newKnowledge.documentFilterLabel": "Belgeleri duruma göre filtreleyin", "newKnowledge.documentLoadErrorDescription": "Bu belge yüklenemedi. Tekrar deneyin.", "newKnowledge.documentLoadErrorTitle": "Belge yüklenemedi", @@ -268,7 +262,6 @@ "newKnowledge.documentsSelected": "{{count}} belgeleri seçildi", "newKnowledge.documentsUnavailable": "— belge", "newKnowledge.downloadDocuments": "İndir", - "newKnowledge.editSource": "Düzenle", "newKnowledge.emptyChunk": "Boş parça", "newKnowledge.emptyDescription": "Belgelerinizi ekleyin; ajanlar indeksleme yapılandırmadan belgeleri doğrudan okuyabilsin.", "newKnowledge.emptyTitle": "Bilgi tabanları oluşturmanın daha akıllı yolu", @@ -344,7 +337,6 @@ "newKnowledge.processingTaskState.succeeded": "Tamamlandı", "newKnowledge.processingTaskState.superseded": "Değiştirildi", "newKnowledge.providerConnected": "{{provider}} bağlandı", - "newKnowledge.providerLabel": "Bir sağlayıcı seçin", "newKnowledge.providerLoadFailed": "Sağlayıcılar yüklenemedi", "newKnowledge.providerNotConfigured": "{{provider}} yapılandırılmadı", "newKnowledge.providerNotConfiguredDescription": "{{provider}} ile web sitelerini taramak için bir API anahtarı veya uç nokta URL'si ekleyin.", @@ -369,12 +361,10 @@ "newKnowledge.revisionState.superseded": "Önceki", "newKnowledge.rootUrl": "Kök URL", "newKnowledge.rootUrlPlaceholder": "https://docs.example.com", - "newKnowledge.routeUnavailable": "Bu bölüm bir sonraki kurulum adımında kullanılabilir hale gelecektir.", "newKnowledge.searchDocuments": "Belgeleri arayın", "newKnowledge.searchSources": "Kaynakları ara", "newKnowledge.selectAll": "Tümünü seç", "newKnowledge.selectAllDocuments": "Tüm belgeleri seç", - "newKnowledge.selectAllSources": "Tüm kaynakları seç", "newKnowledge.selectChunk": "İçeriğini incelemek için bir parça seçin.", "newKnowledge.selectFilesAndFolders": "Senkronize edilecek dosya ve klasörleri seçin", "newKnowledge.skippedFailed": "Atlandı (başarısız)", @@ -384,7 +374,6 @@ "newKnowledge.sourceFilterLabel": "Kaynakları duruma göre filtrele", "newKnowledge.sourceName": "Kaynak adı", "newKnowledge.sourceNamePlaceholder": "ör. ürün belgeleri taraması", - "newKnowledge.sourceNameRequired": "Bir kaynak adı girin.", "newKnowledge.sourceSetupBackendDependency": "Kaynak kurulumu eksiksiz gösterilir; düzenleme ve önizleme işlemleri taslak kaynak arka uç akışını gerektirir.", "newKnowledge.sourceStatus.active": "Aktif", "newKnowledge.sourceStatus.disabled": "Devre Dışı", @@ -431,7 +420,6 @@ "newKnowledge.unavailableDescription": "KnowledgeFS şu anda bu çalışma alanında kullanılamıyor.", "newKnowledge.unavailableTitle": "Yeni Bilgi kullanılamıyor", "newKnowledge.updated": "{{date}} tarihinde güncellendi", - "newKnowledge.updatedAt": "Son güncelleme", "newKnowledge.updatedColumn": "Güncellendi", "newKnowledge.uploadCharactersUnavailable": "Karakterler: —", "newKnowledge.uploadDocuments": "Belgeleri yükleyin", @@ -460,11 +448,9 @@ "retrievalSettings": "Geri Alım Ayarı", "retry": "Yeniden Dene", "selectExternalKnowledgeAPI.placeholder": "Bir Harici Bilgi API'si seçin", - "serviceApi.card.addApiKey": "API Anahtarı Ekle", "serviceApi.card.apiKey": "API Anahtarı", "serviceApi.card.apiReference": "API Referansı", "serviceApi.card.endpoint": "Hizmet API Uç Noktası", - "serviceApi.card.manageApiKey": "API Anahtarını Yönet", "serviceApi.card.title": "Backend servis api", "serviceApi.disabled": "Devre Dışı", "serviceApi.enabled": "Etkin", diff --git a/web/i18n/tr-TR/deployments.json b/web/i18n/locales/tr-TR/deployments.json similarity index 97% rename from web/i18n/tr-TR/deployments.json rename to web/i18n/locales/tr-TR/deployments.json index 091192ed63a..eba68ab88db 100644 --- a/web/i18n/tr-TR/deployments.json +++ b/web/i18n/locales/tr-TR/deployments.json @@ -27,7 +27,6 @@ "list.emptyTitle": "Henüz dağıtım yok", "overview.chip.latest": "en son", "status.RUNTIME_INSTANCE_STATUS_DEPLOYING": "Dağıtılıyor", - "status.RUNTIME_INSTANCE_STATUS_FAILED": "Dağıtım başarısız", "status.RUNTIME_INSTANCE_STATUS_INVALID": "Geçersiz", "status.RUNTIME_INSTANCE_STATUS_READY": "Çalışıyor", "status.RUNTIME_INSTANCE_STATUS_UNDEPLOYED": "Dağıtılmadı", @@ -87,6 +86,5 @@ "studio.versionValue": "Sürüm değeri", "studio.versionsBehind_one": "{{count}} sürüm geride", "studio.versionsBehind_other": "{{count}} sürüm geride", - "versions.checkingReleaseContent": "Sürüm içeriği kontrol ediliyor...", "versions.deployTo": "{{name}} ortamına dağıt" } diff --git a/web/i18n/tr-TR/device-flow.json b/web/i18n/locales/tr-TR/device-flow.json similarity index 100% rename from web/i18n/tr-TR/device-flow.json rename to web/i18n/locales/tr-TR/device-flow.json diff --git a/web/i18n/tr-TR/education.json b/web/i18n/locales/tr-TR/education.json similarity index 100% rename from web/i18n/tr-TR/education.json rename to web/i18n/locales/tr-TR/education.json diff --git a/web/i18n/tr-TR/explore.json b/web/i18n/locales/tr-TR/explore.json similarity index 100% rename from web/i18n/tr-TR/explore.json rename to web/i18n/locales/tr-TR/explore.json diff --git a/web/i18n/tr-TR/layout.json b/web/i18n/locales/tr-TR/layout.json similarity index 100% rename from web/i18n/tr-TR/layout.json rename to web/i18n/locales/tr-TR/layout.json diff --git a/web/i18n/tr-TR/login.json b/web/i18n/locales/tr-TR/login.json similarity index 100% rename from web/i18n/tr-TR/login.json rename to web/i18n/locales/tr-TR/login.json diff --git a/web/i18n/tr-TR/oauth.json b/web/i18n/locales/tr-TR/oauth.json similarity index 100% rename from web/i18n/tr-TR/oauth.json rename to web/i18n/locales/tr-TR/oauth.json diff --git a/web/i18n/tr-TR/permission-keys.json b/web/i18n/locales/tr-TR/permission-keys.json similarity index 100% rename from web/i18n/tr-TR/permission-keys.json rename to web/i18n/locales/tr-TR/permission-keys.json diff --git a/web/i18n/tr-TR/permission.json b/web/i18n/locales/tr-TR/permission.json similarity index 100% rename from web/i18n/tr-TR/permission.json rename to web/i18n/locales/tr-TR/permission.json diff --git a/web/i18n/tr-TR/pipeline.json b/web/i18n/locales/tr-TR/pipeline.json similarity index 100% rename from web/i18n/tr-TR/pipeline.json rename to web/i18n/locales/tr-TR/pipeline.json diff --git a/web/i18n/tr-TR/plugin-tags.json b/web/i18n/locales/tr-TR/plugin-tags.json similarity index 100% rename from web/i18n/tr-TR/plugin-tags.json rename to web/i18n/locales/tr-TR/plugin-tags.json diff --git a/web/i18n/tr-TR/plugin-trigger.json b/web/i18n/locales/tr-TR/plugin-trigger.json similarity index 100% rename from web/i18n/tr-TR/plugin-trigger.json rename to web/i18n/locales/tr-TR/plugin-trigger.json diff --git a/web/i18n/tr-TR/plugin.json b/web/i18n/locales/tr-TR/plugin.json similarity index 99% rename from web/i18n/tr-TR/plugin.json rename to web/i18n/locales/tr-TR/plugin.json index 8210ca99d20..4e02e4a2f04 100644 --- a/web/i18n/tr-TR/plugin.json +++ b/web/i18n/locales/tr-TR/plugin.json @@ -277,7 +277,6 @@ "readmeInfo.noReadmeAvailable": "README mevcut değil", "readmeInfo.title": "BENİOKU", "requestAPlugin": "Bir eklenti iste", - "requestSubmit": "İste veya yayınla", "search": "Ara", "searchCategories": "Arama Kategorileri", "searchInMarketplace": "Pazar Yeri'nde arama yapın", diff --git a/web/i18n/tr-TR/register.json b/web/i18n/locales/tr-TR/register.json similarity index 100% rename from web/i18n/tr-TR/register.json rename to web/i18n/locales/tr-TR/register.json diff --git a/web/i18n/tr-TR/run-log.json b/web/i18n/locales/tr-TR/run-log.json similarity index 100% rename from web/i18n/tr-TR/run-log.json rename to web/i18n/locales/tr-TR/run-log.json diff --git a/web/i18n/tr-TR/share.json b/web/i18n/locales/tr-TR/share.json similarity index 100% rename from web/i18n/tr-TR/share.json rename to web/i18n/locales/tr-TR/share.json diff --git a/web/i18n/tr-TR/skill.json b/web/i18n/locales/tr-TR/skill.json similarity index 83% rename from web/i18n/tr-TR/skill.json rename to web/i18n/locales/tr-TR/skill.json index d41784ff135..ac6af9541bd 100644 --- a/web/i18n/tr-TR/skill.json +++ b/web/i18n/locales/tr-TR/skill.json @@ -1,5 +1,4 @@ { - "skillManagement.clearTags": "Etiketleri temizle", "skillManagement.create": "Oluştur", "skillManagement.createFailed": "Beceri oluşturulamadı.", "skillManagement.createSuccess": "Taslak oluşturuldu.", @@ -13,12 +12,9 @@ "skillManagement.deleteFailed": "Beceri silinemedi.", "skillManagement.deleteSuccess": "Skill silindi.", "skillManagement.detail.addMetadata": "Meta veri ekle", - "skillManagement.detail.addMetadataDescription": "Bu Markdown dosyasına bir ön madde alanı ekleyin.", "skillManagement.detail.addTag": "Etiket ekle", - "skillManagement.detail.addTagDescription": "Bu Skill'a bir etiket oluşturun veya bağlayın. Yeni etiketler Skill meta verileriyle kaydedilir.", "skillManagement.detail.addTagSuccess": "Etiket eklendi.", "skillManagement.detail.back": "Skills'e geri dön", - "skillManagement.detail.builder.applyFailed": "Taslak güncellemeleri uygulanamadı.", "skillManagement.detail.builder.attach": "Dosya ekle", "skillManagement.detail.builder.attachFailed": "Dosya eklenemedi.", "skillManagement.detail.builder.attachLimit": "En fazla 10 dosya ekleyebilirsiniz.", @@ -26,22 +22,12 @@ "skillManagement.detail.builder.attachUnsupported": "Yalnızca resimler, metin ve belge dosyaları eklenebilir.", "skillManagement.detail.builder.attachmentOnlyMessage": "Bu beceriyi geliştirmeye yardımcı olması için ekteki dosyayı kullanın.", "skillManagement.detail.builder.close": "Skill Builder'yi kapat", - "skillManagement.detail.builder.compatibleModelsOnly": "Yalnızca uyumlu modeller gösterilir", "skillManagement.detail.builder.copyResponse": "Yanıtı kopyala", "skillManagement.detail.builder.copySuccess": "Yanıt panoya kopyalandı.", "skillManagement.detail.builder.editIntro": "Bu beceriyi inceledim. Neyi ayarlamak istersiniz?", "skillManagement.detail.builder.exampleIssueTriage": "Müşteri sorunları önceliklendirmesi", "skillManagement.detail.builder.exampleOnboarding": "Yeni işe alım kılavuzu", "skillManagement.detail.builder.exampleSalesFollowUp": "Satış lideri takip stratejisi", - "skillManagement.detail.builder.followUpDisplayName": "Görünen ad olarak Geri Ödeme onayını kullanın", - "skillManagement.detail.builder.followUpNameIcon": "Önerilen adı ve simgeyi uygulayın", - "skillManagement.detail.builder.fromMarketplace": "Pazar Yeri'nden", - "skillManagement.detail.builder.model": "GPT-4o", - "skillManagement.detail.builder.modelCredits.all": "Tüm krediler", - "skillManagement.detail.builder.modelCredits.configure": "Yapılandırma gerekli", - "skillManagement.detail.builder.modelCredits.exhausted": "Kota Tükendi", - "skillManagement.detail.builder.modelProviderSettings": "Model Sağlayıcı Ayarları", - "skillManagement.detail.builder.modelSearch": "Modelleri ara...", "skillManagement.detail.builder.modifyPlaceholder": "Yapay zekadan bu beceriyi değiştirmesini isteyin...", "skillManagement.detail.builder.open": "Skill Builder'yi açın", "skillManagement.detail.builder.placeholder": "Senaryoyu açıklayın...", @@ -61,8 +47,6 @@ "skillManagement.detail.builder.thinkingUnavailable": "Model tarafından hiçbir düşünme içeriği döndürülmedi.", "skillManagement.detail.builder.title": "Skill Builder", "skillManagement.detail.builder.tryExample": "Bir örnek deneyin", - "skillManagement.detail.builder.voice": "Sesli giriş", - "skillManagement.detail.builder.voiceUnavailable": "Ses girişi henüz kullanılamıyor.", "skillManagement.detail.cancelAddMetadata": "Meta veri eklemeyi iptal et", "skillManagement.detail.closeFileTab": "{{name}}'ı kapat", "skillManagement.detail.closeVersions": "Sürümleri kapat", @@ -70,14 +54,10 @@ "skillManagement.detail.copyContentSuccess": "İçerik panoya kopyalandı.", "skillManagement.detail.copyFile": "Kopyala", "skillManagement.detail.copyFileSuccess": "Dosya yapıştırmaya hazır.", - "skillManagement.detail.copyVersionId": "ID Kopyala", - "skillManagement.detail.copyVersionIdSuccess": "Kimlik panoya kopyalandı.", "skillManagement.detail.createFile": "Yeni dosya", - "skillManagement.detail.createFileDescription": "Beceri köküne göre bir dosya yolu girin.", "skillManagement.detail.createFileMenu": "Yeni dosya...", "skillManagement.detail.createFileSuccess": "Dosya oluşturuldu.", "skillManagement.detail.createFolder": "Yeni klasör", - "skillManagement.detail.createFolderDescription": "Beceri köküne göre bir klasör yolu girin.", "skillManagement.detail.createFolderMenu": "Yeni klasör...", "skillManagement.detail.createFolderSuccess": "Klasör oluşturuldu.", "skillManagement.detail.createdBy": "{{name}} tarafından oluşturuldu", @@ -100,10 +80,8 @@ "skillManagement.detail.fileMeta": "{{type}} · {{size}} bayt", "skillManagement.detail.fileMissing": "Dosya bulunamadı.", "skillManagement.detail.fileOperationFailed": "Dosya işlemi başarısız oldu.", - "skillManagement.detail.files": "Dosyalar", "skillManagement.detail.latest": "Sonuncu", "skillManagement.detail.loadFailed": "Beceri yüklenemedi.", - "skillManagement.detail.loadingTags": "Etiketler yükleniyor...", "skillManagement.detail.markdownLiveMode": "Canlı önizleme", "skillManagement.detail.markdownSourceMode": "Kaynak editörü", "skillManagement.detail.metadataKey": "isim", @@ -113,10 +91,7 @@ "skillManagement.detail.nameThisVersion": "Bu versiyona isim ver", "skillManagement.detail.noFileSelected": "Önizlemek için bir dosya seçin.", "skillManagement.detail.noFiles": "Dosya yok.", - "skillManagement.detail.noSearchResults": "Eşleşen dosya yok.", - "skillManagement.detail.noSelectableTags": "Kullanılabilir etiket yok.", "skillManagement.detail.noVersions": "Henüz yayınlanmış bir sürüm yok.", - "skillManagement.detail.pasteFile": "Yapıştır", "skillManagement.detail.pasteFileSuccess": "Dosya yapıştırıldı.", "skillManagement.detail.previewUnsupported": "Bu dosya önizlemeyi desteklemiyor.", "skillManagement.detail.publish": "Yayınla", @@ -128,9 +103,7 @@ "skillManagement.detail.publishUpdate": "Güncellemeyi yayınla", "skillManagement.detail.published": "Yayınlandı", "skillManagement.detail.publishedAt": "{{time}} yayınlandı", - "skillManagement.detail.publishedVersion": "Yayınlandı #{{number}}", "skillManagement.detail.publishing": "Yayınlanıyor...", - "skillManagement.detail.readonly": "Salt okunur", "skillManagement.detail.referenceFiles.confirm": "Gir Onayla", "skillManagement.detail.referenceFiles.empty": "Dosya yok.", "skillManagement.detail.referenceFiles.livePlaceholder": "Temsilci için talimatlar yazın. Dosyalara referans vermek için / yazın.", @@ -138,14 +111,10 @@ "skillManagement.detail.referenceFiles.title": "Referans dosyaları", "skillManagement.detail.referencedBy_one": "{{count}} Agent tarafından kullanılıyor", "skillManagement.detail.referencedBy_other": "{{count}} Agent tarafından kullanılıyor", - "skillManagement.detail.removeMetadata": "{{name}} meta verilerini silin", "skillManagement.detail.removeTag": "{{tag}} öğesini kaldır", "skillManagement.detail.removeTagSuccess": "Etiket kaldırıldı.", - "skillManagement.detail.renameFile": "Dosyayı yeniden adlandır", - "skillManagement.detail.renameFileDescription": "Beceri köküne göre yeni yolu girin.", "skillManagement.detail.renameFileSuccess": "Dosya yeniden adlandırıldı.", "skillManagement.detail.renameSkillSuccess": "Skill yeniden adlandırıldı.", - "skillManagement.detail.renameVersion": "Yeniden Adlandır", "skillManagement.detail.renameVersionFailed": "Sürüm yeniden adlandırılamadı.", "skillManagement.detail.renameVersionPrompt": "Sürüm adı", "skillManagement.detail.renameVersionSuccess": "Sürüm yeniden adlandırıldı.", @@ -155,17 +124,14 @@ "skillManagement.detail.restoreVersionConfirmTitle": "Bu sürüm geri yüklensin mi?", "skillManagement.detail.restoreVersionFailed": "Sürüm geri yüklenemedi.", "skillManagement.detail.restoreVersionSuccess": "Sürüm taslağa geri yüklendi.", - "skillManagement.detail.save": "Kaydet", "skillManagement.detail.saveConflictConfirmDescription": "Bu beceri başka bir yerde güncellendi. Düzenlemeye devam etmek için en son taslağı yükleyin. Mevcut değişiklikleriniz silinecek.", "skillManagement.detail.saveConflictConfirmTitle": "En son taslak yüklensin mi?", "skillManagement.detail.saveConflictReload": "En son taslağı yükle", "skillManagement.detail.saveConflictStatus": "Çakışma tespit edildi. Düzenlemeye devam etmek için en son taslağı yükleyin.", "skillManagement.detail.saveFailed": "Dosya kaydedilemedi.", - "skillManagement.detail.saveSuccess": "Dosya kaydedildi.", "skillManagement.detail.saved": "Kaydedildi", "skillManagement.detail.savedAt": "{{time}} kaydedildi", "skillManagement.detail.saving": "Kaydediliyor...", - "skillManagement.detail.searchFiles": "Dosya ara", "skillManagement.detail.showMoreReferences": "{{count}}'yi daha fazlasını göster", "skillManagement.detail.skillDescriptionPlaceholder": "Bu becerinin ne işe yaradığını ve temsilcilerin bunu ne zaman kullanması gerektiğini açıklayın.", "skillManagement.detail.skillNamePlaceholder": "Küçük harfler, sayılar ve kısa çizgiler kullanın.", @@ -177,16 +143,12 @@ "skillManagement.detail.uploadConflict": "Bu isimde bir dosya zaten mevcut.", "skillManagement.detail.uploadFailureSummary": "{{total}} dosyalarının {{failed}}'si başarısız oldu", "skillManagement.detail.uploadFailureTitle": "Bazı yüklemeler başarısız oldu", - "skillManagement.detail.uploadFile": "Dosya yükle", - "skillManagement.detail.uploadFileFailed": "Dosya yükleme başarısız oldu.", "skillManagement.detail.uploadFileSuccess": "Dosya yüklendi.", "skillManagement.detail.uploadFilesButton": "{{count}} dosyalarını yükle", "skillManagement.detail.uploadFilesFailedStatus_one": "{{count}} dosya yüklemesi başarısız oldu.", "skillManagement.detail.uploadFilesFailedStatus_other": "{{count}} dosya yüklemeleri başarısız oldu.", "skillManagement.detail.uploadFilesMenu": "Dosyaları yükle...", "skillManagement.detail.uploadFilesProgress": "{{completed}}/{{total}} yükleniyor", - "skillManagement.detail.uploadFilesResult": "{{uploaded}} yüklendi · {{failed}} başarısız oldu", - "skillManagement.detail.uploadFilesStatus": "Yükleme Durumu", "skillManagement.detail.uploadInvalidExtension": "Bu dosya türü desteklenmiyor.", "skillManagement.detail.uploadInvalidName": "Geçersiz ad.", "skillManagement.detail.uploadInvalidPath": "Geçersiz dosya yolu.", @@ -206,7 +168,6 @@ "skillManagement.detail.uploadWillReplace": "Mevcut dosyayı değiştirecek", "skillManagement.detail.uploadWillUseName": "{{name}} olarak yüklenecek", "skillManagement.detail.versionHistory": "Sürüm geçmişini aç", - "skillManagement.detail.versionMeta": "{{hash}} · {{time}}", "skillManagement.detail.versionPublishNote": "Sürüm notları", "skillManagement.detail.versionPublishNotePlaceholder": "Bu değişikliği açıklayın", "skillManagement.detail.versionPublishedMeta": "{{time}} · {{name}}", @@ -227,8 +188,6 @@ "skillManagement.errors.missingSkillMd": "Bu pakette SKILL.md yok. Dosyayı ekleyin ve tekrar içe aktarın.", "skillManagement.errors.nameConflict": "\"{{name}}\" adlı bir beceri zaten mevcut. SKILL.md'de yeniden adlandırın ve tekrar içe aktarın.", "skillManagement.errors.workspaceLimit": "Çalışma alanınız 500 beceri sınırına ulaştı. Yeni bir beceri eklemek için bir beceriyi silin.", - "skillManagement.exportDialog.description": "Becerinizin geçerli kayıtlı taslağı dışa aktarılacaktır. Düzenleyicide kaydedilmemiş değişiklikler varsa dışa aktarmadan önce kaydedin.", - "skillManagement.exportDialog.title": "Geçerli Kayıtlı Taslağı Dışa Aktar", "skillManagement.import": "İçe Aktar", "skillManagement.importFailed": "Beceri içe aktarılamadı.", "skillManagement.importSuccess": "Skill içe aktarıldı.", @@ -236,12 +195,10 @@ "skillManagement.loadingError": "Beceriler yüklenemedi", "skillManagement.moreActions": "{{name}} için daha fazla eylem", "skillManagement.noDescription": "Henüz açıklama yok", - "skillManagement.noTags": "Etiket yok", "skillManagement.publishedAt": "{{time}} yayınlandı", "skillManagement.referenceCount_one": "{{count}} Agent", "skillManagement.referenceCount_other": "{{count}} Agent", "skillManagement.searchLabel": "Becerileri ara", "skillManagement.searchPlaceholder": "Ada veya açıklamaya göre arayın", - "skillManagement.tags": "Etiketler", "skillManagement.title": "Beceriler" } diff --git a/web/i18n/tr-TR/snippet.json b/web/i18n/locales/tr-TR/snippet.json similarity index 100% rename from web/i18n/tr-TR/snippet.json rename to web/i18n/locales/tr-TR/snippet.json diff --git a/web/i18n/tr-TR/time.json b/web/i18n/locales/tr-TR/time.json similarity index 100% rename from web/i18n/tr-TR/time.json rename to web/i18n/locales/tr-TR/time.json diff --git a/web/i18n/tr-TR/tools.json b/web/i18n/locales/tr-TR/tools.json similarity index 100% rename from web/i18n/tr-TR/tools.json rename to web/i18n/locales/tr-TR/tools.json diff --git a/web/i18n/tr-TR/workflow.json b/web/i18n/locales/tr-TR/workflow.json similarity index 99% rename from web/i18n/tr-TR/workflow.json rename to web/i18n/locales/tr-TR/workflow.json index cd7d44f5a06..ade539f045b 100644 --- a/web/i18n/tr-TR/workflow.json +++ b/web/i18n/locales/tr-TR/workflow.json @@ -460,8 +460,6 @@ "nodes.agent.pluginNotInstalledDesc": "Bu entegrasyon GitHub'dan yüklenmiştir. Lütfen şuraya gidin: Entegrasyonlar yeniden yüklemek için", "nodes.agent.roster.change": "Değiştir", "nodes.agent.roster.editInConsole": "Agents'da düzenle", - "nodes.agent.roster.editInConsoleDisabled": "Ajanları düzenlemek için Editör veya üzeri bir rol gerekir.", - "nodes.agent.roster.editInConsoleDisabledRbac": "Yalnızca Editör veya üzeri role ya da Agents'ı yönet iznine sahip üyeler ajanları düzenleyebilir.", "nodes.agent.roster.inlineSetup.description": "Yalnızca bu düğümde yaşayan tek seferlik bir kurulum", "nodes.agent.roster.inlineSetup.name": "Inline kurulum", "nodes.agent.roster.inlineSetup.type": "Yalnızca bu düğümde", diff --git a/web/i18n/uk-UA/agent-v-2.json b/web/i18n/locales/uk-UA/agent-v-2.json similarity index 98% rename from web/i18n/uk-UA/agent-v-2.json rename to web/i18n/locales/uk-UA/agent-v-2.json index 6245109c514..57407d1ca15 100644 --- a/web/i18n/uk-UA/agent-v-2.json +++ b/web/i18n/locales/uk-UA/agent-v-2.json @@ -69,6 +69,7 @@ "agentDetail.configure.answer.activity.runningCommands": "Виконання команд", "agentDetail.configure.answer.duration.minute": "{{count}} хв", "agentDetail.configure.answer.duration.second": "{{count}} с", + "agentDetail.configure.answer.responseEnded": "Відповідь завершено", "agentDetail.configure.answer.thinking": "Розмірковує", "agentDetail.configure.build.empty.communityEditionTip": "Community Edition, хоча й пропонує версіонування витягнутих конфігурацій, не підтримує версіонування самої файлової системи. Будьте обережні з діями в режимі Build, адже зміни файлової системи відбуваються в реальному часі й не завжди можуть бути чисто скасовані. Community Edition не є ідеальним вибором для обслуговування масової зовнішньої аудиторії.", "agentDetail.configure.build.empty.description": "Опишіть, що вам потрібно, і форма ліворуч заповнюватиметься під час розмови.", @@ -269,7 +270,6 @@ "agentDetail.configure.workingDirectory.breadcrumbLabel": "Шлях файлової системи", "agentDetail.configure.workingDirectory.description": "Файли, які агент читає і записує під час роботи. Усе, що він створює, наприклад qna_report.pdf, з’являється тут.", "agentDetail.configure.workingDirectory.fileSystem": "Файлова система", - "agentDetail.configure.workingDirectory.open": "Відкрити робочий каталог", "agentDetail.configure.workingDirectory.persistentFiles": "Постійні", "agentDetail.configure.workingDirectory.persistentFilesTooltip": "Файли, які агент зберігає та може використовувати всюди, де він запускається, зокрема в розмовах кінцевих користувачів і робочих процесах. Зміни, внесені тут під час цього чату створення, набудуть чинності після натискання «Застосувати».", "agentDetail.configure.workingDirectory.temporaryFiles": "Тимчасові", @@ -348,7 +348,6 @@ "agentDetail.sections.logs": "Журнали", "agentDetail.sections.monitoring": "Моніторинг", "agentDetail.title": "Агент", - "agentDetail.type": "АГЕНТ", "agentDetail.versionHistory.empty": "Поки що немає версій", "agentDetail.versionHistory.exitVersions": "Вийти з версій", "agentDetail.versionHistory.filter": "Фільтрувати версії", @@ -408,8 +407,5 @@ "roster.sort.lastModified": "Остання зміна", "roster.sort.recentlyCreated": "Нещодавно створені", "roster.title": "Агенти", - "roster.usageStatus.draft": "Чернетка", - "skillManagement.detail.uploadFilesFailedStatus_one": "Не вдалося завантажити {{count}} файл.", - "skillManagement.detail.uploadFilesFailedStatus_other": "Не вдалося завантажити {{count}} файлів.", - "skillManagement.title": "Skills" + "roster.usageStatus.draft": "Чернетка" } diff --git a/web/i18n/uk-UA/app-annotation.json b/web/i18n/locales/uk-UA/app-annotation.json similarity index 100% rename from web/i18n/uk-UA/app-annotation.json rename to web/i18n/locales/uk-UA/app-annotation.json diff --git a/web/i18n/uk-UA/app-api.json b/web/i18n/locales/uk-UA/app-api.json similarity index 100% rename from web/i18n/uk-UA/app-api.json rename to web/i18n/locales/uk-UA/app-api.json diff --git a/web/i18n/uk-UA/app-debug.json b/web/i18n/locales/uk-UA/app-debug.json similarity index 100% rename from web/i18n/uk-UA/app-debug.json rename to web/i18n/locales/uk-UA/app-debug.json diff --git a/web/i18n/uk-UA/app-log.json b/web/i18n/locales/uk-UA/app-log.json similarity index 97% rename from web/i18n/uk-UA/app-log.json rename to web/i18n/locales/uk-UA/app-log.json index 3f63045da09..9270151d2ab 100644 --- a/web/i18n/uk-UA/app-log.json +++ b/web/i18n/locales/uk-UA/app-log.json @@ -4,11 +4,9 @@ "agentLogDetail.iteration": "Ітерація", "agentLogDetail.iterations": "Ітерації", "agentLogDetail.toolUsed": "Використаний інструмент", - "archives.action.downloadMonth": "Завантажити архів за {{month}}", "archives.action.downloadReady": "Архів готовий до завантаження.", "archives.action.prepareDownload": "Підготувати завантаження", "archives.action.prepareFailed": "Не вдалося підготувати завантаження архіву.", - "archives.action.prepareMonth": "Підготувати завантаження архіву за {{month}}", "archives.action.prepareStarted": "Завантаження архіву готується.", "archives.action.preparing": "Підготовка", "archives.description": "Завантажуйте щомісячні CSV-експорти архівних логів запусків workflow у цьому робочому просторі.", diff --git a/web/i18n/uk-UA/app-overview.json b/web/i18n/locales/uk-UA/app-overview.json similarity index 100% rename from web/i18n/uk-UA/app-overview.json rename to web/i18n/locales/uk-UA/app-overview.json diff --git a/web/i18n/uk-UA/app.json b/web/i18n/locales/uk-UA/app.json similarity index 100% rename from web/i18n/uk-UA/app.json rename to web/i18n/locales/uk-UA/app.json diff --git a/web/i18n/uk-UA/billing.json b/web/i18n/locales/uk-UA/billing.json similarity index 100% rename from web/i18n/uk-UA/billing.json rename to web/i18n/locales/uk-UA/billing.json diff --git a/web/i18n/uk-UA/common.json b/web/i18n/locales/uk-UA/common.json similarity index 97% rename from web/i18n/uk-UA/common.json rename to web/i18n/locales/uk-UA/common.json index b85bba18130..39d7e01999b 100644 --- a/web/i18n/uk-UA/common.json +++ b/web/i18n/locales/uk-UA/common.json @@ -209,9 +209,7 @@ "mainNav.marketplace": "Маркетплейс", "mainNav.skills": "Skills", "mainNav.webApps.noResults": "Вебзастосунки не знайдено", - "mainNav.webApps.openApp": "Відкрити вебзастосунок {{name}}", "mainNav.webApps.searchPlaceholder": "Пошук вебзастосунків", - "mainNav.workspace.credits": "{{count}} кредитів", "mainNav.workspace.creditsUnit": "кредити", "mainNav.workspace.inviteMembers": "Invite members", "mainNav.workspace.noResults": "Робочі простори не знайдено", @@ -233,7 +231,6 @@ "members.datasetOperatorTip": "Тільки може управляти базою знань", "members.editRole": "Редагувати роль", "members.editorTip": "Може створювати й редагувати застосунки", - "members.email": "Електронна пошта", "members.emailInvalid": "Недійсний формат електронної пошти", "members.emailNotSetup": "Поштовий сервер не налаштований, тому запрошення електронною поштою не можуть бути надіслані. Будь ласка, повідомте користувачів про посилання для запрошення, яке буде видано після запрошення.", "members.emailPlaceholder": "Введіть або вставте адреси електронної пошти…", @@ -250,7 +247,6 @@ "members.inviteLimitExceeded": "Для всіх одержувачів недостатньо вільних місць.", "members.inviteTeamMember": "Додати учасника команди", "members.inviteTeamMemberTip": "Вони зможуть отримати доступ до даних вашої команди безпосередньо після входу.", - "members.invitedAsRole": "Запрошено як {{role}}", "members.lastActive": "ОСТАННЯ АКТИВНІСТЬ", "members.memberActions": "Дії учасника", "members.memberDetails.assign": "Призначити", @@ -258,8 +254,6 @@ "members.memberDetails.assignedRoles": "Призначені ролі", "members.memberDetails.customGroup": "НАЛАШТОВАНІ", "members.memberDetails.generalGroup": "ЗАГАЛЬНІ", - "members.memberDetails.openAria": "Відкрити деталі учасника {{name}}", - "members.memberDetails.roleActionsAria": "Відкрити дії для ролі {{role}}", "members.memberDetails.roleNoPermissionSummary": "Поточна роль не має дозволів.", "members.memberDetails.rolePermissionSummary": "{{role}} може {{permissions}}", "members.memberDetails.title": "Деталі учасника", @@ -370,7 +364,6 @@ "modelProvider.card.usagePriorityTip": "Встановіть, який ресурс використовувати першим при запуску моделей.", "modelProvider.configureTip": "Налаштуйте api-ключ або додайте модель для використання", "modelProvider.confirmDelete": "підтвердити видалення?", - "modelProvider.credits": "Кредити повідомлень", "modelProvider.defaultConfig": "Конфігурація за замовчуванням", "modelProvider.discoverMore": "Відкрийте для себе більше в", "modelProvider.embeddingModel.key": "Модель вбудовування", @@ -427,7 +420,6 @@ "modelProvider.selector.noProviderConfiguredDesc": "Перегляньте Marketplace для встановлення або налаштуйте постачальників у параметрах.", "modelProvider.selector.onlyCompatibleModelsShown": "Показано лише сумісні моделі", "modelProvider.selector.showIncompatibleModels": "Показати несумісні моделі", - "modelProvider.selector.suggestion": "Рекомендація", "modelProvider.selector.suggestionTip": "Рекомендовано для завдань агента", "modelProvider.showModels": "Показати моделі", "modelProvider.speechToTextModel.key": "Модель перетворення мовлення в текст", @@ -458,6 +450,8 @@ "operation.confirmAction": "Будь ласка, підтвердіть свої дії.", "operation.copied": "Скопійовані", "operation.copy": "Копіювати", + "operation.copyErrorDetails": "Копіювати відомості про помилку", + "operation.copyErrorFailed": "Не вдалося скопіювати. Повторити", "operation.copyImage": "Скопіювати зображення", "operation.create": "Створити", "operation.deSelectAll": "Вимкнути все", @@ -475,7 +469,6 @@ "operation.format": "Формат", "operation.imageCopied": "Скопійоване зображення", "operation.imageCopyFailed": "Не вдалося скопіювати зображення. Спробуйте завантажити його.", - "operation.imageDownloaded": "Зображення завантажено", "operation.in": "В", "operation.learnMore": "Дізнатися більше", "operation.log": "Журнал", @@ -512,7 +505,6 @@ "operation.sure": "Я впевнений", "operation.toggleFullscreen": "Перемкнути повноекранний режим", "operation.toggleMute": "Перемкнути звук", - "operation.upload": "Upload", "operation.view": "Вид", "operation.viewDetails": "Перегляд докладних відомостей", "operation.viewMore": "ДИВИТИСЬ БІЛЬШЕ", @@ -542,7 +534,6 @@ "promptEditor.requestURL.item.title": "URL запиту", "promptEditor.variable.modal.add": "Нова змінна", "promptEditor.variable.modal.addTool": "Новий інструмент", - "provider.anthropicHosted.trialQuotaTip": "Ваша квота на антропічні пробні випробування закінчується 11.03.2025 і після цього більше не буде доступна. Будь ласка, скористайтеся ним вчасно.", "provider.encrypted.back": " технології.", "provider.encrypted.front": "Ваш API-ключ буде зашифрований та збережений за допомогою", "provider.validating": "Перевірка ключа...", @@ -565,7 +556,6 @@ "settings.preferences": "Preferences", "settings.provider": "Постачальник моделі", "settings.resourceAccess": "Доступ до ресурсів", - "settings.resourceAccessDescription": "Налаштуйте правила дозволів, які можуть використовувати застосунки та бази знань. Правила дозволів можна призначати учасникам у конкретних конфігураціях доступу до ресурсів.", "settings.rolesAndPermissions": "Ролі та дозволи", "settings.settings": "Settings", "settings.swaggerAPIAsTool": "Swagger API as Tool", @@ -695,7 +685,6 @@ "userProfile.helpCenter": "Довідковий центр", "userProfile.logout": "Вийти", "userProfile.roadmap": "Дорожня карта", - "userProfile.settings": "Налаштування", "userProfile.workspace": "Робочий простір", "voice.language.arTN": "Туніська арабська", "voice.language.deDE": "Німецька", diff --git a/web/i18n/uk-UA/custom.json b/web/i18n/locales/uk-UA/custom.json similarity index 100% rename from web/i18n/uk-UA/custom.json rename to web/i18n/locales/uk-UA/custom.json diff --git a/web/i18n/uk-UA/dataset-creation.json b/web/i18n/locales/uk-UA/dataset-creation.json similarity index 100% rename from web/i18n/uk-UA/dataset-creation.json rename to web/i18n/locales/uk-UA/dataset-creation.json diff --git a/web/i18n/uk-UA/dataset-documents.json b/web/i18n/locales/uk-UA/dataset-documents.json similarity index 100% rename from web/i18n/uk-UA/dataset-documents.json rename to web/i18n/locales/uk-UA/dataset-documents.json diff --git a/web/i18n/uk-UA/dataset-hit-testing.json b/web/i18n/locales/uk-UA/dataset-hit-testing.json similarity index 100% rename from web/i18n/uk-UA/dataset-hit-testing.json rename to web/i18n/locales/uk-UA/dataset-hit-testing.json diff --git a/web/i18n/uk-UA/dataset-pipeline.json b/web/i18n/locales/uk-UA/dataset-pipeline.json similarity index 100% rename from web/i18n/uk-UA/dataset-pipeline.json rename to web/i18n/locales/uk-UA/dataset-pipeline.json diff --git a/web/i18n/uk-UA/dataset-settings.json b/web/i18n/locales/uk-UA/dataset-settings.json similarity index 100% rename from web/i18n/uk-UA/dataset-settings.json rename to web/i18n/locales/uk-UA/dataset-settings.json diff --git a/web/i18n/uk-UA/dataset.json b/web/i18n/locales/uk-UA/dataset.json similarity index 97% rename from web/i18n/uk-UA/dataset.json rename to web/i18n/locales/uk-UA/dataset.json index a7f322b99b2..04f0c8e2724 100644 --- a/web/i18n/uk-UA/dataset.json +++ b/web/i18n/locales/uk-UA/dataset.json @@ -163,7 +163,6 @@ "newKnowledge.configureProvider": "Налаштувати {{provider}}", "newKnowledge.connectNotion": "Підключити Notion", "newKnowledge.connectProvider": "Підключіть {{provider}}", - "newKnowledge.connectProviderGeneric": "Підключити провайдера", "newKnowledge.connectSource": "Підключити джерело", "newKnowledge.connectSourceDescription": "Вміст залишається синхронізованим із Notion, вебсайтами та іншими сервісами.", "newKnowledge.connectingProvider": "Підключення…", @@ -179,18 +178,14 @@ "newKnowledge.crawlFailedDescription": "Робот не зміг завершити попередній перегляд. Перевірте URL-адресу, змініть параметри сканування або повторіть спробу.", "newKnowledge.crawlFailedProvider": "{{provider}} повідомив про помилку. Перевірте підключення до постачальника й повторіть спробу.", "newKnowledge.crawlFailedTimeout": "Час очікування сканування минув. Спробуйте точнішу URL-адресу або зменште максимальну кількість сторінок.", - "newKnowledge.crawlNotStarted": "Не розпочато", "newKnowledge.crawlOptions": "Параметри сканування", "newKnowledge.crawlPreview": "Попередній перегляд обходу", "newKnowledge.crawlProgress": "Хід сканування {{host}}", - "newKnowledge.crawlSetupUnavailableDescription": "Конфігурацію сканування веб-сайту вимкнено, доки робочий процес сканування не стане доступним.", - "newKnowledge.crawlSetupUnavailableTitle": "Firecrawl готовий", "newKnowledge.crawlStartFailed": "Не вдалося запустити попередній перегляд сканування. Повторіть спробу.", "newKnowledge.crawlStopped": "Сканування зупинено", "newKnowledge.crawling": "Сканування…", "newKnowledge.crawlingPages": "Сканування {{host}}… знайдено сторінок: {{count}}", "newKnowledge.createFailed": "Ми не змогли створити цю базу знань. Перевірте свій доступ і повторіть спробу.", - "newKnowledge.createIllustrationTitle": "Підключіть вміст один раз — кожен застосунок відповідатиме на основі найсвіжіших знань.", "newKnowledge.createTitle": "Створюйте знання", "newKnowledge.createdAt": "Створено", "newKnowledge.creators": "Автори", @@ -220,7 +215,6 @@ "newKnowledge.documentColumn": "документ", "newKnowledge.documentContentIncomplete": "Завантажується решта вмісту документа…", "newKnowledge.documentContents": "Зміст", - "newKnowledge.documentFacts": "Відомості про документ", "newKnowledge.documentFilterLabel": "Фільтр документів за статусом", "newKnowledge.documentLoadErrorDescription": "Не вдалося завантажити цей документ. Спробуйте ще раз.", "newKnowledge.documentLoadErrorTitle": "Не вдалося завантажити документ", @@ -268,7 +262,6 @@ "newKnowledge.documentsSelected": "Вибрані документи {{count}}", "newKnowledge.documentsUnavailable": "— документів", "newKnowledge.downloadDocuments": "Завантажити", - "newKnowledge.editSource": "Редагувати", "newKnowledge.emptyChunk": "Порожній фрагмент", "newKnowledge.emptyDescription": "Додайте документи, і агенти зможуть читати їх безпосередньо — без налаштування індексації.", "newKnowledge.emptyTitle": "Розумніший спосіб створювати бази знань", @@ -344,7 +337,6 @@ "newKnowledge.processingTaskState.succeeded": "Виконано", "newKnowledge.processingTaskState.superseded": "Замінено", "newKnowledge.providerConnected": "{{provider}} підключено", - "newKnowledge.providerLabel": "Оберіть провайдера", "newKnowledge.providerLoadFailed": "Не вдалося завантажити постачальників", "newKnowledge.providerNotConfigured": "{{provider}} не налаштовано", "newKnowledge.providerNotConfiguredDescription": "Додайте ключ API або URL-адресу кінцевої точки, щоб сканувати веб-сайти за допомогою {{provider}}.", @@ -369,12 +361,10 @@ "newKnowledge.revisionState.superseded": "Попередня", "newKnowledge.rootUrl": "Коренева URL-адреса", "newKnowledge.rootUrlPlaceholder": "https://docs.example.com", - "newKnowledge.routeUnavailable": "Цей розділ стане доступним на наступному кроці налаштування.", "newKnowledge.searchDocuments": "Пошук документів", "newKnowledge.searchSources": "Пошук джерел", "newKnowledge.selectAll": "Вибрати все", "newKnowledge.selectAllDocuments": "Виберіть усі документи", - "newKnowledge.selectAllSources": "Вибрати всі джерела", "newKnowledge.selectChunk": "Виберіть фрагмент, щоб переглянути його вміст.", "newKnowledge.selectFilesAndFolders": "Виберіть файли й папки для синхронізації", "newKnowledge.skippedFailed": "Пропущено (помилка)", @@ -384,7 +374,6 @@ "newKnowledge.sourceFilterLabel": "Фільтрувати джерела за статусом", "newKnowledge.sourceName": "Назва джерела", "newKnowledge.sourceNamePlaceholder": "наприклад, сканування документації продукту", - "newKnowledge.sourceNameRequired": "Введіть назву джерела.", "newKnowledge.sourceSetupBackendDependency": "Налаштування джерела показано повністю; для редагування й перегляду потрібен серверний процес чернетки джерела.", "newKnowledge.sourceStatus.active": "Активний", "newKnowledge.sourceStatus.disabled": "Вимкнено", @@ -431,7 +420,6 @@ "newKnowledge.unavailableDescription": "KnowledgeFS зараз недоступний для цього робочого простору.", "newKnowledge.unavailableTitle": "Нова база знань недоступна", "newKnowledge.updated": "Оновлено {{date}}", - "newKnowledge.updatedAt": "Останнє оновлення", "newKnowledge.updatedColumn": "Оновлено", "newKnowledge.uploadCharactersUnavailable": "Символи: —", "newKnowledge.uploadDocuments": "Завантажити документи", @@ -460,11 +448,9 @@ "retrievalSettings": "Налаштування пошуку", "retry": "Повторити спробу", "selectExternalKnowledgeAPI.placeholder": "Виберіть API зовнішніх знань", - "serviceApi.card.addApiKey": "Додати ключ API", "serviceApi.card.apiKey": "Ключ API", "serviceApi.card.apiReference": "Посилання на API", "serviceApi.card.endpoint": "Кінцева точка API сервісу", - "serviceApi.card.manageApiKey": "Керувати ключем API", "serviceApi.card.title": "API бекенд-сервіс", "serviceApi.disabled": "Вимкнено", "serviceApi.enabled": "У службі", diff --git a/web/i18n/uk-UA/deployments.json b/web/i18n/locales/uk-UA/deployments.json similarity index 97% rename from web/i18n/uk-UA/deployments.json rename to web/i18n/locales/uk-UA/deployments.json index e26ac378db5..1f4dcb1a00c 100644 --- a/web/i18n/uk-UA/deployments.json +++ b/web/i18n/locales/uk-UA/deployments.json @@ -27,7 +27,6 @@ "list.emptyTitle": "Поки що немає розгортань", "overview.chip.latest": "останній", "status.RUNTIME_INSTANCE_STATUS_DEPLOYING": "Розгортається", - "status.RUNTIME_INSTANCE_STATUS_FAILED": "Розгортання не вдалося", "status.RUNTIME_INSTANCE_STATUS_INVALID": "Недійсний", "status.RUNTIME_INSTANCE_STATUS_READY": "Запущено", "status.RUNTIME_INSTANCE_STATUS_UNDEPLOYED": "Не розгорнуто", @@ -87,6 +86,5 @@ "studio.versionValue": "Значення версії", "studio.versionsBehind_one": "Відставання на {{count}} версію", "studio.versionsBehind_other": "Відставання на {{count}} версій", - "versions.checkingReleaseContent": "Перевірка вмісту релізу...", "versions.deployTo": "Розгорнути в {{name}}" } diff --git a/web/i18n/uk-UA/device-flow.json b/web/i18n/locales/uk-UA/device-flow.json similarity index 100% rename from web/i18n/uk-UA/device-flow.json rename to web/i18n/locales/uk-UA/device-flow.json diff --git a/web/i18n/uk-UA/education.json b/web/i18n/locales/uk-UA/education.json similarity index 100% rename from web/i18n/uk-UA/education.json rename to web/i18n/locales/uk-UA/education.json diff --git a/web/i18n/uk-UA/explore.json b/web/i18n/locales/uk-UA/explore.json similarity index 100% rename from web/i18n/uk-UA/explore.json rename to web/i18n/locales/uk-UA/explore.json diff --git a/web/i18n/uk-UA/layout.json b/web/i18n/locales/uk-UA/layout.json similarity index 100% rename from web/i18n/uk-UA/layout.json rename to web/i18n/locales/uk-UA/layout.json diff --git a/web/i18n/uk-UA/login.json b/web/i18n/locales/uk-UA/login.json similarity index 100% rename from web/i18n/uk-UA/login.json rename to web/i18n/locales/uk-UA/login.json diff --git a/web/i18n/uk-UA/oauth.json b/web/i18n/locales/uk-UA/oauth.json similarity index 100% rename from web/i18n/uk-UA/oauth.json rename to web/i18n/locales/uk-UA/oauth.json diff --git a/web/i18n/uk-UA/permission-keys.json b/web/i18n/locales/uk-UA/permission-keys.json similarity index 100% rename from web/i18n/uk-UA/permission-keys.json rename to web/i18n/locales/uk-UA/permission-keys.json diff --git a/web/i18n/uk-UA/permission.json b/web/i18n/locales/uk-UA/permission.json similarity index 100% rename from web/i18n/uk-UA/permission.json rename to web/i18n/locales/uk-UA/permission.json diff --git a/web/i18n/uk-UA/pipeline.json b/web/i18n/locales/uk-UA/pipeline.json similarity index 100% rename from web/i18n/uk-UA/pipeline.json rename to web/i18n/locales/uk-UA/pipeline.json diff --git a/web/i18n/uk-UA/plugin-tags.json b/web/i18n/locales/uk-UA/plugin-tags.json similarity index 100% rename from web/i18n/uk-UA/plugin-tags.json rename to web/i18n/locales/uk-UA/plugin-tags.json diff --git a/web/i18n/uk-UA/plugin-trigger.json b/web/i18n/locales/uk-UA/plugin-trigger.json similarity index 100% rename from web/i18n/uk-UA/plugin-trigger.json rename to web/i18n/locales/uk-UA/plugin-trigger.json diff --git a/web/i18n/uk-UA/plugin.json b/web/i18n/locales/uk-UA/plugin.json similarity index 99% rename from web/i18n/uk-UA/plugin.json rename to web/i18n/locales/uk-UA/plugin.json index 6acee2e05bc..6f8e060be9e 100644 --- a/web/i18n/uk-UA/plugin.json +++ b/web/i18n/locales/uk-UA/plugin.json @@ -277,7 +277,6 @@ "readmeInfo.noReadmeAvailable": "README недоступний", "readmeInfo.title": "Прочитай мене", "requestAPlugin": "Запросити плагін", - "requestSubmit": "Запросити або опублікувати", "search": "Шукати", "searchCategories": "Категорії пошуку", "searchInMarketplace": "Пошук у Marketplace", diff --git a/web/i18n/uk-UA/register.json b/web/i18n/locales/uk-UA/register.json similarity index 100% rename from web/i18n/uk-UA/register.json rename to web/i18n/locales/uk-UA/register.json diff --git a/web/i18n/uk-UA/run-log.json b/web/i18n/locales/uk-UA/run-log.json similarity index 100% rename from web/i18n/uk-UA/run-log.json rename to web/i18n/locales/uk-UA/run-log.json diff --git a/web/i18n/uk-UA/share.json b/web/i18n/locales/uk-UA/share.json similarity index 100% rename from web/i18n/uk-UA/share.json rename to web/i18n/locales/uk-UA/share.json diff --git a/web/i18n/uk-UA/skill.json b/web/i18n/locales/uk-UA/skill.json similarity index 82% rename from web/i18n/uk-UA/skill.json rename to web/i18n/locales/uk-UA/skill.json index 83b83933b0f..26468cfe4f6 100644 --- a/web/i18n/uk-UA/skill.json +++ b/web/i18n/locales/uk-UA/skill.json @@ -1,5 +1,4 @@ { - "skillManagement.clearTags": "Очистити теги", "skillManagement.create": "Створити", "skillManagement.createFailed": "Не вдалося створити навик.", "skillManagement.createSuccess": "Чернетку створено.", @@ -13,12 +12,9 @@ "skillManagement.deleteFailed": "Не вдалося видалити навик.", "skillManagement.deleteSuccess": "Skill видалено.", "skillManagement.detail.addMetadata": "Додайте метадані", - "skillManagement.detail.addMetadataDescription": "Додайте поле frontmatter до цього файлу Markdown.", "skillManagement.detail.addTag": "Додати тег", - "skillManagement.detail.addTagDescription": "Створіть або прив’яжіть тег до цього Skill. Нові теги зберігаються з метаданими Skill.", "skillManagement.detail.addTagSuccess": "Тег додано.", "skillManagement.detail.back": "Назад до Skills", - "skillManagement.detail.builder.applyFailed": "Не вдалося застосувати чернетки оновлень.", "skillManagement.detail.builder.attach": "Прикріпити файл", "skillManagement.detail.builder.attachFailed": "Не вдалося прикріпити файл.", "skillManagement.detail.builder.attachLimit": "Ви можете прикріпити до 10 файлів.", @@ -26,22 +22,12 @@ "skillManagement.detail.builder.attachUnsupported": "Можна прикріплювати лише зображення, текст і файли документів.", "skillManagement.detail.builder.attachmentOnlyMessage": "Використовуйте прикріплений файл, щоб допомогти вдосконалити цей навик.", "skillManagement.detail.builder.close": "Закрийте Skill Builder", - "skillManagement.detail.builder.compatibleModelsOnly": "Показано лише сумісні моделі", "skillManagement.detail.builder.copyResponse": "Копіювати відповідь", "skillManagement.detail.builder.copySuccess": "Відповідь скопійовано в буфер обміну.", "skillManagement.detail.builder.editIntro": "Я перевірив цей навик. Що б ви хотіли налаштувати?", "skillManagement.detail.builder.exampleIssueTriage": "Сортування питань клієнта", "skillManagement.detail.builder.exampleOnboarding": "Посібник із адаптації нового найму", "skillManagement.detail.builder.exampleSalesFollowUp": "Стратегія подальших продажів", - "skillManagement.detail.builder.followUpDisplayName": "Використовуйте схвалення відшкодування як відображуване ім’я", - "skillManagement.detail.builder.followUpNameIcon": "Застосуйте запропоноване ім'я та значок", - "skillManagement.detail.builder.fromMarketplace": "З Marketplace", - "skillManagement.detail.builder.model": "GPT-4o", - "skillManagement.detail.builder.modelCredits.all": "Всі кредити", - "skillManagement.detail.builder.modelCredits.configure": "Потрібне налаштування", - "skillManagement.detail.builder.modelCredits.exhausted": "Квоту вичерпано", - "skillManagement.detail.builder.modelProviderSettings": "Налаштування постачальника моделей", - "skillManagement.detail.builder.modelSearch": "Пошук моделей...", "skillManagement.detail.builder.modifyPlaceholder": "Попросіть ШІ змінити цей навик...", "skillManagement.detail.builder.open": "Відкрийте Skill Builder", "skillManagement.detail.builder.placeholder": "Опишіть сценарій...", @@ -61,8 +47,6 @@ "skillManagement.detail.builder.thinkingUnavailable": "Модель не повернула жодного змісту мислення.", "skillManagement.detail.builder.title": "Skill Builder", "skillManagement.detail.builder.tryExample": "Спробуйте приклад", - "skillManagement.detail.builder.voice": "Голосове введення", - "skillManagement.detail.builder.voiceUnavailable": "Голосове введення поки що недоступне.", "skillManagement.detail.cancelAddMetadata": "Скасувати додавання метаданих", "skillManagement.detail.closeFileTab": "Закрити {{name}}", "skillManagement.detail.closeVersions": "Близькі версії", @@ -70,14 +54,10 @@ "skillManagement.detail.copyContentSuccess": "Вміст скопійовано в буфер обміну.", "skillManagement.detail.copyFile": "Копіювати", "skillManagement.detail.copyFileSuccess": "Файл готовий до вставки.", - "skillManagement.detail.copyVersionId": "Копіювати ідентифікатор", - "skillManagement.detail.copyVersionIdSuccess": "ID скопійовано в буфер обміну.", "skillManagement.detail.createFile": "Новий файл", - "skillManagement.detail.createFileDescription": "Введіть шлях до файлу відносно кореня навички.", "skillManagement.detail.createFileMenu": "Новий файл...", "skillManagement.detail.createFileSuccess": "Файл створено.", "skillManagement.detail.createFolder": "Нова папка", - "skillManagement.detail.createFolderDescription": "Введіть шлях до папки відносно кореня навички.", "skillManagement.detail.createFolderMenu": "Нова папка...", "skillManagement.detail.createFolderSuccess": "Папка створена.", "skillManagement.detail.createdBy": "Автор: {{name}}", @@ -100,10 +80,8 @@ "skillManagement.detail.fileMeta": "{{type}} · {{size}} байт", "skillManagement.detail.fileMissing": "Файл не знайдено.", "skillManagement.detail.fileOperationFailed": "Помилка операції з файлом.", - "skillManagement.detail.files": "Файли", "skillManagement.detail.latest": "Останні новини", "skillManagement.detail.loadFailed": "Не вдалося завантажити навик.", - "skillManagement.detail.loadingTags": "Завантаження тегів...", "skillManagement.detail.markdownLiveMode": "Попередній перегляд", "skillManagement.detail.markdownSourceMode": "Редактор джерела", "skillManagement.detail.metadataKey": "назва", @@ -113,10 +91,7 @@ "skillManagement.detail.nameThisVersion": "Назвіть цю версію", "skillManagement.detail.noFileSelected": "Виберіть файл для попереднього перегляду.", "skillManagement.detail.noFiles": "Немає файлів.", - "skillManagement.detail.noSearchResults": "Немає відповідних файлів.", - "skillManagement.detail.noSelectableTags": "Немає доступних тегів.", "skillManagement.detail.noVersions": "Ще немає опублікованих версій.", - "skillManagement.detail.pasteFile": "Вставити", "skillManagement.detail.pasteFileSuccess": "Файл вставлено.", "skillManagement.detail.previewUnsupported": "Попередній перегляд цього файлу не підтримується.", "skillManagement.detail.publish": "Опублікувати", @@ -128,9 +103,7 @@ "skillManagement.detail.publishUpdate": "Опублікувати оновлення", "skillManagement.detail.published": "Опубліковано", "skillManagement.detail.publishedAt": "Опубліковано {{time}}", - "skillManagement.detail.publishedVersion": "Опубліковано #{{number}}", "skillManagement.detail.publishing": "Публікація...", - "skillManagement.detail.readonly": "Тільки для читання", "skillManagement.detail.referenceFiles.confirm": "Введіть Підтвердити", "skillManagement.detail.referenceFiles.empty": "Немає доступних файлів.", "skillManagement.detail.referenceFiles.livePlaceholder": "Напишіть інструкції для агента. Введіть / для посилань на файли.", @@ -138,14 +111,10 @@ "skillManagement.detail.referenceFiles.title": "Довідкові файли", "skillManagement.detail.referencedBy_one": "Використовується {{count}} агентом", "skillManagement.detail.referencedBy_other": "Використовується {{count}} агентами", - "skillManagement.detail.removeMetadata": "Видалити метадані {{name}}", "skillManagement.detail.removeTag": "Видалити {{tag}}", "skillManagement.detail.removeTagSuccess": "Тег видалено.", - "skillManagement.detail.renameFile": "Перейменувати файл", - "skillManagement.detail.renameFileDescription": "Введіть новий шлях відносно кореня навички.", "skillManagement.detail.renameFileSuccess": "Файл перейменовано.", "skillManagement.detail.renameSkillSuccess": "Skill перейменовано.", - "skillManagement.detail.renameVersion": "Перейменувати", "skillManagement.detail.renameVersionFailed": "Не вдалося перейменувати версію.", "skillManagement.detail.renameVersionPrompt": "Назва версії", "skillManagement.detail.renameVersionSuccess": "Версію перейменовано.", @@ -155,17 +124,14 @@ "skillManagement.detail.restoreVersionConfirmTitle": "Відновити цю версію?", "skillManagement.detail.restoreVersionFailed": "Не вдалося відновити версію.", "skillManagement.detail.restoreVersionSuccess": "Версію відновлено до чернетки.", - "skillManagement.detail.save": "Зберегти", "skillManagement.detail.saveConflictConfirmDescription": "Цей навик було оновлено в іншому місці. Завантажте останню чернетку, щоб продовжити редагування. Ваші поточні зміни буде скасовано.", "skillManagement.detail.saveConflictConfirmTitle": "Завантажити останню чернетку?", "skillManagement.detail.saveConflictReload": "Завантажити останню чернетку", "skillManagement.detail.saveConflictStatus": "Виявлено конфлікт. Завантажте останню чернетку, щоб продовжити редагування.", "skillManagement.detail.saveFailed": "Не вдалося зберегти файл.", - "skillManagement.detail.saveSuccess": "Файл збережено.", "skillManagement.detail.saved": "Збережено", "skillManagement.detail.savedAt": "Збережено {{time}}", "skillManagement.detail.saving": "Збереження...", - "skillManagement.detail.searchFiles": "Пошук файлів", "skillManagement.detail.showMoreReferences": "Показати {{count}} більше", "skillManagement.detail.skillDescriptionPlaceholder": "Опишіть, що робить цей навик і коли агенти повинні його використовувати.", "skillManagement.detail.skillNamePlaceholder": "Використовуйте малі літери, цифри та дефіси.", @@ -177,16 +143,12 @@ "skillManagement.detail.uploadConflict": "Файл із такою назвою вже існує.", "skillManagement.detail.uploadFailureSummary": "Збій файлів {{failed}} із {{total}}", "skillManagement.detail.uploadFailureTitle": "Деякі завантаження не вдалося", - "skillManagement.detail.uploadFile": "Завантажити файл", - "skillManagement.detail.uploadFileFailed": "Помилка завантаження файлу.", "skillManagement.detail.uploadFileSuccess": "Файл завантажено.", "skillManagement.detail.uploadFilesButton": "Завантажте файли {{count}}", "skillManagement.detail.uploadFilesFailedStatus_one": "Не вдалося завантажити файл {{count}}.", "skillManagement.detail.uploadFilesFailedStatus_other": "Не вдалося завантажити файл {{count}}.", "skillManagement.detail.uploadFilesMenu": "Завантажити файли...", "skillManagement.detail.uploadFilesProgress": "Завантаження {{completed}}/{{total}}", - "skillManagement.detail.uploadFilesResult": "{{uploaded}} завантажено · {{failed}} не вдалося", - "skillManagement.detail.uploadFilesStatus": "Статус завантаження", "skillManagement.detail.uploadInvalidExtension": "Цей тип файлу не підтримується.", "skillManagement.detail.uploadInvalidName": "Недійсне ім'я.", "skillManagement.detail.uploadInvalidPath": "Недійсний шлях до файлу.", @@ -206,7 +168,6 @@ "skillManagement.detail.uploadWillReplace": "Замінить існуючий файл", "skillManagement.detail.uploadWillUseName": "Буде завантажено як {{name}}", "skillManagement.detail.versionHistory": "Відкрити історію версій", - "skillManagement.detail.versionMeta": "{{hash}} · {{time}}", "skillManagement.detail.versionPublishNote": "Примітки до випуску", "skillManagement.detail.versionPublishNotePlaceholder": "Опишіть цю зміну", "skillManagement.detail.versionPublishedMeta": "{{time}} · {{name}}", @@ -227,8 +188,6 @@ "skillManagement.errors.missingSkillMd": "Цей пакет не має SKILL.md. Додайте файл і імпортуйте його знову.", "skillManagement.errors.nameConflict": "Уміння під назвою \"{{name}}\" уже існує. Перейменуйте його в SKILL.md і імпортуйте знову.", "skillManagement.errors.workspaceLimit": "Ваша робоча область досягла ліміту в 500 навичок. Видаліть навик, щоб додати новий.", - "skillManagement.exportDialog.description": "Буде експортовано поточну збережену чернетку навички. Якщо в редакторі є незбережені зміни, збережіть їх перед експортом.", - "skillManagement.exportDialog.title": "Експорт поточної збереженої чернетки", "skillManagement.import": "Імпортувати", "skillManagement.importFailed": "Не вдалося імпортувати навички.", "skillManagement.importSuccess": "Skill імпортовано.", @@ -236,12 +195,10 @@ "skillManagement.loadingError": "Не вдалося завантажити навички", "skillManagement.moreActions": "Більше дій для {{name}}", "skillManagement.noDescription": "Опису ще немає", - "skillManagement.noTags": "Без тегів", "skillManagement.publishedAt": "Опубліковано {{time}}", "skillManagement.referenceCount_one": "{{count}} агент", "skillManagement.referenceCount_other": "{{count}} агентів", "skillManagement.searchLabel": "Пошук навичок", "skillManagement.searchPlaceholder": "Пошук за назвою або описом", - "skillManagement.tags": "Теги", "skillManagement.title": "Навички" } diff --git a/web/i18n/uk-UA/snippet.json b/web/i18n/locales/uk-UA/snippet.json similarity index 100% rename from web/i18n/uk-UA/snippet.json rename to web/i18n/locales/uk-UA/snippet.json diff --git a/web/i18n/uk-UA/time.json b/web/i18n/locales/uk-UA/time.json similarity index 100% rename from web/i18n/uk-UA/time.json rename to web/i18n/locales/uk-UA/time.json diff --git a/web/i18n/uk-UA/tools.json b/web/i18n/locales/uk-UA/tools.json similarity index 100% rename from web/i18n/uk-UA/tools.json rename to web/i18n/locales/uk-UA/tools.json diff --git a/web/i18n/uk-UA/workflow.json b/web/i18n/locales/uk-UA/workflow.json similarity index 99% rename from web/i18n/uk-UA/workflow.json rename to web/i18n/locales/uk-UA/workflow.json index b63708d92e4..fc394a1bd17 100644 --- a/web/i18n/uk-UA/workflow.json +++ b/web/i18n/locales/uk-UA/workflow.json @@ -460,8 +460,6 @@ "nodes.agent.pluginNotInstalledDesc": "Цей інтеграція встановлюється з GitHub. Будь ласка, перейдіть до Інтеграції для перевстановлення", "nodes.agent.roster.change": "Змінити", "nodes.agent.roster.editInConsole": "Редагувати в Agents", - "nodes.agent.roster.editInConsoleDisabled": "Для редагування агентів потрібна роль «Редактор» або вища.", - "nodes.agent.roster.editInConsoleDisabledRbac": "Редагувати агентів можуть лише учасники з роллю «Редактор» або вищою чи дозволом «Керування Agents».", "nodes.agent.roster.inlineSetup.description": "Одноразове налаштування, що існує лише в цьому вузлі", "nodes.agent.roster.inlineSetup.name": "Вбудоване налаштування", "nodes.agent.roster.inlineSetup.type": "Лише в цьому вузлі", diff --git a/web/i18n/vi-VN/agent-v-2.json b/web/i18n/locales/vi-VN/agent-v-2.json similarity index 98% rename from web/i18n/vi-VN/agent-v-2.json rename to web/i18n/locales/vi-VN/agent-v-2.json index 40398a85ff5..88843ae8ac9 100644 --- a/web/i18n/vi-VN/agent-v-2.json +++ b/web/i18n/locales/vi-VN/agent-v-2.json @@ -69,6 +69,7 @@ "agentDetail.configure.answer.activity.runningCommands": "Đang chạy lệnh", "agentDetail.configure.answer.duration.minute": "{{count}} phút", "agentDetail.configure.answer.duration.second": "{{count}} giây", + "agentDetail.configure.answer.responseEnded": "Phản hồi đã kết thúc", "agentDetail.configure.answer.thinking": "Đang suy nghĩ", "agentDetail.configure.build.empty.communityEditionTip": "Community Edition, mặc dù cung cấp phiên bản cho các cấu hình được trích xuất, không hỗ trợ phiên bản cho chính hệ thống tệp. Hãy cẩn thận với các thao tác trong chế độ Build, vì các thay đổi đối với hệ thống tệp diễn ra theo thời gian thực và không phải lúc nào cũng có thể hoàn tác gọn gàng. Community Edition không phải là lựa chọn lý tưởng để phục vụ lượng lớn khán giả bên ngoài.", "agentDetail.configure.build.empty.description": "Mô tả điều bạn muốn và biểu mẫu bên trái sẽ được điền dần khi trò chuyện.", @@ -269,7 +270,6 @@ "agentDetail.configure.workingDirectory.breadcrumbLabel": "Đường dẫn hệ thống tệp", "agentDetail.configure.workingDirectory.description": "Các tệp mà agent đọc và ghi khi chạy. Mọi thứ nó tạo ra, như qna_report.pdf, sẽ xuất hiện ở đây.", "agentDetail.configure.workingDirectory.fileSystem": "Hệ thống tệp", - "agentDetail.configure.workingDirectory.open": "Mở thư mục làm việc", "agentDetail.configure.workingDirectory.persistentFiles": "Lâu dài", "agentDetail.configure.workingDirectory.persistentFilesTooltip": "Các tệp mà agent lưu giữ và có thể sử dụng ở mọi nơi agent chạy, bao gồm cuộc trò chuyện của người dùng cuối và quy trình làm việc. Mọi thay đổi được thực hiện tại đây trong cuộc trò chuyện xây dựng này sẽ có hiệu lực sau khi bạn nhấp vào “Áp dụng”.", "agentDetail.configure.workingDirectory.temporaryFiles": "Tạm thời", @@ -348,7 +348,6 @@ "agentDetail.sections.logs": "Nhật ký", "agentDetail.sections.monitoring": "Giám sát", "agentDetail.title": "Tác nhân", - "agentDetail.type": "TÁC NHÂN", "agentDetail.versionHistory.empty": "Chưa có phiên bản nào", "agentDetail.versionHistory.exitVersions": "Thoát phiên bản", "agentDetail.versionHistory.filter": "Lọc phiên bản", @@ -408,8 +407,5 @@ "roster.sort.lastModified": "Sửa đổi gần nhất", "roster.sort.recentlyCreated": "Mới tạo gần đây", "roster.title": "Tác nhân", - "roster.usageStatus.draft": "Bản nháp", - "skillManagement.detail.uploadFilesFailedStatus_one": "Tải lên {{count}} tệp thất bại.", - "skillManagement.detail.uploadFilesFailedStatus_other": "Tải lên {{count}} tệp thất bại.", - "skillManagement.title": "Skills" + "roster.usageStatus.draft": "Bản nháp" } diff --git a/web/i18n/vi-VN/app-annotation.json b/web/i18n/locales/vi-VN/app-annotation.json similarity index 100% rename from web/i18n/vi-VN/app-annotation.json rename to web/i18n/locales/vi-VN/app-annotation.json diff --git a/web/i18n/vi-VN/app-api.json b/web/i18n/locales/vi-VN/app-api.json similarity index 100% rename from web/i18n/vi-VN/app-api.json rename to web/i18n/locales/vi-VN/app-api.json diff --git a/web/i18n/vi-VN/app-debug.json b/web/i18n/locales/vi-VN/app-debug.json similarity index 100% rename from web/i18n/vi-VN/app-debug.json rename to web/i18n/locales/vi-VN/app-debug.json diff --git a/web/i18n/vi-VN/app-log.json b/web/i18n/locales/vi-VN/app-log.json similarity index 97% rename from web/i18n/vi-VN/app-log.json rename to web/i18n/locales/vi-VN/app-log.json index 2848609ab07..a28ba8211cd 100644 --- a/web/i18n/vi-VN/app-log.json +++ b/web/i18n/locales/vi-VN/app-log.json @@ -4,11 +4,9 @@ "agentLogDetail.iteration": "Lần lặp", "agentLogDetail.iterations": "Số lần lặp", "agentLogDetail.toolUsed": "Công cụ đã sử dụng", - "archives.action.downloadMonth": "Tải xuống bản lưu trữ {{month}}", "archives.action.downloadReady": "Bản lưu trữ đã sẵn sàng để tải xuống.", "archives.action.prepareDownload": "Chuẩn bị tải xuống", "archives.action.prepareFailed": "Không thể chuẩn bị bản lưu trữ để tải xuống.", - "archives.action.prepareMonth": "Chuẩn bị tải xuống bản lưu trữ {{month}}", "archives.action.prepareStarted": "Đang chuẩn bị bản lưu trữ để tải xuống.", "archives.action.preparing": "Đang chuẩn bị", "archives.description": "Tải xuống các bản xuất CSV hằng tháng của log workflow run đã lưu trữ trong workspace này.", diff --git a/web/i18n/vi-VN/app-overview.json b/web/i18n/locales/vi-VN/app-overview.json similarity index 100% rename from web/i18n/vi-VN/app-overview.json rename to web/i18n/locales/vi-VN/app-overview.json diff --git a/web/i18n/vi-VN/app.json b/web/i18n/locales/vi-VN/app.json similarity index 100% rename from web/i18n/vi-VN/app.json rename to web/i18n/locales/vi-VN/app.json diff --git a/web/i18n/vi-VN/billing.json b/web/i18n/locales/vi-VN/billing.json similarity index 100% rename from web/i18n/vi-VN/billing.json rename to web/i18n/locales/vi-VN/billing.json diff --git a/web/i18n/vi-VN/common.json b/web/i18n/locales/vi-VN/common.json similarity index 98% rename from web/i18n/vi-VN/common.json rename to web/i18n/locales/vi-VN/common.json index 3bf169e1089..44401890f64 100644 --- a/web/i18n/vi-VN/common.json +++ b/web/i18n/locales/vi-VN/common.json @@ -209,9 +209,7 @@ "mainNav.marketplace": "Marketplace", "mainNav.skills": "Skills", "mainNav.webApps.noResults": "Không tìm thấy ứng dụng web", - "mainNav.webApps.openApp": "Mở ứng dụng web {{name}}", "mainNav.webApps.searchPlaceholder": "Tìm kiếm ứng dụng web", - "mainNav.workspace.credits": "{{count}} tín dụng", "mainNav.workspace.creditsUnit": "tín dụng", "mainNav.workspace.inviteMembers": "Invite members", "mainNav.workspace.noResults": "Không tìm thấy không gian làm việc", @@ -233,7 +231,6 @@ "members.datasetOperatorTip": "Chỉ có thể quản lý cơ sở kiến thức", "members.editRole": "Chỉnh sửa vai trò", "members.editorTip": "Có thể tạo và chỉnh sửa ứng dụng", - "members.email": "Email", "members.emailInvalid": "Định dạng Email không hợp lệ", "members.emailNotSetup": "Máy chủ email chưa được thiết lập, vì vậy không thể gửi email mời. Vui lòng thông báo cho người dùng về liên kết mời sẽ được phát hành sau khi mời.", "members.emailPlaceholder": "Nhập hoặc dán địa chỉ email…", @@ -250,7 +247,6 @@ "members.inviteLimitExceeded": "Không đủ chỗ cho tất cả người nhận.", "members.inviteTeamMember": "Mời thành viên nhóm", "members.inviteTeamMemberTip": "Sau khi đăng nhập, họ có thể truy cập trực tiếp vào dữ liệu nhóm của bạn.", - "members.invitedAsRole": "Được mời với vai trò {{role}}", "members.lastActive": "HOẠT ĐỘNG GẦN ĐÂY", "members.memberActions": "Hành động của thành viên", "members.memberDetails.assign": "Gán", @@ -258,8 +254,6 @@ "members.memberDetails.assignedRoles": "Vai trò đã gán", "members.memberDetails.customGroup": "TÙY CHỈNH", "members.memberDetails.generalGroup": "CHUNG", - "members.memberDetails.openAria": "Mở chi tiết thành viên cho {{name}}", - "members.memberDetails.roleActionsAria": "Mở hành động cho vai trò {{role}}", "members.memberDetails.roleNoPermissionSummary": "Vai trò hiện tại không có quyền nào.", "members.memberDetails.rolePermissionSummary": "{{role}} có thể {{permissions}}", "members.memberDetails.title": "Chi tiết thành viên", @@ -370,7 +364,6 @@ "modelProvider.card.usagePriorityTip": "Đặt tài nguyên sử dụng trước khi chạy mô hình.", "modelProvider.configureTip": "Thiết lập api-key hoặc thêm mô hình để sử dụng", "modelProvider.confirmDelete": "Xác nhận xóa?", - "modelProvider.credits": "Tín dụng Tin nhắn", "modelProvider.defaultConfig": "Cấu hình mặc định", "modelProvider.discoverMore": "Khám phá thêm trong", "modelProvider.embeddingModel.key": "Mô hình nhúng", @@ -427,7 +420,6 @@ "modelProvider.selector.noProviderConfiguredDesc": "Duyệt Marketplace để cài đặt hoặc cấu hình nhà cung cấp trong phần cài đặt.", "modelProvider.selector.onlyCompatibleModelsShown": "Chỉ hiển thị các mô hình tương thích", "modelProvider.selector.showIncompatibleModels": "Hiển thị mô hình không tương thích", - "modelProvider.selector.suggestion": "Gợi ý", "modelProvider.selector.suggestionTip": "Được đề xuất cho tác vụ tác nhân", "modelProvider.showModels": "Hiện Mô hình", "modelProvider.speechToTextModel.key": "Mô hình Chuyển đổi Văn bản thành Tiếng nói", @@ -458,6 +450,8 @@ "operation.confirmAction": "Vui lòng xác nhận hành động của bạn.", "operation.copied": "Sao chép", "operation.copy": "Sao chép", + "operation.copyErrorDetails": "Sao chép chi tiết lỗi", + "operation.copyErrorFailed": "Sao chép thất bại. Thử lại", "operation.copyImage": "Sao chép hình ảnh", "operation.create": "Tạo mới", "operation.deSelectAll": "Bỏ chọn tất cả", @@ -475,7 +469,6 @@ "operation.format": "Định dạng", "operation.imageCopied": "Hình ảnh sao chép", "operation.imageCopyFailed": "Không thể sao chép hình ảnh. Vui lòng tải ảnh xuống.", - "operation.imageDownloaded": "Hình ảnh đã được tải xuống", "operation.in": "trong", "operation.learnMore": "Tìm hiểu thêm", "operation.log": "Nhật ký", @@ -512,7 +505,6 @@ "operation.sure": "Tôi chắc chắn", "operation.toggleFullscreen": "Chuyển đổi toàn màn hình", "operation.toggleMute": "Bật/tắt tiếng", - "operation.upload": "Upload", "operation.view": "Cảnh", "operation.viewDetails": "Xem chi tiết", "operation.viewMore": "XEM THÊM", @@ -542,7 +534,6 @@ "promptEditor.requestURL.item.title": "URL yêu cầu", "promptEditor.variable.modal.add": "Biến mới", "promptEditor.variable.modal.addTool": "Công cụ mới", - "provider.anthropicHosted.trialQuotaTip": "Hạn ngạch dùng thử Anthropic của bạn sẽ hết hạn vào 2025/03/11 và sẽ không còn khả dụng sau đó. Vui lòng sử dụng nó kịp thời.", "provider.encrypted.back": " công nghệ.", "provider.encrypted.front": "Khóa API của bạn sẽ được mã hóa và lưu trữ bằng", "provider.validating": "Đang xác minh khóa...", @@ -565,7 +556,6 @@ "settings.preferences": "Preferences", "settings.provider": "Nhà cung cấp mô hình", "settings.resourceAccess": "Quyền truy cập tài nguyên", - "settings.resourceAccessDescription": "Cấu hình các quy tắc quyền mà ứng dụng và cơ sở kiến thức có thể sử dụng. Các quy tắc quyền có thể được gán cho thành viên trong các cấu hình truy cập tài nguyên cụ thể.", "settings.rolesAndPermissions": "Vai trò & Quyền", "settings.settings": "Settings", "settings.swaggerAPIAsTool": "Swagger API as Tool", @@ -695,7 +685,6 @@ "userProfile.helpCenter": "Trung tâm trợ giúp", "userProfile.logout": "Đăng xuất", "userProfile.roadmap": "Lộ trình", - "userProfile.settings": "Cài đặt", "userProfile.workspace": "Không gian làm việc", "voice.language.arTN": "Tiếng Ả Rập Tunisia", "voice.language.deDE": "Tiếng Đức", diff --git a/web/i18n/vi-VN/custom.json b/web/i18n/locales/vi-VN/custom.json similarity index 100% rename from web/i18n/vi-VN/custom.json rename to web/i18n/locales/vi-VN/custom.json diff --git a/web/i18n/vi-VN/dataset-creation.json b/web/i18n/locales/vi-VN/dataset-creation.json similarity index 100% rename from web/i18n/vi-VN/dataset-creation.json rename to web/i18n/locales/vi-VN/dataset-creation.json diff --git a/web/i18n/vi-VN/dataset-documents.json b/web/i18n/locales/vi-VN/dataset-documents.json similarity index 100% rename from web/i18n/vi-VN/dataset-documents.json rename to web/i18n/locales/vi-VN/dataset-documents.json diff --git a/web/i18n/vi-VN/dataset-hit-testing.json b/web/i18n/locales/vi-VN/dataset-hit-testing.json similarity index 100% rename from web/i18n/vi-VN/dataset-hit-testing.json rename to web/i18n/locales/vi-VN/dataset-hit-testing.json diff --git a/web/i18n/vi-VN/dataset-pipeline.json b/web/i18n/locales/vi-VN/dataset-pipeline.json similarity index 100% rename from web/i18n/vi-VN/dataset-pipeline.json rename to web/i18n/locales/vi-VN/dataset-pipeline.json diff --git a/web/i18n/vi-VN/dataset-settings.json b/web/i18n/locales/vi-VN/dataset-settings.json similarity index 100% rename from web/i18n/vi-VN/dataset-settings.json rename to web/i18n/locales/vi-VN/dataset-settings.json diff --git a/web/i18n/vi-VN/dataset.json b/web/i18n/locales/vi-VN/dataset.json similarity index 97% rename from web/i18n/vi-VN/dataset.json rename to web/i18n/locales/vi-VN/dataset.json index e9790c17f13..67326ca5327 100644 --- a/web/i18n/vi-VN/dataset.json +++ b/web/i18n/locales/vi-VN/dataset.json @@ -163,7 +163,6 @@ "newKnowledge.configureProvider": "Định cấu hình {{provider}}", "newKnowledge.connectNotion": "Kết nối Notion", "newKnowledge.connectProvider": "Kết nối {{provider}}", - "newKnowledge.connectProviderGeneric": "Kết nối nhà cung cấp", "newKnowledge.connectSource": "Kết nối nguồn", "newKnowledge.connectSourceDescription": "Nội dung luôn được đồng bộ với Notion, trang web và các dịch vụ khác.", "newKnowledge.connectingProvider": "Đang kết nối…", @@ -179,18 +178,14 @@ "newKnowledge.crawlFailedDescription": "Trình thu thập không thể hoàn tất bản xem trước này. Kiểm tra URL, điều chỉnh tùy chọn thu thập hoặc thử lại.", "newKnowledge.crawlFailedProvider": "{{provider}} đã báo lỗi. Kiểm tra kết nối nhà cung cấp rồi thử lại.", "newKnowledge.crawlFailedTimeout": "Quá thời gian thu thập. Hãy thử URL cụ thể hơn hoặc giảm số trang tối đa.", - "newKnowledge.crawlNotStarted": "Chưa bắt đầu", "newKnowledge.crawlOptions": "Tùy chọn thu thập", "newKnowledge.crawlPreview": "Xem trước thu thập", "newKnowledge.crawlProgress": "Tiến trình thu thập của {{host}}", - "newKnowledge.crawlSetupUnavailableDescription": "Cấu hình thu thập thông tin trang web bị tắt cho đến khi có quy trình thu thập thông tin.", - "newKnowledge.crawlSetupUnavailableTitle": "Firecrawl đã sẵn sàng", "newKnowledge.crawlStartFailed": "Không thể bắt đầu bản xem trước thu thập. Hãy thử lại.", "newKnowledge.crawlStopped": "Đã dừng thu thập", "newKnowledge.crawling": "Đang thu thập…", "newKnowledge.crawlingPages": "Đang thu thập {{host}}… đã tìm thấy {{count}} trang", "newKnowledge.createFailed": "Chúng tôi không thể tạo ra nền tảng kiến thức này. Hãy kiểm tra quyền truy cập của bạn và thử lại.", - "newKnowledge.createIllustrationTitle": "Kết nối nội dung một lần — mọi ứng dụng đều trả lời bằng kiến thức mới nhất.", "newKnowledge.createTitle": "Tạo kiến thức", "newKnowledge.createdAt": "Đã tạo", "newKnowledge.creators": "Người tạo", @@ -220,7 +215,6 @@ "newKnowledge.documentColumn": "tài liệu", "newKnowledge.documentContentIncomplete": "Đang tải phần nội dung tài liệu còn lại…", "newKnowledge.documentContents": "Nội dung", - "newKnowledge.documentFacts": "Chi tiết tài liệu", "newKnowledge.documentFilterLabel": "Lọc tài liệu theo trạng thái", "newKnowledge.documentLoadErrorDescription": "Không thể tải tài liệu này. Hãy thử lại.", "newKnowledge.documentLoadErrorTitle": "Không thể tải tài liệu", @@ -268,7 +262,6 @@ "newKnowledge.documentsSelected": "Đã chọn tài liệu {{count}}", "newKnowledge.documentsUnavailable": "— tài liệu", "newKnowledge.downloadDocuments": "Tải xuống", - "newKnowledge.editSource": "Chỉnh sửa", "newKnowledge.emptyChunk": "Đoạn trống", "newKnowledge.emptyDescription": "Thêm tài liệu để tác nhân có thể đọc trực tiếp — không cần cấu hình lập chỉ mục.", "newKnowledge.emptyTitle": "Cách thông minh hơn để xây dựng cơ sở kiến thức", @@ -344,7 +337,6 @@ "newKnowledge.processingTaskState.succeeded": "Đã hoàn thành", "newKnowledge.processingTaskState.superseded": "Đã thay thế", "newKnowledge.providerConnected": "Đã kết nối {{provider}}", - "newKnowledge.providerLabel": "Chọn nhà cung cấp", "newKnowledge.providerLoadFailed": "Nhà cung cấp không thể tải", "newKnowledge.providerNotConfigured": "{{provider}} chưa được định cấu hình", "newKnowledge.providerNotConfiguredDescription": "Thêm khóa API hoặc URL điểm cuối để thu thập dữ liệu trang web bằng {{provider}}.", @@ -369,12 +361,10 @@ "newKnowledge.revisionState.superseded": "Trước đó", "newKnowledge.rootUrl": "URL gốc", "newKnowledge.rootUrlPlaceholder": "https://docs.example.com", - "newKnowledge.routeUnavailable": "Phần này sẽ có sẵn trong bước thiết lập tiếp theo.", "newKnowledge.searchDocuments": "Tìm kiếm tài liệu", "newKnowledge.searchSources": "Nguồn tìm kiếm", "newKnowledge.selectAll": "Chọn tất cả", "newKnowledge.selectAllDocuments": "Chọn tất cả tài liệu", - "newKnowledge.selectAllSources": "Chọn tất cả nguồn", "newKnowledge.selectChunk": "Chọn một đoạn để xem nội dung.", "newKnowledge.selectFilesAndFolders": "Chọn tệp và thư mục để đồng bộ", "newKnowledge.skippedFailed": "Đã bỏ qua (thất bại)", @@ -384,7 +374,6 @@ "newKnowledge.sourceFilterLabel": "Lọc nguồn theo trạng thái", "newKnowledge.sourceName": "Tên nguồn", "newKnowledge.sourceNamePlaceholder": "ví dụ: thu thập tài liệu sản phẩm", - "newKnowledge.sourceNameRequired": "Nhập tên nguồn.", "newKnowledge.sourceSetupBackendDependency": "Thiết lập nguồn được hiển thị đầy đủ; thao tác chỉnh sửa và xem trước cần quy trình backend cho nguồn nháp.", "newKnowledge.sourceStatus.active": "Đang hoạt động", "newKnowledge.sourceStatus.disabled": "Đã tắt", @@ -431,7 +420,6 @@ "newKnowledge.unavailableDescription": "KnowledgeFS hiện không khả dụng cho không gian làm việc này.", "newKnowledge.unavailableTitle": "Kiến thức mới không khả dụng", "newKnowledge.updated": "Cập nhật {{date}}", - "newKnowledge.updatedAt": "Cập nhật gần nhất", "newKnowledge.updatedColumn": "Đã cập nhật", "newKnowledge.uploadCharactersUnavailable": "Ký tự: —", "newKnowledge.uploadDocuments": "Tải tài liệu lên", @@ -460,11 +448,9 @@ "retrievalSettings": "Cài đặt truy xuất", "retry": "Thử lại", "selectExternalKnowledgeAPI.placeholder": "Chọn một API kiến thức bên ngoài", - "serviceApi.card.addApiKey": "Thêm khóa API", "serviceApi.card.apiKey": "Khóa API", "serviceApi.card.apiReference": "Tham chiếu API", "serviceApi.card.endpoint": "Điểm cuối API dịch vụ", - "serviceApi.card.manageApiKey": "Quản lý khóa API", "serviceApi.card.title": "API dịch vụ backend", "serviceApi.disabled": "Vô hiệu hóa", "serviceApi.enabled": "Đang phục vụ", diff --git a/web/i18n/vi-VN/deployments.json b/web/i18n/locales/vi-VN/deployments.json similarity index 97% rename from web/i18n/vi-VN/deployments.json rename to web/i18n/locales/vi-VN/deployments.json index d870942fdef..270c56f3cba 100644 --- a/web/i18n/vi-VN/deployments.json +++ b/web/i18n/locales/vi-VN/deployments.json @@ -27,7 +27,6 @@ "list.emptyTitle": "Chưa có triển khai nào", "overview.chip.latest": "mới nhất", "status.RUNTIME_INSTANCE_STATUS_DEPLOYING": "Đang triển khai", - "status.RUNTIME_INSTANCE_STATUS_FAILED": "Triển khai thất bại", "status.RUNTIME_INSTANCE_STATUS_INVALID": "Không hợp lệ", "status.RUNTIME_INSTANCE_STATUS_READY": "Đang chạy", "status.RUNTIME_INSTANCE_STATUS_UNDEPLOYED": "Chưa triển khai", @@ -87,6 +86,5 @@ "studio.versionValue": "Giá trị phiên bản", "studio.versionsBehind_one": "Chậm {{count}} phiên bản", "studio.versionsBehind_other": "Chậm {{count}} phiên bản", - "versions.checkingReleaseContent": "Đang kiểm tra nội dung bản phát hành...", "versions.deployTo": "Triển khai đến {{name}}" } diff --git a/web/i18n/vi-VN/device-flow.json b/web/i18n/locales/vi-VN/device-flow.json similarity index 100% rename from web/i18n/vi-VN/device-flow.json rename to web/i18n/locales/vi-VN/device-flow.json diff --git a/web/i18n/vi-VN/education.json b/web/i18n/locales/vi-VN/education.json similarity index 100% rename from web/i18n/vi-VN/education.json rename to web/i18n/locales/vi-VN/education.json diff --git a/web/i18n/vi-VN/explore.json b/web/i18n/locales/vi-VN/explore.json similarity index 100% rename from web/i18n/vi-VN/explore.json rename to web/i18n/locales/vi-VN/explore.json diff --git a/web/i18n/vi-VN/layout.json b/web/i18n/locales/vi-VN/layout.json similarity index 100% rename from web/i18n/vi-VN/layout.json rename to web/i18n/locales/vi-VN/layout.json diff --git a/web/i18n/vi-VN/login.json b/web/i18n/locales/vi-VN/login.json similarity index 100% rename from web/i18n/vi-VN/login.json rename to web/i18n/locales/vi-VN/login.json diff --git a/web/i18n/vi-VN/oauth.json b/web/i18n/locales/vi-VN/oauth.json similarity index 100% rename from web/i18n/vi-VN/oauth.json rename to web/i18n/locales/vi-VN/oauth.json diff --git a/web/i18n/vi-VN/permission-keys.json b/web/i18n/locales/vi-VN/permission-keys.json similarity index 100% rename from web/i18n/vi-VN/permission-keys.json rename to web/i18n/locales/vi-VN/permission-keys.json diff --git a/web/i18n/vi-VN/permission.json b/web/i18n/locales/vi-VN/permission.json similarity index 100% rename from web/i18n/vi-VN/permission.json rename to web/i18n/locales/vi-VN/permission.json diff --git a/web/i18n/vi-VN/pipeline.json b/web/i18n/locales/vi-VN/pipeline.json similarity index 100% rename from web/i18n/vi-VN/pipeline.json rename to web/i18n/locales/vi-VN/pipeline.json diff --git a/web/i18n/vi-VN/plugin-tags.json b/web/i18n/locales/vi-VN/plugin-tags.json similarity index 100% rename from web/i18n/vi-VN/plugin-tags.json rename to web/i18n/locales/vi-VN/plugin-tags.json diff --git a/web/i18n/vi-VN/plugin-trigger.json b/web/i18n/locales/vi-VN/plugin-trigger.json similarity index 100% rename from web/i18n/vi-VN/plugin-trigger.json rename to web/i18n/locales/vi-VN/plugin-trigger.json diff --git a/web/i18n/vi-VN/plugin.json b/web/i18n/locales/vi-VN/plugin.json similarity index 99% rename from web/i18n/vi-VN/plugin.json rename to web/i18n/locales/vi-VN/plugin.json index ff98008f219..2b212829a7c 100644 --- a/web/i18n/vi-VN/plugin.json +++ b/web/i18n/locales/vi-VN/plugin.json @@ -277,7 +277,6 @@ "readmeInfo.noReadmeAvailable": "Không có README", "readmeInfo.title": "ĐỌC_TÔI", "requestAPlugin": "Yêu cầu một plugin", - "requestSubmit": "Yêu cầu hoặc xuất bản", "search": "Tìm kiếm", "searchCategories": "Danh mục tìm kiếm", "searchInMarketplace": "Tìm kiếm trên Marketplace", diff --git a/web/i18n/vi-VN/register.json b/web/i18n/locales/vi-VN/register.json similarity index 100% rename from web/i18n/vi-VN/register.json rename to web/i18n/locales/vi-VN/register.json diff --git a/web/i18n/vi-VN/run-log.json b/web/i18n/locales/vi-VN/run-log.json similarity index 100% rename from web/i18n/vi-VN/run-log.json rename to web/i18n/locales/vi-VN/run-log.json diff --git a/web/i18n/vi-VN/share.json b/web/i18n/locales/vi-VN/share.json similarity index 100% rename from web/i18n/vi-VN/share.json rename to web/i18n/locales/vi-VN/share.json diff --git a/web/i18n/vi-VN/skill.json b/web/i18n/locales/vi-VN/skill.json similarity index 83% rename from web/i18n/vi-VN/skill.json rename to web/i18n/locales/vi-VN/skill.json index 30b67e9a5b5..221e406b000 100644 --- a/web/i18n/vi-VN/skill.json +++ b/web/i18n/locales/vi-VN/skill.json @@ -1,5 +1,4 @@ { - "skillManagement.clearTags": "Xóa thẻ", "skillManagement.create": "Tạo", "skillManagement.createFailed": "Không tạo được kỹ năng.", "skillManagement.createSuccess": "Đã tạo bản nháp.", @@ -13,12 +12,9 @@ "skillManagement.deleteFailed": "Không thể xóa kỹ năng.", "skillManagement.deleteSuccess": "Skill đã xóa.", "skillManagement.detail.addMetadata": "Thêm siêu dữ liệu", - "skillManagement.detail.addMetadataDescription": "Thêm trường frontmatter vào tệp Markdown này.", "skillManagement.detail.addTag": "Thêm thẻ", - "skillManagement.detail.addTagDescription": "Tạo hoặc liên kết thẻ với Skill này. Các thẻ mới được lưu với siêu dữ liệu Skill.", "skillManagement.detail.addTagSuccess": "Đã thêm thẻ.", "skillManagement.detail.back": "Quay lại Skills", - "skillManagement.detail.builder.applyFailed": "Không áp dụng được bản cập nhật nháp.", "skillManagement.detail.builder.attach": "Đính kèm tập tin", "skillManagement.detail.builder.attachFailed": "Không thể đính kèm tập tin.", "skillManagement.detail.builder.attachLimit": "Bạn có thể đính kèm tối đa 10 tập tin.", @@ -26,22 +22,12 @@ "skillManagement.detail.builder.attachUnsupported": "Chỉ có thể đính kèm các tệp hình ảnh, văn bản và tài liệu.", "skillManagement.detail.builder.attachmentOnlyMessage": "Sử dụng file đính kèm để giúp cải thiện kỹ năng này.", "skillManagement.detail.builder.close": "Đóng Skill Builder", - "skillManagement.detail.builder.compatibleModelsOnly": "Chỉ hiển thị các mô hình tương thích", "skillManagement.detail.builder.copyResponse": "Sao chép phản hồi", "skillManagement.detail.builder.copySuccess": "Đã sao chép phản hồi vào bảng nhớ tạm.", "skillManagement.detail.builder.editIntro": "Tôi đã xem lại kỹ năng này. Bạn muốn điều chỉnh gì?", "skillManagement.detail.builder.exampleIssueTriage": "Phân loại vấn đề của khách hàng", "skillManagement.detail.builder.exampleOnboarding": "Hướng dẫn làm quen với nhân viên mới", "skillManagement.detail.builder.exampleSalesFollowUp": "Chiến lược theo dõi khách hàng tiềm năng bán hàng", - "skillManagement.detail.builder.followUpDisplayName": "Sử dụng Phê duyệt hoàn tiền làm tên hiển thị", - "skillManagement.detail.builder.followUpNameIcon": "Áp dụng tên và biểu tượng được đề xuất", - "skillManagement.detail.builder.fromMarketplace": "Từ Marketplace", - "skillManagement.detail.builder.model": "GPT-4o", - "skillManagement.detail.builder.modelCredits.all": "Tất cả các khoản tín dụng", - "skillManagement.detail.builder.modelCredits.configure": "Cần cấu hình", - "skillManagement.detail.builder.modelCredits.exhausted": "Quota đã hết", - "skillManagement.detail.builder.modelProviderSettings": "Cài đặt nhà cung cấp mô hình", - "skillManagement.detail.builder.modelSearch": "Tìm kiếm mô hình...", "skillManagement.detail.builder.modifyPlaceholder": "Yêu cầu AI sửa đổi kỹ năng này...", "skillManagement.detail.builder.open": "Mở Skill Builder", "skillManagement.detail.builder.placeholder": "Hãy mô tả tình huống...", @@ -61,8 +47,6 @@ "skillManagement.detail.builder.thinkingUnavailable": "Không có nội dung suy nghĩ nào được mô hình trả về.", "skillManagement.detail.builder.title": "Skill Builder", "skillManagement.detail.builder.tryExample": "Hãy thử một ví dụ", - "skillManagement.detail.builder.voice": "Nhập bằng giọng nói", - "skillManagement.detail.builder.voiceUnavailable": "Tính năng nhập bằng giọng nói hiện chưa khả dụng.", "skillManagement.detail.cancelAddMetadata": "Hủy thêm siêu dữ liệu", "skillManagement.detail.closeFileTab": "Đóng {{name}}", "skillManagement.detail.closeVersions": "Đóng phiên bản", @@ -70,14 +54,10 @@ "skillManagement.detail.copyContentSuccess": "Nội dung được sao chép vào clipboard.", "skillManagement.detail.copyFile": "Sao chép", "skillManagement.detail.copyFileSuccess": "Tệp đã sẵn sàng để dán.", - "skillManagement.detail.copyVersionId": "Sao chép ID", - "skillManagement.detail.copyVersionIdSuccess": "Đã sao chép ID vào bảng nhớ tạm.", "skillManagement.detail.createFile": "Tập tin mới", - "skillManagement.detail.createFileDescription": "Nhập đường dẫn tệp liên quan đến gốc kỹ năng.", "skillManagement.detail.createFileMenu": "Tập tin mới...", "skillManagement.detail.createFileSuccess": "Đã tạo tệp.", "skillManagement.detail.createFolder": "Thư mục mới", - "skillManagement.detail.createFolderDescription": "Nhập đường dẫn thư mục liên quan đến thư mục gốc của kỹ năng.", "skillManagement.detail.createFolderMenu": "Thư mục mới...", "skillManagement.detail.createFolderSuccess": "Đã tạo thư mục.", "skillManagement.detail.createdBy": "Được tạo bởi {{name}}", @@ -100,10 +80,8 @@ "skillManagement.detail.fileMeta": "{{type}} · {{size}} byte", "skillManagement.detail.fileMissing": "Không tìm thấy tập tin.", "skillManagement.detail.fileOperationFailed": "Thao tác với tệp không thành công.", - "skillManagement.detail.files": "Tệp", "skillManagement.detail.latest": "Mới nhất", "skillManagement.detail.loadFailed": "Không thể tải kỹ năng.", - "skillManagement.detail.loadingTags": "Đang tải thẻ...", "skillManagement.detail.markdownLiveMode": "Xem trước trực tiếp", "skillManagement.detail.markdownSourceMode": "Trình chỉnh sửa nguồn", "skillManagement.detail.metadataKey": "tên", @@ -113,10 +91,7 @@ "skillManagement.detail.nameThisVersion": "Đặt tên cho phiên bản này", "skillManagement.detail.noFileSelected": "Chọn một tập tin để xem trước.", "skillManagement.detail.noFiles": "Không có tập tin.", - "skillManagement.detail.noSearchResults": "Không có tệp phù hợp.", - "skillManagement.detail.noSelectableTags": "Không có thẻ có sẵn.", "skillManagement.detail.noVersions": "Chưa có phiên bản nào được công bố.", - "skillManagement.detail.pasteFile": "Dán", "skillManagement.detail.pasteFileSuccess": "Đã dán tệp.", "skillManagement.detail.previewUnsupported": "Tệp này không hỗ trợ xem trước.", "skillManagement.detail.publish": "Xuất bản", @@ -128,9 +103,7 @@ "skillManagement.detail.publishUpdate": "Xuất bản cập nhật", "skillManagement.detail.published": "Đã xuất bản", "skillManagement.detail.publishedAt": "Đã phát hành {{time}}", - "skillManagement.detail.publishedVersion": "Đã xuất bản #{{number}}", "skillManagement.detail.publishing": "Đang phát hành...", - "skillManagement.detail.readonly": "Chỉ đọc", "skillManagement.detail.referenceFiles.confirm": "Nhập xác nhận", "skillManagement.detail.referenceFiles.empty": "Không có tập tin nào có sẵn.", "skillManagement.detail.referenceFiles.livePlaceholder": "Viết hướng dẫn cho đại lý. Nhập / để tham khảo các tập tin.", @@ -138,14 +111,10 @@ "skillManagement.detail.referenceFiles.title": "Tệp tham khảo", "skillManagement.detail.referencedBy_one": "Được {{count}} Agent sử dụng", "skillManagement.detail.referencedBy_other": "Được {{count}} Agent sử dụng", - "skillManagement.detail.removeMetadata": "Xóa siêu dữ liệu {{name}}", "skillManagement.detail.removeTag": "Xóa {{tag}}", "skillManagement.detail.removeTagSuccess": "Đã xóa thẻ.", - "skillManagement.detail.renameFile": "Đổi tên tập tin", - "skillManagement.detail.renameFileDescription": "Nhập đường dẫn mới liên quan đến gốc kỹ năng.", "skillManagement.detail.renameFileSuccess": "Đã đổi tên tập tin.", "skillManagement.detail.renameSkillSuccess": "Skill đã đổi tên.", - "skillManagement.detail.renameVersion": "Đổi tên", "skillManagement.detail.renameVersionFailed": "Không đổi tên được phiên bản.", "skillManagement.detail.renameVersionPrompt": "Tên phiên bản", "skillManagement.detail.renameVersionSuccess": "Phiên bản được đổi tên.", @@ -155,17 +124,14 @@ "skillManagement.detail.restoreVersionConfirmTitle": "Khôi phục phiên bản này?", "skillManagement.detail.restoreVersionFailed": "Không thể khôi phục phiên bản.", "skillManagement.detail.restoreVersionSuccess": "Phiên bản được khôi phục về bản nháp.", - "skillManagement.detail.save": "Lưu", "skillManagement.detail.saveConflictConfirmDescription": "Kỹ năng này đã được cập nhật ở nơi khác. Tải bản nháp mới nhất để tiếp tục chỉnh sửa. Những thay đổi hiện tại của bạn sẽ bị loại bỏ.", "skillManagement.detail.saveConflictConfirmTitle": "Tải bản nháp mới nhất?", "skillManagement.detail.saveConflictReload": "Tải bản nháp mới nhất", "skillManagement.detail.saveConflictStatus": "Xung đột được phát hiện. Tải bản nháp mới nhất để tiếp tục chỉnh sửa.", "skillManagement.detail.saveFailed": "Không lưu được tập tin.", - "skillManagement.detail.saveSuccess": "Đã lưu tệp.", "skillManagement.detail.saved": "Đã lưu", "skillManagement.detail.savedAt": "Đã lưu {{time}}", "skillManagement.detail.saving": "Đang lưu...", - "skillManagement.detail.searchFiles": "Tìm kiếm tệp", "skillManagement.detail.showMoreReferences": "Hiển thị thêm {{count}}", "skillManagement.detail.skillDescriptionPlaceholder": "Mô tả kỹ năng này làm gì và khi nào nhân viên nên sử dụng nó.", "skillManagement.detail.skillNamePlaceholder": "Sử dụng chữ thường, số và dấu gạch nối.", @@ -177,16 +143,12 @@ "skillManagement.detail.uploadConflict": "Một tập tin có tên này đã tồn tại.", "skillManagement.detail.uploadFailureSummary": "{{failed}} của các tệp {{total}} không thành công", "skillManagement.detail.uploadFailureTitle": "Một số lần tải lên không thành công", - "skillManagement.detail.uploadFile": "Tải lên tệp", - "skillManagement.detail.uploadFileFailed": "Tải tệp lên không thành công.", "skillManagement.detail.uploadFileSuccess": "Đã tải lên tệp.", "skillManagement.detail.uploadFilesButton": "Tải lên tệp {{count}}", "skillManagement.detail.uploadFilesFailedStatus_one": "Tải lên tệp {{count}} không thành công.", "skillManagement.detail.uploadFilesFailedStatus_other": "Tải lên tệp {{count}} không thành công.", "skillManagement.detail.uploadFilesMenu": "Tải tập tin lên...", "skillManagement.detail.uploadFilesProgress": "Đang tải lên {{completed}}/{{total}}", - "skillManagement.detail.uploadFilesResult": "{{uploaded}} đã tải lên · {{failed}} không thành công", - "skillManagement.detail.uploadFilesStatus": "Trạng thái tải lên", "skillManagement.detail.uploadInvalidExtension": "Loại tệp này không được hỗ trợ.", "skillManagement.detail.uploadInvalidName": "Tên không hợp lệ.", "skillManagement.detail.uploadInvalidPath": "Đường dẫn tập tin không hợp lệ.", @@ -206,7 +168,6 @@ "skillManagement.detail.uploadWillReplace": "Sẽ thay thế tập tin hiện có", "skillManagement.detail.uploadWillUseName": "Sẽ tải lên dưới dạng {{name}}", "skillManagement.detail.versionHistory": "Mở lịch sử phiên bản", - "skillManagement.detail.versionMeta": "{{hash}} · {{time}}", "skillManagement.detail.versionPublishNote": "Ghi chú phát hành", "skillManagement.detail.versionPublishNotePlaceholder": "Mô tả sự thay đổi này", "skillManagement.detail.versionPublishedMeta": "{{time}} · {{name}}", @@ -227,8 +188,6 @@ "skillManagement.errors.missingSkillMd": "Gói này không có SKILL.md. Thêm tệp và nhập lại.", "skillManagement.errors.nameConflict": "Một kỹ năng có tên \"{{name}}\" đã tồn tại. Đổi tên nó thành SKILL.md và nhập lại.", "skillManagement.errors.workspaceLimit": "Không gian làm việc của bạn đã đạt đến giới hạn 500 kỹ năng. Xóa một kỹ năng để thêm một kỹ năng mới.", - "skillManagement.exportDialog.description": "Thao tác này sẽ xuất bản nháp hiện đã lưu của kỹ năng. Nếu có thay đổi chưa lưu trong trình chỉnh sửa, hãy lưu trước khi xuất.", - "skillManagement.exportDialog.title": "Xuất bản nháp hiện đã lưu", "skillManagement.import": "Nhập", "skillManagement.importFailed": "Không thể nhập kỹ năng.", "skillManagement.importSuccess": "Skill đã nhập.", @@ -236,12 +195,10 @@ "skillManagement.loadingError": "Không thể tải kỹ năng", "skillManagement.moreActions": "Thêm hành động cho {{name}}", "skillManagement.noDescription": "Chưa có mô tả", - "skillManagement.noTags": "Không có thẻ", "skillManagement.publishedAt": "Đã xuất bản {{time}}", "skillManagement.referenceCount_one": "{{count}} Agent", "skillManagement.referenceCount_other": "{{count}} Agent", "skillManagement.searchLabel": "Tìm kiếm kỹ năng", "skillManagement.searchPlaceholder": "Tìm kiếm theo tên hoặc mô tả", - "skillManagement.tags": "Thẻ", "skillManagement.title": "Kỹ năng" } diff --git a/web/i18n/vi-VN/snippet.json b/web/i18n/locales/vi-VN/snippet.json similarity index 100% rename from web/i18n/vi-VN/snippet.json rename to web/i18n/locales/vi-VN/snippet.json diff --git a/web/i18n/vi-VN/time.json b/web/i18n/locales/vi-VN/time.json similarity index 100% rename from web/i18n/vi-VN/time.json rename to web/i18n/locales/vi-VN/time.json diff --git a/web/i18n/vi-VN/tools.json b/web/i18n/locales/vi-VN/tools.json similarity index 100% rename from web/i18n/vi-VN/tools.json rename to web/i18n/locales/vi-VN/tools.json diff --git a/web/i18n/vi-VN/workflow.json b/web/i18n/locales/vi-VN/workflow.json similarity index 99% rename from web/i18n/vi-VN/workflow.json rename to web/i18n/locales/vi-VN/workflow.json index 74d50197d4c..4e89b9e58a7 100644 --- a/web/i18n/vi-VN/workflow.json +++ b/web/i18n/locales/vi-VN/workflow.json @@ -460,8 +460,6 @@ "nodes.agent.pluginNotInstalledDesc": "Tích hợp này được cài đặt từ GitHub. Vui lòng vào Tích hợp để cài đặt lại", "nodes.agent.roster.change": "Thay đổi", "nodes.agent.roster.editInConsole": "Chỉnh sửa trong Agents", - "nodes.agent.roster.editInConsoleDisabled": "Cần có vai trò Biên tập viên trở lên để chỉnh sửa Agent.", - "nodes.agent.roster.editInConsoleDisabledRbac": "Chỉ thành viên có vai trò Biên tập viên trở lên hoặc quyền Quản lý Agents mới có thể chỉnh sửa Agent.", "nodes.agent.roster.inlineSetup.description": "Một thiết lập một lần chỉ tồn tại trong nút này", "nodes.agent.roster.inlineSetup.name": "Thiết lập inline", "nodes.agent.roster.inlineSetup.type": "Chỉ trong nút này", diff --git a/web/i18n/zh-Hans/agent-v-2.json b/web/i18n/locales/zh-Hans/agent-v-2.json similarity index 98% rename from web/i18n/zh-Hans/agent-v-2.json rename to web/i18n/locales/zh-Hans/agent-v-2.json index 094c924d878..4c32775763e 100644 --- a/web/i18n/zh-Hans/agent-v-2.json +++ b/web/i18n/locales/zh-Hans/agent-v-2.json @@ -69,6 +69,7 @@ "agentDetail.configure.answer.activity.runningCommands": "正在运行命令", "agentDetail.configure.answer.duration.minute": "{{count}}分", "agentDetail.configure.answer.duration.second": "{{count}}秒", + "agentDetail.configure.answer.responseEnded": "回复已结束", "agentDetail.configure.answer.thinking": "思考过程", "agentDetail.configure.build.empty.communityEditionTip": "Community Edition 虽然会对提取出的配置提供版本管理,但不支持文件系统本身的版本管理。请谨慎使用 Build 模式,因为对文件系统的更改会实时发生,并且不一定总能干净地回退。Community Edition 并不是面向大量外部受众提供服务的理想选择。", "agentDetail.configure.build.empty.description": "描述你的需求,它会随着对话填写左侧表单。", @@ -269,7 +270,6 @@ "agentDetail.configure.workingDirectory.breadcrumbLabel": "文件系统路径", "agentDetail.configure.workingDirectory.description": "Agent 运行时读取和写入的文件。它生成的任何内容,例如 qna_report.pdf,都会显示在这里。", "agentDetail.configure.workingDirectory.fileSystem": "文件系统", - "agentDetail.configure.workingDirectory.open": "打开工作目录", "agentDetail.configure.workingDirectory.persistentFiles": "长期", "agentDetail.configure.workingDirectory.persistentFilesTooltip": "长期保留的文件。Agent 可在后续每次运行中使用这些文件,包括终端用户的对话和工作流。在本次构建对话中对此类文件的改动,会在点击「应用」后生效。", "agentDetail.configure.workingDirectory.temporaryFiles": "临时", @@ -348,7 +348,6 @@ "agentDetail.sections.logs": "日志", "agentDetail.sections.monitoring": "监控", "agentDetail.title": "Agent", - "agentDetail.type": "Agent", "agentDetail.versionHistory.empty": "暂无版本", "agentDetail.versionHistory.exitVersions": "退出版本", "agentDetail.versionHistory.filter": "筛选版本", @@ -408,8 +407,5 @@ "roster.sort.lastModified": "最后修改", "roster.sort.recentlyCreated": "最近创建", "roster.title": "Agent", - "roster.usageStatus.draft": "草稿", - "skillManagement.detail.uploadFilesFailedStatus_one": "{{count}} 个文件上传失败。", - "skillManagement.detail.uploadFilesFailedStatus_other": "{{count}} 个文件上传失败。", - "skillManagement.title": "Skills" + "roster.usageStatus.draft": "草稿" } diff --git a/web/i18n/zh-Hans/app-annotation.json b/web/i18n/locales/zh-Hans/app-annotation.json similarity index 100% rename from web/i18n/zh-Hans/app-annotation.json rename to web/i18n/locales/zh-Hans/app-annotation.json diff --git a/web/i18n/zh-Hans/app-api.json b/web/i18n/locales/zh-Hans/app-api.json similarity index 100% rename from web/i18n/zh-Hans/app-api.json rename to web/i18n/locales/zh-Hans/app-api.json diff --git a/web/i18n/zh-Hans/app-debug.json b/web/i18n/locales/zh-Hans/app-debug.json similarity index 100% rename from web/i18n/zh-Hans/app-debug.json rename to web/i18n/locales/zh-Hans/app-debug.json diff --git a/web/i18n/zh-Hans/app-log.json b/web/i18n/locales/zh-Hans/app-log.json similarity index 97% rename from web/i18n/zh-Hans/app-log.json rename to web/i18n/locales/zh-Hans/app-log.json index 76cbe22a6e3..24f8c7149a7 100644 --- a/web/i18n/zh-Hans/app-log.json +++ b/web/i18n/locales/zh-Hans/app-log.json @@ -4,11 +4,9 @@ "agentLogDetail.iteration": "迭代", "agentLogDetail.iterations": "迭代次数", "agentLogDetail.toolUsed": "使用工具", - "archives.action.downloadMonth": "下载 {{month}} 归档", "archives.action.downloadReady": "归档下载已准备好。", "archives.action.prepareDownload": "准备下载", "archives.action.prepareFailed": "归档下载准备失败。", - "archives.action.prepareMonth": "准备下载 {{month}} 归档", "archives.action.prepareStarted": "正在准备归档下载。", "archives.action.preparing": "准备中", "archives.description": "下载此工作空间中已归档工作流运行日志的月度 CSV 导出。", diff --git a/web/i18n/zh-Hans/app-overview.json b/web/i18n/locales/zh-Hans/app-overview.json similarity index 100% rename from web/i18n/zh-Hans/app-overview.json rename to web/i18n/locales/zh-Hans/app-overview.json diff --git a/web/i18n/zh-Hans/app.json b/web/i18n/locales/zh-Hans/app.json similarity index 100% rename from web/i18n/zh-Hans/app.json rename to web/i18n/locales/zh-Hans/app.json diff --git a/web/i18n/zh-Hans/billing.json b/web/i18n/locales/zh-Hans/billing.json similarity index 100% rename from web/i18n/zh-Hans/billing.json rename to web/i18n/locales/zh-Hans/billing.json diff --git a/web/i18n/zh-Hans/common.json b/web/i18n/locales/zh-Hans/common.json similarity index 98% rename from web/i18n/zh-Hans/common.json rename to web/i18n/locales/zh-Hans/common.json index 41929210309..76b326894a8 100644 --- a/web/i18n/zh-Hans/common.json +++ b/web/i18n/locales/zh-Hans/common.json @@ -209,9 +209,7 @@ "mainNav.marketplace": "Marketplace", "mainNav.skills": "Skills", "mainNav.webApps.noResults": "未找到 Web 应用", - "mainNav.webApps.openApp": "打开 {{name}} Web 应用", "mainNav.webApps.searchPlaceholder": "搜索 Web 应用", - "mainNav.workspace.credits": "{{count}} 消息额度", "mainNav.workspace.creditsUnit": "消息额度", "mainNav.workspace.inviteMembers": "邀请成员", "mainNav.workspace.noResults": "未找到工作空间", @@ -233,7 +231,6 @@ "members.datasetOperatorTip": "只能管理知识库", "members.editRole": "编辑角色", "members.editorTip": "能够建立并编辑应用程序,不能管理团队设置", - "members.email": "邮箱", "members.emailInvalid": "邮箱格式无效", "members.emailNotSetup": "由于邮件服务器未设置,无法发送邀请邮件。请将邀请后生成的邀请链接通知用户。", "members.emailPlaceholder": "输入或粘贴邮箱地址…", @@ -250,7 +247,6 @@ "members.inviteLimitExceeded": "成员名额不足,无法发送这些邀请。", "members.inviteTeamMember": "添加团队成员", "members.inviteTeamMemberTip": "对方在登录后可以访问你的团队数据。", - "members.invitedAsRole": "邀请为{{role}}", "members.lastActive": "上次活动时间", "members.memberActions": "成员操作", "members.memberDetails.assign": "分配", @@ -258,8 +254,6 @@ "members.memberDetails.assignedRoles": "已分配角色", "members.memberDetails.customGroup": "自定义", "members.memberDetails.generalGroup": "通用", - "members.memberDetails.openAria": "打开 {{name}} 的成员详情", - "members.memberDetails.roleActionsAria": "打开 {{role}} 角色操作", "members.memberDetails.roleNoPermissionSummary": "当前角色没有任何权限。", "members.memberDetails.rolePermissionSummary": "{{role}} 可以{{permissions}}", "members.memberDetails.title": "成员详情", @@ -370,7 +364,6 @@ "modelProvider.card.usagePriorityTip": "设置运行模型时优先使用的资源。", "modelProvider.configureTip": "请配置 API 密钥,添加模型。", "modelProvider.confirmDelete": "确认删除?", - "modelProvider.credits": "消息额度", "modelProvider.defaultConfig": "默认配置", "modelProvider.discoverMore": "发现更多就在", "modelProvider.embeddingModel.key": "Embedding 模型", @@ -427,7 +420,6 @@ "modelProvider.selector.noProviderConfiguredDesc": "前往 Marketplace 安装,或在设置中配置提供商。", "modelProvider.selector.onlyCompatibleModelsShown": "仅显示兼容的模型", "modelProvider.selector.showIncompatibleModels": "显示不兼容模型", - "modelProvider.selector.suggestion": "推荐", "modelProvider.selector.suggestionTip": "推荐用于 Agent 任务", "modelProvider.showModels": "显示模型", "modelProvider.speechToTextModel.key": "语音转文本模型", @@ -458,6 +450,8 @@ "operation.confirmAction": "请确认您的操作。", "operation.copied": " 已复制", "operation.copy": "复制", + "operation.copyErrorDetails": "复制错误详情", + "operation.copyErrorFailed": "复制失败,重试", "operation.copyImage": "复制图片", "operation.create": "创建", "operation.deSelectAll": "取消全选", @@ -475,7 +469,6 @@ "operation.format": "格式化", "operation.imageCopied": "图片已复制", "operation.imageCopyFailed": "无法复制图片,请尝试下载图片。", - "operation.imageDownloaded": "图片已下载", "operation.in": "在", "operation.learnMore": "了解更多", "operation.log": "日志", @@ -512,7 +505,6 @@ "operation.sure": "我确定", "operation.toggleFullscreen": "切换全屏", "operation.toggleMute": "切换静音", - "operation.upload": "上传", "operation.view": "查看", "operation.viewDetails": "查看详情", "operation.viewMore": "查看更多", @@ -542,7 +534,6 @@ "promptEditor.requestURL.item.title": "请求 URL", "promptEditor.variable.modal.add": "添加新变量", "promptEditor.variable.modal.addTool": "添加工具", - "provider.anthropicHosted.trialQuotaTip": "您的 Anthropic 体验额度将于 2025/03/17 过期,过期后将无法使用,请尽快体验。", "provider.encrypted.back": " 技术进行加密和存储。", "provider.encrypted.front": "密钥将使用 ", "provider.validating": "验证密钥中...", @@ -565,7 +556,6 @@ "settings.preferences": "偏好设置", "settings.provider": "模型供应商", "settings.resourceAccess": "访问权限", - "settings.resourceAccessDescription": "配置应用和知识库可使用的权限规则。权限规则可在具体资源访问配置中分配给成员。", "settings.rolesAndPermissions": "角色与权限", "settings.settings": "设置", "settings.swaggerAPIAsTool": "Swagger API 作为工具", @@ -695,7 +685,6 @@ "userProfile.helpCenter": "查看帮助文档", "userProfile.logout": "登出", "userProfile.roadmap": "路线图", - "userProfile.settings": "设置", "userProfile.workspace": "工作空间", "voice.language.arTN": "突尼斯阿拉伯语", "voice.language.deDE": "德语", diff --git a/web/i18n/zh-Hans/custom.json b/web/i18n/locales/zh-Hans/custom.json similarity index 100% rename from web/i18n/zh-Hans/custom.json rename to web/i18n/locales/zh-Hans/custom.json diff --git a/web/i18n/zh-Hans/dataset-creation.json b/web/i18n/locales/zh-Hans/dataset-creation.json similarity index 100% rename from web/i18n/zh-Hans/dataset-creation.json rename to web/i18n/locales/zh-Hans/dataset-creation.json diff --git a/web/i18n/zh-Hans/dataset-documents.json b/web/i18n/locales/zh-Hans/dataset-documents.json similarity index 100% rename from web/i18n/zh-Hans/dataset-documents.json rename to web/i18n/locales/zh-Hans/dataset-documents.json diff --git a/web/i18n/zh-Hans/dataset-hit-testing.json b/web/i18n/locales/zh-Hans/dataset-hit-testing.json similarity index 100% rename from web/i18n/zh-Hans/dataset-hit-testing.json rename to web/i18n/locales/zh-Hans/dataset-hit-testing.json diff --git a/web/i18n/zh-Hans/dataset-pipeline.json b/web/i18n/locales/zh-Hans/dataset-pipeline.json similarity index 100% rename from web/i18n/zh-Hans/dataset-pipeline.json rename to web/i18n/locales/zh-Hans/dataset-pipeline.json diff --git a/web/i18n/zh-Hans/dataset-settings.json b/web/i18n/locales/zh-Hans/dataset-settings.json similarity index 100% rename from web/i18n/zh-Hans/dataset-settings.json rename to web/i18n/locales/zh-Hans/dataset-settings.json diff --git a/web/i18n/zh-Hans/dataset.json b/web/i18n/locales/zh-Hans/dataset.json similarity index 97% rename from web/i18n/zh-Hans/dataset.json rename to web/i18n/locales/zh-Hans/dataset.json index bd246609fcc..7b03cd5eea4 100644 --- a/web/i18n/zh-Hans/dataset.json +++ b/web/i18n/locales/zh-Hans/dataset.json @@ -163,7 +163,6 @@ "newKnowledge.configureProvider": "配置{{provider}}", "newKnowledge.connectNotion": "连接 Notion", "newKnowledge.connectProvider": "连接{{provider}}", - "newKnowledge.connectProviderGeneric": "连接提供商", "newKnowledge.connectSource": "连接数据源", "newKnowledge.connectSourceDescription": "内容会与 Notion、网站及其他服务保持同步。", "newKnowledge.connectingProvider": "正在连接...", @@ -179,18 +178,14 @@ "newKnowledge.crawlFailedDescription": "抓取工具无法完成此预览。请检查 URL、调整抓取选项,或重试。", "newKnowledge.crawlFailedProvider": "{{provider}} 报告了错误。请检查提供商连接并重试。", "newKnowledge.crawlFailedTimeout": "抓取超时。请尝试范围更小的 URL,或减少最大页面数。", - "newKnowledge.crawlNotStarted": "尚未开始", "newKnowledge.crawlOptions": "抓取选项", "newKnowledge.crawlPreview": "抓取预览", "newKnowledge.crawlProgress": "{{host}} 的抓取进度", - "newKnowledge.crawlSetupUnavailableDescription": "在爬网工作流程可用之前,网站爬网配置将被禁用。", - "newKnowledge.crawlSetupUnavailableTitle": "Firecrawl 已准备就绪", "newKnowledge.crawlStartFailed": "无法启动抓取预览。请重试。", "newKnowledge.crawlStopped": "抓取已停止", "newKnowledge.crawling": "正在抓取…", "newKnowledge.crawlingPages": "正在抓取 {{host}}… 已找到 {{count}} 个页面", "newKnowledge.createFailed": "无法创建此知识库,请检查权限后重试。", - "newKnowledge.createIllustrationTitle": "只需连接一次内容——每个应用都能用最新知识作答。", "newKnowledge.createTitle": "创建知识库", "newKnowledge.createdAt": "创建时间", "newKnowledge.creators": "创建者", @@ -220,7 +215,6 @@ "newKnowledge.documentColumn": "文档", "newKnowledge.documentContentIncomplete": "正在加载剩余文档内容…", "newKnowledge.documentContents": "目录", - "newKnowledge.documentFacts": "文档详情", "newKnowledge.documentFilterLabel": "按状态筛选文档", "newKnowledge.documentLoadErrorDescription": "无法加载此文档,请重试。", "newKnowledge.documentLoadErrorTitle": "文档加载失败", @@ -268,7 +262,6 @@ "newKnowledge.documentsSelected": "已选择 {{count}} 个文档", "newKnowledge.documentsUnavailable": "— 篇文档", "newKnowledge.downloadDocuments": "下载", - "newKnowledge.editSource": "编辑", "newKnowledge.emptyChunk": "空分段", "newKnowledge.emptyDescription": "添加文档后,智能体即可直接读取,无需配置索引。", "newKnowledge.emptyTitle": "更智能的知识库构建方式", @@ -344,7 +337,6 @@ "newKnowledge.processingTaskState.succeeded": "已完成", "newKnowledge.processingTaskState.superseded": "已被取代", "newKnowledge.providerConnected": "{{provider}}已连接", - "newKnowledge.providerLabel": "提供商", "newKnowledge.providerLoadFailed": "提供商无法加载", "newKnowledge.providerNotConfigured": "{{provider}} 未配置", "newKnowledge.providerNotConfiguredDescription": "添加 API 密钥或端点 URL 以使用 {{provider}} 抓取网站。", @@ -369,12 +361,10 @@ "newKnowledge.revisionState.superseded": "上一个", "newKnowledge.rootUrl": "根 URL", "newKnowledge.rootUrlPlaceholder": "https://docs.example.com", - "newKnowledge.routeUnavailable": "此部分将在下一设置步骤中开放。", "newKnowledge.searchDocuments": "搜索文档", "newKnowledge.searchSources": "搜索来源", "newKnowledge.selectAll": "全选", "newKnowledge.selectAllDocuments": "选择全部文档", - "newKnowledge.selectAllSources": "选择所有来源", "newKnowledge.selectChunk": "选择一个分段以查看其内容。", "newKnowledge.selectFilesAndFolders": "选择要同步的文件和文件夹", "newKnowledge.skippedFailed": "已跳过(失败)", @@ -384,7 +374,6 @@ "newKnowledge.sourceFilterLabel": "按状态过滤源", "newKnowledge.sourceName": "来源名称", "newKnowledge.sourceNamePlaceholder": "例如:产品文档抓取", - "newKnowledge.sourceNameRequired": "请输入来源名称。", "newKnowledge.sourceSetupBackendDependency": "来源设置已完整展示;编辑和预览操作需要草稿来源后端流程。", "newKnowledge.sourceStatus.active": "活跃", "newKnowledge.sourceStatus.disabled": "已禁用", @@ -431,7 +420,6 @@ "newKnowledge.unavailableDescription": "KnowledgeFS 当前在此工作区不可用。", "newKnowledge.unavailableTitle": "新版知识库不可用", "newKnowledge.updated": "更新于 {{date}}", - "newKnowledge.updatedAt": "最后更新", "newKnowledge.updatedColumn": "更新时间", "newKnowledge.uploadCharactersUnavailable": "字符数:—", "newKnowledge.uploadDocuments": "上传文档", @@ -460,11 +448,9 @@ "retrievalSettings": "召回设置", "retry": "重试", "selectExternalKnowledgeAPI.placeholder": "选择一个外部知识 API", - "serviceApi.card.addApiKey": "添加 API 密钥", "serviceApi.card.apiKey": "API 密钥", "serviceApi.card.apiReference": "API 文档", "serviceApi.card.endpoint": "API 端点", - "serviceApi.card.manageApiKey": "管理 API 密钥", "serviceApi.card.title": "后端服务 API", "serviceApi.disabled": "已停用", "serviceApi.enabled": "已启用", diff --git a/web/i18n/zh-Hans/deployments.json b/web/i18n/locales/zh-Hans/deployments.json similarity index 97% rename from web/i18n/zh-Hans/deployments.json rename to web/i18n/locales/zh-Hans/deployments.json index fe2ef3ad1ff..53c84958eee 100644 --- a/web/i18n/zh-Hans/deployments.json +++ b/web/i18n/locales/zh-Hans/deployments.json @@ -27,7 +27,6 @@ "list.emptyTitle": "暂无部署", "overview.chip.latest": "最新", "status.RUNTIME_INSTANCE_STATUS_DEPLOYING": "部署中", - "status.RUNTIME_INSTANCE_STATUS_FAILED": "部署失败", "status.RUNTIME_INSTANCE_STATUS_INVALID": "异常", "status.RUNTIME_INSTANCE_STATUS_READY": "运行中", "status.RUNTIME_INSTANCE_STATUS_UNDEPLOYED": "未部署", @@ -87,6 +86,5 @@ "studio.versionValue": "版本值", "studio.versionsBehind_one": "落后 {{count}} 个版本", "studio.versionsBehind_other": "落后 {{count}} 个版本", - "versions.checkingReleaseContent": "正在检查版本内容…", "versions.deployTo": "部署到 {{name}}" } diff --git a/web/i18n/zh-Hans/device-flow.json b/web/i18n/locales/zh-Hans/device-flow.json similarity index 100% rename from web/i18n/zh-Hans/device-flow.json rename to web/i18n/locales/zh-Hans/device-flow.json diff --git a/web/i18n/zh-Hans/education.json b/web/i18n/locales/zh-Hans/education.json similarity index 100% rename from web/i18n/zh-Hans/education.json rename to web/i18n/locales/zh-Hans/education.json diff --git a/web/i18n/zh-Hans/explore.json b/web/i18n/locales/zh-Hans/explore.json similarity index 100% rename from web/i18n/zh-Hans/explore.json rename to web/i18n/locales/zh-Hans/explore.json diff --git a/web/i18n/zh-Hans/layout.json b/web/i18n/locales/zh-Hans/layout.json similarity index 100% rename from web/i18n/zh-Hans/layout.json rename to web/i18n/locales/zh-Hans/layout.json diff --git a/web/i18n/zh-Hans/login.json b/web/i18n/locales/zh-Hans/login.json similarity index 100% rename from web/i18n/zh-Hans/login.json rename to web/i18n/locales/zh-Hans/login.json diff --git a/web/i18n/zh-Hans/oauth.json b/web/i18n/locales/zh-Hans/oauth.json similarity index 100% rename from web/i18n/zh-Hans/oauth.json rename to web/i18n/locales/zh-Hans/oauth.json diff --git a/web/i18n/zh-Hans/permission-keys.json b/web/i18n/locales/zh-Hans/permission-keys.json similarity index 100% rename from web/i18n/zh-Hans/permission-keys.json rename to web/i18n/locales/zh-Hans/permission-keys.json diff --git a/web/i18n/zh-Hans/permission.json b/web/i18n/locales/zh-Hans/permission.json similarity index 100% rename from web/i18n/zh-Hans/permission.json rename to web/i18n/locales/zh-Hans/permission.json diff --git a/web/i18n/zh-Hans/pipeline.json b/web/i18n/locales/zh-Hans/pipeline.json similarity index 100% rename from web/i18n/zh-Hans/pipeline.json rename to web/i18n/locales/zh-Hans/pipeline.json diff --git a/web/i18n/zh-Hans/plugin-tags.json b/web/i18n/locales/zh-Hans/plugin-tags.json similarity index 100% rename from web/i18n/zh-Hans/plugin-tags.json rename to web/i18n/locales/zh-Hans/plugin-tags.json diff --git a/web/i18n/zh-Hans/plugin-trigger.json b/web/i18n/locales/zh-Hans/plugin-trigger.json similarity index 100% rename from web/i18n/zh-Hans/plugin-trigger.json rename to web/i18n/locales/zh-Hans/plugin-trigger.json diff --git a/web/i18n/zh-Hans/plugin.json b/web/i18n/locales/zh-Hans/plugin.json similarity index 99% rename from web/i18n/zh-Hans/plugin.json rename to web/i18n/locales/zh-Hans/plugin.json index 6e016f22a64..df4eaf7905b 100644 --- a/web/i18n/zh-Hans/plugin.json +++ b/web/i18n/locales/zh-Hans/plugin.json @@ -277,7 +277,6 @@ "readmeInfo.noReadmeAvailable": "README 文档不可用", "readmeInfo.title": "README", "requestAPlugin": "申请集成", - "requestSubmit": "申请或发布", "search": "搜索", "searchCategories": "搜索类别", "searchInMarketplace": "在 Marketplace 中搜索", diff --git a/web/i18n/zh-Hans/register.json b/web/i18n/locales/zh-Hans/register.json similarity index 100% rename from web/i18n/zh-Hans/register.json rename to web/i18n/locales/zh-Hans/register.json diff --git a/web/i18n/zh-Hans/run-log.json b/web/i18n/locales/zh-Hans/run-log.json similarity index 100% rename from web/i18n/zh-Hans/run-log.json rename to web/i18n/locales/zh-Hans/run-log.json diff --git a/web/i18n/zh-Hans/share.json b/web/i18n/locales/zh-Hans/share.json similarity index 100% rename from web/i18n/zh-Hans/share.json rename to web/i18n/locales/zh-Hans/share.json diff --git a/web/i18n/zh-Hans/skill.json b/web/i18n/locales/zh-Hans/skill.json similarity index 82% rename from web/i18n/zh-Hans/skill.json rename to web/i18n/locales/zh-Hans/skill.json index ff85565bb84..b238a2ff3a3 100644 --- a/web/i18n/zh-Hans/skill.json +++ b/web/i18n/locales/zh-Hans/skill.json @@ -1,5 +1,4 @@ { - "skillManagement.clearTags": "清除标签", "skillManagement.create": "创建", "skillManagement.createFailed": "Skill 创建失败。", "skillManagement.createSuccess": "草稿已创建。", @@ -13,12 +12,9 @@ "skillManagement.deleteFailed": "Skill 删除失败。", "skillManagement.deleteSuccess": "Skill 已删除。", "skillManagement.detail.addMetadata": "添加元数据", - "skillManagement.detail.addMetadataDescription": "为这个 Markdown 文件添加一个 frontmatter 字段。", "skillManagement.detail.addTag": "添加标签", - "skillManagement.detail.addTagDescription": "创建或绑定一个标签到这个 Skill。新标签会随 Skill 元信息保存。", "skillManagement.detail.addTagSuccess": "标签已添加。", "skillManagement.detail.back": "返回 Skills", - "skillManagement.detail.builder.applyFailed": "草稿更新应用失败。", "skillManagement.detail.builder.attach": "添加文件", "skillManagement.detail.builder.attachFailed": "添加文件失败。", "skillManagement.detail.builder.attachLimit": "最多可以添加 10 个文件。", @@ -26,22 +22,12 @@ "skillManagement.detail.builder.attachUnsupported": "只能添加图片、文本和文档文件。", "skillManagement.detail.builder.attachmentOnlyMessage": "请根据附件改进此 Skill。", "skillManagement.detail.builder.close": "关闭 Skill Builder", - "skillManagement.detail.builder.compatibleModelsOnly": "仅显示兼容模型", "skillManagement.detail.builder.copyResponse": "复制回复", "skillManagement.detail.builder.copySuccess": "回复已复制到剪贴板。", "skillManagement.detail.builder.editIntro": "我已查看此 Skill。想调整哪些内容?", "skillManagement.detail.builder.exampleIssueTriage": "客户问题分级处理", "skillManagement.detail.builder.exampleOnboarding": "新员工入职引导", "skillManagement.detail.builder.exampleSalesFollowUp": "销售线索跟进策略", - "skillManagement.detail.builder.followUpDisplayName": "使用 Refund approval 作为显示名称", - "skillManagement.detail.builder.followUpNameIcon": "应用建议的名称和图标", - "skillManagement.detail.builder.fromMarketplace": "来自 Marketplace", - "skillManagement.detail.builder.model": "GPT-4o", - "skillManagement.detail.builder.modelCredits.all": "全部额度", - "skillManagement.detail.builder.modelCredits.configure": "需要配置", - "skillManagement.detail.builder.modelCredits.exhausted": "额度已用尽", - "skillManagement.detail.builder.modelProviderSettings": "模型供应商设置", - "skillManagement.detail.builder.modelSearch": "搜索模型...", "skillManagement.detail.builder.modifyPlaceholder": "让 AI 修改这个 Skill...", "skillManagement.detail.builder.open": "打开 Skill Builder", "skillManagement.detail.builder.placeholder": "描述场景...", @@ -61,8 +47,6 @@ "skillManagement.detail.builder.thinkingUnavailable": "模型没有返回思考内容。", "skillManagement.detail.builder.title": "Skill Builder", "skillManagement.detail.builder.tryExample": "试试示例", - "skillManagement.detail.builder.voice": "语音输入", - "skillManagement.detail.builder.voiceUnavailable": "暂不支持语音输入。", "skillManagement.detail.cancelAddMetadata": "取消添加元数据", "skillManagement.detail.closeFileTab": "关闭 {{name}}", "skillManagement.detail.closeVersions": "关闭版本面板", @@ -70,14 +54,10 @@ "skillManagement.detail.copyContentSuccess": "内容已复制到剪贴板。", "skillManagement.detail.copyFile": "复制", "skillManagement.detail.copyFileSuccess": "文件已复制,可粘贴。", - "skillManagement.detail.copyVersionId": "复制 ID", - "skillManagement.detail.copyVersionIdSuccess": "ID 已复制到剪贴板。", "skillManagement.detail.createFile": "新建文件", - "skillManagement.detail.createFileDescription": "输入相对于 Skill 根目录的文件路径。", "skillManagement.detail.createFileMenu": "新建文件...", "skillManagement.detail.createFileSuccess": "文件已创建。", "skillManagement.detail.createFolder": "新建文件夹", - "skillManagement.detail.createFolderDescription": "输入相对于 Skill 根目录的文件夹路径。", "skillManagement.detail.createFolderMenu": "新建文件夹...", "skillManagement.detail.createFolderSuccess": "文件夹已创建。", "skillManagement.detail.createdBy": "由 {{name}} 创建", @@ -100,10 +80,8 @@ "skillManagement.detail.fileMeta": "{{type}} · {{size}} 字节", "skillManagement.detail.fileMissing": "文件不存在。", "skillManagement.detail.fileOperationFailed": "文件操作失败。", - "skillManagement.detail.files": "文件", "skillManagement.detail.latest": "Latest", "skillManagement.detail.loadFailed": "Skill 加载失败。", - "skillManagement.detail.loadingTags": "正在加载标签...", "skillManagement.detail.markdownLiveMode": "实时预览", "skillManagement.detail.markdownSourceMode": "源码编辑", "skillManagement.detail.metadataKey": "name", @@ -113,10 +91,7 @@ "skillManagement.detail.nameThisVersion": "命名这个版本", "skillManagement.detail.noFileSelected": "选择一个文件进行预览。", "skillManagement.detail.noFiles": "暂无文件。", - "skillManagement.detail.noSearchResults": "没有匹配的文件。", - "skillManagement.detail.noSelectableTags": "暂无可选标签。", "skillManagement.detail.noVersions": "暂无发布版本。", - "skillManagement.detail.pasteFile": "粘贴", "skillManagement.detail.pasteFileSuccess": "文件已粘贴。", "skillManagement.detail.previewUnsupported": "该文件暂不支持预览。", "skillManagement.detail.publish": "发布", @@ -128,9 +103,7 @@ "skillManagement.detail.publishUpdate": "发布更新", "skillManagement.detail.published": "已发布", "skillManagement.detail.publishedAt": "{{time}}发布", - "skillManagement.detail.publishedVersion": "已发布 #{{number}}", "skillManagement.detail.publishing": "发布中...", - "skillManagement.detail.readonly": "只读", "skillManagement.detail.referenceFiles.confirm": "Enter 确认", "skillManagement.detail.referenceFiles.empty": "暂无可引用的文件。", "skillManagement.detail.referenceFiles.livePlaceholder": "编写 Agent 指令。输入 / 可引用文件。", @@ -138,14 +111,10 @@ "skillManagement.detail.referenceFiles.title": "引用文件", "skillManagement.detail.referencedBy_one": "{{count}} 个 Agent 正在使用", "skillManagement.detail.referencedBy_other": "{{count}} 个 Agent 正在使用", - "skillManagement.detail.removeMetadata": "删除 {{name}} 元数据", "skillManagement.detail.removeTag": "移除 {{tag}}", "skillManagement.detail.removeTagSuccess": "标签已移除。", - "skillManagement.detail.renameFile": "重命名文件", - "skillManagement.detail.renameFileDescription": "输入相对于 Skill 根目录的新路径。", "skillManagement.detail.renameFileSuccess": "文件已重命名。", "skillManagement.detail.renameSkillSuccess": "Skill 已重命名。", - "skillManagement.detail.renameVersion": "重命名", "skillManagement.detail.renameVersionFailed": "版本重命名失败。", "skillManagement.detail.renameVersionPrompt": "版本名称", "skillManagement.detail.renameVersionSuccess": "版本已重命名。", @@ -155,17 +124,14 @@ "skillManagement.detail.restoreVersionConfirmTitle": "确认恢复此版本?", "skillManagement.detail.restoreVersionFailed": "版本恢复失败。", "skillManagement.detail.restoreVersionSuccess": "版本已恢复到草稿。", - "skillManagement.detail.save": "保存", "skillManagement.detail.saveConflictConfirmDescription": "此 Skill 已在其他位置更新。请加载最新草稿后继续编辑。当前更改将被丢弃。", "skillManagement.detail.saveConflictConfirmTitle": "加载最新草稿?", "skillManagement.detail.saveConflictReload": "加载最新草稿", "skillManagement.detail.saveConflictStatus": "检测到冲突,请加载最新草稿后继续编辑。", "skillManagement.detail.saveFailed": "文件保存失败。", - "skillManagement.detail.saveSuccess": "文件已保存。", "skillManagement.detail.saved": "已保存", "skillManagement.detail.savedAt": "{{time}}已保存", "skillManagement.detail.saving": "保存中...", - "skillManagement.detail.searchFiles": "查找文件", "skillManagement.detail.showMoreReferences": "显示其余 {{count}} 个", "skillManagement.detail.skillDescriptionPlaceholder": "描述此 Skill 的用途,以及 Agent 应在何时使用它。", "skillManagement.detail.skillNamePlaceholder": "仅支持小写英文字母、数字和连字符。", @@ -177,16 +143,12 @@ "skillManagement.detail.uploadConflict": "已存在同名文件。", "skillManagement.detail.uploadFailureSummary": "{{total}} 个文件中有 {{failed}} 个失败", "skillManagement.detail.uploadFailureTitle": "部分文件上传失败", - "skillManagement.detail.uploadFile": "上传文件", - "skillManagement.detail.uploadFileFailed": "文件上传失败。", "skillManagement.detail.uploadFileSuccess": "文件已上传。", "skillManagement.detail.uploadFilesButton": "上传 {{count}} 个文件", "skillManagement.detail.uploadFilesFailedStatus_one": "{{count}} 个文件上传失败。", "skillManagement.detail.uploadFilesFailedStatus_other": "{{count}} 个文件上传失败。", "skillManagement.detail.uploadFilesMenu": "上传文件...", "skillManagement.detail.uploadFilesProgress": "上传中 {{completed}}/{{total}}", - "skillManagement.detail.uploadFilesResult": "{{uploaded}} 个已上传 · {{failed}} 个失败", - "skillManagement.detail.uploadFilesStatus": "上传状态", "skillManagement.detail.uploadInvalidExtension": "不支持这种文件类型。", "skillManagement.detail.uploadInvalidName": "文件名无效。", "skillManagement.detail.uploadInvalidPath": "文件路径无效。", @@ -206,7 +168,6 @@ "skillManagement.detail.uploadWillReplace": "将替换现有文件", "skillManagement.detail.uploadWillUseName": "将上传为 {{name}}", "skillManagement.detail.versionHistory": "打开版本历史", - "skillManagement.detail.versionMeta": "{{hash}} · {{time}}", "skillManagement.detail.versionPublishNote": "发布说明", "skillManagement.detail.versionPublishNotePlaceholder": "请描述变更", "skillManagement.detail.versionPublishedMeta": "{{time}} · {{name}}", @@ -227,8 +188,6 @@ "skillManagement.errors.missingSkillMd": "此 Skill 包中没有 SKILL.md。请添加该文件后重新导入。", "skillManagement.errors.nameConflict": "名为「{{name}}」的 Skill 已存在。请在 SKILL.md 中修改名称后重新导入。", "skillManagement.errors.workspaceLimit": "工作空间最多可添加 500 个 Skill。请先删除一个 Skill,再添加新 Skill。", - "skillManagement.exportDialog.description": "此操作将导出此 Skill 当前已保存的草稿。如果编辑器中有未保存的更改,请先保存后再导出。", - "skillManagement.exportDialog.title": "导出当前已保存的草稿", "skillManagement.import": "导入", "skillManagement.importFailed": "Skill 导入失败。", "skillManagement.importSuccess": "Skill 已导入。", @@ -236,12 +195,10 @@ "skillManagement.loadingError": "Skill 加载失败", "skillManagement.moreActions": "{{name}} 的更多操作", "skillManagement.noDescription": "暂无描述", - "skillManagement.noTags": "暂无标签", "skillManagement.publishedAt": "发布于 {{time}}", "skillManagement.referenceCount_one": "{{count}} 个 Agent", "skillManagement.referenceCount_other": "{{count}} 个 Agent", "skillManagement.searchLabel": "搜索 Skill", "skillManagement.searchPlaceholder": "按名称或描述搜索", - "skillManagement.tags": "标签", "skillManagement.title": "Skills" } diff --git a/web/i18n/zh-Hans/snippet.json b/web/i18n/locales/zh-Hans/snippet.json similarity index 100% rename from web/i18n/zh-Hans/snippet.json rename to web/i18n/locales/zh-Hans/snippet.json diff --git a/web/i18n/zh-Hans/time.json b/web/i18n/locales/zh-Hans/time.json similarity index 100% rename from web/i18n/zh-Hans/time.json rename to web/i18n/locales/zh-Hans/time.json diff --git a/web/i18n/zh-Hans/tools.json b/web/i18n/locales/zh-Hans/tools.json similarity index 100% rename from web/i18n/zh-Hans/tools.json rename to web/i18n/locales/zh-Hans/tools.json diff --git a/web/i18n/zh-Hans/workflow.json b/web/i18n/locales/zh-Hans/workflow.json similarity index 99% rename from web/i18n/zh-Hans/workflow.json rename to web/i18n/locales/zh-Hans/workflow.json index 0fd4633818d..78f3eab9391 100644 --- a/web/i18n/zh-Hans/workflow.json +++ b/web/i18n/locales/zh-Hans/workflow.json @@ -460,8 +460,6 @@ "nodes.agent.pluginNotInstalledDesc": "此集成安装自 GitHub。请前往集成重新安装", "nodes.agent.roster.change": "更换", "nodes.agent.roster.editInConsole": "在 Agents 中编辑", - "nodes.agent.roster.editInConsoleDisabled": "编辑 Agent 需要「编辑」及以上角色。", - "nodes.agent.roster.editInConsoleDisabledRbac": "只有拥有「编辑」及以上角色或「管理 Agents」权限的成员,才能编辑 Agent。", "nodes.agent.roster.inlineSetup.description": "仅存在于此节点的一次性设置", "nodes.agent.roster.inlineSetup.name": "内联配置", "nodes.agent.roster.inlineSetup.type": "仅在此节点中", diff --git a/web/i18n/zh-Hant/agent-v-2.json b/web/i18n/locales/zh-Hant/agent-v-2.json similarity index 98% rename from web/i18n/zh-Hant/agent-v-2.json rename to web/i18n/locales/zh-Hant/agent-v-2.json index c36fddbe8de..e15e19a63a5 100644 --- a/web/i18n/zh-Hant/agent-v-2.json +++ b/web/i18n/locales/zh-Hant/agent-v-2.json @@ -69,6 +69,7 @@ "agentDetail.configure.answer.activity.runningCommands": "正在執行命令", "agentDetail.configure.answer.duration.minute": "{{count}}分", "agentDetail.configure.answer.duration.second": "{{count}}秒", + "agentDetail.configure.answer.responseEnded": "回覆已結束", "agentDetail.configure.answer.thinking": "思考過程", "agentDetail.configure.build.empty.communityEditionTip": "Community Edition 雖然會對提取出的設定提供版本管理,但不支援檔案系統本身的版本管理。請謹慎使用 Build 模式,因為對檔案系統的變更會即時發生,且不一定總能乾淨地回復。Community Edition 並不是面向大量外部受眾提供服務的理想選擇。", "agentDetail.configure.build.empty.description": "描述你的需求,它會隨著對話填寫左側表單。", @@ -269,7 +270,6 @@ "agentDetail.configure.workingDirectory.breadcrumbLabel": "檔案系統路徑", "agentDetail.configure.workingDirectory.description": "Agent 執行時讀取和寫入的檔案。它產生的任何內容,例如 qna_report.pdf,都會顯示在這裡。", "agentDetail.configure.workingDirectory.fileSystem": "檔案系統", - "agentDetail.configure.workingDirectory.open": "開啟工作目錄", "agentDetail.configure.workingDirectory.persistentFiles": "長期", "agentDetail.configure.workingDirectory.persistentFilesTooltip": "長期保留的檔案。Agent 可在後續每次執行中使用這些檔案,包括終端使用者的對話和工作流程。在本次建構對話中對此類檔案的變更,會在點擊「套用」後生效。", "agentDetail.configure.workingDirectory.temporaryFiles": "臨時", @@ -348,7 +348,6 @@ "agentDetail.sections.logs": "日誌", "agentDetail.sections.monitoring": "監控", "agentDetail.title": "Agent", - "agentDetail.type": "Agent", "agentDetail.versionHistory.empty": "暫無版本", "agentDetail.versionHistory.exitVersions": "退出版本", "agentDetail.versionHistory.filter": "篩選版本", @@ -408,8 +407,5 @@ "roster.sort.lastModified": "最後修改", "roster.sort.recentlyCreated": "最近建立", "roster.title": "Agent", - "roster.usageStatus.draft": "草稿", - "skillManagement.detail.uploadFilesFailedStatus_one": "{{count}} 個檔案上傳失敗。", - "skillManagement.detail.uploadFilesFailedStatus_other": "{{count}} 個檔案上傳失敗。", - "skillManagement.title": "Skills" + "roster.usageStatus.draft": "草稿" } diff --git a/web/i18n/zh-Hant/app-annotation.json b/web/i18n/locales/zh-Hant/app-annotation.json similarity index 100% rename from web/i18n/zh-Hant/app-annotation.json rename to web/i18n/locales/zh-Hant/app-annotation.json diff --git a/web/i18n/zh-Hant/app-api.json b/web/i18n/locales/zh-Hant/app-api.json similarity index 100% rename from web/i18n/zh-Hant/app-api.json rename to web/i18n/locales/zh-Hant/app-api.json diff --git a/web/i18n/zh-Hant/app-debug.json b/web/i18n/locales/zh-Hant/app-debug.json similarity index 100% rename from web/i18n/zh-Hant/app-debug.json rename to web/i18n/locales/zh-Hant/app-debug.json diff --git a/web/i18n/zh-Hant/app-log.json b/web/i18n/locales/zh-Hant/app-log.json similarity index 97% rename from web/i18n/zh-Hant/app-log.json rename to web/i18n/locales/zh-Hant/app-log.json index 2449973159d..fb257b1d34d 100644 --- a/web/i18n/zh-Hant/app-log.json +++ b/web/i18n/locales/zh-Hant/app-log.json @@ -4,11 +4,9 @@ "agentLogDetail.iteration": "迭代", "agentLogDetail.iterations": "迭代次數", "agentLogDetail.toolUsed": "使用工具", - "archives.action.downloadMonth": "下載 {{month}} 封存", "archives.action.downloadReady": "封存下載已準備好。", "archives.action.prepareDownload": "準備下載", "archives.action.prepareFailed": "封存下載準備失敗。", - "archives.action.prepareMonth": "準備下載 {{month}} 封存", "archives.action.prepareStarted": "正在準備封存下載。", "archives.action.preparing": "準備中", "archives.description": "下載此工作區中已封存工作流程執行記錄的每月 CSV 匯出。", diff --git a/web/i18n/zh-Hant/app-overview.json b/web/i18n/locales/zh-Hant/app-overview.json similarity index 100% rename from web/i18n/zh-Hant/app-overview.json rename to web/i18n/locales/zh-Hant/app-overview.json diff --git a/web/i18n/zh-Hant/app.json b/web/i18n/locales/zh-Hant/app.json similarity index 100% rename from web/i18n/zh-Hant/app.json rename to web/i18n/locales/zh-Hant/app.json diff --git a/web/i18n/zh-Hant/billing.json b/web/i18n/locales/zh-Hant/billing.json similarity index 100% rename from web/i18n/zh-Hant/billing.json rename to web/i18n/locales/zh-Hant/billing.json diff --git a/web/i18n/zh-Hant/common.json b/web/i18n/locales/zh-Hant/common.json similarity index 98% rename from web/i18n/zh-Hant/common.json rename to web/i18n/locales/zh-Hant/common.json index 9ffedb2a0fc..d3d23450632 100644 --- a/web/i18n/zh-Hant/common.json +++ b/web/i18n/locales/zh-Hant/common.json @@ -209,9 +209,7 @@ "mainNav.marketplace": "Marketplace", "mainNav.skills": "Skills", "mainNav.webApps.noResults": "未找到 Web 應用", - "mainNav.webApps.openApp": "開啟 {{name}} Web 應用", "mainNav.webApps.searchPlaceholder": "搜尋 Web 應用", - "mainNav.workspace.credits": "{{count}} 點額度", "mainNav.workspace.creditsUnit": "點額度", "mainNav.workspace.inviteMembers": "邀請成員", "mainNav.workspace.noResults": "找不到工作區", @@ -233,7 +231,6 @@ "members.datasetOperatorTip": "只能管理知識庫", "members.editRole": "編輯角色", "members.editorTip": "能夠建立並編輯應用程式,不能管理團隊設定", - "members.email": "郵箱", "members.emailInvalid": "郵箱格式無效", "members.emailNotSetup": "由於郵件伺服器未設置,無法發送邀請郵件。請將邀請後生成的邀請連結通知用戶。", "members.emailPlaceholder": "輸入或貼上郵箱地址…", @@ -250,7 +247,6 @@ "members.inviteLimitExceeded": "成員名額不足,無法傳送這些邀請。", "members.inviteTeamMember": "新增團隊成員", "members.inviteTeamMemberTip": "對方在登入後可以訪問你的團隊資料。", - "members.invitedAsRole": "邀請為{{role}}", "members.lastActive": "上次活動時間", "members.memberActions": "成員操作", "members.memberDetails.assign": "分配", @@ -258,8 +254,6 @@ "members.memberDetails.assignedRoles": "已分配角色", "members.memberDetails.customGroup": "自訂", "members.memberDetails.generalGroup": "通用", - "members.memberDetails.openAria": "開啟 {{name}} 的成員詳情", - "members.memberDetails.roleActionsAria": "開啟 {{role}} 角色操作", "members.memberDetails.roleNoPermissionSummary": "目前角色沒有任何權限。", "members.memberDetails.rolePermissionSummary": "{{role}} 可以{{permissions}}", "members.memberDetails.title": "成員詳情", @@ -370,7 +364,6 @@ "modelProvider.card.usagePriorityTip": "設定執行模型時優先使用的資源。", "modelProvider.configureTip": "設置 api-key 或新增要使用的模型", "modelProvider.confirmDelete": "確認刪除?", - "modelProvider.credits": "訊息額度", "modelProvider.defaultConfig": "默認配置", "modelProvider.discoverMore": "發現更多", "modelProvider.embeddingModel.key": "Embedding 模型", @@ -427,7 +420,6 @@ "modelProvider.selector.noProviderConfiguredDesc": "前往 Marketplace 安裝,或在設定中配置供應商。", "modelProvider.selector.onlyCompatibleModelsShown": "僅顯示相容的模型", "modelProvider.selector.showIncompatibleModels": "顯示不相容模型", - "modelProvider.selector.suggestion": "推薦", "modelProvider.selector.suggestionTip": "推薦用於 Agent 任務", "modelProvider.showModels": "顯示模型", "modelProvider.speechToTextModel.key": "語音轉文字模型", @@ -458,6 +450,8 @@ "operation.confirmAction": "請確認您的操作。", "operation.copied": "複製", "operation.copy": "複製", + "operation.copyErrorDetails": "複製錯誤詳細資訊", + "operation.copyErrorFailed": "複製失敗,重試", "operation.copyImage": "複製圖像", "operation.create": "建立", "operation.deSelectAll": "全不選", @@ -475,7 +469,6 @@ "operation.format": "格式", "operation.imageCopied": "複製的圖片", "operation.imageCopyFailed": "無法複製圖片,請嘗試下載圖片。", - "operation.imageDownloaded": "圖片已下載", "operation.in": "在", "operation.learnMore": "瞭解更多", "operation.log": "日誌", @@ -512,7 +505,6 @@ "operation.sure": "我確定", "operation.toggleFullscreen": "切換全螢幕", "operation.toggleMute": "切換靜音", - "operation.upload": "上傳", "operation.view": "視圖", "operation.viewDetails": "查看詳情", "operation.viewMore": "查看更多", @@ -542,7 +534,6 @@ "promptEditor.requestURL.item.title": "請求 URL", "promptEditor.variable.modal.add": "新增新變數", "promptEditor.variable.modal.addTool": "新增工具", - "provider.anthropicHosted.trialQuotaTip": "您的 Anthropic 試用配額將於 2025 年 3 月 11 日到期,此後將不再可用。請及時利用。", "provider.encrypted.back": " 技術進行加密和儲存。", "provider.encrypted.front": "金鑰將使用 ", "provider.validating": "驗證金鑰中...", @@ -565,7 +556,6 @@ "settings.preferences": "偏好設定", "settings.provider": "模型供應商", "settings.resourceAccess": "訪問權限", - "settings.resourceAccessDescription": "配置應用和知識庫可使用的權限規則。權限規則可在具體資源訪問配置中分配給成員。", "settings.rolesAndPermissions": "角色與權限", "settings.settings": "設定", "settings.swaggerAPIAsTool": "Swagger API 作為工具", @@ -695,7 +685,6 @@ "userProfile.helpCenter": "查看幫助文件", "userProfile.logout": "登出", "userProfile.roadmap": "路線圖", - "userProfile.settings": "設定", "userProfile.workspace": "工作空間", "voice.language.arTN": "突尼西亞阿拉伯語", "voice.language.deDE": "德語", diff --git a/web/i18n/zh-Hant/custom.json b/web/i18n/locales/zh-Hant/custom.json similarity index 100% rename from web/i18n/zh-Hant/custom.json rename to web/i18n/locales/zh-Hant/custom.json diff --git a/web/i18n/zh-Hant/dataset-creation.json b/web/i18n/locales/zh-Hant/dataset-creation.json similarity index 100% rename from web/i18n/zh-Hant/dataset-creation.json rename to web/i18n/locales/zh-Hant/dataset-creation.json diff --git a/web/i18n/zh-Hant/dataset-documents.json b/web/i18n/locales/zh-Hant/dataset-documents.json similarity index 100% rename from web/i18n/zh-Hant/dataset-documents.json rename to web/i18n/locales/zh-Hant/dataset-documents.json diff --git a/web/i18n/zh-Hant/dataset-hit-testing.json b/web/i18n/locales/zh-Hant/dataset-hit-testing.json similarity index 100% rename from web/i18n/zh-Hant/dataset-hit-testing.json rename to web/i18n/locales/zh-Hant/dataset-hit-testing.json diff --git a/web/i18n/zh-Hant/dataset-pipeline.json b/web/i18n/locales/zh-Hant/dataset-pipeline.json similarity index 100% rename from web/i18n/zh-Hant/dataset-pipeline.json rename to web/i18n/locales/zh-Hant/dataset-pipeline.json diff --git a/web/i18n/zh-Hant/dataset-settings.json b/web/i18n/locales/zh-Hant/dataset-settings.json similarity index 100% rename from web/i18n/zh-Hant/dataset-settings.json rename to web/i18n/locales/zh-Hant/dataset-settings.json diff --git a/web/i18n/zh-Hant/dataset.json b/web/i18n/locales/zh-Hant/dataset.json similarity index 97% rename from web/i18n/zh-Hant/dataset.json rename to web/i18n/locales/zh-Hant/dataset.json index 5948131b730..9f9475a78b2 100644 --- a/web/i18n/zh-Hant/dataset.json +++ b/web/i18n/locales/zh-Hant/dataset.json @@ -163,7 +163,6 @@ "newKnowledge.configureProvider": "配置{{provider}}", "newKnowledge.connectNotion": "連接 Notion", "newKnowledge.connectProvider": "連接{{provider}}", - "newKnowledge.connectProviderGeneric": "連接提供者", "newKnowledge.connectSource": "連接資料來源", "newKnowledge.connectSourceDescription": "內容會與 Notion、網站及其他服務保持同步。", "newKnowledge.connectingProvider": "正在連接...", @@ -179,18 +178,14 @@ "newKnowledge.crawlFailedDescription": "抓取工具無法完成此預覽。請檢查 URL、調整抓取選項,或重試。", "newKnowledge.crawlFailedProvider": "{{provider}} 回報錯誤。請檢查供應商連線並重試。", "newKnowledge.crawlFailedTimeout": "抓取逾時。請嘗試範圍較小的 URL,或減少頁面數上限。", - "newKnowledge.crawlNotStarted": "尚未開始", "newKnowledge.crawlOptions": "抓取選項", "newKnowledge.crawlPreview": "抓取預覽", "newKnowledge.crawlProgress": "{{host}} 的抓取進度", - "newKnowledge.crawlSetupUnavailableDescription": "在爬網工作流程可用之前,網站爬網設定將會被停用。", - "newKnowledge.crawlSetupUnavailableTitle": "Firecrawl 已準備就緒", "newKnowledge.crawlStartFailed": "無法啟動抓取預覽。請重試。", "newKnowledge.crawlStopped": "抓取已停止", "newKnowledge.crawling": "正在抓取…", "newKnowledge.crawlingPages": "正在抓取 {{host}}… 已找到 {{count}} 個頁面", "newKnowledge.createFailed": "無法建立此知識庫,請檢查權限後再試一次。", - "newKnowledge.createIllustrationTitle": "只需連接一次內容——每個應用都能以最新知識作答。", "newKnowledge.createTitle": "建立知識庫", "newKnowledge.createdAt": "建立時間", "newKnowledge.creators": "建立者", @@ -220,7 +215,6 @@ "newKnowledge.documentColumn": "文件", "newKnowledge.documentContentIncomplete": "正在載入剩餘文件內容…", "newKnowledge.documentContents": "目錄", - "newKnowledge.documentFacts": "文件詳細資料", "newKnowledge.documentFilterLabel": "依狀態篩選文件", "newKnowledge.documentLoadErrorDescription": "無法載入此文件,請再試一次。", "newKnowledge.documentLoadErrorTitle": "文件載入失敗", @@ -268,7 +262,6 @@ "newKnowledge.documentsSelected": "已選取 {{count}} 個文件", "newKnowledge.documentsUnavailable": "— 份文件", "newKnowledge.downloadDocuments": "下載", - "newKnowledge.editSource": "編輯", "newKnowledge.emptyChunk": "空分段", "newKnowledge.emptyDescription": "加入文件後,智慧代理即可直接讀取,無需設定索引。", "newKnowledge.emptyTitle": "更聰明的知識庫建立方式", @@ -344,7 +337,6 @@ "newKnowledge.processingTaskState.succeeded": "已完成", "newKnowledge.processingTaskState.superseded": "已被取代", "newKnowledge.providerConnected": "{{provider}}已連接", - "newKnowledge.providerLabel": "供應商", "newKnowledge.providerLoadFailed": "提供者無法載入", "newKnowledge.providerNotConfigured": "{{provider}} 未配置", "newKnowledge.providerNotConfiguredDescription": "新增 API 金鑰或端點 URL 以使用 {{provider}} 抓取網站。", @@ -369,12 +361,10 @@ "newKnowledge.revisionState.superseded": "上一個", "newKnowledge.rootUrl": "根 URL", "newKnowledge.rootUrlPlaceholder": "https://docs.example.com", - "newKnowledge.routeUnavailable": "此區段將在下一個設定步驟中開放。", "newKnowledge.searchDocuments": "搜尋文件", "newKnowledge.searchSources": "搜尋來源", "newKnowledge.selectAll": "全選", "newKnowledge.selectAllDocuments": "選取全部文件", - "newKnowledge.selectAllSources": "選擇所有來源", "newKnowledge.selectChunk": "選取一個分段以查看其內容。", "newKnowledge.selectFilesAndFolders": "選擇要同步的檔案與資料夾", "newKnowledge.skippedFailed": "已略過(失敗)", @@ -384,7 +374,6 @@ "newKnowledge.sourceFilterLabel": "按狀態過濾來源", "newKnowledge.sourceName": "來源名稱", "newKnowledge.sourceNamePlaceholder": "例如:產品文件抓取", - "newKnowledge.sourceNameRequired": "請輸入來源名稱。", "newKnowledge.sourceSetupBackendDependency": "來源設定已完整顯示;編輯與預覽操作需要草稿來源後端流程。", "newKnowledge.sourceStatus.active": "活躍", "newKnowledge.sourceStatus.disabled": "已停用", @@ -431,7 +420,6 @@ "newKnowledge.unavailableDescription": "KnowledgeFS 目前在此工作區不可用。", "newKnowledge.unavailableTitle": "新版知識庫不可用", "newKnowledge.updated": "更新於 {{date}}", - "newKnowledge.updatedAt": "最後更新", "newKnowledge.updatedColumn": "更新時間", "newKnowledge.uploadCharactersUnavailable": "字元數:—", "newKnowledge.uploadDocuments": "上傳文件", @@ -460,11 +448,9 @@ "retrievalSettings": "檢索設置", "retry": "重試", "selectExternalKnowledgeAPI.placeholder": "選擇外部知識 API", - "serviceApi.card.addApiKey": "新增 API 金鑰", "serviceApi.card.apiKey": "API 金鑰", "serviceApi.card.apiReference": "API 參考", "serviceApi.card.endpoint": "服務 API 端點", - "serviceApi.card.manageApiKey": "管理 API 金鑰", "serviceApi.card.title": "後端服務 API", "serviceApi.disabled": "已停用", "serviceApi.enabled": "使用中", diff --git a/web/i18n/zh-Hant/deployments.json b/web/i18n/locales/zh-Hant/deployments.json similarity index 97% rename from web/i18n/zh-Hant/deployments.json rename to web/i18n/locales/zh-Hant/deployments.json index 32d9da399be..66299d47948 100644 --- a/web/i18n/zh-Hant/deployments.json +++ b/web/i18n/locales/zh-Hant/deployments.json @@ -27,7 +27,6 @@ "list.emptyTitle": "尚無部署", "overview.chip.latest": "最新", "status.RUNTIME_INSTANCE_STATUS_DEPLOYING": "部署中", - "status.RUNTIME_INSTANCE_STATUS_FAILED": "部署失敗", "status.RUNTIME_INSTANCE_STATUS_INVALID": "異常", "status.RUNTIME_INSTANCE_STATUS_READY": "執行中", "status.RUNTIME_INSTANCE_STATUS_UNDEPLOYED": "未部署", @@ -87,6 +86,5 @@ "studio.versionValue": "版本值", "studio.versionsBehind_one": "落後 {{count}} 個版本", "studio.versionsBehind_other": "落後 {{count}} 個版本", - "versions.checkingReleaseContent": "正在檢查版本內容…", "versions.deployTo": "部署到 {{name}}" } diff --git a/web/i18n/zh-Hant/device-flow.json b/web/i18n/locales/zh-Hant/device-flow.json similarity index 100% rename from web/i18n/zh-Hant/device-flow.json rename to web/i18n/locales/zh-Hant/device-flow.json diff --git a/web/i18n/zh-Hant/education.json b/web/i18n/locales/zh-Hant/education.json similarity index 100% rename from web/i18n/zh-Hant/education.json rename to web/i18n/locales/zh-Hant/education.json diff --git a/web/i18n/zh-Hant/explore.json b/web/i18n/locales/zh-Hant/explore.json similarity index 100% rename from web/i18n/zh-Hant/explore.json rename to web/i18n/locales/zh-Hant/explore.json diff --git a/web/i18n/zh-Hant/layout.json b/web/i18n/locales/zh-Hant/layout.json similarity index 100% rename from web/i18n/zh-Hant/layout.json rename to web/i18n/locales/zh-Hant/layout.json diff --git a/web/i18n/zh-Hant/login.json b/web/i18n/locales/zh-Hant/login.json similarity index 100% rename from web/i18n/zh-Hant/login.json rename to web/i18n/locales/zh-Hant/login.json diff --git a/web/i18n/zh-Hant/oauth.json b/web/i18n/locales/zh-Hant/oauth.json similarity index 100% rename from web/i18n/zh-Hant/oauth.json rename to web/i18n/locales/zh-Hant/oauth.json diff --git a/web/i18n/zh-Hant/permission-keys.json b/web/i18n/locales/zh-Hant/permission-keys.json similarity index 100% rename from web/i18n/zh-Hant/permission-keys.json rename to web/i18n/locales/zh-Hant/permission-keys.json diff --git a/web/i18n/zh-Hant/permission.json b/web/i18n/locales/zh-Hant/permission.json similarity index 100% rename from web/i18n/zh-Hant/permission.json rename to web/i18n/locales/zh-Hant/permission.json diff --git a/web/i18n/zh-Hant/pipeline.json b/web/i18n/locales/zh-Hant/pipeline.json similarity index 100% rename from web/i18n/zh-Hant/pipeline.json rename to web/i18n/locales/zh-Hant/pipeline.json diff --git a/web/i18n/zh-Hant/plugin-tags.json b/web/i18n/locales/zh-Hant/plugin-tags.json similarity index 100% rename from web/i18n/zh-Hant/plugin-tags.json rename to web/i18n/locales/zh-Hant/plugin-tags.json diff --git a/web/i18n/zh-Hant/plugin-trigger.json b/web/i18n/locales/zh-Hant/plugin-trigger.json similarity index 100% rename from web/i18n/zh-Hant/plugin-trigger.json rename to web/i18n/locales/zh-Hant/plugin-trigger.json diff --git a/web/i18n/zh-Hant/plugin.json b/web/i18n/locales/zh-Hant/plugin.json similarity index 99% rename from web/i18n/zh-Hant/plugin.json rename to web/i18n/locales/zh-Hant/plugin.json index 90a936619cb..3ba7aaad2ec 100644 --- a/web/i18n/zh-Hant/plugin.json +++ b/web/i18n/locales/zh-Hant/plugin.json @@ -277,7 +277,6 @@ "readmeInfo.noReadmeAvailable": "沒有可用的自述文件", "readmeInfo.title": "自述檔", "requestAPlugin": "申请插件", - "requestSubmit": "請求或發佈", "search": "搜索", "searchCategories": "搜索類別", "searchInMarketplace": "在 Marketplace 中搜索", diff --git a/web/i18n/zh-Hant/register.json b/web/i18n/locales/zh-Hant/register.json similarity index 100% rename from web/i18n/zh-Hant/register.json rename to web/i18n/locales/zh-Hant/register.json diff --git a/web/i18n/zh-Hant/run-log.json b/web/i18n/locales/zh-Hant/run-log.json similarity index 100% rename from web/i18n/zh-Hant/run-log.json rename to web/i18n/locales/zh-Hant/run-log.json diff --git a/web/i18n/zh-Hant/share.json b/web/i18n/locales/zh-Hant/share.json similarity index 100% rename from web/i18n/zh-Hant/share.json rename to web/i18n/locales/zh-Hant/share.json diff --git a/web/i18n/zh-Hant/skill.json b/web/i18n/locales/zh-Hant/skill.json similarity index 82% rename from web/i18n/zh-Hant/skill.json rename to web/i18n/locales/zh-Hant/skill.json index fef432ad0ed..6680ec3e5b5 100644 --- a/web/i18n/zh-Hant/skill.json +++ b/web/i18n/locales/zh-Hant/skill.json @@ -1,5 +1,4 @@ { - "skillManagement.clearTags": "清除標籤", "skillManagement.create": "建立", "skillManagement.createFailed": "Skill 建立失敗。", "skillManagement.createSuccess": "草稿已建立。", @@ -13,12 +12,9 @@ "skillManagement.deleteFailed": "Skill 刪除失敗。", "skillManagement.deleteSuccess": "Skill 已刪除。", "skillManagement.detail.addMetadata": "新增元資料", - "skillManagement.detail.addMetadataDescription": "為這個 Markdown 檔案新增一個 frontmatter 欄位。", "skillManagement.detail.addTag": "新增標籤", - "skillManagement.detail.addTagDescription": "建立或綁定一個標籤到這個 Skill。新標籤會隨 Skill 中繼資料保存。", "skillManagement.detail.addTagSuccess": "標籤已新增。", "skillManagement.detail.back": "返回 Skills", - "skillManagement.detail.builder.applyFailed": "無法套用草稿更新。", "skillManagement.detail.builder.attach": "附加文件", "skillManagement.detail.builder.attachFailed": "無法附加文件。", "skillManagement.detail.builder.attachLimit": "您最多可以附加 10 個文件。", @@ -26,22 +22,12 @@ "skillManagement.detail.builder.attachUnsupported": "只能附加圖像、文字和文件文件。", "skillManagement.detail.builder.attachmentOnlyMessage": "使用附加文件來幫助提高這項技能。", "skillManagement.detail.builder.close": "關閉 Skill Builder", - "skillManagement.detail.builder.compatibleModelsOnly": "僅顯示相容的模型", "skillManagement.detail.builder.copyResponse": "複製回覆", "skillManagement.detail.builder.copySuccess": "響應已複製到剪貼簿。", "skillManagement.detail.builder.editIntro": "我已檢閱這個 Skill。你想調整什麼?", "skillManagement.detail.builder.exampleIssueTriage": "客戶問題分類", "skillManagement.detail.builder.exampleOnboarding": "新進員工入職指南", "skillManagement.detail.builder.exampleSalesFollowUp": "銷售線索跟進策略", - "skillManagement.detail.builder.followUpDisplayName": "使用退款批准作為顯示名稱", - "skillManagement.detail.builder.followUpNameIcon": "應用建議的名稱和圖標", - "skillManagement.detail.builder.fromMarketplace": "從 Marketplace 安裝", - "skillManagement.detail.builder.model": "GPT-4o", - "skillManagement.detail.builder.modelCredits.all": "所有學分", - "skillManagement.detail.builder.modelCredits.configure": "需要配置", - "skillManagement.detail.builder.modelCredits.exhausted": "配額已用完", - "skillManagement.detail.builder.modelProviderSettings": "模型供應商設定", - "skillManagement.detail.builder.modelSearch": "搜尋模型...", "skillManagement.detail.builder.modifyPlaceholder": "請 AI 修改這個 Skill...", "skillManagement.detail.builder.open": "開啟Skill Builder", "skillManagement.detail.builder.placeholder": "描述一下場景...", @@ -61,8 +47,6 @@ "skillManagement.detail.builder.thinkingUnavailable": "模型沒有返回思考內容。", "skillManagement.detail.builder.title": "Skill Builder", "skillManagement.detail.builder.tryExample": "嘗試一個例子", - "skillManagement.detail.builder.voice": "語音輸入", - "skillManagement.detail.builder.voiceUnavailable": "語音輸入尚不可用。", "skillManagement.detail.cancelAddMetadata": "取消新增元資料", "skillManagement.detail.closeFileTab": "關閉 {{name}}", "skillManagement.detail.closeVersions": "關閉版本", @@ -70,14 +54,10 @@ "skillManagement.detail.copyContentSuccess": "內容已複製到剪貼簿。", "skillManagement.detail.copyFile": "複製", "skillManagement.detail.copyFileSuccess": "文件準備好貼上。", - "skillManagement.detail.copyVersionId": "複製ID", - "skillManagement.detail.copyVersionIdSuccess": "ID 已複製到剪貼簿。", "skillManagement.detail.createFile": "新增檔案", - "skillManagement.detail.createFileDescription": "輸入相對於 Skill 根目錄的檔案路徑。", "skillManagement.detail.createFileMenu": "新增檔案...", "skillManagement.detail.createFileSuccess": "檔案已建立。", "skillManagement.detail.createFolder": "新增資料夾", - "skillManagement.detail.createFolderDescription": "輸入相對於 Skill 根目錄的資料夾路徑。", "skillManagement.detail.createFolderMenu": "新增資料夾...", "skillManagement.detail.createFolderSuccess": "資料夾已建立。", "skillManagement.detail.createdBy": "由 {{name}} 建立", @@ -100,10 +80,8 @@ "skillManagement.detail.fileMeta": "{{type}} · {{size}} 位元組", "skillManagement.detail.fileMissing": "檔案不存在。", "skillManagement.detail.fileOperationFailed": "檔案操作失敗。", - "skillManagement.detail.files": "檔案", "skillManagement.detail.latest": "最新", "skillManagement.detail.loadFailed": "Skill 載入失敗。", - "skillManagement.detail.loadingTags": "正在加載標籤...", "skillManagement.detail.markdownLiveMode": "即時預覽", "skillManagement.detail.markdownSourceMode": "原始碼編輯", "skillManagement.detail.metadataKey": "姓名", @@ -113,10 +91,7 @@ "skillManagement.detail.nameThisVersion": "給這個版本命名", "skillManagement.detail.noFileSelected": "選擇一個檔案進行預覽。", "skillManagement.detail.noFiles": "暫無檔案。", - "skillManagement.detail.noSearchResults": "沒有符合的檔案。", - "skillManagement.detail.noSelectableTags": "沒有可用的標籤。", "skillManagement.detail.noVersions": "暫無發布版本。", - "skillManagement.detail.pasteFile": "貼上", "skillManagement.detail.pasteFileSuccess": "文件已貼上。", "skillManagement.detail.previewUnsupported": "該檔案暫不支援預覽。", "skillManagement.detail.publish": "發布", @@ -128,9 +103,7 @@ "skillManagement.detail.publishUpdate": "發佈更新", "skillManagement.detail.published": "已發布", "skillManagement.detail.publishedAt": "{{time}}發佈", - "skillManagement.detail.publishedVersion": "已發布#{{number}}", "skillManagement.detail.publishing": "發佈中...", - "skillManagement.detail.readonly": "唯讀", "skillManagement.detail.referenceFiles.confirm": "Enter 確認", "skillManagement.detail.referenceFiles.empty": "暫無可引用的檔案。", "skillManagement.detail.referenceFiles.livePlaceholder": "撰寫 Agent 的指示。輸入 / 以引用檔案。", @@ -138,14 +111,10 @@ "skillManagement.detail.referenceFiles.title": "引用檔案", "skillManagement.detail.referencedBy_one": "{{count}} 個 Agent 正在使用", "skillManagement.detail.referencedBy_other": "{{count}} 個 Agent 正在使用", - "skillManagement.detail.removeMetadata": "刪除 {{name}} 元資料", "skillManagement.detail.removeTag": "移除 {{tag}}", "skillManagement.detail.removeTagSuccess": "標籤已移除。", - "skillManagement.detail.renameFile": "重新命名檔案", - "skillManagement.detail.renameFileDescription": "輸入相對於 Skill 根目錄的新路徑。", "skillManagement.detail.renameFileSuccess": "檔案已重新命名。", "skillManagement.detail.renameSkillSuccess": "Skill 已重新命名。", - "skillManagement.detail.renameVersion": "重新命名", "skillManagement.detail.renameVersionFailed": "版本重新命名失敗。", "skillManagement.detail.renameVersionPrompt": "版本名称", "skillManagement.detail.renameVersionSuccess": "版本已重新命名。", @@ -155,17 +124,14 @@ "skillManagement.detail.restoreVersionConfirmTitle": "恢復這個版本嗎?", "skillManagement.detail.restoreVersionFailed": "版本還原失敗。", "skillManagement.detail.restoreVersionSuccess": "版本已還原到草稿。", - "skillManagement.detail.save": "儲存", "skillManagement.detail.saveConflictConfirmDescription": "該技能已在其他地方更新。載入最新草稿以繼續編輯。您當前的更改將被放棄。", "skillManagement.detail.saveConflictConfirmTitle": "加載最新的草稿?", "skillManagement.detail.saveConflictReload": "載入最新草稿", "skillManagement.detail.saveConflictStatus": "檢測到衝突。載入最新草稿以繼續編輯。", "skillManagement.detail.saveFailed": "檔案儲存失敗。", - "skillManagement.detail.saveSuccess": "檔案已儲存。", "skillManagement.detail.saved": "已儲存", "skillManagement.detail.savedAt": "{{time}}已儲存", "skillManagement.detail.saving": "儲存中...", - "skillManagement.detail.searchFiles": "搜尋檔案", "skillManagement.detail.showMoreReferences": "顯示更多 {{count}}", "skillManagement.detail.skillDescriptionPlaceholder": "描述這個 Skill 的用途,以及 Agent 應在何時使用它。", "skillManagement.detail.skillNamePlaceholder": "使用小寫字母、數字和連字號。", @@ -177,16 +143,12 @@ "skillManagement.detail.uploadConflict": "同名的檔案已存在。", "skillManagement.detail.uploadFailureSummary": "{{total}} 檔案中的 {{failed}} 失敗", "skillManagement.detail.uploadFailureTitle": "部分上傳失敗", - "skillManagement.detail.uploadFile": "上傳檔案", - "skillManagement.detail.uploadFileFailed": "檔案上傳失敗。", "skillManagement.detail.uploadFileSuccess": "檔案已上傳。", "skillManagement.detail.uploadFilesButton": "上傳{{count}}文件", "skillManagement.detail.uploadFilesFailedStatus_one": "{{count}} 檔案上傳失敗。", "skillManagement.detail.uploadFilesFailedStatus_other": "{{count}} 檔案上傳失敗。", "skillManagement.detail.uploadFilesMenu": "上傳檔案...", "skillManagement.detail.uploadFilesProgress": "正在上傳{{completed}}/{{total}}", - "skillManagement.detail.uploadFilesResult": "{{uploaded}} 已上傳 · {{failed}} 失敗", - "skillManagement.detail.uploadFilesStatus": "上傳狀態", "skillManagement.detail.uploadInvalidExtension": "不支援此文件類型。", "skillManagement.detail.uploadInvalidName": "名稱無效。", "skillManagement.detail.uploadInvalidPath": "文件路徑無效。", @@ -206,7 +168,6 @@ "skillManagement.detail.uploadWillReplace": "將替換現有文件", "skillManagement.detail.uploadWillUseName": "將上傳為 {{name}}", "skillManagement.detail.versionHistory": "開啟版本歷史", - "skillManagement.detail.versionMeta": "{{hash}} · {{time}}", "skillManagement.detail.versionPublishNote": "發行說明", "skillManagement.detail.versionPublishNotePlaceholder": "描述一下這個變化", "skillManagement.detail.versionPublishedMeta": "{{time}} · {{name}}", @@ -227,8 +188,6 @@ "skillManagement.errors.missingSkillMd": "這個包沒有SKILL.md。新增檔案並再次匯入。", "skillManagement.errors.nameConflict": "名為「{{name}}」的技能已存在。將其重新命名為SKILL.md並再次匯入。", "skillManagement.errors.workspaceLimit": "您的工作區已達到 500 項技能的限制。刪除一項技能以新增技能。", - "skillManagement.exportDialog.description": "此操作將匯出此 Skill 目前已儲存的草稿。如果編輯器中有尚未儲存的變更,請先儲存再匯出。", - "skillManagement.exportDialog.title": "匯出目前已儲存的草稿", "skillManagement.import": "匯入", "skillManagement.importFailed": "Skill 匯入失敗。", "skillManagement.importSuccess": "Skill 已匯入。", @@ -236,12 +195,10 @@ "skillManagement.loadingError": "Skill 載入失敗", "skillManagement.moreActions": "{{name}} 的更多操作", "skillManagement.noDescription": "還沒有描述", - "skillManagement.noTags": "暫無標籤", "skillManagement.publishedAt": "發布於 {{time}}", "skillManagement.referenceCount_one": "{{count}} 個 Agent", "skillManagement.referenceCount_other": "{{count}} 個 Agent", "skillManagement.searchLabel": "搜尋 Skill", "skillManagement.searchPlaceholder": "按名稱或描述搜尋", - "skillManagement.tags": "標籤", "skillManagement.title": "Skills" } diff --git a/web/i18n/zh-Hant/snippet.json b/web/i18n/locales/zh-Hant/snippet.json similarity index 100% rename from web/i18n/zh-Hant/snippet.json rename to web/i18n/locales/zh-Hant/snippet.json diff --git a/web/i18n/zh-Hant/time.json b/web/i18n/locales/zh-Hant/time.json similarity index 100% rename from web/i18n/zh-Hant/time.json rename to web/i18n/locales/zh-Hant/time.json diff --git a/web/i18n/zh-Hant/tools.json b/web/i18n/locales/zh-Hant/tools.json similarity index 100% rename from web/i18n/zh-Hant/tools.json rename to web/i18n/locales/zh-Hant/tools.json diff --git a/web/i18n/zh-Hant/workflow.json b/web/i18n/locales/zh-Hant/workflow.json similarity index 99% rename from web/i18n/zh-Hant/workflow.json rename to web/i18n/locales/zh-Hant/workflow.json index 5cb96d968d3..16980383bed 100644 --- a/web/i18n/zh-Hant/workflow.json +++ b/web/i18n/locales/zh-Hant/workflow.json @@ -460,8 +460,6 @@ "nodes.agent.pluginNotInstalledDesc": "此集成安裝自 GitHub。請前往集成重新安裝", "nodes.agent.roster.change": "更換", "nodes.agent.roster.editInConsole": "在 Agents 中編輯", - "nodes.agent.roster.editInConsoleDisabled": "編輯 Agent 需要「編輯」及以上角色。", - "nodes.agent.roster.editInConsoleDisabledRbac": "只有擁有「編輯」及以上角色或「管理 Agents」權限的成員,才能編輯 Agent。", "nodes.agent.roster.inlineSetup.description": "僅存在於此節點的一次性設定", "nodes.agent.roster.inlineSetup.name": "內聯設定", "nodes.agent.roster.inlineSetup.type": "僅在此節點中", diff --git a/web/i18n/metadata.ts b/web/i18n/metadata.ts new file mode 100644 index 00000000000..4b9e62cb38a --- /dev/null +++ b/web/i18n/metadata.ts @@ -0,0 +1,28 @@ +import type { Locale } from './locale' + +export type PluginLanguage = 'en_US' | 'zh_Hans' | 'ja_JP' | 'pt_BR' + +export const getPluginLanguage = (locale: Locale): PluginLanguage => { + switch (locale) { + case 'zh-Hans': + return 'zh_Hans' + case 'ja-JP': + return 'ja_JP' + case 'pt-BR': + return 'pt_BR' + default: + return 'en_US' + } +} + +export const getModelLanguage = (locale: Locale) => locale.replace('-', '_') + +export const renderI18nObject = ( + obj: Record | null | undefined, + language: string, +) => { + if (!obj) return '' + if (obj?.[language]) return obj[language] + if (obj?.en_US) return obj.en_US + return Object.values(obj).find((value): value is string => !!value) || '' +} diff --git a/web/i18n-config/resources.ts b/web/i18n/resources.ts similarity index 64% rename from web/i18n-config/resources.ts rename to web/i18n/resources.ts index 641932b838f..98091326fb0 100644 --- a/web/i18n-config/resources.ts +++ b/web/i18n/resources.ts @@ -1,40 +1,40 @@ -import type agentV2 from '../i18n/en-US/agent-v-2.json' -import type appAnnotation from '../i18n/en-US/app-annotation.json' -import type appApi from '../i18n/en-US/app-api.json' -import type appDebug from '../i18n/en-US/app-debug.json' -import type appLog from '../i18n/en-US/app-log.json' -import type appOverview from '../i18n/en-US/app-overview.json' -import type app from '../i18n/en-US/app.json' -import type billing from '../i18n/en-US/billing.json' -import type common from '../i18n/en-US/common.json' -import type custom from '../i18n/en-US/custom.json' -import type datasetCreation from '../i18n/en-US/dataset-creation.json' -import type datasetDocuments from '../i18n/en-US/dataset-documents.json' -import type datasetHitTesting from '../i18n/en-US/dataset-hit-testing.json' -import type datasetPipeline from '../i18n/en-US/dataset-pipeline.json' -import type datasetSettings from '../i18n/en-US/dataset-settings.json' -import type dataset from '../i18n/en-US/dataset.json' -import type deployments from '../i18n/en-US/deployments.json' -import type deviceFlow from '../i18n/en-US/device-flow.json' -import type education from '../i18n/en-US/education.json' -import type explore from '../i18n/en-US/explore.json' -import type layout from '../i18n/en-US/layout.json' -import type login from '../i18n/en-US/login.json' -import type oauth from '../i18n/en-US/oauth.json' -import type permissionKeys from '../i18n/en-US/permission-keys.json' -import type permission from '../i18n/en-US/permission.json' -import type pipeline from '../i18n/en-US/pipeline.json' -import type pluginTags from '../i18n/en-US/plugin-tags.json' -import type pluginTrigger from '../i18n/en-US/plugin-trigger.json' -import type plugin from '../i18n/en-US/plugin.json' -import type register from '../i18n/en-US/register.json' -import type runLog from '../i18n/en-US/run-log.json' -import type share from '../i18n/en-US/share.json' -import type skill from '../i18n/en-US/skill.json' -import type snippet from '../i18n/en-US/snippet.json' -import type time from '../i18n/en-US/time.json' -import type tools from '../i18n/en-US/tools.json' -import type workflow from '../i18n/en-US/workflow.json' +import type agentV2 from './locales/en-US/agent-v-2.json' +import type appAnnotation from './locales/en-US/app-annotation.json' +import type appApi from './locales/en-US/app-api.json' +import type appDebug from './locales/en-US/app-debug.json' +import type appLog from './locales/en-US/app-log.json' +import type appOverview from './locales/en-US/app-overview.json' +import type app from './locales/en-US/app.json' +import type billing from './locales/en-US/billing.json' +import type common from './locales/en-US/common.json' +import type custom from './locales/en-US/custom.json' +import type datasetCreation from './locales/en-US/dataset-creation.json' +import type datasetDocuments from './locales/en-US/dataset-documents.json' +import type datasetHitTesting from './locales/en-US/dataset-hit-testing.json' +import type datasetPipeline from './locales/en-US/dataset-pipeline.json' +import type datasetSettings from './locales/en-US/dataset-settings.json' +import type dataset from './locales/en-US/dataset.json' +import type deployments from './locales/en-US/deployments.json' +import type deviceFlow from './locales/en-US/device-flow.json' +import type education from './locales/en-US/education.json' +import type explore from './locales/en-US/explore.json' +import type layout from './locales/en-US/layout.json' +import type login from './locales/en-US/login.json' +import type oauth from './locales/en-US/oauth.json' +import type permissionKeys from './locales/en-US/permission-keys.json' +import type permission from './locales/en-US/permission.json' +import type pipeline from './locales/en-US/pipeline.json' +import type pluginTags from './locales/en-US/plugin-tags.json' +import type pluginTrigger from './locales/en-US/plugin-trigger.json' +import type plugin from './locales/en-US/plugin.json' +import type register from './locales/en-US/register.json' +import type runLog from './locales/en-US/run-log.json' +import type share from './locales/en-US/share.json' +import type skill from './locales/en-US/skill.json' +import type snippet from './locales/en-US/snippet.json' +import type time from './locales/en-US/time.json' +import type tools from './locales/en-US/tools.json' +import type workflow from './locales/en-US/workflow.json' import { kebabCase } from 'string-ts' type RawResources = { @@ -83,7 +83,6 @@ type PluralBaseResources = { 'agentDetail.access.workflow.nodeCount': string 'agentDetail.configure.buildDraft.changesToApply': string 'agentDetail.configure.publishImpact.workflowCount': string - 'skillManagement.detail.uploadFilesFailedStatus': string } app: { 'accessControlDialog.groups': string diff --git a/web/i18n-config/server.ts b/web/i18n/server.ts similarity index 65% rename from web/i18n-config/server.ts rename to web/i18n/server.ts index fe59a232774..aa6fab82c08 100644 --- a/web/i18n-config/server.ts +++ b/web/i18n/server.ts @@ -8,9 +8,10 @@ import resourcesToBackend from 'i18next-resources-to-backend' import Negotiator from 'negotiator' import { cache } from 'react' import { initReactI18next } from 'react-i18next/initReactI18next' +import { LOCALE_COOKIE_NAME } from '@/config' import { cookies, headers } from '@/next/headers' -import { i18n } from '.' import { loadI18nResource } from './load-resource' +import { canonicalizeLanguageTag, defaultLocale, supportedLocales } from './locale' import { namespacesInFileName } from './resources' import { getInitOptions } from './settings' @@ -42,31 +43,18 @@ export async function getTranslation(lng: Locale, ns?: T) { } export const getLocaleOnServer = cache(async (): Promise => { - const locales: string[] = i18n.locales + const localeCookie = (await cookies()).get(LOCALE_COOKIE_NAME) + const requestedLanguages = localeCookie?.value + ? [localeCookie.value] + : new Negotiator({ + headers: { 'accept-language': (await headers()).get('accept-language') ?? '' }, + }).languages() + const languages = requestedLanguages.flatMap((language) => { + const canonical = canonicalizeLanguageTag(language) + return canonical ? [canonical] : [] + }) - let languages: string[] | undefined - // get locale from cookie - const localeCookie = (await cookies()).get('locale') - languages = localeCookie?.value ? [localeCookie.value] : [] - - if (!languages.length) { - // Negotiator expects plain object so we need to transform headers - const negotiatorHeaders: Record = {} - ;(await headers()).forEach((value, key) => (negotiatorHeaders[key] = value)) - // Use negotiator and intl-localematcher to get best locale - languages = new Negotiator({ headers: negotiatorHeaders }).languages() - } - - // Validate languages - if ( - !Array.isArray(languages) || - languages.length === 0 || - !languages.every((lang) => typeof lang === 'string' && /^[\w-]+$/.test(lang)) - ) - languages = [i18n.defaultLocale] - - // match locale - return match(languages, locales, i18n.defaultLocale) as Locale + return match(languages, supportedLocales, defaultLocale) as Locale }) export const getResources = cache(async (lng: Locale): Promise => { diff --git a/web/i18n-config/settings.ts b/web/i18n/settings.ts similarity index 84% rename from web/i18n-config/settings.ts rename to web/i18n/settings.ts index 04761052ceb..a6b7400ab6f 100644 --- a/web/i18n-config/settings.ts +++ b/web/i18n/settings.ts @@ -1,11 +1,12 @@ import type { InitOptions } from 'i18next' +import { defaultLocale } from './locale' import { defaultNS, namespaces } from './resources' export function getInitOptions(): InitOptions { return { // We do not have en for fallback load: 'currentOnly', - fallbackLng: 'en-US', + fallbackLng: defaultLocale, partialBundledLanguages: true, defaultNS, enableSelector: 'optimize', diff --git a/web/models/access-control.ts b/web/models/access-control.ts index 72b7a1a4457..346c351b97d 100644 --- a/web/models/access-control.ts +++ b/web/models/access-control.ts @@ -1,4 +1,4 @@ -import type { AccessControlTemplateLanguage } from '@/i18n-config/language' +import type { AccessControlTemplateLanguage } from '@/i18n/language' export const SubjectType = { GROUP: 'group', diff --git a/web/models/common.ts b/web/models/common.ts index 692cc7c9b0a..1fbf40d6c9a 100644 --- a/web/models/common.ts +++ b/web/models/common.ts @@ -1,6 +1,6 @@ import type { GetAccountProfileResponse } from '@dify/contracts/api/console/account/types.gen' import type { Role } from './access-control' -import type { I18nText } from '@/i18n-config/language' +import type { I18nText } from '@/i18n/language' import type { Model } from '@/types/app' export type CommonResponse = { diff --git a/web/models/share.ts b/web/models/share.ts index 28463103d04..95116aaec1b 100644 --- a/web/models/share.ts +++ b/web/models/share.ts @@ -1,5 +1,5 @@ import type { AppMode } from '@dify/contracts/api/web/types.gen' -import type { Locale } from '@/i18n-config' +import type { Locale } from '@/i18n' import type { AppIconType } from '@/types/app' export type ConversationItem = { diff --git a/web/package.json b/web/package.json index 850780cff44..4f87e9aff96 100644 --- a/web/package.json +++ b/web/package.json @@ -5,8 +5,8 @@ "type": "module", "imports": { "#i18n": { - "react-server": "./i18n-config/lib.server.ts", - "default": "./i18n-config/lib.client.ts" + "react-server": "./i18n/lib.server.ts", + "default": "./i18n/lib.client.ts" } }, "scripts": { @@ -21,7 +21,6 @@ "gen-icons": "pnpm --filter @dify/iconify-collections generate && node ./scripts/gen-icons.mjs && pnpm -w exec vp check --fix web/app/components/base/icons/src/", "i18n:check": "tsx ./scripts/check-i18n.js", "i18n:migrate-selectors": "tsx ./scripts/migrate-i18n-selectors.ts", - "i18n:prune-unused": "tsx ./scripts/prune-unused-i18n.ts", "lint:a11y": "node ./scripts/lint-a11y.mjs", "lint:tss": "tsslint --project tsconfig.json", "preinstall": "npx only-allow pnpm", @@ -38,7 +37,6 @@ "@amplitude/analytics-browser": "catalog:", "@amplitude/plugin-session-replay-browser": "catalog:", "@base-ui/react": "catalog:", - "@emoji-mart/data": "catalog:", "@floating-ui/react": "catalog:", "@formatjs/intl-localematcher": "catalog:", "@heroicons/react": "catalog:", @@ -80,10 +78,10 @@ "embla-carousel-autoplay": "catalog:", "embla-carousel-fade": "catalog:", "embla-carousel-react": "catalog:", - "emoji-mart": "catalog:", "es-toolkit": "catalog:", "fast-deep-equal": "catalog:", "foxact": "catalog:", + "frimousse": "catalog:", "fuse.js": "catalog:", "hast-util-to-jsx-runtime": "catalog:", "html-entities": "catalog:", diff --git a/web/plugins/eslint/rules/no-extra-keys.js b/web/plugins/eslint/rules/no-extra-keys.js index 024693417e5..147bd55dca5 100644 --- a/web/plugins/eslint/rules/no-extra-keys.js +++ b/web/plugins/eslint/rules/no-extra-keys.js @@ -18,7 +18,7 @@ export default { if (!filename.endsWith('.json')) return const parts = normalize(filename).split(sep) - // e.g., i18n/ar-TN/common.json -> jsonFile = common.json, lang = ar-TN + // e.g., i18n/locales/ar-TN/common.json -> jsonFile = common.json, lang = ar-TN const jsonFile = parts.at(-1) const lang = parts.at(-2) @@ -31,7 +31,7 @@ export default { try { currentJson = JSON.parse(sourceCode.text) // Look for the same filename in en-US folder - // e.g., i18n/ar-TN/common.json -> i18n/en-US/common.json + // e.g., i18n/locales/ar-TN/common.json -> i18n/locales/en-US/common.json const englishFilePath = path.join(path.dirname(filename), '..', 'en-US', jsonFile ?? '') englishJson = JSON.parse(fs.readFileSync(englishFilePath, 'utf8')) } catch (error) { diff --git a/web/scripts/__tests__/prune-unused-i18n.spec.ts b/web/plugins/vite/__tests__/i18n-graph.spec.ts similarity index 76% rename from web/scripts/__tests__/prune-unused-i18n.spec.ts rename to web/plugins/vite/__tests__/i18n-graph.spec.ts index 7781794dfc2..f2f66f305ba 100644 --- a/web/scripts/__tests__/prune-unused-i18n.spec.ts +++ b/web/plugins/vite/__tests__/i18n-graph.spec.ts @@ -1,10 +1,12 @@ +// @vitest-environment node import { mkdirSync, mkdtempSync, rmSync, writeFileSync } from 'node:fs' import { tmpdir } from 'node:os' import path from 'node:path' import { afterEach, beforeEach, describe, expect, it } from 'vite-plus/test' -import { analyzeUnusedTranslations, removeUnusedTranslations } from '../i18n-prune/core' +import { checkTranslationGraph } from '../i18n-prune/graph' let webRoot: string +let modules: Map function writeJson(relativePath: string, value: Record) { mkdirSync(path.dirname(path.join(webRoot, relativePath)), { recursive: true }) @@ -12,23 +14,20 @@ function writeJson(relativePath: string, value: Record) { } function writeSource(relativePath: string, content: string) { + modules.set(path.join(webRoot, relativePath), content) mkdirSync(path.dirname(path.join(webRoot, relativePath)), { recursive: true }) writeFileSync(path.join(webRoot, relativePath), content, 'utf8') } -function sortedUnusedKeysByNamespace( - result: Awaited>, -) { +function sortedUnusedKeysByNamespace(result: ReturnType) { return Object.fromEntries( - Object.entries(result.unusedKeysByNamespace).map(([namespace, keys]) => [ - namespace, - [...keys].sort(), - ]), + Object.entries(result.unused).map(([namespace, keys]) => [namespace, [...keys].sort()]), ) } -describe('prune-unused-i18n', () => { +describe('translation graph analysis', () => { beforeEach(() => { + modules = new Map() webRoot = mkdtempSync(path.join(tmpdir(), 'dify-i18n-prune-')) writeSource('placeholder.ts', '') }) @@ -38,9 +37,101 @@ describe('prune-unused-i18n', () => { }) describe('Usage Analysis', () => { - it('should keep flat keys selected by t and Trans', async () => { + it('resolves generic metadata selectors using the namespace at each call site', () => { + writeJson('i18n/locales/en-US/app.json', { resetPassword: 'Unused app key' }) + writeJson('i18n/locales/en-US/login.json', { + resetPassword: 'Reset password', + unused: 'Unused', + }) + writeJson('i18n/locales/en-US/common.json', { title: 'Title', unused: 'Unused' }) + writeSource( + 'src/metadata.ts', + ` + type Namespace = 'app' | 'login' | 'common' + type SelectorParam = (source: Record) => string + export declare function getRouteMetadata(namespace: T, selector: SelectorParam): string + `, + ) + writeSource( + 'src/page.ts', + ` + import { getRouteMetadata } from './metadata' + export const login = getRouteMetadata('login', $ => $.resetPassword) + const getTitle = getRouteMetadata + export const common = getTitle<'common'>('common', $ => $.title) + `, + ) + + expect(checkTranslationGraph(webRoot, modules).unused).toEqual({ + app: ['resetPassword'], + login: ['unused'], + common: ['unused'], + }) + }) + + it('keeps possible matches across namespaces when a selector namespace cannot be resolved', () => { + writeJson('i18n/locales/en-US/app.json', { title: 'App', unused: 'Unused' }) + writeJson('i18n/locales/en-US/login.json', { title: 'Login', unused: 'Unused' }) + writeSource( + 'src/metadata.ts', + ` + type SelectorParam = (source: Record) => string + declare function metadata(namespace: T, selector: SelectorParam): string + export function page(namespace: T) { + return metadata(namespace, $ => $.title) + } + `, + ) + + expect(checkTranslationGraph(webRoot, modules).unused).toEqual({ + app: ['unused'], + login: ['unused'], + }) + }) + + it('protects every possible namespace when the namespace is dynamic', () => { + writeJson('i18n/locales/en-US/app.json', { used: 'App' }) + writeJson('i18n/locales/en-US/common.json', { used: 'Common' }) + writeSource( + 'src/dynamic-namespace.ts', + ` + import { useTranslation } from 'react-i18next' + export function label(namespace: string, key: string) { + const { t } = useTranslation(['app', namespace]) + return t($ => $[key]) + } + `, + ) + + const result = checkTranslationGraph(webRoot, modules) + + expect(result.protectedNamespaces).toEqual(['app', 'common']) + expect(result.unused).toEqual({}) + }) + + it('matches dynamically namespaced keys without keeping unrelated keys', () => { + writeJson('i18n/locales/en-US/app.json', { used: 'App', unused: 'Unused' }) + writeJson('i18n/locales/en-US/common.json', { used: 'Common', unused: 'Unused' }) + writeSource( + 'src/dynamic-namespace.ts', + ` + import { useTranslation } from 'react-i18next' + export function label(namespace: string) { + const { t } = useTranslation() + return t(\`\${namespace}:used\`) + } + `, + ) + + expect(checkTranslationGraph(webRoot, modules).unused).toEqual({ + app: ['unused'], + common: ['unused'], + }) + }) + + it('should keep flat keys selected by t and Trans', () => { // Arrange - writeJson('i18n/en-US/app.json', { + writeJson('i18n/locales/en-US/app.json', { 'account.changeEmail.title': 'Change email', 'account.changeEmail.description': 'Description', 'account.changeEmail.unused': 'Unused', @@ -67,7 +158,7 @@ describe('prune-unused-i18n', () => { ) // Act - const result = await analyzeUnusedTranslations({ webRoot }) + const result = checkTranslationGraph(webRoot, modules) // Assert expect(sortedUnusedKeysByNamespace(result)).toEqual({ @@ -75,12 +166,12 @@ describe('prune-unused-i18n', () => { }) }) - it('should use the default namespace for unresolved selectors', async () => { + it('should use the default namespace for unresolved selectors', () => { // Arrange - writeJson('i18n/en-US/app.json', { + writeJson('i18n/locales/en-US/app.json', { 'dynamic.app': 'Dynamic app key', }) - writeJson('i18n/en-US/common.json', { + writeJson('i18n/locales/en-US/common.json', { 'unused.common': 'Unused common key', }) writeSource( @@ -96,7 +187,7 @@ describe('prune-unused-i18n', () => { ) // Act - const result = await analyzeUnusedTranslations({ webRoot }) + const result = checkTranslationGraph(webRoot, modules) // Assert expect(result.protectedNamespaces).toEqual(['app']) @@ -105,9 +196,9 @@ describe('prune-unused-i18n', () => { }) }) - it('should resolve selectors stored in variables', async () => { + it('should resolve selectors stored in variables', () => { // Arrange - writeJson('i18n/en-US/app.json', { + writeJson('i18n/locales/en-US/app.json', { members_one: '1 member', members_other: '{{count}} members', unused: 'Unused', @@ -125,7 +216,7 @@ describe('prune-unused-i18n', () => { ) // Act - const result = await analyzeUnusedTranslations({ webRoot }) + const result = checkTranslationGraph(webRoot, modules) // Assert expect(result.protectedNamespaces).toEqual([]) @@ -134,9 +225,9 @@ describe('prune-unused-i18n', () => { }) }) - it('should resolve finite selector maps with computed keys and optional entries', async () => { + it('should resolve finite selector maps with computed keys and optional entries', () => { // Arrange - writeJson('i18n/en-US/app.json', { + writeJson('i18n/locales/en-US/app.json', { first: 'First', second: 'Second', unused: 'Unused', @@ -166,7 +257,7 @@ describe('prune-unused-i18n', () => { ) // Act - const result = await analyzeUnusedTranslations({ webRoot }) + const result = checkTranslationGraph(webRoot, modules) // Assert expect(result.protectedNamespaces).toEqual([]) @@ -175,9 +266,9 @@ describe('prune-unused-i18n', () => { }) }) - it('should protect a selector map namespace when any candidate is unresolved', async () => { + it('should protect a selector map namespace when any candidate is unresolved', () => { // Arrange - writeJson('i18n/en-US/app.json', { + writeJson('i18n/locales/en-US/app.json', { hidden: 'Potentially used by the dynamic selector', used: 'Used', }) @@ -199,16 +290,16 @@ describe('prune-unused-i18n', () => { ) // Act - const result = await analyzeUnusedTranslations({ webRoot }) + const result = checkTranslationGraph(webRoot, modules) // Assert expect(result.protectedNamespaces).toEqual(['app']) expect(sortedUnusedKeysByNamespace(result)).toEqual({}) }) - it('should protect known selector properties that dynamic entries can override', async () => { + it('should protect known selector properties that dynamic entries can override', () => { // Arrange - writeJson('i18n/en-US/app.json', { + writeJson('i18n/locales/en-US/app.json', { hidden: 'Potentially used by the dynamic selector', used: 'Used', }) @@ -239,16 +330,16 @@ describe('prune-unused-i18n', () => { ) // Act - const result = await analyzeUnusedTranslations({ webRoot }) + const result = checkTranslationGraph(webRoot, modules) // Assert expect(result.protectedNamespaces).toEqual(['app']) expect(sortedUnusedKeysByNamespace(result)).toEqual({}) }) - it('should resolve statically computed selector map properties', async () => { + it('should resolve statically computed selector map properties', () => { // Arrange - writeJson('i18n/en-US/app.json', { + writeJson('i18n/locales/en-US/app.json', { unused: 'Unused', used: 'Used', }) @@ -270,7 +361,7 @@ describe('prune-unused-i18n', () => { ) // Act - const result = await analyzeUnusedTranslations({ webRoot }) + const result = checkTranslationGraph(webRoot, modules) // Assert expect(result.protectedNamespaces).toEqual([]) @@ -279,9 +370,9 @@ describe('prune-unused-i18n', () => { }) }) - it('should ignore dotted literals passed to unrelated generic functions', async () => { + it('should ignore dotted literals passed to unrelated generic functions', () => { // Arrange - writeJson('i18n/en-US/app.json', { + writeJson('i18n/locales/en-US/app.json', { used: 'Used', unused: 'Unused', }) @@ -301,18 +392,17 @@ describe('prune-unused-i18n', () => { ) // Act - const result = await analyzeUnusedTranslations({ webRoot }) + const result = checkTranslationGraph(webRoot, modules) // Assert - expect(result.unresolvedUsages).toEqual([]) expect(sortedUnusedKeysByNamespace(result)).toEqual({ app: ['unused'], }) }) - it('should analyze translation adapter consumers without protecting their namespace', async () => { + it('should analyze translation adapter consumers without protecting their namespace', () => { // Arrange - writeJson('i18n/en-US/app.json', { + writeJson('i18n/locales/en-US/app.json', { used: 'Used', unused: 'Unused', }) @@ -337,7 +427,7 @@ describe('prune-unused-i18n', () => { ) // Act - const result = await analyzeUnusedTranslations({ webRoot }) + const result = checkTranslationGraph(webRoot, modules) // Assert expect(result.protectedNamespaces).toEqual([]) @@ -346,12 +436,12 @@ describe('prune-unused-i18n', () => { }) }) - it('should infer the namespace of a typed destructured translation parameter', async () => { + it('should infer the namespace of a typed destructured translation parameter', () => { // Arrange - writeJson('i18n/en-US/app.json', { + writeJson('i18n/locales/en-US/app.json', { 'unused.app': 'Unused app key', }) - writeJson('i18n/en-US/deployments.json', { + writeJson('i18n/locales/en-US/deployments.json', { unused: 'Unused deployment key', 'versions.deployTo': 'Deploy to {{name}}', }) @@ -372,24 +462,23 @@ describe('prune-unused-i18n', () => { ) // Act - const result = await analyzeUnusedTranslations({ webRoot }) + const result = checkTranslationGraph(webRoot, modules) // Assert expect(result.protectedNamespaces).toEqual([]) - expect(result.unresolvedUsages).toEqual([]) expect(sortedUnusedKeysByNamespace(result)).toEqual({ app: ['unused.app'], deployments: ['unused'], }) }) - it('should prefer checker namespaces for a translation parameter named t', async () => { + it('should prefer checker namespaces for a translation parameter named t', () => { // Arrange - writeJson('i18n/en-US/agent-v-2.json', { + writeJson('i18n/locales/en-US/agent-v-2.json', { 'agentDetail.used': 'Used agent key', 'agentDetail.unused': 'Unused agent key', }) - writeJson('i18n/en-US/app.json', { + writeJson('i18n/locales/en-US/app.json', { 'unused.app': 'Unused app key', }) writeSource( @@ -408,25 +497,24 @@ describe('prune-unused-i18n', () => { ) // Act - const result = await analyzeUnusedTranslations({ webRoot }) + const result = checkTranslationGraph(webRoot, modules) // Assert expect(result.protectedNamespaces).toEqual([]) - expect(result.unresolvedUsages).toEqual([]) expect(sortedUnusedKeysByNamespace(result)).toEqual({ agentV2: ['agentDetail.unused'], app: ['unused.app'], }) }) - it('should infer branded TFunction namespaces for direct and destructured parameters', async () => { + it('should infer branded TFunction namespaces for direct and destructured parameters', () => { // Arrange - writeJson('i18n/en-US/agent-v-2.json', { + writeJson('i18n/locales/en-US/agent-v-2.json', { 'agentDetail.direct': 'Direct use', 'agentDetail.destructured': 'Destructured use', 'agentDetail.unused': 'Unused agent key', }) - writeJson('i18n/en-US/app.json', { + writeJson('i18n/locales/en-US/app.json', { 'unused.app': 'Unused app key', }) writeSource( @@ -448,24 +536,23 @@ describe('prune-unused-i18n', () => { ) // Act - const result = await analyzeUnusedTranslations({ webRoot }) + const result = checkTranslationGraph(webRoot, modules) // Assert expect(result.protectedNamespaces).toEqual([]) - expect(result.unresolvedUsages).toEqual([]) expect(sortedUnusedKeysByNamespace(result)).toEqual({ agentV2: ['agentDetail.unused'], app: ['unused.app'], }) }) - it('should ignore typed selector forwarding inside an adapter block body', async () => { + it('should ignore typed selector forwarding inside an adapter block body', () => { // Arrange - writeJson('i18n/en-US/app.json', { + writeJson('i18n/locales/en-US/app.json', { unused: 'Unused', used: 'Used', }) - writeJson('i18n/en-US/common.json', { + writeJson('i18n/locales/en-US/common.json', { 'unused.common': 'Unused common key', }) writeSource( @@ -494,7 +581,7 @@ describe('prune-unused-i18n', () => { ) // Act - const result = await analyzeUnusedTranslations({ webRoot }) + const result = checkTranslationGraph(webRoot, modules) // Assert expect(result.protectedNamespaces).toEqual([]) @@ -504,9 +591,9 @@ describe('prune-unused-i18n', () => { }) }) - it('should ignore a named adapter forwarding its typed selector parameter', async () => { + it('should ignore a named adapter forwarding its typed selector parameter', () => { // Arrange - writeJson('i18n/en-US/workflow.json', { + writeJson('i18n/locales/en-US/workflow.json', { unused: 'Unused', used: 'Used', }) @@ -537,7 +624,7 @@ describe('prune-unused-i18n', () => { ) // Act - const result = await analyzeUnusedTranslations({ webRoot }) + const result = checkTranslationGraph(webRoot, modules) // Assert expect(result.protectedNamespaces).toEqual([]) @@ -546,9 +633,9 @@ describe('prune-unused-i18n', () => { }) }) - it('should resolve a selected field from nested selector map entries', async () => { + it('should resolve a selected field from nested selector map entries', () => { // Arrange - writeJson('i18n/en-US/plugin.json', { + writeJson('i18n/locales/en-US/plugin.json', { 'source.first': 'First source', 'source.second': 'Second source', unused: 'Unused', @@ -591,7 +678,7 @@ describe('prune-unused-i18n', () => { ) // Act - const result = await analyzeUnusedTranslations({ webRoot }) + const result = checkTranslationGraph(webRoot, modules) // Assert expect(result.protectedNamespaces).toEqual([]) @@ -600,9 +687,9 @@ describe('prune-unused-i18n', () => { }) }) - it('should conservatively protect untyped JavaScript translation adapters', async () => { + it('should conservatively protect untyped JavaScript translation adapters', () => { // Arrange - writeJson('i18n/en-US/app.json', { + writeJson('i18n/locales/en-US/app.json', { hidden: 'Potentially used', used: 'Used', }) @@ -620,19 +707,19 @@ describe('prune-unused-i18n', () => { ) // Act - const result = await analyzeUnusedTranslations({ webRoot }) + const result = checkTranslationGraph(webRoot, modules) // Assert expect(result.protectedNamespaces).toEqual(['app']) expect(sortedUnusedKeysByNamespace(result)).toEqual({}) }) - it('should protect the selected namespace for an open string-key adapter', async () => { + it('should protect the selected namespace for an open string-key adapter', () => { // Arrange - writeJson('i18n/en-US/app.json', { + writeJson('i18n/locales/en-US/app.json', { 'unused.app': 'Unused app', }) - writeJson('i18n/en-US/permission-keys.json', { + writeJson('i18n/locales/en-US/permission-keys.json', { 'server.permission': 'Server permission', }) writeSource( @@ -649,7 +736,7 @@ describe('prune-unused-i18n', () => { ) // Act - const result = await analyzeUnusedTranslations({ webRoot }) + const result = checkTranslationGraph(webRoot, modules) // Assert expect(result.protectedNamespaces).toEqual(['permissionKeys']) @@ -658,9 +745,9 @@ describe('prune-unused-i18n', () => { }) }) - it('should keep keys matching a dynamic selector pattern', async () => { + it('should keep keys matching a dynamic selector pattern', () => { // Arrange - writeJson('i18n/en-US/plugin.json', { + writeJson('i18n/locales/en-US/plugin.json', { 'voice.language.enUS': 'English', 'voice.language.zhCN': 'Chinese', unrelated: 'Unrelated', @@ -678,20 +765,17 @@ describe('prune-unused-i18n', () => { ) // Act - const result = await analyzeUnusedTranslations({ webRoot }) + const result = checkTranslationGraph(webRoot, modules) // Assert expect(sortedUnusedKeysByNamespace(result)).toEqual({ plugin: ['unrelated'], }) - expect(result.dynamicKeyPatterns).toEqual([ - expect.objectContaining({ namespace: 'plugin', prefix: 'voice.language.' }), - ]) }) - it('should keep selector keys from a typed union', async () => { + it('should keep selector keys from a typed union', () => { // Arrange - writeJson('i18n/en-US/common.json', { + writeJson('i18n/locales/en-US/common.json', { 'status.ready': 'Ready', 'status.failed': 'Failed', 'status.unused': 'Unused', @@ -711,7 +795,7 @@ describe('prune-unused-i18n', () => { ) // Act - const result = await analyzeUnusedTranslations({ webRoot }) + const result = checkTranslationGraph(webRoot, modules) // Assert expect(sortedUnusedKeysByNamespace(result)).toEqual({ @@ -719,12 +803,12 @@ describe('prune-unused-i18n', () => { }) }) - it('should keep selector keys from secondary namespaces', async () => { + it('should keep selector keys from secondary namespaces', () => { // Arrange - writeJson('i18n/en-US/app.json', { + writeJson('i18n/locales/en-US/app.json', { 'unused.app': 'Unused app', }) - writeJson('i18n/en-US/common.json', { + writeJson('i18n/locales/en-US/common.json', { 'operation.close': 'Close', 'unused.common': 'Unused common', }) @@ -741,7 +825,7 @@ describe('prune-unused-i18n', () => { ) // Act - const result = await analyzeUnusedTranslations({ webRoot }) + const result = checkTranslationGraph(webRoot, modules) // Assert expect(sortedUnusedKeysByNamespace(result)).toEqual({ @@ -750,12 +834,12 @@ describe('prune-unused-i18n', () => { }) }) - it('should keep property and element access selectors from secondary namespaces', async () => { + it('should keep property and element access selectors from secondary namespaces', () => { // Arrange - writeJson('i18n/en-US/app.json', { + writeJson('i18n/locales/en-US/app.json', { 'unused.app': 'Unused app', }) - writeJson('i18n/en-US/common.json', { + writeJson('i18n/locales/en-US/common.json', { close: 'Close', confirm: 'Confirm', unused: 'Unused', @@ -774,7 +858,7 @@ describe('prune-unused-i18n', () => { ) // Act - const result = await analyzeUnusedTranslations({ webRoot }) + const result = checkTranslationGraph(webRoot, modules) // Assert expect(result.protectedNamespaces).toEqual([]) @@ -784,12 +868,12 @@ describe('prune-unused-i18n', () => { }) }) - it('should only protect the selected namespace for an unresolved selector', async () => { + it('should only protect the selected namespace for an unresolved selector', () => { // Arrange - writeJson('i18n/en-US/app.json', { + writeJson('i18n/locales/en-US/app.json', { 'unused.app': 'Unused app', }) - writeJson('i18n/en-US/common.json', { + writeJson('i18n/locales/en-US/common.json', { 'maybe.used': 'Maybe used', }) writeSource( @@ -805,7 +889,7 @@ describe('prune-unused-i18n', () => { ) // Act - const result = await analyzeUnusedTranslations({ webRoot }) + const result = checkTranslationGraph(webRoot, modules) // Assert expect(sortedUnusedKeysByNamespace(result)).toEqual({ @@ -814,15 +898,15 @@ describe('prune-unused-i18n', () => { expect(result.protectedNamespaces).toEqual(['common']) }) - it('should keep literal keys, aliased t functions, ns options, namespace separators, and Trans keys', async () => { + it('should keep literal keys, aliased t functions, ns options, namespace separators, and Trans keys', () => { // Arrange - writeJson('i18n/en-US/app.json', { + writeJson('i18n/locales/en-US/app.json', { 'literal.title': 'Title', withDefault: 'With default', 'trans.shared': 'Shared app', 'unused.app': 'Unused app', }) - writeJson('i18n/en-US/common.json', { + writeJson('i18n/locales/en-US/common.json', { 'operation.close': 'Close', 'trans.shared': 'Shared common', 'unused.common': 'Unused common', @@ -852,7 +936,7 @@ describe('prune-unused-i18n', () => { ) // Act - const result = await analyzeUnusedTranslations({ webRoot }) + const result = checkTranslationGraph(webRoot, modules) // Assert expect(sortedUnusedKeysByNamespace(result)).toEqual({ @@ -861,9 +945,9 @@ describe('prune-unused-i18n', () => { }) }) - it('should expand resolvable dynamic keys and keep matching prefixes for unresolved dynamic keys', async () => { + it('should expand resolvable dynamic keys and keep matching prefixes for unresolved dynamic keys', () => { // Arrange - writeJson('i18n/en-US/plugin.json', { + writeJson('i18n/locales/en-US/plugin.json', { 'notice.fullMessage': 'Full message', 'notice.reason.bad': 'Bad reason', 'notice.reason.legacy': 'Legacy reason', @@ -890,22 +974,17 @@ describe('prune-unused-i18n', () => { ) // Act - const result = await analyzeUnusedTranslations({ webRoot }) + const result = checkTranslationGraph(webRoot, modules) // Assert expect(sortedUnusedKeysByNamespace(result)).toEqual({ plugin: ['notice.reason.legacy', 'unrelated'], }) - expect(result.dynamicKeyPatterns).toEqual( - expect.arrayContaining([ - expect.objectContaining({ namespace: 'plugin', prefix: 'voice.language.' }), - ]), - ) }) - it('should protect an entire namespace when a dynamic key has no static prefix', async () => { + it('should protect an entire namespace when a dynamic key has no static prefix', () => { // Arrange - writeJson('i18n/en-US/app.json', { + writeJson('i18n/locales/en-US/app.json', { 'maybe.used': 'Maybe used', 'otherwise.unused': 'Otherwise unused', }) @@ -922,16 +1001,16 @@ describe('prune-unused-i18n', () => { ) // Act - const result = await analyzeUnusedTranslations({ webRoot }) + const result = checkTranslationGraph(webRoot, modules) // Assert expect(sortedUnusedKeysByNamespace(result)).toEqual({}) expect(result.protectedNamespaces).toEqual(['app']) }) - it('should keep typed key prefixes without protecting the whole namespace', async () => { + it('should keep typed key prefixes without protecting the whole namespace', () => { // Arrange - writeJson('i18n/en-US/app-debug.json', { + writeJson('i18n/locales/en-US/app-debug.json', { 'duplicateError.name': 'Name', 'duplicateError.value': 'Value', 'outside.unused': 'Outside', @@ -950,23 +1029,18 @@ describe('prune-unused-i18n', () => { ) // Act - const result = await analyzeUnusedTranslations({ webRoot }) + const result = checkTranslationGraph(webRoot, modules) // Assert expect(sortedUnusedKeysByNamespace(result)).toEqual({ appDebug: ['outside.unused'], }) expect(result.protectedNamespaces).toEqual([]) - expect(result.dynamicKeyPatterns).toEqual( - expect.arrayContaining([ - expect.objectContaining({ namespace: 'appDebug', prefix: 'duplicateError.' }), - ]), - ) }) - it('should expand object map values when indexed with a dynamic key', async () => { + it('should expand object map values when indexed with a dynamic key', () => { // Arrange - writeJson('i18n/en-US/common.json', { + writeJson('i18n/locales/en-US/common.json', { 'status.ready': 'Ready', 'status.failed': 'Failed', 'status.unused': 'Unused', @@ -989,7 +1063,7 @@ describe('prune-unused-i18n', () => { ) // Act - const result = await analyzeUnusedTranslations({ webRoot }) + const result = checkTranslationGraph(webRoot, modules) // Assert expect(sortedUnusedKeysByNamespace(result)).toEqual({ @@ -997,9 +1071,9 @@ describe('prune-unused-i18n', () => { }) }) - it('should treat simple i18n key identity helpers as their literal argument', async () => { + it('should treat simple i18n key identity helpers as their literal argument', () => { // Arrange - writeJson('i18n/en-US/common.json', { + writeJson('i18n/locales/en-US/common.json', { 'mainNav.workspace.searchPlaceholder': 'Search', 'mainNav.workspace.unused': 'Unused', }) @@ -1012,13 +1086,13 @@ describe('prune-unused-i18n', () => { export function IdentityHelperExample() { const { t } = useTranslation() - return t(workspaceSwitchI18nKey('mainNav.workspace.searchPlaceholder'), { ns: 'common' }) + return t($ => $[workspaceSwitchI18nKey('mainNav.workspace.searchPlaceholder')], { ns: 'common' }) } `, ) // Act - const result = await analyzeUnusedTranslations({ webRoot }) + const result = checkTranslationGraph(webRoot, modules) // Assert expect(sortedUnusedKeysByNamespace(result)).toEqual({ @@ -1026,9 +1100,9 @@ describe('prune-unused-i18n', () => { }) }) - it('should keep i18next plural variants when the base key is referenced', async () => { + it('should keep i18next plural variants when the base key is referenced', () => { // Arrange - writeJson('i18n/en-US/deployments.json', { + writeJson('i18n/locales/en-US/deployments.json', { 'overview.environments_one': '1 environment', 'overview.environments_other': '{{count}} environments', 'overview.unused_one': '1 unused', @@ -1047,7 +1121,7 @@ describe('prune-unused-i18n', () => { ) // Act - const result = await analyzeUnusedTranslations({ webRoot }) + const result = checkTranslationGraph(webRoot, modules) // Assert expect(sortedUnusedKeysByNamespace(result)).toEqual({ @@ -1055,9 +1129,9 @@ describe('prune-unused-i18n', () => { }) }) - it('should infer plural namespaces from typed TFunction parameters', async () => { + it('should infer plural namespaces from typed TFunction parameters', () => { // Arrange - writeJson('i18n/en-US/deployments.json', { + writeJson('i18n/locales/en-US/deployments.json', { 'overview.chip.behind_one': '1 release behind', 'overview.chip.behind_other': '{{count}} releases behind', 'overview.chip.unused_one': '1 unused', @@ -1075,7 +1149,7 @@ describe('prune-unused-i18n', () => { ) // Act - const result = await analyzeUnusedTranslations({ webRoot }) + const result = checkTranslationGraph(webRoot, modules) // Assert expect(sortedUnusedKeysByNamespace(result)).toEqual({ @@ -1083,9 +1157,9 @@ describe('prune-unused-i18n', () => { }) }) - it('should keep literals when conditional i18n key types expand into unions', async () => { + it('should keep literals when conditional i18n key types expand into unions', () => { // Arrange - writeJson('i18n/en-US/agent-v-2.json', { + writeJson('i18n/locales/en-US/agent-v-2.json', { 'agentDetail.configure.tools.credential.authOne': 'Auth 1', 'agentDetail.configure.tools.unused': 'Unused', }) @@ -1117,7 +1191,7 @@ describe('prune-unused-i18n', () => { ) // Act - const result = await analyzeUnusedTranslations({ webRoot }) + const result = checkTranslationGraph(webRoot, modules) // Assert expect(sortedUnusedKeysByNamespace(result)).toEqual({ @@ -1125,9 +1199,9 @@ describe('prune-unused-i18n', () => { }) }) - it('should collect keys from i18next instance t calls', async () => { + it('should collect keys from i18next instance t calls', () => { // Arrange - writeJson('i18n/en-US/app.json', { + writeJson('i18n/locales/en-US/app.json', { 'gotoAnything.actions.createChatflow': 'Chatflow', 'gotoAnything.actions.createChatflowDesc': 'Create a chatflow', 'gotoAnything.actions.unused': 'Unused', @@ -1148,7 +1222,7 @@ describe('prune-unused-i18n', () => { ) // Act - const result = await analyzeUnusedTranslations({ webRoot }) + const result = checkTranslationGraph(webRoot, modules) // Assert expect(sortedUnusedKeysByNamespace(result)).toEqual({ @@ -1156,18 +1230,18 @@ describe('prune-unused-i18n', () => { }) }) - it('should collect keys from imported and parameterized t functions', async () => { + it('should collect keys from imported and parameterized t functions', () => { // Arrange - writeJson('i18n/en-US/app.json', { + writeJson('i18n/locales/en-US/app.json', { noAccessPermission: 'No access', 'typeSelector.chatbot': 'Chatbot', 'unused.app': 'Unused app', }) - writeJson('i18n/en-US/app-api.json', { + writeJson('i18n/locales/en-US/app-api.json', { pause: 'Pause', 'unused.api': 'Unused API', }) - writeJson('i18n/en-US/tools.json', { + writeJson('i18n/locales/en-US/tools.json', { 'mcp.server.publishTip': 'Publish first', 'unused.tools': 'Unused tools', }) @@ -1195,7 +1269,7 @@ describe('prune-unused-i18n', () => { ) // Act - const result = await analyzeUnusedTranslations({ webRoot }) + const result = checkTranslationGraph(webRoot, modules) // Assert expect(sortedUnusedKeysByNamespace(result)).toEqual({ @@ -1205,39 +1279,4 @@ describe('prune-unused-i18n', () => { }) }) }) - - describe('Removal', () => { - it('should remove unused keys from each locale', async () => { - // Arrange - writeJson('i18n/en-US/app.json', { - kept: 'Kept', - unused: 'Unused', - }) - writeJson('i18n/zh-Hans/app.json', { - kept: '保留', - unused: '未使用', - }) - writeSource( - 'src/example.tsx', - ` - import { useTranslation } from 'react-i18next' - - export function Example() { - const { t } = useTranslation('app') - return t('kept') - } - `, - ) - const result = await analyzeUnusedTranslations({ webRoot }) - - // Act - const removal = await removeUnusedTranslations({ webRoot, analysis: result }) - - // Assert - expect(removal.removedKeys).toEqual([ - { locale: 'en-US', namespace: 'app', key: 'unused' }, - { locale: 'zh-Hans', namespace: 'app', key: 'unused' }, - ]) - }) - }) }) diff --git a/web/plugins/vite/__tests__/i18n-prune.spec.ts b/web/plugins/vite/__tests__/i18n-prune.spec.ts new file mode 100644 index 00000000000..461eadec89b --- /dev/null +++ b/web/plugins/vite/__tests__/i18n-prune.spec.ts @@ -0,0 +1,251 @@ +// @vitest-environment node +import { mkdirSync, mkdtempSync, readFileSync, realpathSync, rmSync, writeFileSync } from 'node:fs' +import { tmpdir } from 'node:os' +import path from 'node:path' +import { build, createBuilder, createServer } from 'vite' +import { afterEach, beforeEach, describe, expect, it } from 'vite-plus/test' +import { i18nPrunePlugin } from '../i18n-prune' + +describe('i18n build check', () => { + let root: string + let localeFile: string + + beforeEach(() => { + root = realpathSync(mkdtempSync(path.join(tmpdir(), 'dify-i18n-build-'))) + mkdirSync(path.join(root, 'i18n/locales/en-US'), { recursive: true }) + localeFile = path.join(root, 'i18n/locales/en-US/app.json') + writeFileSync( + path.join(root, 'entry.ts'), + ` + export function translate(t: (key: string) => string) { + return t('app:used') + } + `, + ) + }) + + afterEach(() => { + rmSync(root, { recursive: true, force: true }) + }) + + const buildFixture = () => + build({ + root, + configFile: false, + logLevel: 'silent', + plugins: [i18nPrunePlugin()], + build: { + write: false, + lib: { entry: path.join(root, 'entry.ts'), formats: ['es'] }, + }, + }) + + it('fails builds with actionable findings without modifying translations', async () => { + const content = JSON.stringify({ used: 'Used', unused: 'Unused' }) + writeFileSync(localeFile, content) + + await expect(buildFixture()).rejects.toThrow( + /Found 1 potentially unused i18n keys[\s\S]*static analysis of the current build module graph[\s\S]*false positives or miss unused keys[\s\S]*Review actual usage before removing any keys[\s\S]*app:unused/, + ) + expect(readFileSync(localeFile, 'utf8')).toBe(content) + }) + + it('allows builds after unused translations are removed', async () => { + writeFileSync(localeFile, JSON.stringify({ used: 'Used' })) + + await expect(buildFixture()).resolves.toBeDefined() + }) + + it('does not count usage in modules outside the build graph', async () => { + writeFileSync(localeFile, JSON.stringify({ used: 'Used', testOnly: 'Only in tests' })) + writeFileSync( + path.join(root, 'unimported.spec.ts'), + ` + export function fixture(t: (key: string) => string) { return t('app:testOnly') } + `, + ) + writeFileSync( + path.join(root, 'types.ts'), + ` + export type Label = string + export function fixture(t: (key: string) => string) { return t('app:testOnly') } + `, + ) + writeFileSync( + path.join(root, 'entry.ts'), + ` + import type { Label } from './types' + export function translate(t: (key: string) => Label) { return t('app:used') } + `, + ) + + await expect(buildFixture()).rejects.toThrow('app:testOnly') + }) + + it('includes dynamically imported modules in the graph', async () => { + writeFileSync(localeFile, JSON.stringify({ used: 'Used', lazy: 'Lazy' })) + writeFileSync( + path.join(root, 'lazy.ts'), + ` + export function lazy(t: (key: string) => string) { return t('app:lazy') } + `, + ) + writeFileSync( + path.join(root, 'entry.ts'), + ` + export const lazy = () => import('./lazy') + export function translate(t: (key: string) => string) { return t('app:used') } + `, + ) + + await expect(buildFixture()).resolves.toBeDefined() + }) + + it('checks the union of client, SSR and RSC graphs only after every environment builds', async () => { + writeFileSync(localeFile, JSON.stringify({ client: 'Client', ssr: 'SSR', rsc: 'RSC' })) + for (const name of ['client', 'ssr', 'rsc']) { + writeFileSync( + path.join(root, `${name}.ts`), + ` + export function translate(t: (key: string) => string) { return t('app:${name}') } + `, + ) + } + const completed: string[] = [] + const builder = await createBuilder({ + root, + configFile: false, + logLevel: 'silent', + plugins: [i18nPrunePlugin()], + environments: Object.fromEntries( + ['client', 'ssr', 'rsc'].map((name) => [ + name, + { + build: { + write: false, + lib: { entry: path.join(root, `${name}.ts`), formats: ['es'] }, + }, + }, + ]), + ), + builder: { + async buildApp(builder) { + for (const environment of Object.values(builder.environments)) { + await builder.build(environment) + completed.push(environment.name) + } + }, + }, + }) + + await expect(builder.buildApp()).resolves.toBeUndefined() + expect(completed).toEqual(['client', 'ssr', 'rsc']) + + // A second build must not inherit the first build's server usage. + writeFileSync(path.join(root, 'ssr.ts'), 'export const value = 1') + await expect(builder.buildApp()).rejects.toThrow('app:ssr') + }) + + it('retains usages from environment-specific versions of the same module', async () => { + writeFileSync(localeFile, JSON.stringify({ client: 'Client', ssr: 'SSR' })) + const builder = await createBuilder({ + root, + configFile: false, + logLevel: 'silent', + plugins: [ + { + name: 'environment-source-fixture', + enforce: 'pre', + transform(code, id) { + if (id !== path.join(root, 'entry.ts')) return + return code.replace('app:used', `app:${this.environment.name}`) + }, + }, + i18nPrunePlugin(), + ], + environments: { + client: {}, + ssr: {}, + }, + build: { + write: false, + lib: { entry: path.join(root, 'entry.ts'), formats: ['es'] }, + }, + }) + + await expect(builder.buildApp()).resolves.toBeUndefined() + }) + + it.each([ + ['client', 'ssr'], + ['ssr', 'client'], + ])('resolves imported selectors in their own environments (%s first)', async (first, second) => { + writeFileSync(localeFile, JSON.stringify({ client: 'Client', ssr: 'SSR' })) + writeFileSync( + path.join(root, 'selector.ts'), + ` + export const selector = ($: Record) => $.placeholder + `, + ) + writeFileSync( + path.join(root, 'entry.ts'), + ` + import { selector } from './selector' + export function label(t: (selector: (source: Record) => string) => string) { + return t(selector) + } + `, + ) + const builder = await createBuilder({ + root, + configFile: false, + logLevel: 'silent', + plugins: [ + { + name: 'environment-selector-fixture', + enforce: 'pre', + transform(code, id) { + if (id === path.join(root, 'selector.ts')) + return code.replace('$.placeholder', `$.${this.environment.name}`) + }, + }, + i18nPrunePlugin(), + ], + environments: { client: {}, ssr: {} }, + builder: { + async buildApp(builder) { + for (const name of [first, second]) await builder.build(builder.environments[name]!) + }, + }, + build: { + write: false, + lib: { entry: path.join(root, 'entry.ts'), formats: ['es'] }, + }, + }) + + await expect(builder.buildApp()).resolves.toBeUndefined() + + writeFileSync(localeFile, JSON.stringify({ client: 'Client', ssr: 'SSR', unused: 'Unused' })) + await expect(builder.buildApp()).rejects.toThrow( + /Found 1 potentially unused i18n keys[\s\S]*app:unused/, + ) + }) + + it('does not block development when unused translations exist', async () => { + writeFileSync(localeFile, JSON.stringify({ used: 'Used', unused: 'Unused' })) + const server = await createServer({ + root, + configFile: false, + logLevel: 'silent', + plugins: [i18nPrunePlugin()], + server: { middlewareMode: true, watch: null }, + }) + try { + await expect(server.transformRequest('/entry.ts')).resolves.toMatchObject({ + code: expect.stringContaining('app:used'), + }) + } finally { + await server.close() + } + }) +}) diff --git a/web/plugins/vite/custom-i18n-hmr.ts b/web/plugins/vite/custom-i18n-hmr.ts index 701a187e9b1..db1c77c1280 100644 --- a/web/plugins/vite/custom-i18n-hmr.ts +++ b/web/plugins/vite/custom-i18n-hmr.ts @@ -11,7 +11,7 @@ export const customI18nHmrPlugin = ({ injectTarget }: CustomI18nHmrPluginOptions const i18nHmrClientSnippet = `/* ${i18nHmrClientMarker} */ if (import.meta.hot) { const getI18nUpdateTarget = (file) => { - const match = file.match(/[/\\\\]i18n[/\\\\]([^/\\\\]+)[/\\\\]([^/\\\\]+)\\.json$/) + const match = file.match(/[/\\\\]i18n[/\\\\]locales[/\\\\]([^/\\\\]+)[/\\\\]([^/\\\\]+)\\.json$/) if (!match) return null const [, locale, namespaceFile] = match @@ -53,7 +53,7 @@ if (import.meta.hot) { name: 'custom-i18n-hmr', apply: 'serve', handleHotUpdate({ file, server }) { - if (file.endsWith('.json') && file.includes('/i18n/')) { + if (file.endsWith('.json') && file.includes('/i18n/locales/')) { server.ws.send({ type: 'custom', event: 'i18n-update', diff --git a/web/plugins/vite/i18n-prune.ts b/web/plugins/vite/i18n-prune.ts new file mode 100644 index 00000000000..ccf8c722d80 --- /dev/null +++ b/web/plugins/vite/i18n-prune.ts @@ -0,0 +1,119 @@ +import type { Plugin } from 'vite' +import path from 'node:path' +import { normalizePath } from 'vite' + +const SOURCE_META = 'dify:i18n-source' + +// Vite shares these instances across client, SSR, and RSC environments. Each +// completed graph replaces its preceding scan build; only buildApp finalizes it. +export function i18nPrunePlugin(): Plugin[] { + let root: string + let buildingApp = false + const graphs = new Map>() + + const check = async () => { + const { checkTranslationGraph } = await import('./i18n-prune/graph') + let unused: Record | undefined + const protectedNamespaces = new Set() + for (const graph of graphs.values()) { + // Keep original paths in a separate compiler program per environment so + // imports resolve against that environment's source, not the first build. + const modules = new Map() + for (const [id, code] of graph) modules.set(normalizePath(id.split('?')[0]!), code) + const result = checkTranslationGraph(root, modules) + for (const namespace of result.protectedNamespaces) protectedNamespaces.add(namespace) + // Intersect unused sets: usage (or protection) in any environment keeps a key. + unused = + unused === undefined + ? result.unused + : Object.fromEntries( + Object.entries(unused).map(([namespace, keys]) => { + const remaining = new Set(result.unused[namespace] ?? []) + return [namespace, keys.filter((key) => remaining.has(key))] + }), + ) + } + const keys = Object.entries(unused ?? {}).flatMap(([namespace, unused]) => + unused.map((key) => `${namespace}:${key}`), + ) + if (keys.length) { + throw new Error( + [ + `Found ${keys.length} potentially unused i18n keys in the application build:`, + 'This report uses static analysis of the current build module graph and may contain false positives or miss unused keys.', + 'Review actual usage before removing any keys.', + ...keys.map((key) => ` - ${key}`), + 'Remove confirmed unused keys from web/i18n/locales/ locale files or restore their application usage.', + ...(protectedNamespaces.size + ? [ + `Dynamic keys protect these namespaces: ${[...protectedNamespaces].sort().join(', ')}`, + ] + : []), + ].join('\n'), + ) + } + } + + return [ + { + name: 'i18n-prune:collect', + apply: 'build', + enforce: 'pre', + sharedDuringBuild: true, + configResolved(config) { + root = config.root + }, + async buildApp() { + graphs.clear() + buildingApp = true + }, + buildStart() { + if (!buildingApp) graphs.clear() + }, + transform(code, id) { + const file = normalizePath(id.split('?')[0]!) + if ( + id.startsWith('\0') || + !path.isAbsolute(file) || + file.includes('/node_modules/') || + !/\.[cm]?[jt]sx?$/.test(file) + ) + return + // Persist the original source in module metadata, including cached modules + // on rebuilds, before JSX/TypeScript and RSC transforms discard selectors. + return { code, map: null, meta: { [SOURCE_META]: code } } + }, + async generateBundle() { + const graph = new Map() + for (const id of this.getModuleIds()) { + const source: unknown = this.getModuleInfo(id)?.meta[SOURCE_META] + if (typeof source === 'string') graph.set(id, source) + } + graphs.set(this.environment.name, graph) + if (!buildingApp) await check() + }, + }, + { + name: 'i18n-prune:check', + apply: 'build', + sharedDuringBuild: true, + buildApp: { + order: 'post', + async handler(builder) { + try { + // Vite's default environment loop runs after post hooks. Vinext + // builds earlier; plain Vite needs its default loop completed here. + if (Object.values(builder.environments).every((environment) => !environment.isBuilt)) { + for (const environment of Object.values(builder.environments)) + await builder.build(environment) + } + await check() + } finally { + buildingApp = false + graphs.clear() + } + }, + }, + }, + ] +} diff --git a/web/plugins/vite/i18n-prune/graph.ts b/web/plugins/vite/i18n-prune/graph.ts new file mode 100644 index 00000000000..1069bd3fbce --- /dev/null +++ b/web/plugins/vite/i18n-prune/graph.ts @@ -0,0 +1,525 @@ +import fs from 'node:fs' +import path from 'node:path' +import * as ts from 'typescript' + +const PLURAL = /_(?:zero|one|two|few|many|other)$/ +const MAX_VALUES = 200 +const camelCase = (name: string) => + name.replace(/[-_]+([a-z0-9])/gi, (_, char: string) => char.toUpperCase()) +const escapeRegex = (text: string) => text.replace(/[.*+?^${}()|[\]\\]/g, '\\$&') + +type Translation = { namespaces: string[]; prefix: string; argument: number } +type Value = ts.Expression | ts.FunctionDeclaration +type Key = { text: string; wildcard?: boolean; namespace?: string } + +function unwrap(node: ts.Expression): ts.Expression { + if ( + ts.isParenthesizedExpression(node) || + ts.isAsExpression(node) || + ts.isTypeAssertionExpression(node) || + ts.isNonNullExpression(node) || + ts.isSatisfiesExpression(node) || + ts.isAwaitExpression(node) + ) + return unwrap(node.expression) + return node +} + +function literalTypes(type: ts.Type): string[] | undefined { + if (type.isStringLiteral()) return [type.value] + if (type.isUnion()) { + const values: string[] = [] + for (const member of type.types) { + if (member.flags & (ts.TypeFlags.Undefined | ts.TypeFlags.Null | ts.TypeFlags.Void)) continue + const literals = literalTypes(member) + if (!literals) return + values.push(...literals) + if (values.length > MAX_VALUES) return + } + return values.length ? [...new Set(values)] : undefined + } +} + +function initializer(node: ts.Node): Value | undefined { + if ( + ts.isVariableDeclaration(node) || + ts.isPropertyAssignment(node) || + ts.isParameter(node) || + ts.isPropertyDeclaration(node) + ) + return node.initializer + if (ts.isShorthandPropertyAssignment(node)) return node.name + if (ts.isFunctionDeclaration(node) && node.body) return node +} + +// Only module IDs supplied by Vite are visited. Type dependencies inform static +// expressions but never contribute usage merely by existing on disk. +export function checkTranslationGraph(root: string, modules: ReadonlyMap) { + const catalog = new Map>() + const directory = path.join(root, 'i18n/locales/en-US') + for (const file of fs + .readdirSync(directory) + .filter((file) => file.endsWith('.json')) + .sort()) { + const content: unknown = JSON.parse(fs.readFileSync(path.join(directory, file), 'utf8')) + if (!content || typeof content !== 'object' || Array.isArray(content)) + throw new Error(`Invalid translation catalog: ${file}`) + catalog.set(camelCase(file.slice(0, -5)), new Set(Object.keys(content))) + } + const configPath = ts.findConfigFile(root, ts.sys.fileExists) + const config = configPath ? ts.readConfigFile(configPath, ts.sys.readFile) : undefined + if (config?.error) + throw new Error(ts.flattenDiagnosticMessageText(config.error.messageText, '\n')) + const options = + configPath && config + ? ts.parseJsonConfigFileContent( + config.config, + { ...ts.sys, readDirectory: () => [] }, + path.dirname(configPath), + ).options + : {} + const compilerOptions: ts.CompilerOptions = { + ...options, + allowJs: true, + jsx: ts.JsxEmit.ReactJSX, + noEmit: true, + skipLibCheck: true, + } + const host = ts.createCompilerHost(compilerOptions) + const readFile = host.readFile.bind(host) + host.readFile = (file) => modules.get(file) ?? readFile(file) + const fileExists = host.fileExists.bind(host) + host.fileExists = (file) => modules.has(file) || fileExists(file) + const getSourceFile = host.getSourceFile.bind(host) + host.getSourceFile = (file, languageVersion, onError, shouldCreateNewSourceFile) => { + const source = modules.get(file) + return source === undefined + ? getSourceFile(file, languageVersion, onError, shouldCreateNewSourceFile) + : ts.createSourceFile(file, source, languageVersion, true) + } + const program = ts.createProgram([...modules.keys()], compilerOptions, host) + const checker = program.getTypeChecker() + const used = new Map>() + const protectedNamespaces = new Set() + + function declarations(node: ts.Node): readonly ts.Declaration[] { + const symbol = checker.getSymbolAtLocation(node) + const local = symbol?.declarations ?? [] + return symbol && symbol.flags & ts.SymbolFlags.Alias + ? [...local, ...(checker.getAliasedSymbol(symbol).declarations ?? [])] + : local + } + + function alternatives(expression: Value, seen = new Set()): (Value | undefined)[] { + if (ts.isFunctionDeclaration(expression)) return [expression] + if ( + (ts.isAsExpression(expression) || ts.isTypeAssertionExpression(expression)) && + /I18nKeys(?:By|With)Prefix/.test(expression.type.getText()) + ) + return [expression] + const node = unwrap(expression) + if (seen.has(node)) return [undefined] + const next = new Set(seen).add(node) + if (ts.isConditionalExpression(node)) + return [...alternatives(node.whenTrue, next), ...alternatives(node.whenFalse, next)] + if (ts.isCallExpression(node)) { + for (const fn of alternatives(node.expression, next)) { + if ( + !fn || + !( + ts.isArrowFunction(fn) || + ts.isFunctionExpression(fn) || + ts.isFunctionDeclaration(fn) + ) || + !fn.body + ) + continue + const body = ts.isBlock(fn.body) + ? fn.body.statements.length === 1 && ts.isReturnStatement(fn.body.statements[0]!) + ? fn.body.statements[0]!.expression + : undefined + : fn.body + if (!body) continue + const returned = unwrap(body) + const index = fn.parameters.findIndex( + (parameter) => parameter.name.getText() === returned.getText(), + ) + if (index >= 0 && node.arguments[index]) return alternatives(node.arguments[index]!, next) + } + } + if (ts.isIdentifier(node)) { + if (node.text === 'undefined') return [] + const values = declarations(node) + .map(initializer) + .filter((value) => value !== undefined) + return values.length ? values.flatMap((value) => alternatives(value, next)) : [node] + } + if (ts.isPropertyAccessExpression(node) || ts.isElementAccessExpression(node)) { + const object = checker.getTypeAtLocation(node.expression) + const names = ts.isPropertyAccessExpression(node) + ? [node.name.text] + : strings(node.argumentExpression) + const objects = alternatives(node.expression, next) + if (objects.some((value) => value && ts.isObjectLiteralExpression(value))) { + const select = (value: Value | undefined): (Value | undefined)[] => { + if (!value || !ts.isObjectLiteralExpression(value)) return [undefined] + return value.properties.flatMap((member) => { + if (ts.isSpreadAssignment(member)) + return alternatives(member.expression, next).flatMap(select) + const memberNames = + member.name && ts.isComputedPropertyName(member.name) + ? strings(member.name.expression) + : member.name + ? [member.name.getText().replace(/^['"]|['"]$/g, '')] + : undefined + if (names && memberNames && !memberNames.some((name) => names.includes(name))) return [] + const value = initializer(member) + return value ? alternatives(value, next) : [undefined] + }) + } + return objects.flatMap(select) + } + const symbols = names ? names.map((name) => object.getProperty(name)) : object.getProperties() + const values = symbols.flatMap( + (symbol) => symbol?.declarations?.map(initializer) ?? [undefined], + ) + // An open index signature can supply entries absent from the finite map. + if (!names && object.getStringIndexType()) values.push(undefined) + if (values.length) + return values.flatMap((value) => (value ? alternatives(value, next) : [undefined])) + } + return [node] + } + + function strings(expression: ts.Expression | undefined): string[] | undefined { + if (!expression) return + const node = unwrap(expression) + if (ts.isStringLiteralLike(node)) return [node.text] + if (ts.isArrayLiteralExpression(node)) { + const values = node.elements.map((element) => strings(element)) + return values.every((value) => value !== undefined) ? values.flat() : undefined + } + return literalTypes(checker.getTypeAtLocation(expression)) + } + + function property( + expression: ts.Expression | undefined, + name: string, + ): ts.Expression | undefined { + if (!expression) return + const node = unwrap(expression) + if (!ts.isObjectLiteralExpression(node)) return + for (const member of node.properties) { + if ( + ts.isPropertyAssignment(member) && + member.name.getText().replace(/^['"]|['"]$/g, '') === name + ) + return member.initializer + } + } + + function selectorType(type: ts.Type): ts.Type | undefined { + if (type.aliasSymbol?.getName() === 'SelectorParam') return type + const constraint = checker.getBaseConstraintOfType(type) + return constraint && constraint !== type ? selectorType(constraint) : undefined + } + + function typedTranslation(node: ts.Node, call?: ts.CallExpression): Translation | undefined { + const type = checker.getTypeAtLocation(node) + const brand = type.getProperty('$TFunctionBrand') + if (brand) { + const namespaces = literalTypes(checker.getTypeOfSymbolAtLocation(brand, node)) + if (namespaces?.length) return { namespaces, prefix: '', argument: 0 } + } + // Prefer the instantiated namespace. Inferred selector functions can lose + // their alias, so retain declaration signatures for recognizing adapters. + const resolved = call && checker.getResolvedSignature(call) + const signatures = [...(resolved ? [resolved] : []), ...type.getCallSignatures()] + for (const signature of signatures) { + for (const [argument, parameter] of signature.parameters.entries()) { + const selector = selectorType(checker.getTypeOfSymbolAtLocation(parameter, node)) + if (!selector) continue + const namespaceType = selector.aliasTypeArguments?.[0] + const namespaces = namespaceType ? literalTypes(namespaceType) : undefined + return { namespaces: namespaces ?? [...catalog.keys()], prefix: '', argument } + } + } + } + + function translation( + node: ts.Expression, + call?: ts.CallExpression, + seen = new Set(), + ): Translation | undefined { + if (seen.has(node)) return + seen.add(node) + for (const declaration of declarations( + ts.isPropertyAccessExpression(node) ? node.name : node, + )) { + if ( + ts.isBindingElement(declaration) && + (declaration.propertyName ?? declaration.name).getText() === 't' + ) { + const variable = declaration.parent.parent + if (ts.isParameter(variable)) + return typedTranslation(node, call) ?? { namespaces: ['app'], prefix: '', argument: 0 } + if (ts.isVariableDeclaration(variable) && variable.initializer) { + const call = unwrap(variable.initializer) + if (ts.isCallExpression(call)) { + const name = call.expression.getText() + if (/(?:^|\.)(?:useTranslation|getTranslation)$/.test(name)) { + const nsIndex = name.endsWith('getTranslation') ? 1 : 0 + return { + namespaces: + strings(call.arguments[nsIndex]) ?? + (call.arguments[nsIndex] ? [...catalog.keys()] : ['app']), + prefix: strings(property(call.arguments[nsIndex + 1], 'keyPrefix'))?.[0] ?? '', + argument: 0, + } + } + } + } + } + if ( + ts.isImportSpecifier(declaration) && + (declaration.propertyName ?? declaration.name).text === 't' + ) { + const importDeclaration = declaration.parent.parent.parent + if ( + ts.isImportDeclaration(importDeclaration) && + ts.isStringLiteral(importDeclaration.moduleSpecifier) && + importDeclaration.moduleSpecifier.text === 'i18next' + ) + return { namespaces: ['app'], prefix: '', argument: 0 } + } + if (ts.isParameter(declaration)) { + const match = declaration.type?.getText().match(/TFunction\s*<\s*['"]([^'"]+)/) + if (match) return { namespaces: [match[1]!], prefix: '', argument: 0 } + const typed = typedTranslation(node, call) + if (typed) return typed + if (declaration.name.getText() === 't') + return { namespaces: ['app'], prefix: '', argument: 0 } + } + if (ts.isVariableDeclaration(declaration) && declaration.initializer) { + const alias = translation(unwrap(declaration.initializer), call, seen) + if (alias) return alias + } + } + const typed = typedTranslation(node, call) + if (typed) return typed + if (ts.isPropertyAccessExpression(node) && node.name.text === 't') + return { namespaces: ['app'], prefix: '', argument: 0 } + } + + function keyPatterns(expression: ts.Expression): Key[] { + const node = unwrap(expression) + if (ts.isCallExpression(node)) { + const values = alternatives(node) + if (values.some((value) => value !== node)) { + return values.flatMap((value) => + value && !ts.isFunctionDeclaration(value) + ? keyPatterns(value) + : [{ text: '.*', wildcard: true }], + ) + } + } + const values = strings(expression) + if (values) return values.map((text) => ({ text })) + if ( + (ts.isAsExpression(expression) || ts.isTypeAssertionExpression(expression)) && + ts.isTypeReferenceNode(expression.type) && + /I18nKeys(?:By|With)Prefix$/.test(expression.type.typeName.getText()) + ) { + const prefix = expression.type.typeArguments?.[1] + if (prefix && ts.isLiteralTypeNode(prefix) && ts.isStringLiteral(prefix.literal)) + return [{ text: `${escapeRegex(prefix.literal.text)}.*`, wildcard: true }] + } + if (ts.isTemplateExpression(node)) { + let patterns = [escapeRegex(node.head.text)] + for (const span of node.templateSpans) { + const values = strings(span.expression)?.map(escapeRegex) ?? ['.*'] + if (patterns.length * values.length > MAX_VALUES) return [{ text: '.*', wildcard: true }] + patterns = patterns.flatMap((prefix) => + values.map((value) => prefix + value + escapeRegex(span.literal.text)), + ) + } + return patterns.map((text) => ({ text, wildcard: true })) + } + if (ts.isBinaryExpression(node) && node.operatorToken.kind === ts.SyntaxKind.PlusToken) { + const left = keyPatterns(node.left) + const right = keyPatterns(node.right) + if (left.length * right.length > MAX_VALUES) return [{ text: '.*', wildcard: true }] + return left.flatMap((a) => + right.map((b) => ({ + text: + (a.wildcard ? a.text : escapeRegex(a.text)) + + (b.wildcard ? b.text : escapeRegex(b.text)), + wildcard: true, + })), + ) + } + return [{ text: '.*', wildcard: true }] + } + + function selectorKeys( + node: ts.ArrowFunction | ts.FunctionExpression | ts.FunctionDeclaration, + ): Key[] { + const source = node.parameters[0]?.name + if (!source || !node.body) return [{ text: '.*', wildcard: true }] + const returns: ts.Expression[] = [] + if (ts.isBlock(node.body)) { + const visit = (child: ts.Node) => { + if (ts.isReturnStatement(child) && child.expression) returns.push(child.expression) + else if (!ts.isFunctionLike(child)) ts.forEachChild(child, visit) + } + ts.forEachChild(node.body, visit) + } else returns.push(node.body) + + const access = (expression: ts.Expression): Key[] => { + const value = unwrap(expression) + if (ts.isConditionalExpression(value)) + return [...access(value.whenTrue), ...access(value.whenFalse)] + const chain: (ts.Expression | string)[] = [] + let current = value + while (ts.isPropertyAccessExpression(current) || ts.isElementAccessExpression(current)) { + chain.unshift( + ts.isPropertyAccessExpression(current) ? current.name.text : current.argumentExpression, + ) + current = unwrap(current.expression) + } + if (current.getText() !== source.getText() || !chain.length) + return [{ text: '.*', wildcard: true }] + const key = chain.at(-1)! + const keys = typeof key === 'string' ? [{ text: key }] : keyPatterns(key) + if (chain.length === 1) return keys + const namespacePart = chain[0]! + const namespaces = + typeof namespacePart === 'string' ? [namespacePart] : strings(namespacePart) + if (!namespaces?.length) return [{ text: '.*', wildcard: true, namespace: '*' }] + return namespaces.flatMap((namespace) => keys.map((key) => ({ ...key, namespace }))) + } + return returns.length ? returns.flatMap(access) : [{ text: '.*', wildcard: true }] + } + + function record(keys: Key[], info: Translation) { + for (const key of keys) { + const separator = key.text.indexOf(':') + const namespaces = key.namespace + ? key.namespace === '*' + ? [...catalog.keys()] + : [key.namespace] + : separator > 0 + ? key.wildcard + ? [...catalog.keys()].filter((namespace) => + new RegExp(`^${key.text.slice(0, separator)}$`).test(namespace), + ) + : [key.text.slice(0, separator)] + : info.namespaces + const text = separator > 0 && !key.namespace ? key.text.slice(separator + 1) : key.text + for (const rawNamespace of namespaces) { + const namespace = camelCase(rawNamespace) + if (!catalog.has(namespace)) continue + if (key.wildcard && text === '.*' && !info.prefix) { + protectedNamespaces.add(namespace) + continue + } + const prefix = info.prefix ? `${info.prefix}.` : '' + const pattern = key.wildcard ? new RegExp(`^${escapeRegex(prefix)}${text}$`) : undefined + const matches = (candidate: string) => + pattern ? pattern.test(candidate) : candidate === prefix + text + const kept = used.get(namespace) ?? new Set() + for (const candidate of catalog.get(namespace)!) { + if (matches(candidate) || matches(candidate.replace(PLURAL, ''))) kept.add(candidate) + } + used.set(namespace, kept) + } + } + } + + function consume(expression: ts.Expression, info: Translation) { + // Typed forwarding adapters are checked at their concrete call sites. + if ( + ts.isIdentifier(expression) && + selectorType(checker.getTypeAtLocation(expression)) && + declarations(expression).some(ts.isParameter) + ) + return + for (const value of alternatives(expression)) { + if ( + value && + (ts.isArrowFunction(value) || + ts.isFunctionExpression(value) || + ts.isFunctionDeclaration(value)) + ) { + record(selectorKeys(value), info) + } else { + record( + value && !ts.isFunctionDeclaration(value) + ? keyPatterns(value) + : [{ text: '.*', wildcard: true }], + info, + ) + } + } + } + + function visit(node: ts.Node) { + if (ts.isStringLiteralLike(node) && (node.text.includes('.') || node.text.includes(':'))) { + const contextual = checker.getContextualType(node) + const values = contextual && literalTypes(contextual) + if (values?.includes(node.text)) { + const namespaces = [...catalog] + .filter(([, keys]) => values.every((value) => keys.has(value))) + .map(([namespace]) => namespace) + if (namespaces.length) + record([{ text: node.text }], { namespaces, prefix: '', argument: 0 }) + } + } + if (ts.isCallExpression(node)) { + const info = translation(node.expression, node) + const argument = info && node.arguments[info.argument] + if (info && argument) { + const options = node.arguments.at(-1) + const namespaceOption = property(options, 'ns') + const namespaces = strings(namespaceOption) + consume(argument, { + ...info, + namespaces: namespaces ?? (namespaceOption ? [...catalog.keys()] : info.namespaces), + }) + } + } + if ( + (ts.isJsxSelfClosingElement(node) || ts.isJsxOpeningElement(node)) && + node.tagName.getText() === 'Trans' + ) { + const attributes = new Map() + for (const attribute of node.attributes.properties) { + if (!ts.isJsxAttribute(attribute) || !attribute.initializer) continue + const value = ts.isJsxExpression(attribute.initializer) + ? attribute.initializer.expression + : attribute.initializer + if (value) attributes.set(attribute.name.getText(), value) + } + const key = attributes.get('i18nKey') + if (key) + consume(key, { + namespaces: + strings(attributes.get('ns')) ?? (attributes.has('ns') ? [...catalog.keys()] : ['app']), + prefix: '', + argument: 0, + }) + } + ts.forEachChild(node, visit) + } + for (const id of modules.keys()) { + const source = program.getSourceFile(id) + if (source) visit(source) + } + const unused: Record = {} + for (const [namespace, keys] of catalog) { + if (protectedNamespaces.has(namespace)) continue + const missing = [...keys].filter((key) => !used.get(namespace)?.has(key)).sort() + if (missing.length) unused[namespace] = missing + } + return { unused, protectedNamespaces: [...protectedNamespaces].sort(), moduleCount: modules.size } +} diff --git a/web/public/emoji/EMOJI-MART-LICENSE b/web/public/emoji/EMOJI-MART-LICENSE new file mode 100644 index 00000000000..a82512e9801 --- /dev/null +++ b/web/public/emoji/EMOJI-MART-LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) Missive. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/web/public/emoji/README.md b/web/public/emoji/README.md new file mode 100644 index 00000000000..d5932d33ee2 --- /dev/null +++ b/web/public/emoji/README.md @@ -0,0 +1,16 @@ +# Self-hosted emoji data + +`emojibase-17.0.0/en/{data,messages}.json` are vendored files from +`emojibase-data@17.0.0` (MIT; license included). Frimousse uses only these two +files. A final newline is added where needed to satisfy EditorConfig; JSON contents +are unchanged. Keep `locale="en"` to preserve the existing English search vocabulary. +The URL includes the deployment base path and never falls back to a CDN. + +To update, extract these files and LICENSE from the corresponding npm tarball, +use a new versioned directory, and update the picker URL together. + +`web/utils/emoji-legacy.json` is the frozen compatibility map generated from +`@emoji-mart/data@1.2.1/sets/15/native.json`: each `emojis` key maps to its first +skin's `native` value, followed by every `aliases` key mapped through its target. +It is retained independently of picker data updates so persisted IDs keep working. +Emoji Mart data is MIT licensed: https://github.com/missive/emoji-mart. diff --git a/web/public/emoji/emojibase-17.0.0/LICENSE b/web/public/emoji/emojibase-17.0.0/LICENSE new file mode 100644 index 00000000000..3e21d0b1640 --- /dev/null +++ b/web/public/emoji/emojibase-17.0.0/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2017-2019 Miles Johnson + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/web/public/emoji/emojibase-17.0.0/en/data.json b/web/public/emoji/emojibase-17.0.0/en/data.json new file mode 100644 index 00000000000..b956b50c611 --- /dev/null +++ b/web/public/emoji/emojibase-17.0.0/en/data.json @@ -0,0 +1 @@ +[{"label":"regional indicator A","hexcode":"1F1E6","emoji":"🇦","text":"","type":1,"version":0},{"label":"regional indicator B","hexcode":"1F1E7","emoji":"🇧","text":"","type":1,"version":0},{"label":"regional indicator C","hexcode":"1F1E8","emoji":"🇨","text":"","type":1,"version":0},{"label":"regional indicator D","hexcode":"1F1E9","emoji":"🇩","text":"","type":1,"version":0},{"label":"regional indicator E","hexcode":"1F1EA","emoji":"🇪","text":"","type":1,"version":0},{"label":"regional indicator F","hexcode":"1F1EB","emoji":"🇫","text":"","type":1,"version":0},{"label":"regional indicator G","hexcode":"1F1EC","emoji":"🇬","text":"","type":1,"version":0},{"label":"regional indicator H","hexcode":"1F1ED","emoji":"🇭","text":"","type":1,"version":0},{"label":"regional indicator I","hexcode":"1F1EE","emoji":"🇮","text":"","type":1,"version":0},{"label":"regional indicator J","hexcode":"1F1EF","emoji":"🇯","text":"","type":1,"version":0},{"label":"regional indicator K","hexcode":"1F1F0","emoji":"🇰","text":"","type":1,"version":0},{"label":"regional indicator L","hexcode":"1F1F1","emoji":"🇱","text":"","type":1,"version":0},{"label":"regional indicator M","hexcode":"1F1F2","emoji":"🇲","text":"","type":1,"version":0},{"label":"regional indicator N","hexcode":"1F1F3","emoji":"🇳","text":"","type":1,"version":0},{"label":"regional indicator O","hexcode":"1F1F4","emoji":"🇴","text":"","type":1,"version":0},{"label":"regional indicator P","hexcode":"1F1F5","emoji":"🇵","text":"","type":1,"version":0},{"label":"regional indicator Q","hexcode":"1F1F6","emoji":"🇶","text":"","type":1,"version":0},{"label":"regional indicator R","hexcode":"1F1F7","emoji":"🇷","text":"","type":1,"version":0},{"label":"regional indicator S","hexcode":"1F1F8","emoji":"🇸","text":"","type":1,"version":0},{"label":"regional indicator T","hexcode":"1F1F9","emoji":"🇹","text":"","type":1,"version":0},{"label":"regional indicator U","hexcode":"1F1FA","emoji":"🇺","text":"","type":1,"version":0},{"label":"regional indicator V","hexcode":"1F1FB","emoji":"🇻","text":"","type":1,"version":0},{"label":"regional indicator W","hexcode":"1F1FC","emoji":"🇼","text":"","type":1,"version":0},{"label":"regional indicator X","hexcode":"1F1FD","emoji":"🇽","text":"","type":1,"version":0},{"label":"regional indicator Y","hexcode":"1F1FE","emoji":"🇾","text":"","type":1,"version":0},{"label":"regional indicator Z","hexcode":"1F1FF","emoji":"🇿","text":"","type":1,"version":0},{"label":"grinning face","hexcode":"1F600","tags":["cheerful","cheery","face","grin","grinning","happy","laugh","nice","smile","smiling","teeth"],"emoji":"😀","text":"","type":1,"order":1,"group":0,"subgroup":0,"version":1},{"label":"grinning face with big eyes","hexcode":"1F603","tags":["awesome","big","eyes","face","grin","grinning","happy","mouth","open","smile","smiling","teeth","yay"],"emoji":"😃","text":"","type":1,"order":2,"group":0,"subgroup":0,"version":0.6},{"label":"grinning face with smiling eyes","hexcode":"1F604","tags":["eye","eyes","face","grin","grinning","happy","laugh","lol","mouth","open","smile","smiling"],"emoji":"😄","text":"","type":1,"order":3,"group":0,"subgroup":0,"version":0.6,"emoticon":":D"},{"label":"beaming face with smiling eyes","hexcode":"1F601","tags":["beaming","eye","eyes","face","grin","grinning","happy","nice","smile","smiling","teeth"],"emoji":"😁","text":"","type":1,"order":4,"group":0,"subgroup":0,"version":0.6},{"label":"grinning squinting face","hexcode":"1F606","tags":["closed","eyes","face","grinning","haha","hahaha","happy","laugh","lol","mouth","open","rofl","smile","smiling","squinting"],"emoji":"😆","text":"","type":1,"order":5,"group":0,"subgroup":0,"version":0.6,"emoticon":["xD","XD"]},{"label":"grinning face with sweat","hexcode":"1F605","tags":["cold","dejected","excited","face","grinning","mouth","nervous","open","smile","smiling","stress","stressed","sweat"],"emoji":"😅","text":"","type":1,"order":6,"group":0,"subgroup":0,"version":0.6},{"label":"rolling on the floor laughing","hexcode":"1F923","tags":["crying","face","floor","funny","haha","happy","hehe","hilarious","joy","laugh","lmao","lol","rofl","roflmao","rolling","tear"],"emoji":"🤣","text":"","type":1,"order":7,"group":0,"subgroup":0,"version":3,"emoticon":":'D"},{"label":"face with tears of joy","hexcode":"1F602","tags":["crying","face","feels","funny","haha","happy","hehe","hilarious","joy","laugh","lmao","lol","rofl","roflmao","tear"],"emoji":"😂","text":"","type":1,"order":8,"group":0,"subgroup":0,"version":0.6,"emoticon":":')"},{"label":"slightly smiling face","hexcode":"1F642","tags":["face","happy","slightly","smile","smiling"],"emoji":"🙂","text":"","type":1,"order":9,"group":0,"subgroup":0,"version":1,"emoticon":":)"},{"label":"upside-down face","hexcode":"1F643","tags":["face","hehe","smile","upside-down"],"emoji":"🙃","text":"","type":1,"order":10,"group":0,"subgroup":0,"version":1},{"label":"melting face","hexcode":"1FAE0","tags":["disappear","dissolve","embarrassed","face","haha","heat","hot","liquid","lol","melt","melting","sarcasm","sarcastic"],"emoji":"🫠","text":"","type":1,"order":11,"group":0,"subgroup":0,"version":14},{"label":"winking face","hexcode":"1F609","tags":["face","flirt","heartbreaker","sexy","slide","tease","wink","winking","winks"],"emoji":"😉","text":"","type":1,"order":12,"group":0,"subgroup":0,"version":0.6,"emoticon":";)"},{"label":"smiling face with smiling eyes","hexcode":"1F60A","tags":["blush","eye","eyes","face","glad","satisfied","smile","smiling"],"emoji":"😊","text":"","type":1,"order":13,"group":0,"subgroup":0,"version":0.6,"emoticon":":>"},{"label":"smiling face with halo","hexcode":"1F607","tags":["angel","angelic","angels","blessed","face","fairy","fairytale","fantasy","halo","happy","innocent","peaceful","smile","smiling","spirit","tale"],"emoji":"😇","text":"","type":1,"order":14,"group":0,"subgroup":0,"version":1,"emoticon":["o:)","O:)"]},{"label":"smiling face with hearts","hexcode":"1F970","tags":["3","adore","crush","face","heart","hearts","ily","love","romance","smile","smiling","you"],"emoji":"🥰","text":"","type":1,"order":15,"group":0,"subgroup":1,"version":11},{"label":"smiling face with heart-eyes","hexcode":"1F60D","tags":["143","bae","eye","face","feels","heart-eyes","hearts","ily","kisses","love","romance","romantic","smile","xoxo"],"emoji":"😍","text":"","type":1,"order":16,"group":0,"subgroup":1,"version":0.6},{"label":"star-struck","hexcode":"1F929","tags":["excited","eyes","face","grinning","smile","star","starry-eyed","wow"],"emoji":"🤩","text":"","type":1,"order":17,"group":0,"subgroup":1,"version":5},{"label":"face blowing a kiss","hexcode":"1F618","tags":["adorbs","bae","blowing","face","flirt","heart","ily","kiss","love","lover","miss","muah","romantic","smooch","xoxo","you"],"emoji":"😘","text":"","type":1,"order":18,"group":0,"subgroup":1,"version":0.6,"emoticon":[":x",":X"]},{"label":"kissing face","hexcode":"1F617","tags":["143","date","dating","face","flirt","ily","kiss","love","smooch","smooches","xoxo","you"],"emoji":"😗","text":"","type":1,"order":19,"group":0,"subgroup":1,"version":1},{"label":"smiling face","hexcode":"263A","tags":["face","happy","outlined","relaxed","smile","smiling"],"emoji":"☺️","text":"☺︎","type":0,"order":21,"group":0,"subgroup":1,"version":0.6},{"label":"kissing face with closed eyes","hexcode":"1F61A","tags":["143","bae","blush","closed","date","dating","eye","eyes","face","flirt","ily","kisses","kissing","smooches","xoxo"],"emoji":"😚","text":"","type":1,"order":22,"group":0,"subgroup":1,"version":0.6,"emoticon":":*"},{"label":"kissing face with smiling eyes","hexcode":"1F619","tags":["143","closed","date","dating","eye","eyes","face","flirt","ily","kiss","kisses","kissing","love","night","smile","smiling"],"emoji":"😙","text":"","type":1,"order":23,"group":0,"subgroup":1,"version":1},{"label":"smiling face with tear","hexcode":"1F972","tags":["face","glad","grateful","happy","joy","pain","proud","relieved","smile","smiley","smiling","tear","touched"],"emoji":"🥲","text":"","type":1,"order":24,"group":0,"subgroup":1,"version":13},{"label":"face savoring food","hexcode":"1F60B","tags":["delicious","eat","face","food","full","hungry","savor","smile","smiling","tasty","um","yum","yummy"],"emoji":"😋","text":"","type":1,"order":25,"group":0,"subgroup":2,"version":0.6},{"label":"face with tongue","hexcode":"1F61B","tags":["awesome","cool","face","nice","party","stuck-out","sweet","tongue"],"emoji":"😛","text":"","type":1,"order":26,"group":0,"subgroup":2,"version":1,"emoticon":[":p",":P"]},{"label":"winking face with tongue","hexcode":"1F61C","tags":["crazy","epic","eye","face","funny","joke","loopy","nutty","party","stuck-out","tongue","wacky","weirdo","wink","winking","yolo"],"emoji":"😜","text":"","type":1,"order":27,"group":0,"subgroup":2,"version":0.6,"emoticon":[";p",";P"]},{"label":"zany face","hexcode":"1F92A","tags":["crazy","eye","eyes","face","goofy","large","small","zany"],"emoji":"🤪","text":"","type":1,"order":28,"group":0,"subgroup":2,"version":5},{"label":"squinting face with tongue","hexcode":"1F61D","tags":["closed","eye","eyes","face","gross","horrible","omg","squinting","stuck-out","taste","tongue","whatever","yolo"],"emoji":"😝","text":"","type":1,"order":29,"group":0,"subgroup":2,"version":0.6,"emoticon":["xp","xP","XP"]},{"label":"money-mouth face","hexcode":"1F911","tags":["face","money","money-mouth","mouth","paid"],"emoji":"🤑","text":"","type":1,"order":30,"group":0,"subgroup":2,"version":1},{"label":"smiling face with open hands","hexcode":"1F917","tags":["face","hands","hug","hugging","open","smiling"],"emoji":"🤗","text":"","type":1,"order":31,"group":0,"subgroup":3,"version":1},{"label":"face with hand over mouth","hexcode":"1F92D","tags":["face","giggle","giggling","hand","mouth","oops","realization","secret","shock","sudden","surprise","whoops"],"emoji":"🤭","text":"","type":1,"order":32,"group":0,"subgroup":3,"version":5},{"label":"face with open eyes and hand over mouth","hexcode":"1FAE2","tags":["amazement","awe","disbelief","embarrass","eyes","face","gasp","hand","mouth","omg","open","over","quiet","scared","shock","surprise"],"emoji":"🫢","text":"","type":1,"order":33,"group":0,"subgroup":3,"version":14},{"label":"face with peeking eye","hexcode":"1FAE3","tags":["captivated","embarrass","eye","face","hide","hiding","peek","peeking","peep","scared","shy","stare"],"emoji":"🫣","text":"","type":1,"order":34,"group":0,"subgroup":3,"version":14},{"label":"shushing face","hexcode":"1F92B","tags":["face","quiet","shh","shush","shushing"],"emoji":"🤫","text":"","type":1,"order":35,"group":0,"subgroup":3,"version":5},{"label":"thinking face","hexcode":"1F914","tags":["chin","consider","face","hmm","ponder","pondering","thinking","wondering"],"emoji":"🤔","text":"","type":1,"order":36,"group":0,"subgroup":3,"version":1,"emoticon":[":l",":L"]},{"label":"saluting face","hexcode":"1FAE1","tags":["face","good","luck","ma’am","ok","respect","salute","saluting","sir","troops","yes"],"emoji":"🫡","text":"","type":1,"order":37,"group":0,"subgroup":3,"version":14},{"label":"zipper-mouth face","hexcode":"1F910","tags":["face","keep","mouth","quiet","secret","shut","zip","zipper","zipper-mouth"],"emoji":"🤐","text":"","type":1,"order":38,"group":0,"subgroup":4,"version":1,"emoticon":[":z",":Z"]},{"label":"face with raised eyebrow","hexcode":"1F928","tags":["disapproval","disbelief","distrust","emoji","eyebrow","face","hmm","mild","raised","skeptic","skeptical","skepticism","surprise","what"],"emoji":"🤨","text":"","type":1,"order":39,"group":0,"subgroup":4,"version":5},{"label":"neutral face","hexcode":"1F610","tags":["awkward","blank","deadpan","expressionless","face","fine","jealous","meh","neutral","oh","shade","straight","unamused","unhappy","unimpressed","whatever"],"emoji":"😐️","text":"😐︎","type":1,"order":40,"group":0,"subgroup":4,"version":0.7,"emoticon":":|"},{"label":"expressionless face","hexcode":"1F611","tags":["awkward","dead","expressionless","face","fine","inexpressive","jealous","meh","not","oh","omg","straight","uh","unhappy","unimpressed","whatever"],"emoji":"😑","text":"","type":1,"order":41,"group":0,"subgroup":4,"version":1},{"label":"face without mouth","hexcode":"1F636","tags":["awkward","blank","expressionless","face","mouth","mouthless","mute","quiet","secret","silence","silent","speechless"],"emoji":"😶","text":"","type":1,"order":42,"group":0,"subgroup":4,"version":1,"emoticon":":#"},{"label":"dotted line face","hexcode":"1FAE5","tags":["depressed","disappear","dotted","face","hidden","hide","introvert","invisible","line","meh","whatever","wtv"],"emoji":"🫥","text":"","type":1,"order":43,"group":0,"subgroup":4,"version":14},{"label":"face in clouds","hexcode":"1F636-200D-1F32B-FE0F","tags":["absentminded","clouds","face","fog","head"],"emoji":"😶‍🌫️","text":"","type":1,"order":44,"group":0,"subgroup":4,"version":13.1},{"label":"smirking face","hexcode":"1F60F","tags":["boss","dapper","face","flirt","homie","kidding","leer","shade","slick","sly","smirk","smug","snicker","suave","suspicious","swag"],"emoji":"😏","text":"","type":1,"order":46,"group":0,"subgroup":4,"version":0.6,"emoticon":":j"},{"label":"unamused face","hexcode":"1F612","tags":["...","bored","face","fine","jealous","jel","jelly","pissed","smh","ugh","uhh","unamused","unhappy","weird","whatever"],"emoji":"😒","text":"","type":1,"order":47,"group":0,"subgroup":4,"version":0.6,"emoticon":":?"},{"label":"face with rolling eyes","hexcode":"1F644","tags":["eyeroll","eyes","face","rolling","shade","ugh","whatever"],"emoji":"🙄","text":"","type":1,"order":48,"group":0,"subgroup":4,"version":1},{"label":"grimacing face","hexcode":"1F62C","tags":["awk","awkward","dentist","face","grimace","grimacing","grinning","smile","smiling"],"emoji":"😬","text":"","type":1,"order":49,"group":0,"subgroup":4,"version":1,"emoticon":"8D"},{"label":"face exhaling","hexcode":"1F62E-200D-1F4A8","tags":["blow","blowing","exhale","exhaling","exhausted","face","gasp","groan","relief","sigh","smiley","smoke","whisper","whistle"],"emoji":"😮‍💨","text":"","type":1,"order":50,"group":0,"subgroup":4,"version":13.1},{"label":"lying face","hexcode":"1F925","tags":["face","liar","lie","lying","pinocchio"],"emoji":"🤥","text":"","type":1,"order":51,"group":0,"subgroup":4,"version":3},{"label":"shaking face","hexcode":"1FAE8","tags":["crazy","daze","earthquake","face","omg","panic","shaking","shock","surprise","vibrate","whoa","wow"],"emoji":"🫨","text":"","type":1,"order":52,"group":0,"subgroup":4,"version":15},{"label":"head shaking horizontally","hexcode":"1F642-200D-2194-FE0F","tags":["head","horizontally","no","shake","shaking"],"emoji":"🙂‍↔️","text":"","type":1,"order":53,"group":0,"subgroup":4,"version":15.1},{"label":"head shaking vertically","hexcode":"1F642-200D-2195-FE0F","tags":["head","nod","shaking","vertically","yes"],"emoji":"🙂‍↕️","text":"","type":1,"order":55,"group":0,"subgroup":4,"version":15.1},{"label":"relieved face","hexcode":"1F60C","tags":["calm","face","peace","relief","relieved","zen"],"emoji":"😌","text":"","type":1,"order":57,"group":0,"subgroup":5,"version":0.6},{"label":"pensive face","hexcode":"1F614","tags":["awful","bored","dejected","died","disappointed","face","losing","lost","pensive","sad","sucks"],"emoji":"😔","text":"","type":1,"order":58,"group":0,"subgroup":5,"version":0.6},{"label":"sleepy face","hexcode":"1F62A","tags":["crying","face","good","night","sad","sleep","sleeping","sleepy","tired"],"emoji":"😪","text":"","type":1,"order":59,"group":0,"subgroup":5,"version":0.6},{"label":"drooling face","hexcode":"1F924","tags":["drooling","face"],"emoji":"🤤","text":"","type":1,"order":60,"group":0,"subgroup":5,"version":3},{"label":"sleeping face","hexcode":"1F634","tags":["bed","bedtime","face","good","goodnight","nap","night","sleep","sleeping","tired","whatever","yawn","zzz"],"emoji":"😴","text":"","type":1,"order":61,"group":0,"subgroup":5,"version":1},{"label":"face with bags under eyes","hexcode":"1FAE9","tags":["bags","bored","exhausted","eyes","face","fatigued","late","sleepy","tired","weary"],"emoji":"🫩","text":"","type":1,"order":62,"group":0,"subgroup":5,"version":16},{"label":"face with medical mask","hexcode":"1F637","tags":["cold","dentist","dermatologist","doctor","dr","face","germs","mask","medical","medicine","sick"],"emoji":"😷","text":"","type":1,"order":63,"group":0,"subgroup":6,"version":0.6},{"label":"face with thermometer","hexcode":"1F912","tags":["face","ill","sick","thermometer"],"emoji":"🤒","text":"","type":1,"order":64,"group":0,"subgroup":6,"version":1},{"label":"face with head-bandage","hexcode":"1F915","tags":["bandage","face","head-bandage","hurt","injury","ouch"],"emoji":"🤕","text":"","type":1,"order":65,"group":0,"subgroup":6,"version":1},{"label":"nauseated face","hexcode":"1F922","tags":["face","gross","nasty","nauseated","sick","vomit"],"emoji":"🤢","text":"","type":1,"order":66,"group":0,"subgroup":6,"version":3,"emoticon":"%("},{"label":"face vomiting","hexcode":"1F92E","tags":["barf","ew","face","gross","puke","sick","spew","throw","up","vomit","vomiting"],"emoji":"🤮","text":"","type":1,"order":67,"group":0,"subgroup":6,"version":5},{"label":"sneezing face","hexcode":"1F927","tags":["face","fever","flu","gesundheit","sick","sneeze","sneezing"],"emoji":"🤧","text":"","type":1,"order":68,"group":0,"subgroup":6,"version":3},{"label":"hot face","hexcode":"1F975","tags":["dying","face","feverish","heat","hot","panting","red-faced","stroke","sweating","tongue"],"emoji":"🥵","text":"","type":1,"order":69,"group":0,"subgroup":6,"version":11},{"label":"cold face","hexcode":"1F976","tags":["blue","blue-faced","cold","face","freezing","frostbite","icicles","subzero","teeth"],"emoji":"🥶","text":"","type":1,"order":70,"group":0,"subgroup":6,"version":11},{"label":"woozy face","hexcode":"1F974","tags":["dizzy","drunk","eyes","face","intoxicated","mouth","tipsy","uneven","wavy","woozy"],"emoji":"🥴","text":"","type":1,"order":71,"group":0,"subgroup":6,"version":11,"emoticon":":&"},{"label":"face with crossed-out eyes","hexcode":"1F635","tags":["crossed-out","dead","dizzy","eyes","face","feels","knocked","out","sick","tired"],"emoji":"😵","text":"","type":1,"order":72,"group":0,"subgroup":6,"version":0.6,"emoticon":["xo","XO"]},{"label":"face with spiral eyes","hexcode":"1F635-200D-1F4AB","tags":["confused","dizzy","eyes","face","hypnotized","omg","smiley","spiral","trouble","whoa","woah","woozy"],"emoji":"😵‍💫","text":"","type":1,"order":73,"group":0,"subgroup":6,"version":13.1},{"label":"exploding head","hexcode":"1F92F","tags":["blown","explode","exploding","head","mind","mindblown","no","shocked","way"],"emoji":"🤯","text":"","type":1,"order":74,"group":0,"subgroup":6,"version":5},{"label":"cowboy hat face","hexcode":"1F920","tags":["cowboy","cowgirl","face","hat"],"emoji":"🤠","text":"","type":1,"order":75,"group":0,"subgroup":7,"version":3},{"label":"partying face","hexcode":"1F973","tags":["bday","birthday","celebrate","celebration","excited","face","happy","hat","hooray","horn","party","partying"],"emoji":"🥳","text":"","type":1,"order":76,"group":0,"subgroup":7,"version":11},{"label":"disguised face","hexcode":"1F978","tags":["disguise","eyebrow","face","glasses","incognito","moustache","mustache","nose","person","spy","tache","tash"],"emoji":"🥸","text":"","type":1,"order":77,"group":0,"subgroup":7,"version":13},{"label":"smiling face with sunglasses","hexcode":"1F60E","tags":["awesome","beach","bright","bro","chilling","cool","face","rad","relaxed","shades","slay","smile","style","sunglasses","swag","win"],"emoji":"😎","text":"","type":1,"order":78,"group":0,"subgroup":8,"version":1,"emoticon":"8)"},{"label":"nerd face","hexcode":"1F913","tags":["brainy","clever","expert","face","geek","gifted","glasses","intelligent","nerd","smart"],"emoji":"🤓","text":"","type":1,"order":79,"group":0,"subgroup":8,"version":1,"emoticon":":B"},{"label":"face with monocle","hexcode":"1F9D0","tags":["classy","face","fancy","monocle","rich","stuffy","wealthy"],"emoji":"🧐","text":"","type":1,"order":80,"group":0,"subgroup":8,"version":5},{"label":"confused face","hexcode":"1F615","tags":["befuddled","confused","confusing","dunno","face","frown","hm","meh","not","sad","sorry","sure"],"emoji":"😕","text":"","type":1,"order":81,"group":0,"subgroup":9,"version":1,"emoticon":":/"},{"label":"face with diagonal mouth","hexcode":"1FAE4","tags":["confused","confusion","diagonal","disappointed","doubt","doubtful","face","frustrated","frustration","meh","mouth","skeptical","unsure","whatever","wtv"],"emoji":"🫤","text":"","type":1,"order":82,"group":0,"subgroup":9,"version":14},{"label":"worried face","hexcode":"1F61F","tags":["anxious","butterflies","face","nerves","nervous","sad","stress","stressed","surprised","worried","worry"],"emoji":"😟","text":"","type":1,"order":83,"group":0,"subgroup":9,"version":1},{"label":"slightly frowning face","hexcode":"1F641","tags":["face","frown","frowning","sad","slightly"],"emoji":"🙁","text":"","type":1,"order":84,"group":0,"subgroup":9,"version":1},{"label":"frowning face","hexcode":"2639","tags":["face","frown","frowning","sad"],"emoji":"☹️","text":"☹︎","type":0,"order":86,"group":0,"subgroup":9,"version":0.7,"emoticon":":("},{"label":"face with open mouth","hexcode":"1F62E","tags":["believe","face","forgot","mouth","omg","open","shocked","surprised","sympathy","unbelievable","unreal","whoa","wow","you"],"emoji":"😮","text":"","type":1,"order":87,"group":0,"subgroup":9,"version":1},{"label":"hushed face","hexcode":"1F62F","tags":["epic","face","hushed","omg","stunned","surprised","whoa","woah"],"emoji":"😯","text":"","type":1,"order":88,"group":0,"subgroup":9,"version":1},{"label":"astonished face","hexcode":"1F632","tags":["astonished","cost","face","no","omg","shocked","totally","way"],"emoji":"😲","text":"","type":1,"order":89,"group":0,"subgroup":9,"version":0.6,"emoticon":[":o",":O"]},{"label":"flushed face","hexcode":"1F633","tags":["amazed","awkward","crazy","dazed","dead","disbelief","embarrassed","face","flushed","geez","heat","hot","impressed","jeez","what","wow"],"emoji":"😳","text":"","type":1,"order":90,"group":0,"subgroup":9,"version":0.6,"emoticon":":$"},{"label":"distorted face","hexcode":"1FAEA","tags":["anxiety","bloated","panic","shocked","surprised","vulnerable"],"emoji":"🫪","text":"","type":1,"order":91,"group":0,"subgroup":9,"version":17},{"label":"pleading face","hexcode":"1F97A","tags":["begging","big","eyes","face","mercy","not","pleading","please","pretty","puppy","sad","why"],"emoji":"🥺","text":"","type":1,"order":92,"group":0,"subgroup":9,"version":11},{"label":"face holding back tears","hexcode":"1F979","tags":["admiration","aww","back","cry","embarrassed","face","feelings","grateful","gratitude","holding","joy","please","proud","resist","sad","tears"],"emoji":"🥹","text":"","type":1,"order":93,"group":0,"subgroup":9,"version":14},{"label":"frowning face with open mouth","hexcode":"1F626","tags":["caught","face","frown","frowning","guard","mouth","open","scared","scary","surprise","what","wow"],"emoji":"😦","text":"","type":1,"order":94,"group":0,"subgroup":9,"version":1},{"label":"anguished face","hexcode":"1F627","tags":["anguished","face","forgot","scared","scary","stressed","surprise","unhappy","what","wow"],"emoji":"😧","text":"","type":1,"order":95,"group":0,"subgroup":9,"version":1,"emoticon":[":s",":S"]},{"label":"fearful face","hexcode":"1F628","tags":["afraid","anxious","blame","face","fear","fearful","scared","worried"],"emoji":"😨","text":"","type":1,"order":96,"group":0,"subgroup":9,"version":0.6},{"label":"anxious face with sweat","hexcode":"1F630","tags":["anxious","blue","cold","eek","face","mouth","nervous","open","rushed","scared","sweat","yikes"],"emoji":"😰","text":"","type":1,"order":97,"group":0,"subgroup":9,"version":0.6},{"label":"sad but relieved face","hexcode":"1F625","tags":["anxious","call","close","complicated","disappointed","face","not","relieved","sad","sweat","time","whew"],"emoji":"😥","text":"","type":1,"order":98,"group":0,"subgroup":9,"version":0.6},{"label":"crying face","hexcode":"1F622","tags":["awful","cry","crying","face","feels","miss","sad","tear","triste","unhappy"],"emoji":"😢","text":"","type":1,"order":99,"group":0,"subgroup":9,"version":0.6,"emoticon":":'("},{"label":"loudly crying face","hexcode":"1F62D","tags":["bawling","cry","crying","face","loudly","sad","sob","tear","tears","unhappy"],"emoji":"😭","text":"","type":1,"order":100,"group":0,"subgroup":9,"version":0.6,"emoticon":":'o"},{"label":"face screaming in fear","hexcode":"1F631","tags":["epic","face","fear","fearful","munch","scared","scream","screamer","screaming","shocked","surprised","woah"],"emoji":"😱","text":"","type":1,"order":101,"group":0,"subgroup":9,"version":0.6,"emoticon":"Dx"},{"label":"confounded face","hexcode":"1F616","tags":["annoyed","confounded","confused","cringe","distraught","face","feels","frustrated","mad","sad"],"emoji":"😖","text":"","type":1,"order":102,"group":0,"subgroup":9,"version":0.6,"emoticon":["x(","X("]},{"label":"persevering face","hexcode":"1F623","tags":["concentrate","concentration","face","focus","headache","persevere","persevering"],"emoji":"😣","text":"","type":1,"order":103,"group":0,"subgroup":9,"version":0.6},{"label":"disappointed face","hexcode":"1F61E","tags":["awful","blame","dejected","disappointed","face","fail","losing","sad","unhappy"],"emoji":"😞","text":"","type":1,"order":104,"group":0,"subgroup":9,"version":0.6},{"label":"downcast face with sweat","hexcode":"1F613","tags":["close","cold","downcast","face","feels","headache","nervous","sad","scared","sweat","yikes"],"emoji":"😓","text":"","type":1,"order":105,"group":0,"subgroup":9,"version":0.6,"emoticon":":<"},{"label":"weary face","hexcode":"1F629","tags":["crying","face","fail","feels","hungry","mad","nooo","sad","sleepy","tired","unhappy","weary"],"emoji":"😩","text":"","type":1,"order":106,"group":0,"subgroup":9,"version":0.6,"emoticon":"D:"},{"label":"tired face","hexcode":"1F62B","tags":["cost","face","feels","nap","sad","sneeze","tired"],"emoji":"😫","text":"","type":1,"order":107,"group":0,"subgroup":9,"version":0.6,"emoticon":[":c",":C"]},{"label":"yawning face","hexcode":"1F971","tags":["bedtime","bored","face","goodnight","nap","night","sleep","sleepy","tired","whatever","yawn","yawning","zzz"],"emoji":"🥱","text":"","type":1,"order":108,"group":0,"subgroup":9,"version":12},{"label":"face with steam from nose","hexcode":"1F624","tags":["anger","angry","face","feels","fume","fuming","furious","fury","mad","nose","steam","triumph","unhappy","won"],"emoji":"😤","text":"","type":1,"order":109,"group":0,"subgroup":10,"version":0.6},{"label":"enraged face","hexcode":"1F621","tags":["anger","angry","enraged","face","feels","mad","maddening","pouting","rage","red","shade","unhappy","upset"],"emoji":"😡","text":"","type":1,"order":110,"group":0,"subgroup":10,"version":0.6,"emoticon":">:/"},{"label":"angry face","hexcode":"1F620","tags":["anger","angry","blame","face","feels","frustrated","mad","maddening","rage","shade","unhappy","upset"],"emoji":"😠","text":"","type":1,"order":111,"group":0,"subgroup":10,"version":0.6},{"label":"face with symbols on mouth","hexcode":"1F92C","tags":["censor","cursing","cussing","face","mad","mouth","pissed","swearing","symbols"],"emoji":"🤬","text":"","type":1,"order":112,"group":0,"subgroup":10,"version":5,"emoticon":":@"},{"label":"smiling face with horns","hexcode":"1F608","tags":["demon","devil","evil","face","fairy","fairytale","fantasy","horns","purple","shade","smile","smiling","tale"],"emoji":"😈","text":"","type":1,"order":113,"group":0,"subgroup":10,"version":1,"emoticon":">:)"},{"label":"angry face with horns","hexcode":"1F47F","tags":["angry","demon","devil","evil","face","fairy","fairytale","fantasy","horns","imp","mischievous","purple","shade","tale"],"emoji":"👿","text":"","type":1,"order":114,"group":0,"subgroup":10,"version":0.6,"emoticon":">:("},{"label":"skull","hexcode":"1F480","tags":["body","dead","death","face","fairy","fairytale","i’m","lmao","monster","tale","yolo"],"emoji":"💀","text":"","type":1,"order":115,"group":0,"subgroup":10,"version":0.6},{"label":"skull and crossbones","hexcode":"2620","tags":["bone","crossbones","dead","death","face","monster","skull"],"emoji":"☠️","text":"☠︎","type":0,"order":117,"group":0,"subgroup":10,"version":1},{"label":"pile of poo","hexcode":"1F4A9","tags":["bs","comic","doo","dung","face","fml","monster","pile","poo","poop","smelly","smh","stink","stinks","stinky","turd"],"emoji":"💩","text":"","type":1,"order":118,"group":0,"subgroup":11,"version":0.6},{"label":"clown face","hexcode":"1F921","tags":["clown","face"],"emoji":"🤡","text":"","type":1,"order":119,"group":0,"subgroup":11,"version":3},{"label":"ogre","hexcode":"1F479","tags":["creature","devil","face","fairy","fairytale","fantasy","mask","monster","scary","tale"],"emoji":"👹","text":"","type":1,"order":120,"group":0,"subgroup":11,"version":0.6,"emoticon":">0)"},{"label":"goblin","hexcode":"1F47A","tags":["angry","creature","face","fairy","fairytale","fantasy","mask","mean","monster","tale"],"emoji":"👺","text":"","type":1,"order":121,"group":0,"subgroup":11,"version":0.6},{"label":"ghost","hexcode":"1F47B","tags":["boo","creature","excited","face","fairy","fairytale","fantasy","halloween","haunting","monster","scary","silly","tale"],"emoji":"👻","text":"","type":1,"order":122,"group":0,"subgroup":11,"version":0.6},{"label":"alien","hexcode":"1F47D","tags":["creature","extraterrestrial","face","fairy","fairytale","fantasy","monster","space","tale","ufo"],"emoji":"👽️","text":"👽︎","type":1,"order":123,"group":0,"subgroup":11,"version":0.6},{"label":"alien monster","hexcode":"1F47E","tags":["alien","creature","extraterrestrial","face","fairy","fairytale","fantasy","game","gamer","games","monster","pixelated","space","tale","ufo"],"emoji":"👾","text":"","type":1,"order":124,"group":0,"subgroup":11,"version":0.6},{"label":"robot","hexcode":"1F916","tags":["face","monster"],"emoji":"🤖","text":"","type":1,"order":125,"group":0,"subgroup":11,"version":1},{"label":"grinning cat","hexcode":"1F63A","tags":["animal","cat","face","grinning","mouth","open","smile","smiling"],"emoji":"😺","text":"","type":1,"order":126,"group":0,"subgroup":12,"version":0.6},{"label":"grinning cat with smiling eyes","hexcode":"1F638","tags":["animal","cat","eye","eyes","face","grin","grinning","smile","smiling"],"emoji":"😸","text":"","type":1,"order":127,"group":0,"subgroup":12,"version":0.6},{"label":"cat with tears of joy","hexcode":"1F639","tags":["animal","cat","face","joy","laugh","laughing","lol","tear","tears"],"emoji":"😹","text":"","type":1,"order":128,"group":0,"subgroup":12,"version":0.6},{"label":"smiling cat with heart-eyes","hexcode":"1F63B","tags":["animal","cat","eye","face","heart","heart-eyes","love","smile","smiling"],"emoji":"😻","text":"","type":1,"order":129,"group":0,"subgroup":12,"version":0.6},{"label":"cat with wry smile","hexcode":"1F63C","tags":["animal","cat","face","ironic","smile","wry"],"emoji":"😼","text":"","type":1,"order":130,"group":0,"subgroup":12,"version":0.6},{"label":"kissing cat","hexcode":"1F63D","tags":["animal","cat","closed","eye","eyes","face","kiss","kissing"],"emoji":"😽","text":"","type":1,"order":131,"group":0,"subgroup":12,"version":0.6,"emoticon":":3"},{"label":"weary cat","hexcode":"1F640","tags":["animal","cat","face","oh","surprised","weary"],"emoji":"🙀","text":"","type":1,"order":132,"group":0,"subgroup":12,"version":0.6},{"label":"crying cat","hexcode":"1F63F","tags":["animal","cat","cry","crying","face","sad","tear"],"emoji":"😿","text":"","type":1,"order":133,"group":0,"subgroup":12,"version":0.6},{"label":"pouting cat","hexcode":"1F63E","tags":["animal","cat","face","pouting"],"emoji":"😾","text":"","type":1,"order":134,"group":0,"subgroup":12,"version":0.6},{"label":"see-no-evil monkey","hexcode":"1F648","tags":["embarrassed","evil","face","forbidden","forgot","gesture","hide","monkey","no","omg","prohibited","scared","secret","smh","watch"],"emoji":"🙈","text":"","type":1,"order":135,"group":0,"subgroup":13,"version":0.6},{"label":"hear-no-evil monkey","hexcode":"1F649","tags":["animal","ears","evil","face","forbidden","gesture","hear","listen","monkey","no","not","prohibited","secret","shh","tmi"],"emoji":"🙉","text":"","type":1,"order":136,"group":0,"subgroup":13,"version":0.6},{"label":"speak-no-evil monkey","hexcode":"1F64A","tags":["animal","evil","face","forbidden","gesture","monkey","no","not","oops","prohibited","quiet","secret","speak","stealth"],"emoji":"🙊","text":"","type":1,"order":137,"group":0,"subgroup":13,"version":0.6},{"label":"love letter","hexcode":"1F48C","tags":["heart","letter","love","mail","romance","valentine"],"emoji":"💌","text":"","type":1,"order":138,"group":0,"subgroup":14,"version":0.6},{"label":"heart with arrow","hexcode":"1F498","tags":["143","adorbs","arrow","cupid","date","emotion","heart","ily","love","romance","valentine"],"emoji":"💘","text":"","type":1,"order":139,"group":0,"subgroup":14,"version":0.6},{"label":"heart with ribbon","hexcode":"1F49D","tags":["143","anniversary","emotion","heart","ily","kisses","ribbon","valentine","xoxo"],"emoji":"💝","text":"","type":1,"order":140,"group":0,"subgroup":14,"version":0.6},{"label":"sparkling heart","hexcode":"1F496","tags":["143","emotion","excited","good","heart","ily","kisses","morning","night","sparkle","sparkling","xoxo"],"emoji":"💖","text":"","type":1,"order":141,"group":0,"subgroup":14,"version":0.6},{"label":"growing heart","hexcode":"1F497","tags":["143","emotion","excited","growing","heart","heartpulse","ily","kisses","muah","nervous","pulse","xoxo"],"emoji":"💗","text":"","type":1,"order":142,"group":0,"subgroup":14,"version":0.6},{"label":"beating heart","hexcode":"1F493","tags":["143","beating","cardio","emotion","heart","heartbeat","ily","love","pulsating","pulse"],"emoji":"💓","text":"","type":1,"order":143,"group":0,"subgroup":14,"version":0.6},{"label":"revolving hearts","hexcode":"1F49E","tags":["143","adorbs","anniversary","emotion","heart","hearts","revolving"],"emoji":"💞","text":"","type":1,"order":144,"group":0,"subgroup":14,"version":0.6},{"label":"two hearts","hexcode":"1F495","tags":["143","anniversary","date","dating","emotion","heart","hearts","ily","kisses","love","loving","two","xoxo"],"emoji":"💕","text":"","type":1,"order":145,"group":0,"subgroup":14,"version":0.6},{"label":"heart decoration","hexcode":"1F49F","tags":["143","decoration","emotion","heart","hearth","purple","white"],"emoji":"💟","text":"","type":1,"order":146,"group":0,"subgroup":14,"version":0.6},{"label":"heart exclamation","hexcode":"2763","tags":["exclamation","heart","heavy","mark","punctuation"],"emoji":"❣️","text":"❣︎","type":0,"order":148,"group":0,"subgroup":14,"version":1},{"label":"broken heart","hexcode":"1F494","tags":["break","broken","crushed","emotion","heart","heartbroken","lonely","sad"],"emoji":"💔","text":"","type":1,"order":149,"group":0,"subgroup":14,"version":0.6,"emoticon":"","skins":[{"label":"man mage: light skin tone","hexcode":"1F9D9-1F3FB-200D-2642-FE0F","emoji":"🧙🏻‍♂️","text":"","type":1,"order":1738,"group":1,"subgroup":26,"version":5,"gender":1,"tone":1},{"label":"man mage: medium-light skin tone","hexcode":"1F9D9-1F3FC-200D-2642-FE0F","emoji":"🧙🏼‍♂️","text":"","type":1,"order":1740,"group":1,"subgroup":26,"version":5,"gender":1,"tone":2},{"label":"man mage: medium skin tone","hexcode":"1F9D9-1F3FD-200D-2642-FE0F","emoji":"🧙🏽‍♂️","text":"","type":1,"order":1742,"group":1,"subgroup":26,"version":5,"gender":1,"tone":3},{"label":"man mage: medium-dark skin tone","hexcode":"1F9D9-1F3FE-200D-2642-FE0F","emoji":"🧙🏾‍♂️","text":"","type":1,"order":1744,"group":1,"subgroup":26,"version":5,"gender":1,"tone":4},{"label":"man mage: dark skin tone","hexcode":"1F9D9-1F3FF-200D-2642-FE0F","emoji":"🧙🏿‍♂️","text":"","type":1,"order":1746,"group":1,"subgroup":26,"version":5,"gender":1,"tone":5}]},{"label":"woman mage","hexcode":"1F9D9-200D-2640-FE0F","tags":["fantasy","mage","magic","play","sorcerer","sorceress","sorcery","spell","summon","witch","wizard","woman"],"emoji":"🧙‍♀️","text":"","type":1,"order":1748,"group":1,"subgroup":26,"version":5,"gender":0,"skins":[{"label":"woman mage: light skin tone","hexcode":"1F9D9-1F3FB-200D-2640-FE0F","emoji":"🧙🏻‍♀️","text":"","type":1,"order":1750,"group":1,"subgroup":26,"version":5,"gender":0,"tone":1},{"label":"woman mage: medium-light skin tone","hexcode":"1F9D9-1F3FC-200D-2640-FE0F","emoji":"🧙🏼‍♀️","text":"","type":1,"order":1752,"group":1,"subgroup":26,"version":5,"gender":0,"tone":2},{"label":"woman mage: medium skin tone","hexcode":"1F9D9-1F3FD-200D-2640-FE0F","emoji":"🧙🏽‍♀️","text":"","type":1,"order":1754,"group":1,"subgroup":26,"version":5,"gender":0,"tone":3},{"label":"woman mage: medium-dark skin tone","hexcode":"1F9D9-1F3FE-200D-2640-FE0F","emoji":"🧙🏾‍♀️","text":"","type":1,"order":1756,"group":1,"subgroup":26,"version":5,"gender":0,"tone":4},{"label":"woman mage: dark skin tone","hexcode":"1F9D9-1F3FF-200D-2640-FE0F","emoji":"🧙🏿‍♀️","text":"","type":1,"order":1758,"group":1,"subgroup":26,"version":5,"gender":0,"tone":5}]},{"label":"fairy","hexcode":"1F9DA","tags":["fairytale","fantasy","myth","person","pixie","tale","wings"],"emoji":"🧚","text":"","type":1,"order":1760,"group":1,"subgroup":26,"version":5,"skins":[{"label":"fairy: light skin tone","hexcode":"1F9DA-1F3FB","emoji":"🧚🏻","text":"","type":1,"order":1761,"group":1,"subgroup":26,"version":5,"tone":1},{"label":"fairy: medium-light skin tone","hexcode":"1F9DA-1F3FC","emoji":"🧚🏼","text":"","type":1,"order":1762,"group":1,"subgroup":26,"version":5,"tone":2},{"label":"fairy: medium skin tone","hexcode":"1F9DA-1F3FD","emoji":"🧚🏽","text":"","type":1,"order":1763,"group":1,"subgroup":26,"version":5,"tone":3},{"label":"fairy: medium-dark skin tone","hexcode":"1F9DA-1F3FE","emoji":"🧚🏾","text":"","type":1,"order":1764,"group":1,"subgroup":26,"version":5,"tone":4},{"label":"fairy: dark skin tone","hexcode":"1F9DA-1F3FF","emoji":"🧚🏿","text":"","type":1,"order":1765,"group":1,"subgroup":26,"version":5,"tone":5}]},{"label":"man fairy","hexcode":"1F9DA-200D-2642-FE0F","tags":["fairy","fairytale","fantasy","man","myth","oberon","person","pixie","puck","tale","wings"],"emoji":"🧚‍♂️","text":"","type":1,"order":1766,"group":1,"subgroup":26,"version":5,"gender":1,"skins":[{"label":"man fairy: light skin tone","hexcode":"1F9DA-1F3FB-200D-2642-FE0F","emoji":"🧚🏻‍♂️","text":"","type":1,"order":1768,"group":1,"subgroup":26,"version":5,"gender":1,"tone":1},{"label":"man fairy: medium-light skin tone","hexcode":"1F9DA-1F3FC-200D-2642-FE0F","emoji":"🧚🏼‍♂️","text":"","type":1,"order":1770,"group":1,"subgroup":26,"version":5,"gender":1,"tone":2},{"label":"man fairy: medium skin tone","hexcode":"1F9DA-1F3FD-200D-2642-FE0F","emoji":"🧚🏽‍♂️","text":"","type":1,"order":1772,"group":1,"subgroup":26,"version":5,"gender":1,"tone":3},{"label":"man fairy: medium-dark skin tone","hexcode":"1F9DA-1F3FE-200D-2642-FE0F","emoji":"🧚🏾‍♂️","text":"","type":1,"order":1774,"group":1,"subgroup":26,"version":5,"gender":1,"tone":4},{"label":"man fairy: dark skin tone","hexcode":"1F9DA-1F3FF-200D-2642-FE0F","emoji":"🧚🏿‍♂️","text":"","type":1,"order":1776,"group":1,"subgroup":26,"version":5,"gender":1,"tone":5}]},{"label":"woman fairy","hexcode":"1F9DA-200D-2640-FE0F","tags":["fairy","fairytale","fantasy","myth","person","pixie","tale","titania","wings","woman"],"emoji":"🧚‍♀️","text":"","type":1,"order":1778,"group":1,"subgroup":26,"version":5,"gender":0,"skins":[{"label":"woman fairy: light skin tone","hexcode":"1F9DA-1F3FB-200D-2640-FE0F","emoji":"🧚🏻‍♀️","text":"","type":1,"order":1780,"group":1,"subgroup":26,"version":5,"gender":0,"tone":1},{"label":"woman fairy: medium-light skin tone","hexcode":"1F9DA-1F3FC-200D-2640-FE0F","emoji":"🧚🏼‍♀️","text":"","type":1,"order":1782,"group":1,"subgroup":26,"version":5,"gender":0,"tone":2},{"label":"woman fairy: medium skin tone","hexcode":"1F9DA-1F3FD-200D-2640-FE0F","emoji":"🧚🏽‍♀️","text":"","type":1,"order":1784,"group":1,"subgroup":26,"version":5,"gender":0,"tone":3},{"label":"woman fairy: medium-dark skin tone","hexcode":"1F9DA-1F3FE-200D-2640-FE0F","emoji":"🧚🏾‍♀️","text":"","type":1,"order":1786,"group":1,"subgroup":26,"version":5,"gender":0,"tone":4},{"label":"woman fairy: dark skin tone","hexcode":"1F9DA-1F3FF-200D-2640-FE0F","emoji":"🧚🏿‍♀️","text":"","type":1,"order":1788,"group":1,"subgroup":26,"version":5,"gender":0,"tone":5}]},{"label":"vampire","hexcode":"1F9DB","tags":["blood","dracula","fangs","halloween","scary","supernatural","teeth","undead"],"emoji":"🧛","text":"","type":1,"order":1790,"group":1,"subgroup":26,"version":5,"emoticon":":E","skins":[{"label":"vampire: light skin tone","hexcode":"1F9DB-1F3FB","emoji":"🧛🏻","text":"","type":1,"order":1791,"group":1,"subgroup":26,"version":5,"tone":1},{"label":"vampire: medium-light skin tone","hexcode":"1F9DB-1F3FC","emoji":"🧛🏼","text":"","type":1,"order":1792,"group":1,"subgroup":26,"version":5,"tone":2},{"label":"vampire: medium skin tone","hexcode":"1F9DB-1F3FD","emoji":"🧛🏽","text":"","type":1,"order":1793,"group":1,"subgroup":26,"version":5,"tone":3},{"label":"vampire: medium-dark skin tone","hexcode":"1F9DB-1F3FE","emoji":"🧛🏾","text":"","type":1,"order":1794,"group":1,"subgroup":26,"version":5,"tone":4},{"label":"vampire: dark skin tone","hexcode":"1F9DB-1F3FF","emoji":"🧛🏿","text":"","type":1,"order":1795,"group":1,"subgroup":26,"version":5,"tone":5}]},{"label":"man vampire","hexcode":"1F9DB-200D-2642-FE0F","tags":["blood","fangs","halloween","man","scary","supernatural","teeth","undead","vampire"],"emoji":"🧛‍♂️","text":"","type":1,"order":1796,"group":1,"subgroup":26,"version":5,"gender":1,"skins":[{"label":"man vampire: light skin tone","hexcode":"1F9DB-1F3FB-200D-2642-FE0F","emoji":"🧛🏻‍♂️","text":"","type":1,"order":1798,"group":1,"subgroup":26,"version":5,"gender":1,"tone":1},{"label":"man vampire: medium-light skin tone","hexcode":"1F9DB-1F3FC-200D-2642-FE0F","emoji":"🧛🏼‍♂️","text":"","type":1,"order":1800,"group":1,"subgroup":26,"version":5,"gender":1,"tone":2},{"label":"man vampire: medium skin tone","hexcode":"1F9DB-1F3FD-200D-2642-FE0F","emoji":"🧛🏽‍♂️","text":"","type":1,"order":1802,"group":1,"subgroup":26,"version":5,"gender":1,"tone":3},{"label":"man vampire: medium-dark skin tone","hexcode":"1F9DB-1F3FE-200D-2642-FE0F","emoji":"🧛🏾‍♂️","text":"","type":1,"order":1804,"group":1,"subgroup":26,"version":5,"gender":1,"tone":4},{"label":"man vampire: dark skin tone","hexcode":"1F9DB-1F3FF-200D-2642-FE0F","emoji":"🧛🏿‍♂️","text":"","type":1,"order":1806,"group":1,"subgroup":26,"version":5,"gender":1,"tone":5}]},{"label":"woman vampire","hexcode":"1F9DB-200D-2640-FE0F","tags":["blood","fangs","halloween","scary","supernatural","teeth","undead","vampire","woman"],"emoji":"🧛‍♀️","text":"","type":1,"order":1808,"group":1,"subgroup":26,"version":5,"gender":0,"skins":[{"label":"woman vampire: light skin tone","hexcode":"1F9DB-1F3FB-200D-2640-FE0F","emoji":"🧛🏻‍♀️","text":"","type":1,"order":1810,"group":1,"subgroup":26,"version":5,"gender":0,"tone":1},{"label":"woman vampire: medium-light skin tone","hexcode":"1F9DB-1F3FC-200D-2640-FE0F","emoji":"🧛🏼‍♀️","text":"","type":1,"order":1812,"group":1,"subgroup":26,"version":5,"gender":0,"tone":2},{"label":"woman vampire: medium skin tone","hexcode":"1F9DB-1F3FD-200D-2640-FE0F","emoji":"🧛🏽‍♀️","text":"","type":1,"order":1814,"group":1,"subgroup":26,"version":5,"gender":0,"tone":3},{"label":"woman vampire: medium-dark skin tone","hexcode":"1F9DB-1F3FE-200D-2640-FE0F","emoji":"🧛🏾‍♀️","text":"","type":1,"order":1816,"group":1,"subgroup":26,"version":5,"gender":0,"tone":4},{"label":"woman vampire: dark skin tone","hexcode":"1F9DB-1F3FF-200D-2640-FE0F","emoji":"🧛🏿‍♀️","text":"","type":1,"order":1818,"group":1,"subgroup":26,"version":5,"gender":0,"tone":5}]},{"label":"merperson","hexcode":"1F9DC","tags":["creature","fairytale","folklore","ocean","sea","siren","trident"],"emoji":"🧜","text":"","type":1,"order":1820,"group":1,"subgroup":26,"version":5,"skins":[{"label":"merperson: light skin tone","hexcode":"1F9DC-1F3FB","emoji":"🧜🏻","text":"","type":1,"order":1821,"group":1,"subgroup":26,"version":5,"tone":1},{"label":"merperson: medium-light skin tone","hexcode":"1F9DC-1F3FC","emoji":"🧜🏼","text":"","type":1,"order":1822,"group":1,"subgroup":26,"version":5,"tone":2},{"label":"merperson: medium skin tone","hexcode":"1F9DC-1F3FD","emoji":"🧜🏽","text":"","type":1,"order":1823,"group":1,"subgroup":26,"version":5,"tone":3},{"label":"merperson: medium-dark skin tone","hexcode":"1F9DC-1F3FE","emoji":"🧜🏾","text":"","type":1,"order":1824,"group":1,"subgroup":26,"version":5,"tone":4},{"label":"merperson: dark skin tone","hexcode":"1F9DC-1F3FF","emoji":"🧜🏿","text":"","type":1,"order":1825,"group":1,"subgroup":26,"version":5,"tone":5}]},{"label":"merman","hexcode":"1F9DC-200D-2642-FE0F","tags":["creature","fairytale","folklore","neptune","ocean","poseidon","sea","siren","trident","triton"],"emoji":"🧜‍♂️","text":"","type":1,"order":1826,"group":1,"subgroup":26,"version":5,"gender":1,"skins":[{"label":"merman: light skin tone","hexcode":"1F9DC-1F3FB-200D-2642-FE0F","emoji":"🧜🏻‍♂️","text":"","type":1,"order":1828,"group":1,"subgroup":26,"version":5,"gender":1,"tone":1},{"label":"merman: medium-light skin tone","hexcode":"1F9DC-1F3FC-200D-2642-FE0F","emoji":"🧜🏼‍♂️","text":"","type":1,"order":1830,"group":1,"subgroup":26,"version":5,"gender":1,"tone":2},{"label":"merman: medium skin tone","hexcode":"1F9DC-1F3FD-200D-2642-FE0F","emoji":"🧜🏽‍♂️","text":"","type":1,"order":1832,"group":1,"subgroup":26,"version":5,"gender":1,"tone":3},{"label":"merman: medium-dark skin tone","hexcode":"1F9DC-1F3FE-200D-2642-FE0F","emoji":"🧜🏾‍♂️","text":"","type":1,"order":1834,"group":1,"subgroup":26,"version":5,"gender":1,"tone":4},{"label":"merman: dark skin tone","hexcode":"1F9DC-1F3FF-200D-2642-FE0F","emoji":"🧜🏿‍♂️","text":"","type":1,"order":1836,"group":1,"subgroup":26,"version":5,"gender":1,"tone":5}]},{"label":"mermaid","hexcode":"1F9DC-200D-2640-FE0F","tags":["creature","fairytale","folklore","merwoman","ocean","sea","siren","trident"],"emoji":"🧜‍♀️","text":"","type":1,"order":1838,"group":1,"subgroup":26,"version":5,"gender":0,"skins":[{"label":"mermaid: light skin tone","hexcode":"1F9DC-1F3FB-200D-2640-FE0F","emoji":"🧜🏻‍♀️","text":"","type":1,"order":1840,"group":1,"subgroup":26,"version":5,"gender":0,"tone":1},{"label":"mermaid: medium-light skin tone","hexcode":"1F9DC-1F3FC-200D-2640-FE0F","emoji":"🧜🏼‍♀️","text":"","type":1,"order":1842,"group":1,"subgroup":26,"version":5,"gender":0,"tone":2},{"label":"mermaid: medium skin tone","hexcode":"1F9DC-1F3FD-200D-2640-FE0F","emoji":"🧜🏽‍♀️","text":"","type":1,"order":1844,"group":1,"subgroup":26,"version":5,"gender":0,"tone":3},{"label":"mermaid: medium-dark skin tone","hexcode":"1F9DC-1F3FE-200D-2640-FE0F","emoji":"🧜🏾‍♀️","text":"","type":1,"order":1846,"group":1,"subgroup":26,"version":5,"gender":0,"tone":4},{"label":"mermaid: dark skin tone","hexcode":"1F9DC-1F3FF-200D-2640-FE0F","emoji":"🧜🏿‍♀️","text":"","type":1,"order":1848,"group":1,"subgroup":26,"version":5,"gender":0,"tone":5}]},{"label":"elf","hexcode":"1F9DD","tags":["elves","enchantment","fantasy","folklore","magic","magical","myth"],"emoji":"🧝","text":"","type":1,"order":1850,"group":1,"subgroup":26,"version":5,"skins":[{"label":"elf: light skin tone","hexcode":"1F9DD-1F3FB","emoji":"🧝🏻","text":"","type":1,"order":1851,"group":1,"subgroup":26,"version":5,"tone":1},{"label":"elf: medium-light skin tone","hexcode":"1F9DD-1F3FC","emoji":"🧝🏼","text":"","type":1,"order":1852,"group":1,"subgroup":26,"version":5,"tone":2},{"label":"elf: medium skin tone","hexcode":"1F9DD-1F3FD","emoji":"🧝🏽","text":"","type":1,"order":1853,"group":1,"subgroup":26,"version":5,"tone":3},{"label":"elf: medium-dark skin tone","hexcode":"1F9DD-1F3FE","emoji":"🧝🏾","text":"","type":1,"order":1854,"group":1,"subgroup":26,"version":5,"tone":4},{"label":"elf: dark skin tone","hexcode":"1F9DD-1F3FF","emoji":"🧝🏿","text":"","type":1,"order":1855,"group":1,"subgroup":26,"version":5,"tone":5}]},{"label":"man elf","hexcode":"1F9DD-200D-2642-FE0F","tags":["elf","elves","enchantment","fantasy","folklore","magic","magical","man","myth"],"emoji":"🧝‍♂️","text":"","type":1,"order":1856,"group":1,"subgroup":26,"version":5,"gender":1,"skins":[{"label":"man elf: light skin tone","hexcode":"1F9DD-1F3FB-200D-2642-FE0F","emoji":"🧝🏻‍♂️","text":"","type":1,"order":1858,"group":1,"subgroup":26,"version":5,"gender":1,"tone":1},{"label":"man elf: medium-light skin tone","hexcode":"1F9DD-1F3FC-200D-2642-FE0F","emoji":"🧝🏼‍♂️","text":"","type":1,"order":1860,"group":1,"subgroup":26,"version":5,"gender":1,"tone":2},{"label":"man elf: medium skin tone","hexcode":"1F9DD-1F3FD-200D-2642-FE0F","emoji":"🧝🏽‍♂️","text":"","type":1,"order":1862,"group":1,"subgroup":26,"version":5,"gender":1,"tone":3},{"label":"man elf: medium-dark skin tone","hexcode":"1F9DD-1F3FE-200D-2642-FE0F","emoji":"🧝🏾‍♂️","text":"","type":1,"order":1864,"group":1,"subgroup":26,"version":5,"gender":1,"tone":4},{"label":"man elf: dark skin tone","hexcode":"1F9DD-1F3FF-200D-2642-FE0F","emoji":"🧝🏿‍♂️","text":"","type":1,"order":1866,"group":1,"subgroup":26,"version":5,"gender":1,"tone":5}]},{"label":"woman elf","hexcode":"1F9DD-200D-2640-FE0F","tags":["elf","elves","enchantment","fantasy","folklore","magic","magical","myth","woman"],"emoji":"🧝‍♀️","text":"","type":1,"order":1868,"group":1,"subgroup":26,"version":5,"gender":0,"skins":[{"label":"woman elf: light skin tone","hexcode":"1F9DD-1F3FB-200D-2640-FE0F","emoji":"🧝🏻‍♀️","text":"","type":1,"order":1870,"group":1,"subgroup":26,"version":5,"gender":0,"tone":1},{"label":"woman elf: medium-light skin tone","hexcode":"1F9DD-1F3FC-200D-2640-FE0F","emoji":"🧝🏼‍♀️","text":"","type":1,"order":1872,"group":1,"subgroup":26,"version":5,"gender":0,"tone":2},{"label":"woman elf: medium skin tone","hexcode":"1F9DD-1F3FD-200D-2640-FE0F","emoji":"🧝🏽‍♀️","text":"","type":1,"order":1874,"group":1,"subgroup":26,"version":5,"gender":0,"tone":3},{"label":"woman elf: medium-dark skin tone","hexcode":"1F9DD-1F3FE-200D-2640-FE0F","emoji":"🧝🏾‍♀️","text":"","type":1,"order":1876,"group":1,"subgroup":26,"version":5,"gender":0,"tone":4},{"label":"woman elf: dark skin tone","hexcode":"1F9DD-1F3FF-200D-2640-FE0F","emoji":"🧝🏿‍♀️","text":"","type":1,"order":1878,"group":1,"subgroup":26,"version":5,"gender":0,"tone":5}]},{"label":"genie","hexcode":"1F9DE","tags":["djinn","fantasy","jinn","lamp","myth","rub","wishes"],"emoji":"🧞","text":"","type":1,"order":1880,"group":1,"subgroup":26,"version":5},{"label":"man genie","hexcode":"1F9DE-200D-2642-FE0F","tags":["djinn","fantasy","genie","jinn","lamp","man","myth","rub","wishes"],"emoji":"🧞‍♂️","text":"","type":1,"order":1881,"group":1,"subgroup":26,"version":5,"gender":1},{"label":"woman genie","hexcode":"1F9DE-200D-2640-FE0F","tags":["djinn","fantasy","genie","jinn","lamp","myth","rub","wishes","woman"],"emoji":"🧞‍♀️","text":"","type":1,"order":1883,"group":1,"subgroup":26,"version":5,"gender":0},{"label":"zombie","hexcode":"1F9DF","tags":["apocalypse","dead","halloween","horror","scary","undead","walking"],"emoji":"🧟","text":"","type":1,"order":1885,"group":1,"subgroup":26,"version":5,"emoticon":"8#"},{"label":"man zombie","hexcode":"1F9DF-200D-2642-FE0F","tags":["apocalypse","dead","halloween","horror","man","scary","undead","walking","zombie"],"emoji":"🧟‍♂️","text":"","type":1,"order":1886,"group":1,"subgroup":26,"version":5,"gender":1},{"label":"woman zombie","hexcode":"1F9DF-200D-2640-FE0F","tags":["apocalypse","dead","halloween","horror","scary","undead","walking","woman","zombie"],"emoji":"🧟‍♀️","text":"","type":1,"order":1888,"group":1,"subgroup":26,"version":5,"gender":0},{"label":"troll","hexcode":"1F9CC","tags":["fairy","fantasy","monster","tale","trolling"],"emoji":"🧌","text":"","type":1,"order":1890,"group":1,"subgroup":26,"version":14},{"label":"hairy creature","hexcode":"1FAC8","tags":["bigfoot","cryptid","forest","giant","hairy","sasquatch","woodwose","yeti"],"emoji":"🫈","text":"","type":1,"order":1891,"group":1,"subgroup":26,"version":17},{"label":"person getting massage","hexcode":"1F486","tags":["face","getting","headache","massage","person","relax","relaxing","salon","soothe","spa","tension","therapy","treatment"],"emoji":"💆","text":"","type":1,"order":1892,"group":1,"subgroup":27,"version":0.6,"skins":[{"label":"person getting massage: light skin tone","hexcode":"1F486-1F3FB","emoji":"💆🏻","text":"","type":1,"order":1893,"group":1,"subgroup":27,"version":1,"tone":1},{"label":"person getting massage: medium-light skin tone","hexcode":"1F486-1F3FC","emoji":"💆🏼","text":"","type":1,"order":1894,"group":1,"subgroup":27,"version":1,"tone":2},{"label":"person getting massage: medium skin tone","hexcode":"1F486-1F3FD","emoji":"💆🏽","text":"","type":1,"order":1895,"group":1,"subgroup":27,"version":1,"tone":3},{"label":"person getting massage: medium-dark skin tone","hexcode":"1F486-1F3FE","emoji":"💆🏾","text":"","type":1,"order":1896,"group":1,"subgroup":27,"version":1,"tone":4},{"label":"person getting massage: dark skin tone","hexcode":"1F486-1F3FF","emoji":"💆🏿","text":"","type":1,"order":1897,"group":1,"subgroup":27,"version":1,"tone":5}]},{"label":"man getting massage","hexcode":"1F486-200D-2642-FE0F","tags":["face","getting","headache","man","massage","relax","relaxing","salon","soothe","spa","tension","therapy","treatment"],"emoji":"💆‍♂️","text":"","type":1,"order":1898,"group":1,"subgroup":27,"version":4,"gender":1,"skins":[{"label":"man getting massage: light skin tone","hexcode":"1F486-1F3FB-200D-2642-FE0F","emoji":"💆🏻‍♂️","text":"","type":1,"order":1900,"group":1,"subgroup":27,"version":4,"gender":1,"tone":1},{"label":"man getting massage: medium-light skin tone","hexcode":"1F486-1F3FC-200D-2642-FE0F","emoji":"💆🏼‍♂️","text":"","type":1,"order":1902,"group":1,"subgroup":27,"version":4,"gender":1,"tone":2},{"label":"man getting massage: medium skin tone","hexcode":"1F486-1F3FD-200D-2642-FE0F","emoji":"💆🏽‍♂️","text":"","type":1,"order":1904,"group":1,"subgroup":27,"version":4,"gender":1,"tone":3},{"label":"man getting massage: medium-dark skin tone","hexcode":"1F486-1F3FE-200D-2642-FE0F","emoji":"💆🏾‍♂️","text":"","type":1,"order":1906,"group":1,"subgroup":27,"version":4,"gender":1,"tone":4},{"label":"man getting massage: dark skin tone","hexcode":"1F486-1F3FF-200D-2642-FE0F","emoji":"💆🏿‍♂️","text":"","type":1,"order":1908,"group":1,"subgroup":27,"version":4,"gender":1,"tone":5}]},{"label":"woman getting massage","hexcode":"1F486-200D-2640-FE0F","tags":["face","getting","headache","massage","relax","relaxing","salon","soothe","spa","tension","therapy","treatment","woman"],"emoji":"💆‍♀️","text":"","type":1,"order":1910,"group":1,"subgroup":27,"version":4,"gender":0,"skins":[{"label":"woman getting massage: light skin tone","hexcode":"1F486-1F3FB-200D-2640-FE0F","emoji":"💆🏻‍♀️","text":"","type":1,"order":1912,"group":1,"subgroup":27,"version":4,"gender":0,"tone":1},{"label":"woman getting massage: medium-light skin tone","hexcode":"1F486-1F3FC-200D-2640-FE0F","emoji":"💆🏼‍♀️","text":"","type":1,"order":1914,"group":1,"subgroup":27,"version":4,"gender":0,"tone":2},{"label":"woman getting massage: medium skin tone","hexcode":"1F486-1F3FD-200D-2640-FE0F","emoji":"💆🏽‍♀️","text":"","type":1,"order":1916,"group":1,"subgroup":27,"version":4,"gender":0,"tone":3},{"label":"woman getting massage: medium-dark skin tone","hexcode":"1F486-1F3FE-200D-2640-FE0F","emoji":"💆🏾‍♀️","text":"","type":1,"order":1918,"group":1,"subgroup":27,"version":4,"gender":0,"tone":4},{"label":"woman getting massage: dark skin tone","hexcode":"1F486-1F3FF-200D-2640-FE0F","emoji":"💆🏿‍♀️","text":"","type":1,"order":1920,"group":1,"subgroup":27,"version":4,"gender":0,"tone":5}]},{"label":"person getting haircut","hexcode":"1F487","tags":["barber","beauty","chop","cosmetology","cut","groom","hair","haircut","parlor","person","shears","style"],"emoji":"💇","text":"","type":1,"order":1922,"group":1,"subgroup":27,"version":0.6,"skins":[{"label":"person getting haircut: light skin tone","hexcode":"1F487-1F3FB","emoji":"💇🏻","text":"","type":1,"order":1923,"group":1,"subgroup":27,"version":1,"tone":1},{"label":"person getting haircut: medium-light skin tone","hexcode":"1F487-1F3FC","emoji":"💇🏼","text":"","type":1,"order":1924,"group":1,"subgroup":27,"version":1,"tone":2},{"label":"person getting haircut: medium skin tone","hexcode":"1F487-1F3FD","emoji":"💇🏽","text":"","type":1,"order":1925,"group":1,"subgroup":27,"version":1,"tone":3},{"label":"person getting haircut: medium-dark skin tone","hexcode":"1F487-1F3FE","emoji":"💇🏾","text":"","type":1,"order":1926,"group":1,"subgroup":27,"version":1,"tone":4},{"label":"person getting haircut: dark skin tone","hexcode":"1F487-1F3FF","emoji":"💇🏿","text":"","type":1,"order":1927,"group":1,"subgroup":27,"version":1,"tone":5}]},{"label":"man getting haircut","hexcode":"1F487-200D-2642-FE0F","tags":["barber","beauty","chop","cosmetology","cut","groom","hair","haircut","man","parlor","person","shears","style"],"emoji":"💇‍♂️","text":"","type":1,"order":1928,"group":1,"subgroup":27,"version":4,"gender":1,"skins":[{"label":"man getting haircut: light skin tone","hexcode":"1F487-1F3FB-200D-2642-FE0F","emoji":"💇🏻‍♂️","text":"","type":1,"order":1930,"group":1,"subgroup":27,"version":4,"gender":1,"tone":1},{"label":"man getting haircut: medium-light skin tone","hexcode":"1F487-1F3FC-200D-2642-FE0F","emoji":"💇🏼‍♂️","text":"","type":1,"order":1932,"group":1,"subgroup":27,"version":4,"gender":1,"tone":2},{"label":"man getting haircut: medium skin tone","hexcode":"1F487-1F3FD-200D-2642-FE0F","emoji":"💇🏽‍♂️","text":"","type":1,"order":1934,"group":1,"subgroup":27,"version":4,"gender":1,"tone":3},{"label":"man getting haircut: medium-dark skin tone","hexcode":"1F487-1F3FE-200D-2642-FE0F","emoji":"💇🏾‍♂️","text":"","type":1,"order":1936,"group":1,"subgroup":27,"version":4,"gender":1,"tone":4},{"label":"man getting haircut: dark skin tone","hexcode":"1F487-1F3FF-200D-2642-FE0F","emoji":"💇🏿‍♂️","text":"","type":1,"order":1938,"group":1,"subgroup":27,"version":4,"gender":1,"tone":5}]},{"label":"woman getting haircut","hexcode":"1F487-200D-2640-FE0F","tags":["barber","beauty","chop","cosmetology","cut","groom","hair","haircut","parlor","person","shears","style","woman"],"emoji":"💇‍♀️","text":"","type":1,"order":1940,"group":1,"subgroup":27,"version":4,"gender":0,"skins":[{"label":"woman getting haircut: light skin tone","hexcode":"1F487-1F3FB-200D-2640-FE0F","emoji":"💇🏻‍♀️","text":"","type":1,"order":1942,"group":1,"subgroup":27,"version":4,"gender":0,"tone":1},{"label":"woman getting haircut: medium-light skin tone","hexcode":"1F487-1F3FC-200D-2640-FE0F","emoji":"💇🏼‍♀️","text":"","type":1,"order":1944,"group":1,"subgroup":27,"version":4,"gender":0,"tone":2},{"label":"woman getting haircut: medium skin tone","hexcode":"1F487-1F3FD-200D-2640-FE0F","emoji":"💇🏽‍♀️","text":"","type":1,"order":1946,"group":1,"subgroup":27,"version":4,"gender":0,"tone":3},{"label":"woman getting haircut: medium-dark skin tone","hexcode":"1F487-1F3FE-200D-2640-FE0F","emoji":"💇🏾‍♀️","text":"","type":1,"order":1948,"group":1,"subgroup":27,"version":4,"gender":0,"tone":4},{"label":"woman getting haircut: dark skin tone","hexcode":"1F487-1F3FF-200D-2640-FE0F","emoji":"💇🏿‍♀️","text":"","type":1,"order":1950,"group":1,"subgroup":27,"version":4,"gender":0,"tone":5}]},{"label":"person walking","hexcode":"1F6B6","tags":["amble","gait","hike","man","pace","pedestrian","person","stride","stroll","walk","walking"],"emoji":"🚶","text":"","type":1,"order":1952,"group":1,"subgroup":27,"version":0.6,"skins":[{"label":"person walking: light skin tone","hexcode":"1F6B6-1F3FB","emoji":"🚶🏻","text":"","type":1,"order":1953,"group":1,"subgroup":27,"version":1,"tone":1},{"label":"person walking: medium-light skin tone","hexcode":"1F6B6-1F3FC","emoji":"🚶🏼","text":"","type":1,"order":1954,"group":1,"subgroup":27,"version":1,"tone":2},{"label":"person walking: medium skin tone","hexcode":"1F6B6-1F3FD","emoji":"🚶🏽","text":"","type":1,"order":1955,"group":1,"subgroup":27,"version":1,"tone":3},{"label":"person walking: medium-dark skin tone","hexcode":"1F6B6-1F3FE","emoji":"🚶🏾","text":"","type":1,"order":1956,"group":1,"subgroup":27,"version":1,"tone":4},{"label":"person walking: dark skin tone","hexcode":"1F6B6-1F3FF","emoji":"🚶🏿","text":"","type":1,"order":1957,"group":1,"subgroup":27,"version":1,"tone":5}]},{"label":"man walking","hexcode":"1F6B6-200D-2642-FE0F","tags":["amble","gait","hike","man","pace","pedestrian","stride","stroll","walk","walking"],"emoji":"🚶‍♂️","text":"","type":1,"order":1958,"group":1,"subgroup":27,"version":4,"gender":1,"skins":[{"label":"man walking: light skin tone","hexcode":"1F6B6-1F3FB-200D-2642-FE0F","emoji":"🚶🏻‍♂️","text":"","type":1,"order":1960,"group":1,"subgroup":27,"version":4,"gender":1,"tone":1},{"label":"man walking: medium-light skin tone","hexcode":"1F6B6-1F3FC-200D-2642-FE0F","emoji":"🚶🏼‍♂️","text":"","type":1,"order":1962,"group":1,"subgroup":27,"version":4,"gender":1,"tone":2},{"label":"man walking: medium skin tone","hexcode":"1F6B6-1F3FD-200D-2642-FE0F","emoji":"🚶🏽‍♂️","text":"","type":1,"order":1964,"group":1,"subgroup":27,"version":4,"gender":1,"tone":3},{"label":"man walking: medium-dark skin tone","hexcode":"1F6B6-1F3FE-200D-2642-FE0F","emoji":"🚶🏾‍♂️","text":"","type":1,"order":1966,"group":1,"subgroup":27,"version":4,"gender":1,"tone":4},{"label":"man walking: dark skin tone","hexcode":"1F6B6-1F3FF-200D-2642-FE0F","emoji":"🚶🏿‍♂️","text":"","type":1,"order":1968,"group":1,"subgroup":27,"version":4,"gender":1,"tone":5}]},{"label":"woman walking","hexcode":"1F6B6-200D-2640-FE0F","tags":["amble","gait","hike","man","pace","pedestrian","stride","stroll","walk","walking","woman"],"emoji":"🚶‍♀️","text":"","type":1,"order":1970,"group":1,"subgroup":27,"version":4,"gender":0,"skins":[{"label":"woman walking: light skin tone","hexcode":"1F6B6-1F3FB-200D-2640-FE0F","emoji":"🚶🏻‍♀️","text":"","type":1,"order":1972,"group":1,"subgroup":27,"version":4,"gender":0,"tone":1},{"label":"woman walking: medium-light skin tone","hexcode":"1F6B6-1F3FC-200D-2640-FE0F","emoji":"🚶🏼‍♀️","text":"","type":1,"order":1974,"group":1,"subgroup":27,"version":4,"gender":0,"tone":2},{"label":"woman walking: medium skin tone","hexcode":"1F6B6-1F3FD-200D-2640-FE0F","emoji":"🚶🏽‍♀️","text":"","type":1,"order":1976,"group":1,"subgroup":27,"version":4,"gender":0,"tone":3},{"label":"woman walking: medium-dark skin tone","hexcode":"1F6B6-1F3FE-200D-2640-FE0F","emoji":"🚶🏾‍♀️","text":"","type":1,"order":1978,"group":1,"subgroup":27,"version":4,"gender":0,"tone":4},{"label":"woman walking: dark skin tone","hexcode":"1F6B6-1F3FF-200D-2640-FE0F","emoji":"🚶🏿‍♀️","text":"","type":1,"order":1980,"group":1,"subgroup":27,"version":4,"gender":0,"tone":5}]},{"label":"person walking: facing right","hexcode":"1F6B6-200D-27A1-FE0F","tags":["amble","facing","gait","hike","man","pace","pedestrian","person","right","stride","stroll","walk","walking"],"emoji":"🚶‍➡️","text":"","type":1,"order":1982,"group":1,"subgroup":27,"version":15.1,"skins":[{"label":"person walking: light skin tone, facing right","hexcode":"1F6B6-1F3FB-200D-27A1-FE0F","emoji":"🚶🏻‍➡️","text":"","type":1,"order":1984,"group":1,"subgroup":27,"version":15.1,"tone":1},{"label":"person walking: medium-light skin tone, facing right","hexcode":"1F6B6-1F3FC-200D-27A1-FE0F","emoji":"🚶🏼‍➡️","text":"","type":1,"order":1986,"group":1,"subgroup":27,"version":15.1,"tone":2},{"label":"person walking: medium skin tone, facing right","hexcode":"1F6B6-1F3FD-200D-27A1-FE0F","emoji":"🚶🏽‍➡️","text":"","type":1,"order":1988,"group":1,"subgroup":27,"version":15.1,"tone":3},{"label":"person walking: medium-dark skin tone, facing right","hexcode":"1F6B6-1F3FE-200D-27A1-FE0F","emoji":"🚶🏾‍➡️","text":"","type":1,"order":1990,"group":1,"subgroup":27,"version":15.1,"tone":4},{"label":"person walking: dark skin tone, facing right","hexcode":"1F6B6-1F3FF-200D-27A1-FE0F","emoji":"🚶🏿‍➡️","text":"","type":1,"order":1992,"group":1,"subgroup":27,"version":15.1,"tone":5}]},{"label":"woman walking: facing right","hexcode":"1F6B6-200D-2640-FE0F-200D-27A1-FE0F","tags":["amble","facing","gait","hike","man","pace","pedestrian","right","stride","stroll","walk","walking","woman"],"emoji":"🚶‍♀️‍➡️","text":"","type":1,"order":1994,"group":1,"subgroup":27,"version":15.1,"gender":0,"skins":[{"label":"woman walking: light skin tone, facing right","hexcode":"1F6B6-1F3FB-200D-2640-FE0F-200D-27A1-FE0F","emoji":"🚶🏻‍♀️‍➡️","text":"","type":1,"order":1998,"group":1,"subgroup":27,"version":15.1,"gender":0,"tone":1},{"label":"woman walking: medium-light skin tone, facing right","hexcode":"1F6B6-1F3FC-200D-2640-FE0F-200D-27A1-FE0F","emoji":"🚶🏼‍♀️‍➡️","text":"","type":1,"order":2002,"group":1,"subgroup":27,"version":15.1,"gender":0,"tone":2},{"label":"woman walking: medium skin tone, facing right","hexcode":"1F6B6-1F3FD-200D-2640-FE0F-200D-27A1-FE0F","emoji":"🚶🏽‍♀️‍➡️","text":"","type":1,"order":2006,"group":1,"subgroup":27,"version":15.1,"gender":0,"tone":3},{"label":"woman walking: medium-dark skin tone, facing right","hexcode":"1F6B6-1F3FE-200D-2640-FE0F-200D-27A1-FE0F","emoji":"🚶🏾‍♀️‍➡️","text":"","type":1,"order":2010,"group":1,"subgroup":27,"version":15.1,"gender":0,"tone":4},{"label":"woman walking: dark skin tone, facing right","hexcode":"1F6B6-1F3FF-200D-2640-FE0F-200D-27A1-FE0F","emoji":"🚶🏿‍♀️‍➡️","text":"","type":1,"order":2014,"group":1,"subgroup":27,"version":15.1,"gender":0,"tone":5}]},{"label":"man walking: facing right","hexcode":"1F6B6-200D-2642-FE0F-200D-27A1-FE0F","tags":["amble","facing","gait","hike","man","pace","pedestrian","right","stride","stroll","walk","walking"],"emoji":"🚶‍♂️‍➡️","text":"","type":1,"order":2018,"group":1,"subgroup":27,"version":15.1,"gender":1,"skins":[{"label":"man walking: light skin tone, facing right","hexcode":"1F6B6-1F3FB-200D-2642-FE0F-200D-27A1-FE0F","emoji":"🚶🏻‍♂️‍➡️","text":"","type":1,"order":2022,"group":1,"subgroup":27,"version":15.1,"gender":1,"tone":1},{"label":"man walking: medium-light skin tone, facing right","hexcode":"1F6B6-1F3FC-200D-2642-FE0F-200D-27A1-FE0F","emoji":"🚶🏼‍♂️‍➡️","text":"","type":1,"order":2026,"group":1,"subgroup":27,"version":15.1,"gender":1,"tone":2},{"label":"man walking: medium skin tone, facing right","hexcode":"1F6B6-1F3FD-200D-2642-FE0F-200D-27A1-FE0F","emoji":"🚶🏽‍♂️‍➡️","text":"","type":1,"order":2030,"group":1,"subgroup":27,"version":15.1,"gender":1,"tone":3},{"label":"man walking: medium-dark skin tone, facing right","hexcode":"1F6B6-1F3FE-200D-2642-FE0F-200D-27A1-FE0F","emoji":"🚶🏾‍♂️‍➡️","text":"","type":1,"order":2034,"group":1,"subgroup":27,"version":15.1,"gender":1,"tone":4},{"label":"man walking: dark skin tone, facing right","hexcode":"1F6B6-1F3FF-200D-2642-FE0F-200D-27A1-FE0F","emoji":"🚶🏿‍♂️‍➡️","text":"","type":1,"order":2038,"group":1,"subgroup":27,"version":15.1,"gender":1,"tone":5}]},{"label":"person standing","hexcode":"1F9CD","tags":["person","stand","standing"],"emoji":"🧍","text":"","type":1,"order":2042,"group":1,"subgroup":27,"version":12,"skins":[{"label":"person standing: light skin tone","hexcode":"1F9CD-1F3FB","emoji":"🧍🏻","text":"","type":1,"order":2043,"group":1,"subgroup":27,"version":12,"tone":1},{"label":"person standing: medium-light skin tone","hexcode":"1F9CD-1F3FC","emoji":"🧍🏼","text":"","type":1,"order":2044,"group":1,"subgroup":27,"version":12,"tone":2},{"label":"person standing: medium skin tone","hexcode":"1F9CD-1F3FD","emoji":"🧍🏽","text":"","type":1,"order":2045,"group":1,"subgroup":27,"version":12,"tone":3},{"label":"person standing: medium-dark skin tone","hexcode":"1F9CD-1F3FE","emoji":"🧍🏾","text":"","type":1,"order":2046,"group":1,"subgroup":27,"version":12,"tone":4},{"label":"person standing: dark skin tone","hexcode":"1F9CD-1F3FF","emoji":"🧍🏿","text":"","type":1,"order":2047,"group":1,"subgroup":27,"version":12,"tone":5}]},{"label":"man standing","hexcode":"1F9CD-200D-2642-FE0F","tags":["man","stand","standing"],"emoji":"🧍‍♂️","text":"","type":1,"order":2048,"group":1,"subgroup":27,"version":12,"gender":1,"skins":[{"label":"man standing: light skin tone","hexcode":"1F9CD-1F3FB-200D-2642-FE0F","emoji":"🧍🏻‍♂️","text":"","type":1,"order":2050,"group":1,"subgroup":27,"version":12,"gender":1,"tone":1},{"label":"man standing: medium-light skin tone","hexcode":"1F9CD-1F3FC-200D-2642-FE0F","emoji":"🧍🏼‍♂️","text":"","type":1,"order":2052,"group":1,"subgroup":27,"version":12,"gender":1,"tone":2},{"label":"man standing: medium skin tone","hexcode":"1F9CD-1F3FD-200D-2642-FE0F","emoji":"🧍🏽‍♂️","text":"","type":1,"order":2054,"group":1,"subgroup":27,"version":12,"gender":1,"tone":3},{"label":"man standing: medium-dark skin tone","hexcode":"1F9CD-1F3FE-200D-2642-FE0F","emoji":"🧍🏾‍♂️","text":"","type":1,"order":2056,"group":1,"subgroup":27,"version":12,"gender":1,"tone":4},{"label":"man standing: dark skin tone","hexcode":"1F9CD-1F3FF-200D-2642-FE0F","emoji":"🧍🏿‍♂️","text":"","type":1,"order":2058,"group":1,"subgroup":27,"version":12,"gender":1,"tone":5}]},{"label":"woman standing","hexcode":"1F9CD-200D-2640-FE0F","tags":["stand","standing","woman"],"emoji":"🧍‍♀️","text":"","type":1,"order":2060,"group":1,"subgroup":27,"version":12,"gender":0,"skins":[{"label":"woman standing: light skin tone","hexcode":"1F9CD-1F3FB-200D-2640-FE0F","emoji":"🧍🏻‍♀️","text":"","type":1,"order":2062,"group":1,"subgroup":27,"version":12,"gender":0,"tone":1},{"label":"woman standing: medium-light skin tone","hexcode":"1F9CD-1F3FC-200D-2640-FE0F","emoji":"🧍🏼‍♀️","text":"","type":1,"order":2064,"group":1,"subgroup":27,"version":12,"gender":0,"tone":2},{"label":"woman standing: medium skin tone","hexcode":"1F9CD-1F3FD-200D-2640-FE0F","emoji":"🧍🏽‍♀️","text":"","type":1,"order":2066,"group":1,"subgroup":27,"version":12,"gender":0,"tone":3},{"label":"woman standing: medium-dark skin tone","hexcode":"1F9CD-1F3FE-200D-2640-FE0F","emoji":"🧍🏾‍♀️","text":"","type":1,"order":2068,"group":1,"subgroup":27,"version":12,"gender":0,"tone":4},{"label":"woman standing: dark skin tone","hexcode":"1F9CD-1F3FF-200D-2640-FE0F","emoji":"🧍🏿‍♀️","text":"","type":1,"order":2070,"group":1,"subgroup":27,"version":12,"gender":0,"tone":5}]},{"label":"person kneeling","hexcode":"1F9CE","tags":["kneel","kneeling","knees","person"],"emoji":"🧎","text":"","type":1,"order":2072,"group":1,"subgroup":27,"version":12,"skins":[{"label":"person kneeling: light skin tone","hexcode":"1F9CE-1F3FB","emoji":"🧎🏻","text":"","type":1,"order":2073,"group":1,"subgroup":27,"version":12,"tone":1},{"label":"person kneeling: medium-light skin tone","hexcode":"1F9CE-1F3FC","emoji":"🧎🏼","text":"","type":1,"order":2074,"group":1,"subgroup":27,"version":12,"tone":2},{"label":"person kneeling: medium skin tone","hexcode":"1F9CE-1F3FD","emoji":"🧎🏽","text":"","type":1,"order":2075,"group":1,"subgroup":27,"version":12,"tone":3},{"label":"person kneeling: medium-dark skin tone","hexcode":"1F9CE-1F3FE","emoji":"🧎🏾","text":"","type":1,"order":2076,"group":1,"subgroup":27,"version":12,"tone":4},{"label":"person kneeling: dark skin tone","hexcode":"1F9CE-1F3FF","emoji":"🧎🏿","text":"","type":1,"order":2077,"group":1,"subgroup":27,"version":12,"tone":5}]},{"label":"man kneeling","hexcode":"1F9CE-200D-2642-FE0F","tags":["kneel","kneeling","knees","man"],"emoji":"🧎‍♂️","text":"","type":1,"order":2078,"group":1,"subgroup":27,"version":12,"gender":1,"skins":[{"label":"man kneeling: light skin tone","hexcode":"1F9CE-1F3FB-200D-2642-FE0F","emoji":"🧎🏻‍♂️","text":"","type":1,"order":2080,"group":1,"subgroup":27,"version":12,"gender":1,"tone":1},{"label":"man kneeling: medium-light skin tone","hexcode":"1F9CE-1F3FC-200D-2642-FE0F","emoji":"🧎🏼‍♂️","text":"","type":1,"order":2082,"group":1,"subgroup":27,"version":12,"gender":1,"tone":2},{"label":"man kneeling: medium skin tone","hexcode":"1F9CE-1F3FD-200D-2642-FE0F","emoji":"🧎🏽‍♂️","text":"","type":1,"order":2084,"group":1,"subgroup":27,"version":12,"gender":1,"tone":3},{"label":"man kneeling: medium-dark skin tone","hexcode":"1F9CE-1F3FE-200D-2642-FE0F","emoji":"🧎🏾‍♂️","text":"","type":1,"order":2086,"group":1,"subgroup":27,"version":12,"gender":1,"tone":4},{"label":"man kneeling: dark skin tone","hexcode":"1F9CE-1F3FF-200D-2642-FE0F","emoji":"🧎🏿‍♂️","text":"","type":1,"order":2088,"group":1,"subgroup":27,"version":12,"gender":1,"tone":5}]},{"label":"woman kneeling","hexcode":"1F9CE-200D-2640-FE0F","tags":["kneel","kneeling","knees","woman"],"emoji":"🧎‍♀️","text":"","type":1,"order":2090,"group":1,"subgroup":27,"version":12,"gender":0,"skins":[{"label":"woman kneeling: light skin tone","hexcode":"1F9CE-1F3FB-200D-2640-FE0F","emoji":"🧎🏻‍♀️","text":"","type":1,"order":2092,"group":1,"subgroup":27,"version":12,"gender":0,"tone":1},{"label":"woman kneeling: medium-light skin tone","hexcode":"1F9CE-1F3FC-200D-2640-FE0F","emoji":"🧎🏼‍♀️","text":"","type":1,"order":2094,"group":1,"subgroup":27,"version":12,"gender":0,"tone":2},{"label":"woman kneeling: medium skin tone","hexcode":"1F9CE-1F3FD-200D-2640-FE0F","emoji":"🧎🏽‍♀️","text":"","type":1,"order":2096,"group":1,"subgroup":27,"version":12,"gender":0,"tone":3},{"label":"woman kneeling: medium-dark skin tone","hexcode":"1F9CE-1F3FE-200D-2640-FE0F","emoji":"🧎🏾‍♀️","text":"","type":1,"order":2098,"group":1,"subgroup":27,"version":12,"gender":0,"tone":4},{"label":"woman kneeling: dark skin tone","hexcode":"1F9CE-1F3FF-200D-2640-FE0F","emoji":"🧎🏿‍♀️","text":"","type":1,"order":2100,"group":1,"subgroup":27,"version":12,"gender":0,"tone":5}]},{"label":"person kneeling: facing right","hexcode":"1F9CE-200D-27A1-FE0F","tags":["facing","kneel","kneeling","knees","person","right"],"emoji":"🧎‍➡️","text":"","type":1,"order":2102,"group":1,"subgroup":27,"version":15.1,"skins":[{"label":"person kneeling: light skin tone, facing right","hexcode":"1F9CE-1F3FB-200D-27A1-FE0F","emoji":"🧎🏻‍➡️","text":"","type":1,"order":2104,"group":1,"subgroup":27,"version":15.1,"tone":1},{"label":"person kneeling: medium-light skin tone, facing right","hexcode":"1F9CE-1F3FC-200D-27A1-FE0F","emoji":"🧎🏼‍➡️","text":"","type":1,"order":2106,"group":1,"subgroup":27,"version":15.1,"tone":2},{"label":"person kneeling: medium skin tone, facing right","hexcode":"1F9CE-1F3FD-200D-27A1-FE0F","emoji":"🧎🏽‍➡️","text":"","type":1,"order":2108,"group":1,"subgroup":27,"version":15.1,"tone":3},{"label":"person kneeling: medium-dark skin tone, facing right","hexcode":"1F9CE-1F3FE-200D-27A1-FE0F","emoji":"🧎🏾‍➡️","text":"","type":1,"order":2110,"group":1,"subgroup":27,"version":15.1,"tone":4},{"label":"person kneeling: dark skin tone, facing right","hexcode":"1F9CE-1F3FF-200D-27A1-FE0F","emoji":"🧎🏿‍➡️","text":"","type":1,"order":2112,"group":1,"subgroup":27,"version":15.1,"tone":5}]},{"label":"woman kneeling: facing right","hexcode":"1F9CE-200D-2640-FE0F-200D-27A1-FE0F","tags":["facing","kneel","kneeling","knees","right","woman"],"emoji":"🧎‍♀️‍➡️","text":"","type":1,"order":2114,"group":1,"subgroup":27,"version":15.1,"gender":0,"skins":[{"label":"woman kneeling: light skin tone, facing right","hexcode":"1F9CE-1F3FB-200D-2640-FE0F-200D-27A1-FE0F","emoji":"🧎🏻‍♀️‍➡️","text":"","type":1,"order":2118,"group":1,"subgroup":27,"version":15.1,"gender":0,"tone":1},{"label":"woman kneeling: medium-light skin tone, facing right","hexcode":"1F9CE-1F3FC-200D-2640-FE0F-200D-27A1-FE0F","emoji":"🧎🏼‍♀️‍➡️","text":"","type":1,"order":2122,"group":1,"subgroup":27,"version":15.1,"gender":0,"tone":2},{"label":"woman kneeling: medium skin tone, facing right","hexcode":"1F9CE-1F3FD-200D-2640-FE0F-200D-27A1-FE0F","emoji":"🧎🏽‍♀️‍➡️","text":"","type":1,"order":2126,"group":1,"subgroup":27,"version":15.1,"gender":0,"tone":3},{"label":"woman kneeling: medium-dark skin tone, facing right","hexcode":"1F9CE-1F3FE-200D-2640-FE0F-200D-27A1-FE0F","emoji":"🧎🏾‍♀️‍➡️","text":"","type":1,"order":2130,"group":1,"subgroup":27,"version":15.1,"gender":0,"tone":4},{"label":"woman kneeling: dark skin tone, facing right","hexcode":"1F9CE-1F3FF-200D-2640-FE0F-200D-27A1-FE0F","emoji":"🧎🏿‍♀️‍➡️","text":"","type":1,"order":2134,"group":1,"subgroup":27,"version":15.1,"gender":0,"tone":5}]},{"label":"man kneeling: facing right","hexcode":"1F9CE-200D-2642-FE0F-200D-27A1-FE0F","tags":["facing","kneel","kneeling","knees","man","right"],"emoji":"🧎‍♂️‍➡️","text":"","type":1,"order":2138,"group":1,"subgroup":27,"version":15.1,"gender":1,"skins":[{"label":"man kneeling: light skin tone, facing right","hexcode":"1F9CE-1F3FB-200D-2642-FE0F-200D-27A1-FE0F","emoji":"🧎🏻‍♂️‍➡️","text":"","type":1,"order":2142,"group":1,"subgroup":27,"version":15.1,"gender":1,"tone":1},{"label":"man kneeling: medium-light skin tone, facing right","hexcode":"1F9CE-1F3FC-200D-2642-FE0F-200D-27A1-FE0F","emoji":"🧎🏼‍♂️‍➡️","text":"","type":1,"order":2146,"group":1,"subgroup":27,"version":15.1,"gender":1,"tone":2},{"label":"man kneeling: medium skin tone, facing right","hexcode":"1F9CE-1F3FD-200D-2642-FE0F-200D-27A1-FE0F","emoji":"🧎🏽‍♂️‍➡️","text":"","type":1,"order":2150,"group":1,"subgroup":27,"version":15.1,"gender":1,"tone":3},{"label":"man kneeling: medium-dark skin tone, facing right","hexcode":"1F9CE-1F3FE-200D-2642-FE0F-200D-27A1-FE0F","emoji":"🧎🏾‍♂️‍➡️","text":"","type":1,"order":2154,"group":1,"subgroup":27,"version":15.1,"gender":1,"tone":4},{"label":"man kneeling: dark skin tone, facing right","hexcode":"1F9CE-1F3FF-200D-2642-FE0F-200D-27A1-FE0F","emoji":"🧎🏿‍♂️‍➡️","text":"","type":1,"order":2158,"group":1,"subgroup":27,"version":15.1,"gender":1,"tone":5}]},{"label":"person with white cane","hexcode":"1F9D1-200D-1F9AF","tags":["accessibility","blind","cane","person","probing","white"],"emoji":"🧑‍🦯","text":"","type":1,"order":2162,"group":1,"subgroup":27,"version":12.1,"skins":[{"label":"person with white cane: light skin tone","hexcode":"1F9D1-1F3FB-200D-1F9AF","emoji":"🧑🏻‍🦯","text":"","type":1,"order":2163,"group":1,"subgroup":27,"version":12.1,"tone":1},{"label":"person with white cane: medium-light skin tone","hexcode":"1F9D1-1F3FC-200D-1F9AF","emoji":"🧑🏼‍🦯","text":"","type":1,"order":2164,"group":1,"subgroup":27,"version":12.1,"tone":2},{"label":"person with white cane: medium skin tone","hexcode":"1F9D1-1F3FD-200D-1F9AF","emoji":"🧑🏽‍🦯","text":"","type":1,"order":2165,"group":1,"subgroup":27,"version":12.1,"tone":3},{"label":"person with white cane: medium-dark skin tone","hexcode":"1F9D1-1F3FE-200D-1F9AF","emoji":"🧑🏾‍🦯","text":"","type":1,"order":2166,"group":1,"subgroup":27,"version":12.1,"tone":4},{"label":"person with white cane: dark skin tone","hexcode":"1F9D1-1F3FF-200D-1F9AF","emoji":"🧑🏿‍🦯","text":"","type":1,"order":2167,"group":1,"subgroup":27,"version":12.1,"tone":5}]},{"label":"person with white cane: facing right","hexcode":"1F9D1-200D-1F9AF-200D-27A1-FE0F","tags":["accessibility","blind","cane","facing","person","probing","right","white"],"emoji":"🧑‍🦯‍➡️","text":"","type":1,"order":2168,"group":1,"subgroup":27,"version":15.1,"skins":[{"label":"person with white cane: light skin tone, facing right","hexcode":"1F9D1-1F3FB-200D-1F9AF-200D-27A1-FE0F","emoji":"🧑🏻‍🦯‍➡️","text":"","type":1,"order":2170,"group":1,"subgroup":27,"version":15.1,"tone":1},{"label":"person with white cane: medium-light skin tone, facing right","hexcode":"1F9D1-1F3FC-200D-1F9AF-200D-27A1-FE0F","emoji":"🧑🏼‍🦯‍➡️","text":"","type":1,"order":2172,"group":1,"subgroup":27,"version":15.1,"tone":2},{"label":"person with white cane: medium skin tone, facing right","hexcode":"1F9D1-1F3FD-200D-1F9AF-200D-27A1-FE0F","emoji":"🧑🏽‍🦯‍➡️","text":"","type":1,"order":2174,"group":1,"subgroup":27,"version":15.1,"tone":3},{"label":"person with white cane: medium-dark skin tone, facing right","hexcode":"1F9D1-1F3FE-200D-1F9AF-200D-27A1-FE0F","emoji":"🧑🏾‍🦯‍➡️","text":"","type":1,"order":2176,"group":1,"subgroup":27,"version":15.1,"tone":4},{"label":"person with white cane: dark skin tone, facing right","hexcode":"1F9D1-1F3FF-200D-1F9AF-200D-27A1-FE0F","emoji":"🧑🏿‍🦯‍➡️","text":"","type":1,"order":2178,"group":1,"subgroup":27,"version":15.1,"tone":5}]},{"label":"man with white cane","hexcode":"1F468-200D-1F9AF","tags":["accessibility","blind","cane","man","probing","white"],"emoji":"👨‍🦯","text":"","type":1,"order":2180,"group":1,"subgroup":27,"version":12,"skins":[{"label":"man with white cane: light skin tone","hexcode":"1F468-1F3FB-200D-1F9AF","emoji":"👨🏻‍🦯","text":"","type":1,"order":2181,"group":1,"subgroup":27,"version":12,"tone":1},{"label":"man with white cane: medium-light skin tone","hexcode":"1F468-1F3FC-200D-1F9AF","emoji":"👨🏼‍🦯","text":"","type":1,"order":2182,"group":1,"subgroup":27,"version":12,"tone":2},{"label":"man with white cane: medium skin tone","hexcode":"1F468-1F3FD-200D-1F9AF","emoji":"👨🏽‍🦯","text":"","type":1,"order":2183,"group":1,"subgroup":27,"version":12,"tone":3},{"label":"man with white cane: medium-dark skin tone","hexcode":"1F468-1F3FE-200D-1F9AF","emoji":"👨🏾‍🦯","text":"","type":1,"order":2184,"group":1,"subgroup":27,"version":12,"tone":4},{"label":"man with white cane: dark skin tone","hexcode":"1F468-1F3FF-200D-1F9AF","emoji":"👨🏿‍🦯","text":"","type":1,"order":2185,"group":1,"subgroup":27,"version":12,"tone":5}]},{"label":"man with white cane: facing right","hexcode":"1F468-200D-1F9AF-200D-27A1-FE0F","tags":["accessibility","blind","cane","facing","man","probing","right","white"],"emoji":"👨‍🦯‍➡️","text":"","type":1,"order":2186,"group":1,"subgroup":27,"version":15.1,"skins":[{"label":"man with white cane: light skin tone, facing right","hexcode":"1F468-1F3FB-200D-1F9AF-200D-27A1-FE0F","emoji":"👨🏻‍🦯‍➡️","text":"","type":1,"order":2188,"group":1,"subgroup":27,"version":15.1,"tone":1},{"label":"man with white cane: medium-light skin tone, facing right","hexcode":"1F468-1F3FC-200D-1F9AF-200D-27A1-FE0F","emoji":"👨🏼‍🦯‍➡️","text":"","type":1,"order":2190,"group":1,"subgroup":27,"version":15.1,"tone":2},{"label":"man with white cane: medium skin tone, facing right","hexcode":"1F468-1F3FD-200D-1F9AF-200D-27A1-FE0F","emoji":"👨🏽‍🦯‍➡️","text":"","type":1,"order":2192,"group":1,"subgroup":27,"version":15.1,"tone":3},{"label":"man with white cane: medium-dark skin tone, facing right","hexcode":"1F468-1F3FE-200D-1F9AF-200D-27A1-FE0F","emoji":"👨🏾‍🦯‍➡️","text":"","type":1,"order":2194,"group":1,"subgroup":27,"version":15.1,"tone":4},{"label":"man with white cane: dark skin tone, facing right","hexcode":"1F468-1F3FF-200D-1F9AF-200D-27A1-FE0F","emoji":"👨🏿‍🦯‍➡️","text":"","type":1,"order":2196,"group":1,"subgroup":27,"version":15.1,"tone":5}]},{"label":"woman with white cane","hexcode":"1F469-200D-1F9AF","tags":["accessibility","blind","cane","probing","white","woman"],"emoji":"👩‍🦯","text":"","type":1,"order":2198,"group":1,"subgroup":27,"version":12,"skins":[{"label":"woman with white cane: light skin tone","hexcode":"1F469-1F3FB-200D-1F9AF","emoji":"👩🏻‍🦯","text":"","type":1,"order":2199,"group":1,"subgroup":27,"version":12,"tone":1},{"label":"woman with white cane: medium-light skin tone","hexcode":"1F469-1F3FC-200D-1F9AF","emoji":"👩🏼‍🦯","text":"","type":1,"order":2200,"group":1,"subgroup":27,"version":12,"tone":2},{"label":"woman with white cane: medium skin tone","hexcode":"1F469-1F3FD-200D-1F9AF","emoji":"👩🏽‍🦯","text":"","type":1,"order":2201,"group":1,"subgroup":27,"version":12,"tone":3},{"label":"woman with white cane: medium-dark skin tone","hexcode":"1F469-1F3FE-200D-1F9AF","emoji":"👩🏾‍🦯","text":"","type":1,"order":2202,"group":1,"subgroup":27,"version":12,"tone":4},{"label":"woman with white cane: dark skin tone","hexcode":"1F469-1F3FF-200D-1F9AF","emoji":"👩🏿‍🦯","text":"","type":1,"order":2203,"group":1,"subgroup":27,"version":12,"tone":5}]},{"label":"woman with white cane: facing right","hexcode":"1F469-200D-1F9AF-200D-27A1-FE0F","tags":["accessibility","blind","cane","facing","probing","right","white","woman"],"emoji":"👩‍🦯‍➡️","text":"","type":1,"order":2204,"group":1,"subgroup":27,"version":15.1,"skins":[{"label":"woman with white cane: light skin tone, facing right","hexcode":"1F469-1F3FB-200D-1F9AF-200D-27A1-FE0F","emoji":"👩🏻‍🦯‍➡️","text":"","type":1,"order":2206,"group":1,"subgroup":27,"version":15.1,"tone":1},{"label":"woman with white cane: medium-light skin tone, facing right","hexcode":"1F469-1F3FC-200D-1F9AF-200D-27A1-FE0F","emoji":"👩🏼‍🦯‍➡️","text":"","type":1,"order":2208,"group":1,"subgroup":27,"version":15.1,"tone":2},{"label":"woman with white cane: medium skin tone, facing right","hexcode":"1F469-1F3FD-200D-1F9AF-200D-27A1-FE0F","emoji":"👩🏽‍🦯‍➡️","text":"","type":1,"order":2210,"group":1,"subgroup":27,"version":15.1,"tone":3},{"label":"woman with white cane: medium-dark skin tone, facing right","hexcode":"1F469-1F3FE-200D-1F9AF-200D-27A1-FE0F","emoji":"👩🏾‍🦯‍➡️","text":"","type":1,"order":2212,"group":1,"subgroup":27,"version":15.1,"tone":4},{"label":"woman with white cane: dark skin tone, facing right","hexcode":"1F469-1F3FF-200D-1F9AF-200D-27A1-FE0F","emoji":"👩🏿‍🦯‍➡️","text":"","type":1,"order":2214,"group":1,"subgroup":27,"version":15.1,"tone":5}]},{"label":"person in motorized wheelchair","hexcode":"1F9D1-200D-1F9BC","tags":["accessibility","motorized","person","wheelchair"],"emoji":"🧑‍🦼","text":"","type":1,"order":2216,"group":1,"subgroup":27,"version":12.1,"skins":[{"label":"person in motorized wheelchair: light skin tone","hexcode":"1F9D1-1F3FB-200D-1F9BC","emoji":"🧑🏻‍🦼","text":"","type":1,"order":2217,"group":1,"subgroup":27,"version":12.1,"tone":1},{"label":"person in motorized wheelchair: medium-light skin tone","hexcode":"1F9D1-1F3FC-200D-1F9BC","emoji":"🧑🏼‍🦼","text":"","type":1,"order":2218,"group":1,"subgroup":27,"version":12.1,"tone":2},{"label":"person in motorized wheelchair: medium skin tone","hexcode":"1F9D1-1F3FD-200D-1F9BC","emoji":"🧑🏽‍🦼","text":"","type":1,"order":2219,"group":1,"subgroup":27,"version":12.1,"tone":3},{"label":"person in motorized wheelchair: medium-dark skin tone","hexcode":"1F9D1-1F3FE-200D-1F9BC","emoji":"🧑🏾‍🦼","text":"","type":1,"order":2220,"group":1,"subgroup":27,"version":12.1,"tone":4},{"label":"person in motorized wheelchair: dark skin tone","hexcode":"1F9D1-1F3FF-200D-1F9BC","emoji":"🧑🏿‍🦼","text":"","type":1,"order":2221,"group":1,"subgroup":27,"version":12.1,"tone":5}]},{"label":"person in motorized wheelchair: facing right","hexcode":"1F9D1-200D-1F9BC-200D-27A1-FE0F","tags":["accessibility","facing","motorized","person","right","wheelchair"],"emoji":"🧑‍🦼‍➡️","text":"","type":1,"order":2222,"group":1,"subgroup":27,"version":15.1,"skins":[{"label":"person in motorized wheelchair: light skin tone, facing right","hexcode":"1F9D1-1F3FB-200D-1F9BC-200D-27A1-FE0F","emoji":"🧑🏻‍🦼‍➡️","text":"","type":1,"order":2224,"group":1,"subgroup":27,"version":15.1,"tone":1},{"label":"person in motorized wheelchair: medium-light skin tone, facing right","hexcode":"1F9D1-1F3FC-200D-1F9BC-200D-27A1-FE0F","emoji":"🧑🏼‍🦼‍➡️","text":"","type":1,"order":2226,"group":1,"subgroup":27,"version":15.1,"tone":2},{"label":"person in motorized wheelchair: medium skin tone, facing right","hexcode":"1F9D1-1F3FD-200D-1F9BC-200D-27A1-FE0F","emoji":"🧑🏽‍🦼‍➡️","text":"","type":1,"order":2228,"group":1,"subgroup":27,"version":15.1,"tone":3},{"label":"person in motorized wheelchair: medium-dark skin tone, facing right","hexcode":"1F9D1-1F3FE-200D-1F9BC-200D-27A1-FE0F","emoji":"🧑🏾‍🦼‍➡️","text":"","type":1,"order":2230,"group":1,"subgroup":27,"version":15.1,"tone":4},{"label":"person in motorized wheelchair: dark skin tone, facing right","hexcode":"1F9D1-1F3FF-200D-1F9BC-200D-27A1-FE0F","emoji":"🧑🏿‍🦼‍➡️","text":"","type":1,"order":2232,"group":1,"subgroup":27,"version":15.1,"tone":5}]},{"label":"man in motorized wheelchair","hexcode":"1F468-200D-1F9BC","tags":["accessibility","man","motorized","wheelchair"],"emoji":"👨‍🦼","text":"","type":1,"order":2234,"group":1,"subgroup":27,"version":12,"skins":[{"label":"man in motorized wheelchair: light skin tone","hexcode":"1F468-1F3FB-200D-1F9BC","emoji":"👨🏻‍🦼","text":"","type":1,"order":2235,"group":1,"subgroup":27,"version":12,"tone":1},{"label":"man in motorized wheelchair: medium-light skin tone","hexcode":"1F468-1F3FC-200D-1F9BC","emoji":"👨🏼‍🦼","text":"","type":1,"order":2236,"group":1,"subgroup":27,"version":12,"tone":2},{"label":"man in motorized wheelchair: medium skin tone","hexcode":"1F468-1F3FD-200D-1F9BC","emoji":"👨🏽‍🦼","text":"","type":1,"order":2237,"group":1,"subgroup":27,"version":12,"tone":3},{"label":"man in motorized wheelchair: medium-dark skin tone","hexcode":"1F468-1F3FE-200D-1F9BC","emoji":"👨🏾‍🦼","text":"","type":1,"order":2238,"group":1,"subgroup":27,"version":12,"tone":4},{"label":"man in motorized wheelchair: dark skin tone","hexcode":"1F468-1F3FF-200D-1F9BC","emoji":"👨🏿‍🦼","text":"","type":1,"order":2239,"group":1,"subgroup":27,"version":12,"tone":5}]},{"label":"man in motorized wheelchair: facing right","hexcode":"1F468-200D-1F9BC-200D-27A1-FE0F","tags":["accessibility","facing","man","motorized","right","wheelchair"],"emoji":"👨‍🦼‍➡️","text":"","type":1,"order":2240,"group":1,"subgroup":27,"version":15.1,"skins":[{"label":"man in motorized wheelchair: light skin tone, facing right","hexcode":"1F468-1F3FB-200D-1F9BC-200D-27A1-FE0F","emoji":"👨🏻‍🦼‍➡️","text":"","type":1,"order":2242,"group":1,"subgroup":27,"version":15.1,"tone":1},{"label":"man in motorized wheelchair: medium-light skin tone, facing right","hexcode":"1F468-1F3FC-200D-1F9BC-200D-27A1-FE0F","emoji":"👨🏼‍🦼‍➡️","text":"","type":1,"order":2244,"group":1,"subgroup":27,"version":15.1,"tone":2},{"label":"man in motorized wheelchair: medium skin tone, facing right","hexcode":"1F468-1F3FD-200D-1F9BC-200D-27A1-FE0F","emoji":"👨🏽‍🦼‍➡️","text":"","type":1,"order":2246,"group":1,"subgroup":27,"version":15.1,"tone":3},{"label":"man in motorized wheelchair: medium-dark skin tone, facing right","hexcode":"1F468-1F3FE-200D-1F9BC-200D-27A1-FE0F","emoji":"👨🏾‍🦼‍➡️","text":"","type":1,"order":2248,"group":1,"subgroup":27,"version":15.1,"tone":4},{"label":"man in motorized wheelchair: dark skin tone, facing right","hexcode":"1F468-1F3FF-200D-1F9BC-200D-27A1-FE0F","emoji":"👨🏿‍🦼‍➡️","text":"","type":1,"order":2250,"group":1,"subgroup":27,"version":15.1,"tone":5}]},{"label":"woman in motorized wheelchair","hexcode":"1F469-200D-1F9BC","tags":["accessibility","motorized","wheelchair","woman"],"emoji":"👩‍🦼","text":"","type":1,"order":2252,"group":1,"subgroup":27,"version":12,"skins":[{"label":"woman in motorized wheelchair: light skin tone","hexcode":"1F469-1F3FB-200D-1F9BC","emoji":"👩🏻‍🦼","text":"","type":1,"order":2253,"group":1,"subgroup":27,"version":12,"tone":1},{"label":"woman in motorized wheelchair: medium-light skin tone","hexcode":"1F469-1F3FC-200D-1F9BC","emoji":"👩🏼‍🦼","text":"","type":1,"order":2254,"group":1,"subgroup":27,"version":12,"tone":2},{"label":"woman in motorized wheelchair: medium skin tone","hexcode":"1F469-1F3FD-200D-1F9BC","emoji":"👩🏽‍🦼","text":"","type":1,"order":2255,"group":1,"subgroup":27,"version":12,"tone":3},{"label":"woman in motorized wheelchair: medium-dark skin tone","hexcode":"1F469-1F3FE-200D-1F9BC","emoji":"👩🏾‍🦼","text":"","type":1,"order":2256,"group":1,"subgroup":27,"version":12,"tone":4},{"label":"woman in motorized wheelchair: dark skin tone","hexcode":"1F469-1F3FF-200D-1F9BC","emoji":"👩🏿‍🦼","text":"","type":1,"order":2257,"group":1,"subgroup":27,"version":12,"tone":5}]},{"label":"woman in motorized wheelchair: facing right","hexcode":"1F469-200D-1F9BC-200D-27A1-FE0F","tags":["accessibility","facing","motorized","right","wheelchair","woman"],"emoji":"👩‍🦼‍➡️","text":"","type":1,"order":2258,"group":1,"subgroup":27,"version":15.1,"skins":[{"label":"woman in motorized wheelchair: light skin tone, facing right","hexcode":"1F469-1F3FB-200D-1F9BC-200D-27A1-FE0F","emoji":"👩🏻‍🦼‍➡️","text":"","type":1,"order":2260,"group":1,"subgroup":27,"version":15.1,"tone":1},{"label":"woman in motorized wheelchair: medium-light skin tone, facing right","hexcode":"1F469-1F3FC-200D-1F9BC-200D-27A1-FE0F","emoji":"👩🏼‍🦼‍➡️","text":"","type":1,"order":2262,"group":1,"subgroup":27,"version":15.1,"tone":2},{"label":"woman in motorized wheelchair: medium skin tone, facing right","hexcode":"1F469-1F3FD-200D-1F9BC-200D-27A1-FE0F","emoji":"👩🏽‍🦼‍➡️","text":"","type":1,"order":2264,"group":1,"subgroup":27,"version":15.1,"tone":3},{"label":"woman in motorized wheelchair: medium-dark skin tone, facing right","hexcode":"1F469-1F3FE-200D-1F9BC-200D-27A1-FE0F","emoji":"👩🏾‍🦼‍➡️","text":"","type":1,"order":2266,"group":1,"subgroup":27,"version":15.1,"tone":4},{"label":"woman in motorized wheelchair: dark skin tone, facing right","hexcode":"1F469-1F3FF-200D-1F9BC-200D-27A1-FE0F","emoji":"👩🏿‍🦼‍➡️","text":"","type":1,"order":2268,"group":1,"subgroup":27,"version":15.1,"tone":5}]},{"label":"person in manual wheelchair","hexcode":"1F9D1-200D-1F9BD","tags":["accessibility","manual","person","wheelchair"],"emoji":"🧑‍🦽","text":"","type":1,"order":2270,"group":1,"subgroup":27,"version":12.1,"skins":[{"label":"person in manual wheelchair: light skin tone","hexcode":"1F9D1-1F3FB-200D-1F9BD","emoji":"🧑🏻‍🦽","text":"","type":1,"order":2271,"group":1,"subgroup":27,"version":12.1,"tone":1},{"label":"person in manual wheelchair: medium-light skin tone","hexcode":"1F9D1-1F3FC-200D-1F9BD","emoji":"🧑🏼‍🦽","text":"","type":1,"order":2272,"group":1,"subgroup":27,"version":12.1,"tone":2},{"label":"person in manual wheelchair: medium skin tone","hexcode":"1F9D1-1F3FD-200D-1F9BD","emoji":"🧑🏽‍🦽","text":"","type":1,"order":2273,"group":1,"subgroup":27,"version":12.1,"tone":3},{"label":"person in manual wheelchair: medium-dark skin tone","hexcode":"1F9D1-1F3FE-200D-1F9BD","emoji":"🧑🏾‍🦽","text":"","type":1,"order":2274,"group":1,"subgroup":27,"version":12.1,"tone":4},{"label":"person in manual wheelchair: dark skin tone","hexcode":"1F9D1-1F3FF-200D-1F9BD","emoji":"🧑🏿‍🦽","text":"","type":1,"order":2275,"group":1,"subgroup":27,"version":12.1,"tone":5}]},{"label":"person in manual wheelchair: facing right","hexcode":"1F9D1-200D-1F9BD-200D-27A1-FE0F","tags":["accessibility","facing","manual","person","right","wheelchair"],"emoji":"🧑‍🦽‍➡️","text":"","type":1,"order":2276,"group":1,"subgroup":27,"version":15.1,"skins":[{"label":"person in manual wheelchair: light skin tone, facing right","hexcode":"1F9D1-1F3FB-200D-1F9BD-200D-27A1-FE0F","emoji":"🧑🏻‍🦽‍➡️","text":"","type":1,"order":2278,"group":1,"subgroup":27,"version":15.1,"tone":1},{"label":"person in manual wheelchair: medium-light skin tone, facing right","hexcode":"1F9D1-1F3FC-200D-1F9BD-200D-27A1-FE0F","emoji":"🧑🏼‍🦽‍➡️","text":"","type":1,"order":2280,"group":1,"subgroup":27,"version":15.1,"tone":2},{"label":"person in manual wheelchair: medium skin tone, facing right","hexcode":"1F9D1-1F3FD-200D-1F9BD-200D-27A1-FE0F","emoji":"🧑🏽‍🦽‍➡️","text":"","type":1,"order":2282,"group":1,"subgroup":27,"version":15.1,"tone":3},{"label":"person in manual wheelchair: medium-dark skin tone, facing right","hexcode":"1F9D1-1F3FE-200D-1F9BD-200D-27A1-FE0F","emoji":"🧑🏾‍🦽‍➡️","text":"","type":1,"order":2284,"group":1,"subgroup":27,"version":15.1,"tone":4},{"label":"person in manual wheelchair: dark skin tone, facing right","hexcode":"1F9D1-1F3FF-200D-1F9BD-200D-27A1-FE0F","emoji":"🧑🏿‍🦽‍➡️","text":"","type":1,"order":2286,"group":1,"subgroup":27,"version":15.1,"tone":5}]},{"label":"man in manual wheelchair","hexcode":"1F468-200D-1F9BD","tags":["accessibility","man","manual","wheelchair"],"emoji":"👨‍🦽","text":"","type":1,"order":2288,"group":1,"subgroup":27,"version":12,"skins":[{"label":"man in manual wheelchair: light skin tone","hexcode":"1F468-1F3FB-200D-1F9BD","emoji":"👨🏻‍🦽","text":"","type":1,"order":2289,"group":1,"subgroup":27,"version":12,"tone":1},{"label":"man in manual wheelchair: medium-light skin tone","hexcode":"1F468-1F3FC-200D-1F9BD","emoji":"👨🏼‍🦽","text":"","type":1,"order":2290,"group":1,"subgroup":27,"version":12,"tone":2},{"label":"man in manual wheelchair: medium skin tone","hexcode":"1F468-1F3FD-200D-1F9BD","emoji":"👨🏽‍🦽","text":"","type":1,"order":2291,"group":1,"subgroup":27,"version":12,"tone":3},{"label":"man in manual wheelchair: medium-dark skin tone","hexcode":"1F468-1F3FE-200D-1F9BD","emoji":"👨🏾‍🦽","text":"","type":1,"order":2292,"group":1,"subgroup":27,"version":12,"tone":4},{"label":"man in manual wheelchair: dark skin tone","hexcode":"1F468-1F3FF-200D-1F9BD","emoji":"👨🏿‍🦽","text":"","type":1,"order":2293,"group":1,"subgroup":27,"version":12,"tone":5}]},{"label":"man in manual wheelchair: facing right","hexcode":"1F468-200D-1F9BD-200D-27A1-FE0F","tags":["accessibility","facing","man","manual","right","wheelchair"],"emoji":"👨‍🦽‍➡️","text":"","type":1,"order":2294,"group":1,"subgroup":27,"version":15.1,"skins":[{"label":"man in manual wheelchair: light skin tone, facing right","hexcode":"1F468-1F3FB-200D-1F9BD-200D-27A1-FE0F","emoji":"👨🏻‍🦽‍➡️","text":"","type":1,"order":2296,"group":1,"subgroup":27,"version":15.1,"tone":1},{"label":"man in manual wheelchair: medium-light skin tone, facing right","hexcode":"1F468-1F3FC-200D-1F9BD-200D-27A1-FE0F","emoji":"👨🏼‍🦽‍➡️","text":"","type":1,"order":2298,"group":1,"subgroup":27,"version":15.1,"tone":2},{"label":"man in manual wheelchair: medium skin tone, facing right","hexcode":"1F468-1F3FD-200D-1F9BD-200D-27A1-FE0F","emoji":"👨🏽‍🦽‍➡️","text":"","type":1,"order":2300,"group":1,"subgroup":27,"version":15.1,"tone":3},{"label":"man in manual wheelchair: medium-dark skin tone, facing right","hexcode":"1F468-1F3FE-200D-1F9BD-200D-27A1-FE0F","emoji":"👨🏾‍🦽‍➡️","text":"","type":1,"order":2302,"group":1,"subgroup":27,"version":15.1,"tone":4},{"label":"man in manual wheelchair: dark skin tone, facing right","hexcode":"1F468-1F3FF-200D-1F9BD-200D-27A1-FE0F","emoji":"👨🏿‍🦽‍➡️","text":"","type":1,"order":2304,"group":1,"subgroup":27,"version":15.1,"tone":5}]},{"label":"woman in manual wheelchair","hexcode":"1F469-200D-1F9BD","tags":["accessibility","manual","wheelchair","woman"],"emoji":"👩‍🦽","text":"","type":1,"order":2306,"group":1,"subgroup":27,"version":12,"skins":[{"label":"woman in manual wheelchair: light skin tone","hexcode":"1F469-1F3FB-200D-1F9BD","emoji":"👩🏻‍🦽","text":"","type":1,"order":2307,"group":1,"subgroup":27,"version":12,"tone":1},{"label":"woman in manual wheelchair: medium-light skin tone","hexcode":"1F469-1F3FC-200D-1F9BD","emoji":"👩🏼‍🦽","text":"","type":1,"order":2308,"group":1,"subgroup":27,"version":12,"tone":2},{"label":"woman in manual wheelchair: medium skin tone","hexcode":"1F469-1F3FD-200D-1F9BD","emoji":"👩🏽‍🦽","text":"","type":1,"order":2309,"group":1,"subgroup":27,"version":12,"tone":3},{"label":"woman in manual wheelchair: medium-dark skin tone","hexcode":"1F469-1F3FE-200D-1F9BD","emoji":"👩🏾‍🦽","text":"","type":1,"order":2310,"group":1,"subgroup":27,"version":12,"tone":4},{"label":"woman in manual wheelchair: dark skin tone","hexcode":"1F469-1F3FF-200D-1F9BD","emoji":"👩🏿‍🦽","text":"","type":1,"order":2311,"group":1,"subgroup":27,"version":12,"tone":5}]},{"label":"woman in manual wheelchair: facing right","hexcode":"1F469-200D-1F9BD-200D-27A1-FE0F","tags":["accessibility","facing","manual","right","wheelchair","woman"],"emoji":"👩‍🦽‍➡️","text":"","type":1,"order":2312,"group":1,"subgroup":27,"version":15.1,"skins":[{"label":"woman in manual wheelchair: light skin tone, facing right","hexcode":"1F469-1F3FB-200D-1F9BD-200D-27A1-FE0F","emoji":"👩🏻‍🦽‍➡️","text":"","type":1,"order":2314,"group":1,"subgroup":27,"version":15.1,"tone":1},{"label":"woman in manual wheelchair: medium-light skin tone, facing right","hexcode":"1F469-1F3FC-200D-1F9BD-200D-27A1-FE0F","emoji":"👩🏼‍🦽‍➡️","text":"","type":1,"order":2316,"group":1,"subgroup":27,"version":15.1,"tone":2},{"label":"woman in manual wheelchair: medium skin tone, facing right","hexcode":"1F469-1F3FD-200D-1F9BD-200D-27A1-FE0F","emoji":"👩🏽‍🦽‍➡️","text":"","type":1,"order":2318,"group":1,"subgroup":27,"version":15.1,"tone":3},{"label":"woman in manual wheelchair: medium-dark skin tone, facing right","hexcode":"1F469-1F3FE-200D-1F9BD-200D-27A1-FE0F","emoji":"👩🏾‍🦽‍➡️","text":"","type":1,"order":2320,"group":1,"subgroup":27,"version":15.1,"tone":4},{"label":"woman in manual wheelchair: dark skin tone, facing right","hexcode":"1F469-1F3FF-200D-1F9BD-200D-27A1-FE0F","emoji":"👩🏿‍🦽‍➡️","text":"","type":1,"order":2322,"group":1,"subgroup":27,"version":15.1,"tone":5}]},{"label":"person running","hexcode":"1F3C3","tags":["fast","hurry","marathon","move","person","quick","race","racing","run","rush","speed"],"emoji":"🏃","text":"","type":1,"order":2324,"group":1,"subgroup":27,"version":0.6,"skins":[{"label":"person running: light skin tone","hexcode":"1F3C3-1F3FB","emoji":"🏃🏻","text":"","type":1,"order":2325,"group":1,"subgroup":27,"version":1,"tone":1},{"label":"person running: medium-light skin tone","hexcode":"1F3C3-1F3FC","emoji":"🏃🏼","text":"","type":1,"order":2326,"group":1,"subgroup":27,"version":1,"tone":2},{"label":"person running: medium skin tone","hexcode":"1F3C3-1F3FD","emoji":"🏃🏽","text":"","type":1,"order":2327,"group":1,"subgroup":27,"version":1,"tone":3},{"label":"person running: medium-dark skin tone","hexcode":"1F3C3-1F3FE","emoji":"🏃🏾","text":"","type":1,"order":2328,"group":1,"subgroup":27,"version":1,"tone":4},{"label":"person running: dark skin tone","hexcode":"1F3C3-1F3FF","emoji":"🏃🏿","text":"","type":1,"order":2329,"group":1,"subgroup":27,"version":1,"tone":5}]},{"label":"man running","hexcode":"1F3C3-200D-2642-FE0F","tags":["fast","hurry","man","marathon","move","quick","race","racing","run","rush","speed"],"emoji":"🏃‍♂️","text":"","type":1,"order":2330,"group":1,"subgroup":27,"version":4,"gender":1,"skins":[{"label":"man running: light skin tone","hexcode":"1F3C3-1F3FB-200D-2642-FE0F","emoji":"🏃🏻‍♂️","text":"","type":1,"order":2332,"group":1,"subgroup":27,"version":4,"gender":1,"tone":1},{"label":"man running: medium-light skin tone","hexcode":"1F3C3-1F3FC-200D-2642-FE0F","emoji":"🏃🏼‍♂️","text":"","type":1,"order":2334,"group":1,"subgroup":27,"version":4,"gender":1,"tone":2},{"label":"man running: medium skin tone","hexcode":"1F3C3-1F3FD-200D-2642-FE0F","emoji":"🏃🏽‍♂️","text":"","type":1,"order":2336,"group":1,"subgroup":27,"version":4,"gender":1,"tone":3},{"label":"man running: medium-dark skin tone","hexcode":"1F3C3-1F3FE-200D-2642-FE0F","emoji":"🏃🏾‍♂️","text":"","type":1,"order":2338,"group":1,"subgroup":27,"version":4,"gender":1,"tone":4},{"label":"man running: dark skin tone","hexcode":"1F3C3-1F3FF-200D-2642-FE0F","emoji":"🏃🏿‍♂️","text":"","type":1,"order":2340,"group":1,"subgroup":27,"version":4,"gender":1,"tone":5}]},{"label":"woman running","hexcode":"1F3C3-200D-2640-FE0F","tags":["fast","hurry","marathon","move","quick","race","racing","run","rush","speed","woman"],"emoji":"🏃‍♀️","text":"","type":1,"order":2342,"group":1,"subgroup":27,"version":4,"gender":0,"skins":[{"label":"woman running: light skin tone","hexcode":"1F3C3-1F3FB-200D-2640-FE0F","emoji":"🏃🏻‍♀️","text":"","type":1,"order":2344,"group":1,"subgroup":27,"version":4,"gender":0,"tone":1},{"label":"woman running: medium-light skin tone","hexcode":"1F3C3-1F3FC-200D-2640-FE0F","emoji":"🏃🏼‍♀️","text":"","type":1,"order":2346,"group":1,"subgroup":27,"version":4,"gender":0,"tone":2},{"label":"woman running: medium skin tone","hexcode":"1F3C3-1F3FD-200D-2640-FE0F","emoji":"🏃🏽‍♀️","text":"","type":1,"order":2348,"group":1,"subgroup":27,"version":4,"gender":0,"tone":3},{"label":"woman running: medium-dark skin tone","hexcode":"1F3C3-1F3FE-200D-2640-FE0F","emoji":"🏃🏾‍♀️","text":"","type":1,"order":2350,"group":1,"subgroup":27,"version":4,"gender":0,"tone":4},{"label":"woman running: dark skin tone","hexcode":"1F3C3-1F3FF-200D-2640-FE0F","emoji":"🏃🏿‍♀️","text":"","type":1,"order":2352,"group":1,"subgroup":27,"version":4,"gender":0,"tone":5}]},{"label":"person running: facing right","hexcode":"1F3C3-200D-27A1-FE0F","tags":["facing","fast","hurry","marathon","move","person","quick","race","racing","right","run","rush","speed"],"emoji":"🏃‍➡️","text":"","type":1,"order":2354,"group":1,"subgroup":27,"version":15.1,"skins":[{"label":"person running: light skin tone, facing right","hexcode":"1F3C3-1F3FB-200D-27A1-FE0F","emoji":"🏃🏻‍➡️","text":"","type":1,"order":2356,"group":1,"subgroup":27,"version":15.1,"tone":1},{"label":"person running: medium-light skin tone, facing right","hexcode":"1F3C3-1F3FC-200D-27A1-FE0F","emoji":"🏃🏼‍➡️","text":"","type":1,"order":2358,"group":1,"subgroup":27,"version":15.1,"tone":2},{"label":"person running: medium skin tone, facing right","hexcode":"1F3C3-1F3FD-200D-27A1-FE0F","emoji":"🏃🏽‍➡️","text":"","type":1,"order":2360,"group":1,"subgroup":27,"version":15.1,"tone":3},{"label":"person running: medium-dark skin tone, facing right","hexcode":"1F3C3-1F3FE-200D-27A1-FE0F","emoji":"🏃🏾‍➡️","text":"","type":1,"order":2362,"group":1,"subgroup":27,"version":15.1,"tone":4},{"label":"person running: dark skin tone, facing right","hexcode":"1F3C3-1F3FF-200D-27A1-FE0F","emoji":"🏃🏿‍➡️","text":"","type":1,"order":2364,"group":1,"subgroup":27,"version":15.1,"tone":5}]},{"label":"woman running: facing right","hexcode":"1F3C3-200D-2640-FE0F-200D-27A1-FE0F","tags":["facing","fast","hurry","marathon","move","quick","race","racing","right","run","rush","speed","woman"],"emoji":"🏃‍♀️‍➡️","text":"","type":1,"order":2366,"group":1,"subgroup":27,"version":15.1,"gender":0,"skins":[{"label":"woman running: light skin tone, facing right","hexcode":"1F3C3-1F3FB-200D-2640-FE0F-200D-27A1-FE0F","emoji":"🏃🏻‍♀️‍➡️","text":"","type":1,"order":2370,"group":1,"subgroup":27,"version":15.1,"gender":0,"tone":1},{"label":"woman running: medium-light skin tone, facing right","hexcode":"1F3C3-1F3FC-200D-2640-FE0F-200D-27A1-FE0F","emoji":"🏃🏼‍♀️‍➡️","text":"","type":1,"order":2374,"group":1,"subgroup":27,"version":15.1,"gender":0,"tone":2},{"label":"woman running: medium skin tone, facing right","hexcode":"1F3C3-1F3FD-200D-2640-FE0F-200D-27A1-FE0F","emoji":"🏃🏽‍♀️‍➡️","text":"","type":1,"order":2378,"group":1,"subgroup":27,"version":15.1,"gender":0,"tone":3},{"label":"woman running: medium-dark skin tone, facing right","hexcode":"1F3C3-1F3FE-200D-2640-FE0F-200D-27A1-FE0F","emoji":"🏃🏾‍♀️‍➡️","text":"","type":1,"order":2382,"group":1,"subgroup":27,"version":15.1,"gender":0,"tone":4},{"label":"woman running: dark skin tone, facing right","hexcode":"1F3C3-1F3FF-200D-2640-FE0F-200D-27A1-FE0F","emoji":"🏃🏿‍♀️‍➡️","text":"","type":1,"order":2386,"group":1,"subgroup":27,"version":15.1,"gender":0,"tone":5}]},{"label":"man running: facing right","hexcode":"1F3C3-200D-2642-FE0F-200D-27A1-FE0F","tags":["facing","fast","hurry","man","marathon","move","quick","race","racing","right","run","rush","speed"],"emoji":"🏃‍♂️‍➡️","text":"","type":1,"order":2390,"group":1,"subgroup":27,"version":15.1,"gender":1,"skins":[{"label":"man running: light skin tone, facing right","hexcode":"1F3C3-1F3FB-200D-2642-FE0F-200D-27A1-FE0F","emoji":"🏃🏻‍♂️‍➡️","text":"","type":1,"order":2394,"group":1,"subgroup":27,"version":15.1,"gender":1,"tone":1},{"label":"man running: medium-light skin tone, facing right","hexcode":"1F3C3-1F3FC-200D-2642-FE0F-200D-27A1-FE0F","emoji":"🏃🏼‍♂️‍➡️","text":"","type":1,"order":2398,"group":1,"subgroup":27,"version":15.1,"gender":1,"tone":2},{"label":"man running: medium skin tone, facing right","hexcode":"1F3C3-1F3FD-200D-2642-FE0F-200D-27A1-FE0F","emoji":"🏃🏽‍♂️‍➡️","text":"","type":1,"order":2402,"group":1,"subgroup":27,"version":15.1,"gender":1,"tone":3},{"label":"man running: medium-dark skin tone, facing right","hexcode":"1F3C3-1F3FE-200D-2642-FE0F-200D-27A1-FE0F","emoji":"🏃🏾‍♂️‍➡️","text":"","type":1,"order":2406,"group":1,"subgroup":27,"version":15.1,"gender":1,"tone":4},{"label":"man running: dark skin tone, facing right","hexcode":"1F3C3-1F3FF-200D-2642-FE0F-200D-27A1-FE0F","emoji":"🏃🏿‍♂️‍➡️","text":"","type":1,"order":2410,"group":1,"subgroup":27,"version":15.1,"gender":1,"tone":5}]},{"label":"ballet dancer","hexcode":"1F9D1-200D-1FA70","tags":["ballet","dancer"],"emoji":"🧑‍🩰","text":"","type":1,"order":2414,"group":1,"subgroup":27,"version":17,"skins":[{"label":"ballet dancer: light skin tone","hexcode":"1F9D1-1F3FB-200D-1FA70","emoji":"🧑🏻‍🩰","text":"","type":1,"order":2415,"group":1,"subgroup":27,"version":17,"tone":1},{"label":"ballet dancer: medium-light skin tone","hexcode":"1F9D1-1F3FC-200D-1FA70","emoji":"🧑🏼‍🩰","text":"","type":1,"order":2416,"group":1,"subgroup":27,"version":17,"tone":2},{"label":"ballet dancer: medium skin tone","hexcode":"1F9D1-1F3FD-200D-1FA70","emoji":"🧑🏽‍🩰","text":"","type":1,"order":2417,"group":1,"subgroup":27,"version":17,"tone":3},{"label":"ballet dancer: medium-dark skin tone","hexcode":"1F9D1-1F3FE-200D-1FA70","emoji":"🧑🏾‍🩰","text":"","type":1,"order":2418,"group":1,"subgroup":27,"version":17,"tone":4},{"label":"ballet dancer: dark skin tone","hexcode":"1F9D1-1F3FF-200D-1FA70","emoji":"🧑🏿‍🩰","text":"","type":1,"order":2419,"group":1,"subgroup":27,"version":17,"tone":5}]},{"label":"woman dancing","hexcode":"1F483","tags":["dance","dancer","dancing","elegant","festive","flair","flamenco","groove","let’s","salsa","tango","woman"],"emoji":"💃","text":"","type":1,"order":2420,"group":1,"subgroup":27,"version":0.6,"skins":[{"label":"woman dancing: light skin tone","hexcode":"1F483-1F3FB","emoji":"💃🏻","text":"","type":1,"order":2421,"group":1,"subgroup":27,"version":1,"tone":1},{"label":"woman dancing: medium-light skin tone","hexcode":"1F483-1F3FC","emoji":"💃🏼","text":"","type":1,"order":2422,"group":1,"subgroup":27,"version":1,"tone":2},{"label":"woman dancing: medium skin tone","hexcode":"1F483-1F3FD","emoji":"💃🏽","text":"","type":1,"order":2423,"group":1,"subgroup":27,"version":1,"tone":3},{"label":"woman dancing: medium-dark skin tone","hexcode":"1F483-1F3FE","emoji":"💃🏾","text":"","type":1,"order":2424,"group":1,"subgroup":27,"version":1,"tone":4},{"label":"woman dancing: dark skin tone","hexcode":"1F483-1F3FF","emoji":"💃🏿","text":"","type":1,"order":2425,"group":1,"subgroup":27,"version":1,"tone":5}]},{"label":"man dancing","hexcode":"1F57A","tags":["dance","dancer","dancing","elegant","festive","flair","flamenco","groove","let’s","man","salsa","tango"],"emoji":"🕺","text":"","type":1,"order":2426,"group":1,"subgroup":27,"version":3,"skins":[{"label":"man dancing: light skin tone","hexcode":"1F57A-1F3FB","emoji":"🕺🏻","text":"","type":1,"order":2427,"group":1,"subgroup":27,"version":3,"tone":1},{"label":"man dancing: medium-light skin tone","hexcode":"1F57A-1F3FC","emoji":"🕺🏼","text":"","type":1,"order":2428,"group":1,"subgroup":27,"version":3,"tone":2},{"label":"man dancing: medium skin tone","hexcode":"1F57A-1F3FD","emoji":"🕺🏽","text":"","type":1,"order":2429,"group":1,"subgroup":27,"version":3,"tone":3},{"label":"man dancing: medium-dark skin tone","hexcode":"1F57A-1F3FE","emoji":"🕺🏾","text":"","type":1,"order":2430,"group":1,"subgroup":27,"version":3,"tone":4},{"label":"man dancing: dark skin tone","hexcode":"1F57A-1F3FF","emoji":"🕺🏿","text":"","type":1,"order":2431,"group":1,"subgroup":27,"version":3,"tone":5}]},{"label":"person in suit levitating","hexcode":"1F574","tags":["business","levitating","person","suit"],"emoji":"🕴️","text":"🕴︎","type":0,"order":2433,"group":1,"subgroup":27,"version":0.7,"skins":[{"label":"person in suit levitating: light skin tone","hexcode":"1F574-1F3FB","emoji":"🕴🏻","text":"","type":1,"order":2434,"group":1,"subgroup":27,"version":4,"tone":1},{"label":"person in suit levitating: medium-light skin tone","hexcode":"1F574-1F3FC","emoji":"🕴🏼","text":"","type":1,"order":2435,"group":1,"subgroup":27,"version":4,"tone":2},{"label":"person in suit levitating: medium skin tone","hexcode":"1F574-1F3FD","emoji":"🕴🏽","text":"","type":1,"order":2436,"group":1,"subgroup":27,"version":4,"tone":3},{"label":"person in suit levitating: medium-dark skin tone","hexcode":"1F574-1F3FE","emoji":"🕴🏾","text":"","type":1,"order":2437,"group":1,"subgroup":27,"version":4,"tone":4},{"label":"person in suit levitating: dark skin tone","hexcode":"1F574-1F3FF","emoji":"🕴🏿","text":"","type":1,"order":2438,"group":1,"subgroup":27,"version":4,"tone":5}]},{"label":"people with bunny ears","hexcode":"1F46F","tags":["bestie","bff","bunny","counterpart","dancer","double","ear","identical","pair","party","partying","people","soulmate","twin","twinsies"],"emoji":"👯","text":"","type":1,"order":2439,"group":1,"subgroup":27,"version":0.6,"skins":[{"label":"people with bunny ears: light skin tone","hexcode":"1F46F-1F3FB","emoji":"👯🏻","text":"","type":1,"order":2440,"group":1,"subgroup":27,"version":17,"tone":1},{"label":"people with bunny ears: medium-light skin tone","hexcode":"1F46F-1F3FC","emoji":"👯🏼","text":"","type":1,"order":2441,"group":1,"subgroup":27,"version":17,"tone":2},{"label":"people with bunny ears: medium skin tone","hexcode":"1F46F-1F3FD","emoji":"👯🏽","text":"","type":1,"order":2442,"group":1,"subgroup":27,"version":17,"tone":3},{"label":"people with bunny ears: medium-dark skin tone","hexcode":"1F46F-1F3FE","emoji":"👯🏾","text":"","type":1,"order":2443,"group":1,"subgroup":27,"version":17,"tone":4},{"label":"people with bunny ears: dark skin tone","hexcode":"1F46F-1F3FF","emoji":"👯🏿","text":"","type":1,"order":2444,"group":1,"subgroup":27,"version":17,"tone":5},{"label":"people with bunny ears: light skin tone, medium-light skin tone","hexcode":"1F9D1-1F3FB-200D-1F430-200D-1F9D1-1F3FC","emoji":"🧑🏻‍🐰‍🧑🏼","text":"","type":1,"order":2469,"group":1,"subgroup":27,"version":17,"tone":[1,2]},{"label":"people with bunny ears: light skin tone, medium skin tone","hexcode":"1F9D1-1F3FB-200D-1F430-200D-1F9D1-1F3FD","emoji":"🧑🏻‍🐰‍🧑🏽","text":"","type":1,"order":2470,"group":1,"subgroup":27,"version":17,"tone":[1,3]},{"label":"people with bunny ears: light skin tone, medium-dark skin tone","hexcode":"1F9D1-1F3FB-200D-1F430-200D-1F9D1-1F3FE","emoji":"🧑🏻‍🐰‍🧑🏾","text":"","type":1,"order":2471,"group":1,"subgroup":27,"version":17,"tone":[1,4]},{"label":"people with bunny ears: light skin tone, dark skin tone","hexcode":"1F9D1-1F3FB-200D-1F430-200D-1F9D1-1F3FF","emoji":"🧑🏻‍🐰‍🧑🏿","text":"","type":1,"order":2472,"group":1,"subgroup":27,"version":17,"tone":[1,5]},{"label":"people with bunny ears: medium-light skin tone, light skin tone","hexcode":"1F9D1-1F3FC-200D-1F430-200D-1F9D1-1F3FB","emoji":"🧑🏼‍🐰‍🧑🏻","text":"","type":1,"order":2473,"group":1,"subgroup":27,"version":17,"tone":[2,1]},{"label":"people with bunny ears: medium-light skin tone, medium skin tone","hexcode":"1F9D1-1F3FC-200D-1F430-200D-1F9D1-1F3FD","emoji":"🧑🏼‍🐰‍🧑🏽","text":"","type":1,"order":2474,"group":1,"subgroup":27,"version":17,"tone":[2,3]},{"label":"people with bunny ears: medium-light skin tone, medium-dark skin tone","hexcode":"1F9D1-1F3FC-200D-1F430-200D-1F9D1-1F3FE","emoji":"🧑🏼‍🐰‍🧑🏾","text":"","type":1,"order":2475,"group":1,"subgroup":27,"version":17,"tone":[2,4]},{"label":"people with bunny ears: medium-light skin tone, dark skin tone","hexcode":"1F9D1-1F3FC-200D-1F430-200D-1F9D1-1F3FF","emoji":"🧑🏼‍🐰‍🧑🏿","text":"","type":1,"order":2476,"group":1,"subgroup":27,"version":17,"tone":[2,5]},{"label":"people with bunny ears: medium skin tone, light skin tone","hexcode":"1F9D1-1F3FD-200D-1F430-200D-1F9D1-1F3FB","emoji":"🧑🏽‍🐰‍🧑🏻","text":"","type":1,"order":2477,"group":1,"subgroup":27,"version":17,"tone":[3,1]},{"label":"people with bunny ears: medium skin tone, medium-light skin tone","hexcode":"1F9D1-1F3FD-200D-1F430-200D-1F9D1-1F3FC","emoji":"🧑🏽‍🐰‍🧑🏼","text":"","type":1,"order":2478,"group":1,"subgroup":27,"version":17,"tone":[3,2]},{"label":"people with bunny ears: medium skin tone, medium-dark skin tone","hexcode":"1F9D1-1F3FD-200D-1F430-200D-1F9D1-1F3FE","emoji":"🧑🏽‍🐰‍🧑🏾","text":"","type":1,"order":2479,"group":1,"subgroup":27,"version":17,"tone":[3,4]},{"label":"people with bunny ears: medium skin tone, dark skin tone","hexcode":"1F9D1-1F3FD-200D-1F430-200D-1F9D1-1F3FF","emoji":"🧑🏽‍🐰‍🧑🏿","text":"","type":1,"order":2480,"group":1,"subgroup":27,"version":17,"tone":[3,5]},{"label":"people with bunny ears: medium-dark skin tone, light skin tone","hexcode":"1F9D1-1F3FE-200D-1F430-200D-1F9D1-1F3FB","emoji":"🧑🏾‍🐰‍🧑🏻","text":"","type":1,"order":2481,"group":1,"subgroup":27,"version":17,"tone":[4,1]},{"label":"people with bunny ears: medium-dark skin tone, medium-light skin tone","hexcode":"1F9D1-1F3FE-200D-1F430-200D-1F9D1-1F3FC","emoji":"🧑🏾‍🐰‍🧑🏼","text":"","type":1,"order":2482,"group":1,"subgroup":27,"version":17,"tone":[4,2]},{"label":"people with bunny ears: medium-dark skin tone, medium skin tone","hexcode":"1F9D1-1F3FE-200D-1F430-200D-1F9D1-1F3FD","emoji":"🧑🏾‍🐰‍🧑🏽","text":"","type":1,"order":2483,"group":1,"subgroup":27,"version":17,"tone":[4,3]},{"label":"people with bunny ears: medium-dark skin tone, dark skin tone","hexcode":"1F9D1-1F3FE-200D-1F430-200D-1F9D1-1F3FF","emoji":"🧑🏾‍🐰‍🧑🏿","text":"","type":1,"order":2484,"group":1,"subgroup":27,"version":17,"tone":[4,5]},{"label":"people with bunny ears: dark skin tone, light skin tone","hexcode":"1F9D1-1F3FF-200D-1F430-200D-1F9D1-1F3FB","emoji":"🧑🏿‍🐰‍🧑🏻","text":"","type":1,"order":2485,"group":1,"subgroup":27,"version":17,"tone":[5,1]},{"label":"people with bunny ears: dark skin tone, medium-light skin tone","hexcode":"1F9D1-1F3FF-200D-1F430-200D-1F9D1-1F3FC","emoji":"🧑🏿‍🐰‍🧑🏼","text":"","type":1,"order":2486,"group":1,"subgroup":27,"version":17,"tone":[5,2]},{"label":"people with bunny ears: dark skin tone, medium skin tone","hexcode":"1F9D1-1F3FF-200D-1F430-200D-1F9D1-1F3FD","emoji":"🧑🏿‍🐰‍🧑🏽","text":"","type":1,"order":2487,"group":1,"subgroup":27,"version":17,"tone":[5,3]},{"label":"people with bunny ears: dark skin tone, medium-dark skin tone","hexcode":"1F9D1-1F3FF-200D-1F430-200D-1F9D1-1F3FE","emoji":"🧑🏿‍🐰‍🧑🏾","text":"","type":1,"order":2488,"group":1,"subgroup":27,"version":17,"tone":[5,4]}]},{"label":"men with bunny ears","hexcode":"1F46F-200D-2642-FE0F","tags":["bestie","bff","bunny","counterpart","dancer","double","ear","identical","men","pair","party","partying","people","soulmate","twin","twinsies"],"emoji":"👯‍♂️","text":"","type":1,"order":2445,"group":1,"subgroup":27,"version":4,"gender":1,"skins":[{"label":"men with bunny ears: light skin tone, medium-light skin tone","hexcode":"1F468-1F3FB-200D-1F430-200D-1F468-1F3FC","emoji":"👨🏻‍🐰‍👨🏼","text":"","type":1,"order":2489,"group":1,"subgroup":27,"version":17,"tone":[1,2]},{"label":"men with bunny ears: light skin tone, medium skin tone","hexcode":"1F468-1F3FB-200D-1F430-200D-1F468-1F3FD","emoji":"👨🏻‍🐰‍👨🏽","text":"","type":1,"order":2490,"group":1,"subgroup":27,"version":17,"tone":[1,3]},{"label":"men with bunny ears: light skin tone, medium-dark skin tone","hexcode":"1F468-1F3FB-200D-1F430-200D-1F468-1F3FE","emoji":"👨🏻‍🐰‍👨🏾","text":"","type":1,"order":2491,"group":1,"subgroup":27,"version":17,"tone":[1,4]},{"label":"men with bunny ears: light skin tone, dark skin tone","hexcode":"1F468-1F3FB-200D-1F430-200D-1F468-1F3FF","emoji":"👨🏻‍🐰‍👨🏿","text":"","type":1,"order":2492,"group":1,"subgroup":27,"version":17,"tone":[1,5]},{"label":"men with bunny ears: medium-light skin tone, light skin tone","hexcode":"1F468-1F3FC-200D-1F430-200D-1F468-1F3FB","emoji":"👨🏼‍🐰‍👨🏻","text":"","type":1,"order":2493,"group":1,"subgroup":27,"version":17,"tone":[2,1]},{"label":"men with bunny ears: medium-light skin tone, medium skin tone","hexcode":"1F468-1F3FC-200D-1F430-200D-1F468-1F3FD","emoji":"👨🏼‍🐰‍👨🏽","text":"","type":1,"order":2494,"group":1,"subgroup":27,"version":17,"tone":[2,3]},{"label":"men with bunny ears: medium-light skin tone, medium-dark skin tone","hexcode":"1F468-1F3FC-200D-1F430-200D-1F468-1F3FE","emoji":"👨🏼‍🐰‍👨🏾","text":"","type":1,"order":2495,"group":1,"subgroup":27,"version":17,"tone":[2,4]},{"label":"men with bunny ears: medium-light skin tone, dark skin tone","hexcode":"1F468-1F3FC-200D-1F430-200D-1F468-1F3FF","emoji":"👨🏼‍🐰‍👨🏿","text":"","type":1,"order":2496,"group":1,"subgroup":27,"version":17,"tone":[2,5]},{"label":"men with bunny ears: medium skin tone, light skin tone","hexcode":"1F468-1F3FD-200D-1F430-200D-1F468-1F3FB","emoji":"👨🏽‍🐰‍👨🏻","text":"","type":1,"order":2497,"group":1,"subgroup":27,"version":17,"tone":[3,1]},{"label":"men with bunny ears: medium skin tone, medium-light skin tone","hexcode":"1F468-1F3FD-200D-1F430-200D-1F468-1F3FC","emoji":"👨🏽‍🐰‍👨🏼","text":"","type":1,"order":2498,"group":1,"subgroup":27,"version":17,"tone":[3,2]},{"label":"men with bunny ears: medium skin tone, medium-dark skin tone","hexcode":"1F468-1F3FD-200D-1F430-200D-1F468-1F3FE","emoji":"👨🏽‍🐰‍👨🏾","text":"","type":1,"order":2499,"group":1,"subgroup":27,"version":17,"tone":[3,4]},{"label":"men with bunny ears: medium skin tone, dark skin tone","hexcode":"1F468-1F3FD-200D-1F430-200D-1F468-1F3FF","emoji":"👨🏽‍🐰‍👨🏿","text":"","type":1,"order":2500,"group":1,"subgroup":27,"version":17,"tone":[3,5]},{"label":"men with bunny ears: medium-dark skin tone, light skin tone","hexcode":"1F468-1F3FE-200D-1F430-200D-1F468-1F3FB","emoji":"👨🏾‍🐰‍👨🏻","text":"","type":1,"order":2501,"group":1,"subgroup":27,"version":17,"tone":[4,1]},{"label":"men with bunny ears: medium-dark skin tone, medium-light skin tone","hexcode":"1F468-1F3FE-200D-1F430-200D-1F468-1F3FC","emoji":"👨🏾‍🐰‍👨🏼","text":"","type":1,"order":2502,"group":1,"subgroup":27,"version":17,"tone":[4,2]},{"label":"men with bunny ears: medium-dark skin tone, medium skin tone","hexcode":"1F468-1F3FE-200D-1F430-200D-1F468-1F3FD","emoji":"👨🏾‍🐰‍👨🏽","text":"","type":1,"order":2503,"group":1,"subgroup":27,"version":17,"tone":[4,3]},{"label":"men with bunny ears: medium-dark skin tone, dark skin tone","hexcode":"1F468-1F3FE-200D-1F430-200D-1F468-1F3FF","emoji":"👨🏾‍🐰‍👨🏿","text":"","type":1,"order":2504,"group":1,"subgroup":27,"version":17,"tone":[4,5]},{"label":"men with bunny ears: dark skin tone, light skin tone","hexcode":"1F468-1F3FF-200D-1F430-200D-1F468-1F3FB","emoji":"👨🏿‍🐰‍👨🏻","text":"","type":1,"order":2505,"group":1,"subgroup":27,"version":17,"tone":[5,1]},{"label":"men with bunny ears: dark skin tone, medium-light skin tone","hexcode":"1F468-1F3FF-200D-1F430-200D-1F468-1F3FC","emoji":"👨🏿‍🐰‍👨🏼","text":"","type":1,"order":2506,"group":1,"subgroup":27,"version":17,"tone":[5,2]},{"label":"men with bunny ears: dark skin tone, medium skin tone","hexcode":"1F468-1F3FF-200D-1F430-200D-1F468-1F3FD","emoji":"👨🏿‍🐰‍👨🏽","text":"","type":1,"order":2507,"group":1,"subgroup":27,"version":17,"tone":[5,3]},{"label":"men with bunny ears: dark skin tone, medium-dark skin tone","hexcode":"1F468-1F3FF-200D-1F430-200D-1F468-1F3FE","emoji":"👨🏿‍🐰‍👨🏾","text":"","type":1,"order":2508,"group":1,"subgroup":27,"version":17,"tone":[5,4]},{"label":"men with bunny ears: light skin tone","hexcode":"1F46F-1F3FB-200D-2642-FE0F","emoji":"👯🏻‍♂️","text":"","type":1,"order":2447,"group":1,"subgroup":27,"version":17,"gender":1,"tone":1},{"label":"men with bunny ears: medium-light skin tone","hexcode":"1F46F-1F3FC-200D-2642-FE0F","emoji":"👯🏼‍♂️","text":"","type":1,"order":2449,"group":1,"subgroup":27,"version":17,"gender":1,"tone":2},{"label":"men with bunny ears: medium skin tone","hexcode":"1F46F-1F3FD-200D-2642-FE0F","emoji":"👯🏽‍♂️","text":"","type":1,"order":2451,"group":1,"subgroup":27,"version":17,"gender":1,"tone":3},{"label":"men with bunny ears: medium-dark skin tone","hexcode":"1F46F-1F3FE-200D-2642-FE0F","emoji":"👯🏾‍♂️","text":"","type":1,"order":2453,"group":1,"subgroup":27,"version":17,"gender":1,"tone":4},{"label":"men with bunny ears: dark skin tone","hexcode":"1F46F-1F3FF-200D-2642-FE0F","emoji":"👯🏿‍♂️","text":"","type":1,"order":2455,"group":1,"subgroup":27,"version":17,"gender":1,"tone":5}]},{"label":"women with bunny ears","hexcode":"1F46F-200D-2640-FE0F","tags":["bestie","bff","bunny","counterpart","dancer","double","ear","identical","pair","party","partying","people","soulmate","twin","twinsies","women"],"emoji":"👯‍♀️","text":"","type":1,"order":2457,"group":1,"subgroup":27,"version":4,"gender":0,"skins":[{"label":"women with bunny ears: light skin tone, medium-light skin tone","hexcode":"1F469-1F3FB-200D-1F430-200D-1F469-1F3FC","emoji":"👩🏻‍🐰‍👩🏼","text":"","type":1,"order":2509,"group":1,"subgroup":27,"version":17,"tone":[1,2]},{"label":"women with bunny ears: light skin tone, medium skin tone","hexcode":"1F469-1F3FB-200D-1F430-200D-1F469-1F3FD","emoji":"👩🏻‍🐰‍👩🏽","text":"","type":1,"order":2510,"group":1,"subgroup":27,"version":17,"tone":[1,3]},{"label":"women with bunny ears: light skin tone, medium-dark skin tone","hexcode":"1F469-1F3FB-200D-1F430-200D-1F469-1F3FE","emoji":"👩🏻‍🐰‍👩🏾","text":"","type":1,"order":2511,"group":1,"subgroup":27,"version":17,"tone":[1,4]},{"label":"women with bunny ears: light skin tone, dark skin tone","hexcode":"1F469-1F3FB-200D-1F430-200D-1F469-1F3FF","emoji":"👩🏻‍🐰‍👩🏿","text":"","type":1,"order":2512,"group":1,"subgroup":27,"version":17,"tone":[1,5]},{"label":"women with bunny ears: medium-light skin tone, light skin tone","hexcode":"1F469-1F3FC-200D-1F430-200D-1F469-1F3FB","emoji":"👩🏼‍🐰‍👩🏻","text":"","type":1,"order":2513,"group":1,"subgroup":27,"version":17,"tone":[2,1]},{"label":"women with bunny ears: medium-light skin tone, medium skin tone","hexcode":"1F469-1F3FC-200D-1F430-200D-1F469-1F3FD","emoji":"👩🏼‍🐰‍👩🏽","text":"","type":1,"order":2514,"group":1,"subgroup":27,"version":17,"tone":[2,3]},{"label":"women with bunny ears: medium-light skin tone, medium-dark skin tone","hexcode":"1F469-1F3FC-200D-1F430-200D-1F469-1F3FE","emoji":"👩🏼‍🐰‍👩🏾","text":"","type":1,"order":2515,"group":1,"subgroup":27,"version":17,"tone":[2,4]},{"label":"women with bunny ears: medium-light skin tone, dark skin tone","hexcode":"1F469-1F3FC-200D-1F430-200D-1F469-1F3FF","emoji":"👩🏼‍🐰‍👩🏿","text":"","type":1,"order":2516,"group":1,"subgroup":27,"version":17,"tone":[2,5]},{"label":"women with bunny ears: medium skin tone, light skin tone","hexcode":"1F469-1F3FD-200D-1F430-200D-1F469-1F3FB","emoji":"👩🏽‍🐰‍👩🏻","text":"","type":1,"order":2517,"group":1,"subgroup":27,"version":17,"tone":[3,1]},{"label":"women with bunny ears: medium skin tone, medium-light skin tone","hexcode":"1F469-1F3FD-200D-1F430-200D-1F469-1F3FC","emoji":"👩🏽‍🐰‍👩🏼","text":"","type":1,"order":2518,"group":1,"subgroup":27,"version":17,"tone":[3,2]},{"label":"women with bunny ears: medium skin tone, medium-dark skin tone","hexcode":"1F469-1F3FD-200D-1F430-200D-1F469-1F3FE","emoji":"👩🏽‍🐰‍👩🏾","text":"","type":1,"order":2519,"group":1,"subgroup":27,"version":17,"tone":[3,4]},{"label":"women with bunny ears: medium skin tone, dark skin tone","hexcode":"1F469-1F3FD-200D-1F430-200D-1F469-1F3FF","emoji":"👩🏽‍🐰‍👩🏿","text":"","type":1,"order":2520,"group":1,"subgroup":27,"version":17,"tone":[3,5]},{"label":"women with bunny ears: medium-dark skin tone, light skin tone","hexcode":"1F469-1F3FE-200D-1F430-200D-1F469-1F3FB","emoji":"👩🏾‍🐰‍👩🏻","text":"","type":1,"order":2521,"group":1,"subgroup":27,"version":17,"tone":[4,1]},{"label":"women with bunny ears: medium-dark skin tone, medium-light skin tone","hexcode":"1F469-1F3FE-200D-1F430-200D-1F469-1F3FC","emoji":"👩🏾‍🐰‍👩🏼","text":"","type":1,"order":2522,"group":1,"subgroup":27,"version":17,"tone":[4,2]},{"label":"women with bunny ears: medium-dark skin tone, medium skin tone","hexcode":"1F469-1F3FE-200D-1F430-200D-1F469-1F3FD","emoji":"👩🏾‍🐰‍👩🏽","text":"","type":1,"order":2523,"group":1,"subgroup":27,"version":17,"tone":[4,3]},{"label":"women with bunny ears: medium-dark skin tone, dark skin tone","hexcode":"1F469-1F3FE-200D-1F430-200D-1F469-1F3FF","emoji":"👩🏾‍🐰‍👩🏿","text":"","type":1,"order":2524,"group":1,"subgroup":27,"version":17,"tone":[4,5]},{"label":"women with bunny ears: dark skin tone, light skin tone","hexcode":"1F469-1F3FF-200D-1F430-200D-1F469-1F3FB","emoji":"👩🏿‍🐰‍👩🏻","text":"","type":1,"order":2525,"group":1,"subgroup":27,"version":17,"tone":[5,1]},{"label":"women with bunny ears: dark skin tone, medium-light skin tone","hexcode":"1F469-1F3FF-200D-1F430-200D-1F469-1F3FC","emoji":"👩🏿‍🐰‍👩🏼","text":"","type":1,"order":2526,"group":1,"subgroup":27,"version":17,"tone":[5,2]},{"label":"women with bunny ears: dark skin tone, medium skin tone","hexcode":"1F469-1F3FF-200D-1F430-200D-1F469-1F3FD","emoji":"👩🏿‍🐰‍👩🏽","text":"","type":1,"order":2527,"group":1,"subgroup":27,"version":17,"tone":[5,3]},{"label":"women with bunny ears: dark skin tone, medium-dark skin tone","hexcode":"1F469-1F3FF-200D-1F430-200D-1F469-1F3FE","emoji":"👩🏿‍🐰‍👩🏾","text":"","type":1,"order":2528,"group":1,"subgroup":27,"version":17,"tone":[5,4]},{"label":"women with bunny ears: light skin tone","hexcode":"1F46F-1F3FB-200D-2640-FE0F","emoji":"👯🏻‍♀️","text":"","type":1,"order":2459,"group":1,"subgroup":27,"version":17,"gender":0,"tone":1},{"label":"women with bunny ears: medium-light skin tone","hexcode":"1F46F-1F3FC-200D-2640-FE0F","emoji":"👯🏼‍♀️","text":"","type":1,"order":2461,"group":1,"subgroup":27,"version":17,"gender":0,"tone":2},{"label":"women with bunny ears: medium skin tone","hexcode":"1F46F-1F3FD-200D-2640-FE0F","emoji":"👯🏽‍♀️","text":"","type":1,"order":2463,"group":1,"subgroup":27,"version":17,"gender":0,"tone":3},{"label":"women with bunny ears: medium-dark skin tone","hexcode":"1F46F-1F3FE-200D-2640-FE0F","emoji":"👯🏾‍♀️","text":"","type":1,"order":2465,"group":1,"subgroup":27,"version":17,"gender":0,"tone":4},{"label":"women with bunny ears: dark skin tone","hexcode":"1F46F-1F3FF-200D-2640-FE0F","emoji":"👯🏿‍♀️","text":"","type":1,"order":2467,"group":1,"subgroup":27,"version":17,"gender":0,"tone":5}]},{"label":"person in steamy room","hexcode":"1F9D6","tags":["day","luxurious","pamper","person","relax","room","sauna","spa","steam","steambath","unwind"],"emoji":"🧖","text":"","type":1,"order":2529,"group":1,"subgroup":27,"version":5,"skins":[{"label":"person in steamy room: light skin tone","hexcode":"1F9D6-1F3FB","emoji":"🧖🏻","text":"","type":1,"order":2530,"group":1,"subgroup":27,"version":5,"tone":1},{"label":"person in steamy room: medium-light skin tone","hexcode":"1F9D6-1F3FC","emoji":"🧖🏼","text":"","type":1,"order":2531,"group":1,"subgroup":27,"version":5,"tone":2},{"label":"person in steamy room: medium skin tone","hexcode":"1F9D6-1F3FD","emoji":"🧖🏽","text":"","type":1,"order":2532,"group":1,"subgroup":27,"version":5,"tone":3},{"label":"person in steamy room: medium-dark skin tone","hexcode":"1F9D6-1F3FE","emoji":"🧖🏾","text":"","type":1,"order":2533,"group":1,"subgroup":27,"version":5,"tone":4},{"label":"person in steamy room: dark skin tone","hexcode":"1F9D6-1F3FF","emoji":"🧖🏿","text":"","type":1,"order":2534,"group":1,"subgroup":27,"version":5,"tone":5}]},{"label":"man in steamy room","hexcode":"1F9D6-200D-2642-FE0F","tags":["day","luxurious","man","pamper","relax","room","sauna","spa","steam","steambath","unwind"],"emoji":"🧖‍♂️","text":"","type":1,"order":2535,"group":1,"subgroup":27,"version":5,"gender":1,"skins":[{"label":"man in steamy room: light skin tone","hexcode":"1F9D6-1F3FB-200D-2642-FE0F","emoji":"🧖🏻‍♂️","text":"","type":1,"order":2537,"group":1,"subgroup":27,"version":5,"gender":1,"tone":1},{"label":"man in steamy room: medium-light skin tone","hexcode":"1F9D6-1F3FC-200D-2642-FE0F","emoji":"🧖🏼‍♂️","text":"","type":1,"order":2539,"group":1,"subgroup":27,"version":5,"gender":1,"tone":2},{"label":"man in steamy room: medium skin tone","hexcode":"1F9D6-1F3FD-200D-2642-FE0F","emoji":"🧖🏽‍♂️","text":"","type":1,"order":2541,"group":1,"subgroup":27,"version":5,"gender":1,"tone":3},{"label":"man in steamy room: medium-dark skin tone","hexcode":"1F9D6-1F3FE-200D-2642-FE0F","emoji":"🧖🏾‍♂️","text":"","type":1,"order":2543,"group":1,"subgroup":27,"version":5,"gender":1,"tone":4},{"label":"man in steamy room: dark skin tone","hexcode":"1F9D6-1F3FF-200D-2642-FE0F","emoji":"🧖🏿‍♂️","text":"","type":1,"order":2545,"group":1,"subgroup":27,"version":5,"gender":1,"tone":5}]},{"label":"woman in steamy room","hexcode":"1F9D6-200D-2640-FE0F","tags":["day","luxurious","pamper","relax","room","sauna","spa","steam","steambath","unwind","woman"],"emoji":"🧖‍♀️","text":"","type":1,"order":2547,"group":1,"subgroup":27,"version":5,"gender":0,"skins":[{"label":"woman in steamy room: light skin tone","hexcode":"1F9D6-1F3FB-200D-2640-FE0F","emoji":"🧖🏻‍♀️","text":"","type":1,"order":2549,"group":1,"subgroup":27,"version":5,"gender":0,"tone":1},{"label":"woman in steamy room: medium-light skin tone","hexcode":"1F9D6-1F3FC-200D-2640-FE0F","emoji":"🧖🏼‍♀️","text":"","type":1,"order":2551,"group":1,"subgroup":27,"version":5,"gender":0,"tone":2},{"label":"woman in steamy room: medium skin tone","hexcode":"1F9D6-1F3FD-200D-2640-FE0F","emoji":"🧖🏽‍♀️","text":"","type":1,"order":2553,"group":1,"subgroup":27,"version":5,"gender":0,"tone":3},{"label":"woman in steamy room: medium-dark skin tone","hexcode":"1F9D6-1F3FE-200D-2640-FE0F","emoji":"🧖🏾‍♀️","text":"","type":1,"order":2555,"group":1,"subgroup":27,"version":5,"gender":0,"tone":4},{"label":"woman in steamy room: dark skin tone","hexcode":"1F9D6-1F3FF-200D-2640-FE0F","emoji":"🧖🏿‍♀️","text":"","type":1,"order":2557,"group":1,"subgroup":27,"version":5,"gender":0,"tone":5}]},{"label":"person climbing","hexcode":"1F9D7","tags":["climb","climber","climbing","mountain","person","rock","scale","up"],"emoji":"🧗","text":"","type":1,"order":2559,"group":1,"subgroup":27,"version":5,"skins":[{"label":"person climbing: light skin tone","hexcode":"1F9D7-1F3FB","emoji":"🧗🏻","text":"","type":1,"order":2560,"group":1,"subgroup":27,"version":5,"tone":1},{"label":"person climbing: medium-light skin tone","hexcode":"1F9D7-1F3FC","emoji":"🧗🏼","text":"","type":1,"order":2561,"group":1,"subgroup":27,"version":5,"tone":2},{"label":"person climbing: medium skin tone","hexcode":"1F9D7-1F3FD","emoji":"🧗🏽","text":"","type":1,"order":2562,"group":1,"subgroup":27,"version":5,"tone":3},{"label":"person climbing: medium-dark skin tone","hexcode":"1F9D7-1F3FE","emoji":"🧗🏾","text":"","type":1,"order":2563,"group":1,"subgroup":27,"version":5,"tone":4},{"label":"person climbing: dark skin tone","hexcode":"1F9D7-1F3FF","emoji":"🧗🏿","text":"","type":1,"order":2564,"group":1,"subgroup":27,"version":5,"tone":5}]},{"label":"man climbing","hexcode":"1F9D7-200D-2642-FE0F","tags":["climb","climber","climbing","man","mountain","rock","scale","up"],"emoji":"🧗‍♂️","text":"","type":1,"order":2565,"group":1,"subgroup":27,"version":5,"gender":1,"skins":[{"label":"man climbing: light skin tone","hexcode":"1F9D7-1F3FB-200D-2642-FE0F","emoji":"🧗🏻‍♂️","text":"","type":1,"order":2567,"group":1,"subgroup":27,"version":5,"gender":1,"tone":1},{"label":"man climbing: medium-light skin tone","hexcode":"1F9D7-1F3FC-200D-2642-FE0F","emoji":"🧗🏼‍♂️","text":"","type":1,"order":2569,"group":1,"subgroup":27,"version":5,"gender":1,"tone":2},{"label":"man climbing: medium skin tone","hexcode":"1F9D7-1F3FD-200D-2642-FE0F","emoji":"🧗🏽‍♂️","text":"","type":1,"order":2571,"group":1,"subgroup":27,"version":5,"gender":1,"tone":3},{"label":"man climbing: medium-dark skin tone","hexcode":"1F9D7-1F3FE-200D-2642-FE0F","emoji":"🧗🏾‍♂️","text":"","type":1,"order":2573,"group":1,"subgroup":27,"version":5,"gender":1,"tone":4},{"label":"man climbing: dark skin tone","hexcode":"1F9D7-1F3FF-200D-2642-FE0F","emoji":"🧗🏿‍♂️","text":"","type":1,"order":2575,"group":1,"subgroup":27,"version":5,"gender":1,"tone":5}]},{"label":"woman climbing","hexcode":"1F9D7-200D-2640-FE0F","tags":["climb","climber","climbing","mountain","rock","scale","up","woman"],"emoji":"🧗‍♀️","text":"","type":1,"order":2577,"group":1,"subgroup":27,"version":5,"gender":0,"skins":[{"label":"woman climbing: light skin tone","hexcode":"1F9D7-1F3FB-200D-2640-FE0F","emoji":"🧗🏻‍♀️","text":"","type":1,"order":2579,"group":1,"subgroup":27,"version":5,"gender":0,"tone":1},{"label":"woman climbing: medium-light skin tone","hexcode":"1F9D7-1F3FC-200D-2640-FE0F","emoji":"🧗🏼‍♀️","text":"","type":1,"order":2581,"group":1,"subgroup":27,"version":5,"gender":0,"tone":2},{"label":"woman climbing: medium skin tone","hexcode":"1F9D7-1F3FD-200D-2640-FE0F","emoji":"🧗🏽‍♀️","text":"","type":1,"order":2583,"group":1,"subgroup":27,"version":5,"gender":0,"tone":3},{"label":"woman climbing: medium-dark skin tone","hexcode":"1F9D7-1F3FE-200D-2640-FE0F","emoji":"🧗🏾‍♀️","text":"","type":1,"order":2585,"group":1,"subgroup":27,"version":5,"gender":0,"tone":4},{"label":"woman climbing: dark skin tone","hexcode":"1F9D7-1F3FF-200D-2640-FE0F","emoji":"🧗🏿‍♀️","text":"","type":1,"order":2587,"group":1,"subgroup":27,"version":5,"gender":0,"tone":5}]},{"label":"person fencing","hexcode":"1F93A","tags":["fencer","fencing","person","sword"],"emoji":"🤺","text":"","type":1,"order":2589,"group":1,"subgroup":28,"version":3},{"label":"horse racing","hexcode":"1F3C7","tags":["horse","jockey","racehorse","racing","riding","sport"],"emoji":"🏇","text":"","type":1,"order":2590,"group":1,"subgroup":28,"version":1,"skins":[{"label":"horse racing: light skin tone","hexcode":"1F3C7-1F3FB","emoji":"🏇🏻","text":"","type":1,"order":2591,"group":1,"subgroup":28,"version":1,"tone":1},{"label":"horse racing: medium-light skin tone","hexcode":"1F3C7-1F3FC","emoji":"🏇🏼","text":"","type":1,"order":2592,"group":1,"subgroup":28,"version":1,"tone":2},{"label":"horse racing: medium skin tone","hexcode":"1F3C7-1F3FD","emoji":"🏇🏽","text":"","type":1,"order":2593,"group":1,"subgroup":28,"version":1,"tone":3},{"label":"horse racing: medium-dark skin tone","hexcode":"1F3C7-1F3FE","emoji":"🏇🏾","text":"","type":1,"order":2594,"group":1,"subgroup":28,"version":1,"tone":4},{"label":"horse racing: dark skin tone","hexcode":"1F3C7-1F3FF","emoji":"🏇🏿","text":"","type":1,"order":2595,"group":1,"subgroup":28,"version":1,"tone":5}]},{"label":"skier","hexcode":"26F7","tags":["ski","snow"],"emoji":"⛷️","text":"⛷︎","type":0,"order":2597,"group":1,"subgroup":28,"version":0.7},{"label":"snowboarder","hexcode":"1F3C2","tags":["ski","snow","snowboard","sport"],"emoji":"🏂️","text":"🏂︎","type":1,"order":2598,"group":1,"subgroup":28,"version":0.6,"skins":[{"label":"snowboarder: light skin tone","hexcode":"1F3C2-1F3FB","emoji":"🏂🏻","text":"","type":1,"order":2599,"group":1,"subgroup":28,"version":1,"tone":1},{"label":"snowboarder: medium-light skin tone","hexcode":"1F3C2-1F3FC","emoji":"🏂🏼","text":"","type":1,"order":2600,"group":1,"subgroup":28,"version":1,"tone":2},{"label":"snowboarder: medium skin tone","hexcode":"1F3C2-1F3FD","emoji":"🏂🏽","text":"","type":1,"order":2601,"group":1,"subgroup":28,"version":1,"tone":3},{"label":"snowboarder: medium-dark skin tone","hexcode":"1F3C2-1F3FE","emoji":"🏂🏾","text":"","type":1,"order":2602,"group":1,"subgroup":28,"version":1,"tone":4},{"label":"snowboarder: dark skin tone","hexcode":"1F3C2-1F3FF","emoji":"🏂🏿","text":"","type":1,"order":2603,"group":1,"subgroup":28,"version":1,"tone":5}]},{"label":"person golfing","hexcode":"1F3CC","tags":["ball","birdie","caddy","driving","golf","golfing","green","person","pga","putt","range","tee"],"emoji":"🏌️","text":"🏌︎","type":0,"order":2605,"group":1,"subgroup":28,"version":0.7,"skins":[{"label":"person golfing: light skin tone","hexcode":"1F3CC-1F3FB","emoji":"🏌🏻","text":"","type":1,"order":2606,"group":1,"subgroup":28,"version":4,"tone":1},{"label":"person golfing: medium-light skin tone","hexcode":"1F3CC-1F3FC","emoji":"🏌🏼","text":"","type":1,"order":2607,"group":1,"subgroup":28,"version":4,"tone":2},{"label":"person golfing: medium skin tone","hexcode":"1F3CC-1F3FD","emoji":"🏌🏽","text":"","type":1,"order":2608,"group":1,"subgroup":28,"version":4,"tone":3},{"label":"person golfing: medium-dark skin tone","hexcode":"1F3CC-1F3FE","emoji":"🏌🏾","text":"","type":1,"order":2609,"group":1,"subgroup":28,"version":4,"tone":4},{"label":"person golfing: dark skin tone","hexcode":"1F3CC-1F3FF","emoji":"🏌🏿","text":"","type":1,"order":2610,"group":1,"subgroup":28,"version":4,"tone":5}]},{"label":"man golfing","hexcode":"1F3CC-FE0F-200D-2642-FE0F","tags":["ball","birdie","caddy","driving","golf","golfing","green","man","pga","putt","range","tee"],"emoji":"🏌️‍♂️","text":"","type":1,"order":2611,"group":1,"subgroup":28,"version":4,"gender":1,"skins":[{"label":"man golfing: light skin tone","hexcode":"1F3CC-1F3FB-200D-2642-FE0F","emoji":"🏌🏻‍♂️","text":"","type":1,"order":2615,"group":1,"subgroup":28,"version":4,"gender":1,"tone":1},{"label":"man golfing: medium-light skin tone","hexcode":"1F3CC-1F3FC-200D-2642-FE0F","emoji":"🏌🏼‍♂️","text":"","type":1,"order":2617,"group":1,"subgroup":28,"version":4,"gender":1,"tone":2},{"label":"man golfing: medium skin tone","hexcode":"1F3CC-1F3FD-200D-2642-FE0F","emoji":"🏌🏽‍♂️","text":"","type":1,"order":2619,"group":1,"subgroup":28,"version":4,"gender":1,"tone":3},{"label":"man golfing: medium-dark skin tone","hexcode":"1F3CC-1F3FE-200D-2642-FE0F","emoji":"🏌🏾‍♂️","text":"","type":1,"order":2621,"group":1,"subgroup":28,"version":4,"gender":1,"tone":4},{"label":"man golfing: dark skin tone","hexcode":"1F3CC-1F3FF-200D-2642-FE0F","emoji":"🏌🏿‍♂️","text":"","type":1,"order":2623,"group":1,"subgroup":28,"version":4,"gender":1,"tone":5}]},{"label":"woman golfing","hexcode":"1F3CC-FE0F-200D-2640-FE0F","tags":["ball","birdie","caddy","driving","golf","golfing","green","pga","putt","range","tee","woman"],"emoji":"🏌️‍♀️","text":"","type":1,"order":2625,"group":1,"subgroup":28,"version":4,"gender":0,"skins":[{"label":"woman golfing: light skin tone","hexcode":"1F3CC-1F3FB-200D-2640-FE0F","emoji":"🏌🏻‍♀️","text":"","type":1,"order":2629,"group":1,"subgroup":28,"version":4,"gender":0,"tone":1},{"label":"woman golfing: medium-light skin tone","hexcode":"1F3CC-1F3FC-200D-2640-FE0F","emoji":"🏌🏼‍♀️","text":"","type":1,"order":2631,"group":1,"subgroup":28,"version":4,"gender":0,"tone":2},{"label":"woman golfing: medium skin tone","hexcode":"1F3CC-1F3FD-200D-2640-FE0F","emoji":"🏌🏽‍♀️","text":"","type":1,"order":2633,"group":1,"subgroup":28,"version":4,"gender":0,"tone":3},{"label":"woman golfing: medium-dark skin tone","hexcode":"1F3CC-1F3FE-200D-2640-FE0F","emoji":"🏌🏾‍♀️","text":"","type":1,"order":2635,"group":1,"subgroup":28,"version":4,"gender":0,"tone":4},{"label":"woman golfing: dark skin tone","hexcode":"1F3CC-1F3FF-200D-2640-FE0F","emoji":"🏌🏿‍♀️","text":"","type":1,"order":2637,"group":1,"subgroup":28,"version":4,"gender":0,"tone":5}]},{"label":"person surfing","hexcode":"1F3C4","tags":["beach","ocean","person","sport","surf","surfer","surfing","swell","waves"],"emoji":"🏄️","text":"🏄︎","type":1,"order":2639,"group":1,"subgroup":28,"version":0.6,"skins":[{"label":"person surfing: light skin tone","hexcode":"1F3C4-1F3FB","emoji":"🏄🏻","text":"","type":1,"order":2640,"group":1,"subgroup":28,"version":1,"tone":1},{"label":"person surfing: medium-light skin tone","hexcode":"1F3C4-1F3FC","emoji":"🏄🏼","text":"","type":1,"order":2641,"group":1,"subgroup":28,"version":1,"tone":2},{"label":"person surfing: medium skin tone","hexcode":"1F3C4-1F3FD","emoji":"🏄🏽","text":"","type":1,"order":2642,"group":1,"subgroup":28,"version":1,"tone":3},{"label":"person surfing: medium-dark skin tone","hexcode":"1F3C4-1F3FE","emoji":"🏄🏾","text":"","type":1,"order":2643,"group":1,"subgroup":28,"version":1,"tone":4},{"label":"person surfing: dark skin tone","hexcode":"1F3C4-1F3FF","emoji":"🏄🏿","text":"","type":1,"order":2644,"group":1,"subgroup":28,"version":1,"tone":5}]},{"label":"man surfing","hexcode":"1F3C4-200D-2642-FE0F","tags":["beach","man","ocean","sport","surf","surfer","surfing","swell","waves"],"emoji":"🏄‍♂️","text":"","type":1,"order":2645,"group":1,"subgroup":28,"version":4,"gender":1,"skins":[{"label":"man surfing: light skin tone","hexcode":"1F3C4-1F3FB-200D-2642-FE0F","emoji":"🏄🏻‍♂️","text":"","type":1,"order":2647,"group":1,"subgroup":28,"version":4,"gender":1,"tone":1},{"label":"man surfing: medium-light skin tone","hexcode":"1F3C4-1F3FC-200D-2642-FE0F","emoji":"🏄🏼‍♂️","text":"","type":1,"order":2649,"group":1,"subgroup":28,"version":4,"gender":1,"tone":2},{"label":"man surfing: medium skin tone","hexcode":"1F3C4-1F3FD-200D-2642-FE0F","emoji":"🏄🏽‍♂️","text":"","type":1,"order":2651,"group":1,"subgroup":28,"version":4,"gender":1,"tone":3},{"label":"man surfing: medium-dark skin tone","hexcode":"1F3C4-1F3FE-200D-2642-FE0F","emoji":"🏄🏾‍♂️","text":"","type":1,"order":2653,"group":1,"subgroup":28,"version":4,"gender":1,"tone":4},{"label":"man surfing: dark skin tone","hexcode":"1F3C4-1F3FF-200D-2642-FE0F","emoji":"🏄🏿‍♂️","text":"","type":1,"order":2655,"group":1,"subgroup":28,"version":4,"gender":1,"tone":5}]},{"label":"woman surfing","hexcode":"1F3C4-200D-2640-FE0F","tags":["beach","ocean","person","sport","surf","surfer","surfing","swell","waves"],"emoji":"🏄‍♀️","text":"","type":1,"order":2657,"group":1,"subgroup":28,"version":4,"gender":0,"skins":[{"label":"woman surfing: light skin tone","hexcode":"1F3C4-1F3FB-200D-2640-FE0F","emoji":"🏄🏻‍♀️","text":"","type":1,"order":2659,"group":1,"subgroup":28,"version":4,"gender":0,"tone":1},{"label":"woman surfing: medium-light skin tone","hexcode":"1F3C4-1F3FC-200D-2640-FE0F","emoji":"🏄🏼‍♀️","text":"","type":1,"order":2661,"group":1,"subgroup":28,"version":4,"gender":0,"tone":2},{"label":"woman surfing: medium skin tone","hexcode":"1F3C4-1F3FD-200D-2640-FE0F","emoji":"🏄🏽‍♀️","text":"","type":1,"order":2663,"group":1,"subgroup":28,"version":4,"gender":0,"tone":3},{"label":"woman surfing: medium-dark skin tone","hexcode":"1F3C4-1F3FE-200D-2640-FE0F","emoji":"🏄🏾‍♀️","text":"","type":1,"order":2665,"group":1,"subgroup":28,"version":4,"gender":0,"tone":4},{"label":"woman surfing: dark skin tone","hexcode":"1F3C4-1F3FF-200D-2640-FE0F","emoji":"🏄🏿‍♀️","text":"","type":1,"order":2667,"group":1,"subgroup":28,"version":4,"gender":0,"tone":5}]},{"label":"person rowing boat","hexcode":"1F6A3","tags":["boat","canoe","cruise","fishing","lake","oar","paddle","person","raft","river","row","rowboat","rowing"],"emoji":"🚣","text":"","type":1,"order":2669,"group":1,"subgroup":28,"version":1,"skins":[{"label":"person rowing boat: light skin tone","hexcode":"1F6A3-1F3FB","emoji":"🚣🏻","text":"","type":1,"order":2670,"group":1,"subgroup":28,"version":1,"tone":1},{"label":"person rowing boat: medium-light skin tone","hexcode":"1F6A3-1F3FC","emoji":"🚣🏼","text":"","type":1,"order":2671,"group":1,"subgroup":28,"version":1,"tone":2},{"label":"person rowing boat: medium skin tone","hexcode":"1F6A3-1F3FD","emoji":"🚣🏽","text":"","type":1,"order":2672,"group":1,"subgroup":28,"version":1,"tone":3},{"label":"person rowing boat: medium-dark skin tone","hexcode":"1F6A3-1F3FE","emoji":"🚣🏾","text":"","type":1,"order":2673,"group":1,"subgroup":28,"version":1,"tone":4},{"label":"person rowing boat: dark skin tone","hexcode":"1F6A3-1F3FF","emoji":"🚣🏿","text":"","type":1,"order":2674,"group":1,"subgroup":28,"version":1,"tone":5}]},{"label":"man rowing boat","hexcode":"1F6A3-200D-2642-FE0F","tags":["boat","canoe","cruise","fishing","lake","man","oar","paddle","raft","river","row","rowboat","rowing"],"emoji":"🚣‍♂️","text":"","type":1,"order":2675,"group":1,"subgroup":28,"version":4,"gender":1,"skins":[{"label":"man rowing boat: light skin tone","hexcode":"1F6A3-1F3FB-200D-2642-FE0F","emoji":"🚣🏻‍♂️","text":"","type":1,"order":2677,"group":1,"subgroup":28,"version":4,"gender":1,"tone":1},{"label":"man rowing boat: medium-light skin tone","hexcode":"1F6A3-1F3FC-200D-2642-FE0F","emoji":"🚣🏼‍♂️","text":"","type":1,"order":2679,"group":1,"subgroup":28,"version":4,"gender":1,"tone":2},{"label":"man rowing boat: medium skin tone","hexcode":"1F6A3-1F3FD-200D-2642-FE0F","emoji":"🚣🏽‍♂️","text":"","type":1,"order":2681,"group":1,"subgroup":28,"version":4,"gender":1,"tone":3},{"label":"man rowing boat: medium-dark skin tone","hexcode":"1F6A3-1F3FE-200D-2642-FE0F","emoji":"🚣🏾‍♂️","text":"","type":1,"order":2683,"group":1,"subgroup":28,"version":4,"gender":1,"tone":4},{"label":"man rowing boat: dark skin tone","hexcode":"1F6A3-1F3FF-200D-2642-FE0F","emoji":"🚣🏿‍♂️","text":"","type":1,"order":2685,"group":1,"subgroup":28,"version":4,"gender":1,"tone":5}]},{"label":"woman rowing boat","hexcode":"1F6A3-200D-2640-FE0F","tags":["boat","canoe","cruise","fishing","lake","oar","paddle","raft","river","row","rowboat","rowing","woman"],"emoji":"🚣‍♀️","text":"","type":1,"order":2687,"group":1,"subgroup":28,"version":4,"gender":0,"skins":[{"label":"woman rowing boat: light skin tone","hexcode":"1F6A3-1F3FB-200D-2640-FE0F","emoji":"🚣🏻‍♀️","text":"","type":1,"order":2689,"group":1,"subgroup":28,"version":4,"gender":0,"tone":1},{"label":"woman rowing boat: medium-light skin tone","hexcode":"1F6A3-1F3FC-200D-2640-FE0F","emoji":"🚣🏼‍♀️","text":"","type":1,"order":2691,"group":1,"subgroup":28,"version":4,"gender":0,"tone":2},{"label":"woman rowing boat: medium skin tone","hexcode":"1F6A3-1F3FD-200D-2640-FE0F","emoji":"🚣🏽‍♀️","text":"","type":1,"order":2693,"group":1,"subgroup":28,"version":4,"gender":0,"tone":3},{"label":"woman rowing boat: medium-dark skin tone","hexcode":"1F6A3-1F3FE-200D-2640-FE0F","emoji":"🚣🏾‍♀️","text":"","type":1,"order":2695,"group":1,"subgroup":28,"version":4,"gender":0,"tone":4},{"label":"woman rowing boat: dark skin tone","hexcode":"1F6A3-1F3FF-200D-2640-FE0F","emoji":"🚣🏿‍♀️","text":"","type":1,"order":2697,"group":1,"subgroup":28,"version":4,"gender":0,"tone":5}]},{"label":"person swimming","hexcode":"1F3CA","tags":["freestyle","person","sport","swim","swimmer","swimming","triathlon"],"emoji":"🏊️","text":"🏊︎","type":1,"order":2699,"group":1,"subgroup":28,"version":0.6,"skins":[{"label":"person swimming: light skin tone","hexcode":"1F3CA-1F3FB","emoji":"🏊🏻","text":"","type":1,"order":2700,"group":1,"subgroup":28,"version":1,"tone":1},{"label":"person swimming: medium-light skin tone","hexcode":"1F3CA-1F3FC","emoji":"🏊🏼","text":"","type":1,"order":2701,"group":1,"subgroup":28,"version":1,"tone":2},{"label":"person swimming: medium skin tone","hexcode":"1F3CA-1F3FD","emoji":"🏊🏽","text":"","type":1,"order":2702,"group":1,"subgroup":28,"version":1,"tone":3},{"label":"person swimming: medium-dark skin tone","hexcode":"1F3CA-1F3FE","emoji":"🏊🏾","text":"","type":1,"order":2703,"group":1,"subgroup":28,"version":1,"tone":4},{"label":"person swimming: dark skin tone","hexcode":"1F3CA-1F3FF","emoji":"🏊🏿","text":"","type":1,"order":2704,"group":1,"subgroup":28,"version":1,"tone":5}]},{"label":"man swimming","hexcode":"1F3CA-200D-2642-FE0F","tags":["freestyle","man","sport","swim","swimmer","swimming","triathlon"],"emoji":"🏊‍♂️","text":"","type":1,"order":2705,"group":1,"subgroup":28,"version":4,"gender":1,"skins":[{"label":"man swimming: light skin tone","hexcode":"1F3CA-1F3FB-200D-2642-FE0F","emoji":"🏊🏻‍♂️","text":"","type":1,"order":2707,"group":1,"subgroup":28,"version":4,"gender":1,"tone":1},{"label":"man swimming: medium-light skin tone","hexcode":"1F3CA-1F3FC-200D-2642-FE0F","emoji":"🏊🏼‍♂️","text":"","type":1,"order":2709,"group":1,"subgroup":28,"version":4,"gender":1,"tone":2},{"label":"man swimming: medium skin tone","hexcode":"1F3CA-1F3FD-200D-2642-FE0F","emoji":"🏊🏽‍♂️","text":"","type":1,"order":2711,"group":1,"subgroup":28,"version":4,"gender":1,"tone":3},{"label":"man swimming: medium-dark skin tone","hexcode":"1F3CA-1F3FE-200D-2642-FE0F","emoji":"🏊🏾‍♂️","text":"","type":1,"order":2713,"group":1,"subgroup":28,"version":4,"gender":1,"tone":4},{"label":"man swimming: dark skin tone","hexcode":"1F3CA-1F3FF-200D-2642-FE0F","emoji":"🏊🏿‍♂️","text":"","type":1,"order":2715,"group":1,"subgroup":28,"version":4,"gender":1,"tone":5}]},{"label":"woman swimming","hexcode":"1F3CA-200D-2640-FE0F","tags":["freestyle","man","sport","swim","swimmer","swimming","triathlon"],"emoji":"🏊‍♀️","text":"","type":1,"order":2717,"group":1,"subgroup":28,"version":4,"gender":0,"skins":[{"label":"woman swimming: light skin tone","hexcode":"1F3CA-1F3FB-200D-2640-FE0F","emoji":"🏊🏻‍♀️","text":"","type":1,"order":2719,"group":1,"subgroup":28,"version":4,"gender":0,"tone":1},{"label":"woman swimming: medium-light skin tone","hexcode":"1F3CA-1F3FC-200D-2640-FE0F","emoji":"🏊🏼‍♀️","text":"","type":1,"order":2721,"group":1,"subgroup":28,"version":4,"gender":0,"tone":2},{"label":"woman swimming: medium skin tone","hexcode":"1F3CA-1F3FD-200D-2640-FE0F","emoji":"🏊🏽‍♀️","text":"","type":1,"order":2723,"group":1,"subgroup":28,"version":4,"gender":0,"tone":3},{"label":"woman swimming: medium-dark skin tone","hexcode":"1F3CA-1F3FE-200D-2640-FE0F","emoji":"🏊🏾‍♀️","text":"","type":1,"order":2725,"group":1,"subgroup":28,"version":4,"gender":0,"tone":4},{"label":"woman swimming: dark skin tone","hexcode":"1F3CA-1F3FF-200D-2640-FE0F","emoji":"🏊🏿‍♀️","text":"","type":1,"order":2727,"group":1,"subgroup":28,"version":4,"gender":0,"tone":5}]},{"label":"person bouncing ball","hexcode":"26F9","tags":["athletic","ball","basketball","bouncing","championship","dribble","net","person","player","throw"],"emoji":"⛹️","text":"⛹︎","type":0,"order":2730,"group":1,"subgroup":28,"version":0.7,"skins":[{"label":"person bouncing ball: light skin tone","hexcode":"26F9-1F3FB","emoji":"⛹🏻","text":"","type":1,"order":2731,"group":1,"subgroup":28,"version":2,"tone":1},{"label":"person bouncing ball: medium-light skin tone","hexcode":"26F9-1F3FC","emoji":"⛹🏼","text":"","type":1,"order":2732,"group":1,"subgroup":28,"version":2,"tone":2},{"label":"person bouncing ball: medium skin tone","hexcode":"26F9-1F3FD","emoji":"⛹🏽","text":"","type":1,"order":2733,"group":1,"subgroup":28,"version":2,"tone":3},{"label":"person bouncing ball: medium-dark skin tone","hexcode":"26F9-1F3FE","emoji":"⛹🏾","text":"","type":1,"order":2734,"group":1,"subgroup":28,"version":2,"tone":4},{"label":"person bouncing ball: dark skin tone","hexcode":"26F9-1F3FF","emoji":"⛹🏿","text":"","type":1,"order":2735,"group":1,"subgroup":28,"version":2,"tone":5}]},{"label":"man bouncing ball","hexcode":"26F9-FE0F-200D-2642-FE0F","tags":["athletic","ball","basketball","bouncing","championship","dribble","man","net","player","throw"],"emoji":"⛹️‍♂️","text":"","type":1,"order":2736,"group":1,"subgroup":28,"version":4,"gender":1,"skins":[{"label":"man bouncing ball: light skin tone","hexcode":"26F9-1F3FB-200D-2642-FE0F","emoji":"⛹🏻‍♂️","text":"","type":1,"order":2740,"group":1,"subgroup":28,"version":4,"gender":1,"tone":1},{"label":"man bouncing ball: medium-light skin tone","hexcode":"26F9-1F3FC-200D-2642-FE0F","emoji":"⛹🏼‍♂️","text":"","type":1,"order":2742,"group":1,"subgroup":28,"version":4,"gender":1,"tone":2},{"label":"man bouncing ball: medium skin tone","hexcode":"26F9-1F3FD-200D-2642-FE0F","emoji":"⛹🏽‍♂️","text":"","type":1,"order":2744,"group":1,"subgroup":28,"version":4,"gender":1,"tone":3},{"label":"man bouncing ball: medium-dark skin tone","hexcode":"26F9-1F3FE-200D-2642-FE0F","emoji":"⛹🏾‍♂️","text":"","type":1,"order":2746,"group":1,"subgroup":28,"version":4,"gender":1,"tone":4},{"label":"man bouncing ball: dark skin tone","hexcode":"26F9-1F3FF-200D-2642-FE0F","emoji":"⛹🏿‍♂️","text":"","type":1,"order":2748,"group":1,"subgroup":28,"version":4,"gender":1,"tone":5}]},{"label":"woman bouncing ball","hexcode":"26F9-FE0F-200D-2640-FE0F","tags":["athletic","ball","basketball","bouncing","championship","dribble","net","player","throw","woman"],"emoji":"⛹️‍♀️","text":"","type":1,"order":2750,"group":1,"subgroup":28,"version":4,"gender":0,"skins":[{"label":"woman bouncing ball: light skin tone","hexcode":"26F9-1F3FB-200D-2640-FE0F","emoji":"⛹🏻‍♀️","text":"","type":1,"order":2754,"group":1,"subgroup":28,"version":4,"gender":0,"tone":1},{"label":"woman bouncing ball: medium-light skin tone","hexcode":"26F9-1F3FC-200D-2640-FE0F","emoji":"⛹🏼‍♀️","text":"","type":1,"order":2756,"group":1,"subgroup":28,"version":4,"gender":0,"tone":2},{"label":"woman bouncing ball: medium skin tone","hexcode":"26F9-1F3FD-200D-2640-FE0F","emoji":"⛹🏽‍♀️","text":"","type":1,"order":2758,"group":1,"subgroup":28,"version":4,"gender":0,"tone":3},{"label":"woman bouncing ball: medium-dark skin tone","hexcode":"26F9-1F3FE-200D-2640-FE0F","emoji":"⛹🏾‍♀️","text":"","type":1,"order":2760,"group":1,"subgroup":28,"version":4,"gender":0,"tone":4},{"label":"woman bouncing ball: dark skin tone","hexcode":"26F9-1F3FF-200D-2640-FE0F","emoji":"⛹🏿‍♀️","text":"","type":1,"order":2762,"group":1,"subgroup":28,"version":4,"gender":0,"tone":5}]},{"label":"person lifting weights","hexcode":"1F3CB","tags":["barbell","bodybuilder","deadlift","lifter","lifting","person","powerlifting","weight","weightlifter","weights","workout"],"emoji":"🏋️","text":"🏋︎","type":0,"order":2765,"group":1,"subgroup":28,"version":0.7,"skins":[{"label":"person lifting weights: light skin tone","hexcode":"1F3CB-1F3FB","emoji":"🏋🏻","text":"","type":1,"order":2766,"group":1,"subgroup":28,"version":2,"tone":1},{"label":"person lifting weights: medium-light skin tone","hexcode":"1F3CB-1F3FC","emoji":"🏋🏼","text":"","type":1,"order":2767,"group":1,"subgroup":28,"version":2,"tone":2},{"label":"person lifting weights: medium skin tone","hexcode":"1F3CB-1F3FD","emoji":"🏋🏽","text":"","type":1,"order":2768,"group":1,"subgroup":28,"version":2,"tone":3},{"label":"person lifting weights: medium-dark skin tone","hexcode":"1F3CB-1F3FE","emoji":"🏋🏾","text":"","type":1,"order":2769,"group":1,"subgroup":28,"version":2,"tone":4},{"label":"person lifting weights: dark skin tone","hexcode":"1F3CB-1F3FF","emoji":"🏋🏿","text":"","type":1,"order":2770,"group":1,"subgroup":28,"version":2,"tone":5}]},{"label":"man lifting weights","hexcode":"1F3CB-FE0F-200D-2642-FE0F","tags":["barbell","bodybuilder","deadlift","lifter","lifting","man","powerlifting","weight","weightlifter","weights","workout"],"emoji":"🏋️‍♂️","text":"","type":1,"order":2771,"group":1,"subgroup":28,"version":4,"gender":1,"skins":[{"label":"man lifting weights: light skin tone","hexcode":"1F3CB-1F3FB-200D-2642-FE0F","emoji":"🏋🏻‍♂️","text":"","type":1,"order":2775,"group":1,"subgroup":28,"version":4,"gender":1,"tone":1},{"label":"man lifting weights: medium-light skin tone","hexcode":"1F3CB-1F3FC-200D-2642-FE0F","emoji":"🏋🏼‍♂️","text":"","type":1,"order":2777,"group":1,"subgroup":28,"version":4,"gender":1,"tone":2},{"label":"man lifting weights: medium skin tone","hexcode":"1F3CB-1F3FD-200D-2642-FE0F","emoji":"🏋🏽‍♂️","text":"","type":1,"order":2779,"group":1,"subgroup":28,"version":4,"gender":1,"tone":3},{"label":"man lifting weights: medium-dark skin tone","hexcode":"1F3CB-1F3FE-200D-2642-FE0F","emoji":"🏋🏾‍♂️","text":"","type":1,"order":2781,"group":1,"subgroup":28,"version":4,"gender":1,"tone":4},{"label":"man lifting weights: dark skin tone","hexcode":"1F3CB-1F3FF-200D-2642-FE0F","emoji":"🏋🏿‍♂️","text":"","type":1,"order":2783,"group":1,"subgroup":28,"version":4,"gender":1,"tone":5}]},{"label":"woman lifting weights","hexcode":"1F3CB-FE0F-200D-2640-FE0F","tags":["barbell","bodybuilder","deadlift","lifter","lifting","powerlifting","weight","weightlifter","weights","woman","workout"],"emoji":"🏋️‍♀️","text":"","type":1,"order":2785,"group":1,"subgroup":28,"version":4,"gender":0,"skins":[{"label":"woman lifting weights: light skin tone","hexcode":"1F3CB-1F3FB-200D-2640-FE0F","emoji":"🏋🏻‍♀️","text":"","type":1,"order":2789,"group":1,"subgroup":28,"version":4,"gender":0,"tone":1},{"label":"woman lifting weights: medium-light skin tone","hexcode":"1F3CB-1F3FC-200D-2640-FE0F","emoji":"🏋🏼‍♀️","text":"","type":1,"order":2791,"group":1,"subgroup":28,"version":4,"gender":0,"tone":2},{"label":"woman lifting weights: medium skin tone","hexcode":"1F3CB-1F3FD-200D-2640-FE0F","emoji":"🏋🏽‍♀️","text":"","type":1,"order":2793,"group":1,"subgroup":28,"version":4,"gender":0,"tone":3},{"label":"woman lifting weights: medium-dark skin tone","hexcode":"1F3CB-1F3FE-200D-2640-FE0F","emoji":"🏋🏾‍♀️","text":"","type":1,"order":2795,"group":1,"subgroup":28,"version":4,"gender":0,"tone":4},{"label":"woman lifting weights: dark skin tone","hexcode":"1F3CB-1F3FF-200D-2640-FE0F","emoji":"🏋🏿‍♀️","text":"","type":1,"order":2797,"group":1,"subgroup":28,"version":4,"gender":0,"tone":5}]},{"label":"person biking","hexcode":"1F6B4","tags":["bicycle","bicyclist","bike","biking","cycle","cyclist","person","riding","sport"],"emoji":"🚴","text":"","type":1,"order":2799,"group":1,"subgroup":28,"version":1,"skins":[{"label":"person biking: light skin tone","hexcode":"1F6B4-1F3FB","emoji":"🚴🏻","text":"","type":1,"order":2800,"group":1,"subgroup":28,"version":1,"tone":1},{"label":"person biking: medium-light skin tone","hexcode":"1F6B4-1F3FC","emoji":"🚴🏼","text":"","type":1,"order":2801,"group":1,"subgroup":28,"version":1,"tone":2},{"label":"person biking: medium skin tone","hexcode":"1F6B4-1F3FD","emoji":"🚴🏽","text":"","type":1,"order":2802,"group":1,"subgroup":28,"version":1,"tone":3},{"label":"person biking: medium-dark skin tone","hexcode":"1F6B4-1F3FE","emoji":"🚴🏾","text":"","type":1,"order":2803,"group":1,"subgroup":28,"version":1,"tone":4},{"label":"person biking: dark skin tone","hexcode":"1F6B4-1F3FF","emoji":"🚴🏿","text":"","type":1,"order":2804,"group":1,"subgroup":28,"version":1,"tone":5}]},{"label":"man biking","hexcode":"1F6B4-200D-2642-FE0F","tags":["bicycle","bicyclist","bike","biking","cycle","cyclist","man","riding","sport"],"emoji":"🚴‍♂️","text":"","type":1,"order":2805,"group":1,"subgroup":28,"version":4,"gender":1,"skins":[{"label":"man biking: light skin tone","hexcode":"1F6B4-1F3FB-200D-2642-FE0F","emoji":"🚴🏻‍♂️","text":"","type":1,"order":2807,"group":1,"subgroup":28,"version":4,"gender":1,"tone":1},{"label":"man biking: medium-light skin tone","hexcode":"1F6B4-1F3FC-200D-2642-FE0F","emoji":"🚴🏼‍♂️","text":"","type":1,"order":2809,"group":1,"subgroup":28,"version":4,"gender":1,"tone":2},{"label":"man biking: medium skin tone","hexcode":"1F6B4-1F3FD-200D-2642-FE0F","emoji":"🚴🏽‍♂️","text":"","type":1,"order":2811,"group":1,"subgroup":28,"version":4,"gender":1,"tone":3},{"label":"man biking: medium-dark skin tone","hexcode":"1F6B4-1F3FE-200D-2642-FE0F","emoji":"🚴🏾‍♂️","text":"","type":1,"order":2813,"group":1,"subgroup":28,"version":4,"gender":1,"tone":4},{"label":"man biking: dark skin tone","hexcode":"1F6B4-1F3FF-200D-2642-FE0F","emoji":"🚴🏿‍♂️","text":"","type":1,"order":2815,"group":1,"subgroup":28,"version":4,"gender":1,"tone":5}]},{"label":"woman biking","hexcode":"1F6B4-200D-2640-FE0F","tags":["bicycle","bicyclist","bike","biking","cycle","cyclist","riding","sport","woman"],"emoji":"🚴‍♀️","text":"","type":1,"order":2817,"group":1,"subgroup":28,"version":4,"gender":0,"skins":[{"label":"woman biking: light skin tone","hexcode":"1F6B4-1F3FB-200D-2640-FE0F","emoji":"🚴🏻‍♀️","text":"","type":1,"order":2819,"group":1,"subgroup":28,"version":4,"gender":0,"tone":1},{"label":"woman biking: medium-light skin tone","hexcode":"1F6B4-1F3FC-200D-2640-FE0F","emoji":"🚴🏼‍♀️","text":"","type":1,"order":2821,"group":1,"subgroup":28,"version":4,"gender":0,"tone":2},{"label":"woman biking: medium skin tone","hexcode":"1F6B4-1F3FD-200D-2640-FE0F","emoji":"🚴🏽‍♀️","text":"","type":1,"order":2823,"group":1,"subgroup":28,"version":4,"gender":0,"tone":3},{"label":"woman biking: medium-dark skin tone","hexcode":"1F6B4-1F3FE-200D-2640-FE0F","emoji":"🚴🏾‍♀️","text":"","type":1,"order":2825,"group":1,"subgroup":28,"version":4,"gender":0,"tone":4},{"label":"woman biking: dark skin tone","hexcode":"1F6B4-1F3FF-200D-2640-FE0F","emoji":"🚴🏿‍♀️","text":"","type":1,"order":2827,"group":1,"subgroup":28,"version":4,"gender":0,"tone":5}]},{"label":"person mountain biking","hexcode":"1F6B5","tags":["bicycle","bicyclist","bike","biking","cycle","cyclist","mountain","person","riding","sport"],"emoji":"🚵","text":"","type":1,"order":2829,"group":1,"subgroup":28,"version":1,"skins":[{"label":"person mountain biking: light skin tone","hexcode":"1F6B5-1F3FB","emoji":"🚵🏻","text":"","type":1,"order":2830,"group":1,"subgroup":28,"version":1,"tone":1},{"label":"person mountain biking: medium-light skin tone","hexcode":"1F6B5-1F3FC","emoji":"🚵🏼","text":"","type":1,"order":2831,"group":1,"subgroup":28,"version":1,"tone":2},{"label":"person mountain biking: medium skin tone","hexcode":"1F6B5-1F3FD","emoji":"🚵🏽","text":"","type":1,"order":2832,"group":1,"subgroup":28,"version":1,"tone":3},{"label":"person mountain biking: medium-dark skin tone","hexcode":"1F6B5-1F3FE","emoji":"🚵🏾","text":"","type":1,"order":2833,"group":1,"subgroup":28,"version":1,"tone":4},{"label":"person mountain biking: dark skin tone","hexcode":"1F6B5-1F3FF","emoji":"🚵🏿","text":"","type":1,"order":2834,"group":1,"subgroup":28,"version":1,"tone":5}]},{"label":"man mountain biking","hexcode":"1F6B5-200D-2642-FE0F","tags":["bicycle","bicyclist","bike","biking","cycle","cyclist","man","mountain","riding","sport"],"emoji":"🚵‍♂️","text":"","type":1,"order":2835,"group":1,"subgroup":28,"version":4,"gender":1,"skins":[{"label":"man mountain biking: light skin tone","hexcode":"1F6B5-1F3FB-200D-2642-FE0F","emoji":"🚵🏻‍♂️","text":"","type":1,"order":2837,"group":1,"subgroup":28,"version":4,"gender":1,"tone":1},{"label":"man mountain biking: medium-light skin tone","hexcode":"1F6B5-1F3FC-200D-2642-FE0F","emoji":"🚵🏼‍♂️","text":"","type":1,"order":2839,"group":1,"subgroup":28,"version":4,"gender":1,"tone":2},{"label":"man mountain biking: medium skin tone","hexcode":"1F6B5-1F3FD-200D-2642-FE0F","emoji":"🚵🏽‍♂️","text":"","type":1,"order":2841,"group":1,"subgroup":28,"version":4,"gender":1,"tone":3},{"label":"man mountain biking: medium-dark skin tone","hexcode":"1F6B5-1F3FE-200D-2642-FE0F","emoji":"🚵🏾‍♂️","text":"","type":1,"order":2843,"group":1,"subgroup":28,"version":4,"gender":1,"tone":4},{"label":"man mountain biking: dark skin tone","hexcode":"1F6B5-1F3FF-200D-2642-FE0F","emoji":"🚵🏿‍♂️","text":"","type":1,"order":2845,"group":1,"subgroup":28,"version":4,"gender":1,"tone":5}]},{"label":"woman mountain biking","hexcode":"1F6B5-200D-2640-FE0F","tags":["bicycle","bicyclist","bike","biking","cycle","cyclist","mountain","riding","sport","woman"],"emoji":"🚵‍♀️","text":"","type":1,"order":2847,"group":1,"subgroup":28,"version":4,"gender":0,"skins":[{"label":"woman mountain biking: light skin tone","hexcode":"1F6B5-1F3FB-200D-2640-FE0F","emoji":"🚵🏻‍♀️","text":"","type":1,"order":2849,"group":1,"subgroup":28,"version":4,"gender":0,"tone":1},{"label":"woman mountain biking: medium-light skin tone","hexcode":"1F6B5-1F3FC-200D-2640-FE0F","emoji":"🚵🏼‍♀️","text":"","type":1,"order":2851,"group":1,"subgroup":28,"version":4,"gender":0,"tone":2},{"label":"woman mountain biking: medium skin tone","hexcode":"1F6B5-1F3FD-200D-2640-FE0F","emoji":"🚵🏽‍♀️","text":"","type":1,"order":2853,"group":1,"subgroup":28,"version":4,"gender":0,"tone":3},{"label":"woman mountain biking: medium-dark skin tone","hexcode":"1F6B5-1F3FE-200D-2640-FE0F","emoji":"🚵🏾‍♀️","text":"","type":1,"order":2855,"group":1,"subgroup":28,"version":4,"gender":0,"tone":4},{"label":"woman mountain biking: dark skin tone","hexcode":"1F6B5-1F3FF-200D-2640-FE0F","emoji":"🚵🏿‍♀️","text":"","type":1,"order":2857,"group":1,"subgroup":28,"version":4,"gender":0,"tone":5}]},{"label":"person cartwheeling","hexcode":"1F938","tags":["active","cartwheel","cartwheeling","excited","flip","gymnastics","happy","person","somersault"],"emoji":"🤸","text":"","type":1,"order":2859,"group":1,"subgroup":28,"version":3,"skins":[{"label":"person cartwheeling: light skin tone","hexcode":"1F938-1F3FB","emoji":"🤸🏻","text":"","type":1,"order":2860,"group":1,"subgroup":28,"version":3,"tone":1},{"label":"person cartwheeling: medium-light skin tone","hexcode":"1F938-1F3FC","emoji":"🤸🏼","text":"","type":1,"order":2861,"group":1,"subgroup":28,"version":3,"tone":2},{"label":"person cartwheeling: medium skin tone","hexcode":"1F938-1F3FD","emoji":"🤸🏽","text":"","type":1,"order":2862,"group":1,"subgroup":28,"version":3,"tone":3},{"label":"person cartwheeling: medium-dark skin tone","hexcode":"1F938-1F3FE","emoji":"🤸🏾","text":"","type":1,"order":2863,"group":1,"subgroup":28,"version":3,"tone":4},{"label":"person cartwheeling: dark skin tone","hexcode":"1F938-1F3FF","emoji":"🤸🏿","text":"","type":1,"order":2864,"group":1,"subgroup":28,"version":3,"tone":5}]},{"label":"man cartwheeling","hexcode":"1F938-200D-2642-FE0F","tags":["active","cartwheel","cartwheeling","excited","flip","gymnastics","happy","man","somersault"],"emoji":"🤸‍♂️","text":"","type":1,"order":2865,"group":1,"subgroup":28,"version":4,"gender":1,"skins":[{"label":"man cartwheeling: light skin tone","hexcode":"1F938-1F3FB-200D-2642-FE0F","emoji":"🤸🏻‍♂️","text":"","type":1,"order":2867,"group":1,"subgroup":28,"version":4,"gender":1,"tone":1},{"label":"man cartwheeling: medium-light skin tone","hexcode":"1F938-1F3FC-200D-2642-FE0F","emoji":"🤸🏼‍♂️","text":"","type":1,"order":2869,"group":1,"subgroup":28,"version":4,"gender":1,"tone":2},{"label":"man cartwheeling: medium skin tone","hexcode":"1F938-1F3FD-200D-2642-FE0F","emoji":"🤸🏽‍♂️","text":"","type":1,"order":2871,"group":1,"subgroup":28,"version":4,"gender":1,"tone":3},{"label":"man cartwheeling: medium-dark skin tone","hexcode":"1F938-1F3FE-200D-2642-FE0F","emoji":"🤸🏾‍♂️","text":"","type":1,"order":2873,"group":1,"subgroup":28,"version":4,"gender":1,"tone":4},{"label":"man cartwheeling: dark skin tone","hexcode":"1F938-1F3FF-200D-2642-FE0F","emoji":"🤸🏿‍♂️","text":"","type":1,"order":2875,"group":1,"subgroup":28,"version":4,"gender":1,"tone":5}]},{"label":"woman cartwheeling","hexcode":"1F938-200D-2640-FE0F","tags":["active","cartwheel","cartwheeling","excited","flip","gymnastics","happy","somersault","woman"],"emoji":"🤸‍♀️","text":"","type":1,"order":2877,"group":1,"subgroup":28,"version":4,"gender":0,"skins":[{"label":"woman cartwheeling: light skin tone","hexcode":"1F938-1F3FB-200D-2640-FE0F","emoji":"🤸🏻‍♀️","text":"","type":1,"order":2879,"group":1,"subgroup":28,"version":4,"gender":0,"tone":1},{"label":"woman cartwheeling: medium-light skin tone","hexcode":"1F938-1F3FC-200D-2640-FE0F","emoji":"🤸🏼‍♀️","text":"","type":1,"order":2881,"group":1,"subgroup":28,"version":4,"gender":0,"tone":2},{"label":"woman cartwheeling: medium skin tone","hexcode":"1F938-1F3FD-200D-2640-FE0F","emoji":"🤸🏽‍♀️","text":"","type":1,"order":2883,"group":1,"subgroup":28,"version":4,"gender":0,"tone":3},{"label":"woman cartwheeling: medium-dark skin tone","hexcode":"1F938-1F3FE-200D-2640-FE0F","emoji":"🤸🏾‍♀️","text":"","type":1,"order":2885,"group":1,"subgroup":28,"version":4,"gender":0,"tone":4},{"label":"woman cartwheeling: dark skin tone","hexcode":"1F938-1F3FF-200D-2640-FE0F","emoji":"🤸🏿‍♀️","text":"","type":1,"order":2887,"group":1,"subgroup":28,"version":4,"gender":0,"tone":5}]},{"label":"people wrestling","hexcode":"1F93C","tags":["combat","duel","grapple","people","ring","tournament","wrestle","wrestling"],"emoji":"🤼","text":"","type":1,"order":2889,"group":1,"subgroup":28,"version":3,"skins":[{"label":"people wrestling: light skin tone","hexcode":"1F93C-1F3FB","emoji":"🤼🏻","text":"","type":1,"order":2890,"group":1,"subgroup":28,"version":17,"tone":1},{"label":"people wrestling: medium-light skin tone","hexcode":"1F93C-1F3FC","emoji":"🤼🏼","text":"","type":1,"order":2891,"group":1,"subgroup":28,"version":17,"tone":2},{"label":"people wrestling: medium skin tone","hexcode":"1F93C-1F3FD","emoji":"🤼🏽","text":"","type":1,"order":2892,"group":1,"subgroup":28,"version":17,"tone":3},{"label":"people wrestling: medium-dark skin tone","hexcode":"1F93C-1F3FE","emoji":"🤼🏾","text":"","type":1,"order":2893,"group":1,"subgroup":28,"version":17,"tone":4},{"label":"people wrestling: dark skin tone","hexcode":"1F93C-1F3FF","emoji":"🤼🏿","text":"","type":1,"order":2894,"group":1,"subgroup":28,"version":17,"tone":5},{"label":"people wrestling: light skin tone, medium-light skin tone","hexcode":"1F9D1-1F3FB-200D-1FAEF-200D-1F9D1-1F3FC","emoji":"🧑🏻‍🫯‍🧑🏼","text":"","type":1,"order":2919,"group":1,"subgroup":28,"version":17,"tone":[1,2]},{"label":"people wrestling: light skin tone, medium skin tone","hexcode":"1F9D1-1F3FB-200D-1FAEF-200D-1F9D1-1F3FD","emoji":"🧑🏻‍🫯‍🧑🏽","text":"","type":1,"order":2920,"group":1,"subgroup":28,"version":17,"tone":[1,3]},{"label":"people wrestling: light skin tone, medium-dark skin tone","hexcode":"1F9D1-1F3FB-200D-1FAEF-200D-1F9D1-1F3FE","emoji":"🧑🏻‍🫯‍🧑🏾","text":"","type":1,"order":2921,"group":1,"subgroup":28,"version":17,"tone":[1,4]},{"label":"people wrestling: light skin tone, dark skin tone","hexcode":"1F9D1-1F3FB-200D-1FAEF-200D-1F9D1-1F3FF","emoji":"🧑🏻‍🫯‍🧑🏿","text":"","type":1,"order":2922,"group":1,"subgroup":28,"version":17,"tone":[1,5]},{"label":"people wrestling: medium-light skin tone, light skin tone","hexcode":"1F9D1-1F3FC-200D-1FAEF-200D-1F9D1-1F3FB","emoji":"🧑🏼‍🫯‍🧑🏻","text":"","type":1,"order":2923,"group":1,"subgroup":28,"version":17,"tone":[2,1]},{"label":"people wrestling: medium-light skin tone, medium skin tone","hexcode":"1F9D1-1F3FC-200D-1FAEF-200D-1F9D1-1F3FD","emoji":"🧑🏼‍🫯‍🧑🏽","text":"","type":1,"order":2924,"group":1,"subgroup":28,"version":17,"tone":[2,3]},{"label":"people wrestling: medium-light skin tone, medium-dark skin tone","hexcode":"1F9D1-1F3FC-200D-1FAEF-200D-1F9D1-1F3FE","emoji":"🧑🏼‍🫯‍🧑🏾","text":"","type":1,"order":2925,"group":1,"subgroup":28,"version":17,"tone":[2,4]},{"label":"people wrestling: medium-light skin tone, dark skin tone","hexcode":"1F9D1-1F3FC-200D-1FAEF-200D-1F9D1-1F3FF","emoji":"🧑🏼‍🫯‍🧑🏿","text":"","type":1,"order":2926,"group":1,"subgroup":28,"version":17,"tone":[2,5]},{"label":"people wrestling: medium skin tone, light skin tone","hexcode":"1F9D1-1F3FD-200D-1FAEF-200D-1F9D1-1F3FB","emoji":"🧑🏽‍🫯‍🧑🏻","text":"","type":1,"order":2927,"group":1,"subgroup":28,"version":17,"tone":[3,1]},{"label":"people wrestling: medium skin tone, medium-light skin tone","hexcode":"1F9D1-1F3FD-200D-1FAEF-200D-1F9D1-1F3FC","emoji":"🧑🏽‍🫯‍🧑🏼","text":"","type":1,"order":2928,"group":1,"subgroup":28,"version":17,"tone":[3,2]},{"label":"people wrestling: medium skin tone, medium-dark skin tone","hexcode":"1F9D1-1F3FD-200D-1FAEF-200D-1F9D1-1F3FE","emoji":"🧑🏽‍🫯‍🧑🏾","text":"","type":1,"order":2929,"group":1,"subgroup":28,"version":17,"tone":[3,4]},{"label":"people wrestling: medium skin tone, dark skin tone","hexcode":"1F9D1-1F3FD-200D-1FAEF-200D-1F9D1-1F3FF","emoji":"🧑🏽‍🫯‍🧑🏿","text":"","type":1,"order":2930,"group":1,"subgroup":28,"version":17,"tone":[3,5]},{"label":"people wrestling: medium-dark skin tone, light skin tone","hexcode":"1F9D1-1F3FE-200D-1FAEF-200D-1F9D1-1F3FB","emoji":"🧑🏾‍🫯‍🧑🏻","text":"","type":1,"order":2931,"group":1,"subgroup":28,"version":17,"tone":[4,1]},{"label":"people wrestling: medium-dark skin tone, medium-light skin tone","hexcode":"1F9D1-1F3FE-200D-1FAEF-200D-1F9D1-1F3FC","emoji":"🧑🏾‍🫯‍🧑🏼","text":"","type":1,"order":2932,"group":1,"subgroup":28,"version":17,"tone":[4,2]},{"label":"people wrestling: medium-dark skin tone, medium skin tone","hexcode":"1F9D1-1F3FE-200D-1FAEF-200D-1F9D1-1F3FD","emoji":"🧑🏾‍🫯‍🧑🏽","text":"","type":1,"order":2933,"group":1,"subgroup":28,"version":17,"tone":[4,3]},{"label":"people wrestling: medium-dark skin tone, dark skin tone","hexcode":"1F9D1-1F3FE-200D-1FAEF-200D-1F9D1-1F3FF","emoji":"🧑🏾‍🫯‍🧑🏿","text":"","type":1,"order":2934,"group":1,"subgroup":28,"version":17,"tone":[4,5]},{"label":"people wrestling: dark skin tone, light skin tone","hexcode":"1F9D1-1F3FF-200D-1FAEF-200D-1F9D1-1F3FB","emoji":"🧑🏿‍🫯‍🧑🏻","text":"","type":1,"order":2935,"group":1,"subgroup":28,"version":17,"tone":[5,1]},{"label":"people wrestling: dark skin tone, medium-light skin tone","hexcode":"1F9D1-1F3FF-200D-1FAEF-200D-1F9D1-1F3FC","emoji":"🧑🏿‍🫯‍🧑🏼","text":"","type":1,"order":2936,"group":1,"subgroup":28,"version":17,"tone":[5,2]},{"label":"people wrestling: dark skin tone, medium skin tone","hexcode":"1F9D1-1F3FF-200D-1FAEF-200D-1F9D1-1F3FD","emoji":"🧑🏿‍🫯‍🧑🏽","text":"","type":1,"order":2937,"group":1,"subgroup":28,"version":17,"tone":[5,3]},{"label":"people wrestling: dark skin tone, medium-dark skin tone","hexcode":"1F9D1-1F3FF-200D-1FAEF-200D-1F9D1-1F3FE","emoji":"🧑🏿‍🫯‍🧑🏾","text":"","type":1,"order":2938,"group":1,"subgroup":28,"version":17,"tone":[5,4]}]},{"label":"men wrestling","hexcode":"1F93C-200D-2642-FE0F","tags":["combat","duel","grapple","men","ring","tournament","wrestle","wrestling"],"emoji":"🤼‍♂️","text":"","type":1,"order":2895,"group":1,"subgroup":28,"version":4,"gender":1,"skins":[{"label":"men wrestling: light skin tone, medium-light skin tone","hexcode":"1F468-1F3FB-200D-1FAEF-200D-1F468-1F3FC","emoji":"👨🏻‍🫯‍👨🏼","text":"","type":1,"order":2939,"group":1,"subgroup":28,"version":17,"tone":[1,2]},{"label":"men wrestling: light skin tone, medium skin tone","hexcode":"1F468-1F3FB-200D-1FAEF-200D-1F468-1F3FD","emoji":"👨🏻‍🫯‍👨🏽","text":"","type":1,"order":2940,"group":1,"subgroup":28,"version":17,"tone":[1,3]},{"label":"men wrestling: light skin tone, medium-dark skin tone","hexcode":"1F468-1F3FB-200D-1FAEF-200D-1F468-1F3FE","emoji":"👨🏻‍🫯‍👨🏾","text":"","type":1,"order":2941,"group":1,"subgroup":28,"version":17,"tone":[1,4]},{"label":"men wrestling: light skin tone, dark skin tone","hexcode":"1F468-1F3FB-200D-1FAEF-200D-1F468-1F3FF","emoji":"👨🏻‍🫯‍👨🏿","text":"","type":1,"order":2942,"group":1,"subgroup":28,"version":17,"tone":[1,5]},{"label":"men wrestling: medium-light skin tone, light skin tone","hexcode":"1F468-1F3FC-200D-1FAEF-200D-1F468-1F3FB","emoji":"👨🏼‍🫯‍👨🏻","text":"","type":1,"order":2943,"group":1,"subgroup":28,"version":17,"tone":[2,1]},{"label":"men wrestling: medium-light skin tone, medium skin tone","hexcode":"1F468-1F3FC-200D-1FAEF-200D-1F468-1F3FD","emoji":"👨🏼‍🫯‍👨🏽","text":"","type":1,"order":2944,"group":1,"subgroup":28,"version":17,"tone":[2,3]},{"label":"men wrestling: medium-light skin tone, medium-dark skin tone","hexcode":"1F468-1F3FC-200D-1FAEF-200D-1F468-1F3FE","emoji":"👨🏼‍🫯‍👨🏾","text":"","type":1,"order":2945,"group":1,"subgroup":28,"version":17,"tone":[2,4]},{"label":"men wrestling: medium-light skin tone, dark skin tone","hexcode":"1F468-1F3FC-200D-1FAEF-200D-1F468-1F3FF","emoji":"👨🏼‍🫯‍👨🏿","text":"","type":1,"order":2946,"group":1,"subgroup":28,"version":17,"tone":[2,5]},{"label":"men wrestling: medium skin tone, light skin tone","hexcode":"1F468-1F3FD-200D-1FAEF-200D-1F468-1F3FB","emoji":"👨🏽‍🫯‍👨🏻","text":"","type":1,"order":2947,"group":1,"subgroup":28,"version":17,"tone":[3,1]},{"label":"men wrestling: medium skin tone, medium-light skin tone","hexcode":"1F468-1F3FD-200D-1FAEF-200D-1F468-1F3FC","emoji":"👨🏽‍🫯‍👨🏼","text":"","type":1,"order":2948,"group":1,"subgroup":28,"version":17,"tone":[3,2]},{"label":"men wrestling: medium skin tone, medium-dark skin tone","hexcode":"1F468-1F3FD-200D-1FAEF-200D-1F468-1F3FE","emoji":"👨🏽‍🫯‍👨🏾","text":"","type":1,"order":2949,"group":1,"subgroup":28,"version":17,"tone":[3,4]},{"label":"men wrestling: medium skin tone, dark skin tone","hexcode":"1F468-1F3FD-200D-1FAEF-200D-1F468-1F3FF","emoji":"👨🏽‍🫯‍👨🏿","text":"","type":1,"order":2950,"group":1,"subgroup":28,"version":17,"tone":[3,5]},{"label":"men wrestling: medium-dark skin tone, light skin tone","hexcode":"1F468-1F3FE-200D-1FAEF-200D-1F468-1F3FB","emoji":"👨🏾‍🫯‍👨🏻","text":"","type":1,"order":2951,"group":1,"subgroup":28,"version":17,"tone":[4,1]},{"label":"men wrestling: medium-dark skin tone, medium-light skin tone","hexcode":"1F468-1F3FE-200D-1FAEF-200D-1F468-1F3FC","emoji":"👨🏾‍🫯‍👨🏼","text":"","type":1,"order":2952,"group":1,"subgroup":28,"version":17,"tone":[4,2]},{"label":"men wrestling: medium-dark skin tone, medium skin tone","hexcode":"1F468-1F3FE-200D-1FAEF-200D-1F468-1F3FD","emoji":"👨🏾‍🫯‍👨🏽","text":"","type":1,"order":2953,"group":1,"subgroup":28,"version":17,"tone":[4,3]},{"label":"men wrestling: medium-dark skin tone, dark skin tone","hexcode":"1F468-1F3FE-200D-1FAEF-200D-1F468-1F3FF","emoji":"👨🏾‍🫯‍👨🏿","text":"","type":1,"order":2954,"group":1,"subgroup":28,"version":17,"tone":[4,5]},{"label":"men wrestling: dark skin tone, light skin tone","hexcode":"1F468-1F3FF-200D-1FAEF-200D-1F468-1F3FB","emoji":"👨🏿‍🫯‍👨🏻","text":"","type":1,"order":2955,"group":1,"subgroup":28,"version":17,"tone":[5,1]},{"label":"men wrestling: dark skin tone, medium-light skin tone","hexcode":"1F468-1F3FF-200D-1FAEF-200D-1F468-1F3FC","emoji":"👨🏿‍🫯‍👨🏼","text":"","type":1,"order":2956,"group":1,"subgroup":28,"version":17,"tone":[5,2]},{"label":"men wrestling: dark skin tone, medium skin tone","hexcode":"1F468-1F3FF-200D-1FAEF-200D-1F468-1F3FD","emoji":"👨🏿‍🫯‍👨🏽","text":"","type":1,"order":2957,"group":1,"subgroup":28,"version":17,"tone":[5,3]},{"label":"men wrestling: dark skin tone, medium-dark skin tone","hexcode":"1F468-1F3FF-200D-1FAEF-200D-1F468-1F3FE","emoji":"👨🏿‍🫯‍👨🏾","text":"","type":1,"order":2958,"group":1,"subgroup":28,"version":17,"tone":[5,4]},{"label":"men wrestling: light skin tone","hexcode":"1F93C-1F3FB-200D-2642-FE0F","emoji":"🤼🏻‍♂️","text":"","type":1,"order":2897,"group":1,"subgroup":28,"version":17,"gender":1,"tone":1},{"label":"men wrestling: medium-light skin tone","hexcode":"1F93C-1F3FC-200D-2642-FE0F","emoji":"🤼🏼‍♂️","text":"","type":1,"order":2899,"group":1,"subgroup":28,"version":17,"gender":1,"tone":2},{"label":"men wrestling: medium skin tone","hexcode":"1F93C-1F3FD-200D-2642-FE0F","emoji":"🤼🏽‍♂️","text":"","type":1,"order":2901,"group":1,"subgroup":28,"version":17,"gender":1,"tone":3},{"label":"men wrestling: medium-dark skin tone","hexcode":"1F93C-1F3FE-200D-2642-FE0F","emoji":"🤼🏾‍♂️","text":"","type":1,"order":2903,"group":1,"subgroup":28,"version":17,"gender":1,"tone":4},{"label":"men wrestling: dark skin tone","hexcode":"1F93C-1F3FF-200D-2642-FE0F","emoji":"🤼🏿‍♂️","text":"","type":1,"order":2905,"group":1,"subgroup":28,"version":17,"gender":1,"tone":5}]},{"label":"women wrestling","hexcode":"1F93C-200D-2640-FE0F","tags":["combat","duel","grapple","ring","tournament","women","wrestle","wrestling"],"emoji":"🤼‍♀️","text":"","type":1,"order":2907,"group":1,"subgroup":28,"version":4,"gender":0,"skins":[{"label":"women wrestling: light skin tone, medium-light skin tone","hexcode":"1F469-1F3FB-200D-1FAEF-200D-1F469-1F3FC","emoji":"👩🏻‍🫯‍👩🏼","text":"","type":1,"order":2959,"group":1,"subgroup":28,"version":17,"tone":[1,2]},{"label":"women wrestling: light skin tone, medium skin tone","hexcode":"1F469-1F3FB-200D-1FAEF-200D-1F469-1F3FD","emoji":"👩🏻‍🫯‍👩🏽","text":"","type":1,"order":2960,"group":1,"subgroup":28,"version":17,"tone":[1,3]},{"label":"women wrestling: light skin tone, medium-dark skin tone","hexcode":"1F469-1F3FB-200D-1FAEF-200D-1F469-1F3FE","emoji":"👩🏻‍🫯‍👩🏾","text":"","type":1,"order":2961,"group":1,"subgroup":28,"version":17,"tone":[1,4]},{"label":"women wrestling: light skin tone, dark skin tone","hexcode":"1F469-1F3FB-200D-1FAEF-200D-1F469-1F3FF","emoji":"👩🏻‍🫯‍👩🏿","text":"","type":1,"order":2962,"group":1,"subgroup":28,"version":17,"tone":[1,5]},{"label":"women wrestling: medium-light skin tone, light skin tone","hexcode":"1F469-1F3FC-200D-1FAEF-200D-1F469-1F3FB","emoji":"👩🏼‍🫯‍👩🏻","text":"","type":1,"order":2963,"group":1,"subgroup":28,"version":17,"tone":[2,1]},{"label":"women wrestling: medium-light skin tone, medium skin tone","hexcode":"1F469-1F3FC-200D-1FAEF-200D-1F469-1F3FD","emoji":"👩🏼‍🫯‍👩🏽","text":"","type":1,"order":2964,"group":1,"subgroup":28,"version":17,"tone":[2,3]},{"label":"women wrestling: medium-light skin tone, medium-dark skin tone","hexcode":"1F469-1F3FC-200D-1FAEF-200D-1F469-1F3FE","emoji":"👩🏼‍🫯‍👩🏾","text":"","type":1,"order":2965,"group":1,"subgroup":28,"version":17,"tone":[2,4]},{"label":"women wrestling: medium-light skin tone, dark skin tone","hexcode":"1F469-1F3FC-200D-1FAEF-200D-1F469-1F3FF","emoji":"👩🏼‍🫯‍👩🏿","text":"","type":1,"order":2966,"group":1,"subgroup":28,"version":17,"tone":[2,5]},{"label":"women wrestling: medium skin tone, light skin tone","hexcode":"1F469-1F3FD-200D-1FAEF-200D-1F469-1F3FB","emoji":"👩🏽‍🫯‍👩🏻","text":"","type":1,"order":2967,"group":1,"subgroup":28,"version":17,"tone":[3,1]},{"label":"women wrestling: medium skin tone, medium-light skin tone","hexcode":"1F469-1F3FD-200D-1FAEF-200D-1F469-1F3FC","emoji":"👩🏽‍🫯‍👩🏼","text":"","type":1,"order":2968,"group":1,"subgroup":28,"version":17,"tone":[3,2]},{"label":"women wrestling: medium skin tone, medium-dark skin tone","hexcode":"1F469-1F3FD-200D-1FAEF-200D-1F469-1F3FE","emoji":"👩🏽‍🫯‍👩🏾","text":"","type":1,"order":2969,"group":1,"subgroup":28,"version":17,"tone":[3,4]},{"label":"women wrestling: medium skin tone, dark skin tone","hexcode":"1F469-1F3FD-200D-1FAEF-200D-1F469-1F3FF","emoji":"👩🏽‍🫯‍👩🏿","text":"","type":1,"order":2970,"group":1,"subgroup":28,"version":17,"tone":[3,5]},{"label":"women wrestling: medium-dark skin tone, light skin tone","hexcode":"1F469-1F3FE-200D-1FAEF-200D-1F469-1F3FB","emoji":"👩🏾‍🫯‍👩🏻","text":"","type":1,"order":2971,"group":1,"subgroup":28,"version":17,"tone":[4,1]},{"label":"women wrestling: medium-dark skin tone, medium-light skin tone","hexcode":"1F469-1F3FE-200D-1FAEF-200D-1F469-1F3FC","emoji":"👩🏾‍🫯‍👩🏼","text":"","type":1,"order":2972,"group":1,"subgroup":28,"version":17,"tone":[4,2]},{"label":"women wrestling: medium-dark skin tone, medium skin tone","hexcode":"1F469-1F3FE-200D-1FAEF-200D-1F469-1F3FD","emoji":"👩🏾‍🫯‍👩🏽","text":"","type":1,"order":2973,"group":1,"subgroup":28,"version":17,"tone":[4,3]},{"label":"women wrestling: medium-dark skin tone, dark skin tone","hexcode":"1F469-1F3FE-200D-1FAEF-200D-1F469-1F3FF","emoji":"👩🏾‍🫯‍👩🏿","text":"","type":1,"order":2974,"group":1,"subgroup":28,"version":17,"tone":[4,5]},{"label":"women wrestling: dark skin tone, light skin tone","hexcode":"1F469-1F3FF-200D-1FAEF-200D-1F469-1F3FB","emoji":"👩🏿‍🫯‍👩🏻","text":"","type":1,"order":2975,"group":1,"subgroup":28,"version":17,"tone":[5,1]},{"label":"women wrestling: dark skin tone, medium-light skin tone","hexcode":"1F469-1F3FF-200D-1FAEF-200D-1F469-1F3FC","emoji":"👩🏿‍🫯‍👩🏼","text":"","type":1,"order":2976,"group":1,"subgroup":28,"version":17,"tone":[5,2]},{"label":"women wrestling: dark skin tone, medium skin tone","hexcode":"1F469-1F3FF-200D-1FAEF-200D-1F469-1F3FD","emoji":"👩🏿‍🫯‍👩🏽","text":"","type":1,"order":2977,"group":1,"subgroup":28,"version":17,"tone":[5,3]},{"label":"women wrestling: dark skin tone, medium-dark skin tone","hexcode":"1F469-1F3FF-200D-1FAEF-200D-1F469-1F3FE","emoji":"👩🏿‍🫯‍👩🏾","text":"","type":1,"order":2978,"group":1,"subgroup":28,"version":17,"tone":[5,4]},{"label":"women wrestling: light skin tone","hexcode":"1F93C-1F3FB-200D-2640-FE0F","emoji":"🤼🏻‍♀️","text":"","type":1,"order":2909,"group":1,"subgroup":28,"version":17,"gender":0,"tone":1},{"label":"women wrestling: medium-light skin tone","hexcode":"1F93C-1F3FC-200D-2640-FE0F","emoji":"🤼🏼‍♀️","text":"","type":1,"order":2911,"group":1,"subgroup":28,"version":17,"gender":0,"tone":2},{"label":"women wrestling: medium skin tone","hexcode":"1F93C-1F3FD-200D-2640-FE0F","emoji":"🤼🏽‍♀️","text":"","type":1,"order":2913,"group":1,"subgroup":28,"version":17,"gender":0,"tone":3},{"label":"women wrestling: medium-dark skin tone","hexcode":"1F93C-1F3FE-200D-2640-FE0F","emoji":"🤼🏾‍♀️","text":"","type":1,"order":2915,"group":1,"subgroup":28,"version":17,"gender":0,"tone":4},{"label":"women wrestling: dark skin tone","hexcode":"1F93C-1F3FF-200D-2640-FE0F","emoji":"🤼🏿‍♀️","text":"","type":1,"order":2917,"group":1,"subgroup":28,"version":17,"gender":0,"tone":5}]},{"label":"person playing water polo","hexcode":"1F93D","tags":["person","playing","polo","sport","swimming","water","waterpolo"],"emoji":"🤽","text":"","type":1,"order":2979,"group":1,"subgroup":28,"version":3,"skins":[{"label":"person playing water polo: light skin tone","hexcode":"1F93D-1F3FB","emoji":"🤽🏻","text":"","type":1,"order":2980,"group":1,"subgroup":28,"version":3,"tone":1},{"label":"person playing water polo: medium-light skin tone","hexcode":"1F93D-1F3FC","emoji":"🤽🏼","text":"","type":1,"order":2981,"group":1,"subgroup":28,"version":3,"tone":2},{"label":"person playing water polo: medium skin tone","hexcode":"1F93D-1F3FD","emoji":"🤽🏽","text":"","type":1,"order":2982,"group":1,"subgroup":28,"version":3,"tone":3},{"label":"person playing water polo: medium-dark skin tone","hexcode":"1F93D-1F3FE","emoji":"🤽🏾","text":"","type":1,"order":2983,"group":1,"subgroup":28,"version":3,"tone":4},{"label":"person playing water polo: dark skin tone","hexcode":"1F93D-1F3FF","emoji":"🤽🏿","text":"","type":1,"order":2984,"group":1,"subgroup":28,"version":3,"tone":5}]},{"label":"man playing water polo","hexcode":"1F93D-200D-2642-FE0F","tags":["man","playing","polo","sport","swimming","water","waterpolo"],"emoji":"🤽‍♂️","text":"","type":1,"order":2985,"group":1,"subgroup":28,"version":4,"gender":1,"skins":[{"label":"man playing water polo: light skin tone","hexcode":"1F93D-1F3FB-200D-2642-FE0F","emoji":"🤽🏻‍♂️","text":"","type":1,"order":2987,"group":1,"subgroup":28,"version":4,"gender":1,"tone":1},{"label":"man playing water polo: medium-light skin tone","hexcode":"1F93D-1F3FC-200D-2642-FE0F","emoji":"🤽🏼‍♂️","text":"","type":1,"order":2989,"group":1,"subgroup":28,"version":4,"gender":1,"tone":2},{"label":"man playing water polo: medium skin tone","hexcode":"1F93D-1F3FD-200D-2642-FE0F","emoji":"🤽🏽‍♂️","text":"","type":1,"order":2991,"group":1,"subgroup":28,"version":4,"gender":1,"tone":3},{"label":"man playing water polo: medium-dark skin tone","hexcode":"1F93D-1F3FE-200D-2642-FE0F","emoji":"🤽🏾‍♂️","text":"","type":1,"order":2993,"group":1,"subgroup":28,"version":4,"gender":1,"tone":4},{"label":"man playing water polo: dark skin tone","hexcode":"1F93D-1F3FF-200D-2642-FE0F","emoji":"🤽🏿‍♂️","text":"","type":1,"order":2995,"group":1,"subgroup":28,"version":4,"gender":1,"tone":5}]},{"label":"woman playing water polo","hexcode":"1F93D-200D-2640-FE0F","tags":["playing","polo","sport","swimming","water","waterpolo","woman"],"emoji":"🤽‍♀️","text":"","type":1,"order":2997,"group":1,"subgroup":28,"version":4,"gender":0,"skins":[{"label":"woman playing water polo: light skin tone","hexcode":"1F93D-1F3FB-200D-2640-FE0F","emoji":"🤽🏻‍♀️","text":"","type":1,"order":2999,"group":1,"subgroup":28,"version":4,"gender":0,"tone":1},{"label":"woman playing water polo: medium-light skin tone","hexcode":"1F93D-1F3FC-200D-2640-FE0F","emoji":"🤽🏼‍♀️","text":"","type":1,"order":3001,"group":1,"subgroup":28,"version":4,"gender":0,"tone":2},{"label":"woman playing water polo: medium skin tone","hexcode":"1F93D-1F3FD-200D-2640-FE0F","emoji":"🤽🏽‍♀️","text":"","type":1,"order":3003,"group":1,"subgroup":28,"version":4,"gender":0,"tone":3},{"label":"woman playing water polo: medium-dark skin tone","hexcode":"1F93D-1F3FE-200D-2640-FE0F","emoji":"🤽🏾‍♀️","text":"","type":1,"order":3005,"group":1,"subgroup":28,"version":4,"gender":0,"tone":4},{"label":"woman playing water polo: dark skin tone","hexcode":"1F93D-1F3FF-200D-2640-FE0F","emoji":"🤽🏿‍♀️","text":"","type":1,"order":3007,"group":1,"subgroup":28,"version":4,"gender":0,"tone":5}]},{"label":"person playing handball","hexcode":"1F93E","tags":["athletics","ball","catch","chuck","handball","hurl","lob","person","pitch","playing","sport","throw","toss"],"emoji":"🤾","text":"","type":1,"order":3009,"group":1,"subgroup":28,"version":3,"skins":[{"label":"person playing handball: light skin tone","hexcode":"1F93E-1F3FB","emoji":"🤾🏻","text":"","type":1,"order":3010,"group":1,"subgroup":28,"version":3,"tone":1},{"label":"person playing handball: medium-light skin tone","hexcode":"1F93E-1F3FC","emoji":"🤾🏼","text":"","type":1,"order":3011,"group":1,"subgroup":28,"version":3,"tone":2},{"label":"person playing handball: medium skin tone","hexcode":"1F93E-1F3FD","emoji":"🤾🏽","text":"","type":1,"order":3012,"group":1,"subgroup":28,"version":3,"tone":3},{"label":"person playing handball: medium-dark skin tone","hexcode":"1F93E-1F3FE","emoji":"🤾🏾","text":"","type":1,"order":3013,"group":1,"subgroup":28,"version":3,"tone":4},{"label":"person playing handball: dark skin tone","hexcode":"1F93E-1F3FF","emoji":"🤾🏿","text":"","type":1,"order":3014,"group":1,"subgroup":28,"version":3,"tone":5}]},{"label":"man playing handball","hexcode":"1F93E-200D-2642-FE0F","tags":["athletics","ball","catch","chuck","handball","hurl","lob","man","pitch","playing","sport","throw","toss"],"emoji":"🤾‍♂️","text":"","type":1,"order":3015,"group":1,"subgroup":28,"version":4,"gender":1,"skins":[{"label":"man playing handball: light skin tone","hexcode":"1F93E-1F3FB-200D-2642-FE0F","emoji":"🤾🏻‍♂️","text":"","type":1,"order":3017,"group":1,"subgroup":28,"version":4,"gender":1,"tone":1},{"label":"man playing handball: medium-light skin tone","hexcode":"1F93E-1F3FC-200D-2642-FE0F","emoji":"🤾🏼‍♂️","text":"","type":1,"order":3019,"group":1,"subgroup":28,"version":4,"gender":1,"tone":2},{"label":"man playing handball: medium skin tone","hexcode":"1F93E-1F3FD-200D-2642-FE0F","emoji":"🤾🏽‍♂️","text":"","type":1,"order":3021,"group":1,"subgroup":28,"version":4,"gender":1,"tone":3},{"label":"man playing handball: medium-dark skin tone","hexcode":"1F93E-1F3FE-200D-2642-FE0F","emoji":"🤾🏾‍♂️","text":"","type":1,"order":3023,"group":1,"subgroup":28,"version":4,"gender":1,"tone":4},{"label":"man playing handball: dark skin tone","hexcode":"1F93E-1F3FF-200D-2642-FE0F","emoji":"🤾🏿‍♂️","text":"","type":1,"order":3025,"group":1,"subgroup":28,"version":4,"gender":1,"tone":5}]},{"label":"woman playing handball","hexcode":"1F93E-200D-2640-FE0F","tags":["athletics","ball","catch","chuck","handball","hurl","lob","pitch","playing","sport","throw","toss","woman"],"emoji":"🤾‍♀️","text":"","type":1,"order":3027,"group":1,"subgroup":28,"version":4,"gender":0,"skins":[{"label":"woman playing handball: light skin tone","hexcode":"1F93E-1F3FB-200D-2640-FE0F","emoji":"🤾🏻‍♀️","text":"","type":1,"order":3029,"group":1,"subgroup":28,"version":4,"gender":0,"tone":1},{"label":"woman playing handball: medium-light skin tone","hexcode":"1F93E-1F3FC-200D-2640-FE0F","emoji":"🤾🏼‍♀️","text":"","type":1,"order":3031,"group":1,"subgroup":28,"version":4,"gender":0,"tone":2},{"label":"woman playing handball: medium skin tone","hexcode":"1F93E-1F3FD-200D-2640-FE0F","emoji":"🤾🏽‍♀️","text":"","type":1,"order":3033,"group":1,"subgroup":28,"version":4,"gender":0,"tone":3},{"label":"woman playing handball: medium-dark skin tone","hexcode":"1F93E-1F3FE-200D-2640-FE0F","emoji":"🤾🏾‍♀️","text":"","type":1,"order":3035,"group":1,"subgroup":28,"version":4,"gender":0,"tone":4},{"label":"woman playing handball: dark skin tone","hexcode":"1F93E-1F3FF-200D-2640-FE0F","emoji":"🤾🏿‍♀️","text":"","type":1,"order":3037,"group":1,"subgroup":28,"version":4,"gender":0,"tone":5}]},{"label":"person juggling","hexcode":"1F939","tags":["act","balance","balancing","handle","juggle","juggling","manage","multitask","person","skill"],"emoji":"🤹","text":"","type":1,"order":3039,"group":1,"subgroup":28,"version":3,"skins":[{"label":"person juggling: light skin tone","hexcode":"1F939-1F3FB","emoji":"🤹🏻","text":"","type":1,"order":3040,"group":1,"subgroup":28,"version":3,"tone":1},{"label":"person juggling: medium-light skin tone","hexcode":"1F939-1F3FC","emoji":"🤹🏼","text":"","type":1,"order":3041,"group":1,"subgroup":28,"version":3,"tone":2},{"label":"person juggling: medium skin tone","hexcode":"1F939-1F3FD","emoji":"🤹🏽","text":"","type":1,"order":3042,"group":1,"subgroup":28,"version":3,"tone":3},{"label":"person juggling: medium-dark skin tone","hexcode":"1F939-1F3FE","emoji":"🤹🏾","text":"","type":1,"order":3043,"group":1,"subgroup":28,"version":3,"tone":4},{"label":"person juggling: dark skin tone","hexcode":"1F939-1F3FF","emoji":"🤹🏿","text":"","type":1,"order":3044,"group":1,"subgroup":28,"version":3,"tone":5}]},{"label":"man juggling","hexcode":"1F939-200D-2642-FE0F","tags":["act","balance","balancing","handle","juggle","juggling","man","manage","multitask","skill"],"emoji":"🤹‍♂️","text":"","type":1,"order":3045,"group":1,"subgroup":28,"version":4,"gender":1,"skins":[{"label":"man juggling: light skin tone","hexcode":"1F939-1F3FB-200D-2642-FE0F","emoji":"🤹🏻‍♂️","text":"","type":1,"order":3047,"group":1,"subgroup":28,"version":4,"gender":1,"tone":1},{"label":"man juggling: medium-light skin tone","hexcode":"1F939-1F3FC-200D-2642-FE0F","emoji":"🤹🏼‍♂️","text":"","type":1,"order":3049,"group":1,"subgroup":28,"version":4,"gender":1,"tone":2},{"label":"man juggling: medium skin tone","hexcode":"1F939-1F3FD-200D-2642-FE0F","emoji":"🤹🏽‍♂️","text":"","type":1,"order":3051,"group":1,"subgroup":28,"version":4,"gender":1,"tone":3},{"label":"man juggling: medium-dark skin tone","hexcode":"1F939-1F3FE-200D-2642-FE0F","emoji":"🤹🏾‍♂️","text":"","type":1,"order":3053,"group":1,"subgroup":28,"version":4,"gender":1,"tone":4},{"label":"man juggling: dark skin tone","hexcode":"1F939-1F3FF-200D-2642-FE0F","emoji":"🤹🏿‍♂️","text":"","type":1,"order":3055,"group":1,"subgroup":28,"version":4,"gender":1,"tone":5}]},{"label":"woman juggling","hexcode":"1F939-200D-2640-FE0F","tags":["act","balance","balancing","handle","juggle","juggling","manage","multitask","skill","woman"],"emoji":"🤹‍♀️","text":"","type":1,"order":3057,"group":1,"subgroup":28,"version":4,"gender":0,"skins":[{"label":"woman juggling: light skin tone","hexcode":"1F939-1F3FB-200D-2640-FE0F","emoji":"🤹🏻‍♀️","text":"","type":1,"order":3059,"group":1,"subgroup":28,"version":4,"gender":0,"tone":1},{"label":"woman juggling: medium-light skin tone","hexcode":"1F939-1F3FC-200D-2640-FE0F","emoji":"🤹🏼‍♀️","text":"","type":1,"order":3061,"group":1,"subgroup":28,"version":4,"gender":0,"tone":2},{"label":"woman juggling: medium skin tone","hexcode":"1F939-1F3FD-200D-2640-FE0F","emoji":"🤹🏽‍♀️","text":"","type":1,"order":3063,"group":1,"subgroup":28,"version":4,"gender":0,"tone":3},{"label":"woman juggling: medium-dark skin tone","hexcode":"1F939-1F3FE-200D-2640-FE0F","emoji":"🤹🏾‍♀️","text":"","type":1,"order":3065,"group":1,"subgroup":28,"version":4,"gender":0,"tone":4},{"label":"woman juggling: dark skin tone","hexcode":"1F939-1F3FF-200D-2640-FE0F","emoji":"🤹🏿‍♀️","text":"","type":1,"order":3067,"group":1,"subgroup":28,"version":4,"gender":0,"tone":5}]},{"label":"person in lotus position","hexcode":"1F9D8","tags":["cross","legged","legs","lotus","meditation","peace","person","position","relax","serenity","yoga","yogi","zen"],"emoji":"🧘","text":"","type":1,"order":3069,"group":1,"subgroup":29,"version":5,"skins":[{"label":"person in lotus position: light skin tone","hexcode":"1F9D8-1F3FB","emoji":"🧘🏻","text":"","type":1,"order":3070,"group":1,"subgroup":29,"version":5,"tone":1},{"label":"person in lotus position: medium-light skin tone","hexcode":"1F9D8-1F3FC","emoji":"🧘🏼","text":"","type":1,"order":3071,"group":1,"subgroup":29,"version":5,"tone":2},{"label":"person in lotus position: medium skin tone","hexcode":"1F9D8-1F3FD","emoji":"🧘🏽","text":"","type":1,"order":3072,"group":1,"subgroup":29,"version":5,"tone":3},{"label":"person in lotus position: medium-dark skin tone","hexcode":"1F9D8-1F3FE","emoji":"🧘🏾","text":"","type":1,"order":3073,"group":1,"subgroup":29,"version":5,"tone":4},{"label":"person in lotus position: dark skin tone","hexcode":"1F9D8-1F3FF","emoji":"🧘🏿","text":"","type":1,"order":3074,"group":1,"subgroup":29,"version":5,"tone":5}]},{"label":"man in lotus position","hexcode":"1F9D8-200D-2642-FE0F","tags":["cross","legged","legs","lotus","man","meditation","peace","position","relax","serenity","yoga","yogi","zen"],"emoji":"🧘‍♂️","text":"","type":1,"order":3075,"group":1,"subgroup":29,"version":5,"gender":1,"skins":[{"label":"man in lotus position: light skin tone","hexcode":"1F9D8-1F3FB-200D-2642-FE0F","emoji":"🧘🏻‍♂️","text":"","type":1,"order":3077,"group":1,"subgroup":29,"version":5,"gender":1,"tone":1},{"label":"man in lotus position: medium-light skin tone","hexcode":"1F9D8-1F3FC-200D-2642-FE0F","emoji":"🧘🏼‍♂️","text":"","type":1,"order":3079,"group":1,"subgroup":29,"version":5,"gender":1,"tone":2},{"label":"man in lotus position: medium skin tone","hexcode":"1F9D8-1F3FD-200D-2642-FE0F","emoji":"🧘🏽‍♂️","text":"","type":1,"order":3081,"group":1,"subgroup":29,"version":5,"gender":1,"tone":3},{"label":"man in lotus position: medium-dark skin tone","hexcode":"1F9D8-1F3FE-200D-2642-FE0F","emoji":"🧘🏾‍♂️","text":"","type":1,"order":3083,"group":1,"subgroup":29,"version":5,"gender":1,"tone":4},{"label":"man in lotus position: dark skin tone","hexcode":"1F9D8-1F3FF-200D-2642-FE0F","emoji":"🧘🏿‍♂️","text":"","type":1,"order":3085,"group":1,"subgroup":29,"version":5,"gender":1,"tone":5}]},{"label":"woman in lotus position","hexcode":"1F9D8-200D-2640-FE0F","tags":["cross","legged","legs","lotus","meditation","peace","position","relax","serenity","woman","yoga","yogi","zen"],"emoji":"🧘‍♀️","text":"","type":1,"order":3087,"group":1,"subgroup":29,"version":5,"gender":0,"skins":[{"label":"woman in lotus position: light skin tone","hexcode":"1F9D8-1F3FB-200D-2640-FE0F","emoji":"🧘🏻‍♀️","text":"","type":1,"order":3089,"group":1,"subgroup":29,"version":5,"gender":0,"tone":1},{"label":"woman in lotus position: medium-light skin tone","hexcode":"1F9D8-1F3FC-200D-2640-FE0F","emoji":"🧘🏼‍♀️","text":"","type":1,"order":3091,"group":1,"subgroup":29,"version":5,"gender":0,"tone":2},{"label":"woman in lotus position: medium skin tone","hexcode":"1F9D8-1F3FD-200D-2640-FE0F","emoji":"🧘🏽‍♀️","text":"","type":1,"order":3093,"group":1,"subgroup":29,"version":5,"gender":0,"tone":3},{"label":"woman in lotus position: medium-dark skin tone","hexcode":"1F9D8-1F3FE-200D-2640-FE0F","emoji":"🧘🏾‍♀️","text":"","type":1,"order":3095,"group":1,"subgroup":29,"version":5,"gender":0,"tone":4},{"label":"woman in lotus position: dark skin tone","hexcode":"1F9D8-1F3FF-200D-2640-FE0F","emoji":"🧘🏿‍♀️","text":"","type":1,"order":3097,"group":1,"subgroup":29,"version":5,"gender":0,"tone":5}]},{"label":"person taking bath","hexcode":"1F6C0","tags":["bath","bathtub","person","taking","tub"],"emoji":"🛀","text":"","type":1,"order":3099,"group":1,"subgroup":29,"version":0.6,"skins":[{"label":"person taking bath: light skin tone","hexcode":"1F6C0-1F3FB","emoji":"🛀🏻","text":"","type":1,"order":3100,"group":1,"subgroup":29,"version":1,"tone":1},{"label":"person taking bath: medium-light skin tone","hexcode":"1F6C0-1F3FC","emoji":"🛀🏼","text":"","type":1,"order":3101,"group":1,"subgroup":29,"version":1,"tone":2},{"label":"person taking bath: medium skin tone","hexcode":"1F6C0-1F3FD","emoji":"🛀🏽","text":"","type":1,"order":3102,"group":1,"subgroup":29,"version":1,"tone":3},{"label":"person taking bath: medium-dark skin tone","hexcode":"1F6C0-1F3FE","emoji":"🛀🏾","text":"","type":1,"order":3103,"group":1,"subgroup":29,"version":1,"tone":4},{"label":"person taking bath: dark skin tone","hexcode":"1F6C0-1F3FF","emoji":"🛀🏿","text":"","type":1,"order":3104,"group":1,"subgroup":29,"version":1,"tone":5}]},{"label":"person in bed","hexcode":"1F6CC","tags":["bed","bedtime","good","goodnight","hotel","nap","night","person","sleep","tired","zzz"],"emoji":"🛌","text":"","type":1,"order":3105,"group":1,"subgroup":29,"version":1,"skins":[{"label":"person in bed: light skin tone","hexcode":"1F6CC-1F3FB","emoji":"🛌🏻","text":"","type":1,"order":3106,"group":1,"subgroup":29,"version":4,"tone":1},{"label":"person in bed: medium-light skin tone","hexcode":"1F6CC-1F3FC","emoji":"🛌🏼","text":"","type":1,"order":3107,"group":1,"subgroup":29,"version":4,"tone":2},{"label":"person in bed: medium skin tone","hexcode":"1F6CC-1F3FD","emoji":"🛌🏽","text":"","type":1,"order":3108,"group":1,"subgroup":29,"version":4,"tone":3},{"label":"person in bed: medium-dark skin tone","hexcode":"1F6CC-1F3FE","emoji":"🛌🏾","text":"","type":1,"order":3109,"group":1,"subgroup":29,"version":4,"tone":4},{"label":"person in bed: dark skin tone","hexcode":"1F6CC-1F3FF","emoji":"🛌🏿","text":"","type":1,"order":3110,"group":1,"subgroup":29,"version":4,"tone":5}]},{"label":"people holding hands","hexcode":"1F9D1-200D-1F91D-200D-1F9D1","tags":["bae","bestie","bff","couple","dating","flirt","friends","hand","hold","people","twins"],"emoji":"🧑‍🤝‍🧑","text":"","type":1,"order":3111,"group":1,"subgroup":30,"version":12,"skins":[{"label":"people holding hands: light skin tone","hexcode":"1F9D1-1F3FB-200D-1F91D-200D-1F9D1-1F3FB","emoji":"🧑🏻‍🤝‍🧑🏻","text":"","type":1,"order":3112,"group":1,"subgroup":30,"version":12,"tone":1},{"label":"people holding hands: light skin tone, medium-light skin tone","hexcode":"1F9D1-1F3FB-200D-1F91D-200D-1F9D1-1F3FC","emoji":"🧑🏻‍🤝‍🧑🏼","text":"","type":1,"order":3113,"group":1,"subgroup":30,"version":12.1,"tone":[1,2]},{"label":"people holding hands: light skin tone, medium skin tone","hexcode":"1F9D1-1F3FB-200D-1F91D-200D-1F9D1-1F3FD","emoji":"🧑🏻‍🤝‍🧑🏽","text":"","type":1,"order":3114,"group":1,"subgroup":30,"version":12.1,"tone":[1,3]},{"label":"people holding hands: light skin tone, medium-dark skin tone","hexcode":"1F9D1-1F3FB-200D-1F91D-200D-1F9D1-1F3FE","emoji":"🧑🏻‍🤝‍🧑🏾","text":"","type":1,"order":3115,"group":1,"subgroup":30,"version":12.1,"tone":[1,4]},{"label":"people holding hands: light skin tone, dark skin tone","hexcode":"1F9D1-1F3FB-200D-1F91D-200D-1F9D1-1F3FF","emoji":"🧑🏻‍🤝‍🧑🏿","text":"","type":1,"order":3116,"group":1,"subgroup":30,"version":12.1,"tone":[1,5]},{"label":"people holding hands: medium-light skin tone, light skin tone","hexcode":"1F9D1-1F3FC-200D-1F91D-200D-1F9D1-1F3FB","emoji":"🧑🏼‍🤝‍🧑🏻","text":"","type":1,"order":3117,"group":1,"subgroup":30,"version":12,"tone":[2,1]},{"label":"people holding hands: medium-light skin tone","hexcode":"1F9D1-1F3FC-200D-1F91D-200D-1F9D1-1F3FC","emoji":"🧑🏼‍🤝‍🧑🏼","text":"","type":1,"order":3118,"group":1,"subgroup":30,"version":12,"tone":2},{"label":"people holding hands: medium-light skin tone, medium skin tone","hexcode":"1F9D1-1F3FC-200D-1F91D-200D-1F9D1-1F3FD","emoji":"🧑🏼‍🤝‍🧑🏽","text":"","type":1,"order":3119,"group":1,"subgroup":30,"version":12.1,"tone":[2,3]},{"label":"people holding hands: medium-light skin tone, medium-dark skin tone","hexcode":"1F9D1-1F3FC-200D-1F91D-200D-1F9D1-1F3FE","emoji":"🧑🏼‍🤝‍🧑🏾","text":"","type":1,"order":3120,"group":1,"subgroup":30,"version":12.1,"tone":[2,4]},{"label":"people holding hands: medium-light skin tone, dark skin tone","hexcode":"1F9D1-1F3FC-200D-1F91D-200D-1F9D1-1F3FF","emoji":"🧑🏼‍🤝‍🧑🏿","text":"","type":1,"order":3121,"group":1,"subgroup":30,"version":12.1,"tone":[2,5]},{"label":"people holding hands: medium skin tone, light skin tone","hexcode":"1F9D1-1F3FD-200D-1F91D-200D-1F9D1-1F3FB","emoji":"🧑🏽‍🤝‍🧑🏻","text":"","type":1,"order":3122,"group":1,"subgroup":30,"version":12,"tone":[3,1]},{"label":"people holding hands: medium skin tone, medium-light skin tone","hexcode":"1F9D1-1F3FD-200D-1F91D-200D-1F9D1-1F3FC","emoji":"🧑🏽‍🤝‍🧑🏼","text":"","type":1,"order":3123,"group":1,"subgroup":30,"version":12,"tone":[3,2]},{"label":"people holding hands: medium skin tone","hexcode":"1F9D1-1F3FD-200D-1F91D-200D-1F9D1-1F3FD","emoji":"🧑🏽‍🤝‍🧑🏽","text":"","type":1,"order":3124,"group":1,"subgroup":30,"version":12,"tone":3},{"label":"people holding hands: medium skin tone, medium-dark skin tone","hexcode":"1F9D1-1F3FD-200D-1F91D-200D-1F9D1-1F3FE","emoji":"🧑🏽‍🤝‍🧑🏾","text":"","type":1,"order":3125,"group":1,"subgroup":30,"version":12.1,"tone":[3,4]},{"label":"people holding hands: medium skin tone, dark skin tone","hexcode":"1F9D1-1F3FD-200D-1F91D-200D-1F9D1-1F3FF","emoji":"🧑🏽‍🤝‍🧑🏿","text":"","type":1,"order":3126,"group":1,"subgroup":30,"version":12.1,"tone":[3,5]},{"label":"people holding hands: medium-dark skin tone, light skin tone","hexcode":"1F9D1-1F3FE-200D-1F91D-200D-1F9D1-1F3FB","emoji":"🧑🏾‍🤝‍🧑🏻","text":"","type":1,"order":3127,"group":1,"subgroup":30,"version":12,"tone":[4,1]},{"label":"people holding hands: medium-dark skin tone, medium-light skin tone","hexcode":"1F9D1-1F3FE-200D-1F91D-200D-1F9D1-1F3FC","emoji":"🧑🏾‍🤝‍🧑🏼","text":"","type":1,"order":3128,"group":1,"subgroup":30,"version":12,"tone":[4,2]},{"label":"people holding hands: medium-dark skin tone, medium skin tone","hexcode":"1F9D1-1F3FE-200D-1F91D-200D-1F9D1-1F3FD","emoji":"🧑🏾‍🤝‍🧑🏽","text":"","type":1,"order":3129,"group":1,"subgroup":30,"version":12,"tone":[4,3]},{"label":"people holding hands: medium-dark skin tone","hexcode":"1F9D1-1F3FE-200D-1F91D-200D-1F9D1-1F3FE","emoji":"🧑🏾‍🤝‍🧑🏾","text":"","type":1,"order":3130,"group":1,"subgroup":30,"version":12,"tone":4},{"label":"people holding hands: medium-dark skin tone, dark skin tone","hexcode":"1F9D1-1F3FE-200D-1F91D-200D-1F9D1-1F3FF","emoji":"🧑🏾‍🤝‍🧑🏿","text":"","type":1,"order":3131,"group":1,"subgroup":30,"version":12.1,"tone":[4,5]},{"label":"people holding hands: dark skin tone, light skin tone","hexcode":"1F9D1-1F3FF-200D-1F91D-200D-1F9D1-1F3FB","emoji":"🧑🏿‍🤝‍🧑🏻","text":"","type":1,"order":3132,"group":1,"subgroup":30,"version":12,"tone":[5,1]},{"label":"people holding hands: dark skin tone, medium-light skin tone","hexcode":"1F9D1-1F3FF-200D-1F91D-200D-1F9D1-1F3FC","emoji":"🧑🏿‍🤝‍🧑🏼","text":"","type":1,"order":3133,"group":1,"subgroup":30,"version":12,"tone":[5,2]},{"label":"people holding hands: dark skin tone, medium skin tone","hexcode":"1F9D1-1F3FF-200D-1F91D-200D-1F9D1-1F3FD","emoji":"🧑🏿‍🤝‍🧑🏽","text":"","type":1,"order":3134,"group":1,"subgroup":30,"version":12,"tone":[5,3]},{"label":"people holding hands: dark skin tone, medium-dark skin tone","hexcode":"1F9D1-1F3FF-200D-1F91D-200D-1F9D1-1F3FE","emoji":"🧑🏿‍🤝‍🧑🏾","text":"","type":1,"order":3135,"group":1,"subgroup":30,"version":12,"tone":[5,4]},{"label":"people holding hands: dark skin tone","hexcode":"1F9D1-1F3FF-200D-1F91D-200D-1F9D1-1F3FF","emoji":"🧑🏿‍🤝‍🧑🏿","text":"","type":1,"order":3136,"group":1,"subgroup":30,"version":12,"tone":5}]},{"label":"women holding hands","hexcode":"1F46D","tags":["bae","bestie","bff","couple","dating","flirt","friends","girls","hand","hold","sisters","twins","women"],"emoji":"👭","text":"","type":1,"order":3137,"group":1,"subgroup":30,"version":1,"skins":[{"label":"women holding hands: light skin tone","hexcode":"1F46D-1F3FB","emoji":"👭🏻","text":"","type":1,"order":3138,"group":1,"subgroup":30,"version":12,"tone":1},{"label":"women holding hands: medium-light skin tone","hexcode":"1F46D-1F3FC","emoji":"👭🏼","text":"","type":1,"order":3144,"group":1,"subgroup":30,"version":12,"tone":2},{"label":"women holding hands: medium skin tone","hexcode":"1F46D-1F3FD","emoji":"👭🏽","text":"","type":1,"order":3150,"group":1,"subgroup":30,"version":12,"tone":3},{"label":"women holding hands: medium-dark skin tone","hexcode":"1F46D-1F3FE","emoji":"👭🏾","text":"","type":1,"order":3156,"group":1,"subgroup":30,"version":12,"tone":4},{"label":"women holding hands: dark skin tone","hexcode":"1F46D-1F3FF","emoji":"👭🏿","text":"","type":1,"order":3162,"group":1,"subgroup":30,"version":12,"tone":5},{"label":"women holding hands: light skin tone, medium-light skin tone","hexcode":"1F469-1F3FB-200D-1F91D-200D-1F469-1F3FC","emoji":"👩🏻‍🤝‍👩🏼","text":"","type":1,"order":3139,"group":1,"subgroup":30,"version":12.1,"tone":[1,2]},{"label":"women holding hands: light skin tone, medium skin tone","hexcode":"1F469-1F3FB-200D-1F91D-200D-1F469-1F3FD","emoji":"👩🏻‍🤝‍👩🏽","text":"","type":1,"order":3140,"group":1,"subgroup":30,"version":12.1,"tone":[1,3]},{"label":"women holding hands: light skin tone, medium-dark skin tone","hexcode":"1F469-1F3FB-200D-1F91D-200D-1F469-1F3FE","emoji":"👩🏻‍🤝‍👩🏾","text":"","type":1,"order":3141,"group":1,"subgroup":30,"version":12.1,"tone":[1,4]},{"label":"women holding hands: light skin tone, dark skin tone","hexcode":"1F469-1F3FB-200D-1F91D-200D-1F469-1F3FF","emoji":"👩🏻‍🤝‍👩🏿","text":"","type":1,"order":3142,"group":1,"subgroup":30,"version":12.1,"tone":[1,5]},{"label":"women holding hands: medium-light skin tone, light skin tone","hexcode":"1F469-1F3FC-200D-1F91D-200D-1F469-1F3FB","emoji":"👩🏼‍🤝‍👩🏻","text":"","type":1,"order":3143,"group":1,"subgroup":30,"version":12,"tone":[2,1]},{"label":"women holding hands: medium-light skin tone, medium skin tone","hexcode":"1F469-1F3FC-200D-1F91D-200D-1F469-1F3FD","emoji":"👩🏼‍🤝‍👩🏽","text":"","type":1,"order":3145,"group":1,"subgroup":30,"version":12.1,"tone":[2,3]},{"label":"women holding hands: medium-light skin tone, medium-dark skin tone","hexcode":"1F469-1F3FC-200D-1F91D-200D-1F469-1F3FE","emoji":"👩🏼‍🤝‍👩🏾","text":"","type":1,"order":3146,"group":1,"subgroup":30,"version":12.1,"tone":[2,4]},{"label":"women holding hands: medium-light skin tone, dark skin tone","hexcode":"1F469-1F3FC-200D-1F91D-200D-1F469-1F3FF","emoji":"👩🏼‍🤝‍👩🏿","text":"","type":1,"order":3147,"group":1,"subgroup":30,"version":12.1,"tone":[2,5]},{"label":"women holding hands: medium skin tone, light skin tone","hexcode":"1F469-1F3FD-200D-1F91D-200D-1F469-1F3FB","emoji":"👩🏽‍🤝‍👩🏻","text":"","type":1,"order":3148,"group":1,"subgroup":30,"version":12,"tone":[3,1]},{"label":"women holding hands: medium skin tone, medium-light skin tone","hexcode":"1F469-1F3FD-200D-1F91D-200D-1F469-1F3FC","emoji":"👩🏽‍🤝‍👩🏼","text":"","type":1,"order":3149,"group":1,"subgroup":30,"version":12,"tone":[3,2]},{"label":"women holding hands: medium skin tone, medium-dark skin tone","hexcode":"1F469-1F3FD-200D-1F91D-200D-1F469-1F3FE","emoji":"👩🏽‍🤝‍👩🏾","text":"","type":1,"order":3151,"group":1,"subgroup":30,"version":12.1,"tone":[3,4]},{"label":"women holding hands: medium skin tone, dark skin tone","hexcode":"1F469-1F3FD-200D-1F91D-200D-1F469-1F3FF","emoji":"👩🏽‍🤝‍👩🏿","text":"","type":1,"order":3152,"group":1,"subgroup":30,"version":12.1,"tone":[3,5]},{"label":"women holding hands: medium-dark skin tone, light skin tone","hexcode":"1F469-1F3FE-200D-1F91D-200D-1F469-1F3FB","emoji":"👩🏾‍🤝‍👩🏻","text":"","type":1,"order":3153,"group":1,"subgroup":30,"version":12,"tone":[4,1]},{"label":"women holding hands: medium-dark skin tone, medium-light skin tone","hexcode":"1F469-1F3FE-200D-1F91D-200D-1F469-1F3FC","emoji":"👩🏾‍🤝‍👩🏼","text":"","type":1,"order":3154,"group":1,"subgroup":30,"version":12,"tone":[4,2]},{"label":"women holding hands: medium-dark skin tone, medium skin tone","hexcode":"1F469-1F3FE-200D-1F91D-200D-1F469-1F3FD","emoji":"👩🏾‍🤝‍👩🏽","text":"","type":1,"order":3155,"group":1,"subgroup":30,"version":12,"tone":[4,3]},{"label":"women holding hands: medium-dark skin tone, dark skin tone","hexcode":"1F469-1F3FE-200D-1F91D-200D-1F469-1F3FF","emoji":"👩🏾‍🤝‍👩🏿","text":"","type":1,"order":3157,"group":1,"subgroup":30,"version":12.1,"tone":[4,5]},{"label":"women holding hands: dark skin tone, light skin tone","hexcode":"1F469-1F3FF-200D-1F91D-200D-1F469-1F3FB","emoji":"👩🏿‍🤝‍👩🏻","text":"","type":1,"order":3158,"group":1,"subgroup":30,"version":12,"tone":[5,1]},{"label":"women holding hands: dark skin tone, medium-light skin tone","hexcode":"1F469-1F3FF-200D-1F91D-200D-1F469-1F3FC","emoji":"👩🏿‍🤝‍👩🏼","text":"","type":1,"order":3159,"group":1,"subgroup":30,"version":12,"tone":[5,2]},{"label":"women holding hands: dark skin tone, medium skin tone","hexcode":"1F469-1F3FF-200D-1F91D-200D-1F469-1F3FD","emoji":"👩🏿‍🤝‍👩🏽","text":"","type":1,"order":3160,"group":1,"subgroup":30,"version":12,"tone":[5,3]},{"label":"women holding hands: dark skin tone, medium-dark skin tone","hexcode":"1F469-1F3FF-200D-1F91D-200D-1F469-1F3FE","emoji":"👩🏿‍🤝‍👩🏾","text":"","type":1,"order":3161,"group":1,"subgroup":30,"version":12,"tone":[5,4]}]},{"label":"woman and man holding hands","hexcode":"1F46B","tags":["bae","bestie","bff","couple","dating","flirt","friends","hand","hold","man","twins","woman"],"emoji":"👫","text":"","type":1,"order":3163,"group":1,"subgroup":30,"version":0.6,"skins":[{"label":"woman and man holding hands: light skin tone","hexcode":"1F46B-1F3FB","emoji":"👫🏻","text":"","type":1,"order":3164,"group":1,"subgroup":30,"version":12,"tone":1},{"label":"woman and man holding hands: medium-light skin tone","hexcode":"1F46B-1F3FC","emoji":"👫🏼","text":"","type":1,"order":3170,"group":1,"subgroup":30,"version":12,"tone":2},{"label":"woman and man holding hands: medium skin tone","hexcode":"1F46B-1F3FD","emoji":"👫🏽","text":"","type":1,"order":3176,"group":1,"subgroup":30,"version":12,"tone":3},{"label":"woman and man holding hands: medium-dark skin tone","hexcode":"1F46B-1F3FE","emoji":"👫🏾","text":"","type":1,"order":3182,"group":1,"subgroup":30,"version":12,"tone":4},{"label":"woman and man holding hands: dark skin tone","hexcode":"1F46B-1F3FF","emoji":"👫🏿","text":"","type":1,"order":3188,"group":1,"subgroup":30,"version":12,"tone":5},{"label":"woman and man holding hands: light skin tone, medium-light skin tone","hexcode":"1F469-1F3FB-200D-1F91D-200D-1F468-1F3FC","emoji":"👩🏻‍🤝‍👨🏼","text":"","type":1,"order":3165,"group":1,"subgroup":30,"version":12,"tone":[1,2]},{"label":"woman and man holding hands: light skin tone, medium skin tone","hexcode":"1F469-1F3FB-200D-1F91D-200D-1F468-1F3FD","emoji":"👩🏻‍🤝‍👨🏽","text":"","type":1,"order":3166,"group":1,"subgroup":30,"version":12,"tone":[1,3]},{"label":"woman and man holding hands: light skin tone, medium-dark skin tone","hexcode":"1F469-1F3FB-200D-1F91D-200D-1F468-1F3FE","emoji":"👩🏻‍🤝‍👨🏾","text":"","type":1,"order":3167,"group":1,"subgroup":30,"version":12,"tone":[1,4]},{"label":"woman and man holding hands: light skin tone, dark skin tone","hexcode":"1F469-1F3FB-200D-1F91D-200D-1F468-1F3FF","emoji":"👩🏻‍🤝‍👨🏿","text":"","type":1,"order":3168,"group":1,"subgroup":30,"version":12,"tone":[1,5]},{"label":"woman and man holding hands: medium-light skin tone, light skin tone","hexcode":"1F469-1F3FC-200D-1F91D-200D-1F468-1F3FB","emoji":"👩🏼‍🤝‍👨🏻","text":"","type":1,"order":3169,"group":1,"subgroup":30,"version":12,"tone":[2,1]},{"label":"woman and man holding hands: medium-light skin tone, medium skin tone","hexcode":"1F469-1F3FC-200D-1F91D-200D-1F468-1F3FD","emoji":"👩🏼‍🤝‍👨🏽","text":"","type":1,"order":3171,"group":1,"subgroup":30,"version":12,"tone":[2,3]},{"label":"woman and man holding hands: medium-light skin tone, medium-dark skin tone","hexcode":"1F469-1F3FC-200D-1F91D-200D-1F468-1F3FE","emoji":"👩🏼‍🤝‍👨🏾","text":"","type":1,"order":3172,"group":1,"subgroup":30,"version":12,"tone":[2,4]},{"label":"woman and man holding hands: medium-light skin tone, dark skin tone","hexcode":"1F469-1F3FC-200D-1F91D-200D-1F468-1F3FF","emoji":"👩🏼‍🤝‍👨🏿","text":"","type":1,"order":3173,"group":1,"subgroup":30,"version":12,"tone":[2,5]},{"label":"woman and man holding hands: medium skin tone, light skin tone","hexcode":"1F469-1F3FD-200D-1F91D-200D-1F468-1F3FB","emoji":"👩🏽‍🤝‍👨🏻","text":"","type":1,"order":3174,"group":1,"subgroup":30,"version":12,"tone":[3,1]},{"label":"woman and man holding hands: medium skin tone, medium-light skin tone","hexcode":"1F469-1F3FD-200D-1F91D-200D-1F468-1F3FC","emoji":"👩🏽‍🤝‍👨🏼","text":"","type":1,"order":3175,"group":1,"subgroup":30,"version":12,"tone":[3,2]},{"label":"woman and man holding hands: medium skin tone, medium-dark skin tone","hexcode":"1F469-1F3FD-200D-1F91D-200D-1F468-1F3FE","emoji":"👩🏽‍🤝‍👨🏾","text":"","type":1,"order":3177,"group":1,"subgroup":30,"version":12,"tone":[3,4]},{"label":"woman and man holding hands: medium skin tone, dark skin tone","hexcode":"1F469-1F3FD-200D-1F91D-200D-1F468-1F3FF","emoji":"👩🏽‍🤝‍👨🏿","text":"","type":1,"order":3178,"group":1,"subgroup":30,"version":12,"tone":[3,5]},{"label":"woman and man holding hands: medium-dark skin tone, light skin tone","hexcode":"1F469-1F3FE-200D-1F91D-200D-1F468-1F3FB","emoji":"👩🏾‍🤝‍👨🏻","text":"","type":1,"order":3179,"group":1,"subgroup":30,"version":12,"tone":[4,1]},{"label":"woman and man holding hands: medium-dark skin tone, medium-light skin tone","hexcode":"1F469-1F3FE-200D-1F91D-200D-1F468-1F3FC","emoji":"👩🏾‍🤝‍👨🏼","text":"","type":1,"order":3180,"group":1,"subgroup":30,"version":12,"tone":[4,2]},{"label":"woman and man holding hands: medium-dark skin tone, medium skin tone","hexcode":"1F469-1F3FE-200D-1F91D-200D-1F468-1F3FD","emoji":"👩🏾‍🤝‍👨🏽","text":"","type":1,"order":3181,"group":1,"subgroup":30,"version":12,"tone":[4,3]},{"label":"woman and man holding hands: medium-dark skin tone, dark skin tone","hexcode":"1F469-1F3FE-200D-1F91D-200D-1F468-1F3FF","emoji":"👩🏾‍🤝‍👨🏿","text":"","type":1,"order":3183,"group":1,"subgroup":30,"version":12,"tone":[4,5]},{"label":"woman and man holding hands: dark skin tone, light skin tone","hexcode":"1F469-1F3FF-200D-1F91D-200D-1F468-1F3FB","emoji":"👩🏿‍🤝‍👨🏻","text":"","type":1,"order":3184,"group":1,"subgroup":30,"version":12,"tone":[5,1]},{"label":"woman and man holding hands: dark skin tone, medium-light skin tone","hexcode":"1F469-1F3FF-200D-1F91D-200D-1F468-1F3FC","emoji":"👩🏿‍🤝‍👨🏼","text":"","type":1,"order":3185,"group":1,"subgroup":30,"version":12,"tone":[5,2]},{"label":"woman and man holding hands: dark skin tone, medium skin tone","hexcode":"1F469-1F3FF-200D-1F91D-200D-1F468-1F3FD","emoji":"👩🏿‍🤝‍👨🏽","text":"","type":1,"order":3186,"group":1,"subgroup":30,"version":12,"tone":[5,3]},{"label":"woman and man holding hands: dark skin tone, medium-dark skin tone","hexcode":"1F469-1F3FF-200D-1F91D-200D-1F468-1F3FE","emoji":"👩🏿‍🤝‍👨🏾","text":"","type":1,"order":3187,"group":1,"subgroup":30,"version":12,"tone":[5,4]}]},{"label":"men holding hands","hexcode":"1F46C","tags":["bae","bestie","bff","boys","brothers","couple","dating","flirt","friends","hand","hold","men","twins"],"emoji":"👬","text":"","type":1,"order":3189,"group":1,"subgroup":30,"version":1,"skins":[{"label":"men holding hands: light skin tone","hexcode":"1F46C-1F3FB","emoji":"👬🏻","text":"","type":1,"order":3190,"group":1,"subgroup":30,"version":12,"tone":1},{"label":"men holding hands: medium-light skin tone","hexcode":"1F46C-1F3FC","emoji":"👬🏼","text":"","type":1,"order":3196,"group":1,"subgroup":30,"version":12,"tone":2},{"label":"men holding hands: medium skin tone","hexcode":"1F46C-1F3FD","emoji":"👬🏽","text":"","type":1,"order":3202,"group":1,"subgroup":30,"version":12,"tone":3},{"label":"men holding hands: medium-dark skin tone","hexcode":"1F46C-1F3FE","emoji":"👬🏾","text":"","type":1,"order":3208,"group":1,"subgroup":30,"version":12,"tone":4},{"label":"men holding hands: dark skin tone","hexcode":"1F46C-1F3FF","emoji":"👬🏿","text":"","type":1,"order":3214,"group":1,"subgroup":30,"version":12,"tone":5},{"label":"men holding hands: light skin tone, medium-light skin tone","hexcode":"1F468-1F3FB-200D-1F91D-200D-1F468-1F3FC","emoji":"👨🏻‍🤝‍👨🏼","text":"","type":1,"order":3191,"group":1,"subgroup":30,"version":12.1,"tone":[1,2]},{"label":"men holding hands: light skin tone, medium skin tone","hexcode":"1F468-1F3FB-200D-1F91D-200D-1F468-1F3FD","emoji":"👨🏻‍🤝‍👨🏽","text":"","type":1,"order":3192,"group":1,"subgroup":30,"version":12.1,"tone":[1,3]},{"label":"men holding hands: light skin tone, medium-dark skin tone","hexcode":"1F468-1F3FB-200D-1F91D-200D-1F468-1F3FE","emoji":"👨🏻‍🤝‍👨🏾","text":"","type":1,"order":3193,"group":1,"subgroup":30,"version":12.1,"tone":[1,4]},{"label":"men holding hands: light skin tone, dark skin tone","hexcode":"1F468-1F3FB-200D-1F91D-200D-1F468-1F3FF","emoji":"👨🏻‍🤝‍👨🏿","text":"","type":1,"order":3194,"group":1,"subgroup":30,"version":12.1,"tone":[1,5]},{"label":"men holding hands: medium-light skin tone, light skin tone","hexcode":"1F468-1F3FC-200D-1F91D-200D-1F468-1F3FB","emoji":"👨🏼‍🤝‍👨🏻","text":"","type":1,"order":3195,"group":1,"subgroup":30,"version":12,"tone":[2,1]},{"label":"men holding hands: medium-light skin tone, medium skin tone","hexcode":"1F468-1F3FC-200D-1F91D-200D-1F468-1F3FD","emoji":"👨🏼‍🤝‍👨🏽","text":"","type":1,"order":3197,"group":1,"subgroup":30,"version":12.1,"tone":[2,3]},{"label":"men holding hands: medium-light skin tone, medium-dark skin tone","hexcode":"1F468-1F3FC-200D-1F91D-200D-1F468-1F3FE","emoji":"👨🏼‍🤝‍👨🏾","text":"","type":1,"order":3198,"group":1,"subgroup":30,"version":12.1,"tone":[2,4]},{"label":"men holding hands: medium-light skin tone, dark skin tone","hexcode":"1F468-1F3FC-200D-1F91D-200D-1F468-1F3FF","emoji":"👨🏼‍🤝‍👨🏿","text":"","type":1,"order":3199,"group":1,"subgroup":30,"version":12.1,"tone":[2,5]},{"label":"men holding hands: medium skin tone, light skin tone","hexcode":"1F468-1F3FD-200D-1F91D-200D-1F468-1F3FB","emoji":"👨🏽‍🤝‍👨🏻","text":"","type":1,"order":3200,"group":1,"subgroup":30,"version":12,"tone":[3,1]},{"label":"men holding hands: medium skin tone, medium-light skin tone","hexcode":"1F468-1F3FD-200D-1F91D-200D-1F468-1F3FC","emoji":"👨🏽‍🤝‍👨🏼","text":"","type":1,"order":3201,"group":1,"subgroup":30,"version":12,"tone":[3,2]},{"label":"men holding hands: medium skin tone, medium-dark skin tone","hexcode":"1F468-1F3FD-200D-1F91D-200D-1F468-1F3FE","emoji":"👨🏽‍🤝‍👨🏾","text":"","type":1,"order":3203,"group":1,"subgroup":30,"version":12.1,"tone":[3,4]},{"label":"men holding hands: medium skin tone, dark skin tone","hexcode":"1F468-1F3FD-200D-1F91D-200D-1F468-1F3FF","emoji":"👨🏽‍🤝‍👨🏿","text":"","type":1,"order":3204,"group":1,"subgroup":30,"version":12.1,"tone":[3,5]},{"label":"men holding hands: medium-dark skin tone, light skin tone","hexcode":"1F468-1F3FE-200D-1F91D-200D-1F468-1F3FB","emoji":"👨🏾‍🤝‍👨🏻","text":"","type":1,"order":3205,"group":1,"subgroup":30,"version":12,"tone":[4,1]},{"label":"men holding hands: medium-dark skin tone, medium-light skin tone","hexcode":"1F468-1F3FE-200D-1F91D-200D-1F468-1F3FC","emoji":"👨🏾‍🤝‍👨🏼","text":"","type":1,"order":3206,"group":1,"subgroup":30,"version":12,"tone":[4,2]},{"label":"men holding hands: medium-dark skin tone, medium skin tone","hexcode":"1F468-1F3FE-200D-1F91D-200D-1F468-1F3FD","emoji":"👨🏾‍🤝‍👨🏽","text":"","type":1,"order":3207,"group":1,"subgroup":30,"version":12,"tone":[4,3]},{"label":"men holding hands: medium-dark skin tone, dark skin tone","hexcode":"1F468-1F3FE-200D-1F91D-200D-1F468-1F3FF","emoji":"👨🏾‍🤝‍👨🏿","text":"","type":1,"order":3209,"group":1,"subgroup":30,"version":12.1,"tone":[4,5]},{"label":"men holding hands: dark skin tone, light skin tone","hexcode":"1F468-1F3FF-200D-1F91D-200D-1F468-1F3FB","emoji":"👨🏿‍🤝‍👨🏻","text":"","type":1,"order":3210,"group":1,"subgroup":30,"version":12,"tone":[5,1]},{"label":"men holding hands: dark skin tone, medium-light skin tone","hexcode":"1F468-1F3FF-200D-1F91D-200D-1F468-1F3FC","emoji":"👨🏿‍🤝‍👨🏼","text":"","type":1,"order":3211,"group":1,"subgroup":30,"version":12,"tone":[5,2]},{"label":"men holding hands: dark skin tone, medium skin tone","hexcode":"1F468-1F3FF-200D-1F91D-200D-1F468-1F3FD","emoji":"👨🏿‍🤝‍👨🏽","text":"","type":1,"order":3212,"group":1,"subgroup":30,"version":12,"tone":[5,3]},{"label":"men holding hands: dark skin tone, medium-dark skin tone","hexcode":"1F468-1F3FF-200D-1F91D-200D-1F468-1F3FE","emoji":"👨🏿‍🤝‍👨🏾","text":"","type":1,"order":3213,"group":1,"subgroup":30,"version":12,"tone":[5,4]}]},{"label":"kiss","hexcode":"1F48F","tags":["anniversary","babe","bae","couple","date","dating","heart","love","mwah","person","romance","together","xoxo"],"emoji":"💏","text":"","type":1,"order":3215,"group":1,"subgroup":30,"version":0.6,"skins":[{"label":"kiss: light skin tone","hexcode":"1F48F-1F3FB","emoji":"💏🏻","text":"","type":1,"order":3216,"group":1,"subgroup":30,"version":13.1,"tone":1},{"label":"kiss: medium-light skin tone","hexcode":"1F48F-1F3FC","emoji":"💏🏼","text":"","type":1,"order":3217,"group":1,"subgroup":30,"version":13.1,"tone":2},{"label":"kiss: medium skin tone","hexcode":"1F48F-1F3FD","emoji":"💏🏽","text":"","type":1,"order":3218,"group":1,"subgroup":30,"version":13.1,"tone":3},{"label":"kiss: medium-dark skin tone","hexcode":"1F48F-1F3FE","emoji":"💏🏾","text":"","type":1,"order":3219,"group":1,"subgroup":30,"version":13.1,"tone":4},{"label":"kiss: dark skin tone","hexcode":"1F48F-1F3FF","emoji":"💏🏿","text":"","type":1,"order":3220,"group":1,"subgroup":30,"version":13.1,"tone":5},{"label":"kiss: person, person, light skin tone, medium-light skin tone","hexcode":"1F9D1-1F3FB-200D-2764-FE0F-200D-1F48B-200D-1F9D1-1F3FC","emoji":"🧑🏻‍❤️‍💋‍🧑🏼","text":"","type":1,"order":3221,"group":1,"subgroup":30,"version":13.1,"tone":[1,2]},{"label":"kiss: person, person, light skin tone, medium skin tone","hexcode":"1F9D1-1F3FB-200D-2764-FE0F-200D-1F48B-200D-1F9D1-1F3FD","emoji":"🧑🏻‍❤️‍💋‍🧑🏽","text":"","type":1,"order":3223,"group":1,"subgroup":30,"version":13.1,"tone":[1,3]},{"label":"kiss: person, person, light skin tone, medium-dark skin tone","hexcode":"1F9D1-1F3FB-200D-2764-FE0F-200D-1F48B-200D-1F9D1-1F3FE","emoji":"🧑🏻‍❤️‍💋‍🧑🏾","text":"","type":1,"order":3225,"group":1,"subgroup":30,"version":13.1,"tone":[1,4]},{"label":"kiss: person, person, light skin tone, dark skin tone","hexcode":"1F9D1-1F3FB-200D-2764-FE0F-200D-1F48B-200D-1F9D1-1F3FF","emoji":"🧑🏻‍❤️‍💋‍🧑🏿","text":"","type":1,"order":3227,"group":1,"subgroup":30,"version":13.1,"tone":[1,5]},{"label":"kiss: person, person, medium-light skin tone, light skin tone","hexcode":"1F9D1-1F3FC-200D-2764-FE0F-200D-1F48B-200D-1F9D1-1F3FB","emoji":"🧑🏼‍❤️‍💋‍🧑🏻","text":"","type":1,"order":3229,"group":1,"subgroup":30,"version":13.1,"tone":[2,1]},{"label":"kiss: person, person, medium-light skin tone, medium skin tone","hexcode":"1F9D1-1F3FC-200D-2764-FE0F-200D-1F48B-200D-1F9D1-1F3FD","emoji":"🧑🏼‍❤️‍💋‍🧑🏽","text":"","type":1,"order":3231,"group":1,"subgroup":30,"version":13.1,"tone":[2,3]},{"label":"kiss: person, person, medium-light skin tone, medium-dark skin tone","hexcode":"1F9D1-1F3FC-200D-2764-FE0F-200D-1F48B-200D-1F9D1-1F3FE","emoji":"🧑🏼‍❤️‍💋‍🧑🏾","text":"","type":1,"order":3233,"group":1,"subgroup":30,"version":13.1,"tone":[2,4]},{"label":"kiss: person, person, medium-light skin tone, dark skin tone","hexcode":"1F9D1-1F3FC-200D-2764-FE0F-200D-1F48B-200D-1F9D1-1F3FF","emoji":"🧑🏼‍❤️‍💋‍🧑🏿","text":"","type":1,"order":3235,"group":1,"subgroup":30,"version":13.1,"tone":[2,5]},{"label":"kiss: person, person, medium skin tone, light skin tone","hexcode":"1F9D1-1F3FD-200D-2764-FE0F-200D-1F48B-200D-1F9D1-1F3FB","emoji":"🧑🏽‍❤️‍💋‍🧑🏻","text":"","type":1,"order":3237,"group":1,"subgroup":30,"version":13.1,"tone":[3,1]},{"label":"kiss: person, person, medium skin tone, medium-light skin tone","hexcode":"1F9D1-1F3FD-200D-2764-FE0F-200D-1F48B-200D-1F9D1-1F3FC","emoji":"🧑🏽‍❤️‍💋‍🧑🏼","text":"","type":1,"order":3239,"group":1,"subgroup":30,"version":13.1,"tone":[3,2]},{"label":"kiss: person, person, medium skin tone, medium-dark skin tone","hexcode":"1F9D1-1F3FD-200D-2764-FE0F-200D-1F48B-200D-1F9D1-1F3FE","emoji":"🧑🏽‍❤️‍💋‍🧑🏾","text":"","type":1,"order":3241,"group":1,"subgroup":30,"version":13.1,"tone":[3,4]},{"label":"kiss: person, person, medium skin tone, dark skin tone","hexcode":"1F9D1-1F3FD-200D-2764-FE0F-200D-1F48B-200D-1F9D1-1F3FF","emoji":"🧑🏽‍❤️‍💋‍🧑🏿","text":"","type":1,"order":3243,"group":1,"subgroup":30,"version":13.1,"tone":[3,5]},{"label":"kiss: person, person, medium-dark skin tone, light skin tone","hexcode":"1F9D1-1F3FE-200D-2764-FE0F-200D-1F48B-200D-1F9D1-1F3FB","emoji":"🧑🏾‍❤️‍💋‍🧑🏻","text":"","type":1,"order":3245,"group":1,"subgroup":30,"version":13.1,"tone":[4,1]},{"label":"kiss: person, person, medium-dark skin tone, medium-light skin tone","hexcode":"1F9D1-1F3FE-200D-2764-FE0F-200D-1F48B-200D-1F9D1-1F3FC","emoji":"🧑🏾‍❤️‍💋‍🧑🏼","text":"","type":1,"order":3247,"group":1,"subgroup":30,"version":13.1,"tone":[4,2]},{"label":"kiss: person, person, medium-dark skin tone, medium skin tone","hexcode":"1F9D1-1F3FE-200D-2764-FE0F-200D-1F48B-200D-1F9D1-1F3FD","emoji":"🧑🏾‍❤️‍💋‍🧑🏽","text":"","type":1,"order":3249,"group":1,"subgroup":30,"version":13.1,"tone":[4,3]},{"label":"kiss: person, person, medium-dark skin tone, dark skin tone","hexcode":"1F9D1-1F3FE-200D-2764-FE0F-200D-1F48B-200D-1F9D1-1F3FF","emoji":"🧑🏾‍❤️‍💋‍🧑🏿","text":"","type":1,"order":3251,"group":1,"subgroup":30,"version":13.1,"tone":[4,5]},{"label":"kiss: person, person, dark skin tone, light skin tone","hexcode":"1F9D1-1F3FF-200D-2764-FE0F-200D-1F48B-200D-1F9D1-1F3FB","emoji":"🧑🏿‍❤️‍💋‍🧑🏻","text":"","type":1,"order":3253,"group":1,"subgroup":30,"version":13.1,"tone":[5,1]},{"label":"kiss: person, person, dark skin tone, medium-light skin tone","hexcode":"1F9D1-1F3FF-200D-2764-FE0F-200D-1F48B-200D-1F9D1-1F3FC","emoji":"🧑🏿‍❤️‍💋‍🧑🏼","text":"","type":1,"order":3255,"group":1,"subgroup":30,"version":13.1,"tone":[5,2]},{"label":"kiss: person, person, dark skin tone, medium skin tone","hexcode":"1F9D1-1F3FF-200D-2764-FE0F-200D-1F48B-200D-1F9D1-1F3FD","emoji":"🧑🏿‍❤️‍💋‍🧑🏽","text":"","type":1,"order":3257,"group":1,"subgroup":30,"version":13.1,"tone":[5,3]},{"label":"kiss: person, person, dark skin tone, medium-dark skin tone","hexcode":"1F9D1-1F3FF-200D-2764-FE0F-200D-1F48B-200D-1F9D1-1F3FE","emoji":"🧑🏿‍❤️‍💋‍🧑🏾","text":"","type":1,"order":3259,"group":1,"subgroup":30,"version":13.1,"tone":[5,4]}]},{"label":"kiss: woman, man","hexcode":"1F469-200D-2764-FE0F-200D-1F48B-200D-1F468","tags":["anniversary","babe","bae","couple","date","dating","heart","kiss","love","man","mwah","person","romance","together","woman","xoxo"],"emoji":"👩‍❤️‍💋‍👨","text":"","type":1,"order":3261,"group":1,"subgroup":30,"version":2,"skins":[{"label":"kiss: woman, man, light skin tone","hexcode":"1F469-1F3FB-200D-2764-FE0F-200D-1F48B-200D-1F468-1F3FB","emoji":"👩🏻‍❤️‍💋‍👨🏻","text":"","type":1,"order":3263,"group":1,"subgroup":30,"version":13.1,"tone":1},{"label":"kiss: woman, man, light skin tone, medium-light skin tone","hexcode":"1F469-1F3FB-200D-2764-FE0F-200D-1F48B-200D-1F468-1F3FC","emoji":"👩🏻‍❤️‍💋‍👨🏼","text":"","type":1,"order":3265,"group":1,"subgroup":30,"version":13.1,"tone":[1,2]},{"label":"kiss: woman, man, light skin tone, medium skin tone","hexcode":"1F469-1F3FB-200D-2764-FE0F-200D-1F48B-200D-1F468-1F3FD","emoji":"👩🏻‍❤️‍💋‍👨🏽","text":"","type":1,"order":3267,"group":1,"subgroup":30,"version":13.1,"tone":[1,3]},{"label":"kiss: woman, man, light skin tone, medium-dark skin tone","hexcode":"1F469-1F3FB-200D-2764-FE0F-200D-1F48B-200D-1F468-1F3FE","emoji":"👩🏻‍❤️‍💋‍👨🏾","text":"","type":1,"order":3269,"group":1,"subgroup":30,"version":13.1,"tone":[1,4]},{"label":"kiss: woman, man, light skin tone, dark skin tone","hexcode":"1F469-1F3FB-200D-2764-FE0F-200D-1F48B-200D-1F468-1F3FF","emoji":"👩🏻‍❤️‍💋‍👨🏿","text":"","type":1,"order":3271,"group":1,"subgroup":30,"version":13.1,"tone":[1,5]},{"label":"kiss: woman, man, medium-light skin tone, light skin tone","hexcode":"1F469-1F3FC-200D-2764-FE0F-200D-1F48B-200D-1F468-1F3FB","emoji":"👩🏼‍❤️‍💋‍👨🏻","text":"","type":1,"order":3273,"group":1,"subgroup":30,"version":13.1,"tone":[2,1]},{"label":"kiss: woman, man, medium-light skin tone","hexcode":"1F469-1F3FC-200D-2764-FE0F-200D-1F48B-200D-1F468-1F3FC","emoji":"👩🏼‍❤️‍💋‍👨🏼","text":"","type":1,"order":3275,"group":1,"subgroup":30,"version":13.1,"tone":2},{"label":"kiss: woman, man, medium-light skin tone, medium skin tone","hexcode":"1F469-1F3FC-200D-2764-FE0F-200D-1F48B-200D-1F468-1F3FD","emoji":"👩🏼‍❤️‍💋‍👨🏽","text":"","type":1,"order":3277,"group":1,"subgroup":30,"version":13.1,"tone":[2,3]},{"label":"kiss: woman, man, medium-light skin tone, medium-dark skin tone","hexcode":"1F469-1F3FC-200D-2764-FE0F-200D-1F48B-200D-1F468-1F3FE","emoji":"👩🏼‍❤️‍💋‍👨🏾","text":"","type":1,"order":3279,"group":1,"subgroup":30,"version":13.1,"tone":[2,4]},{"label":"kiss: woman, man, medium-light skin tone, dark skin tone","hexcode":"1F469-1F3FC-200D-2764-FE0F-200D-1F48B-200D-1F468-1F3FF","emoji":"👩🏼‍❤️‍💋‍👨🏿","text":"","type":1,"order":3281,"group":1,"subgroup":30,"version":13.1,"tone":[2,5]},{"label":"kiss: woman, man, medium skin tone, light skin tone","hexcode":"1F469-1F3FD-200D-2764-FE0F-200D-1F48B-200D-1F468-1F3FB","emoji":"👩🏽‍❤️‍💋‍👨🏻","text":"","type":1,"order":3283,"group":1,"subgroup":30,"version":13.1,"tone":[3,1]},{"label":"kiss: woman, man, medium skin tone, medium-light skin tone","hexcode":"1F469-1F3FD-200D-2764-FE0F-200D-1F48B-200D-1F468-1F3FC","emoji":"👩🏽‍❤️‍💋‍👨🏼","text":"","type":1,"order":3285,"group":1,"subgroup":30,"version":13.1,"tone":[3,2]},{"label":"kiss: woman, man, medium skin tone","hexcode":"1F469-1F3FD-200D-2764-FE0F-200D-1F48B-200D-1F468-1F3FD","emoji":"👩🏽‍❤️‍💋‍👨🏽","text":"","type":1,"order":3287,"group":1,"subgroup":30,"version":13.1,"tone":3},{"label":"kiss: woman, man, medium skin tone, medium-dark skin tone","hexcode":"1F469-1F3FD-200D-2764-FE0F-200D-1F48B-200D-1F468-1F3FE","emoji":"👩🏽‍❤️‍💋‍👨🏾","text":"","type":1,"order":3289,"group":1,"subgroup":30,"version":13.1,"tone":[3,4]},{"label":"kiss: woman, man, medium skin tone, dark skin tone","hexcode":"1F469-1F3FD-200D-2764-FE0F-200D-1F48B-200D-1F468-1F3FF","emoji":"👩🏽‍❤️‍💋‍👨🏿","text":"","type":1,"order":3291,"group":1,"subgroup":30,"version":13.1,"tone":[3,5]},{"label":"kiss: woman, man, medium-dark skin tone, light skin tone","hexcode":"1F469-1F3FE-200D-2764-FE0F-200D-1F48B-200D-1F468-1F3FB","emoji":"👩🏾‍❤️‍💋‍👨🏻","text":"","type":1,"order":3293,"group":1,"subgroup":30,"version":13.1,"tone":[4,1]},{"label":"kiss: woman, man, medium-dark skin tone, medium-light skin tone","hexcode":"1F469-1F3FE-200D-2764-FE0F-200D-1F48B-200D-1F468-1F3FC","emoji":"👩🏾‍❤️‍💋‍👨🏼","text":"","type":1,"order":3295,"group":1,"subgroup":30,"version":13.1,"tone":[4,2]},{"label":"kiss: woman, man, medium-dark skin tone, medium skin tone","hexcode":"1F469-1F3FE-200D-2764-FE0F-200D-1F48B-200D-1F468-1F3FD","emoji":"👩🏾‍❤️‍💋‍👨🏽","text":"","type":1,"order":3297,"group":1,"subgroup":30,"version":13.1,"tone":[4,3]},{"label":"kiss: woman, man, medium-dark skin tone","hexcode":"1F469-1F3FE-200D-2764-FE0F-200D-1F48B-200D-1F468-1F3FE","emoji":"👩🏾‍❤️‍💋‍👨🏾","text":"","type":1,"order":3299,"group":1,"subgroup":30,"version":13.1,"tone":4},{"label":"kiss: woman, man, medium-dark skin tone, dark skin tone","hexcode":"1F469-1F3FE-200D-2764-FE0F-200D-1F48B-200D-1F468-1F3FF","emoji":"👩🏾‍❤️‍💋‍👨🏿","text":"","type":1,"order":3301,"group":1,"subgroup":30,"version":13.1,"tone":[4,5]},{"label":"kiss: woman, man, dark skin tone, light skin tone","hexcode":"1F469-1F3FF-200D-2764-FE0F-200D-1F48B-200D-1F468-1F3FB","emoji":"👩🏿‍❤️‍💋‍👨🏻","text":"","type":1,"order":3303,"group":1,"subgroup":30,"version":13.1,"tone":[5,1]},{"label":"kiss: woman, man, dark skin tone, medium-light skin tone","hexcode":"1F469-1F3FF-200D-2764-FE0F-200D-1F48B-200D-1F468-1F3FC","emoji":"👩🏿‍❤️‍💋‍👨🏼","text":"","type":1,"order":3305,"group":1,"subgroup":30,"version":13.1,"tone":[5,2]},{"label":"kiss: woman, man, dark skin tone, medium skin tone","hexcode":"1F469-1F3FF-200D-2764-FE0F-200D-1F48B-200D-1F468-1F3FD","emoji":"👩🏿‍❤️‍💋‍👨🏽","text":"","type":1,"order":3307,"group":1,"subgroup":30,"version":13.1,"tone":[5,3]},{"label":"kiss: woman, man, dark skin tone, medium-dark skin tone","hexcode":"1F469-1F3FF-200D-2764-FE0F-200D-1F48B-200D-1F468-1F3FE","emoji":"👩🏿‍❤️‍💋‍👨🏾","text":"","type":1,"order":3309,"group":1,"subgroup":30,"version":13.1,"tone":[5,4]},{"label":"kiss: woman, man, dark skin tone","hexcode":"1F469-1F3FF-200D-2764-FE0F-200D-1F48B-200D-1F468-1F3FF","emoji":"👩🏿‍❤️‍💋‍👨🏿","text":"","type":1,"order":3311,"group":1,"subgroup":30,"version":13.1,"tone":5}]},{"label":"kiss: man, man","hexcode":"1F468-200D-2764-FE0F-200D-1F48B-200D-1F468","tags":["anniversary","babe","bae","couple","date","dating","heart","kiss","love","man","mwah","person","romance","together","xoxo"],"emoji":"👨‍❤️‍💋‍👨","text":"","type":1,"order":3313,"group":1,"subgroup":30,"version":2,"skins":[{"label":"kiss: man, man, light skin tone","hexcode":"1F468-1F3FB-200D-2764-FE0F-200D-1F48B-200D-1F468-1F3FB","emoji":"👨🏻‍❤️‍💋‍👨🏻","text":"","type":1,"order":3315,"group":1,"subgroup":30,"version":13.1,"tone":1},{"label":"kiss: man, man, light skin tone, medium-light skin tone","hexcode":"1F468-1F3FB-200D-2764-FE0F-200D-1F48B-200D-1F468-1F3FC","emoji":"👨🏻‍❤️‍💋‍👨🏼","text":"","type":1,"order":3317,"group":1,"subgroup":30,"version":13.1,"tone":[1,2]},{"label":"kiss: man, man, light skin tone, medium skin tone","hexcode":"1F468-1F3FB-200D-2764-FE0F-200D-1F48B-200D-1F468-1F3FD","emoji":"👨🏻‍❤️‍💋‍👨🏽","text":"","type":1,"order":3319,"group":1,"subgroup":30,"version":13.1,"tone":[1,3]},{"label":"kiss: man, man, light skin tone, medium-dark skin tone","hexcode":"1F468-1F3FB-200D-2764-FE0F-200D-1F48B-200D-1F468-1F3FE","emoji":"👨🏻‍❤️‍💋‍👨🏾","text":"","type":1,"order":3321,"group":1,"subgroup":30,"version":13.1,"tone":[1,4]},{"label":"kiss: man, man, light skin tone, dark skin tone","hexcode":"1F468-1F3FB-200D-2764-FE0F-200D-1F48B-200D-1F468-1F3FF","emoji":"👨🏻‍❤️‍💋‍👨🏿","text":"","type":1,"order":3323,"group":1,"subgroup":30,"version":13.1,"tone":[1,5]},{"label":"kiss: man, man, medium-light skin tone, light skin tone","hexcode":"1F468-1F3FC-200D-2764-FE0F-200D-1F48B-200D-1F468-1F3FB","emoji":"👨🏼‍❤️‍💋‍👨🏻","text":"","type":1,"order":3325,"group":1,"subgroup":30,"version":13.1,"tone":[2,1]},{"label":"kiss: man, man, medium-light skin tone","hexcode":"1F468-1F3FC-200D-2764-FE0F-200D-1F48B-200D-1F468-1F3FC","emoji":"👨🏼‍❤️‍💋‍👨🏼","text":"","type":1,"order":3327,"group":1,"subgroup":30,"version":13.1,"tone":2},{"label":"kiss: man, man, medium-light skin tone, medium skin tone","hexcode":"1F468-1F3FC-200D-2764-FE0F-200D-1F48B-200D-1F468-1F3FD","emoji":"👨🏼‍❤️‍💋‍👨🏽","text":"","type":1,"order":3329,"group":1,"subgroup":30,"version":13.1,"tone":[2,3]},{"label":"kiss: man, man, medium-light skin tone, medium-dark skin tone","hexcode":"1F468-1F3FC-200D-2764-FE0F-200D-1F48B-200D-1F468-1F3FE","emoji":"👨🏼‍❤️‍💋‍👨🏾","text":"","type":1,"order":3331,"group":1,"subgroup":30,"version":13.1,"tone":[2,4]},{"label":"kiss: man, man, medium-light skin tone, dark skin tone","hexcode":"1F468-1F3FC-200D-2764-FE0F-200D-1F48B-200D-1F468-1F3FF","emoji":"👨🏼‍❤️‍💋‍👨🏿","text":"","type":1,"order":3333,"group":1,"subgroup":30,"version":13.1,"tone":[2,5]},{"label":"kiss: man, man, medium skin tone, light skin tone","hexcode":"1F468-1F3FD-200D-2764-FE0F-200D-1F48B-200D-1F468-1F3FB","emoji":"👨🏽‍❤️‍💋‍👨🏻","text":"","type":1,"order":3335,"group":1,"subgroup":30,"version":13.1,"tone":[3,1]},{"label":"kiss: man, man, medium skin tone, medium-light skin tone","hexcode":"1F468-1F3FD-200D-2764-FE0F-200D-1F48B-200D-1F468-1F3FC","emoji":"👨🏽‍❤️‍💋‍👨🏼","text":"","type":1,"order":3337,"group":1,"subgroup":30,"version":13.1,"tone":[3,2]},{"label":"kiss: man, man, medium skin tone","hexcode":"1F468-1F3FD-200D-2764-FE0F-200D-1F48B-200D-1F468-1F3FD","emoji":"👨🏽‍❤️‍💋‍👨🏽","text":"","type":1,"order":3339,"group":1,"subgroup":30,"version":13.1,"tone":3},{"label":"kiss: man, man, medium skin tone, medium-dark skin tone","hexcode":"1F468-1F3FD-200D-2764-FE0F-200D-1F48B-200D-1F468-1F3FE","emoji":"👨🏽‍❤️‍💋‍👨🏾","text":"","type":1,"order":3341,"group":1,"subgroup":30,"version":13.1,"tone":[3,4]},{"label":"kiss: man, man, medium skin tone, dark skin tone","hexcode":"1F468-1F3FD-200D-2764-FE0F-200D-1F48B-200D-1F468-1F3FF","emoji":"👨🏽‍❤️‍💋‍👨🏿","text":"","type":1,"order":3343,"group":1,"subgroup":30,"version":13.1,"tone":[3,5]},{"label":"kiss: man, man, medium-dark skin tone, light skin tone","hexcode":"1F468-1F3FE-200D-2764-FE0F-200D-1F48B-200D-1F468-1F3FB","emoji":"👨🏾‍❤️‍💋‍👨🏻","text":"","type":1,"order":3345,"group":1,"subgroup":30,"version":13.1,"tone":[4,1]},{"label":"kiss: man, man, medium-dark skin tone, medium-light skin tone","hexcode":"1F468-1F3FE-200D-2764-FE0F-200D-1F48B-200D-1F468-1F3FC","emoji":"👨🏾‍❤️‍💋‍👨🏼","text":"","type":1,"order":3347,"group":1,"subgroup":30,"version":13.1,"tone":[4,2]},{"label":"kiss: man, man, medium-dark skin tone, medium skin tone","hexcode":"1F468-1F3FE-200D-2764-FE0F-200D-1F48B-200D-1F468-1F3FD","emoji":"👨🏾‍❤️‍💋‍👨🏽","text":"","type":1,"order":3349,"group":1,"subgroup":30,"version":13.1,"tone":[4,3]},{"label":"kiss: man, man, medium-dark skin tone","hexcode":"1F468-1F3FE-200D-2764-FE0F-200D-1F48B-200D-1F468-1F3FE","emoji":"👨🏾‍❤️‍💋‍👨🏾","text":"","type":1,"order":3351,"group":1,"subgroup":30,"version":13.1,"tone":4},{"label":"kiss: man, man, medium-dark skin tone, dark skin tone","hexcode":"1F468-1F3FE-200D-2764-FE0F-200D-1F48B-200D-1F468-1F3FF","emoji":"👨🏾‍❤️‍💋‍👨🏿","text":"","type":1,"order":3353,"group":1,"subgroup":30,"version":13.1,"tone":[4,5]},{"label":"kiss: man, man, dark skin tone, light skin tone","hexcode":"1F468-1F3FF-200D-2764-FE0F-200D-1F48B-200D-1F468-1F3FB","emoji":"👨🏿‍❤️‍💋‍👨🏻","text":"","type":1,"order":3355,"group":1,"subgroup":30,"version":13.1,"tone":[5,1]},{"label":"kiss: man, man, dark skin tone, medium-light skin tone","hexcode":"1F468-1F3FF-200D-2764-FE0F-200D-1F48B-200D-1F468-1F3FC","emoji":"👨🏿‍❤️‍💋‍👨🏼","text":"","type":1,"order":3357,"group":1,"subgroup":30,"version":13.1,"tone":[5,2]},{"label":"kiss: man, man, dark skin tone, medium skin tone","hexcode":"1F468-1F3FF-200D-2764-FE0F-200D-1F48B-200D-1F468-1F3FD","emoji":"👨🏿‍❤️‍💋‍👨🏽","text":"","type":1,"order":3359,"group":1,"subgroup":30,"version":13.1,"tone":[5,3]},{"label":"kiss: man, man, dark skin tone, medium-dark skin tone","hexcode":"1F468-1F3FF-200D-2764-FE0F-200D-1F48B-200D-1F468-1F3FE","emoji":"👨🏿‍❤️‍💋‍👨🏾","text":"","type":1,"order":3361,"group":1,"subgroup":30,"version":13.1,"tone":[5,4]},{"label":"kiss: man, man, dark skin tone","hexcode":"1F468-1F3FF-200D-2764-FE0F-200D-1F48B-200D-1F468-1F3FF","emoji":"👨🏿‍❤️‍💋‍👨🏿","text":"","type":1,"order":3363,"group":1,"subgroup":30,"version":13.1,"tone":5}]},{"label":"kiss: woman, woman","hexcode":"1F469-200D-2764-FE0F-200D-1F48B-200D-1F469","tags":["anniversary","babe","bae","couple","date","dating","heart","kiss","love","mwah","person","romance","together","woman","xoxo"],"emoji":"👩‍❤️‍💋‍👩","text":"","type":1,"order":3365,"group":1,"subgroup":30,"version":2,"skins":[{"label":"kiss: woman, woman, light skin tone","hexcode":"1F469-1F3FB-200D-2764-FE0F-200D-1F48B-200D-1F469-1F3FB","emoji":"👩🏻‍❤️‍💋‍👩🏻","text":"","type":1,"order":3367,"group":1,"subgroup":30,"version":13.1,"tone":1},{"label":"kiss: woman, woman, light skin tone, medium-light skin tone","hexcode":"1F469-1F3FB-200D-2764-FE0F-200D-1F48B-200D-1F469-1F3FC","emoji":"👩🏻‍❤️‍💋‍👩🏼","text":"","type":1,"order":3369,"group":1,"subgroup":30,"version":13.1,"tone":[1,2]},{"label":"kiss: woman, woman, light skin tone, medium skin tone","hexcode":"1F469-1F3FB-200D-2764-FE0F-200D-1F48B-200D-1F469-1F3FD","emoji":"👩🏻‍❤️‍💋‍👩🏽","text":"","type":1,"order":3371,"group":1,"subgroup":30,"version":13.1,"tone":[1,3]},{"label":"kiss: woman, woman, light skin tone, medium-dark skin tone","hexcode":"1F469-1F3FB-200D-2764-FE0F-200D-1F48B-200D-1F469-1F3FE","emoji":"👩🏻‍❤️‍💋‍👩🏾","text":"","type":1,"order":3373,"group":1,"subgroup":30,"version":13.1,"tone":[1,4]},{"label":"kiss: woman, woman, light skin tone, dark skin tone","hexcode":"1F469-1F3FB-200D-2764-FE0F-200D-1F48B-200D-1F469-1F3FF","emoji":"👩🏻‍❤️‍💋‍👩🏿","text":"","type":1,"order":3375,"group":1,"subgroup":30,"version":13.1,"tone":[1,5]},{"label":"kiss: woman, woman, medium-light skin tone, light skin tone","hexcode":"1F469-1F3FC-200D-2764-FE0F-200D-1F48B-200D-1F469-1F3FB","emoji":"👩🏼‍❤️‍💋‍👩🏻","text":"","type":1,"order":3377,"group":1,"subgroup":30,"version":13.1,"tone":[2,1]},{"label":"kiss: woman, woman, medium-light skin tone","hexcode":"1F469-1F3FC-200D-2764-FE0F-200D-1F48B-200D-1F469-1F3FC","emoji":"👩🏼‍❤️‍💋‍👩🏼","text":"","type":1,"order":3379,"group":1,"subgroup":30,"version":13.1,"tone":2},{"label":"kiss: woman, woman, medium-light skin tone, medium skin tone","hexcode":"1F469-1F3FC-200D-2764-FE0F-200D-1F48B-200D-1F469-1F3FD","emoji":"👩🏼‍❤️‍💋‍👩🏽","text":"","type":1,"order":3381,"group":1,"subgroup":30,"version":13.1,"tone":[2,3]},{"label":"kiss: woman, woman, medium-light skin tone, medium-dark skin tone","hexcode":"1F469-1F3FC-200D-2764-FE0F-200D-1F48B-200D-1F469-1F3FE","emoji":"👩🏼‍❤️‍💋‍👩🏾","text":"","type":1,"order":3383,"group":1,"subgroup":30,"version":13.1,"tone":[2,4]},{"label":"kiss: woman, woman, medium-light skin tone, dark skin tone","hexcode":"1F469-1F3FC-200D-2764-FE0F-200D-1F48B-200D-1F469-1F3FF","emoji":"👩🏼‍❤️‍💋‍👩🏿","text":"","type":1,"order":3385,"group":1,"subgroup":30,"version":13.1,"tone":[2,5]},{"label":"kiss: woman, woman, medium skin tone, light skin tone","hexcode":"1F469-1F3FD-200D-2764-FE0F-200D-1F48B-200D-1F469-1F3FB","emoji":"👩🏽‍❤️‍💋‍👩🏻","text":"","type":1,"order":3387,"group":1,"subgroup":30,"version":13.1,"tone":[3,1]},{"label":"kiss: woman, woman, medium skin tone, medium-light skin tone","hexcode":"1F469-1F3FD-200D-2764-FE0F-200D-1F48B-200D-1F469-1F3FC","emoji":"👩🏽‍❤️‍💋‍👩🏼","text":"","type":1,"order":3389,"group":1,"subgroup":30,"version":13.1,"tone":[3,2]},{"label":"kiss: woman, woman, medium skin tone","hexcode":"1F469-1F3FD-200D-2764-FE0F-200D-1F48B-200D-1F469-1F3FD","emoji":"👩🏽‍❤️‍💋‍👩🏽","text":"","type":1,"order":3391,"group":1,"subgroup":30,"version":13.1,"tone":3},{"label":"kiss: woman, woman, medium skin tone, medium-dark skin tone","hexcode":"1F469-1F3FD-200D-2764-FE0F-200D-1F48B-200D-1F469-1F3FE","emoji":"👩🏽‍❤️‍💋‍👩🏾","text":"","type":1,"order":3393,"group":1,"subgroup":30,"version":13.1,"tone":[3,4]},{"label":"kiss: woman, woman, medium skin tone, dark skin tone","hexcode":"1F469-1F3FD-200D-2764-FE0F-200D-1F48B-200D-1F469-1F3FF","emoji":"👩🏽‍❤️‍💋‍👩🏿","text":"","type":1,"order":3395,"group":1,"subgroup":30,"version":13.1,"tone":[3,5]},{"label":"kiss: woman, woman, medium-dark skin tone, light skin tone","hexcode":"1F469-1F3FE-200D-2764-FE0F-200D-1F48B-200D-1F469-1F3FB","emoji":"👩🏾‍❤️‍💋‍👩🏻","text":"","type":1,"order":3397,"group":1,"subgroup":30,"version":13.1,"tone":[4,1]},{"label":"kiss: woman, woman, medium-dark skin tone, medium-light skin tone","hexcode":"1F469-1F3FE-200D-2764-FE0F-200D-1F48B-200D-1F469-1F3FC","emoji":"👩🏾‍❤️‍💋‍👩🏼","text":"","type":1,"order":3399,"group":1,"subgroup":30,"version":13.1,"tone":[4,2]},{"label":"kiss: woman, woman, medium-dark skin tone, medium skin tone","hexcode":"1F469-1F3FE-200D-2764-FE0F-200D-1F48B-200D-1F469-1F3FD","emoji":"👩🏾‍❤️‍💋‍👩🏽","text":"","type":1,"order":3401,"group":1,"subgroup":30,"version":13.1,"tone":[4,3]},{"label":"kiss: woman, woman, medium-dark skin tone","hexcode":"1F469-1F3FE-200D-2764-FE0F-200D-1F48B-200D-1F469-1F3FE","emoji":"👩🏾‍❤️‍💋‍👩🏾","text":"","type":1,"order":3403,"group":1,"subgroup":30,"version":13.1,"tone":4},{"label":"kiss: woman, woman, medium-dark skin tone, dark skin tone","hexcode":"1F469-1F3FE-200D-2764-FE0F-200D-1F48B-200D-1F469-1F3FF","emoji":"👩🏾‍❤️‍💋‍👩🏿","text":"","type":1,"order":3405,"group":1,"subgroup":30,"version":13.1,"tone":[4,5]},{"label":"kiss: woman, woman, dark skin tone, light skin tone","hexcode":"1F469-1F3FF-200D-2764-FE0F-200D-1F48B-200D-1F469-1F3FB","emoji":"👩🏿‍❤️‍💋‍👩🏻","text":"","type":1,"order":3407,"group":1,"subgroup":30,"version":13.1,"tone":[5,1]},{"label":"kiss: woman, woman, dark skin tone, medium-light skin tone","hexcode":"1F469-1F3FF-200D-2764-FE0F-200D-1F48B-200D-1F469-1F3FC","emoji":"👩🏿‍❤️‍💋‍👩🏼","text":"","type":1,"order":3409,"group":1,"subgroup":30,"version":13.1,"tone":[5,2]},{"label":"kiss: woman, woman, dark skin tone, medium skin tone","hexcode":"1F469-1F3FF-200D-2764-FE0F-200D-1F48B-200D-1F469-1F3FD","emoji":"👩🏿‍❤️‍💋‍👩🏽","text":"","type":1,"order":3411,"group":1,"subgroup":30,"version":13.1,"tone":[5,3]},{"label":"kiss: woman, woman, dark skin tone, medium-dark skin tone","hexcode":"1F469-1F3FF-200D-2764-FE0F-200D-1F48B-200D-1F469-1F3FE","emoji":"👩🏿‍❤️‍💋‍👩🏾","text":"","type":1,"order":3413,"group":1,"subgroup":30,"version":13.1,"tone":[5,4]},{"label":"kiss: woman, woman, dark skin tone","hexcode":"1F469-1F3FF-200D-2764-FE0F-200D-1F48B-200D-1F469-1F3FF","emoji":"👩🏿‍❤️‍💋‍👩🏿","text":"","type":1,"order":3415,"group":1,"subgroup":30,"version":13.1,"tone":5}]},{"label":"couple with heart","hexcode":"1F491","tags":["anniversary","babe","bae","couple","dating","heart","kiss","love","person","relationship","romance","together","you"],"emoji":"💑","text":"","type":1,"order":3417,"group":1,"subgroup":30,"version":0.6,"skins":[{"label":"couple with heart: light skin tone","hexcode":"1F491-1F3FB","emoji":"💑🏻","text":"","type":1,"order":3418,"group":1,"subgroup":30,"version":13.1,"tone":1},{"label":"couple with heart: medium-light skin tone","hexcode":"1F491-1F3FC","emoji":"💑🏼","text":"","type":1,"order":3419,"group":1,"subgroup":30,"version":13.1,"tone":2},{"label":"couple with heart: medium skin tone","hexcode":"1F491-1F3FD","emoji":"💑🏽","text":"","type":1,"order":3420,"group":1,"subgroup":30,"version":13.1,"tone":3},{"label":"couple with heart: medium-dark skin tone","hexcode":"1F491-1F3FE","emoji":"💑🏾","text":"","type":1,"order":3421,"group":1,"subgroup":30,"version":13.1,"tone":4},{"label":"couple with heart: dark skin tone","hexcode":"1F491-1F3FF","emoji":"💑🏿","text":"","type":1,"order":3422,"group":1,"subgroup":30,"version":13.1,"tone":5},{"label":"couple with heart: person, person, light skin tone, medium-light skin tone","hexcode":"1F9D1-1F3FB-200D-2764-FE0F-200D-1F9D1-1F3FC","emoji":"🧑🏻‍❤️‍🧑🏼","text":"","type":1,"order":3423,"group":1,"subgroup":30,"version":13.1,"tone":[1,2]},{"label":"couple with heart: person, person, light skin tone, medium skin tone","hexcode":"1F9D1-1F3FB-200D-2764-FE0F-200D-1F9D1-1F3FD","emoji":"🧑🏻‍❤️‍🧑🏽","text":"","type":1,"order":3425,"group":1,"subgroup":30,"version":13.1,"tone":[1,3]},{"label":"couple with heart: person, person, light skin tone, medium-dark skin tone","hexcode":"1F9D1-1F3FB-200D-2764-FE0F-200D-1F9D1-1F3FE","emoji":"🧑🏻‍❤️‍🧑🏾","text":"","type":1,"order":3427,"group":1,"subgroup":30,"version":13.1,"tone":[1,4]},{"label":"couple with heart: person, person, light skin tone, dark skin tone","hexcode":"1F9D1-1F3FB-200D-2764-FE0F-200D-1F9D1-1F3FF","emoji":"🧑🏻‍❤️‍🧑🏿","text":"","type":1,"order":3429,"group":1,"subgroup":30,"version":13.1,"tone":[1,5]},{"label":"couple with heart: person, person, medium-light skin tone, light skin tone","hexcode":"1F9D1-1F3FC-200D-2764-FE0F-200D-1F9D1-1F3FB","emoji":"🧑🏼‍❤️‍🧑🏻","text":"","type":1,"order":3431,"group":1,"subgroup":30,"version":13.1,"tone":[2,1]},{"label":"couple with heart: person, person, medium-light skin tone, medium skin tone","hexcode":"1F9D1-1F3FC-200D-2764-FE0F-200D-1F9D1-1F3FD","emoji":"🧑🏼‍❤️‍🧑🏽","text":"","type":1,"order":3433,"group":1,"subgroup":30,"version":13.1,"tone":[2,3]},{"label":"couple with heart: person, person, medium-light skin tone, medium-dark skin tone","hexcode":"1F9D1-1F3FC-200D-2764-FE0F-200D-1F9D1-1F3FE","emoji":"🧑🏼‍❤️‍🧑🏾","text":"","type":1,"order":3435,"group":1,"subgroup":30,"version":13.1,"tone":[2,4]},{"label":"couple with heart: person, person, medium-light skin tone, dark skin tone","hexcode":"1F9D1-1F3FC-200D-2764-FE0F-200D-1F9D1-1F3FF","emoji":"🧑🏼‍❤️‍🧑🏿","text":"","type":1,"order":3437,"group":1,"subgroup":30,"version":13.1,"tone":[2,5]},{"label":"couple with heart: person, person, medium skin tone, light skin tone","hexcode":"1F9D1-1F3FD-200D-2764-FE0F-200D-1F9D1-1F3FB","emoji":"🧑🏽‍❤️‍🧑🏻","text":"","type":1,"order":3439,"group":1,"subgroup":30,"version":13.1,"tone":[3,1]},{"label":"couple with heart: person, person, medium skin tone, medium-light skin tone","hexcode":"1F9D1-1F3FD-200D-2764-FE0F-200D-1F9D1-1F3FC","emoji":"🧑🏽‍❤️‍🧑🏼","text":"","type":1,"order":3441,"group":1,"subgroup":30,"version":13.1,"tone":[3,2]},{"label":"couple with heart: person, person, medium skin tone, medium-dark skin tone","hexcode":"1F9D1-1F3FD-200D-2764-FE0F-200D-1F9D1-1F3FE","emoji":"🧑🏽‍❤️‍🧑🏾","text":"","type":1,"order":3443,"group":1,"subgroup":30,"version":13.1,"tone":[3,4]},{"label":"couple with heart: person, person, medium skin tone, dark skin tone","hexcode":"1F9D1-1F3FD-200D-2764-FE0F-200D-1F9D1-1F3FF","emoji":"🧑🏽‍❤️‍🧑🏿","text":"","type":1,"order":3445,"group":1,"subgroup":30,"version":13.1,"tone":[3,5]},{"label":"couple with heart: person, person, medium-dark skin tone, light skin tone","hexcode":"1F9D1-1F3FE-200D-2764-FE0F-200D-1F9D1-1F3FB","emoji":"🧑🏾‍❤️‍🧑🏻","text":"","type":1,"order":3447,"group":1,"subgroup":30,"version":13.1,"tone":[4,1]},{"label":"couple with heart: person, person, medium-dark skin tone, medium-light skin tone","hexcode":"1F9D1-1F3FE-200D-2764-FE0F-200D-1F9D1-1F3FC","emoji":"🧑🏾‍❤️‍🧑🏼","text":"","type":1,"order":3449,"group":1,"subgroup":30,"version":13.1,"tone":[4,2]},{"label":"couple with heart: person, person, medium-dark skin tone, medium skin tone","hexcode":"1F9D1-1F3FE-200D-2764-FE0F-200D-1F9D1-1F3FD","emoji":"🧑🏾‍❤️‍🧑🏽","text":"","type":1,"order":3451,"group":1,"subgroup":30,"version":13.1,"tone":[4,3]},{"label":"couple with heart: person, person, medium-dark skin tone, dark skin tone","hexcode":"1F9D1-1F3FE-200D-2764-FE0F-200D-1F9D1-1F3FF","emoji":"🧑🏾‍❤️‍🧑🏿","text":"","type":1,"order":3453,"group":1,"subgroup":30,"version":13.1,"tone":[4,5]},{"label":"couple with heart: person, person, dark skin tone, light skin tone","hexcode":"1F9D1-1F3FF-200D-2764-FE0F-200D-1F9D1-1F3FB","emoji":"🧑🏿‍❤️‍🧑🏻","text":"","type":1,"order":3455,"group":1,"subgroup":30,"version":13.1,"tone":[5,1]},{"label":"couple with heart: person, person, dark skin tone, medium-light skin tone","hexcode":"1F9D1-1F3FF-200D-2764-FE0F-200D-1F9D1-1F3FC","emoji":"🧑🏿‍❤️‍🧑🏼","text":"","type":1,"order":3457,"group":1,"subgroup":30,"version":13.1,"tone":[5,2]},{"label":"couple with heart: person, person, dark skin tone, medium skin tone","hexcode":"1F9D1-1F3FF-200D-2764-FE0F-200D-1F9D1-1F3FD","emoji":"🧑🏿‍❤️‍🧑🏽","text":"","type":1,"order":3459,"group":1,"subgroup":30,"version":13.1,"tone":[5,3]},{"label":"couple with heart: person, person, dark skin tone, medium-dark skin tone","hexcode":"1F9D1-1F3FF-200D-2764-FE0F-200D-1F9D1-1F3FE","emoji":"🧑🏿‍❤️‍🧑🏾","text":"","type":1,"order":3461,"group":1,"subgroup":30,"version":13.1,"tone":[5,4]}]},{"label":"couple with heart: woman, man","hexcode":"1F469-200D-2764-FE0F-200D-1F468","tags":["anniversary","babe","bae","couple","dating","heart","kiss","love","man","person","relationship","romance","together","woman","you"],"emoji":"👩‍❤️‍👨","text":"","type":1,"order":3463,"group":1,"subgroup":30,"version":2,"skins":[{"label":"couple with heart: woman, man, light skin tone","hexcode":"1F469-1F3FB-200D-2764-FE0F-200D-1F468-1F3FB","emoji":"👩🏻‍❤️‍👨🏻","text":"","type":1,"order":3465,"group":1,"subgroup":30,"version":13.1,"tone":1},{"label":"couple with heart: woman, man, light skin tone, medium-light skin tone","hexcode":"1F469-1F3FB-200D-2764-FE0F-200D-1F468-1F3FC","emoji":"👩🏻‍❤️‍👨🏼","text":"","type":1,"order":3467,"group":1,"subgroup":30,"version":13.1,"tone":[1,2]},{"label":"couple with heart: woman, man, light skin tone, medium skin tone","hexcode":"1F469-1F3FB-200D-2764-FE0F-200D-1F468-1F3FD","emoji":"👩🏻‍❤️‍👨🏽","text":"","type":1,"order":3469,"group":1,"subgroup":30,"version":13.1,"tone":[1,3]},{"label":"couple with heart: woman, man, light skin tone, medium-dark skin tone","hexcode":"1F469-1F3FB-200D-2764-FE0F-200D-1F468-1F3FE","emoji":"👩🏻‍❤️‍👨🏾","text":"","type":1,"order":3471,"group":1,"subgroup":30,"version":13.1,"tone":[1,4]},{"label":"couple with heart: woman, man, light skin tone, dark skin tone","hexcode":"1F469-1F3FB-200D-2764-FE0F-200D-1F468-1F3FF","emoji":"👩🏻‍❤️‍👨🏿","text":"","type":1,"order":3473,"group":1,"subgroup":30,"version":13.1,"tone":[1,5]},{"label":"couple with heart: woman, man, medium-light skin tone, light skin tone","hexcode":"1F469-1F3FC-200D-2764-FE0F-200D-1F468-1F3FB","emoji":"👩🏼‍❤️‍👨🏻","text":"","type":1,"order":3475,"group":1,"subgroup":30,"version":13.1,"tone":[2,1]},{"label":"couple with heart: woman, man, medium-light skin tone","hexcode":"1F469-1F3FC-200D-2764-FE0F-200D-1F468-1F3FC","emoji":"👩🏼‍❤️‍👨🏼","text":"","type":1,"order":3477,"group":1,"subgroup":30,"version":13.1,"tone":2},{"label":"couple with heart: woman, man, medium-light skin tone, medium skin tone","hexcode":"1F469-1F3FC-200D-2764-FE0F-200D-1F468-1F3FD","emoji":"👩🏼‍❤️‍👨🏽","text":"","type":1,"order":3479,"group":1,"subgroup":30,"version":13.1,"tone":[2,3]},{"label":"couple with heart: woman, man, medium-light skin tone, medium-dark skin tone","hexcode":"1F469-1F3FC-200D-2764-FE0F-200D-1F468-1F3FE","emoji":"👩🏼‍❤️‍👨🏾","text":"","type":1,"order":3481,"group":1,"subgroup":30,"version":13.1,"tone":[2,4]},{"label":"couple with heart: woman, man, medium-light skin tone, dark skin tone","hexcode":"1F469-1F3FC-200D-2764-FE0F-200D-1F468-1F3FF","emoji":"👩🏼‍❤️‍👨🏿","text":"","type":1,"order":3483,"group":1,"subgroup":30,"version":13.1,"tone":[2,5]},{"label":"couple with heart: woman, man, medium skin tone, light skin tone","hexcode":"1F469-1F3FD-200D-2764-FE0F-200D-1F468-1F3FB","emoji":"👩🏽‍❤️‍👨🏻","text":"","type":1,"order":3485,"group":1,"subgroup":30,"version":13.1,"tone":[3,1]},{"label":"couple with heart: woman, man, medium skin tone, medium-light skin tone","hexcode":"1F469-1F3FD-200D-2764-FE0F-200D-1F468-1F3FC","emoji":"👩🏽‍❤️‍👨🏼","text":"","type":1,"order":3487,"group":1,"subgroup":30,"version":13.1,"tone":[3,2]},{"label":"couple with heart: woman, man, medium skin tone","hexcode":"1F469-1F3FD-200D-2764-FE0F-200D-1F468-1F3FD","emoji":"👩🏽‍❤️‍👨🏽","text":"","type":1,"order":3489,"group":1,"subgroup":30,"version":13.1,"tone":3},{"label":"couple with heart: woman, man, medium skin tone, medium-dark skin tone","hexcode":"1F469-1F3FD-200D-2764-FE0F-200D-1F468-1F3FE","emoji":"👩🏽‍❤️‍👨🏾","text":"","type":1,"order":3491,"group":1,"subgroup":30,"version":13.1,"tone":[3,4]},{"label":"couple with heart: woman, man, medium skin tone, dark skin tone","hexcode":"1F469-1F3FD-200D-2764-FE0F-200D-1F468-1F3FF","emoji":"👩🏽‍❤️‍👨🏿","text":"","type":1,"order":3493,"group":1,"subgroup":30,"version":13.1,"tone":[3,5]},{"label":"couple with heart: woman, man, medium-dark skin tone, light skin tone","hexcode":"1F469-1F3FE-200D-2764-FE0F-200D-1F468-1F3FB","emoji":"👩🏾‍❤️‍👨🏻","text":"","type":1,"order":3495,"group":1,"subgroup":30,"version":13.1,"tone":[4,1]},{"label":"couple with heart: woman, man, medium-dark skin tone, medium-light skin tone","hexcode":"1F469-1F3FE-200D-2764-FE0F-200D-1F468-1F3FC","emoji":"👩🏾‍❤️‍👨🏼","text":"","type":1,"order":3497,"group":1,"subgroup":30,"version":13.1,"tone":[4,2]},{"label":"couple with heart: woman, man, medium-dark skin tone, medium skin tone","hexcode":"1F469-1F3FE-200D-2764-FE0F-200D-1F468-1F3FD","emoji":"👩🏾‍❤️‍👨🏽","text":"","type":1,"order":3499,"group":1,"subgroup":30,"version":13.1,"tone":[4,3]},{"label":"couple with heart: woman, man, medium-dark skin tone","hexcode":"1F469-1F3FE-200D-2764-FE0F-200D-1F468-1F3FE","emoji":"👩🏾‍❤️‍👨🏾","text":"","type":1,"order":3501,"group":1,"subgroup":30,"version":13.1,"tone":4},{"label":"couple with heart: woman, man, medium-dark skin tone, dark skin tone","hexcode":"1F469-1F3FE-200D-2764-FE0F-200D-1F468-1F3FF","emoji":"👩🏾‍❤️‍👨🏿","text":"","type":1,"order":3503,"group":1,"subgroup":30,"version":13.1,"tone":[4,5]},{"label":"couple with heart: woman, man, dark skin tone, light skin tone","hexcode":"1F469-1F3FF-200D-2764-FE0F-200D-1F468-1F3FB","emoji":"👩🏿‍❤️‍👨🏻","text":"","type":1,"order":3505,"group":1,"subgroup":30,"version":13.1,"tone":[5,1]},{"label":"couple with heart: woman, man, dark skin tone, medium-light skin tone","hexcode":"1F469-1F3FF-200D-2764-FE0F-200D-1F468-1F3FC","emoji":"👩🏿‍❤️‍👨🏼","text":"","type":1,"order":3507,"group":1,"subgroup":30,"version":13.1,"tone":[5,2]},{"label":"couple with heart: woman, man, dark skin tone, medium skin tone","hexcode":"1F469-1F3FF-200D-2764-FE0F-200D-1F468-1F3FD","emoji":"👩🏿‍❤️‍👨🏽","text":"","type":1,"order":3509,"group":1,"subgroup":30,"version":13.1,"tone":[5,3]},{"label":"couple with heart: woman, man, dark skin tone, medium-dark skin tone","hexcode":"1F469-1F3FF-200D-2764-FE0F-200D-1F468-1F3FE","emoji":"👩🏿‍❤️‍👨🏾","text":"","type":1,"order":3511,"group":1,"subgroup":30,"version":13.1,"tone":[5,4]},{"label":"couple with heart: woman, man, dark skin tone","hexcode":"1F469-1F3FF-200D-2764-FE0F-200D-1F468-1F3FF","emoji":"👩🏿‍❤️‍👨🏿","text":"","type":1,"order":3513,"group":1,"subgroup":30,"version":13.1,"tone":5}]},{"label":"couple with heart: man, man","hexcode":"1F468-200D-2764-FE0F-200D-1F468","tags":["anniversary","babe","bae","couple","dating","heart","kiss","love","man","person","relationship","romance","together","you"],"emoji":"👨‍❤️‍👨","text":"","type":1,"order":3515,"group":1,"subgroup":30,"version":2,"skins":[{"label":"couple with heart: man, man, light skin tone","hexcode":"1F468-1F3FB-200D-2764-FE0F-200D-1F468-1F3FB","emoji":"👨🏻‍❤️‍👨🏻","text":"","type":1,"order":3517,"group":1,"subgroup":30,"version":13.1,"tone":1},{"label":"couple with heart: man, man, light skin tone, medium-light skin tone","hexcode":"1F468-1F3FB-200D-2764-FE0F-200D-1F468-1F3FC","emoji":"👨🏻‍❤️‍👨🏼","text":"","type":1,"order":3519,"group":1,"subgroup":30,"version":13.1,"tone":[1,2]},{"label":"couple with heart: man, man, light skin tone, medium skin tone","hexcode":"1F468-1F3FB-200D-2764-FE0F-200D-1F468-1F3FD","emoji":"👨🏻‍❤️‍👨🏽","text":"","type":1,"order":3521,"group":1,"subgroup":30,"version":13.1,"tone":[1,3]},{"label":"couple with heart: man, man, light skin tone, medium-dark skin tone","hexcode":"1F468-1F3FB-200D-2764-FE0F-200D-1F468-1F3FE","emoji":"👨🏻‍❤️‍👨🏾","text":"","type":1,"order":3523,"group":1,"subgroup":30,"version":13.1,"tone":[1,4]},{"label":"couple with heart: man, man, light skin tone, dark skin tone","hexcode":"1F468-1F3FB-200D-2764-FE0F-200D-1F468-1F3FF","emoji":"👨🏻‍❤️‍👨🏿","text":"","type":1,"order":3525,"group":1,"subgroup":30,"version":13.1,"tone":[1,5]},{"label":"couple with heart: man, man, medium-light skin tone, light skin tone","hexcode":"1F468-1F3FC-200D-2764-FE0F-200D-1F468-1F3FB","emoji":"👨🏼‍❤️‍👨🏻","text":"","type":1,"order":3527,"group":1,"subgroup":30,"version":13.1,"tone":[2,1]},{"label":"couple with heart: man, man, medium-light skin tone","hexcode":"1F468-1F3FC-200D-2764-FE0F-200D-1F468-1F3FC","emoji":"👨🏼‍❤️‍👨🏼","text":"","type":1,"order":3529,"group":1,"subgroup":30,"version":13.1,"tone":2},{"label":"couple with heart: man, man, medium-light skin tone, medium skin tone","hexcode":"1F468-1F3FC-200D-2764-FE0F-200D-1F468-1F3FD","emoji":"👨🏼‍❤️‍👨🏽","text":"","type":1,"order":3531,"group":1,"subgroup":30,"version":13.1,"tone":[2,3]},{"label":"couple with heart: man, man, medium-light skin tone, medium-dark skin tone","hexcode":"1F468-1F3FC-200D-2764-FE0F-200D-1F468-1F3FE","emoji":"👨🏼‍❤️‍👨🏾","text":"","type":1,"order":3533,"group":1,"subgroup":30,"version":13.1,"tone":[2,4]},{"label":"couple with heart: man, man, medium-light skin tone, dark skin tone","hexcode":"1F468-1F3FC-200D-2764-FE0F-200D-1F468-1F3FF","emoji":"👨🏼‍❤️‍👨🏿","text":"","type":1,"order":3535,"group":1,"subgroup":30,"version":13.1,"tone":[2,5]},{"label":"couple with heart: man, man, medium skin tone, light skin tone","hexcode":"1F468-1F3FD-200D-2764-FE0F-200D-1F468-1F3FB","emoji":"👨🏽‍❤️‍👨🏻","text":"","type":1,"order":3537,"group":1,"subgroup":30,"version":13.1,"tone":[3,1]},{"label":"couple with heart: man, man, medium skin tone, medium-light skin tone","hexcode":"1F468-1F3FD-200D-2764-FE0F-200D-1F468-1F3FC","emoji":"👨🏽‍❤️‍👨🏼","text":"","type":1,"order":3539,"group":1,"subgroup":30,"version":13.1,"tone":[3,2]},{"label":"couple with heart: man, man, medium skin tone","hexcode":"1F468-1F3FD-200D-2764-FE0F-200D-1F468-1F3FD","emoji":"👨🏽‍❤️‍👨🏽","text":"","type":1,"order":3541,"group":1,"subgroup":30,"version":13.1,"tone":3},{"label":"couple with heart: man, man, medium skin tone, medium-dark skin tone","hexcode":"1F468-1F3FD-200D-2764-FE0F-200D-1F468-1F3FE","emoji":"👨🏽‍❤️‍👨🏾","text":"","type":1,"order":3543,"group":1,"subgroup":30,"version":13.1,"tone":[3,4]},{"label":"couple with heart: man, man, medium skin tone, dark skin tone","hexcode":"1F468-1F3FD-200D-2764-FE0F-200D-1F468-1F3FF","emoji":"👨🏽‍❤️‍👨🏿","text":"","type":1,"order":3545,"group":1,"subgroup":30,"version":13.1,"tone":[3,5]},{"label":"couple with heart: man, man, medium-dark skin tone, light skin tone","hexcode":"1F468-1F3FE-200D-2764-FE0F-200D-1F468-1F3FB","emoji":"👨🏾‍❤️‍👨🏻","text":"","type":1,"order":3547,"group":1,"subgroup":30,"version":13.1,"tone":[4,1]},{"label":"couple with heart: man, man, medium-dark skin tone, medium-light skin tone","hexcode":"1F468-1F3FE-200D-2764-FE0F-200D-1F468-1F3FC","emoji":"👨🏾‍❤️‍👨🏼","text":"","type":1,"order":3549,"group":1,"subgroup":30,"version":13.1,"tone":[4,2]},{"label":"couple with heart: man, man, medium-dark skin tone, medium skin tone","hexcode":"1F468-1F3FE-200D-2764-FE0F-200D-1F468-1F3FD","emoji":"👨🏾‍❤️‍👨🏽","text":"","type":1,"order":3551,"group":1,"subgroup":30,"version":13.1,"tone":[4,3]},{"label":"couple with heart: man, man, medium-dark skin tone","hexcode":"1F468-1F3FE-200D-2764-FE0F-200D-1F468-1F3FE","emoji":"👨🏾‍❤️‍👨🏾","text":"","type":1,"order":3553,"group":1,"subgroup":30,"version":13.1,"tone":4},{"label":"couple with heart: man, man, medium-dark skin tone, dark skin tone","hexcode":"1F468-1F3FE-200D-2764-FE0F-200D-1F468-1F3FF","emoji":"👨🏾‍❤️‍👨🏿","text":"","type":1,"order":3555,"group":1,"subgroup":30,"version":13.1,"tone":[4,5]},{"label":"couple with heart: man, man, dark skin tone, light skin tone","hexcode":"1F468-1F3FF-200D-2764-FE0F-200D-1F468-1F3FB","emoji":"👨🏿‍❤️‍👨🏻","text":"","type":1,"order":3557,"group":1,"subgroup":30,"version":13.1,"tone":[5,1]},{"label":"couple with heart: man, man, dark skin tone, medium-light skin tone","hexcode":"1F468-1F3FF-200D-2764-FE0F-200D-1F468-1F3FC","emoji":"👨🏿‍❤️‍👨🏼","text":"","type":1,"order":3559,"group":1,"subgroup":30,"version":13.1,"tone":[5,2]},{"label":"couple with heart: man, man, dark skin tone, medium skin tone","hexcode":"1F468-1F3FF-200D-2764-FE0F-200D-1F468-1F3FD","emoji":"👨🏿‍❤️‍👨🏽","text":"","type":1,"order":3561,"group":1,"subgroup":30,"version":13.1,"tone":[5,3]},{"label":"couple with heart: man, man, dark skin tone, medium-dark skin tone","hexcode":"1F468-1F3FF-200D-2764-FE0F-200D-1F468-1F3FE","emoji":"👨🏿‍❤️‍👨🏾","text":"","type":1,"order":3563,"group":1,"subgroup":30,"version":13.1,"tone":[5,4]},{"label":"couple with heart: man, man, dark skin tone","hexcode":"1F468-1F3FF-200D-2764-FE0F-200D-1F468-1F3FF","emoji":"👨🏿‍❤️‍👨🏿","text":"","type":1,"order":3565,"group":1,"subgroup":30,"version":13.1,"tone":5}]},{"label":"couple with heart: woman, woman","hexcode":"1F469-200D-2764-FE0F-200D-1F469","tags":["anniversary","babe","bae","couple","dating","heart","kiss","love","person","relationship","romance","together","woman","you"],"emoji":"👩‍❤️‍👩","text":"","type":1,"order":3567,"group":1,"subgroup":30,"version":2,"skins":[{"label":"couple with heart: woman, woman, light skin tone","hexcode":"1F469-1F3FB-200D-2764-FE0F-200D-1F469-1F3FB","emoji":"👩🏻‍❤️‍👩🏻","text":"","type":1,"order":3569,"group":1,"subgroup":30,"version":13.1,"tone":1},{"label":"couple with heart: woman, woman, light skin tone, medium-light skin tone","hexcode":"1F469-1F3FB-200D-2764-FE0F-200D-1F469-1F3FC","emoji":"👩🏻‍❤️‍👩🏼","text":"","type":1,"order":3571,"group":1,"subgroup":30,"version":13.1,"tone":[1,2]},{"label":"couple with heart: woman, woman, light skin tone, medium skin tone","hexcode":"1F469-1F3FB-200D-2764-FE0F-200D-1F469-1F3FD","emoji":"👩🏻‍❤️‍👩🏽","text":"","type":1,"order":3573,"group":1,"subgroup":30,"version":13.1,"tone":[1,3]},{"label":"couple with heart: woman, woman, light skin tone, medium-dark skin tone","hexcode":"1F469-1F3FB-200D-2764-FE0F-200D-1F469-1F3FE","emoji":"👩🏻‍❤️‍👩🏾","text":"","type":1,"order":3575,"group":1,"subgroup":30,"version":13.1,"tone":[1,4]},{"label":"couple with heart: woman, woman, light skin tone, dark skin tone","hexcode":"1F469-1F3FB-200D-2764-FE0F-200D-1F469-1F3FF","emoji":"👩🏻‍❤️‍👩🏿","text":"","type":1,"order":3577,"group":1,"subgroup":30,"version":13.1,"tone":[1,5]},{"label":"couple with heart: woman, woman, medium-light skin tone, light skin tone","hexcode":"1F469-1F3FC-200D-2764-FE0F-200D-1F469-1F3FB","emoji":"👩🏼‍❤️‍👩🏻","text":"","type":1,"order":3579,"group":1,"subgroup":30,"version":13.1,"tone":[2,1]},{"label":"couple with heart: woman, woman, medium-light skin tone","hexcode":"1F469-1F3FC-200D-2764-FE0F-200D-1F469-1F3FC","emoji":"👩🏼‍❤️‍👩🏼","text":"","type":1,"order":3581,"group":1,"subgroup":30,"version":13.1,"tone":2},{"label":"couple with heart: woman, woman, medium-light skin tone, medium skin tone","hexcode":"1F469-1F3FC-200D-2764-FE0F-200D-1F469-1F3FD","emoji":"👩🏼‍❤️‍👩🏽","text":"","type":1,"order":3583,"group":1,"subgroup":30,"version":13.1,"tone":[2,3]},{"label":"couple with heart: woman, woman, medium-light skin tone, medium-dark skin tone","hexcode":"1F469-1F3FC-200D-2764-FE0F-200D-1F469-1F3FE","emoji":"👩🏼‍❤️‍👩🏾","text":"","type":1,"order":3585,"group":1,"subgroup":30,"version":13.1,"tone":[2,4]},{"label":"couple with heart: woman, woman, medium-light skin tone, dark skin tone","hexcode":"1F469-1F3FC-200D-2764-FE0F-200D-1F469-1F3FF","emoji":"👩🏼‍❤️‍👩🏿","text":"","type":1,"order":3587,"group":1,"subgroup":30,"version":13.1,"tone":[2,5]},{"label":"couple with heart: woman, woman, medium skin tone, light skin tone","hexcode":"1F469-1F3FD-200D-2764-FE0F-200D-1F469-1F3FB","emoji":"👩🏽‍❤️‍👩🏻","text":"","type":1,"order":3589,"group":1,"subgroup":30,"version":13.1,"tone":[3,1]},{"label":"couple with heart: woman, woman, medium skin tone, medium-light skin tone","hexcode":"1F469-1F3FD-200D-2764-FE0F-200D-1F469-1F3FC","emoji":"👩🏽‍❤️‍👩🏼","text":"","type":1,"order":3591,"group":1,"subgroup":30,"version":13.1,"tone":[3,2]},{"label":"couple with heart: woman, woman, medium skin tone","hexcode":"1F469-1F3FD-200D-2764-FE0F-200D-1F469-1F3FD","emoji":"👩🏽‍❤️‍👩🏽","text":"","type":1,"order":3593,"group":1,"subgroup":30,"version":13.1,"tone":3},{"label":"couple with heart: woman, woman, medium skin tone, medium-dark skin tone","hexcode":"1F469-1F3FD-200D-2764-FE0F-200D-1F469-1F3FE","emoji":"👩🏽‍❤️‍👩🏾","text":"","type":1,"order":3595,"group":1,"subgroup":30,"version":13.1,"tone":[3,4]},{"label":"couple with heart: woman, woman, medium skin tone, dark skin tone","hexcode":"1F469-1F3FD-200D-2764-FE0F-200D-1F469-1F3FF","emoji":"👩🏽‍❤️‍👩🏿","text":"","type":1,"order":3597,"group":1,"subgroup":30,"version":13.1,"tone":[3,5]},{"label":"couple with heart: woman, woman, medium-dark skin tone, light skin tone","hexcode":"1F469-1F3FE-200D-2764-FE0F-200D-1F469-1F3FB","emoji":"👩🏾‍❤️‍👩🏻","text":"","type":1,"order":3599,"group":1,"subgroup":30,"version":13.1,"tone":[4,1]},{"label":"couple with heart: woman, woman, medium-dark skin tone, medium-light skin tone","hexcode":"1F469-1F3FE-200D-2764-FE0F-200D-1F469-1F3FC","emoji":"👩🏾‍❤️‍👩🏼","text":"","type":1,"order":3601,"group":1,"subgroup":30,"version":13.1,"tone":[4,2]},{"label":"couple with heart: woman, woman, medium-dark skin tone, medium skin tone","hexcode":"1F469-1F3FE-200D-2764-FE0F-200D-1F469-1F3FD","emoji":"👩🏾‍❤️‍👩🏽","text":"","type":1,"order":3603,"group":1,"subgroup":30,"version":13.1,"tone":[4,3]},{"label":"couple with heart: woman, woman, medium-dark skin tone","hexcode":"1F469-1F3FE-200D-2764-FE0F-200D-1F469-1F3FE","emoji":"👩🏾‍❤️‍👩🏾","text":"","type":1,"order":3605,"group":1,"subgroup":30,"version":13.1,"tone":4},{"label":"couple with heart: woman, woman, medium-dark skin tone, dark skin tone","hexcode":"1F469-1F3FE-200D-2764-FE0F-200D-1F469-1F3FF","emoji":"👩🏾‍❤️‍👩🏿","text":"","type":1,"order":3607,"group":1,"subgroup":30,"version":13.1,"tone":[4,5]},{"label":"couple with heart: woman, woman, dark skin tone, light skin tone","hexcode":"1F469-1F3FF-200D-2764-FE0F-200D-1F469-1F3FB","emoji":"👩🏿‍❤️‍👩🏻","text":"","type":1,"order":3609,"group":1,"subgroup":30,"version":13.1,"tone":[5,1]},{"label":"couple with heart: woman, woman, dark skin tone, medium-light skin tone","hexcode":"1F469-1F3FF-200D-2764-FE0F-200D-1F469-1F3FC","emoji":"👩🏿‍❤️‍👩🏼","text":"","type":1,"order":3611,"group":1,"subgroup":30,"version":13.1,"tone":[5,2]},{"label":"couple with heart: woman, woman, dark skin tone, medium skin tone","hexcode":"1F469-1F3FF-200D-2764-FE0F-200D-1F469-1F3FD","emoji":"👩🏿‍❤️‍👩🏽","text":"","type":1,"order":3613,"group":1,"subgroup":30,"version":13.1,"tone":[5,3]},{"label":"couple with heart: woman, woman, dark skin tone, medium-dark skin tone","hexcode":"1F469-1F3FF-200D-2764-FE0F-200D-1F469-1F3FE","emoji":"👩🏿‍❤️‍👩🏾","text":"","type":1,"order":3615,"group":1,"subgroup":30,"version":13.1,"tone":[5,4]},{"label":"couple with heart: woman, woman, dark skin tone","hexcode":"1F469-1F3FF-200D-2764-FE0F-200D-1F469-1F3FF","emoji":"👩🏿‍❤️‍👩🏿","text":"","type":1,"order":3617,"group":1,"subgroup":30,"version":13.1,"tone":5}]},{"label":"family: man, woman, boy","hexcode":"1F468-200D-1F469-200D-1F466","tags":["boy","child","family","man","woman"],"emoji":"👨‍👩‍👦","text":"","type":1,"order":3619,"group":1,"subgroup":30,"version":2},{"label":"family: man, woman, girl","hexcode":"1F468-200D-1F469-200D-1F467","tags":["child","family","girl","man","woman"],"emoji":"👨‍👩‍👧","text":"","type":1,"order":3620,"group":1,"subgroup":30,"version":2},{"label":"family: man, woman, girl, boy","hexcode":"1F468-200D-1F469-200D-1F467-200D-1F466","tags":["boy","child","family","girl","man","woman"],"emoji":"👨‍👩‍👧‍👦","text":"","type":1,"order":3621,"group":1,"subgroup":30,"version":2},{"label":"family: man, woman, boy, boy","hexcode":"1F468-200D-1F469-200D-1F466-200D-1F466","tags":["boy","child","family","man","woman"],"emoji":"👨‍👩‍👦‍👦","text":"","type":1,"order":3622,"group":1,"subgroup":30,"version":2},{"label":"family: man, woman, girl, girl","hexcode":"1F468-200D-1F469-200D-1F467-200D-1F467","tags":["child","family","girl","man","woman"],"emoji":"👨‍👩‍👧‍👧","text":"","type":1,"order":3623,"group":1,"subgroup":30,"version":2},{"label":"family: man, man, boy","hexcode":"1F468-200D-1F468-200D-1F466","tags":["boy","child","family","man"],"emoji":"👨‍👨‍👦","text":"","type":1,"order":3624,"group":1,"subgroup":30,"version":2},{"label":"family: man, man, girl","hexcode":"1F468-200D-1F468-200D-1F467","tags":["child","family","girl","man"],"emoji":"👨‍👨‍👧","text":"","type":1,"order":3625,"group":1,"subgroup":30,"version":2},{"label":"family: man, man, girl, boy","hexcode":"1F468-200D-1F468-200D-1F467-200D-1F466","tags":["boy","child","family","girl","man"],"emoji":"👨‍👨‍👧‍👦","text":"","type":1,"order":3626,"group":1,"subgroup":30,"version":2},{"label":"family: man, man, boy, boy","hexcode":"1F468-200D-1F468-200D-1F466-200D-1F466","tags":["boy","child","family","man"],"emoji":"👨‍👨‍👦‍👦","text":"","type":1,"order":3627,"group":1,"subgroup":30,"version":2},{"label":"family: man, man, girl, girl","hexcode":"1F468-200D-1F468-200D-1F467-200D-1F467","tags":["child","family","girl","man"],"emoji":"👨‍👨‍👧‍👧","text":"","type":1,"order":3628,"group":1,"subgroup":30,"version":2},{"label":"family: woman, woman, boy","hexcode":"1F469-200D-1F469-200D-1F466","tags":["boy","child","family","woman"],"emoji":"👩‍👩‍👦","text":"","type":1,"order":3629,"group":1,"subgroup":30,"version":2},{"label":"family: woman, woman, girl","hexcode":"1F469-200D-1F469-200D-1F467","tags":["child","family","girl","woman"],"emoji":"👩‍👩‍👧","text":"","type":1,"order":3630,"group":1,"subgroup":30,"version":2},{"label":"family: woman, woman, girl, boy","hexcode":"1F469-200D-1F469-200D-1F467-200D-1F466","tags":["boy","child","family","girl","woman"],"emoji":"👩‍👩‍👧‍👦","text":"","type":1,"order":3631,"group":1,"subgroup":30,"version":2},{"label":"family: woman, woman, boy, boy","hexcode":"1F469-200D-1F469-200D-1F466-200D-1F466","tags":["boy","child","family","woman"],"emoji":"👩‍👩‍👦‍👦","text":"","type":1,"order":3632,"group":1,"subgroup":30,"version":2},{"label":"family: woman, woman, girl, girl","hexcode":"1F469-200D-1F469-200D-1F467-200D-1F467","tags":["child","family","girl","woman"],"emoji":"👩‍👩‍👧‍👧","text":"","type":1,"order":3633,"group":1,"subgroup":30,"version":2},{"label":"family: man, boy","hexcode":"1F468-200D-1F466","tags":["boy","child","family","man"],"emoji":"👨‍👦","text":"","type":1,"order":3634,"group":1,"subgroup":30,"version":4},{"label":"family: man, boy, boy","hexcode":"1F468-200D-1F466-200D-1F466","tags":["boy","child","family","man"],"emoji":"👨‍👦‍👦","text":"","type":1,"order":3635,"group":1,"subgroup":30,"version":4},{"label":"family: man, girl","hexcode":"1F468-200D-1F467","tags":["child","family","girl","man"],"emoji":"👨‍👧","text":"","type":1,"order":3636,"group":1,"subgroup":30,"version":4},{"label":"family: man, girl, boy","hexcode":"1F468-200D-1F467-200D-1F466","tags":["boy","child","family","girl","man"],"emoji":"👨‍👧‍👦","text":"","type":1,"order":3637,"group":1,"subgroup":30,"version":4},{"label":"family: man, girl, girl","hexcode":"1F468-200D-1F467-200D-1F467","tags":["child","family","girl","man"],"emoji":"👨‍👧‍👧","text":"","type":1,"order":3638,"group":1,"subgroup":30,"version":4},{"label":"family: woman, boy","hexcode":"1F469-200D-1F466","tags":["boy","child","family","woman"],"emoji":"👩‍👦","text":"","type":1,"order":3639,"group":1,"subgroup":30,"version":4},{"label":"family: woman, boy, boy","hexcode":"1F469-200D-1F466-200D-1F466","tags":["boy","child","family","woman"],"emoji":"👩‍👦‍👦","text":"","type":1,"order":3640,"group":1,"subgroup":30,"version":4},{"label":"family: woman, girl","hexcode":"1F469-200D-1F467","tags":["child","family","girl","woman"],"emoji":"👩‍👧","text":"","type":1,"order":3641,"group":1,"subgroup":30,"version":4},{"label":"family: woman, girl, boy","hexcode":"1F469-200D-1F467-200D-1F466","tags":["boy","child","family","girl","woman"],"emoji":"👩‍👧‍👦","text":"","type":1,"order":3642,"group":1,"subgroup":30,"version":4},{"label":"family: woman, girl, girl","hexcode":"1F469-200D-1F467-200D-1F467","tags":["child","family","girl","woman"],"emoji":"👩‍👧‍👧","text":"","type":1,"order":3643,"group":1,"subgroup":30,"version":4},{"label":"speaking head","hexcode":"1F5E3","tags":["face","head","silhouette","speak","speaking"],"emoji":"🗣️","text":"🗣︎","type":0,"order":3645,"group":1,"subgroup":31,"version":0.7},{"label":"bust in silhouette","hexcode":"1F464","tags":["bust","mysterious","shadow","silhouette"],"emoji":"👤","text":"","type":1,"order":3646,"group":1,"subgroup":31,"version":0.6},{"label":"busts in silhouette","hexcode":"1F465","tags":["bff","bust","busts","everyone","friend","friends","people","silhouette"],"emoji":"👥","text":"","type":1,"order":3647,"group":1,"subgroup":31,"version":1},{"label":"people hugging","hexcode":"1FAC2","tags":["comfort","embrace","farewell","friendship","goodbye","hello","hug","hugging","love","people","thanks"],"emoji":"🫂","text":"","type":1,"order":3648,"group":1,"subgroup":31,"version":13},{"label":"family","hexcode":"1F46A","tags":["child"],"emoji":"👪️","text":"👪︎","type":1,"order":3649,"group":1,"subgroup":31,"version":0.6},{"label":"family: adult, adult, child","hexcode":"1F9D1-200D-1F9D1-200D-1F9D2","tags":["adult","child","family"],"emoji":"🧑‍🧑‍🧒","text":"","type":1,"order":3650,"group":1,"subgroup":31,"version":15.1},{"label":"family: adult, adult, child, child","hexcode":"1F9D1-200D-1F9D1-200D-1F9D2-200D-1F9D2","tags":["adult","child","family"],"emoji":"🧑‍🧑‍🧒‍🧒","text":"","type":1,"order":3651,"group":1,"subgroup":31,"version":15.1},{"label":"family: adult, child","hexcode":"1F9D1-200D-1F9D2","tags":["adult","child","family"],"emoji":"🧑‍🧒","text":"","type":1,"order":3652,"group":1,"subgroup":31,"version":15.1},{"label":"family: adult, child, child","hexcode":"1F9D1-200D-1F9D2-200D-1F9D2","tags":["adult","child","family"],"emoji":"🧑‍🧒‍🧒","text":"","type":1,"order":3653,"group":1,"subgroup":31,"version":15.1},{"label":"footprints","hexcode":"1F463","tags":["barefoot","clothing","footprint","omw","print","walk"],"emoji":"👣","text":"","type":1,"order":3654,"group":1,"subgroup":31,"version":0.6},{"label":"fingerprint","hexcode":"1FAC6","tags":["clue","crime","detective","forensics","identity","mystery","print","safety","trace"],"emoji":"🫆","text":"","type":1,"order":3655,"group":1,"subgroup":31,"version":16},{"label":"light skin tone","hexcode":"1F3FB","tags":["1–2","light","skin","tone","type"],"emoji":"🏻","text":"","type":1,"order":3656,"group":2,"subgroup":32,"version":1},{"label":"medium-light skin tone","hexcode":"1F3FC","tags":["3","medium-light","skin","tone","type"],"emoji":"🏼","text":"","type":1,"order":3657,"group":2,"subgroup":32,"version":1},{"label":"medium skin tone","hexcode":"1F3FD","tags":["4","medium","skin","tone","type"],"emoji":"🏽","text":"","type":1,"order":3658,"group":2,"subgroup":32,"version":1},{"label":"medium-dark skin tone","hexcode":"1F3FE","tags":["5","medium-dark","skin","tone","type"],"emoji":"🏾","text":"","type":1,"order":3659,"group":2,"subgroup":32,"version":1},{"label":"dark skin tone","hexcode":"1F3FF","tags":["6","dark","skin","tone","type"],"emoji":"🏿","text":"","type":1,"order":3660,"group":2,"subgroup":32,"version":1},{"label":"red hair","hexcode":"1F9B0","tags":["ginger","hair","red","redhead"],"emoji":"🦰","text":"","type":1,"order":3661,"group":2,"subgroup":33,"version":11},{"label":"curly hair","hexcode":"1F9B1","tags":["afro","curly","hair","ringlets"],"emoji":"🦱","text":"","type":1,"order":3662,"group":2,"subgroup":33,"version":11},{"label":"white hair","hexcode":"1F9B3","tags":["gray","hair","old","white"],"emoji":"🦳","text":"","type":1,"order":3663,"group":2,"subgroup":33,"version":11},{"label":"bald","hexcode":"1F9B2","tags":["chemotherapy","hair","hairless","no","shaven"],"emoji":"🦲","text":"","type":1,"order":3664,"group":2,"subgroup":33,"version":11},{"label":"monkey face","hexcode":"1F435","tags":["animal","banana","face","monkey"],"emoji":"🐵","text":"","type":1,"order":3665,"group":3,"subgroup":34,"version":0.6},{"label":"monkey","hexcode":"1F412","tags":["animal","banana"],"emoji":"🐒","text":"","type":1,"order":3666,"group":3,"subgroup":34,"version":0.6},{"label":"gorilla","hexcode":"1F98D","tags":["animal"],"emoji":"🦍","text":"","type":1,"order":3667,"group":3,"subgroup":34,"version":3},{"label":"orangutan","hexcode":"1F9A7","tags":["animal","ape","monkey"],"emoji":"🦧","text":"","type":1,"order":3668,"group":3,"subgroup":34,"version":12},{"label":"dog face","hexcode":"1F436","tags":["adorbs","animal","dog","face","pet","puppies","puppy"],"emoji":"🐶","text":"","type":1,"order":3669,"group":3,"subgroup":34,"version":0.6},{"label":"dog","hexcode":"1F415","tags":["animal","animals","dogs","pet"],"emoji":"🐕️","text":"🐕︎","type":1,"order":3670,"group":3,"subgroup":34,"version":0.7},{"label":"guide dog","hexcode":"1F9AE","tags":["accessibility","animal","blind","dog","guide"],"emoji":"🦮","text":"","type":1,"order":3671,"group":3,"subgroup":34,"version":12},{"label":"service dog","hexcode":"1F415-200D-1F9BA","tags":["accessibility","animal","assistance","dog","service"],"emoji":"🐕‍🦺","text":"","type":1,"order":3672,"group":3,"subgroup":34,"version":12},{"label":"poodle","hexcode":"1F429","tags":["animal","dog","fluffy"],"emoji":"🐩","text":"","type":1,"order":3673,"group":3,"subgroup":34,"version":0.6},{"label":"wolf","hexcode":"1F43A","tags":["animal","face"],"emoji":"🐺","text":"","type":1,"order":3674,"group":3,"subgroup":34,"version":0.6},{"label":"fox","hexcode":"1F98A","tags":["animal","face"],"emoji":"🦊","text":"","type":1,"order":3675,"group":3,"subgroup":34,"version":3},{"label":"raccoon","hexcode":"1F99D","tags":["animal","curious","sly"],"emoji":"🦝","text":"","type":1,"order":3676,"group":3,"subgroup":34,"version":11},{"label":"cat face","hexcode":"1F431","tags":["animal","cat","face","kitten","kitty","pet"],"emoji":"🐱","text":"","type":1,"order":3677,"group":3,"subgroup":34,"version":0.6},{"label":"cat","hexcode":"1F408","tags":["animal","animals","cats","kitten","pet"],"emoji":"🐈️","text":"🐈︎","type":1,"order":3678,"group":3,"subgroup":34,"version":0.7},{"label":"black cat","hexcode":"1F408-200D-2B1B","tags":["animal","black","cat","feline","halloween","meow","unlucky"],"emoji":"🐈‍⬛","text":"","type":1,"order":3679,"group":3,"subgroup":34,"version":13},{"label":"lion","hexcode":"1F981","tags":["alpha","animal","face","leo","mane","order","rawr","roar","safari","strong","zodiac"],"emoji":"🦁","text":"","type":1,"order":3680,"group":3,"subgroup":34,"version":1},{"label":"tiger face","hexcode":"1F42F","tags":["animal","big","cat","face","predator","tiger"],"emoji":"🐯","text":"","type":1,"order":3681,"group":3,"subgroup":34,"version":0.6},{"label":"tiger","hexcode":"1F405","tags":["animal","big","cat","predator","zoo"],"emoji":"🐅","text":"","type":1,"order":3682,"group":3,"subgroup":34,"version":1},{"label":"leopard","hexcode":"1F406","tags":["animal","big","cat","predator","zoo"],"emoji":"🐆","text":"","type":1,"order":3683,"group":3,"subgroup":34,"version":1},{"label":"horse face","hexcode":"1F434","tags":["animal","dressage","equine","face","farm","horse","horses"],"emoji":"🐴","text":"","type":1,"order":3684,"group":3,"subgroup":34,"version":0.6},{"label":"moose","hexcode":"1FACE","tags":["alces","animal","antlers","elk","mammal"],"emoji":"🫎","text":"","type":1,"order":3685,"group":3,"subgroup":34,"version":15},{"label":"donkey","hexcode":"1FACF","tags":["animal","ass","burro","hinny","mammal","mule","stubborn"],"emoji":"🫏","text":"","type":1,"order":3686,"group":3,"subgroup":34,"version":15},{"label":"horse","hexcode":"1F40E","tags":["animal","equestrian","farm","racehorse","racing"],"emoji":"🐎","text":"","type":1,"order":3687,"group":3,"subgroup":34,"version":0.6},{"label":"unicorn","hexcode":"1F984","tags":["face"],"emoji":"🦄","text":"","type":1,"order":3688,"group":3,"subgroup":34,"version":1},{"label":"zebra","hexcode":"1F993","tags":["animal","stripe"],"emoji":"🦓","text":"","type":1,"order":3689,"group":3,"subgroup":34,"version":5},{"label":"deer","hexcode":"1F98C","tags":["animal"],"emoji":"🦌","text":"","type":1,"order":3690,"group":3,"subgroup":34,"version":3},{"label":"bison","hexcode":"1F9AC","tags":["animal","buffalo","herd","wisent"],"emoji":"🦬","text":"","type":1,"order":3691,"group":3,"subgroup":34,"version":13},{"label":"cow face","hexcode":"1F42E","tags":["animal","cow","face","farm","milk","moo"],"emoji":"🐮","text":"","type":1,"order":3692,"group":3,"subgroup":34,"version":0.6},{"label":"ox","hexcode":"1F402","tags":["animal","animals","bull","farm","taurus","zodiac"],"emoji":"🐂","text":"","type":1,"order":3693,"group":3,"subgroup":34,"version":1},{"label":"water buffalo","hexcode":"1F403","tags":["animal","buffalo","water","zoo"],"emoji":"🐃","text":"","type":1,"order":3694,"group":3,"subgroup":34,"version":1},{"label":"cow","hexcode":"1F404","tags":["animal","animals","farm","milk","moo"],"emoji":"🐄","text":"","type":1,"order":3695,"group":3,"subgroup":34,"version":1},{"label":"pig face","hexcode":"1F437","tags":["animal","bacon","face","farm","pig","pork"],"emoji":"🐷","text":"","type":1,"order":3696,"group":3,"subgroup":34,"version":0.6},{"label":"pig","hexcode":"1F416","tags":["animal","bacon","farm","pork","sow"],"emoji":"🐖","text":"","type":1,"order":3697,"group":3,"subgroup":34,"version":1},{"label":"boar","hexcode":"1F417","tags":["animal","pig"],"emoji":"🐗","text":"","type":1,"order":3698,"group":3,"subgroup":34,"version":0.6},{"label":"pig nose","hexcode":"1F43D","tags":["animal","face","farm","nose","pig","smell","snout"],"emoji":"🐽","text":"","type":1,"order":3699,"group":3,"subgroup":34,"version":0.6},{"label":"ram","hexcode":"1F40F","tags":["animal","aries","horns","male","sheep","zodiac","zoo"],"emoji":"🐏","text":"","type":1,"order":3700,"group":3,"subgroup":34,"version":1},{"label":"ewe","hexcode":"1F411","tags":["animal","baa","farm","female","fluffy","lamb","sheep","wool"],"emoji":"🐑","text":"","type":1,"order":3701,"group":3,"subgroup":34,"version":0.6},{"label":"goat","hexcode":"1F410","tags":["animal","capricorn","farm","milk","zodiac"],"emoji":"🐐","text":"","type":1,"order":3702,"group":3,"subgroup":34,"version":1},{"label":"camel","hexcode":"1F42A","tags":["animal","desert","dromedary","hump","one"],"emoji":"🐪","text":"","type":1,"order":3703,"group":3,"subgroup":34,"version":1},{"label":"two-hump camel","hexcode":"1F42B","tags":["animal","bactrian","camel","desert","hump","two","two-hump"],"emoji":"🐫","text":"","type":1,"order":3704,"group":3,"subgroup":34,"version":0.6},{"label":"llama","hexcode":"1F999","tags":["alpaca","animal","guanaco","vicuña","wool"],"emoji":"🦙","text":"","type":1,"order":3705,"group":3,"subgroup":34,"version":11},{"label":"giraffe","hexcode":"1F992","tags":["animal","spots"],"emoji":"🦒","text":"","type":1,"order":3706,"group":3,"subgroup":34,"version":5},{"label":"elephant","hexcode":"1F418","tags":["animal"],"emoji":"🐘","text":"","type":1,"order":3707,"group":3,"subgroup":34,"version":0.6},{"label":"mammoth","hexcode":"1F9A3","tags":["animal","extinction","large","tusk","wooly"],"emoji":"🦣","text":"","type":1,"order":3708,"group":3,"subgroup":34,"version":13},{"label":"rhinoceros","hexcode":"1F98F","tags":["animal"],"emoji":"🦏","text":"","type":1,"order":3709,"group":3,"subgroup":34,"version":3},{"label":"hippopotamus","hexcode":"1F99B","tags":["animal","hippo"],"emoji":"🦛","text":"","type":1,"order":3710,"group":3,"subgroup":34,"version":11},{"label":"mouse face","hexcode":"1F42D","tags":["animal","face","mouse"],"emoji":"🐭","text":"","type":1,"order":3711,"group":3,"subgroup":34,"version":0.6},{"label":"mouse","hexcode":"1F401","tags":["animal","animals"],"emoji":"🐁","text":"","type":1,"order":3712,"group":3,"subgroup":34,"version":1},{"label":"rat","hexcode":"1F400","tags":["animal"],"emoji":"🐀","text":"","type":1,"order":3713,"group":3,"subgroup":34,"version":1},{"label":"hamster","hexcode":"1F439","tags":["animal","face","pet"],"emoji":"🐹","text":"","type":1,"order":3714,"group":3,"subgroup":34,"version":0.6},{"label":"rabbit face","hexcode":"1F430","tags":["animal","bunny","face","pet","rabbit"],"emoji":"🐰","text":"","type":1,"order":3715,"group":3,"subgroup":34,"version":0.6},{"label":"rabbit","hexcode":"1F407","tags":["animal","bunny","pet"],"emoji":"🐇","text":"","type":1,"order":3716,"group":3,"subgroup":34,"version":1},{"label":"chipmunk","hexcode":"1F43F","tags":["animal","squirrel"],"emoji":"🐿️","text":"🐿︎","type":0,"order":3718,"group":3,"subgroup":34,"version":0.7},{"label":"beaver","hexcode":"1F9AB","tags":["animal","dam","teeth"],"emoji":"🦫","text":"","type":1,"order":3719,"group":3,"subgroup":34,"version":13},{"label":"hedgehog","hexcode":"1F994","tags":["animal","spiny"],"emoji":"🦔","text":"","type":1,"order":3720,"group":3,"subgroup":34,"version":5},{"label":"bat","hexcode":"1F987","tags":["animal","vampire"],"emoji":"🦇","text":"","type":1,"order":3721,"group":3,"subgroup":34,"version":3},{"label":"bear","hexcode":"1F43B","tags":["animal","face","grizzly","growl","honey"],"emoji":"🐻","text":"","type":1,"order":3722,"group":3,"subgroup":34,"version":0.6},{"label":"polar bear","hexcode":"1F43B-200D-2744-FE0F","tags":["animal","arctic","bear","polar","white"],"emoji":"🐻‍❄️","text":"","type":1,"order":3723,"group":3,"subgroup":34,"version":13},{"label":"koala","hexcode":"1F428","tags":["animal","australia","bear","down","face","marsupial","under"],"emoji":"🐨","text":"","type":1,"order":3725,"group":3,"subgroup":34,"version":0.6},{"label":"panda","hexcode":"1F43C","tags":["animal","bamboo","face"],"emoji":"🐼","text":"","type":1,"order":3726,"group":3,"subgroup":34,"version":0.6},{"label":"sloth","hexcode":"1F9A5","tags":["lazy","slow"],"emoji":"🦥","text":"","type":1,"order":3727,"group":3,"subgroup":34,"version":12},{"label":"otter","hexcode":"1F9A6","tags":["animal","fishing","playful"],"emoji":"🦦","text":"","type":1,"order":3728,"group":3,"subgroup":34,"version":12},{"label":"skunk","hexcode":"1F9A8","tags":["animal","stink"],"emoji":"🦨","text":"","type":1,"order":3729,"group":3,"subgroup":34,"version":12},{"label":"kangaroo","hexcode":"1F998","tags":["animal","joey","jump","marsupial"],"emoji":"🦘","text":"","type":1,"order":3730,"group":3,"subgroup":34,"version":11},{"label":"badger","hexcode":"1F9A1","tags":["animal","honey","pester"],"emoji":"🦡","text":"","type":1,"order":3731,"group":3,"subgroup":34,"version":11},{"label":"paw prints","hexcode":"1F43E","tags":["feet","paw","paws","print","prints"],"emoji":"🐾","text":"","type":1,"order":3732,"group":3,"subgroup":34,"version":0.6},{"label":"turkey","hexcode":"1F983","tags":["bird","gobble","thanksgiving"],"emoji":"🦃","text":"","type":1,"order":3733,"group":3,"subgroup":35,"version":1},{"label":"chicken","hexcode":"1F414","tags":["animal","bird","ornithology"],"emoji":"🐔","text":"","type":1,"order":3734,"group":3,"subgroup":35,"version":0.6},{"label":"rooster","hexcode":"1F413","tags":["animal","bird","ornithology"],"emoji":"🐓","text":"","type":1,"order":3735,"group":3,"subgroup":35,"version":1},{"label":"hatching chick","hexcode":"1F423","tags":["animal","baby","bird","chick","egg","hatching"],"emoji":"🐣","text":"","type":1,"order":3736,"group":3,"subgroup":35,"version":0.6},{"label":"baby chick","hexcode":"1F424","tags":["animal","baby","bird","chick","ornithology"],"emoji":"🐤","text":"","type":1,"order":3737,"group":3,"subgroup":35,"version":0.6},{"label":"front-facing baby chick","hexcode":"1F425","tags":["animal","baby","bird","chick","front-facing","newborn","ornithology"],"emoji":"🐥","text":"","type":1,"order":3738,"group":3,"subgroup":35,"version":0.6},{"label":"bird","hexcode":"1F426","tags":["animal","ornithology"],"emoji":"🐦️","text":"🐦︎","type":1,"order":3739,"group":3,"subgroup":35,"version":0.6},{"label":"penguin","hexcode":"1F427","tags":["animal","antarctica","bird","ornithology"],"emoji":"🐧","text":"","type":1,"order":3740,"group":3,"subgroup":35,"version":0.6},{"label":"dove","hexcode":"1F54A","tags":["bird","fly","ornithology","peace"],"emoji":"🕊️","text":"🕊︎","type":0,"order":3742,"group":3,"subgroup":35,"version":0.7},{"label":"eagle","hexcode":"1F985","tags":["animal","bird","ornithology"],"emoji":"🦅","text":"","type":1,"order":3743,"group":3,"subgroup":35,"version":3},{"label":"duck","hexcode":"1F986","tags":["animal","bird","ornithology"],"emoji":"🦆","text":"","type":1,"order":3744,"group":3,"subgroup":35,"version":3},{"label":"swan","hexcode":"1F9A2","tags":["animal","bird","cygnet","duckling","ornithology","ugly"],"emoji":"🦢","text":"","type":1,"order":3745,"group":3,"subgroup":35,"version":11},{"label":"owl","hexcode":"1F989","tags":["animal","bird","ornithology","wise"],"emoji":"🦉","text":"","type":1,"order":3746,"group":3,"subgroup":35,"version":3},{"label":"dodo","hexcode":"1F9A4","tags":["animal","bird","extinction","large","ornithology"],"emoji":"🦤","text":"","type":1,"order":3747,"group":3,"subgroup":35,"version":13},{"label":"feather","hexcode":"1FAB6","tags":["bird","flight","light","plumage"],"emoji":"🪶","text":"","type":1,"order":3748,"group":3,"subgroup":35,"version":13},{"label":"flamingo","hexcode":"1F9A9","tags":["animal","bird","flamboyant","ornithology","tropical"],"emoji":"🦩","text":"","type":1,"order":3749,"group":3,"subgroup":35,"version":12},{"label":"peacock","hexcode":"1F99A","tags":["animal","bird","colorful","ornithology","ostentatious","peahen","pretty","proud"],"emoji":"🦚","text":"","type":1,"order":3750,"group":3,"subgroup":35,"version":11},{"label":"parrot","hexcode":"1F99C","tags":["animal","bird","ornithology","pirate","talk"],"emoji":"🦜","text":"","type":1,"order":3751,"group":3,"subgroup":35,"version":11},{"label":"wing","hexcode":"1FABD","tags":["angelic","ascend","aviation","bird","fly","flying","heavenly","mythology","soar"],"emoji":"🪽","text":"","type":1,"order":3752,"group":3,"subgroup":35,"version":15},{"label":"black bird","hexcode":"1F426-200D-2B1B","tags":["animal","beak","bird","black","caw","corvid","crow","ornithology","raven","rook"],"emoji":"🐦‍⬛","text":"","type":1,"order":3753,"group":3,"subgroup":35,"version":15},{"label":"goose","hexcode":"1FABF","tags":["animal","bird","duck","flock","fowl","gaggle","gander","geese","honk","ornithology","silly"],"emoji":"🪿","text":"","type":1,"order":3754,"group":3,"subgroup":35,"version":15},{"label":"phoenix","hexcode":"1F426-200D-1F525","tags":["ascend","ascension","emerge","fantasy","firebird","glory","immortal","rebirth","reincarnation","reinvent","renewal","revival","revive","rise","transform"],"emoji":"🐦‍🔥","text":"","type":1,"order":3755,"group":3,"subgroup":35,"version":15.1},{"label":"frog","hexcode":"1F438","tags":["animal","face"],"emoji":"🐸","text":"","type":1,"order":3756,"group":3,"subgroup":36,"version":0.6},{"label":"crocodile","hexcode":"1F40A","tags":["animal","zoo"],"emoji":"🐊","text":"","type":1,"order":3757,"group":3,"subgroup":37,"version":1},{"label":"turtle","hexcode":"1F422","tags":["animal","terrapin","tortoise"],"emoji":"🐢","text":"","type":1,"order":3758,"group":3,"subgroup":37,"version":0.6},{"label":"lizard","hexcode":"1F98E","tags":["animal","reptile"],"emoji":"🦎","text":"","type":1,"order":3759,"group":3,"subgroup":37,"version":3},{"label":"snake","hexcode":"1F40D","tags":["animal","bearer","ophiuchus","serpent","zodiac"],"emoji":"🐍","text":"","type":1,"order":3760,"group":3,"subgroup":37,"version":0.6},{"label":"dragon face","hexcode":"1F432","tags":["animal","dragon","face","fairy","fairytale","tale"],"emoji":"🐲","text":"","type":1,"order":3761,"group":3,"subgroup":37,"version":0.6},{"label":"dragon","hexcode":"1F409","tags":["animal","fairy","fairytale","knights","tale"],"emoji":"🐉","text":"","type":1,"order":3762,"group":3,"subgroup":37,"version":1},{"label":"sauropod","hexcode":"1F995","tags":["brachiosaurus","brontosaurus","dinosaur","diplodocus"],"emoji":"🦕","text":"","type":1,"order":3763,"group":3,"subgroup":37,"version":5},{"label":"T-Rex","hexcode":"1F996","tags":["dinosaur","rex","t","t-rex","tyrannosaurus"],"emoji":"🦖","text":"","type":1,"order":3764,"group":3,"subgroup":37,"version":5},{"label":"spouting whale","hexcode":"1F433","tags":["animal","beach","face","ocean","spouting","whale"],"emoji":"🐳","text":"","type":1,"order":3765,"group":3,"subgroup":38,"version":0.6},{"label":"whale","hexcode":"1F40B","tags":["animal","beach","ocean"],"emoji":"🐋","text":"","type":1,"order":3766,"group":3,"subgroup":38,"version":1},{"label":"dolphin","hexcode":"1F42C","tags":["animal","beach","flipper","ocean"],"emoji":"🐬","text":"","type":1,"order":3767,"group":3,"subgroup":38,"version":0.6},{"label":"orca","hexcode":"1FACD","tags":["marine","ocean","whale"],"emoji":"🫍","text":"","type":1,"order":3768,"group":3,"subgroup":38,"version":17},{"label":"seal","hexcode":"1F9AD","tags":["animal","lion","ocean","sea"],"emoji":"🦭","text":"","type":1,"order":3769,"group":3,"subgroup":38,"version":13},{"label":"fish","hexcode":"1F41F","tags":["animal","dinner","fishes","fishing","pisces","zodiac"],"emoji":"🐟️","text":"🐟︎","type":1,"order":3770,"group":3,"subgroup":38,"version":0.6},{"label":"tropical fish","hexcode":"1F420","tags":["animal","fish","fishes","tropical"],"emoji":"🐠","text":"","type":1,"order":3771,"group":3,"subgroup":38,"version":0.6},{"label":"blowfish","hexcode":"1F421","tags":["animal","fish"],"emoji":"🐡","text":"","type":1,"order":3772,"group":3,"subgroup":38,"version":0.6},{"label":"shark","hexcode":"1F988","tags":["animal","fish"],"emoji":"🦈","text":"","type":1,"order":3773,"group":3,"subgroup":38,"version":3},{"label":"octopus","hexcode":"1F419","tags":["animal","creature","ocean"],"emoji":"🐙","text":"","type":1,"order":3774,"group":3,"subgroup":38,"version":0.6},{"label":"spiral shell","hexcode":"1F41A","tags":["animal","beach","conch","sea","shell","spiral"],"emoji":"🐚","text":"","type":1,"order":3775,"group":3,"subgroup":38,"version":0.6},{"label":"coral","hexcode":"1FAB8","tags":["change","climate","ocean","reef","sea"],"emoji":"🪸","text":"","type":1,"order":3776,"group":3,"subgroup":38,"version":14},{"label":"jellyfish","hexcode":"1FABC","tags":["animal","aquarium","burn","invertebrate","jelly","life","marine","ocean","ouch","plankton","sea","sting","stinger","tentacles"],"emoji":"🪼","text":"","type":1,"order":3777,"group":3,"subgroup":38,"version":15},{"label":"crab","hexcode":"1F980","tags":["cancer","zodiac"],"emoji":"🦀","text":"","type":1,"order":3778,"group":3,"subgroup":38,"version":1},{"label":"lobster","hexcode":"1F99E","tags":["animal","bisque","claws","seafood"],"emoji":"🦞","text":"","type":1,"order":3779,"group":3,"subgroup":38,"version":11},{"label":"shrimp","hexcode":"1F990","tags":["food","shellfish","small"],"emoji":"🦐","text":"","type":1,"order":3780,"group":3,"subgroup":38,"version":3},{"label":"squid","hexcode":"1F991","tags":["animal","food","mollusk"],"emoji":"🦑","text":"","type":1,"order":3781,"group":3,"subgroup":38,"version":3},{"label":"oyster","hexcode":"1F9AA","tags":["diving","pearl"],"emoji":"🦪","text":"","type":1,"order":3782,"group":3,"subgroup":38,"version":12},{"label":"snail","hexcode":"1F40C","tags":["animal","escargot","garden","nature","slug"],"emoji":"🐌","text":"","type":1,"order":3783,"group":3,"subgroup":39,"version":0.6},{"label":"butterfly","hexcode":"1F98B","tags":["insect","pretty"],"emoji":"🦋","text":"","type":1,"order":3784,"group":3,"subgroup":39,"version":3},{"label":"bug","hexcode":"1F41B","tags":["animal","garden","insect"],"emoji":"🐛","text":"","type":1,"order":3785,"group":3,"subgroup":39,"version":0.6},{"label":"ant","hexcode":"1F41C","tags":["animal","garden","insect"],"emoji":"🐜","text":"","type":1,"order":3786,"group":3,"subgroup":39,"version":0.6},{"label":"honeybee","hexcode":"1F41D","tags":["animal","bee","bumblebee","honey","insect","nature","spring"],"emoji":"🐝","text":"","type":1,"order":3787,"group":3,"subgroup":39,"version":0.6},{"label":"beetle","hexcode":"1FAB2","tags":["animal","bug","insect"],"emoji":"🪲","text":"","type":1,"order":3788,"group":3,"subgroup":39,"version":13},{"label":"lady beetle","hexcode":"1F41E","tags":["animal","beetle","garden","insect","lady","ladybird","ladybug","nature"],"emoji":"🐞","text":"","type":1,"order":3789,"group":3,"subgroup":39,"version":0.6},{"label":"cricket","hexcode":"1F997","tags":["animal","bug","grasshopper","insect","orthoptera"],"emoji":"🦗","text":"","type":1,"order":3790,"group":3,"subgroup":39,"version":5},{"label":"cockroach","hexcode":"1FAB3","tags":["animal","insect","pest","roach"],"emoji":"🪳","text":"","type":1,"order":3791,"group":3,"subgroup":39,"version":13},{"label":"spider","hexcode":"1F577","tags":["animal","insect"],"emoji":"🕷️","text":"🕷︎","type":0,"order":3793,"group":3,"subgroup":39,"version":0.7},{"label":"spider web","hexcode":"1F578","tags":["spider","web"],"emoji":"🕸️","text":"🕸︎","type":0,"order":3795,"group":3,"subgroup":39,"version":0.7},{"label":"scorpion","hexcode":"1F982","tags":["scorpio","scorpius","zodiac"],"emoji":"🦂","text":"","type":1,"order":3796,"group":3,"subgroup":39,"version":1},{"label":"mosquito","hexcode":"1F99F","tags":["bite","disease","fever","insect","malaria","pest","virus"],"emoji":"🦟","text":"","type":1,"order":3797,"group":3,"subgroup":39,"version":11},{"label":"fly","hexcode":"1FAB0","tags":["animal","disease","insect","maggot","pest","rotting"],"emoji":"🪰","text":"","type":1,"order":3798,"group":3,"subgroup":39,"version":13},{"label":"worm","hexcode":"1FAB1","tags":["animal","annelid","earthworm","parasite"],"emoji":"🪱","text":"","type":1,"order":3799,"group":3,"subgroup":39,"version":13},{"label":"microbe","hexcode":"1F9A0","tags":["amoeba","bacteria","science","virus"],"emoji":"🦠","text":"","type":1,"order":3800,"group":3,"subgroup":39,"version":11},{"label":"bouquet","hexcode":"1F490","tags":["anniversary","birthday","date","flower","love","plant","romance"],"emoji":"💐","text":"","type":1,"order":3801,"group":3,"subgroup":40,"version":0.6},{"label":"cherry blossom","hexcode":"1F338","tags":["blossom","cherry","flower","plant","spring","springtime"],"emoji":"🌸","text":"","type":1,"order":3802,"group":3,"subgroup":40,"version":0.6},{"label":"white flower","hexcode":"1F4AE","tags":["flower","white"],"emoji":"💮","text":"","type":1,"order":3803,"group":3,"subgroup":40,"version":0.6},{"label":"lotus","hexcode":"1FAB7","tags":["beauty","buddhism","calm","flower","hinduism","peace","purity","serenity"],"emoji":"🪷","text":"","type":1,"order":3804,"group":3,"subgroup":40,"version":14},{"label":"rosette","hexcode":"1F3F5","tags":["plant"],"emoji":"🏵️","text":"🏵︎","type":0,"order":3806,"group":3,"subgroup":40,"version":0.7},{"label":"rose","hexcode":"1F339","tags":["beauty","elegant","flower","love","plant","red","valentine"],"emoji":"🌹","text":"","type":1,"order":3807,"group":3,"subgroup":40,"version":0.6},{"label":"wilted flower","hexcode":"1F940","tags":["dying","flower","wilted"],"emoji":"🥀","text":"","type":1,"order":3808,"group":3,"subgroup":40,"version":3},{"label":"hibiscus","hexcode":"1F33A","tags":["flower","plant"],"emoji":"🌺","text":"","type":1,"order":3809,"group":3,"subgroup":40,"version":0.6},{"label":"sunflower","hexcode":"1F33B","tags":["flower","outdoors","plant","sun"],"emoji":"🌻","text":"","type":1,"order":3810,"group":3,"subgroup":40,"version":0.6},{"label":"blossom","hexcode":"1F33C","tags":["buttercup","dandelion","flower","plant"],"emoji":"🌼","text":"","type":1,"order":3811,"group":3,"subgroup":40,"version":0.6},{"label":"tulip","hexcode":"1F337","tags":["blossom","flower","growth","plant"],"emoji":"🌷","text":"","type":1,"order":3812,"group":3,"subgroup":40,"version":0.6},{"label":"hyacinth","hexcode":"1FABB","tags":["bloom","bluebonnet","flower","indigo","lavender","lilac","lupine","plant","purple","shrub","snapdragon","spring","violet"],"emoji":"🪻","text":"","type":1,"order":3813,"group":3,"subgroup":40,"version":15},{"label":"seedling","hexcode":"1F331","tags":["plant","sapling","sprout","young"],"emoji":"🌱","text":"","type":1,"order":3814,"group":3,"subgroup":41,"version":0.6},{"label":"potted plant","hexcode":"1FAB4","tags":["decor","grow","house","nurturing","plant","pot","potted"],"emoji":"🪴","text":"","type":1,"order":3815,"group":3,"subgroup":41,"version":13},{"label":"evergreen tree","hexcode":"1F332","tags":["christmas","evergreen","forest","pine","tree"],"emoji":"🌲","text":"","type":1,"order":3816,"group":3,"subgroup":41,"version":1},{"label":"deciduous tree","hexcode":"1F333","tags":["deciduous","forest","green","habitat","shedding","tree"],"emoji":"🌳","text":"","type":1,"order":3817,"group":3,"subgroup":41,"version":1},{"label":"palm tree","hexcode":"1F334","tags":["beach","palm","plant","tree","tropical"],"emoji":"🌴","text":"","type":1,"order":3818,"group":3,"subgroup":41,"version":0.6},{"label":"cactus","hexcode":"1F335","tags":["desert","drought","nature","plant"],"emoji":"🌵","text":"","type":1,"order":3819,"group":3,"subgroup":41,"version":0.6},{"label":"sheaf of rice","hexcode":"1F33E","tags":["ear","grain","grains","plant","rice","sheaf"],"emoji":"🌾","text":"","type":1,"order":3820,"group":3,"subgroup":41,"version":0.6},{"label":"herb","hexcode":"1F33F","tags":["leaf","plant"],"emoji":"🌿","text":"","type":1,"order":3821,"group":3,"subgroup":41,"version":0.6},{"label":"shamrock","hexcode":"2618","tags":["irish","plant"],"emoji":"☘️","text":"☘︎","type":0,"order":3823,"group":3,"subgroup":41,"version":1},{"label":"four leaf clover","hexcode":"1F340","tags":["4","clover","four","four-leaf","irish","leaf","lucky","plant"],"emoji":"🍀","text":"","type":1,"order":3824,"group":3,"subgroup":41,"version":0.6},{"label":"maple leaf","hexcode":"1F341","tags":["falling","leaf","maple"],"emoji":"🍁","text":"","type":1,"order":3825,"group":3,"subgroup":41,"version":0.6},{"label":"fallen leaf","hexcode":"1F342","tags":["autumn","fall","fallen","falling","leaf"],"emoji":"🍂","text":"","type":1,"order":3826,"group":3,"subgroup":41,"version":0.6},{"label":"leaf fluttering in wind","hexcode":"1F343","tags":["blow","flutter","fluttering","leaf","wind"],"emoji":"🍃","text":"","type":1,"order":3827,"group":3,"subgroup":41,"version":0.6},{"label":"empty nest","hexcode":"1FAB9","tags":["branch","empty","home","nest","nesting"],"emoji":"🪹","text":"","type":1,"order":3828,"group":3,"subgroup":41,"version":14},{"label":"nest with eggs","hexcode":"1FABA","tags":["bird","branch","egg","eggs","nest","nesting"],"emoji":"🪺","text":"","type":1,"order":3829,"group":3,"subgroup":41,"version":14},{"label":"mushroom","hexcode":"1F344","tags":["fungus","toadstool"],"emoji":"🍄","text":"","type":1,"order":3830,"group":3,"subgroup":41,"version":0.6},{"label":"leafless tree","hexcode":"1FABE","tags":["bare","barren","branches","dead","drought","leafless","tree","trunk","winter","wood"],"emoji":"🪾","text":"","type":1,"order":3831,"group":3,"subgroup":41,"version":16},{"label":"grapes","hexcode":"1F347","tags":["dionysus","fruit","grape"],"emoji":"🍇","text":"","type":1,"order":3832,"group":4,"subgroup":42,"version":0.6},{"label":"melon","hexcode":"1F348","tags":["cantaloupe","fruit"],"emoji":"🍈","text":"","type":1,"order":3833,"group":4,"subgroup":42,"version":0.6},{"label":"watermelon","hexcode":"1F349","tags":["fruit"],"emoji":"🍉","text":"","type":1,"order":3834,"group":4,"subgroup":42,"version":0.6},{"label":"tangerine","hexcode":"1F34A","tags":["c","citrus","fruit","nectarine","orange","vitamin"],"emoji":"🍊","text":"","type":1,"order":3835,"group":4,"subgroup":42,"version":0.6},{"label":"lemon","hexcode":"1F34B","tags":["citrus","fruit","sour"],"emoji":"🍋","text":"","type":1,"order":3836,"group":4,"subgroup":42,"version":1},{"label":"lime","hexcode":"1F34B-200D-1F7E9","tags":["acidity","citrus","cocktail","fruit","garnish","key","margarita","mojito","refreshing","salsa","sour","tangy","tequila","tropical","zest"],"emoji":"🍋‍🟩","text":"","type":1,"order":3837,"group":4,"subgroup":42,"version":15.1},{"label":"banana","hexcode":"1F34C","tags":["fruit","potassium"],"emoji":"🍌","text":"","type":1,"order":3838,"group":4,"subgroup":42,"version":0.6},{"label":"pineapple","hexcode":"1F34D","tags":["colada","fruit","pina","tropical"],"emoji":"🍍","text":"","type":1,"order":3839,"group":4,"subgroup":42,"version":0.6},{"label":"mango","hexcode":"1F96D","tags":["food","fruit","tropical"],"emoji":"🥭","text":"","type":1,"order":3840,"group":4,"subgroup":42,"version":11},{"label":"red apple","hexcode":"1F34E","tags":["apple","diet","food","fruit","health","red","ripe"],"emoji":"🍎","text":"","type":1,"order":3841,"group":4,"subgroup":42,"version":0.6},{"label":"green apple","hexcode":"1F34F","tags":["apple","fruit","green"],"emoji":"🍏","text":"","type":1,"order":3842,"group":4,"subgroup":42,"version":0.6},{"label":"pear","hexcode":"1F350","tags":["fruit"],"emoji":"🍐","text":"","type":1,"order":3843,"group":4,"subgroup":42,"version":1},{"label":"peach","hexcode":"1F351","tags":["fruit"],"emoji":"🍑","text":"","type":1,"order":3844,"group":4,"subgroup":42,"version":0.6},{"label":"cherries","hexcode":"1F352","tags":["berries","cherry","fruit","red"],"emoji":"🍒","text":"","type":1,"order":3845,"group":4,"subgroup":42,"version":0.6},{"label":"strawberry","hexcode":"1F353","tags":["berry","fruit"],"emoji":"🍓","text":"","type":1,"order":3846,"group":4,"subgroup":42,"version":0.6},{"label":"blueberries","hexcode":"1FAD0","tags":["berries","berry","bilberry","blue","blueberry","food","fruit"],"emoji":"🫐","text":"","type":1,"order":3847,"group":4,"subgroup":42,"version":13},{"label":"kiwi fruit","hexcode":"1F95D","tags":["food","fruit","kiwi"],"emoji":"🥝","text":"","type":1,"order":3848,"group":4,"subgroup":42,"version":3},{"label":"tomato","hexcode":"1F345","tags":["food","fruit","vegetable"],"emoji":"🍅","text":"","type":1,"order":3849,"group":4,"subgroup":42,"version":0.6},{"label":"olive","hexcode":"1FAD2","tags":["food"],"emoji":"🫒","text":"","type":1,"order":3850,"group":4,"subgroup":42,"version":13},{"label":"coconut","hexcode":"1F965","tags":["colada","palm","piña"],"emoji":"🥥","text":"","type":1,"order":3851,"group":4,"subgroup":42,"version":5},{"label":"avocado","hexcode":"1F951","tags":["food","fruit"],"emoji":"🥑","text":"","type":1,"order":3852,"group":4,"subgroup":43,"version":3},{"label":"eggplant","hexcode":"1F346","tags":["aubergine","vegetable"],"emoji":"🍆","text":"","type":1,"order":3853,"group":4,"subgroup":43,"version":0.6},{"label":"potato","hexcode":"1F954","tags":["food","vegetable"],"emoji":"🥔","text":"","type":1,"order":3854,"group":4,"subgroup":43,"version":3},{"label":"carrot","hexcode":"1F955","tags":["food","vegetable"],"emoji":"🥕","text":"","type":1,"order":3855,"group":4,"subgroup":43,"version":3},{"label":"ear of corn","hexcode":"1F33D","tags":["corn","crops","ear","farm","maize","maze"],"emoji":"🌽","text":"","type":1,"order":3856,"group":4,"subgroup":43,"version":0.6},{"label":"hot pepper","hexcode":"1F336","tags":["hot","pepper"],"emoji":"🌶️","text":"🌶︎","type":0,"order":3858,"group":4,"subgroup":43,"version":0.7},{"label":"bell pepper","hexcode":"1FAD1","tags":["bell","capsicum","food","pepper","vegetable"],"emoji":"🫑","text":"","type":1,"order":3859,"group":4,"subgroup":43,"version":13},{"label":"cucumber","hexcode":"1F952","tags":["food","pickle","vegetable"],"emoji":"🥒","text":"","type":1,"order":3860,"group":4,"subgroup":43,"version":3},{"label":"leafy green","hexcode":"1F96C","tags":["bok","burgers","cabbage","choy","green","kale","leafy","lettuce","salad"],"emoji":"🥬","text":"","type":1,"order":3861,"group":4,"subgroup":43,"version":11},{"label":"broccoli","hexcode":"1F966","tags":["cabbage","wild"],"emoji":"🥦","text":"","type":1,"order":3862,"group":4,"subgroup":43,"version":5},{"label":"garlic","hexcode":"1F9C4","tags":["flavoring"],"emoji":"🧄","text":"","type":1,"order":3863,"group":4,"subgroup":43,"version":12},{"label":"onion","hexcode":"1F9C5","tags":["flavoring"],"emoji":"🧅","text":"","type":1,"order":3864,"group":4,"subgroup":43,"version":12},{"label":"peanuts","hexcode":"1F95C","tags":["food","nut","peanut","vegetable"],"emoji":"🥜","text":"","type":1,"order":3865,"group":4,"subgroup":43,"version":3},{"label":"beans","hexcode":"1FAD8","tags":["food","kidney","legume","small"],"emoji":"🫘","text":"","type":1,"order":3866,"group":4,"subgroup":43,"version":14},{"label":"chestnut","hexcode":"1F330","tags":["almond","plant"],"emoji":"🌰","text":"","type":1,"order":3867,"group":4,"subgroup":43,"version":0.6},{"label":"ginger root","hexcode":"1FADA","tags":["beer","ginger","health","herb","natural","root","spice"],"emoji":"🫚","text":"","type":1,"order":3868,"group":4,"subgroup":43,"version":15},{"label":"pea pod","hexcode":"1FADB","tags":["beans","beanstalk","edamame","legume","pea","pod","soybean","vegetable","veggie"],"emoji":"🫛","text":"","type":1,"order":3869,"group":4,"subgroup":43,"version":15},{"label":"brown mushroom","hexcode":"1F344-200D-1F7EB","tags":["food","fungi","fungus","mushroom","nature","pizza","portobello","shiitake","shroom","spore","sprout","toppings","truffle","vegetable","vegetarian","veggie"],"emoji":"🍄‍🟫","text":"","type":1,"order":3870,"group":4,"subgroup":43,"version":15.1},{"label":"root vegetable","hexcode":"1FADC","tags":["beet","food","garden","radish","root","salad","turnip","vegetable","vegetarian"],"emoji":"🫜","text":"","type":1,"order":3871,"group":4,"subgroup":43,"version":16},{"label":"bread","hexcode":"1F35E","tags":["carbs","food","grain","loaf","restaurant","toast","wheat"],"emoji":"🍞","text":"","type":1,"order":3872,"group":4,"subgroup":44,"version":0.6},{"label":"croissant","hexcode":"1F950","tags":["bread","breakfast","crescent","food","french","roll"],"emoji":"🥐","text":"","type":1,"order":3873,"group":4,"subgroup":44,"version":3},{"label":"baguette bread","hexcode":"1F956","tags":["baguette","bread","food","french"],"emoji":"🥖","text":"","type":1,"order":3874,"group":4,"subgroup":44,"version":3},{"label":"flatbread","hexcode":"1FAD3","tags":["arepa","bread","food","gordita","lavash","naan","pita"],"emoji":"🫓","text":"","type":1,"order":3875,"group":4,"subgroup":44,"version":13},{"label":"pretzel","hexcode":"1F968","tags":["convoluted","twisted"],"emoji":"🥨","text":"","type":1,"order":3876,"group":4,"subgroup":44,"version":5},{"label":"bagel","hexcode":"1F96F","tags":["bakery","bread","breakfast","schmear"],"emoji":"🥯","text":"","type":1,"order":3877,"group":4,"subgroup":44,"version":11},{"label":"pancakes","hexcode":"1F95E","tags":["breakfast","crêpe","food","hotcake","pancake"],"emoji":"🥞","text":"","type":1,"order":3878,"group":4,"subgroup":44,"version":3},{"label":"waffle","hexcode":"1F9C7","tags":["breakfast","indecisive","iron"],"emoji":"🧇","text":"","type":1,"order":3879,"group":4,"subgroup":44,"version":12},{"label":"cheese wedge","hexcode":"1F9C0","tags":["cheese","wedge"],"emoji":"🧀","text":"","type":1,"order":3880,"group":4,"subgroup":44,"version":1},{"label":"meat on bone","hexcode":"1F356","tags":["bone","meat"],"emoji":"🍖","text":"","type":1,"order":3881,"group":4,"subgroup":44,"version":0.6},{"label":"poultry leg","hexcode":"1F357","tags":["bone","chicken","drumstick","hungry","leg","poultry","turkey"],"emoji":"🍗","text":"","type":1,"order":3882,"group":4,"subgroup":44,"version":0.6},{"label":"cut of meat","hexcode":"1F969","tags":["chop","cut","lambchop","meat","porkchop","red","steak"],"emoji":"🥩","text":"","type":1,"order":3883,"group":4,"subgroup":44,"version":5},{"label":"bacon","hexcode":"1F953","tags":["breakfast","food","meat"],"emoji":"🥓","text":"","type":1,"order":3884,"group":4,"subgroup":44,"version":3},{"label":"hamburger","hexcode":"1F354","tags":["burger","eat","fast","food","hungry"],"emoji":"🍔","text":"","type":1,"order":3885,"group":4,"subgroup":44,"version":0.6},{"label":"french fries","hexcode":"1F35F","tags":["fast","food","french","fries"],"emoji":"🍟","text":"","type":1,"order":3886,"group":4,"subgroup":44,"version":0.6},{"label":"pizza","hexcode":"1F355","tags":["cheese","food","hungry","pepperoni","slice"],"emoji":"🍕","text":"","type":1,"order":3887,"group":4,"subgroup":44,"version":0.6},{"label":"hot dog","hexcode":"1F32D","tags":["dog","frankfurter","hot","hotdog","sausage"],"emoji":"🌭","text":"","type":1,"order":3888,"group":4,"subgroup":44,"version":1},{"label":"sandwich","hexcode":"1F96A","tags":["bread"],"emoji":"🥪","text":"","type":1,"order":3889,"group":4,"subgroup":44,"version":5},{"label":"taco","hexcode":"1F32E","tags":["mexican"],"emoji":"🌮","text":"","type":1,"order":3890,"group":4,"subgroup":44,"version":1},{"label":"burrito","hexcode":"1F32F","tags":["mexican","wrap"],"emoji":"🌯","text":"","type":1,"order":3891,"group":4,"subgroup":44,"version":1},{"label":"tamale","hexcode":"1FAD4","tags":["food","mexican","pamonha","wrapped"],"emoji":"🫔","text":"","type":1,"order":3892,"group":4,"subgroup":44,"version":13},{"label":"stuffed flatbread","hexcode":"1F959","tags":["falafel","flatbread","food","gyro","kebab","stuffed"],"emoji":"🥙","text":"","type":1,"order":3893,"group":4,"subgroup":44,"version":3},{"label":"falafel","hexcode":"1F9C6","tags":["chickpea","meatball"],"emoji":"🧆","text":"","type":1,"order":3894,"group":4,"subgroup":44,"version":12},{"label":"egg","hexcode":"1F95A","tags":["breakfast","food"],"emoji":"🥚","text":"","type":1,"order":3895,"group":4,"subgroup":44,"version":3},{"label":"cooking","hexcode":"1F373","tags":["breakfast","easy","egg","fry","frying","over","pan","restaurant","side","sunny","up"],"emoji":"🍳","text":"","type":1,"order":3896,"group":4,"subgroup":44,"version":0.6},{"label":"shallow pan of food","hexcode":"1F958","tags":["casserole","food","paella","pan","shallow"],"emoji":"🥘","text":"","type":1,"order":3897,"group":4,"subgroup":44,"version":3},{"label":"pot of food","hexcode":"1F372","tags":["food","pot","soup","stew"],"emoji":"🍲","text":"","type":1,"order":3898,"group":4,"subgroup":44,"version":0.6},{"label":"fondue","hexcode":"1FAD5","tags":["cheese","chocolate","food","melted","pot","ski"],"emoji":"🫕","text":"","type":1,"order":3899,"group":4,"subgroup":44,"version":13},{"label":"bowl with spoon","hexcode":"1F963","tags":["bowl","breakfast","cereal","congee","oatmeal","porridge","spoon"],"emoji":"🥣","text":"","type":1,"order":3900,"group":4,"subgroup":44,"version":5},{"label":"green salad","hexcode":"1F957","tags":["food","green","salad"],"emoji":"🥗","text":"","type":1,"order":3901,"group":4,"subgroup":44,"version":3},{"label":"popcorn","hexcode":"1F37F","tags":["corn","movie","pop"],"emoji":"🍿","text":"","type":1,"order":3902,"group":4,"subgroup":44,"version":1},{"label":"butter","hexcode":"1F9C8","tags":["dairy"],"emoji":"🧈","text":"","type":1,"order":3903,"group":4,"subgroup":44,"version":12},{"label":"salt","hexcode":"1F9C2","tags":["condiment","flavor","mad","salty","shaker","taste","upset"],"emoji":"🧂","text":"","type":1,"order":3904,"group":4,"subgroup":44,"version":11},{"label":"canned food","hexcode":"1F96B","tags":["can","canned","food"],"emoji":"🥫","text":"","type":1,"order":3905,"group":4,"subgroup":44,"version":5},{"label":"bento box","hexcode":"1F371","tags":["bento","box","food"],"emoji":"🍱","text":"","type":1,"order":3906,"group":4,"subgroup":45,"version":0.6},{"label":"rice cracker","hexcode":"1F358","tags":["cracker","food","rice"],"emoji":"🍘","text":"","type":1,"order":3907,"group":4,"subgroup":45,"version":0.6},{"label":"rice ball","hexcode":"1F359","tags":["ball","food","japanese","rice"],"emoji":"🍙","text":"","type":1,"order":3908,"group":4,"subgroup":45,"version":0.6},{"label":"cooked rice","hexcode":"1F35A","tags":["cooked","food","rice"],"emoji":"🍚","text":"","type":1,"order":3909,"group":4,"subgroup":45,"version":0.6},{"label":"curry rice","hexcode":"1F35B","tags":["curry","food","rice"],"emoji":"🍛","text":"","type":1,"order":3910,"group":4,"subgroup":45,"version":0.6},{"label":"steaming bowl","hexcode":"1F35C","tags":["bowl","chopsticks","food","noodle","pho","ramen","soup","steaming"],"emoji":"🍜","text":"","type":1,"order":3911,"group":4,"subgroup":45,"version":0.6},{"label":"spaghetti","hexcode":"1F35D","tags":["food","meatballs","pasta","restaurant"],"emoji":"🍝","text":"","type":1,"order":3912,"group":4,"subgroup":45,"version":0.6},{"label":"roasted sweet potato","hexcode":"1F360","tags":["food","potato","roasted","sweet"],"emoji":"🍠","text":"","type":1,"order":3913,"group":4,"subgroup":45,"version":0.6},{"label":"oden","hexcode":"1F362","tags":["food","kebab","restaurant","seafood","skewer","stick"],"emoji":"🍢","text":"","type":1,"order":3914,"group":4,"subgroup":45,"version":0.6},{"label":"sushi","hexcode":"1F363","tags":["food"],"emoji":"🍣","text":"","type":1,"order":3915,"group":4,"subgroup":45,"version":0.6},{"label":"fried shrimp","hexcode":"1F364","tags":["fried","prawn","shrimp","tempura"],"emoji":"🍤","text":"","type":1,"order":3916,"group":4,"subgroup":45,"version":0.6},{"label":"fish cake with swirl","hexcode":"1F365","tags":["cake","fish","food","pastry","restaurant","swirl"],"emoji":"🍥","text":"","type":1,"order":3917,"group":4,"subgroup":45,"version":0.6},{"label":"moon cake","hexcode":"1F96E","tags":["autumn","cake","festival","moon","yuèbǐng"],"emoji":"🥮","text":"","type":1,"order":3918,"group":4,"subgroup":45,"version":11},{"label":"dango","hexcode":"1F361","tags":["dessert","japanese","skewer","stick","sweet"],"emoji":"🍡","text":"","type":1,"order":3919,"group":4,"subgroup":45,"version":0.6},{"label":"dumpling","hexcode":"1F95F","tags":["empanada","gyōza","jiaozi","pierogi","potsticker"],"emoji":"🥟","text":"","type":1,"order":3920,"group":4,"subgroup":45,"version":5},{"label":"fortune cookie","hexcode":"1F960","tags":["cookie","fortune","prophecy"],"emoji":"🥠","text":"","type":1,"order":3921,"group":4,"subgroup":45,"version":5},{"label":"takeout box","hexcode":"1F961","tags":["box","chopsticks","delivery","food","oyster","pail","takeout"],"emoji":"🥡","text":"","type":1,"order":3922,"group":4,"subgroup":45,"version":5},{"label":"soft ice cream","hexcode":"1F366","tags":["cream","dessert","food","ice","icecream","restaurant","serve","soft","sweet"],"emoji":"🍦","text":"","type":1,"order":3923,"group":4,"subgroup":46,"version":0.6},{"label":"shaved ice","hexcode":"1F367","tags":["dessert","ice","restaurant","shaved","sweet"],"emoji":"🍧","text":"","type":1,"order":3924,"group":4,"subgroup":46,"version":0.6},{"label":"ice cream","hexcode":"1F368","tags":["cream","dessert","food","ice","restaurant","sweet"],"emoji":"🍨","text":"","type":1,"order":3925,"group":4,"subgroup":46,"version":0.6},{"label":"doughnut","hexcode":"1F369","tags":["breakfast","dessert","donut","food","sweet"],"emoji":"🍩","text":"","type":1,"order":3926,"group":4,"subgroup":46,"version":0.6},{"label":"cookie","hexcode":"1F36A","tags":["chip","chocolate","dessert","sweet"],"emoji":"🍪","text":"","type":1,"order":3927,"group":4,"subgroup":46,"version":0.6},{"label":"birthday cake","hexcode":"1F382","tags":["bday","birthday","cake","celebration","dessert","happy","pastry","sweet"],"emoji":"🎂","text":"","type":1,"order":3928,"group":4,"subgroup":46,"version":0.6},{"label":"shortcake","hexcode":"1F370","tags":["cake","dessert","pastry","slice","sweet"],"emoji":"🍰","text":"","type":1,"order":3929,"group":4,"subgroup":46,"version":0.6},{"label":"cupcake","hexcode":"1F9C1","tags":["bakery","dessert","sprinkles","sugar","sweet","treat"],"emoji":"🧁","text":"","type":1,"order":3930,"group":4,"subgroup":46,"version":11},{"label":"pie","hexcode":"1F967","tags":["apple","filling","fruit","meat","pastry","pumpkin","slice"],"emoji":"🥧","text":"","type":1,"order":3931,"group":4,"subgroup":46,"version":5},{"label":"chocolate bar","hexcode":"1F36B","tags":["bar","candy","chocolate","dessert","halloween","sweet","tooth"],"emoji":"🍫","text":"","type":1,"order":3932,"group":4,"subgroup":46,"version":0.6},{"label":"candy","hexcode":"1F36C","tags":["cavities","dessert","halloween","restaurant","sweet","tooth","wrapper"],"emoji":"🍬","text":"","type":1,"order":3933,"group":4,"subgroup":46,"version":0.6},{"label":"lollipop","hexcode":"1F36D","tags":["candy","dessert","food","restaurant","sweet"],"emoji":"🍭","text":"","type":1,"order":3934,"group":4,"subgroup":46,"version":0.6},{"label":"custard","hexcode":"1F36E","tags":["dessert","pudding","sweet"],"emoji":"🍮","text":"","type":1,"order":3935,"group":4,"subgroup":46,"version":0.6},{"label":"honey pot","hexcode":"1F36F","tags":["barrel","bear","food","honey","honeypot","jar","pot","sweet"],"emoji":"🍯","text":"","type":1,"order":3936,"group":4,"subgroup":46,"version":0.6},{"label":"baby bottle","hexcode":"1F37C","tags":["babies","baby","birth","born","bottle","drink","infant","milk","newborn"],"emoji":"🍼","text":"","type":1,"order":3937,"group":4,"subgroup":47,"version":1},{"label":"glass of milk","hexcode":"1F95B","tags":["drink","glass","milk"],"emoji":"🥛","text":"","type":1,"order":3938,"group":4,"subgroup":47,"version":3},{"label":"hot beverage","hexcode":"2615","tags":["beverage","cafe","caffeine","chai","coffee","drink","hot","morning","steaming","tea"],"emoji":"☕️","text":"☕︎","type":1,"order":3939,"group":4,"subgroup":47,"version":0.6},{"label":"teapot","hexcode":"1FAD6","tags":["brew","drink","food","pot","tea"],"emoji":"🫖","text":"","type":1,"order":3940,"group":4,"subgroup":47,"version":13},{"label":"teacup without handle","hexcode":"1F375","tags":["beverage","cup","drink","handle","oolong","tea","teacup"],"emoji":"🍵","text":"","type":1,"order":3941,"group":4,"subgroup":47,"version":0.6},{"label":"sake","hexcode":"1F376","tags":["bar","beverage","bottle","cup","drink","restaurant"],"emoji":"🍶","text":"","type":1,"order":3942,"group":4,"subgroup":47,"version":0.6},{"label":"bottle with popping cork","hexcode":"1F37E","tags":["bar","bottle","cork","drink","popping"],"emoji":"🍾","text":"","type":1,"order":3943,"group":4,"subgroup":47,"version":1},{"label":"wine glass","hexcode":"1F377","tags":["alcohol","bar","beverage","booze","club","drink","drinking","drinks","glass","restaurant","wine"],"emoji":"🍷","text":"","type":1,"order":3944,"group":4,"subgroup":47,"version":0.6},{"label":"cocktail glass","hexcode":"1F378","tags":["alcohol","bar","booze","club","cocktail","drink","drinking","drinks","glass","mad","martini","men"],"emoji":"🍸️","text":"🍸︎","type":1,"order":3945,"group":4,"subgroup":47,"version":0.6},{"label":"tropical drink","hexcode":"1F379","tags":["alcohol","bar","booze","club","cocktail","drink","drinking","drinks","drunk","mai","party","tai","tropical","tropics"],"emoji":"🍹","text":"","type":1,"order":3946,"group":4,"subgroup":47,"version":0.6},{"label":"beer mug","hexcode":"1F37A","tags":["alcohol","ale","bar","beer","booze","drink","drinking","drinks","mug","octoberfest","oktoberfest","pint","stein","summer"],"emoji":"🍺","text":"","type":1,"order":3947,"group":4,"subgroup":47,"version":0.6},{"label":"clinking beer mugs","hexcode":"1F37B","tags":["alcohol","bar","beer","booze","bottoms","cheers","clink","clinking","drinking","drinks","mugs"],"emoji":"🍻","text":"","type":1,"order":3948,"group":4,"subgroup":47,"version":0.6},{"label":"clinking glasses","hexcode":"1F942","tags":["celebrate","clink","clinking","drink","glass","glasses"],"emoji":"🥂","text":"","type":1,"order":3949,"group":4,"subgroup":47,"version":3},{"label":"tumbler glass","hexcode":"1F943","tags":["glass","liquor","scotch","shot","tumbler","whiskey","whisky"],"emoji":"🥃","text":"","type":1,"order":3950,"group":4,"subgroup":47,"version":3},{"label":"pouring liquid","hexcode":"1FAD7","tags":["accident","drink","empty","glass","liquid","oops","pour","pouring","spill","water"],"emoji":"🫗","text":"","type":1,"order":3951,"group":4,"subgroup":47,"version":14},{"label":"cup with straw","hexcode":"1F964","tags":["cup","drink","juice","malt","soda","soft","straw","water"],"emoji":"🥤","text":"","type":1,"order":3952,"group":4,"subgroup":47,"version":5},{"label":"bubble tea","hexcode":"1F9CB","tags":["boba","bubble","food","milk","pearl","tea"],"emoji":"🧋","text":"","type":1,"order":3953,"group":4,"subgroup":47,"version":13},{"label":"beverage box","hexcode":"1F9C3","tags":["beverage","box","juice","straw","sweet"],"emoji":"🧃","text":"","type":1,"order":3954,"group":4,"subgroup":47,"version":12},{"label":"mate","hexcode":"1F9C9","tags":["drink"],"emoji":"🧉","text":"","type":1,"order":3955,"group":4,"subgroup":47,"version":12},{"label":"ice","hexcode":"1F9CA","tags":["cold","cube","iceberg"],"emoji":"🧊","text":"","type":1,"order":3956,"group":4,"subgroup":47,"version":12},{"label":"chopsticks","hexcode":"1F962","tags":["hashi","jeotgarak","kuaizi"],"emoji":"🥢","text":"","type":1,"order":3957,"group":4,"subgroup":48,"version":5},{"label":"fork and knife with plate","hexcode":"1F37D","tags":["cooking","dinner","eat","fork","knife","plate"],"emoji":"🍽️","text":"🍽︎","type":0,"order":3959,"group":4,"subgroup":48,"version":0.7},{"label":"fork and knife","hexcode":"1F374","tags":["breakfast","breaky","cooking","cutlery","delicious","dinner","eat","feed","food","fork","hungry","knife","lunch","restaurant","yum","yummy"],"emoji":"🍴","text":"","type":1,"order":3960,"group":4,"subgroup":48,"version":0.6},{"label":"spoon","hexcode":"1F944","tags":["eat","tableware"],"emoji":"🥄","text":"","type":1,"order":3961,"group":4,"subgroup":48,"version":3},{"label":"kitchen knife","hexcode":"1F52A","tags":["chef","cooking","hocho","kitchen","knife","tool","weapon"],"emoji":"🔪","text":"","type":1,"order":3962,"group":4,"subgroup":48,"version":0.6},{"label":"jar","hexcode":"1FAD9","tags":["condiment","container","empty","nothing","sauce","store"],"emoji":"🫙","text":"","type":1,"order":3963,"group":4,"subgroup":48,"version":14},{"label":"amphora","hexcode":"1F3FA","tags":["aquarius","cooking","drink","jug","tool","weapon","zodiac"],"emoji":"🏺","text":"","type":1,"order":3964,"group":4,"subgroup":48,"version":1},{"label":"globe showing Europe-Africa","hexcode":"1F30D","tags":["africa","earth","europe","europe-africa","globe","showing","world"],"emoji":"🌍️","text":"🌍︎","type":1,"order":3965,"group":5,"subgroup":49,"version":0.7},{"label":"globe showing Americas","hexcode":"1F30E","tags":["americas","earth","globe","showing","world"],"emoji":"🌎️","text":"🌎︎","type":1,"order":3966,"group":5,"subgroup":49,"version":0.7},{"label":"globe showing Asia-Australia","hexcode":"1F30F","tags":["asia","asia-australia","australia","earth","globe","showing","world"],"emoji":"🌏️","text":"🌏︎","type":1,"order":3967,"group":5,"subgroup":49,"version":0.6},{"label":"globe with meridians","hexcode":"1F310","tags":["earth","globe","internet","meridians","web","world","worldwide"],"emoji":"🌐","text":"","type":1,"order":3968,"group":5,"subgroup":49,"version":1},{"label":"world map","hexcode":"1F5FA","tags":["map","world"],"emoji":"🗺️","text":"🗺︎","type":0,"order":3970,"group":5,"subgroup":49,"version":0.7},{"label":"map of Japan","hexcode":"1F5FE","tags":["japan","map"],"emoji":"🗾","text":"","type":1,"order":3971,"group":5,"subgroup":49,"version":0.6},{"label":"compass","hexcode":"1F9ED","tags":["direction","magnetic","navigation","orienteering"],"emoji":"🧭","text":"","type":1,"order":3972,"group":5,"subgroup":49,"version":11},{"label":"snow-capped mountain","hexcode":"1F3D4","tags":["cold","mountain","snow","snow-capped"],"emoji":"🏔️","text":"🏔︎","type":0,"order":3974,"group":5,"subgroup":50,"version":0.7},{"label":"mountain","hexcode":"26F0","tags":["mountain"],"emoji":"⛰️","text":"⛰︎","type":0,"order":3976,"group":5,"subgroup":50,"version":0.7},{"label":"landslide","hexcode":"1F6D8","tags":["avalanche","danger","disaster","earthquake","mountain","mudslide","rocks"],"emoji":"🛘","text":"","type":1,"order":3977,"group":5,"subgroup":50,"version":17},{"label":"volcano","hexcode":"1F30B","tags":["eruption","mountain","nature"],"emoji":"🌋","text":"","type":1,"order":3978,"group":5,"subgroup":50,"version":0.6},{"label":"mount fuji","hexcode":"1F5FB","tags":["fuji","mount","mountain","nature"],"emoji":"🗻","text":"","type":1,"order":3979,"group":5,"subgroup":50,"version":0.6},{"label":"camping","hexcode":"1F3D5","tags":["camping"],"emoji":"🏕️","text":"🏕︎","type":0,"order":3981,"group":5,"subgroup":50,"version":0.7},{"label":"beach with umbrella","hexcode":"1F3D6","tags":["beach","umbrella"],"emoji":"🏖️","text":"🏖︎","type":0,"order":3983,"group":5,"subgroup":50,"version":0.7},{"label":"desert","hexcode":"1F3DC","tags":["desert"],"emoji":"🏜️","text":"🏜︎","type":0,"order":3985,"group":5,"subgroup":50,"version":0.7},{"label":"desert island","hexcode":"1F3DD","tags":["desert","island"],"emoji":"🏝️","text":"🏝︎","type":0,"order":3987,"group":5,"subgroup":50,"version":0.7},{"label":"national park","hexcode":"1F3DE","tags":["national","park"],"emoji":"🏞️","text":"🏞︎","type":0,"order":3989,"group":5,"subgroup":50,"version":0.7},{"label":"stadium","hexcode":"1F3DF","tags":["stadium"],"emoji":"🏟️","text":"🏟︎","type":0,"order":3991,"group":5,"subgroup":51,"version":0.7},{"label":"classical building","hexcode":"1F3DB","tags":["building","classical"],"emoji":"🏛️","text":"🏛︎","type":0,"order":3993,"group":5,"subgroup":51,"version":0.7},{"label":"building construction","hexcode":"1F3D7","tags":["building","construction","crane"],"emoji":"🏗️","text":"🏗︎","type":0,"order":3995,"group":5,"subgroup":51,"version":0.7},{"label":"brick","hexcode":"1F9F1","tags":["bricks","clay","mortar","wall"],"emoji":"🧱","text":"","type":1,"order":3996,"group":5,"subgroup":51,"version":11},{"label":"rock","hexcode":"1FAA8","tags":["boulder","heavy","solid","stone","tough"],"emoji":"🪨","text":"","type":1,"order":3997,"group":5,"subgroup":51,"version":13},{"label":"wood","hexcode":"1FAB5","tags":["log","lumber","timber"],"emoji":"🪵","text":"","type":1,"order":3998,"group":5,"subgroup":51,"version":13},{"label":"hut","hexcode":"1F6D6","tags":["home","house","roundhouse","shelter","yurt"],"emoji":"🛖","text":"","type":1,"order":3999,"group":5,"subgroup":51,"version":13},{"label":"houses","hexcode":"1F3D8","tags":["house"],"emoji":"🏘️","text":"🏘︎","type":0,"order":4001,"group":5,"subgroup":51,"version":0.7},{"label":"derelict house","hexcode":"1F3DA","tags":["derelict","home","house"],"emoji":"🏚️","text":"🏚︎","type":0,"order":4003,"group":5,"subgroup":51,"version":0.7},{"label":"house","hexcode":"1F3E0","tags":["building","country","heart","home","ranch","settle","simple","suburban","suburbia","where"],"emoji":"🏠️","text":"🏠︎","type":1,"order":4004,"group":5,"subgroup":51,"version":0.6},{"label":"house with garden","hexcode":"1F3E1","tags":["building","country","garden","heart","home","house","ranch","settle","simple","suburban","suburbia","where"],"emoji":"🏡","text":"","type":1,"order":4005,"group":5,"subgroup":51,"version":0.6},{"label":"office building","hexcode":"1F3E2","tags":["building","city","cubical","job","office"],"emoji":"🏢","text":"","type":1,"order":4006,"group":5,"subgroup":51,"version":0.6},{"label":"Japanese post office","hexcode":"1F3E3","tags":["building","japanese","office","post"],"emoji":"🏣","text":"","type":1,"order":4007,"group":5,"subgroup":51,"version":0.6},{"label":"post office","hexcode":"1F3E4","tags":["building","european","office","post"],"emoji":"🏤","text":"","type":1,"order":4008,"group":5,"subgroup":51,"version":1},{"label":"hospital","hexcode":"1F3E5","tags":["building","doctor","medicine"],"emoji":"🏥","text":"","type":1,"order":4009,"group":5,"subgroup":51,"version":0.6},{"label":"bank","hexcode":"1F3E6","tags":["building"],"emoji":"🏦","text":"","type":1,"order":4010,"group":5,"subgroup":51,"version":0.6},{"label":"hotel","hexcode":"1F3E8","tags":["building"],"emoji":"🏨","text":"","type":1,"order":4011,"group":5,"subgroup":51,"version":0.6},{"label":"love hotel","hexcode":"1F3E9","tags":["building","hotel","love"],"emoji":"🏩","text":"","type":1,"order":4012,"group":5,"subgroup":51,"version":0.6},{"label":"convenience store","hexcode":"1F3EA","tags":["24","building","convenience","hours","store"],"emoji":"🏪","text":"","type":1,"order":4013,"group":5,"subgroup":51,"version":0.6},{"label":"school","hexcode":"1F3EB","tags":["building"],"emoji":"🏫","text":"","type":1,"order":4014,"group":5,"subgroup":51,"version":0.6},{"label":"department store","hexcode":"1F3EC","tags":["building","department","store"],"emoji":"🏬","text":"","type":1,"order":4015,"group":5,"subgroup":51,"version":0.6},{"label":"factory","hexcode":"1F3ED","tags":["building"],"emoji":"🏭️","text":"🏭︎","type":1,"order":4016,"group":5,"subgroup":51,"version":0.6},{"label":"Japanese castle","hexcode":"1F3EF","tags":["building","castle","japanese"],"emoji":"🏯","text":"","type":1,"order":4017,"group":5,"subgroup":51,"version":0.6},{"label":"castle","hexcode":"1F3F0","tags":["building","european"],"emoji":"🏰","text":"","type":1,"order":4018,"group":5,"subgroup":51,"version":0.6},{"label":"wedding","hexcode":"1F492","tags":["chapel","hitched","nuptials","romance"],"emoji":"💒","text":"","type":1,"order":4019,"group":5,"subgroup":51,"version":0.6},{"label":"Tokyo tower","hexcode":"1F5FC","tags":["tokyo","tower"],"emoji":"🗼","text":"","type":1,"order":4020,"group":5,"subgroup":51,"version":0.6},{"label":"Statue of Liberty","hexcode":"1F5FD","tags":["liberty","new","ny","nyc","statue","york"],"emoji":"🗽","text":"","type":1,"order":4021,"group":5,"subgroup":51,"version":0.6},{"label":"church","hexcode":"26EA","tags":["bless","chapel","christian","cross","religion"],"emoji":"⛪️","text":"⛪︎","type":1,"order":4022,"group":5,"subgroup":52,"version":0.6},{"label":"mosque","hexcode":"1F54C","tags":["islam","masjid","muslim","religion"],"emoji":"🕌","text":"","type":1,"order":4023,"group":5,"subgroup":52,"version":1},{"label":"hindu temple","hexcode":"1F6D5","tags":["hindu","temple"],"emoji":"🛕","text":"","type":1,"order":4024,"group":5,"subgroup":52,"version":12},{"label":"synagogue","hexcode":"1F54D","tags":["jew","jewish","judaism","religion","temple"],"emoji":"🕍","text":"","type":1,"order":4025,"group":5,"subgroup":52,"version":1},{"label":"shinto shrine","hexcode":"26E9","tags":["religion","shinto","shrine"],"emoji":"⛩️","text":"⛩︎","type":0,"order":4027,"group":5,"subgroup":52,"version":0.7},{"label":"kaaba","hexcode":"1F54B","tags":["hajj","islam","muslim","religion","umrah"],"emoji":"🕋","text":"","type":1,"order":4028,"group":5,"subgroup":52,"version":1},{"label":"fountain","hexcode":"26F2","tags":["fountain"],"emoji":"⛲️","text":"⛲︎","type":1,"order":4029,"group":5,"subgroup":53,"version":0.6},{"label":"tent","hexcode":"26FA","tags":["camping"],"emoji":"⛺️","text":"⛺︎","type":1,"order":4030,"group":5,"subgroup":53,"version":0.6},{"label":"foggy","hexcode":"1F301","tags":["fog"],"emoji":"🌁","text":"","type":1,"order":4031,"group":5,"subgroup":53,"version":0.6},{"label":"night with stars","hexcode":"1F303","tags":["night","star","stars"],"emoji":"🌃","text":"","type":1,"order":4032,"group":5,"subgroup":53,"version":0.6},{"label":"cityscape","hexcode":"1F3D9","tags":["city"],"emoji":"🏙️","text":"🏙︎","type":0,"order":4034,"group":5,"subgroup":53,"version":0.7},{"label":"sunrise over mountains","hexcode":"1F304","tags":["morning","mountains","over","sun","sunrise"],"emoji":"🌄","text":"","type":1,"order":4035,"group":5,"subgroup":53,"version":0.6},{"label":"sunrise","hexcode":"1F305","tags":["morning","nature","sun"],"emoji":"🌅","text":"","type":1,"order":4036,"group":5,"subgroup":53,"version":0.6},{"label":"cityscape at dusk","hexcode":"1F306","tags":["at","building","city","cityscape","dusk","evening","landscape","sun","sunset"],"emoji":"🌆","text":"","type":1,"order":4037,"group":5,"subgroup":53,"version":0.6},{"label":"sunset","hexcode":"1F307","tags":["building","dusk","sun"],"emoji":"🌇","text":"","type":1,"order":4038,"group":5,"subgroup":53,"version":0.6},{"label":"bridge at night","hexcode":"1F309","tags":["at","bridge","night"],"emoji":"🌉","text":"","type":1,"order":4039,"group":5,"subgroup":53,"version":0.6},{"label":"hot springs","hexcode":"2668","tags":["hot","hotsprings","springs","steaming"],"emoji":"♨️","text":"♨︎","type":0,"order":4041,"group":5,"subgroup":53,"version":0.6},{"label":"carousel horse","hexcode":"1F3A0","tags":["carousel","entertainment","horse"],"emoji":"🎠","text":"","type":1,"order":4042,"group":5,"subgroup":53,"version":0.6},{"label":"playground slide","hexcode":"1F6DD","tags":["amusement","park","play","playground","playing","slide","sliding","theme"],"emoji":"🛝","text":"","type":1,"order":4043,"group":5,"subgroup":53,"version":14},{"label":"ferris wheel","hexcode":"1F3A1","tags":["amusement","ferris","park","theme","wheel"],"emoji":"🎡","text":"","type":1,"order":4044,"group":5,"subgroup":53,"version":0.6},{"label":"roller coaster","hexcode":"1F3A2","tags":["amusement","coaster","park","roller","theme"],"emoji":"🎢","text":"","type":1,"order":4045,"group":5,"subgroup":53,"version":0.6},{"label":"barber pole","hexcode":"1F488","tags":["barber","cut","fresh","haircut","pole","shave"],"emoji":"💈","text":"","type":1,"order":4046,"group":5,"subgroup":53,"version":0.6},{"label":"circus tent","hexcode":"1F3AA","tags":["circus","tent"],"emoji":"🎪","text":"","type":1,"order":4047,"group":5,"subgroup":53,"version":0.6},{"label":"locomotive","hexcode":"1F682","tags":["caboose","engine","railway","steam","train","trains","travel"],"emoji":"🚂","text":"","type":1,"order":4048,"group":5,"subgroup":54,"version":1},{"label":"railway car","hexcode":"1F683","tags":["car","electric","railway","train","tram","travel","trolleybus"],"emoji":"🚃","text":"","type":1,"order":4049,"group":5,"subgroup":54,"version":0.6},{"label":"high-speed train","hexcode":"1F684","tags":["high-speed","railway","shinkansen","speed","train"],"emoji":"🚄","text":"","type":1,"order":4050,"group":5,"subgroup":54,"version":0.6},{"label":"bullet train","hexcode":"1F685","tags":["bullet","high-speed","nose","railway","shinkansen","speed","train","travel"],"emoji":"🚅","text":"","type":1,"order":4051,"group":5,"subgroup":54,"version":0.6},{"label":"train","hexcode":"1F686","tags":["arrived","choo","railway"],"emoji":"🚆","text":"","type":1,"order":4052,"group":5,"subgroup":54,"version":1},{"label":"metro","hexcode":"1F687","tags":["subway","travel"],"emoji":"🚇️","text":"🚇︎","type":1,"order":4053,"group":5,"subgroup":54,"version":0.6},{"label":"light rail","hexcode":"1F688","tags":["arrived","light","monorail","rail","railway"],"emoji":"🚈","text":"","type":1,"order":4054,"group":5,"subgroup":54,"version":1},{"label":"station","hexcode":"1F689","tags":["railway","train"],"emoji":"🚉","text":"","type":1,"order":4055,"group":5,"subgroup":54,"version":0.6},{"label":"tram","hexcode":"1F68A","tags":["trolleybus"],"emoji":"🚊","text":"","type":1,"order":4056,"group":5,"subgroup":54,"version":1},{"label":"monorail","hexcode":"1F69D","tags":["vehicle"],"emoji":"🚝","text":"","type":1,"order":4057,"group":5,"subgroup":54,"version":1},{"label":"mountain railway","hexcode":"1F69E","tags":["car","mountain","railway","trip"],"emoji":"🚞","text":"","type":1,"order":4058,"group":5,"subgroup":54,"version":1},{"label":"tram car","hexcode":"1F68B","tags":["bus","car","tram","trolley","trolleybus"],"emoji":"🚋","text":"","type":1,"order":4059,"group":5,"subgroup":54,"version":1},{"label":"bus","hexcode":"1F68C","tags":["school","vehicle"],"emoji":"🚌","text":"","type":1,"order":4060,"group":5,"subgroup":54,"version":0.6},{"label":"oncoming bus","hexcode":"1F68D","tags":["bus","cars","oncoming"],"emoji":"🚍️","text":"🚍︎","type":1,"order":4061,"group":5,"subgroup":54,"version":0.7},{"label":"trolleybus","hexcode":"1F68E","tags":["bus","tram","trolley"],"emoji":"🚎","text":"","type":1,"order":4062,"group":5,"subgroup":54,"version":1},{"label":"minibus","hexcode":"1F690","tags":["bus","drive","van","vehicle"],"emoji":"🚐","text":"","type":1,"order":4063,"group":5,"subgroup":54,"version":1},{"label":"ambulance","hexcode":"1F691","tags":["emergency","vehicle"],"emoji":"🚑️","text":"🚑︎","type":1,"order":4064,"group":5,"subgroup":54,"version":0.6},{"label":"fire engine","hexcode":"1F692","tags":["engine","fire","truck"],"emoji":"🚒","text":"","type":1,"order":4065,"group":5,"subgroup":54,"version":0.6},{"label":"police car","hexcode":"1F693","tags":["5–0","car","cops","patrol","police"],"emoji":"🚓","text":"","type":1,"order":4066,"group":5,"subgroup":54,"version":0.6},{"label":"oncoming police car","hexcode":"1F694","tags":["car","oncoming","police"],"emoji":"🚔️","text":"🚔︎","type":1,"order":4067,"group":5,"subgroup":54,"version":0.7},{"label":"taxi","hexcode":"1F695","tags":["cab","cabbie","car","drive","vehicle","yellow"],"emoji":"🚕","text":"","type":1,"order":4068,"group":5,"subgroup":54,"version":0.6},{"label":"oncoming taxi","hexcode":"1F696","tags":["cab","cabbie","cars","drove","hail","oncoming","taxi","yellow"],"emoji":"🚖","text":"","type":1,"order":4069,"group":5,"subgroup":54,"version":1},{"label":"automobile","hexcode":"1F697","tags":["car","driving","vehicle"],"emoji":"🚗","text":"","type":1,"order":4070,"group":5,"subgroup":54,"version":0.6},{"label":"oncoming automobile","hexcode":"1F698","tags":["automobile","car","cars","drove","oncoming","vehicle"],"emoji":"🚘️","text":"🚘︎","type":1,"order":4071,"group":5,"subgroup":54,"version":0.7},{"label":"sport utility vehicle","hexcode":"1F699","tags":["car","drive","recreational","sport","sportutility","utility","vehicle"],"emoji":"🚙","text":"","type":1,"order":4072,"group":5,"subgroup":54,"version":0.6},{"label":"pickup truck","hexcode":"1F6FB","tags":["automobile","car","flatbed","pick-up","pickup","transportation","truck"],"emoji":"🛻","text":"","type":1,"order":4073,"group":5,"subgroup":54,"version":13},{"label":"delivery truck","hexcode":"1F69A","tags":["car","delivery","drive","truck","vehicle"],"emoji":"🚚","text":"","type":1,"order":4074,"group":5,"subgroup":54,"version":0.6},{"label":"articulated lorry","hexcode":"1F69B","tags":["articulated","car","drive","lorry","move","semi","truck","vehicle"],"emoji":"🚛","text":"","type":1,"order":4075,"group":5,"subgroup":54,"version":1},{"label":"tractor","hexcode":"1F69C","tags":["vehicle"],"emoji":"🚜","text":"","type":1,"order":4076,"group":5,"subgroup":54,"version":1},{"label":"racing car","hexcode":"1F3CE","tags":["car","racing","zoom"],"emoji":"🏎️","text":"🏎︎","type":0,"order":4078,"group":5,"subgroup":54,"version":0.7},{"label":"motorcycle","hexcode":"1F3CD","tags":["racing"],"emoji":"🏍️","text":"🏍︎","type":0,"order":4080,"group":5,"subgroup":54,"version":0.7},{"label":"motor scooter","hexcode":"1F6F5","tags":["motor","scooter"],"emoji":"🛵","text":"","type":1,"order":4081,"group":5,"subgroup":54,"version":3},{"label":"manual wheelchair","hexcode":"1F9BD","tags":["accessibility","manual","wheelchair"],"emoji":"🦽","text":"","type":1,"order":4082,"group":5,"subgroup":54,"version":12},{"label":"motorized wheelchair","hexcode":"1F9BC","tags":["accessibility","motorized","wheelchair"],"emoji":"🦼","text":"","type":1,"order":4083,"group":5,"subgroup":54,"version":12},{"label":"auto rickshaw","hexcode":"1F6FA","tags":["auto","rickshaw","tuk"],"emoji":"🛺","text":"","type":1,"order":4084,"group":5,"subgroup":54,"version":12},{"label":"bicycle","hexcode":"1F6B2","tags":["bike","class","cycle","cycling","cyclist","gang","ride","spin","spinning"],"emoji":"🚲️","text":"🚲︎","type":1,"order":4085,"group":5,"subgroup":54,"version":0.6},{"label":"kick scooter","hexcode":"1F6F4","tags":["kick","scooter"],"emoji":"🛴","text":"","type":1,"order":4086,"group":5,"subgroup":54,"version":3},{"label":"skateboard","hexcode":"1F6F9","tags":["board","skate","skater","wheels"],"emoji":"🛹","text":"","type":1,"order":4087,"group":5,"subgroup":54,"version":11},{"label":"roller skate","hexcode":"1F6FC","tags":["blades","roller","skate","skates","sport"],"emoji":"🛼","text":"","type":1,"order":4088,"group":5,"subgroup":54,"version":13},{"label":"bus stop","hexcode":"1F68F","tags":["bus","busstop","stop"],"emoji":"🚏","text":"","type":1,"order":4089,"group":5,"subgroup":54,"version":0.6},{"label":"motorway","hexcode":"1F6E3","tags":["highway","road"],"emoji":"🛣️","text":"🛣︎","type":0,"order":4091,"group":5,"subgroup":54,"version":0.7},{"label":"railway track","hexcode":"1F6E4","tags":["railway","track","train"],"emoji":"🛤️","text":"🛤︎","type":0,"order":4093,"group":5,"subgroup":54,"version":0.7},{"label":"oil drum","hexcode":"1F6E2","tags":["drum","oil"],"emoji":"🛢️","text":"🛢︎","type":0,"order":4095,"group":5,"subgroup":54,"version":0.7},{"label":"fuel pump","hexcode":"26FD","tags":["diesel","fuel","fuelpump","gas","gasoline","pump","station"],"emoji":"⛽️","text":"⛽︎","type":1,"order":4096,"group":5,"subgroup":54,"version":0.6},{"label":"wheel","hexcode":"1F6DE","tags":["car","circle","tire","turn","vehicle"],"emoji":"🛞","text":"","type":1,"order":4097,"group":5,"subgroup":54,"version":14},{"label":"police car light","hexcode":"1F6A8","tags":["alarm","alert","beacon","car","emergency","light","police","revolving","siren"],"emoji":"🚨","text":"","type":1,"order":4098,"group":5,"subgroup":54,"version":0.6},{"label":"horizontal traffic light","hexcode":"1F6A5","tags":["horizontal","intersection","light","signal","stop","stoplight","traffic"],"emoji":"🚥","text":"","type":1,"order":4099,"group":5,"subgroup":54,"version":0.6},{"label":"vertical traffic light","hexcode":"1F6A6","tags":["drove","intersection","light","signal","stop","stoplight","traffic","vertical"],"emoji":"🚦","text":"","type":1,"order":4100,"group":5,"subgroup":54,"version":1},{"label":"stop sign","hexcode":"1F6D1","tags":["octagonal","sign","stop"],"emoji":"🛑","text":"","type":1,"order":4101,"group":5,"subgroup":54,"version":3},{"label":"construction","hexcode":"1F6A7","tags":["barrier"],"emoji":"🚧","text":"","type":1,"order":4102,"group":5,"subgroup":54,"version":0.6},{"label":"anchor","hexcode":"2693","tags":["ship","tool"],"emoji":"⚓️","text":"⚓︎","type":1,"order":4103,"group":5,"subgroup":55,"version":0.6},{"label":"ring buoy","hexcode":"1F6DF","tags":["buoy","float","life","lifesaver","preserver","rescue","ring","safety","save","saver","swim"],"emoji":"🛟","text":"","type":1,"order":4104,"group":5,"subgroup":55,"version":14},{"label":"sailboat","hexcode":"26F5","tags":["boat","resort","sailing","sea","yacht"],"emoji":"⛵️","text":"⛵︎","type":1,"order":4105,"group":5,"subgroup":55,"version":0.6},{"label":"canoe","hexcode":"1F6F6","tags":["boat"],"emoji":"🛶","text":"","type":1,"order":4106,"group":5,"subgroup":55,"version":3},{"label":"speedboat","hexcode":"1F6A4","tags":["billionaire","boat","lake","luxury","millionaire","summer","travel"],"emoji":"🚤","text":"","type":1,"order":4107,"group":5,"subgroup":55,"version":0.6},{"label":"passenger ship","hexcode":"1F6F3","tags":["passenger","ship"],"emoji":"🛳️","text":"🛳︎","type":0,"order":4109,"group":5,"subgroup":55,"version":0.7},{"label":"ferry","hexcode":"26F4","tags":["boat","passenger"],"emoji":"⛴️","text":"⛴︎","type":0,"order":4111,"group":5,"subgroup":55,"version":0.7},{"label":"motor boat","hexcode":"1F6E5","tags":["boat","motor","motorboat"],"emoji":"🛥️","text":"🛥︎","type":0,"order":4113,"group":5,"subgroup":55,"version":0.7},{"label":"ship","hexcode":"1F6A2","tags":["boat","passenger","travel"],"emoji":"🚢","text":"","type":1,"order":4114,"group":5,"subgroup":55,"version":0.6},{"label":"airplane","hexcode":"2708","tags":["aeroplane","fly","flying","jet","plane","travel"],"emoji":"✈️","text":"✈︎","type":0,"order":4116,"group":5,"subgroup":56,"version":0.6},{"label":"small airplane","hexcode":"1F6E9","tags":["aeroplane","airplane","plane","small"],"emoji":"🛩️","text":"🛩︎","type":0,"order":4118,"group":5,"subgroup":56,"version":0.7},{"label":"airplane departure","hexcode":"1F6EB","tags":["aeroplane","airplane","check-in","departure","departures","plane"],"emoji":"🛫","text":"","type":1,"order":4119,"group":5,"subgroup":56,"version":1},{"label":"airplane arrival","hexcode":"1F6EC","tags":["aeroplane","airplane","arrival","arrivals","arriving","landing","plane"],"emoji":"🛬","text":"","type":1,"order":4120,"group":5,"subgroup":56,"version":1},{"label":"parachute","hexcode":"1FA82","tags":["hang-glide","parasail","skydive"],"emoji":"🪂","text":"","type":1,"order":4121,"group":5,"subgroup":56,"version":12},{"label":"seat","hexcode":"1F4BA","tags":["chair"],"emoji":"💺","text":"","type":1,"order":4122,"group":5,"subgroup":56,"version":0.6},{"label":"helicopter","hexcode":"1F681","tags":["copter","roflcopter","travel","vehicle"],"emoji":"🚁","text":"","type":1,"order":4123,"group":5,"subgroup":56,"version":1},{"label":"suspension railway","hexcode":"1F69F","tags":["railway","suspension"],"emoji":"🚟","text":"","type":1,"order":4124,"group":5,"subgroup":56,"version":1},{"label":"mountain cableway","hexcode":"1F6A0","tags":["cable","cableway","gondola","lift","mountain","ski"],"emoji":"🚠","text":"","type":1,"order":4125,"group":5,"subgroup":56,"version":1},{"label":"aerial tramway","hexcode":"1F6A1","tags":["aerial","cable","car","gondola","ropeway","tramway"],"emoji":"🚡","text":"","type":1,"order":4126,"group":5,"subgroup":56,"version":1},{"label":"satellite","hexcode":"1F6F0","tags":["space"],"emoji":"🛰️","text":"🛰︎","type":0,"order":4128,"group":5,"subgroup":56,"version":0.7},{"label":"rocket","hexcode":"1F680","tags":["launch","rockets","space","travel"],"emoji":"🚀","text":"","type":1,"order":4129,"group":5,"subgroup":56,"version":0.6},{"label":"flying saucer","hexcode":"1F6F8","tags":["aliens","extra","flying","saucer","terrestrial","ufo"],"emoji":"🛸","text":"","type":1,"order":4130,"group":5,"subgroup":56,"version":5},{"label":"bellhop bell","hexcode":"1F6CE","tags":["bell","bellhop","hotel"],"emoji":"🛎️","text":"🛎︎","type":0,"order":4132,"group":5,"subgroup":57,"version":0.7},{"label":"luggage","hexcode":"1F9F3","tags":["bag","packing","roller","suitcase","travel"],"emoji":"🧳","text":"","type":1,"order":4133,"group":5,"subgroup":57,"version":11},{"label":"hourglass done","hexcode":"231B","tags":["done","hourglass","sand","time","timer"],"emoji":"⌛️","text":"⌛︎","type":1,"order":4134,"group":5,"subgroup":58,"version":0.6},{"label":"hourglass not done","hexcode":"23F3","tags":["done","flowing","hourglass","hours","not","sand","timer","waiting","yolo"],"emoji":"⏳️","text":"⏳︎","type":1,"order":4135,"group":5,"subgroup":58,"version":0.6},{"label":"watch","hexcode":"231A","tags":["clock","time"],"emoji":"⌚️","text":"⌚︎","type":1,"order":4136,"group":5,"subgroup":58,"version":0.6},{"label":"alarm clock","hexcode":"23F0","tags":["alarm","clock","hours","hrs","late","time","waiting"],"emoji":"⏰️","text":"⏰︎","type":1,"order":4137,"group":5,"subgroup":58,"version":0.6},{"label":"stopwatch","hexcode":"23F1","tags":["clock","time"],"emoji":"⏱️","text":"⏱︎","type":0,"order":4139,"group":5,"subgroup":58,"version":1},{"label":"timer clock","hexcode":"23F2","tags":["clock","timer"],"emoji":"⏲️","text":"⏲︎","type":0,"order":4141,"group":5,"subgroup":58,"version":1},{"label":"mantelpiece clock","hexcode":"1F570","tags":["clock","mantelpiece","time"],"emoji":"🕰️","text":"🕰︎","type":0,"order":4143,"group":5,"subgroup":58,"version":0.7},{"label":"twelve o’clock","hexcode":"1F55B","tags":["12","12:00","clock","o’clock","time","twelve"],"emoji":"🕛️","text":"🕛︎","type":1,"order":4144,"group":5,"subgroup":58,"version":0.6},{"label":"twelve-thirty","hexcode":"1F567","tags":["12","12:30","30","clock","thirty","time","twelve"],"emoji":"🕧️","text":"🕧︎","type":1,"order":4145,"group":5,"subgroup":58,"version":0.7},{"label":"one o’clock","hexcode":"1F550","tags":["1","1:00","clock","one","o’clock","time"],"emoji":"🕐️","text":"🕐︎","type":1,"order":4146,"group":5,"subgroup":58,"version":0.6},{"label":"one-thirty","hexcode":"1F55C","tags":["1","1:30","30","clock","one","thirty","time"],"emoji":"🕜️","text":"🕜︎","type":1,"order":4147,"group":5,"subgroup":58,"version":0.7},{"label":"two o’clock","hexcode":"1F551","tags":["2","2:00","clock","o’clock","time","two"],"emoji":"🕑️","text":"🕑︎","type":1,"order":4148,"group":5,"subgroup":58,"version":0.6},{"label":"two-thirty","hexcode":"1F55D","tags":["2","2:30","30","clock","thirty","time","two"],"emoji":"🕝️","text":"🕝︎","type":1,"order":4149,"group":5,"subgroup":58,"version":0.7},{"label":"three o’clock","hexcode":"1F552","tags":["3","3:00","clock","o’clock","three","time"],"emoji":"🕒️","text":"🕒︎","type":1,"order":4150,"group":5,"subgroup":58,"version":0.6},{"label":"three-thirty","hexcode":"1F55E","tags":["3","30","3:30","clock","thirty","three","time"],"emoji":"🕞️","text":"🕞︎","type":1,"order":4151,"group":5,"subgroup":58,"version":0.7},{"label":"four o’clock","hexcode":"1F553","tags":["4","4:00","clock","four","o’clock","time"],"emoji":"🕓️","text":"🕓︎","type":1,"order":4152,"group":5,"subgroup":58,"version":0.6},{"label":"four-thirty","hexcode":"1F55F","tags":["30","4","4:30","clock","four","thirty","time"],"emoji":"🕟️","text":"🕟︎","type":1,"order":4153,"group":5,"subgroup":58,"version":0.7},{"label":"five o’clock","hexcode":"1F554","tags":["5","5:00","clock","five","o’clock","time"],"emoji":"🕔️","text":"🕔︎","type":1,"order":4154,"group":5,"subgroup":58,"version":0.6},{"label":"five-thirty","hexcode":"1F560","tags":["30","5","5:30","clock","five","thirty","time"],"emoji":"🕠️","text":"🕠︎","type":1,"order":4155,"group":5,"subgroup":58,"version":0.7},{"label":"six o’clock","hexcode":"1F555","tags":["6","6:00","clock","o’clock","six","time"],"emoji":"🕕️","text":"🕕︎","type":1,"order":4156,"group":5,"subgroup":58,"version":0.6},{"label":"six-thirty","hexcode":"1F561","tags":["30","6","6:30","clock","six","thirty"],"emoji":"🕡️","text":"🕡︎","type":1,"order":4157,"group":5,"subgroup":58,"version":0.7},{"label":"seven o’clock","hexcode":"1F556","tags":["0","7","7:00","clock","o’clock","seven"],"emoji":"🕖️","text":"🕖︎","type":1,"order":4158,"group":5,"subgroup":58,"version":0.6},{"label":"seven-thirty","hexcode":"1F562","tags":["30","7","7:30","clock","seven","thirty"],"emoji":"🕢️","text":"🕢︎","type":1,"order":4159,"group":5,"subgroup":58,"version":0.7},{"label":"eight o’clock","hexcode":"1F557","tags":["8","8:00","clock","eight","o’clock","time"],"emoji":"🕗️","text":"🕗︎","type":1,"order":4160,"group":5,"subgroup":58,"version":0.6},{"label":"eight-thirty","hexcode":"1F563","tags":["30","8","8:30","clock","eight","thirty","time"],"emoji":"🕣️","text":"🕣︎","type":1,"order":4161,"group":5,"subgroup":58,"version":0.7},{"label":"nine o’clock","hexcode":"1F558","tags":["9","9:00","clock","nine","o’clock","time"],"emoji":"🕘️","text":"🕘︎","type":1,"order":4162,"group":5,"subgroup":58,"version":0.6},{"label":"nine-thirty","hexcode":"1F564","tags":["30","9","9:30","clock","nine","thirty","time"],"emoji":"🕤️","text":"🕤︎","type":1,"order":4163,"group":5,"subgroup":58,"version":0.7},{"label":"ten o’clock","hexcode":"1F559","tags":["0","10","10:00","clock","o’clock","ten"],"emoji":"🕙️","text":"🕙︎","type":1,"order":4164,"group":5,"subgroup":58,"version":0.6},{"label":"ten-thirty","hexcode":"1F565","tags":["10","10:30","30","clock","ten","thirty","time"],"emoji":"🕥️","text":"🕥︎","type":1,"order":4165,"group":5,"subgroup":58,"version":0.7},{"label":"eleven o’clock","hexcode":"1F55A","tags":["11","11:00","clock","eleven","o’clock","time"],"emoji":"🕚️","text":"🕚︎","type":1,"order":4166,"group":5,"subgroup":58,"version":0.6},{"label":"eleven-thirty","hexcode":"1F566","tags":["11","11:30","30","clock","eleven","thirty","time"],"emoji":"🕦️","text":"🕦︎","type":1,"order":4167,"group":5,"subgroup":58,"version":0.7},{"label":"new moon","hexcode":"1F311","tags":["dark","moon","new","space"],"emoji":"🌑","text":"","type":1,"order":4168,"group":5,"subgroup":59,"version":0.6},{"label":"waxing crescent moon","hexcode":"1F312","tags":["crescent","dreams","moon","space","waxing"],"emoji":"🌒","text":"","type":1,"order":4169,"group":5,"subgroup":59,"version":1},{"label":"first quarter moon","hexcode":"1F313","tags":["first","moon","quarter","space"],"emoji":"🌓","text":"","type":1,"order":4170,"group":5,"subgroup":59,"version":0.6},{"label":"waxing gibbous moon","hexcode":"1F314","tags":["gibbous","moon","space","waxing"],"emoji":"🌔","text":"","type":1,"order":4171,"group":5,"subgroup":59,"version":0.6},{"label":"full moon","hexcode":"1F315","tags":["full","moon","space"],"emoji":"🌕️","text":"🌕︎","type":1,"order":4172,"group":5,"subgroup":59,"version":0.6},{"label":"waning gibbous moon","hexcode":"1F316","tags":["gibbous","moon","space","waning"],"emoji":"🌖","text":"","type":1,"order":4173,"group":5,"subgroup":59,"version":1},{"label":"last quarter moon","hexcode":"1F317","tags":["last","moon","quarter","space"],"emoji":"🌗","text":"","type":1,"order":4174,"group":5,"subgroup":59,"version":1},{"label":"waning crescent moon","hexcode":"1F318","tags":["crescent","moon","space","waning"],"emoji":"🌘","text":"","type":1,"order":4175,"group":5,"subgroup":59,"version":1},{"label":"crescent moon","hexcode":"1F319","tags":["crescent","moon","ramadan","space"],"emoji":"🌙","text":"","type":1,"order":4176,"group":5,"subgroup":59,"version":0.6},{"label":"new moon face","hexcode":"1F31A","tags":["face","moon","new","space"],"emoji":"🌚","text":"","type":1,"order":4177,"group":5,"subgroup":59,"version":1},{"label":"first quarter moon face","hexcode":"1F31B","tags":["face","first","moon","quarter","space"],"emoji":"🌛","text":"","type":1,"order":4178,"group":5,"subgroup":59,"version":0.6},{"label":"last quarter moon face","hexcode":"1F31C","tags":["dreams","face","last","moon","quarter"],"emoji":"🌜️","text":"🌜︎","type":1,"order":4179,"group":5,"subgroup":59,"version":0.7},{"label":"thermometer","hexcode":"1F321","tags":["weather"],"emoji":"🌡️","text":"🌡︎","type":0,"order":4181,"group":5,"subgroup":59,"version":0.7},{"label":"sun","hexcode":"2600","tags":["bright","rays","space","sunny","weather"],"emoji":"☀️","text":"☀︎","type":0,"order":4183,"group":5,"subgroup":59,"version":0.6},{"label":"full moon face","hexcode":"1F31D","tags":["bright","face","full","moon"],"emoji":"🌝","text":"","type":1,"order":4184,"group":5,"subgroup":59,"version":1},{"label":"sun with face","hexcode":"1F31E","tags":["beach","bright","day","face","heat","shine","sun","sunny","sunshine","weather"],"emoji":"🌞","text":"","type":1,"order":4185,"group":5,"subgroup":59,"version":1},{"label":"ringed planet","hexcode":"1FA90","tags":["planet","ringed","saturn","saturnine"],"emoji":"🪐","text":"","type":1,"order":4186,"group":5,"subgroup":59,"version":12},{"label":"star","hexcode":"2B50","tags":["astronomy","medium","stars","white"],"emoji":"⭐️","text":"⭐︎","type":1,"order":4187,"group":5,"subgroup":59,"version":0.6},{"label":"glowing star","hexcode":"1F31F","tags":["glittery","glow","glowing","night","shining","sparkle","star","win"],"emoji":"🌟","text":"","type":1,"order":4188,"group":5,"subgroup":59,"version":0.6},{"label":"shooting star","hexcode":"1F320","tags":["falling","night","shooting","space","star"],"emoji":"🌠","text":"","type":1,"order":4189,"group":5,"subgroup":59,"version":0.6},{"label":"milky way","hexcode":"1F30C","tags":["milky","space","way"],"emoji":"🌌","text":"","type":1,"order":4190,"group":5,"subgroup":59,"version":0.6},{"label":"cloud","hexcode":"2601","tags":["weather"],"emoji":"☁️","text":"☁︎","type":0,"order":4192,"group":5,"subgroup":59,"version":0.6},{"label":"sun behind cloud","hexcode":"26C5","tags":["behind","cloud","cloudy","sun","weather"],"emoji":"⛅️","text":"⛅︎","type":1,"order":4193,"group":5,"subgroup":59,"version":0.6},{"label":"cloud with lightning and rain","hexcode":"26C8","tags":["cloud","lightning","rain","thunder","thunderstorm"],"emoji":"⛈️","text":"⛈︎","type":0,"order":4195,"group":5,"subgroup":59,"version":0.7},{"label":"sun behind small cloud","hexcode":"1F324","tags":["behind","cloud","sun","weather"],"emoji":"🌤️","text":"🌤︎","type":0,"order":4197,"group":5,"subgroup":59,"version":0.7},{"label":"sun behind large cloud","hexcode":"1F325","tags":["behind","cloud","sun","weather"],"emoji":"🌥️","text":"🌥︎","type":0,"order":4199,"group":5,"subgroup":59,"version":0.7},{"label":"sun behind rain cloud","hexcode":"1F326","tags":["behind","cloud","rain","sun","weather"],"emoji":"🌦️","text":"🌦︎","type":0,"order":4201,"group":5,"subgroup":59,"version":0.7},{"label":"cloud with rain","hexcode":"1F327","tags":["cloud","rain","weather"],"emoji":"🌧️","text":"🌧︎","type":0,"order":4203,"group":5,"subgroup":59,"version":0.7},{"label":"cloud with snow","hexcode":"1F328","tags":["cloud","cold","snow","weather"],"emoji":"🌨️","text":"🌨︎","type":0,"order":4205,"group":5,"subgroup":59,"version":0.7},{"label":"cloud with lightning","hexcode":"1F329","tags":["cloud","lightning","weather"],"emoji":"🌩️","text":"🌩︎","type":0,"order":4207,"group":5,"subgroup":59,"version":0.7},{"label":"tornado","hexcode":"1F32A","tags":["cloud","weather","whirlwind"],"emoji":"🌪️","text":"🌪︎","type":0,"order":4209,"group":5,"subgroup":59,"version":0.7},{"label":"fog","hexcode":"1F32B","tags":["cloud","weather"],"emoji":"🌫️","text":"🌫︎","type":0,"order":4211,"group":5,"subgroup":59,"version":0.7},{"label":"wind face","hexcode":"1F32C","tags":["blow","cloud","face","wind"],"emoji":"🌬️","text":"🌬︎","type":0,"order":4213,"group":5,"subgroup":59,"version":0.7},{"label":"cyclone","hexcode":"1F300","tags":["dizzy","hurricane","twister","typhoon","weather"],"emoji":"🌀","text":"","type":1,"order":4214,"group":5,"subgroup":59,"version":0.6},{"label":"rainbow","hexcode":"1F308","tags":["gay","genderqueer","glbt","glbtq","lesbian","lgbt","lgbtq","lgbtqia","nature","pride","queer","rain","trans","transgender","weather"],"emoji":"🌈","text":"","type":1,"order":4215,"group":5,"subgroup":59,"version":0.6},{"label":"closed umbrella","hexcode":"1F302","tags":["closed","clothing","rain","umbrella"],"emoji":"🌂","text":"","type":1,"order":4216,"group":5,"subgroup":59,"version":0.6},{"label":"umbrella","hexcode":"2602","tags":["clothing","rain"],"emoji":"☂️","text":"☂︎","type":0,"order":4218,"group":5,"subgroup":59,"version":0.7},{"label":"umbrella with rain drops","hexcode":"2614","tags":["clothing","drop","drops","rain","umbrella","weather"],"emoji":"☔️","text":"☔︎","type":1,"order":4219,"group":5,"subgroup":59,"version":0.6},{"label":"umbrella on ground","hexcode":"26F1","tags":["ground","rain","sun","umbrella"],"emoji":"⛱️","text":"⛱︎","type":0,"order":4221,"group":5,"subgroup":59,"version":0.7},{"label":"high voltage","hexcode":"26A1","tags":["danger","electric","electricity","high","lightning","nature","thunder","thunderbolt","voltage","zap"],"emoji":"⚡️","text":"⚡︎","type":1,"order":4222,"group":5,"subgroup":59,"version":0.6},{"label":"snowflake","hexcode":"2744","tags":["cold","snow","weather"],"emoji":"❄️","text":"❄︎","type":0,"order":4224,"group":5,"subgroup":59,"version":0.6},{"label":"snowman","hexcode":"2603","tags":["cold","man","snow"],"emoji":"☃️","text":"☃︎","type":0,"order":4226,"group":5,"subgroup":59,"version":0.7},{"label":"snowman without snow","hexcode":"26C4","tags":["cold","man","snow","snowman"],"emoji":"⛄️","text":"⛄︎","type":1,"order":4227,"group":5,"subgroup":59,"version":0.6},{"label":"comet","hexcode":"2604","tags":["space"],"emoji":"☄️","text":"☄︎","type":0,"order":4229,"group":5,"subgroup":59,"version":1},{"label":"fire","hexcode":"1F525","tags":["af","burn","flame","hot","lit","litaf","tool"],"emoji":"🔥","text":"","type":1,"order":4230,"group":5,"subgroup":59,"version":0.6},{"label":"droplet","hexcode":"1F4A7","tags":["cold","comic","drop","nature","sad","sweat","tear","water","weather"],"emoji":"💧","text":"","type":1,"order":4231,"group":5,"subgroup":59,"version":0.6},{"label":"water wave","hexcode":"1F30A","tags":["nature","ocean","surf","surfer","surfing","water","wave"],"emoji":"🌊","text":"","type":1,"order":4232,"group":5,"subgroup":59,"version":0.6},{"label":"jack-o-lantern","hexcode":"1F383","tags":["celebration","halloween","jack","lantern","pumpkin"],"emoji":"🎃","text":"","type":1,"order":4233,"group":6,"subgroup":60,"version":0.6},{"label":"Christmas tree","hexcode":"1F384","tags":["celebration","christmas","tree"],"emoji":"🎄","text":"","type":1,"order":4234,"group":6,"subgroup":60,"version":0.6},{"label":"fireworks","hexcode":"1F386","tags":["boom","celebration","entertainment","yolo"],"emoji":"🎆","text":"","type":1,"order":4235,"group":6,"subgroup":60,"version":0.6},{"label":"sparkler","hexcode":"1F387","tags":["boom","celebration","fireworks","sparkle"],"emoji":"🎇","text":"","type":1,"order":4236,"group":6,"subgroup":60,"version":0.6},{"label":"firecracker","hexcode":"1F9E8","tags":["dynamite","explosive","fire","fireworks","light","pop","popping","spark"],"emoji":"🧨","text":"","type":1,"order":4237,"group":6,"subgroup":60,"version":11},{"label":"sparkles","hexcode":"2728","tags":["*","magic","sparkle","star"],"emoji":"✨️","text":"✨︎","type":1,"order":4238,"group":6,"subgroup":60,"version":0.6},{"label":"balloon","hexcode":"1F388","tags":["birthday","celebrate","celebration"],"emoji":"🎈","text":"","type":1,"order":4239,"group":6,"subgroup":60,"version":0.6},{"label":"party popper","hexcode":"1F389","tags":["awesome","birthday","celebrate","celebration","excited","hooray","party","popper","tada","woohoo"],"emoji":"🎉","text":"","type":1,"order":4240,"group":6,"subgroup":60,"version":0.6},{"label":"confetti ball","hexcode":"1F38A","tags":["ball","celebrate","celebration","confetti","party","woohoo"],"emoji":"🎊","text":"","type":1,"order":4241,"group":6,"subgroup":60,"version":0.6},{"label":"tanabata tree","hexcode":"1F38B","tags":["banner","celebration","japanese","tanabata","tree"],"emoji":"🎋","text":"","type":1,"order":4242,"group":6,"subgroup":60,"version":0.6},{"label":"pine decoration","hexcode":"1F38D","tags":["bamboo","celebration","decoration","japanese","pine","plant"],"emoji":"🎍","text":"","type":1,"order":4243,"group":6,"subgroup":60,"version":0.6},{"label":"Japanese dolls","hexcode":"1F38E","tags":["celebration","doll","dolls","festival","japanese"],"emoji":"🎎","text":"","type":1,"order":4244,"group":6,"subgroup":60,"version":0.6},{"label":"carp streamer","hexcode":"1F38F","tags":["carp","celebration","streamer"],"emoji":"🎏","text":"","type":1,"order":4245,"group":6,"subgroup":60,"version":0.6},{"label":"wind chime","hexcode":"1F390","tags":["bell","celebration","chime","wind"],"emoji":"🎐","text":"","type":1,"order":4246,"group":6,"subgroup":60,"version":0.6},{"label":"moon viewing ceremony","hexcode":"1F391","tags":["celebration","ceremony","moon","viewing"],"emoji":"🎑","text":"","type":1,"order":4247,"group":6,"subgroup":60,"version":0.6},{"label":"red envelope","hexcode":"1F9E7","tags":["envelope","gift","good","hóngbāo","lai","luck","money","red","see"],"emoji":"🧧","text":"","type":1,"order":4248,"group":6,"subgroup":60,"version":11},{"label":"ribbon","hexcode":"1F380","tags":["celebration"],"emoji":"🎀","text":"","type":1,"order":4249,"group":6,"subgroup":60,"version":0.6},{"label":"wrapped gift","hexcode":"1F381","tags":["birthday","bow","box","celebration","christmas","gift","present","surprise","wrapped"],"emoji":"🎁","text":"","type":1,"order":4250,"group":6,"subgroup":60,"version":0.6},{"label":"reminder ribbon","hexcode":"1F397","tags":["celebration","reminder","ribbon"],"emoji":"🎗️","text":"🎗︎","type":0,"order":4252,"group":6,"subgroup":60,"version":0.7},{"label":"admission tickets","hexcode":"1F39F","tags":["admission","ticket","tickets"],"emoji":"🎟️","text":"🎟︎","type":0,"order":4254,"group":6,"subgroup":60,"version":0.7},{"label":"ticket","hexcode":"1F3AB","tags":["admission","stub"],"emoji":"🎫","text":"","type":1,"order":4255,"group":6,"subgroup":60,"version":0.6},{"label":"military medal","hexcode":"1F396","tags":["award","celebration","medal","military"],"emoji":"🎖️","text":"🎖︎","type":0,"order":4257,"group":6,"subgroup":61,"version":0.7},{"label":"trophy","hexcode":"1F3C6","tags":["champion","champs","prize","slay","sport","victory","win","winning"],"emoji":"🏆️","text":"🏆︎","type":1,"order":4258,"group":6,"subgroup":61,"version":0.6},{"label":"sports medal","hexcode":"1F3C5","tags":["award","gold","medal","sports","winner"],"emoji":"🏅","text":"","type":1,"order":4259,"group":6,"subgroup":61,"version":1},{"label":"1st place medal","hexcode":"1F947","tags":["1st","first","gold","medal","place"],"emoji":"🥇","text":"","type":1,"order":4260,"group":6,"subgroup":61,"version":3},{"label":"2nd place medal","hexcode":"1F948","tags":["2nd","medal","place","second","silver"],"emoji":"🥈","text":"","type":1,"order":4261,"group":6,"subgroup":61,"version":3},{"label":"3rd place medal","hexcode":"1F949","tags":["3rd","bronze","medal","place","third"],"emoji":"🥉","text":"","type":1,"order":4262,"group":6,"subgroup":61,"version":3},{"label":"soccer ball","hexcode":"26BD","tags":["ball","football","futbol","soccer","sport"],"emoji":"⚽️","text":"⚽︎","type":1,"order":4263,"group":6,"subgroup":62,"version":0.6},{"label":"baseball","hexcode":"26BE","tags":["ball","sport"],"emoji":"⚾️","text":"⚾︎","type":1,"order":4264,"group":6,"subgroup":62,"version":0.6},{"label":"softball","hexcode":"1F94E","tags":["ball","glove","sports","underarm"],"emoji":"🥎","text":"","type":1,"order":4265,"group":6,"subgroup":62,"version":11},{"label":"basketball","hexcode":"1F3C0","tags":["ball","hoop","sport"],"emoji":"🏀","text":"","type":1,"order":4266,"group":6,"subgroup":62,"version":0.6},{"label":"volleyball","hexcode":"1F3D0","tags":["ball","game"],"emoji":"🏐","text":"","type":1,"order":4267,"group":6,"subgroup":62,"version":1},{"label":"american football","hexcode":"1F3C8","tags":["american","ball","bowl","football","sport","super"],"emoji":"🏈","text":"","type":1,"order":4268,"group":6,"subgroup":62,"version":0.6},{"label":"rugby football","hexcode":"1F3C9","tags":["ball","football","rugby","sport"],"emoji":"🏉","text":"","type":1,"order":4269,"group":6,"subgroup":62,"version":1},{"label":"tennis","hexcode":"1F3BE","tags":["ball","racquet","sport"],"emoji":"🎾","text":"","type":1,"order":4270,"group":6,"subgroup":62,"version":0.6},{"label":"flying disc","hexcode":"1F94F","tags":["disc","flying","ultimate"],"emoji":"🥏","text":"","type":1,"order":4271,"group":6,"subgroup":62,"version":11},{"label":"bowling","hexcode":"1F3B3","tags":["ball","game","sport","strike"],"emoji":"🎳","text":"","type":1,"order":4272,"group":6,"subgroup":62,"version":0.6},{"label":"cricket game","hexcode":"1F3CF","tags":["ball","bat","cricket","game"],"emoji":"🏏","text":"","type":1,"order":4273,"group":6,"subgroup":62,"version":1},{"label":"field hockey","hexcode":"1F3D1","tags":["ball","field","game","hockey","stick"],"emoji":"🏑","text":"","type":1,"order":4274,"group":6,"subgroup":62,"version":1},{"label":"ice hockey","hexcode":"1F3D2","tags":["game","hockey","ice","puck","stick"],"emoji":"🏒","text":"","type":1,"order":4275,"group":6,"subgroup":62,"version":1},{"label":"lacrosse","hexcode":"1F94D","tags":["ball","goal","sports","stick"],"emoji":"🥍","text":"","type":1,"order":4276,"group":6,"subgroup":62,"version":11},{"label":"ping pong","hexcode":"1F3D3","tags":["ball","bat","game","paddle","ping","pingpong","pong","table","tennis"],"emoji":"🏓","text":"","type":1,"order":4277,"group":6,"subgroup":62,"version":1},{"label":"badminton","hexcode":"1F3F8","tags":["birdie","game","racquet","shuttlecock"],"emoji":"🏸","text":"","type":1,"order":4278,"group":6,"subgroup":62,"version":1},{"label":"boxing glove","hexcode":"1F94A","tags":["boxing","glove"],"emoji":"🥊","text":"","type":1,"order":4279,"group":6,"subgroup":62,"version":3},{"label":"martial arts uniform","hexcode":"1F94B","tags":["arts","judo","karate","martial","taekwondo","uniform"],"emoji":"🥋","text":"","type":1,"order":4280,"group":6,"subgroup":62,"version":3},{"label":"goal net","hexcode":"1F945","tags":["goal","net"],"emoji":"🥅","text":"","type":1,"order":4281,"group":6,"subgroup":62,"version":3},{"label":"flag in hole","hexcode":"26F3","tags":["flag","golf","hole","sport"],"emoji":"⛳️","text":"⛳︎","type":1,"order":4282,"group":6,"subgroup":62,"version":0.6},{"label":"ice skate","hexcode":"26F8","tags":["ice","skate","skating"],"emoji":"⛸️","text":"⛸︎","type":0,"order":4284,"group":6,"subgroup":62,"version":0.7},{"label":"fishing pole","hexcode":"1F3A3","tags":["entertainment","fish","fishing","pole","sport"],"emoji":"🎣","text":"","type":1,"order":4285,"group":6,"subgroup":62,"version":0.6},{"label":"diving mask","hexcode":"1F93F","tags":["diving","mask","scuba","snorkeling"],"emoji":"🤿","text":"","type":1,"order":4286,"group":6,"subgroup":62,"version":12},{"label":"running shirt","hexcode":"1F3BD","tags":["athletics","running","sash","shirt"],"emoji":"🎽","text":"","type":1,"order":4287,"group":6,"subgroup":62,"version":0.6},{"label":"skis","hexcode":"1F3BF","tags":["ski","snow","sport"],"emoji":"🎿","text":"","type":1,"order":4288,"group":6,"subgroup":62,"version":0.6},{"label":"sled","hexcode":"1F6F7","tags":["luge","sledge","sleigh","snow","toboggan"],"emoji":"🛷","text":"","type":1,"order":4289,"group":6,"subgroup":62,"version":5},{"label":"curling stone","hexcode":"1F94C","tags":["curling","game","rock","stone"],"emoji":"🥌","text":"","type":1,"order":4290,"group":6,"subgroup":62,"version":5},{"label":"bullseye","hexcode":"1F3AF","tags":["bull","dart","direct","entertainment","game","hit","target"],"emoji":"🎯","text":"","type":1,"order":4291,"group":6,"subgroup":63,"version":0.6},{"label":"yo-yo","hexcode":"1FA80","tags":["fluctuate","toy"],"emoji":"🪀","text":"","type":1,"order":4292,"group":6,"subgroup":63,"version":12},{"label":"kite","hexcode":"1FA81","tags":["fly","soar"],"emoji":"🪁","text":"","type":1,"order":4293,"group":6,"subgroup":63,"version":12},{"label":"water pistol","hexcode":"1F52B","tags":["gun","handgun","pistol","revolver","tool","water","weapon"],"emoji":"🔫","text":"","type":1,"order":4294,"group":6,"subgroup":63,"version":0.6},{"label":"pool 8 ball","hexcode":"1F3B1","tags":["8","8ball","ball","billiard","eight","game","pool"],"emoji":"🎱","text":"","type":1,"order":4295,"group":6,"subgroup":63,"version":0.6},{"label":"crystal ball","hexcode":"1F52E","tags":["ball","crystal","fairy","fairytale","fantasy","fortune","future","magic","tale","tool"],"emoji":"🔮","text":"","type":1,"order":4296,"group":6,"subgroup":63,"version":0.6},{"label":"magic wand","hexcode":"1FA84","tags":["magic","magician","wand","witch","wizard"],"emoji":"🪄","text":"","type":1,"order":4297,"group":6,"subgroup":63,"version":13},{"label":"video game","hexcode":"1F3AE","tags":["controller","entertainment","game","video"],"emoji":"🎮️","text":"🎮︎","type":1,"order":4298,"group":6,"subgroup":63,"version":0.6},{"label":"joystick","hexcode":"1F579","tags":["game","video","videogame"],"emoji":"🕹️","text":"🕹︎","type":0,"order":4300,"group":6,"subgroup":63,"version":0.7},{"label":"slot machine","hexcode":"1F3B0","tags":["casino","gamble","gambling","game","machine","slot","slots"],"emoji":"🎰","text":"","type":1,"order":4301,"group":6,"subgroup":63,"version":0.6},{"label":"game die","hexcode":"1F3B2","tags":["dice","die","entertainment","game"],"emoji":"🎲","text":"","type":1,"order":4302,"group":6,"subgroup":63,"version":0.6},{"label":"puzzle piece","hexcode":"1F9E9","tags":["clue","interlocking","jigsaw","piece","puzzle"],"emoji":"🧩","text":"","type":1,"order":4303,"group":6,"subgroup":63,"version":11},{"label":"teddy bear","hexcode":"1F9F8","tags":["bear","plaything","plush","stuffed","teddy","toy"],"emoji":"🧸","text":"","type":1,"order":4304,"group":6,"subgroup":63,"version":11},{"label":"piñata","hexcode":"1FA85","tags":["candy","celebrate","celebration","cinco","de","festive","mayo","party","pinada","pinata"],"emoji":"🪅","text":"","type":1,"order":4305,"group":6,"subgroup":63,"version":13},{"label":"mirror ball","hexcode":"1FAA9","tags":["ball","dance","disco","glitter","mirror","party"],"emoji":"🪩","text":"","type":1,"order":4306,"group":6,"subgroup":63,"version":14},{"label":"nesting dolls","hexcode":"1FA86","tags":["babooshka","baboushka","babushka","doll","dolls","matryoshka","nesting","russia"],"emoji":"🪆","text":"","type":1,"order":4307,"group":6,"subgroup":63,"version":13},{"label":"spade suit","hexcode":"2660","tags":["card","game","spade","suit"],"emoji":"♠️","text":"♠︎","type":0,"order":4309,"group":6,"subgroup":63,"version":0.6},{"label":"heart suit","hexcode":"2665","tags":["card","emotion","game","heart","hearts","suit"],"emoji":"♥️","text":"♥︎","type":0,"order":4311,"group":6,"subgroup":63,"version":0.6},{"label":"diamond suit","hexcode":"2666","tags":["card","diamond","game","suit"],"emoji":"♦️","text":"♦︎","type":0,"order":4313,"group":6,"subgroup":63,"version":0.6},{"label":"club suit","hexcode":"2663","tags":["card","club","clubs","game","suit"],"emoji":"♣️","text":"♣︎","type":0,"order":4315,"group":6,"subgroup":63,"version":0.6},{"label":"chess pawn","hexcode":"265F","tags":["chess","dupe","expendable","pawn"],"emoji":"♟️","text":"♟︎","type":0,"order":4317,"group":6,"subgroup":63,"version":11},{"label":"joker","hexcode":"1F0CF","tags":["card","game","wildcard"],"emoji":"🃏","text":"","type":1,"order":4318,"group":6,"subgroup":63,"version":0.6},{"label":"mahjong red dragon","hexcode":"1F004","tags":["dragon","game","mahjong","red"],"emoji":"🀄️","text":"🀄︎","type":1,"order":4319,"group":6,"subgroup":63,"version":0.6},{"label":"flower playing cards","hexcode":"1F3B4","tags":["card","cards","flower","game","japanese","playing"],"emoji":"🎴","text":"","type":1,"order":4320,"group":6,"subgroup":63,"version":0.6},{"label":"performing arts","hexcode":"1F3AD","tags":["actor","actress","art","arts","entertainment","mask","performing","theater","theatre","thespian"],"emoji":"🎭️","text":"🎭︎","type":1,"order":4321,"group":6,"subgroup":64,"version":0.6},{"label":"framed picture","hexcode":"1F5BC","tags":["art","frame","framed","museum","painting","picture"],"emoji":"🖼️","text":"🖼︎","type":0,"order":4323,"group":6,"subgroup":64,"version":0.7},{"label":"artist palette","hexcode":"1F3A8","tags":["art","artist","artsy","arty","colorful","creative","entertainment","museum","painter","painting","palette"],"emoji":"🎨","text":"","type":1,"order":4324,"group":6,"subgroup":64,"version":0.6},{"label":"thread","hexcode":"1F9F5","tags":["needle","sewing","spool","string"],"emoji":"🧵","text":"","type":1,"order":4325,"group":6,"subgroup":64,"version":11},{"label":"sewing needle","hexcode":"1FAA1","tags":["embroidery","needle","sew","sewing","stitches","sutures","tailoring","thread"],"emoji":"🪡","text":"","type":1,"order":4326,"group":6,"subgroup":64,"version":13},{"label":"yarn","hexcode":"1F9F6","tags":["ball","crochet","knit"],"emoji":"🧶","text":"","type":1,"order":4327,"group":6,"subgroup":64,"version":11},{"label":"knot","hexcode":"1FAA2","tags":["cord","rope","tangled","tie","twine","twist"],"emoji":"🪢","text":"","type":1,"order":4328,"group":6,"subgroup":64,"version":13},{"label":"glasses","hexcode":"1F453","tags":["clothing","eye","eyeglasses","eyewear"],"emoji":"👓️","text":"👓︎","type":1,"order":4329,"group":7,"subgroup":65,"version":0.6},{"label":"sunglasses","hexcode":"1F576","tags":["dark","eye","eyewear","glasses"],"emoji":"🕶️","text":"🕶︎","type":0,"order":4331,"group":7,"subgroup":65,"version":0.7},{"label":"goggles","hexcode":"1F97D","tags":["dive","eye","protection","scuba","swimming","welding"],"emoji":"🥽","text":"","type":1,"order":4332,"group":7,"subgroup":65,"version":11},{"label":"lab coat","hexcode":"1F97C","tags":["clothes","coat","doctor","dr","experiment","jacket","lab","scientist","white"],"emoji":"🥼","text":"","type":1,"order":4333,"group":7,"subgroup":65,"version":11},{"label":"safety vest","hexcode":"1F9BA","tags":["emergency","safety","vest"],"emoji":"🦺","text":"","type":1,"order":4334,"group":7,"subgroup":65,"version":12},{"label":"necktie","hexcode":"1F454","tags":["clothing","employed","serious","shirt","tie"],"emoji":"👔","text":"","type":1,"order":4335,"group":7,"subgroup":65,"version":0.6},{"label":"t-shirt","hexcode":"1F455","tags":["blue","casual","clothes","clothing","collar","dressed","shirt","shopping","tshirt","weekend"],"emoji":"👕","text":"","type":1,"order":4336,"group":7,"subgroup":65,"version":0.6},{"label":"jeans","hexcode":"1F456","tags":["blue","casual","clothes","clothing","denim","dressed","pants","shopping","trousers","weekend"],"emoji":"👖","text":"","type":1,"order":4337,"group":7,"subgroup":65,"version":0.6},{"label":"scarf","hexcode":"1F9E3","tags":["bundle","cold","neck","up"],"emoji":"🧣","text":"","type":1,"order":4338,"group":7,"subgroup":65,"version":5},{"label":"gloves","hexcode":"1F9E4","tags":["hand"],"emoji":"🧤","text":"","type":1,"order":4339,"group":7,"subgroup":65,"version":5},{"label":"coat","hexcode":"1F9E5","tags":["brr","bundle","cold","jacket","up"],"emoji":"🧥","text":"","type":1,"order":4340,"group":7,"subgroup":65,"version":5},{"label":"socks","hexcode":"1F9E6","tags":["stocking"],"emoji":"🧦","text":"","type":1,"order":4341,"group":7,"subgroup":65,"version":5},{"label":"dress","hexcode":"1F457","tags":["clothes","clothing","dressed","fancy","shopping"],"emoji":"👗","text":"","type":1,"order":4342,"group":7,"subgroup":65,"version":0.6},{"label":"kimono","hexcode":"1F458","tags":["clothing","comfortable"],"emoji":"👘","text":"","type":1,"order":4343,"group":7,"subgroup":65,"version":0.6},{"label":"sari","hexcode":"1F97B","tags":["clothing","dress"],"emoji":"🥻","text":"","type":1,"order":4344,"group":7,"subgroup":65,"version":12},{"label":"one-piece swimsuit","hexcode":"1FA71","tags":["bathing","one-piece","suit","swimsuit"],"emoji":"🩱","text":"","type":1,"order":4345,"group":7,"subgroup":65,"version":12},{"label":"briefs","hexcode":"1FA72","tags":["bathing","one-piece","suit","swimsuit","underwear"],"emoji":"🩲","text":"","type":1,"order":4346,"group":7,"subgroup":65,"version":12},{"label":"shorts","hexcode":"1FA73","tags":["bathing","pants","suit","swimsuit","underwear"],"emoji":"🩳","text":"","type":1,"order":4347,"group":7,"subgroup":65,"version":12},{"label":"bikini","hexcode":"1F459","tags":["bathing","beach","clothing","pool","suit","swim"],"emoji":"👙","text":"","type":1,"order":4348,"group":7,"subgroup":65,"version":0.6},{"label":"woman’s clothes","hexcode":"1F45A","tags":["blouse","clothes","clothing","collar","dress","dressed","lady","shirt","shopping","woman","woman’s"],"emoji":"👚","text":"","type":1,"order":4349,"group":7,"subgroup":65,"version":0.6},{"label":"folding hand fan","hexcode":"1FAAD","tags":["clack","clap","cool","cooling","dance","fan","flirt","flutter","folding","hand","hot","shy"],"emoji":"🪭","text":"","type":1,"order":4350,"group":7,"subgroup":65,"version":15},{"label":"purse","hexcode":"1F45B","tags":["clothes","clothing","coin","dress","fancy","handbag","shopping"],"emoji":"👛","text":"","type":1,"order":4351,"group":7,"subgroup":65,"version":0.6},{"label":"handbag","hexcode":"1F45C","tags":["bag","clothes","clothing","dress","lady","purse","shopping"],"emoji":"👜","text":"","type":1,"order":4352,"group":7,"subgroup":65,"version":0.6},{"label":"clutch bag","hexcode":"1F45D","tags":["bag","clothes","clothing","clutch","dress","handbag","pouch","purse"],"emoji":"👝","text":"","type":1,"order":4353,"group":7,"subgroup":65,"version":0.6},{"label":"shopping bags","hexcode":"1F6CD","tags":["bag","bags","hotel","shopping"],"emoji":"🛍️","text":"🛍︎","type":0,"order":4355,"group":7,"subgroup":65,"version":0.7},{"label":"backpack","hexcode":"1F392","tags":["backpacking","bag","bookbag","education","rucksack","satchel","school"],"emoji":"🎒","text":"","type":1,"order":4356,"group":7,"subgroup":65,"version":0.6},{"label":"thong sandal","hexcode":"1FA74","tags":["beach","flip","flop","sandal","sandals","shoe","thong","thongs","zōri"],"emoji":"🩴","text":"","type":1,"order":4357,"group":7,"subgroup":65,"version":13},{"label":"man’s shoe","hexcode":"1F45E","tags":["brown","clothes","clothing","feet","foot","kick","man","man’s","shoe","shoes","shopping"],"emoji":"👞","text":"","type":1,"order":4358,"group":7,"subgroup":65,"version":0.6},{"label":"running shoe","hexcode":"1F45F","tags":["athletic","clothes","clothing","fast","kick","running","shoe","shoes","shopping","sneaker","tennis"],"emoji":"👟","text":"","type":1,"order":4359,"group":7,"subgroup":65,"version":0.6},{"label":"hiking boot","hexcode":"1F97E","tags":["backpacking","boot","brown","camping","hiking","outdoors","shoe"],"emoji":"🥾","text":"","type":1,"order":4360,"group":7,"subgroup":65,"version":11},{"label":"flat shoe","hexcode":"1F97F","tags":["ballet","comfy","flat","flats","shoe","slip-on","slipper"],"emoji":"🥿","text":"","type":1,"order":4361,"group":7,"subgroup":65,"version":11},{"label":"high-heeled shoe","hexcode":"1F460","tags":["clothes","clothing","dress","fashion","heel","heels","high-heeled","shoe","shoes","shopping","stiletto","woman"],"emoji":"👠","text":"","type":1,"order":4362,"group":7,"subgroup":65,"version":0.6},{"label":"woman’s sandal","hexcode":"1F461","tags":["clothing","sandal","shoe","woman","woman’s"],"emoji":"👡","text":"","type":1,"order":4363,"group":7,"subgroup":65,"version":0.6},{"label":"ballet shoes","hexcode":"1FA70","tags":["ballet","dance","shoes"],"emoji":"🩰","text":"","type":1,"order":4364,"group":7,"subgroup":65,"version":12},{"label":"woman’s boot","hexcode":"1F462","tags":["boot","clothes","clothing","dress","shoe","shoes","shopping","woman","woman’s"],"emoji":"👢","text":"","type":1,"order":4365,"group":7,"subgroup":65,"version":0.6},{"label":"hair pick","hexcode":"1FAAE","tags":["afro","comb","groom","hair","pick"],"emoji":"🪮","text":"","type":1,"order":4366,"group":7,"subgroup":65,"version":15},{"label":"crown","hexcode":"1F451","tags":["clothing","family","king","medieval","queen","royal","royalty","win"],"emoji":"👑","text":"","type":1,"order":4367,"group":7,"subgroup":65,"version":0.6},{"label":"woman’s hat","hexcode":"1F452","tags":["clothes","clothing","garden","hat","hats","party","woman","woman’s"],"emoji":"👒","text":"","type":1,"order":4368,"group":7,"subgroup":65,"version":0.6},{"label":"top hat","hexcode":"1F3A9","tags":["clothes","clothing","fancy","formal","hat","magic","top","tophat"],"emoji":"🎩","text":"","type":1,"order":4369,"group":7,"subgroup":65,"version":0.6},{"label":"graduation cap","hexcode":"1F393","tags":["cap","celebration","clothing","education","graduation","hat","scholar"],"emoji":"🎓️","text":"🎓︎","type":1,"order":4370,"group":7,"subgroup":65,"version":0.6},{"label":"billed cap","hexcode":"1F9E2","tags":["baseball","bent","billed","cap","dad","hat"],"emoji":"🧢","text":"","type":1,"order":4371,"group":7,"subgroup":65,"version":5},{"label":"military helmet","hexcode":"1FA96","tags":["army","helmet","military","soldier","war","warrior"],"emoji":"🪖","text":"","type":1,"order":4372,"group":7,"subgroup":65,"version":13},{"label":"rescue worker’s helmet","hexcode":"26D1","tags":["aid","cross","face","hat","helmet","rescue","worker’s"],"emoji":"⛑️","text":"⛑︎","type":0,"order":4374,"group":7,"subgroup":65,"version":0.7},{"label":"prayer beads","hexcode":"1F4FF","tags":["beads","clothing","necklace","prayer","religion"],"emoji":"📿","text":"","type":1,"order":4375,"group":7,"subgroup":65,"version":1},{"label":"lipstick","hexcode":"1F484","tags":["cosmetics","date","makeup"],"emoji":"💄","text":"","type":1,"order":4376,"group":7,"subgroup":65,"version":0.6},{"label":"ring","hexcode":"1F48D","tags":["diamond","engaged","engagement","married","romance","shiny","sparkling","wedding"],"emoji":"💍","text":"","type":1,"order":4377,"group":7,"subgroup":65,"version":0.6},{"label":"gem stone","hexcode":"1F48E","tags":["diamond","engagement","gem","jewel","money","romance","stone","wedding"],"emoji":"💎","text":"","type":1,"order":4378,"group":7,"subgroup":65,"version":0.6},{"label":"muted speaker","hexcode":"1F507","tags":["mute","muted","quiet","silent","sound","speaker"],"emoji":"🔇","text":"","type":1,"order":4379,"group":7,"subgroup":66,"version":1},{"label":"speaker low volume","hexcode":"1F508","tags":["low","soft","sound","speaker","volume"],"emoji":"🔈️","text":"🔈︎","type":1,"order":4380,"group":7,"subgroup":66,"version":0.7},{"label":"speaker medium volume","hexcode":"1F509","tags":["medium","sound","speaker","volume"],"emoji":"🔉","text":"","type":1,"order":4381,"group":7,"subgroup":66,"version":1},{"label":"speaker high volume","hexcode":"1F50A","tags":["high","loud","music","sound","speaker","volume"],"emoji":"🔊","text":"","type":1,"order":4382,"group":7,"subgroup":66,"version":0.6},{"label":"loudspeaker","hexcode":"1F4E2","tags":["address","communication","loud","public","sound"],"emoji":"📢","text":"","type":1,"order":4383,"group":7,"subgroup":66,"version":0.6},{"label":"megaphone","hexcode":"1F4E3","tags":["cheering","sound"],"emoji":"📣","text":"","type":1,"order":4384,"group":7,"subgroup":66,"version":0.6},{"label":"postal horn","hexcode":"1F4EF","tags":["horn","post","postal"],"emoji":"📯","text":"","type":1,"order":4385,"group":7,"subgroup":66,"version":1},{"label":"bell","hexcode":"1F514","tags":["break","church","sound"],"emoji":"🔔","text":"","type":1,"order":4386,"group":7,"subgroup":66,"version":0.6},{"label":"bell with slash","hexcode":"1F515","tags":["bell","forbidden","mute","no","not","prohibited","quiet","silent","slash","sound"],"emoji":"🔕","text":"","type":1,"order":4387,"group":7,"subgroup":66,"version":1},{"label":"musical score","hexcode":"1F3BC","tags":["music","musical","note","score"],"emoji":"🎼","text":"","type":1,"order":4388,"group":7,"subgroup":67,"version":0.6},{"label":"musical note","hexcode":"1F3B5","tags":["music","musical","note","sound"],"emoji":"🎵","text":"","type":1,"order":4389,"group":7,"subgroup":67,"version":0.6},{"label":"musical notes","hexcode":"1F3B6","tags":["music","musical","note","notes","sound"],"emoji":"🎶","text":"","type":1,"order":4390,"group":7,"subgroup":67,"version":0.6},{"label":"studio microphone","hexcode":"1F399","tags":["mic","microphone","music","studio"],"emoji":"🎙️","text":"🎙︎","type":0,"order":4392,"group":7,"subgroup":67,"version":0.7},{"label":"level slider","hexcode":"1F39A","tags":["level","music","slider"],"emoji":"🎚️","text":"🎚︎","type":0,"order":4394,"group":7,"subgroup":67,"version":0.7},{"label":"control knobs","hexcode":"1F39B","tags":["control","knobs","music"],"emoji":"🎛️","text":"🎛︎","type":0,"order":4396,"group":7,"subgroup":67,"version":0.7},{"label":"microphone","hexcode":"1F3A4","tags":["karaoke","mic","music","sing","sound"],"emoji":"🎤","text":"","type":1,"order":4397,"group":7,"subgroup":67,"version":0.6},{"label":"headphone","hexcode":"1F3A7","tags":["earbud","sound"],"emoji":"🎧️","text":"🎧︎","type":1,"order":4398,"group":7,"subgroup":67,"version":0.6},{"label":"radio","hexcode":"1F4FB","tags":["entertainment","tbt","video"],"emoji":"📻️","text":"📻︎","type":1,"order":4399,"group":7,"subgroup":67,"version":0.6},{"label":"saxophone","hexcode":"1F3B7","tags":["instrument","music","sax"],"emoji":"🎷","text":"","type":1,"order":4400,"group":7,"subgroup":68,"version":0.6},{"label":"trumpet","hexcode":"1F3BA","tags":["instrument","music"],"emoji":"🎺","text":"","type":1,"order":4401,"group":7,"subgroup":68,"version":0.6},{"label":"trombone","hexcode":"1FA8A","tags":["brass","instrument","jazz","music","sad","slide"],"emoji":"🪊","text":"","type":1,"order":4402,"group":7,"subgroup":68,"version":17},{"label":"accordion","hexcode":"1FA97","tags":["box","concertina","instrument","music","squeeze","squeezebox"],"emoji":"🪗","text":"","type":1,"order":4403,"group":7,"subgroup":68,"version":13},{"label":"guitar","hexcode":"1F3B8","tags":["instrument","music","strat"],"emoji":"🎸","text":"","type":1,"order":4404,"group":7,"subgroup":68,"version":0.6},{"label":"musical keyboard","hexcode":"1F3B9","tags":["instrument","keyboard","music","musical","piano"],"emoji":"🎹","text":"","type":1,"order":4405,"group":7,"subgroup":68,"version":0.6},{"label":"violin","hexcode":"1F3BB","tags":["instrument","music"],"emoji":"🎻","text":"","type":1,"order":4406,"group":7,"subgroup":68,"version":0.6},{"label":"banjo","hexcode":"1FA95","tags":["music","stringed"],"emoji":"🪕","text":"","type":1,"order":4407,"group":7,"subgroup":68,"version":12},{"label":"drum","hexcode":"1F941","tags":["drumsticks","music"],"emoji":"🥁","text":"","type":1,"order":4408,"group":7,"subgroup":68,"version":3},{"label":"long drum","hexcode":"1FA98","tags":["beat","conga","drum","instrument","long","rhythm"],"emoji":"🪘","text":"","type":1,"order":4409,"group":7,"subgroup":68,"version":13},{"label":"maracas","hexcode":"1FA87","tags":["cha","dance","instrument","music","party","percussion","rattle","shake","shaker"],"emoji":"🪇","text":"","type":1,"order":4410,"group":7,"subgroup":68,"version":15},{"label":"flute","hexcode":"1FA88","tags":["band","fife","flautist","instrument","marching","music","orchestra","piccolo","pipe","recorder","woodwind"],"emoji":"🪈","text":"","type":1,"order":4411,"group":7,"subgroup":68,"version":15},{"label":"harp","hexcode":"1FA89","tags":["cupid","instrument","love","music","orchestra"],"emoji":"🪉","text":"","type":1,"order":4412,"group":7,"subgroup":68,"version":16},{"label":"mobile phone","hexcode":"1F4F1","tags":["cell","communication","mobile","phone","telephone"],"emoji":"📱","text":"","type":1,"order":4413,"group":7,"subgroup":69,"version":0.6},{"label":"mobile phone with arrow","hexcode":"1F4F2","tags":["arrow","build","call","cell","communication","mobile","phone","receive","telephone"],"emoji":"📲","text":"","type":1,"order":4414,"group":7,"subgroup":69,"version":0.6},{"label":"telephone","hexcode":"260E","tags":["phone"],"emoji":"☎️","text":"☎︎","type":0,"order":4416,"group":7,"subgroup":69,"version":0.6},{"label":"telephone receiver","hexcode":"1F4DE","tags":["communication","phone","receiver","telephone","voip"],"emoji":"📞","text":"","type":1,"order":4417,"group":7,"subgroup":69,"version":0.6},{"label":"pager","hexcode":"1F4DF","tags":["communication"],"emoji":"📟️","text":"📟︎","type":1,"order":4418,"group":7,"subgroup":69,"version":0.6},{"label":"fax machine","hexcode":"1F4E0","tags":["communication","fax","machine"],"emoji":"📠","text":"","type":1,"order":4419,"group":7,"subgroup":69,"version":0.6},{"label":"battery","hexcode":"1F50B","tags":["battery"],"emoji":"🔋","text":"","type":1,"order":4420,"group":7,"subgroup":70,"version":0.6},{"label":"low battery","hexcode":"1FAAB","tags":["battery","drained","electronic","energy","low","power"],"emoji":"🪫","text":"","type":1,"order":4421,"group":7,"subgroup":70,"version":14},{"label":"electric plug","hexcode":"1F50C","tags":["electric","electricity","plug"],"emoji":"🔌","text":"","type":1,"order":4422,"group":7,"subgroup":70,"version":0.6},{"label":"laptop","hexcode":"1F4BB","tags":["computer","office","pc","personal"],"emoji":"💻️","text":"💻︎","type":1,"order":4423,"group":7,"subgroup":70,"version":0.6},{"label":"desktop computer","hexcode":"1F5A5","tags":["computer","desktop","monitor"],"emoji":"🖥️","text":"🖥︎","type":0,"order":4425,"group":7,"subgroup":70,"version":0.7},{"label":"printer","hexcode":"1F5A8","tags":["computer"],"emoji":"🖨️","text":"🖨︎","type":0,"order":4427,"group":7,"subgroup":70,"version":0.7},{"label":"keyboard","hexcode":"2328","tags":["computer"],"emoji":"⌨️","text":"⌨︎","type":0,"order":4429,"group":7,"subgroup":70,"version":1},{"label":"computer mouse","hexcode":"1F5B1","tags":["computer","mouse"],"emoji":"🖱️","text":"🖱︎","type":0,"order":4431,"group":7,"subgroup":70,"version":0.7},{"label":"trackball","hexcode":"1F5B2","tags":["computer"],"emoji":"🖲️","text":"🖲︎","type":0,"order":4433,"group":7,"subgroup":70,"version":0.7},{"label":"computer disk","hexcode":"1F4BD","tags":["computer","disk","minidisk","optical"],"emoji":"💽","text":"","type":1,"order":4434,"group":7,"subgroup":70,"version":0.6},{"label":"floppy disk","hexcode":"1F4BE","tags":["computer","disk","floppy"],"emoji":"💾","text":"","type":1,"order":4435,"group":7,"subgroup":70,"version":0.6},{"label":"optical disk","hexcode":"1F4BF","tags":["blu-ray","cd","computer","disk","dvd","optical"],"emoji":"💿️","text":"💿︎","type":1,"order":4436,"group":7,"subgroup":70,"version":0.6},{"label":"dvd","hexcode":"1F4C0","tags":["blu-ray","cd","computer","disk","optical"],"emoji":"📀","text":"","type":1,"order":4437,"group":7,"subgroup":70,"version":0.6},{"label":"abacus","hexcode":"1F9EE","tags":["calculation","calculator"],"emoji":"🧮","text":"","type":1,"order":4438,"group":7,"subgroup":70,"version":11},{"label":"movie camera","hexcode":"1F3A5","tags":["bollywood","camera","cinema","film","hollywood","movie","record"],"emoji":"🎥","text":"","type":1,"order":4439,"group":7,"subgroup":71,"version":0.6},{"label":"film frames","hexcode":"1F39E","tags":["cinema","film","frames","movie"],"emoji":"🎞️","text":"🎞︎","type":0,"order":4441,"group":7,"subgroup":71,"version":0.7},{"label":"film projector","hexcode":"1F4FD","tags":["cinema","film","movie","projector","video"],"emoji":"📽️","text":"📽︎","type":0,"order":4443,"group":7,"subgroup":71,"version":0.7},{"label":"clapper board","hexcode":"1F3AC","tags":["action","board","clapper","movie"],"emoji":"🎬️","text":"🎬︎","type":1,"order":4444,"group":7,"subgroup":71,"version":0.6},{"label":"television","hexcode":"1F4FA","tags":["tv","video"],"emoji":"📺️","text":"📺︎","type":1,"order":4445,"group":7,"subgroup":71,"version":0.6},{"label":"camera","hexcode":"1F4F7","tags":["photo","selfie","snap","tbt","trip","video"],"emoji":"📷️","text":"📷︎","type":1,"order":4446,"group":7,"subgroup":71,"version":0.6},{"label":"camera with flash","hexcode":"1F4F8","tags":["camera","flash","video"],"emoji":"📸","text":"","type":1,"order":4447,"group":7,"subgroup":71,"version":1},{"label":"video camera","hexcode":"1F4F9","tags":["camcorder","camera","tbt","video"],"emoji":"📹️","text":"📹︎","type":1,"order":4448,"group":7,"subgroup":71,"version":0.6},{"label":"videocassette","hexcode":"1F4FC","tags":["old","school","tape","vcr","vhs","video"],"emoji":"📼","text":"","type":1,"order":4449,"group":7,"subgroup":71,"version":0.6},{"label":"magnifying glass tilted left","hexcode":"1F50D","tags":["glass","lab","left","left-pointing","magnifying","science","search","tilted","tool"],"emoji":"🔍️","text":"🔍︎","type":1,"order":4450,"group":7,"subgroup":71,"version":0.6},{"label":"magnifying glass tilted right","hexcode":"1F50E","tags":["contact","glass","lab","magnifying","right","right-pointing","science","search","tilted","tool"],"emoji":"🔎","text":"","type":1,"order":4451,"group":7,"subgroup":71,"version":0.6},{"label":"candle","hexcode":"1F56F","tags":["light"],"emoji":"🕯️","text":"🕯︎","type":0,"order":4453,"group":7,"subgroup":71,"version":0.7},{"label":"light bulb","hexcode":"1F4A1","tags":["bulb","comic","electric","idea","light"],"emoji":"💡","text":"","type":1,"order":4454,"group":7,"subgroup":71,"version":0.6},{"label":"flashlight","hexcode":"1F526","tags":["electric","light","tool","torch"],"emoji":"🔦","text":"","type":1,"order":4455,"group":7,"subgroup":71,"version":0.6},{"label":"red paper lantern","hexcode":"1F3EE","tags":["bar","lantern","light","paper","red","restaurant"],"emoji":"🏮","text":"","type":1,"order":4456,"group":7,"subgroup":71,"version":0.6},{"label":"diya lamp","hexcode":"1FA94","tags":["diya","lamp","light","oil"],"emoji":"🪔","text":"","type":1,"order":4457,"group":7,"subgroup":71,"version":12},{"label":"notebook with decorative cover","hexcode":"1F4D4","tags":["book","cover","decorated","decorative","education","notebook","school","writing"],"emoji":"📔","text":"","type":1,"order":4458,"group":7,"subgroup":72,"version":0.6},{"label":"closed book","hexcode":"1F4D5","tags":["book","closed","education"],"emoji":"📕","text":"","type":1,"order":4459,"group":7,"subgroup":72,"version":0.6},{"label":"open book","hexcode":"1F4D6","tags":["book","education","fantasy","knowledge","library","novels","open","reading"],"emoji":"📖","text":"","type":1,"order":4460,"group":7,"subgroup":72,"version":0.6},{"label":"green book","hexcode":"1F4D7","tags":["book","education","fantasy","green","library","reading"],"emoji":"📗","text":"","type":1,"order":4461,"group":7,"subgroup":72,"version":0.6},{"label":"blue book","hexcode":"1F4D8","tags":["blue","book","education","fantasy","library","reading"],"emoji":"📘","text":"","type":1,"order":4462,"group":7,"subgroup":72,"version":0.6},{"label":"orange book","hexcode":"1F4D9","tags":["book","education","fantasy","library","orange","reading"],"emoji":"📙","text":"","type":1,"order":4463,"group":7,"subgroup":72,"version":0.6},{"label":"books","hexcode":"1F4DA","tags":["book","education","fantasy","knowledge","library","novels","reading","school","study"],"emoji":"📚️","text":"📚︎","type":1,"order":4464,"group":7,"subgroup":72,"version":0.6},{"label":"notebook","hexcode":"1F4D3","tags":["notebook"],"emoji":"📓","text":"","type":1,"order":4465,"group":7,"subgroup":72,"version":0.6},{"label":"ledger","hexcode":"1F4D2","tags":["notebook"],"emoji":"📒","text":"","type":1,"order":4466,"group":7,"subgroup":72,"version":0.6},{"label":"page with curl","hexcode":"1F4C3","tags":["curl","document","page","paper"],"emoji":"📃","text":"","type":1,"order":4467,"group":7,"subgroup":72,"version":0.6},{"label":"scroll","hexcode":"1F4DC","tags":["paper"],"emoji":"📜","text":"","type":1,"order":4468,"group":7,"subgroup":72,"version":0.6},{"label":"page facing up","hexcode":"1F4C4","tags":["document","facing","page","paper","up"],"emoji":"📄","text":"","type":1,"order":4469,"group":7,"subgroup":72,"version":0.6},{"label":"newspaper","hexcode":"1F4F0","tags":["communication","news","paper"],"emoji":"📰","text":"","type":1,"order":4470,"group":7,"subgroup":72,"version":0.6},{"label":"rolled-up newspaper","hexcode":"1F5DE","tags":["news","newspaper","paper","rolled","rolled-up"],"emoji":"🗞️","text":"🗞︎","type":0,"order":4472,"group":7,"subgroup":72,"version":0.7},{"label":"bookmark tabs","hexcode":"1F4D1","tags":["bookmark","mark","marker","tabs"],"emoji":"📑","text":"","type":1,"order":4473,"group":7,"subgroup":72,"version":0.6},{"label":"bookmark","hexcode":"1F516","tags":["mark"],"emoji":"🔖","text":"","type":1,"order":4474,"group":7,"subgroup":72,"version":0.6},{"label":"label","hexcode":"1F3F7","tags":["tag"],"emoji":"🏷️","text":"🏷︎","type":0,"order":4476,"group":7,"subgroup":72,"version":0.7},{"label":"coin","hexcode":"1FA99","tags":["dollar","euro","gold","metal","money","rich","silver","treasure"],"emoji":"🪙","text":"","type":1,"order":4477,"group":7,"subgroup":73,"version":13},{"label":"money bag","hexcode":"1F4B0","tags":["bag","bank","bet","billion","cash","cost","dollar","gold","million","money","moneybag","paid","paying","pot","rich","win"],"emoji":"💰️","text":"💰︎","type":1,"order":4478,"group":7,"subgroup":73,"version":0.6},{"label":"treasure chest","hexcode":"1FA8E","tags":["gem","gold","jewels","loot","money","prize","silver","valuables","wealth"],"emoji":"🪎","text":"","type":1,"order":4479,"group":7,"subgroup":73,"version":17},{"label":"yen banknote","hexcode":"1F4B4","tags":["bank","banknote","bill","currency","money","note","yen"],"emoji":"💴","text":"","type":1,"order":4480,"group":7,"subgroup":73,"version":0.6},{"label":"dollar banknote","hexcode":"1F4B5","tags":["bank","banknote","bill","currency","dollar","money","note"],"emoji":"💵","text":"","type":1,"order":4481,"group":7,"subgroup":73,"version":0.6},{"label":"euro banknote","hexcode":"1F4B6","tags":["100","bank","banknote","bill","currency","euro","money","note","rich"],"emoji":"💶","text":"","type":1,"order":4482,"group":7,"subgroup":73,"version":1},{"label":"pound banknote","hexcode":"1F4B7","tags":["bank","banknote","bill","billion","cash","currency","money","note","pound","pounds"],"emoji":"💷","text":"","type":1,"order":4483,"group":7,"subgroup":73,"version":1},{"label":"money with wings","hexcode":"1F4B8","tags":["bank","banknote","bill","billion","cash","dollar","fly","million","money","note","pay","wings"],"emoji":"💸","text":"","type":1,"order":4484,"group":7,"subgroup":73,"version":0.6},{"label":"credit card","hexcode":"1F4B3","tags":["bank","card","cash","charge","credit","money","pay"],"emoji":"💳️","text":"💳︎","type":1,"order":4485,"group":7,"subgroup":73,"version":0.6},{"label":"receipt","hexcode":"1F9FE","tags":["accounting","bookkeeping","evidence","invoice","proof"],"emoji":"🧾","text":"","type":1,"order":4486,"group":7,"subgroup":73,"version":11},{"label":"chart increasing with yen","hexcode":"1F4B9","tags":["bank","chart","currency","graph","growth","increasing","market","money","rise","trend","upward","yen"],"emoji":"💹","text":"","type":1,"order":4487,"group":7,"subgroup":73,"version":0.6},{"label":"envelope","hexcode":"2709","tags":["e-mail","email","letter"],"emoji":"✉️","text":"✉︎","type":0,"order":4489,"group":7,"subgroup":74,"version":0.6},{"label":"e-mail","hexcode":"1F4E7","tags":["email","letter","mail"],"emoji":"📧","text":"","type":1,"order":4490,"group":7,"subgroup":74,"version":0.6},{"label":"incoming envelope","hexcode":"1F4E8","tags":["delivering","e-mail","email","envelope","incoming","letter","mail","receive","sent"],"emoji":"📨","text":"","type":1,"order":4491,"group":7,"subgroup":74,"version":0.6},{"label":"envelope with arrow","hexcode":"1F4E9","tags":["arrow","communication","down","e-mail","email","envelope","letter","mail","outgoing","send","sent"],"emoji":"📩","text":"","type":1,"order":4492,"group":7,"subgroup":74,"version":0.6},{"label":"outbox tray","hexcode":"1F4E4","tags":["box","email","letter","mail","outbox","sent","tray"],"emoji":"📤️","text":"📤︎","type":1,"order":4493,"group":7,"subgroup":74,"version":0.6},{"label":"inbox tray","hexcode":"1F4E5","tags":["box","email","inbox","letter","mail","receive","tray","zero"],"emoji":"📥️","text":"📥︎","type":1,"order":4494,"group":7,"subgroup":74,"version":0.6},{"label":"package","hexcode":"1F4E6","tags":["box","communication","delivery","parcel","shipping"],"emoji":"📦️","text":"📦︎","type":1,"order":4495,"group":7,"subgroup":74,"version":0.6},{"label":"closed mailbox with raised flag","hexcode":"1F4EB","tags":["closed","communication","flag","mail","mailbox","postbox","raised"],"emoji":"📫️","text":"📫︎","type":1,"order":4496,"group":7,"subgroup":74,"version":0.6},{"label":"closed mailbox with lowered flag","hexcode":"1F4EA","tags":["closed","flag","lowered","mail","mailbox","postbox"],"emoji":"📪️","text":"📪︎","type":1,"order":4497,"group":7,"subgroup":74,"version":0.6},{"label":"open mailbox with raised flag","hexcode":"1F4EC","tags":["flag","mail","mailbox","open","postbox","raised"],"emoji":"📬️","text":"📬︎","type":1,"order":4498,"group":7,"subgroup":74,"version":0.7},{"label":"open mailbox with lowered flag","hexcode":"1F4ED","tags":["flag","lowered","mail","mailbox","open","postbox"],"emoji":"📭️","text":"📭︎","type":1,"order":4499,"group":7,"subgroup":74,"version":0.7},{"label":"postbox","hexcode":"1F4EE","tags":["mail","mailbox"],"emoji":"📮","text":"","type":1,"order":4500,"group":7,"subgroup":74,"version":0.6},{"label":"ballot box with ballot","hexcode":"1F5F3","tags":["ballot","box"],"emoji":"🗳️","text":"🗳︎","type":0,"order":4502,"group":7,"subgroup":74,"version":0.7},{"label":"pencil","hexcode":"270F","tags":["pencil"],"emoji":"✏️","text":"✏︎","type":0,"order":4504,"group":7,"subgroup":75,"version":0.6},{"label":"black nib","hexcode":"2712","tags":["black","nib","pen"],"emoji":"✒️","text":"✒︎","type":0,"order":4506,"group":7,"subgroup":75,"version":0.6},{"label":"fountain pen","hexcode":"1F58B","tags":["fountain","pen"],"emoji":"🖋️","text":"🖋︎","type":0,"order":4508,"group":7,"subgroup":75,"version":0.7},{"label":"pen","hexcode":"1F58A","tags":["ballpoint"],"emoji":"🖊️","text":"🖊︎","type":0,"order":4510,"group":7,"subgroup":75,"version":0.7},{"label":"paintbrush","hexcode":"1F58C","tags":["painting"],"emoji":"🖌️","text":"🖌︎","type":0,"order":4512,"group":7,"subgroup":75,"version":0.7},{"label":"crayon","hexcode":"1F58D","tags":["crayon"],"emoji":"🖍️","text":"🖍︎","type":0,"order":4514,"group":7,"subgroup":75,"version":0.7},{"label":"memo","hexcode":"1F4DD","tags":["communication","media","notes","pencil"],"emoji":"📝","text":"","type":1,"order":4515,"group":7,"subgroup":75,"version":0.6},{"label":"briefcase","hexcode":"1F4BC","tags":["office"],"emoji":"💼","text":"","type":1,"order":4516,"group":7,"subgroup":76,"version":0.6},{"label":"file folder","hexcode":"1F4C1","tags":["file","folder"],"emoji":"📁","text":"","type":1,"order":4517,"group":7,"subgroup":76,"version":0.6},{"label":"open file folder","hexcode":"1F4C2","tags":["file","folder","open"],"emoji":"📂","text":"","type":1,"order":4518,"group":7,"subgroup":76,"version":0.6},{"label":"card index dividers","hexcode":"1F5C2","tags":["card","dividers","index"],"emoji":"🗂️","text":"🗂︎","type":0,"order":4520,"group":7,"subgroup":76,"version":0.7},{"label":"calendar","hexcode":"1F4C5","tags":["date"],"emoji":"📅","text":"","type":1,"order":4521,"group":7,"subgroup":76,"version":0.6},{"label":"tear-off calendar","hexcode":"1F4C6","tags":["calendar","tear-off"],"emoji":"📆","text":"","type":1,"order":4522,"group":7,"subgroup":76,"version":0.6},{"label":"spiral notepad","hexcode":"1F5D2","tags":["note","notepad","pad","spiral"],"emoji":"🗒️","text":"🗒︎","type":0,"order":4524,"group":7,"subgroup":76,"version":0.7},{"label":"spiral calendar","hexcode":"1F5D3","tags":["calendar","pad","spiral"],"emoji":"🗓️","text":"🗓︎","type":0,"order":4526,"group":7,"subgroup":76,"version":0.7},{"label":"card index","hexcode":"1F4C7","tags":["card","index","old","rolodex","school"],"emoji":"📇","text":"","type":1,"order":4527,"group":7,"subgroup":76,"version":0.6},{"label":"chart increasing","hexcode":"1F4C8","tags":["chart","data","graph","growth","increasing","right","trend","up","upward"],"emoji":"📈","text":"","type":1,"order":4528,"group":7,"subgroup":76,"version":0.6},{"label":"chart decreasing","hexcode":"1F4C9","tags":["chart","data","decreasing","down","downward","graph","negative","trend"],"emoji":"📉","text":"","type":1,"order":4529,"group":7,"subgroup":76,"version":0.6},{"label":"bar chart","hexcode":"1F4CA","tags":["bar","chart","data","graph"],"emoji":"📊","text":"","type":1,"order":4530,"group":7,"subgroup":76,"version":0.6},{"label":"clipboard","hexcode":"1F4CB","tags":["do","list","notes"],"emoji":"📋️","text":"📋︎","type":1,"order":4531,"group":7,"subgroup":76,"version":0.6},{"label":"pushpin","hexcode":"1F4CC","tags":["collage","pin"],"emoji":"📌","text":"","type":1,"order":4532,"group":7,"subgroup":76,"version":0.6},{"label":"round pushpin","hexcode":"1F4CD","tags":["location","map","pin","pushpin","round"],"emoji":"📍","text":"","type":1,"order":4533,"group":7,"subgroup":76,"version":0.6},{"label":"paperclip","hexcode":"1F4CE","tags":["paperclip"],"emoji":"📎","text":"","type":1,"order":4534,"group":7,"subgroup":76,"version":0.6},{"label":"linked paperclips","hexcode":"1F587","tags":["link","linked","paperclip","paperclips"],"emoji":"🖇️","text":"🖇︎","type":0,"order":4536,"group":7,"subgroup":76,"version":0.7},{"label":"straight ruler","hexcode":"1F4CF","tags":["angle","edge","math","ruler","straight","straightedge"],"emoji":"📏","text":"","type":1,"order":4537,"group":7,"subgroup":76,"version":0.6},{"label":"triangular ruler","hexcode":"1F4D0","tags":["angle","math","rule","ruler","set","slide","triangle","triangular"],"emoji":"📐","text":"","type":1,"order":4538,"group":7,"subgroup":76,"version":0.6},{"label":"scissors","hexcode":"2702","tags":["cut","cutting","paper","tool"],"emoji":"✂️","text":"✂︎","type":0,"order":4540,"group":7,"subgroup":76,"version":0.6},{"label":"card file box","hexcode":"1F5C3","tags":["box","card","file"],"emoji":"🗃️","text":"🗃︎","type":0,"order":4542,"group":7,"subgroup":76,"version":0.7},{"label":"file cabinet","hexcode":"1F5C4","tags":["cabinet","file","filing","paper"],"emoji":"🗄️","text":"🗄︎","type":0,"order":4544,"group":7,"subgroup":76,"version":0.7},{"label":"wastebasket","hexcode":"1F5D1","tags":["can","garbage","trash","waste"],"emoji":"🗑️","text":"🗑︎","type":0,"order":4546,"group":7,"subgroup":76,"version":0.7},{"label":"locked","hexcode":"1F512","tags":["closed","lock","private"],"emoji":"🔒️","text":"🔒︎","type":1,"order":4547,"group":7,"subgroup":77,"version":0.6},{"label":"unlocked","hexcode":"1F513","tags":["cracked","lock","open","unlock"],"emoji":"🔓️","text":"🔓︎","type":1,"order":4548,"group":7,"subgroup":77,"version":0.6},{"label":"locked with pen","hexcode":"1F50F","tags":["ink","lock","locked","nib","pen","privacy"],"emoji":"🔏","text":"","type":1,"order":4549,"group":7,"subgroup":77,"version":0.6},{"label":"locked with key","hexcode":"1F510","tags":["bike","closed","key","lock","locked","secure"],"emoji":"🔐","text":"","type":1,"order":4550,"group":7,"subgroup":77,"version":0.6},{"label":"key","hexcode":"1F511","tags":["keys","lock","major","password","unlock"],"emoji":"🔑","text":"","type":1,"order":4551,"group":7,"subgroup":77,"version":0.6},{"label":"old key","hexcode":"1F5DD","tags":["clue","key","lock","old"],"emoji":"🗝️","text":"🗝︎","type":0,"order":4553,"group":7,"subgroup":77,"version":0.7},{"label":"hammer","hexcode":"1F528","tags":["home","improvement","repairs","tool"],"emoji":"🔨","text":"","type":1,"order":4554,"group":7,"subgroup":78,"version":0.6},{"label":"axe","hexcode":"1FA93","tags":["ax","chop","hatchet","split","wood"],"emoji":"🪓","text":"","type":1,"order":4555,"group":7,"subgroup":78,"version":12},{"label":"pick","hexcode":"26CF","tags":["hammer","mining","tool"],"emoji":"⛏️","text":"⛏︎","type":0,"order":4557,"group":7,"subgroup":78,"version":0.7},{"label":"hammer and pick","hexcode":"2692","tags":["hammer","pick","tool"],"emoji":"⚒️","text":"⚒︎","type":0,"order":4559,"group":7,"subgroup":78,"version":1},{"label":"hammer and wrench","hexcode":"1F6E0","tags":["hammer","spanner","tool","wrench"],"emoji":"🛠️","text":"🛠︎","type":0,"order":4561,"group":7,"subgroup":78,"version":0.7},{"label":"dagger","hexcode":"1F5E1","tags":["knife","weapon"],"emoji":"🗡️","text":"🗡︎","type":0,"order":4563,"group":7,"subgroup":78,"version":0.7},{"label":"crossed swords","hexcode":"2694","tags":["crossed","swords","weapon"],"emoji":"⚔️","text":"⚔︎","type":0,"order":4565,"group":7,"subgroup":78,"version":1},{"label":"bomb","hexcode":"1F4A3","tags":["boom","comic","dangerous","explosion","hot"],"emoji":"💣️","text":"💣︎","type":1,"order":4566,"group":7,"subgroup":78,"version":0.6},{"label":"boomerang","hexcode":"1FA83","tags":["rebound","repercussion","weapon"],"emoji":"🪃","text":"","type":1,"order":4567,"group":7,"subgroup":78,"version":13},{"label":"bow and arrow","hexcode":"1F3F9","tags":["archer","archery","arrow","bow","sagittarius","tool","weapon","zodiac"],"emoji":"🏹","text":"","type":1,"order":4568,"group":7,"subgroup":78,"version":1},{"label":"shield","hexcode":"1F6E1","tags":["weapon"],"emoji":"🛡️","text":"🛡︎","type":0,"order":4570,"group":7,"subgroup":78,"version":0.7},{"label":"carpentry saw","hexcode":"1FA9A","tags":["carpenter","carpentry","cut","lumber","saw","tool","trim"],"emoji":"🪚","text":"","type":1,"order":4571,"group":7,"subgroup":78,"version":13},{"label":"wrench","hexcode":"1F527","tags":["home","improvement","spanner","tool"],"emoji":"🔧","text":"","type":1,"order":4572,"group":7,"subgroup":78,"version":0.6},{"label":"screwdriver","hexcode":"1FA9B","tags":["flathead","handy","screw","tool"],"emoji":"🪛","text":"","type":1,"order":4573,"group":7,"subgroup":78,"version":13},{"label":"nut and bolt","hexcode":"1F529","tags":["bolt","home","improvement","nut","tool"],"emoji":"🔩","text":"","type":1,"order":4574,"group":7,"subgroup":78,"version":0.6},{"label":"gear","hexcode":"2699","tags":["cog","cogwheel","tool"],"emoji":"⚙️","text":"⚙︎","type":0,"order":4576,"group":7,"subgroup":78,"version":1},{"label":"clamp","hexcode":"1F5DC","tags":["compress","tool","vice"],"emoji":"🗜️","text":"🗜︎","type":0,"order":4578,"group":7,"subgroup":78,"version":0.7},{"label":"balance scale","hexcode":"2696","tags":["balance","justice","libra","scale","scales","tool","weight","zodiac"],"emoji":"⚖️","text":"⚖︎","type":0,"order":4580,"group":7,"subgroup":78,"version":1},{"label":"white cane","hexcode":"1F9AF","tags":["accessibility","blind","cane","probing","white"],"emoji":"🦯","text":"","type":1,"order":4581,"group":7,"subgroup":78,"version":12},{"label":"link","hexcode":"1F517","tags":["links"],"emoji":"🔗","text":"","type":1,"order":4582,"group":7,"subgroup":78,"version":0.6},{"label":"broken chain","hexcode":"26D3-FE0F-200D-1F4A5","tags":["break","breaking","broken","chain","cuffs","freedom"],"emoji":"⛓️‍💥","text":"","type":1,"order":4583,"group":7,"subgroup":78,"version":15.1},{"label":"chains","hexcode":"26D3","tags":["chain"],"emoji":"⛓️","text":"⛓︎","type":0,"order":4586,"group":7,"subgroup":78,"version":0.7},{"label":"hook","hexcode":"1FA9D","tags":["catch","crook","curve","ensnare","point","selling"],"emoji":"🪝","text":"","type":1,"order":4587,"group":7,"subgroup":78,"version":13},{"label":"toolbox","hexcode":"1F9F0","tags":["box","chest","mechanic","red","tool"],"emoji":"🧰","text":"","type":1,"order":4588,"group":7,"subgroup":78,"version":11},{"label":"magnet","hexcode":"1F9F2","tags":["attraction","horseshoe","magnetic","negative","positive","shape","u"],"emoji":"🧲","text":"","type":1,"order":4589,"group":7,"subgroup":78,"version":11},{"label":"ladder","hexcode":"1FA9C","tags":["climb","rung","step"],"emoji":"🪜","text":"","type":1,"order":4590,"group":7,"subgroup":78,"version":13},{"label":"shovel","hexcode":"1FA8F","tags":["bury","dig","garden","hole","plant","scoop","snow","spade"],"emoji":"🪏","text":"","type":1,"order":4591,"group":7,"subgroup":78,"version":16},{"label":"alembic","hexcode":"2697","tags":["chemistry","tool"],"emoji":"⚗️","text":"⚗︎","type":0,"order":4593,"group":7,"subgroup":79,"version":1},{"label":"test tube","hexcode":"1F9EA","tags":["chemist","chemistry","experiment","lab","science","test","tube"],"emoji":"🧪","text":"","type":1,"order":4594,"group":7,"subgroup":79,"version":11},{"label":"petri dish","hexcode":"1F9EB","tags":["bacteria","biologist","biology","culture","dish","lab","petri"],"emoji":"🧫","text":"","type":1,"order":4595,"group":7,"subgroup":79,"version":11},{"label":"dna","hexcode":"1F9EC","tags":["biologist","evolution","gene","genetics","life"],"emoji":"🧬","text":"","type":1,"order":4596,"group":7,"subgroup":79,"version":11},{"label":"microscope","hexcode":"1F52C","tags":["experiment","lab","science","tool"],"emoji":"🔬","text":"","type":1,"order":4597,"group":7,"subgroup":79,"version":1},{"label":"telescope","hexcode":"1F52D","tags":["contact","extraterrestrial","science","tool"],"emoji":"🔭","text":"","type":1,"order":4598,"group":7,"subgroup":79,"version":1},{"label":"satellite antenna","hexcode":"1F4E1","tags":["aliens","antenna","contact","dish","satellite","science"],"emoji":"📡","text":"","type":1,"order":4599,"group":7,"subgroup":79,"version":0.6},{"label":"syringe","hexcode":"1F489","tags":["doctor","flu","medicine","needle","shot","sick","tool","vaccination"],"emoji":"💉","text":"","type":1,"order":4600,"group":7,"subgroup":80,"version":0.6},{"label":"drop of blood","hexcode":"1FA78","tags":["bleed","blood","donation","drop","injury","medicine","menstruation"],"emoji":"🩸","text":"","type":1,"order":4601,"group":7,"subgroup":80,"version":12},{"label":"pill","hexcode":"1F48A","tags":["doctor","drugs","medicated","medicine","pills","sick","vitamin"],"emoji":"💊","text":"","type":1,"order":4602,"group":7,"subgroup":80,"version":0.6},{"label":"adhesive bandage","hexcode":"1FA79","tags":["adhesive","bandage"],"emoji":"🩹","text":"","type":1,"order":4603,"group":7,"subgroup":80,"version":12},{"label":"crutch","hexcode":"1FA7C","tags":["aid","cane","disability","help","hurt","injured","mobility","stick"],"emoji":"🩼","text":"","type":1,"order":4604,"group":7,"subgroup":80,"version":14},{"label":"stethoscope","hexcode":"1FA7A","tags":["doctor","heart","medicine"],"emoji":"🩺","text":"","type":1,"order":4605,"group":7,"subgroup":80,"version":12},{"label":"x-ray","hexcode":"1FA7B","tags":["bones","doctor","medical","skeleton","skull","xray"],"emoji":"🩻","text":"","type":1,"order":4606,"group":7,"subgroup":80,"version":14},{"label":"door","hexcode":"1F6AA","tags":["back","closet","front"],"emoji":"🚪","text":"","type":1,"order":4607,"group":7,"subgroup":81,"version":0.6},{"label":"elevator","hexcode":"1F6D7","tags":["accessibility","hoist","lift"],"emoji":"🛗","text":"","type":1,"order":4608,"group":7,"subgroup":81,"version":13},{"label":"mirror","hexcode":"1FA9E","tags":["makeup","reflection","reflector","speculum"],"emoji":"🪞","text":"","type":1,"order":4609,"group":7,"subgroup":81,"version":13},{"label":"window","hexcode":"1FA9F","tags":["air","frame","fresh","opening","transparent","view"],"emoji":"🪟","text":"","type":1,"order":4610,"group":7,"subgroup":81,"version":13},{"label":"bed","hexcode":"1F6CF","tags":["hotel","sleep"],"emoji":"🛏️","text":"🛏︎","type":0,"order":4612,"group":7,"subgroup":81,"version":0.7},{"label":"couch and lamp","hexcode":"1F6CB","tags":["couch","hotel","lamp"],"emoji":"🛋️","text":"🛋︎","type":0,"order":4614,"group":7,"subgroup":81,"version":0.7},{"label":"chair","hexcode":"1FA91","tags":["seat","sit"],"emoji":"🪑","text":"","type":1,"order":4615,"group":7,"subgroup":81,"version":12},{"label":"toilet","hexcode":"1F6BD","tags":["bathroom"],"emoji":"🚽","text":"","type":1,"order":4616,"group":7,"subgroup":81,"version":0.6},{"label":"plunger","hexcode":"1FAA0","tags":["cup","force","plumber","poop","suction","toilet"],"emoji":"🪠","text":"","type":1,"order":4617,"group":7,"subgroup":81,"version":13},{"label":"shower","hexcode":"1F6BF","tags":["water"],"emoji":"🚿","text":"","type":1,"order":4618,"group":7,"subgroup":81,"version":1},{"label":"bathtub","hexcode":"1F6C1","tags":["bath"],"emoji":"🛁","text":"","type":1,"order":4619,"group":7,"subgroup":81,"version":1},{"label":"mouse trap","hexcode":"1FAA4","tags":["bait","cheese","lure","mouse","mousetrap","snare","trap"],"emoji":"🪤","text":"","type":1,"order":4620,"group":7,"subgroup":81,"version":13},{"label":"razor","hexcode":"1FA92","tags":["sharp","shave"],"emoji":"🪒","text":"","type":1,"order":4621,"group":7,"subgroup":81,"version":12},{"label":"lotion bottle","hexcode":"1F9F4","tags":["bottle","lotion","moisturizer","shampoo","sunscreen"],"emoji":"🧴","text":"","type":1,"order":4622,"group":7,"subgroup":81,"version":11},{"label":"safety pin","hexcode":"1F9F7","tags":["diaper","pin","punk","rock","safety"],"emoji":"🧷","text":"","type":1,"order":4623,"group":7,"subgroup":81,"version":11},{"label":"broom","hexcode":"1F9F9","tags":["cleaning","sweeping","witch"],"emoji":"🧹","text":"","type":1,"order":4624,"group":7,"subgroup":81,"version":11},{"label":"basket","hexcode":"1F9FA","tags":["farming","laundry","picnic"],"emoji":"🧺","text":"","type":1,"order":4625,"group":7,"subgroup":81,"version":11},{"label":"roll of paper","hexcode":"1F9FB","tags":["paper","roll","toilet","towels"],"emoji":"🧻","text":"","type":1,"order":4626,"group":7,"subgroup":81,"version":11},{"label":"bucket","hexcode":"1FAA3","tags":["cask","pail","vat"],"emoji":"🪣","text":"","type":1,"order":4627,"group":7,"subgroup":81,"version":13},{"label":"soap","hexcode":"1F9FC","tags":["bar","bathing","clean","cleaning","lather","soapdish"],"emoji":"🧼","text":"","type":1,"order":4628,"group":7,"subgroup":81,"version":11},{"label":"bubbles","hexcode":"1FAE7","tags":["bubble","burp","clean","floating","pearl","soap","underwater"],"emoji":"🫧","text":"","type":1,"order":4629,"group":7,"subgroup":81,"version":14},{"label":"toothbrush","hexcode":"1FAA5","tags":["bathroom","brush","clean","dental","hygiene","teeth","toiletry"],"emoji":"🪥","text":"","type":1,"order":4630,"group":7,"subgroup":81,"version":13},{"label":"sponge","hexcode":"1F9FD","tags":["absorbing","cleaning","porous","soak"],"emoji":"🧽","text":"","type":1,"order":4631,"group":7,"subgroup":81,"version":11},{"label":"fire extinguisher","hexcode":"1F9EF","tags":["extinguish","extinguisher","fire","quench"],"emoji":"🧯","text":"","type":1,"order":4632,"group":7,"subgroup":81,"version":11},{"label":"shopping cart","hexcode":"1F6D2","tags":["cart","shopping","trolley"],"emoji":"🛒","text":"","type":1,"order":4633,"group":7,"subgroup":81,"version":3},{"label":"cigarette","hexcode":"1F6AC","tags":["smoking"],"emoji":"🚬","text":"","type":1,"order":4634,"group":7,"subgroup":82,"version":0.6},{"label":"coffin","hexcode":"26B0","tags":["dead","death","vampire"],"emoji":"⚰️","text":"⚰︎","type":0,"order":4636,"group":7,"subgroup":82,"version":1},{"label":"headstone","hexcode":"1FAA6","tags":["cemetery","dead","grave","graveyard","memorial","rip","tomb","tombstone"],"emoji":"🪦","text":"","type":1,"order":4637,"group":7,"subgroup":82,"version":13},{"label":"funeral urn","hexcode":"26B1","tags":["ashes","death","funeral","urn"],"emoji":"⚱️","text":"⚱︎","type":0,"order":4639,"group":7,"subgroup":82,"version":1},{"label":"nazar amulet","hexcode":"1F9FF","tags":["amulet","bead","blue","charm","evil-eye","nazar","talisman"],"emoji":"🧿","text":"","type":1,"order":4640,"group":7,"subgroup":82,"version":11},{"label":"hamsa","hexcode":"1FAAC","tags":["amulet","fatima","fortune","guide","hand","mary","miriam","palm","protect","protection"],"emoji":"🪬","text":"","type":1,"order":4641,"group":7,"subgroup":82,"version":14},{"label":"moai","hexcode":"1F5FF","tags":["face","moyai","statue","stoneface","travel"],"emoji":"🗿","text":"","type":1,"order":4642,"group":7,"subgroup":82,"version":0.6},{"label":"placard","hexcode":"1FAA7","tags":["card","demonstration","notice","picket","plaque","protest","sign"],"emoji":"🪧","text":"","type":1,"order":4643,"group":7,"subgroup":82,"version":13},{"label":"identification card","hexcode":"1FAAA","tags":["card","credentials","document","id","identification","license","security"],"emoji":"🪪","text":"","type":1,"order":4644,"group":7,"subgroup":82,"version":14},{"label":"ATM sign","hexcode":"1F3E7","tags":["atm","automated","bank","cash","money","sign","teller"],"emoji":"🏧","text":"","type":1,"order":4645,"group":8,"subgroup":83,"version":0.6},{"label":"litter in bin sign","hexcode":"1F6AE","tags":["bin","litter","litterbin","sign"],"emoji":"🚮","text":"","type":1,"order":4646,"group":8,"subgroup":83,"version":1},{"label":"potable water","hexcode":"1F6B0","tags":["drinking","potable","water"],"emoji":"🚰","text":"","type":1,"order":4647,"group":8,"subgroup":83,"version":1},{"label":"wheelchair symbol","hexcode":"267F","tags":["access","handicap","symbol","wheelchair"],"emoji":"♿️","text":"♿︎","type":1,"order":4648,"group":8,"subgroup":83,"version":0.6},{"label":"men’s room","hexcode":"1F6B9","tags":["bathroom","lavatory","man","men’s","restroom","room","toilet","wc"],"emoji":"🚹️","text":"🚹︎","type":1,"order":4649,"group":8,"subgroup":83,"version":0.6},{"label":"women’s room","hexcode":"1F6BA","tags":["bathroom","lavatory","restroom","room","toilet","wc","woman","women’s"],"emoji":"🚺️","text":"🚺︎","type":1,"order":4650,"group":8,"subgroup":83,"version":0.6},{"label":"restroom","hexcode":"1F6BB","tags":["bathroom","lavatory","toilet","wc"],"emoji":"🚻","text":"","type":1,"order":4651,"group":8,"subgroup":83,"version":0.6},{"label":"baby symbol","hexcode":"1F6BC","tags":["baby","changing","symbol"],"emoji":"🚼️","text":"🚼︎","type":1,"order":4652,"group":8,"subgroup":83,"version":0.6},{"label":"water closet","hexcode":"1F6BE","tags":["bathroom","closet","lavatory","restroom","toilet","water","wc"],"emoji":"🚾","text":"","type":1,"order":4653,"group":8,"subgroup":83,"version":0.6},{"label":"passport control","hexcode":"1F6C2","tags":["control","passport"],"emoji":"🛂","text":"","type":1,"order":4654,"group":8,"subgroup":83,"version":1},{"label":"customs","hexcode":"1F6C3","tags":["packing"],"emoji":"🛃","text":"","type":1,"order":4655,"group":8,"subgroup":83,"version":1},{"label":"baggage claim","hexcode":"1F6C4","tags":["arrived","baggage","bags","case","checked","claim","journey","packing","plane","ready","travel","trip"],"emoji":"🛄","text":"","type":1,"order":4656,"group":8,"subgroup":83,"version":1},{"label":"left luggage","hexcode":"1F6C5","tags":["baggage","case","left","locker","luggage"],"emoji":"🛅","text":"","type":1,"order":4657,"group":8,"subgroup":83,"version":1},{"label":"warning","hexcode":"26A0","tags":["caution"],"emoji":"⚠️","text":"⚠︎","type":0,"order":4659,"group":8,"subgroup":84,"version":0.6},{"label":"children crossing","hexcode":"1F6B8","tags":["child","children","crossing","pedestrian","traffic"],"emoji":"🚸","text":"","type":1,"order":4660,"group":8,"subgroup":84,"version":1},{"label":"no entry","hexcode":"26D4","tags":["do","entry","fail","forbidden","no","not","pass","prohibited","traffic"],"emoji":"⛔️","text":"⛔︎","type":1,"order":4661,"group":8,"subgroup":84,"version":0.6},{"label":"prohibited","hexcode":"1F6AB","tags":["entry","forbidden","no","not","smoke"],"emoji":"🚫","text":"","type":1,"order":4662,"group":8,"subgroup":84,"version":0.6},{"label":"no bicycles","hexcode":"1F6B3","tags":["bicycle","bicycles","bike","forbidden","no","not","prohibited"],"emoji":"🚳","text":"","type":1,"order":4663,"group":8,"subgroup":84,"version":1},{"label":"no smoking","hexcode":"1F6AD","tags":["forbidden","no","not","prohibited","smoke","smoking"],"emoji":"🚭️","text":"🚭︎","type":1,"order":4664,"group":8,"subgroup":84,"version":0.6},{"label":"no littering","hexcode":"1F6AF","tags":["forbidden","litter","littering","no","not","prohibited"],"emoji":"🚯","text":"","type":1,"order":4665,"group":8,"subgroup":84,"version":1},{"label":"non-potable water","hexcode":"1F6B1","tags":["dry","non-drinking","non-potable","prohibited","water"],"emoji":"🚱","text":"","type":1,"order":4666,"group":8,"subgroup":84,"version":1},{"label":"no pedestrians","hexcode":"1F6B7","tags":["forbidden","no","not","pedestrian","pedestrians","prohibited"],"emoji":"🚷","text":"","type":1,"order":4667,"group":8,"subgroup":84,"version":1},{"label":"no mobile phones","hexcode":"1F4F5","tags":["cell","forbidden","mobile","no","not","phone","phones","prohibited","telephone"],"emoji":"📵","text":"","type":1,"order":4668,"group":8,"subgroup":84,"version":1},{"label":"no one under eighteen","hexcode":"1F51E","tags":["18","age","eighteen","forbidden","no","not","one","prohibited","restriction","underage"],"emoji":"🔞","text":"","type":1,"order":4669,"group":8,"subgroup":84,"version":0.6},{"label":"radioactive","hexcode":"2622","tags":["sign"],"emoji":"☢️","text":"☢︎","type":0,"order":4671,"group":8,"subgroup":84,"version":1},{"label":"biohazard","hexcode":"2623","tags":["sign"],"emoji":"☣️","text":"☣︎","type":0,"order":4673,"group":8,"subgroup":84,"version":1},{"label":"up arrow","hexcode":"2B06","tags":["arrow","cardinal","direction","north","up"],"emoji":"⬆️","text":"⬆︎","type":0,"order":4675,"group":8,"subgroup":85,"version":0.6},{"label":"up-right arrow","hexcode":"2197","tags":["arrow","direction","intercardinal","northeast","up-right"],"emoji":"↗️","text":"↗︎","type":0,"order":4677,"group":8,"subgroup":85,"version":0.6},{"label":"right arrow","hexcode":"27A1","tags":["arrow","cardinal","direction","east","right"],"emoji":"➡️","text":"➡︎","type":0,"order":4679,"group":8,"subgroup":85,"version":0.6},{"label":"down-right arrow","hexcode":"2198","tags":["arrow","direction","down-right","intercardinal","southeast"],"emoji":"↘️","text":"↘︎","type":0,"order":4681,"group":8,"subgroup":85,"version":0.6},{"label":"down arrow","hexcode":"2B07","tags":["arrow","cardinal","direction","down","south"],"emoji":"⬇️","text":"⬇︎","type":0,"order":4683,"group":8,"subgroup":85,"version":0.6},{"label":"down-left arrow","hexcode":"2199","tags":["arrow","direction","down-left","intercardinal","southwest"],"emoji":"↙️","text":"↙︎","type":0,"order":4685,"group":8,"subgroup":85,"version":0.6},{"label":"left arrow","hexcode":"2B05","tags":["arrow","cardinal","direction","left","west"],"emoji":"⬅️","text":"⬅︎","type":0,"order":4687,"group":8,"subgroup":85,"version":0.6},{"label":"up-left arrow","hexcode":"2196","tags":["arrow","direction","intercardinal","northwest","up-left"],"emoji":"↖️","text":"↖︎","type":0,"order":4689,"group":8,"subgroup":85,"version":0.6},{"label":"up-down arrow","hexcode":"2195","tags":["arrow","up-down"],"emoji":"↕️","text":"↕︎","type":0,"order":4691,"group":8,"subgroup":85,"version":0.6},{"label":"left-right arrow","hexcode":"2194","tags":["arrow","left-right"],"emoji":"↔️","text":"↔︎","type":0,"order":4693,"group":8,"subgroup":85,"version":0.6},{"label":"right arrow curving left","hexcode":"21A9","tags":["arrow","curving","left","right"],"emoji":"↩️","text":"↩︎","type":0,"order":4695,"group":8,"subgroup":85,"version":0.6},{"label":"left arrow curving right","hexcode":"21AA","tags":["arrow","curving","left","right"],"emoji":"↪️","text":"↪︎","type":0,"order":4697,"group":8,"subgroup":85,"version":0.6},{"label":"right arrow curving up","hexcode":"2934","tags":["arrow","curving","right","up"],"emoji":"⤴️","text":"⤴︎","type":0,"order":4699,"group":8,"subgroup":85,"version":0.6},{"label":"right arrow curving down","hexcode":"2935","tags":["arrow","curving","down","right"],"emoji":"⤵️","text":"⤵︎","type":0,"order":4701,"group":8,"subgroup":85,"version":0.6},{"label":"clockwise vertical arrows","hexcode":"1F503","tags":["arrow","arrows","clockwise","refresh","reload","vertical"],"emoji":"🔃","text":"","type":1,"order":4702,"group":8,"subgroup":85,"version":0.6},{"label":"counterclockwise arrows button","hexcode":"1F504","tags":["again","anticlockwise","arrow","arrows","button","counterclockwise","deja","refresh","rewindershins","vu"],"emoji":"🔄","text":"","type":1,"order":4703,"group":8,"subgroup":85,"version":1},{"label":"BACK arrow","hexcode":"1F519","tags":["arrow","back"],"emoji":"🔙","text":"","type":1,"order":4704,"group":8,"subgroup":85,"version":0.6},{"label":"END arrow","hexcode":"1F51A","tags":["arrow","end"],"emoji":"🔚","text":"","type":1,"order":4705,"group":8,"subgroup":85,"version":0.6},{"label":"ON! arrow","hexcode":"1F51B","tags":["arrow","mark","on!"],"emoji":"🔛","text":"","type":1,"order":4706,"group":8,"subgroup":85,"version":0.6},{"label":"SOON arrow","hexcode":"1F51C","tags":["arrow","brb","omw","soon"],"emoji":"🔜","text":"","type":1,"order":4707,"group":8,"subgroup":85,"version":0.6},{"label":"TOP arrow","hexcode":"1F51D","tags":["arrow","homie","top","up"],"emoji":"🔝","text":"","type":1,"order":4708,"group":8,"subgroup":85,"version":0.6},{"label":"place of worship","hexcode":"1F6D0","tags":["place","pray","religion","worship"],"emoji":"🛐","text":"","type":1,"order":4709,"group":8,"subgroup":86,"version":1},{"label":"atom symbol","hexcode":"269B","tags":["atheist","atom","symbol"],"emoji":"⚛️","text":"⚛︎","type":0,"order":4711,"group":8,"subgroup":86,"version":1},{"label":"om","hexcode":"1F549","tags":["hindu","religion"],"emoji":"🕉️","text":"🕉︎","type":0,"order":4713,"group":8,"subgroup":86,"version":0.7},{"label":"star of David","hexcode":"2721","tags":["david","jew","jewish","judaism","religion","star"],"emoji":"✡️","text":"✡︎","type":0,"order":4715,"group":8,"subgroup":86,"version":0.7},{"label":"wheel of dharma","hexcode":"2638","tags":["buddhist","dharma","religion","wheel"],"emoji":"☸️","text":"☸︎","type":0,"order":4717,"group":8,"subgroup":86,"version":0.7},{"label":"yin yang","hexcode":"262F","tags":["difficult","lives","religion","tao","taoist","total","yang","yin","yinyang"],"emoji":"☯️","text":"☯︎","type":0,"order":4719,"group":8,"subgroup":86,"version":0.7},{"label":"latin cross","hexcode":"271D","tags":["christ","christian","cross","latin","religion"],"emoji":"✝️","text":"✝︎","type":0,"order":4721,"group":8,"subgroup":86,"version":0.7},{"label":"orthodox cross","hexcode":"2626","tags":["christian","cross","orthodox","religion"],"emoji":"☦️","text":"☦︎","type":0,"order":4723,"group":8,"subgroup":86,"version":1},{"label":"star and crescent","hexcode":"262A","tags":["crescent","islam","muslim","ramadan","religion","star"],"emoji":"☪️","text":"☪︎","type":0,"order":4725,"group":8,"subgroup":86,"version":0.7},{"label":"peace symbol","hexcode":"262E","tags":["healing","peace","peaceful","symbol"],"emoji":"☮️","text":"☮︎","type":0,"order":4727,"group":8,"subgroup":86,"version":1},{"label":"menorah","hexcode":"1F54E","tags":["candelabrum","candlestick","hanukkah","jewish","judaism","religion"],"emoji":"🕎","text":"","type":1,"order":4728,"group":8,"subgroup":86,"version":1},{"label":"dotted six-pointed star","hexcode":"1F52F","tags":["dotted","fortune","jewish","judaism","six-pointed","star"],"emoji":"🔯","text":"","type":1,"order":4729,"group":8,"subgroup":86,"version":0.6},{"label":"khanda","hexcode":"1FAAF","tags":["deg","fateh","khalsa","religion","sikh","sikhism","tegh"],"emoji":"🪯","text":"","type":1,"order":4730,"group":8,"subgroup":86,"version":15},{"label":"Aries","hexcode":"2648","tags":["aries","horoscope","ram","zodiac"],"emoji":"♈️","text":"♈︎","type":1,"order":4731,"group":8,"subgroup":87,"version":0.6},{"label":"Taurus","hexcode":"2649","tags":["bull","horoscope","ox","taurus","zodiac"],"emoji":"♉️","text":"♉︎","type":1,"order":4732,"group":8,"subgroup":87,"version":0.6},{"label":"Gemini","hexcode":"264A","tags":["gemini","horoscope","twins","zodiac"],"emoji":"♊️","text":"♊︎","type":1,"order":4733,"group":8,"subgroup":87,"version":0.6},{"label":"Cancer","hexcode":"264B","tags":["cancer","crab","horoscope","zodiac"],"emoji":"♋️","text":"♋︎","type":1,"order":4734,"group":8,"subgroup":87,"version":0.6},{"label":"Leo","hexcode":"264C","tags":["horoscope","leo","lion","zodiac"],"emoji":"♌️","text":"♌︎","type":1,"order":4735,"group":8,"subgroup":87,"version":0.6},{"label":"Virgo","hexcode":"264D","tags":["horoscope","virgo","zodiac"],"emoji":"♍️","text":"♍︎","type":1,"order":4736,"group":8,"subgroup":87,"version":0.6},{"label":"Libra","hexcode":"264E","tags":["balance","horoscope","justice","libra","scales","zodiac"],"emoji":"♎️","text":"♎︎","type":1,"order":4737,"group":8,"subgroup":87,"version":0.6},{"label":"Scorpio","hexcode":"264F","tags":["horoscope","scorpio","scorpion","scorpius","zodiac"],"emoji":"♏️","text":"♏︎","type":1,"order":4738,"group":8,"subgroup":87,"version":0.6},{"label":"Sagittarius","hexcode":"2650","tags":["archer","horoscope","sagittarius","zodiac"],"emoji":"♐️","text":"♐︎","type":1,"order":4739,"group":8,"subgroup":87,"version":0.6},{"label":"Capricorn","hexcode":"2651","tags":["capricorn","goat","horoscope","zodiac"],"emoji":"♑️","text":"♑︎","type":1,"order":4740,"group":8,"subgroup":87,"version":0.6},{"label":"Aquarius","hexcode":"2652","tags":["aquarius","bearer","horoscope","water","zodiac"],"emoji":"♒️","text":"♒︎","type":1,"order":4741,"group":8,"subgroup":87,"version":0.6},{"label":"Pisces","hexcode":"2653","tags":["fish","horoscope","pisces","zodiac"],"emoji":"♓️","text":"♓︎","type":1,"order":4742,"group":8,"subgroup":87,"version":0.6},{"label":"Ophiuchus","hexcode":"26CE","tags":["bearer","ophiuchus","serpent","snake","zodiac"],"emoji":"⛎️","text":"⛎︎","type":1,"order":4743,"group":8,"subgroup":87,"version":0.6},{"label":"shuffle tracks button","hexcode":"1F500","tags":["arrow","button","crossed","shuffle","tracks"],"emoji":"🔀","text":"","type":1,"order":4744,"group":8,"subgroup":88,"version":1},{"label":"repeat button","hexcode":"1F501","tags":["arrow","button","clockwise","repeat"],"emoji":"🔁","text":"","type":1,"order":4745,"group":8,"subgroup":88,"version":1},{"label":"repeat single button","hexcode":"1F502","tags":["arrow","button","clockwise","once","repeat","single"],"emoji":"🔂","text":"","type":1,"order":4746,"group":8,"subgroup":88,"version":1},{"label":"play button","hexcode":"25B6","tags":["arrow","button","play","right","triangle"],"emoji":"▶️","text":"▶︎","type":0,"order":4748,"group":8,"subgroup":88,"version":0.6},{"label":"fast-forward button","hexcode":"23E9","tags":["arrow","button","double","fast","fast-forward","forward"],"emoji":"⏩️","text":"⏩︎","type":1,"order":4749,"group":8,"subgroup":88,"version":0.6},{"label":"next track button","hexcode":"23ED","tags":["arrow","button","next","scene","track","triangle"],"emoji":"⏭️","text":"⏭︎","type":0,"order":4751,"group":8,"subgroup":88,"version":0.7},{"label":"play or pause button","hexcode":"23EF","tags":["arrow","button","pause","play","right","triangle"],"emoji":"⏯️","text":"⏯︎","type":0,"order":4753,"group":8,"subgroup":88,"version":1},{"label":"reverse button","hexcode":"25C0","tags":["arrow","button","left","reverse","triangle"],"emoji":"◀️","text":"◀︎","type":0,"order":4755,"group":8,"subgroup":88,"version":0.6},{"label":"fast reverse button","hexcode":"23EA","tags":["arrow","button","double","fast","reverse","rewind"],"emoji":"⏪️","text":"⏪︎","type":1,"order":4756,"group":8,"subgroup":88,"version":0.6},{"label":"last track button","hexcode":"23EE","tags":["arrow","button","last","previous","scene","track","triangle"],"emoji":"⏮️","text":"⏮︎","type":0,"order":4758,"group":8,"subgroup":88,"version":0.7},{"label":"upwards button","hexcode":"1F53C","tags":["arrow","button","red","up","upwards"],"emoji":"🔼","text":"","type":1,"order":4759,"group":8,"subgroup":88,"version":0.6},{"label":"fast up button","hexcode":"23EB","tags":["arrow","button","double","fast","up"],"emoji":"⏫️","text":"⏫︎","type":1,"order":4760,"group":8,"subgroup":88,"version":0.6},{"label":"downwards button","hexcode":"1F53D","tags":["arrow","button","down","downwards","red"],"emoji":"🔽","text":"","type":1,"order":4761,"group":8,"subgroup":88,"version":0.6},{"label":"fast down button","hexcode":"23EC","tags":["arrow","button","double","down","fast"],"emoji":"⏬️","text":"⏬︎","type":1,"order":4762,"group":8,"subgroup":88,"version":0.6},{"label":"pause button","hexcode":"23F8","tags":["bar","button","double","pause","vertical"],"emoji":"⏸️","text":"⏸︎","type":0,"order":4764,"group":8,"subgroup":88,"version":0.7},{"label":"stop button","hexcode":"23F9","tags":["button","square","stop"],"emoji":"⏹️","text":"⏹︎","type":0,"order":4766,"group":8,"subgroup":88,"version":0.7},{"label":"record button","hexcode":"23FA","tags":["button","circle","record"],"emoji":"⏺️","text":"⏺︎","type":0,"order":4768,"group":8,"subgroup":88,"version":0.7},{"label":"eject button","hexcode":"23CF","tags":["button","eject"],"emoji":"⏏️","text":"⏏︎","type":0,"order":4770,"group":8,"subgroup":88,"version":1},{"label":"cinema","hexcode":"1F3A6","tags":["camera","film","movie"],"emoji":"🎦","text":"","type":1,"order":4771,"group":8,"subgroup":88,"version":0.6},{"label":"dim button","hexcode":"1F505","tags":["brightness","button","dim","low"],"emoji":"🔅","text":"","type":1,"order":4772,"group":8,"subgroup":88,"version":1},{"label":"bright button","hexcode":"1F506","tags":["bright","brightness","button","light"],"emoji":"🔆","text":"","type":1,"order":4773,"group":8,"subgroup":88,"version":1},{"label":"antenna bars","hexcode":"1F4F6","tags":["antenna","bar","bars","cell","communication","mobile","phone","signal","telephone"],"emoji":"📶","text":"","type":1,"order":4774,"group":8,"subgroup":88,"version":0.6},{"label":"wireless","hexcode":"1F6DC","tags":["broadband","computer","connectivity","hotspot","internet","network","router","smartphone","wi-fi","wifi","wlan"],"emoji":"🛜","text":"","type":1,"order":4775,"group":8,"subgroup":88,"version":15},{"label":"vibration mode","hexcode":"1F4F3","tags":["cell","communication","mobile","mode","phone","telephone","vibration"],"emoji":"📳","text":"","type":1,"order":4776,"group":8,"subgroup":88,"version":0.6},{"label":"mobile phone off","hexcode":"1F4F4","tags":["cell","mobile","off","phone","telephone"],"emoji":"📴","text":"","type":1,"order":4777,"group":8,"subgroup":88,"version":0.6},{"label":"female sign","hexcode":"2640","tags":["female","sign","woman"],"emoji":"♀️","text":"♀︎","type":0,"order":4779,"group":8,"subgroup":89,"version":4},{"label":"male sign","hexcode":"2642","tags":["male","man","sign"],"emoji":"♂️","text":"♂︎","type":0,"order":4781,"group":8,"subgroup":89,"version":4},{"label":"transgender symbol","hexcode":"26A7","tags":["symbol","transgender"],"emoji":"⚧️","text":"⚧︎","type":0,"order":4783,"group":8,"subgroup":89,"version":13},{"label":"multiply","hexcode":"2716","tags":["cancel","multiplication","sign","x","×"],"emoji":"✖️","text":"✖︎","type":0,"order":4785,"group":8,"subgroup":90,"version":0.6},{"label":"plus","hexcode":"2795","tags":["+"],"emoji":"➕️","text":"➕︎","type":1,"order":4786,"group":8,"subgroup":90,"version":0.6},{"label":"minus","hexcode":"2796","tags":["-","heavy","math","sign","−"],"emoji":"➖️","text":"➖︎","type":1,"order":4787,"group":8,"subgroup":90,"version":0.6},{"label":"divide","hexcode":"2797","tags":["division","heavy","math","sign","÷"],"emoji":"➗️","text":"➗︎","type":1,"order":4788,"group":8,"subgroup":90,"version":0.6},{"label":"heavy equals sign","hexcode":"1F7F0","tags":["answer","equal","equality","equals","heavy","math","sign"],"emoji":"🟰","text":"","type":1,"order":4789,"group":8,"subgroup":90,"version":14},{"label":"infinity","hexcode":"267E","tags":["forever","unbounded","universal"],"emoji":"♾️","text":"♾︎","type":0,"order":4791,"group":8,"subgroup":90,"version":11},{"label":"double exclamation mark","hexcode":"203C","tags":["!","!!","bangbang","double","exclamation","mark","punctuation"],"emoji":"‼️","text":"‼︎","type":0,"order":4793,"group":8,"subgroup":91,"version":0.6},{"label":"exclamation question mark","hexcode":"2049","tags":["!","!?","?","exclamation","interrobang","mark","punctuation","question"],"emoji":"⁉️","text":"⁉︎","type":0,"order":4795,"group":8,"subgroup":91,"version":0.6},{"label":"red question mark","hexcode":"2753","tags":["?","mark","punctuation","question","red"],"emoji":"❓️","text":"❓︎","type":1,"order":4796,"group":8,"subgroup":91,"version":0.6},{"label":"white question mark","hexcode":"2754","tags":["?","mark","outlined","punctuation","question","white"],"emoji":"❔️","text":"❔︎","type":1,"order":4797,"group":8,"subgroup":91,"version":0.6},{"label":"white exclamation mark","hexcode":"2755","tags":["!","exclamation","mark","outlined","punctuation","white"],"emoji":"❕️","text":"❕︎","type":1,"order":4798,"group":8,"subgroup":91,"version":0.6},{"label":"red exclamation mark","hexcode":"2757","tags":["!","exclamation","mark","punctuation","red"],"emoji":"❗️","text":"❗︎","type":1,"order":4799,"group":8,"subgroup":91,"version":0.6},{"label":"wavy dash","hexcode":"3030","tags":["dash","punctuation","wavy"],"emoji":"〰️","text":"〰︎","type":0,"order":4801,"group":8,"subgroup":91,"version":0.6},{"label":"currency exchange","hexcode":"1F4B1","tags":["bank","currency","exchange","money"],"emoji":"💱","text":"","type":1,"order":4802,"group":8,"subgroup":92,"version":0.6},{"label":"heavy dollar sign","hexcode":"1F4B2","tags":["billion","cash","charge","currency","dollar","heavy","million","money","pay","sign"],"emoji":"💲","text":"","type":1,"order":4803,"group":8,"subgroup":92,"version":0.6},{"label":"medical symbol","hexcode":"2695","tags":["aesculapius","medical","medicine","staff","symbol"],"emoji":"⚕️","text":"⚕︎","type":0,"order":4805,"group":8,"subgroup":93,"version":4},{"label":"recycling symbol","hexcode":"267B","tags":["recycle","recycling","symbol"],"emoji":"♻️","text":"♻︎","type":0,"order":4807,"group":8,"subgroup":93,"version":0.6},{"label":"fleur-de-lis","hexcode":"269C","tags":["knights"],"emoji":"⚜️","text":"⚜︎","type":0,"order":4809,"group":8,"subgroup":93,"version":1},{"label":"trident emblem","hexcode":"1F531","tags":["anchor","emblem","poseidon","ship","tool","trident"],"emoji":"🔱","text":"","type":1,"order":4810,"group":8,"subgroup":93,"version":0.6},{"label":"name badge","hexcode":"1F4DB","tags":["badge","name"],"emoji":"📛","text":"","type":1,"order":4811,"group":8,"subgroup":93,"version":0.6},{"label":"Japanese symbol for beginner","hexcode":"1F530","tags":["beginner","chevron","green","japanese","leaf","symbol","tool","yellow"],"emoji":"🔰","text":"","type":1,"order":4812,"group":8,"subgroup":93,"version":0.6},{"label":"hollow red circle","hexcode":"2B55","tags":["circle","heavy","hollow","large","o","red"],"emoji":"⭕️","text":"⭕︎","type":1,"order":4813,"group":8,"subgroup":93,"version":0.6},{"label":"check mark button","hexcode":"2705","tags":["button","check","checked","checkmark","complete","completed","done","fixed","mark","tick","✓"],"emoji":"✅️","text":"✅︎","type":1,"order":4814,"group":8,"subgroup":93,"version":0.6},{"label":"check box with check","hexcode":"2611","tags":["ballot","box","check","checked","done","off","tick","✓"],"emoji":"☑️","text":"☑︎","type":0,"order":4816,"group":8,"subgroup":93,"version":0.6},{"label":"check mark","hexcode":"2714","tags":["check","checked","checkmark","done","heavy","mark","tick","✓"],"emoji":"✔️","text":"✔︎","type":0,"order":4818,"group":8,"subgroup":93,"version":0.6},{"label":"cross mark","hexcode":"274C","tags":["cancel","cross","mark","multiplication","multiply","x","×"],"emoji":"❌️","text":"❌︎","type":1,"order":4819,"group":8,"subgroup":93,"version":0.6},{"label":"cross mark button","hexcode":"274E","tags":["button","cross","mark","multiplication","multiply","square","x","×"],"emoji":"❎️","text":"❎︎","type":1,"order":4820,"group":8,"subgroup":93,"version":0.6},{"label":"curly loop","hexcode":"27B0","tags":["curl","curly","loop"],"emoji":"➰️","text":"➰︎","type":1,"order":4821,"group":8,"subgroup":93,"version":0.6},{"label":"double curly loop","hexcode":"27BF","tags":["curl","curly","double","loop"],"emoji":"➿️","text":"➿︎","type":1,"order":4822,"group":8,"subgroup":93,"version":1},{"label":"part alternation mark","hexcode":"303D","tags":["alternation","mark","part"],"emoji":"〽️","text":"〽︎","type":0,"order":4824,"group":8,"subgroup":93,"version":0.6},{"label":"eight-spoked asterisk","hexcode":"2733","tags":["*","asterisk","eight-spoked"],"emoji":"✳️","text":"✳︎","type":0,"order":4826,"group":8,"subgroup":93,"version":0.6},{"label":"eight-pointed star","hexcode":"2734","tags":["*","eight-pointed","star"],"emoji":"✴️","text":"✴︎","type":0,"order":4828,"group":8,"subgroup":93,"version":0.6},{"label":"sparkle","hexcode":"2747","tags":["*"],"emoji":"❇️","text":"❇︎","type":0,"order":4830,"group":8,"subgroup":93,"version":0.6},{"label":"copyright","hexcode":"00A9","tags":["c"],"emoji":"©️","text":"©︎","type":0,"order":4832,"group":8,"subgroup":93,"version":0.6},{"label":"registered","hexcode":"00AE","tags":["r"],"emoji":"®️","text":"®︎","type":0,"order":4834,"group":8,"subgroup":93,"version":0.6},{"label":"trade mark","hexcode":"2122","tags":["mark","tm","trade","trademark"],"emoji":"™️","text":"™︎","type":0,"order":4836,"group":8,"subgroup":93,"version":0.6},{"label":"splatter","hexcode":"1FADF","tags":["drip","holi","ink","liquid","mess","paint","spill","stain"],"emoji":"🫟","text":"","type":1,"order":4837,"group":8,"subgroup":93,"version":16},{"label":"keycap: #","hexcode":"0023-FE0F-20E3","tags":["keycap"],"emoji":"#️⃣","text":"","type":1,"order":4838,"group":8,"subgroup":94,"version":0.6},{"label":"keycap: *","hexcode":"002A-FE0F-20E3","tags":["keycap"],"emoji":"*️⃣","text":"","type":1,"order":4840,"group":8,"subgroup":94,"version":2},{"label":"keycap: 0","hexcode":"0030-FE0F-20E3","tags":["0","keycap","zero"],"emoji":"0️⃣","text":"","type":1,"order":4842,"group":8,"subgroup":94,"version":0.6},{"label":"keycap: 1","hexcode":"0031-FE0F-20E3","tags":["1","keycap","one"],"emoji":"1️⃣","text":"","type":1,"order":4844,"group":8,"subgroup":94,"version":0.6},{"label":"keycap: 2","hexcode":"0032-FE0F-20E3","tags":["2","keycap","two"],"emoji":"2️⃣","text":"","type":1,"order":4846,"group":8,"subgroup":94,"version":0.6},{"label":"keycap: 3","hexcode":"0033-FE0F-20E3","tags":["3","keycap","three"],"emoji":"3️⃣","text":"","type":1,"order":4848,"group":8,"subgroup":94,"version":0.6},{"label":"keycap: 4","hexcode":"0034-FE0F-20E3","tags":["4","four","keycap"],"emoji":"4️⃣","text":"","type":1,"order":4850,"group":8,"subgroup":94,"version":0.6},{"label":"keycap: 5","hexcode":"0035-FE0F-20E3","tags":["5","five","keycap"],"emoji":"5️⃣","text":"","type":1,"order":4852,"group":8,"subgroup":94,"version":0.6},{"label":"keycap: 6","hexcode":"0036-FE0F-20E3","tags":["6","keycap","six"],"emoji":"6️⃣","text":"","type":1,"order":4854,"group":8,"subgroup":94,"version":0.6},{"label":"keycap: 7","hexcode":"0037-FE0F-20E3","tags":["7","keycap","seven"],"emoji":"7️⃣","text":"","type":1,"order":4856,"group":8,"subgroup":94,"version":0.6},{"label":"keycap: 8","hexcode":"0038-FE0F-20E3","tags":["8","eight","keycap"],"emoji":"8️⃣","text":"","type":1,"order":4858,"group":8,"subgroup":94,"version":0.6},{"label":"keycap: 9","hexcode":"0039-FE0F-20E3","tags":["9","keycap","nine"],"emoji":"9️⃣","text":"","type":1,"order":4860,"group":8,"subgroup":94,"version":0.6},{"label":"keycap: 10","hexcode":"1F51F","tags":["keycap"],"emoji":"🔟","text":"","type":1,"order":4862,"group":8,"subgroup":94,"version":0.6},{"label":"input latin uppercase","hexcode":"1F520","tags":["abcd","input","latin","letters","uppercase"],"emoji":"🔠","text":"","type":1,"order":4863,"group":8,"subgroup":95,"version":0.6},{"label":"input latin lowercase","hexcode":"1F521","tags":["abcd","input","latin","letters","lowercase"],"emoji":"🔡","text":"","type":1,"order":4864,"group":8,"subgroup":95,"version":0.6},{"label":"input numbers","hexcode":"1F522","tags":["1234","input","numbers"],"emoji":"🔢","text":"","type":1,"order":4865,"group":8,"subgroup":95,"version":0.6},{"label":"input symbols","hexcode":"1F523","tags":["%","&","input","symbols","♪","〒"],"emoji":"🔣","text":"","type":1,"order":4866,"group":8,"subgroup":95,"version":0.6},{"label":"input latin letters","hexcode":"1F524","tags":["abc","alphabet","input","latin","letters"],"emoji":"🔤","text":"","type":1,"order":4867,"group":8,"subgroup":95,"version":0.6},{"label":"A button (blood type)","hexcode":"1F170","tags":["blood","button","type"],"emoji":"🅰️","text":"🅰︎","type":0,"order":4869,"group":8,"subgroup":95,"version":0.6},{"label":"AB button (blood type)","hexcode":"1F18E","tags":["ab","blood","button","type"],"emoji":"🆎","text":"","type":1,"order":4870,"group":8,"subgroup":95,"version":0.6},{"label":"B button (blood type)","hexcode":"1F171","tags":["b","blood","button","type"],"emoji":"🅱️","text":"🅱︎","type":0,"order":4872,"group":8,"subgroup":95,"version":0.6},{"label":"CL button","hexcode":"1F191","tags":["button","cl"],"emoji":"🆑","text":"","type":1,"order":4873,"group":8,"subgroup":95,"version":0.6},{"label":"COOL button","hexcode":"1F192","tags":["button","cool"],"emoji":"🆒","text":"","type":1,"order":4874,"group":8,"subgroup":95,"version":0.6},{"label":"FREE button","hexcode":"1F193","tags":["button","free"],"emoji":"🆓","text":"","type":1,"order":4875,"group":8,"subgroup":95,"version":0.6},{"label":"information","hexcode":"2139","tags":["i"],"emoji":"ℹ️","text":"ℹ︎","type":0,"order":4877,"group":8,"subgroup":95,"version":0.6},{"label":"ID button","hexcode":"1F194","tags":["button","id","identity"],"emoji":"🆔","text":"","type":1,"order":4878,"group":8,"subgroup":95,"version":0.6},{"label":"circled M","hexcode":"24C2","tags":["circle","circled","m"],"emoji":"Ⓜ️","text":"Ⓜ︎","type":0,"order":4880,"group":8,"subgroup":95,"version":0.6},{"label":"NEW button","hexcode":"1F195","tags":["button","new"],"emoji":"🆕","text":"","type":1,"order":4881,"group":8,"subgroup":95,"version":0.6},{"label":"NG button","hexcode":"1F196","tags":["button","ng"],"emoji":"🆖","text":"","type":1,"order":4882,"group":8,"subgroup":95,"version":0.6},{"label":"O button (blood type)","hexcode":"1F17E","tags":["blood","button","o","type"],"emoji":"🅾️","text":"🅾︎","type":0,"order":4884,"group":8,"subgroup":95,"version":0.6},{"label":"OK button","hexcode":"1F197","tags":["button","ok","okay"],"emoji":"🆗","text":"","type":1,"order":4885,"group":8,"subgroup":95,"version":0.6},{"label":"P button","hexcode":"1F17F","tags":["button","p","parking"],"emoji":"🅿️","text":"🅿︎","type":0,"order":4887,"group":8,"subgroup":95,"version":0.6},{"label":"SOS button","hexcode":"1F198","tags":["button","help","sos"],"emoji":"🆘","text":"","type":1,"order":4888,"group":8,"subgroup":95,"version":0.6},{"label":"UP! button","hexcode":"1F199","tags":["button","mark","up","up!"],"emoji":"🆙","text":"","type":1,"order":4889,"group":8,"subgroup":95,"version":0.6},{"label":"VS button","hexcode":"1F19A","tags":["button","versus","vs"],"emoji":"🆚","text":"","type":1,"order":4890,"group":8,"subgroup":95,"version":0.6},{"label":"Japanese “here” button","hexcode":"1F201","tags":["button","here","japanese","katakana"],"emoji":"🈁","text":"","type":1,"order":4891,"group":8,"subgroup":95,"version":0.6},{"label":"Japanese “service charge” button","hexcode":"1F202","tags":["button","charge","japanese","katakana","service"],"emoji":"🈂️","text":"🈂︎","type":0,"order":4893,"group":8,"subgroup":95,"version":0.6},{"label":"Japanese “monthly amount” button","hexcode":"1F237","tags":["amount","button","ideograph","japanese","monthly"],"emoji":"🈷️","text":"🈷︎","type":0,"order":4895,"group":8,"subgroup":95,"version":0.6},{"label":"Japanese “not free of charge” button","hexcode":"1F236","tags":["button","charge","free","ideograph","japanese","not"],"emoji":"🈶","text":"","type":1,"order":4896,"group":8,"subgroup":95,"version":0.6},{"label":"Japanese “reserved” button","hexcode":"1F22F","tags":["button","ideograph","japanese","reserved"],"emoji":"🈯️","text":"🈯︎","type":1,"order":4897,"group":8,"subgroup":95,"version":0.6},{"label":"Japanese “bargain” button","hexcode":"1F250","tags":["bargain","button","ideograph","japanese"],"emoji":"🉐","text":"","type":1,"order":4898,"group":8,"subgroup":95,"version":0.6},{"label":"Japanese “discount” button","hexcode":"1F239","tags":["button","discount","ideograph","japanese"],"emoji":"🈹","text":"","type":1,"order":4899,"group":8,"subgroup":95,"version":0.6},{"label":"Japanese “free of charge” button","hexcode":"1F21A","tags":["button","charge","free","ideograph","japanese"],"emoji":"🈚️","text":"🈚︎","type":1,"order":4900,"group":8,"subgroup":95,"version":0.6},{"label":"Japanese “prohibited” button","hexcode":"1F232","tags":["button","ideograph","japanese","prohibited"],"emoji":"🈲","text":"","type":1,"order":4901,"group":8,"subgroup":95,"version":0.6},{"label":"Japanese “acceptable” button","hexcode":"1F251","tags":["acceptable","button","ideograph","japanese"],"emoji":"🉑","text":"","type":1,"order":4902,"group":8,"subgroup":95,"version":0.6},{"label":"Japanese “application” button","hexcode":"1F238","tags":["application","button","ideograph","japanese"],"emoji":"🈸","text":"","type":1,"order":4903,"group":8,"subgroup":95,"version":0.6},{"label":"Japanese “passing grade” button","hexcode":"1F234","tags":["button","grade","ideograph","japanese","passing"],"emoji":"🈴","text":"","type":1,"order":4904,"group":8,"subgroup":95,"version":0.6},{"label":"Japanese “vacancy” button","hexcode":"1F233","tags":["button","ideograph","japanese","vacancy"],"emoji":"🈳","text":"","type":1,"order":4905,"group":8,"subgroup":95,"version":0.6},{"label":"Japanese “congratulations” button","hexcode":"3297","tags":["button","congratulations","ideograph","japanese"],"emoji":"㊗️","text":"㊗︎","type":0,"order":4907,"group":8,"subgroup":95,"version":0.6},{"label":"Japanese “secret” button","hexcode":"3299","tags":["button","ideograph","japanese","secret"],"emoji":"㊙️","text":"㊙︎","type":0,"order":4909,"group":8,"subgroup":95,"version":0.6},{"label":"Japanese “open for business” button","hexcode":"1F23A","tags":["business","button","ideograph","japanese","open"],"emoji":"🈺","text":"","type":1,"order":4910,"group":8,"subgroup":95,"version":0.6},{"label":"Japanese “no vacancy” button","hexcode":"1F235","tags":["button","ideograph","japanese","no","vacancy"],"emoji":"🈵","text":"","type":1,"order":4911,"group":8,"subgroup":95,"version":0.6},{"label":"red circle","hexcode":"1F534","tags":["circle","geometric","red"],"emoji":"🔴","text":"","type":1,"order":4912,"group":8,"subgroup":96,"version":0.6},{"label":"orange circle","hexcode":"1F7E0","tags":["circle","orange"],"emoji":"🟠","text":"","type":1,"order":4913,"group":8,"subgroup":96,"version":12},{"label":"yellow circle","hexcode":"1F7E1","tags":["circle","yellow"],"emoji":"🟡","text":"","type":1,"order":4914,"group":8,"subgroup":96,"version":12},{"label":"green circle","hexcode":"1F7E2","tags":["circle","green"],"emoji":"🟢","text":"","type":1,"order":4915,"group":8,"subgroup":96,"version":12},{"label":"blue circle","hexcode":"1F535","tags":["blue","circle","geometric"],"emoji":"🔵","text":"","type":1,"order":4916,"group":8,"subgroup":96,"version":0.6},{"label":"purple circle","hexcode":"1F7E3","tags":["circle","purple"],"emoji":"🟣","text":"","type":1,"order":4917,"group":8,"subgroup":96,"version":12},{"label":"brown circle","hexcode":"1F7E4","tags":["brown","circle"],"emoji":"🟤","text":"","type":1,"order":4918,"group":8,"subgroup":96,"version":12},{"label":"black circle","hexcode":"26AB","tags":["black","circle","geometric"],"emoji":"⚫️","text":"⚫︎","type":1,"order":4919,"group":8,"subgroup":96,"version":0.6},{"label":"white circle","hexcode":"26AA","tags":["circle","geometric","white"],"emoji":"⚪️","text":"⚪︎","type":1,"order":4920,"group":8,"subgroup":96,"version":0.6},{"label":"red square","hexcode":"1F7E5","tags":["card","penalty","red","square"],"emoji":"🟥","text":"","type":1,"order":4921,"group":8,"subgroup":96,"version":12},{"label":"orange square","hexcode":"1F7E7","tags":["orange","square"],"emoji":"🟧","text":"","type":1,"order":4922,"group":8,"subgroup":96,"version":12},{"label":"yellow square","hexcode":"1F7E8","tags":["card","penalty","square","yellow"],"emoji":"🟨","text":"","type":1,"order":4923,"group":8,"subgroup":96,"version":12},{"label":"green square","hexcode":"1F7E9","tags":["green","square"],"emoji":"🟩","text":"","type":1,"order":4924,"group":8,"subgroup":96,"version":12},{"label":"blue square","hexcode":"1F7E6","tags":["blue","square"],"emoji":"🟦","text":"","type":1,"order":4925,"group":8,"subgroup":96,"version":12},{"label":"purple square","hexcode":"1F7EA","tags":["purple","square"],"emoji":"🟪","text":"","type":1,"order":4926,"group":8,"subgroup":96,"version":12},{"label":"brown square","hexcode":"1F7EB","tags":["brown","square"],"emoji":"🟫","text":"","type":1,"order":4927,"group":8,"subgroup":96,"version":12},{"label":"black large square","hexcode":"2B1B","tags":["black","geometric","large","square"],"emoji":"⬛️","text":"⬛︎","type":1,"order":4928,"group":8,"subgroup":96,"version":0.6},{"label":"white large square","hexcode":"2B1C","tags":["geometric","large","square","white"],"emoji":"⬜️","text":"⬜︎","type":1,"order":4929,"group":8,"subgroup":96,"version":0.6},{"label":"black medium square","hexcode":"25FC","tags":["black","geometric","medium","square"],"emoji":"◼️","text":"◼︎","type":0,"order":4931,"group":8,"subgroup":96,"version":0.6},{"label":"white medium square","hexcode":"25FB","tags":["geometric","medium","square","white"],"emoji":"◻️","text":"◻︎","type":0,"order":4933,"group":8,"subgroup":96,"version":0.6},{"label":"black medium-small square","hexcode":"25FE","tags":["black","geometric","medium-small","square"],"emoji":"◾️","text":"◾︎","type":1,"order":4934,"group":8,"subgroup":96,"version":0.6},{"label":"white medium-small square","hexcode":"25FD","tags":["geometric","medium-small","square","white"],"emoji":"◽️","text":"◽︎","type":1,"order":4935,"group":8,"subgroup":96,"version":0.6},{"label":"black small square","hexcode":"25AA","tags":["black","geometric","small","square"],"emoji":"▪️","text":"▪︎","type":0,"order":4937,"group":8,"subgroup":96,"version":0.6},{"label":"white small square","hexcode":"25AB","tags":["geometric","small","square","white"],"emoji":"▫️","text":"▫︎","type":0,"order":4939,"group":8,"subgroup":96,"version":0.6},{"label":"large orange diamond","hexcode":"1F536","tags":["diamond","geometric","large","orange"],"emoji":"🔶","text":"","type":1,"order":4940,"group":8,"subgroup":96,"version":0.6},{"label":"large blue diamond","hexcode":"1F537","tags":["blue","diamond","geometric","large"],"emoji":"🔷","text":"","type":1,"order":4941,"group":8,"subgroup":96,"version":0.6},{"label":"small orange diamond","hexcode":"1F538","tags":["diamond","geometric","orange","small"],"emoji":"🔸","text":"","type":1,"order":4942,"group":8,"subgroup":96,"version":0.6},{"label":"small blue diamond","hexcode":"1F539","tags":["blue","diamond","geometric","small"],"emoji":"🔹","text":"","type":1,"order":4943,"group":8,"subgroup":96,"version":0.6},{"label":"red triangle pointed up","hexcode":"1F53A","tags":["geometric","pointed","red","triangle","up"],"emoji":"🔺","text":"","type":1,"order":4944,"group":8,"subgroup":96,"version":0.6},{"label":"red triangle pointed down","hexcode":"1F53B","tags":["down","geometric","pointed","red","triangle"],"emoji":"🔻","text":"","type":1,"order":4945,"group":8,"subgroup":96,"version":0.6},{"label":"diamond with a dot","hexcode":"1F4A0","tags":["comic","diamond","dot","geometric"],"emoji":"💠","text":"","type":1,"order":4946,"group":8,"subgroup":96,"version":0.6},{"label":"radio button","hexcode":"1F518","tags":["button","geometric","radio"],"emoji":"🔘","text":"","type":1,"order":4947,"group":8,"subgroup":96,"version":0.6},{"label":"white square button","hexcode":"1F533","tags":["button","geometric","outlined","square","white"],"emoji":"🔳","text":"","type":1,"order":4948,"group":8,"subgroup":96,"version":0.6},{"label":"black square button","hexcode":"1F532","tags":["black","button","geometric","square"],"emoji":"🔲","text":"","type":1,"order":4949,"group":8,"subgroup":96,"version":0.6},{"label":"chequered flag","hexcode":"1F3C1","tags":["checkered","chequered","finish","flag","flags","game","race","racing","sport","win"],"emoji":"🏁","text":"","type":1,"order":4950,"group":9,"subgroup":97,"version":0.6},{"label":"triangular flag","hexcode":"1F6A9","tags":["construction","flag","golf","post","triangular"],"emoji":"🚩","text":"","type":1,"order":4951,"group":9,"subgroup":97,"version":0.6},{"label":"crossed flags","hexcode":"1F38C","tags":["celebration","cross","crossed","flags","japanese"],"emoji":"🎌","text":"","type":1,"order":4952,"group":9,"subgroup":97,"version":0.6},{"label":"black flag","hexcode":"1F3F4","tags":["black","flag","waving"],"emoji":"🏴","text":"","type":1,"order":4953,"group":9,"subgroup":97,"version":1},{"label":"white flag","hexcode":"1F3F3","tags":["flag","waving","white"],"emoji":"🏳️","text":"🏳︎","type":0,"order":4955,"group":9,"subgroup":97,"version":0.7},{"label":"rainbow flag","hexcode":"1F3F3-FE0F-200D-1F308","tags":["bisexual","flag","gay","genderqueer","glbt","glbtq","lesbian","lgbt","lgbtq","lgbtqia","pride","queer","rainbow","trans","transgender"],"emoji":"🏳️‍🌈","text":"","type":1,"order":4956,"group":9,"subgroup":97,"version":4},{"label":"transgender flag","hexcode":"1F3F3-FE0F-200D-26A7-FE0F","tags":["blue","flag","light","pink","transgender","white"],"emoji":"🏳️‍⚧️","text":"","type":1,"order":4958,"group":9,"subgroup":97,"version":13},{"label":"pirate flag","hexcode":"1F3F4-200D-2620-FE0F","tags":["flag","jolly","pirate","plunder","roger","treasure"],"emoji":"🏴‍☠️","text":"","type":1,"order":4962,"group":9,"subgroup":97,"version":11},{"label":"flag: Ascension Island","hexcode":"1F1E6-1F1E8","tags":["AC","flag"],"emoji":"🇦🇨","text":"","type":1,"order":4964,"group":9,"subgroup":98,"version":2},{"label":"flag: Andorra","hexcode":"1F1E6-1F1E9","tags":["AD","flag"],"emoji":"🇦🇩","text":"","type":1,"order":4965,"group":9,"subgroup":98,"version":2},{"label":"flag: United Arab Emirates","hexcode":"1F1E6-1F1EA","tags":["AE","flag"],"emoji":"🇦🇪","text":"","type":1,"order":4966,"group":9,"subgroup":98,"version":2},{"label":"flag: Afghanistan","hexcode":"1F1E6-1F1EB","tags":["AF","flag"],"emoji":"🇦🇫","text":"","type":1,"order":4967,"group":9,"subgroup":98,"version":2},{"label":"flag: Antigua & Barbuda","hexcode":"1F1E6-1F1EC","tags":["AG","flag"],"emoji":"🇦🇬","text":"","type":1,"order":4968,"group":9,"subgroup":98,"version":2},{"label":"flag: Anguilla","hexcode":"1F1E6-1F1EE","tags":["AI","flag"],"emoji":"🇦🇮","text":"","type":1,"order":4969,"group":9,"subgroup":98,"version":2},{"label":"flag: Albania","hexcode":"1F1E6-1F1F1","tags":["AL","flag"],"emoji":"🇦🇱","text":"","type":1,"order":4970,"group":9,"subgroup":98,"version":2},{"label":"flag: Armenia","hexcode":"1F1E6-1F1F2","tags":["AM","flag"],"emoji":"🇦🇲","text":"","type":1,"order":4971,"group":9,"subgroup":98,"version":2},{"label":"flag: Angola","hexcode":"1F1E6-1F1F4","tags":["AO","flag"],"emoji":"🇦🇴","text":"","type":1,"order":4972,"group":9,"subgroup":98,"version":2},{"label":"flag: Antarctica","hexcode":"1F1E6-1F1F6","tags":["AQ","flag"],"emoji":"🇦🇶","text":"","type":1,"order":4973,"group":9,"subgroup":98,"version":2},{"label":"flag: Argentina","hexcode":"1F1E6-1F1F7","tags":["AR","flag"],"emoji":"🇦🇷","text":"","type":1,"order":4974,"group":9,"subgroup":98,"version":2},{"label":"flag: American Samoa","hexcode":"1F1E6-1F1F8","tags":["AS","flag"],"emoji":"🇦🇸","text":"","type":1,"order":4975,"group":9,"subgroup":98,"version":2},{"label":"flag: Austria","hexcode":"1F1E6-1F1F9","tags":["AT","flag"],"emoji":"🇦🇹","text":"","type":1,"order":4976,"group":9,"subgroup":98,"version":2},{"label":"flag: Australia","hexcode":"1F1E6-1F1FA","tags":["AU","flag"],"emoji":"🇦🇺","text":"","type":1,"order":4977,"group":9,"subgroup":98,"version":2},{"label":"flag: Aruba","hexcode":"1F1E6-1F1FC","tags":["AW","flag"],"emoji":"🇦🇼","text":"","type":1,"order":4978,"group":9,"subgroup":98,"version":2},{"label":"flag: Åland Islands","hexcode":"1F1E6-1F1FD","tags":["AX","flag"],"emoji":"🇦🇽","text":"","type":1,"order":4979,"group":9,"subgroup":98,"version":2},{"label":"flag: Azerbaijan","hexcode":"1F1E6-1F1FF","tags":["AZ","flag"],"emoji":"🇦🇿","text":"","type":1,"order":4980,"group":9,"subgroup":98,"version":2},{"label":"flag: Bosnia & Herzegovina","hexcode":"1F1E7-1F1E6","tags":["BA","flag"],"emoji":"🇧🇦","text":"","type":1,"order":4981,"group":9,"subgroup":98,"version":2},{"label":"flag: Barbados","hexcode":"1F1E7-1F1E7","tags":["BB","flag"],"emoji":"🇧🇧","text":"","type":1,"order":4982,"group":9,"subgroup":98,"version":2},{"label":"flag: Bangladesh","hexcode":"1F1E7-1F1E9","tags":["BD","flag"],"emoji":"🇧🇩","text":"","type":1,"order":4983,"group":9,"subgroup":98,"version":2},{"label":"flag: Belgium","hexcode":"1F1E7-1F1EA","tags":["BE","flag"],"emoji":"🇧🇪","text":"","type":1,"order":4984,"group":9,"subgroup":98,"version":2},{"label":"flag: Burkina Faso","hexcode":"1F1E7-1F1EB","tags":["BF","flag"],"emoji":"🇧🇫","text":"","type":1,"order":4985,"group":9,"subgroup":98,"version":2},{"label":"flag: Bulgaria","hexcode":"1F1E7-1F1EC","tags":["BG","flag"],"emoji":"🇧🇬","text":"","type":1,"order":4986,"group":9,"subgroup":98,"version":2},{"label":"flag: Bahrain","hexcode":"1F1E7-1F1ED","tags":["BH","flag"],"emoji":"🇧🇭","text":"","type":1,"order":4987,"group":9,"subgroup":98,"version":2},{"label":"flag: Burundi","hexcode":"1F1E7-1F1EE","tags":["BI","flag"],"emoji":"🇧🇮","text":"","type":1,"order":4988,"group":9,"subgroup":98,"version":2},{"label":"flag: Benin","hexcode":"1F1E7-1F1EF","tags":["BJ","flag"],"emoji":"🇧🇯","text":"","type":1,"order":4989,"group":9,"subgroup":98,"version":2},{"label":"flag: St. Barthélemy","hexcode":"1F1E7-1F1F1","tags":["BL","flag"],"emoji":"🇧🇱","text":"","type":1,"order":4990,"group":9,"subgroup":98,"version":2},{"label":"flag: Bermuda","hexcode":"1F1E7-1F1F2","tags":["BM","flag"],"emoji":"🇧🇲","text":"","type":1,"order":4991,"group":9,"subgroup":98,"version":2},{"label":"flag: Brunei","hexcode":"1F1E7-1F1F3","tags":["BN","flag"],"emoji":"🇧🇳","text":"","type":1,"order":4992,"group":9,"subgroup":98,"version":2},{"label":"flag: Bolivia","hexcode":"1F1E7-1F1F4","tags":["BO","flag"],"emoji":"🇧🇴","text":"","type":1,"order":4993,"group":9,"subgroup":98,"version":2},{"label":"flag: Caribbean Netherlands","hexcode":"1F1E7-1F1F6","tags":["BQ","flag"],"emoji":"🇧🇶","text":"","type":1,"order":4994,"group":9,"subgroup":98,"version":2},{"label":"flag: Brazil","hexcode":"1F1E7-1F1F7","tags":["BR","flag"],"emoji":"🇧🇷","text":"","type":1,"order":4995,"group":9,"subgroup":98,"version":2},{"label":"flag: Bahamas","hexcode":"1F1E7-1F1F8","tags":["BS","flag"],"emoji":"🇧🇸","text":"","type":1,"order":4996,"group":9,"subgroup":98,"version":2},{"label":"flag: Bhutan","hexcode":"1F1E7-1F1F9","tags":["BT","flag"],"emoji":"🇧🇹","text":"","type":1,"order":4997,"group":9,"subgroup":98,"version":2},{"label":"flag: Bouvet Island","hexcode":"1F1E7-1F1FB","tags":["BV","flag"],"emoji":"🇧🇻","text":"","type":1,"order":4998,"group":9,"subgroup":98,"version":2},{"label":"flag: Botswana","hexcode":"1F1E7-1F1FC","tags":["BW","flag"],"emoji":"🇧🇼","text":"","type":1,"order":4999,"group":9,"subgroup":98,"version":2},{"label":"flag: Belarus","hexcode":"1F1E7-1F1FE","tags":["BY","flag"],"emoji":"🇧🇾","text":"","type":1,"order":5000,"group":9,"subgroup":98,"version":2},{"label":"flag: Belize","hexcode":"1F1E7-1F1FF","tags":["BZ","flag"],"emoji":"🇧🇿","text":"","type":1,"order":5001,"group":9,"subgroup":98,"version":2},{"label":"flag: Canada","hexcode":"1F1E8-1F1E6","tags":["CA","flag"],"emoji":"🇨🇦","text":"","type":1,"order":5002,"group":9,"subgroup":98,"version":2},{"label":"flag: Cocos (Keeling) Islands","hexcode":"1F1E8-1F1E8","tags":["CC","flag"],"emoji":"🇨🇨","text":"","type":1,"order":5003,"group":9,"subgroup":98,"version":2},{"label":"flag: Congo - Kinshasa","hexcode":"1F1E8-1F1E9","tags":["CD","flag"],"emoji":"🇨🇩","text":"","type":1,"order":5004,"group":9,"subgroup":98,"version":2},{"label":"flag: Central African Republic","hexcode":"1F1E8-1F1EB","tags":["CF","flag"],"emoji":"🇨🇫","text":"","type":1,"order":5005,"group":9,"subgroup":98,"version":2},{"label":"flag: Congo - Brazzaville","hexcode":"1F1E8-1F1EC","tags":["CG","flag"],"emoji":"🇨🇬","text":"","type":1,"order":5006,"group":9,"subgroup":98,"version":2},{"label":"flag: Switzerland","hexcode":"1F1E8-1F1ED","tags":["CH","flag"],"emoji":"🇨🇭","text":"","type":1,"order":5007,"group":9,"subgroup":98,"version":2},{"label":"flag: Côte d’Ivoire","hexcode":"1F1E8-1F1EE","tags":["CI","flag"],"emoji":"🇨🇮","text":"","type":1,"order":5008,"group":9,"subgroup":98,"version":2},{"label":"flag: Cook Islands","hexcode":"1F1E8-1F1F0","tags":["CK","flag"],"emoji":"🇨🇰","text":"","type":1,"order":5009,"group":9,"subgroup":98,"version":2},{"label":"flag: Chile","hexcode":"1F1E8-1F1F1","tags":["CL","flag"],"emoji":"🇨🇱","text":"","type":1,"order":5010,"group":9,"subgroup":98,"version":2},{"label":"flag: Cameroon","hexcode":"1F1E8-1F1F2","tags":["CM","flag"],"emoji":"🇨🇲","text":"","type":1,"order":5011,"group":9,"subgroup":98,"version":2},{"label":"flag: China","hexcode":"1F1E8-1F1F3","tags":["CN","flag"],"emoji":"🇨🇳","text":"","type":1,"order":5012,"group":9,"subgroup":98,"version":0.6},{"label":"flag: Colombia","hexcode":"1F1E8-1F1F4","tags":["CO","flag"],"emoji":"🇨🇴","text":"","type":1,"order":5013,"group":9,"subgroup":98,"version":2},{"label":"flag: Clipperton Island","hexcode":"1F1E8-1F1F5","tags":["CP","flag"],"emoji":"🇨🇵","text":"","type":1,"order":5014,"group":9,"subgroup":98,"version":2},{"label":"flag: Sark","hexcode":"1F1E8-1F1F6","tags":["CQ","flag"],"emoji":"🇨🇶","text":"","type":1,"order":5015,"group":9,"subgroup":98,"version":16},{"label":"flag: Costa Rica","hexcode":"1F1E8-1F1F7","tags":["CR","flag"],"emoji":"🇨🇷","text":"","type":1,"order":5016,"group":9,"subgroup":98,"version":2},{"label":"flag: Cuba","hexcode":"1F1E8-1F1FA","tags":["CU","flag"],"emoji":"🇨🇺","text":"","type":1,"order":5017,"group":9,"subgroup":98,"version":2},{"label":"flag: Cape Verde","hexcode":"1F1E8-1F1FB","tags":["CV","flag"],"emoji":"🇨🇻","text":"","type":1,"order":5018,"group":9,"subgroup":98,"version":2},{"label":"flag: Curaçao","hexcode":"1F1E8-1F1FC","tags":["CW","flag"],"emoji":"🇨🇼","text":"","type":1,"order":5019,"group":9,"subgroup":98,"version":2},{"label":"flag: Christmas Island","hexcode":"1F1E8-1F1FD","tags":["CX","flag"],"emoji":"🇨🇽","text":"","type":1,"order":5020,"group":9,"subgroup":98,"version":2},{"label":"flag: Cyprus","hexcode":"1F1E8-1F1FE","tags":["CY","flag"],"emoji":"🇨🇾","text":"","type":1,"order":5021,"group":9,"subgroup":98,"version":2},{"label":"flag: Czechia","hexcode":"1F1E8-1F1FF","tags":["CZ","flag"],"emoji":"🇨🇿","text":"","type":1,"order":5022,"group":9,"subgroup":98,"version":2},{"label":"flag: Germany","hexcode":"1F1E9-1F1EA","tags":["DE","flag"],"emoji":"🇩🇪","text":"","type":1,"order":5023,"group":9,"subgroup":98,"version":0.6},{"label":"flag: Diego Garcia","hexcode":"1F1E9-1F1EC","tags":["DG","flag"],"emoji":"🇩🇬","text":"","type":1,"order":5024,"group":9,"subgroup":98,"version":2},{"label":"flag: Djibouti","hexcode":"1F1E9-1F1EF","tags":["DJ","flag"],"emoji":"🇩🇯","text":"","type":1,"order":5025,"group":9,"subgroup":98,"version":2},{"label":"flag: Denmark","hexcode":"1F1E9-1F1F0","tags":["DK","flag"],"emoji":"🇩🇰","text":"","type":1,"order":5026,"group":9,"subgroup":98,"version":2},{"label":"flag: Dominica","hexcode":"1F1E9-1F1F2","tags":["DM","flag"],"emoji":"🇩🇲","text":"","type":1,"order":5027,"group":9,"subgroup":98,"version":2},{"label":"flag: Dominican Republic","hexcode":"1F1E9-1F1F4","tags":["DO","flag"],"emoji":"🇩🇴","text":"","type":1,"order":5028,"group":9,"subgroup":98,"version":2},{"label":"flag: Algeria","hexcode":"1F1E9-1F1FF","tags":["DZ","flag"],"emoji":"🇩🇿","text":"","type":1,"order":5029,"group":9,"subgroup":98,"version":2},{"label":"flag: Ceuta & Melilla","hexcode":"1F1EA-1F1E6","tags":["EA","flag"],"emoji":"🇪🇦","text":"","type":1,"order":5030,"group":9,"subgroup":98,"version":2},{"label":"flag: Ecuador","hexcode":"1F1EA-1F1E8","tags":["EC","flag"],"emoji":"🇪🇨","text":"","type":1,"order":5031,"group":9,"subgroup":98,"version":2},{"label":"flag: Estonia","hexcode":"1F1EA-1F1EA","tags":["EE","flag"],"emoji":"🇪🇪","text":"","type":1,"order":5032,"group":9,"subgroup":98,"version":2},{"label":"flag: Egypt","hexcode":"1F1EA-1F1EC","tags":["EG","flag"],"emoji":"🇪🇬","text":"","type":1,"order":5033,"group":9,"subgroup":98,"version":2},{"label":"flag: Western Sahara","hexcode":"1F1EA-1F1ED","tags":["EH","flag"],"emoji":"🇪🇭","text":"","type":1,"order":5034,"group":9,"subgroup":98,"version":2},{"label":"flag: Eritrea","hexcode":"1F1EA-1F1F7","tags":["ER","flag"],"emoji":"🇪🇷","text":"","type":1,"order":5035,"group":9,"subgroup":98,"version":2},{"label":"flag: Spain","hexcode":"1F1EA-1F1F8","tags":["ES","flag"],"emoji":"🇪🇸","text":"","type":1,"order":5036,"group":9,"subgroup":98,"version":0.6},{"label":"flag: Ethiopia","hexcode":"1F1EA-1F1F9","tags":["ET","flag"],"emoji":"🇪🇹","text":"","type":1,"order":5037,"group":9,"subgroup":98,"version":2},{"label":"flag: European Union","hexcode":"1F1EA-1F1FA","tags":["EU","flag"],"emoji":"🇪🇺","text":"","type":1,"order":5038,"group":9,"subgroup":98,"version":2},{"label":"flag: Finland","hexcode":"1F1EB-1F1EE","tags":["FI","flag"],"emoji":"🇫🇮","text":"","type":1,"order":5039,"group":9,"subgroup":98,"version":2},{"label":"flag: Fiji","hexcode":"1F1EB-1F1EF","tags":["FJ","flag"],"emoji":"🇫🇯","text":"","type":1,"order":5040,"group":9,"subgroup":98,"version":2},{"label":"flag: Falkland Islands","hexcode":"1F1EB-1F1F0","tags":["FK","flag"],"emoji":"🇫🇰","text":"","type":1,"order":5041,"group":9,"subgroup":98,"version":2},{"label":"flag: Micronesia","hexcode":"1F1EB-1F1F2","tags":["FM","flag"],"emoji":"🇫🇲","text":"","type":1,"order":5042,"group":9,"subgroup":98,"version":2},{"label":"flag: Faroe Islands","hexcode":"1F1EB-1F1F4","tags":["FO","flag"],"emoji":"🇫🇴","text":"","type":1,"order":5043,"group":9,"subgroup":98,"version":2},{"label":"flag: France","hexcode":"1F1EB-1F1F7","tags":["FR","flag"],"emoji":"🇫🇷","text":"","type":1,"order":5044,"group":9,"subgroup":98,"version":0.6},{"label":"flag: Gabon","hexcode":"1F1EC-1F1E6","tags":["GA","flag"],"emoji":"🇬🇦","text":"","type":1,"order":5045,"group":9,"subgroup":98,"version":2},{"label":"flag: United Kingdom","hexcode":"1F1EC-1F1E7","tags":["GB","flag"],"emoji":"🇬🇧","text":"","type":1,"order":5046,"group":9,"subgroup":98,"version":0.6},{"label":"flag: Grenada","hexcode":"1F1EC-1F1E9","tags":["GD","flag"],"emoji":"🇬🇩","text":"","type":1,"order":5047,"group":9,"subgroup":98,"version":2},{"label":"flag: Georgia","hexcode":"1F1EC-1F1EA","tags":["GE","flag"],"emoji":"🇬🇪","text":"","type":1,"order":5048,"group":9,"subgroup":98,"version":2},{"label":"flag: French Guiana","hexcode":"1F1EC-1F1EB","tags":["GF","flag"],"emoji":"🇬🇫","text":"","type":1,"order":5049,"group":9,"subgroup":98,"version":2},{"label":"flag: Guernsey","hexcode":"1F1EC-1F1EC","tags":["GG","flag"],"emoji":"🇬🇬","text":"","type":1,"order":5050,"group":9,"subgroup":98,"version":2},{"label":"flag: Ghana","hexcode":"1F1EC-1F1ED","tags":["GH","flag"],"emoji":"🇬🇭","text":"","type":1,"order":5051,"group":9,"subgroup":98,"version":2},{"label":"flag: Gibraltar","hexcode":"1F1EC-1F1EE","tags":["GI","flag"],"emoji":"🇬🇮","text":"","type":1,"order":5052,"group":9,"subgroup":98,"version":2},{"label":"flag: Greenland","hexcode":"1F1EC-1F1F1","tags":["GL","flag"],"emoji":"🇬🇱","text":"","type":1,"order":5053,"group":9,"subgroup":98,"version":2},{"label":"flag: Gambia","hexcode":"1F1EC-1F1F2","tags":["GM","flag"],"emoji":"🇬🇲","text":"","type":1,"order":5054,"group":9,"subgroup":98,"version":2},{"label":"flag: Guinea","hexcode":"1F1EC-1F1F3","tags":["GN","flag"],"emoji":"🇬🇳","text":"","type":1,"order":5055,"group":9,"subgroup":98,"version":2},{"label":"flag: Guadeloupe","hexcode":"1F1EC-1F1F5","tags":["GP","flag"],"emoji":"🇬🇵","text":"","type":1,"order":5056,"group":9,"subgroup":98,"version":2},{"label":"flag: Equatorial Guinea","hexcode":"1F1EC-1F1F6","tags":["GQ","flag"],"emoji":"🇬🇶","text":"","type":1,"order":5057,"group":9,"subgroup":98,"version":2},{"label":"flag: Greece","hexcode":"1F1EC-1F1F7","tags":["GR","flag"],"emoji":"🇬🇷","text":"","type":1,"order":5058,"group":9,"subgroup":98,"version":2},{"label":"flag: South Georgia & South Sandwich Islands","hexcode":"1F1EC-1F1F8","tags":["GS","flag"],"emoji":"🇬🇸","text":"","type":1,"order":5059,"group":9,"subgroup":98,"version":2},{"label":"flag: Guatemala","hexcode":"1F1EC-1F1F9","tags":["GT","flag"],"emoji":"🇬🇹","text":"","type":1,"order":5060,"group":9,"subgroup":98,"version":2},{"label":"flag: Guam","hexcode":"1F1EC-1F1FA","tags":["GU","flag"],"emoji":"🇬🇺","text":"","type":1,"order":5061,"group":9,"subgroup":98,"version":2},{"label":"flag: Guinea-Bissau","hexcode":"1F1EC-1F1FC","tags":["GW","flag"],"emoji":"🇬🇼","text":"","type":1,"order":5062,"group":9,"subgroup":98,"version":2},{"label":"flag: Guyana","hexcode":"1F1EC-1F1FE","tags":["GY","flag"],"emoji":"🇬🇾","text":"","type":1,"order":5063,"group":9,"subgroup":98,"version":2},{"label":"flag: Hong Kong SAR China","hexcode":"1F1ED-1F1F0","tags":["HK","flag"],"emoji":"🇭🇰","text":"","type":1,"order":5064,"group":9,"subgroup":98,"version":2},{"label":"flag: Heard & McDonald Islands","hexcode":"1F1ED-1F1F2","tags":["HM","flag"],"emoji":"🇭🇲","text":"","type":1,"order":5065,"group":9,"subgroup":98,"version":2},{"label":"flag: Honduras","hexcode":"1F1ED-1F1F3","tags":["HN","flag"],"emoji":"🇭🇳","text":"","type":1,"order":5066,"group":9,"subgroup":98,"version":2},{"label":"flag: Croatia","hexcode":"1F1ED-1F1F7","tags":["HR","flag"],"emoji":"🇭🇷","text":"","type":1,"order":5067,"group":9,"subgroup":98,"version":2},{"label":"flag: Haiti","hexcode":"1F1ED-1F1F9","tags":["HT","flag"],"emoji":"🇭🇹","text":"","type":1,"order":5068,"group":9,"subgroup":98,"version":2},{"label":"flag: Hungary","hexcode":"1F1ED-1F1FA","tags":["HU","flag"],"emoji":"🇭🇺","text":"","type":1,"order":5069,"group":9,"subgroup":98,"version":2},{"label":"flag: Canary Islands","hexcode":"1F1EE-1F1E8","tags":["IC","flag"],"emoji":"🇮🇨","text":"","type":1,"order":5070,"group":9,"subgroup":98,"version":2},{"label":"flag: Indonesia","hexcode":"1F1EE-1F1E9","tags":["ID","flag"],"emoji":"🇮🇩","text":"","type":1,"order":5071,"group":9,"subgroup":98,"version":2},{"label":"flag: Ireland","hexcode":"1F1EE-1F1EA","tags":["IE","flag"],"emoji":"🇮🇪","text":"","type":1,"order":5072,"group":9,"subgroup":98,"version":2},{"label":"flag: Israel","hexcode":"1F1EE-1F1F1","tags":["IL","flag"],"emoji":"🇮🇱","text":"","type":1,"order":5073,"group":9,"subgroup":98,"version":2},{"label":"flag: Isle of Man","hexcode":"1F1EE-1F1F2","tags":["IM","flag"],"emoji":"🇮🇲","text":"","type":1,"order":5074,"group":9,"subgroup":98,"version":2},{"label":"flag: India","hexcode":"1F1EE-1F1F3","tags":["IN","flag"],"emoji":"🇮🇳","text":"","type":1,"order":5075,"group":9,"subgroup":98,"version":2},{"label":"flag: British Indian Ocean Territory","hexcode":"1F1EE-1F1F4","tags":["IO","flag"],"emoji":"🇮🇴","text":"","type":1,"order":5076,"group":9,"subgroup":98,"version":2},{"label":"flag: Iraq","hexcode":"1F1EE-1F1F6","tags":["IQ","flag"],"emoji":"🇮🇶","text":"","type":1,"order":5077,"group":9,"subgroup":98,"version":2},{"label":"flag: Iran","hexcode":"1F1EE-1F1F7","tags":["IR","flag"],"emoji":"🇮🇷","text":"","type":1,"order":5078,"group":9,"subgroup":98,"version":2},{"label":"flag: Iceland","hexcode":"1F1EE-1F1F8","tags":["IS","flag"],"emoji":"🇮🇸","text":"","type":1,"order":5079,"group":9,"subgroup":98,"version":2},{"label":"flag: Italy","hexcode":"1F1EE-1F1F9","tags":["IT","flag"],"emoji":"🇮🇹","text":"","type":1,"order":5080,"group":9,"subgroup":98,"version":0.6},{"label":"flag: Jersey","hexcode":"1F1EF-1F1EA","tags":["JE","flag"],"emoji":"🇯🇪","text":"","type":1,"order":5081,"group":9,"subgroup":98,"version":2},{"label":"flag: Jamaica","hexcode":"1F1EF-1F1F2","tags":["JM","flag"],"emoji":"🇯🇲","text":"","type":1,"order":5082,"group":9,"subgroup":98,"version":2},{"label":"flag: Jordan","hexcode":"1F1EF-1F1F4","tags":["JO","flag"],"emoji":"🇯🇴","text":"","type":1,"order":5083,"group":9,"subgroup":98,"version":2},{"label":"flag: Japan","hexcode":"1F1EF-1F1F5","tags":["JP","flag"],"emoji":"🇯🇵","text":"","type":1,"order":5084,"group":9,"subgroup":98,"version":0.6},{"label":"flag: Kenya","hexcode":"1F1F0-1F1EA","tags":["KE","flag"],"emoji":"🇰🇪","text":"","type":1,"order":5085,"group":9,"subgroup":98,"version":2},{"label":"flag: Kyrgyzstan","hexcode":"1F1F0-1F1EC","tags":["KG","flag"],"emoji":"🇰🇬","text":"","type":1,"order":5086,"group":9,"subgroup":98,"version":2},{"label":"flag: Cambodia","hexcode":"1F1F0-1F1ED","tags":["KH","flag"],"emoji":"🇰🇭","text":"","type":1,"order":5087,"group":9,"subgroup":98,"version":2},{"label":"flag: Kiribati","hexcode":"1F1F0-1F1EE","tags":["KI","flag"],"emoji":"🇰🇮","text":"","type":1,"order":5088,"group":9,"subgroup":98,"version":2},{"label":"flag: Comoros","hexcode":"1F1F0-1F1F2","tags":["KM","flag"],"emoji":"🇰🇲","text":"","type":1,"order":5089,"group":9,"subgroup":98,"version":2},{"label":"flag: St. Kitts & Nevis","hexcode":"1F1F0-1F1F3","tags":["KN","flag"],"emoji":"🇰🇳","text":"","type":1,"order":5090,"group":9,"subgroup":98,"version":2},{"label":"flag: North Korea","hexcode":"1F1F0-1F1F5","tags":["KP","flag"],"emoji":"🇰🇵","text":"","type":1,"order":5091,"group":9,"subgroup":98,"version":2},{"label":"flag: South Korea","hexcode":"1F1F0-1F1F7","tags":["KR","flag"],"emoji":"🇰🇷","text":"","type":1,"order":5092,"group":9,"subgroup":98,"version":0.6},{"label":"flag: Kuwait","hexcode":"1F1F0-1F1FC","tags":["KW","flag"],"emoji":"🇰🇼","text":"","type":1,"order":5093,"group":9,"subgroup":98,"version":2},{"label":"flag: Cayman Islands","hexcode":"1F1F0-1F1FE","tags":["KY","flag"],"emoji":"🇰🇾","text":"","type":1,"order":5094,"group":9,"subgroup":98,"version":2},{"label":"flag: Kazakhstan","hexcode":"1F1F0-1F1FF","tags":["KZ","flag"],"emoji":"🇰🇿","text":"","type":1,"order":5095,"group":9,"subgroup":98,"version":2},{"label":"flag: Laos","hexcode":"1F1F1-1F1E6","tags":["LA","flag"],"emoji":"🇱🇦","text":"","type":1,"order":5096,"group":9,"subgroup":98,"version":2},{"label":"flag: Lebanon","hexcode":"1F1F1-1F1E7","tags":["LB","flag"],"emoji":"🇱🇧","text":"","type":1,"order":5097,"group":9,"subgroup":98,"version":2},{"label":"flag: St. Lucia","hexcode":"1F1F1-1F1E8","tags":["LC","flag"],"emoji":"🇱🇨","text":"","type":1,"order":5098,"group":9,"subgroup":98,"version":2},{"label":"flag: Liechtenstein","hexcode":"1F1F1-1F1EE","tags":["LI","flag"],"emoji":"🇱🇮","text":"","type":1,"order":5099,"group":9,"subgroup":98,"version":2},{"label":"flag: Sri Lanka","hexcode":"1F1F1-1F1F0","tags":["LK","flag"],"emoji":"🇱🇰","text":"","type":1,"order":5100,"group":9,"subgroup":98,"version":2},{"label":"flag: Liberia","hexcode":"1F1F1-1F1F7","tags":["LR","flag"],"emoji":"🇱🇷","text":"","type":1,"order":5101,"group":9,"subgroup":98,"version":2},{"label":"flag: Lesotho","hexcode":"1F1F1-1F1F8","tags":["LS","flag"],"emoji":"🇱🇸","text":"","type":1,"order":5102,"group":9,"subgroup":98,"version":2},{"label":"flag: Lithuania","hexcode":"1F1F1-1F1F9","tags":["LT","flag"],"emoji":"🇱🇹","text":"","type":1,"order":5103,"group":9,"subgroup":98,"version":2},{"label":"flag: Luxembourg","hexcode":"1F1F1-1F1FA","tags":["LU","flag"],"emoji":"🇱🇺","text":"","type":1,"order":5104,"group":9,"subgroup":98,"version":2},{"label":"flag: Latvia","hexcode":"1F1F1-1F1FB","tags":["LV","flag"],"emoji":"🇱🇻","text":"","type":1,"order":5105,"group":9,"subgroup":98,"version":2},{"label":"flag: Libya","hexcode":"1F1F1-1F1FE","tags":["LY","flag"],"emoji":"🇱🇾","text":"","type":1,"order":5106,"group":9,"subgroup":98,"version":2},{"label":"flag: Morocco","hexcode":"1F1F2-1F1E6","tags":["MA","flag"],"emoji":"🇲🇦","text":"","type":1,"order":5107,"group":9,"subgroup":98,"version":2},{"label":"flag: Monaco","hexcode":"1F1F2-1F1E8","tags":["MC","flag"],"emoji":"🇲🇨","text":"","type":1,"order":5108,"group":9,"subgroup":98,"version":2},{"label":"flag: Moldova","hexcode":"1F1F2-1F1E9","tags":["MD","flag"],"emoji":"🇲🇩","text":"","type":1,"order":5109,"group":9,"subgroup":98,"version":2},{"label":"flag: Montenegro","hexcode":"1F1F2-1F1EA","tags":["ME","flag"],"emoji":"🇲🇪","text":"","type":1,"order":5110,"group":9,"subgroup":98,"version":2},{"label":"flag: St. Martin","hexcode":"1F1F2-1F1EB","tags":["MF","flag"],"emoji":"🇲🇫","text":"","type":1,"order":5111,"group":9,"subgroup":98,"version":2},{"label":"flag: Madagascar","hexcode":"1F1F2-1F1EC","tags":["MG","flag"],"emoji":"🇲🇬","text":"","type":1,"order":5112,"group":9,"subgroup":98,"version":2},{"label":"flag: Marshall Islands","hexcode":"1F1F2-1F1ED","tags":["MH","flag"],"emoji":"🇲🇭","text":"","type":1,"order":5113,"group":9,"subgroup":98,"version":2},{"label":"flag: North Macedonia","hexcode":"1F1F2-1F1F0","tags":["MK","flag"],"emoji":"🇲🇰","text":"","type":1,"order":5114,"group":9,"subgroup":98,"version":2},{"label":"flag: Mali","hexcode":"1F1F2-1F1F1","tags":["ML","flag"],"emoji":"🇲🇱","text":"","type":1,"order":5115,"group":9,"subgroup":98,"version":2},{"label":"flag: Myanmar (Burma)","hexcode":"1F1F2-1F1F2","tags":["MM","flag"],"emoji":"🇲🇲","text":"","type":1,"order":5116,"group":9,"subgroup":98,"version":2},{"label":"flag: Mongolia","hexcode":"1F1F2-1F1F3","tags":["MN","flag"],"emoji":"🇲🇳","text":"","type":1,"order":5117,"group":9,"subgroup":98,"version":2},{"label":"flag: Macao SAR China","hexcode":"1F1F2-1F1F4","tags":["MO","flag"],"emoji":"🇲🇴","text":"","type":1,"order":5118,"group":9,"subgroup":98,"version":2},{"label":"flag: Northern Mariana Islands","hexcode":"1F1F2-1F1F5","tags":["MP","flag"],"emoji":"🇲🇵","text":"","type":1,"order":5119,"group":9,"subgroup":98,"version":2},{"label":"flag: Martinique","hexcode":"1F1F2-1F1F6","tags":["MQ","flag"],"emoji":"🇲🇶","text":"","type":1,"order":5120,"group":9,"subgroup":98,"version":2},{"label":"flag: Mauritania","hexcode":"1F1F2-1F1F7","tags":["MR","flag"],"emoji":"🇲🇷","text":"","type":1,"order":5121,"group":9,"subgroup":98,"version":2},{"label":"flag: Montserrat","hexcode":"1F1F2-1F1F8","tags":["MS","flag"],"emoji":"🇲🇸","text":"","type":1,"order":5122,"group":9,"subgroup":98,"version":2},{"label":"flag: Malta","hexcode":"1F1F2-1F1F9","tags":["MT","flag"],"emoji":"🇲🇹","text":"","type":1,"order":5123,"group":9,"subgroup":98,"version":2},{"label":"flag: Mauritius","hexcode":"1F1F2-1F1FA","tags":["MU","flag"],"emoji":"🇲🇺","text":"","type":1,"order":5124,"group":9,"subgroup":98,"version":2},{"label":"flag: Maldives","hexcode":"1F1F2-1F1FB","tags":["MV","flag"],"emoji":"🇲🇻","text":"","type":1,"order":5125,"group":9,"subgroup":98,"version":2},{"label":"flag: Malawi","hexcode":"1F1F2-1F1FC","tags":["MW","flag"],"emoji":"🇲🇼","text":"","type":1,"order":5126,"group":9,"subgroup":98,"version":2},{"label":"flag: Mexico","hexcode":"1F1F2-1F1FD","tags":["MX","flag"],"emoji":"🇲🇽","text":"","type":1,"order":5127,"group":9,"subgroup":98,"version":2},{"label":"flag: Malaysia","hexcode":"1F1F2-1F1FE","tags":["MY","flag"],"emoji":"🇲🇾","text":"","type":1,"order":5128,"group":9,"subgroup":98,"version":2},{"label":"flag: Mozambique","hexcode":"1F1F2-1F1FF","tags":["MZ","flag"],"emoji":"🇲🇿","text":"","type":1,"order":5129,"group":9,"subgroup":98,"version":2},{"label":"flag: Namibia","hexcode":"1F1F3-1F1E6","tags":["NA","flag"],"emoji":"🇳🇦","text":"","type":1,"order":5130,"group":9,"subgroup":98,"version":2},{"label":"flag: New Caledonia","hexcode":"1F1F3-1F1E8","tags":["NC","flag"],"emoji":"🇳🇨","text":"","type":1,"order":5131,"group":9,"subgroup":98,"version":2},{"label":"flag: Niger","hexcode":"1F1F3-1F1EA","tags":["NE","flag"],"emoji":"🇳🇪","text":"","type":1,"order":5132,"group":9,"subgroup":98,"version":2},{"label":"flag: Norfolk Island","hexcode":"1F1F3-1F1EB","tags":["NF","flag"],"emoji":"🇳🇫","text":"","type":1,"order":5133,"group":9,"subgroup":98,"version":2},{"label":"flag: Nigeria","hexcode":"1F1F3-1F1EC","tags":["NG","flag"],"emoji":"🇳🇬","text":"","type":1,"order":5134,"group":9,"subgroup":98,"version":2},{"label":"flag: Nicaragua","hexcode":"1F1F3-1F1EE","tags":["NI","flag"],"emoji":"🇳🇮","text":"","type":1,"order":5135,"group":9,"subgroup":98,"version":2},{"label":"flag: Netherlands","hexcode":"1F1F3-1F1F1","tags":["NL","flag"],"emoji":"🇳🇱","text":"","type":1,"order":5136,"group":9,"subgroup":98,"version":2},{"label":"flag: Norway","hexcode":"1F1F3-1F1F4","tags":["NO","flag"],"emoji":"🇳🇴","text":"","type":1,"order":5137,"group":9,"subgroup":98,"version":2},{"label":"flag: Nepal","hexcode":"1F1F3-1F1F5","tags":["NP","flag"],"emoji":"🇳🇵","text":"","type":1,"order":5138,"group":9,"subgroup":98,"version":2},{"label":"flag: Nauru","hexcode":"1F1F3-1F1F7","tags":["NR","flag"],"emoji":"🇳🇷","text":"","type":1,"order":5139,"group":9,"subgroup":98,"version":2},{"label":"flag: Niue","hexcode":"1F1F3-1F1FA","tags":["NU","flag"],"emoji":"🇳🇺","text":"","type":1,"order":5140,"group":9,"subgroup":98,"version":2},{"label":"flag: New Zealand","hexcode":"1F1F3-1F1FF","tags":["NZ","flag"],"emoji":"🇳🇿","text":"","type":1,"order":5141,"group":9,"subgroup":98,"version":2},{"label":"flag: Oman","hexcode":"1F1F4-1F1F2","tags":["OM","flag"],"emoji":"🇴🇲","text":"","type":1,"order":5142,"group":9,"subgroup":98,"version":2},{"label":"flag: Panama","hexcode":"1F1F5-1F1E6","tags":["PA","flag"],"emoji":"🇵🇦","text":"","type":1,"order":5143,"group":9,"subgroup":98,"version":2},{"label":"flag: Peru","hexcode":"1F1F5-1F1EA","tags":["PE","flag"],"emoji":"🇵🇪","text":"","type":1,"order":5144,"group":9,"subgroup":98,"version":2},{"label":"flag: French Polynesia","hexcode":"1F1F5-1F1EB","tags":["PF","flag"],"emoji":"🇵🇫","text":"","type":1,"order":5145,"group":9,"subgroup":98,"version":2},{"label":"flag: Papua New Guinea","hexcode":"1F1F5-1F1EC","tags":["PG","flag"],"emoji":"🇵🇬","text":"","type":1,"order":5146,"group":9,"subgroup":98,"version":2},{"label":"flag: Philippines","hexcode":"1F1F5-1F1ED","tags":["PH","flag"],"emoji":"🇵🇭","text":"","type":1,"order":5147,"group":9,"subgroup":98,"version":2},{"label":"flag: Pakistan","hexcode":"1F1F5-1F1F0","tags":["PK","flag"],"emoji":"🇵🇰","text":"","type":1,"order":5148,"group":9,"subgroup":98,"version":2},{"label":"flag: Poland","hexcode":"1F1F5-1F1F1","tags":["PL","flag"],"emoji":"🇵🇱","text":"","type":1,"order":5149,"group":9,"subgroup":98,"version":2},{"label":"flag: St. Pierre & Miquelon","hexcode":"1F1F5-1F1F2","tags":["PM","flag"],"emoji":"🇵🇲","text":"","type":1,"order":5150,"group":9,"subgroup":98,"version":2},{"label":"flag: Pitcairn Islands","hexcode":"1F1F5-1F1F3","tags":["PN","flag"],"emoji":"🇵🇳","text":"","type":1,"order":5151,"group":9,"subgroup":98,"version":2},{"label":"flag: Puerto Rico","hexcode":"1F1F5-1F1F7","tags":["PR","flag"],"emoji":"🇵🇷","text":"","type":1,"order":5152,"group":9,"subgroup":98,"version":2},{"label":"flag: Palestinian Territories","hexcode":"1F1F5-1F1F8","tags":["PS","flag"],"emoji":"🇵🇸","text":"","type":1,"order":5153,"group":9,"subgroup":98,"version":2},{"label":"flag: Portugal","hexcode":"1F1F5-1F1F9","tags":["PT","flag"],"emoji":"🇵🇹","text":"","type":1,"order":5154,"group":9,"subgroup":98,"version":2},{"label":"flag: Palau","hexcode":"1F1F5-1F1FC","tags":["PW","flag"],"emoji":"🇵🇼","text":"","type":1,"order":5155,"group":9,"subgroup":98,"version":2},{"label":"flag: Paraguay","hexcode":"1F1F5-1F1FE","tags":["PY","flag"],"emoji":"🇵🇾","text":"","type":1,"order":5156,"group":9,"subgroup":98,"version":2},{"label":"flag: Qatar","hexcode":"1F1F6-1F1E6","tags":["QA","flag"],"emoji":"🇶🇦","text":"","type":1,"order":5157,"group":9,"subgroup":98,"version":2},{"label":"flag: Réunion","hexcode":"1F1F7-1F1EA","tags":["RE","flag"],"emoji":"🇷🇪","text":"","type":1,"order":5158,"group":9,"subgroup":98,"version":2},{"label":"flag: Romania","hexcode":"1F1F7-1F1F4","tags":["RO","flag"],"emoji":"🇷🇴","text":"","type":1,"order":5159,"group":9,"subgroup":98,"version":2},{"label":"flag: Serbia","hexcode":"1F1F7-1F1F8","tags":["RS","flag"],"emoji":"🇷🇸","text":"","type":1,"order":5160,"group":9,"subgroup":98,"version":2},{"label":"flag: Russia","hexcode":"1F1F7-1F1FA","tags":["RU","flag"],"emoji":"🇷🇺","text":"","type":1,"order":5161,"group":9,"subgroup":98,"version":0.6},{"label":"flag: Rwanda","hexcode":"1F1F7-1F1FC","tags":["RW","flag"],"emoji":"🇷🇼","text":"","type":1,"order":5162,"group":9,"subgroup":98,"version":2},{"label":"flag: Saudi Arabia","hexcode":"1F1F8-1F1E6","tags":["SA","flag"],"emoji":"🇸🇦","text":"","type":1,"order":5163,"group":9,"subgroup":98,"version":2},{"label":"flag: Solomon Islands","hexcode":"1F1F8-1F1E7","tags":["SB","flag"],"emoji":"🇸🇧","text":"","type":1,"order":5164,"group":9,"subgroup":98,"version":2},{"label":"flag: Seychelles","hexcode":"1F1F8-1F1E8","tags":["SC","flag"],"emoji":"🇸🇨","text":"","type":1,"order":5165,"group":9,"subgroup":98,"version":2},{"label":"flag: Sudan","hexcode":"1F1F8-1F1E9","tags":["SD","flag"],"emoji":"🇸🇩","text":"","type":1,"order":5166,"group":9,"subgroup":98,"version":2},{"label":"flag: Sweden","hexcode":"1F1F8-1F1EA","tags":["SE","flag"],"emoji":"🇸🇪","text":"","type":1,"order":5167,"group":9,"subgroup":98,"version":2},{"label":"flag: Singapore","hexcode":"1F1F8-1F1EC","tags":["SG","flag"],"emoji":"🇸🇬","text":"","type":1,"order":5168,"group":9,"subgroup":98,"version":2},{"label":"flag: St. Helena","hexcode":"1F1F8-1F1ED","tags":["SH","flag"],"emoji":"🇸🇭","text":"","type":1,"order":5169,"group":9,"subgroup":98,"version":2},{"label":"flag: Slovenia","hexcode":"1F1F8-1F1EE","tags":["SI","flag"],"emoji":"🇸🇮","text":"","type":1,"order":5170,"group":9,"subgroup":98,"version":2},{"label":"flag: Svalbard & Jan Mayen","hexcode":"1F1F8-1F1EF","tags":["SJ","flag"],"emoji":"🇸🇯","text":"","type":1,"order":5171,"group":9,"subgroup":98,"version":2},{"label":"flag: Slovakia","hexcode":"1F1F8-1F1F0","tags":["SK","flag"],"emoji":"🇸🇰","text":"","type":1,"order":5172,"group":9,"subgroup":98,"version":2},{"label":"flag: Sierra Leone","hexcode":"1F1F8-1F1F1","tags":["SL","flag"],"emoji":"🇸🇱","text":"","type":1,"order":5173,"group":9,"subgroup":98,"version":2},{"label":"flag: San Marino","hexcode":"1F1F8-1F1F2","tags":["SM","flag"],"emoji":"🇸🇲","text":"","type":1,"order":5174,"group":9,"subgroup":98,"version":2},{"label":"flag: Senegal","hexcode":"1F1F8-1F1F3","tags":["SN","flag"],"emoji":"🇸🇳","text":"","type":1,"order":5175,"group":9,"subgroup":98,"version":2},{"label":"flag: Somalia","hexcode":"1F1F8-1F1F4","tags":["SO","flag"],"emoji":"🇸🇴","text":"","type":1,"order":5176,"group":9,"subgroup":98,"version":2},{"label":"flag: Suriname","hexcode":"1F1F8-1F1F7","tags":["SR","flag"],"emoji":"🇸🇷","text":"","type":1,"order":5177,"group":9,"subgroup":98,"version":2},{"label":"flag: South Sudan","hexcode":"1F1F8-1F1F8","tags":["SS","flag"],"emoji":"🇸🇸","text":"","type":1,"order":5178,"group":9,"subgroup":98,"version":2},{"label":"flag: São Tomé & Príncipe","hexcode":"1F1F8-1F1F9","tags":["ST","flag"],"emoji":"🇸🇹","text":"","type":1,"order":5179,"group":9,"subgroup":98,"version":2},{"label":"flag: El Salvador","hexcode":"1F1F8-1F1FB","tags":["SV","flag"],"emoji":"🇸🇻","text":"","type":1,"order":5180,"group":9,"subgroup":98,"version":2},{"label":"flag: Sint Maarten","hexcode":"1F1F8-1F1FD","tags":["SX","flag"],"emoji":"🇸🇽","text":"","type":1,"order":5181,"group":9,"subgroup":98,"version":2},{"label":"flag: Syria","hexcode":"1F1F8-1F1FE","tags":["SY","flag"],"emoji":"🇸🇾","text":"","type":1,"order":5182,"group":9,"subgroup":98,"version":2},{"label":"flag: Eswatini","hexcode":"1F1F8-1F1FF","tags":["SZ","flag"],"emoji":"🇸🇿","text":"","type":1,"order":5183,"group":9,"subgroup":98,"version":2},{"label":"flag: Tristan da Cunha","hexcode":"1F1F9-1F1E6","tags":["TA","flag"],"emoji":"🇹🇦","text":"","type":1,"order":5184,"group":9,"subgroup":98,"version":2},{"label":"flag: Turks & Caicos Islands","hexcode":"1F1F9-1F1E8","tags":["TC","flag"],"emoji":"🇹🇨","text":"","type":1,"order":5185,"group":9,"subgroup":98,"version":2},{"label":"flag: Chad","hexcode":"1F1F9-1F1E9","tags":["TD","flag"],"emoji":"🇹🇩","text":"","type":1,"order":5186,"group":9,"subgroup":98,"version":2},{"label":"flag: French Southern Territories","hexcode":"1F1F9-1F1EB","tags":["TF","flag"],"emoji":"🇹🇫","text":"","type":1,"order":5187,"group":9,"subgroup":98,"version":2},{"label":"flag: Togo","hexcode":"1F1F9-1F1EC","tags":["TG","flag"],"emoji":"🇹🇬","text":"","type":1,"order":5188,"group":9,"subgroup":98,"version":2},{"label":"flag: Thailand","hexcode":"1F1F9-1F1ED","tags":["TH","flag"],"emoji":"🇹🇭","text":"","type":1,"order":5189,"group":9,"subgroup":98,"version":2},{"label":"flag: Tajikistan","hexcode":"1F1F9-1F1EF","tags":["TJ","flag"],"emoji":"🇹🇯","text":"","type":1,"order":5190,"group":9,"subgroup":98,"version":2},{"label":"flag: Tokelau","hexcode":"1F1F9-1F1F0","tags":["TK","flag"],"emoji":"🇹🇰","text":"","type":1,"order":5191,"group":9,"subgroup":98,"version":2},{"label":"flag: Timor-Leste","hexcode":"1F1F9-1F1F1","tags":["TL","flag"],"emoji":"🇹🇱","text":"","type":1,"order":5192,"group":9,"subgroup":98,"version":2},{"label":"flag: Turkmenistan","hexcode":"1F1F9-1F1F2","tags":["TM","flag"],"emoji":"🇹🇲","text":"","type":1,"order":5193,"group":9,"subgroup":98,"version":2},{"label":"flag: Tunisia","hexcode":"1F1F9-1F1F3","tags":["TN","flag"],"emoji":"🇹🇳","text":"","type":1,"order":5194,"group":9,"subgroup":98,"version":2},{"label":"flag: Tonga","hexcode":"1F1F9-1F1F4","tags":["TO","flag"],"emoji":"🇹🇴","text":"","type":1,"order":5195,"group":9,"subgroup":98,"version":2},{"label":"flag: Türkiye","hexcode":"1F1F9-1F1F7","tags":["TR","flag"],"emoji":"🇹🇷","text":"","type":1,"order":5196,"group":9,"subgroup":98,"version":2},{"label":"flag: Trinidad & Tobago","hexcode":"1F1F9-1F1F9","tags":["TT","flag"],"emoji":"🇹🇹","text":"","type":1,"order":5197,"group":9,"subgroup":98,"version":2},{"label":"flag: Tuvalu","hexcode":"1F1F9-1F1FB","tags":["TV","flag"],"emoji":"🇹🇻","text":"","type":1,"order":5198,"group":9,"subgroup":98,"version":2},{"label":"flag: Taiwan","hexcode":"1F1F9-1F1FC","tags":["TW","flag"],"emoji":"🇹🇼","text":"","type":1,"order":5199,"group":9,"subgroup":98,"version":2},{"label":"flag: Tanzania","hexcode":"1F1F9-1F1FF","tags":["TZ","flag"],"emoji":"🇹🇿","text":"","type":1,"order":5200,"group":9,"subgroup":98,"version":2},{"label":"flag: Ukraine","hexcode":"1F1FA-1F1E6","tags":["UA","flag"],"emoji":"🇺🇦","text":"","type":1,"order":5201,"group":9,"subgroup":98,"version":2},{"label":"flag: Uganda","hexcode":"1F1FA-1F1EC","tags":["UG","flag"],"emoji":"🇺🇬","text":"","type":1,"order":5202,"group":9,"subgroup":98,"version":2},{"label":"flag: U.S. Outlying Islands","hexcode":"1F1FA-1F1F2","tags":["UM","flag"],"emoji":"🇺🇲","text":"","type":1,"order":5203,"group":9,"subgroup":98,"version":2},{"label":"flag: United Nations","hexcode":"1F1FA-1F1F3","tags":["UN","flag"],"emoji":"🇺🇳","text":"","type":1,"order":5204,"group":9,"subgroup":98,"version":4},{"label":"flag: United States","hexcode":"1F1FA-1F1F8","tags":["US","flag"],"emoji":"🇺🇸","text":"","type":1,"order":5205,"group":9,"subgroup":98,"version":0.6},{"label":"flag: Uruguay","hexcode":"1F1FA-1F1FE","tags":["UY","flag"],"emoji":"🇺🇾","text":"","type":1,"order":5206,"group":9,"subgroup":98,"version":2},{"label":"flag: Uzbekistan","hexcode":"1F1FA-1F1FF","tags":["UZ","flag"],"emoji":"🇺🇿","text":"","type":1,"order":5207,"group":9,"subgroup":98,"version":2},{"label":"flag: Vatican City","hexcode":"1F1FB-1F1E6","tags":["VA","flag"],"emoji":"🇻🇦","text":"","type":1,"order":5208,"group":9,"subgroup":98,"version":2},{"label":"flag: St. Vincent & Grenadines","hexcode":"1F1FB-1F1E8","tags":["VC","flag"],"emoji":"🇻🇨","text":"","type":1,"order":5209,"group":9,"subgroup":98,"version":2},{"label":"flag: Venezuela","hexcode":"1F1FB-1F1EA","tags":["VE","flag"],"emoji":"🇻🇪","text":"","type":1,"order":5210,"group":9,"subgroup":98,"version":2},{"label":"flag: British Virgin Islands","hexcode":"1F1FB-1F1EC","tags":["VG","flag"],"emoji":"🇻🇬","text":"","type":1,"order":5211,"group":9,"subgroup":98,"version":2},{"label":"flag: U.S. Virgin Islands","hexcode":"1F1FB-1F1EE","tags":["VI","flag"],"emoji":"🇻🇮","text":"","type":1,"order":5212,"group":9,"subgroup":98,"version":2},{"label":"flag: Vietnam","hexcode":"1F1FB-1F1F3","tags":["VN","flag"],"emoji":"🇻🇳","text":"","type":1,"order":5213,"group":9,"subgroup":98,"version":2},{"label":"flag: Vanuatu","hexcode":"1F1FB-1F1FA","tags":["VU","flag"],"emoji":"🇻🇺","text":"","type":1,"order":5214,"group":9,"subgroup":98,"version":2},{"label":"flag: Wallis & Futuna","hexcode":"1F1FC-1F1EB","tags":["WF","flag"],"emoji":"🇼🇫","text":"","type":1,"order":5215,"group":9,"subgroup":98,"version":2},{"label":"flag: Samoa","hexcode":"1F1FC-1F1F8","tags":["WS","flag"],"emoji":"🇼🇸","text":"","type":1,"order":5216,"group":9,"subgroup":98,"version":2},{"label":"flag: Kosovo","hexcode":"1F1FD-1F1F0","tags":["XK","flag"],"emoji":"🇽🇰","text":"","type":1,"order":5217,"group":9,"subgroup":98,"version":2},{"label":"flag: Yemen","hexcode":"1F1FE-1F1EA","tags":["YE","flag"],"emoji":"🇾🇪","text":"","type":1,"order":5218,"group":9,"subgroup":98,"version":2},{"label":"flag: Mayotte","hexcode":"1F1FE-1F1F9","tags":["YT","flag"],"emoji":"🇾🇹","text":"","type":1,"order":5219,"group":9,"subgroup":98,"version":2},{"label":"flag: South Africa","hexcode":"1F1FF-1F1E6","tags":["ZA","flag"],"emoji":"🇿🇦","text":"","type":1,"order":5220,"group":9,"subgroup":98,"version":2},{"label":"flag: Zambia","hexcode":"1F1FF-1F1F2","tags":["ZM","flag"],"emoji":"🇿🇲","text":"","type":1,"order":5221,"group":9,"subgroup":98,"version":2},{"label":"flag: Zimbabwe","hexcode":"1F1FF-1F1FC","tags":["ZW","flag"],"emoji":"🇿🇼","text":"","type":1,"order":5222,"group":9,"subgroup":98,"version":2},{"label":"flag: England","hexcode":"1F3F4-E0067-E0062-E0065-E006E-E0067-E007F","tags":["flag","gbeng"],"emoji":"🏴󠁧󠁢󠁥󠁮󠁧󠁿","text":"","type":1,"order":5223,"group":9,"subgroup":99,"version":5},{"label":"flag: Scotland","hexcode":"1F3F4-E0067-E0062-E0073-E0063-E0074-E007F","tags":["flag","gbsct"],"emoji":"🏴󠁧󠁢󠁳󠁣󠁴󠁿","text":"","type":1,"order":5224,"group":9,"subgroup":99,"version":5},{"label":"flag: Wales","hexcode":"1F3F4-E0067-E0062-E0077-E006C-E0073-E007F","tags":["flag","gbwls"],"emoji":"🏴󠁧󠁢󠁷󠁬󠁳󠁿","text":"","type":1,"order":5225,"group":9,"subgroup":99,"version":5}] \ No newline at end of file diff --git a/web/public/emoji/emojibase-17.0.0/en/messages.json b/web/public/emoji/emojibase-17.0.0/en/messages.json new file mode 100644 index 00000000000..44579a8587c --- /dev/null +++ b/web/public/emoji/emojibase-17.0.0/en/messages.json @@ -0,0 +1 @@ +{"groups":[{"key":"smileys-emotion","message":"smileys & emotion","order":0},{"key":"people-body","message":"people & body","order":1},{"key":"component","message":"components","order":2},{"key":"animals-nature","message":"animals & nature","order":3},{"key":"food-drink","message":"food & drink","order":4},{"key":"travel-places","message":"travel & places","order":5},{"key":"activities","message":"activities","order":6},{"key":"objects","message":"objects","order":7},{"key":"symbols","message":"symbols","order":8},{"key":"flags","message":"flags","order":9}],"skinTones":[{"key":"dark","message":"dark skin tone"},{"key":"light","message":"light skin tone"},{"key":"medium","message":"medium skin tone"},{"key":"medium-dark","message":"medium-dark skin tone"},{"key":"medium-light","message":"medium-light skin tone"}],"subgroups":[{"key":"face-smiling","message":"smiling","order":0},{"key":"face-affection","message":"affectionate","order":1},{"key":"face-tongue","message":"with tongue","order":2},{"key":"face-hand","message":"with hands","order":3},{"key":"face-neutral-skeptical","message":"neutral / skeptical","order":4},{"key":"face-sleepy","message":"sleepy","order":5},{"key":"face-unwell","message":"unwell","order":6},{"key":"face-hat","message":"with hats","order":7},{"key":"face-glasses","message":"with glasses","order":8},{"key":"face-concerned","message":"concerned","order":9},{"key":"face-negative","message":"negative","order":10},{"key":"face-costume","message":"costumed & creatures","order":11},{"key":"cat-face","message":"cat faces","order":12},{"key":"monkey-face","message":"monkey faces","order":13},{"key":"heart","message":"hearts","order":14},{"key":"emotion","message":"emotions","order":15},{"key":"hand-fingers-open","message":"fingers open","order":16},{"key":"hand-fingers-partial","message":"hand signs","order":17},{"key":"hand-single-finger","message":"finger pointing","order":18},{"key":"hand-fingers-closed","message":"fingers closed","order":19},{"key":"hands","message":"hands","order":20},{"key":"hand-prop","message":"hand props","order":21},{"key":"body-parts","message":"body parts","order":22},{"key":"person","message":"people","order":23},{"key":"person-gesture","message":"gestures","order":24},{"key":"person-role","message":"roles & careers","order":25},{"key":"person-fantasy","message":"fantasy","order":26},{"key":"person-activity","message":"activities","order":27},{"key":"person-sport","message":"athletics","order":28},{"key":"person-resting","message":"resting","order":29},{"key":"family","message":"family","order":30},{"key":"person-symbol","message":"people symbols","order":31},{"key":"skin-tone","message":"skin tones","order":32},{"key":"hair-style","message":"hair styles","order":33},{"key":"animal-mammal","message":"mammals","order":34},{"key":"animal-bird","message":"birds","order":35},{"key":"animal-amphibian","message":"amphibians","order":36},{"key":"animal-reptile","message":"reptiles","order":37},{"key":"animal-marine","message":"marine life","order":38},{"key":"animal-bug","message":"bugs","order":39},{"key":"plant-flower","message":"flowers","order":40},{"key":"plant-other","message":"other plants","order":41},{"key":"food-fruit","message":"fruit","order":42},{"key":"food-vegetable","message":"vegetables","order":43},{"key":"food-prepared","message":"cooked / prepared","order":44},{"key":"food-asian","message":"asian","order":45},{"key":"food-marine","message":"seafood","order":46},{"key":"food-sweet","message":"sweets & candy","order":46},{"key":"drink","message":"drink","order":47},{"key":"dishware","message":"dishware","order":48},{"key":"place-map","message":"globes & maps","order":49},{"key":"place-geographic","message":"geographic locations","order":50},{"key":"place-building","message":"buildings","order":51},{"key":"place-religious","message":"religious buildings","order":52},{"key":"place-other","message":"other places","order":53},{"key":"transport-ground","message":"ground transportation","order":54},{"key":"transport-water","message":"water transportation","order":55},{"key":"transport-air","message":"air transportation","order":56},{"key":"hotel","message":"hotel","order":57},{"key":"time","message":"time","order":58},{"key":"sky-weather","message":"weather","order":59},{"key":"event","message":"events & holidays","order":60},{"key":"award-medal","message":"award medals","order":61},{"key":"sport","message":"sports","order":62},{"key":"game","message":"games & hobbies","order":63},{"key":"arts-crafts","message":"arts & crafts","order":64},{"key":"clothing","message":"clothing","order":65},{"key":"sound","message":"sound","order":66},{"key":"music","message":"music","order":67},{"key":"musical-instrument","message":"musical instruments","order":68},{"key":"phone","message":"phone","order":69},{"key":"computer","message":"computer","order":70},{"key":"light-video","message":"light, film & video","order":71},{"key":"book-paper","message":"books & paper","order":72},{"key":"money","message":"money","order":73},{"key":"mail","message":"mail","order":74},{"key":"writing","message":"writing","order":75},{"key":"office","message":"office supplies","order":76},{"key":"lock","message":"lock & keys","order":77},{"key":"tool","message":"tools","order":78},{"key":"science","message":"science equipment","order":79},{"key":"medical","message":"medical","order":80},{"key":"household","message":"household items","order":81},{"key":"other-object","message":"other objects","order":82},{"key":"transport-sign","message":"transport signs","order":83},{"key":"warning","message":"warning symbols","order":84},{"key":"arrow","message":"arrows","order":85},{"key":"religion","message":"religious symbols","order":86},{"key":"zodiac","message":"zodiac signs","order":87},{"key":"av-symbol","message":"audio & video symbols","order":88},{"key":"gender","message":"gender signs","order":89},{"key":"math","message":"math symbols","order":90},{"key":"punctuation","message":"punctuation","order":91},{"key":"currency","message":"currencies","order":92},{"key":"other-symbol","message":"other symbols","order":93},{"key":"keycap","message":"keypad characters","order":94},{"key":"alphanum","message":"alphanumeric symbols","order":95},{"key":"geometric","message":"shapes & colors","order":96},{"key":"flag","message":"other flags","order":97},{"key":"country-flag","message":"country flags","order":98},{"key":"subdivision-flag","message":"subdivision flags","order":99}]} diff --git a/web/scripts/__tests__/migrate-i18n-selectors.spec.ts b/web/scripts/__tests__/migrate-i18n-selectors.spec.ts index b7c26c077b6..c1f75bb3bd9 100644 --- a/web/scripts/__tests__/migrate-i18n-selectors.spec.ts +++ b/web/scripts/__tests__/migrate-i18n-selectors.spec.ts @@ -145,7 +145,7 @@ export function translateLegacy(t: any) { it('should follow aliased translation factory bindings without trusting shadowed names', () => { // Arrange const source = `import { useTranslation as useI18n } from 'react-i18next' -import { getTranslation as loadTranslation } from '@/i18n-config/server' +import { getTranslation as loadTranslation } from '@/i18n/server' function renderUnrelated(useI18n: () => { t: (key: string) => string }) { const { t } = useI18n() @@ -164,7 +164,7 @@ export async function Example() { // Assert expect(result.output).toBe(`import { useTranslation as useI18n } from 'react-i18next' -import { getTranslation as loadTranslation } from '@/i18n-config/server' +import { getTranslation as loadTranslation } from '@/i18n/server' function renderUnrelated(useI18n: () => { t: (key: string) => string }) { const { t } = useI18n() diff --git a/web/scripts/analyze-i18n-diff.ts b/web/scripts/analyze-i18n-diff.ts index 7120948c98d..fbe27cd3e07 100644 --- a/web/scripts/analyze-i18n-diff.ts +++ b/web/scripts/analyze-i18n-diff.ts @@ -19,7 +19,7 @@ import { fileURLToPath } from 'node:url' const __filename = fileURLToPath(import.meta.url) const __dirname = path.dirname(__filename) -const I18N_DIR = path.join(__dirname, '../i18n/en-US') +const I18N_DIR = path.join(__dirname, '../i18n/locales/en-US') const LOCALE = 'en-US' type TranslationValue = string | string[] @@ -117,7 +117,7 @@ function parseTsContent(content: string): NestedTranslation { */ function getMainBranchFile(filePath: string): string | null { try { - const relativePath = `./i18n/${LOCALE}/${filePath}` + const relativePath = `./i18n/locales/${LOCALE}/${filePath}` return execSync(`git show main:${relativePath}`, { encoding: 'utf-8', @@ -141,7 +141,7 @@ function getTranslationFiles(): string[] { */ function getMainBranchNamespaces(): string[] { try { - const relativePath = `./i18n/${LOCALE}` + const relativePath = `./i18n/locales/${LOCALE}` const output = execSync(`git ls-tree --name-only main ${relativePath}/`, { encoding: 'utf-8', diff --git a/web/scripts/check-i18n.js b/web/scripts/check-i18n.js index 78617fb9e1b..cc206914fbd 100644 --- a/web/scripts/check-i18n.js +++ b/web/scripts/check-i18n.js @@ -1,7 +1,7 @@ import fs from 'node:fs' import path from 'node:path' import { fileURLToPath } from 'node:url' -import data from '../i18n-config/languages' +import data from '../i18n/languages' const __filename = fileURLToPath(import.meta.url) const __dirname = path.dirname(__filename) @@ -105,7 +105,7 @@ Examples: async function getKeysFromLanguage(language) { return new Promise((resolve, reject) => { - const folderPath = path.resolve(__dirname, '../i18n', language) + const folderPath = path.resolve(__dirname, '../i18n/locales', language) const allKeys = [] fs.readdir(folderPath, (err, files) => { if (err) { @@ -146,7 +146,7 @@ async function getKeysFromLanguage(language) { } async function removeExtraKeysFromFile(language, fileName, extraKeys) { - const filePath = path.resolve(__dirname, '../i18n', language, `${fileName}.json`) + const filePath = path.resolve(__dirname, '../i18n/locales', language, `${fileName}.json`) if (!fs.existsSync(filePath)) { console.log(`⚠️ File not found: ${filePath}`) @@ -260,7 +260,7 @@ async function main() { console.log(`\n🤖 Auto-removing extra keys from ${language}...`) // Get all translation files - const i18nFolder = path.resolve(__dirname, '../i18n', language) + const i18nFolder = path.resolve(__dirname, '../i18n/locales', language) const files = fs .readdirSync(i18nFolder) .filter((file) => file.endsWith('.json')) diff --git a/web/scripts/i18n-prune/core.ts b/web/scripts/i18n-prune/core.ts deleted file mode 100644 index 8c565529391..00000000000 --- a/web/scripts/i18n-prune/core.ts +++ /dev/null @@ -1,1619 +0,0 @@ -import fs from 'node:fs' -import path from 'node:path' -import { fileURLToPath } from 'node:url' -import * as ts from 'typescript' - -const DEFAULT_LOCALE = 'en-US' -const DEFAULT_NAMESPACE = 'app' -const MAX_EXPANDED_VALUES = 200 -const SOURCE_EXTENSIONS = new Set(['.js', '.jsx', '.ts', '.tsx']) -const PLURAL_SUFFIXES = ['_zero', '_one', '_two', '_few', '_many', '_other'] -const SKIPPED_DIRECTORIES = new Set([ - '.next', - '.turbo', - '.vinext', - '.vscode', - 'coverage', - 'dist', - 'i18n', - 'node_modules', - 'public', -]) - -type StaticValue = - | { kind: 'strings'; values: string[] } - | { - kind: 'object' - properties: Map - unknownKeyValues: StaticValue[] - complete: boolean - } - | { kind: 'array'; elements: StaticValue[] } - | { kind: 'identityFunction' } - | { kind: 'selector'; expression: ts.Expression } - | { kind: 'selectors'; expressions: ts.Expression[] } - | { kind: 'unknown' } - -type KeyExpressionUsage = - | { kind: 'keys'; keys: string[] } - | { kind: 'patterns'; patterns: Array<{ prefix: string; suffix: string }> } - | { kind: 'mixed'; keys: string[]; patterns: Array<{ prefix: string; suffix: string }> } - | { kind: 'unknown'; namespace?: string } - -type TranslationFunctionInfo = { - namespaces: string[] - keyPrefix?: string - selectorArgumentIndex?: number -} - -type UsageLocation = { - file: string - line: number - expression: string -} - -export type DynamicKeyPattern = UsageLocation & { - namespace: string - prefix: string - suffix: string -} - -export type UnresolvedUsage = UsageLocation & { - namespace?: string - reason: string -} - -type Catalog = { - keysByNamespace: Map> - fileNameByNamespace: Map - namespaceByFileName: Map -} - -export type AnalyzeUnusedTranslationsOptions = { - webRoot?: string - defaultLocale?: string - files?: string[] - sourceFiles?: string[] -} - -export type AnalyzeUnusedTranslationsResult = { - webRoot: string - defaultLocale: string - unusedKeysByNamespace: Record - usedKeysByNamespace: Record - allKeysByNamespace: Record - dynamicKeyPatterns: DynamicKeyPattern[] - protectedNamespaces: string[] - unresolvedUsages: UnresolvedUsage[] - namespaceFiles: Record -} - -export type RemoveUnusedTranslationsOptions = { - webRoot?: string - locales?: string[] - analysis: AnalyzeUnusedTranslationsResult -} - -export type RemovedKey = { - locale: string - namespace: string - key: string -} - -export type RemoveUnusedTranslationsResult = { - removedKeys: RemovedKey[] -} - -// The analyzer keeps the pruning pipeline explicit: load the default catalog, -// collect statically provable usage, protect ambiguous dynamic usage, then prune. -type UsageCollector = { - exactKeys: Map> - patterns: DynamicKeyPattern[] - protectedNamespaces: Set - unresolvedUsages: UnresolvedUsage[] -} - -type Scope = { - values: Map - translationFunctions: Map -} - -function toPosixPath(filePath: string) { - return filePath.split(path.sep).join('/') -} - -function fileNameToNamespace(fileName: string) { - return fileName.replace(/[-_]+([a-z0-9])/gi, (_, char: string) => char.toUpperCase()) -} - -function namespaceToFileName(namespace: string, catalog: Catalog) { - return ( - catalog.fileNameByNamespace.get(namespace) ?? - namespace.replace(/[A-Z0-9]/g, (char) => `-${char.toLowerCase()}`) - ) -} - -function isStringNode(node: ts.Node): node is ts.StringLiteral | ts.NoSubstitutionTemplateLiteral { - return ts.isStringLiteral(node) || ts.isNoSubstitutionTemplateLiteral(node) -} - -function uniqueSorted(values: Iterable) { - return Array.from(new Set(values)).sort() -} - -function normalizeNamespace(namespace: string, catalog: Catalog) { - if (catalog.keysByNamespace.has(namespace)) return namespace - - const fromFileName = catalog.namespaceByFileName.get(namespace) - if (fromFileName) return fromFileName - - const camelNamespace = fileNameToNamespace(namespace) - return catalog.keysByNamespace.has(camelNamespace) ? camelNamespace : namespace -} - -function unwrapExpression(expression: ts.Expression): ts.Expression { - let current = expression - let changed = true - - while (changed) { - changed = false - if ( - ts.isParenthesizedExpression(current) || - ts.isAsExpression(current) || - ts.isTypeAssertionExpression(current) || - ts.isNonNullExpression(current) || - ts.isSatisfiesExpression(current) - ) { - current = current.expression - changed = true - } - } - - return current -} - -function combineStringValues(left: string[], right: string[]) { - const combined: string[] = [] - for (const leftValue of left) { - for (const rightValue of right) { - combined.push(`${leftValue}${rightValue}`) - if (combined.length > MAX_EXPANDED_VALUES) return [] - } - } - return uniqueSorted(combined) -} - -function staticStrings(values: string[]): StaticValue { - return { kind: 'strings', values: uniqueSorted(values) } -} - -function unknownStaticValue(): StaticValue { - return { kind: 'unknown' } -} - -function getPropertyNameText(name: ts.PropertyName) { - if (ts.isIdentifier(name) || ts.isStringLiteral(name) || ts.isNumericLiteral(name)) - return name.text - return undefined -} - -function isFunctionLikeWithParameters( - node: ts.Node, -): node is - | ts.FunctionDeclaration - | ts.FunctionExpression - | ts.ArrowFunction - | ts.MethodDeclaration - | ts.ConstructorDeclaration { - return ( - ts.isFunctionDeclaration(node) || - ts.isFunctionExpression(node) || - ts.isArrowFunction(node) || - ts.isMethodDeclaration(node) || - ts.isConstructorDeclaration(node) - ) -} - -function isLikelyTranslationFunctionType(typeNode: ts.TypeNode | undefined) { - const typeText = typeNode?.getText() - return Boolean( - typeText && (/\bTFunction\b/.test(typeText) || /\buseTranslation\b/.test(typeText)), - ) -} - -function getTranslationFunctionInfoFromType( - typeNode: ts.TypeNode | undefined, - catalog: Catalog, -): TranslationFunctionInfo | undefined { - if (!typeNode) return undefined - - if (ts.isTypeReferenceNode(typeNode) && /(?:^|\.)TFunction$/.test(typeNode.typeName.getText())) { - const namespace = getStringLiteralTypeValue(typeNode.typeArguments?.[0]) - return { - namespaces: [namespace ? normalizeNamespace(namespace, catalog) : DEFAULT_NAMESPACE], - keyPrefix: undefined, - } - } - - if (isLikelyTranslationFunctionType(typeNode)) - return { namespaces: [DEFAULT_NAMESPACE], keyPrefix: undefined } - - return undefined -} - -function addTranslationFunction( - scope: Scope, - name: string, - info: TranslationFunctionInfo = { namespaces: [DEFAULT_NAMESPACE], keyPrefix: undefined }, -) { - scope.translationFunctions.set(name, info) -} - -function lookupValue(name: string, scopes: Scope[]) { - for (let index = scopes.length - 1; index >= 0; index--) { - const value = scopes[index]!.values.get(name) - if (value) return value - } - return undefined -} - -function lookupTranslationFunction(name: string, scopes: Scope[]) { - for (let index = scopes.length - 1; index >= 0; index--) { - const value = scopes[index]!.translationFunctions.get(name) - if (value) return value - } - return undefined -} - -function evaluateStaticExpression(expression: ts.Expression, scopes: Scope[]): StaticValue { - const unwrapped = unwrapExpression(expression) - - if (isStringNode(unwrapped)) return staticStrings([unwrapped.text]) - - if (ts.isTemplateExpression(unwrapped)) { - let values = [unwrapped.head.text] - for (const span of unwrapped.templateSpans) { - const expressionValue = evaluateStaticExpression(span.expression, scopes) - if (expressionValue.kind !== 'strings') return unknownStaticValue() - - values = combineStringValues(values, expressionValue.values) - if (!values.length) return unknownStaticValue() - - values = values.map((value) => `${value}${span.literal.text}`) - } - return staticStrings(values) - } - - if ( - ts.isBinaryExpression(unwrapped) && - unwrapped.operatorToken.kind === ts.SyntaxKind.PlusToken - ) { - const left = evaluateStaticExpression(unwrapped.left, scopes) - const right = evaluateStaticExpression(unwrapped.right, scopes) - if (left.kind === 'strings' && right.kind === 'strings') { - const values = combineStringValues(left.values, right.values) - return values.length ? staticStrings(values) : unknownStaticValue() - } - return unknownStaticValue() - } - - if ( - ts.isBinaryExpression(unwrapped) && - (unwrapped.operatorToken.kind === ts.SyntaxKind.BarBarToken || - unwrapped.operatorToken.kind === ts.SyntaxKind.QuestionQuestionToken) - ) { - const left = evaluateStaticExpression(unwrapped.left, scopes) - const right = evaluateStaticExpression(unwrapped.right, scopes) - if (left.kind === 'strings' && right.kind === 'strings') - return staticStrings([...left.values, ...right.values]) - return unknownStaticValue() - } - - if (ts.isConditionalExpression(unwrapped)) { - const whenTrue = evaluateStaticExpression(unwrapped.whenTrue, scopes) - const whenFalse = evaluateStaticExpression(unwrapped.whenFalse, scopes) - if (whenTrue.kind === 'strings' && whenFalse.kind === 'strings') - return staticStrings([...whenTrue.values, ...whenFalse.values]) - const trueExpression = unwrapExpression(unwrapped.whenTrue) - const falseExpression = unwrapExpression(unwrapped.whenFalse) - if ( - ts.isIdentifier(falseExpression) && - falseExpression.text === 'undefined' && - (whenTrue.kind === 'selector' || whenTrue.kind === 'selectors') - ) - return whenTrue - if ( - ts.isIdentifier(trueExpression) && - trueExpression.text === 'undefined' && - (whenFalse.kind === 'selector' || whenFalse.kind === 'selectors') - ) - return whenFalse - return unknownStaticValue() - } - - if (ts.isIdentifier(unwrapped)) return lookupValue(unwrapped.text, scopes) ?? unknownStaticValue() - - if (ts.isPropertyAccessExpression(unwrapped)) { - const container = evaluateStaticExpression(unwrapped.expression, scopes) - if (container.kind === 'object') { - const value = container.properties.get(unwrapped.name.text) - if (value && container.complete && !container.unknownKeyValues.length) return value - if (value || !container.complete || container.unknownKeyValues.length) - return unknownStaticValue() - } - return unknownStaticValue() - } - - if (ts.isElementAccessExpression(unwrapped) && unwrapped.argumentExpression) { - const container = evaluateStaticExpression(unwrapped.expression, scopes) - const argument = evaluateStaticExpression(unwrapped.argumentExpression, scopes) - if (container.kind === 'object') { - if (argument.kind !== 'strings') { - if (!container.complete) return unknownStaticValue() - return collectIndexedValues([ - ...container.properties.values(), - ...container.unknownKeyValues, - ]) - } - - const selectedValues: StaticValue[] = [] - for (const key of argument.values) { - const value = container.properties.get(key) - if (value && container.complete && !container.unknownKeyValues.length) - selectedValues.push(value) - else return unknownStaticValue() - } - return collectIndexedValues(selectedValues) - } - - if (container.kind === 'array') { - if (argument.kind !== 'strings') return collectIndexedValues(container.elements) - - return collectIndexedValues( - argument.values.flatMap((key) => { - const index = Number(key) - return Number.isInteger(index) ? (container.elements[index] ?? []) : [] - }), - ) - } - } - - if (ts.isObjectLiteralExpression(unwrapped)) { - const properties = new Map() - const unknownKeyValues: StaticValue[] = [] - let complete = true - for (const property of unwrapped.properties) { - if (!ts.isPropertyAssignment(property)) { - complete = false - continue - } - - const propertyValue = evaluateStaticExpression(property.initializer, scopes) - if (ts.isComputedPropertyName(property.name)) { - const propertyNames = evaluateStaticExpression(property.name.expression, scopes) - if (propertyNames.kind === 'strings') { - for (const propertyName of propertyNames.values) - properties.set(propertyName, propertyValue) - } else { - unknownKeyValues.push(propertyValue) - } - continue - } - - const propertyName = getPropertyNameText(property.name) - if (propertyName) properties.set(propertyName, propertyValue) - else complete = false - } - return { kind: 'object', properties, unknownKeyValues, complete } - } - - if (ts.isArrayLiteralExpression(unwrapped)) - return { - kind: 'array', - elements: unwrapped.elements.map((element) => evaluateStaticExpression(element, scopes)), - } - - if (ts.isArrowFunction(unwrapped)) { - if (getSelectorKeyExpression(unwrapped)) return { kind: 'selector', expression: unwrapped } - - const parameter = unwrapped.parameters[0] - const body = ts.isBlock(unwrapped.body) ? undefined : unwrapExpression(unwrapped.body) - if ( - parameter && - ts.isIdentifier(parameter.name) && - body && - ts.isIdentifier(body) && - body.text === parameter.name.text - ) { - return { kind: 'identityFunction' } - } - } - - if ( - ts.isCallExpression(unwrapped) && - ts.isIdentifier(unwrapped.expression) && - unwrapped.arguments.length === 1 - ) { - const callee = lookupValue(unwrapped.expression.text, scopes) - if (callee?.kind === 'identityFunction') - return evaluateStaticExpression(unwrapped.arguments[0]! as ts.Expression, scopes) - } - - return unknownStaticValue() -} - -function collectStringValues(value: StaticValue): string[] { - if (value.kind === 'strings') return value.values - - if (value.kind === 'array') - return uniqueSorted(value.elements.flatMap((element) => collectStringValues(element))) - - if (value.kind === 'object') - return uniqueSorted( - [...value.properties.values(), ...value.unknownKeyValues].flatMap((property) => - collectStringValues(property), - ), - ) - - return [] -} - -function collectSelectorExpressions(value: StaticValue): ts.Expression[] { - if (value.kind === 'selector') return [value.expression] - if (value.kind === 'selectors') return value.expressions - if (value.kind === 'array') - return value.elements.flatMap((element) => collectSelectorExpressions(element)) - if (value.kind === 'object') - return [...value.properties.values(), ...value.unknownKeyValues].flatMap((property) => - collectSelectorExpressions(property), - ) - return [] -} - -function hasUnknownAggregateValue(value: StaticValue): boolean { - if (value.kind === 'unknown' || value.kind === 'identityFunction') return true - if (value.kind === 'array') - return value.elements.some((element) => hasUnknownAggregateValue(element)) - if (value.kind === 'object') - return ( - !value.complete || - [...value.properties.values(), ...value.unknownKeyValues].some((property) => - hasUnknownAggregateValue(property), - ) - ) - return false -} - -function collectIndexedValues(values: StaticValue[]): StaticValue { - if (!values.length) return unknownStaticValue() - - if ( - values.every( - (value): value is Extract => value.kind === 'object', - ) - ) { - const propertyNames = Array.from(values[0]!.properties.keys()) - const haveMatchingShape = values.every((value) => { - return ( - value.complete && - !value.unknownKeyValues.length && - value.properties.size === propertyNames.length && - propertyNames.every((propertyName) => value.properties.has(propertyName)) - ) - }) - if (!haveMatchingShape) return unknownStaticValue() - - return { - kind: 'object', - properties: new Map( - propertyNames.map((propertyName) => { - return [ - propertyName, - collectIndexedValues(values.map((value) => value.properties.get(propertyName)!)), - ] - }), - ), - unknownKeyValues: [], - complete: true, - } - } - - if (values.some((value) => hasUnknownAggregateValue(value))) return unknownStaticValue() - - const selectors = values.flatMap((value) => collectSelectorExpressions(value)) - const strings = values.flatMap((value) => collectStringValues(value)) - if (selectors.length && strings.length) return unknownStaticValue() - if (selectors.length) - return selectors.length === 1 - ? { kind: 'selector', expression: selectors[0]! } - : { kind: 'selectors', expressions: selectors } - - return strings.length ? staticStrings(strings) : unknownStaticValue() -} - -function getObjectProperty(objectLiteral: ts.ObjectLiteralExpression, name: string) { - return objectLiteral.properties.find((property): property is ts.PropertyAssignment => { - if (!ts.isPropertyAssignment(property)) return false - const propertyName = getPropertyNameText(property.name) - return propertyName === name - }) -} - -function evaluateNamespaceExpression( - expression: ts.Expression | undefined, - scopes: Scope[], - catalog: Catalog, -) { - if (!expression) return [] - - const value = evaluateStaticExpression(expression, scopes) - if (value.kind === 'strings') - return uniqueSorted(value.values.map((namespace) => normalizeNamespace(namespace, catalog))) - - if (value.kind === 'array') { - const namespaces: string[] = [] - for (const element of value.elements) { - if (element.kind === 'strings') - namespaces.push( - ...element.values.map((namespace) => normalizeNamespace(namespace, catalog)), - ) - } - return uniqueSorted(namespaces) - } - - return [] -} - -function getStringLiteralTypeValue(typeNode: ts.TypeNode | undefined) { - if (typeNode && ts.isLiteralTypeNode(typeNode) && ts.isStringLiteral(typeNode.literal)) { - return typeNode.literal.text - } - - return undefined -} - -function evaluateAssertedTypeKeyUsage(expression: ts.Expression): KeyExpressionUsage | undefined { - const unwrapped = expression - if (!ts.isAsExpression(unwrapped) && !ts.isTypeAssertionExpression(unwrapped)) return undefined - - const literalValue = getStringLiteralTypeValue(unwrapped.type) - if (literalValue) return { kind: 'keys', keys: [literalValue] } - - if (!ts.isTypeReferenceNode(unwrapped.type)) return undefined - - const typeName = unwrapped.type.typeName.getText() - if (typeName !== 'I18nKeysByPrefix' && typeName !== 'I18nKeysWithPrefix') return undefined - - const prefix = getStringLiteralTypeValue(unwrapped.type.typeArguments?.[1]) - if (!prefix) return undefined - - return { - kind: 'patterns', - patterns: [{ prefix, suffix: '' }], - } -} - -function extractStringValuesFromType( - type: ts.Type, - seen = new Set(), -): string[] | undefined { - if (seen.has(type)) return undefined - seen.add(type) - - if (type.isStringLiteral()) return [type.value] - - if (type.isUnion()) { - const values: string[] = [] - for (const subtype of type.types) { - if (subtype.flags & (ts.TypeFlags.Undefined | ts.TypeFlags.Null | ts.TypeFlags.Void)) continue - - const subtypeValues = extractStringValuesFromType(subtype, seen) - if (!subtypeValues) return undefined - - values.push(...subtypeValues) - if (values.length > MAX_EXPANDED_VALUES) return undefined - } - return values.length ? uniqueSorted(values) : undefined - } - - return undefined -} - -function evaluateCheckerKeyUsage( - expression: ts.Expression, - checker?: ts.TypeChecker, -): KeyExpressionUsage | undefined { - if (!checker) return undefined - - const values = extractStringValuesFromType(checker.getTypeAtLocation(expression)) - if (!values?.length) return undefined - - return { kind: 'keys', keys: values } -} - -function isSelectorParamType( - type: ts.Type, - checker: ts.TypeChecker, - seen = new Set(), -): boolean { - if (seen.has(type)) return false - seen.add(type) - - if (type.aliasSymbol?.getName() === 'SelectorParam') return true - - const constraint = checker.getBaseConstraintOfType(type) - return Boolean( - constraint && constraint !== type && isSelectorParamType(constraint, checker, seen), - ) -} - -function selectorNamespacesFromTypes(types: Array, catalog: Catalog) { - for (const type of types) { - const namespaceType = type?.aliasTypeArguments?.[0] - if (!namespaceType) continue - - const namespaces = extractStringValuesFromType(namespaceType) - ?.map((namespace) => normalizeNamespace(namespace, catalog)) - .filter((namespace) => catalog.keysByNamespace.has(namespace)) - if (namespaces?.length && namespaces.length < catalog.keysByNamespace.size) - return uniqueSorted(namespaces) - } - - return [] -} - -function selectorNamespacesFromTFunctionBrand( - type: ts.Type, - expression: ts.Node, - checker: ts.TypeChecker, - catalog: Catalog, -) { - const brand = type.getProperty('$TFunctionBrand') - if (!brand) return [] - - const brandType = checker.getTypeOfSymbolAtLocation(brand, expression) - return uniqueSorted( - (extractStringValuesFromType(brandType) ?? []) - .map((namespace) => normalizeNamespace(namespace, catalog)) - .filter((namespace) => catalog.keysByNamespace.has(namespace)), - ) -} - -function getCheckerTranslationFunctionInfo( - expression: ts.Node, - checker: ts.TypeChecker | undefined, - catalog: Catalog, -): TranslationFunctionInfo | undefined { - if (!checker) return undefined - - const calleeType = checker.getTypeAtLocation(expression) - const brandedNamespaces = selectorNamespacesFromTFunctionBrand( - calleeType, - expression, - checker, - catalog, - ) - if (brandedNamespaces.length) { - return { - namespaces: brandedNamespaces, - keyPrefix: undefined, - selectorArgumentIndex: 0, - } - } - - for (const signature of calleeType.getCallSignatures()) { - for (const [parameterIndex, parameter] of signature.parameters.entries()) { - const parameterType = checker.getTypeOfSymbolAtLocation(parameter, expression) - const constraint = checker.getBaseConstraintOfType(parameterType) - if (!isSelectorParamType(parameterType, checker)) continue - - return { - namespaces: selectorNamespacesFromTypes([calleeType, parameterType, constraint], catalog), - keyPrefix: undefined, - selectorArgumentIndex: parameterIndex, - } - } - } - - return undefined -} - -function combineKeyExpressionUsages( - left: KeyExpressionUsage, - right: KeyExpressionUsage, -): KeyExpressionUsage { - if (left.kind === 'unknown' || right.kind === 'unknown') return { kind: 'unknown' } - - const keys = [ - ...(left.kind === 'keys' || left.kind === 'mixed' ? left.keys : []), - ...(right.kind === 'keys' || right.kind === 'mixed' ? right.keys : []), - ] - const patterns = [ - ...(left.kind === 'patterns' || left.kind === 'mixed' ? left.patterns : []), - ...(right.kind === 'patterns' || right.kind === 'mixed' ? right.patterns : []), - ] - - if (keys.length && patterns.length) return { kind: 'mixed', keys: uniqueSorted(keys), patterns } - - if (keys.length) return { kind: 'keys', keys: uniqueSorted(keys) } - - return { kind: 'patterns', patterns } -} - -function getSelectorNamespace(source: ts.Expression, parameterName: string) { - const unwrapped = unwrapExpression(source) - if ( - ts.isPropertyAccessExpression(unwrapped) && - ts.isIdentifier(unwrapped.expression) && - unwrapped.expression.text === parameterName - ) { - return unwrapped.name.text - } - - if ( - ts.isElementAccessExpression(unwrapped) && - ts.isIdentifier(unwrapped.expression) && - unwrapped.expression.text === parameterName && - unwrapped.argumentExpression && - isStringNode(unwrapExpression(unwrapped.argumentExpression)) - ) { - return (unwrapExpression(unwrapped.argumentExpression) as ts.StringLiteralLike).text - } - - return undefined -} - -function getSelectorKeyExpression(expression: ts.Expression) { - const selector = unwrapExpression(expression) - if (!ts.isArrowFunction(selector) || ts.isBlock(selector.body)) return undefined - - const parameter = selector.parameters[0] - if (!parameter || !ts.isIdentifier(parameter.name)) return undefined - - const body = unwrapExpression(selector.body) - let keyExpression: ts.Expression | undefined - let sourceExpression: ts.Expression | undefined - if (ts.isPropertyAccessExpression(body)) { - keyExpression = ts.factory.createStringLiteral(body.name.text) - sourceExpression = body.expression - } else if (ts.isElementAccessExpression(body) && body.argumentExpression) { - keyExpression = body.argumentExpression - sourceExpression = body.expression - } - - if (!keyExpression || !sourceExpression) return undefined - - const source = unwrapExpression(sourceExpression) - if (ts.isIdentifier(source) && source.text === parameter.name.text) return { keyExpression } - - const namespace = getSelectorNamespace(source, parameter.name.text) - if (namespace) { - return { - keyExpression, - namespace, - } - } - - return undefined -} - -function prependNamespace(usage: KeyExpressionUsage, namespace?: string): KeyExpressionUsage { - if (!namespace) return usage - - if (usage.kind === 'unknown') return { kind: 'unknown', namespace } - - const prefix = `${namespace}:` - if (usage.kind === 'keys') - return { kind: 'keys', keys: usage.keys.map((key) => `${prefix}${key}`) } - - if (usage.kind === 'patterns') { - return { - kind: 'patterns', - patterns: usage.patterns.map((pattern) => ({ - prefix: `${prefix}${pattern.prefix}`, - suffix: pattern.suffix, - })), - } - } - - return { - kind: 'mixed', - keys: usage.keys.map((key) => `${prefix}${key}`), - patterns: usage.patterns.map((pattern) => ({ - prefix: `${prefix}${pattern.prefix}`, - suffix: pattern.suffix, - })), - } -} - -function evaluateKeyExpression( - expression: ts.Expression, - scopes: Scope[], - checker?: ts.TypeChecker, -): KeyExpressionUsage { - const selector = getSelectorKeyExpression(expression) - if (selector) { - return prependNamespace( - evaluateKeyExpression(selector.keyExpression, scopes, checker), - selector.namespace, - ) - } - - const value = evaluateStaticExpression(expression, scopes) - if (value.kind === 'strings') return { kind: 'keys', keys: value.values } - if (value.kind === 'selector') return evaluateKeyExpression(value.expression, scopes, checker) - if (value.kind === 'selectors') { - return value.expressions - .map((selectorExpression) => evaluateKeyExpression(selectorExpression, scopes, checker)) - .reduce(combineKeyExpressionUsages) - } - - const assertedTypeUsage = evaluateAssertedTypeKeyUsage(expression) - if (assertedTypeUsage) return assertedTypeUsage - - const checkerUsage = evaluateCheckerKeyUsage(expression, checker) - if (checkerUsage) return checkerUsage - - const unwrapped = unwrapExpression(expression) - if (ts.isConditionalExpression(unwrapped)) { - return combineKeyExpressionUsages( - evaluateKeyExpression(unwrapped.whenTrue, scopes, checker), - evaluateKeyExpression(unwrapped.whenFalse, scopes, checker), - ) - } - - if (!ts.isTemplateExpression(unwrapped)) return { kind: 'unknown' } - - const spans = unwrapped.templateSpans - const unknownIndexes = spans - .map((span, index) => - evaluateStaticExpression(span.expression, scopes).kind === 'strings' ? -1 : index, - ) - .filter((index) => index >= 0) - - if (!unknownIndexes.length) return { kind: 'unknown' } - - const firstUnknownIndex = unknownIndexes[0]! - const lastUnknownIndex = unknownIndexes.at(-1)! - - let prefixes = [unwrapped.head.text] - for (let index = 0; index < firstUnknownIndex; index++) { - const span = spans[index]! - const spanValue = evaluateStaticExpression(span.expression, scopes) - if (spanValue.kind !== 'strings') break - - prefixes = combineStringValues(prefixes, spanValue.values) - if (!prefixes.length) return { kind: 'unknown' } - - prefixes = prefixes.map((prefix) => `${prefix}${span.literal.text}`) - } - - let suffixes = [spans[lastUnknownIndex]!.literal.text] - for (let index = lastUnknownIndex + 1; index < spans.length; index++) { - const span = spans[index]! - const spanValue = evaluateStaticExpression(span.expression, scopes) - if (spanValue.kind !== 'strings') { - suffixes = [''] - } else { - suffixes = combineStringValues(suffixes, spanValue.values) - if (!suffixes.length) suffixes = [''] - } - suffixes = suffixes.map((suffix) => `${suffix}${span.literal.text}`) - } - - return { - kind: 'patterns', - patterns: prefixes.flatMap((prefix) => suffixes.map((suffix) => ({ prefix, suffix }))), - } -} - -function prependKeyPrefix(usage: KeyExpressionUsage, keyPrefix?: string): KeyExpressionUsage { - if (!keyPrefix) return usage - - const prefix = `${keyPrefix}.` - if (usage.kind === 'keys') - return { kind: 'keys', keys: usage.keys.map((key) => `${prefix}${key}`) } - - if (usage.kind === 'patterns') { - return { - kind: 'patterns', - patterns: usage.patterns.map((pattern) => ({ - prefix: `${prefix}${pattern.prefix}`, - suffix: pattern.suffix, - })), - } - } - - return { kind: 'patterns', patterns: [{ prefix, suffix: '' }] } -} - -function getLastOptionsObject(callExpression: ts.CallExpression) { - for (let index = callExpression.arguments.length - 1; index >= 1; index--) { - const argument = unwrapExpression(callExpression.arguments[index]! as ts.Expression) - if (ts.isObjectLiteralExpression(argument)) return argument - } - return undefined -} - -function getUseTranslationInfo( - callExpression: ts.CallExpression, - scopes: Scope[], - catalog: Catalog, -): TranslationFunctionInfo { - const namespaces = evaluateNamespaceExpression( - callExpression.arguments[0] as ts.Expression | undefined, - scopes, - catalog, - ) - const options = callExpression.arguments[1] - const optionsObject = options ? unwrapExpression(options as ts.Expression) : undefined - const keyPrefixProperty = - optionsObject && ts.isObjectLiteralExpression(optionsObject) - ? getObjectProperty(optionsObject, 'keyPrefix') - : undefined - const keyPrefixValue = keyPrefixProperty - ? evaluateStaticExpression(keyPrefixProperty.initializer, scopes) - : undefined - - return { - namespaces: callExpression.arguments[0] ? namespaces : [DEFAULT_NAMESPACE], - keyPrefix: - keyPrefixValue?.kind === 'strings' && keyPrefixValue.values.length === 1 - ? keyPrefixValue.values[0] - : undefined, - } -} - -function addExactKey(collector: UsageCollector, namespace: string, key: string, catalog: Catalog) { - if (!collector.exactKeys.has(namespace)) collector.exactKeys.set(namespace, new Set()) - - const exactKeys = collector.exactKeys.get(namespace)! - exactKeys.add(key) - - const namespaceKeys = catalog.keysByNamespace.get(namespace) - if (!namespaceKeys) return - - for (const suffix of PLURAL_SUFFIXES) { - const pluralKey = `${key}${suffix}` - if (namespaceKeys.has(pluralKey)) exactKeys.add(pluralKey) - } -} - -function addPattern( - collector: UsageCollector, - namespace: string, - pattern: { prefix: string; suffix: string }, - location: UsageLocation, -) { - if (!pattern.prefix && !pattern.suffix) { - collector.protectedNamespaces.add(namespace) - return - } - - collector.patterns.push({ - namespace, - prefix: pattern.prefix, - suffix: pattern.suffix, - ...location, - }) -} - -function splitNamespaceKey(key: string, catalog: Catalog) { - const separatorIndex = key.indexOf(':') - if (separatorIndex <= 0) return undefined - - const namespace = normalizeNamespace(key.slice(0, separatorIndex), catalog) - if (!catalog.keysByNamespace.has(namespace)) return undefined - - return { - namespace, - key: key.slice(separatorIndex + 1), - } -} - -function inferNamespacesForKey(key: string, catalog: Catalog) { - const namespaces: string[] = [] - for (const [namespace, keys] of catalog.keysByNamespace.entries()) { - if (keys.has(key) || PLURAL_SUFFIXES.some((suffix) => keys.has(`${key}${suffix}`))) - namespaces.push(namespace) - } - return uniqueSorted(namespaces) -} - -function patternMatchesKey(pattern: { prefix: string; suffix: string }, key: string) { - return key.startsWith(pattern.prefix) && key.endsWith(pattern.suffix) -} - -function inferNamespacesForPattern(pattern: { prefix: string; suffix: string }, catalog: Catalog) { - const namespaces: string[] = [] - for (const [namespace, keys] of catalog.keysByNamespace.entries()) { - if (Array.from(keys).some((key) => patternMatchesKey(pattern, key))) namespaces.push(namespace) - } - return uniqueSorted(namespaces) -} - -function locationFor(node: ts.Node, sourceFile: ts.SourceFile): UsageLocation { - return { - file: toPosixPath(path.relative(process.cwd(), sourceFile.fileName)), - line: sourceFile.getLineAndCharacterOfPosition(node.getStart(sourceFile)).line + 1, - expression: node.getText(sourceFile), - } -} - -function recordUnknownUsage( - collector: UsageCollector, - namespaces: string[], - catalog: Catalog, - location: UsageLocation, - reason: string, -) { - const targetNamespaces = namespaces.length - ? namespaces - : Array.from(catalog.keysByNamespace.keys()) - for (const namespace of targetNamespaces) collector.protectedNamespaces.add(namespace) - - collector.unresolvedUsages.push({ - ...location, - namespace: namespaces.length === 1 ? namespaces[0] : undefined, - reason, - }) -} - -function recordKeyUsage( - collector: UsageCollector, - usage: KeyExpressionUsage, - namespaces: string[], - catalog: Catalog, - location: UsageLocation, -) { - if (usage.kind === 'unknown') { - const targetNamespaces = usage.namespace - ? [normalizeNamespace(usage.namespace, catalog)] - : namespaces - recordUnknownUsage( - collector, - targetNamespaces, - catalog, - location, - 'Unable to statically resolve translation key', - ) - return - } - - if (usage.kind === 'keys' || usage.kind === 'mixed') { - for (const rawKey of usage.keys) { - const namespacedKey = splitNamespaceKey(rawKey, catalog) - if (namespacedKey) { - addExactKey(collector, namespacedKey.namespace, namespacedKey.key, catalog) - continue - } - - const targetNamespaces = namespaces.length - ? namespaces - : inferNamespacesForKey(rawKey, catalog) - if (!targetNamespaces.length) { - collector.unresolvedUsages.push({ - ...location, - reason: `No namespace contains key "${rawKey}"`, - }) - continue - } - - for (const namespace of targetNamespaces) addExactKey(collector, namespace, rawKey, catalog) - } - } - - if (usage.kind === 'keys') return - - const patterns = usage.kind === 'mixed' ? usage.patterns : usage.patterns - for (const rawPattern of patterns) { - const namespacedPattern = splitNamespaceKey(rawPattern.prefix, catalog) - if (namespacedPattern) { - addPattern( - collector, - namespacedPattern.namespace, - { - prefix: namespacedPattern.key, - suffix: rawPattern.suffix, - }, - location, - ) - continue - } - - const targetNamespaces = namespaces.length - ? namespaces - : inferNamespacesForPattern(rawPattern, catalog) - if (!targetNamespaces.length) continue - - for (const namespace of targetNamespaces) addPattern(collector, namespace, rawPattern, location) - } -} - -function getJsxAttribute(element: ts.JsxOpeningLikeElement, name: string) { - return element.attributes.properties.find((property): property is ts.JsxAttribute => { - return ( - ts.isJsxAttribute(property) && ts.isIdentifier(property.name) && property.name.text === name - ) - }) -} - -function getJsxAttributeExpression(attribute: ts.JsxAttribute | undefined) { - if (!attribute?.initializer) return undefined - - if (isStringNode(attribute.initializer)) return attribute.initializer - - if (ts.isJsxExpression(attribute.initializer) && attribute.initializer.expression) - return attribute.initializer.expression - - return undefined -} - -function isSelectorTranslationForwarder( - node: ts.CallExpression, - keyExpression: ts.Expression, - isRegisteredSelectorCallable: boolean, - checker: ts.TypeChecker | undefined, - catalog: Catalog, -) { - if (!ts.isIdentifier(keyExpression)) return false - - let current: ts.Node | undefined = node.parent - let functionLike: ts.FunctionLikeDeclaration | undefined - while (current && !ts.isSourceFile(current)) { - if (isFunctionLikeWithParameters(current)) { - functionLike = current - break - } - current = current.parent - } - - const selectorParameter = functionLike?.parameters.find((parameter) => { - return ts.isIdentifier(parameter.name) && parameter.name.text === keyExpression.text - }) - const forwardsSelectorParameter = Boolean( - selectorParameter && - checker && - isSelectorParamType(checker.getTypeAtLocation(selectorParameter), checker), - ) - const calleeIsSelectorCallable = - isRegisteredSelectorCallable || - Boolean(getCheckerTranslationFunctionInfo(node.expression, checker, catalog)) - return Boolean(forwardsSelectorParameter && calleeIsSelectorCallable) -} - -function analyzeSourceFile( - filePath: string, - catalog: Catalog, - collector: UsageCollector, - checker?: ts.TypeChecker, - sourceFileFromProgram?: ts.SourceFile, -) { - const sourceFile = - sourceFileFromProgram ?? - (() => { - const sourceText = fs.readFileSync(filePath, 'utf8') - const scriptKind = - filePath.endsWith('.tsx') || filePath.endsWith('.jsx') - ? ts.ScriptKind.TSX - : ts.ScriptKind.TS - return ts.createSourceFile(filePath, sourceText, ts.ScriptTarget.Latest, true, scriptKind) - })() - const scopes: Scope[] = [{ values: new Map(), translationFunctions: new Map() }] - - const withScope = (callback: () => void) => { - scopes.push({ values: new Map(), translationFunctions: new Map() }) - callback() - scopes.pop() - } - - function visit(node: ts.Node) { - if (isFunctionLikeWithParameters(node)) { - withScope(() => { - const currentScope = scopes.at(-1)! - for (const parameter of node.parameters) handleFunctionParameter(parameter, currentScope) - - ts.forEachChild(node, visit) - }) - return - } - - if (ts.isBlock(node)) { - withScope(() => ts.forEachChild(node, visit)) - return - } - - if (ts.isImportDeclaration(node)) handleImportDeclaration(node) - - if (ts.isVariableDeclaration(node)) { - handleVariableDeclaration(node) - } - - if (isStringNode(node)) handleContextualI18nLiteral(node) - - if (ts.isCallExpression(node)) handleCallExpression(node) - - if (ts.isJsxSelfClosingElement(node) || ts.isJsxOpeningElement(node)) handleJsxElement(node) - - ts.forEachChild(node, visit) - } - - function handleImportDeclaration(node: ts.ImportDeclaration) { - if (!ts.isStringLiteral(node.moduleSpecifier) || node.moduleSpecifier.text !== 'i18next') return - - const namedBindings = node.importClause?.namedBindings - if (!namedBindings || !ts.isNamedImports(namedBindings)) return - - const currentScope = scopes.at(-1)! - for (const element of namedBindings.elements) { - const importedName = element.propertyName?.text ?? element.name.text - if (importedName === 't') addTranslationFunction(currentScope, element.name.text) - } - } - - function handleFunctionParameter(node: ts.ParameterDeclaration, scope: Scope) { - const translationInfoFromType = getTranslationFunctionInfoFromType(node.type, catalog) - if (ts.isIdentifier(node.name)) { - const checkerTranslationInfo = getCheckerTranslationFunctionInfo(node.name, checker, catalog) - const translationInfo = checkerTranslationInfo ?? translationInfoFromType - if (node.name.text === 't' || translationInfo) - addTranslationFunction(scope, node.name.text, translationInfo) - return - } - - if (!ts.isObjectBindingPattern(node.name)) return - - for (const element of node.name.elements) { - if (!ts.isIdentifier(element.name)) continue - - const propertyName = - element.propertyName && ts.isIdentifier(element.propertyName) - ? element.propertyName.text - : undefined - if (propertyName === 't' || element.name.text === 't') { - const translationInfo = getCheckerTranslationFunctionInfo(element.name, checker, catalog) - addTranslationFunction(scope, element.name.text, translationInfo) - } - } - } - - function handleContextualI18nLiteral(node: ts.StringLiteral | ts.NoSubstitutionTemplateLiteral) { - if (!checker || (!node.text.includes('.') && !node.text.includes(':'))) return - - const contextualType = checker.getContextualType(node) - if (!contextualType) return - - const contextualKeys = extractStringValuesFromType(contextualType) - if (!contextualKeys?.includes(node.text)) return - - const preservesI18nAlias = /\bI18nKeys?\b|\bI18nKeys?(?:By|With)Prefix\b/.test( - checker.typeToString(contextualType), - ) - // Imported conditional aliases are commonly expanded into literal unions by the checker. - const containsOnlyCatalogKeys = contextualKeys.every( - (key) => - Boolean(splitNamespaceKey(key, catalog)) || inferNamespacesForKey(key, catalog).length, - ) - if (!preservesI18nAlias && !containsOnlyCatalogKeys) return - - recordKeyUsage( - collector, - { kind: 'keys', keys: [node.text] }, - [], - catalog, - locationFor(node, sourceFile), - ) - } - - function handleVariableDeclaration(node: ts.VariableDeclaration) { - if (!node.initializer) return - - const currentScope = scopes.at(-1)! - if (ts.isIdentifier(node.name)) - currentScope.values.set(node.name.text, evaluateStaticExpression(node.initializer, scopes)) - - if (!ts.isObjectBindingPattern(node.name)) return - - const initializer = unwrapExpression(node.initializer) - const callExpression = ts.isAwaitExpression(initializer) - ? unwrapExpression(initializer.expression) - : initializer - - if (!ts.isCallExpression(callExpression) || !ts.isIdentifier(callExpression.expression)) return - - const calleeName = callExpression.expression.text - if (calleeName !== 'useTranslation' && calleeName !== 'getTranslation') return - - const translationInfo = - calleeName === 'useTranslation' - ? getUseTranslationInfo(callExpression, scopes, catalog) - : { - namespaces: callExpression.arguments[1] - ? evaluateNamespaceExpression( - callExpression.arguments[1] as ts.Expression, - scopes, - catalog, - ) - : [DEFAULT_NAMESPACE], - keyPrefix: undefined, - } - - for (const element of node.name.elements) { - const propertyName = - element.propertyName && ts.isIdentifier(element.propertyName) - ? element.propertyName.text - : ts.isIdentifier(element.name) - ? element.name.text - : undefined - if (propertyName !== 't' || !ts.isIdentifier(element.name)) continue - - currentScope.translationFunctions.set(element.name.text, translationInfo) - } - } - - function handleCallExpression(node: ts.CallExpression) { - const registeredIdentifierTranslationInfo = ts.isIdentifier(node.expression) - ? lookupTranslationFunction(node.expression.text, scopes) - : undefined - const registeredTranslationInfo = - registeredIdentifierTranslationInfo ?? - (ts.isPropertyAccessExpression(node.expression) && node.expression.name.text === 't' - ? { namespaces: [DEFAULT_NAMESPACE], keyPrefix: undefined } - : undefined) - const translationInfo = - registeredTranslationInfo ?? - getCheckerTranslationFunctionInfo(node.expression, checker, catalog) - if (!translationInfo || !node.arguments.length) return - - const options = getLastOptionsObject(node) - const namespaceProperty = options ? getObjectProperty(options, 'ns') : undefined - const optionNamespaces = namespaceProperty - ? evaluateNamespaceExpression(namespaceProperty.initializer, scopes, catalog) - : [] - const namespaces = optionNamespaces.length ? optionNamespaces : translationInfo.namespaces - const keyExpression = node.arguments[translationInfo.selectorArgumentIndex ?? 0] as - | ts.Expression - | undefined - if (!keyExpression) return - const keyUsage = prependKeyPrefix( - evaluateKeyExpression(keyExpression, scopes, checker), - translationInfo.keyPrefix, - ) - - if ( - keyUsage.kind === 'unknown' && - isSelectorTranslationForwarder( - node, - keyExpression, - Boolean(registeredIdentifierTranslationInfo), - checker, - catalog, - ) - ) { - return - } - - recordKeyUsage(collector, keyUsage, namespaces, catalog, locationFor(node, sourceFile)) - } - - function handleJsxElement(node: ts.JsxOpeningLikeElement) { - if (node.tagName.getText(sourceFile) !== 'Trans') return - - const keyExpression = getJsxAttributeExpression(getJsxAttribute(node, 'i18nKey')) - if (!keyExpression) return - - const namespaceExpression = getJsxAttributeExpression(getJsxAttribute(node, 'ns')) - const namespaces = namespaceExpression - ? evaluateNamespaceExpression(namespaceExpression, scopes, catalog) - : [DEFAULT_NAMESPACE] - const keyUsage = evaluateKeyExpression(keyExpression, scopes, checker) - - recordKeyUsage(collector, keyUsage, namespaces, catalog, locationFor(node, sourceFile)) - } - - visit(sourceFile) -} - -function getCatalog(webRoot: string, defaultLocale: string, targetFiles: string[] = []): Catalog { - const localeDir = path.join(webRoot, 'i18n', defaultLocale) - const targetFileNames = new Set(targetFiles.map((file) => file.replace(/\.json$/, ''))) - const targetNamespaces = new Set( - targetFiles.map((file) => fileNameToNamespace(file.replace(/\.json$/, ''))), - ) - const keysByNamespace = new Map>() - const fileNameByNamespace = new Map() - const namespaceByFileName = new Map() - - for (const file of fs - .readdirSync(localeDir) - .filter((file) => file.endsWith('.json')) - .sort()) { - const fileName = file.replace(/\.json$/, '') - const namespace = fileNameToNamespace(fileName) - if (targetFiles.length && !targetFileNames.has(fileName) && !targetNamespaces.has(namespace)) - continue - - const content = JSON.parse(fs.readFileSync(path.join(localeDir, file), 'utf8')) as Record< - string, - unknown - > - keysByNamespace.set(namespace, new Set(Object.keys(content))) - fileNameByNamespace.set(namespace, fileName) - namespaceByFileName.set(fileName, namespace) - } - - return { keysByNamespace, fileNameByNamespace, namespaceByFileName } -} - -function listSourceFiles(webRoot: string, explicitSourceFiles?: string[]) { - if (explicitSourceFiles?.length) - return explicitSourceFiles.map((file) => - path.isAbsolute(file) ? file : path.join(webRoot, file), - ) - - const sourceFiles: string[] = [] - const walk = (directory: string) => { - for (const entry of fs.readdirSync(directory, { withFileTypes: true })) { - if (entry.isDirectory()) { - if (!SKIPPED_DIRECTORIES.has(entry.name)) walk(path.join(directory, entry.name)) - continue - } - - if (!entry.isFile()) continue - - const filePath = path.join(directory, entry.name) - if (filePath.endsWith('.d.ts')) continue - - if (SOURCE_EXTENSIONS.has(path.extname(entry.name))) sourceFiles.push(filePath) - } - } - - walk(webRoot) - return sourceFiles.sort() -} - -function mapToRecord(map: Map>) { - const record: Record = {} - for (const [namespace, values] of map.entries()) { - const sortedValues = uniqueSorted(values) - if (sortedValues.length) record[namespace] = sortedValues - } - return record -} - -function createTypeChecker(webRoot: string, sourceFiles: string[]) { - try { - const configPath = ts.findConfigFile(webRoot, ts.sys.fileExists, 'tsconfig.json') - const config = configPath ? ts.readConfigFile(configPath, ts.sys.readFile) : undefined - const parsedConfig = - configPath && config && !config.error - ? ts.parseJsonConfigFileContent(config.config, ts.sys, webRoot) - : undefined - - const compilerOptions: ts.CompilerOptions = { - ...(parsedConfig?.options ?? {}), - allowJs: true, - jsx: ts.JsxEmit.ReactJSX, - noEmit: true, - skipLibCheck: true, - } - - const program = ts.createProgram({ - rootNames: sourceFiles, - options: compilerOptions, - }) - - return { - checker: program.getTypeChecker(), - sourceFileByPath: new Map( - program - .getSourceFiles() - .map((sourceFile) => [path.resolve(sourceFile.fileName), sourceFile]), - ), - } - } catch { - return undefined - } -} - -export async function analyzeUnusedTranslations( - options: AnalyzeUnusedTranslationsOptions = {}, -): Promise { - const webRoot = options.webRoot ?? path.resolve(fileURLToPath(new URL('../..', import.meta.url))) - const defaultLocale = options.defaultLocale ?? DEFAULT_LOCALE - const catalog = getCatalog(webRoot, defaultLocale, options.files) - const sourceFiles = listSourceFiles(webRoot, options.sourceFiles) - const typeChecker = createTypeChecker(webRoot, sourceFiles) - const collector: UsageCollector = { - exactKeys: new Map(), - patterns: [], - protectedNamespaces: new Set(), - unresolvedUsages: [], - } - - for (const file of sourceFiles) - analyzeSourceFile( - file, - catalog, - collector, - typeChecker?.checker, - typeChecker?.sourceFileByPath.get(path.resolve(file)), - ) - - const unusedKeys = new Map>() - for (const [namespace, keys] of catalog.keysByNamespace.entries()) { - if (collector.protectedNamespaces.has(namespace)) continue - - const exactKeys = collector.exactKeys.get(namespace) ?? new Set() - const patterns = collector.patterns.filter((pattern) => pattern.namespace === namespace) - for (const key of keys) { - if (exactKeys.has(key) || patterns.some((pattern) => patternMatchesKey(pattern, key))) - continue - - if (!unusedKeys.has(namespace)) unusedKeys.set(namespace, new Set()) - unusedKeys.get(namespace)!.add(key) - } - } - - return { - webRoot, - defaultLocale, - unusedKeysByNamespace: mapToRecord(unusedKeys), - usedKeysByNamespace: mapToRecord(collector.exactKeys), - allKeysByNamespace: mapToRecord(catalog.keysByNamespace), - dynamicKeyPatterns: collector.patterns.sort((left, right) => { - return `${left.namespace}:${left.prefix}:${left.suffix}`.localeCompare( - `${right.namespace}:${right.prefix}:${right.suffix}`, - ) - }), - protectedNamespaces: uniqueSorted(collector.protectedNamespaces), - unresolvedUsages: collector.unresolvedUsages, - namespaceFiles: Object.fromEntries(catalog.fileNameByNamespace.entries()), - } -} - -function listLocales(webRoot: string) { - return fs - .readdirSync(path.join(webRoot, 'i18n'), { withFileTypes: true }) - .filter((entry) => entry.isDirectory()) - .map((entry) => entry.name) - .sort() -} - -export async function removeUnusedTranslations( - options: RemoveUnusedTranslationsOptions, -): Promise { - const webRoot = options.webRoot ?? options.analysis.webRoot - const locales = options.locales?.length ? options.locales : listLocales(webRoot) - const removedKeys: RemovedKey[] = [] - - for (const locale of locales) { - for (const [namespace, keys] of Object.entries(options.analysis.unusedKeysByNamespace).sort()) { - const fileName = - options.analysis.namespaceFiles[namespace] ?? - namespaceToFileName(namespace, { - keysByNamespace: new Map(), - fileNameByNamespace: new Map( - Object.entries(options.analysis.namespaceFiles).map(([ns, file]) => [ns, file]), - ), - namespaceByFileName: new Map(), - }) - const filePath = path.join(webRoot, 'i18n', locale, `${fileName}.json`) - if (!fs.existsSync(filePath)) continue - - const content = JSON.parse(fs.readFileSync(filePath, 'utf8')) as Record - let modified = false - for (const key of keys) { - if (!(key in content)) continue - - delete content[key] - modified = true - removedKeys.push({ locale, namespace, key }) - } - - if (modified) fs.writeFileSync(filePath, `${JSON.stringify(content, null, 2)}\n`, 'utf8') - } - } - - return { removedKeys } -} diff --git a/web/scripts/migrate-i18n-selectors.ts b/web/scripts/migrate-i18n-selectors.ts index a9e4639c159..0e552538f0e 100644 --- a/web/scripts/migrate-i18n-selectors.ts +++ b/web/scripts/migrate-i18n-selectors.ts @@ -27,7 +27,7 @@ const SKIPPED_DIRECTORIES = new Set([ '.vinext', 'coverage', 'dist', - 'i18n', + 'locales', 'node_modules', 'public', ]) diff --git a/web/scripts/prune-unused-i18n.ts b/web/scripts/prune-unused-i18n.ts deleted file mode 100644 index 9ec212956fc..00000000000 --- a/web/scripts/prune-unused-i18n.ts +++ /dev/null @@ -1,163 +0,0 @@ -import type { - AnalyzeUnusedTranslationsResult, - RemoveUnusedTranslationsResult, -} from './i18n-prune/core' -import path from 'node:path' -import { pathToFileURL } from 'node:url' -import { analyzeUnusedTranslations, removeUnusedTranslations } from './i18n-prune/core' - -type CliArgs = { - write: boolean - json: boolean - help: boolean - files: string[] - locales: string[] - errors: string[] -} - -function collectValues(argv: string[], startIndex: number) { - const values: string[] = [] - let cursor = startIndex + 1 - while (cursor < argv.length && !argv[cursor]!.startsWith('--')) { - values.push(argv[cursor]!) - cursor++ - } - return { values, nextIndex: cursor - 1 } -} - -function parseArgs(argv: string[]): CliArgs { - const args: CliArgs = { - write: false, - json: false, - help: false, - files: [], - locales: [], - errors: [], - } - - for (let index = 2; index < argv.length; index++) { - const arg = argv[index]! - if (arg === '--write') { - args.write = true - continue - } - if (arg === '--json') { - args.json = true - continue - } - if (arg === '-h' || arg === '--help') { - args.help = true - continue - } - if (arg === '--file') { - const { values, nextIndex } = collectValues(argv, index) - if (!values.length) args.errors.push('--file requires at least one value') - args.files.push(...values) - index = nextIndex - continue - } - if (arg === '--lang') { - const { values, nextIndex } = collectValues(argv, index) - if (!values.length) args.errors.push('--lang requires at least one value') - args.locales.push(...values) - index = nextIndex - continue - } - args.errors.push(`Unknown argument: ${arg}`) - } - - return args -} - -function printHelp() { - console.log(`Usage: pnpm run i18n:prune-unused [options] - -Options: - --write Remove unused keys from locale JSON files - --file Analyze only specific namespace files or namespaces - --lang With --write, remove only from specific locales - --json Print JSON output - -h, --help Show help - -Examples: - pnpm run i18n:prune-unused - pnpm run i18n:prune-unused --file app common - pnpm run i18n:prune-unused --write -`) -} - -function countUnusedKeys(result: AnalyzeUnusedTranslationsResult) { - return Object.values(result.unusedKeysByNamespace).reduce((total, keys) => total + keys.length, 0) -} - -function printHumanSummary( - result: AnalyzeUnusedTranslationsResult, - removed?: RemoveUnusedTranslationsResult, -) { - const totalUnused = countUnusedKeys(result) - console.log(`Found ${totalUnused} unused i18n keys.`) - - for (const [namespace, keys] of Object.entries(result.unusedKeysByNamespace)) { - console.log(`\n${namespace} (${keys.length})`) - for (const key of keys) console.log(` - ${key}`) - } - - if (result.protectedNamespaces.length) { - console.log( - `\nProtected namespaces with unresolved dynamic keys: ${result.protectedNamespaces.join(', ')}`, - ) - console.log( - 'These namespaces were not pruned because at least one key could not be statically resolved.', - ) - } - - if (result.dynamicKeyPatterns.length) { - console.log(`\nDynamic key patterns kept: ${result.dynamicKeyPatterns.length}`) - for (const pattern of result.dynamicKeyPatterns.slice(0, 20)) { - const suffix = pattern.suffix ? ` ... ${pattern.suffix}` : '' - console.log(` - ${pattern.namespace}: ${pattern.prefix}*${suffix}`) - } - if (result.dynamicKeyPatterns.length > 20) - console.log(` ... ${result.dynamicKeyPatterns.length - 20} more`) - } - - if (removed) console.log(`\nRemoved ${removed.removedKeys.length} keys across locale files.`) - else if (totalUnused) console.log('\nRun again with --write to remove these keys.') -} - -async function runCli() { - const args = parseArgs(process.argv) - if (args.help) { - printHelp() - return - } - - if (args.errors.length) { - for (const error of args.errors) console.error(error) - printHelp() - process.exitCode = 1 - return - } - - const result = await analyzeUnusedTranslations({ files: args.files }) - const removed = args.write - ? await removeUnusedTranslations({ analysis: result, locales: args.locales }) - : undefined - - if (args.json) { - console.log(JSON.stringify({ analysis: result, removal: removed }, null, 2)) - } else { - printHumanSummary(result, removed) - } - - if (!args.write && countUnusedKeys(result)) process.exitCode = 1 -} - -const entryPath = process.argv[1] ? pathToFileURL(path.resolve(process.argv[1])).href : '' -if (import.meta.url === entryPath) { - runCli().catch((error: unknown) => { - const message = error instanceof Error ? error.message : String(error) - console.error(`Unexpected error: ${message}`) - process.exit(1) - }) -} diff --git a/web/service/__tests__/request-error-toast.spec.ts b/web/service/__tests__/request-error-toast.spec.ts index c76780b4c29..ce0542e6fe9 100644 --- a/web/service/__tests__/request-error-toast.spec.ts +++ b/web/service/__tests__/request-error-toast.spec.ts @@ -1,10 +1,10 @@ -import { toast } from '@langgenius/dify-ui/toast' import { afterEach, beforeEach, describe, expect, it, vi } from 'vite-plus/test' +import { toast } from '@/app/notifications' // oxlint-disable-next-line no-restricted-imports -- Exercise the shared request transport and its notification behavior. import { base } from '../fetch' import { clearRequestErrorToasts, notifyRequestError } from '../request-error-toast' -vi.mock('@langgenius/dify-ui/toast', () => ({ +vi.mock('@/app/notifications', () => ({ toast: { error: vi.fn() }, })) diff --git a/web/service/__tests__/use-plugins.spec.tsx b/web/service/__tests__/use-plugins.spec.tsx index c6b64fdef43..0a8a2a8edce 100644 --- a/web/service/__tests__/use-plugins.spec.tsx +++ b/web/service/__tests__/use-plugins.spec.tsx @@ -292,6 +292,16 @@ const createPluginEntity = (): PluginEntity => ({ }) describe('normalizeInstalledPluginDetail', () => { + it('preserves Portuguese metadata alongside the existing UI locale spelling', () => { + const plugin = createPluginEntity() + plugin.declaration.label = { en_US: 'Plugin', pt_BR: 'Extensão' } + const detail = normalizeInstalledPluginDetail(plugin) + + expect(detail.declaration.label.pt_BR).toBe('Extensão') + expect(detail.declaration.label['pt-BR']).toBe('Extensão') + expect(detail.declaration.description.pt_BR).toBe('Plugin description') + }) + it('adapts generic and category list items to the legacy detail model', () => { const genericDetail = normalizeInstalledPluginDetail(createPluginEntity()) const categoryDetail = normalizeInstalledPluginDetail( diff --git a/web/service/__tests__/use-triggers.spec.ts b/web/service/__tests__/use-triggers.spec.ts index b6757e7b821..3699fccce64 100644 --- a/web/service/__tests__/use-triggers.spec.ts +++ b/web/service/__tests__/use-triggers.spec.ts @@ -101,6 +101,16 @@ const createGeneratedTriggerProvider = (): GeneratedTriggerProvider => ({ }) describe('trigger provider normalization', () => { + it('preserves Portuguese metadata alongside the existing UI locale spelling', () => { + const provider = createGeneratedTriggerProvider() + provider.label = { en_US: 'Provider', pt_BR: 'Provedor' } + const normalized = normalizeTriggerProvider(provider) + + expect(normalized.label.pt_BR).toBe('Provedor') + expect(normalized.label['pt-BR']).toBe('Provedor') + expect(normalized.description.pt_BR).toBe('GitHub trigger provider') + }) + it('should preserve falsy event parameter defaults', () => { const normalizedProvider = normalizeTriggerProvider(createGeneratedTriggerProvider()) const triggerWithProvider = convertToTriggerWithProvider(normalizedProvider) diff --git a/web/service/access-control/use-app-access-config.ts b/web/service/access-control/use-app-access-config.ts index ee134da5ff6..41f66097974 100644 --- a/web/service/access-control/use-app-access-config.ts +++ b/web/service/access-control/use-app-access-config.ts @@ -1,4 +1,4 @@ -import type { AccessControlTemplateLanguage } from '@/i18n-config/language' +import type { AccessControlTemplateLanguage } from '@/i18n/language' import type { RemoveAppAccessPolicyMemberBindingsRequest, UpdateAppUserAccessSettingsRequest, diff --git a/web/service/access-control/use-dataset-access-config.ts b/web/service/access-control/use-dataset-access-config.ts index 2361a5fb776..b976a88ad85 100644 --- a/web/service/access-control/use-dataset-access-config.ts +++ b/web/service/access-control/use-dataset-access-config.ts @@ -1,4 +1,4 @@ -import type { AccessControlTemplateLanguage } from '@/i18n-config/language' +import type { AccessControlTemplateLanguage } from '@/i18n/language' import type { RemoveDatasetAccessPolicyMemberBindingsRequest, UpdateDatasetUserAccessSettingsRequest, diff --git a/web/service/access-control/use-member-roles.ts b/web/service/access-control/use-member-roles.ts index 8a351fc46b7..c7202da7923 100644 --- a/web/service/access-control/use-member-roles.ts +++ b/web/service/access-control/use-member-roles.ts @@ -1,4 +1,4 @@ -import type { AccessControlTemplateLanguage } from '@/i18n-config/language' +import type { AccessControlTemplateLanguage } from '@/i18n/language' import type { RolesOfMemberResponse, UpdateRolesOfMemberRequest } from '@/models/access-control' import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query' import { get, put } from '../base' diff --git a/web/service/base.spec.ts b/web/service/base.spec.ts index 2360eefc26a..586e4fcb9f4 100644 --- a/web/service/base.spec.ts +++ b/web/service/base.spec.ts @@ -1,6 +1,6 @@ -import { toast } from '@langgenius/dify-ui/toast' import { waitFor } from '@testing-library/react' import { afterEach, beforeEach, describe, expect, it, vi } from 'vite-plus/test' +import { toast } from '@/app/notifications' import { PUBLIC_API_PREFIX } from '@/config' // oxlint-disable-next-line no-restricted-imports import { @@ -18,7 +18,7 @@ import { const refreshAccessTokenOrReLoginMock = vi.hoisted(() => vi.fn()) -vi.mock('@langgenius/dify-ui/toast', () => ({ +vi.mock('@/app/notifications', () => ({ toast: { error: vi.fn(), }, diff --git a/web/service/base.ts b/web/service/base.ts index 4d0d2f9c9f9..a50169071d6 100644 --- a/web/service/base.ts +++ b/web/service/base.ts @@ -28,9 +28,9 @@ import type { WorkflowPausedResponse, WorkflowStartedResponse, } from '@/types/workflow' -import { toast } from '@langgenius/dify-ui/toast' import Cookies from 'js-cookie' import { discardRegistrationSessionState } from '@/app/components/base/amplitude/registration-session-state' +import { toast } from '@/app/notifications' import { API_PREFIX, CSRF_COOKIE_NAME, diff --git a/web/service/fetch.spec.ts b/web/service/fetch.spec.ts index 55c7cc613cc..e74a774911b 100644 --- a/web/service/fetch.spec.ts +++ b/web/service/fetch.spec.ts @@ -8,14 +8,14 @@ import { // oxlint-disable-next-line no-restricted-imports import { base } from './fetch' -vi.mock('@langgenius/dify-ui/toast', () => ({ +vi.mock('@/app/notifications', () => ({ toast: { add: vi.fn(), error: vi.fn(), }, })) -const { toast } = await import('@langgenius/dify-ui/toast') +const { toast } = await import('@/app/notifications') describe('base', () => { beforeEach(() => { diff --git a/web/service/request-error-toast.ts b/web/service/request-error-toast.ts index 5588e0753c1..f1d9743fd32 100644 --- a/web/service/request-error-toast.ts +++ b/web/service/request-error-toast.ts @@ -1,4 +1,4 @@ -import { toast } from '@langgenius/dify-ui/toast' +import { toast } from '@/app/notifications' const DEDUPE_WINDOW_MS = 10000 diff --git a/web/service/use-common.ts b/web/service/use-common.ts index 62483cbd829..3ae58ec4e21 100644 --- a/web/service/use-common.ts +++ b/web/service/use-common.ts @@ -4,7 +4,7 @@ import type { ModelProvider, ModelTypeEnum, } from '@/app/components/header/account-setting/model-provider-page/declarations' -import type { AccessControlTemplateLanguage } from '@/i18n-config/language' +import type { AccessControlTemplateLanguage } from '@/i18n/language' import type { CodeBasedExtension, CommonResponse, diff --git a/web/service/use-explore.ts b/web/service/use-explore.ts index c7ca887fbe1..8da10f67f2a 100644 --- a/web/service/use-explore.ts +++ b/web/service/use-explore.ts @@ -1,6 +1,6 @@ import type { App, AppCategory } from '@/models/explore' import { useQuery } from '@tanstack/react-query' -import { useLocale } from '@/context/i18n' +import { useLocale } from '#i18n' import { consoleQuery } from '@/service/console' import { fetchAppList, fetchLearnDifyAppList } from './explore' diff --git a/web/service/use-plugins.ts b/web/service/use-plugins.ts index aae158bf66d..12bb54648b4 100644 --- a/web/service/use-plugins.ts +++ b/web/service/use-plugins.ts @@ -133,6 +133,7 @@ const normalizeI18nObject = ( en_US: en, zh_Hans: zhHans, ja_JP: ja, + pt_BR: ptBr, } } diff --git a/web/service/use-triggers.ts b/web/service/use-triggers.ts index 02d8225cf3b..e83ace5455a 100644 --- a/web/service/use-triggers.ts +++ b/web/service/use-triggers.ts @@ -91,6 +91,7 @@ const normalizeI18nObject = (value: GeneratedI18nObject | null | undefined, fall en_US: en, zh_Hans: zhHans, ja_JP: ja, + pt_BR: ptBr, } } @@ -129,6 +130,7 @@ const normalizeUnknownI18nObject = (value: unknown, fallback = '') => { en_US: en, zh_Hans: zhHans, ja_JP: ja, + pt_BR: ptBr, } } diff --git a/web/test/emoji-picker.ts b/web/test/emoji-picker.ts new file mode 100644 index 00000000000..dfd328285f8 --- /dev/null +++ b/web/test/emoji-picker.ts @@ -0,0 +1,22 @@ +import data from '@/public/emoji/emojibase-17.0.0/en/data.json' +import messages from '@/public/emoji/emojibase-17.0.0/en/messages.json' + +/** Exercise the real picker with the same data served in production, without network. */ +export function mockEmojiData() { + beforeEach(() => { + // oxlint-disable-next-line no-restricted-globals -- Reset Frimousse's own cache to exercise its data loader. + localStorage.removeItem('frimousse/data/en') + sessionStorage.removeItem('frimousse/metadata') + sessionStorage.setItem( + 'frimousse/metadata', + JSON.stringify({ emojiVersion: 17, countryFlags: true }), + ) + vi.spyOn(globalThis, 'fetch').mockImplementation(async (input) => { + const url = String(input) + if (!/^\/.*emoji\/emojibase-17\.0\.0\/en\/(?:data|messages)\.json$/.test(url)) + throw new Error(`Unexpected emoji data URL: ${url}`) + return new Response(JSON.stringify(url.endsWith('/messages.json') ? messages : data)) + }) + }) + afterEach(() => vi.restoreAllMocks()) +} diff --git a/web/types/app.ts b/web/types/app.ts index 48dda778bcb..b50027ada36 100644 --- a/web/types/app.ts +++ b/web/types/app.ts @@ -1,7 +1,7 @@ import type { ToolProviderType } from '@dify/contracts/api/console/agent/types.gen' import type { TagResponse as Tag } from '@dify/contracts/api/console/tags/types.gen' import type { UploadFileSetting } from '@/app/components/workflow/types' -import type { LanguagesSupported } from '@/i18n-config/language' +import type { LanguagesSupported } from '@/i18n/language' import type { AccessMode } from '@/models/access-control' import type { ExternalDataTool } from '@/models/common' import type { RerankingModeEnum, WeightedScoreEnum } from '@/models/datasets' diff --git a/web/types/i18n.d.ts b/web/types/i18n.d.ts index dc1d0965a3e..bef0f328350 100644 --- a/web/types/i18n.d.ts +++ b/web/types/i18n.d.ts @@ -1,4 +1,4 @@ -import type { defaultNS, Namespace, Resources } from '../i18n-config/resources' +import type { defaultNS, Namespace, Resources } from '../i18n/resources' import 'i18next' declare module 'i18next' { diff --git a/web/types/jsx.d.ts b/web/types/jsx.d.ts deleted file mode 100644 index f5f72af5204..00000000000 --- a/web/types/jsx.d.ts +++ /dev/null @@ -1,12 +0,0 @@ -// TypeScript type definitions for custom JSX elements -// Custom JSX elements for emoji-mart web components - -import 'react' - -declare module 'react' { - namespace JSX { - interface IntrinsicElements { - 'em-emoji': React.DetailedHTMLProps, HTMLElement> - } - } -} diff --git a/web/utils/__tests__/emoji.spec.ts b/web/utils/__tests__/emoji.spec.ts new file mode 100644 index 00000000000..1c9bb650739 --- /dev/null +++ b/web/utils/__tests__/emoji.spec.ts @@ -0,0 +1,17 @@ +import { resolveEmoji } from '../emoji' + +describe('persisted emoji compatibility', () => { + it.each([ + ['rabbit', '🐰'], + ['robot_face', '🤖'], + ['satisfied', '😆'], + [':+1:', '👍'], + ['🐻', '🐻'], + ['👩🏽‍💻', '👩🏽‍💻'], + ['unknown-id', 'unknown-id'], + ['', '🤖'], + [undefined, '🤖'], + ])('resolves %s to %s', (value, expected) => { + expect(resolveEmoji(value)).toBe(expected) + }) +}) diff --git a/web/utils/emoji-legacy.json b/web/utils/emoji-legacy.json new file mode 100644 index 00000000000..3ee2903d3a6 --- /dev/null +++ b/web/utils/emoji-legacy.json @@ -0,0 +1,1933 @@ +{ + "100": "💯", + "1234": "🔢", + "grinning": "😀", + "smiley": "😃", + "smile": "😄", + "grin": "😁", + "laughing": "😆", + "sweat_smile": "😅", + "rolling_on_the_floor_laughing": "🤣", + "joy": "😂", + "slightly_smiling_face": "🙂", + "upside_down_face": "🙃", + "melting_face": "🫠", + "wink": "😉", + "blush": "😊", + "innocent": "😇", + "smiling_face_with_3_hearts": "🥰", + "heart_eyes": "😍", + "star-struck": "🤩", + "kissing_heart": "😘", + "kissing": "😗", + "relaxed": "☺️", + "kissing_closed_eyes": "😚", + "kissing_smiling_eyes": "😙", + "smiling_face_with_tear": "🥲", + "yum": "😋", + "stuck_out_tongue": "😛", + "stuck_out_tongue_winking_eye": "😜", + "zany_face": "🤪", + "stuck_out_tongue_closed_eyes": "😝", + "money_mouth_face": "🤑", + "hugging_face": "🤗", + "face_with_hand_over_mouth": "🤭", + "face_with_open_eyes_and_hand_over_mouth": "🫢", + "face_with_peeking_eye": "🫣", + "shushing_face": "🤫", + "thinking_face": "🤔", + "saluting_face": "🫡", + "zipper_mouth_face": "🤐", + "face_with_raised_eyebrow": "🤨", + "neutral_face": "😐", + "expressionless": "😑", + "no_mouth": "😶", + "dotted_line_face": "🫥", + "face_in_clouds": "😶‍🌫️", + "smirk": "😏", + "unamused": "😒", + "face_with_rolling_eyes": "🙄", + "grimacing": "😬", + "face_exhaling": "😮‍💨", + "lying_face": "🤥", + "shaking_face": "🫨", + "relieved": "😌", + "pensive": "😔", + "sleepy": "😪", + "drooling_face": "🤤", + "sleeping": "😴", + "mask": "😷", + "face_with_thermometer": "🤒", + "face_with_head_bandage": "🤕", + "nauseated_face": "🤢", + "face_vomiting": "🤮", + "sneezing_face": "🤧", + "hot_face": "🥵", + "cold_face": "🥶", + "woozy_face": "🥴", + "dizzy_face": "😵", + "face_with_spiral_eyes": "😵‍💫", + "exploding_head": "🤯", + "face_with_cowboy_hat": "🤠", + "partying_face": "🥳", + "disguised_face": "🥸", + "sunglasses": "😎", + "nerd_face": "🤓", + "face_with_monocle": "🧐", + "confused": "😕", + "face_with_diagonal_mouth": "🫤", + "worried": "😟", + "slightly_frowning_face": "🙁", + "white_frowning_face": "☹️", + "open_mouth": "😮", + "hushed": "😯", + "astonished": "😲", + "flushed": "😳", + "pleading_face": "🥺", + "face_holding_back_tears": "🥹", + "frowning": "😦", + "anguished": "😧", + "fearful": "😨", + "cold_sweat": "😰", + "disappointed_relieved": "😥", + "cry": "😢", + "sob": "😭", + "scream": "😱", + "confounded": "😖", + "persevere": "😣", + "disappointed": "😞", + "sweat": "😓", + "weary": "😩", + "tired_face": "😫", + "yawning_face": "🥱", + "triumph": "😤", + "rage": "😡", + "angry": "😠", + "face_with_symbols_on_mouth": "🤬", + "smiling_imp": "😈", + "imp": "👿", + "skull": "💀", + "skull_and_crossbones": "☠️", + "hankey": "💩", + "clown_face": "🤡", + "japanese_ogre": "👹", + "japanese_goblin": "👺", + "ghost": "👻", + "alien": "👽", + "space_invader": "👾", + "robot_face": "🤖", + "smiley_cat": "😺", + "smile_cat": "😸", + "joy_cat": "😹", + "heart_eyes_cat": "😻", + "smirk_cat": "😼", + "kissing_cat": "😽", + "scream_cat": "🙀", + "crying_cat_face": "😿", + "pouting_cat": "😾", + "see_no_evil": "🙈", + "hear_no_evil": "🙉", + "speak_no_evil": "🙊", + "love_letter": "💌", + "cupid": "💘", + "gift_heart": "💝", + "sparkling_heart": "💖", + "heartpulse": "💗", + "heartbeat": "💓", + "revolving_hearts": "💞", + "two_hearts": "💕", + "heart_decoration": "💟", + "heavy_heart_exclamation_mark_ornament": "❣️", + "broken_heart": "💔", + "heart_on_fire": "❤️‍🔥", + "mending_heart": "❤️‍🩹", + "heart": "❤️", + "pink_heart": "🩷", + "orange_heart": "🧡", + "yellow_heart": "💛", + "green_heart": "💚", + "blue_heart": "💙", + "light_blue_heart": "🩵", + "purple_heart": "💜", + "brown_heart": "🤎", + "black_heart": "🖤", + "grey_heart": "🩶", + "white_heart": "🤍", + "kiss": "💋", + "anger": "💢", + "boom": "💥", + "dizzy": "💫", + "sweat_drops": "💦", + "dash": "💨", + "hole": "🕳️", + "speech_balloon": "💬", + "eye-in-speech-bubble": "👁️‍🗨️", + "left_speech_bubble": "🗨️", + "right_anger_bubble": "🗯️", + "thought_balloon": "💭", + "zzz": "💤", + "wave": "👋", + "raised_back_of_hand": "🤚", + "raised_hand_with_fingers_splayed": "🖐️", + "hand": "✋", + "spock-hand": "🖖", + "rightwards_hand": "🫱", + "leftwards_hand": "🫲", + "palm_down_hand": "🫳", + "palm_up_hand": "🫴", + "leftwards_pushing_hand": "🫷", + "rightwards_pushing_hand": "🫸", + "ok_hand": "👌", + "pinched_fingers": "🤌", + "pinching_hand": "🤏", + "v": "✌️", + "crossed_fingers": "🤞", + "hand_with_index_finger_and_thumb_crossed": "🫰", + "i_love_you_hand_sign": "🤟", + "the_horns": "🤘", + "call_me_hand": "🤙", + "point_left": "👈", + "point_right": "👉", + "point_up_2": "👆", + "middle_finger": "🖕", + "point_down": "👇", + "point_up": "☝️", + "index_pointing_at_the_viewer": "🫵", + "+1": "👍", + "-1": "👎", + "fist": "✊", + "facepunch": "👊", + "left-facing_fist": "🤛", + "right-facing_fist": "🤜", + "clap": "👏", + "raised_hands": "🙌", + "heart_hands": "🫶", + "open_hands": "👐", + "palms_up_together": "🤲", + "handshake": "🤝", + "pray": "🙏", + "writing_hand": "✍️", + "nail_care": "💅", + "selfie": "🤳", + "muscle": "💪", + "mechanical_arm": "🦾", + "mechanical_leg": "🦿", + "leg": "🦵", + "foot": "🦶", + "ear": "👂", + "ear_with_hearing_aid": "🦻", + "nose": "👃", + "brain": "🧠", + "anatomical_heart": "🫀", + "lungs": "🫁", + "tooth": "🦷", + "bone": "🦴", + "eyes": "👀", + "eye": "👁️", + "tongue": "👅", + "lips": "👄", + "biting_lip": "🫦", + "baby": "👶", + "child": "🧒", + "boy": "👦", + "girl": "👧", + "adult": "🧑", + "person_with_blond_hair": "👱", + "man": "👨", + "bearded_person": "🧔", + "man_with_beard": "🧔‍♂️", + "woman_with_beard": "🧔‍♀️", + "red_haired_man": "👨‍🦰", + "curly_haired_man": "👨‍🦱", + "white_haired_man": "👨‍🦳", + "bald_man": "👨‍🦲", + "woman": "👩", + "red_haired_woman": "👩‍🦰", + "red_haired_person": "🧑‍🦰", + "curly_haired_woman": "👩‍🦱", + "curly_haired_person": "🧑‍🦱", + "white_haired_woman": "👩‍🦳", + "white_haired_person": "🧑‍🦳", + "bald_woman": "👩‍🦲", + "bald_person": "🧑‍🦲", + "blond-haired-woman": "👱‍♀️", + "blond-haired-man": "👱‍♂️", + "older_adult": "🧓", + "older_man": "👴", + "older_woman": "👵", + "person_frowning": "🙍", + "man-frowning": "🙍‍♂️", + "woman-frowning": "🙍‍♀️", + "person_with_pouting_face": "🙎", + "man-pouting": "🙎‍♂️", + "woman-pouting": "🙎‍♀️", + "no_good": "🙅", + "man-gesturing-no": "🙅‍♂️", + "woman-gesturing-no": "🙅‍♀️", + "ok_woman": "🙆", + "man-gesturing-ok": "🙆‍♂️", + "woman-gesturing-ok": "🙆‍♀️", + "information_desk_person": "💁", + "man-tipping-hand": "💁‍♂️", + "woman-tipping-hand": "💁‍♀️", + "raising_hand": "🙋", + "man-raising-hand": "🙋‍♂️", + "woman-raising-hand": "🙋‍♀️", + "deaf_person": "🧏", + "deaf_man": "🧏‍♂️", + "deaf_woman": "🧏‍♀️", + "bow": "🙇", + "man-bowing": "🙇‍♂️", + "woman-bowing": "🙇‍♀️", + "face_palm": "🤦", + "man-facepalming": "🤦‍♂️", + "woman-facepalming": "🤦‍♀️", + "shrug": "🤷", + "man-shrugging": "🤷‍♂️", + "woman-shrugging": "🤷‍♀️", + "health_worker": "🧑‍⚕️", + "male-doctor": "👨‍⚕️", + "female-doctor": "👩‍⚕️", + "student": "🧑‍🎓", + "male-student": "👨‍🎓", + "female-student": "👩‍🎓", + "teacher": "🧑‍🏫", + "male-teacher": "👨‍🏫", + "female-teacher": "👩‍🏫", + "judge": "🧑‍⚖️", + "male-judge": "👨‍⚖️", + "female-judge": "👩‍⚖️", + "farmer": "🧑‍🌾", + "male-farmer": "👨‍🌾", + "female-farmer": "👩‍🌾", + "cook": "🧑‍🍳", + "male-cook": "👨‍🍳", + "female-cook": "👩‍🍳", + "mechanic": "🧑‍🔧", + "male-mechanic": "👨‍🔧", + "female-mechanic": "👩‍🔧", + "factory_worker": "🧑‍🏭", + "male-factory-worker": "👨‍🏭", + "female-factory-worker": "👩‍🏭", + "office_worker": "🧑‍💼", + "male-office-worker": "👨‍💼", + "female-office-worker": "👩‍💼", + "scientist": "🧑‍🔬", + "male-scientist": "👨‍🔬", + "female-scientist": "👩‍🔬", + "technologist": "🧑‍💻", + "male-technologist": "👨‍💻", + "female-technologist": "👩‍💻", + "singer": "🧑‍🎤", + "male-singer": "👨‍🎤", + "female-singer": "👩‍🎤", + "artist": "🧑‍🎨", + "male-artist": "👨‍🎨", + "female-artist": "👩‍🎨", + "pilot": "🧑‍✈️", + "male-pilot": "👨‍✈️", + "female-pilot": "👩‍✈️", + "astronaut": "🧑‍🚀", + "male-astronaut": "👨‍🚀", + "female-astronaut": "👩‍🚀", + "firefighter": "🧑‍🚒", + "male-firefighter": "👨‍🚒", + "female-firefighter": "👩‍🚒", + "cop": "👮", + "male-police-officer": "👮‍♂️", + "female-police-officer": "👮‍♀️", + "sleuth_or_spy": "🕵️", + "male-detective": "🕵️‍♂️", + "female-detective": "🕵️‍♀️", + "guardsman": "💂", + "male-guard": "💂‍♂️", + "female-guard": "💂‍♀️", + "ninja": "🥷", + "construction_worker": "👷", + "male-construction-worker": "👷‍♂️", + "female-construction-worker": "👷‍♀️", + "person_with_crown": "🫅", + "prince": "🤴", + "princess": "👸", + "man_with_turban": "👳", + "man-wearing-turban": "👳‍♂️", + "woman-wearing-turban": "👳‍♀️", + "man_with_gua_pi_mao": "👲", + "person_with_headscarf": "🧕", + "person_in_tuxedo": "🤵", + "man_in_tuxedo": "🤵‍♂️", + "woman_in_tuxedo": "🤵‍♀️", + "bride_with_veil": "👰", + "man_with_veil": "👰‍♂️", + "woman_with_veil": "👰‍♀️", + "pregnant_woman": "🤰", + "pregnant_man": "🫃", + "pregnant_person": "🫄", + "breast-feeding": "🤱", + "woman_feeding_baby": "👩‍🍼", + "man_feeding_baby": "👨‍🍼", + "person_feeding_baby": "🧑‍🍼", + "angel": "👼", + "santa": "🎅", + "mrs_claus": "🤶", + "mx_claus": "🧑‍🎄", + "superhero": "🦸", + "male_superhero": "🦸‍♂️", + "female_superhero": "🦸‍♀️", + "supervillain": "🦹", + "male_supervillain": "🦹‍♂️", + "female_supervillain": "🦹‍♀️", + "mage": "🧙", + "male_mage": "🧙‍♂️", + "female_mage": "🧙‍♀️", + "fairy": "🧚", + "male_fairy": "🧚‍♂️", + "female_fairy": "🧚‍♀️", + "vampire": "🧛", + "male_vampire": "🧛‍♂️", + "female_vampire": "🧛‍♀️", + "merperson": "🧜", + "merman": "🧜‍♂️", + "mermaid": "🧜‍♀️", + "elf": "🧝", + "male_elf": "🧝‍♂️", + "female_elf": "🧝‍♀️", + "genie": "🧞", + "male_genie": "🧞‍♂️", + "female_genie": "🧞‍♀️", + "zombie": "🧟", + "male_zombie": "🧟‍♂️", + "female_zombie": "🧟‍♀️", + "troll": "🧌", + "massage": "💆", + "man-getting-massage": "💆‍♂️", + "woman-getting-massage": "💆‍♀️", + "haircut": "💇", + "man-getting-haircut": "💇‍♂️", + "woman-getting-haircut": "💇‍♀️", + "walking": "🚶", + "man-walking": "🚶‍♂️", + "woman-walking": "🚶‍♀️", + "standing_person": "🧍", + "man_standing": "🧍‍♂️", + "woman_standing": "🧍‍♀️", + "kneeling_person": "🧎", + "man_kneeling": "🧎‍♂️", + "woman_kneeling": "🧎‍♀️", + "person_with_probing_cane": "🧑‍🦯", + "man_with_probing_cane": "👨‍🦯", + "woman_with_probing_cane": "👩‍🦯", + "person_in_motorized_wheelchair": "🧑‍🦼", + "man_in_motorized_wheelchair": "👨‍🦼", + "woman_in_motorized_wheelchair": "👩‍🦼", + "person_in_manual_wheelchair": "🧑‍🦽", + "man_in_manual_wheelchair": "👨‍🦽", + "woman_in_manual_wheelchair": "👩‍🦽", + "runner": "🏃", + "man-running": "🏃‍♂️", + "woman-running": "🏃‍♀️", + "dancer": "💃", + "man_dancing": "🕺", + "man_in_business_suit_levitating": "🕴️", + "dancers": "👯", + "men-with-bunny-ears-partying": "👯‍♂️", + "women-with-bunny-ears-partying": "👯‍♀️", + "person_in_steamy_room": "🧖", + "man_in_steamy_room": "🧖‍♂️", + "woman_in_steamy_room": "🧖‍♀️", + "person_climbing": "🧗", + "man_climbing": "🧗‍♂️", + "woman_climbing": "🧗‍♀️", + "fencer": "🤺", + "horse_racing": "🏇", + "skier": "⛷️", + "snowboarder": "🏂", + "golfer": "🏌️", + "man-golfing": "🏌️‍♂️", + "woman-golfing": "🏌️‍♀️", + "surfer": "🏄", + "man-surfing": "🏄‍♂️", + "woman-surfing": "🏄‍♀️", + "rowboat": "🚣", + "man-rowing-boat": "🚣‍♂️", + "woman-rowing-boat": "🚣‍♀️", + "swimmer": "🏊", + "man-swimming": "🏊‍♂️", + "woman-swimming": "🏊‍♀️", + "person_with_ball": "⛹️", + "man-bouncing-ball": "⛹️‍♂️", + "woman-bouncing-ball": "⛹️‍♀️", + "weight_lifter": "🏋️", + "man-lifting-weights": "🏋️‍♂️", + "woman-lifting-weights": "🏋️‍♀️", + "bicyclist": "🚴", + "man-biking": "🚴‍♂️", + "woman-biking": "🚴‍♀️", + "mountain_bicyclist": "🚵", + "man-mountain-biking": "🚵‍♂️", + "woman-mountain-biking": "🚵‍♀️", + "person_doing_cartwheel": "🤸", + "man-cartwheeling": "🤸‍♂️", + "woman-cartwheeling": "🤸‍♀️", + "wrestlers": "🤼", + "man-wrestling": "🤼‍♂️", + "woman-wrestling": "🤼‍♀️", + "water_polo": "🤽", + "man-playing-water-polo": "🤽‍♂️", + "woman-playing-water-polo": "🤽‍♀️", + "handball": "🤾", + "man-playing-handball": "🤾‍♂️", + "woman-playing-handball": "🤾‍♀️", + "juggling": "🤹", + "man-juggling": "🤹‍♂️", + "woman-juggling": "🤹‍♀️", + "person_in_lotus_position": "🧘", + "man_in_lotus_position": "🧘‍♂️", + "woman_in_lotus_position": "🧘‍♀️", + "bath": "🛀", + "sleeping_accommodation": "🛌", + "people_holding_hands": "🧑‍🤝‍🧑", + "two_women_holding_hands": "👭", + "man_and_woman_holding_hands": "👫", + "two_men_holding_hands": "👬", + "couplekiss": "💏", + "woman-kiss-man": "👩‍❤️‍💋‍👨", + "man-kiss-man": "👨‍❤️‍💋‍👨", + "woman-kiss-woman": "👩‍❤️‍💋‍👩", + "couple_with_heart": "💑", + "woman-heart-man": "👩‍❤️‍👨", + "man-heart-man": "👨‍❤️‍👨", + "woman-heart-woman": "👩‍❤️‍👩", + "family": "👪", + "man-woman-boy": "👨‍👩‍👦", + "man-woman-girl": "👨‍👩‍👧", + "man-woman-girl-boy": "👨‍👩‍👧‍👦", + "man-woman-boy-boy": "👨‍👩‍👦‍👦", + "man-woman-girl-girl": "👨‍👩‍👧‍👧", + "man-man-boy": "👨‍👨‍👦", + "man-man-girl": "👨‍👨‍👧", + "man-man-girl-boy": "👨‍👨‍👧‍👦", + "man-man-boy-boy": "👨‍👨‍👦‍👦", + "man-man-girl-girl": "👨‍👨‍👧‍👧", + "woman-woman-boy": "👩‍👩‍👦", + "woman-woman-girl": "👩‍👩‍👧", + "woman-woman-girl-boy": "👩‍👩‍👧‍👦", + "woman-woman-boy-boy": "👩‍👩‍👦‍👦", + "woman-woman-girl-girl": "👩‍👩‍👧‍👧", + "man-boy": "👨‍👦", + "man-boy-boy": "👨‍👦‍👦", + "man-girl": "👨‍👧", + "man-girl-boy": "👨‍👧‍👦", + "man-girl-girl": "👨‍👧‍👧", + "woman-boy": "👩‍👦", + "woman-boy-boy": "👩‍👦‍👦", + "woman-girl": "👩‍👧", + "woman-girl-boy": "👩‍👧‍👦", + "woman-girl-girl": "👩‍👧‍👧", + "speaking_head_in_silhouette": "🗣️", + "bust_in_silhouette": "👤", + "busts_in_silhouette": "👥", + "people_hugging": "🫂", + "footprints": "👣", + "monkey_face": "🐵", + "monkey": "🐒", + "gorilla": "🦍", + "orangutan": "🦧", + "dog": "🐶", + "dog2": "🐕", + "guide_dog": "🦮", + "service_dog": "🐕‍🦺", + "poodle": "🐩", + "wolf": "🐺", + "fox_face": "🦊", + "raccoon": "🦝", + "cat": "🐱", + "cat2": "🐈", + "black_cat": "🐈‍⬛", + "lion_face": "🦁", + "tiger": "🐯", + "tiger2": "🐅", + "leopard": "🐆", + "horse": "🐴", + "moose": "🫎", + "donkey": "🫏", + "racehorse": "🐎", + "unicorn_face": "🦄", + "zebra_face": "🦓", + "deer": "🦌", + "bison": "🦬", + "cow": "🐮", + "ox": "🐂", + "water_buffalo": "🐃", + "cow2": "🐄", + "pig": "🐷", + "pig2": "🐖", + "boar": "🐗", + "pig_nose": "🐽", + "ram": "🐏", + "sheep": "🐑", + "goat": "🐐", + "dromedary_camel": "🐪", + "camel": "🐫", + "llama": "🦙", + "giraffe_face": "🦒", + "elephant": "🐘", + "mammoth": "🦣", + "rhinoceros": "🦏", + "hippopotamus": "🦛", + "mouse": "🐭", + "mouse2": "🐁", + "rat": "🐀", + "hamster": "🐹", + "rabbit": "🐰", + "rabbit2": "🐇", + "chipmunk": "🐿️", + "beaver": "🦫", + "hedgehog": "🦔", + "bat": "🦇", + "bear": "🐻", + "polar_bear": "🐻‍❄️", + "koala": "🐨", + "panda_face": "🐼", + "sloth": "🦥", + "otter": "🦦", + "skunk": "🦨", + "kangaroo": "🦘", + "badger": "🦡", + "feet": "🐾", + "turkey": "🦃", + "chicken": "🐔", + "rooster": "🐓", + "hatching_chick": "🐣", + "baby_chick": "🐤", + "hatched_chick": "🐥", + "bird": "🐦", + "penguin": "🐧", + "dove_of_peace": "🕊️", + "eagle": "🦅", + "duck": "🦆", + "swan": "🦢", + "owl": "🦉", + "dodo": "🦤", + "feather": "🪶", + "flamingo": "🦩", + "peacock": "🦚", + "parrot": "🦜", + "wing": "🪽", + "black_bird": "🐦‍⬛", + "goose": "🪿", + "frog": "🐸", + "crocodile": "🐊", + "turtle": "🐢", + "lizard": "🦎", + "snake": "🐍", + "dragon_face": "🐲", + "dragon": "🐉", + "sauropod": "🦕", + "t-rex": "🦖", + "whale": "🐳", + "whale2": "🐋", + "dolphin": "🐬", + "seal": "🦭", + "fish": "🐟", + "tropical_fish": "🐠", + "blowfish": "🐡", + "shark": "🦈", + "octopus": "🐙", + "shell": "🐚", + "coral": "🪸", + "jellyfish": "🪼", + "snail": "🐌", + "butterfly": "🦋", + "bug": "🐛", + "ant": "🐜", + "bee": "🐝", + "beetle": "🪲", + "ladybug": "🐞", + "cricket": "🦗", + "cockroach": "🪳", + "spider": "🕷️", + "spider_web": "🕸️", + "scorpion": "🦂", + "mosquito": "🦟", + "fly": "🪰", + "worm": "🪱", + "microbe": "🦠", + "bouquet": "💐", + "cherry_blossom": "🌸", + "white_flower": "💮", + "lotus": "🪷", + "rosette": "🏵️", + "rose": "🌹", + "wilted_flower": "🥀", + "hibiscus": "🌺", + "sunflower": "🌻", + "blossom": "🌼", + "tulip": "🌷", + "hyacinth": "🪻", + "seedling": "🌱", + "potted_plant": "🪴", + "evergreen_tree": "🌲", + "deciduous_tree": "🌳", + "palm_tree": "🌴", + "cactus": "🌵", + "ear_of_rice": "🌾", + "herb": "🌿", + "shamrock": "☘️", + "four_leaf_clover": "🍀", + "maple_leaf": "🍁", + "fallen_leaf": "🍂", + "leaves": "🍃", + "empty_nest": "🪹", + "nest_with_eggs": "🪺", + "mushroom": "🍄", + "grapes": "🍇", + "melon": "🍈", + "watermelon": "🍉", + "tangerine": "🍊", + "lemon": "🍋", + "banana": "🍌", + "pineapple": "🍍", + "mango": "🥭", + "apple": "🍎", + "green_apple": "🍏", + "pear": "🍐", + "peach": "🍑", + "cherries": "🍒", + "strawberry": "🍓", + "blueberries": "🫐", + "kiwifruit": "🥝", + "tomato": "🍅", + "olive": "🫒", + "coconut": "🥥", + "avocado": "🥑", + "eggplant": "🍆", + "potato": "🥔", + "carrot": "🥕", + "corn": "🌽", + "hot_pepper": "🌶️", + "bell_pepper": "🫑", + "cucumber": "🥒", + "leafy_green": "🥬", + "broccoli": "🥦", + "garlic": "🧄", + "onion": "🧅", + "peanuts": "🥜", + "beans": "🫘", + "chestnut": "🌰", + "ginger_root": "🫚", + "pea_pod": "🫛", + "bread": "🍞", + "croissant": "🥐", + "baguette_bread": "🥖", + "flatbread": "🫓", + "pretzel": "🥨", + "bagel": "🥯", + "pancakes": "🥞", + "waffle": "🧇", + "cheese_wedge": "🧀", + "meat_on_bone": "🍖", + "poultry_leg": "🍗", + "cut_of_meat": "🥩", + "bacon": "🥓", + "hamburger": "🍔", + "fries": "🍟", + "pizza": "🍕", + "hotdog": "🌭", + "sandwich": "🥪", + "taco": "🌮", + "burrito": "🌯", + "tamale": "🫔", + "stuffed_flatbread": "🥙", + "falafel": "🧆", + "egg": "🥚", + "fried_egg": "🍳", + "shallow_pan_of_food": "🥘", + "stew": "🍲", + "fondue": "🫕", + "bowl_with_spoon": "🥣", + "green_salad": "🥗", + "popcorn": "🍿", + "butter": "🧈", + "salt": "🧂", + "canned_food": "🥫", + "bento": "🍱", + "rice_cracker": "🍘", + "rice_ball": "🍙", + "rice": "🍚", + "curry": "🍛", + "ramen": "🍜", + "spaghetti": "🍝", + "sweet_potato": "🍠", + "oden": "🍢", + "sushi": "🍣", + "fried_shrimp": "🍤", + "fish_cake": "🍥", + "moon_cake": "🥮", + "dango": "🍡", + "dumpling": "🥟", + "fortune_cookie": "🥠", + "takeout_box": "🥡", + "crab": "🦀", + "lobster": "🦞", + "shrimp": "🦐", + "squid": "🦑", + "oyster": "🦪", + "icecream": "🍦", + "shaved_ice": "🍧", + "ice_cream": "🍨", + "doughnut": "🍩", + "cookie": "🍪", + "birthday": "🎂", + "cake": "🍰", + "cupcake": "🧁", + "pie": "🥧", + "chocolate_bar": "🍫", + "candy": "🍬", + "lollipop": "🍭", + "custard": "🍮", + "honey_pot": "🍯", + "baby_bottle": "🍼", + "glass_of_milk": "🥛", + "coffee": "☕", + "teapot": "🫖", + "tea": "🍵", + "sake": "🍶", + "champagne": "🍾", + "wine_glass": "🍷", + "cocktail": "🍸", + "tropical_drink": "🍹", + "beer": "🍺", + "beers": "🍻", + "clinking_glasses": "🥂", + "tumbler_glass": "🥃", + "pouring_liquid": "🫗", + "cup_with_straw": "🥤", + "bubble_tea": "🧋", + "beverage_box": "🧃", + "mate_drink": "🧉", + "ice_cube": "🧊", + "chopsticks": "🥢", + "knife_fork_plate": "🍽️", + "fork_and_knife": "🍴", + "spoon": "🥄", + "hocho": "🔪", + "jar": "🫙", + "amphora": "🏺", + "earth_africa": "🌍", + "earth_americas": "🌎", + "earth_asia": "🌏", + "globe_with_meridians": "🌐", + "world_map": "🗺️", + "japan": "🗾", + "compass": "🧭", + "snow_capped_mountain": "🏔️", + "mountain": "⛰️", + "volcano": "🌋", + "mount_fuji": "🗻", + "camping": "🏕️", + "beach_with_umbrella": "🏖️", + "desert": "🏜️", + "desert_island": "🏝️", + "national_park": "🏞️", + "stadium": "🏟️", + "classical_building": "🏛️", + "building_construction": "🏗️", + "bricks": "🧱", + "rock": "🪨", + "wood": "🪵", + "hut": "🛖", + "house_buildings": "🏘️", + "derelict_house_building": "🏚️", + "house": "🏠", + "house_with_garden": "🏡", + "office": "🏢", + "post_office": "🏣", + "european_post_office": "🏤", + "hospital": "🏥", + "bank": "🏦", + "hotel": "🏨", + "love_hotel": "🏩", + "convenience_store": "🏪", + "school": "🏫", + "department_store": "🏬", + "factory": "🏭", + "japanese_castle": "🏯", + "european_castle": "🏰", + "wedding": "💒", + "tokyo_tower": "🗼", + "statue_of_liberty": "🗽", + "church": "⛪", + "mosque": "🕌", + "hindu_temple": "🛕", + "synagogue": "🕍", + "shinto_shrine": "⛩️", + "kaaba": "🕋", + "fountain": "⛲", + "tent": "⛺", + "foggy": "🌁", + "night_with_stars": "🌃", + "cityscape": "🏙️", + "sunrise_over_mountains": "🌄", + "sunrise": "🌅", + "city_sunset": "🌆", + "city_sunrise": "🌇", + "bridge_at_night": "🌉", + "hotsprings": "♨️", + "carousel_horse": "🎠", + "playground_slide": "🛝", + "ferris_wheel": "🎡", + "roller_coaster": "🎢", + "barber": "💈", + "circus_tent": "🎪", + "steam_locomotive": "🚂", + "railway_car": "🚃", + "bullettrain_side": "🚄", + "bullettrain_front": "🚅", + "train2": "🚆", + "metro": "🚇", + "light_rail": "🚈", + "station": "🚉", + "tram": "🚊", + "monorail": "🚝", + "mountain_railway": "🚞", + "train": "🚋", + "bus": "🚌", + "oncoming_bus": "🚍", + "trolleybus": "🚎", + "minibus": "🚐", + "ambulance": "🚑", + "fire_engine": "🚒", + "police_car": "🚓", + "oncoming_police_car": "🚔", + "taxi": "🚕", + "oncoming_taxi": "🚖", + "car": "🚗", + "oncoming_automobile": "🚘", + "blue_car": "🚙", + "pickup_truck": "🛻", + "truck": "🚚", + "articulated_lorry": "🚛", + "tractor": "🚜", + "racing_car": "🏎️", + "racing_motorcycle": "🏍️", + "motor_scooter": "🛵", + "manual_wheelchair": "🦽", + "motorized_wheelchair": "🦼", + "auto_rickshaw": "🛺", + "bike": "🚲", + "scooter": "🛴", + "skateboard": "🛹", + "roller_skate": "🛼", + "busstop": "🚏", + "motorway": "🛣️", + "railway_track": "🛤️", + "oil_drum": "🛢️", + "fuelpump": "⛽", + "wheel": "🛞", + "rotating_light": "🚨", + "traffic_light": "🚥", + "vertical_traffic_light": "🚦", + "octagonal_sign": "🛑", + "construction": "🚧", + "anchor": "⚓", + "ring_buoy": "🛟", + "boat": "⛵", + "canoe": "🛶", + "speedboat": "🚤", + "passenger_ship": "🛳️", + "ferry": "⛴️", + "motor_boat": "🛥️", + "ship": "🚢", + "airplane": "✈️", + "small_airplane": "🛩️", + "airplane_departure": "🛫", + "airplane_arriving": "🛬", + "parachute": "🪂", + "seat": "💺", + "helicopter": "🚁", + "suspension_railway": "🚟", + "mountain_cableway": "🚠", + "aerial_tramway": "🚡", + "satellite": "🛰️", + "rocket": "🚀", + "flying_saucer": "🛸", + "bellhop_bell": "🛎️", + "luggage": "🧳", + "hourglass": "⌛", + "hourglass_flowing_sand": "⏳", + "watch": "⌚", + "alarm_clock": "⏰", + "stopwatch": "⏱️", + "timer_clock": "⏲️", + "mantelpiece_clock": "🕰️", + "clock12": "🕛", + "clock1230": "🕧", + "clock1": "🕐", + "clock130": "🕜", + "clock2": "🕑", + "clock230": "🕝", + "clock3": "🕒", + "clock330": "🕞", + "clock4": "🕓", + "clock430": "🕟", + "clock5": "🕔", + "clock530": "🕠", + "clock6": "🕕", + "clock630": "🕡", + "clock7": "🕖", + "clock730": "🕢", + "clock8": "🕗", + "clock830": "🕣", + "clock9": "🕘", + "clock930": "🕤", + "clock10": "🕙", + "clock1030": "🕥", + "clock11": "🕚", + "clock1130": "🕦", + "new_moon": "🌑", + "waxing_crescent_moon": "🌒", + "first_quarter_moon": "🌓", + "moon": "🌔", + "full_moon": "🌕", + "waning_gibbous_moon": "🌖", + "last_quarter_moon": "🌗", + "waning_crescent_moon": "🌘", + "crescent_moon": "🌙", + "new_moon_with_face": "🌚", + "first_quarter_moon_with_face": "🌛", + "last_quarter_moon_with_face": "🌜", + "thermometer": "🌡️", + "sunny": "☀️", + "full_moon_with_face": "🌝", + "sun_with_face": "🌞", + "ringed_planet": "🪐", + "star": "⭐", + "star2": "🌟", + "stars": "🌠", + "milky_way": "🌌", + "cloud": "☁️", + "partly_sunny": "⛅", + "thunder_cloud_and_rain": "⛈️", + "mostly_sunny": "🌤️", + "barely_sunny": "🌥️", + "partly_sunny_rain": "🌦️", + "rain_cloud": "🌧️", + "snow_cloud": "🌨️", + "lightning": "🌩️", + "tornado": "🌪️", + "fog": "🌫️", + "wind_blowing_face": "🌬️", + "cyclone": "🌀", + "rainbow": "🌈", + "closed_umbrella": "🌂", + "umbrella": "☂️", + "umbrella_with_rain_drops": "☔", + "umbrella_on_ground": "⛱️", + "zap": "⚡", + "snowflake": "❄️", + "snowman": "☃️", + "snowman_without_snow": "⛄", + "comet": "☄️", + "fire": "🔥", + "droplet": "💧", + "ocean": "🌊", + "jack_o_lantern": "🎃", + "christmas_tree": "🎄", + "fireworks": "🎆", + "sparkler": "🎇", + "firecracker": "🧨", + "sparkles": "✨", + "balloon": "🎈", + "tada": "🎉", + "confetti_ball": "🎊", + "tanabata_tree": "🎋", + "bamboo": "🎍", + "dolls": "🎎", + "flags": "🎏", + "wind_chime": "🎐", + "rice_scene": "🎑", + "red_envelope": "🧧", + "ribbon": "🎀", + "gift": "🎁", + "reminder_ribbon": "🎗️", + "admission_tickets": "🎟️", + "ticket": "🎫", + "medal": "🎖️", + "trophy": "🏆", + "sports_medal": "🏅", + "first_place_medal": "🥇", + "second_place_medal": "🥈", + "third_place_medal": "🥉", + "soccer": "⚽", + "baseball": "⚾", + "softball": "🥎", + "basketball": "🏀", + "volleyball": "🏐", + "football": "🏈", + "rugby_football": "🏉", + "tennis": "🎾", + "flying_disc": "🥏", + "bowling": "🎳", + "cricket_bat_and_ball": "🏏", + "field_hockey_stick_and_ball": "🏑", + "ice_hockey_stick_and_puck": "🏒", + "lacrosse": "🥍", + "table_tennis_paddle_and_ball": "🏓", + "badminton_racquet_and_shuttlecock": "🏸", + "boxing_glove": "🥊", + "martial_arts_uniform": "🥋", + "goal_net": "🥅", + "golf": "⛳", + "ice_skate": "⛸️", + "fishing_pole_and_fish": "🎣", + "diving_mask": "🤿", + "running_shirt_with_sash": "🎽", + "ski": "🎿", + "sled": "🛷", + "curling_stone": "🥌", + "dart": "🎯", + "yo-yo": "🪀", + "kite": "🪁", + "gun": "🔫", + "8ball": "🎱", + "crystal_ball": "🔮", + "magic_wand": "🪄", + "video_game": "🎮", + "joystick": "🕹️", + "slot_machine": "🎰", + "game_die": "🎲", + "jigsaw": "🧩", + "teddy_bear": "🧸", + "pinata": "🪅", + "mirror_ball": "🪩", + "nesting_dolls": "🪆", + "spades": "♠️", + "hearts": "♥️", + "diamonds": "♦️", + "clubs": "♣️", + "chess_pawn": "♟️", + "black_joker": "🃏", + "mahjong": "🀄", + "flower_playing_cards": "🎴", + "performing_arts": "🎭", + "frame_with_picture": "🖼️", + "art": "🎨", + "thread": "🧵", + "sewing_needle": "🪡", + "yarn": "🧶", + "knot": "🪢", + "eyeglasses": "👓", + "dark_sunglasses": "🕶️", + "goggles": "🥽", + "lab_coat": "🥼", + "safety_vest": "🦺", + "necktie": "👔", + "shirt": "👕", + "jeans": "👖", + "scarf": "🧣", + "gloves": "🧤", + "coat": "🧥", + "socks": "🧦", + "dress": "👗", + "kimono": "👘", + "sari": "🥻", + "one-piece_swimsuit": "🩱", + "briefs": "🩲", + "shorts": "🩳", + "bikini": "👙", + "womans_clothes": "👚", + "folding_hand_fan": "🪭", + "purse": "👛", + "handbag": "👜", + "pouch": "👝", + "shopping_bags": "🛍️", + "school_satchel": "🎒", + "thong_sandal": "🩴", + "mans_shoe": "👞", + "athletic_shoe": "👟", + "hiking_boot": "🥾", + "womans_flat_shoe": "🥿", + "high_heel": "👠", + "sandal": "👡", + "ballet_shoes": "🩰", + "boot": "👢", + "hair_pick": "🪮", + "crown": "👑", + "womans_hat": "👒", + "tophat": "🎩", + "mortar_board": "🎓", + "billed_cap": "🧢", + "military_helmet": "🪖", + "helmet_with_white_cross": "⛑️", + "prayer_beads": "📿", + "lipstick": "💄", + "ring": "💍", + "gem": "💎", + "mute": "🔇", + "speaker": "🔈", + "sound": "🔉", + "loud_sound": "🔊", + "loudspeaker": "📢", + "mega": "📣", + "postal_horn": "📯", + "bell": "🔔", + "no_bell": "🔕", + "musical_score": "🎼", + "musical_note": "🎵", + "notes": "🎶", + "studio_microphone": "🎙️", + "level_slider": "🎚️", + "control_knobs": "🎛️", + "microphone": "🎤", + "headphones": "🎧", + "radio": "📻", + "saxophone": "🎷", + "accordion": "🪗", + "guitar": "🎸", + "musical_keyboard": "🎹", + "trumpet": "🎺", + "violin": "🎻", + "banjo": "🪕", + "drum_with_drumsticks": "🥁", + "long_drum": "🪘", + "maracas": "🪇", + "flute": "🪈", + "iphone": "📱", + "calling": "📲", + "phone": "☎️", + "telephone_receiver": "📞", + "pager": "📟", + "fax": "📠", + "battery": "🔋", + "low_battery": "🪫", + "electric_plug": "🔌", + "computer": "💻", + "desktop_computer": "🖥️", + "printer": "🖨️", + "keyboard": "⌨️", + "three_button_mouse": "🖱️", + "trackball": "🖲️", + "minidisc": "💽", + "floppy_disk": "💾", + "cd": "💿", + "dvd": "📀", + "abacus": "🧮", + "movie_camera": "🎥", + "film_frames": "🎞️", + "film_projector": "📽️", + "clapper": "🎬", + "tv": "📺", + "camera": "📷", + "camera_with_flash": "📸", + "video_camera": "📹", + "vhs": "📼", + "mag": "🔍", + "mag_right": "🔎", + "candle": "🕯️", + "bulb": "💡", + "flashlight": "🔦", + "izakaya_lantern": "🏮", + "diya_lamp": "🪔", + "notebook_with_decorative_cover": "📔", + "closed_book": "📕", + "book": "📖", + "green_book": "📗", + "blue_book": "📘", + "orange_book": "📙", + "books": "📚", + "notebook": "📓", + "ledger": "📒", + "page_with_curl": "📃", + "scroll": "📜", + "page_facing_up": "📄", + "newspaper": "📰", + "rolled_up_newspaper": "🗞️", + "bookmark_tabs": "📑", + "bookmark": "🔖", + "label": "🏷️", + "moneybag": "💰", + "coin": "🪙", + "yen": "💴", + "dollar": "💵", + "euro": "💶", + "pound": "💷", + "money_with_wings": "💸", + "credit_card": "💳", + "receipt": "🧾", + "chart": "💹", + "email": "✉️", + "e-mail": "📧", + "incoming_envelope": "📨", + "envelope_with_arrow": "📩", + "outbox_tray": "📤", + "inbox_tray": "📥", + "package": "📦", + "mailbox": "📫", + "mailbox_closed": "📪", + "mailbox_with_mail": "📬", + "mailbox_with_no_mail": "📭", + "postbox": "📮", + "ballot_box_with_ballot": "🗳️", + "pencil2": "✏️", + "black_nib": "✒️", + "lower_left_fountain_pen": "🖋️", + "lower_left_ballpoint_pen": "🖊️", + "lower_left_paintbrush": "🖌️", + "lower_left_crayon": "🖍️", + "memo": "📝", + "briefcase": "💼", + "file_folder": "📁", + "open_file_folder": "📂", + "card_index_dividers": "🗂️", + "date": "📅", + "calendar": "📆", + "spiral_note_pad": "🗒️", + "spiral_calendar_pad": "🗓️", + "card_index": "📇", + "chart_with_upwards_trend": "📈", + "chart_with_downwards_trend": "📉", + "bar_chart": "📊", + "clipboard": "📋", + "pushpin": "📌", + "round_pushpin": "📍", + "paperclip": "📎", + "linked_paperclips": "🖇️", + "straight_ruler": "📏", + "triangular_ruler": "📐", + "scissors": "✂️", + "card_file_box": "🗃️", + "file_cabinet": "🗄️", + "wastebasket": "🗑️", + "lock": "🔒", + "unlock": "🔓", + "lock_with_ink_pen": "🔏", + "closed_lock_with_key": "🔐", + "key": "🔑", + "old_key": "🗝️", + "hammer": "🔨", + "axe": "🪓", + "pick": "⛏️", + "hammer_and_pick": "⚒️", + "hammer_and_wrench": "🛠️", + "dagger_knife": "🗡️", + "crossed_swords": "⚔️", + "bomb": "💣", + "boomerang": "🪃", + "bow_and_arrow": "🏹", + "shield": "🛡️", + "carpentry_saw": "🪚", + "wrench": "🔧", + "screwdriver": "🪛", + "nut_and_bolt": "🔩", + "gear": "⚙️", + "compression": "🗜️", + "scales": "⚖️", + "probing_cane": "🦯", + "link": "🔗", + "chains": "⛓️", + "hook": "🪝", + "toolbox": "🧰", + "magnet": "🧲", + "ladder": "🪜", + "alembic": "⚗️", + "test_tube": "🧪", + "petri_dish": "🧫", + "dna": "🧬", + "microscope": "🔬", + "telescope": "🔭", + "satellite_antenna": "📡", + "syringe": "💉", + "drop_of_blood": "🩸", + "pill": "💊", + "adhesive_bandage": "🩹", + "crutch": "🩼", + "stethoscope": "🩺", + "x-ray": "🩻", + "door": "🚪", + "elevator": "🛗", + "mirror": "🪞", + "window": "🪟", + "bed": "🛏️", + "couch_and_lamp": "🛋️", + "chair": "🪑", + "toilet": "🚽", + "plunger": "🪠", + "shower": "🚿", + "bathtub": "🛁", + "mouse_trap": "🪤", + "razor": "🪒", + "lotion_bottle": "🧴", + "safety_pin": "🧷", + "broom": "🧹", + "basket": "🧺", + "roll_of_paper": "🧻", + "bucket": "🪣", + "soap": "🧼", + "bubbles": "🫧", + "toothbrush": "🪥", + "sponge": "🧽", + "fire_extinguisher": "🧯", + "shopping_trolley": "🛒", + "smoking": "🚬", + "coffin": "⚰️", + "headstone": "🪦", + "funeral_urn": "⚱️", + "nazar_amulet": "🧿", + "hamsa": "🪬", + "moyai": "🗿", + "placard": "🪧", + "identification_card": "🪪", + "atm": "🏧", + "put_litter_in_its_place": "🚮", + "potable_water": "🚰", + "wheelchair": "♿", + "mens": "🚹", + "womens": "🚺", + "restroom": "🚻", + "baby_symbol": "🚼", + "wc": "🚾", + "passport_control": "🛂", + "customs": "🛃", + "baggage_claim": "🛄", + "left_luggage": "🛅", + "warning": "⚠️", + "children_crossing": "🚸", + "no_entry": "⛔", + "no_entry_sign": "🚫", + "no_bicycles": "🚳", + "no_smoking": "🚭", + "do_not_litter": "🚯", + "non-potable_water": "🚱", + "no_pedestrians": "🚷", + "no_mobile_phones": "📵", + "underage": "🔞", + "radioactive_sign": "☢️", + "biohazard_sign": "☣️", + "arrow_up": "⬆️", + "arrow_upper_right": "↗️", + "arrow_right": "➡️", + "arrow_lower_right": "↘️", + "arrow_down": "⬇️", + "arrow_lower_left": "↙️", + "arrow_left": "⬅️", + "arrow_upper_left": "↖️", + "arrow_up_down": "↕️", + "left_right_arrow": "↔️", + "leftwards_arrow_with_hook": "↩️", + "arrow_right_hook": "↪️", + "arrow_heading_up": "⤴️", + "arrow_heading_down": "⤵️", + "arrows_clockwise": "🔃", + "arrows_counterclockwise": "🔄", + "back": "🔙", + "end": "🔚", + "on": "🔛", + "soon": "🔜", + "top": "🔝", + "place_of_worship": "🛐", + "atom_symbol": "⚛️", + "om_symbol": "🕉️", + "star_of_david": "✡️", + "wheel_of_dharma": "☸️", + "yin_yang": "☯️", + "latin_cross": "✝️", + "orthodox_cross": "☦️", + "star_and_crescent": "☪️", + "peace_symbol": "☮️", + "menorah_with_nine_branches": "🕎", + "six_pointed_star": "🔯", + "khanda": "🪯", + "aries": "♈", + "taurus": "♉", + "gemini": "♊", + "cancer": "♋", + "leo": "♌", + "virgo": "♍", + "libra": "♎", + "scorpius": "♏", + "sagittarius": "♐", + "capricorn": "♑", + "aquarius": "♒", + "pisces": "♓", + "ophiuchus": "⛎", + "twisted_rightwards_arrows": "🔀", + "repeat": "🔁", + "repeat_one": "🔂", + "arrow_forward": "▶️", + "fast_forward": "⏩", + "black_right_pointing_double_triangle_with_vertical_bar": "⏭️", + "black_right_pointing_triangle_with_double_vertical_bar": "⏯️", + "arrow_backward": "◀️", + "rewind": "⏪", + "black_left_pointing_double_triangle_with_vertical_bar": "⏮️", + "arrow_up_small": "🔼", + "arrow_double_up": "⏫", + "arrow_down_small": "🔽", + "arrow_double_down": "⏬", + "double_vertical_bar": "⏸️", + "black_square_for_stop": "⏹️", + "black_circle_for_record": "⏺️", + "eject": "⏏️", + "cinema": "🎦", + "low_brightness": "🔅", + "high_brightness": "🔆", + "signal_strength": "📶", + "wireless": "🛜", + "vibration_mode": "📳", + "mobile_phone_off": "📴", + "female_sign": "♀️", + "male_sign": "♂️", + "transgender_symbol": "⚧️", + "heavy_multiplication_x": "✖️", + "heavy_plus_sign": "➕", + "heavy_minus_sign": "➖", + "heavy_division_sign": "➗", + "heavy_equals_sign": "🟰", + "infinity": "♾️", + "bangbang": "‼️", + "interrobang": "⁉️", + "question": "❓", + "grey_question": "❔", + "grey_exclamation": "❕", + "exclamation": "❗", + "wavy_dash": "〰️", + "currency_exchange": "💱", + "heavy_dollar_sign": "💲", + "medical_symbol": "⚕️", + "recycle": "♻️", + "fleur_de_lis": "⚜️", + "trident": "🔱", + "name_badge": "📛", + "beginner": "🔰", + "o": "⭕", + "white_check_mark": "✅", + "ballot_box_with_check": "☑️", + "heavy_check_mark": "✔️", + "x": "❌", + "negative_squared_cross_mark": "❎", + "curly_loop": "➰", + "loop": "➿", + "part_alternation_mark": "〽️", + "eight_spoked_asterisk": "✳️", + "eight_pointed_black_star": "✴️", + "sparkle": "❇️", + "copyright": "©️", + "registered": "®️", + "tm": "™️", + "hash": "#️⃣", + "keycap_star": "*️⃣", + "zero": "0️⃣", + "one": "1️⃣", + "two": "2️⃣", + "three": "3️⃣", + "four": "4️⃣", + "five": "5️⃣", + "six": "6️⃣", + "seven": "7️⃣", + "eight": "8️⃣", + "nine": "9️⃣", + "keycap_ten": "🔟", + "capital_abcd": "🔠", + "abcd": "🔡", + "symbols": "🔣", + "abc": "🔤", + "a": "🅰️", + "ab": "🆎", + "b": "🅱️", + "cl": "🆑", + "cool": "🆒", + "free": "🆓", + "information_source": "ℹ️", + "id": "🆔", + "m": "Ⓜ️", + "new": "🆕", + "ng": "🆖", + "o2": "🅾️", + "ok": "🆗", + "parking": "🅿️", + "sos": "🆘", + "up": "🆙", + "vs": "🆚", + "koko": "🈁", + "sa": "🈂️", + "u6708": "🈷️", + "u6709": "🈶", + "u6307": "🈯", + "ideograph_advantage": "🉐", + "u5272": "🈹", + "u7121": "🈚", + "u7981": "🈲", + "accept": "🉑", + "u7533": "🈸", + "u5408": "🈴", + "u7a7a": "🈳", + "congratulations": "㊗️", + "secret": "㊙️", + "u55b6": "🈺", + "u6e80": "🈵", + "red_circle": "🔴", + "large_orange_circle": "🟠", + "large_yellow_circle": "🟡", + "large_green_circle": "🟢", + "large_blue_circle": "🔵", + "large_purple_circle": "🟣", + "large_brown_circle": "🟤", + "black_circle": "⚫", + "white_circle": "⚪", + "large_red_square": "🟥", + "large_orange_square": "🟧", + "large_yellow_square": "🟨", + "large_green_square": "🟩", + "large_blue_square": "🟦", + "large_purple_square": "🟪", + "large_brown_square": "🟫", + "black_large_square": "⬛", + "white_large_square": "⬜", + "black_medium_square": "◼️", + "white_medium_square": "◻️", + "black_medium_small_square": "◾", + "white_medium_small_square": "◽", + "black_small_square": "▪️", + "white_small_square": "▫️", + "large_orange_diamond": "🔶", + "large_blue_diamond": "🔷", + "small_orange_diamond": "🔸", + "small_blue_diamond": "🔹", + "small_red_triangle": "🔺", + "small_red_triangle_down": "🔻", + "diamond_shape_with_a_dot_inside": "💠", + "radio_button": "🔘", + "white_square_button": "🔳", + "black_square_button": "🔲", + "checkered_flag": "🏁", + "triangular_flag_on_post": "🚩", + "crossed_flags": "🎌", + "waving_black_flag": "🏴", + "waving_white_flag": "🏳️", + "rainbow-flag": "🏳️‍🌈", + "transgender_flag": "🏳️‍⚧️", + "pirate_flag": "🏴‍☠️", + "flag-ac": "🇦🇨", + "flag-ad": "🇦🇩", + "flag-ae": "🇦🇪", + "flag-af": "🇦🇫", + "flag-ag": "🇦🇬", + "flag-ai": "🇦🇮", + "flag-al": "🇦🇱", + "flag-am": "🇦🇲", + "flag-ao": "🇦🇴", + "flag-aq": "🇦🇶", + "flag-ar": "🇦🇷", + "flag-as": "🇦🇸", + "flag-at": "🇦🇹", + "flag-au": "🇦🇺", + "flag-aw": "🇦🇼", + "flag-ax": "🇦🇽", + "flag-az": "🇦🇿", + "flag-ba": "🇧🇦", + "flag-bb": "🇧🇧", + "flag-bd": "🇧🇩", + "flag-be": "🇧🇪", + "flag-bf": "🇧🇫", + "flag-bg": "🇧🇬", + "flag-bh": "🇧🇭", + "flag-bi": "🇧🇮", + "flag-bj": "🇧🇯", + "flag-bl": "🇧🇱", + "flag-bm": "🇧🇲", + "flag-bn": "🇧🇳", + "flag-bo": "🇧🇴", + "flag-bq": "🇧🇶", + "flag-br": "🇧🇷", + "flag-bs": "🇧🇸", + "flag-bt": "🇧🇹", + "flag-bv": "🇧🇻", + "flag-bw": "🇧🇼", + "flag-by": "🇧🇾", + "flag-bz": "🇧🇿", + "flag-ca": "🇨🇦", + "flag-cc": "🇨🇨", + "flag-cd": "🇨🇩", + "flag-cf": "🇨🇫", + "flag-cg": "🇨🇬", + "flag-ch": "🇨🇭", + "flag-ci": "🇨🇮", + "flag-ck": "🇨🇰", + "flag-cl": "🇨🇱", + "flag-cm": "🇨🇲", + "cn": "🇨🇳", + "flag-co": "🇨🇴", + "flag-cp": "🇨🇵", + "flag-cr": "🇨🇷", + "flag-cu": "🇨🇺", + "flag-cv": "🇨🇻", + "flag-cw": "🇨🇼", + "flag-cx": "🇨🇽", + "flag-cy": "🇨🇾", + "flag-cz": "🇨🇿", + "de": "🇩🇪", + "flag-dg": "🇩🇬", + "flag-dj": "🇩🇯", + "flag-dk": "🇩🇰", + "flag-dm": "🇩🇲", + "flag-do": "🇩🇴", + "flag-dz": "🇩🇿", + "flag-ea": "🇪🇦", + "flag-ec": "🇪🇨", + "flag-ee": "🇪🇪", + "flag-eg": "🇪🇬", + "flag-eh": "🇪🇭", + "flag-er": "🇪🇷", + "es": "🇪🇸", + "flag-et": "🇪🇹", + "flag-eu": "🇪🇺", + "flag-fi": "🇫🇮", + "flag-fj": "🇫🇯", + "flag-fk": "🇫🇰", + "flag-fm": "🇫🇲", + "flag-fo": "🇫🇴", + "fr": "🇫🇷", + "flag-ga": "🇬🇦", + "gb": "🇬🇧", + "flag-gd": "🇬🇩", + "flag-ge": "🇬🇪", + "flag-gf": "🇬🇫", + "flag-gg": "🇬🇬", + "flag-gh": "🇬🇭", + "flag-gi": "🇬🇮", + "flag-gl": "🇬🇱", + "flag-gm": "🇬🇲", + "flag-gn": "🇬🇳", + "flag-gp": "🇬🇵", + "flag-gq": "🇬🇶", + "flag-gr": "🇬🇷", + "flag-gs": "🇬🇸", + "flag-gt": "🇬🇹", + "flag-gu": "🇬🇺", + "flag-gw": "🇬🇼", + "flag-gy": "🇬🇾", + "flag-hk": "🇭🇰", + "flag-hm": "🇭🇲", + "flag-hn": "🇭🇳", + "flag-hr": "🇭🇷", + "flag-ht": "🇭🇹", + "flag-hu": "🇭🇺", + "flag-ic": "🇮🇨", + "flag-id": "🇮🇩", + "flag-ie": "🇮🇪", + "flag-il": "🇮🇱", + "flag-im": "🇮🇲", + "flag-in": "🇮🇳", + "flag-io": "🇮🇴", + "flag-iq": "🇮🇶", + "flag-ir": "🇮🇷", + "flag-is": "🇮🇸", + "it": "🇮🇹", + "flag-je": "🇯🇪", + "flag-jm": "🇯🇲", + "flag-jo": "🇯🇴", + "jp": "🇯🇵", + "flag-ke": "🇰🇪", + "flag-kg": "🇰🇬", + "flag-kh": "🇰🇭", + "flag-ki": "🇰🇮", + "flag-km": "🇰🇲", + "flag-kn": "🇰🇳", + "flag-kp": "🇰🇵", + "kr": "🇰🇷", + "flag-kw": "🇰🇼", + "flag-ky": "🇰🇾", + "flag-kz": "🇰🇿", + "flag-la": "🇱🇦", + "flag-lb": "🇱🇧", + "flag-lc": "🇱🇨", + "flag-li": "🇱🇮", + "flag-lk": "🇱🇰", + "flag-lr": "🇱🇷", + "flag-ls": "🇱🇸", + "flag-lt": "🇱🇹", + "flag-lu": "🇱🇺", + "flag-lv": "🇱🇻", + "flag-ly": "🇱🇾", + "flag-ma": "🇲🇦", + "flag-mc": "🇲🇨", + "flag-md": "🇲🇩", + "flag-me": "🇲🇪", + "flag-mf": "🇲🇫", + "flag-mg": "🇲🇬", + "flag-mh": "🇲🇭", + "flag-mk": "🇲🇰", + "flag-ml": "🇲🇱", + "flag-mm": "🇲🇲", + "flag-mn": "🇲🇳", + "flag-mo": "🇲🇴", + "flag-mp": "🇲🇵", + "flag-mq": "🇲🇶", + "flag-mr": "🇲🇷", + "flag-ms": "🇲🇸", + "flag-mt": "🇲🇹", + "flag-mu": "🇲🇺", + "flag-mv": "🇲🇻", + "flag-mw": "🇲🇼", + "flag-mx": "🇲🇽", + "flag-my": "🇲🇾", + "flag-mz": "🇲🇿", + "flag-na": "🇳🇦", + "flag-nc": "🇳🇨", + "flag-ne": "🇳🇪", + "flag-nf": "🇳🇫", + "flag-ng": "🇳🇬", + "flag-ni": "🇳🇮", + "flag-nl": "🇳🇱", + "flag-no": "🇳🇴", + "flag-np": "🇳🇵", + "flag-nr": "🇳🇷", + "flag-nu": "🇳🇺", + "flag-nz": "🇳🇿", + "flag-om": "🇴🇲", + "flag-pa": "🇵🇦", + "flag-pe": "🇵🇪", + "flag-pf": "🇵🇫", + "flag-pg": "🇵🇬", + "flag-ph": "🇵🇭", + "flag-pk": "🇵🇰", + "flag-pl": "🇵🇱", + "flag-pm": "🇵🇲", + "flag-pn": "🇵🇳", + "flag-pr": "🇵🇷", + "flag-ps": "🇵🇸", + "flag-pt": "🇵🇹", + "flag-pw": "🇵🇼", + "flag-py": "🇵🇾", + "flag-qa": "🇶🇦", + "flag-re": "🇷🇪", + "flag-ro": "🇷🇴", + "flag-rs": "🇷🇸", + "ru": "🇷🇺", + "flag-rw": "🇷🇼", + "flag-sa": "🇸🇦", + "flag-sb": "🇸🇧", + "flag-sc": "🇸🇨", + "flag-sd": "🇸🇩", + "flag-se": "🇸🇪", + "flag-sg": "🇸🇬", + "flag-sh": "🇸🇭", + "flag-si": "🇸🇮", + "flag-sj": "🇸🇯", + "flag-sk": "🇸🇰", + "flag-sl": "🇸🇱", + "flag-sm": "🇸🇲", + "flag-sn": "🇸🇳", + "flag-so": "🇸🇴", + "flag-sr": "🇸🇷", + "flag-ss": "🇸🇸", + "flag-st": "🇸🇹", + "flag-sv": "🇸🇻", + "flag-sx": "🇸🇽", + "flag-sy": "🇸🇾", + "flag-sz": "🇸🇿", + "flag-ta": "🇹🇦", + "flag-tc": "🇹🇨", + "flag-td": "🇹🇩", + "flag-tf": "🇹🇫", + "flag-tg": "🇹🇬", + "flag-th": "🇹🇭", + "flag-tj": "🇹🇯", + "flag-tk": "🇹🇰", + "flag-tl": "🇹🇱", + "flag-tm": "🇹🇲", + "flag-tn": "🇹🇳", + "flag-to": "🇹🇴", + "flag-tr": "🇹🇷", + "flag-tt": "🇹🇹", + "flag-tv": "🇹🇻", + "flag-tw": "🇹🇼", + "flag-tz": "🇹🇿", + "flag-ua": "🇺🇦", + "flag-ug": "🇺🇬", + "flag-um": "🇺🇲", + "flag-un": "🇺🇳", + "us": "🇺🇸", + "flag-uy": "🇺🇾", + "flag-uz": "🇺🇿", + "flag-va": "🇻🇦", + "flag-vc": "🇻🇨", + "flag-ve": "🇻🇪", + "flag-vg": "🇻🇬", + "flag-vi": "🇻🇮", + "flag-vn": "🇻🇳", + "flag-vu": "🇻🇺", + "flag-wf": "🇼🇫", + "flag-ws": "🇼🇸", + "flag-xk": "🇽🇰", + "flag-ye": "🇾🇪", + "flag-yt": "🇾🇹", + "flag-za": "🇿🇦", + "flag-zm": "🇿🇲", + "flag-zw": "🇿🇼", + "flag-england": "🏴󠁧󠁢󠁥󠁮󠁧󠁿", + "flag-scotland": "🏴󠁧󠁢󠁳󠁣󠁴󠁿", + "flag-wales": "🏴󠁧󠁢󠁷󠁬󠁳󠁿", + "satisfied": "😆", + "grinning_face_with_star_eyes": "🤩", + "grinning_face_with_one_large_and_one_small_eye": "🤪", + "smiling_face_with_smiling_eyes_and_hand_covering_mouth": "🤭", + "face_with_finger_covering_closed_lips": "🤫", + "face_with_one_eyebrow_raised": "🤨", + "face_with_open_mouth_vomiting": "🤮", + "shocked_face_with_exploding_head": "🤯", + "serious_face_with_symbols_covering_mouth": "🤬", + "poop": "💩", + "shit": "💩", + "collision": "💥", + "raised_hand": "✋", + "hand_with_index_and_middle_fingers_crossed": "🤞", + "sign_of_the_horns": "🤘", + "reversed_hand_with_middle_finger_extended": "🖕", + "thumbsup": "👍", + "thumbsdown": "👎", + "punch": "👊", + "mother_christmas": "🤶", + "running": "🏃", + "man-with-bunny-ears-partying": "👯‍♂️", + "woman-with-bunny-ears-partying": "👯‍♀️", + "women_holding_hands": "👭", + "woman_and_man_holding_hands": "👫", + "couple": "👫", + "men_holding_hands": "👬", + "paw_prints": "🐾", + "flipper": "🐬", + "honeybee": "🐝", + "lady_beetle": "🐞", + "cooking": "🍳", + "knife": "🔪", + "red_car": "🚗", + "sailboat": "⛵", + "waxing_gibbous_moon": "🌔", + "sun_small_cloud": "🌤️", + "sun_behind_cloud": "🌥️", + "sun_behind_rain_cloud": "🌦️", + "lightning_cloud": "🌩️", + "tornado_cloud": "🌪️", + "tshirt": "👕", + "shoe": "👞", + "telephone": "☎️", + "lantern": "🏮", + "open_book": "📖", + "envelope": "✉️", + "pencil": "📝", + "heavy_exclamation_mark": "❗", + "staff_of_aesculapius": "⚕️", + "flag-cn": "🇨🇳", + "flag-de": "🇩🇪", + "flag-es": "🇪🇸", + "flag-fr": "🇫🇷", + "uk": "🇬🇧", + "flag-gb": "🇬🇧", + "flag-it": "🇮🇹", + "flag-jp": "🇯🇵", + "flag-kr": "🇰🇷", + "flag-ru": "🇷🇺", + "flag-us": "🇺🇸" +} diff --git a/web/utils/emoji.ts b/web/utils/emoji.ts index 8ec0bfdaa82..9561c93bd7c 100644 --- a/web/utils/emoji.ts +++ b/web/utils/emoji.ts @@ -1,11 +1,9 @@ -import type { Emoji } from '@emoji-mart/data' -import { SearchIndex } from 'emoji-mart' +import legacyEmojis from './emoji-legacy.json' -export async function searchEmoji(value: string) { - const emojis: Emoji[] = (await SearchIndex.search(value)) || [] - - const results = emojis.map((emoji) => { - return emoji.skins[0]!.native - }) - return results +/** Resolve persisted emoji-mart IDs (including aliases) without loading picker data. */ +export function resolveEmoji(value?: string | null): string { + if (!value) return '🤖' + const id = value.replace(/^:|:$/g, '') + if (Object.hasOwn(legacyEmojis, id)) return legacyEmojis[id as keyof typeof legacyEmojis] + return value } diff --git a/web/utils/format.ts b/web/utils/format.ts index 13c94937072..80657e0f6c9 100644 --- a/web/utils/format.ts +++ b/web/utils/format.ts @@ -1,6 +1,6 @@ import type { Dayjs } from 'dayjs' -import type { Locale } from '@/i18n-config' -import { localeMap } from '@/i18n-config/language' +import type { Locale } from '@/i18n' +import { localeMap } from '@/i18n/language' import 'dayjs/locale/az' import 'dayjs/locale/de' import 'dayjs/locale/es' diff --git a/web/vite.config.ts b/web/vite.config.ts index 43b2b7a31cb..22986aaa0b2 100644 --- a/web/vite.config.ts +++ b/web/vite.config.ts @@ -2,6 +2,7 @@ import { fileURLToPath } from 'node:url' import { configDefaults, defineConfig, lazyPlugins } from 'vite-plus' import { playwright } from 'vite-plus/test/browser-playwright' import { customI18nHmrPlugin } from './plugins/vite/custom-i18n-hmr.ts' +import { i18nPrunePlugin } from './plugins/vite/i18n-prune.ts' import { getRootClientInjectTarget } from './plugins/vite/inject-target.ts' import { nextStaticImageTestPlugin } from './plugins/vite/next-static-image-test.ts' @@ -39,6 +40,7 @@ export default defineConfig(({ command, mode, isPreview }) => { : undefined return [ + i18nPrunePlugin(), Inspect(), inspector, tailwindcss(), @@ -68,10 +70,6 @@ export default defineConfig(({ command, mode, isPreview }) => { server: { port: 3000, }, - ssr: { - // SyntaxError: Named export not found. The requested module is a CommonJS module, which may not support all module.exports as named exports - noExternal: ['emoji-mart'], - }, } : {}),